Skip to content
Snippets Groups Projects
virtio-device.cc 4.93 KiB
#include "drivers/pci-device.hh"
#include "drivers/virtio-vring.hh"

#include "virtio-device.hh"
#include "virtio-vring.hh"

using namespace pci;

namespace virtio {

    virtio_device::virtio_device(u8 bus, u8 device, u8 func)
        : pci_device(bus, device, func),  _num_queues(0), _bar1(nullptr)
    {
        for (unsigned i=0; i < max_virtqueues_nr; i++) {
            _queues[i] = nullptr;
        }
    }

    virtio_device::~virtio_device()
    {

    }

    void virtio_device::dump_config(void)
    {
        pci_device::dump_config();

        debug(fmt("    virtio features: "), false);
        for (int i=0;i<32;i++)
            debug(fmt("%d") % get_device_feature_bit(i), false);
        debug(fmt("\n"), false);
    }

    bool virtio_device::parse_pci_config(void)
    {
        if (!pci_device::parse_pci_config()) {
            return (false);
        }

        // Test whether bar1 is present
        _bar1 = get_bar(1);
        if (_bar1 == nullptr) {
            return (false);
        }

        // Check ABI version
        u8 rev = get_revision_id();
        if (rev != VIRTIO_PCI_ABI_VERSION) {
            debug(fmt("Wrong virtio revision=%x") % rev);
            return (false);
        }

        // Check device ID
        u16 dev_id = get_device_id();
        if ((dev_id < VIRTIO_PCI_ID_MIN) || (dev_id > VIRTIO_PCI_ID_MAX)) {
            debug(fmt("Wrong virtio dev id %x") % dev_id);
            return (false);
        }

        return (true);
    }

    void virtio_device::reset_host_side()
    {
        set_dev_status(0);
        virtio_conf_writel(VIRTIO_PCI_QUEUE_PFN,(u32)0);
    }

    void virtio_device::free_queues(void)
    {
        for (unsigned i=0; i < max_virtqueues_nr; i++) {
            if (nullptr != _queues[i]) {
                delete (_queues[i]);
                _queues[i] = nullptr;
            }
        }
    }

    bool virtio_device::kick(int queue)
    {
        virtio_conf_writel(VIRTIO_PCI_QUEUE_NOTIFY, queue);
        return true;
    }

    bool virtio_device::probe_virt_queues(void)
    {
        u16 qsize = 0;

        do {

            if (_num_queues >= max_virtqueues_nr) {
                return false;
            }

            // Read queue size
            virtio_conf_writel(VIRTIO_PCI_QUEUE_SEL, _num_queues);
            qsize = virtio_conf_readw(VIRTIO_PCI_QUEUE_NUM);
            if (0 == qsize) {
                break;
            }

            // Init a new queue
            vring * queue = new vring(this, qsize, _num_queues);
            _queues[_num_queues++] = queue;

            // Tell host about pfn
            // TODO: Yak, this is a bug in the design, on large memory we'll have PFNs > 32 bit
            // Dor to notify Rusty
            virtio_conf_writel(VIRTIO_PCI_QUEUE_PFN, (u32)(queue->get_paddr() >> VIRTIO_PCI_QUEUE_ADDR_SHIFT));

            // Debug print
            debug(fmt("Queue[%d] -> size %d, paddr %x") % (_num_queues-1) % qsize % queue->get_paddr());

        } while (true);

        return true;
    }

    vring* virtio_device::get_virt_queue(unsigned idx)
    {
        if (idx >= _num_queues) {
            return (nullptr);
        }

        return (_queues[idx]);
    }

    u32 virtio_device::get_device_features(void)
    {
        return (virtio_conf_readl(VIRTIO_PCI_HOST_FEATURES));
    }

    bool virtio_device::get_device_feature_bit(int bit)
    {
        return (get_virtio_config_bit(VIRTIO_PCI_HOST_FEATURES, bit));
    }

    void virtio_device::set_guest_features(u32 features)
    {
        virtio_conf_writel(VIRTIO_PCI_GUEST_FEATURES, features);
    }

    void virtio_device::set_guest_feature_bit(int bit, bool on)
    {
        set_virtio_config_bit(VIRTIO_PCI_GUEST_FEATURES, bit, on);
    }

    u32 virtio_device::get_dev_status(void)
    {
        return (virtio_conf_readl(VIRTIO_PCI_STATUS));
    }

    void virtio_device::set_dev_status(u32 status)
    {
        virtio_conf_writel(VIRTIO_PCI_STATUS, status);
    }

    void virtio_device::add_dev_status(u32 status)
    {
        set_dev_status(get_dev_status() | status);
    }

    void virtio_device::del_dev_status(u32 status)
    {
        set_dev_status(get_dev_status() & ~status);
    }

    bool virtio_device::get_virtio_config_bit(u32 offset, int bit)
    {
        return (virtio_conf_readl(offset) & (1 << bit));
    }

    void virtio_device::set_virtio_config_bit(u32 offset, int bit, bool on)
    {
        u32 val = virtio_conf_readl(offset);
        u32 newval = ( val & ~(1 << bit) ) | ((int)(on)<<bit);
        virtio_conf_writel(offset, newval);
    }

    void virtio_device::virtio_conf_write(u32 offset, void* buf, int length)
    {
        u8* ptr = reinterpret_cast<u8*>(buf);
        for (int i=0;i<length;i++)
            _bar1->write(offset+i, ptr[i]);
    }

    void virtio_device::virtio_conf_read(u32 offset, void* buf, int length)
    {
        unsigned char* ptr = reinterpret_cast<unsigned char*>(buf);
        for (int i=0;i<length;i++)
            ptr[i] = _bar1->readb(offset+i);
    }
}