/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Implementation of the generic device interface to handle
 *          all devices attached to the emulator.
 *
 * Authors: Fred N. van Kempen, <decwiz@yahoo.com>
 *          Miran Grca, <mgrca8@gmail.com>
 *          Sarah Walker, <https://pcem-emulator.co.uk/>
 *
 *          Copyright 2017-2019 Fred N. van Kempen.
 *          Copyright 2016-2019 Miran Grca.
 *          Copyright 2008-2019 Sarah Walker.
 *          Copyright 2021      Andreas J. Reichel.
 *          Copyright 2021-2025 Jasmine Iwanek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free  Software  Foundation; either  version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is  distributed in the hope that it will be useful, but
 * WITHOUT   ANY  WARRANTY;  without  even   the  implied  warranty  of
 * MERCHANTABILITY  or FITNESS  FOR A PARTICULAR  PURPOSE. See  the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the:
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place - Suite 330
 *   Boston, MA 02111-1307
 *   USA.
 */
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/ini.h>
#include <86box/config.h>
#include <86box/device.h>
#include <86box/machine.h>
#include <86box/mem.h>
#include <86box/plat.h>
#include <86box/rom.h>
#include <86box/sound.h>
#include <86box/ui.h>

#define DEVICE_MAX 256 /* max # of devices */

static device_t        *devices[DEVICE_MAX];
static void            *device_priv[DEVICE_MAX];
static device_context_t device_current;
static device_context_t device_prev;
static void            *device_common_priv;

#ifdef ENABLE_DEVICE_LOG
int device_do_log = ENABLE_DEVICE_LOG;

