port virtio_disk to virtio spec 1.0+
The legacy interface is confusing. It's better to make virtio_disk conform to the virtio spec. This is supported in QEMU since 4.2 by disabling force-legacy for virtio-mmio.
This commit is contained in:
parent
a1da53a5a1
commit
cd00a8233a
1
Makefile
1
Makefile
|
@ -157,6 +157,7 @@ CPUS := 3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
QEMUOPTS = -machine virt -bios none -kernel $K/kernel -m 128M -smp $(CPUS) -nographic
|
QEMUOPTS = -machine virt -bios none -kernel $K/kernel -m 128M -smp $(CPUS) -nographic
|
||||||
|
QEMUOPTS += -global virtio-mmio.force-legacy=false
|
||||||
QEMUOPTS += -drive file=fs.img,if=none,format=raw,id=x0
|
QEMUOPTS += -drive file=fs.img,if=none,format=raw,id=x0
|
||||||
QEMUOPTS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
|
QEMUOPTS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// virtio device definitions.
|
// virtio device definitions.
|
||||||
// for both the mmio interface, and virtio descriptors.
|
// for both the mmio interface, and virtio descriptors.
|
||||||
// only tested with qemu.
|
// only tested with qemu.
|
||||||
// this is the "legacy" virtio interface.
|
|
||||||
//
|
//
|
||||||
// the virtio spec:
|
// the virtio spec:
|
||||||
// https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.pdf
|
// https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.pdf
|
||||||
|
@ -16,17 +15,20 @@
|
||||||
#define VIRTIO_MMIO_VENDOR_ID 0x00c // 0x554d4551
|
#define VIRTIO_MMIO_VENDOR_ID 0x00c // 0x554d4551
|
||||||
#define VIRTIO_MMIO_DEVICE_FEATURES 0x010
|
#define VIRTIO_MMIO_DEVICE_FEATURES 0x010
|
||||||
#define VIRTIO_MMIO_DRIVER_FEATURES 0x020
|
#define VIRTIO_MMIO_DRIVER_FEATURES 0x020
|
||||||
#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 // page size for PFN, write-only
|
|
||||||
#define VIRTIO_MMIO_QUEUE_SEL 0x030 // select queue, write-only
|
#define VIRTIO_MMIO_QUEUE_SEL 0x030 // select queue, write-only
|
||||||
#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 // max size of current queue, read-only
|
#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 // max size of current queue, read-only
|
||||||
#define VIRTIO_MMIO_QUEUE_NUM 0x038 // size of current queue, write-only
|
#define VIRTIO_MMIO_QUEUE_NUM 0x038 // size of current queue, write-only
|
||||||
#define VIRTIO_MMIO_QUEUE_ALIGN 0x03c // used ring alignment, write-only
|
|
||||||
#define VIRTIO_MMIO_QUEUE_PFN 0x040 // physical page number for queue, read/write
|
|
||||||
#define VIRTIO_MMIO_QUEUE_READY 0x044 // ready bit
|
#define VIRTIO_MMIO_QUEUE_READY 0x044 // ready bit
|
||||||
#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 // write-only
|
#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 // write-only
|
||||||
#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 // read-only
|
#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 // read-only
|
||||||
#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 // write-only
|
#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 // write-only
|
||||||
#define VIRTIO_MMIO_STATUS 0x070 // read/write
|
#define VIRTIO_MMIO_STATUS 0x070 // read/write
|
||||||
|
#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 // physical address for descriptor table, write-only
|
||||||
|
#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084
|
||||||
|
#define VIRTIO_MMIO_DRIVER_DESC_LOW 0x090 // physical address for available ring, write-only
|
||||||
|
#define VIRTIO_MMIO_DRIVER_DESC_HIGH 0x094
|
||||||
|
#define VIRTIO_MMIO_DEVICE_DESC_LOW 0x0a0 // physical address for used ring, write-only
|
||||||
|
#define VIRTIO_MMIO_DEVICE_DESC_HIGH 0x0a4
|
||||||
|
|
||||||
// status register bits, from qemu virtio_config.h
|
// status register bits, from qemu virtio_config.h
|
||||||
#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1
|
#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//
|
//
|
||||||
// driver for qemu's virtio disk device.
|
// driver for qemu's virtio disk device.
|
||||||
// uses qemu's mmio interface to virtio.
|
// uses qemu's mmio interface to virtio.
|
||||||
// qemu presents a "legacy" virtio interface.
|
|
||||||
//
|
//
|
||||||
// qemu ... -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
|
// qemu ... -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
|
||||||
//
|
//
|
||||||
|
@ -21,36 +20,22 @@
|
||||||
#define R(r) ((volatile uint32 *)(VIRTIO0 + (r)))
|
#define R(r) ((volatile uint32 *)(VIRTIO0 + (r)))
|
||||||
|
|
||||||
static struct disk {
|
static struct disk {
|
||||||
// the virtio driver and device mostly communicate through a set of
|
// the first is a set (not a ring) of DMA descriptors, with which the
|
||||||
// structures in RAM. pages[] allocates that memory. pages[] is a
|
// driver tells the device where to read and write individual
|
||||||
// global (instead of calls to kalloc()) because it must consist of
|
// disk operations. there are NUM descriptors.
|
||||||
// two contiguous pages of page-aligned physical memory.
|
|
||||||
char pages[2*PGSIZE];
|
|
||||||
|
|
||||||
// pages[] is divided into three regions (descriptors, avail, and
|
|
||||||
// used), as explained in Section 2.6 of the virtio specification
|
|
||||||
// for the legacy interface.
|
|
||||||
// https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.pdf
|
|
||||||
|
|
||||||
// the first region of pages[] is a set (not a ring) of DMA
|
|
||||||
// descriptors, with which the driver tells the device where to read
|
|
||||||
// and write individual disk operations. there are NUM descriptors.
|
|
||||||
// most commands consist of a "chain" (a linked list) of a couple of
|
// most commands consist of a "chain" (a linked list) of a couple of
|
||||||
// these descriptors.
|
// these descriptors.
|
||||||
// points into pages[].
|
|
||||||
struct virtq_desc *desc;
|
struct virtq_desc *desc;
|
||||||
|
|
||||||
// next is a ring in which the driver writes descriptor numbers
|
// next is a ring in which the driver writes descriptor numbers
|
||||||
// that the driver would like the device to process. it only
|
// that the driver would like the device to process. it only
|
||||||
// includes the head descriptor of each chain. the ring has
|
// includes the head descriptor of each chain. the ring has
|
||||||
// NUM elements.
|
// NUM elements.
|
||||||
// points into pages[].
|
|
||||||
struct virtq_avail *avail;
|
struct virtq_avail *avail;
|
||||||
|
|
||||||
// finally a ring in which the device writes descriptor numbers that
|
// finally a ring in which the device writes descriptor numbers that
|
||||||
// the device has finished processing (just the head of each chain).
|
// the device has finished processing (just the head of each chain).
|
||||||
// there are NUM used ring entries.
|
// there are NUM used ring entries.
|
||||||
// points into pages[].
|
|
||||||
struct virtq_used *used;
|
struct virtq_used *used;
|
||||||
|
|
||||||
// our own book-keeping.
|
// our own book-keeping.
|
||||||
|
@ -71,7 +56,7 @@ static struct disk {
|
||||||
|
|
||||||
struct spinlock vdisk_lock;
|
struct spinlock vdisk_lock;
|
||||||
|
|
||||||
} __attribute__ ((aligned (PGSIZE))) disk;
|
} disk;
|
||||||
|
|
||||||
void
|
void
|
||||||
virtio_disk_init(void)
|
virtio_disk_init(void)
|
||||||
|
@ -81,15 +66,20 @@ virtio_disk_init(void)
|
||||||
initlock(&disk.vdisk_lock, "virtio_disk");
|
initlock(&disk.vdisk_lock, "virtio_disk");
|
||||||
|
|
||||||
if(*R(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 ||
|
if(*R(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 ||
|
||||||
*R(VIRTIO_MMIO_VERSION) != 1 ||
|
*R(VIRTIO_MMIO_VERSION) != 2 ||
|
||||||
*R(VIRTIO_MMIO_DEVICE_ID) != 2 ||
|
*R(VIRTIO_MMIO_DEVICE_ID) != 2 ||
|
||||||
*R(VIRTIO_MMIO_VENDOR_ID) != 0x554d4551){
|
*R(VIRTIO_MMIO_VENDOR_ID) != 0x554d4551){
|
||||||
panic("could not find virtio disk");
|
panic("could not find virtio disk");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset device
|
||||||
|
*R(VIRTIO_MMIO_STATUS) = status;
|
||||||
|
|
||||||
|
// set ACKNOWLEDGE status bit
|
||||||
status |= VIRTIO_CONFIG_S_ACKNOWLEDGE;
|
status |= VIRTIO_CONFIG_S_ACKNOWLEDGE;
|
||||||
*R(VIRTIO_MMIO_STATUS) = status;
|
*R(VIRTIO_MMIO_STATUS) = status;
|
||||||
|
|
||||||
|
// set DRIVER status bit
|
||||||
status |= VIRTIO_CONFIG_S_DRIVER;
|
status |= VIRTIO_CONFIG_S_DRIVER;
|
||||||
*R(VIRTIO_MMIO_STATUS) = status;
|
*R(VIRTIO_MMIO_STATUS) = status;
|
||||||
|
|
||||||
|
@ -108,35 +98,57 @@ virtio_disk_init(void)
|
||||||
status |= VIRTIO_CONFIG_S_FEATURES_OK;
|
status |= VIRTIO_CONFIG_S_FEATURES_OK;
|
||||||
*R(VIRTIO_MMIO_STATUS) = status;
|
*R(VIRTIO_MMIO_STATUS) = status;
|
||||||
|
|
||||||
// tell device we're completely ready.
|
// re-read status to ensure FEATURES_OK is set.
|
||||||
status |= VIRTIO_CONFIG_S_DRIVER_OK;
|
status = *R(VIRTIO_MMIO_STATUS);
|
||||||
*R(VIRTIO_MMIO_STATUS) = status;
|
if(!(status & VIRTIO_CONFIG_S_FEATURES_OK))
|
||||||
|
panic("virtio disk FEATURES_OK unset");
|
||||||
*R(VIRTIO_MMIO_GUEST_PAGE_SIZE) = PGSIZE;
|
|
||||||
|
|
||||||
// initialize queue 0.
|
// initialize queue 0.
|
||||||
*R(VIRTIO_MMIO_QUEUE_SEL) = 0;
|
*R(VIRTIO_MMIO_QUEUE_SEL) = 0;
|
||||||
|
|
||||||
|
// ensure queue 0 is not in use.
|
||||||
|
if(*R(VIRTIO_MMIO_QUEUE_READY))
|
||||||
|
panic("virtio disk should not be ready");
|
||||||
|
|
||||||
|
// check maximum queue size.
|
||||||
uint32 max = *R(VIRTIO_MMIO_QUEUE_NUM_MAX);
|
uint32 max = *R(VIRTIO_MMIO_QUEUE_NUM_MAX);
|
||||||
if(max == 0)
|
if(max == 0)
|
||||||
panic("virtio disk has no queue 0");
|
panic("virtio disk has no queue 0");
|
||||||
if(max < NUM)
|
if(max < NUM)
|
||||||
panic("virtio disk max queue too short");
|
panic("virtio disk max queue too short");
|
||||||
|
|
||||||
|
// allocate and zero queue memory.
|
||||||
|
disk.desc = kalloc();
|
||||||
|
disk.avail = kalloc();
|
||||||
|
disk.used = kalloc();
|
||||||
|
if(!disk.desc || !disk.avail || !disk.used)
|
||||||
|
panic("virtio disk kalloc");
|
||||||
|
memset(disk.desc, 0, PGSIZE);
|
||||||
|
memset(disk.avail, 0, PGSIZE);
|
||||||
|
memset(disk.used, 0, PGSIZE);
|
||||||
|
|
||||||
|
// set queue size.
|
||||||
*R(VIRTIO_MMIO_QUEUE_NUM) = NUM;
|
*R(VIRTIO_MMIO_QUEUE_NUM) = NUM;
|
||||||
memset(disk.pages, 0, sizeof(disk.pages));
|
|
||||||
*R(VIRTIO_MMIO_QUEUE_PFN) = ((uint64)disk.pages) >> PGSHIFT;
|
|
||||||
|
|
||||||
// desc = pages -- num * virtq_desc
|
// write physical addresses.
|
||||||
// avail = pages + 0x40 -- 2 * uint16, then num * uint16
|
*R(VIRTIO_MMIO_QUEUE_DESC_LOW) = (uint64)disk.desc;
|
||||||
// used = pages + 4096 -- 2 * uint16, then num * vRingUsedElem
|
*R(VIRTIO_MMIO_QUEUE_DESC_HIGH) = (uint64)disk.desc >> 32;
|
||||||
|
*R(VIRTIO_MMIO_DRIVER_DESC_LOW) = (uint64)disk.avail;
|
||||||
|
*R(VIRTIO_MMIO_DRIVER_DESC_HIGH) = (uint64)disk.avail >> 32;
|
||||||
|
*R(VIRTIO_MMIO_DEVICE_DESC_LOW) = (uint64)disk.used;
|
||||||
|
*R(VIRTIO_MMIO_DEVICE_DESC_HIGH) = (uint64)disk.used >> 32;
|
||||||
|
|
||||||
disk.desc = (struct virtq_desc *) disk.pages;
|
// queue is ready.
|
||||||
disk.avail = (struct virtq_avail *)(disk.pages + NUM*sizeof(struct virtq_desc));
|
*R(VIRTIO_MMIO_QUEUE_READY) = 0x1;
|
||||||
disk.used = (struct virtq_used *) (disk.pages + PGSIZE);
|
|
||||||
|
|
||||||
// all NUM descriptors start out unused.
|
// all NUM descriptors start out unused.
|
||||||
for(int i = 0; i < NUM; i++)
|
for(int i = 0; i < NUM; i++)
|
||||||
disk.free[i] = 1;
|
disk.free[i] = 1;
|
||||||
|
|
||||||
|
// tell device we're completely ready.
|
||||||
|
status |= VIRTIO_CONFIG_S_DRIVER_OK;
|
||||||
|
*R(VIRTIO_MMIO_STATUS) = status;
|
||||||
|
|
||||||
// plic.c and trap.c arrange for interrupts from VIRTIO0_IRQ.
|
// plic.c and trap.c arrange for interrupts from VIRTIO0_IRQ.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue