mirror of
https://github.com/cccriscv/mini-riscv-os.git
synced 2025-11-16 12:34:33 +00:00
312 lines
7.6 KiB
C
312 lines
7.6 KiB
C
/* This Code derived from xv6-riscv (64bit)
|
|
* -- https://github.com/mit-pdos/xv6-riscv/
|
|
*/
|
|
|
|
#include "virtio.h"
|
|
#include "os.h"
|
|
|
|
#define R(addr) ((volatile uint32 *)(VIRTIO_MMIO_BASE + (addr)))
|
|
#define BSIZE 512 // block size
|
|
#define PGSHIFT 12
|
|
|
|
struct blk
|
|
{
|
|
uint32 dev;
|
|
uint32 blockno;
|
|
lock_t lock;
|
|
int disk;
|
|
unsigned char data[BSIZE];
|
|
};
|
|
|
|
static struct disk
|
|
{
|
|
char pages[2 * PGSIZE];
|
|
/* descriptor */
|
|
virtq_desc_t *desc;
|
|
/* AvailableRing */
|
|
virtq_avail_t *avail;
|
|
/* UsedRing */
|
|
virtq_used_t *used;
|
|
/* For decord each descriptor is free or not */
|
|
char free[NUM];
|
|
struct
|
|
{
|
|
struct blk *b;
|
|
char status;
|
|
} info[NUM];
|
|
uint16 used_idx;
|
|
/* Disk command headers */
|
|
virtio_blk_req_t ops[NUM];
|
|
|
|
struct lock vdisk_lock;
|
|
} __attribute__((aligned(PGSIZE))) disk;
|
|
|
|
struct blk b[3];
|
|
|
|
void virtio_tester(int write)
|
|
{
|
|
if (!b[0].dev)
|
|
{
|
|
lib_puts("buffer init...\n");
|
|
for (size_t i = 0; i < 1; i++)
|
|
{
|
|
b[i].dev = 1; // always is 1
|
|
b[i].blockno = 1;
|
|
for (size_t j = 0; j < BSIZE; j++)
|
|
{
|
|
b[i].data[j] = 0;
|
|
}
|
|
|
|
lock_init(&(b[i].lock));
|
|
}
|
|
}
|
|
|
|
lib_puts("block read...\n");
|
|
virtio_disk_rw(&b[0], write);
|
|
size_t i = 0;
|
|
for (; i < 10; i++)
|
|
{
|
|
lib_printf("%x \n", b[0].data[i]);
|
|
}
|
|
lib_puts("\n");
|
|
}
|
|
|
|
void virtio_disk_init()
|
|
{
|
|
uint32 status = 0;
|
|
|
|
lock_init(&disk.vdisk_lock);
|
|
|
|
if (*R(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 ||
|
|
*R(VIRTIO_MMIO_VERSION) != 1 ||
|
|
*R(VIRTIO_MMIO_DEVICE_ID) != 2 ||
|
|
*R(VIRTIO_MMIO_VENDOR_ID) != 0x554d4551)
|
|
{
|
|
panic("could not find virtio disk");
|
|
}
|
|
/* Reset the device */
|
|
*R(VIRTIO_MMIO_STATUS) = status;
|
|
/* Set the ACKNOWLEDGE status bit to the status register. */
|
|
status |= VIRTIO_CONFIG_S_ACKNOWLEDGE;
|
|
*R(VIRTIO_MMIO_STATUS) = status;
|
|
/* Set the DRIVER status bit to the status register. */
|
|
status |= VIRTIO_CONFIG_S_DRIVER;
|
|
*R(VIRTIO_MMIO_STATUS) = status;
|
|
/* negotiate features */
|
|
uint32 features = *R(VIRTIO_MMIO_DEVICE_FEATURES);
|
|
features &= ~(1 << VIRTIO_BLK_F_RO);
|
|
features &= ~(1 << VIRTIO_BLK_F_SCSI);
|
|
features &= ~(1 << VIRTIO_BLK_F_CONFIG_WCE);
|
|
features &= ~(1 << VIRTIO_BLK_F_MQ);
|
|
features &= ~(1 << VIRTIO_F_ANY_LAYOUT);
|
|
features &= ~(1 << VIRTIO_RING_F_EVENT_IDX);
|
|
features &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC);
|
|
*R(VIRTIO_MMIO_DRIVER_FEATURES) = features;
|
|
|
|
/* tell device that feature negotiation is complete. */
|
|
status |= VIRTIO_CONFIG_S_FEATURES_OK;
|
|
*R(VIRTIO_MMIO_STATUS) = status;
|
|
|
|
/* tell device we're completely ready. */
|
|
status |= VIRTIO_CONFIG_S_DRIVER_OK;
|
|
*R(VIRTIO_MMIO_STATUS) = status;
|
|
|
|
*R(VIRTIO_MMIO_GUEST_PAGE_SIZE) = PGSIZE;
|
|
/* initialize queue 0. */
|
|
*R(VIRTIO_MMIO_QUEUE_SEL) = 0;
|
|
uint32 max = *R(VIRTIO_MMIO_QUEUE_NUM_MAX);
|
|
if (max == 0)
|
|
panic("virtio disk has no queue 0\n");
|
|
if (max < NUM)
|
|
panic("virtio disk max queue too short\n");
|
|
*R(VIRTIO_MMIO_QUEUE_NUM) = NUM;
|
|
memset(disk.pages, 0, sizeof(disk.pages));
|
|
*R(VIRTIO_MMIO_QUEUE_PFN) = ((uint32)disk.pages) / PGSIZE;
|
|
*R(VIRTIO_MMIO_QUEUE_ALIGN) = PGSIZE;
|
|
*R(VIRTIO_MMIO_QUEUE_READY) = 1;
|
|
// desc = pages -- num * virtq_desc
|
|
// avail = pages + 0x40 -- 2 * uint16, then num * uint16
|
|
// used = pages + 4096 -- 2 * uint16, then num * vRingUsedElem
|
|
disk.desc = (struct virtq_desc *)disk.pages;
|
|
disk.avail = (struct virtq_avail *)(disk.pages + NUM * sizeof(virtq_desc_t));
|
|
disk.used = (struct virtq_used *)(disk.pages + PGSIZE);
|
|
|
|
*R(VIRTIO_MMIO_QueueDescLow) = disk.desc;
|
|
*R(VIRTIO_MMIO_QueueAvailLow) = disk.avail;
|
|
*R(VIRTIO_MMIO_QueueUsedLow) = disk.used;
|
|
|
|
// all NUM descriptors start out unused.
|
|
for (int i = 0; i < NUM; i++)
|
|
disk.free[i] = 1;
|
|
lib_puts("Disk init work is success!\n");
|
|
}
|
|
|
|
// find a free descriptor, mark it non-free, return its index.
|
|
static int
|
|
alloc_desc()
|
|
{
|
|
for (int i = 0; i < NUM; i++)
|
|
{
|
|
if (disk.free[i])
|
|
{
|
|
disk.free[i] = 0;
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// mark a descriptor as free.
|
|
static void
|
|
free_desc(int i)
|
|
{
|
|
if (i >= NUM)
|
|
panic("free_desc 1");
|
|
if (disk.free[i])
|
|
panic("free_desc 2");
|
|
disk.desc[i].addr = 0;
|
|
disk.desc[i].len = 0;
|
|
disk.desc[i].flags = 0;
|
|
disk.desc[i].next = 0;
|
|
disk.free[i] = 1;
|
|
}
|
|
|
|
// free a chain of descriptors.
|
|
static void
|
|
free_chain(int i)
|
|
{
|
|
while (1)
|
|
{
|
|
int flag = disk.desc[i].flags;
|
|
int nxt = disk.desc[i].next;
|
|
free_desc(i);
|
|
if (flag & VRING_DESC_F_NEXT)
|
|
i = nxt;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
// allocate three descriptors (they need not be contiguous).
|
|
// disk transfers always use three descriptors.
|
|
static int
|
|
alloc3_desc(int *idx)
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
idx[i] = alloc_desc();
|
|
if (idx[i] < 0)
|
|
{
|
|
for (int j = 0; j < i; j++)
|
|
free_desc(idx[j]);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void virtio_disk_rw(struct blk *b, int write)
|
|
{
|
|
uint64 sector = b->blockno * (BSIZE / 512);
|
|
|
|
lock_acquire(&disk.vdisk_lock);
|
|
|
|
// allocate the three descriptors.
|
|
int idx[3];
|
|
while (1)
|
|
{
|
|
if (alloc3_desc(idx) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// format the three descriptors.
|
|
// qemu's virtio-blk.c reads them.
|
|
|
|
virtio_blk_req_t *buf0 = &disk.ops[idx[0]];
|
|
|
|
if (write)
|
|
buf0->type = VIRTIO_BLK_T_OUT; // write the disk
|
|
else
|
|
buf0->type = VIRTIO_BLK_T_IN; // read the disk
|
|
buf0->reserved = 0; // The reserved portion is used to pad the header to 16 bytes and move the 32-bit sector field to the correct place.
|
|
buf0->sector = sector; // specify the sector that we wanna modified.
|
|
|
|
disk.desc[idx[0]].addr = buf0;
|
|
disk.desc[idx[0]].len = sizeof(struct virtio_blk_req);
|
|
disk.desc[idx[0]].flags = VRING_DESC_F_NEXT;
|
|
disk.desc[idx[0]].next = idx[1];
|
|
|
|
disk.desc[idx[1]].addr = ((uint32)b->data) & 0xffffffff;
|
|
disk.desc[idx[1]].len = BSIZE;
|
|
if (write)
|
|
disk.desc[idx[1]].flags = 0; // device reads b->data
|
|
else
|
|
disk.desc[idx[1]].flags = VRING_DESC_F_WRITE; // device writes b->data
|
|
disk.desc[idx[1]].flags |= VRING_DESC_F_NEXT;
|
|
disk.desc[idx[1]].next = idx[2];
|
|
|
|
disk.info[idx[0]].status = 0;
|
|
disk.desc[idx[2]].addr = (uint32)&disk.info[idx[0]].status;
|
|
disk.desc[idx[2]].len = 1;
|
|
disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; // device writes the status
|
|
disk.desc[idx[2]].next = 0;
|
|
|
|
// record struct buf for virtio_disk_intr().
|
|
b->disk = 1;
|
|
disk.info[idx[0]].b = b;
|
|
|
|
__sync_synchronize();
|
|
|
|
// tell the device the first index in our chain of descriptors.
|
|
disk.avail->ring[disk.avail->idx % NUM] = idx[0];
|
|
|
|
__sync_synchronize();
|
|
|
|
// tell the device another avail ring entry is available.
|
|
disk.avail->idx += 1; // not % NUM ...
|
|
|
|
*R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // The device starts immediately when we write 0 to queue_notify.
|
|
while (b->disk == 1)
|
|
{
|
|
}
|
|
|
|
disk.info[idx[0]].b = 0;
|
|
free_chain(idx[0]);
|
|
|
|
lock_free(&disk.vdisk_lock);
|
|
}
|
|
|
|
void virtio_disk_isr()
|
|
{
|
|
//lock_acquire(&disk.vdisk_lock);
|
|
|
|
// the device won't raise another interrupt until we tell it
|
|
// we've seen this interrupt, which the following line does.
|
|
// this may race with the device writing new entries to
|
|
// the "used" ring, in which case we may process the new
|
|
// completion entries in this interrupt, and have nothing to do
|
|
// in the next interrupt, which is harmless.
|
|
*R(VIRTIO_MMIO_INTERRUPT_ACK) = *R(VIRTIO_MMIO_INTERRUPT_STATUS) & 0x3;
|
|
|
|
__sync_synchronize();
|
|
|
|
// the device increments disk.used->idx when it
|
|
// adds an entry to the used ring.
|
|
|
|
while (disk.used_idx != disk.used->idx)
|
|
{
|
|
__sync_synchronize();
|
|
int id = disk.used->ring[disk.used_idx % NUM].id;
|
|
|
|
if (disk.info[id].status != 0)
|
|
panic("virtio_disk_intr status");
|
|
|
|
struct blk *b = disk.info[id].b;
|
|
b->disk = 0;
|
|
disk.used_idx += 1;
|
|
}
|
|
|
|
//lock_free(&disk.vdisk_lock);
|
|
} |