/*
 * 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 the PCI bus.
 *
 * Authors: Miran Grca, <mgrca8@gmail.com>
 *
 *          Copyright 2023 Miran Grca.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/machine.h>
#include "cpu.h"
#include "x86.h"
#include <86box/io.h>
#include <86box/pic.h>
#include <86box/mem.h>
#include <86box/device.h>
#include <86box/dma.h>
#include <86box/pci.h>
#include <86box/keyboard.h>
#include <86box/plat_unused.h>

#define PCI_ENABLED               0x80000000

typedef struct pci_card_t {
    uint8_t     bus;
    uint8_t     id;
    uint8_t     type;
    uint8_t     irq_routing[PCI_INT_PINS_NUM];

    void *      priv;
    void        (*write)(int func, int addr, uint8_t val, void *priv);
    uint8_t     (*read)(int func, int addr, void *priv);
} pci_card_t;

typedef struct pci_card_desc_t {
    uint8_t     type;
    void *      priv;
    void        (*write)(int func, int addr, uint8_t val, void *priv);
    uint8_t     (*read)(int func, int addr, void *priv);
    uint8_t     *slot;
} pci_card_desc_t;

typedef struct pci_mirq_t {
    uint8_t     enabled;
    uint8_t     irq_line;
    uint8_t     irq_level;
    uint8_t     pad;
} pci_mirq_t;

int         pci_burst_time;
int         agp_burst_time;
int         pci_nonburst_time;
int         agp_nonburst_time;

int         pci_flags;

uint32_t    pci_base = 0xc000;
uint32_t    pci_size = 0x1000;

static pci_card_t  pci_cards[PCI_CARDS_NUM];
static pci_card_desc_t  pci_card_descs[PCI_CARDS_NUM];
static uint8_t     pci_pmc = 0;
static uint8_t     last_pci_card = 0;
static uint8_t     last_normal_pci_card = 0;
static uint8_t     last_normal_pci_card_id = 0;
static uint8_t     last_pci_bus = 1;
static uint8_t     next_pci_card = 0;
static uint8_t     normal_pci_cards = 0;
static uint8_t     next_normal_pci_card = 0;
static uint8_t     pci_card_to_slot_mapping[256][PCI_CARDS_NUM];
static uint8_t     pci_bus_number_to_index_mapping[256];
static uint8_t     pci_irqs[PCI_IRQS_NUM];
static uint8_t     pci_irq_level[PCI_IRQS_NUM];
static uint64_t    pci_irq_hold[PCI_IRQS_NUM];
static pci_mirq_t  pci_mirqs[PCI_MIRQS_NUM];
static int         pci_index;
static int         pci_func;
static int         pci_card;
static int         pci_bus;
static int         pci_key;
static int         pci_trc_reg = 0;
static uint32_t    pci_enable = 0x00000000;

static void        pci_reset_regs(void);

#ifdef ENABLE_PCI_LOG
int pci_do_log = ENABLE_PCI_LOG;

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

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

void
pci_set_irq_routing(int pci_int, int irq)
{
    pci_irqs[pci_int - 1] = irq;
}

void
pci_set_irq_level(int pci_int, int level)
{
    pci_irq_level[pci_int - 1] = !!level;
}

void
pci_enable_mirq(int mirq)
{
    pci_mirqs[mirq].enabled = 1;
}

void
pci_set_mirq_routing(int mirq, uint8_t irq)
{
    pci_mirqs[mirq].irq_line = irq;
}

uint8_t
pci_get_mirq_level(int mirq)
{
    return pci_mirqs[mirq].irq_level;
}

void
pci_set_mirq_level(int mirq, uint8_t level)
{
    pci_mirqs[mirq].irq_level = level;
}

/* PCI raise IRQ: the first parameter is slot if < PCI_MIRQ_BASE, MIRQ if >= PCI_MIRQ_BASE
                  and < PCI_DIRQ_BASE, and direct IRQ line if >= PCI_DIRQ_BASE (RichardG's
                  hack that may no longer be needed). */
