mirror of
https://github.com/Mathewnd/Astral.git
synced 2026-10-29 13:48:21 +00:00
kernel: virtio
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user