root/src/add-ons/kernel/busses/virtio/virtio_pci/virtio_pci.cpp
/*
 * Copyright 2013, 2018, Jérôme Duval, jerome.duval@gmail.com.
 * Distributed under the terms of the MIT License.
 */


#include <new>
#include <stdio.h>
#include <string.h>

#include <bus/PCI.h>
#include <SupportDefs.h>

#include <cpu.h>
#include <kernel.h>
#include <virtio.h>

#include "virtio_pci.h"


//#define TRACE_VIRTIO
#ifdef TRACE_VIRTIO
#       define TRACE(x...) dprintf("\33[33mvirtio_pci:\33[0m " x)
#else
#       define TRACE(x...) ;
#endif
#define TRACE_ALWAYS(x...)      dprintf("\33[33mvirtio_pci:\33[0m " x)
#define ERROR(x...)                     dprintf("\33[33mvirtio_pci:\33[0m " x)
#define CALLED(x...)            TRACE("CALLED %s\n", __PRETTY_FUNCTION__)


#define VIRTIO_PCI_DEVICE_MODULE_NAME "busses/virtio/virtio_pci/driver_v1"
#define VIRTIO_PCI_SIM_MODULE_NAME "busses/virtio/virtio_pci/device/v1"

#define VIRTIO_PCI_CONTROLLER_TYPE_NAME "virtio pci controller"

typedef enum {
        VIRTIO_IRQ_LEGACY,
        VIRTIO_IRQ_MSI_X_SHARED,
        VIRTIO_IRQ_MSI_X,
} virtio_irq_type;

typedef struct {
        virtio_sim sim;
        uint16 queue;
} virtio_pci_queue_cookie;

typedef struct {
        pci_device_module_info* pci;
        pci_device* device;
        bool virtio1;
        addr_t base_addr;
        area_id registersArea[6];
        addr_t commonCfgAddr;
        addr_t isrAddr;
        addr_t notifyAddr;
        uint32 notifyOffsetMultiplier;
        uint32 irq;
        virtio_irq_type irq_type;
        virtio_sim sim;
        uint16 queue_count;
        addr_t* notifyOffsets;

        device_node* node;
        pci_info info;

        virtio_pci_queue_cookie *cookies;
} virtio_pci_sim_info;


device_manager_info* gDeviceManager;
virtio_for_controller_interface* gVirtio;


static status_t
virtio_pci_find_capability(virtio_pci_sim_info* bus, uint8 cfgType,
        void* buffer, size_t size)
{
        uint8 capabilityOffset;
        if (bus->pci->find_pci_capability(bus->device, PCI_cap_id_vendspec, &capabilityOffset) != B_OK)
                return B_ENTRY_NOT_FOUND;

        if (size < sizeof(virtio_pci_cap))
                return B_RESULT_NOT_REPRESENTABLE;
        union regs {
                uint32 reg[8];
                struct virtio_pci_cap capability;
        } * v = (union regs*)buffer;

        while (capabilityOffset != 0) {
                for (int i = 0; i < 4; i++) {
                        v->reg[i] = bus->pci->read_pci_config(bus->device, capabilityOffset + i * 4, 4);
                }
                if (v->capability.cfg_type == cfgType)
                        break;
                capabilityOffset = v->capability.cap_next;
        }
        if (capabilityOffset == 0)
                return B_ENTRY_NOT_FOUND;

        if (v->capability.length > sizeof(virtio_pci_cap)) {
                size_t length = min_c(ROUNDUP(v->capability.length, sizeof(uint32)), size);
                for (size_t i = 4; i < length / sizeof(uint32); i++)
                        v->reg[i] = bus->pci->read_pci_config(bus->device, capabilityOffset + i * 4, 4);
        }
        return B_OK;
}


