Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 72 additions & 62 deletions src/bus.rs
Original file line number Diff line number Diff line change
@@ -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<u64, Exception>;
/// 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<u64>;

fn reset(&mut self);
}

/// The system bus.
pub struct Bus {
devices: Vec<(RangeInclusive<u64>, Arc<Mutex<dyn Device>>)>,

pub clint: Clint,
pub plic: Plic,
pub uart: Uart,
pub virtio: Virtio,
dram: Dram,
pub rom: Rom,
clint_addr_range: RangeInclusive<u64>,
plic_addr_range: RangeInclusive<u64>,
}

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<u8>) {
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<u8>) {
self.virtio.initialize(data);
pub fn mount(&mut self, memory_start: u64, device: Arc<Mutex<dyn Device>>) {
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<u64, Exception> {
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)
}
}
}
121 changes: 91 additions & 30 deletions src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the stack is based on where the system wants it, we don't know where we would have to initialize this, and it needs to be done elsewhere. When the DRAM is initialized on the bus, I do set the stack pointer to the end of ram.

// 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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<u64, Exception> {
pub fn execute(&mut self) -> Result<ExecCycle, Exception> {
// 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 {
Expand All @@ -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,
Expand Down
11 changes: 6 additions & 5 deletions src/csr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 //
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand All @@ -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),
),
)
)
Expand Down
Loading