mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-13 07:48:30 +00:00
901 lines
42 KiB
Rust
901 lines
42 KiB
Rust
use core::{mem::size_of, slice};
|
|
|
|
#[cfg(feature = "atsama5d27")]
|
|
pub use crate::platform::atsama5d27::load::InitialProcess;
|
|
use crate::*;
|
|
|
|
#[repr(C)]
|
|
#[cfg(not(feature = "atsama5d27"))]
|
|
pub struct InitialProcess {
|
|
/// The RISC-V SATP value, which includes the offset of the root page
|
|
/// table plus the process ID.
|
|
pub satp: usize,
|
|
|
|
/// Where execution begins
|
|
pub entrypoint: usize,
|
|
|
|
/// Address of the top of the stack
|
|
pub sp: usize,
|
|
|
|
/// Address of the start of the env block
|
|
pub env: usize,
|
|
}
|
|
|
|
/// Phase 1:
|
|
///
|
|
/// Copy processes from FLASH to RAM, allocating memory one page at a time starting from high
|
|
/// addresses and working down. The allocations are computed from the kernel arguments, and the
|
|
/// allocated amount is re-computed and used in phase 2 to setup the page tables.
|
|
///
|
|
/// We don't memorize the allocated results (in part because we don't have malloc/alloc to stick
|
|
/// the table, and we don't know a priori how big it will be); we simply memorize the maximum extent,
|
|
/// after which we allocate the book-keeping tables.
|
|
pub fn phase_1(cfg: &mut BootConfig, detached_app: bool) {
|
|
// Allocate space for the stack pointer.
|
|
// The bootloader should have placed the stack pointer at the end of RAM
|
|
// prior to jumping to our program. Reserve space for the stack, so that it does not smash
|
|
// run time allocations.
|
|
//
|
|
// All other allocations will be placed below the stack pointer.
|
|
//
|
|
// As of Xous 0.8, the top page is bootloader stack, and the page below that is the 'clean suspend' page.
|
|
cfg.init_size += GUARD_MEMORY_BYTES;
|
|
println!("Loader runtime stack should not exceed: {:x}", cfg.get_top() as usize);
|
|
|
|
// The first region is defined as being "main RAM", which will be used
|
|
// to keep track of allocations.
|
|
println!("Allocating regions");
|
|
allocate_regions(cfg);
|
|
|
|
// The kernel, as well as initial processes, are all stored in RAM.
|
|
println!("Allocating processes");
|
|
allocate_processes(cfg);
|
|
|
|
// Copy the arguments, if requested
|
|
if cfg.no_copy {
|
|
// TODO: place args into cfg.args
|
|
} else {
|
|
println!("Copying args");
|
|
copy_args(cfg, detached_app);
|
|
}
|
|
if VDBG {
|
|
let check_iter = cfg.args.iter();
|
|
for _a in check_iter {
|
|
crate::println!(
|
|
"{}: base {:x} size {:x} data {:x?}",
|
|
core::str::from_utf8(&_a.name.to_le_bytes()).unwrap_or("invalid name"),
|
|
_a.this as usize,
|
|
_a.size,
|
|
_a.data
|
|
);
|
|
}
|
|
}
|
|
|
|
// All further allocations must be page-aligned.
|
|
#[cfg(feature = "swap")]
|
|
if SDBG {
|
|
println!(" -> cfg end pre-alignment: {:x}(-{:x})", cfg.get_top() as usize, cfg.init_size);
|
|
}
|
|
cfg.init_size = (cfg.init_size + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
|
#[cfg(feature = "swap")]
|
|
if SDBG {
|
|
println!(" -> cfg end post-alignment: {:x}(-{:x})", cfg.get_top() as usize, cfg.init_size);
|
|
}
|
|
|
|
#[cfg(feature = "swap")]
|
|
allocate_swap(cfg);
|
|
|
|
// debug addresses of all key cfg fields
|
|
#[cfg(feature = "debug-print")]
|
|
{
|
|
println!("*** CFG debug ***");
|
|
println!(" base_addr: {:x}", cfg.base_addr as usize);
|
|
println!(
|
|
" regions: {:x} -> {:x}",
|
|
cfg.regions.as_ptr() as usize,
|
|
cfg.regions.as_ptr() as usize + cfg.regions.len() * size_of::<MemoryRegionExtra>()
|
|
);
|
|
println!(
|
|
" sram_start: {:x}, sram_end: {:x}",
|
|
cfg.sram_start as usize,
|
|
cfg.sram_start as usize + cfg.sram_size
|
|
);
|
|
println!(
|
|
" RPT: {:x} -> {:x}",
|
|
cfg.runtime_page_tracker.as_ptr() as usize,
|
|
cfg.runtime_page_tracker.as_ptr() as usize
|
|
+ cfg.runtime_page_tracker.len() * size_of::<XousAlloc>()
|
|
);
|
|
println!(
|
|
" XPT: {:x} -> {:x}",
|
|
cfg.extra_page_tracker.as_ptr() as usize,
|
|
cfg.extra_page_tracker.as_ptr() as usize + cfg.extra_page_tracker.len() * size_of::<XousPid>()
|
|
);
|
|
println!(
|
|
" processes: {:x} -> {:x}",
|
|
cfg.processes.as_ptr() as usize,
|
|
cfg.processes.as_ptr() as usize + cfg.processes.len() * size_of::<InitialProcess>()
|
|
);
|
|
#[cfg(feature = "swap")]
|
|
{
|
|
if let Some(hal) = cfg.swap_hal.as_ref() {
|
|
println!(
|
|
" (Stack) HAL: {:x} -> {:x}",
|
|
hal as *const SwapHal as usize,
|
|
hal as *const SwapHal as usize + size_of::<SwapHal>()
|
|
);
|
|
} else {
|
|
println!(" NO SWAP HAL!!!");
|
|
}
|
|
if let Some(hal) = cfg.swap_hal.as_ref() {
|
|
println!(
|
|
" (Stack) HAL decrypt buf: {:x} -> {:x}",
|
|
hal.buf_as_ref().as_ptr() as usize,
|
|
hal.buf_as_ref().as_ptr() as usize + hal.buf_as_ref().len() * size_of::<u8>()
|
|
);
|
|
} else {
|
|
println!(" NO SWAP HAL!!!");
|
|
}
|
|
println!(
|
|
" swap_root: {:x} -> {:x}",
|
|
cfg.swap_root.as_ptr() as usize,
|
|
cfg.swap_root.as_ptr() as usize + cfg.swap_root.len() * size_of::<usize>()
|
|
);
|
|
}
|
|
}
|
|
|
|
println!("Runtime Page Tracker: {} bytes", cfg.runtime_page_tracker.len());
|
|
#[cfg(feature = "swap")]
|
|
if SDBG {
|
|
println!("Occupied RPT entries (at this point, the list should be nil):");
|
|
for (_i, entry) in cfg.runtime_page_tracker.iter().enumerate() {
|
|
if entry.raw_vpn() != 0 {
|
|
println!(" {:x}: {:x} [{}]", _i, entry.raw_vpn(), entry.timestamp());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Additionally, from this point on all allocations come from
|
|
// their respective processes rather than kernel memory.
|
|
|
|
// Copy the processes to RAM, if requested.
|
|
if !cfg.no_copy {
|
|
println!("Copying processes");
|
|
copy_processes(cfg);
|
|
}
|
|
// activate this to debug stack-smashing during copy_process(). The RPT is the first structure that gets
|
|
// smashed if the stack overflows! It should be all 0's if the stack did not overrun.
|
|
#[cfg(feature = "swap")]
|
|
if SDBG && VDBG {
|
|
for (_i, _r) in cfg.runtime_page_tracker
|
|
[cfg.runtime_page_tracker.len() - 1024.min(cfg.runtime_page_tracker.len())..]
|
|
.chunks(32)
|
|
.enumerate()
|
|
{
|
|
println!(" rpt {:08x}: {:02x?}", cfg.runtime_page_tracker.len() - 1024 + _i * 32, _r);
|
|
}
|
|
}
|
|
|
|
// Mark all pages as in-use by the kernel.
|
|
// NOTE: This causes the .text section to be owned by the kernel! This
|
|
// will require us to transfer ownership in `stage3`.
|
|
// Note also that we skip the first four indices, causing the stack to be
|
|
// returned to the process pool.
|
|
|
|
// We also skip the an additional index as that is the clean suspend page. This
|
|
// needs to be claimed by the susres server before the kernel allocates it.
|
|
// Lower numbered indices corresponding to higher address pages.
|
|
println!("Marking loader pages as in-use");
|
|
for i in ((GUARD_MEMORY_BYTES / PAGE_SIZE) + 1)..(cfg.init_size / PAGE_SIZE) {
|
|
cfg.runtime_page_tracker[cfg.sram_size / PAGE_SIZE - i] = XousAlloc::from(1);
|
|
}
|
|
}
|
|
|
|
/// Allocate and initialize memory regions.
|
|
/// Returns a pointer to the start of the memory region.
|
|
pub fn allocate_regions(cfg: &mut BootConfig) {
|
|
// Number of individual pages in the system
|
|
let mut rpt_pages = cfg.sram_size / PAGE_SIZE;
|
|
// Round the tracker to a multiple of the pointer size, so as to keep memory
|
|
// operations fast.
|
|
rpt_pages = (rpt_pages + size_of::<usize>() - 1) & !(size_of::<usize>() - 1);
|
|
|
|
// allocate the RPT
|
|
#[cfg(not(feature = "swap"))]
|
|
{
|
|
cfg.init_size += rpt_pages * mem::size_of::<XousAlloc>();
|
|
}
|
|
#[cfg(feature = "swap")]
|
|
{
|
|
println!("RPT raw pages: {:x}", rpt_pages);
|
|
|
|
// Round the allocation to a multiple of the page size, so that it can be mapped into userspace
|
|
let proposed_alloc = rpt_pages * mem::size_of::<XousAlloc>();
|
|
let page_aligned_alloc = (proposed_alloc + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
|
assert!(cfg.init_size & 0xFFF == 0, "init_size should be page aligned going into the RPT alloc");
|
|
cfg.init_size += page_aligned_alloc;
|
|
assert!(cfg.init_size & 0xFFF == 0, "init_size should be page aligned leaving the RPT alloc");
|
|
println!(
|
|
"RPT ALLOC init_size before: {:x}, ext pages: {:x}, minimal alloc: {:x}, page-aligned alloc: {:x}, sizeof entry: {:x}",
|
|
cfg.init_size,
|
|
cfg.extra_pages,
|
|
proposed_alloc,
|
|
page_aligned_alloc,
|
|
size_of::<XousAlloc>(),
|
|
);
|
|
}
|
|
|
|
// Clear all memory pages such that they're not owned by anyone
|
|
let runtime_page_tracker = cfg.get_top();
|
|
println!("rpt value: {:x}", runtime_page_tracker as usize);
|
|
#[cfg(feature = "swap")]
|
|
{
|
|
assert!(runtime_page_tracker as usize & 0xFFF == 0); // this needs to be page-aligned for swap to work
|
|
}
|
|
assert!((runtime_page_tracker as usize) < (cfg.sram_start as usize) + cfg.sram_size);
|
|
unsafe {
|
|
bzero(
|
|
runtime_page_tracker as *mut XousAlloc,
|
|
(runtime_page_tracker as *mut XousAlloc).add(rpt_pages),
|
|
);
|
|
}
|
|
|
|
cfg.runtime_page_tracker =
|
|
unsafe { slice::from_raw_parts_mut(runtime_page_tracker as *mut XousAlloc, rpt_pages) };
|
|
#[cfg(feature = "swap")]
|
|
if SDBG {
|
|
println!(
|
|
" -> RPT range: {:x} - {:x}",
|
|
runtime_page_tracker as usize,
|
|
runtime_page_tracker as usize + rpt_pages * size_of::<XousAlloc>()
|
|
);
|
|
}
|
|
|
|
// allocate the XPT
|
|
let mut xpt_pages = 0;
|
|
for region in cfg.regions.iter() {
|
|
println!(
|
|
"Discovered memory region {:08x} ({:08x} - {:08x}) -- {} bytes",
|
|
region.name,
|
|
region.start,
|
|
region.start + region.length,
|
|
region.length
|
|
);
|
|
let region_length_rounded = (region.length as usize + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
|
xpt_pages += region_length_rounded / PAGE_SIZE;
|
|
}
|
|
// Round the tracker to a multiple of the pointer size, so as to keep memory
|
|
// operations fast.
|
|
xpt_pages = (xpt_pages + size_of::<usize>() - 1) & !(size_of::<usize>() - 1);
|
|
println!(
|
|
"XPT ALLOC init_size bef: {:x}, ext pages: {:x}, rpt alloc: {:x}",
|
|
cfg.init_size,
|
|
cfg.extra_pages,
|
|
xpt_pages * size_of::<XousPid>()
|
|
);
|
|
|
|
cfg.init_size += xpt_pages * size_of::<XousPid>();
|
|
|
|
// Clear all memory pages such that they're not owned by anyone
|
|
let extra_page_tracker = cfg.get_top();
|
|
println!("xpt value: {:x}", extra_page_tracker as usize);
|
|
assert!((extra_page_tracker as usize) < (cfg.sram_start as usize) + cfg.sram_size);
|
|
unsafe {
|
|
bzero(extra_page_tracker as *mut XousPid, (extra_page_tracker as *mut XousPid).add(xpt_pages));
|
|
}
|
|
|
|
cfg.extra_page_tracker =
|
|
unsafe { slice::from_raw_parts_mut(extra_page_tracker as *mut XousPid, xpt_pages) };
|
|
#[cfg(feature = "swap")]
|
|
if SDBG {
|
|
println!(
|
|
" -> XPT range: {:x} - {:x}",
|
|
extra_page_tracker as usize,
|
|
extra_page_tracker as usize + xpt_pages * size_of::<XousPid>()
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn allocate_processes(cfg: &mut BootConfig) {
|
|
let process_count = cfg.init_process_count + 1;
|
|
println!("Allocating tables for {} processes", process_count);
|
|
let table_size = process_count * mem::size_of::<InitialProcess>();
|
|
// Allocate the process table
|
|
cfg.init_size += table_size;
|
|
let processes = cfg.get_top();
|
|
unsafe {
|
|
bzero(
|
|
processes as *mut InitialProcess,
|
|
(processes as *mut InitialProcess).add(process_count as usize),
|
|
);
|
|
}
|
|
cfg.processes =
|
|
unsafe { slice::from_raw_parts_mut(processes as *mut InitialProcess, process_count as usize) };
|
|
|
|
#[cfg(feature = "swap")]
|
|
{
|
|
if SDBG {
|
|
println!(
|
|
" -> Processes range: {:x} - {:x}",
|
|
processes as usize,
|
|
processes as usize + cfg.processes.len() * mem::size_of::<InitialProcess>()
|
|
);
|
|
}
|
|
let swap_root_pt_size = process_count * mem::size_of::<usize>();
|
|
cfg.init_size += swap_root_pt_size;
|
|
let swap_root_pt = cfg.get_top();
|
|
unsafe {
|
|
bzero(swap_root_pt, swap_root_pt.add(process_count));
|
|
}
|
|
cfg.swap_root = unsafe { slice::from_raw_parts_mut(swap_root_pt, process_count as usize) };
|
|
if SDBG {
|
|
println!(
|
|
" -> Swap root pt range: {:x} - {:x}",
|
|
swap_root_pt as usize,
|
|
swap_root_pt as usize + cfg.swap_root.len() * mem::size_of::<usize>()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "swap")]
|
|
pub fn allocate_swap(cfg: &mut BootConfig) {
|
|
let process_count = cfg.init_process_count + 1;
|
|
let swap_pt_size = process_count * size_of::<PageTable>();
|
|
cfg.init_size += swap_pt_size;
|
|
cfg.swap_offset += swap_pt_size;
|
|
let swap_pt_base = cfg.get_top();
|
|
unsafe {
|
|
bzero(swap_pt_base as *mut PageTable, (swap_pt_base as *mut PageTable).add(cfg.swap_root.len()))
|
|
}
|
|
// The page table proper is "unbound": we don't put it in a slice, we simply put references
|
|
// to each page in cfg.swap_root[]. I wonder if this is UB?
|
|
for (index, root) in cfg.swap_root.iter_mut().enumerate() {
|
|
*root = swap_pt_base as usize + index * mem::size_of::<PageTable>();
|
|
}
|
|
|
|
if SDBG {
|
|
println!(
|
|
" -> Swap pt data range: {:x} - {:x}",
|
|
swap_pt_base as usize,
|
|
swap_pt_base as usize + cfg.swap_root.len() * mem::size_of::<PageTable>()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[cfg(all(feature = "swap", feature = "bao1x"))]
|
|
pub fn copy_args(cfg: &mut BootConfig, _detached_app: bool) {
|
|
// With swap enabled, copy_args also merges the IniS arguments from the swap region into the kernel
|
|
// arguments, and patches the length field accordingly.
|
|
|
|
// Read in the swap arguments: should be located at beginning of the first page of swap.
|
|
// Safety: only safe because we know that the decrypt was setup by read_swap_config(), and no pages
|
|
// were decrypted between then and now!
|
|
let page0 = unsafe { cfg.swap_hal.as_mut().unwrap().get_decrypt() };
|
|
let swap_args = KernelArguments::new(page0.as_ptr() as *const usize);
|
|
|
|
// Carve out a much larger region than anticipated for argument processing. A "typical" target for
|
|
// the new argument stack is around 1800 bytes.
|
|
//
|
|
// It's assumed we have at least 4 pages of RAM at this point because we haven't allocated anything and
|
|
// our target should have more memory than that. So, pass a total of 16,384 bytes region with the
|
|
// anticipation that the actual allocation will be much smaller than that.
|
|
let dest_region = unsafe {
|
|
core::slice::from_raw_parts_mut(
|
|
(cfg.get_top() as usize - PAGE_SIZE * 4) as *mut u32,
|
|
PAGE_SIZE * 4 / size_of::<u32>(),
|
|
)
|
|
};
|
|
|
|
// Replace the original args pointer in FLASH with a new copy in RAM that incorporates any merged
|
|
// regions that the kernel should be aware of.
|
|
let args_size: usize;
|
|
(cfg.args, args_size) = unsafe {
|
|
cfg.args
|
|
.merge_args(&[swap_args], &[u32::from_le_bytes(*b"IniS")], dest_region)
|
|
.expect("Couldn't merge kernel arguments")
|
|
};
|
|
|
|
// Advance the init_size pointer by the actual allocation consumed by the merge_args() function.
|
|
cfg.init_size += args_size;
|
|
crate::println!("Argument merge done, total size consumed: {}", args_size);
|
|
}
|
|
|
|
/// No swap but bao1x implies a dabao configuration. Check for a detached-app image.
|
|
#[cfg(all(not(feature = "swap"), feature = "bao1x"))]
|
|
pub fn copy_args(cfg: &mut BootConfig, detached_app: bool) {
|
|
if detached_app {
|
|
// Read in the detached app arguments.
|
|
// Safety: only safe because the image is aligned.
|
|
let app_args = KernelArguments::new(
|
|
(bao1x_api::offsets::dabao::APP_RRAM_START + bao1x_api::signatures::SIGBLOCK_LEN) as *const usize,
|
|
);
|
|
|
|
// Carve out a much larger region than anticipated for argument processing. A "typical" target for
|
|
// the new argument stack is around 1800 bytes.
|
|
//
|
|
// It's assumed we have at least 4 pages of RAM at this point because we haven't allocated
|
|
// anything and our target should have more memory than that. So, pass a total of
|
|
// 16,384 bytes region with the anticipation that the actual allocation will be much
|
|
// smaller than that.
|
|
let dest_region = unsafe {
|
|
core::slice::from_raw_parts_mut(
|
|
(cfg.get_top() as usize - PAGE_SIZE * 4) as *mut u32,
|
|
PAGE_SIZE * 4 / size_of::<u32>(),
|
|
)
|
|
};
|
|
|
|
// Replace the original args pointer in FLASH with a new copy in RAM that incorporates any merged
|
|
// regions that the kernel should be aware of.
|
|
let args_size: usize;
|
|
(cfg.args, args_size) = unsafe {
|
|
cfg.args
|
|
.merge_args(&[app_args], &[u32::from_le_bytes(*b"IniF")], dest_region)
|
|
.expect("Couldn't merge kernel arguments")
|
|
};
|
|
|
|
// Advance the init_size pointer by the actual allocation consumed by the merge_args() function.
|
|
cfg.init_size += args_size;
|
|
crate::println!("Argument merge done, total size consumed: {}", args_size);
|
|
} else {
|
|
// Straight copy of the kernel arguments to RAM, no merging.
|
|
cfg.init_size += cfg.args.size();
|
|
let runtime_arg_buffer = cfg.get_top();
|
|
unsafe { memcpy(runtime_arg_buffer, cfg.args.base as *const usize, cfg.args.size() as usize) };
|
|
cfg.args = KernelArguments::new(runtime_arg_buffer);
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "swap")]
|
|
fn remaining_in_page(addr: usize) -> usize { PAGE_SIZE - (addr & (PAGE_SIZE - 1)) }
|
|
|
|
#[cfg(all(not(feature = "swap"), not(feature = "bao1x")))]
|
|
pub fn copy_args(cfg: &mut BootConfig, _detached_app: bool) {
|
|
// Copy the args list to target RAM
|
|
cfg.init_size += cfg.args.size();
|
|
let runtime_arg_buffer = cfg.get_top();
|
|
unsafe {
|
|
#[allow(clippy::cast_ptr_alignment)]
|
|
memcpy(runtime_arg_buffer, cfg.args.base as *const usize, cfg.args.size() as usize)
|
|
};
|
|
cfg.args = KernelArguments::new(runtime_arg_buffer);
|
|
}
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
enum TagType {
|
|
IniE,
|
|
IniF,
|
|
IniS,
|
|
XKrn,
|
|
Other,
|
|
}
|
|
impl From<u32> for TagType {
|
|
fn from(code: u32) -> Self {
|
|
if code == u32::from_le_bytes(*b"IniE") {
|
|
TagType::IniE
|
|
} else if code == u32::from_le_bytes(*b"IniF") {
|
|
TagType::IniF
|
|
} else if code == u32::from_le_bytes(*b"IniS") {
|
|
TagType::IniS
|
|
} else if code == u32::from_le_bytes(*b"XKrn") {
|
|
TagType::XKrn
|
|
} else {
|
|
TagType::Other
|
|
}
|
|
}
|
|
}
|
|
impl TagType {
|
|
#[cfg(feature = "debug-print")]
|
|
pub fn to_str(&self) -> &'static str {
|
|
match self {
|
|
TagType::IniE => "IniE",
|
|
TagType::IniF => "IniF",
|
|
TagType::IniS => "IniS",
|
|
TagType::XKrn => "XKrn",
|
|
TagType::Other => "Other",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Copy program data from the SPI flash into newly-allocated RAM
|
|
/// located at the end of memory space.
|
|
fn copy_processes(cfg: &mut BootConfig) {
|
|
let mut _pid = 1;
|
|
for tag in cfg.args.iter() {
|
|
let tag_type = TagType::from(tag.name);
|
|
match tag_type {
|
|
TagType::IniF | TagType::IniE => {
|
|
_pid += 1;
|
|
let mut top = core::ptr::null_mut::<u8>();
|
|
|
|
let inie = MiniElf::new(&tag);
|
|
let mut src_paddr =
|
|
unsafe { cfg.base_addr.add(inie.load_offset as usize / size_of::<usize>()) } as *const u8;
|
|
|
|
println!("\n\n{} {} has {} sections", tag_type.to_str(), _pid, inie.sections.len());
|
|
println!(
|
|
"Initial top: {:x}, extra_pages: {:x}, init_size: {:x}, base_addr: {:x}",
|
|
cfg.get_top() as *mut u8 as u32,
|
|
cfg.extra_pages,
|
|
cfg.init_size,
|
|
cfg.base_addr as u32
|
|
);
|
|
|
|
let mut last_page_vaddr = 0;
|
|
let mut last_section_perfect_fit = false;
|
|
|
|
for section in inie.sections.iter() {
|
|
let flags = section.flags() as u8;
|
|
// any section that requires "write" must be copied to RAM
|
|
// note that ELF helpfully adds a 4096-byte gap between non-write pages and write-pages
|
|
// allowing us to just trundle through the pages and not have to deal with partially
|
|
// writeable pages.
|
|
// IniE is always copy_to_ram
|
|
let copy_to_ram = (flags & MINIELF_FLG_W != 0) || (tag_type == TagType::IniE);
|
|
|
|
if (section.virt as usize) < last_page_vaddr {
|
|
panic!(
|
|
"init section addresses are not strictly increasing (new virt: {:08x}, last virt: {:08x})",
|
|
section.virt, last_page_vaddr
|
|
);
|
|
}
|
|
|
|
// cfg.extra_pages tracks how many pages of RAM we've allocated so far
|
|
// cfg.top() points to the bottom of the most recently allocated page
|
|
// - so if cfg.extra_pages is 0, nothing is allocated, and cfg.top() points to
|
|
// previously reserved space
|
|
//
|
|
// The section length always matches the stride between sections in physical memory.
|
|
//
|
|
// However, the section length has nothing to do with the distance between sections in
|
|
// virtual memory; the virtual start address is allowed to be an
|
|
// arbitrary number of bytes higher than the previous section end, for
|
|
// alignment and padding reasons.
|
|
if copy_to_ram {
|
|
let mut dst_page_vaddr = section.virt as usize;
|
|
let mut bytes_to_copy = section.len();
|
|
|
|
if (last_page_vaddr & !(PAGE_SIZE - 1)) != (dst_page_vaddr & !(PAGE_SIZE - 1))
|
|
|| last_section_perfect_fit
|
|
{
|
|
// this condition is always true for the first section's first iteration, because
|
|
// current_vpage_addr starts as NULL; thus we are guaranteed to always
|
|
// trigger the page allocate/zero mechanism the first time through the loop
|
|
//
|
|
// `last_section_perfect_fit` triggers a page allocation as well, because in this
|
|
// case we had exactly enough data to fill out the
|
|
// previous section, so we have no more space left in
|
|
// the current page. We don't automatically allocate a new page because
|
|
// if it was actually the very last section we *shouldn't* allocate another page;
|
|
// and we can only know if there's another section
|
|
// available by dropping off the end of the
|
|
// loop and coming back to the surrounding for-loop iterator.
|
|
cfg.extra_pages += 1;
|
|
top = cfg.get_top() as *mut u8;
|
|
unsafe {
|
|
bzero(top, top.add(PAGE_SIZE as usize));
|
|
}
|
|
}
|
|
|
|
// Copy the start copying the source data into virtual memory, until the current
|
|
// page is exhausted.
|
|
while bytes_to_copy > 0 {
|
|
let bytes_remaining_in_vpage = PAGE_SIZE - (dst_page_vaddr & (PAGE_SIZE - 1));
|
|
let copyable_bytes = bytes_remaining_in_vpage.min(bytes_to_copy);
|
|
last_section_perfect_fit = bytes_remaining_in_vpage == bytes_to_copy;
|
|
if !section.no_copy() {
|
|
unsafe {
|
|
memcpy(
|
|
top.add(dst_page_vaddr & (PAGE_SIZE - 1)),
|
|
src_paddr,
|
|
copyable_bytes,
|
|
);
|
|
src_paddr = src_paddr.add(copyable_bytes);
|
|
}
|
|
} else {
|
|
// chunk is already zeroed, because we zeroed the whole page when we got it.
|
|
}
|
|
bytes_to_copy -= copyable_bytes;
|
|
dst_page_vaddr += copyable_bytes;
|
|
|
|
if copyable_bytes == bytes_remaining_in_vpage && bytes_to_copy > 0 {
|
|
// we've reached the end of the vpage, and there's more to copy:
|
|
// grab a new page
|
|
cfg.extra_pages += 1;
|
|
top = cfg.get_top() as *mut u8;
|
|
if bytes_to_copy < PAGE_SIZE {
|
|
// pre-zero out the page if the remaining data won't fill it.
|
|
unsafe {
|
|
bzero(top, top.add(PAGE_SIZE as usize));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// set the vpage based on our current vpage. This allows us to allocate
|
|
// a new vpage on the next iteration in case there is surprise padding in
|
|
// the section load address.
|
|
last_page_vaddr = dst_page_vaddr;
|
|
} else {
|
|
top = cfg.get_top() as *mut u8;
|
|
// forward the FLASH address pointer by the length of the section.
|
|
src_paddr = unsafe { src_paddr.add(section.len()) };
|
|
}
|
|
|
|
if VDBG {
|
|
println!("Looping to the next section");
|
|
println!(
|
|
"top: {:x}, extra_pages: {:x}, init_size: {:x}, base_addr: {:x}",
|
|
cfg.get_top() as *mut u8 as u32,
|
|
cfg.extra_pages,
|
|
cfg.init_size,
|
|
cfg.base_addr as u32
|
|
);
|
|
println!("last_page_vaddr: {:x}", last_page_vaddr);
|
|
}
|
|
}
|
|
println!("Done with sections");
|
|
}
|
|
TagType::IniS => {
|
|
// if swap is not enabled, don't pull this code in, to keep the bootloader light-weight
|
|
#[cfg(feature = "swap")]
|
|
{
|
|
// IniS does not necessarily exist in linear memory space, so it requires special
|
|
// handling. Instead of copying the IniS data into RAM, it's copied
|
|
// into encrypted swap (e.g. the RAM area (again, not necessarily in
|
|
// linear space) reserved for swap processes).
|
|
|
|
/*
|
|
Example of an IniS section:
|
|
1 IniS: entrypoint @ 00021e68, loaded from 00001114. Sections:
|
|
Physical offset in swap source image Destination range in virtual memory
|
|
src_swap_img_addr dst_page_vaddr
|
|
| |
|
|
v v
|
|
Loaded from 00001114 - Section .gcc_except_table 4056 bytes loading into 00010114..000110ec flags: NONE
|
|
Loaded from 000020ec - Section .rodata 19080 bytes loading into 000110f0..00015b78 flags: NONE
|
|
Loaded from 00006b74 - Section .eh_frame_hdr 2172 bytes loading into 00015b78..000163f4 flags: EH_HEADER
|
|
Loaded from 000073f0 - Section .eh_frame 7740 bytes loading into 000163f4..00018230 flags: EH_FRAME
|
|
Loaded from 0000922c - Section .text 67428 bytes loading into 00019230..00029994 flags: EXECUTE
|
|
Loaded from 00019990 - Section .data 4 bytes loading into 0002a994..0002a998 flags: WRITE
|
|
Loaded from 00019994 - Section .sdata 32 bytes loading into 0002a998..0002a9b8 flags: WRITE
|
|
Loaded from 000199b4 - Section .sbss 64 bytes loading into 0002a9b8..0002a9f8 flags: WRITE | NOCOPY
|
|
Loaded from 000199f4 - Section .bss 532 bytes loading into 0002a9f8..0002ac0c flags: WRITE | NOCOPY
|
|
|
|
Note that we have full control over what swap block we put things into, but the swap block's
|
|
address offsets should have a 1:1 correlation to the *virtual* destination addresess. We track
|
|
the current swap page with `working_page_swap_offset`.
|
|
*/
|
|
|
|
_pid += 1;
|
|
let mut working_page_swap_offset: Option<usize> = None;
|
|
let mut working_buf = [0u8; 4096];
|
|
let mut working_buf_dirty = false;
|
|
|
|
let inis = MiniElf::new(&tag);
|
|
let mut src_swap_img_addr = inis.load_offset as usize;
|
|
|
|
println!("\n\n{} {} has {} sections", tag_type.to_str(), _pid, inis.sections.len());
|
|
println!("Swap free page at swap addr: {:x}", cfg.swap_free_page,);
|
|
|
|
let mut last_copy_vaddr = 0;
|
|
|
|
for (index, section) in inis.sections.iter().enumerate() {
|
|
if SDBG {
|
|
println!("Section {}: {:x?}", index, section);
|
|
}
|
|
let mut dst_page_vaddr = section.virt as usize;
|
|
let mut bytes_to_copy = section.len();
|
|
|
|
if let Some(swap_offset) = working_page_swap_offset {
|
|
if (last_copy_vaddr & !(PAGE_SIZE - 1)) != (dst_page_vaddr & !(PAGE_SIZE - 1)) {
|
|
if SDBG {
|
|
println!(
|
|
"New section not aligned: last_copy_vaddr {:x}, dst_page_vaddr {:x}",
|
|
last_copy_vaddr, dst_page_vaddr
|
|
);
|
|
}
|
|
// handle case that the new section destination address is outside of the
|
|
// current page
|
|
cfg.swap_hal.as_mut().expect("swap HAL uninit").encrypt_swap_to(
|
|
&mut working_buf,
|
|
swap_offset * 0x1000,
|
|
last_copy_vaddr & !(PAGE_SIZE - 1),
|
|
_pid,
|
|
);
|
|
working_buf.fill(0);
|
|
working_page_swap_offset = Some(cfg.swap_free_page);
|
|
working_buf_dirty = false;
|
|
cfg.swap_free_page += 1;
|
|
}
|
|
} else {
|
|
// very first time through the loop. working_buf is guaranteed to be zero.
|
|
working_page_swap_offset = Some(cfg.swap_free_page);
|
|
cfg.swap_free_page += 1;
|
|
}
|
|
|
|
// Decrypt the source image data and re-encrypt it to swap for the section at hand.
|
|
// - dst_page_vaddr is the virtual address of the section. We only care about this
|
|
// for tracking offsets in pages, at this stage.
|
|
// - working_page_swap_offset is the current destination swap RAM page
|
|
// - src_swap_img_addr is the offset of the section in source swap FLASH.
|
|
// - no_copy sections need to set the corresponding bytes in swap RAM to zero.
|
|
//
|
|
//
|
|
while bytes_to_copy > 0 {
|
|
// here are the cases we have to handle:
|
|
// - the available decrypted data is larger than the target region to encrypt
|
|
// - the available decrypted data is smaller than the target region to encrypt
|
|
// - the available decrypted data is equal to the target region to encrypt
|
|
let src_swap_img_page = src_swap_img_addr & !(PAGE_SIZE - 1);
|
|
let src_swap_img_offset = src_swap_img_addr & (PAGE_SIZE - 1);
|
|
// it's almost free to check, so we check at every loop start
|
|
if (cfg.swap_hal.as_ref().expect("swap HAL uninit").decrypt_page_addr()
|
|
!= src_swap_img_page)
|
|
&& !section.no_copy()
|
|
{
|
|
cfg.swap_hal
|
|
.as_mut()
|
|
.expect("swap HAL uninit")
|
|
.decrypt_src_page_at(src_swap_img_page)
|
|
.unwrap();
|
|
}
|
|
let decrypt_avail = remaining_in_page(src_swap_img_addr);
|
|
let dst_page_avail = remaining_in_page(dst_page_vaddr);
|
|
let dst_page_offset = dst_page_vaddr & (PAGE_SIZE - 1);
|
|
let copyable = if decrypt_avail >= dst_page_avail {
|
|
dst_page_avail.min(bytes_to_copy)
|
|
} else {
|
|
decrypt_avail.min(bytes_to_copy)
|
|
};
|
|
if !section.no_copy() {
|
|
working_buf[dst_page_offset..dst_page_offset + copyable].copy_from_slice(
|
|
&cfg.swap_hal.as_ref().expect("swap HAL uninit").buf_as_ref()
|
|
[src_swap_img_offset..src_swap_img_offset + copyable],
|
|
);
|
|
working_buf_dirty = true;
|
|
} else {
|
|
// do nothing, because working_buff is filled with 0 on alloc
|
|
// but, mark the buffer as dirty, because, it still needs to be committed
|
|
working_buf_dirty = true;
|
|
}
|
|
bytes_to_copy -= copyable;
|
|
dst_page_vaddr += copyable;
|
|
src_swap_img_addr += copyable;
|
|
// if we filled up the destination, grab another page.
|
|
if (dst_page_vaddr & (PAGE_SIZE - 1)) == 0 {
|
|
// the current vaddr is pointing to a new, empty vaddr; we want to write
|
|
// out the previous, full page. Compute that here.
|
|
let full_page_vaddr = dst_page_vaddr - PAGE_SIZE;
|
|
// we copied exactly dst_page_avail, causing us to wrap around to 0
|
|
// write the existing page to swap, and allocate a new swap page
|
|
cfg.swap_hal.as_mut().expect("swap HAL uninit").encrypt_swap_to(
|
|
&mut working_buf,
|
|
working_page_swap_offset.unwrap() * 0x1000,
|
|
full_page_vaddr & !(PAGE_SIZE - 1),
|
|
_pid,
|
|
);
|
|
working_buf.fill(0);
|
|
working_page_swap_offset = Some(cfg.swap_free_page);
|
|
working_buf_dirty = false;
|
|
cfg.swap_free_page += 1;
|
|
}
|
|
}
|
|
// set the vpage based on our current vpage. This allows us to allocate
|
|
// a new vpage on the next iteration in case there is surprise padding in
|
|
// the section load address.
|
|
last_copy_vaddr = dst_page_vaddr;
|
|
|
|
if SDBG && VVDBG {
|
|
println!("Looping to the next section (swap)");
|
|
println!(
|
|
" swap_free_page: {:x}, dst_page_vaddr: {:x}, src_swap_img_addr: {:x}",
|
|
cfg.swap_free_page, dst_page_vaddr, src_swap_img_addr,
|
|
);
|
|
println!(" last_copy_vaddr: {:x}", last_copy_vaddr);
|
|
}
|
|
}
|
|
// flush the encryption buffer
|
|
if working_buf_dirty {
|
|
cfg.swap_hal.as_mut().expect("swap HAL uninit").encrypt_swap_to(
|
|
&mut working_buf,
|
|
working_page_swap_offset.unwrap() * 0x1000,
|
|
last_copy_vaddr & !(PAGE_SIZE - 1),
|
|
_pid,
|
|
);
|
|
} else {
|
|
// we didn't use the current page, de-allocate it
|
|
cfg.swap_free_page -= 1;
|
|
}
|
|
println!("Done with sections");
|
|
}
|
|
}
|
|
TagType::XKrn => {
|
|
let prog = unsafe { &*(tag.data.as_ptr() as *const ProgramDescription) };
|
|
|
|
// TEXT SECTION
|
|
// Round it off to a page boundary
|
|
let load_size_rounded = (prog.text_size as usize + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
|
cfg.extra_pages += load_size_rounded / PAGE_SIZE;
|
|
let top = cfg.get_top();
|
|
println!("\n\nKernel top: {:x}, extra_pages: {:x}", top as u32, cfg.extra_pages);
|
|
unsafe {
|
|
// Copy the program to the target address, rounding it off to the load size.
|
|
let src_addr = cfg.base_addr.add(prog.load_offset as usize / size_of::<usize>());
|
|
println!(
|
|
" Copying TEXT from {:08x}-{:08x} to {:08x}-{:08x} ({} bytes long)",
|
|
src_addr as usize,
|
|
src_addr as u32 + prog.text_size,
|
|
top as usize,
|
|
top as u32 + prog.text_size + 4,
|
|
prog.text_size + 4
|
|
);
|
|
println!(
|
|
" Zeroing out TEXT from {:08x}-{:08x}",
|
|
top.add(prog.text_size as usize / size_of::<usize>()) as usize,
|
|
top.add(load_size_rounded as usize / size_of::<usize>()) as usize,
|
|
);
|
|
|
|
memcpy(top, src_addr, prog.text_size as usize + 1);
|
|
|
|
// Zero out the remaining data.
|
|
bzero(
|
|
top.add(prog.text_size as usize / size_of::<usize>()),
|
|
top.add(load_size_rounded as usize / size_of::<usize>()),
|
|
)
|
|
};
|
|
|
|
// DATA SECTION
|
|
// Round it off to a page boundary
|
|
let load_size_rounded =
|
|
((prog.data_size + prog.bss_size) as usize + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
|
|
cfg.extra_pages += load_size_rounded / PAGE_SIZE;
|
|
let top = cfg.get_top();
|
|
unsafe {
|
|
// Copy the program to the target address, rounding it off to the load size.
|
|
let src_addr = cfg
|
|
.base_addr
|
|
.add((prog.load_offset + prog.text_size + 4) as usize / size_of::<usize>() - 1);
|
|
println!(
|
|
" Copying DATA from {:08x}-{:08x} to {:08x}-{:08x} ({} bytes long)",
|
|
src_addr as usize,
|
|
src_addr as u32 + prog.data_size,
|
|
top as usize,
|
|
top as u32 + prog.data_size,
|
|
prog.data_size
|
|
);
|
|
memcpy(top, src_addr, prog.data_size as usize + 1);
|
|
|
|
// Zero out the remaining data.
|
|
println!(
|
|
" Zeroing out DATA from {:08x} - {:08x}",
|
|
top.add(prog.data_size as usize / size_of::<usize>()) as usize,
|
|
top.add(load_size_rounded as usize / size_of::<usize>()) as usize
|
|
);
|
|
bzero(
|
|
top.add(prog.data_size as usize / size_of::<usize>()),
|
|
top.add(load_size_rounded as usize / size_of::<usize>()),
|
|
)
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub unsafe fn memcpy<T>(dest: *mut T, src: *const T, count: usize)
|
|
where
|
|
T: Copy,
|
|
{
|
|
if VDBG {
|
|
println!(
|
|
"COPY (align {}): {:08x} - {:08x} {} {:08x} - {:08x}",
|
|
size_of::<T>(),
|
|
src as usize,
|
|
src as usize + count,
|
|
count,
|
|
dest as usize,
|
|
dest as usize + count
|
|
);
|
|
}
|
|
core::ptr::copy_nonoverlapping(src, dest, count / size_of::<T>());
|
|
}
|