static int32
virtio_pci_interrupt(void *data)
{
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)data;
        uint8 isr;
        if (bus->virtio1) {
                uint8* isrAddr = (uint8*)bus->isrAddr;
                isr = *isrAddr;
        } else {
                isr = bus->pci->read_io_8(bus->device,
                        bus->base_addr + VIRTIO_PCI_ISR);
        }
        if (isr == 0)
                return B_UNHANDLED_INTERRUPT;

        if (isr & VIRTIO_PCI_ISR_CONFIG)
                gVirtio->config_interrupt_handler(bus->sim);

        if (isr & VIRTIO_PCI_ISR_INTR)
                gVirtio->queue_interrupt_handler(bus->sim, INT16_MAX);

        return B_HANDLED_INTERRUPT;
}


static int32
virtio_pci_config_interrupt(void *data)
{
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)data;
        gVirtio->config_interrupt_handler(bus->sim);

        return B_HANDLED_INTERRUPT;
}


static int32
virtio_pci_queue_interrupt(void *data)
{
        virtio_pci_queue_cookie* cookie = (virtio_pci_queue_cookie*)data;
        gVirtio->queue_interrupt_handler(cookie->sim, cookie->queue);

        return B_HANDLED_INTERRUPT;
}


static int32
virtio_pci_queues_interrupt(void *data)
{
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)data;
        gVirtio->queue_interrupt_handler(bus->sim, INT16_MAX);

        return B_HANDLED_INTERRUPT;
}


static status_t
virtio_pci_setup_msix_interrupts(virtio_pci_sim_info* bus)
{
        CALLED();
        uint8 irq = 0; // first irq slot
        if (bus->virtio1) {
                volatile uint16 *msixVector = (uint16*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, config_msix_vector));
                *msixVector = irq;
        } else {
                bus->pci->write_io_16(bus->device, bus->base_addr
                        + VIRTIO_MSI_CONFIG_VECTOR, irq);
                if (bus->pci->read_io_16(bus->device, bus->base_addr
                        + VIRTIO_MSI_CONFIG_VECTOR) == VIRTIO_MSI_NO_VECTOR) {
                        ERROR("msix config vector incorrect\n");
                        return B_BAD_VALUE;
                }
        }
        if (bus->irq_type == VIRTIO_IRQ_MSI_X || bus->irq_type == VIRTIO_IRQ_MSI_X_SHARED)
                irq++;

        for (uint16 queue = 0; queue < bus->queue_count; queue++) {
                if (bus->virtio1) {
                        volatile uint16* queueSelect = (uint16*)(bus->commonCfgAddr
                                + offsetof(struct virtio_pci_common_cfg, queue_select));
                        *queueSelect = queue;
                        volatile uint16* msixVector = (uint16*)(bus->commonCfgAddr
                                + offsetof(struct virtio_pci_common_cfg, queue_msix_vector));
                        *msixVector = irq;
                } else {
                        bus->pci->write_io_16(bus->device, bus->base_addr
                                + VIRTIO_PCI_QUEUE_SEL, queue);
                        bus->pci->write_io_16(bus->device, bus->base_addr
                                + VIRTIO_MSI_QUEUE_VECTOR, irq);

                        if (bus->pci->read_io_16(bus->device, bus->base_addr
                                + VIRTIO_MSI_QUEUE_VECTOR) == VIRTIO_MSI_NO_VECTOR) {
                                ERROR("msix queue vector incorrect\n");
                                return B_BAD_VALUE;
                        }
                }
                if (bus->irq_type == VIRTIO_IRQ_MSI_X)
                        irq++;
        }

        return B_OK;
}


static void
set_sim(void* cookie, virtio_sim sim)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;
        bus->sim = sim;
}


static status_t
read_host_features(void* cookie, uint64 *features)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;

        TRACE("read_host_features() %p node %p pci %p device %p\n", bus,
                bus->node, bus->pci, bus->device);

        if (bus->virtio1) {
                volatile uint32 *select = (uint32*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, device_feature_select));
                volatile uint32 *feature = (uint32*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, device_feature));
                *select = 0;
                *features = *feature;
                *select = 1;
                *features |= ((uint64)*feature << 32) ;
                TRACE("read_host_features() %" B_PRIx64 "\n", *features);
        } else {
                *features = bus->pci->read_io_32(bus->device,
                        bus->base_addr + VIRTIO_PCI_HOST_FEATURES);
        }
        return B_OK;
}