void
pci_irq(uint8_t slot, uint8_t pci_int, int level, int set, uint8_t *irq_state)
{
    uint8_t irq_routing   = 0;
    uint8_t pci_int_index = pci_int - PCI_INTA;
    uint8_t irq_line      = 0;
    uint8_t is_vfio       = 0;

    /* The fast path out an invalid PCI card. */
    if (slot == PCI_CARD_INVALID)
        return;

    switch (slot) {
        default:
            return;

        case 0x00 ... PCI_CARD_MAX:
            /* PCI card. */
            if (!last_pci_card)
                return;

            if (pci_flags & FLAG_NO_IRQ_STEERING)
                irq_line = pci_cards[slot].read(0, 0x3c, pci_cards[slot].priv);
            else {
                irq_routing = pci_cards[slot].irq_routing[pci_int_index];

                switch (irq_routing) {
                    default:
                    case 0x00:
                        return;

                    case 0x01 ... PCI_IRQS_NUM:
                        is_vfio     = pci_cards[slot].type & PCI_CARD_VFIO;
                        irq_routing = (irq_routing - PCI_INTA) & PCI_IRQ_MAX;

                        irq_line = pci_irqs[irq_routing];
                        /* Ignore what was provided to us as a parameter and override it with whatever
                           the chipset is set to. */
                        level    = !!pci_irq_level[irq_routing];
                        if (level && is_vfio)
                            level--;
                        break;

                    /* Sometimes, PCI devices are mapped to direct IRQ's. */
                    case (PCI_DIRQ_BASE | 0x00) ... (PCI_DIRQ_BASE | PCI_DIRQ_MAX):
                        /* Direct IRQ line, always edge-triggered. */
                        irq_line = slot & PCI_IRQ_MAX;
                        break;
                }
            }
            break;
        case (PCI_IIRQ_BASE | 0x00) ... (PCI_IIRQ_BASE | PCI_IIRQS_NUM):
            /* PCI internal routing. */
            if (slot > 0x00) {
                slot = (slot - 1) & PCI_INT_PINS_MAX;

                irq_line    = pci_irqs[slot];

                /* Ignore what was provided to us as a parameter and override it with whatever
                   the chipset is set to. */
                level       = !!pci_irq_level[slot];
            } else {
                irq_line    = 0xff;
                level       = 0;
            }
            break;
        case (PCI_MIRQ_BASE | 0x00) ... (PCI_MIRQ_BASE | PCI_MIRQ_MAX):
            /* MIRQ */
            slot &= PCI_MIRQ_MAX;

            if (!pci_mirqs[slot].enabled)
                return;

            irq_line = pci_mirqs[slot].irq_line;
            break;
        case (PCI_DIRQ_BASE | 0x00) ... (PCI_DIRQ_BASE | PCI_DIRQ_MAX):
            /* Direct IRQ line (RichardG's ACPI workaround, may no longer be needed). */
            irq_line = slot & PCI_IRQ_MAX;
            break;
    }

    if (irq_line > PCI_IRQ_MAX)
        return;

    picint_common(1 << irq_line, level, set, irq_state);
}

uint8_t
pci_get_int(uint8_t slot, uint8_t pci_int)
{
    return pci_cards[slot].irq_routing[pci_int - PCI_INTA];
}

static void
pci_clear_slot(int card)
{
    pci_card_to_slot_mapping[pci_cards[card].bus][pci_cards[card].id] = PCI_CARD_INVALID;

    pci_cards[card].id   = 0xff;
    pci_cards[card].type = 0xff;

    for (uint8_t i = 0; i < 4; i++)
        pci_cards[card].irq_routing[i] = 0;

    pci_cards[card].read  = NULL;
    pci_cards[card].write = NULL;
    pci_cards[card].priv  = NULL;
}