static void
device_log(const char *fmt, ...)
{
    va_list ap;

    if (device_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define device_log(fmt, ...)
#endif

/* Initialize the module for use. */
void
device_init(void)
{
    memset(devices, 0x00, sizeof(devices));
}

void
device_video_config_migrate(const device_t *dev, const char *old_internal_name, int inst)
{
    /* Migrate the old section (new bios internal name = old gfxcard internal name) */
    const void *sec             = config_find_section(dev->name);
    const char *bios            = device_get_bios_name(dev, old_internal_name);
    uint32_t    rev             = ((uint32_t) device_get_bios_local(dev, old_internal_name)) >> 24;
    char        old_name[2048]  = { 0 };
    char        bios_name[2048] = { 0 };
    char        old_name2[2560] = { 0 };

    if (!strcmp(bios_name, "Generic") && (strstr(dev->name, "Trio3D") || strstr(dev->name, "ViRGE"))) {
        uint32_t chip_id = ((uint32_t) device_get_bios_local(dev, old_internal_name)) >> 16;
        strcpy(old_name, dev->name);
        sprintf(old_name + strlen(dev->name) - 3, "(%3i)", chip_id);
        strcpy(old_name + strlen(old_name), dev->name + strlen(dev->name) - 4);
    } else if (strstr(dev->name, "9000B") || strstr(dev->name, "DEC") || strstr(dev->name, "SMC"))
        snprintf(old_name,  2047, "%s (%s)", bios, dev->name);
    else
        snprintf(old_name,  2047, "%s (%s)", dev->name, bios);
    if (strlen(bios) >= 9) {
        snprintf(bios_name, 2047, "%s", &(bios[8]));
        /* Layout is "Rev. X (Name)" */
        bios_name[strlen(bios_name) - 1] = 0x00;
        if (!strcmp(bios_name, "Generic"))
            snprintf(old_name2, 2559, "%s Rev. %c", dev->name, rev);
        else
            snprintf(old_name2, 2559, "%s Rev. %c (%s)", dev->name, rev, bios_name);
    }

    void *      old_sec  = config_find_section(old_name);
    void *      old_sec2 = NULL;
    void *      bios_sec = config_find_section(bios);

    if (old_name2[0] != 0x00)
        old_sec2 = config_find_section(old_name2);

    if ((old_name2[0] != 0x00) && (rev >= 'B') && (rev <= 'D') && (sec == NULL)) {
        /* Tseng ET4000/W32p Migration. */
        if (old_sec2 != NULL)
            config_rename_section(old_sec2, dev->name);
        else
            config_create_section(dev->name);
        /* Do not set BIOS variable for on-board devices. */
        if (strstr(dev->name, "oard") == NULL)
            config_set_string(dev->name, "bios", old_internal_name);
    } if (sec == NULL) {
        if (bios_sec != NULL)
            config_rename_section(bios_sec, dev->name);
        else if (old_sec != NULL)
            config_rename_section(old_sec, dev->name);
        else
            config_create_section(dev->name);
        /* Do not set BIOS variable for on-board devices. */
        if (strstr(dev->name, "oard") == NULL)
            config_set_string(dev->name, "bios", old_internal_name);
    } else {
        /* The section was already there, just add the BIOS. */
        if (strstr(dev->name, "oard") == NULL)
            config_set_string(dev->name, "bios", old_internal_name);
    }
}

void
device_set_context(device_context_t *ctx, const device_t *dev, int inst)
{
    static const struct {
        const char *old;
        const char *new;
    } section_migrations[] = {
        { .old = "Standard PS/2 Mouse", .new = "PS/2 Mouse" },
        { .old = "Microsoft RAMCard for IBM PC", .new = "Microsoft RAMCard" },
        { .old = "Western Digital WD1007V-SE1 (ESDI)", .new = "WD1007V-SE1 (ESDI)" },
        { .old = "WD1003 AT MFM/RLL Controller", .new = "WD1003-WAH (MFM/RLL)" },
        { .old = "ST-11R RLL Fixed Disk Adapter", .new = "ST-11R (RLL)" },
        { .old = "WD1002A-WX1 MFM Fixed Disk Adapter", .new = "WD1002A-WX1 (MFM)" },
        { .old = "WD1002A-WX1 MFM Fixed Disk Adapter (No BIOS)", .new = "WD1002A-WX1 (MFM) (No BIOS)" },
        { .old = "WD1002A-27X RLL Fixed Disk Adapter", .new = "WD1002A-27X (RLL)" },
        { .old = "WD1004A-WX1 MFM Fixed Disk Adapter", .new = "WD1004A-WX1 (MFM)" },
        { .old = "WD1004-27X RLL Fixed Disk Adapter", .new = "WD1004-27X (RLL)" },
        { .old = "WD1004a-27X RLL Fixed Disk Adapter", .new = "WD1004a-27X (RLL)" },
        { .old = "Victor V86P RLL Fixed Disk Adapter", .new = "Victor V86P Fixed Disk Adapter (RLL)" },
        { .old = "WDXT-150 XTA Fixed Disk Controller", .new = "WDXT-150 (XTA)" },
        { .old = "WDXT-150 XTA Fixed Disk Controller (PC3086)", .new = "WDXT-150 (XTA) (PC3086)" },
        { .old = "ST-50X Fixed Disk Controller", .new = "ST-50X (XTA)" },
        { .old = "ST-50X Fixed Disk Controller (PC5086)", .new = "ST-50X (XTA) (PC5086)" },
        { .old = "Acculogic XT IDE", .new = "Acculogic sIDE-1/16 (IDE)" },
        { .old = "Multitech PC-500 / Franklin PC 8000", .new = "Multitech PC-500" }, /* 6.0 pre-release */
        { .old = "Multitech PC-500 plus", .new = "Multitech PC-500+" },
        { .old = "Multitech PC-700 / Siemens SICOMP PC 16 05", .new = "Multitech PC-700" }, /* 6.0 pre-release */
        { .old = "Vendex 888T", .new = "Vendex HeadStart Turbo 888-XT" },
        { .old = "VTech Laser Turbo XT", .new = "VTech Laser XT3" },
        { .old = "PS/1 2011", .new = "IBM PS/1 model 2011" },
        { .old = "IBM XT Model 286", .new = "IBM XT model 286" },
        { .old = "Packard Bell Legend 300SX", .new = "Packard Bell PB300" },
        { .old = "Packard Bell PB300/PB320", .new = "Packard Bell PB300" }, /* 6.0 pre-release */
        { .old = "DataExpert SX495", .new = "DataExpert OPTI-495SX" },
        { .old = "Packard Bell PB410/PB410A/PB420/PB420T", .new = "Packard Bell PB410A" }, /* 6.0 pre-release */
        { .old = "Intel Premiere/PCI (Batman)", .new = "Intel Premiere/PCI" },
        { .old = "Intel Premiere/PCI II (Plato)", .new = "Intel Premiere/PCI II" },
        { .old = "Intel Advanced/ZP (Zappa)", .new = "Intel Advanced/ZP" },
        { .old = "AST Bravo MS P/90", .new = "AST Bravo MS" },
        { .old = "AST Bravo MS/MS-T/MS-L (Rattler)", .new = "AST Bravo MS" }, /* 6.0 pre-release */
        { .old = "Intel Advanced/ATX (Thor)", .new = "Intel Advanced/ATX" },
        { .old = "Intel Advanced/MA (Monaco)", .new = "Intel Advanced/MA" },
        { .old = "Chaintech 5SBM/5SBM2 (M103)", .new = "Chaintech 5SBM2" },
        { .old = "Intel CU430HX (Cumberland)", .new = "Intel CU430HX" },
        { .old = "Intel TC430HX (Tucson)", .new = "Intel TC430HX" },
        { .old = "LG IBM Multinet x52 (MSI MS-5136)", .new = "LG IBM Multinet x52" },
        { .old = "Intel AN430TX (Anchorage)", .new = "Intel AN430TX" },
        { .old = "Intel VS440FX (Venus)", .new = "Intel VS440FX" },
        { .old = "Advanced Integration Research (AIR) P6KDI", .new = "AIR P6KDI" }, /* 6.0 pre-release */
        { .old = "DTK PII-151B (MiniMicro) Floppy Drive Controller", .new = "DTK PII-151B (MiniMicro) FDC" },
        { .old = "DTK PII-158B (MiniMicro4) Floppy Drive Controller", .new = "DTK PII-158B (MiniMicro4) FDC" },
        { .old = "Monster FDC Floppy Drive Controller", .new = "Monster FDC" },
        { .old = "Panasonic/MKE CD-ROM interface (Creative)", .new = "MKE/Panasonic interface (Creative)" },
        { .old = "Panasonic/MKE CD-ROM interface", .new = "MKE/Panasonic interface" },
        { .old = "S3 Trio32 VLB On-Board (Phoenix)", .new = "S3 Trio32 VLB On-Board" },
        { .old = "S3 Trio32 PCI On-Board (Phoenix)", .new = "S3 Trio32 PCI On-Board" },
        { .old = "S3 Trio64 PCI On-Board (Phoenix)", .new = "S3 Trio64 PCI On-Board" },
        { .old = "S3 Trio64V+ PCI On-Board (Phoenix)", .new = "S3 Trio64V+ PCI On-Board" },
        { .old = "Tseng Labs ET4000/w32 ISA (MachSpeed VGA GUI 2400S)", .new = "Tseng Labs ET4000/w32 ISA" },
        { .old = "Tseng Labs ET4000/w32 VLB (MachSpeed VGA GUI 2400S)", .new = "Tseng Labs ET4000/w32 VLB" },
        { .old = "Tseng Labs ET4000/w32i Rev. B ISA (Axis MicroDevice)", .new = "Tseng Labs ET4000/w32i ISA" },
        { .old = "Tseng Labs ET4000/w32i Rev. B VLB (Hercules Dynamite Pro)", .new = "Tseng Labs ET4000/w32i VLB" },
        { .old = "S3 ViRGE/GX (385) PCI", .new = "S3 ViRGE/GX PCI" },
        { .old = "S3 ViRGE/GX2 (357) PCI", .new = "S3 ViRGE/GX2 PCI" },
        { .old = "ATI 28800-6 (ATI VGA Wonder 1024D XL Plus)", .new = "ATI 28800-6" },
        { .old = "DEC DE-500A Fast Ethernet (DECchip 21143 \"Tulip\")", .new = "DECchip 21143 \"Tulip\"" },
        { .old = "DEC DE-435 EtherWorks Turbo (DECchip 21040 \"Tulip\")", .new = "DECchip 21040 \"Tulip\"" },
        { .old = "SMC EtherPower II 9432 (SMC 83C170 \"EPIC/100\")", .new = "SMC 83C170 \"EPIC/100\"" },
        { .old = "Aztech Sound Galaxy Pro 16 II (AZT2316R)", .new = "Aztech Sound Galaxy Pro 16 II" },
        { .old = "Aztech Sound Galaxy Pro 16 AB (Washington)", .new = "Aztech Sound Galaxy Pro 16 AB" },
        { .old = "Aztech Sound Galaxy Nova 16 Extra (Clinton)", .new = "Aztech Sound Galaxy Nova 16 Extra" },
        { .old = "Aztech Sound Galaxy Pro 16 (AZTPR16)", .new = "Aztech Sound Galaxy Pro 16" },
        { .old = "HP Multimedia Pro 16V-A (AZT2320)", .new = "HP Multimedia Pro 16V-A" },
        { .old = "IBM PS/2 ESDI Fixed Disk Adapter (MCA)", .new = "IBM ESDI Fixed Disk Adapter" },
        { .old = "IBM Integrated Fixed Disk and Controller (MCA)", .new = "IBM Integrated Fixed Disk" },
        { .old = "IBM PS/2 ST506 Fixed Disk Adapter (MCA)", .new = "IBM ST506 Fixed Disk Adapter" },
        { .old = "Cirrus Logic GD5401 (ISA) (ACUMOS AVGA1)", .new = "Cirrus Logic GD5401 (ISA)" },
        { .old = "Cirrus Logic GD5401 (ISA) (ACUMOS AVGA1) (On-Board)", .new = "Cirrus Logic GD5401 (ISA) (On-Board)" },
        { .old = "Cirrus Logic GD5402 (ISA) (ACUMOS AVGA2)", .new = "Cirrus Logic GD5402 (ISA)" },
        { .old = "Cirrus Logic GD5402 (ISA) (ACUMOS AVGA2) (On-Board)", .new = "Cirrus Logic GD5402 (ISA) (On-Board)" },
        { .old = "Cirrus Logic GD5402 (ISA) (ACUMOS AVGA2) (On-Board) (Commodore)", .new = "Cirrus Logic GD5402 (ISA) (On-Board) (Commodore)" },
        { .old = "Cirrus Logic GD5428 (MCA) (IBM SVGA Adapter/A)", .new = "Cirrus Logic GD5428 (MCA)" },
        { .old = "Cirrus Logic GD5426 (MCA) (Reply Video Adapter)", .new = "Cirrus Logic GD5426 (MCA)" },
        { .old = "3dfx Voodoo3 2000 (On-Board 8MB SGRAM)", .new = "3dfx Voodoo3 2000 (On-Board)" },
        { 0 }
    };

    ctx->dev      = dev;
    ctx->instance = inst;
    if (inst)
        sprintf(ctx->name, "%s #%i", dev->name, inst);
    else
        strncpy(ctx->name, dev->name, sizeof(ctx->name) - 1);

    if (!config_find_section(ctx->name)) {
        /* Find and migrate old config sections. */
        void *old_sec;
        char  old_name[2048];
        for (int i = 0; section_migrations[i].new; i++) {
            if (!strcmp(dev->name, section_migrations[i].new)) {
                if (inst) {
                    sprintf(old_name, "%s #%i", section_migrations[i].old, inst);
                    old_sec = config_find_section(old_name);
                    if (old_sec) {
                        config_rename_section(old_sec, ctx->name);
                        return; /* numbering below does not apply */
                    }
                }
                old_sec = config_find_section((char *) section_migrations[i].old);
                if (old_sec) {
                    config_rename_section(old_sec, ctx->name);
                    return; /* if inst, numbering below was already done here */
                }
            }
        }

        if (inst) {
            /* If a numbered section is not present, but a non-numbered of
               the same name is, rename the non-numbered section to numbered. */
            old_sec = config_find_section((char *) dev->name);
            if (old_sec)
                config_rename_section(old_sec, ctx->name);
        }
    }
}

static void
device_context_common(const device_t *dev, int inst)
{
    memcpy(&device_prev, &device_current, sizeof(device_context_t));
    device_set_context(&device_current, dev, inst);
}

void
device_context(const device_t *dev)
{
    device_context_common(dev, 0);
}

void
device_context_inst(const device_t *dev, int inst)
{
    device_context_common(dev, inst);
}

void
device_context_restore(void)
{
    memcpy(&device_current, &device_prev, sizeof(device_context_t));
}

static void *
device_add_common(const device_t *dev, void *p, void *params, int inst)
{
    device_t *init_dev = NULL;
    void     *priv     = NULL;
    int16_t   c;

    /*
       IMPORTANT: This is needed to gracefully handle machine
                  device addition if the relevant device is NULL.
     */
    if (dev == NULL)
        return NULL;

    if (!device_available(dev)) {
        char temp[512] = { 0 };
        snprintf(temp, sizeof(temp),
                 plat_get_string(STRING_HW_NOT_AVAILABLE_DEVICE),
                 dev->name);
        ui_msgbox_header(MBX_WARNING,
                         plat_get_string(STRING_HW_NOT_AVAILABLE_TITLE),
                         temp);
        return ((void *) dev->name);
    }

    if (params != NULL) {
        init_dev = calloc(1, sizeof(device_t));
        memcpy(init_dev, dev, sizeof(device_t));
        init_dev->local |= (uintptr_t) params;
    } else
        init_dev = (device_t *) dev;

    for (c = 0; c < DEVICE_MAX; c++) {
        if (!inst && (devices[c] == dev)) {
            device_log("DEVICE: device already exists!\n");
            return (NULL);
        }
        if (devices[c] == NULL)
            break;
    }
    if (c >= DEVICE_MAX) {
        fatal("Attempting to initialize more than the maximum "
              "limit of %i devices\n", DEVICE_MAX);
        return NULL;
    }

    /* Do this so that a chained device_add will not identify the same ID
       its master device is already trying to assign. */
    devices[c] = (device_t *) dev;
    if (!strcmp(dev->name, "None") || !strcmp(dev->name, "Internal"))
        fatal("Attempting to add dummy device of type: %s\n", dev->name);

    if (p == NULL) {
        memcpy(&device_prev, &device_current, sizeof(device_context_t));
        device_set_context(&device_current, dev, inst);

        if (dev->init != NULL) {
            /* Give it our temporary device in case we have dynamically changed info->local. */
            priv = dev->init(init_dev);

            if (priv == NULL) {
#ifdef ENABLE_DEVICE_LOG
                if (dev->name)
                    device_log("DEVICE: device '%s' init failed\n", dev->name);
                else
                    device_log("DEVICE: device init failed\n");
#endif

                devices[c]     = NULL;
                device_priv[c] = NULL;

                if ((init_dev != NULL) && (init_dev != (device_t *) dev))
                    free(init_dev);

                return (NULL);
            }
        }

#ifdef ENABLE_DEVICE_LOG
        if (dev->name)
            device_log("DEVICE: device '%s' init successful\n", dev->name);
        else
            device_log("DEVICE: device init successful\n");
#endif

        memcpy(&device_current, &device_prev, sizeof(device_context_t));
        device_priv[c] = priv;
    } else
        device_priv[c] = p;

    if (init_dev != dev)
        free(init_dev);

    return priv;
}

const char *
device_get_internal_name(const device_t *dev)
{
    if (dev == NULL)
        return "";

    return dev->internal_name;
}

const char *
device_get_alias(const device_t *dev)
{
    if (dev == NULL)
        return "";

    return dev->alias;
}

const char *
device_get_machine(const device_t *dev)
{
    if (dev == NULL)
        return NULL;

    return dev->machine;
}

void *
device_add(const device_t *dev)
{
    return device_add_common(dev, NULL, NULL, 0);
}

void *
device_add_linked(const device_t *dev, void *priv)
{
    void *ret;

    device_common_priv = priv;
    ret = device_add_common(dev, NULL, NULL, 0);
    device_common_priv = NULL;
    return ret;
}

void *
device_add_params(const device_t *dev, void *params)
{
    return device_add_common(dev, NULL, params, 0);
}

/* For devices that do not have an init function (internal video etc.) */
void
device_add_ex(const device_t *dev, void *priv)
{
    device_add_common(dev, priv, NULL, 0);
}

void
device_add_ex_params(const device_t *dev, void *priv, void *params)
{
    device_add_common(dev, priv, params, 0);
}

void *
device_add_inst(const device_t *dev, int inst)
{
    return device_add_common(dev, NULL, NULL, inst);
}

void *
device_add_inst_params(const device_t *dev, int inst, void *params)
{
    return device_add_common(dev, NULL, params, inst);
}

/* For devices that do not have an init function (internal video etc.) */
void
device_add_inst_ex(const device_t *dev, void *priv, int inst)
{
    device_add_common(dev, priv, NULL, inst);
}

void
device_add_inst_ex_params(const device_t *dev, void *priv, int inst, void *params)
{
    device_add_common(dev, priv, params, inst);
}

void *
device_get_common_priv(void)
{
    return device_common_priv;
}

void
device_close(const device_t *device)
{
    int16_t c;

    for (c = (DEVICE_MAX - 1); c >= 0; c--) {
        if (devices[c] == device) {
#ifdef ENABLE_DEVICE_LOG
            if (devices[c]->name)
                device_log("Closing device: \"%s\"...\n", devices[c]->name);
#endif
            if (devices[c]->close != NULL)
                devices[c]->close(device_priv[c]);
            devices[c]     = NULL;
            device_priv[c] = NULL;
            break;
        }
    }

    if (c >= 0)  for (int16_t d = c; d <= (DEVICE_MAX - 1); d++) {
        if (d == (DEVICE_MAX - 1)) {
            devices[d]     = NULL;
            device_priv[d] = NULL;
        } else {
            devices[d]     = devices[d + 1];
            device_priv[d] = device_priv[d + 1];
        }
    }
}

void
device_close_all(void)
{
    for (int16_t c = (DEVICE_MAX - 1); c >= 0; c--) {
        if (devices[c] != NULL) {
#ifdef ENABLE_DEVICE_LOG
            if (devices[c]->name)
                device_log("Closing device: \"%s\"...\n", devices[c]->name);
#endif
            if (devices[c]->close != NULL)
                devices[c]->close(device_priv[c]);
            devices[c]     = NULL;
            device_priv[c] = NULL;
        }
    }
}

void
device_close_by_flags(uint32_t match_flags)
{
    for (int16_t c = (DEVICE_MAX - 1); c >= 0; c--) {
        if ((devices[c] != NULL) && ((devices[c]->flags & match_flags) == match_flags)) {
#ifdef ENABLE_DEVICE_LOG
            if (devices[c]->name)
                device_log("Closing device: \"%s\"...\n", devices[c]->name);
#endif
            if (devices[c]->close != NULL)
                devices[c]->close(device_priv[c]);
            devices[c]     = NULL;
            device_priv[c] = NULL;
        }
    }
}

void
device_reset_all(uint32_t match_flags)
{
    for (uint16_t c = 0; c < DEVICE_MAX; c++) {
        if (devices[c] != NULL) {
            if ((devices[c]->reset != NULL) && (devices[c]->flags & match_flags))
                devices[c]->reset(device_priv[c]);
        }
    }
}

void *
device_find_first_priv(uint32_t match_flags)
{
    void *ret = NULL;

    for (uint16_t c = 0; c < DEVICE_MAX; c++) {
        if (devices[c] != NULL) {
            if ((device_priv[c] != NULL) && (devices[c]->flags & match_flags)) {
                ret = device_priv[c];
                break;
            }
        }
    }

    return ret;
}

void *
device_get_priv(const device_t *dev)
{
    for (uint16_t c = 0; c < DEVICE_MAX; c++) {
        if (devices[c] != NULL) {
            if (devices[c] == dev)
                return (device_priv[c]);
        }
    }

    return (NULL);
}

int
device_available(const device_t *dev)
{
    int ret = machine_device_available(dev);

    if (ret == 0) {
        /* No CONFIG_BIOS field present, use the classic available(). */
        if (dev->available != NULL)
            ret = (dev->available());
        else
            ret = (dev != NULL);
    } else
        ret = (ret == -1);

    return ret;
}

static const device_config_bios_t *
device_get_bios(const device_t *dev, const char *internal_name)
{
    if (dev != NULL) {
        const device_config_t *config = dev->config;
        while (config && (config->type != CONFIG_END)) {
            if (config->type == CONFIG_BIOS) {
                const device_config_bios_t *bios = (const device_config_bios_t *) config->bios;

                /* Go through the ROMs in the device configuration. */
                while ((bios != NULL) &&
                       (bios->name != NULL) &&
                       (bios->internal_name != NULL) &&
                       (bios->files_no != 0)) {
                    if (!strcmp(internal_name, bios->internal_name))
                        return bios;
                    bios++;
                }

                /* Unknown value, fall back to the default ROMs. */
                if (strcmp(internal_name, config->default_string))
                    return device_get_bios(dev, config->default_string);
            }
            config++;
        }
    }

    return NULL;
}

const char *
device_get_bios_name(const device_t *dev, const char *internal_name)
{
    const device_config_bios_t *bios = device_get_bios(dev, internal_name);
    return bios ? bios->name : 0;
}

uint8_t
device_get_bios_type(const device_t *dev, const char *internal_name)
{
    const device_config_bios_t *bios = device_get_bios(dev, internal_name);
    return bios ? bios->bios_type : 0;
}

uint8_t
device_get_bios_num_files(const device_t *dev, const char *internal_name)
{
    const device_config_bios_t *bios = device_get_bios(dev, internal_name);
    return bios ? bios->files_no : 0;
}

uint32_t
device_get_bios_local(const device_t *dev, const char *internal_name)
{
    const device_config_bios_t *bios = device_get_bios(dev, internal_name);
    return bios ? bios->local : 0;
}

uint32_t
device_get_bios_file_size(const device_t *dev, const char *internal_name)
{
    const device_config_bios_t *bios = device_get_bios(dev, internal_name);
    return bios ? bios->size : 0;
}

uint64_t
device_get_bios_flags(const device_t *dev, const char *internal_name)
{
    const device_config_bios_t *bios = device_get_bios(dev, internal_name);
    return bios ? bios->flags : 0;
}

const char *
device_get_bios_file(const device_t *dev, const char *internal_name, unsigned int file_no)
{
    const device_config_bios_t *bios = device_get_bios(dev, internal_name);
    return (bios && (file_no < bios->files_no)) ? bios->files[file_no] : NULL;
}

int
device_has_config(const device_t *dev)
{
    int                    c = 0;
    const device_config_t *config;

    if (dev == NULL)
        return 0;

    if (dev->config == NULL)
        return 0;

    config = dev->config;

    while (config->type != CONFIG_END) {
        c++;
        config++;
    }

    return (c > 0) ? 1 : 0;
}

const char *
device_get_bus_name(const device_t *dev)
{
    const char *sbus = NULL;

    if ((dev->flags & (DEVICE_SIDECAR | DEVICE_ISA)) == (DEVICE_SIDECAR | DEVICE_ISA))
        sbus = "ISA/Sidecar";
    else if (dev->flags & DEVICE_SIDECAR)
        sbus = "Sidecar";
    else if (dev->flags & DEVICE_XT_KBC)
        sbus = "XT KBC";
    else if (dev->flags & DEVICE_ISA16)
        sbus = "ISA16";
    else if (dev->flags & DEVICE_AT_KBC)
        sbus = "AT KBC";
    else if (dev->flags & DEVICE_PS2_KBC)
        sbus = "PS/2 KBC";
    else if (dev->flags & DEVICE_ISA)
        sbus = "ISA";
    else if (dev->flags & DEVICE_CBUS)
        sbus = "C-BUS";
    else if (dev->flags & DEVICE_PCMCIA)
        sbus = "PCMCIA";
    else if (dev->flags & DEVICE_MCA)
        sbus = "MCA";
    else if (dev->flags & DEVICE_MCA32)
        sbus = "MCA32";
    else if (dev->flags & DEVICE_HIL)
        sbus = "HP HIL";
    else if (dev->flags & DEVICE_EISA)
        sbus = "EISA";
    else if (dev->flags & DEVICE_AT32)
        sbus = "AT/32";
    else if (dev->flags & DEVICE_OLB)
        sbus = "OLB";
    else if (dev->flags & DEVICE_VLB)
        sbus = "VLB";
    else if (dev->flags & DEVICE_PCI)
        sbus = "PCI";
    else if (dev->flags & DEVICE_CARDBUS)
        sbus = "CardBus";
    else if (dev->flags & DEVICE_USB)
        sbus = "USB";
    else if (dev->flags & DEVICE_AGP)
        sbus = "AGP";
    else if (dev->flags & DEVICE_AC97)
        sbus = "AMR";
    else if (dev->flags & DEVICE_COM)
        sbus = "COM";
    else if (dev->flags & DEVICE_LPT)
        sbus = "LPT";

    return sbus;
}

void
device_get_name(const device_t *dev, int bus, char *name)
{
    const char *sbus = NULL;
    const char *fbus;
    char       *tname;
    char        pbus[16] = { 0 };

    if (dev == NULL)
        return;

    name[0] = 0x00;

    if (bus) {
        sbus = device_get_bus_name(dev);

        if (sbus != NULL) {
            /* First concatenate [<Bus>] before the device's name. */
            if (bus > 0) {
                strcat(name, "[");
                strcat(name, sbus);
                strcat(name, "] ");
            }

            /* Then change string from ISA16 to ISA if applicable. */
            if (!strcmp(sbus, "ISA16"))
                sbus = "ISA";

            /* Generate the bus string with parentheses. */
            strcat(pbus, "(");
            strcat(pbus, sbus);
            strcat(pbus, ")");

            /* Allocate the temporary device name string and set it to all zeroes. */
            tname = (char *) calloc(1, strlen(dev->name) + 1);


            /* First strip the bus string with parentheses. */
            fbus = strstr(dev->name, pbus);
            if (fbus == dev->name)
                strcat(tname, dev->name + strlen(pbus) + 1);
            else if (fbus == NULL)
                strcat(tname, dev->name);
            else {
                strncat(tname, dev->name, fbus - dev->name - 1);
                strcat(tname, fbus + strlen(pbus));
            }

            /* Special case for LPT DACs - don't strip LPT */
            int is_dac = 0;
            if (!strcmp(sbus, "LPT"))
                is_dac = (strstr(dev->name, "LPT DAC") != NULL);

            /* Then also strip the bus string without parentheses. */
            fbus = strstr(tname, sbus);
            if ((fbus == tname) && !is_dac)
                strcat(name, tname + strlen(sbus) + 1);
            /* Special case to not strip the "oPCI" from "Ensoniq AudioPCI",
               the "-ISA" from "AMD PCnet-ISA" or the " PCI" from "CMD PCI-064x". */
            else if ((fbus < &tname[2]) || (*(fbus - 1) == 'o') || (*(fbus - 1) == '-') || (*(fbus - 2) == 'r') || ((fbus[0] == 'P') && (fbus[1] == 'C') && (fbus[2] == 'I') && (fbus[3] == '-')) || is_dac)
                strcat(name, tname);
            else {
                strncat(name, tname, fbus - tname - 1);
                strcat(name, fbus + strlen(sbus));
            }

            /* Free the temporary device name string. */
            free(tname);
            tname = NULL;
        } else
            strcat(name, dev->name);
    } else
        strcat(name, dev->name);
}

void
device_speed_changed(void)
{
    for (uint16_t c = 0; c < DEVICE_MAX; c++) {
        if (devices[c] != NULL) {
            device_log("DEVICE: device '%s' speed changed\n", devices[c]->name);

            if (devices[c]->speed_changed != NULL)
                devices[c]->speed_changed(device_priv[c]);
        }
    }

    sound_speed_changed();
}

void
device_force_redraw(void)
{
    for (uint16_t c = 0; c < DEVICE_MAX; c++) {
        if (devices[c] != NULL) {
            if (devices[c]->force_redraw != NULL)
                devices[c]->force_redraw(device_priv[c]);
        }
    }
}

int
device_get_instance(void)
{
    return device_current.instance;
}

const char *
device_get_config_string(const char *str)
{
    const char *ret = "";

    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name)) {
                const char *s = (config_get_string((char *) device_current.name,
                                 (char *) str, (char *) cfg->default_string));
                ret = (s == NULL) ? "" : s;
                break;
            }

            cfg++;
        }
    }

    return ret;
}

