mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-13 07:48:30 +00:00
Turns out we 'just got lucky' with our previous success in getting arg extension to work (scary!!): the arg format is pretty uhh...flexible...in terms of what it will accept for arguments. This fixes those bugs, the arg copy looks clean now, and also removed the over-allocation problem by descending the new argument list twice, once to sum the space needed, and once to actually do the copy.
271 lines
12 KiB
Rust
271 lines
12 KiB
Rust
use crate::VDBG;
|
|
|
|
/// Convert a four-letter string into a 32-bit int.
|
|
#[macro_export]
|
|
macro_rules! make_type {
|
|
($fcc:expr) => {{
|
|
let mut c: [u8; 4] = Default::default();
|
|
c.copy_from_slice($fcc.as_bytes());
|
|
u32::from_le_bytes(c)
|
|
}};
|
|
}
|
|
|
|
#[repr(C)]
|
|
/// This is a representation of tag data *on disk*. It can be located at any 4-byte
|
|
/// alignment within the argument list.
|
|
pub struct Tag {
|
|
/// Ascii-printable name, not null-terminated, in little endian format.
|
|
pub tag: u32,
|
|
|
|
/// CRC16 of the data section, using CCITT polynomial.
|
|
pub crc16: u16,
|
|
|
|
/// Size of the data section, in 4-byte words.
|
|
pub size: u16,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct XargArg {
|
|
tag: Tag,
|
|
/// Full length of the entire argument structure (not just this argument)
|
|
/// reported in units of u32.
|
|
arg_size_u32: u32,
|
|
version: u32,
|
|
ram_start: u32,
|
|
ram_size: u32,
|
|
ram_name: u32,
|
|
}
|
|
|
|
/// KernelArguments is a self-extracting structure which handles variable-length fields
|
|
/// that only needs a pointer to its start to derive the rest of its structure.
|
|
#[derive(Clone, Copy)]
|
|
pub struct KernelArguments {
|
|
pub base: *const u32,
|
|
}
|
|
|
|
// Alignment target of the resulting KernelArguments structure. I think we can trim it more
|
|
// aggressively but I have had troubles in the past with unsafe code and the optimizer fighting
|
|
// each other when alignments are too ragged. 16 bytes is, iirc, what the rv32-llvm expects
|
|
// things to be aligned to for function prologues so it's a nice safe number to shoot for
|
|
// given the amount of unsafes in the block below.
|
|
const TARGET_ALIGNMENT: usize = 16;
|
|
impl KernelArguments {
|
|
pub fn new(base: *const usize) -> KernelArguments { KernelArguments { base: base as *const u32 } }
|
|
|
|
pub fn iter(self) -> KernelArgumentsIterator {
|
|
KernelArgumentsIterator { base: self.base, size: self.size(), offset: 0 }
|
|
}
|
|
|
|
/// Returns a size in bytes
|
|
pub fn size(&self) -> usize {
|
|
// Add a rudimentary check to make sure that KernelArguments are pointed to the correct
|
|
// structure, by checking for the right magic number in the first field.
|
|
let tag = unsafe { (self.base as *const Tag).as_ref().unwrap() };
|
|
if tag.tag != u32::from_le_bytes(*b"XArg") {
|
|
crate::println!("FATAL: Couldn't find XArg magic number on kernel tags.");
|
|
panic!(); // nothing we can do to repair this defect.
|
|
}
|
|
let xarg_tag = unsafe { (self.base as *const XargArg).as_ref().unwrap() };
|
|
xarg_tag.arg_size_u32 as usize * size_of::<u32>()
|
|
}
|
|
|
|
/// Takes the `self` argument list as the `base`, and creates a copy of the argument list into
|
|
/// `dest_region`. At the same time, it will iterate through any other argument lists presented to
|
|
/// it from `args`, and filter them for the tags listed in `tag_filter`, and append them to the list.
|
|
///
|
|
/// `dest_region` needs to be of a size large enough to hold the new merge arguments. `dest_region`
|
|
/// will have the new argument list jammed up near the top of its allocation area and a new
|
|
/// KernelArguments structure returned as a pointer to that region. The remainder of `dest_region` is
|
|
/// free to use after this operation is done.
|
|
///
|
|
/// Returns `None` if the merged arguments will not fit within the proposed destination region. The final
|
|
/// size is often much smaller than `dest_region`; it should be read out of the KernelArguments to
|
|
/// determine how far the free memory tracker should be advanced for optimal space packing in the
|
|
/// loader.
|
|
///
|
|
/// Returns `Some<(KernelArguments, allocation size>)` on success. The `cfg.init_size` pointer needs
|
|
/// to be advanced by the allocation size.
|
|
pub unsafe fn merge_args(
|
|
self,
|
|
incoming_args: &[KernelArguments],
|
|
tag_filter: &[u32],
|
|
dest_region: &mut [u32],
|
|
) -> Option<(KernelArguments, usize)> {
|
|
// compute the incoming size. Requires iterating through the incoming list once. This
|
|
// means we iterate through the list a total of two times, once to figure out the size,
|
|
// and once to do the copy. This is a space-time trade-off: if we wanted O(1) time for
|
|
// this step we could just take the whole size of the incoming argument list as an
|
|
// upper bound and call it a day. But the arg lists are tiny so the extra computation
|
|
// is trivial, and book-keeping bytes used in the loader are bytes lost to application space.
|
|
let mut incoming_size = 0;
|
|
for arg_list in incoming_args.iter() {
|
|
for arg in arg_list.iter() {
|
|
if tag_filter.iter().any(|&t| t == arg.name) {
|
|
incoming_size += arg.size as usize + size_of::<Tag>();
|
|
}
|
|
}
|
|
}
|
|
let size_bound = self.size() + incoming_size;
|
|
if size_bound > dest_region.len() * size_of::<u32>() {
|
|
return None;
|
|
}
|
|
// check that size_bound meets the alignment requirements of the `KernelArguments` structure.
|
|
// This is necessary to enforce guarantees on the `unsafe` operations later on.
|
|
assert!(size_bound % size_of::<u32>() == 0);
|
|
|
|
// we can fit - so now, we're going to do some unsafe casts to manipulate the region of memory
|
|
// represented by `dest_region`. These unsafe casts are only going to be safe because every
|
|
// value can be represented, and also the alignment guarantee of `dest_region` is appropriate
|
|
// per the caller spec in the method invocation comments.
|
|
//
|
|
// NOTE: `dest_region` is assumed to be *much* larger than the region we need to go into,
|
|
// and we want the argument buffer to slot into the top of `dest_region`.
|
|
let mut arg_buffer =
|
|
(dest_region.as_mut_ptr() as *mut u32).add(dest_region.len()).sub(size_bound / size_of::<u32>());
|
|
let alignment_overhead = (arg_buffer as usize) % TARGET_ALIGNMENT;
|
|
arg_buffer = (arg_buffer as usize & !(TARGET_ALIGNMENT - 1)) as *mut u32;
|
|
let aligned_size = size_bound + alignment_overhead;
|
|
|
|
// this copy whacks the base arguments into place within the `dest_region`
|
|
// It's safe because `dest_region` has an alignment appropriate for `KernelArguments`; it's
|
|
// not overlapping with the source buffer (the source buffer should be coming from flash,
|
|
// this is going to RAM), and all elements have valid values.
|
|
unsafe {
|
|
crate::phase1::memcpy(arg_buffer, self.base as *const u32, self.size() as usize);
|
|
}
|
|
|
|
// now iterate through the `incoming_args` and append the records that match the `tag_filter`
|
|
// the pointer below tracks where the incoming argument should be appended.
|
|
let mut incoming_arg_ptr: usize = arg_buffer.add(self.size() / size_of::<u32>()) as usize;
|
|
for arg_list in incoming_args {
|
|
for arg in arg_list.iter() {
|
|
// filter the argument based on the list of tags to include in the copy
|
|
if tag_filter.iter().any(|&t| t == arg.name) {
|
|
let extra_size_bytes = arg.size as usize + size_of::<Tag>();
|
|
assert!(
|
|
extra_size_bytes % size_of::<usize>() == 0,
|
|
"Argument did not conform to size spec!"
|
|
);
|
|
if VDBG {
|
|
crate::println!(
|
|
"copying new args total size {} / {:x?}, {:x}, {:x?}",
|
|
extra_size_bytes,
|
|
arg.this as usize,
|
|
arg.name,
|
|
arg.data
|
|
);
|
|
}
|
|
// safety:
|
|
// - all new arguments meet the alignment criteria (as checked by the assert above)
|
|
// - all values are representable
|
|
// - space at incoming_arg_ptr is guaranteed to be available
|
|
unsafe {
|
|
crate::phase1::memcpy(
|
|
incoming_arg_ptr as *mut u32,
|
|
arg.this as *mut u32,
|
|
extra_size_bytes,
|
|
);
|
|
}
|
|
// increment the arg pointer to the next argument slot
|
|
incoming_arg_ptr += extra_size_bytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check that we didn't blow out the size bound after doing the copies
|
|
let final_size_bytes = incoming_arg_ptr - arg_buffer as usize;
|
|
assert!(final_size_bytes <= size_bound);
|
|
// note how much space we ended up wasting. We can reduce this by improving the computation of
|
|
// `size_bound` later on if we need to wring out a few more bytes from the boot process. Part
|
|
// of the reason the few bytes matters is that these are "unrecoverable", i.e. they are mapped
|
|
// into the kernel, used once, and never de-allocated, so we want the convenience overhead to be
|
|
// reasonable.
|
|
crate::println!("Argument merge complete. {} bytes over-allocated", size_bound - final_size_bytes);
|
|
|
|
// now fixup the CRC & size of the base XArg argument.
|
|
// extract the first argument as an XargArg by just overlaying the structure template on the pointer.
|
|
// only safe because the arg_buffer pointer is aligned and is actually an XArg tag.
|
|
let xarg = unsafe { (arg_buffer as *mut XargArg).as_mut().unwrap() };
|
|
xarg.arg_size_u32 = (final_size_bytes / size_of::<u32>()) as u32;
|
|
|
|
// compute the CRC of the XArg argument's data section
|
|
use crc::{Hasher16, crc16};
|
|
// create a u8-region out of the XArg's data section. The data section is known to follow the Tag
|
|
// header and be exactly the length of the size in the Tag section.
|
|
let xarg_data = unsafe {
|
|
core::slice::from_raw_parts(
|
|
(self.base as *const u8).add(size_of::<Tag>()),
|
|
xarg.tag.size as usize,
|
|
)
|
|
};
|
|
let mut digest = crc16::Digest::new(crc16::X25);
|
|
digest.write(&xarg_data);
|
|
xarg.tag.crc16 = digest.sum16();
|
|
|
|
// return the new argument buffer
|
|
Some((KernelArguments::new(arg_buffer as *const usize), aligned_size))
|
|
}
|
|
}
|
|
|
|
/// KernelArgument is a stack-allocated structure that describes a pointer to tagged data.
|
|
/// It effectively decompressed the tag name + size field and converts the embedded variable-length
|
|
/// data into a slice.
|
|
///
|
|
/// It is constructed by referring to the base of the tag structure, and then providing an
|
|
/// offset. The base + offset should point to the top of a Tag structure.
|
|
pub struct KernelArgument {
|
|
pub base: *const u32,
|
|
|
|
pub name: u32,
|
|
|
|
// Total number of bytes in the data section
|
|
pub size: u32,
|
|
|
|
// Data section, as a slice of 32-bit ints
|
|
pub data: &'static [u32],
|
|
|
|
pub this: *const u32,
|
|
}
|
|
|
|
impl KernelArgument {
|
|
pub fn new(base: *const u32, offset: u32) -> Self {
|
|
let tag = unsafe { (base.add(offset as usize / size_of::<u32>()) as *const Tag).as_ref().unwrap() };
|
|
let name = tag.tag;
|
|
let size = tag.size as u32;
|
|
// adding 1 to the tag yields the address immediately after the tag data.
|
|
let data =
|
|
unsafe { core::slice::from_raw_parts((tag as *const Tag).add(1) as *const u32, size as usize) };
|
|
// Return the stack-allocated KernelArgument, which embeds a reference to the now base/bounds
|
|
// protected data slice.
|
|
KernelArgument {
|
|
base,
|
|
name,
|
|
size: size * size_of::<u32>() as u32,
|
|
data,
|
|
this: unsafe { base.add(offset as usize / size_of::<u32>()) },
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The arguments iterator is constructed by reading into kernel arguments structure, assuming
|
|
/// it is a well-formed structure where the first meta-tag is placed correctly, allowing the
|
|
/// extraction of a `size` field.
|
|
pub struct KernelArgumentsIterator {
|
|
base: *const u32,
|
|
size: usize,
|
|
offset: u32,
|
|
}
|
|
|
|
impl Iterator for KernelArgumentsIterator {
|
|
type Item = KernelArgument;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if self.offset as usize >= self.size {
|
|
None
|
|
} else {
|
|
let new_arg = KernelArgument::new(self.base, self.offset);
|
|
self.offset += new_arg.size + size_of::<Tag>() as u32;
|
|
Some(new_arg)
|
|
}
|
|
}
|
|
}
|