/* Relocate a PCI device to a new slot, required for the configurable
   IDSEL's of ALi M1543(c). */
void
pci_relocate_slot(int type, int new_slot)
{
    int     card = -1;
    int     old_slot;

    if ((new_slot < 0) || (new_slot > 31))
        return;

    for (uint8_t i = 0; i < PCI_CARDS_NUM; i++) {
        if ((pci_cards[i].bus == 0) && (pci_cards[i].type == type)) {
            card = i;
            break;
        }
    }

    if (card == -1)
        return;

    old_slot                              = pci_cards[card].id;
    pci_cards[card].id                    = new_slot;

    if (pci_card_to_slot_mapping[0][old_slot] == card)
        pci_card_to_slot_mapping[0][old_slot] = PCI_CARD_INVALID;

    if (pci_card_to_slot_mapping[0][new_slot] == PCI_CARD_INVALID)
        pci_card_to_slot_mapping[0][new_slot] = card;
}

/* Write PCI enable/disable key, split for the ALi M1435. */
void
pci_key_write(uint8_t val)
{
    pci_key = val & 0xf0;

    if (pci_key)
        pci_flags |= FLAG_CONFIG_IO_ON;
    else
        pci_flags &= ~FLAG_CONFIG_IO_ON;
}

static void
pci_io_handlers(int set)
{
    io_handler(set, 0x0cf8, 4, pci_read, pci_readw, pci_readl, pci_write, pci_writew, pci_writel, NULL);

    if (pci_flags & FLAG_MECHANISM_1)
        io_handler(set, 0x0cfc, 4, pci_read, pci_readw, pci_readl, pci_write, pci_writew, pci_writel, NULL);

    if (pci_flags & FLAG_MECHANISM_2) {
        if (set && pci_key)
            pci_flags |= FLAG_CONFIG_IO_ON;
        else
            pci_flags &= ~FLAG_CONFIG_IO_ON;
    }
}

/* Set PMC (ie. change PCI configuration mechanism), 0 = #2, 1 = #1. */
void
pci_set_pmc(uint8_t pmc)
{
    pci_log("pci_set_pmc(%02X)\n", pmc);

    pci_io_handlers(0);

    pci_flags &= ~FLAG_MECHANISM_MASK;
    pci_flags |= (FLAG_MECHANISM_1 + !(pmc & 0x01));

    pci_io_handlers(1);

    pci_pmc = (pmc & 0x01);
}

static void
pci_reg_write(uint16_t port, uint8_t val)
{
    uint8_t slot = 0;

    if (port >= 0xc000) {
        pci_card  = (port >> 8) & 0xf;
        pci_index = port & 0xfc;
    }

    slot = pci_card_to_slot_mapping[pci_bus_number_to_index_mapping[pci_bus]][pci_card];
    if (slot != PCI_CARD_INVALID) {
        if (pci_cards[slot].write)
            pci_cards[slot].write(pci_func, pci_index | (port & 0x03), val, pci_cards[slot].priv);
    }
    pci_log("PCI: [WB] Mechanism #%i, slot %02X, %s card %02X:%02X, function %02X, index %02X = %02X\n",
            (port >= 0xc000) ? 2 : 1, slot,
            (slot == PCI_CARD_INVALID) ? "non-existent" : (pci_cards[slot].write ? "used" : "unused"),
            pci_card, pci_bus, pci_func, pci_index | (port & 0x03), val);
}

static void
pci_reset_regs(void)
{
    pci_index = pci_card = pci_func = pci_bus = pci_key = 0;
    pci_enable = 0x00000000;

    pci_flags &= ~(FLAG_CONFIG_IO_ON | FLAG_CONFIG_M1_IO_ON);
}

void
pci_pic_reset(void)
{
    pic_reset();
    pic_set_pci_flag(last_pci_card > 0);
}