int
device_get_config_int(const char *str)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name))
                return (config_get_int((char *) device_current.name, (char *) str, cfg->default_int));

            cfg++;
        }
    }

    return 0;
}

int
device_get_config_int_ex(const char *str, int def)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name))
                return (config_get_int((char *) device_current.name, (char *) str, def));

            cfg++;
        }
    }

    return def;
}

int
device_get_config_hex16(const char *str)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name))
                return (config_get_hex16((char *) device_current.name, (char *) str, cfg->default_int));

            cfg++;
        }
    }

    return 0;
}

int
device_get_config_hex20(const char *str)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name))
                return (config_get_hex20((char *) device_current.name, (char *) str, cfg->default_int));

            cfg++;
        }
    }

    return 0;
}

int
device_get_config_mac(const char *str, int def)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name))
                return (config_get_mac((char *) device_current.name, (char *) str, def));

            cfg++;
        }
    }

    return def;
}

void
device_set_config_string(const char *str, const char *val)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name)) {
                config_set_string((char *) device_current.name, (char *) str, val);
                break;
            }

            cfg++;
        }
    }
}

void
device_set_config_int(const char *str, int val)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name)) {
                config_set_int((char *) device_current.name, (char *) str, val);
                break;
            }

            cfg++;
        }
    }
}

