use crate::bus::VIRTIO_BASE;
use crate::cpu::{Cpu, BYTE, DOUBLEWORD, HALFWORD, WORD};
use crate::exception::Exception;
use std::ops::RangeInclusive;
pub const VIRTIO_IRQ: u64 = 1;
const VRING_DESC_SIZE: u64 = 16;
const QUEUE_SIZE: u64 = 8;
const SECTOR_SIZE: u64 = 512;
const VIRTQ_DESC_F_NEXT: u64 = 1;
const VIRTQ_DESC_F_WRITE: u64 = 2;
const _VIRTQ_DESC_F_INDIRECT: u64 = 4;
const MAGIC_RANGE: RangeInclusive<u64> = RangeInclusive::new(VIRTIO_BASE, VIRTIO_BASE + 0x3);
const VERSION_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x4, VIRTIO_BASE + 0x7);
const DEVICE_ID_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x8, VIRTIO_BASE + 0xb);
const VENDOR_ID_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0xc, VIRTIO_BASE + 0xf);
const DEVICE_FEATURES_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x10, VIRTIO_BASE + 0x13);
const DEVICE_FEATURES_SEL_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x14, VIRTIO_BASE + 0x17);
const DRIVER_FEATURES_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x20, VIRTIO_BASE + 0x23);
const DRIVER_FEATURES_SEL_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x24, VIRTIO_BASE + 0x27);
const GUEST_PAGE_SIZE_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x28, VIRTIO_BASE + 0x2b);
const QUEUE_SEL_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x30, VIRTIO_BASE + 0x33);
const QUEUE_NUM_MAX_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x34, VIRTIO_BASE + 0x37);
const QUEUE_NUM_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x38, VIRTIO_BASE + 0x3b);
const QUEUE_ALIGN_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x3c, VIRTIO_BASE + 0x3f);
const QUEUE_PFN_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x40, VIRTIO_BASE + 0x43);
const QUEUE_NOTIFY_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x50, VIRTIO_BASE + 0x53);
const INTERRUPT_STATUS_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x60, VIRTIO_BASE + 0x63);
const INTERRUPT_ACK_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x64, VIRTIO_BASE + 0x67);
const STATUS_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x70, VIRTIO_BASE + 0x73);
const CONFIG_RANGE: RangeInclusive<u64> =
RangeInclusive::new(VIRTIO_BASE + 0x100, VIRTIO_BASE + 0x107);
#[derive(Debug, Copy, Clone)]
struct VirtqueueAddr {
desc_addr: u64,
avail_addr: u64,
used_addr: u64,
}
impl VirtqueueAddr {
fn new(virtio: &Virtio) -> Self {
let base_addr = virtio.queue_pfn as u64 * virtio.guest_page_size as u64;
let align = virtio.queue_align as u64;
let size = virtio.queue_num as u64;
let avail_ring_end = base_addr + (16 * size) + (6 + 2 * size);
Self {
desc_addr: base_addr,
avail_addr: base_addr + 16 * size,
used_addr: (avail_ring_end.wrapping_div(align) + 1).wrapping_mul(align),
}
}
}
#[derive(Debug)]
struct VirtqDesc {
addr: u64,
len: u64,
flags: u64,
next: u64,
}
impl VirtqDesc {
fn new(cpu: &mut Cpu, addr: u64) -> Result<Self, Exception> {
Ok(Self {
addr: cpu.bus.read(addr, DOUBLEWORD)?,
len: cpu.bus.read(addr.wrapping_add(8), WORD)?,
flags: cpu.bus.read(addr.wrapping_add(12), HALFWORD)?,
next: cpu.bus.read(addr.wrapping_add(14), HALFWORD)?,
})
}
}
#[derive(Debug)]
struct VirtqAvail {
flags: u16,
idx: u16,
ring_start_addr: u64,
}
impl VirtqAvail {
fn new(cpu: &mut Cpu, addr: u64) -> Result<Self, Exception> {
Ok(Self {
flags: cpu.bus.read(addr, HALFWORD)? as u16,
idx: cpu.bus.read(addr.wrapping_add(2), HALFWORD)? as u16,
ring_start_addr: addr.wrapping_add(4),
})
}
}
struct _VirtqUsed {
flags: u16,
idx: u16,
ring: Vec<_VirtqUsedElem>,
}
struct _VirtqUsedElem {
id: u32,
len: u32,
}
pub struct Virtio {
id: u64,
device_features: [u32; 2],
device_features_sel: u32,
driver_features: [u32; 2],
driver_features_sel: u32,
guest_page_size: u32,
queue_num: u32,
queue_align: u32,
queue_pfn: u32,
queue_notify: u32,
interrupt_status: u32,
status: u32,
config: [u8; 8],
disk: Vec<u8>,
virtqueue: Option<VirtqueueAddr>,
}
impl Virtio {
pub fn new() -> Self {
let mut config = [0; 8];
config[1] = 0x20;
config[2] = 0x03;
Self {
id: 0,
device_features: Virtio::device_features(),
device_features_sel: 0,
driver_features: [0; 2],
driver_features_sel: 0,
guest_page_size: 0,
queue_num: 0,
queue_align: 0x1000,
queue_pfn: 0,
queue_notify: u32::MAX,
interrupt_status: 0,
status: 0,
config,
disk: Vec::new(),
virtqueue: None,
}
}
fn device_features() -> [u32; 2] {
let mut features = [0; 2];
features[1] = features[1] | (1 << 3);
return features;
}
fn init_virtqueue(&mut self) {
let queue = VirtqueueAddr::new(self);
self.virtqueue = Some(queue);
}
fn virtqueue(&self) -> VirtqueueAddr {
match self.virtqueue {
Some(queue) => queue,
None => VirtqueueAddr::new(self),
}
}
fn reset(&mut self) {
self.id = 0;
self.interrupt_status = 0;
}
pub fn is_interrupting(&mut self) -> bool {
if self.queue_notify != u32::MAX {
self.queue_notify = u32::MAX;
return true;
}
false
}
pub fn initialize(&mut self, binary: Vec<u8>) {
self.disk.extend(binary.iter().cloned());
}
pub fn read(&self, addr: u64, size: u8) -> Result<u64, Exception> {
let (reg, offset) = match addr {
addr if MAGIC_RANGE.contains(&addr) => (0x74726976, addr - MAGIC_RANGE.start()),
addr if VERSION_RANGE.contains(&addr) => (0x1, addr - VERSION_RANGE.start()),
addr if DEVICE_ID_RANGE.contains(&addr) => (0x2, addr - DEVICE_ID_RANGE.start()),
addr if VENDOR_ID_RANGE.contains(&addr) => (0x554d4551, addr - VENDOR_ID_RANGE.start()),
addr if DEVICE_FEATURES_RANGE.contains(&addr) => (
self.device_features[self.device_features_sel as usize],
addr - DEVICE_FEATURES_RANGE.start(),
),
addr if QUEUE_NUM_MAX_RANGE.contains(&addr) => {
(QUEUE_SIZE as u32, addr - QUEUE_NUM_MAX_RANGE.start())
}
addr if QUEUE_PFN_RANGE.contains(&addr) => {
(self.queue_pfn, addr - QUEUE_PFN_RANGE.start())
}
addr if INTERRUPT_STATUS_RANGE.contains(&addr) => {
(self.interrupt_status, addr - INTERRUPT_STATUS_RANGE.start())
}
addr if STATUS_RANGE.contains(&addr) => (self.status, addr - STATUS_RANGE.start()),
addr if CONFIG_RANGE.contains(&addr) => {
if size != BYTE {
return Err(Exception::StoreAMOAccessFault);
}
let index = addr - CONFIG_RANGE.start();
(self.config[index as usize] as u32, 0)
}
_ => return Err(Exception::LoadAccessFault),
};
let value = match size {
BYTE => (reg >> (offset * 8)) & 0xff,
HALFWORD => (reg >> (offset * 8)) & 0xffff,
WORD => (reg >> (offset * 8)) & 0xffffffff,
_ => return Err(Exception::LoadAccessFault),
};
println!(
"[read] virtio block: addr {:#x} size {} reg {:#x}",
addr, size, reg
);
Ok(value as u64)
}
pub fn write(&mut self, addr: u64, value: u32, size: u8) -> Result<(), Exception> {
let (mut reg, offset) = match addr {
addr if DEVICE_FEATURES_SEL_RANGE.contains(&addr) => (
self.device_features_sel,
addr - DEVICE_FEATURES_SEL_RANGE.start(),
),
addr if DRIVER_FEATURES_RANGE.contains(&addr) => (
self.driver_features[self.driver_features_sel as usize],
addr - DRIVER_FEATURES_RANGE.start(),
),
addr if DRIVER_FEATURES_SEL_RANGE.contains(&addr) => (
self.driver_features_sel,
addr - DRIVER_FEATURES_SEL_RANGE.start(),
),
addr if GUEST_PAGE_SIZE_RANGE.contains(&addr) => {
(self.guest_page_size, addr - GUEST_PAGE_SIZE_RANGE.start())
}
addr if QUEUE_SEL_RANGE.contains(&addr) => {
if value != 0 {
panic!("Multiple virtual queues are not supported.");
}
return Ok(());
}
addr if QUEUE_NUM_RANGE.contains(&addr) => {
(self.queue_num, addr - QUEUE_NUM_RANGE.start())
}
addr if QUEUE_ALIGN_RANGE.contains(&addr) => {
(self.queue_align, addr - QUEUE_ALIGN_RANGE.start())
}
addr if QUEUE_PFN_RANGE.contains(&addr) => {
(self.queue_pfn, addr - QUEUE_PFN_RANGE.start())
}
addr if QUEUE_NOTIFY_RANGE.contains(&addr) => {
(self.queue_notify, addr - QUEUE_NOTIFY_RANGE.start())
}
addr if INTERRUPT_ACK_RANGE.contains(&addr) => {
(self.interrupt_status, addr - INTERRUPT_ACK_RANGE.start())
}
addr if STATUS_RANGE.contains(&addr) => (self.status, addr - STATUS_RANGE.start()),
addr if CONFIG_RANGE.contains(&addr) => {
if size != BYTE {
return Err(Exception::StoreAMOAccessFault);
}
let index = addr - CONFIG_RANGE.start();
self.config[index as usize] = (value >> (index * 8)) as u8;
return Ok(());
}
_ => return Err(Exception::StoreAMOAccessFault),
};
match size {
BYTE => {
reg = reg & (!(0xff << (offset * 8)));
reg = reg | ((value & 0xff) << (offset * 8));
}
HALFWORD => {
reg = reg & (!(0xffff << (offset * 8)));
reg = reg | ((value & 0xffff) << (offset * 8));
}
WORD => {
reg = value;
}
_ => return Err(Exception::StoreAMOAccessFault),
}
match addr {
addr if DEVICE_FEATURES_SEL_RANGE.contains(&addr) => self.device_features_sel = reg,
addr if DRIVER_FEATURES_RANGE.contains(&addr) => {
self.driver_features[self.driver_features_sel as usize] = reg
}
addr if DRIVER_FEATURES_SEL_RANGE.contains(&addr) => self.driver_features_sel = reg,
addr if GUEST_PAGE_SIZE_RANGE.contains(&addr) => self.guest_page_size = reg,
addr if QUEUE_NUM_RANGE.contains(&addr) => self.queue_num = reg,
addr if QUEUE_ALIGN_RANGE.contains(&addr) => self.queue_align = reg,
addr if QUEUE_PFN_RANGE.contains(&addr) => self.queue_pfn = reg,
addr if QUEUE_NOTIFY_RANGE.contains(&addr) => self.queue_notify = reg,
addr if INTERRUPT_ACK_RANGE.contains(&addr) => self.interrupt_status = reg,
addr if STATUS_RANGE.contains(&addr) => {
self.status = reg;
if self.status == 0 {
self.reset();
}
if self.status & 0x4 == 1 {
self.init_virtqueue();
}
if (self.status & 128) == 1 {
panic!("virtio: device status FAILED");
}
}
_ => return Err(Exception::StoreAMOAccessFault),
}
println!(
"[write] virtio block: addr {:#x} size {} reg {:#x} value {:#x}",
addr, size, reg, value
);
Ok(())
}
fn read_disk(&self, addr: u64) -> u64 {
self.disk[addr as usize] as u64
}
fn write_disk(&mut self, addr: u64, value: u64) {
self.disk[addr as usize] = value as u8
}
pub fn disk_access(cpu: &mut Cpu) -> Result<(), Exception> {
println!("disk_access !!!!!!!!!!!!!!!!!!!!");
cpu.bus.virtio.interrupt_status |= 0x1;
let virtq = cpu.bus.virtio.virtqueue();
println!("virtqueue: {:?}", virtq);
let avail = VirtqAvail::new(cpu, virtq.avail_addr)?;
println!("available ring: {:?}", avail);
let head_index = cpu.bus.read(
avail.ring_start_addr + avail.idx as u64 % QUEUE_SIZE,
HALFWORD,
)?;
let desc0 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * head_index)?;
assert_eq!(desc0.flags & VIRTQ_DESC_F_NEXT, 1);
let desc1 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * desc0.next)?;
assert_eq!(desc1.flags & VIRTQ_DESC_F_NEXT, 1);
let sector = cpu.bus.read(desc0.addr.wrapping_add(8), DOUBLEWORD)?;
match (desc1.flags & VIRTQ_DESC_F_WRITE) == 0 {
true => {
for i in 0..desc1.len {
let data = cpu.bus.read(desc1.addr + i, BYTE)?;
cpu.bus.virtio.write_disk(sector * SECTOR_SIZE + i, data);
}
}
false => {
for i in 0..desc1.len {
let data = cpu.bus.virtio.read_disk(sector * SECTOR_SIZE + i);
cpu.bus.write(desc1.addr + i, data, BYTE)?;
}
}
};
let desc2 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * desc1.next)?;
assert_eq!(desc2.flags & VIRTQ_DESC_F_NEXT, 0);
cpu.bus.write(desc2.addr, 0, BYTE)?;
cpu.bus.write(
virtq
.used_addr
.wrapping_add(4)
.wrapping_add((cpu.bus.virtio.id as u64 % QUEUE_SIZE) * 8),
head_index,
WORD,
)?;
cpu.bus.virtio.id = cpu.bus.virtio.id.wrapping_add(1);
cpu.bus
.write(virtq.used_addr.wrapping_add(2), cpu.bus.virtio.id, HALFWORD)?;
println!("virtio disk_access finishes!");
Ok(())
}
}