static void
pci_reset_hard(void)
{
    pci_reset_regs();

    for (uint8_t i = 0; i < PCI_IRQS_NUM; i++) {
        if (pci_irq_hold[i]) {
            pci_irq_hold[i] = 0;

            picintc(1 << i);
        }
    }

    pci_pic_reset();
}

void
pci_reset(void)
{
    if (pci_flags & FLAG_MECHANISM_SWITCH) {
        pci_log("pci_reset(): Switchable configuration mechanism\n");
        pci_set_pmc(0x00);
    }

    pci_reset_hard();
}

static void
pci_trc_reset(uint8_t val)
{
    if (val & 2) {
        dma_reset();
        dma_set_at(1);

        device_reset_all(DEVICE_ALL);

        cpu_alt_reset = 0;

        pci_reset();

        mem_a20_alt = 0;
        mem_a20_recalc();

        flushmmucache();

        if (is_p6)
            mem_zero();
    }

#ifdef USE_DYNAREC
    if (cpu_use_dynarec)
        cpu_init = 1;
    else
        resetx86();
#else
    resetx86();
#endif
}

void
pci_write(uint16_t port, uint8_t val, UNUSED(void *priv))
{
    pci_log("PCI: [WB] Mechanism #%i port %04X = %02X\n", ((port >= 0xcfc) && (port <= 0xcff)) ? 1 : 2, port, val);

    switch (port) {
        case 0xcf8:
            if (pci_flags & FLAG_MECHANISM_2) {
                pci_func = (val >> 1) & 7;
                pci_key_write(val);

                pci_log("PCI: Mechanism #2 CF8: %sllocating ports %04X-%04X...\n", (pci_flags & FLAG_CONFIG_IO_ON) ? "A" : "Dea",
                        pci_base, pci_base + pci_size - 1);
            }
            break;
        case 0xcf9:
            if (pci_flags & FLAG_TRC_CONTROLS_CPURST)
                cpu_cpurst_on_sr = !(val & 0x10);

            if (!(pci_trc_reg & 4) && (val & 4))
                pci_trc_reset(val);

            pci_trc_reg = val & 0xfd;

            if (val & 2)
                pci_trc_reg &= 0xfb;
            break;
        case 0xcfa:
            if (pci_flags & FLAG_MECHANISM_2)
                pci_bus = val;
            break;
        case 0xcfb:
            if (pci_flags & FLAG_MECHANISM_SWITCH)
                pci_set_pmc(val);
            break;

        case 0xcfc:
        case 0xcfd:
        case 0xcfe:
        case 0xcff:
            if ((pci_flags & FLAG_MECHANISM_1) && (pci_flags & FLAG_CONFIG_M1_IO_ON))
                pci_reg_write(port, val);
           break;

        case 0xc000 ... 0xc0ff:
            if ((pci_flags & FLAG_MECHANISM_2) && (pci_flags & (FLAG_CONFIG_IO_ON | FLAG_CONFIG_DEV0_IO_ON)))
                pci_reg_write(port, val);
            break;

        case 0xc100 ... 0xcfff:
            if ((pci_flags & FLAG_MECHANISM_2) && (pci_flags & FLAG_CONFIG_IO_ON))
                pci_reg_write(port, val);
            break;

        default:
            break;
    }
}

void
pci_writew(uint16_t port, uint16_t val, UNUSED(void *priv))
{
    if (port & 0x0001) {
        /* Non-aligned access, split into two byte accesses. */
        pci_write(port, val & 0xff, priv);
        pci_write(port + 1, val >> 8, priv);
    } else {
        /* Aligned access, still split because we cheat. */
        switch (port) {
            case 0xcfc:
            case 0xcfe:
            case 0xc000 ... 0xcffe:
                pci_write(port, val & 0xff, priv);
                pci_write(port + 1, val >> 8, priv);
                break;

            default:
                break;
        }
    }
}