void
device_set_config_hex16(const char *str, int val)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name)) {
                config_set_hex16((char *) device_current.name, (char *) str, val);
                break;
            }

            cfg++;
        }
    }
}

void
device_set_config_hex20(const char *str, int val)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name)) {
                config_set_hex20((char *) device_current.name, (char *) str, val);
                break;
            }

        cfg++;
        }
    }
}

void
device_set_config_mac(const char *str, int val)
{
    if (device_current.dev != NULL) {
        const device_config_t *cfg = device_current.dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name)) {
                config_set_mac((char *) device_current.name, (char *) str, val);
                break;
            }

            cfg++;
        }
    }
}

int
device_is_valid(const device_t *device, int mch)
{
    int ret = 1;

    if ((device != NULL) && ((device->flags & DEVICE_BUS) != 0)) {
        /* Hide PCI devices on machines with only an internal PCI bus. */
        if ((device->flags & DEVICE_PCI) &&
            machine_has_flags(mch, MACHINE_PCI_INTERNAL))
            ret = 0;
        else
            ret = machine_has_bus(mch, device->flags & DEVICE_BUS);
    }

    return ret;
}

int
machine_get_config_int(char *str)
{
    const device_t *dev = machine_get_device(machine);

    if (dev != NULL) {
        const device_config_t *cfg = dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name))
                return (config_get_int((char *) dev->name, str, cfg->default_int));

            cfg++;
        }
    }
    return 0;
}