static status_t
write_guest_features(void* cookie, uint64 features)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;
        if (bus->virtio1) {
                volatile uint32 *select = (uint32*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, device_feature_select));
                volatile uint32 *feature = (uint32*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, device_feature));
                *select = 0;
                *feature = features & 0xffffffff;
                *select = 1;
                *feature = (features >> 32) ;
        } else {
                bus->pci->write_io_32(bus->device, bus->base_addr
                        + VIRTIO_PCI_GUEST_FEATURES, features);
        }
        return B_OK;
}


static uint8
get_status(void* cookie)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;
        if (bus->virtio1) {
                uint8 *addr = (uint8*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, device_status));
                return *addr;
        } else {
                return bus->pci->read_io_8(bus->device, bus->base_addr + VIRTIO_PCI_STATUS);
        }
}


static void
set_status(void* cookie, uint8 status)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;
        if (bus->virtio1) {
                volatile uint8 *addr = (uint8*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, device_status));
                if (status == 0) {
                        *addr = 0;
                        while (*addr != 0)
                                cpu_pause();
                } else {
                        uint8 old = 0;
                        if (status != 0)
                                old = *addr;
                        *addr = status | old;
                }
        } else {
                if (status == 0) {
                        bus->pci->write_io_8(bus->device, bus->base_addr + VIRTIO_PCI_STATUS, 0);
                        while (bus->pci->read_io_8(bus->device, bus->base_addr + VIRTIO_PCI_STATUS) != 0)
                                cpu_pause();
                } else {
                        uint8 old = bus->pci->read_io_8(bus->device, bus->base_addr + VIRTIO_PCI_STATUS);
                        bus->pci->write_io_8(bus->device, bus->base_addr + VIRTIO_PCI_STATUS, status | old);
                }
        }
}


static status_t
read_device_config(void* cookie, uint8 _offset, void* _buffer,
        size_t bufferSize)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;

        addr_t offset = bus->base_addr + _offset;
        if (!bus->virtio1)
                offset += VIRTIO_PCI_CONFIG(bus);
        uint8* buffer = (uint8*)_buffer;
        while (bufferSize > 0) {
                uint8 size = 4;
                if (bufferSize == 1) {
                        size = 1;
                        if (bus->virtio1) {
                                *buffer = *(uint8*)offset;
                        } else {
                                *buffer = bus->pci->read_io_8(bus->device, offset);
                        }
                } else if (bufferSize <= 3) {
                        size = 2;
                        if (bus->virtio1) {
                                *(uint16*)buffer = *(uint16*)offset;
                        } else {
                                *(uint16*)buffer = bus->pci->read_io_16(bus->device, offset);
                        }
                } else {
                        if (bus->virtio1) {
                                *(uint32*)buffer = *(uint32*)offset;
                        } else {
                                *(uint32*)buffer = bus->pci->read_io_32(bus->device,
                                        offset);
                        }
                }
                buffer += size;
                bufferSize -= size;
                offset += size;
        }

        return B_OK;
}


static status_t
write_device_config(void* cookie, uint8 _offset, const void* _buffer,
        size_t bufferSize)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;

        addr_t offset = bus->base_addr + _offset;
        if (!bus->virtio1)
                offset += VIRTIO_PCI_CONFIG(bus);
        const uint8* buffer = (const uint8*)_buffer;
        while (bufferSize > 0) {
                uint8 size = 4;
                if (bufferSize == 1) {
                        size = 1;
                        if (bus->virtio1) {
                                *(uint8*)offset = *buffer;
                        } else {
                                bus->pci->write_io_8(bus->device, offset, *buffer);
                        }
                } else if (bufferSize <= 3) {
                        size = 2;
                        if (bus->virtio1) {
                                *(uint16*)offset = *(uint16*)buffer;
                        } else {
                                bus->pci->write_io_16(bus->device, offset, *(const uint16*)buffer);
                        }
                } else {
                        if (bus->virtio1) {
                                *(uint32*)offset = *(uint32*)buffer;
                        } else {
                                bus->pci->write_io_32(bus->device, offset, *(const uint32*)buffer);
                        }
                }
                buffer += size;
                bufferSize -= size;
                offset += size;
        }
        return B_OK;
}


