Skip to content

Commit

Permalink
WIP virtio-pci
Browse files Browse the repository at this point in the history
  • Loading branch information
travisg committed May 10, 2024
1 parent 2f98fbf commit 79ab47c
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 19 deletions.
1 change: 0 additions & 1 deletion arch/x86/include/arch/arch_ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,4 @@ static inline uint arch_curr_cpu_num(void) {
#define smp_rmb() CF
#endif


#endif // !ASSEMBLY
12 changes: 12 additions & 0 deletions dev/bus/pci/bus_mgr/bus_mgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,18 @@ status_t pci_bus_mgr_allocate_irq(const pci_location_t loc, uint *irqbase) {
return d->allocate_irq(irqbase);
}

ssize_t pci_read_vendor_capability(const pci_location_t loc, size_t index, void *buf, size_t buflen) {
char str[14];
LTRACEF("%s\n", pci_loc_string(loc, str));

device *d = lookup_device_by_loc(loc);
if (!d) {
return ERR_NOT_FOUND;
}

return d->read_vendor_capability(index, buf, buflen);
}

void pci_dump_bar(const pci_bar_t *bar, int index) {
if (bar->addr >= UINT32_MAX || bar->size >= UINT32_MAX) {
printf("BAR %d: addr %-#16llx size %-#16zx io %d 64b %d pref %d\n",
Expand Down
29 changes: 29 additions & 0 deletions dev/bus/pci/bus_mgr/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ void device::dump(size_t indent) {
pci_dump_bar(bars_ + b, b);
}
}

capability *cap;
list_for_every_entry(&capability_list_, cap, capability, node) {
for (size_t i = 0; i < indent + 2; i++) {
printf(" ");
}
printf("capability: offset %#x id %#x\n", cap->config_offset, cap->id);
}
}

status_t device::enable() {
Expand Down Expand Up @@ -220,6 +228,27 @@ status_t device::probe_capabilities() {
return NO_ERROR;
}

ssize_t device::read_vendor_capability(size_t index, void *buf, size_t buflen) {
const capability *cap;
list_for_every_entry(&capability_list_, cap, capability, node) {
if (cap->id == 0x9) { // vendor specific
if (index == 0) {
uint8_t len;
pci_read_config_byte(loc(), cap->config_offset + 2, &len);

const size_t readlen = MIN(len, buflen);
for (size_t i = 0; i < readlen; i++) {
pci_read_config_byte(loc(), cap->config_offset + i, static_cast<uint8_t *>(buf) + i);
}
return len;
}
index--;
}
}

return ERR_NOT_FOUND;
}

status_t device::init_msi_capability(capability *cap) {
LTRACE_ENTRY;

Expand Down
1 change: 1 addition & 0 deletions dev/bus/pci/bus_mgr/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class device {
uint8_t header_type() const { return config_.header_type & PCI_HEADER_TYPE_MASK; }

status_t read_bars(pci_bar_t bar[6]);
ssize_t read_vendor_capability(size_t index, void *buf, size_t buflen);

bool has_msi() const { return msi_cap_; }
bool has_msix() const { return msix_cap_; }
Expand Down
4 changes: 3 additions & 1 deletion dev/bus/pci/drivers/rules.mk
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Fake module that just declares deps on all the PCI drivers in the system.
#
MODULES += dev/bus/pci

MODULES += dev/net/e1000
MODULES += dev/virtio/block
MODULES += dev/virtio/net
MODULES += dev/virtio/gpu
3 changes: 3 additions & 0 deletions dev/bus/pci/include/dev/bus/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ status_t pci_bus_mgr_allocate_msi(const pci_location_t loc, size_t num_requested
// allocate a regular irq for this device and return it in irqbase
status_t pci_bus_mgr_allocate_irq(const pci_location_t loc, uint *irqbase);

// XXX sort this nicely
ssize_t pci_read_vendor_capability(const pci_location_t loc, size_t index, void *buf, size_t buflen);

// return a pointer to a formatted string
const char *pci_loc_string(pci_location_t loc, char out_str[14]);

Expand Down
4 changes: 2 additions & 2 deletions dev/virtio/rules.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ LOCAL_DIR := $(GET_LOCAL_DIR)

MODULE := $(LOCAL_DIR)

MODULE_SRCS += \
$(LOCAL_DIR)/virtio.c
MODULE_SRCS += $(LOCAL_DIR)/virtio.c
MODULE_SRCS += $(LOCAL_DIR)/virtio_pci.c

include make/module.mk
276 changes: 276 additions & 0 deletions dev/virtio/virtio_pci.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/*
* Copyright (c) 2024 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#if WITH_DEV_BUS_PCI

#include <stdlib.h>
#include <dev/virtio.h>
#include <lk/init.h>
#include <lk/err.h>
#include <lk/trace.h>
#include <lk/list.h>
#if WITH_KERNEL_VM
#include <kernel/vm.h>
#endif

#define LOCAL_TRACE 1

#include <dev/bus/pci.h>

struct virtio_devices {
uint16_t device_id;
bool legacy;
status_t (*init)(pci_location_t loc, const struct virtio_devices *dev, size_t ordinal);
};

static status_t init_block(pci_location_t loc, const struct virtio_devices *dev, size_t ordinal);

const struct virtio_devices devices[] = {
{ 0x1000, true, NULL }, // transitional network
{ 0x1001, true, &init_block }, // transitional block
{ 0x1009, true, NULL }, // legacy virtio 9p
{ 0x1041, false, NULL }, // non-transitional network
{ 0x1042, false, &init_block }, // non-transitional block
{ 0x1043, false, NULL }, // non-transitional console
{ 0x1050, false, NULL }, // non-transitional gpu
{ 0x1052, false, NULL }, // non-transitional input
};

struct virtio_pci_cap {
uint8_t cap_vndr;
uint8_t cap_next;
uint8_t cap_len;
/* Common configuration */
#define VIRTIO_PCI_CAP_COMMON_CFG 1
/* Notifications */
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
/* ISR Status */
#define VIRTIO_PCI_CAP_ISR_CFG 3
/* Device specific configuration */
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
/* PCI configuration access */
#define VIRTIO_PCI_CAP_PCI_CFG 5
/* Shared memory region */
#define VIRTIO_PCI_CAP_SHARED_MEMORY_CFG 8
/* Vendor-specific data */
#define VIRTIO_PCI_CAP_VENDOR_CFG 9
uint8_t cfg_type;
uint8_t bar;
uint8_t id;
uint8_t padding[2];
uint32_t offset;
uint32_t length;
};

STATIC_ASSERT(sizeof(struct virtio_pci_cap) == 16);

static void dump_pci_cap(const struct virtio_pci_cap *cap) {
const char *type;

switch (cap->cfg_type) {
case VIRTIO_PCI_CAP_COMMON_CFG: type = "common"; break;
case VIRTIO_PCI_CAP_NOTIFY_CFG: type = "notify"; break;
case VIRTIO_PCI_CAP_ISR_CFG: type = "isr"; break;
case VIRTIO_PCI_CAP_DEVICE_CFG: type = "device"; break;
case VIRTIO_PCI_CAP_PCI_CFG: type = "pci"; break;
case VIRTIO_PCI_CAP_SHARED_MEMORY_CFG: type = "shared mem"; break;
case VIRTIO_PCI_CAP_VENDOR_CFG: type = "vendor"; break;
default: type = "unknown"; break;
}

printf("PCI capability: vendor %#hhx next %#hhx len %#hhx type %#hhx (%s) bar %#hhx id %#hhx offset %#x length %#x\n",
cap->cap_vndr, cap->cap_next, cap->cap_len, cap->cfg_type, type, cap->bar, cap->id, cap->offset, cap->length);
}

struct virtio_pci_common_cfg {
/* About the whole device. */
uint32_t device_feature_select; /* read-write */
uint32_t device_feature; /* read-only for driver */
uint32_t driver_feature_select; /* read-write */
uint32_t driver_feature; /* read-write */
uint16_t config_msix_vector; /* read-write */
uint16_t num_queues; /* read-only for driver */
uint8_t device_status; /* read-write */
uint8_t config_generation; /* read-only for driver */

/* About a specific virtqueue. */
uint16_t queue_select; /* read-write */
uint16_t queue_size; /* read-write */
uint16_t queue_msix_vector; /* read-write */
uint16_t queue_enable; /* read-write */
uint16_t queue_notify_off; /* read-only for driver */
uint64_t queue_desc; /* read-write */
uint64_t queue_driver; /* read-write */
uint64_t queue_device; /* read-write */
uint16_t queue_notif_config_data; /* read-only for driver */
uint16_t queue_reset; /* read-write */

/* About the administration virtqueue. */
uint16_t admin_queue_index; /* read-only for driver */
uint16_t admin_queue_num; /* read-only for driver */
};

STATIC_ASSERT(sizeof(struct virtio_pci_common_cfg) == 64);

static void dump_pci_common_config(const volatile struct virtio_pci_common_cfg *cfg) {
printf("PCI common config:\n");
printf("\tdevice feature select %#x features %#x\n", cfg->device_feature_select, cfg->device_feature);
printf("\tdriver feature select %#x features %#x\n", cfg->driver_feature_select, cfg->driver_feature);
printf("\tmsix vector %#x num queues %#x status %#x config gen %#x\n", cfg->config_msix_vector, cfg->num_queues,
cfg->device_status, cfg->config_generation);


}

struct config_pointer {
bool valid;
int bar;
size_t offset;
size_t length;
};

struct mapped_bars {
bool mapped;
void *vaddr;
};

struct virtio_pci_device {
pci_location_t loc;

struct mapped_bars bar_map[6];

struct config_pointer common_cfg;
struct config_pointer notify_cfg;
struct config_pointer isr_cfg;
struct config_pointer device_cfg;
struct config_pointer pci_cfg;
};

static status_t init_block(pci_location_t loc, const struct virtio_devices *dev, size_t index) {
LTRACE_ENTRY;

struct virtio_pci_device *pdev = calloc(1, sizeof(struct virtio_pci_device));
pdev->loc = loc;

// read all of the capabilities for this virtio device
bool map_bars[6] = {};
for (size_t i = 0;; i++) {
struct virtio_pci_cap cap;
ssize_t err = pci_read_vendor_capability(loc, i, &cap, sizeof(cap));
if (err < NO_ERROR || (size_t)err < sizeof(cap)) {
break;
}
if (LOCAL_TRACE) dump_pci_cap(&cap);

// save off the bar + range of all of the capabilities we care about
struct config_pointer *cfg;
switch (cap.cfg_type) {
case VIRTIO_PCI_CAP_COMMON_CFG:
cfg = &pdev->common_cfg;
goto common;
case VIRTIO_PCI_CAP_NOTIFY_CFG:
cfg = &pdev->notify_cfg;
goto common;
case VIRTIO_PCI_CAP_ISR_CFG:
cfg = &pdev->isr_cfg;
goto common;
case VIRTIO_PCI_CAP_DEVICE_CFG:
cfg = &pdev->device_cfg;
goto common;
case VIRTIO_PCI_CAP_PCI_CFG:
cfg = &pdev->pci_cfg;
// fallthrough
common:
DEBUG_ASSERT(cfg);
cfg->valid = true;
cfg->bar = cap.bar;
cfg->offset = cap.offset;
cfg->length = cap.length;
if (cap.bar < 6) {
map_bars[cap.bar] = true;
}
}
}

// check that at least the mandatory capabilities are present
if (!pdev->common_cfg.valid) {
return ERR_NOT_FOUND;
}

// map in the bars we care about
pci_bar_t bars[6];
status_t err = pci_bus_mgr_read_bars(loc, bars);
if (err != NO_ERROR) {
return err;
}

LTRACEF("virtio-pci BARS:\n");
if (LOCAL_TRACE) pci_dump_bars(bars, 6);

for (int i = 0; i < 6; i++) {
if (map_bars[i] && !bars[i].io) {
#if WITH_KERNEL_VM
char str[32];
snprintf(str, sizeof(str), "virtio%zu bar%d", index, i);
err = vmm_alloc_physical(vmm_get_kernel_aspace(), str, bars[i].size, &pdev->bar_map[i].vaddr, 0,
bars[i].addr, /* vmm_flags */ 0, ARCH_MMU_FLAG_UNCACHED_DEVICE);
if (err != NO_ERROR) {
printf("error mapping bar %d\n", i);
continue;
}
pdev->bar_map[i].mapped = true;
printf("bar %d mapped at %p\n", i, pdev->bar_map[i].vaddr);
#else
// no need to map, it's already available at the physical address
if (sizeof(void *) < 8 && (bars[i].addr + bars[i].size) > UINT32_MAX) {
TRACEF("aborting due to 64bit BAR on 32bit arch\n");
return ERR_NO_MEMORY;
}
pdev->bar_map[i].vaddr = (void *)(uintptr_t)bars[i].addr;
#endif
}
}

// enable the device
pci_bus_mgr_enable_device(loc);

// look at the common configuration
volatile struct virtio_pci_common_cfg *ccfg;
ccfg = pdev->bar_map[pdev->common_cfg.bar].vaddr + pdev->common_cfg.offset;
if (LOCAL_TRACE) dump_pci_common_config(ccfg);

return NO_ERROR;
}

static void virtio_pci_init(uint level) {
LTRACE_ENTRY;

for (size_t j = 0; j < countof(devices); j++) {
for (size_t i = 0; ; i++) {
pci_location_t loc;
status_t err = pci_bus_mgr_find_device(&loc, devices[j].device_id, 0x1af4, i);
if (err != NO_ERROR) {
break;
}

char str[14];
LTRACEF("virtio-pci: looking at device at %s\n", pci_loc_string(loc, str));

// call the init routine
if (devices[j].init) {
devices[j].init(loc, &devices[j], i);
}
}
}

LTRACE_EXIT;
}

LK_INIT_HOOK(virtio_pci, &virtio_pci_init, LK_INIT_LEVEL_PLATFORM + 1);

#endif

Loading

0 comments on commit 79ab47c

Please sign in to comment.