void
pci_writel(uint16_t port, uint32_t val, UNUSED(void *priv))
{
    if (port & 0x0003) {
        /* Non-aligned access, split into two word accesses. */
        pci_writew(port, val & 0xffff, priv);
        pci_writew(port + 2, val >> 16, priv);
    } else {
        /* Aligned access. */
        switch (port) {
            case 0xcf8:
                /* No split here, actual 32-bit access. */
                if (pci_flags & FLAG_MECHANISM_1) {
                    pci_log("PCI: [WL] Mechanism #1 port 0CF8 = %08X\n", val);

                    pci_index  = val & 0xff;
                    pci_func   = (val >> 8) & 7;
                    pci_card   = (val >> 11) & 31;
                    pci_bus    = (val >> 16) & 0xff;
                    pci_enable = (val & PCI_ENABLED);

                    if (pci_enable)
                        pci_flags |= FLAG_CONFIG_M1_IO_ON;
                    else
                        pci_flags &= ~FLAG_CONFIG_M1_IO_ON;
                    break;
                }
                break;
            case 0xcfc:
            case 0xc000 ... 0xcffc:
                /* Still split because we cheat. */
                pci_writew(port, val & 0xffff, priv);
                pci_writew(port + 2, val >> 16, priv);
                break;

            default:
                break;
        }
    }
}

static uint8_t
pci_reg_read(uint16_t port)
{
    uint8_t slot = 0;
    uint8_t ret  = 0xff;

    if (port >= 0xc000) {
        pci_card  = (port >> 8) & 0xf;
        pci_index = port & 0xfc;
    }

    slot = pci_card_to_slot_mapping[pci_bus_number_to_index_mapping[pci_bus]][pci_card];
    if (slot != PCI_CARD_INVALID) {
        if (pci_cards[slot].read)
            ret = pci_cards[slot].read(pci_func, pci_index | (port & 0x03), pci_cards[slot].priv);
    }
    pci_log("PCI: [RB] Mechanism #%i, slot %02X, %s card %02X:%02X, function %02X, index %02X = %02X\n",
            (port >= 0xc000) ? 2 : 1, slot,
            (slot == PCI_CARD_INVALID) ? "non-existent" : (pci_cards[slot].read ? "used" : "unused"),
            pci_card, pci_bus, pci_func, pci_index | (port & 0x03), ret);

    return ret;
}

uint8_t
pci_read(uint16_t port, UNUSED(void *priv))
{
    uint8_t ret  = 0xff;

    switch (port) {
        case 0xcf8:
            if (pci_flags & FLAG_MECHANISM_2)
                ret = pci_key | (pci_func << 1);
            break;
        case 0xcf9:
            ret = pci_trc_reg & 0xfb;
            break;
        case 0xcfa:
            if (pci_flags & FLAG_MECHANISM_2)
                ret = pci_bus;
            break;
       case 0xcfb:
            if (pci_flags & FLAG_MECHANISM_SWITCH)
                ret = pci_pmc;
            break;

        case 0xcfc:
        case 0xcfd:
        case 0xcfe:
        case 0xcff:
            if ((pci_flags & FLAG_MECHANISM_1) && (pci_flags & FLAG_CONFIG_M1_IO_ON))
                ret = pci_reg_read(port);
            break;

        case 0xc000 ... 0xc0ff:
            if ((pci_flags & FLAG_MECHANISM_2) && (pci_flags & (FLAG_CONFIG_IO_ON | FLAG_CONFIG_DEV0_IO_ON)))
                ret = pci_reg_read(port);
            break;

        case 0xc100 ... 0xcfff:
            if ((pci_flags & FLAG_MECHANISM_2) && (pci_flags & FLAG_CONFIG_IO_ON))
                ret = pci_reg_read(port);
            break;

        default:
            break;
    }

    pci_log("PCI: [RB] Mechanism #%i port %04X = %02X\n", ((port >= 0xcfc) && (port <= 0xcff)) ? 1 : 2, port, ret);

    return ret;
}