static uint16
get_queue_ring_size(void* cookie, uint16 queue)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;
        if (bus->virtio1) {
                volatile uint16* queueSelect = (uint16*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, queue_select));
                *queueSelect = queue;
                volatile uint16* ringSize = (volatile uint16*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, queue_size));
                return *ringSize;
        } else {
                bus->pci->write_io_16(bus->device, bus->base_addr + VIRTIO_PCI_QUEUE_SEL,
                        queue);
                return bus->pci->read_io_16(bus->device, bus->base_addr
                        + VIRTIO_PCI_QUEUE_NUM);
        }
}


static status_t
setup_queue(void* cookie, uint16 queue, phys_addr_t phy, phys_addr_t phyAvail,
        phys_addr_t phyUsed)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;
        if (bus->virtio1) {
                if (queue >= bus->queue_count)
                        return B_BAD_VALUE;
                volatile uint16* queueSelect = (uint16*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, queue_select));
                *queueSelect = queue;

                volatile uint64* queueDesc = (volatile uint64*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, queue_desc));
                *queueDesc = phy;
                volatile uint64* queueAvail = (volatile uint64*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, queue_avail));
                *queueAvail = phyAvail;
                volatile uint64* queueUsed = (volatile uint64*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, queue_used));
                *queueUsed = phyUsed;
                volatile uint16* queueEnable = (volatile uint16*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, queue_enable));
                *queueEnable = 1;

                volatile uint16* queueNotifyOffset = (volatile uint16*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, queue_notify_off));
                bus->notifyOffsets[queue] = *queueNotifyOffset * bus->notifyOffsetMultiplier;
        } else {
                bus->pci->write_io_16(bus->device, bus->base_addr + VIRTIO_PCI_QUEUE_SEL, queue);
                bus->pci->write_io_32(bus->device, bus->base_addr + VIRTIO_PCI_QUEUE_PFN,
                        (uint32)phy >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
        }
        return B_OK;
}


