mirror of
https://github.com/cccriscv/mini-riscv-os.git
synced 2025-12-26 17:18:27 +00:00
Add 08-doc and modified the device driver (On going...)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
#include "os.h"
|
#include "os.h"
|
||||||
extern void trap_vector();
|
extern void trap_vector();
|
||||||
|
extern void virtio_disk_isr();
|
||||||
void trap_init()
|
void trap_init()
|
||||||
{
|
{
|
||||||
// set the machine-mode trap handler.
|
// set the machine-mode trap handler.
|
||||||
@@ -14,6 +14,10 @@ void external_handler()
|
|||||||
{
|
{
|
||||||
lib_isr();
|
lib_isr();
|
||||||
}
|
}
|
||||||
|
else if (irq == VIRTIO_IRQ)
|
||||||
|
{
|
||||||
|
virtio_disk_isr();
|
||||||
|
}
|
||||||
else if (irq)
|
else if (irq)
|
||||||
{
|
{
|
||||||
lib_printf("unexpected interrupt irq = %d\n", irq);
|
lib_printf("unexpected interrupt irq = %d\n", irq);
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
#include "virtio.h"
|
#include "virtio.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
#define R(addr) ((volatile uint32_t *)(VIRTIO_MMIO_BASE + (addr)))
|
#define R(addr) ((volatile uint32_t *)(VIRTIO_MMIO_BASE + (addr)))
|
||||||
|
#define BSIZE 1024 // block size
|
||||||
|
#define PGSHIFT 12
|
||||||
|
|
||||||
|
struct buf
|
||||||
|
{
|
||||||
|
int valid; // has data been read from disk?
|
||||||
|
int disk; // does disk "own" buf?
|
||||||
|
uint32_t dev;
|
||||||
|
uint32_t blockno;
|
||||||
|
lock_t lock;
|
||||||
|
uint32_t refcnt;
|
||||||
|
struct buf *prev; // LRU cache list
|
||||||
|
struct buf *next;
|
||||||
|
unsigned char data[BSIZE];
|
||||||
|
};
|
||||||
|
|
||||||
static struct disk
|
static struct disk
|
||||||
{
|
{
|
||||||
@@ -13,80 +28,90 @@ static struct disk
|
|||||||
virtq_used_t *used;
|
virtq_used_t *used;
|
||||||
/* For decord each descriptor is free or not */
|
/* For decord each descriptor is free or not */
|
||||||
char free[NUM];
|
char free[NUM];
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
struct buf *b;
|
||||||
|
char status;
|
||||||
|
} info[NUM];
|
||||||
|
uint16_t used_idx;
|
||||||
/* Disk command headers */
|
/* Disk command headers */
|
||||||
virtio_blk_req_t ops[NUM];
|
virtio_blk_req_t ops[NUM];
|
||||||
|
|
||||||
struct lock vdisk_lock;
|
struct lock vdisk_lock;
|
||||||
} __attribute__((aligned(PGSIZE))) disk;
|
} __attribute__((aligned(PGSIZE))) disk;
|
||||||
|
|
||||||
void virtio_disk_init(){
|
void virtio_disk_init()
|
||||||
uint32_t status = 0;
|
{
|
||||||
|
uint32_t status = 0;
|
||||||
|
|
||||||
lock_init(&disk.vdisk_lock);
|
lock_init(&disk.vdisk_lock);
|
||||||
|
|
||||||
if(*R(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 ||
|
if (*R(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 ||
|
||||||
*R(VIRTIO_MMIO_VERSION) != 1 ||
|
*R(VIRTIO_MMIO_VERSION) != 1 ||
|
||||||
*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");
|
||||||
}
|
}
|
||||||
/* Set the ACKNOWLEDGE status bit to the status register. */
|
/* Set the ACKNOWLEDGE status bit to the status register. */
|
||||||
status |= VIRTIO_CONFIG_S_ACKNOWLEDGE;
|
status |= VIRTIO_CONFIG_S_ACKNOWLEDGE;
|
||||||
*R(VIRTIO_MMIO_STATUS) = status;
|
*R(VIRTIO_MMIO_STATUS) = status;
|
||||||
/* Set the DRIVER status bit to the status register. */
|
/* Set the DRIVER status bit to the status register. */
|
||||||
status |= VIRTIO_CONFIG_S_DRIVER;
|
status |= VIRTIO_CONFIG_S_DRIVER;
|
||||||
*R(VIRTIO_MMIO_STATUS) = status;
|
*R(VIRTIO_MMIO_STATUS) = status;
|
||||||
/* negotiate features */
|
/* negotiate features */
|
||||||
uint64 features = *R(VIRTIO_MMIO_DEVICE_FEATURES);
|
uint64_t features = *R(VIRTIO_MMIO_DEVICE_FEATURES);
|
||||||
features &= ~(1 << VIRTIO_BLK_F_RO);
|
features &= ~(1 << VIRTIO_BLK_F_RO);
|
||||||
features &= ~(1 << VIRTIO_BLK_F_SCSI);
|
features &= ~(1 << VIRTIO_BLK_F_SCSI);
|
||||||
features &= ~(1 << VIRTIO_BLK_F_CONFIG_WCE);
|
features &= ~(1 << VIRTIO_BLK_F_CONFIG_WCE);
|
||||||
features &= ~(1 << VIRTIO_BLK_F_MQ);
|
features &= ~(1 << VIRTIO_BLK_F_MQ);
|
||||||
features &= ~(1 << VIRTIO_F_ANY_LAYOUT);
|
features &= ~(1 << VIRTIO_F_ANY_LAYOUT);
|
||||||
features &= ~(1 << VIRTIO_RING_F_EVENT_IDX);
|
features &= ~(1 << VIRTIO_RING_F_EVENT_IDX);
|
||||||
features &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC);
|
features &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC);
|
||||||
*R(VIRTIO_MMIO_DRIVER_FEATURES) = features;
|
*R(VIRTIO_MMIO_DRIVER_FEATURES) = features;
|
||||||
|
|
||||||
/* tell device that feature negotiation is complete. */
|
/* tell device that feature negotiation is complete. */
|
||||||
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. */
|
/* tell device we're completely ready. */
|
||||||
status |= VIRTIO_CONFIG_S_DRIVER_OK;
|
status |= VIRTIO_CONFIG_S_DRIVER_OK;
|
||||||
*R(VIRTIO_MMIO_STATUS) = status;
|
*R(VIRTIO_MMIO_STATUS) = status;
|
||||||
|
|
||||||
*R(VIRTIO_MMIO_GUEST_PAGE_SIZE) = PGSIZE;
|
*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;
|
||||||
uint32 max = *R(VIRTIO_MMIO_QUEUE_NUM_MAX);
|
uint32_t max = *R(VIRTIO_MMIO_QUEUE_NUM_MAX);
|
||||||
if (max == 0)
|
if (max == 0)
|
||||||
lib_puts("virtio disk has no queue 0\n");
|
lib_puts("virtio disk has no queue 0\n");
|
||||||
if (max < NUM)
|
if (max < NUM)
|
||||||
lib_puts("virtio disk max queue too short\n");
|
lib_puts("virtio disk max queue too short\n");
|
||||||
*R(VIRTIO_MMIO_QUEUE_NUM) = NUM;
|
*R(VIRTIO_MMIO_QUEUE_NUM) = NUM;
|
||||||
memset(disk.pages, 0, sizeof(disk.pages));
|
memset(disk.pages, 0, sizeof(disk.pages));
|
||||||
*R(VIRTIO_MMIO_QUEUE_PFN) = ((uint64)disk.pages) >> PGSHIFT;
|
*R(VIRTIO_MMIO_QUEUE_PFN) = ((uint64_t)disk.pages) >> PGSHIFT;
|
||||||
|
|
||||||
// desc = pages -- num * virtq_desc
|
// desc = pages -- num * virtq_desc
|
||||||
// avail = pages + 0x40 -- 2 * uint16, then num * uint16
|
// avail = pages + 0x40 -- 2 * uint16, then num * uint16
|
||||||
// used = pages + 4096 -- 2 * uint16, then num * vRingUsedElem
|
// used = pages + 4096 -- 2 * uint16, then num * vRingUsedElem
|
||||||
|
|
||||||
disk.desc = (struct virtq_desc *)disk.pages;
|
disk.desc = (struct virtq_desc *)disk.pages;
|
||||||
disk.avail = (struct virtq_avail *)(disk.pages + NUM * sizeof(struct virtq_desc));
|
disk.avail = (struct virtq_avail *)(disk.pages + NUM * sizeof(struct virtq_desc));
|
||||||
disk.used = (struct virtq_used *)(disk.pages + PGSIZE);
|
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;
|
||||||
lib_puts("Disk init work is success!\n");
|
lib_puts("Disk init work is success!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// find a free descriptor, mark it non-free, return its index.
|
// find a free descriptor, mark it non-free, return its index.
|
||||||
static int
|
static int
|
||||||
alloc_desc()
|
alloc_desc()
|
||||||
{
|
{
|
||||||
for(int i = 0; i < NUM; i++){
|
for (int i = 0; i < NUM; i++)
|
||||||
if(disk.free[i]){
|
{
|
||||||
|
if (disk.free[i])
|
||||||
|
{
|
||||||
disk.free[i] = 0;
|
disk.free[i] = 0;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -98,9 +123,9 @@ alloc_desc()
|
|||||||
static void
|
static void
|
||||||
free_desc(int i)
|
free_desc(int i)
|
||||||
{
|
{
|
||||||
if(i >= NUM)
|
if (i >= NUM)
|
||||||
panic("free_desc 1");
|
panic("free_desc 1");
|
||||||
if(disk.free[i])
|
if (disk.free[i])
|
||||||
panic("free_desc 2");
|
panic("free_desc 2");
|
||||||
disk.desc[i].addr = 0;
|
disk.desc[i].addr = 0;
|
||||||
disk.desc[i].len = 0;
|
disk.desc[i].len = 0;
|
||||||
@@ -114,11 +139,12 @@ free_desc(int i)
|
|||||||
static void
|
static void
|
||||||
free_chain(int i)
|
free_chain(int i)
|
||||||
{
|
{
|
||||||
while(1){
|
while (1)
|
||||||
|
{
|
||||||
int flag = disk.desc[i].flags;
|
int flag = disk.desc[i].flags;
|
||||||
int nxt = disk.desc[i].next;
|
int nxt = disk.desc[i].next;
|
||||||
free_desc(i);
|
free_desc(i);
|
||||||
if(flag & VRING_DESC_F_NEXT)
|
if (flag & VRING_DESC_F_NEXT)
|
||||||
i = nxt;
|
i = nxt;
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
@@ -130,10 +156,12 @@ free_chain(int i)
|
|||||||
static int
|
static int
|
||||||
alloc3_desc(int *idx)
|
alloc3_desc(int *idx)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < 3; i++){
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
idx[i] = alloc_desc();
|
idx[i] = alloc_desc();
|
||||||
if(idx[i] < 0){
|
if (idx[i] < 0)
|
||||||
for(int j = 0; j < i; j++)
|
{
|
||||||
|
for (int j = 0; j < i; j++)
|
||||||
free_desc(idx[j]);
|
free_desc(idx[j]);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -141,10 +169,9 @@ alloc3_desc(int *idx)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void virtio_disk_rw(struct buf *b, int write)
|
||||||
virtio_disk_rw(struct buf *b, int write)
|
|
||||||
{
|
{
|
||||||
uint64 sector = b->blockno * (BSIZE / 512);
|
uint64_t sector = b->blockno * (BSIZE / 512);
|
||||||
|
|
||||||
lock_acquire(&disk.vdisk_lock);
|
lock_acquire(&disk.vdisk_lock);
|
||||||
|
|
||||||
@@ -154,8 +181,10 @@ virtio_disk_rw(struct buf *b, int write)
|
|||||||
|
|
||||||
// allocate the three descriptors.
|
// allocate the three descriptors.
|
||||||
int idx[3];
|
int idx[3];
|
||||||
while(1){
|
while (1)
|
||||||
if(alloc3_desc(idx) == 0) {
|
{
|
||||||
|
if (alloc3_desc(idx) == 0)
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,21 +194,21 @@ virtio_disk_rw(struct buf *b, int write)
|
|||||||
|
|
||||||
struct virtio_blk_req *buf0 = &disk.ops[idx[0]];
|
struct virtio_blk_req *buf0 = &disk.ops[idx[0]];
|
||||||
|
|
||||||
if(write)
|
if (write)
|
||||||
buf0->type = VIRTIO_BLK_T_OUT; // write the disk
|
buf0->type = VIRTIO_BLK_T_OUT; // write the disk
|
||||||
else
|
else
|
||||||
buf0->type = VIRTIO_BLK_T_IN; // read the disk
|
buf0->type = VIRTIO_BLK_T_IN; // read the disk
|
||||||
buf0->reserved = 0;
|
buf0->reserved = 0; // The reserved portion is used to pad the header to 16 bytes and move the 64-bit sector field to the correct place.
|
||||||
buf0->sector = sector;
|
buf0->sector = sector; // specify the sector that we wanna modified.
|
||||||
|
|
||||||
disk.desc[idx[0]].addr = (uint64) buf0;
|
disk.desc[idx[0]].addr = (uint64_t)buf0;
|
||||||
disk.desc[idx[0]].len = sizeof(struct virtio_blk_req);
|
disk.desc[idx[0]].len = sizeof(struct virtio_blk_req);
|
||||||
disk.desc[idx[0]].flags = VRING_DESC_F_NEXT;
|
disk.desc[idx[0]].flags = VRING_DESC_F_NEXT;
|
||||||
disk.desc[idx[0]].next = idx[1];
|
disk.desc[idx[0]].next = idx[1];
|
||||||
|
|
||||||
disk.desc[idx[1]].addr = (uint64) b->data;
|
disk.desc[idx[1]].addr = (uint64_t)b->data;
|
||||||
disk.desc[idx[1]].len = BSIZE;
|
disk.desc[idx[1]].len = BSIZE;
|
||||||
if(write)
|
if (write)
|
||||||
disk.desc[idx[1]].flags = 0; // device reads b->data
|
disk.desc[idx[1]].flags = 0; // device reads b->data
|
||||||
else
|
else
|
||||||
disk.desc[idx[1]].flags = VRING_DESC_F_WRITE; // device writes b->data
|
disk.desc[idx[1]].flags = VRING_DESC_F_WRITE; // device writes b->data
|
||||||
@@ -187,7 +216,7 @@ virtio_disk_rw(struct buf *b, int write)
|
|||||||
disk.desc[idx[1]].next = idx[2];
|
disk.desc[idx[1]].next = idx[2];
|
||||||
|
|
||||||
disk.info[idx[0]].status = 0xff; // device writes 0 on success
|
disk.info[idx[0]].status = 0xff; // device writes 0 on success
|
||||||
disk.desc[idx[2]].addr = (uint64) &disk.info[idx[0]].status;
|
disk.desc[idx[2]].addr = (uint64_t)&disk.info[idx[0]].status;
|
||||||
disk.desc[idx[2]].len = 1;
|
disk.desc[idx[2]].len = 1;
|
||||||
disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; // device writes the status
|
disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; // device writes the status
|
||||||
disk.desc[idx[2]].next = 0;
|
disk.desc[idx[2]].next = 0;
|
||||||
@@ -209,7 +238,8 @@ virtio_disk_rw(struct buf *b, int write)
|
|||||||
*R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // value is queue number
|
*R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // value is queue number
|
||||||
|
|
||||||
// Wait for virtio_disk_intr() to say request has finished.
|
// Wait for virtio_disk_intr() to say request has finished.
|
||||||
while(b->disk == 1) {
|
while (b->disk == 1)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
disk.info[idx[0]].b = 0;
|
disk.info[idx[0]].b = 0;
|
||||||
@@ -218,8 +248,7 @@ virtio_disk_rw(struct buf *b, int write)
|
|||||||
lock_free(&disk.vdisk_lock);
|
lock_free(&disk.vdisk_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void virtio_disk_isr()
|
||||||
virtio_disk_intr()
|
|
||||||
{
|
{
|
||||||
lock_acquire(&disk.vdisk_lock);
|
lock_acquire(&disk.vdisk_lock);
|
||||||
|
|
||||||
@@ -236,15 +265,16 @@ virtio_disk_intr()
|
|||||||
// the device increments disk.used->idx when it
|
// the device increments disk.used->idx when it
|
||||||
// adds an entry to the used ring.
|
// adds an entry to the used ring.
|
||||||
|
|
||||||
while(disk.used_idx != disk.used->idx){
|
while (disk.used_idx != disk.used->idx)
|
||||||
|
{
|
||||||
__sync_synchronize();
|
__sync_synchronize();
|
||||||
int id = disk.used->ring[disk.used_idx % NUM].id;
|
int id = disk.used->ring[disk.used_idx % NUM].id;
|
||||||
|
|
||||||
if(disk.info[id].status != 0)
|
if (disk.info[id].status != 0)
|
||||||
panic("virtio_disk_intr status");
|
panic("virtio_disk_intr status");
|
||||||
|
|
||||||
struct buf *b = disk.info[id].b;
|
struct buf *b = disk.info[id].b;
|
||||||
b->disk = 0; // disk is done with buf
|
b->disk = 0; // disk is done with buf
|
||||||
wakeup(b);
|
wakeup(b);
|
||||||
|
|
||||||
disk.used_idx += 1;
|
disk.used_idx += 1;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#define VIRTIO_MMIO_BASE 0x10001000
|
#define VIRTIO_MMIO_BASE 0x10001000
|
||||||
/* OFFSET */
|
/* OFFSET */
|
||||||
#define VIRTIO_MMIO_MAGIC_VALUE 0x000 // Magic value must be 0x74726976
|
#define VIRTIO_MMIO_MAGIC_VALUE 0x000 // Magic value must be 0x74726976
|
||||||
#define VIRTIO_MMIO_VERSION 0x004 // Version: 1 (Legacy)
|
#define VIRTIO_MMIO_VERSION 0x004 // Version: 1 (Legacy)
|
||||||
/*
|
/*
|
||||||
* Device ID:
|
* Device ID:
|
||||||
* 1 (Network Device)
|
* 1 (Network Device)
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
* 18 (Input Device)
|
* 18 (Input Device)
|
||||||
*/
|
*/
|
||||||
#define VIRTIO_MMIO_DEVICE_ID 0x008
|
#define VIRTIO_MMIO_DEVICE_ID 0x008
|
||||||
|
#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_GUEST_PAGE_SIZE 0x028 // page size for PFN, write-only
|
||||||
@@ -31,19 +32,19 @@
|
|||||||
#define VIRTIO_MMIO_STATUS 0x070 // read/write
|
#define VIRTIO_MMIO_STATUS 0x070 // read/write
|
||||||
|
|
||||||
// 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
|
||||||
#define VIRTIO_CONFIG_S_DRIVER 2
|
#define VIRTIO_CONFIG_S_DRIVER 2
|
||||||
#define VIRTIO_CONFIG_S_DRIVER_OK 4
|
#define VIRTIO_CONFIG_S_DRIVER_OK 4
|
||||||
#define VIRTIO_CONFIG_S_FEATURES_OK 8
|
#define VIRTIO_CONFIG_S_FEATURES_OK 8
|
||||||
|
|
||||||
// device feature bits
|
// device feature bits
|
||||||
#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */
|
#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */
|
||||||
#define VIRTIO_BLK_F_SCSI 7 /* Supports scsi command passthru */
|
#define VIRTIO_BLK_F_SCSI 7 /* Supports scsi command passthru */
|
||||||
#define VIRTIO_BLK_F_CONFIG_WCE 11 /* Writeback mode available in config */
|
#define VIRTIO_BLK_F_CONFIG_WCE 11 /* Writeback mode available in config */
|
||||||
#define VIRTIO_BLK_F_MQ 12 /* support more than one vq */
|
#define VIRTIO_BLK_F_MQ 12 /* support more than one vq */
|
||||||
#define VIRTIO_F_ANY_LAYOUT 27
|
#define VIRTIO_F_ANY_LAYOUT 27
|
||||||
#define VIRTIO_RING_F_INDIRECT_DESC 28
|
#define VIRTIO_RING_F_INDIRECT_DESC 28
|
||||||
#define VIRTIO_RING_F_EVENT_IDX 29
|
#define VIRTIO_RING_F_EVENT_IDX 29
|
||||||
|
|
||||||
// this many virtio descriptors.
|
// this many virtio descriptors.
|
||||||
#define NUM 8
|
#define NUM 8
|
||||||
|
|||||||
@@ -37,8 +37,10 @@ And you should start your git-bash to build the project. (It works for me in vsc
|
|||||||
- Basic preemptive scheduling
|
- Basic preemptive scheduling
|
||||||
- [06-Spinlock](06-Spinlock)
|
- [06-Spinlock](06-Spinlock)
|
||||||
- Lock implementation for protec critical sections
|
- Lock implementation for protec critical sections
|
||||||
- [07-ExternInterrupt](07-ExternInterrupt)
|
- [07-ExterInterrupt](07-ExterInterrupt)
|
||||||
- Learing PLIC & external interruption
|
- Learing PLIC & external interruption
|
||||||
|
- [08-BlockDeviceDriver](08-BlockDeviceDriver)
|
||||||
|
- Learning VirtIO Protocol & Device driver implementation
|
||||||
|
|
||||||
## Building and Verification
|
## Building and Verification
|
||||||
|
|
||||||
|
|||||||
252
doc/tw/08-BlockDeviceDriver.md
Normal file
252
doc/tw/08-BlockDeviceDriver.md
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
# 08-BlockDeviceDriver -- RISC-V 的嵌入式作業系統
|
||||||
|
|
||||||
|
在實現外部中斷的機制以後,我們已經在先前的 Lab 中加入了 UART 的 ISR ,為了讓作業系統能夠讀取磁碟資料,我們必須加入 Virtio 的 ISR :
|
||||||
|
|
||||||
|
```c=
|
||||||
|
void external_handler()
|
||||||
|
{
|
||||||
|
int irq = plic_claim();
|
||||||
|
if (irq == UART0_IRQ)
|
||||||
|
{
|
||||||
|
lib_isr();
|
||||||
|
}
|
||||||
|
else if (irq == VIRTIO_IRQ)
|
||||||
|
{
|
||||||
|
virtio_disk_isr();
|
||||||
|
}
|
||||||
|
else if (irq)
|
||||||
|
{
|
||||||
|
lib_printf("unexpected interrupt irq = %d\n", irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (irq)
|
||||||
|
{
|
||||||
|
plic_complete(irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
當然,在開始之前我們仍需要認識一下 VirtIO 協定。
|
||||||
|
|
||||||
|
## 先備知識: Virtio
|
||||||
|
|
||||||
|
### Descriptor
|
||||||
|
|
||||||
|
Descriptor 包含這些訊息: 地址,地址長度,某些 flag 和其他信息。
|
||||||
|
使用 Descriptor ,我們可以將設備指向 RAM 中任何緩衝區的內存位址。
|
||||||
|
|
||||||
|
```c=
|
||||||
|
struct virtq_desc
|
||||||
|
{
|
||||||
|
uint64 addr;
|
||||||
|
uint32 len;
|
||||||
|
uint16 flags;
|
||||||
|
uint16 next;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- addr: 我們可以在 64-bit 內存地址內的任何位置告訴設備存儲位置。
|
||||||
|
- len: 讓 Device 知道有多少內存可用。
|
||||||
|
- flags: 用於控制 descriptor 。
|
||||||
|
- next: 告訴 Device 下一個描述符的 Index 。如果指定了 VIRTQ_DESC_F_NEXT, Device 僅讀取該字段。否則無效。
|
||||||
|
|
||||||
|
### AvailableRing
|
||||||
|
|
||||||
|
用來存放 Descriptor 的索引,當 Device 收到通知時,它會檢查 AvailableRing 確認需要讀取哪些 Descriptor 。
|
||||||
|
|
||||||
|
> 需要注意的是: Descriptor 和 AvailableRing 都存儲在 RAM 中。
|
||||||
|
|
||||||
|
```c=
|
||||||
|
struct virtq_avail
|
||||||
|
{
|
||||||
|
uint16 flags; // always zero
|
||||||
|
uint16 idx; // driver will write ring[idx] next
|
||||||
|
uint16 ring[NUM]; // descriptor numbers of chain heads
|
||||||
|
uint16 unused;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### UsedRing
|
||||||
|
|
||||||
|
UsedRing 讓 Device 能夠向 OS 發送訊息,因此, Device 通常使用它來告知 OS 它已完成先前通知的請求。
|
||||||
|
AvailableRing 與 UsedRing 非常相似,差別在於: OS 需要查看 UsedRing 得知哪個 Descriptor 已經被服務。
|
||||||
|
|
||||||
|
```c=
|
||||||
|
struct virtq_used_elem
|
||||||
|
{
|
||||||
|
uint32 id; // index of start of completed descriptor chain
|
||||||
|
uint32 len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct virtq_used
|
||||||
|
{
|
||||||
|
uint16 flags; // always zero
|
||||||
|
uint16 idx; // device increments when it adds a ring[] entry
|
||||||
|
struct virtq_used_elem ring[NUM];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 發送讀寫請求
|
||||||
|
|
||||||
|
為了節省閱讀 Virtio Spec 的時間,本次的 Block Device Driver 大量閱讀並參考了 xv6-riscv 的實作,不過在 xv6 的檔案系統實作中有非常多層:
|
||||||
|
|
||||||
|
```
|
||||||
|
+------------------+
|
||||||
|
| File descriptor |
|
||||||
|
+------------------+
|
||||||
|
| Pathname |
|
||||||
|
+------------------+
|
||||||
|
| Directory |
|
||||||
|
+------------------+
|
||||||
|
| Inode |
|
||||||
|
+------------------+
|
||||||
|
| Logging |
|
||||||
|
+------------------+
|
||||||
|
| Buffer cache |
|
||||||
|
+------------------+
|
||||||
|
| Disk |
|
||||||
|
+------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
為了精簡,我們會將 Buffer 抽離,完成 Device Driver 後會更方便我們在日後實現檔案系統。
|
||||||
|
|
||||||
|
### 指定寫入的 Sector
|
||||||
|
|
||||||
|
```c=
|
||||||
|
uint64_t sector = b->blockno * (BSIZE / 512);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分配 descriptor
|
||||||
|
|
||||||
|
因為 [qemu-virt](https://github.com/qemu/qemu/blob/master/hw/block/virtio-blk.c) 會一次讀取 3 個 descriptor ,所以在發送請求之前我們要先分配好這些空間。
|
||||||
|
|
||||||
|
```c=
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 發送 Block request
|
||||||
|
|
||||||
|
宣告 req 的結構:
|
||||||
|
|
||||||
|
```c=
|
||||||
|
struct virtio_blk_req *buf0 = &disk.ops[idx[0]];
|
||||||
|
```
|
||||||
|
|
||||||
|
因為磁碟有讀寫操作之分,為了讓 qemu 知道要讀還是要寫,我們要在請求中的 `type` 成員中寫入 **flag** :
|
||||||
|
|
||||||
|
```c=
|
||||||
|
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 64-bit sector field to the correct place.
|
||||||
|
buf0->sector = sector; // specify the sector that we wanna modified.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 填充 Descriptor
|
||||||
|
|
||||||
|
到了這一步,我們已經分配好 Descriptor 與 req 的基本資料了,接著我們可以對這三個 Descriptor 做資料填充:
|
||||||
|
|
||||||
|
```c=
|
||||||
|
disk.desc[idx[0]].addr = (uint64_t)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 = (uint64_t)b->data;
|
||||||
|
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 = 0xff; // device writes 0 on success
|
||||||
|
disk.desc[idx[2]].addr = (uint64_t)&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;
|
||||||
|
|
||||||
|
// 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 ...
|
||||||
|
|
||||||
|
__sync_synchronize();
|
||||||
|
|
||||||
|
*R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // value is queue number
|
||||||
|
|
||||||
|
// Wait for virtio_disk_intr() to say request has finished.
|
||||||
|
while (b->disk == 1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
disk.info[idx[0]].b = 0;
|
||||||
|
free_chain(idx[0]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 實作 VirtIO 的 ISR
|
||||||
|
|
||||||
|
```c=
|
||||||
|
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 buf *b = disk.info[id].b;
|
||||||
|
b->disk = 0; // disk is done with buf
|
||||||
|
wakeup(b);
|
||||||
|
|
||||||
|
disk.used_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock_free(&disk.vdisk_lock);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [xv6-riscv](https://github.com/mit-pdos/xv6-riscv)
|
||||||
|
- [Lecture: Virtual I/O Protocol Operating Systems Stephen Marz](https://web.eecs.utk.edu/~smarz1/courses/cosc361/notes/virtio/)
|
||||||
Reference in New Issue
Block a user