mirror of
https://github.com/AmbiML/sparrow-kata-full.git
synced 2025-09-17 07:28:46 +00:00
ProcessManager: add bundle image support for loading apps & models
Add support for BundleImage, a file format for loading applications and models from a bundle. BundleImage is simple, compact, and streamable, BundleImage files are constructed with the prepare_bundle_image tool. TODO: add compression TODO: check crc32 Change-Id: I0770608a075cac9754a54e0bb244d75673ae1be6 GitOrigin-RevId: 368dabd3a5af19d47fe7f8084b8a0a0b57b8471d
This commit is contained in:
@@ -26,11 +26,14 @@ component DebugConsole {
|
|||||||
// TODO(b/200707300): for debugging
|
// TODO(b/200707300): for debugging
|
||||||
uses StorageInterface storage;
|
uses StorageInterface storage;
|
||||||
|
|
||||||
|
uses Timer timer;
|
||||||
|
|
||||||
// Enable KataOS CAmkES support.
|
// Enable KataOS CAmkES support.
|
||||||
attribute int kataos = true;
|
attribute int kataos = true;
|
||||||
|
|
||||||
// Add a bunch of free slots for test code to use.
|
// Add a bunch of free slots for test code to use.
|
||||||
attribute int cnode_headroom = 64;
|
attribute int cnode_headroom = 64;
|
||||||
|
|
||||||
uses Timer timer;
|
// Copyregions for loading bundle images.
|
||||||
|
has copyregion BUNDLE_IMAGE;
|
||||||
}
|
}
|
||||||
|
@@ -14,4 +14,7 @@ component ProcessManager {
|
|||||||
|
|
||||||
// Enable KataOS CAmkES support.
|
// Enable KataOS CAmkES support.
|
||||||
attribute int kataos = true;
|
attribute int kataos = true;
|
||||||
|
|
||||||
|
// Copyregions for loading bundle images.
|
||||||
|
has copyregion BUNDLE_IMAGE;
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,10 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cstr_core = "0.2.3"
|
cstr_core = "0.2.3"
|
||||||
|
kata-memory-interface = { path = "../../MemoryManager/kata-memory-interface" }
|
||||||
|
kata-io = { path = "../../DebugConsole/kata-io" }
|
||||||
|
kata-os-common = { path = "../../kata-os-common" }
|
||||||
kata-security-interface = { path = "../../SecurityCoordinator/kata-security-interface" }
|
kata-security-interface = { path = "../../SecurityCoordinator/kata-security-interface" }
|
||||||
|
log = "0.4"
|
||||||
postcard = { version = "0.7", features = ["alloc"], default-features = false }
|
postcard = { version = "0.7", features = ["alloc"], default-features = false }
|
||||||
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
|
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
|
||||||
|
@@ -0,0 +1,314 @@
|
|||||||
|
//! Kata OS Bundle image loader.
|
||||||
|
|
||||||
|
use core::cmp;
|
||||||
|
use core::mem::size_of;
|
||||||
|
use core::ops::Range;
|
||||||
|
use core::ptr;
|
||||||
|
use kata_memory_interface::ObjDescBundle;
|
||||||
|
use kata_os_common::sel4_sys;
|
||||||
|
use kata_os_common::slot_allocator;
|
||||||
|
use log::{error, trace};
|
||||||
|
|
||||||
|
use sel4_sys::seL4_CapRights;
|
||||||
|
use sel4_sys::seL4_CPtr;
|
||||||
|
use sel4_sys::seL4_Error;
|
||||||
|
use sel4_sys::seL4_PageBits;
|
||||||
|
|
||||||
|
// TODO(sleffler): belongs in sel4_sys
|
||||||
|
use sel4_sys::seL4_RISCV_Page_Map as seL4_Page_Map;
|
||||||
|
use sel4_sys::seL4_RISCV_Page_Unmap as seL4_Page_Unmap;
|
||||||
|
use sel4_sys::seL4_RISCV_VMAttributes::Default_VMAttributes as seL4_Default_VMAttributes;
|
||||||
|
|
||||||
|
use slot_allocator::CSpaceSlot;
|
||||||
|
|
||||||
|
use kata_io as io;
|
||||||
|
use io::Read;
|
||||||
|
use io::Seek;
|
||||||
|
|
||||||
|
// TODO(sleffler): use ObjDesc::size_bytes and support multiple page sizes
|
||||||
|
const PAGE_SIZE: usize = 1 << seL4_PageBits;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
static SELF_VSPACE_ROOT: seL4_CPtr;
|
||||||
|
static mut BUNDLE_IMAGE: [u8; PAGE_SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(dead_code)] // until BadSection* are used
|
||||||
|
enum BundleImageError {
|
||||||
|
PageMapFailed,
|
||||||
|
PageUnmapFailed,
|
||||||
|
PageNotFound,
|
||||||
|
CapMoveFailed,
|
||||||
|
BadSectionMagic,
|
||||||
|
BadSectionCrc,
|
||||||
|
BadSectionIO,
|
||||||
|
}
|
||||||
|
impl From<seL4_Error> for BundleImageError {
|
||||||
|
fn from(err: seL4_Error) -> BundleImageError {
|
||||||
|
match err {
|
||||||
|
_ => BundleImageError::CapMoveFailed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On-disk header format.
|
||||||
|
#[repr(packed)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct SectionHeader {
|
||||||
|
magic: u64, // Magic number
|
||||||
|
vaddr: u64, // Virtual address of section (bytes)
|
||||||
|
entry: u64, // Entry point; valid only when SECTION_ENTRYPOINT is set in flags
|
||||||
|
flags: u32, // See below
|
||||||
|
fsize: u32, // Length of data that follows (bytes)
|
||||||
|
msize: u32, // Size of memory region (bytes)
|
||||||
|
align: u32, // Section data alignment (bytes)
|
||||||
|
pad: u32, // <ignore, reserved for future use>
|
||||||
|
crc32: u32, // CRC32 of the data that follows
|
||||||
|
}
|
||||||
|
const SECTION_MAGIC: u64 = 0x0405_1957_1014_1955;
|
||||||
|
|
||||||
|
const SECTION_READ: u32 = 0x1; // Data are readable
|
||||||
|
const SECTION_WRITE: u32 = 0x2; // Data are writeable
|
||||||
|
const SECTION_EXEC: u32 = 0x4; // Data are executable
|
||||||
|
const SECTION_ENTRYPOINT: u32 = 0x8; // Entry point valid
|
||||||
|
|
||||||
|
// In-memory (parsed) section format.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BundleImageSection {
|
||||||
|
flags: u32,
|
||||||
|
pub fsize: usize,
|
||||||
|
pub msize: usize,
|
||||||
|
pub crc32: usize,
|
||||||
|
pub align: usize,
|
||||||
|
pub entry: Option<usize>,
|
||||||
|
pub vaddr: usize,
|
||||||
|
}
|
||||||
|
impl BundleImageSection {
|
||||||
|
pub fn is_read(&self) -> bool { (self.flags & SECTION_READ) != 0 }
|
||||||
|
pub fn is_write(&self) -> bool { (self.flags & SECTION_WRITE) != 0 }
|
||||||
|
pub fn is_exec(&self) -> bool { (self.flags & SECTION_EXEC) != 0 }
|
||||||
|
pub fn get_rights(&self) -> seL4_CapRights {
|
||||||
|
seL4_CapRights::new(
|
||||||
|
/*grantreply=*/ 0,
|
||||||
|
/*grant=*/ self.is_exec() as usize,
|
||||||
|
/*read=*/ self.is_read() as usize,
|
||||||
|
/*write=*/ self.is_write() as usize,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn data_range(&self) -> Range<usize> { 0..self.fsize }
|
||||||
|
pub fn zero_range(&self) -> Range<usize> { self.fsize..self.msize }
|
||||||
|
}
|
||||||
|
|
||||||
|
// BundleImage is a loadable image that backs a Bundle. There are images
|
||||||
|
// for a bundle's application and optionally one or more images for models
|
||||||
|
// that can be loaded into the vector core. The BundleImage format is
|
||||||
|
// optimized for loading a page at a time from unmapped frame objects
|
||||||
|
// and is typically transient (create, load contents, destroy).
|
||||||
|
//
|
||||||
|
// NB: this packages a section iterator together with i/o traits to
|
||||||
|
// avoid multi-borrow issues.
|
||||||
|
pub struct BundleImage<'a> {
|
||||||
|
// I/O traits state.
|
||||||
|
frames: &'a ObjDescBundle,
|
||||||
|
cur_frame: Option<seL4_CPtr>,
|
||||||
|
last_frame: Option<seL4_CPtr>,
|
||||||
|
cur_pos: u64, // Current position in i/o stream
|
||||||
|
bounce: CSpaceSlot, // Top-level CNode slot for doing map
|
||||||
|
mapped_page: *mut u8, // Currently mapped page frame
|
||||||
|
mapped_bytes: usize, // Bytes in mapped frame, 0 when no frame mapped
|
||||||
|
bytes_read: usize, // Bytes read from mapped frame
|
||||||
|
|
||||||
|
// Section iterator state.
|
||||||
|
next_section: usize, // Byte offset to next section
|
||||||
|
}
|
||||||
|
impl<'a> BundleImage<'a> {
|
||||||
|
pub fn new(frames: &'a ObjDescBundle) -> Self {
|
||||||
|
BundleImage {
|
||||||
|
frames: frames,
|
||||||
|
cur_frame: None,
|
||||||
|
last_frame: None,
|
||||||
|
cur_pos: 0,
|
||||||
|
bounce: CSpaceSlot::new(),
|
||||||
|
mapped_page: unsafe { ptr::addr_of_mut!(BUNDLE_IMAGE[0]) },
|
||||||
|
mapped_bytes: 0,
|
||||||
|
bytes_read: 0,
|
||||||
|
|
||||||
|
next_section: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(&mut self) {
|
||||||
|
self.unmap_current_frame().expect("finish");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the current section header and setup to advance to the next
|
||||||
|
// section on the next call. This is used in lieu of an iterator to
|
||||||
|
// avoid BundleImage borrow issues.
|
||||||
|
// XXX change to Result so errors are visible
|
||||||
|
pub fn next_section(&mut self) -> Option<BundleImageSection> {
|
||||||
|
self.seek(io::SeekFrom::Start(self.next_section as u64)).ok()?;
|
||||||
|
let raw_data = &mut [0u8; size_of::<SectionHeader>()];
|
||||||
|
self.read_exact(raw_data).ok()?;
|
||||||
|
let magic = u64::from_be_bytes(raw_data[0..8].try_into().unwrap());
|
||||||
|
if magic != SECTION_MAGIC {
|
||||||
|
// NB: happens when the image does not end on a page boundary,
|
||||||
|
// check magic as a hack to detect this
|
||||||
|
if magic != 0 {
|
||||||
|
error!("Invalid magic number at offset {} expected 0x{:x} got 0x{:x}",
|
||||||
|
self.next_section, SECTION_MAGIC, magic);
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut hdr = BundleImageSection {
|
||||||
|
vaddr: u64::from_be_bytes(raw_data[8..16].try_into().unwrap()) as usize,
|
||||||
|
entry: None,
|
||||||
|
flags: u32::from_be_bytes(raw_data[24..28].try_into().unwrap()),
|
||||||
|
fsize: u32::from_be_bytes(raw_data[28..32].try_into().unwrap()) as usize,
|
||||||
|
msize: u32::from_be_bytes(raw_data[32..36].try_into().unwrap()) as usize,
|
||||||
|
align: u32::from_be_bytes(raw_data[36..40].try_into().unwrap()) as usize,
|
||||||
|
// pad [40..44]
|
||||||
|
crc32: u32::from_be_bytes(raw_data[44..48].try_into().unwrap()) as usize,
|
||||||
|
};
|
||||||
|
if (hdr.flags & SECTION_ENTRYPOINT) != 0 {
|
||||||
|
hdr.entry = Some(u64::from_be_bytes(raw_data[16..24].try_into().unwrap()) as usize);
|
||||||
|
}
|
||||||
|
self.next_section = (self.cur_pos as usize) + hdr.fsize;
|
||||||
|
Some(hdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap the current page and reset state.
|
||||||
|
fn unmap_current_frame(&mut self) -> Result<(), BundleImageError> {
|
||||||
|
if let Some(cptr) = self.cur_frame {
|
||||||
|
// XXX if unmap fails bounce is cleaned up on drop but we probably want it moved instead
|
||||||
|
unsafe { seL4_Page_Unmap(self.bounce.slot) }
|
||||||
|
.map_err(|_| BundleImageError::PageUnmapFailed)?;
|
||||||
|
self.bounce.move_from(self.frames.cnode, cptr, self.frames.depth)
|
||||||
|
.map_err(|_| BundleImageError::CapMoveFailed)?;
|
||||||
|
}
|
||||||
|
self.last_frame = self.cur_frame;
|
||||||
|
self.cur_frame = None;
|
||||||
|
self.mapped_bytes = 0;
|
||||||
|
self.bytes_read = 0;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the frame containing self.|cur_pos| into our VSpace.
|
||||||
|
fn map_next_frame(&mut self) -> Result<(), BundleImageError> {
|
||||||
|
assert_eq!(self.cur_frame, None);
|
||||||
|
let mut od_off: u64 = 0; // Running byte offset to start of current ObjDesc
|
||||||
|
// n^2 in ObjDesc, track last frame
|
||||||
|
for od in &self.frames.objs {
|
||||||
|
// TODO(sleffler): maybe move page index logic to ObjDesc
|
||||||
|
let size_bytes = od.size_bytes().unwrap() as u64;
|
||||||
|
if od_off <= self.cur_pos && self.cur_pos < od_off + size_bytes {
|
||||||
|
// The frame is in this ObjDesc, calculate the page index.
|
||||||
|
let index = ((self.cur_pos - od_off) / (PAGE_SIZE as u64)) as usize;
|
||||||
|
assert!(index < od.retype_count());
|
||||||
|
|
||||||
|
// Bounce through the top-level CNode.
|
||||||
|
sel4_sys::debug_assert_slot_empty!(self.bounce.slot,
|
||||||
|
"{}: expected slot {:?} empty but has cap type {:?}",
|
||||||
|
"map_next_frame", self.bounce.slot,
|
||||||
|
sel4_sys::cap_identify(self.bounce.slot));
|
||||||
|
self.bounce.move_to(
|
||||||
|
self.frames.cnode,
|
||||||
|
od.cptr + index,
|
||||||
|
self.frames.depth
|
||||||
|
).map_err(|_| BundleImageError::CapMoveFailed)?;
|
||||||
|
|
||||||
|
// Map the page into our VSpace
|
||||||
|
// TODO(sleffler): if this fails maybe undo move_to
|
||||||
|
sel4_sys::debug_assert_slot_frame!(self.bounce.slot,
|
||||||
|
"{}: expected frame in slot {:?} but has cap type {:?}",
|
||||||
|
"map_next_frame", self.bounce.slot,
|
||||||
|
sel4_sys::cap_identify(self.bounce.slot));
|
||||||
|
unsafe {
|
||||||
|
seL4_Page_Map(
|
||||||
|
/*sel4_page=*/ self.bounce.slot,
|
||||||
|
/*seL4_pd=*/ SELF_VSPACE_ROOT,
|
||||||
|
/*vaddr=*/ self.mapped_page as usize,
|
||||||
|
seL4_CapRights::new(
|
||||||
|
/*grant_reply=*/0, /*grant=*/0, /*read=1*/1, /*write=*/0,
|
||||||
|
),
|
||||||
|
seL4_Default_VMAttributes,
|
||||||
|
)
|
||||||
|
}.map_err(|_| BundleImageError::PageMapFailed)?;
|
||||||
|
self.cur_frame = Some(od.cptr + index);
|
||||||
|
self.mapped_bytes = PAGE_SIZE;
|
||||||
|
self.bytes_read = ((self.cur_pos - od_off) % (PAGE_SIZE as u64)) as usize;
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
od_off += size_bytes;
|
||||||
|
}
|
||||||
|
error!("No page at offset {}", self.cur_pos);
|
||||||
|
Err(BundleImageError::PageNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Drop for BundleImage<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> io::Seek for BundleImage<'a> {
|
||||||
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||||
|
let new_pos = match pos {
|
||||||
|
io::SeekFrom::Current(p) => {
|
||||||
|
let ipos = (self.cur_pos as i64) + p;
|
||||||
|
if ipos < 0 { return Err(io::Error) }
|
||||||
|
ipos as u64
|
||||||
|
}
|
||||||
|
io::SeekFrom::End(p) => {
|
||||||
|
// NB: potentially expensive to calculate
|
||||||
|
let ipos = (self.frames.size_bytes() as i64) + p;
|
||||||
|
if ipos < 0 { return Err(io::Error) }
|
||||||
|
ipos as u64
|
||||||
|
}
|
||||||
|
io::SeekFrom::Start(p) => p,
|
||||||
|
};
|
||||||
|
if new_pos != self.cur_pos {
|
||||||
|
trace!("SEEK: cur {} new {}", self.cur_pos, new_pos);
|
||||||
|
// TODO(sleffler): handle seek within same page
|
||||||
|
self.unmap_current_frame().map_err(|_| io::Error)?;
|
||||||
|
self.cur_pos = new_pos;
|
||||||
|
}
|
||||||
|
Ok(self.cur_pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> io::Read for BundleImage<'a> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
let mut cursor = &mut *buf;
|
||||||
|
while cursor.len() > 0 {
|
||||||
|
let available_bytes = self.mapped_bytes - self.bytes_read;
|
||||||
|
if available_bytes > 0 {
|
||||||
|
// Fill from the current frame (as space permits).
|
||||||
|
let region = unsafe {
|
||||||
|
core::slice::from_raw_parts_mut(self.mapped_page, self.mapped_bytes)
|
||||||
|
};
|
||||||
|
let bytes_to_read = cmp::min(available_bytes, cursor.len());
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
region[self.bytes_read..].as_ptr(),
|
||||||
|
cursor.as_mut_ptr(),
|
||||||
|
bytes_to_read
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.bytes_read += bytes_to_read;
|
||||||
|
self.cur_pos += bytes_to_read as u64;
|
||||||
|
cursor = &mut cursor[bytes_to_read..];
|
||||||
|
|
||||||
|
assert!(self.bytes_read <= self.mapped_bytes);
|
||||||
|
if self.bytes_read == self.mapped_bytes {
|
||||||
|
// Current frame is empty; unmap and prepare for next.
|
||||||
|
self.unmap_current_frame().map_err(|_| io::Error)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cursor.len() == 0 { break }
|
||||||
|
|
||||||
|
// Map the next frame for read.
|
||||||
|
self.map_next_frame().map_err(|_| io::Error)?;
|
||||||
|
}
|
||||||
|
// TODO(sleffler): self.digest.write(buf); // Update crc32 calculation
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
}
|
@@ -11,6 +11,9 @@ use kata_security_interface::SecurityRequestError;
|
|||||||
use postcard;
|
use postcard;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod bundle_image;
|
||||||
|
pub use bundle_image::*;
|
||||||
|
|
||||||
pub type BundleIdArray = Vec<String>;
|
pub type BundleIdArray = Vec<String>;
|
||||||
|
|
||||||
// Size of the data buffer used to pass a serialized BundleIdArray between Rust <> C.
|
// Size of the data buffer used to pass a serialized BundleIdArray between Rust <> C.
|
||||||
|
Reference in New Issue
Block a user