static status_t
setup_interrupt(void* cookie, uint16 queueCount)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;
        pci_info *pciInfo = &bus->info;

        bus->queue_count = queueCount;

        // try MSI-X
        uint32 msixCount = bus->pci->get_msix_count(bus->device);
        if (msixCount >= 2) {
                uint32 vectorCount = queueCount + 1;
                if (msixCount >= vectorCount) {
                        uint32 vector;
                        bus->cookies = new(std::nothrow)
                                virtio_pci_queue_cookie[queueCount];
                        if (bus->cookies != NULL
                                && bus->pci->configure_msix(bus->device, vectorCount,
                                        &vector) == B_OK
                                && bus->pci->enable_msix(bus->device) == B_OK) {
                                TRACE_ALWAYS("using MSI-X count %" B_PRIu32 " starting at %" B_PRIu32 "\n",
                                        vectorCount, vector);
                                bus->irq = vector;
                                bus->irq_type = VIRTIO_IRQ_MSI_X;
                        } else {
                                ERROR("couldn't use MSI-X\n");
                        }
                } else {
                        uint32 vector;
                        if (bus->pci->configure_msix(bus->device, 2, &vector) == B_OK
                                && bus->pci->enable_msix(bus->device) == B_OK) {
                                TRACE_ALWAYS("using MSI-X vector shared %" B_PRIu32 "\n", vector);
                                bus->irq = vector;
                                bus->irq_type = VIRTIO_IRQ_MSI_X_SHARED;
                        } else {
                                ERROR("couldn't use MSI-X SHARED\n");
                        }
                }
        }

        if (bus->irq_type == VIRTIO_IRQ_LEGACY) {
                bus->irq = pciInfo->u.h0.interrupt_line;
                if (bus->irq == 0xff)
                        bus->irq = 0;
                TRACE_ALWAYS("using legacy interrupt %" B_PRIu32 "\n", bus->irq);
        }
        if (bus->irq == 0) {
                ERROR("PCI IRQ not assigned\n");
                delete bus;
                return B_ERROR;
        }

        if (bus->irq_type != VIRTIO_IRQ_LEGACY) {
                status_t status = install_io_interrupt_handler(bus->irq,
                        virtio_pci_config_interrupt, bus, 0);
                if (status != B_OK) {
                        ERROR("can't install interrupt handler\n");
                        return status;
                }
                int32 irq = bus->irq + 1;
                if (bus->irq_type == VIRTIO_IRQ_MSI_X) {
                        for (int32 queue = 0; queue < queueCount; queue++, irq++) {
                                bus->cookies[queue].sim = bus->sim;
                                bus->cookies[queue].queue = queue;
                                status_t status = install_io_interrupt_handler(irq,
                                        virtio_pci_queue_interrupt, &bus->cookies[queue], 0);
                                if (status != B_OK) {
                                        ERROR("can't install interrupt handler\n");
                                        return status;
                                }
                        }
                } else {
                        status_t status = install_io_interrupt_handler(irq,
                                virtio_pci_queues_interrupt, bus, 0);
                        if (status != B_OK) {
                                ERROR("can't install interrupt handler\n");
                                return status;
                        }
                }
                TRACE("setup_interrupt() installed MSI-X interrupt handlers\n");
                virtio_pci_setup_msix_interrupts(bus);
        } else {
                // setup interrupt handler
                status_t status = install_io_interrupt_handler(bus->irq,
                        virtio_pci_interrupt, bus, 0);
                if (status != B_OK) {
                        ERROR("can't install interrupt handler\n");
                        return status;
                }
                TRACE("setup_interrupt() installed legacy interrupt handler\n");
        }

        return B_OK;
}


static status_t
free_interrupt(void* cookie)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;

        if (bus->irq_type != VIRTIO_IRQ_LEGACY) {
                remove_io_interrupt_handler(bus->irq, virtio_pci_config_interrupt, bus);
                int32 irq = bus->irq + 1;
                if (bus->irq_type == VIRTIO_IRQ_MSI_X) {
                        for (int32 queue = 0; queue < bus->queue_count; queue++, irq++)
                                remove_io_interrupt_handler(irq, virtio_pci_queue_interrupt, &bus->cookies[queue]);
                        delete[] bus->cookies;
                        bus->cookies = NULL;
                } else {
                        remove_io_interrupt_handler(irq, virtio_pci_queues_interrupt, bus);
                }
                bus->pci->disable_msi(bus->device);
                bus->pci->unconfigure_msi(bus->device);
        } else
                remove_io_interrupt_handler(bus->irq, virtio_pci_interrupt, bus);

        return B_OK;
}


static void
notify_queue(void* cookie, uint16 queue)
{
        CALLED();
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)cookie;
        if (queue >= bus->queue_count)
                return;
        if (bus->virtio1) {
                volatile uint16* notifyAddr = (volatile uint16*)(bus->notifyAddr + bus->notifyOffsets[queue]);
                *notifyAddr = queue;
        } else {
                bus->pci->write_io_16(bus->device, bus->base_addr
                        + VIRTIO_PCI_QUEUE_NOTIFY, queue);
        }
}


//      #pragma mark -


