use crate::common::{PhysAddr, SimError};
use crate::config::Config;
use crate::isa::csr;
use crate::isa::encoding::privileged as sys_ops;
use crate::isa::privileged::PrivilegeMode;
use crate::isa::reg;
use crate::sim::memory::GlobalMemory;
use crate::system::SystemState;
use object::{Object, ObjectSymbol};
use std::fs;
pub fn load_binary(path: &str) -> Result<Vec<u8>, SimError> {
fs::read(path).map_err(|source| SimError::FileRead { path: path.to_owned(), source })
}
#[derive(Clone, Debug, Default)]
pub struct KernelBoot {
pub kernel: Option<String>,
pub firmware: Option<String>,
pub dtb: Option<String>,
}
enum Firmware {
Jump(String),
Dynamic(String),
}
impl Firmware {
fn find(boot: &KernelBoot) -> Option<Self> {
if let Some(path) = &boot.firmware {
return Some(Self::Jump(path.clone()));
}
let jump = "software/linux/output/fw_jump.bin";
let dynamic = "software/linux/output/fw_dynamic.bin";
if fs::metadata(jump).is_ok() {
Some(Self::Jump(jump.to_owned()))
} else if fs::metadata(dynamic).is_ok() {
Some(Self::Dynamic(dynamic.to_owned()))
} else {
None
}
}
fn path(&self) -> &str {
match self {
Self::Jump(path) | Self::Dynamic(path) => path,
}
}
}
pub(crate) fn setup_kernel_load(
state: &mut SystemState,
config: &Config,
boot: &KernelBoot,
) -> Result<(), SimError> {
let ram_base = config.system.ram_base;
let opensbi_addr = ram_base;
let kernel_addr = ram_base + 0x200000;
let dtb_addr = ram_base + 0x2200000;
if let Some(path) = &boot.dtb {
let dtb_data = load_binary(path)?;
state.load_binary_at(&dtb_data, PhysAddr::new(dtb_addr));
} else {
let dtb_data = crate::system::dtb::generate_dtb(config);
state.load_binary_at(&dtb_data, PhysAddr::new(dtb_addr));
}
if let Some(firmware) = Firmware::find(boot) {
let sbi_data = load_binary(firmware.path())?;
state.load_binary_at(&sbi_data, PhysAddr::new(opensbi_addr));
let default_kernel_path = "software/linux/output/Image";
let kernel_path = boot.kernel.as_deref().unwrap_or(default_kernel_path);
if fs::metadata(kernel_path).is_ok() {
let kernel_data = load_binary(kernel_path)?;
state.load_binary_at(&kernel_data, PhysAddr::new(kernel_addr));
} else {
tracing::warn!(target: "rvsim::loader", path = kernel_path, "no kernel image; booting firmware only");
}
for hart in &mut state.harts {
hart.pc = opensbi_addr;
hart.privilege = PrivilegeMode::Machine;
hart.regs.write(reg::REG_A0, u64::from(hart.hart_id.val()));
hart.regs.write(reg::REG_A1, dtb_addr);
}
if matches!(firmware, Firmware::Dynamic(_)) {
const FW_DYNAMIC_INFO_MAGIC: u64 = 0x4942534f;
const FW_DYNAMIC_INFO_VERSION: u64 = 2;
const NEXT_MODE_S: u64 = 1;
let info_addr = dtb_addr - 0x200;
let fields: [u64; 7] = [
FW_DYNAMIC_INFO_MAGIC,
FW_DYNAMIC_INFO_VERSION,
kernel_addr,
NEXT_MODE_S,
0,
u64::MAX,
dtb_addr,
];
let mut info_bytes = Vec::with_capacity(56);
for field in &fields {
info_bytes.extend_from_slice(&field.to_le_bytes());
}
state.load_binary_at(&info_bytes, PhysAddr::new(info_addr));
for hart in &mut state.harts {
hart.regs.write(reg::REG_A2, info_addr);
}
} else {
for hart in &mut state.harts {
hart.regs.write(reg::REG_A2, 0);
}
}
} else {
let load_addr = ram_base + config.system.kernel_offset;
state.load_binary_at(&sys_ops::MRET.to_le_bytes(), PhysAddr::new(ram_base));
for core in 0..state.cores.len() {
let mut ctx = state.core_ctx(core);
ctx.hart.pc = ram_base;
ctx.hart.privilege = PrivilegeMode::Machine;
ctx.csr_write(csr::MEPC, load_addr);
let hart_id = u64::from(ctx.hart.hart_id.val());
ctx.hart.regs.write(reg::REG_A0, hart_id);
ctx.hart.regs.write(reg::REG_A1, dtb_addr);
}
}
Ok(())
}
#[derive(Debug)]
pub(crate) struct ElfLoadResult {
pub entry: u64,
pub tohost_addr: Option<u64>,
}
pub(crate) fn try_load_elf(data: &[u8], memory: &mut GlobalMemory) -> Option<ElfLoadResult> {
if data.len() < 4 || &data[..4] != b"\x7fELF" {
return None;
}
let file = object::File::parse(data).ok()?;
let entry = file.entry();
for segment in file.segments() {
use object::ObjectSegment;
let p_memsz = segment.size();
if p_memsz == 0 {
continue;
}
let paddr = segment.address();
if let Ok(seg_data) = segment.data() {
if !seg_data.is_empty() {
memory.load(PhysAddr::new(paddr), seg_data);
}
let p_filesz = seg_data.len() as u64;
if p_memsz > p_filesz {
let bss_start = paddr + p_filesz;
let bss_size = (p_memsz - p_filesz) as usize;
memory.load(PhysAddr::new(bss_start), &vec![0u8; bss_size]);
}
} else if p_memsz > 0 {
memory.load(PhysAddr::new(paddr), &vec![0u8; p_memsz as usize]);
}
}
let tohost_addr = file.symbols().find(|s| s.name() == Ok("tohost")).map(|s| s.address());
Some(ElfLoadResult { entry, tohost_addr })
}
#[cfg(test)]
#[allow(clippy::unwrap_used, unused_results)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn test_try_load_elf_invalid() {
let mut memory = GlobalMemory::new(None, 1, 64);
let data = b"NOT AN ELF FILE";
let result = try_load_elf(data, &mut memory);
assert!(result.is_none());
}
#[test]
fn test_try_load_elf_too_short() {
let mut memory = GlobalMemory::new(None, 1, 64);
let data = b"EL";
let result = try_load_elf(data, &mut memory);
assert!(result.is_none());
}
#[test]
fn test_load_binary_success() {
let mut temp_file = tempfile::NamedTempFile::new().unwrap();
temp_file.write_all(b"Hello World").unwrap();
let path = temp_file.path().to_str().unwrap();
let data = load_binary(path).unwrap();
assert_eq!(data, b"Hello World");
}
#[test]
fn test_load_binary_missing_file() {
let result = load_binary("/nonexistent/path/that/cannot/exist.bin");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("/nonexistent/path/that/cannot/exist.bin"));
}
#[test]
fn test_setup_kernel_load_fallback() {
let config = Config::default();
let mut state = SystemState::build(&config, "");
setup_kernel_load(&mut state, &config, &KernelBoot::default()).unwrap();
let ram_base = config.system.ram_base;
let load_addr = ram_base + config.system.kernel_offset;
assert_eq!(state.harts[0].pc, ram_base);
assert_eq!(state.harts[0].privilege, PrivilegeMode::Machine);
assert_eq!(state.core_ctx(0).csr_read(csr::MEPC), load_addr);
assert_eq!(state.harts[0].regs.read(reg::REG_A0), 0);
assert_eq!(state.harts[0].regs.read(reg::REG_A1), ram_base + 0x2200000);
}
}