uint16_t
pci_readw(uint16_t port, UNUSED(void *priv))
{
    uint16_t ret  = 0xffff;

    if (port & 0x0001) {
        /* Non-aligned access, split into two byte accesses. */
        ret = pci_read(port, priv);
        ret |= ((uint16_t) pci_read(port + 1, priv)) << 8;
    } else {
        /* Aligned access, still split because we cheat. */
        switch (port) {
            case 0xcfc:
            case 0xcfe:
            case 0xc000 ... 0xcffe:
                ret = pci_read(port, priv);
                ret |= ((uint16_t) pci_read(port + 1, priv)) << 8;
                break;

            default:
                break;
        }
    }

    return ret;
}

uint32_t
pci_readl(uint16_t port, UNUSED(void *priv))
{
    uint32_t ret  = 0xffffffff;

    if (port & 0x0003) {
        /* Non-aligned access, split into two word accesses. */
        ret = pci_readw(port, priv);
        ret |= ((uint32_t) pci_readw(port + 2, priv)) << 16;
    } else {
        /* Aligned access. */
        switch (port) {
            case 0xcf8:
                /* No split here, actual 32-bit access. */
                if (pci_flags & FLAG_MECHANISM_1) {
                    ret = pci_index | (pci_func << 8) | (pci_card << 11) | (pci_bus << 16);
                    if (pci_flags & FLAG_CONFIG_M1_IO_ON)
                        ret |= PCI_ENABLED;

                    pci_log("PCI: [RL] Mechanism #1 port 0CF8 = %08X\n", ret);

                    return ret;
                }
                break;
            case 0xcfc:
            case 0xc000 ... 0xcffc:
                /* Still split because we cheat. */
                ret = pci_readw(port, priv);
                ret |= ((uint32_t) pci_readw(port + 2, priv)) << 16;
                break;
        }
    }

    return ret;
}

uint8_t
pci_register_bus(void)
{
    return last_pci_bus++;
}

void
pci_remap_bus(uint8_t bus_index, uint8_t bus_number)
{
    uint8_t i = 1;
    do {
        if (pci_bus_number_to_index_mapping[i] == bus_index)
            pci_bus_number_to_index_mapping[i] = PCI_BUS_INVALID;
    } while (i++ < 0xff);

    if ((bus_number > 0) && (bus_number < 0xff))
        pci_bus_number_to_index_mapping[bus_number] = bus_index;
}

void
pci_register_bus_slot(int bus, int card, int type, int inta, int intb, int intc, int intd)
{
    pci_card_t *dev = &pci_cards[last_pci_card];

    dev->bus                            = bus;
    dev->id                             = card;
    dev->type                           = type;
    dev->irq_routing[0]                 = inta;
    dev->irq_routing[1]                 = intb;
    dev->irq_routing[2]                 = intc;
    dev->irq_routing[3]                 = intd;
    dev->read                           = NULL;
    dev->write                          = NULL;
    dev->priv                           = NULL;
    pci_card_to_slot_mapping[bus][card] = last_pci_card;

    pci_log("pci_register_slot(): pci_cards[%i].bus = %02X; .id = %02X\n", last_pci_card, bus, card);

    if (type == PCI_CARD_NORMAL) {
        last_normal_pci_card++;
        /* This is needed to know at what position to add the bridge. */
        last_normal_pci_card_id = last_pci_card;
    }

    last_pci_card++;
}