static status_t
init_bus(device_node* node, void** bus_cookie)
{
        CALLED();
        status_t status = B_OK;

        virtio_pci_sim_info* bus = new(std::nothrow) virtio_pci_sim_info;
        if (bus == NULL) {
                return B_NO_MEMORY;
        }
        memset(bus, 0, sizeof(virtio_pci_sim_info));

        pci_device_module_info* pci;
        pci_device* device;
        {
                device_node* parent = gDeviceManager->get_parent_node(node);
                device_node* pciParent = gDeviceManager->get_parent_node(parent);
                gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci,
                        (void**)&device);
                gDeviceManager->put_node(pciParent);
                gDeviceManager->put_node(parent);
        }

        bus->node = node;
        bus->pci = pci;
        bus->device = device;
        bus->cookies = NULL;
        bus->irq_type = VIRTIO_IRQ_LEGACY;

        pci_info *pciInfo = &bus->info;
        pci->get_pci_info(device, pciInfo);

        bus->virtio1 = pciInfo->device_id >= VIRTIO_PCI_DEVICEID_MODERN_MIN
                && pciInfo->device_id <= VIRTIO_PCI_DEVICEID_MODERN_MAX;

        if (bus->virtio1) {
                struct virtio_pci_cap common, isr, deviceCap;
                struct virtio_pci_notify_cap notify;
                bool deviceCfgFound = false;
                if (virtio_pci_find_capability(bus, VIRTIO_PCI_CAP_COMMON_CFG, &common,
                        sizeof(common)) != B_OK) {
                        return B_DEVICE_NOT_FOUND;
                }
                if (virtio_pci_find_capability(bus, VIRTIO_PCI_CAP_ISR_CFG, &isr,
                        sizeof(isr)) != B_OK) {
                        return B_DEVICE_NOT_FOUND;
                }
                if (virtio_pci_find_capability(bus, VIRTIO_PCI_CAP_DEVICE_CFG, &deviceCap,
                        sizeof(deviceCap)) != B_OK) {
                        memset(&deviceCap, 0, sizeof(deviceCap));
                } else {
                        deviceCfgFound = true;
                }
                if (virtio_pci_find_capability(bus, VIRTIO_PCI_CAP_NOTIFY_CFG, &notify,
                        sizeof(notify)) != B_OK) {
                        return B_DEVICE_NOT_FOUND;
                }

                size_t bars[6] = {0};
                if (common.length > 0)
                        bars[common.bar] = common.offset + common.length;
                if (isr.length > 0)
                        bars[isr.bar] = max_c(bars[isr.bar], isr.offset + isr.length);
                if (notify.cap.length > 0) {
                        bars[notify.cap.bar] = max_c(bars[notify.cap.bar], notify.cap.offset
                                + notify.cap.length);
                }
                if (deviceCfgFound && deviceCap.length > 0)
                        bars[deviceCap.bar] = max_c(bars[deviceCap.bar], deviceCap.offset + deviceCap.length);

                size_t index = 0;
                addr_t registers[B_COUNT_OF(bars)] = {0};
                for (size_t i = 0; i < B_COUNT_OF(bars); i++, index++) {
                        if (bars[index] == 0) {
                                bus->registersArea[index] = -1;
                                continue;
                        }
                        phys_addr_t barAddr = pciInfo->u.h0.base_registers[i];
                        size_t barSize = pciInfo->u.h0.base_register_sizes[i];
                        if ((pciInfo->u.h0.base_register_flags[i] & PCI_address_type) == PCI_address_type_64
                                && i < (B_COUNT_OF(bars) - 1)) {
                                i++;
                                barAddr |= (uint64)pciInfo->u.h0.base_registers[i] << 32;
                                barSize |= (uint64)pciInfo->u.h0.base_register_sizes[i] << 32;
                        }

                        bus->registersArea[index] = map_physical_memory("Virtio PCI memory mapped registers",
                                barAddr, barSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
                                (void**)&registers[index]);
                }

                bus->commonCfgAddr = registers[common.bar] + common.offset;
                bus->isrAddr = registers[isr.bar] + isr.offset;
                bus->notifyAddr = registers[notify.cap.bar] + notify.cap.offset;
                bus->notifyOffsetMultiplier = notify.notify_off_multiplier;
                if (deviceCfgFound)
                        bus->base_addr = registers[deviceCap.bar] + deviceCap.offset;

                // enable bus master and memory
                uint16 pcicmd = pci->read_pci_config(device, PCI_command, 2);
                pcicmd &= ~(PCI_command_int_disable | PCI_command_io);
                pci->write_pci_config(device, PCI_command, 2,
                        pcicmd | PCI_command_master | PCI_command_memory);

                volatile uint16 *queueCount = (uint16*)(bus->commonCfgAddr
                        + offsetof(struct virtio_pci_common_cfg, num_queues));
                bus->queue_count = *queueCount;
                bus->notifyOffsets = new addr_t[bus->queue_count];

        } else {
                // legacy interrupt
                bus->base_addr = pciInfo->u.h0.base_registers[0];

                // enable bus master and io
                uint16 pcicmd = pci->read_pci_config(device, PCI_command, 2);
                pcicmd &= ~(PCI_command_memory | PCI_command_int_disable);
                pcicmd |= PCI_command_master | PCI_command_io;
                pci->write_pci_config(device, PCI_command, 2, pcicmd);
        }


        set_status(bus, VIRTIO_CONFIG_STATUS_RESET);
        set_status(bus, VIRTIO_CONFIG_STATUS_ACK);

        TRACE("init_bus() %p node %p pci %p device %p\n", bus, node,
                bus->pci, bus->device);

        *bus_cookie = bus;
        return status;
}


