Skip to content

Commit

Permalink
[dev][virtio][pci] PCI based virtio
Browse files Browse the repository at this point in the history
First pass at PCI based virtio.
Added a PCI based virtio-bus layer that abstracts the details from the
device layer.
Only wired up for virtio-blk at the moment.
IRQs are wired up in legacy mode (no MSI-X support in PCI yet).
  • Loading branch information
travisg committed May 29, 2024
1 parent c9e27b9 commit ad5f529
Show file tree
Hide file tree
Showing 22 changed files with 726 additions and 87 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
42 changes: 38 additions & 4 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 Expand Up @@ -256,16 +285,21 @@ status_t device::init_msix_capability(capability *cap) {
status_t device::allocate_irq(uint *irq) {
LTRACE_ENTRY;

uint8_t interrupt_line;
status_t err = pci_read_config_byte(loc(), PCI_CONFIG_INTERRUPT_LINE, &interrupt_line);
uint8_t interrupt_pin;
status_t err = pci_read_config_byte(loc(), PCI_CONFIG_INTERRUPT_PIN, &interrupt_pin);
if (err != NO_ERROR) return err;

if (interrupt_line == 0) {
if (interrupt_pin == 0) {
return ERR_NO_RESOURCES;
}

// map the irq number in config space to platform vector space
err = platform_pci_int_to_vector(interrupt_line, irq);
err = platform_pci_int_to_vector(interrupt_pin, irq);
if (err != NO_ERROR) return err;

// write it back to the pci config in the interrupt line offset
pci_write_config_byte(loc(), PCI_CONFIG_INTERRUPT_LINE, *irq);

return err;
}

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
5 changes: 3 additions & 2 deletions dev/bus/pci/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ static void pci_list(void) {

if (config.vendor_id != 0xffff) {
printf("%04x:%02x:%02x.%0x vendor_id=%04x device_id=%04x, header_type=%02x "
"base_class=%02x, sub_class=%02x, interface=%02x, irq=%u\n",
"base_class=%02x, sub_class=%02x, interface=%02x, irq_line=%u, irq_pin=%u\n",
state.segment, state.bus, state.dev, state.fn,
config.vendor_id, config.device_id, config.header_type, config.base_class,
config.sub_class, config.program_interface, config.type0.interrupt_line);
config.sub_class, config.program_interface, config.type0.interrupt_line,
config.type0.interrupt_pin);
devices++;
lines++;
}
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
1 change: 1 addition & 0 deletions dev/virtio/9p/virtio-9p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ status_t virtio_9p_init(virtio_device *dev, uint32_t host_features)

/* set our irq handler */
dev->set_irq_callbacks(&virtio_9p_irq_driver_callback, nullptr);
dev->bus()->unmask_interrupt();

/* set DRIVER_OK */
dev->bus()->virtio_status_driver_ok();
Expand Down
1 change: 1 addition & 0 deletions dev/virtio/block/virtio-block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ status_t virtio_block_init(virtio_device *dev, uint32_t host_features) {

/* set our irq handler */
dev->set_irq_callbacks(&virtio_block_irq_driver_callback, nullptr);
dev->bus()->unmask_interrupt();

/* set DRIVER_OK */
dev->bus()->virtio_status_driver_ok();
Expand Down
1 change: 1 addition & 0 deletions dev/virtio/gpu/virtio-gpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ status_t virtio_gpu_init(virtio_device *dev, uint32_t host_features) {

/* set our irq handler */
dev->set_irq_callbacks(&virtio_gpu_irq_driver_callback, &virtio_gpu_config_change_callback);
dev->bus()->unmask_interrupt();

/* set DRIVER_OK */
dev->bus()->virtio_status_driver_ok();
Expand Down
20 changes: 20 additions & 0 deletions dev/virtio/include/dev/virtio/virtio-bus.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#pragma once

#include <stdint.h>
#include <platform/interrupts.h>

class virtio_bus {
public:
Expand All @@ -21,4 +22,23 @@ class virtio_bus {
virtual void virtio_status_driver_ok() = 0;
virtual void virtio_kick(uint16_t ring_index) = 0;
virtual void register_ring(uint32_t page_size, uint32_t queue_sel, uint32_t queue_num, uint32_t queue_align, uint32_t queue_pfn) = 0;

uint64_t virtio_read_host_feature_word_64(uint32_t word) {
return virtio_read_host_feature_word(word) | static_cast<uint64_t>(virtio_read_host_feature_word(word + 1)) << 32;
}

// A simple set of routines to handle a single IRQ.
// TODO: rethink where this goes in a more complicated MSI based solution.
void set_irq(uint32_t irq) { irq_ = irq; }

void mask_interrupt() {
::mask_interrupt(irq_);
}

void unmask_interrupt() {
::unmask_interrupt(irq_);
}

private:
uint32_t irq_ {};
};
21 changes: 8 additions & 13 deletions dev/virtio/include/dev/virtio/virtio-device.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <dev/virtio.h>
#include <dev/virtio/virtio_ring.h>
#include <dev/virtio/virtio-bus.h>
#include <platform/interrupts.h>

class virtio_device {
public:
Expand Down Expand Up @@ -52,6 +53,7 @@ class virtio_device {

void *get_config_ptr() { return config_ptr_; }
const void *get_config_ptr() const { return config_ptr_; }
void set_config_ptr(void *ptr) { config_ptr_ = ptr; }

using irq_driver_callback = enum handler_return (*)(virtio_device *dev, uint ring, const vring_used_elem *e);
using config_change_callback = enum handler_return (*)(virtio_device *dev);
Expand All @@ -61,33 +63,26 @@ class virtio_device {
config_change_callback_ = config;
}

// From low level bus layer
// Interrupt handler callbacks from the bus layer, which is responsible
// for the first layer of IRQ handling
handler_return handle_queue_interrupt();
handler_return handle_config_interrupt();

// TODO: allow an aribitrary number of rings
static const size_t MAX_VIRTIO_RINGS = 4;

private:
friend class virtio_bus;

// XXX move this into constructor
friend int virtio_mmio_detect(void *ptr, uint count, const uint irqs[], size_t stride);

// mmio or pci
virtio_bus *bus_ = {};

// points into bus's configuration spot
// TODO: is this feasible for both PCI and mmio?
void *config_ptr_ = {};

void *priv_ = {}; /* a place for the driver to put private data */

bool valid_ = {};

uint index_ = {};
uint irq_ = {};
// a place for the driver to put private data, usually a pointer to device
// specific details.
void *priv_ = {};

// interrupt handlers that the device-specific layer registers with our layer
irq_driver_callback irq_driver_callback_ = {};
config_change_callback config_change_callback_ = {};

Expand Down
82 changes: 82 additions & 0 deletions dev/virtio/include/dev/virtio/virtio-pci-bus.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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
*/
#pragma once

#include <stdint.h>
#include <sys/types.h>

#include <dev/virtio/virtio-bus.h>

#include <dev/bus/pci.h>

struct virtio_pci_common_cfg;
class virtio_device;

class virtio_pci_bus final : public virtio_bus {
public:
virtio_pci_bus() = default;
~virtio_pci_bus() override = default;

status_t init(virtio_device *dev, pci_location_t loc, size_t index);

void virtio_reset_device() override;
void virtio_status_acknowledge_driver() override;
uint32_t virtio_read_host_feature_word(uint32_t word) override;
void virtio_set_guest_features(uint32_t word, uint32_t features) override;
void virtio_status_driver_ok() override;
void virtio_kick(uint16_t ring_index) override;
void register_ring(uint32_t page_size, uint32_t queue_sel, uint32_t queue_num, uint32_t queue_align, uint32_t queue_pfn) override;

volatile virtio_pci_common_cfg *common_config() {
return reinterpret_cast<volatile virtio_pci_common_cfg *>(config_ptr(common_cfg_));
}

void *device_config() {
return reinterpret_cast<void *>(config_ptr(device_cfg_));
}

private:
static handler_return virtio_pci_irq(void *arg);

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

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

virtio_device *dev_ = {};

pci_location_t loc_ = {};

mapped_bars bar_map_[6] = {};

config_pointer common_cfg_ = {};
config_pointer notify_cfg_ = {};
config_pointer isr_cfg_ = {};
config_pointer device_cfg_ = {};
config_pointer pci_cfg_ = {};

uint32_t notify_offset_multiplier_ = {};

// Given one of the config_pointer structs, return a uint8_t * pointer
// to its mapping.
uint8_t *config_ptr(const config_pointer &cfg) {
if (!cfg.valid) {
return nullptr;
}

auto &bar = bar_map_[cfg.bar];
return bar.vaddr + cfg.offset;
}
};
3 changes: 2 additions & 1 deletion dev/virtio/net/virtio-net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,12 @@ status_t virtio_net_init(virtio_device *dev) {
dev->bus()->virtio_status_acknowledge_driver();

// XXX check features bits and ack/nak them
uint64_t host_features = dev->bus()->virtio_read_host_feature_word(0) | (uint64_t)dev->bus()->virtio_read_host_feature_word(1) << 32;
uint64_t host_features = dev->bus()->virtio_read_host_feature_word_64(0);
dump_feature_bits(host_features);

/* set our irq handler */
dev->set_irq_callbacks(&virtio_net_irq_driver_callback, nullptr);
dev->bus()->unmask_interrupt();

/* set DRIVER_OK */
dev->bus()->virtio_status_driver_ok();
Expand Down
3 changes: 2 additions & 1 deletion dev/virtio/rules.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ LOCAL_DIR := $(GET_LOCAL_DIR)

MODULE := $(LOCAL_DIR)

MODULE_SRCS += $(LOCAL_DIR)/virtio.cpp
MODULE_SRCS += $(LOCAL_DIR)/virtio-bus.cpp
MODULE_SRCS += $(LOCAL_DIR)/virtio-device.cpp
MODULE_SRCS += $(LOCAL_DIR)/virtio-mmio-bus.cpp
MODULE_SRCS += $(LOCAL_DIR)/virtio-pci-bus.cpp
MODULE_SRCS += $(LOCAL_DIR)/virtio.cpp

include make/module.mk
1 change: 1 addition & 0 deletions dev/virtio/virtio-device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ handler_return virtio_device::handle_queue_interrupt() {

vring &ring = ring_[r];

LTRACEF("desc %p, avail %p, used %p\n", ring.desc, ring.avail, ring.used);
LTRACEF("ring %u: used flags 0x%hx idx 0x%hx last_used %u\n", r, ring.used->flags, ring.used->idx, ring.last_used);

uint cur_idx = ring.used->idx;
Expand Down
Loading

0 comments on commit ad5f529

Please sign in to comment.