static uint8_t
pci_find_slot(uint8_t add_type, uint8_t ignore_slot)
{
    const pci_card_t *dev;
    /* Is the device being added with a strict slot type matching requirement? */
    uint8_t           strict = (add_type & PCI_ADD_STRICT);
    /* The actual type of the device being added, with the strip flag, if any,
       masked. */
    uint8_t           masked_add_type = (add_type & PCI_ADD_MASK);
    /* Is the device being added normal, ie. without the possibility of ever
       being used as an on-board device? */
    uint8_t           normal_add_type = (masked_add_type >= PCI_CARD_NORMAL);
    uint8_t           match;
    uint8_t           normal;
    uint8_t           empty;
    uint8_t           process;
    uint8_t           ret = PCI_CARD_INVALID;

    /* Iterate i until we have either exhausted all the slot or the value of
       ret has changed to something other than PCI_CARD_INVALID. */
    for (uint8_t i = 0; (ret == PCI_CARD_INVALID) && (i < last_pci_card); i++) {
        dev = &pci_cards[i];

        /* Is the slot we are looking at of the exact same type as the device being
           added? */
        match = (dev->type == masked_add_type);
        /* Is the slot we are looking at a normal slot (ie. not an on-board chip)? */
        normal = (dev->type == PCI_CARD_NORMAL);
        /* Is the slot we are looking at empty? */
        empty = !dev->read && !dev->write;
        /* Should we process this slot, ie. were we told to ignore it, if any at all? */
        process = (ignore_slot == PCI_IGNORE_NO_SLOT) || (i != ignore_slot);

        /* This condition is now refactored and made to be easily human-readable. */
        if (empty && process && (match || (!strict && normal && normal_add_type)))
            ret = i;
    }

    return ret;
}

/* Add a PCI card. */
void
pci_add_card(uint8_t add_type, uint8_t (*read)(int func, int addr, void *priv),
             void (*write)(int func, int addr, uint8_t val, void *priv), void *priv, uint8_t *slot)
{
    pci_card_desc_t *dev;

    pci_log("pci_add_card(): PCI card #%02i: type = %i\n", next_pci_card, add_type);

    if (next_pci_card < PCI_CARDS_NUM) {
        dev = &pci_card_descs[next_pci_card];

        dev->type  = add_type | PCI_ADD_STRICT;
        dev->read  = read;
        dev->write = write;
        dev->priv  = priv;
        dev->slot  = slot;

        *(dev->slot) = PCI_CARD_INVALID;

        next_pci_card++;
        if (add_type == PCI_ADD_NORMAL)
            normal_pci_cards++;
    }
}

static void
pci_clear_card(UNUSED(int pci_card))
{
    pci_card_desc_t *dev;

    if (next_pci_card < PCI_CARDS_NUM) {
        dev = &pci_card_descs[next_pci_card];

        memset(dev, 0x00, sizeof(pci_card_desc_t));
    }
}

static uint8_t
pci_register_card(int pci_card)
{
    pci_card_desc_t *dev;
    pci_card_t *card;
    uint8_t     i;
    uint8_t     ret = PCI_CARD_INVALID;

    if (pci_card < PCI_CARDS_NUM) {
        dev = &pci_card_descs[pci_card];

        if (last_pci_card) {
            /* First, find the next available slot. */
            i = pci_find_slot(dev->type, 0xff);

            if (i != PCI_CARD_INVALID) {
                card = &pci_cards[i];
                card->read  = dev->read;
                card->write = dev->write;
                card->priv  = dev->priv;
                card->type |= (dev->type & PCI_CARD_VFIO);

                *(dev->slot) = i;

                ret = i;
            }
        }

        pci_clear_card(pci_card);
    }

    return ret;
}

/* Add an instance of the PCI bridge. */
void
pci_add_bridge(uint8_t add_type, uint8_t (*read)(int func, int addr, void *priv), void (*write)(int func, int addr, uint8_t val, void *priv), void *priv, uint8_t *slot)
{
    pci_card_t *card;
    uint8_t bridge_slot = (add_type == PCI_ADD_NORMAL) ? last_normal_pci_card_id : pci_find_slot(add_type, 0xff);

    if (bridge_slot != PCI_CARD_INVALID) {
        card = &pci_cards[bridge_slot];
        card->read  = read;
        card->write = write;
        card->priv  = priv;
    }

    *slot = bridge_slot;
}

