kernel: virtio

This commit is contained in:
Matheus
2023-12-31 15:48:01 -03:00
parent abb39478df
commit d49ca437ed
3 changed files with 263 additions and 0 deletions
+2
View File
@@ -31,6 +31,7 @@
#include <kernel/ext2.h>
#include <kernel/cmdline.h>
#include <arch/smp.h>
#include <kernel/virtio.h>
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);
+108
View File
@@ -0,0 +1,108 @@
#ifndef _VIRTIO_H
#define _VIRTIO_H
#include <stddef.h>
#include <stdint.h>
#include <kernel/pci.h>
#include <kernel/alloc.h>
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
+153
View File
@@ -0,0 +1,153 @@
#include <kernel/virtio.h>
#include <kernel/pci.h>
#include <logging.h>
#include <kernel/alloc.h>
#include <kernel/pmm.h>
#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);
}
}
}