From: Tulio A M Mendes Date: Sun, 15 Feb 2026 01:32:01 +0000 (-0300) Subject: feat: virtio-blk PCI legacy driver X-Git-Url: https://projects.tadryanom.me/docs/static/git-logo.png?a=commitdiff_plain;h=6fcdbce6a2589a5d906fdb3c038b4d91b323ac07;p=AdrOS.git feat: virtio-blk PCI legacy driver - Detects virtio-blk device (vendor 0x1AF4, device 0x1001) - Legacy PIO-based virtqueue with polling completion - Read/write sector-at-a-time via 3-descriptor chain - Registered as HAL_DRV_BLOCK priority 25 - 35/35 smoke tests pass, cppcheck clean --- diff --git a/include/virtio_blk.h b/include/virtio_blk.h new file mode 100644 index 0000000..19abfff --- /dev/null +++ b/include/virtio_blk.h @@ -0,0 +1,17 @@ +#ifndef VIRTIO_BLK_H +#define VIRTIO_BLK_H + +#include + +/* Virtio PCI legacy device IDs */ +#define VIRTIO_VENDOR_ID 0x1AF4 +#define VIRTIO_BLK_DEVICE_ID 0x1001 /* transitional virtio-blk */ + +int virtio_blk_init(void); +int virtio_blk_read(uint64_t sector, void* buf, uint32_t count); +int virtio_blk_write(uint64_t sector, const void* buf, uint32_t count); +uint64_t virtio_blk_capacity(void); + +void virtio_blk_driver_register(void); + +#endif diff --git a/src/drivers/virtio_blk.c b/src/drivers/virtio_blk.c new file mode 100644 index 0000000..fd15c98 --- /dev/null +++ b/src/drivers/virtio_blk.c @@ -0,0 +1,336 @@ +/* + * Virtio-blk PCI legacy driver. + * + * Implements a minimal virtio 0.9 (legacy) block device driver using + * a single virtqueue for both reads and writes. Uses PIO (port I/O) + * for device configuration and a polling completion model. + * + * References: + * - Virtual I/O Device (VIRTIO) Version 1.0, §2 (legacy interface) + * - QEMU virtio-blk-pci device + */ +#include "virtio_blk.h" +#include "pci.h" +#include "console.h" +#include "utils.h" +#include "spinlock.h" +#include "pmm.h" +#include "vmm.h" +#include "interrupts.h" +#include "hal/driver.h" + +#include + +#define KERNEL_VIRT_BASE 0xC0000000U +#define V2P(x) ((uintptr_t)(x) - KERNEL_VIRT_BASE) + +/* ---- Virtio PCI legacy register offsets (from BAR0, I/O space) ---- */ +#define VIRTIO_PCI_HOST_FEATURES 0x00 +#define VIRTIO_PCI_GUEST_FEATURES 0x04 +#define VIRTIO_PCI_QUEUE_PFN 0x08 +#define VIRTIO_PCI_QUEUE_SIZE 0x0C /* 16-bit */ +#define VIRTIO_PCI_QUEUE_SEL 0x0E /* 16-bit */ +#define VIRTIO_PCI_QUEUE_NOTIFY 0x10 /* 16-bit */ +#define VIRTIO_PCI_STATUS 0x12 /* 8-bit */ +#define VIRTIO_PCI_ISR 0x13 /* 8-bit */ +/* Device-specific config starts at offset 0x14 for legacy */ +#define VIRTIO_PCI_BLK_CAPACITY 0x14 /* 64-bit: capacity in 512-byte sectors */ + +/* Virtio device status bits */ +#define VIRTIO_STATUS_ACK 0x01 +#define VIRTIO_STATUS_DRIVER 0x02 +#define VIRTIO_STATUS_DRIVER_OK 0x04 +#define VIRTIO_STATUS_FAILED 0x80 + +/* Virtio descriptor flags */ +#define VRING_DESC_F_NEXT 1 +#define VRING_DESC_F_WRITE 2 + +/* Virtio-blk request types */ +#define VIRTIO_BLK_T_IN 0 /* read */ +#define VIRTIO_BLK_T_OUT 1 /* write */ + +/* ---- Vring structures ---- */ +struct vring_desc { + uint64_t addr; + uint32_t len; + uint16_t flags; + uint16_t next; +} __attribute__((packed)); + +struct vring_avail { + uint16_t flags; + uint16_t idx; + uint16_t ring[]; +} __attribute__((packed)); + +struct vring_used_elem { + uint32_t id; + uint32_t len; +} __attribute__((packed)); + +struct vring_used { + uint16_t flags; + uint16_t idx; + struct vring_used_elem ring[]; +} __attribute__((packed)); + +/* Virtio-blk request header */ +struct virtio_blk_req { + uint32_t type; + uint32_t reserved; + uint64_t sector; +} __attribute__((packed)); + +/* ---- Driver state ---- */ +static uint16_t vblk_iobase; +static uint16_t vblk_queue_size; +static uint64_t vblk_capacity_sectors; +static int vblk_ready; + +static struct vring_desc* vblk_desc; +static struct vring_avail* vblk_avail; +static struct vring_used* vblk_used; +static uint16_t vblk_last_used_idx; + +/* Statically allocated request header and status byte (DMA-accessible) */ +static struct virtio_blk_req vblk_req_hdr __attribute__((aligned(16))); +static uint8_t vblk_status_byte __attribute__((aligned(4))); + +static spinlock_t vblk_lock = {0}; + +/* ---- Port I/O helpers ---- */ +static inline void outl(uint16_t port, uint32_t val) { + __asm__ volatile("outl %0, %w1" :: "a"(val), "Nd"(port)); +} +static inline uint32_t inl(uint16_t port) { + uint32_t val; + __asm__ volatile("inl %w1, %0" : "=a"(val) : "Nd"(port)); + return val; +} +static inline void outw(uint16_t port, uint16_t val) { + __asm__ volatile("outw %0, %w1" :: "a"(val), "Nd"(port)); +} +static inline uint16_t inw(uint16_t port) { + uint16_t val; + __asm__ volatile("inw %w1, %0" : "=a"(val) : "Nd"(port)); + return val; +} +static inline void outb_port(uint16_t port, uint8_t val) { + __asm__ volatile("outb %0, %w1" :: "a"(val), "Nd"(port)); +} +static inline uint8_t inb_port(uint16_t port) { + uint8_t val; + __asm__ volatile("inb %w1, %0" : "=a"(val) : "Nd"(port)); + return val; +} + +/* ---- Vring size calculation (legacy) ---- */ +static uint32_t vring_size(uint32_t num) { + /* desc table + avail ring (aligned to page) + used ring */ + uint32_t s = num * (uint32_t)sizeof(struct vring_desc); + s += sizeof(uint16_t) * (3 + num); /* avail: flags + idx + ring[num] + used_event */ + s = (s + 4095U) & ~4095U; + s += sizeof(uint16_t) * 3 + num * (uint32_t)sizeof(struct vring_used_elem); + return s; +} + +/* ---- Init ---- */ +int virtio_blk_init(void) { + const struct pci_device* dev = pci_find_device(VIRTIO_VENDOR_ID, VIRTIO_BLK_DEVICE_ID); + if (!dev) { + kprintf("[VIRTIO-BLK] Device not found.\n"); + return -1; + } + + /* BAR0 should be I/O space for legacy virtio */ + if (!(dev->bar[0] & 1)) { + kprintf("[VIRTIO-BLK] BAR0 is MMIO, expected I/O.\n"); + return -1; + } + vblk_iobase = (uint16_t)(dev->bar[0] & 0xFFFCU); + + /* Enable PCI I/O space + bus mastering */ + uint32_t cmd = pci_config_read(dev->bus, dev->slot, dev->func, 0x04); + cmd |= (1U << 0) | (1U << 2); /* I/O Space | Bus Master */ + pci_config_write(dev->bus, dev->slot, dev->func, 0x04, cmd); + + /* Reset device */ + outb_port(vblk_iobase + VIRTIO_PCI_STATUS, 0); + + /* Acknowledge */ + outb_port(vblk_iobase + VIRTIO_PCI_STATUS, VIRTIO_STATUS_ACK); + outb_port(vblk_iobase + VIRTIO_PCI_STATUS, + VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER); + + /* Read host features, accept none for simplicity */ + (void)inl(vblk_iobase + VIRTIO_PCI_HOST_FEATURES); + outl(vblk_iobase + VIRTIO_PCI_GUEST_FEATURES, 0); + + /* Read capacity */ + uint32_t cap_lo = inl(vblk_iobase + VIRTIO_PCI_BLK_CAPACITY); + uint32_t cap_hi = inl(vblk_iobase + VIRTIO_PCI_BLK_CAPACITY + 4); + vblk_capacity_sectors = ((uint64_t)cap_hi << 32) | cap_lo; + + /* Select queue 0 */ + outw(vblk_iobase + VIRTIO_PCI_QUEUE_SEL, 0); + vblk_queue_size = inw(vblk_iobase + VIRTIO_PCI_QUEUE_SIZE); + if (vblk_queue_size == 0) { + kprintf("[VIRTIO-BLK] Queue size is 0.\n"); + outb_port(vblk_iobase + VIRTIO_PCI_STATUS, VIRTIO_STATUS_FAILED); + return -1; + } + + /* Allocate vring pages */ + uint32_t total = vring_size(vblk_queue_size); + uint32_t pages = (total + 4095U) / 4096U; + + /* Use a fixed VA range for virtio vring */ + #define VIRTIO_VRING_VA 0xC0340000U + for (uint32_t i = 0; i < pages; i++) { + void* frame = pmm_alloc_page(); + if (!frame) { + kprintf("[VIRTIO-BLK] Failed to alloc vring page.\n"); + outb_port(vblk_iobase + VIRTIO_PCI_STATUS, VIRTIO_STATUS_FAILED); + return -1; + } + vmm_map_page((uint64_t)(uintptr_t)frame, + (uint64_t)(VIRTIO_VRING_VA + i * 4096U), + VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE); + } + memset((void*)VIRTIO_VRING_VA, 0, pages * 4096U); + + /* Set up vring pointers */ + vblk_desc = (struct vring_desc*)VIRTIO_VRING_VA; + uint32_t avail_off = vblk_queue_size * (uint32_t)sizeof(struct vring_desc); + vblk_avail = (struct vring_avail*)(VIRTIO_VRING_VA + avail_off); + uint32_t used_off = avail_off + sizeof(uint16_t) * (3 + vblk_queue_size); + used_off = (used_off + 4095U) & ~4095U; + vblk_used = (struct vring_used*)(VIRTIO_VRING_VA + used_off); + vblk_last_used_idx = 0; + + /* Tell device where the vring lives (page-aligned physical address) */ + uint32_t vring_phys = (uint32_t)V2P(VIRTIO_VRING_VA); + outl(vblk_iobase + VIRTIO_PCI_QUEUE_PFN, vring_phys / 4096U); + + /* Mark driver ready */ + outb_port(vblk_iobase + VIRTIO_PCI_STATUS, + VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_DRIVER_OK); + + vblk_ready = 1; + kprintf("[VIRTIO-BLK] Initialized: %llu sectors (%llu MB), queue=%u, IO=0x%x\n", + (unsigned long long)vblk_capacity_sectors, + (unsigned long long)(vblk_capacity_sectors / 2048), + (unsigned)vblk_queue_size, + (unsigned)vblk_iobase); + + return 0; +} + +/* ---- Submit a single request and poll for completion ---- */ +static int vblk_do_request(uint32_t type, uint64_t sector, + void* buf, uint32_t bytes) { + if (!vblk_ready) return -1; + + uintptr_t fl = spin_lock_irqsave(&vblk_lock); + + /* Set up request header */ + vblk_req_hdr.type = type; + vblk_req_hdr.reserved = 0; + vblk_req_hdr.sector = sector; + vblk_status_byte = 0xFF; + + /* Descriptor 0: request header (device-readable) */ + vblk_desc[0].addr = (uint64_t)V2P((uintptr_t)&vblk_req_hdr); + vblk_desc[0].len = sizeof(vblk_req_hdr); + vblk_desc[0].flags = VRING_DESC_F_NEXT; + vblk_desc[0].next = 1; + + /* Descriptor 1: data buffer */ + vblk_desc[1].addr = (uint64_t)V2P((uintptr_t)buf); + vblk_desc[1].len = bytes; + vblk_desc[1].flags = VRING_DESC_F_NEXT; + if (type == VIRTIO_BLK_T_IN) { + vblk_desc[1].flags |= VRING_DESC_F_WRITE; /* device writes to buffer */ + } + vblk_desc[1].next = 2; + + /* Descriptor 2: status byte (device-writable) */ + vblk_desc[2].addr = (uint64_t)V2P((uintptr_t)&vblk_status_byte); + vblk_desc[2].len = 1; + vblk_desc[2].flags = VRING_DESC_F_WRITE; + vblk_desc[2].next = 0; + + /* Add to available ring */ + uint16_t avail_idx = vblk_avail->idx; + vblk_avail->ring[avail_idx % vblk_queue_size] = 0; /* head descriptor */ + __sync_synchronize(); + vblk_avail->idx = avail_idx + 1; + __sync_synchronize(); + + /* Notify device (queue 0) */ + outw(vblk_iobase + VIRTIO_PCI_QUEUE_NOTIFY, 0); + + /* Poll for completion */ + uint32_t spins = 0; + while (vblk_used->idx == vblk_last_used_idx) { + __sync_synchronize(); + if (++spins > 10000000U) { + spin_unlock_irqrestore(&vblk_lock, fl); + kprintf("[VIRTIO-BLK] Request timeout.\n"); + return -1; + } + } + vblk_last_used_idx++; + + /* Read ISR to clear interrupt */ + (void)inb_port(vblk_iobase + VIRTIO_PCI_ISR); + + int ret = (vblk_status_byte == 0) ? 0 : -1; + spin_unlock_irqrestore(&vblk_lock, fl); + return ret; +} + +int virtio_blk_read(uint64_t sector, void* buf, uint32_t count) { + if (!buf || count == 0) return -1; + for (uint32_t i = 0; i < count; i++) { + int rc = vblk_do_request(VIRTIO_BLK_T_IN, sector + i, + (uint8_t*)buf + i * 512, 512); + if (rc < 0) return rc; + } + return 0; +} + +int virtio_blk_write(uint64_t sector, const void* buf, uint32_t count) { + if (!buf || count == 0) return -1; + for (uint32_t i = 0; i < count; i++) { + int rc = vblk_do_request(VIRTIO_BLK_T_OUT, sector + i, + (void*)((uint8_t*)buf + i * 512), 512); + if (rc < 0) return rc; + } + return 0; +} + +uint64_t virtio_blk_capacity(void) { + return vblk_capacity_sectors; +} + +/* ---- HAL driver registration ---- */ +static int vblk_drv_probe(void) { + return pci_find_device(VIRTIO_VENDOR_ID, VIRTIO_BLK_DEVICE_ID) ? 0 : -1; +} + +static const struct hal_driver vblk_hal_driver = { + .name = "virtio-blk", + .type = HAL_DRV_BLOCK, + .priority = 25, + .ops = { + .probe = vblk_drv_probe, + .init = (int (*)(void))virtio_blk_init, + .shutdown = NULL, + }, +}; + +void virtio_blk_driver_register(void) { + hal_driver_register(&vblk_hal_driver); +} diff --git a/src/kernel/init.c b/src/kernel/init.c index 39dffb4..7f1de7d 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -212,6 +212,8 @@ int init_start(const struct boot_info* bi) { /* Register hardware drivers with HAL and init in priority order */ pci_driver_register(); /* priority 10: bus */ e1000_driver_register(); /* priority 20: NIC (probes PCI) */ + extern void virtio_blk_driver_register(void); + virtio_blk_driver_register(); /* priority 25: virtio-blk */ hal_drivers_init_all(); net_init();