const char *
machine_get_config_string(char *str)
{
    const device_t *dev = machine_get_device(machine);
    const char     *ret = "";

    if (dev != NULL) {
        const device_config_t *cfg = dev->config;

        while ((cfg != NULL) && (cfg->type != CONFIG_END)) {
            if (!strcmp(str, cfg->name)) {
                const char *s = config_get_string((char *) dev->name, str,
                                                  (char *) cfg->default_string);
                ret = (s == NULL) ? "" : s;
                break;
            }

            cfg++;
        }
    }

    return ret;
}

int
machine_device_available(const device_t *dev)
{
    if (dev != NULL) {
        const device_config_t *config = dev->config;
        if ((config != NULL) && (config->type == CONFIG_BIOS)) {
            int roms_present = 0;
            const device_config_bios_t *bios = (const device_config_bios_t *) config->bios;

            /* Go through the ROM's in the device configuration. */
            while ((bios != NULL) &&
                   (bios->name != NULL) &&
                   (bios->internal_name != NULL) &&
                   (bios->files_no != 0)) {
                int i = 0;
                if (bios->files_no > 0) {
                    for (uint8_t bf = 0; bf < bios->files_no; bf++)
                        i += !!rom_present(bios->files[bf]);
                }
                if ((bios->files_no == -1) || (i == bios->files_no))
                    roms_present++;
                bios++;
            }

            return (roms_present ? -1 : -2);
        }
    }

    /* NULL device or no CONFIG_BIOS field, return 0. */
    return 0;
}

const device_t *
device_context_get_device(void)
{
    return device_current.dev;
}

const device_t device_none = {
    .name          = "None",
    .internal_name = "none",
    .flags         = 0,
    .local         = 0,
    .init          = NULL,
    .close         = NULL,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};

const device_t device_internal = {
    .name          = "Internal",
    .internal_name = "internal",
    .flags         = 0,
    .local         = 0,
    .init          = NULL,
    .close         = NULL,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};
