use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{AccessConstraints, AddressSpace, MemAttrs, MemOps, MemResult};
use crate::core::space::{Region, RegionRef};
use crate::core::sync::{LockRank, Mutex};
use crate::machine::realize::{BindCtx, Instance};
use super::dt::{CpuSpec, TreeConfig};
pub const CLASS_NAME: &str = "riscv.boot";
pub const DTB_OFFSET: u64 = 0x20;
const ENTRY_OFFSET: u64 = 0x18;
pub const DEFAULT_SIZE: u64 = 0xf000;
pub const DEFAULT_ENTRY: u64 = 0x8000_0000;
pub mod asm {
#[must_use]
pub const fn auipc(rd: u32, imm: u32) -> u32 {
(imm << 12) | (rd << 7) | 0b0010111
}
#[must_use]
pub const fn addi(rd: u32, rs1: u32, imm: i32) -> u32 {
i_type(0b0010011, 0b000, rd, rs1, imm)
}
#[must_use]
pub const fn ld(rd: u32, rs1: u32, imm: i32) -> u32 {
i_type(0b0000011, 0b011, rd, rs1, imm)
}
#[must_use]
pub const fn lw(rd: u32, rs1: u32, imm: i32) -> u32 {
i_type(0b0000011, 0b010, rd, rs1, imm)
}
#[must_use]
pub const fn jalr(rd: u32, rs1: u32, imm: i32) -> u32 {
i_type(0b1100111, 0b000, rd, rs1, imm)
}
#[must_use]
pub const fn csrr(rd: u32, csr: u32) -> u32 {
i_type(0b1110011, 0b010, rd, 0, csr as i32)
}
const fn i_type(opcode: u32, funct3: u32, rd: u32, rs1: u32, imm: i32) -> u32 {
(((imm as u32) & 0xfff) << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
}
pub const T0: u32 = 5;
pub const A0: u32 = 10;
pub const A1: u32 = 11;
pub const ZERO: u32 = 0;
pub const CSR_MHARTID: u32 = 0xf14;
}
#[derive(Debug)]
struct Contents {
image: Vec<u8>,
error: Option<String>,
dtb_len: usize,
space: Option<Arc<AddressSpace>>,
}
#[derive(Debug)]
struct Rom {
contents: Mutex<Contents>,
len: u64,
}
impl MemOps for Rom {
fn read(&self, offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
let contents = self.contents.lock();
for (i, byte) in dst.iter_mut().enumerate() {
let at = offset + i as u64;
*byte = usize::try_from(at)
.ok()
.and_then(|at| contents.image.get(at))
.copied()
.unwrap_or(0);
}
Ok(())
}
fn write(&self, _offset: u64, _src: &[u8], _attrs: MemAttrs) -> MemResult {
Err(BusError::BadAccess)
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::ANY
}
}
#[derive(Debug)]
pub struct BootRom {
rom: Arc<Rom>,
region: RegionRef,
entry: u64,
rv32: bool,
config: TreeConfig,
}
impl BootRom {
pub fn new(props: &Props) -> Result<BootRom> {
let mut r = props.reader();
let size = r.or_size("size", DEFAULT_SIZE)?;
let entry = r.or_addr("entry", DEFAULT_ENTRY)?;
let harts = r.or_range("harts", 1u64, 1..=4096)?;
let boot_hart = r.or_range("boot-hart", 0u64, 0..=harts - 1)?;
let isa = r.or("isa", String::from("rv64imafdc"))?;
let mmu = r.or("mmu", String::from("sv39"))?;
let bootargs = r.or("bootargs", String::new())?;
let model = r.or("model", String::from("rsemu riscv-virt"))?;
let timebase = r.or_range(
"timebase",
u64::from(super::clint::DEFAULT_TIMEBASE_HZ),
1..=u64::from(u32::MAX),
)?;
r.finish()?;
if size < DTB_OFFSET + 0x100 {
return Err(Error::Property(format!(
"property `size`: a boot ROM of {size} byte(s) has no room for a device tree; \
it needs at least {}",
DTB_OFFSET + 0x100
)));
}
let rv32 = isa.starts_with("rv32");
let rom = Arc::new(Rom {
contents: Mutex::with_rank(
LockRank::DEVICE,
Contents {
image: Vec::new(),
error: None,
dtb_len: 0,
space: None,
},
),
len: size,
});
let region: RegionRef = Arc::new(Region::io(
"riscv.boot",
size,
Arc::clone(&rom) as Arc<dyn MemOps>,
));
Ok(BootRom {
rom,
region,
entry,
rv32,
config: TreeConfig {
model,
bootargs,
cpus: CpuSpec {
harts: harts as u32,
isa,
mmu: if mmu == "none" { String::new() } else { mmu },
boot_hart: boot_hart as u32,
},
default_timebase_hz: timebase as u32,
},
})
}
#[must_use]
pub fn entry(&self) -> u64 {
self.entry
}
#[must_use]
pub fn last_error(&self) -> Option<String> {
self.rom.contents.lock().error.clone()
}
#[must_use]
pub fn device_tree(&self) -> Vec<u8> {
let contents = self.rom.contents.lock();
let at = DTB_OFFSET as usize;
contents
.image
.get(at..at + contents.dtb_len)
.map(<[u8]>::to_vec)
.unwrap_or_default()
}
#[must_use]
pub fn stub(&self) -> Vec<u8> {
use asm::{A0, A1, CSR_MHARTID, T0, ZERO};
let load = if self.rv32 {
asm::lw(T0, T0, ENTRY_OFFSET as i32)
} else {
asm::ld(T0, T0, ENTRY_OFFSET as i32)
};
let words = [
asm::auipc(T0, 0),
asm::addi(A1, T0, DTB_OFFSET as i32),
asm::csrr(A0, CSR_MHARTID),
load,
asm::jalr(ZERO, T0, 0),
0,
];
let mut out = Vec::with_capacity(ENTRY_OFFSET as usize + 8);
for word in words {
out.extend_from_slice(&word.to_le_bytes());
}
debug_assert_eq!(out.len() as u64, ENTRY_OFFSET);
out.extend_from_slice(&self.entry.to_le_bytes());
out
}
pub fn regenerate(&self) -> Result<()> {
let space = self.rom.contents.lock().space.clone();
let Some(space) = space else {
return Err(Error::Config {
at: CLASS_NAME.to_string(),
message: String::from(
"a boot ROM needs an address space to describe (`space = mem`)",
),
});
};
let dtb = super::dt::generate(&space, &self.config)?;
let mut image = self.stub();
image.resize(DTB_OFFSET as usize, 0);
image.extend_from_slice(&dtb);
if image.len() as u64 > self.rom.len {
return Err(Error::Config {
at: CLASS_NAME.to_string(),
message: format!(
"the generated device tree needs {} byte(s) and the boot ROM is {}; \
give the object a larger `size`",
image.len(),
self.rom.len
),
});
}
let mut contents = self.rom.contents.lock();
contents.dtb_len = dtb.len();
contents.image = image;
contents.error = None;
Ok(())
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: 1,
summary: "the reset vector, and the device tree generated from the realized machine",
properties: &[
PropertySpec {
name: "size",
kind: ValueKind::Size,
required: false,
summary: "how much address space the ROM answers (default 60K)",
},
PropertySpec {
name: "entry",
kind: ValueKind::Addr,
required: false,
summary: "the address the stub jumps to (default 0x80000000)",
},
PropertySpec {
name: "harts",
kind: ValueKind::Uint,
required: false,
summary: "how many harts the tree describes (default 1)",
},
PropertySpec {
name: "boot-hart",
kind: ValueKind::Uint,
required: false,
summary: "the hart the firmware is entered on (default 0)",
},
PropertySpec {
name: "isa",
kind: ValueKind::Str,
required: false,
summary: "the `riscv,isa` string the tree reports (default rv64imafdc)",
},
PropertySpec {
name: "mmu",
kind: ValueKind::Str,
required: false,
summary: "the `mmu-type` suffix, or `none` (default sv39)",
},
PropertySpec {
name: "bootargs",
kind: ValueKind::Str,
required: false,
summary: "the kernel command line, as `/chosen/bootargs`",
},
PropertySpec {
name: "model",
kind: ValueKind::Str,
required: false,
summary: "the tree's `model` property",
},
PropertySpec {
name: "timebase",
kind: ValueKind::Uint,
required: false,
summary: "the timebase to report when no CLINT is mapped, in Hz",
},
],
construct: |props| Ok(Box::new(BootRom::new(props)?)),
};
impl Device for BootRom {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
if let Err(e) = self.regenerate() {
self.rom.contents.lock().error = Some(e.to_string());
}
}
fn region(&self, name: &str) -> Option<RegionRef> {
matches!(name, "" | "rom").then(|| Arc::clone(&self.region))
}
}
impl Instance for BootRom {
fn bind(&self, ctx: &BindCtx<'_>) -> Result<()> {
let space = ctx.space().ok_or_else(|| Error::Config {
at: ctx.path().to_string(),
message: String::from("a boot ROM needs an address space to describe (`space = mem`)"),
})?;
self.rom.contents.lock().space = Some(Arc::clone(space));
Ok(())
}
}
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(BootRom::new(props)?)))
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PropSchema};
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("size", ValueKind::Size))
.prop(PropSchema::new("entry", ValueKind::Addr))
.prop(PropSchema::new("harts", ValueKind::Uint).range(1, 4096))
.prop(PropSchema::new("boot-hart", ValueKind::Uint))
.prop(PropSchema::new("isa", ValueKind::Str))
.prop(PropSchema::new("mmu", ValueKind::Str))
.prop(PropSchema::new("bootargs", ValueKind::Str))
.prop(PropSchema::new("model", ValueKind::Str))
.prop(PropSchema::new("timebase", ValueKind::Uint))
.region("")
.region("rom")
}