diff --git a/src/bus.rs b/src/bus.rs index 33ce2bf..23e9169 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,104 +1,114 @@ //! The bus module contains the system bus which can access the memroy or memory-mapped peripheral //! devices. -use crate::devices::{clint::Clint, plic::Plic, uart::Uart, virtio_blk::Virtio}; -use crate::dram::{Dram, DRAM_SIZE}; +use crate::devices::clint::Clint; +use crate::devices::plic::Plic; use crate::exception::Exception; -use crate::rom::Rom; +use std::sync::{Arc, Mutex}; -// QEMU virt machine: -// https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c#L46-L63 - -/// The address which the mask ROM starts. -pub const MROM_BASE: u64 = 0x1000; -/// The address which the mask ROM ends. -const MROM_END: u64 = MROM_BASE + 0xf000; +use std::ops::RangeInclusive; /// The address which the core-local interruptor (CLINT) starts. It contains the timer and generates /// per-hart software interrupts and timer interrupts. pub const CLINT_BASE: u64 = 0x200_0000; -/// The address which the core-local interruptor (CLINT) ends. -const CLINT_END: u64 = CLINT_BASE + 0x10000; /// The address which the platform-level interrupt controller (PLIC) starts. The PLIC connects all /// external interrupts in the system to all hart contexts in the system, via the external interrupt /// source in each hart. pub const PLIC_BASE: u64 = 0xc00_0000; -/// The address which the platform-level interrupt controller (PLIC) ends. -const PLIC_END: u64 = PLIC_BASE + 0x208000; - -/// The address which UART starts. QEMU puts UART registers here in physical memory. -pub const UART_BASE: u64 = 0x1000_0000; -/// The size of UART. -pub const UART_SIZE: u64 = 0x100; -/// The address which UART ends. -const UART_END: u64 = UART_BASE + 0x100; - -/// The address which virtio starts. -pub const VIRTIO_BASE: u64 = 0x1000_1000; -/// The address which virtio ends. -const VIRTIO_END: u64 = VIRTIO_BASE + 0x1000; - -/// The address which DRAM starts. -pub const DRAM_BASE: u64 = 0x8000_0000; -/// The address which DRAM ends. -const DRAM_END: u64 = DRAM_BASE + DRAM_SIZE; + +pub trait Device { + /// Load `size`-bit data from the memory. + fn read(&mut self, addr: u64, size: u8) -> Result; + /// Store `size`-bit data to the memory. + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception>; + + fn size(&self) -> u64; + + fn is_interrupting(&mut self) -> bool; + + fn irq(&self) -> Option; + + fn reset(&mut self); +} /// The system bus. pub struct Bus { + devices: Vec<(RangeInclusive, Arc>)>, + pub clint: Clint, pub plic: Plic, - pub uart: Uart, - pub virtio: Virtio, - dram: Dram, - pub rom: Rom, + clint_addr_range: RangeInclusive, + plic_addr_range: RangeInclusive, } impl Bus { /// Create a new bus object. pub fn new() -> Bus { + let clint = Clint::new(); + let plic = Plic::new(); Self { - clint: Clint::new(), - plic: Plic::new(), - uart: Uart::new(), - virtio: Virtio::new(), - dram: Dram::new(), - rom: Rom::new(), + devices: vec![], + clint_addr_range: (CLINT_BASE..=(CLINT_BASE + clint.size())), + plic_addr_range: (PLIC_BASE..=(PLIC_BASE + plic.size())), + clint: clint, + plic: plic, } } - /// Set the binary data to the memory. - pub fn initialize_dram(&mut self, data: Vec) { - self.dram.initialize(data); + pub fn is_interrupting(&mut self) -> bool { + let mut saw_interrupt = false; + for (_, device) in self.devices.iter_mut() { + let mut device = device.lock().unwrap(); + if device.is_interrupting() { + if let Some(irq) = device.irq() { + self.plic.update_pending(irq); + saw_interrupt = true; + } + } + } + saw_interrupt } - /// Set the binary data to the virtIO disk. - pub fn initialize_disk(&mut self, data: Vec) { - self.virtio.initialize(data); + pub fn mount(&mut self, memory_start: u64, device: Arc>) { + let size = device.lock().unwrap().size(); + let addr_range = memory_start..=(memory_start + size); + self.devices.push((addr_range, device)); } /// Load a `size`-bit data from the device that connects to the system bus. pub fn read(&mut self, addr: u64, size: u8) -> Result { - match addr { - MROM_BASE..=MROM_END => self.rom.read(addr, size), - CLINT_BASE..=CLINT_END => self.clint.read(addr, size), - PLIC_BASE..=PLIC_END => self.plic.read(addr, size), - UART_BASE..=UART_END => self.uart.read(addr, size), - VIRTIO_BASE..=VIRTIO_END => self.virtio.read(addr, size), - DRAM_BASE..=DRAM_END => self.dram.read(addr, size), - _ => Err(Exception::LoadAccessFault), + if self.clint_addr_range.contains(&addr) { + self.clint.read(addr - self.clint_addr_range.start(), size) + } else if self.plic_addr_range.contains(&addr) { + self.plic.read(addr - self.plic_addr_range.start(), size) + } else { + for (addr_range, device) in self.devices.iter_mut() { + if addr_range.contains(&addr) { + let mut device = device.lock().unwrap(); + return device.read(addr - addr_range.start(), size); + } + } + Err(Exception::LoadAccessFault) } } /// Store a `size`-bit data to the device that connects to the system bus. pub fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { - match addr { - CLINT_BASE..=CLINT_END => self.clint.write(addr, value, size), - PLIC_BASE..=PLIC_END => self.plic.write(addr, value, size), - UART_BASE..=UART_END => self.uart.write(addr, value as u8, size), - VIRTIO_BASE..=VIRTIO_END => self.virtio.write(addr, value as u32, size), - DRAM_BASE..=DRAM_END => self.dram.write(addr, value, size), - _ => Err(Exception::StoreAMOAccessFault), + if self.clint_addr_range.contains(&addr) { + self.clint + .write(addr - self.clint_addr_range.start(), value, size) + } else if self.plic_addr_range.contains(&addr) { + self.plic + .write(addr - self.plic_addr_range.start(), value, size) + } else { + for (addr_range, device) in self.devices.iter_mut() { + if addr_range.contains(&addr) { + let mut device = device.lock().unwrap(); + return device.write(addr - addr_range.start(), value, size); + } + } + Err(Exception::StoreAMOAccessFault) } } } diff --git a/src/cpu.rs b/src/cpu.rs index 759dc40..efe8062 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -6,17 +6,73 @@ use std::collections::BTreeMap; use std::fmt; use std::num::FpCategory; -use crate::{ - bus::{Bus, DRAM_BASE}, - csr::*, - devices::{ - uart::UART_IRQ, - virtio_blk::{Virtio, VIRTIO_IRQ}, - }, - dram::DRAM_SIZE, - exception::Exception, - interrupt::Interrupt, -}; +use crate::{bus::Bus, csr::*, exception::Exception, interrupt::Interrupt}; + +pub const REG_FT0: u64 = 0; +pub const REG_FT1: u64 = 1; +pub const REG_FT2: u64 = 2; +pub const REG_FT3: u64 = 3; +pub const REG_FT4: u64 = 4; +pub const REG_FT5: u64 = 5; +pub const REG_FT6: u64 = 6; +pub const REG_FT7: u64 = 7; +pub const REG_FS0: u64 = 8; +pub const REG_FS1: u64 = 9; +pub const REG_FA0: u64 = 10; +pub const REG_FA1: u64 = 11; +pub const REG_FA2: u64 = 12; +pub const REG_FA3: u64 = 13; +pub const REG_FA4: u64 = 14; +pub const REG_FA5: u64 = 15; +pub const REG_FA6: u64 = 16; +pub const REG_FA7: u64 = 17; +pub const REG_FS2: u64 = 18; +pub const REG_FS3: u64 = 19; +pub const REG_FS4: u64 = 20; +pub const REG_FS5: u64 = 21; +pub const REG_FS6: u64 = 22; +pub const REG_FS7: u64 = 23; +pub const REG_FS8: u64 = 24; +pub const REG_FS9: u64 = 25; +pub const REG_FS10: u64 = 26; +pub const REG_FS11: u64 = 27; +pub const REG_FT8: u64 = 28; +pub const REG_FT9: u64 = 29; +pub const REG_FT10: u64 = 30; +pub const REG_FT11: u64 = 31; + +pub const REG_ZERO: u64 = 0; +pub const REG_RA: u64 = 1; +pub const REG_SP: u64 = 2; +pub const REG_GP: u64 = 3; +pub const REG_TP: u64 = 4; +pub const REG_T0: u64 = 5; +pub const REG_T1: u64 = 6; +pub const REG_T2: u64 = 7; +pub const REG_S0: u64 = 8; +pub const REG_S1: u64 = 9; +pub const REG_A0: u64 = 10; +pub const REG_A1: u64 = 11; +pub const REG_A2: u64 = 12; +pub const REG_A3: u64 = 13; +pub const REG_A4: u64 = 14; +pub const REG_A5: u64 = 15; +pub const REG_A6: u64 = 16; +pub const REG_A7: u64 = 17; +pub const REG_S2: u64 = 18; +pub const REG_S3: u64 = 19; +pub const REG_S4: u64 = 20; +pub const REG_S5: u64 = 21; +pub const REG_S6: u64 = 22; +pub const REG_S7: u64 = 23; +pub const REG_S8: u64 = 24; +pub const REG_S9: u64 = 25; +pub const REG_S10: u64 = 26; +pub const REG_S11: u64 = 27; +pub const REG_T3: u64 = 28; +pub const REG_T4: u64 = 29; +pub const REG_T5: u64 = 30; +pub const REG_T6: u64 = 31; /// The number of registers. pub const REGISTERS_COUNT: usize = 32; @@ -75,8 +131,6 @@ impl XRegisters { /// Create a new `XRegisters` object. pub fn new() -> Self { let mut xregs = [0; REGISTERS_COUNT]; - // The stack pointer is set in the default maximum memory size + the start address of dram. - xregs[2] = DRAM_BASE + DRAM_SIZE; // From riscv-pk: // https://github.com/riscv/riscv-pk/blob/master/machine/mentry.S#L233-L235 // save a0 and a1; arguments from previous boot loader stage: @@ -209,6 +263,25 @@ impl fmt::Display for FRegisters { } } +pub struct ExecutedOp { + pub opcode: u64, + pub pc: u64, +} + +pub enum ExecCycle { + Idle, + Opcode(ExecutedOp), +} + +impl ExecutedOp { + pub fn new(opcode: u64, pc: u64) -> ExecutedOp { + ExecutedOp { + opcode: opcode, + pc: pc, + } + } +} + /// The CPU to contain registers, a program counter, status, and a privileged mode. pub struct Cpu { /// 64-bit integer registers. @@ -342,22 +415,9 @@ impl Cpu { // TODO: Take interrupts based on priorities. - // Check external interrupt for uart and virtio. - let irq; - if self.bus.uart.is_interrupting() { - irq = UART_IRQ; - } else if self.bus.virtio.is_interrupting() { - // An interrupt is raised after a disk access is done. - Virtio::disk_access(self).expect("failed to access the disk"); - irq = VIRTIO_IRQ; - } else { - irq = 0; - } - - if irq != 0 { + if self.bus.is_interrupting() { // TODO: assume that hart is 0 // TODO: write a value to MCLAIM if the mode is machine - self.bus.plic.update_pending(irq); self.state.write(MIP, self.state.read(MIP) | SEIP_BIT); } @@ -664,15 +724,16 @@ impl Cpu { /// Execute an instruction. Raises an exception if something is wrong, otherwise, returns /// the instruction executed in this cycle. - pub fn execute(&mut self) -> Result { + pub fn execute(&mut self) -> Result { // WFI is called and pending interrupts don't exist. if self.idle { - return Ok(0); + return Ok(ExecCycle::Idle); } // Fetch. let inst16 = self.fetch(HALFWORD)?; let inst; + let prev_pc = self.pc; match inst16 & 0b11 { 0 | 1 | 2 => { if inst16 == 0 { @@ -692,7 +753,7 @@ impl Cpu { } } self.pre_inst = inst; - Ok(inst) + Ok(ExecCycle::Opcode(ExecutedOp::new(inst, prev_pc))) } /// Execute a compressed instruction. Raised an exception if something is wrong, otherwise, diff --git a/src/csr.rs b/src/csr.rs index a2634ef..04bb394 100644 --- a/src/csr.rs +++ b/src/csr.rs @@ -37,7 +37,7 @@ pub const FCSR: CsrAddress = 0x003; // User Counter/Timers. /// Timer for RDTIME instruction. -const TIME: CsrAddress = 0xc01; +pub const TIME: CsrAddress = 0xc01; ///////////////////////////////////// // Supervisor-level CSR addresses // @@ -180,9 +180,9 @@ impl fmt::Display for State { f, "{}", format!( - "{}\n{}\n{}", + "{}{}{}", format!( - "mstatus={:>#18x} mtvec={:>#18x} mepc={:>#18x}\n mcause={:>#18x} medeleg={:>#18x} mideleg={:>#18x}", + "mstatus={:>#18x} mtvec={:>#18x} mepc={:>#18x} mcause={:>#18x}\nmedeleg={:>#18x} mideleg={:>#18x}", self.read(MSTATUS), self.read(MTVEC), self.read(MEPC), @@ -191,7 +191,7 @@ impl fmt::Display for State { self.read(MIDELEG), ), format!( - "sstatus={:>#18x} stvec={:>#18x} sepc={:>#18x}\n scause={:>#18x} sedeleg={:>#18x} sideleg={:>#18x}", + "sstatus={:>#18x} stvec={:>#18x}\n sepc={:>#18x} scause={:>#18x} sedeleg={:>#18x} sideleg={:>#18x}\n", self.read(SSTATUS), self.read(STVEC), self.read(SEPC), @@ -200,11 +200,12 @@ impl fmt::Display for State { self.read(SIDELEG), ), format!( - "ustatus={:>#18x} utvec={:>#18x} uepc={:>#18x}\n ucause={:>#18x}", + "ustatus={:>#18x} utvec={:>#18x} uepc={:>#18x} ucause={:>#18x}\n time={:>#18x}", self.read(USTATUS), self.read(UTVEC), self.read(UEPC), self.read(UCAUSE), + self.read(TIME), ), ) ) diff --git a/src/devices/clint.rs b/src/devices/clint.rs index 14ee554..f5dd38c 100644 --- a/src/devices/clint.rs +++ b/src/devices/clint.rs @@ -12,26 +12,27 @@ // - https://github.com/qemu/qemu/blob/master/hw/intc/sifive_clint.c // - https://github.com/qemu/qemu/blob/master/include/hw/intc/sifive_clint.h -use crate::bus::CLINT_BASE; use crate::cpu::{BYTE, DOUBLEWORD, HALFWORD, WORD}; use crate::csr::{State, MIP, MSIP_BIT, MTIP_BIT}; use crate::exception::Exception; +use crate::bus::Device; + /// The address that a msip register starts. A msip is a machine mode software interrupt pending /// register, used to assert a software interrupt for a CPU. -const MSIP: u64 = CLINT_BASE; +const MSIP: u64 = 0; /// The address that a msip register ends. `msip` is a 4-byte register. const MSIP_END: u64 = MSIP + 0x4; /// The address that a mtimecmp register starts. A mtimecmp is a memory mapped machine mode timer /// compare register, used to trigger an interrupt when mtimecmp is greater than or equal to mtime. -const MTIMECMP: u64 = CLINT_BASE + 0x4000; +const MTIMECMP: u64 = 0x4000; /// The address that a mtimecmp register ends. `mtimecmp` is a 8-byte register. const MTIMECMP_END: u64 = MTIMECMP + 0x8; /// The address that a timer register starts. A mtime is a machine mode timer register which runs /// at a constant frequency. -const MTIME: u64 = CLINT_BASE + 0xbff8; +const MTIME: u64 = 0xbff8; /// The address that a timer register ends. `mtime` is a 8-byte register. const MTIME_END: u64 = MTIME + 0x8; @@ -88,9 +89,24 @@ impl Clint { state.write(MIP, state.read(MIP) | MTIP_BIT); } } +} +impl Device for Clint { + fn size(&self) -> u64 { + 0x10000 + } + + fn is_interrupting(&mut self) -> bool { + false + } + + fn irq(&self) -> Option { + None + } + + fn reset(&mut self) {} /// Load `size`-bit data from a register located at `addr` in CLINT. - pub fn read(&self, addr: u64, size: u8) -> Result { + fn read(&mut self, addr: u64, size: u8) -> Result { // `reg` is the value of a target register in CLINT and `offset` is the byte of the start // position in the register. let (reg, offset) = match addr { @@ -110,7 +126,7 @@ impl Clint { } /// Store `size`-bit data to a register located at `addr` in CLINT. - pub fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { // `reg` is the value of a target register in CLINT and `offset` is the byte of the start // position in the register. let (mut reg, offset) = match addr { diff --git a/src/dram.rs b/src/devices/dram.rs similarity index 78% rename from src/dram.rs rename to src/devices/dram.rs index f5673a2..9554db6 100644 --- a/src/dram.rs +++ b/src/devices/dram.rs @@ -1,47 +1,43 @@ //! The memory module contains the memory structure and implementation to read/write the memory. -use crate::bus::DRAM_BASE; +use crate::bus::Device; use crate::cpu::{BYTE, DOUBLEWORD, HALFWORD, WORD}; use crate::exception::Exception; -/// Default memory size (1GiB). -pub const DRAM_SIZE: u64 = 1024 * 1024 * 1024; - /// The memory used by the emulator. #[derive(Debug)] pub struct Dram { pub dram: Vec, - code_size: u64, } -impl Dram { - /// Create a new memory object with default memory size. - pub fn new() -> Self { - Self { - dram: vec![0; DRAM_SIZE as usize], - code_size: 0, - } +impl Device for Dram { + fn size(&self) -> u64 { + self.dram.len() as u64 } - /// Set the binary in the memory. - pub fn initialize(&mut self, binary: Vec) { - self.code_size = binary.len() as u64; - self.dram.splice(..binary.len(), binary.iter().cloned()); + fn is_interrupting(&mut self) -> bool { + false + } + + fn irq(&self) -> Option { + None } + fn reset(&mut self) {} + /// Load `size`-bit data from the memory. - pub fn read(&self, addr: u64, size: u8) -> Result { + fn read(&mut self, addr: u64, size: u8) -> Result { match size { BYTE => Ok(self.read8(addr)), HALFWORD => Ok(self.read16(addr)), WORD => Ok(self.read32(addr)), DOUBLEWORD => Ok(self.read64(addr)), - _ => return Err(Exception::LoadAccessFault), + _ => Err(Exception::LoadAccessFault), } } /// Store `size`-bit data to the memory. - pub fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { match size { BYTE => self.write8(addr, value), HALFWORD => self.write16(addr, value), @@ -51,23 +47,37 @@ impl Dram { } Ok(()) } +} + +impl Dram { + /// Create a new memory object + pub fn new(size: u64) -> Self { + Self { + dram: vec![0; size as usize], + } + } + + /// Set the binary in the memory. + pub fn initialize(&mut self, binary: Vec) { + self.dram.splice(..binary.len(), binary.iter().cloned()); + } /// Write a byte to the memory. fn write8(&mut self, addr: u64, val: u64) { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] = val as u8 } /// Write 2 bytes to the memory with little endian. fn write16(&mut self, addr: u64, val: u64) { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] = (val & 0xff) as u8; self.dram[index + 1] = ((val >> 8) & 0xff) as u8; } /// Write 4 bytes to the memory with little endian. fn write32(&mut self, addr: u64, val: u64) { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] = (val & 0xff) as u8; self.dram[index + 1] = ((val >> 8) & 0xff) as u8; self.dram[index + 2] = ((val >> 16) & 0xff) as u8; @@ -76,7 +86,7 @@ impl Dram { /// Write 8 bytes to the memory with little endian. fn write64(&mut self, addr: u64, val: u64) { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] = (val & 0xff) as u8; self.dram[index + 1] = ((val >> 8) & 0xff) as u8; self.dram[index + 2] = ((val >> 16) & 0xff) as u8; @@ -89,19 +99,19 @@ impl Dram { /// Read a byte from the memory. fn read8(&self, addr: u64) -> u64 { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; self.dram[index] as u64 } /// Read 2 bytes from the memory with little endian. fn read16(&self, addr: u64) -> u64 { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; return (self.dram[index] as u64) | ((self.dram[index + 1] as u64) << 8); } /// Read 4 bytes from the memory with little endian. fn read32(&self, addr: u64) -> u64 { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; return (self.dram[index] as u64) | ((self.dram[index + 1] as u64) << 8) | ((self.dram[index + 2] as u64) << 16) @@ -110,7 +120,7 @@ impl Dram { /// Read 8 bytes from the memory with little endian. fn read64(&self, addr: u64) -> u64 { - let index = (addr - DRAM_BASE) as usize; + let index = addr as usize; return (self.dram[index] as u64) | ((self.dram[index + 1] as u64) << 8) | ((self.dram[index + 2] as u64) << 16) diff --git a/src/devices/mod.rs b/src/devices/mod.rs index b5f391a..42239bf 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,7 +1,9 @@ //! The devices module contains peripheral devices. pub mod clint; +pub mod dram; pub mod plic; +pub mod rom; pub mod virtio_blk; #[cfg(not(target_arch = "wasm32"))] diff --git a/src/devices/plic.rs b/src/devices/plic.rs index 2d0ee44..f107637 100644 --- a/src/devices/plic.rs +++ b/src/devices/plic.rs @@ -12,23 +12,24 @@ // - https://github.com/qemu/qemu/blob/master/hw/intc/sifive_plic.c // - https://github.com/qemu/qemu/blob/master/include/hw/intc/sifive_plic.h -use crate::bus::PLIC_BASE; use crate::cpu::WORD; use crate::exception::Exception; +use crate::bus::Device; + /// The address for interrupt source priority. 1024 4-byte registers exist. Each interrupt into the /// PLIC has a configurable priority, from 1-7, with 7 being the highest priority. A value of 0 /// means do not interrupt, effectively disabling that interrupt. -const SOURCE_PRIORITY: u64 = PLIC_BASE; -const SOURCE_PRIORITY_END: u64 = PLIC_BASE + 0xfff; +const SOURCE_PRIORITY: u64 = 0; +const SOURCE_PRIORITY_END: u64 = 0 + 0xfff; /// The address range for interrupt pending bits. 32 4-byte (1024 bits) registers exist. /// /// https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc#memory-map /// base + 0x001000: Interrupt Pending bit 0-31 /// base + 0x00107C: Interrupt Pending bit 992-1023 -const PENDING: u64 = PLIC_BASE + 0x1000; -const PENDING_END: u64 = PLIC_BASE + 0x107f; +const PENDING: u64 = 0 + 0x1000; +const PENDING_END: u64 = 0 + 0x107f; /// The address range for enable registers. The maximum number of contexts is 15871 but this PLIC /// supports only 2 contexts. @@ -42,8 +43,8 @@ const PENDING_END: u64 = PLIC_BASE + 0x107f; /// base + 0x002084: Enable bits for sources 32-63 on context 1 /// ... /// base + 0x0020FF: Enable bits for sources 992-1023 on context 1 -const ENABLE: u64 = PLIC_BASE + 0x2000; -const ENABLE_END: u64 = PLIC_BASE + 0x20ff; +const ENABLE: u64 = 0 + 0x2000; +const ENABLE_END: u64 = 0 + 0x20ff; /// The address range for priority thresholds and claim/complete registers. The maximum number of /// contexts is 15871 but this PLIC supports only 2 contexts. @@ -56,8 +57,8 @@ const ENABLE_END: u64 = PLIC_BASE + 0x20ff; /// base + 0x200FFC: Reserved /// base + 0x201000: Priority threshold for context 1 /// base + 0x201004: Claim/complete for context 1 -const THRESHOLD_AND_CLAIM: u64 = PLIC_BASE + 0x200000; -const THRESHOLD_AND_CLAIM_END: u64 = PLIC_BASE + 0x201007; +const THRESHOLD_AND_CLAIM: u64 = 0 + 0x200000; +const THRESHOLD_AND_CLAIM_END: u64 = 0 + 0x201007; const WORD_SIZE: u64 = 0x4; const CONTEXT_OFFSET: u64 = 0x1000; @@ -125,9 +126,25 @@ impl Plic { let offset = (irq.wrapping_rem(SOURCE_NUM)).wrapping_rem(WORD_SIZE * 8); return ((self.enable[(context * 32 + index) as usize] >> offset) & 1) == 1; } +} + +impl Device for Plic { + fn size(&self) -> u64 { + 0x208000 + } + + fn is_interrupting(&mut self) -> bool { + false + } + + fn irq(&self) -> Option { + None + } + + fn reset(&mut self) {} /// Load `size`-bit data from a register located at `addr` in PLIC. - pub fn read(&self, addr: u64, size: u8) -> Result { + fn read(&mut self, addr: u64, size: u8) -> Result { // TODO: should support byte-base access. if size != WORD { return Err(Exception::LoadAccessFault); @@ -171,7 +188,7 @@ impl Plic { } /// Store `size`-bit data to a register located at `addr` in PLIC. - pub fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { // TODO: should support byte-base access. if size != WORD { return Err(Exception::StoreAMOAccessFault); diff --git a/src/rom.rs b/src/devices/rom.rs similarity index 92% rename from src/rom.rs rename to src/devices/rom.rs index 3542f53..48c331b 100644 --- a/src/rom.rs +++ b/src/devices/rom.rs @@ -1,6 +1,6 @@ //! The rom module contains the read-only memory structure and implementation to read the memory. ROM includes a device tree blob (DTB) compiled from a device tree source (DTS). -use crate::bus::MROM_BASE; +use crate::bus::Device; use crate::cpu::{BYTE, DOUBLEWORD, HALFWORD, WORD}; use crate::exception::Exception; @@ -134,6 +134,38 @@ pub struct Rom { data: Vec, } +impl Device for Rom { + fn size(&self) -> u64 { + 0xf000 + } + + fn is_interrupting(&mut self) -> bool { + false + } + + fn irq(&self) -> Option { + None + } + + fn reset(&mut self) {} + + /// Load `size`-bit data from the memory. + fn read(&mut self, addr: u64, size: u8) -> Result { + match size { + BYTE => Ok(self.read8(addr)), + HALFWORD => Ok(self.read16(addr)), + WORD => Ok(self.read32(addr)), + DOUBLEWORD => Ok(self.read64(addr)), + _ => return Err(Exception::LoadAccessFault), + } + } + + /// Store `size`-bit data to the memory. Returns the exception because the ROM is read-only. + fn write(&mut self, _addr: u64, _value: u64, _size: u8) -> Result<(), Exception> { + Err(Exception::StoreAMOAccessFault) + } +} + impl Rom { /// Create a new `rom` object. pub fn new() -> Self { @@ -163,37 +195,21 @@ impl Rom { Rom { data } } - /// Load `size`-bit data from the memory. - pub fn read(&self, addr: u64, size: u8) -> Result { - match size { - BYTE => Ok(self.read8(addr)), - HALFWORD => Ok(self.read16(addr)), - WORD => Ok(self.read32(addr)), - DOUBLEWORD => Ok(self.read64(addr)), - _ => return Err(Exception::LoadAccessFault), - } - } - - /// Store `size`-bit data to the memory. Returns the exception because the ROM is read-only. - pub fn write(&self, _addr: u64, _value: u64, _size: u8) -> Result<(), Exception> { - Err(Exception::StoreAMOAccessFault) - } - /// Read a byte from the rom. fn read8(&self, addr: u64) -> u64 { - let index = (addr - MROM_BASE) as usize; + let index = (addr) as usize; self.data[index] as u64 } /// Read 2 bytes from the rom. fn read16(&self, addr: u64) -> u64 { - let index = (addr - MROM_BASE) as usize; + let index = (addr) as usize; return (self.data[index] as u64) | ((self.data[index + 1] as u64) << 8); } /// Read 4 bytes from the rom. fn read32(&self, addr: u64) -> u64 { - let index = (addr - MROM_BASE) as usize; + let index = (addr) as usize; return (self.data[index] as u64) | ((self.data[index + 1] as u64) << 8) | ((self.data[index + 2] as u64) << 16) @@ -202,7 +218,7 @@ impl Rom { /// Read 8 bytes from the rom. fn read64(&self, addr: u64) -> u64 { - let index = (addr - MROM_BASE) as usize; + let index = (addr) as usize; return (self.data[index] as u64) | ((self.data[index + 1] as u64) << 8) | ((self.data[index + 2] as u64) << 16) diff --git a/src/devices/uart_cli.rs b/src/devices/uart_cli.rs index 97dee47..2cb6a33 100644 --- a/src/devices/uart_cli.rs +++ b/src/devices/uart_cli.rs @@ -10,29 +10,33 @@ use std::sync::{ }; use std::thread; -use crate::bus::{UART_BASE, UART_SIZE}; use crate::cpu::BYTE; use crate::exception::Exception; +use crate::bus::Device; + +/// The size of UART. +pub const UART_SIZE: u64 = 0x100; + /// The interrupt request of UART. pub const UART_IRQ: u64 = 10; /// Receive holding register (for input bytes). -const UART_RHR: u64 = UART_BASE + 0; +const UART_RHR: u64 = 0 + 0; /// Transmit holding register (for output bytes). -const UART_THR: u64 = UART_BASE + 0; +const UART_THR: u64 = 0 + 0; /// Interrupt enable register. -const _UART_IER: u64 = UART_BASE + 1; +const _UART_IER: u64 = 0 + 1; /// FIFO control register. -const _UART_FCR: u64 = UART_BASE + 2; +const _UART_FCR: u64 = 0 + 2; /// Interrupt status register. /// ISR BIT-0: /// 0 = an interrupt is pending and the ISR contents may be used as a pointer to the appropriate /// interrupt service routine. /// 1 = no interrupt is pending. -const _UART_ISR: u64 = UART_BASE + 2; +const _UART_ISR: u64 = 0 + 2; /// Line control register. -const _UART_LCR: u64 = UART_BASE + 3; +const _UART_LCR: u64 = 0 + 3; /// Line status register. /// LSR BIT 0: /// 0 = no data in receive holding register or FIFO. @@ -40,7 +44,7 @@ const _UART_LCR: u64 = UART_BASE + 3; /// LSR BIT 5: /// 0 = transmit holding register is full. 16550 will not accept any data for transmission. /// 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character. -const UART_LSR: u64 = UART_BASE + 5; +const UART_LSR: u64 = 0 + 5; /// The receiver (RX). const UART_LSR_RX: u8 = 1; @@ -51,18 +55,19 @@ const UART_LSR_TX: u8 = 1 << 5; pub struct Uart { uart: Arc<(Mutex<[u8; UART_SIZE as usize]>, Condvar)>, interrupting: Arc, + irq: u64, } impl Uart { /// Create a new UART object. - pub fn new() -> Self { + pub fn new(irq: u64) -> Self { let uart = Arc::new((Mutex::new([0; UART_SIZE as usize]), Condvar::new())); let interrupting = Arc::new(AtomicBool::new(false)); { let (uart, _cvar) = &*uart; let mut uart = uart.lock().expect("failed to get an UART object"); // Transmitter hold register is empty. It allows input anytime. - uart[(UART_LSR - UART_BASE) as usize] |= UART_LSR_TX; + uart[(UART_LSR - 0) as usize] |= UART_LSR_TX; } // Create a new thread for waiting for input. @@ -75,13 +80,13 @@ impl Uart { let (uart, cvar) = &*cloned_uart; let mut uart = uart.lock().expect("failed to get an UART object"); // Wait for the thread to start up. - while (uart[(UART_LSR - UART_BASE) as usize] & UART_LSR_RX) == 1 { + while (uart[(UART_LSR - 0) as usize] & UART_LSR_RX) == 1 { uart = cvar.wait(uart).expect("the mutex is poisoned"); } uart[0] = byte[0]; cloned_interrupting.store(true, Ordering::Release); // Data has been receive. - uart[(UART_LSR - UART_BASE) as usize] |= UART_LSR_RX; + uart[(UART_LSR - 0) as usize] |= UART_LSR_RX; } Err(e) => { println!("input via UART is error: {}", e); @@ -89,16 +94,32 @@ impl Uart { } }); - Self { uart, interrupting } + Self { + uart, + interrupting, + irq, + } + } +} + +impl Device for Uart { + fn size(&self) -> u64 { + UART_SIZE } /// Return true if an interrupt is pending. Clear the interrupting flag by swapping a value. - pub fn is_interrupting(&self) -> bool { + fn is_interrupting(&mut self) -> bool { self.interrupting.swap(false, Ordering::Acquire) } + fn irq(&self) -> Option { + Some(self.irq) + } + + fn reset(&mut self) {} + /// Read a byte from the receive holding register. - pub fn read(&mut self, index: u64, size: u8) -> Result { + fn read(&mut self, index: u64, size: u8) -> Result { if size != BYTE { return Err(Exception::LoadAccessFault); } @@ -108,15 +129,16 @@ impl Uart { match index { UART_RHR => { cvar.notify_one(); - uart[(UART_LSR - UART_BASE) as usize] &= !UART_LSR_RX; - Ok(uart[(UART_RHR - UART_BASE) as usize] as u64) + uart[(UART_LSR - 0) as usize] &= !UART_LSR_RX; + Ok(uart[(UART_RHR - 0) as usize] as u64) } - _ => Ok(uart[(index - UART_BASE) as usize] as u64), + _ => Ok(uart[(index - 0) as usize] as u64), } } /// Write a byte to the transmit holding register. - pub fn write(&mut self, index: u64, value: u8, size: u8) -> Result<(), Exception> { + fn write(&mut self, index: u64, value: u64, size: u8) -> Result<(), Exception> { + let value = (value & 0xff) as u8; if size != BYTE { return Err(Exception::StoreAMOAccessFault); } @@ -139,7 +161,7 @@ impl Uart { io::stdout().flush().expect("failed to flush stdout"); } _ => { - uart[(index - UART_BASE) as usize] = value; + uart[(index - 0) as usize] = value; } } Ok(()) diff --git a/src/devices/uart_wasm.rs b/src/devices/uart_wasm.rs index 14efec6..c42615b 100644 --- a/src/devices/uart_wasm.rs +++ b/src/devices/uart_wasm.rs @@ -6,7 +6,6 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; use web_sys::Window; -use crate::bus::{UART_BASE, UART_SIZE}; use crate::cpu::BYTE; use crate::exception::Exception; @@ -16,25 +15,28 @@ extern "C" { fn log(s: &str); } +/// The size of UART. +pub const UART_SIZE: u64 = 0x100; + /// The interrupt request of UART. pub const UART_IRQ: u64 = 10; /// Receive holding register (for input bytes). -const UART_RHR: u64 = UART_BASE + 0; +const UART_RHR: u64 = 0 + 0; /// Transmit holding register (for output bytes). -const UART_THR: u64 = UART_BASE + 0; +const UART_THR: u64 = 0 + 0; /// Interrupt enable register. -const _UART_IER: u64 = UART_BASE + 1; +const _UART_IER: u64 = 0 + 1; /// FIFO control register. -const _UART_FCR: u64 = UART_BASE + 2; +const _UART_FCR: u64 = 0 + 2; /// Interrupt status register. /// ISR BIT-0: /// 0 = an interrupt is pending and the ISR contents may be used as a pointer to the appropriate /// interrupt service routine. /// 1 = no interrupt is pending. -const UART_ISR: u64 = UART_BASE + 2; +const UART_ISR: u64 = 0 + 2; /// Line control register. -const _UART_LCR: u64 = UART_BASE + 3; +const _UART_LCR: u64 = 0 + 3; /// Line status register. /// LSR BIT 0: /// 0 = no data in receive holding register or FIFO. @@ -42,7 +44,7 @@ const _UART_LCR: u64 = UART_BASE + 3; /// LSR BIT 6: /// 0 = transmitter holding and shift registers are full. /// 1 = transmit holding register is empty. In FIFO mode this bit is set to one whenever the the transmitter FIFO and transmit shift register are empty. -const UART_LSR: u64 = UART_BASE + 5; +const UART_LSR: u64 = 0 + 5; fn get_input(window: &Window) -> u8 { let document = window.document().expect("failed to get a document object"); @@ -70,22 +72,29 @@ pub struct Uart { clock: u64, not_null: bool, window: web_sys::Window, + irq: u64, } impl Uart { /// Create a new UART object. - pub fn new() -> Self { + pub fn new(irq: u64) -> Self { let mut uart = [0; UART_SIZE as usize]; - uart[(UART_ISR - UART_BASE) as usize] |= 1; - uart[(UART_LSR - UART_BASE) as usize] |= 1 << 5; + uart[(UART_ISR - 0) as usize] |= 1; + uart[(UART_LSR - 0) as usize] |= 1 << 5; Self { uart, clock: 0, not_null: false, window: web_sys::window().expect("failed to get a global window object"), + irq, } } +} +impl Device for Uart { + fn size(&self) -> u64 { + UART_SIZE + } /// Return true if the byte buffer in UART is full. pub fn is_interrupting(&mut self) -> bool { @@ -99,7 +108,7 @@ impl Uart { return false; } self.uart[0] = b; - self.uart[(UART_LSR - UART_BASE) as usize] |= 1; + self.uart[(UART_LSR - 0) as usize] |= 1; // Found a byte in this step, so it might find a byte again in the next step. self.not_null = true; return true; @@ -107,23 +116,29 @@ impl Uart { false } + fn irq(&self) -> Option { + Some(self.irq) + } + + fn reset(&mut self) {} + /// Read a byte from the receive holding register. - pub fn read(&mut self, index: u64, size: u8) -> Result { + fn read(&mut self, index: u64, size: u8) -> Result { if size != BYTE { return Err(Exception::LoadAccessFault); } match index { UART_RHR => { - self.uart[(UART_LSR - UART_BASE) as usize] &= !1; - Ok(self.uart[(index - UART_BASE) as usize] as u64) + self.uart[(UART_LSR - 0) as usize] &= !1; + Ok(self.uart[(index - 0) as usize] as u64) } - _ => Ok(self.uart[(index - UART_BASE) as usize] as u64), + _ => Ok(self.uart[(index - 0) as usize] as u64), } } /// Write a byte to the transmit holding register. - pub fn write(&mut self, index: u64, value: u8, size: u8) -> Result<(), Exception> { + fn write(&mut self, index: u64, value: u8, size: u8) -> Result<(), Exception> { if size != BYTE { return Err(Exception::StoreAMOAccessFault); } @@ -135,7 +150,7 @@ impl Uart { .expect("failed to post message"); } _ => { - self.uart[(index - UART_BASE) as usize] = value; + self.uart[(index - 0) as usize] = value; } } Ok(()) diff --git a/src/devices/virtio_blk.rs b/src/devices/virtio_blk.rs index 86260a4..49647c8 100644 --- a/src/devices/virtio_blk.rs +++ b/src/devices/virtio_blk.rs @@ -5,8 +5,8 @@ //! 5.2 Block Device: //! https://docs.oasis-open.org/virtio/virtio/v1.1/cs01/virtio-v1.1-cs01.html#x1-2390002 -use crate::bus::VIRTIO_BASE; -use crate::cpu::{Cpu, BYTE, DOUBLEWORD, HALFWORD, WORD}; +use crate::bus::Device; +use crate::cpu::{BYTE, DOUBLEWORD, HALFWORD, WORD}; use crate::exception::Exception; /// The interrupt request of virtio. @@ -29,70 +29,70 @@ const _VIRTQ_DESC_F_INDIRECT: u64 = 4; // 4.2.2 MMIO Device Register Layout // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1460002 /// Magic value. Always return 0x74726976 (a Little Endian equivalent of the "virt" string). -const MAGIC: u64 = VIRTIO_BASE; -const MAGIC_END: u64 = VIRTIO_BASE + 0x3; +const MAGIC: u64 = 0; +const MAGIC_END: u64 = 0 + 0x3; /// Device version number. 1 is legacy. -const VERSION: u64 = VIRTIO_BASE + 0x4; -const VERSION_END: u64 = VIRTIO_BASE + 0x7; +const VERSION: u64 = 0 + 0x4; +const VERSION_END: u64 = 0 + 0x7; /// Virtio Subsystem Device ID. 1 is network, 2 is block device. -const DEVICE_ID: u64 = VIRTIO_BASE + 0x8; -const DEVICE_ID_END: u64 = VIRTIO_BASE + 0xb; +const DEVICE_ID: u64 = 0 + 0x8; +const DEVICE_ID_END: u64 = 0 + 0xb; /// Virtio Subsystem Vendor ID. Always return 0x554d4551 -const VENDOR_ID: u64 = VIRTIO_BASE + 0xc; -const VENDOR_ID_END: u64 = VIRTIO_BASE + 0xf; +const VENDOR_ID: u64 = 0 + 0xc; +const VENDOR_ID_END: u64 = 0 + 0xf; /// Flags representing features the device supports. Access to this register returns bits /// DeviceFeaturesSel ∗ 32 to (DeviceFeaturesSel ∗ 32) + 31. -const DEVICE_FEATURES: u64 = VIRTIO_BASE + 0x10; -const DEVICE_FEATURES_END: u64 = VIRTIO_BASE + 0x13; +const DEVICE_FEATURES: u64 = 0 + 0x10; +const DEVICE_FEATURES_END: u64 = 0 + 0x13; /// Device (host) features word selection. -const DEVICE_FEATURES_SEL: u64 = VIRTIO_BASE + 0x14; -const DEVICE_FEATURES_SEL_END: u64 = VIRTIO_BASE + 0x17; +const DEVICE_FEATURES_SEL: u64 = 0 + 0x14; +const DEVICE_FEATURES_SEL_END: u64 = 0 + 0x17; /// Flags representing device features understood and activated by the driver. Access to this /// register sets bits DriverFeaturesSel ∗ 32 to (DriverFeaturesSel ∗ 32) + 31. -const DRIVER_FEATURES: u64 = VIRTIO_BASE + 0x20; -const DRIVER_FEATURES_END: u64 = VIRTIO_BASE + 0x23; +const DRIVER_FEATURES: u64 = 0 + 0x20; +const DRIVER_FEATURES_END: u64 = 0 + 0x23; /// Activated (guest) features word selection. -const DRIVER_FEATURES_SEL: u64 = VIRTIO_BASE + 0x24; -const DRIVER_FEATURES_SEL_END: u64 = VIRTIO_BASE + 0x27; +const DRIVER_FEATURES_SEL: u64 = 0 + 0x24; +const DRIVER_FEATURES_SEL_END: u64 = 0 + 0x27; // 4.2.4 Legacy interface // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1560004 /// Guest page size. The driver writes the guest page size in bytes to the register during /// initialization, before any queues are used. This value should be a power of 2 and is used by /// the device to calculate the Guest address of the first queue page. Write-only. -const GUEST_PAGE_SIZE: u64 = VIRTIO_BASE + 0x28; -const GUEST_PAGE_SIZE_END: u64 = VIRTIO_BASE + 0x2b; +const GUEST_PAGE_SIZE: u64 = 0 + 0x28; +const GUEST_PAGE_SIZE_END: u64 = 0 + 0x2b; /// Virtual queue index. Writing to this register selects the virtual queue that the following /// operations on the QueueNumMax, QueueNum, QueueAlign and QueuePFN registers apply to. The index /// number of the first queue is zero (0x0). Write-only. -const QUEUE_SEL: u64 = VIRTIO_BASE + 0x30; -const QUEUE_SEL_END: u64 = VIRTIO_BASE + 0x33; +const QUEUE_SEL: u64 = 0 + 0x30; +const QUEUE_SEL_END: u64 = 0 + 0x33; /// Maximum virtual queue size. Reading from the register returns the maximum size of the queue the /// device is ready to process or zero (0x0) if the queue is not available. This applies to the /// queue selected by writing to QueueSel and is allowed only when QueuePFN is set to zero (0x0), /// so when the queue is not actively used. Read-only. In QEMU, `VIRTIO_COUNT = 8`. -const QUEUE_NUM_MAX: u64 = VIRTIO_BASE + 0x34; -const QUEUE_NUM_MAX_END: u64 = VIRTIO_BASE + 0x37; +const QUEUE_NUM_MAX: u64 = 0 + 0x34; +const QUEUE_NUM_MAX_END: u64 = 0 + 0x37; /// Virtual queue size. Queue size is the number of elements in the queue, therefore size of the /// descriptor table and both available and used rings. Writing to this register notifies the /// device what size of the queue the driver will use. This applies to the queue selected by /// writing to QueueSel. Write-only. -const QUEUE_NUM: u64 = VIRTIO_BASE + 0x38; -const QUEUE_NUM_END: u64 = VIRTIO_BASE + 0x3b; +const QUEUE_NUM: u64 = 0 + 0x38; +const QUEUE_NUM_END: u64 = 0 + 0x3b; /// Used Ring alignment in the virtual queue. -const QUEUE_ALIGN: u64 = VIRTIO_BASE + 0x3c; -const QUEUE_ALIGN_END: u64 = VIRTIO_BASE + 0x3f; +const QUEUE_ALIGN: u64 = 0 + 0x3c; +const QUEUE_ALIGN_END: u64 = 0 + 0x3f; /// Guest physical page number of the virtual queue. Writing to this register notifies the device /// about location of the virtual queue in the Guest’s physical address space. This value is the @@ -101,35 +101,35 @@ const QUEUE_ALIGN_END: u64 = VIRTIO_BASE + 0x3f; /// writes zero (0x0) to this register. Reading from this register returns the currently used page /// number of the queue, therefore a value other than zero (0x0) means that the queue is in use. /// Both read and write accesses apply to the queue selected by writing to QueueSel. -const QUEUE_PFN: u64 = VIRTIO_BASE + 0x40; -const QUEUE_PFN_END: u64 = VIRTIO_BASE + 0x43; +const QUEUE_PFN: u64 = 0 + 0x40; +const QUEUE_PFN_END: u64 = 0 + 0x43; // 4.2.2 MMIO Device Register Layout // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1460002 /// Queue notifier. Writing a queue index to this register notifies the device that there are new /// buffers to process in the queue. Write-only. -const QUEUE_NOTIFY: u64 = VIRTIO_BASE + 0x50; -const QUEUE_NOTIFY_END: u64 = VIRTIO_BASE + 0x53; +const QUEUE_NOTIFY: u64 = 0 + 0x50; +const QUEUE_NOTIFY_END: u64 = 0 + 0x53; /// Interrupt status. Reading from this register returns a bit mask of events that caused the /// device interrupt to be asserted. -const INTERRUPT_STATUS: u64 = VIRTIO_BASE + 0x60; -const INTERRUPT_STATUS_END: u64 = VIRTIO_BASE + 0x63; +const INTERRUPT_STATUS: u64 = 0 + 0x60; +const INTERRUPT_STATUS_END: u64 = 0 + 0x63; /// Interrupt acknowledge. Writing a value with bits set as defined in InterruptStatus to this /// register notifies the device that events causing the interrupt have been handled. -const INTERRUPT_ACK: u64 = VIRTIO_BASE + 0x64; -const INTERRUPT_ACK_END: u64 = VIRTIO_BASE + 0x67; +const INTERRUPT_ACK: u64 = 0 + 0x64; +const INTERRUPT_ACK_END: u64 = 0 + 0x67; /// Device status. Reading from this register returns the current device status flags. Writing /// non-zero values to this register sets the status flags, indicating the driver progress. Writing /// zero (0x0) to this register triggers a device reset. -const STATUS: u64 = VIRTIO_BASE + 0x70; -const STATUS_END: u64 = VIRTIO_BASE + 0x73; +const STATUS: u64 = 0 + 0x70; +const STATUS_END: u64 = 0 + 0x73; /// Configuration space. -const CONFIG: u64 = VIRTIO_BASE + 0x100; -const CONFIG_END: u64 = VIRTIO_BASE + 0x107; +const CONFIG: u64 = 0 + 0x100; +const CONFIG_END: u64 = 0 + 0x107; /// https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-230005 /// "Each virtqueue can consist of up to 3 parts: @@ -221,12 +221,12 @@ struct VirtqDesc { impl VirtqDesc { /// Creates a new virtqueue descriptor based on the address that stores the content of the /// descriptor. - fn new(cpu: &mut Cpu, addr: u64) -> Result { + fn new(virtio: &mut Virtio, addr: u64) -> Result { 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)?, + addr: virtio.read(addr, DOUBLEWORD)?, + len: virtio.read(addr.wrapping_add(8), WORD)?, + flags: virtio.read(addr.wrapping_add(12), HALFWORD)?, + next: virtio.read(addr.wrapping_add(14), HALFWORD)?, }) } } @@ -247,16 +247,19 @@ impl VirtqDesc { /// ``` #[derive(Debug)] struct VirtqAvail { + // JSK: I am not sure where this is used, but also don't know enough + // to feel comfortable getting rid of it. + #[allow(dead_code)] flags: u16, idx: u16, ring_start_addr: u64, } impl VirtqAvail { - fn new(cpu: &mut Cpu, addr: u64) -> Result { + fn new(virtio: &mut Virtio, addr: u64) -> Result { Ok(Self { - flags: cpu.bus.read(addr, HALFWORD)? as u16, - idx: cpu.bus.read(addr.wrapping_add(2), HALFWORD)? as u16, + flags: virtio.read(addr, HALFWORD)? as u16, + idx: virtio.read(addr.wrapping_add(2), HALFWORD)? as u16, ring_start_addr: addr.wrapping_add(4), }) } @@ -279,11 +282,12 @@ pub struct Virtio { config: [u8; 8], disk: Vec, virtqueue: Option, + irq: u64, } impl Virtio { /// Creates a new virtio object. - pub fn new() -> Self { + pub fn new(irq: u64) -> Self { let mut config = [0; 8]; // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2440004 // 5.2.4 Device configuration layout @@ -314,6 +318,7 @@ impl Virtio { config, disk: Vec::new(), virtqueue: None, + irq: irq, } } @@ -342,17 +347,124 @@ impl Virtio { } } - /// Resets the device when `status` is written to 0. - fn reset(&mut self) { - self.id = 0; - // 4.2.2.1 Device Requirements: MMIO Device Register Layout - // "Upon reset, the device MUST clear all bits in InterruptStatus and ready bits in the - // QueueReady register for all queues in the device." - self.interrupt_status = 0; + /// Sets the binary in the virtio disk. + pub fn initialize(&mut self, binary: Vec) { + self.disk.extend(binary.iter().cloned()); + } + + 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 + } + + /// Accesses the disk via virtio. This is an associated function which takes a `cpu` object to + /// read and write with a memory directly (DMA). + pub fn disk_access(&mut self) -> Result<(), Exception> { + // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1460002 + // "Used Buffer Notification + // - bit 0 - the interrupt was asserted because the device has used a buffer in at + // least one of the active virtual queues." + self.interrupt_status |= 0x1; + + let virtq = self.virtqueue(); + + let avail = VirtqAvail::new(self, virtq.avail_addr)?; + + let head_index = self.read( + avail.ring_start_addr + avail.idx as u64 % QUEUE_SIZE, + HALFWORD, + )?; + + // First descriptor. + let desc0 = VirtqDesc::new(self, virtq.desc_addr + VRING_DESC_SIZE * head_index)?; + assert_eq!(desc0.flags & VIRTQ_DESC_F_NEXT, 1); + + // Second descriptor. + let desc1 = VirtqDesc::new(self, virtq.desc_addr + VRING_DESC_SIZE * desc0.next)?; + assert_eq!(desc1.flags & VIRTQ_DESC_F_NEXT, 1); + + // 5.2.6 Device Operation + // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2500006 + // struct virtio_blk_req { + // le32 type; + // le32 reserved; + // le64 sector; + // u8 data[][512]; + // u8 status; + // }; + let sector = self.read(desc0.addr.wrapping_add(8), DOUBLEWORD)?; + + // Write to a device if the second bit of `flags` is set. + match (desc1.flags & VIRTQ_DESC_F_WRITE) == 0 { + true => { + // Read memory data and write it to a disk. + for i in 0..desc1.len { + let data = self.read(desc1.addr + i, BYTE)?; + self.write_disk(sector * SECTOR_SIZE + i, data); + } + } + false => { + // Read disk data and write it to memory. + for i in 0..desc1.len { + let data = self.read_disk(sector * SECTOR_SIZE + i); + self.write(desc1.addr + i, data, BYTE)?; + } + } + }; + + // Third descriptor address. + let desc2 = VirtqDesc::new(self, virtq.desc_addr + VRING_DESC_SIZE * desc1.next)?; + assert_eq!(desc2.flags & VIRTQ_DESC_F_NEXT, 0); + // Tell success. + self.write(desc2.addr, 0, BYTE)?; + + // 2.6.7.2 Device Requirements: Used Buffer Notification Suppression + // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-400007 + // After the device writes a descriptor index into the used ring: + // If flags is 1, the device SHOULD NOT send a notification. + // If flags is 0, the device MUST send a notification. + // TODO: check the flags in the available ring. + + // "The used ring is where the device returns buffers once it is done with them: it is only + // written to by the device, and read by the driver." + // + // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-430008 + // + // ```c + // #define VIRTQ_USED_F_NO_NOTIFY 1 + // struct virtq_used { + // le16 flags; + // le16 idx; + // struct virtq_used_elem ring[ /* Queue Size */]; + // le16 avail_event; /* Only if VIRTIO_F_EVENT_IDX */ + // }; + // ``` + self.write( + virtq + .used_addr + .wrapping_add(4) + .wrapping_add((self.id as u64 % QUEUE_SIZE) * 8), + head_index, + WORD, + )?; + + self.id = self.id.wrapping_add(1); + self.write(virtq.used_addr.wrapping_add(2), self.id, HALFWORD)?; + + Ok(()) + } +} + +impl Device for Virtio { + fn size(&self) -> u64 { + 0x1000 } /// Returns true if an interrupt is pending. - pub fn is_interrupting(&mut self) -> bool { + fn is_interrupting(&mut self) -> bool { if self.queue_notify != u32::MAX { self.queue_notify = u32::MAX; return true; @@ -360,13 +472,21 @@ impl Virtio { false } - /// Sets the binary in the virtio disk. - pub fn initialize(&mut self, binary: Vec) { - self.disk.extend(binary.iter().cloned()); + fn irq(&self) -> Option { + Some(self.irq) + } + + /// Resets the device when `status` is written to 0. + fn reset(&mut self) { + self.id = 0; + // 4.2.2.1 Device Requirements: MMIO Device Register Layout + // "Upon reset, the device MUST clear all bits in InterruptStatus and ready bits in the + // QueueReady register for all queues in the device." + self.interrupt_status = 0; } /// Loads `size`-bit data from a register located at `addr` in the virtio block device. - pub fn read(&self, addr: u64, size: u8) -> Result { + fn read(&mut self, addr: u64, size: u8) -> Result { // `reg` is the value of a target register in the virtio block device and `offset` is the // byte of the start position in the register. let (reg, offset) = match addr { @@ -409,7 +529,8 @@ impl Virtio { } /// Stores `size`-bit data to a register located at `addr` in the virtio block device. - pub fn write(&mut self, addr: u64, value: u32, size: u8) -> Result<(), Exception> { + fn write(&mut self, addr: u64, value: u64, size: u8) -> Result<(), Exception> { + let value = (value & 0xffffffff) as u32; // `reg` is the value of a target register in the virtio block device and `offset` is the // byte of the start position in the register. let (mut reg, offset) = match addr { @@ -508,110 +629,4 @@ impl Virtio { 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 - } - - /// Accesses the disk via virtio. This is an associated function which takes a `cpu` object to - /// read and write with a memory directly (DMA). - pub fn disk_access(cpu: &mut Cpu) -> Result<(), Exception> { - // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-1460002 - // "Used Buffer Notification - // - bit 0 - the interrupt was asserted because the device has used a buffer in at - // least one of the active virtual queues." - cpu.bus.virtio.interrupt_status |= 0x1; - - let virtq = cpu.bus.virtio.virtqueue(); - - let avail = VirtqAvail::new(cpu, virtq.avail_addr)?; - - let head_index = cpu.bus.read( - avail.ring_start_addr + avail.idx as u64 % QUEUE_SIZE, - HALFWORD, - )?; - - // First descriptor. - let desc0 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * head_index)?; - assert_eq!(desc0.flags & VIRTQ_DESC_F_NEXT, 1); - - // Second descriptor. - let desc1 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * desc0.next)?; - assert_eq!(desc1.flags & VIRTQ_DESC_F_NEXT, 1); - - // 5.2.6 Device Operation - // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-2500006 - // struct virtio_blk_req { - // le32 type; - // le32 reserved; - // le64 sector; - // u8 data[][512]; - // u8 status; - // }; - let sector = cpu.bus.read(desc0.addr.wrapping_add(8), DOUBLEWORD)?; - - // Write to a device if the second bit of `flags` is set. - match (desc1.flags & VIRTQ_DESC_F_WRITE) == 0 { - true => { - // Read memory data and write it to a disk. - 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 => { - // Read disk data and write it to memory. - 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)?; - } - } - }; - - // Third descriptor address. - let desc2 = VirtqDesc::new(cpu, virtq.desc_addr + VRING_DESC_SIZE * desc1.next)?; - assert_eq!(desc2.flags & VIRTQ_DESC_F_NEXT, 0); - // Tell success. - cpu.bus.write(desc2.addr, 0, BYTE)?; - - // 2.6.7.2 Device Requirements: Used Buffer Notification Suppression - // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-400007 - // After the device writes a descriptor index into the used ring: - // If flags is 1, the device SHOULD NOT send a notification. - // If flags is 0, the device MUST send a notification. - // TODO: check the flags in the available ring. - - // "The used ring is where the device returns buffers once it is done with them: it is only - // written to by the device, and read by the driver." - // - // https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html#x1-430008 - // - // ```c - // #define VIRTQ_USED_F_NO_NOTIFY 1 - // struct virtq_used { - // le16 flags; - // le16 idx; - // struct virtq_used_elem ring[ /* Queue Size */]; - // le16 avail_event; /* Only if VIRTIO_F_EVENT_IDX */ - // }; - // ``` - 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)?; - - Ok(()) - } } diff --git a/src/emulator.rs b/src/emulator.rs index 14af18b..7f90b5e 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -1,7 +1,44 @@ //! The emulator module represents an entire computer. -use crate::cpu::Cpu; +use crate::cpu::{Cpu, ExecCycle, REG_SP}; +use crate::devices::dram::Dram; +//use crate::devices::rom::Rom; +use crate::devices::uart::Uart; +use crate::devices::virtio_blk::Virtio; use crate::exception::Trap; +use std::sync::{Arc, Mutex}; + +// QEMU virt machine: +// https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c#L46-L63 + +/// The address which the mask ROM starts. +pub const MROM_BASE: u64 = 0x1000; + +/// The address which the core-local interruptor (CLINT) starts. It contains the timer and generates +/// per-hart software interrupts and timer interrupts. +pub const CLINT_BASE: u64 = 0x200_0000; + +/// The address which the platform-level interrupt controller (PLIC) starts. The PLIC connects all +/// external interrupts in the system to all hart contexts in the system, via the external interrupt +/// source in each hart. +pub const PLIC_BASE: u64 = 0xc00_0000; + +/// The address which UART starts. QEMU puts UART registers here in physical memory. +pub const UART_BASE: u64 = 0x1000_0000; +/// The size of UART. +pub const UART_SIZE: u64 = 0x100; + +pub const UART_IRQ: u64 = 10; + +/// The address which virtio starts. +pub const VIRTIO_BASE: u64 = 0x1000_1000; + +pub const VIRTIO_IRQ: u64 = 1; + +/// +pub const DRAM_SIZE: u64 = 0x40000000; +/// The address which DRAM starts. +pub const DRAM_BASE: u64 = 0x8000_0000; /// The emulator to hold a CPU. pub struct Emulator { @@ -14,8 +51,11 @@ pub struct Emulator { impl Emulator { /// Constructor for an emulator. pub fn new() -> Emulator { + let mut cpu = Cpu::new(); + let uart = Arc::new(Mutex::new(Uart::new(UART_IRQ))); + cpu.bus.mount(UART_BASE, uart); Self { - cpu: Cpu::new(), + cpu: cpu, is_debug: false, } } @@ -27,12 +67,17 @@ impl Emulator { /// Set binary data to the beginning of the DRAM from the emulator console. pub fn initialize_dram(&mut self, data: Vec) { - self.cpu.bus.initialize_dram(data); + let dram = Arc::new(Mutex::new(Dram::new(DRAM_SIZE))); + dram.lock().unwrap().initialize(data); + self.cpu.bus.mount(DRAM_BASE, dram); + self.initialize_sp(DRAM_BASE + DRAM_SIZE); } /// Set binary data to the virtio disk from the emulator console. pub fn initialize_disk(&mut self, data: Vec) { - self.cpu.bus.initialize_disk(data); + let disk = Arc::new(Mutex::new(Virtio::new(VIRTIO_IRQ))); + disk.lock().unwrap().initialize(data); + self.cpu.bus.mount(VIRTIO_BASE, disk); } /// Set the program counter to the CPU field. @@ -40,6 +85,10 @@ impl Emulator { self.cpu.pc = pc; } + pub fn initialize_sp(&mut self, sp: u64) { + self.cpu.xregs.write(REG_SP, sp); + } + /// Start executing the emulator with limited range of program. This method is for test. /// No interrupts happen. pub fn test_start(&mut self, start: u64, end: u64) { @@ -56,8 +105,12 @@ impl Emulator { } match self.cpu.execute() { - Ok(inst) => { - println!("pc: {:#x}, inst: {:#x}", self.cpu.pc.wrapping_sub(4), inst); + Ok(ExecCycle::Idle) => { + println!("Idle"); + Trap::Requested + } + Ok(ExecCycle::Opcode(eop)) => { + println!("pc: {:#x}, inst: {:#x}", eop.pc, eop.opcode); Trap::Requested } Err(exception) => { @@ -88,11 +141,16 @@ impl Emulator { // Execute an instruction. let trap = match self.cpu.execute() { - Ok(inst) => { + Ok(ExecCycle::Idle) => { + println!("Idle"); + Trap::Requested + } + Ok(ExecCycle::Opcode(eop)) => { if self.is_debug { + let inst = eop.opcode; println!( "pc: {:#x}, inst: {:#x}, is_inst 16? {} pre_inst: {:#x}", - self.cpu.pc.wrapping_sub(4), + eop.pc, inst, // Check if an instruction is one of the compressed instructions. inst & 0b11 == 0 || inst & 0b11 == 1 || inst & 0b11 == 2, diff --git a/src/lib.rs b/src/lib.rs index edaba06..1cbfbab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ //! Create an `Emulator` object, place a binary data in DRAM and set the program counter to //! `DRAM_BASE`. The binary data must contain no headers for now. The example is here: //! ```rust -//! use rvemu::bus::DRAM_BASE; +//! use rvemu::emulator::DRAM_BASE; //! use rvemu::emulator::Emulator; //! //! fn main() { @@ -35,8 +35,6 @@ pub mod bus; pub mod cpu; pub mod csr; pub mod devices; -pub mod dram; pub mod emulator; pub mod exception; pub mod interrupt; -pub mod rom; diff --git a/tests/exception.rs b/tests/exception.rs index 6bf2cbe..92efb1f 100644 --- a/tests/exception.rs +++ b/tests/exception.rs @@ -1,6 +1,6 @@ -use rvemu::bus::DRAM_BASE; use rvemu::csr::MEPC; use rvemu::emulator::Emulator; +use rvemu::emulator::DRAM_BASE; #[test] fn illegal_isa() { diff --git a/tests/helper.rs b/tests/helper.rs index e0ea3d9..fcd843a 100644 --- a/tests/helper.rs +++ b/tests/helper.rs @@ -1,7 +1,7 @@ -use rvemu::bus::DRAM_BASE; use rvemu::cpu::{POINTER_TO_DTB, REGISTERS_COUNT}; -use rvemu::dram::DRAM_SIZE; use rvemu::emulator::Emulator; +use rvemu::emulator::DRAM_BASE; +use rvemu::emulator::DRAM_SIZE; pub const DEFAULT_SP: u64 = DRAM_BASE + DRAM_SIZE; diff --git a/tests/rv32i.rs b/tests/rv32i.rs index c7ea9c5..3ed0e0d 100644 --- a/tests/rv32i.rs +++ b/tests/rv32i.rs @@ -1,7 +1,7 @@ mod helper; use rvemu::emulator::Emulator; -use rvemu::bus::DRAM_BASE; +use rvemu::emulator::DRAM_BASE; #[test] fn lb_rd_offset_rs1() { diff --git a/tests/rv64_user_p.rs b/tests/rv64_user_p.rs index db2036e..22449d6 100644 --- a/tests/rv64_user_p.rs +++ b/tests/rv64_user_p.rs @@ -5,7 +5,7 @@ use std::io; use std::io::prelude::*; use std::path::PathBuf; -use rvemu::{bus::DRAM_BASE, cpu::Mode, emulator::Emulator}; +use rvemu::{cpu::Mode, emulator::Emulator, emulator::DRAM_BASE}; #[macro_export] macro_rules! add_test {