static void
uninit_bus(void* bus_cookie)
{
        virtio_pci_sim_info* bus = (virtio_pci_sim_info*)bus_cookie;
        if (bus->irq_type != VIRTIO_IRQ_LEGACY) {
                int32 irq = bus->irq + 1;
                if (bus->irq_type == VIRTIO_IRQ_MSI_X) {
                        for (int32 queue = 0; queue < bus->queue_count; queue++, irq++)
                                remove_io_interrupt_handler(irq, virtio_pci_queue_interrupt, &bus->cookies[queue]);
                        delete[] bus->cookies;
                } else {
                        remove_io_interrupt_handler(irq, virtio_pci_queues_interrupt, bus);
                }
                remove_io_interrupt_handler(bus->irq, virtio_pci_config_interrupt,
                                bus);

                bus->pci->disable_msi(bus->device);
                bus->pci->unconfigure_msi(bus->device);
        } else
                remove_io_interrupt_handler(bus->irq, virtio_pci_interrupt, bus);

        if (bus->virtio1) {
                for (int i = 0; i < 6; i++) {
                        if (bus->registersArea[i] >= 0)
                                delete_area(bus->registersArea[i]);
                        else
                                break;
                }
                delete[] bus->notifyOffsets;
        }

        delete bus;
}


static void
bus_removed(void* bus_cookie)
{
        return;
}


//      #pragma mark -


static status_t
register_child_devices(void* cookie)
{
        CALLED();
        device_node* node = (device_node*)cookie;
        device_node* parent = gDeviceManager->get_parent_node(node);
        pci_device_module_info* pci;
        pci_device* device;
        gDeviceManager->get_driver(parent, (driver_module_info**)&pci,
                (void**)&device);

        uint16 pciSubDeviceId = pci->read_pci_config(device, PCI_subsystem_id, 2);
        uint16 pciDeviceId = pci->read_pci_config(device, PCI_device_id, 2);

        uint8 virtioVersion = 0;
        uint16 virtioDeviceId = pciSubDeviceId;
        if (pciDeviceId >= VIRTIO_PCI_DEVICEID_MODERN_MIN) {
                virtioVersion = 1;
                virtioDeviceId = pciDeviceId - VIRTIO_PCI_DEVICEID_MODERN_MIN;
        }

        char prettyName[25];
        sprintf(prettyName, "Virtio Device %" B_PRIu16, virtioDeviceId);

        device_attr attrs[] = {
                // properties of this controller for virtio bus manager
                { B_DEVICE_PRETTY_NAME, B_STRING_TYPE,
                        { .string = prettyName }},
                { B_DEVICE_FIXED_CHILD, B_STRING_TYPE,
                        { .string = VIRTIO_FOR_CONTROLLER_MODULE_NAME }},

                // private data to identify the device
                { VIRTIO_DEVICE_TYPE_ITEM, B_UINT16_TYPE,
                        { .ui16 = virtioDeviceId }},
                { VIRTIO_VRING_ALIGNMENT_ITEM, B_UINT16_TYPE,
                        { .ui16 = VIRTIO_PCI_VRING_ALIGN }},
                { VIRTIO_VERSION_ITEM, B_UINT8_TYPE,
                        { .ui8 = virtioVersion }},
                { NULL }
        };

        return gDeviceManager->register_node(node, VIRTIO_PCI_SIM_MODULE_NAME,
                attrs, NULL, &node);
}