/* Register the cards that have been added into slots. */
void
pci_register_cards(void)
{
    uint8_t normal;
#ifdef ENABLE_PCI_LOG
    uint8_t type;
    uint8_t *slot;
#endif

    next_normal_pci_card = 0;

    if (next_pci_card > 0) {
        for (uint8_t i = 0; i < next_pci_card; i++) {
#ifdef ENABLE_PCI_LOG
            type = pci_card_descs[i].type;
            slot = pci_card_descs[i].slot;
#endif
            normal = ((pci_card_descs[i].type & ~PCI_ADD_STRICT) == PCI_CARD_NORMAL);

            /* If this is a normal card, increase the next normal card index. */
            if (normal)
                next_normal_pci_card++;

            /* If this is a normal card and the next one is going to be beyond the last slot,
               add the bridge. */
            if (normal && (next_normal_pci_card >= last_normal_pci_card) &&
                (normal_pci_cards > last_normal_pci_card) && !(pci_flags & FLAG_NO_BRIDGES))
                device_add_inst(&dec21150_device, last_pci_bus);

            pci_register_card(i);
            pci_log("pci_register_cards(): PCI card #%02i: type = %02X, pci device = %02X:%02X\n",
                    i, type, pci_cards[*slot].bus, pci_cards[*slot].id);
        }
    }

    next_pci_card = 0;
    normal_pci_cards = 0;

    next_normal_pci_card = 0;
}

static void
pci_slots_clear(void)
{
    uint8_t i;

    last_pci_card = last_normal_pci_card = 0;
    last_normal_pci_card                 = 0;
    last_pci_bus                         = 1;

    next_pci_card                        = 0;
    normal_pci_cards                     = 0;

    next_normal_pci_card                 = 0;

    for (i = 0; i < PCI_CARDS_NUM; i++)
        pci_clear_slot(i);

    i = 0;
    do {
        for (uint8_t j = 0; j < PCI_CARDS_NUM; j++)
            pci_card_to_slot_mapping[i][j] = PCI_CARD_INVALID;
        pci_bus_number_to_index_mapping[i] = PCI_BUS_INVALID;
    } while (i++ < 0xff);

    pci_bus_number_to_index_mapping[0] = 0; /* always map bus 0 to index 0 */
}

void
pci_init(int flags)
{
    int c;

    pci_base = 0xc000;
    pci_size = 0x1000;

    pci_slots_clear();

    pci_reset_hard();

    pci_trc_reg = 0;
    pci_flags   = flags;

    if (pci_flags & FLAG_NO_IRQ_STEERING) {
        pic_elcr_io_handler(0);
        pic_elcr_set_enabled(0);
    } else {
        pic_elcr_io_handler(1);
        pic_elcr_set_enabled(1);
    }

    pci_pmc = (pci_flags & FLAG_MECHANISM_1) ? 0x01 : 0x00;

    if ((pci_flags & FLAG_MECHANISM_2) && (pci_flags & FLAG_CONFIG_DEV0_IO_ON)) {
        pci_log("PCI: Always expose device 0\n");
        pci_base = 0xc100;
        pci_size = 0x0f00;
    }

    if (pci_flags & FLAG_MECHANISM_SWITCH) {
        pci_log("PCI: Switchable configuration mechanism\n");
        pci_set_pmc(pci_pmc);
    } else
        pci_io_handlers(1);

    for (c = 0; c < PCI_IRQS_NUM; c++) {
        pci_irqs[c]      = PCI_IRQ_DISABLED;
        pci_irq_level[c] = (pci_flags & FLAG_NO_IRQ_STEERING) ? 0 : 1;
    }

    for (c = 0; c < PCI_MIRQS_NUM; c++) {
        pci_mirqs[c].enabled  = 0;
        pci_mirqs[c].irq_line = PCI_IRQ_DISABLED;
    }

    pic_set_pci_flag(1);
}
