diff --git a/arch/x86/include/arch/arch_ops.h b/arch/x86/include/arch/arch_ops.h index b3092d602..0f10feb35 100644 --- a/arch/x86/include/arch/arch_ops.h +++ b/arch/x86/include/arch/arch_ops.h @@ -91,5 +91,4 @@ static inline uint arch_curr_cpu_num(void) { #define smp_rmb() CF #endif - #endif // !ASSEMBLY diff --git a/dev/bus/pci/bus_mgr/bus_mgr.cpp b/dev/bus/pci/bus_mgr/bus_mgr.cpp index 9c9756afe..75e46d823 100644 --- a/dev/bus/pci/bus_mgr/bus_mgr.cpp +++ b/dev/bus/pci/bus_mgr/bus_mgr.cpp @@ -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", diff --git a/dev/bus/pci/bus_mgr/device.cpp b/dev/bus/pci/bus_mgr/device.cpp index ac071d634..5611472f7 100644 --- a/dev/bus/pci/bus_mgr/device.cpp +++ b/dev/bus/pci/bus_mgr/device.cpp @@ -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() { @@ -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(buf) + i); + } + return len; + } + index--; + } + } + + return ERR_NOT_FOUND; +} + status_t device::init_msi_capability(capability *cap) { LTRACE_ENTRY; diff --git a/dev/bus/pci/bus_mgr/device.h b/dev/bus/pci/bus_mgr/device.h index ae2b9f28c..df2a6d576 100644 --- a/dev/bus/pci/bus_mgr/device.h +++ b/dev/bus/pci/bus_mgr/device.h @@ -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_; } diff --git a/dev/bus/pci/drivers/rules.mk b/dev/bus/pci/drivers/rules.mk index 6a3ac16c5..ea86441d3 100644 --- a/dev/bus/pci/drivers/rules.mk +++ b/dev/bus/pci/drivers/rules.mk @@ -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 diff --git a/dev/bus/pci/include/dev/bus/pci.h b/dev/bus/pci/include/dev/bus/pci.h index 0368205e3..b99a05cea 100644 --- a/dev/bus/pci/include/dev/bus/pci.h +++ b/dev/bus/pci/include/dev/bus/pci.h @@ -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]); diff --git a/dev/virtio/rules.mk b/dev/virtio/rules.mk index 22c8c0b1b..0570ed040 100644 --- a/dev/virtio/rules.mk +++ b/dev/virtio/rules.mk @@ -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 diff --git a/dev/virtio/virtio_pci.c b/dev/virtio/virtio_pci.c new file mode 100644 index 000000000..5ad8e1200 --- /dev/null +++ b/dev/virtio/virtio_pci.c @@ -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 +#include +#include +#include +#include +#include +#if WITH_KERNEL_VM +#include +#endif + +#define LOCAL_TRACE 1 + +#include + +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 + diff --git a/scripts/do-qemuarm b/scripts/do-qemuarm index 70b753435..820f914b9 100755 --- a/scripts/do-qemuarm +++ b/scripts/do-qemuarm @@ -4,6 +4,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" function HELP { echo "help:" + echo "-p : override LK project" echo "-6 : 64bit arm" echo "-3 : cortex-m3 based platform" echo "-v : boot kernel at EL2" @@ -18,6 +19,7 @@ function HELP { echo "-t : a virtio tap network device" echo "-g : a virtio display" echo "-f : a virtio 9p device with a host shared directory" + echo "-P : PCI based virtio devices" echo echo "-h for help" echo "all arguments after -- are passed to qemu directly" @@ -36,12 +38,13 @@ DO_CMPCTMALLOC=0 DO_MINIHEAP=0 DO_V9P=0 DO_V9P_DIR="" +DO_PCI_VIRTIO=0 SMP=1 MEMSIZE=512 SUDO="" PROJECT="" -while getopts cd:ghm:Mnt36vp:s:f: FLAG; do +while getopts cd:ghm:Mnt36vp:Ps:f: FLAG; do case $FLAG in c) DO_CMPCTMALLOC=1;; d) DO_DISK=1; DISK_IMAGE=$OPTARG;; @@ -56,6 +59,7 @@ while getopts cd:ghm:Mnt36vp:s:f: FLAG; do m) MEMSIZE=$OPTARG;; s) SMP=$OPTARG;; p) PROJECT=$OPTARG;; + P) DO_PCI_VIRTIO=1;; h) HELP;; \?) echo unrecognized option @@ -94,14 +98,30 @@ fi ARGS=" -cpu $CPU -m $MEMSIZE -smp $SMP -machine $MACHINE -kernel build-${PROJECT}/lk.elf" +if (( $DO_PCI_VIRTIO )); then + VIRTIO_BLOCK_DEVICE="virtio-blk-pci" + VIRTIO_NET_DEVICE="virtio-net-pci" + VIRTIO_GPU_DEVICE="virtio-gpu-pci" + VIRTIO_KEYBOARD_DEVICE="virtio-keyboard-pci" + VIRTIO_MOUSE_DEVICE="virtio-mouse-pci" + VIRTIO_9P_DEVICE="virtio-9p-pci" +else + VIRTIO_BLOCK_DEVICE="virtio-blk-device" + VIRTIO_NET_DEVICE="virtio-net-device" + VIRTIO_GPU_DEVICE="virtio-gpu-device" + VIRTIO_KEYBOARD_DEVICE="virtio-keyboard-device" + VIRTIO_MOUSE_DEVICE="virtio-mouse-device" + VIRTIO_9P_DEVICE="virtio-9p-device" +fi + if (( $DO_DISK )); then ARGS+=" -drive if=none,file=${DISK_IMAGE},id=blk,format=raw" - ARGS+=" -device virtio-blk-device,drive=blk" + ARGS+=" -device ${VIRTIO_BLOCK_DEVICE},drive=blk" fi if (( $DO_NET )); then ARGS+=" -netdev user,id=vmnic,hostname=qemu " - ARGS+=" -device virtio-net-device,netdev=vmnic" + ARGS+=" -device ${VIRTIO_NET_DEVICE},netdev=vmnic" elif (( $DO_NET_TAP )); then # quick note to enable tap interface # IFNAME=qemu0 @@ -110,7 +130,7 @@ elif (( $DO_NET_TAP )); then # sudo ifconfig ${IFNAME} up # sudo ip link set ${IFNAME} master ${BRIDGE} ARGS+=" -netdev tap,id=vmnic,ifname=qemu0,script=no,downscript=no" - ARGS+=" -device virtio-net-device,netdev=vmnic" + ARGS+=" -device ${VIRTIO_NET_DEVICE},netdev=vmnic" #SUDO="sudo " else NO_NET_ARGS=" -net none" @@ -118,16 +138,16 @@ else fi if (( $DO_DISPLAY )); then - ARGS+=" -device virtio-gpu-device -serial stdio" - ARGS+=" -device virtio-keyboard-device" - ARGS+=" -device virtio-mouse-device" + ARGS+=" -device ${VIRTIO_GPU_DEVICE} -serial stdio" + ARGS+=" -device ${VIRTIO_KEYBOARD_DEVICE}" + ARGS+=" -device ${VIRTIO_MOUSE_DEVICE}" else ARGS+=" -nographic" fi if (( $DO_V9P )); then ARGS+=" -fsdev local,path=$DO_V9P_DIR,security_model=mapped,id=v9p0" - ARGS+=" -device virtio-9p-device,fsdev=v9p0,mount_tag=V9P0" + ARGS+=" -device ${VIRTIO_9P_DEVICE},fsdev=v9p0,mount_tag=V9P0" fi MAKE_VARS="" diff --git a/scripts/do-qemuriscv b/scripts/do-qemuriscv index 88d4ffecd..8bee3ef1f 100755 --- a/scripts/do-qemuriscv +++ b/scripts/do-qemuriscv @@ -19,6 +19,7 @@ function HELP { echo "-n : a virtio network device" echo "-t : a virtio tap network device" echo "-g : a virtio display" + echo "-P : PCI based virtio devices" echo echo "-h for help" echo "all arguments after -- are passed to qemu directly" @@ -36,12 +37,13 @@ DO_DISPLAY=0 DO_CMPCTMALLOC=0 DO_MINIHEAP=0 DO_SUPERVISOR=0 +DO_PCI_VIRTIO=0 SMP=1 MEMSIZE=512 SUDO="" PROJECT="" -while getopts cd:ghm:Mmnteu6p:s:S FLAG; do +while getopts cd:ghm:Mmnteu6p:Ps:S FLAG; do case $FLAG in c) DO_CMPCTMALLOC=1;; d) DO_DISK=1; DISK_IMAGE=$OPTARG;; @@ -56,6 +58,7 @@ while getopts cd:ghm:Mmnteu6p:s:S FLAG; do s) SMP=$OPTARG;; S) DO_SUPERVISOR=1;; p) PROJECT=$OPTARG;; + P) DO_PCI_VIRTIO=1;; h) HELP;; \?) echo unrecognized option @@ -106,6 +109,22 @@ if [[ -z "$PROJECT" ]]; then PROJECT=$_PROJECT fi +if (( $DO_PCI_VIRTIO )); then + VIRTIO_BLOCK_DEVICE="virtio-blk-pci" + VIRTIO_NET_DEVICE="virtio-net-pci" + VIRTIO_GPU_DEVICE="virtio-gpu-pci" + VIRTIO_KEYBOARD_DEVICE="virtio-keyboard-pci" + VIRTIO_MOUSE_DEVICE="virtio-mouse-pci" + VIRTIO_9P_DEVICE="virtio-9p-pci" +else + VIRTIO_BLOCK_DEVICE="virtio-blk-device" + VIRTIO_NET_DEVICE="virtio-net-device" + VIRTIO_GPU_DEVICE="virtio-gpu-device" + VIRTIO_KEYBOARD_DEVICE="virtio-keyboard-device" + VIRTIO_MOUSE_DEVICE="virtio-mouse-device" + VIRTIO_9P_DEVICE="virtio-9p-device" +fi + # construct a list of args based on previous variables ARGS=" -machine $MACHINE" #ARGS+=",dumpdtb=riscv.dtb" # uncheck this to get a binary dump of the device tree for this config @@ -124,11 +143,11 @@ if [[ ! -z "$BIOS" ]]; then fi if (( $DO_DISK )); then ARGS+=" -drive if=none,file=${DISK_IMAGE},id=blk,format=raw" - ARGS+=" -device virtio-blk-device,drive=blk" + ARGS+=" -device ${VIRTIO_BLOCK_DEVICE},drive=blk" fi if (( $DO_NET )); then ARGS+=" -netdev user,id=vmnic,hostname=qemu" - ARGS+=" -device virtio-net-device,netdev=vmnic" + ARGS+=" -device ${VIRTIO_NET_DEVICE},netdev=vmnic" fi if (( $DO_NET_TAP )); then # quick note to enable tap interface @@ -138,13 +157,13 @@ if (( $DO_NET_TAP )); then # sudo ifconfig ${IFNAME} up # sudo ip link set ${IFNAME} master ${BRIDGE} ARGS+=" -netdev tap,id=vmnic,ifname=qemu0,script=no,downscript=no" - ARGS+=" -device virtio-net-device,netdev=vmnic" + ARGS+=" -device ${VIRTIO_NET_DEVICE},netdev=vmnic" #SUDO="sudo " fi if (( $DO_DISPLAY )); then - ARGS+=" -device virtio-gpu-device -serial stdio" - ARGS+=" -device virtio-keyboard-device" - ARGS+=" -device virtio-mouse-device" + ARGS+=" -device ${VIRTIO_GPU_DEVICE} -serial stdio" + ARGS+=" -device ${VIRTIO_KEYBOARD_DEVICE}" + ARGS+=" -device ${VIRTIO_MOUSE_DEVICE}" else ARGS+=" -nographic" fi