static status_t
init_device(device_node* node, void** device_cookie)
{
        CALLED();
        *device_cookie = node;
        return B_OK;
}


static status_t
register_device(device_node* parent)
{
        device_attr attrs[] = {
                {B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Virtio PCI"}},
                {}
        };

        return gDeviceManager->register_node(parent, VIRTIO_PCI_DEVICE_MODULE_NAME,
                attrs, NULL, NULL);
}


static float
supports_device(device_node* parent)
{
        CALLED();
        const char* bus;
        uint16 vendorID, deviceID;

        // make sure parent is a PCI Virtio device node
        if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK
                || gDeviceManager->get_attr_uint16(parent, B_DEVICE_VENDOR_ID,
                                &vendorID, false) < B_OK
                || gDeviceManager->get_attr_uint16(parent, B_DEVICE_ID, &deviceID,
                                false) < B_OK) {
                return -1;
        }

        if (strcmp(bus, "pci") != 0)
                return 0.0f;

        if (vendorID == VIRTIO_PCI_VENDORID) {
                if (deviceID < VIRTIO_PCI_DEVICEID_MIN
                        || deviceID > VIRTIO_PCI_DEVICEID_MODERN_MAX) {
                        return 0.0f;
                }

                pci_device_module_info* pci;
                pci_device* device;
                gDeviceManager->get_driver(parent, (driver_module_info**)&pci,
                        (void**)&device);
                uint8 pciRevision = pci->read_pci_config(device, PCI_revision,
                        1);
                if (deviceID >= VIRTIO_PCI_DEVICEID_MIN
                        && deviceID <= VIRTIO_PCI_DEVICEID_LEGACY_MAX
                        && pciRevision != 0) {
                        return 0.0f;
                }

                TRACE("Virtio device found! vendor 0x%04x, device 0x%04x\n", vendorID,
                        deviceID);
                return 0.8f;
        }

        return 0.0f;
}


//      #pragma mark -


module_dependency module_dependencies[] = {
        { VIRTIO_FOR_CONTROLLER_MODULE_NAME, (module_info**)&gVirtio },
        { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager },
        {}
};


static virtio_sim_interface gVirtioPCIDeviceModule = {
        {
                {
                        VIRTIO_PCI_SIM_MODULE_NAME,
                        0,
                        NULL
                },

                NULL,   // supports device
                NULL,   // register device
                init_bus,
                uninit_bus,
                NULL,   // register child devices
                NULL,   // rescan
                bus_removed,
        },

        set_sim,
        read_host_features,
        write_guest_features,
        get_status,
        set_status,
        read_device_config,
        write_device_config,
        get_queue_ring_size,
        setup_queue,
        setup_interrupt,
        free_interrupt,
        notify_queue
};


static driver_module_info sVirtioDevice = {
        {
                VIRTIO_PCI_DEVICE_MODULE_NAME,
                0,
                NULL
        },

        supports_device,
        register_device,
        init_device,
        NULL,   // uninit
        register_child_devices,
        NULL,   // rescan
        NULL,   // device removed
};

module_info* modules[] = {
        (module_info* )&sVirtioDevice,
        (module_info* )&gVirtioPCIDeviceModule,
        NULL
};