first functional block read

This commit is contained in:
uosfz 2026-05-27 23:22:00 +02:00
parent 78f2f4c75a
commit 085d3f4538
Signed by: uosfz
SSH key fingerprint: SHA256:FlktuluyhTQg3jHZNLKwxOOC5hbfrUXM0tz3IA3lGJo
6 changed files with 120 additions and 44 deletions

View file

@ -64,6 +64,7 @@ KERNEL_SOURCES := \
src/karlos-logo.c \
src/tlsf.c \
src/virtio-common.c \
src/virtio-block.c \
$(KERNEL_SOURCES_$(ARCH)) \
# end of kernel sources list

View file

@ -66,6 +66,12 @@ struct virtio_blk_req {
#define VIRTIO_BLK_T_WRITE_ZEROES 13
#define VIRTIO_BLK_T_SECURE_ERASE 14
void virtio_blk_req_init_in(struct virtio_blk_req *req, uint8_t *data, uint64_t sector);
void virtio_blk_req_init_in(struct virtio_blk_req *req, uint64_t sector);
void virtio_blk_req_init_out(struct virtio_blk_req *req, uint64_t sector);
void virtio_blk_req_init_flush(struct virtio_blk_req *req);
#define VIRTIO_BLK_S_OK 0
#define VIRTIO_BLK_S_IOERR 1
#define VIRTIO_BLK_S_UNSUPP 2
#endif

View file

@ -133,6 +133,16 @@ struct virtio_pci_cap {
/* Vendor-specific data */
#define VIRTIO_PCI_CAP_VENDOR_CFG 9
struct virtio_cap {
unsigned type;
uint8_t pci_offset;
uint8_t barno;
uint32_t offset;
uint32_t length;
};
bool virtio_find_cap_of_type(uint16_t bdf, unsigned type, struct virtio_cap *cap);
struct virtio_pci_common_cfg {
le32 device_feature_select;
le32 device_feature;
@ -155,4 +165,9 @@ struct virtio_pci_common_cfg {
le16 queue_reset; // only for VIRTIO_F_RING_-RESET
};
struct virtio_pci_notify_cap {
struct virtio_pci_cap cap;
le32 notify_off_multiplier; /* Multiplier for queue_notify_off. */
};
#endif

View file

@ -94,48 +94,15 @@ void main(void) {
uint8_t cap_ptr = pci_config_read_u8(bdf, PCI_HEADER_CAP_PTR);
// pci spec p. 234
// virtio spec p. 49
uint8_t common_bar = 0xff;
uint32_t common_offset = 0xff;
uint32_t common_length = 0xff;
uint8_t device_bar = 0xff;
uint32_t device_offset = 0xff;
uint32_t device_length = 0xff;
do {
uint16_t as16 = pci_config_read_u16(bdf, cap_ptr + 0);
uint8_t cap_id = (as16) & 0xff;
uint8_t cap_next = (as16 >> 8) & 0xff;
if (cap_id != 9) {
cap_ptr = cap_next;
continue; // ignore non-virtio
}
as16 = pci_config_read_u16(bdf, cap_ptr + 2);
uint8_t cap_len = (as16) & 0xff;
uint8_t cfg_type = (as16 >> 8) & 0xff;
printlinef("cap_len %u cfg_type %u", cap_len, cfg_type);
uint32_t as32 = pci_config_read_u32(bdf, cap_ptr + 4);
uint8_t bar = (as32) & 0xff;
uint32_t offset = pci_config_read_u32(bdf, cap_ptr + 8);
uint32_t length = pci_config_read_u32(bdf, cap_ptr + 12);
printlinef("bar %u offset %u length %u", bar, offset, length);
if (cfg_type == 1) {
ASSERT(common_bar = 0xff); // only 1 (I know there may be several)
common_bar = bar;
common_offset = offset;
common_length = length;
} else if (cfg_type == 4) {
ASSERT(device_bar == 0xff);
device_bar = bar;
device_offset = offset;
device_length = length;
}
cap_ptr = cap_next;
} while (cap_ptr != 0);
printlinef("common: bar %u offset %u length %u", common_bar, common_offset, common_length);
struct pci_bar bar = pci_config_get_bar(bdf, common_bar);
struct pa pa = pa_from_value(bar.base_address + common_offset);
struct virtio_cap cap_common, cap_notify, cap_device;
ASSERT(virtio_find_cap_of_type(bdf, VIRTIO_PCI_CAP_COMMON_CFG, &cap_common));
ASSERT(virtio_find_cap_of_type(bdf, VIRTIO_PCI_CAP_NOTIFY_CFG, &cap_notify));
ASSERT(virtio_find_cap_of_type(bdf, VIRTIO_PCI_CAP_DEVICE_CFG, &cap_device));
struct pci_bar bar = pci_config_get_bar(bdf, cap_common.barno);
struct pa pa = pa_from_value(bar.base_address + cap_common.offset);
volatile struct virtio_pci_common_cfg *cfg = pa_to_pointer(pa);
bar = pci_config_get_bar(bdf, device_bar);
pa = pa_from_value(bar.base_address + device_offset);
bar = pci_config_get_bar(bdf, cap_device.barno);
pa = pa_from_value(bar.base_address + cap_device.offset);
volatile struct virtio_blk_config *dev_cfg = pa_to_pointer(pa);
// TODO read configuration correctly (make sure `generation` doesn't change; p.24)
@ -216,6 +183,13 @@ void main(void) {
// set up virtqueue 0
LE16_WRITE(cfg->queue_select, 0);
while (LE16_READ(cfg->queue_select) != 0) ; // wait.
// notification stuff
// NOTE: this read depends on queue_select, so it may be different for every queue.
uint32_t queue_notify_off = LE32_READ(cfg->queue_notify_off);
uint32_t notify_off_multiplier = pci_config_read_u32(bdf, cap_notify.pci_offset + 16);
bar = pci_config_get_bar(bdf, cap_notify.barno);
pa = pa_from_value(bar.base_address + cap_notify.offset + queue_notify_off * notify_off_multiplier);
volatile le16 *queue0_notify_location = pa_to_pointer(pa);
uint16_t queue_size = LE16_READ(cfg->queue_size);
printlinef("queue_size %u", queue_size);
@ -245,6 +219,56 @@ void main(void) {
while (cfg->device_status != wanted_status) ; // wait.
printline("virtio block device initialized.");
// want to read a single sector.
struct virtq_desc *queue_desc = ppn_to_pointer(queue_desc_ppn);
// descriptor 0: header (device-readable)
struct virtq_desc *desc0 = queue_desc;
struct ppn buf0_ppn;
ASSERT(ram_alloc_frame_zeroed(&buf0_ppn, RAM_PAGE_NORMAL));
uint64_t buf0_addr = ppn_to_value(buf0_ppn);
LE64_WRITE(desc0->addr, buf0_addr);
LE32_WRITE(desc0->len, 16);
LE16_WRITE(desc0->flags, VIRTQ_DESC_F_NEXT);
LE16_WRITE(desc0->next, 1);
// descriptor 1: data + status (device-writable)
struct virtq_desc *desc1 = queue_desc + 1;
struct ppn buf1_ppn;
ASSERT(ram_alloc_frame_zeroed(&buf1_ppn, RAM_PAGE_NORMAL));
uint64_t buf1_addr = ppn_to_value(buf1_ppn);
LE64_WRITE(desc1->addr, buf1_addr);
LE32_WRITE(desc1->len, 513); // TROUBLESHOOT: maybe 512? but then where is status?
LE16_WRITE(desc1->flags, VIRTQ_DESC_F_WRITE);
LE16_WRITE(desc1->next, 0); // probably not necessary. 0 is also valid here, unlike NULL.
// fill request
struct virtio_blk_req *req = ppn_to_pointer(buf0_ppn);
virtio_blk_req_init_in(req, 1);
struct virtq_avail *avail = ppn_to_pointer(queue_driver_ppn);
LE16_WRITE(avail->ring[LE16_READ(avail->idx)], 0); // our chain starts with 0
__asm__ __volatile__("" ::: "memory");
LE16_WRITE(avail->idx, LE16_READ(avail->idx) + 1);
__asm__ __volatile__("" ::: "memory");
// TODO here, check if notifications are disabled
// TODO: send avail buffer notification for read
// queue is 0
LE16_WRITE(*queue0_notify_location, 0);
// we hope for the best and check if it works even without notifications
struct virtq_used *used = ppn_to_pointer(queue_device_ppn);
while (LE16_READ(used->idx) < 1) ; // wait.
struct virtq_used_elem *used_elem = used->ring + 0;
uint32_t used_id = LE32_READ(used_elem->id);
uint32_t used_len = LE32_READ(used_elem->len);
printlinef("used id %u len %u", used_id, used_len);
// read status
uint8_t *datap = (uint8_t*)ppn_to_pointer(buf1_ppn);
uint8_t status = datap[512];
ASSERT(status == VIRTIO_BLK_S_OK);
printlinef("%X8 %X8 %X8 %X8", datap[0], datap[1], datap[2], datap[3]);
while (1) {
fb_refresh();
__asm__ ("hlt");

View file

@ -2,11 +2,10 @@
#include "virtio-block.h"
#include "std.h"
void virtio_blk_req_init_in(struct virtio_blk_req *req, uint8_t *data, uint64_t sector) {
void virtio_blk_req_init_in(struct virtio_blk_req *req, uint64_t sector) {
LE32_WRITE(req->type, VIRTIO_BLK_T_IN);
LE32_WRITE(req->reserved, 0);
LE64_WRITE(req->sector, sector);
memcpy(req->data, data, 512);
}
void virtio_blk_req_init_out(struct virtio_blk_req *req, uint64_t sector) {

View file

@ -37,3 +37,34 @@ bool virtio_dev_iter_next(struct virtio_dev_iter *it, uint16_t *bdf_out) {
}
return false;
}
#define CAP_VNDR_VIRTIO 0x09
bool virtio_find_cap_of_type(uint16_t bdf, unsigned type, struct virtio_cap *cap) {
uint8_t cap_ptr = pci_config_read_u8(bdf, PCI_HEADER_CAP_PTR);
do {
// for some reason you need to read this as 16-bit; 2 8-bit reads don't work
uint16_t as16 = pci_config_read_u16(bdf, cap_ptr + 0);
uint8_t cap_id = (as16) & 0xff;
uint8_t cap_next = (as16 >> 8) & 0xff;
if (cap_id != CAP_VNDR_VIRTIO) {
cap_ptr = cap_next;
continue;
}
as16 = pci_config_read_u16(bdf, cap_ptr + 2);
uint8_t cap_len = (as16) & 0xff; // TODO there is 64-bit extension; how to handle?
uint8_t cfg_type = (as16 >> 8) & 0xff;
if (cfg_type != type) {
cap_ptr = cap_next;
continue;
}
cap->type = type;
cap->pci_offset = cap_ptr;
uint32_t as32 = pci_config_read_u32(bdf, cap_ptr + 4);
cap->barno = (as32) & 0xff;
cap->offset = pci_config_read_u32(bdf, cap_ptr + 8);
cap->length = pci_config_read_u32(bdf, cap_ptr + 12);
return true;
} while (cap_ptr != 0);
return false;
}