virtio stuff, discovery of virtio devices over PCI

This commit is contained in:
uosfz 2026-05-22 22:13:20 +02:00
parent b52e397303
commit e48ef201d5
Signed by: uosfz
SSH key fingerprint: SHA256:FlktuluyhTQg3jHZNLKwxOOC5hbfrUXM0tz3IA3lGJo
9 changed files with 216 additions and 3 deletions

1
.gitignore vendored
View file

@ -2,6 +2,7 @@ config.mk
/tags
build/
drive.img
drive-virtio.img
/compile_commands.json
/opt
/.cache

View file

@ -63,6 +63,7 @@ KERNEL_SOURCES := \
src/font.c \
src/karlos-logo.c \
src/tlsf.c \
src/virtio-common.c \
$(KERNEL_SOURCES_$(ARCH)) \
# end of kernel sources list
@ -74,10 +75,13 @@ KERNEL_TARGET := build/boot/sys/core
.PHONY: all clean
all: build/disk.img drive.img
all: build/disk.img drive.img drive-virtio.img
drive.img:
truncate -s 16M drive.img
truncate -s 16M $@
drive-virtio.img:
truncate -s 16M $@
clean:
rm -rf build/*

View file

@ -13,6 +13,9 @@ QEMU_OPTS="$QEMU_OPTS -drive if=ide,format=raw,file=build/disk.img"
# SATA drive
QEMU_OPTS="$QEMU_OPTS -drive id=disk,format=raw,file=drive.img,if=none -device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0,serial=DEADBEEF"
# VIRTIO drive
QEMU_OPTS="$QEMU_OPTS -drive file=drive-virtio.img,format=raw,if=virtio"
# Multicore
QEMU_OPTS="$QEMU_OPTS -smp sockets=1,cores=2,threads=1"

54
include/virtio-block.h Normal file
View file

@ -0,0 +1,54 @@
#ifndef KARLOS_VIRTIO_BLOCK_H
#define KARLOS_VIRTIO_BLOCK_H
#include "virtio-common.h"
#define VIRTIO_BLK_F_SIZE_MAX 1
#define VIRTIO_BLK_F_SEG_MAX 2
#define VIRTIO_BLK_F_GEOMETRY 4
#define VIRTIO_BLK_F_BLK_SIZE 6
#define VIRTIO_BLK_F_FLUSH 9
#define VIRTIO_BLK_F_TOPOLOGY 10
#define VIRTIO_BLK_F_WRITE_ZEROES 14
struct virtio_blk_config {
le64 capacity;
le64 size_max;
le32 seg_max;
struct virtio_blk_geometry {
le16 cylinders;
uint8_t heads;
uint8_t sectors;
} geometry;
le32 blk_size;
struct virtio_blk_topology {
uint8_t physical_block_exp;
uint8_t alignment_offset;
le16 min_io_size;
le32 opt_io_size;
} topology;
uint8_t writeback;
uint8_t unused0;
uint16_t num_queues; // TODO uint16_t? Shouldn't this be le16?
le32 max_discard_sectors;
le32 max_discard_seg;
le32 discard_sector_alignment;
le32 max_write_zeroes_sectors;
le32 max_write_zeroes_seg;
uint8_t write_zeroes_may_unmap;
uint8_t unused1[3];
le32 max_secure_erase_sectors;
le32 max_secure_erase_seg;
le32 secure_erase_sector_alignment;
};
struct virtio_blk_req {
le32 type;
le32 reserved;
le64 sector;
uint8_t data[];
// uint8_t status;
};
#endif

100
include/virtio-common.h Normal file
View file

@ -0,0 +1,100 @@
#ifndef KARLOS_VIRTIO_COMMON_H
#define KARLOS_VIRTIO_COMMON_H
#include <stdint.h>
#include <stdbool.h>
#include "pci.h"
// struct so you don't accidentally access the value; use following macros to read/write
// TODO these are aligned, we have no way to specify unaligned ints currently
typedef struct le16 {
uint16_t value;
} le16;
typedef struct le32 {
uint32_t value;
} le32;
typedef struct le64 {
uint64_t value;
} le64;
#if !defined(__BYTE_ORDER__) || (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#define LE16_READ(v) TODO()
#define LE16_WRITE(v, new_value) TODO()
#define LE32_READ(v) TODO()
#define LE32_WRITE(v, new_value) TODO()
#define LE64_READ(v) TODO()
#define LE64_WRITE(v, new_value) TODO()
#else
#define LE16_READ(v) ((v).value)
#define LE16_WRITE(v, new_value) do { (v).value = (new_value); } while (0)
#define LE32_READ(v) ((v).value)
#define LE32_WRITE(v, new_value) do { (v).value = (new_value); } while (0)
#define LE64_READ(v) ((v).value)
#define LE64_WRITE(v, new_value) do { (v).value = (new_value); } while (0)
#endif
// OS acknowledged the device
#define VIRTIO_DEV_STATUS_ACKNOWLEDGE 1
// OS knows how to drive the device
#define VIRTIO_DEV_STATUS_DRIVER 2
// guest has given up on the device
#define VIRTIO_DEV_STATUS_FAILED 128
// feature negotiation complete
#define VIRTIO_DEV_STATUS_FEATURES_OK 8
// driver is set up
#define VIRTIO_DEV_STATUS_DRIVER_OK 4
// unrecoverable error
#define VIRTIO_DEV_STATUS_DEVICE_NEEDS_RESET 64
// buffer continues via `next` field
#define VIRTQ_DESC_F_NEXT 1
// buffer is device write-only (not set: device read-only)
#define VIRTQ_DESC_F_WRITE 2
// buffer contains list of buffer descriptors (ONLY AVAILABLE BY VIRTIO_F_INDIRECT_DESC; DO NOT SET THIS AND F_NEXT)
#define VIRTQ_DESC_F_INDIRECT 4
struct virtq_desc {
le64 addr;
le32 len;
le16 flags; // see macros above
le16 next;
};
#define VIRTQ_AVAIL_F_NO_INTERRUPT 1
struct virtq_avail {
le16 flags;
// where driver would put next descriptor entry, modulo queue size
le16 idx;
le16 ring[];
// le16 used_event; // only when VIRTIO_F_EVENT_IDX
};
struct virtq_used_elem {
// index of start of used descriptor chain
le32 id;
// number of bytes written into device writable portion
// useful to avoid data leaks (e.g. passing buffer directly to userspace)
le32 len;
};
#define VIRTQ_USED_F_NO_NOTIFY 1
struct virtq_used {
le16 flags;
le16 idx;
struct virtq_used_elem ring [];
// le16 avail_event; // only when VIRTIO_F_EVENT_IDX
};
// searching for devices
#define VIRTIO_VENDOR_ID 0x1af4
enum virtio_dev_kind {
VIRTIO_DEV_KIND_BLOCK,
// TODO
};
struct virtio_dev_iter {
struct pci_iter iter;
enum virtio_dev_kind kind;
};
void virtio_dev_iter_init(struct virtio_dev_iter *it, enum virtio_dev_kind kind);
bool virtio_dev_iter_next(struct virtio_dev_iter *it, uint16_t *bdf);
#endif

View file

@ -8,6 +8,9 @@ QEMU_OPTS="$QEMU_OPTS -drive if=ide,format=raw,file=build/disk.img"
# SATA drive
QEMU_OPTS="$QEMU_OPTS -drive id=disk,format=raw,file=drive.img,if=none -device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0,serial=DEADBEEF"
# VIRTIO drive
QEMU_OPTS="$QEMU_OPTS -drive file=drive-virtio.img,format=raw,if=virtio"
# Multicore
QEMU_OPTS="$QEMU_OPTS -smp sockets=1,cores=2,threads=1"

View file

@ -5,6 +5,7 @@
#include "app.h"
#include "bootboot.h"
#include "keyboard.h"
#include "pci.h"
#include "std.h"
#include "cpu.h"
#include "texture.h"
@ -16,6 +17,7 @@
#include "bootparam.h"
#include "framebuffer.h"
#include "virtio-common.h"
#include "x86_64/interrupt.h"
/* imported virtual addresses, see linker script */
@ -75,6 +77,14 @@ void main(void) {
// app_push(&snake_template, NULL, &RECT_XYWH(10, 10, 500, 500));
__asm__ ("sti");
struct virtio_dev_iter iter;
virtio_dev_iter_init(&iter, VIRTIO_DEV_KIND_BLOCK);
uint16_t bdf;
ASSERT(virtio_dev_iter_next(&iter, &bdf));
// search for exactly one device
ASSERT(!virtio_dev_iter_next(&iter, &bdf));
printlinef("found virtio block device with bdf %X8", bdf);
while (1) {
fb_refresh();
__asm__ ("hlt");

View file

@ -170,7 +170,7 @@ void pci_print_listing() {
uint8_t prog_if = (rev_class >> 8) & 0xFF;
uint8_t sub_class = (rev_class >> 16) & 0xFF;
uint8_t base_class = (rev_class >> 24) & 0xFF;
printlinef("PCI %x %x:%x %x:%x:%x\n", bdf,
printlinef("PCI %x %x:%x %x:%x:%x", bdf,
vendor_id, device_id, base_class, sub_class, prog_if);
}
}

View file

@ -1 +1,39 @@
#include "virtio-common.h"
#include "std.h"
static unsigned virtio_dev_kind_transitional_id(enum virtio_dev_kind kind) {
switch (kind) {
case VIRTIO_DEV_KIND_BLOCK: return 0x1001;
}
UNREACHABLE();
}
static unsigned virtio_dev_kind_device_id(enum virtio_dev_kind kind) {
switch (kind) {
case VIRTIO_DEV_KIND_BLOCK: return 0x1040 + 2;
}
UNREACHABLE();
}
void virtio_dev_iter_init(struct virtio_dev_iter *it, enum virtio_dev_kind kind) {
pci_iter_reset(&it->iter);
it->kind = kind;
}
bool virtio_dev_iter_next(struct virtio_dev_iter *it, uint16_t *bdf_out) {
while (pci_iter_next(&it->iter)) {
uint16_t bdf = pci_iter_get_bdf(&it->iter);
uint16_t vendor_id = pci_config_read_u16(bdf, PCI_HEADER_VENDOR_ID);
if (vendor_id != VIRTIO_VENDOR_ID) {
continue;
}
uint16_t device_id = pci_config_read_u16(bdf, PCI_HEADER_DEVICE_ID);
if (device_id == virtio_dev_kind_device_id(it->kind)
|| device_id == virtio_dev_kind_transitional_id(it->kind))
{
*bdf_out = bdf;
return true;
}
}
return false;
}