From d49ca437ed2165ea72751c3b6625774128978f70 Mon Sep 17 00:00:00 2001 From: Matheus Date: Sun, 31 Dec 2023 15:48:01 -0300 Subject: [PATCH] kernel: virtio --- kernel-src/arch/x86-64/main.c | 2 + kernel-src/include/kernel/virtio.h | 108 ++++++++++++++++++++ kernel-src/io/virtio.c | 153 +++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 kernel-src/include/kernel/virtio.h create mode 100644 kernel-src/io/virtio.c diff --git a/kernel-src/arch/x86-64/main.c b/kernel-src/arch/x86-64/main.c index 3b368643..36aedb74 100644 --- a/kernel-src/arch/x86-64/main.c +++ b/kernel-src/arch/x86-64/main.c @@ -31,6 +31,7 @@ #include #include #include +#include static cpu_t bsp_cpu; @@ -72,6 +73,7 @@ void kernel_entry() { arch_ps2_init(); fb_init(); nvme_init(); + virtio_init(); console_init(); logging_sethook(console_putc); diff --git a/kernel-src/include/kernel/virtio.h b/kernel-src/include/kernel/virtio.h new file mode 100644 index 00000000..d5ba638c --- /dev/null +++ b/kernel-src/include/kernel/virtio.h @@ -0,0 +1,108 @@ +#ifndef _VIRTIO_H +#define _VIRTIO_H + +#include +#include + +#include +#include + +typedef struct { + uint32_t devicefeatureselect; + uint32_t devicefeature; + uint32_t driverfeatureselect; + uint32_t driverfeature; + uint16_t msixvector; + uint16_t queuecount; + uint8_t status; + uint8_t gen; + uint16_t queueselect; + uint16_t queuesize; + uint16_t queuemsixvector; + uint16_t queueenable; + uint16_t queuenotifyoffset; + uint64_t queuedesc; + uint64_t queuedriver; + uint64_t queuedevice; + uint16_t queuenotifydata; + uint16_t queuereset; +} __attribute__((packed)) viocommonconfig_t; + +typedef struct { + pcienum_t *e; + volatile viocommonconfig_t *config; + volatile void *devconfig; + volatile uint16_t *notify; + size_t notifymultiplier; +} viodevice_t; + +void virtio_init(); +size_t virtio_queuesize(viodevice_t *viodevice, int queue); +uint16_t *virtio_queuenotifyaddress(viodevice_t *viodevice, int queue); +void virtio_enablequeue(viodevice_t *viodevice, int queue); +void *virtio_createqueue(viodevice_t *viodevice, int queue, size_t size, int msix); +void virtio_enabledevice(viodevice_t *viodevice); + +#define VIO_CONFIG_STATUS_SET(x, v) (x)->config->status |= v +#define VIO_CONFIG_STATUS_ACK 1 +#define VIO_CONFIG_STATUS_DRIVER 2 +#define VIO_CONFIG_STATUS_DRIVEROK 4 +#define VIO_CONFIG_STATUS_FEATURESOK 8 +#define VIO_CONFIG_STATUS_NEEDSRESET 64 +#define VIO_CONFIG_STATUS_FAILED 128 + +#define VIO_QUEUE_BUFFER_NEXT 1 +#define VIO_QUEUE_BUFFER_DEVICE 2 + +typedef struct { + uint64_t address; + uint32_t length; + uint16_t flags; + uint16_t next; +} __attribute__((packed)) viobuffer_t; + +typedef struct { + uint32_t index; + uint32_t length; +} __attribute__((packed)) viousedentry_t; + +/* +struct { + // 16 byte alignment + struct { + uint64_t address; + uint32_t length; + uint16_t flags; + uint16_t next; + } __attribute__((packed)) buffer[size]; + // 2 byte alignment + struct { + uint16_t flags; + uint16_t index; + uint16_t ring[size]; + uint16_t evidx; + } __attribute__((packed)) driver; + uint16_t padding[(size % 2) + 1]; + // 4 byte alignment + struct { + uint16_t flags; + uint16_t index; + struct { + uint32_t index; + uint32_t length; + } __attribute__((packed)) usedring[size]; + uint16_t avail; + } __attribute__((packed)) device; +} __attribute__((packed)) +*/ + +#define VIO_QUEUE_BYTESIZE(s) (sizeof(viobuffer_t) * (s) + sizeof(uint16_t) * ((s) + 6) + sizeof(viousedentry_t) * (s)) +#define VIO_QUEUE_BUFFERS(q, s) ((volatile viobuffer_t *)(q)) +#define VIO_QUEUE_DRV(q, s) ((volatile uint16_t *)((uintptr_t)(q) + (s) * sizeof(viobuffer_t))) +#define VIO_QUEUE_DRV_IDX(q, s) ((volatile uint16_t *)((uintptr_t)(q) + (s) * sizeof(viobuffer_t)))[1] +#define VIO_QUEUE_DRV_RING(q, s) ((volatile uint16_t *)((uintptr_t)(q) + (s) * sizeof(viobuffer_t) + 2 * sizeof(uint16_t))) +#define VIO_QUEUE_DEV(q, s) ((volatile uint16_t *)((uintptr_t)(q) + (s) * sizeof(viobuffer_t) + sizeof(uint16_t) * ((s) + 3 + (1 - ((s) % 2))))) +#define VIO_QUEUE_DEV_IDX(q, s) ((volatile uint16_t *)((uintptr_t)(q) + (s) * sizeof(viobuffer_t) + sizeof(uint16_t) * ((s) + 3 + (1 - ((s) % 2)))))[1] +#define VIO_QUEUE_DEV_RING(q, s) ((volatile viousedentry_t *)((uintptr_t)(q) + (s) * sizeof(viobuffer_t) + sizeof(uint16_t) * ((s) + 5 + (1 - ((s) % 2))))) + +#endif diff --git a/kernel-src/io/virtio.c b/kernel-src/io/virtio.c new file mode 100644 index 00000000..89bdbed1 --- /dev/null +++ b/kernel-src/io/virtio.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include + +#define VIRTIO_VENDOR 0x1af4 +#define VIRTIO_DEVICE_MIN 0x1000 +#define VIRTIO_DEVICE_MAX 0x103f + +// TODO fatal + +static char *devname[] = { + "network", + "block" +}; + +void vioblk_init(); +int vioblk_newdevice(viodevice_t *); + +static int (*drivedevice[]) (viodevice_t *) = { + NULL, + vioblk_newdevice +}; + +size_t virtio_queuesize(viodevice_t *viodevice, int queue) { + viodevice->config->queueselect = queue; + return viodevice->config->queuesize; +} + +void virtio_enablequeue(viodevice_t *viodevice, int queue) { + viodevice->config->queueselect = queue; + viodevice->config->queueenable = 1; +} + +uint16_t *virtio_queuenotifyaddress(viodevice_t *viodevice, int queue) { + viodevice->config->queueselect = queue; + return (uint16_t *)((uintptr_t)viodevice->notify + viodevice->config->queuenotifyoffset * viodevice->notifymultiplier); +} + +void *virtio_createqueue(viodevice_t *viodevice, int queue, size_t size, int msix) { + size_t bytesize = VIO_QUEUE_BYTESIZE(size); + size_t pagesize = ROUND_UP(bytesize, PAGE_SIZE) / PAGE_SIZE; + + void *queuephys = pmm_alloc(pagesize, PMM_SECTION_DEFAULT); + __assert(queuephys); + memset(MAKE_HHDM(queuephys), 0, bytesize); + + viodevice->config->queueselect = queue; + viodevice->config->queuesize = size; + viodevice->config->queuemsixvector = msix; + __assert(viodevice->config->queuemsixvector == msix); + viodevice->config->queuedesc = (uint64_t)VIO_QUEUE_BUFFERS(queuephys, size); + viodevice->config->queuedriver = (uint64_t)VIO_QUEUE_DRV(queuephys, size); + viodevice->config->queuedevice = (uint64_t)VIO_QUEUE_DEV(queuephys, size); + + return queuephys; +} + +void virtio_enabledevice(viodevice_t *viodevice) { + VIO_CONFIG_STATUS_SET(viodevice, VIO_CONFIG_STATUS_DRIVEROK); +} + +#define CAP_TYPE 3 +#define CAP_BAR 4 +#define CAP_BAROFFSET 8 +#define CAP_NOTIFICATION_MULTIPLIER 16 + +#define CAP_TYPE_COMMONCONFIG 1 +#define CAP_TYPE_NOTIFICATION 2 +#define CAP_TYPE_DEVICECONFIG 4 + +static void virtio_newdevice(pcienum_t *e) { + pci_setcommand(e, PCI_COMMAND_MMIO, 1); + pci_setcommand(e, PCI_COMMAND_IO, 0); + pci_setcommand(e, PCI_COMMAND_IRQDISABLE, 1); + + int devtype = e->deviceid - VIRTIO_DEVICE_MIN; + + viodevice_t *viodevice = alloc(sizeof(viodevice_t)); + __assert(viodevice); + + viodevice->e = e; + + // get bars of important structures + int i = 0; + for (;;) { + int off = pci_getcapoffset(e, PCI_CAP_VENDORSPECIFIC, i++); + if (off == 0) + break; + + uint8_t type = PCI_READ8(e, off + CAP_TYPE); + uint8_t barnum = PCI_READ8(e, off + CAP_BAR); + uint32_t baroffset = PCI_READ32(e, off + CAP_BAROFFSET); + pcibar_t bar = pci_getbar(e, barnum); + + switch (type) { + case CAP_TYPE_COMMONCONFIG: + __assert(bar.mmio); + viodevice->config = (volatile viocommonconfig_t *)(bar.address + baroffset); + break; + case CAP_TYPE_NOTIFICATION: + __assert(bar.mmio); + viodevice->notify = (volatile uint16_t *)(bar.address + baroffset); + viodevice->notifymultiplier = PCI_READ32(e, off + CAP_NOTIFICATION_MULTIPLIER); + break; + case CAP_TYPE_DEVICECONFIG: + __assert(bar.mmio); + viodevice->devconfig = (volatile void *)(bar.address + baroffset); + break; + } + } + + __assert(viodevice->config); + __assert(viodevice->devconfig); + // reset device + viodevice->config->status = 0; + + // set acknowledge bit in status + VIO_CONFIG_STATUS_SET(viodevice, VIO_CONFIG_STATUS_ACK); + + // set driver bit if there is a driver for it, otherwise bail out + if (&devname[devtype] >= (char **)((uintptr_t)devname + sizeof(devname)) || devname[devtype] == NULL) { + printf("virtio: unknown device at %02d:%02d.%d\n", e->bus, e->device, e->function); + VIO_CONFIG_STATUS_SET(viodevice, VIO_CONFIG_STATUS_FAILED); + free(viodevice); + return; + } + + printf("virtio: found %s device at %02d:%02d.%d\n", devname[devtype], e->bus, e->device, e->function); + VIO_CONFIG_STATUS_SET(viodevice, VIO_CONFIG_STATUS_DRIVER); + + // pass control to device specific driver + if (drivedevice[devtype](viodevice)) { + printf("virtio: failed to start device\n"); + free(viodevice); + return; + } +} + +void virtio_init() { + vioblk_init(); + + for (int dev = VIRTIO_DEVICE_MIN; dev <= VIRTIO_DEVICE_MAX; ++dev) { + int i = 0; + for (;;) { + pcienum_t *e = pci_getenum(-1, -1, -1, VIRTIO_VENDOR, dev, -1, i++); + if (e == NULL) + break; + virtio_newdevice(e); + } + } +}