Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,28 @@ jobs:
- name: Build documentation (fail on warnings)
run: ./.github/tools/github_actions_run_cargo doc --no-deps --all-features --document-private-items --workspace --exclude litebox_runner_lvbs --exclude litebox_runner_snp

build_and_test_arm64:
name: Build and Test (AArch64)
runs-on: ubuntu-24.04-arm
env:
RUSTFLAGS: -Dwarnings
steps:
- name: Check out repo
uses: actions/checkout@v6
- name: Set up Rust
run: |
rustup toolchain install $(awk -F'"' '/channel/{print $2}' rust-toolchain.toml) --profile minimal --no-self-update --component clippy
- name: Set up Nextest
uses: taiki-e/install-action@v2
with:
tool: nextest@${{ env.NEXTEST_VERSION }}
- uses: Swatinem/rust-cache@v2
- run: ./.github/tools/github_actions_run_cargo fmt
- run: |
./.github/tools/github_actions_run_cargo clippy --all-targets --all-features -p litebox -p litebox_common_linux
./.github/tools/github_actions_run_cargo build -p litebox -p litebox_common_linux
./.github/tools/github_actions_run_cargo nextest -p litebox -p litebox_common_linux -- --skip nine_p

build_and_test_lvbs:
name: Build and Test LVBS
runs-on: ubuntu-latest
Expand Down
15 changes: 15 additions & 0 deletions litebox/src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,22 +222,34 @@ bitflags! {
/// `O_CREAT`: if path does not exist, create it as a regular file
const CREAT = 0x40;
/// `O_DIRECT`: try to minimize cache effects of I/O for this file
#[cfg(target_arch = "x86_64")]
const DIRECT = 0x4000;
#[cfg(target_arch = "aarch64")]
const DIRECT = 0x10000;
/// `O_DIRECTORY`: fail if not a directory
#[cfg(target_arch = "x86_64")]
const DIRECTORY = 0x10000;
#[cfg(target_arch = "aarch64")]
const DIRECTORY = 0x4000;
/// `O_DSYNC`: write operations on the file will complete according to the requirements of
/// synchronized I/O *data* integrity completion.
const DSYNC = 0x1000;
/// `O_EXCL`: exclusive use
const EXCL = 0x80;
/// `O_LARGEFILE`: allow large file support
#[cfg(target_arch = "x86_64")]
const LARGEFILE = 0x8000;
#[cfg(target_arch = "aarch64")]
const LARGEFILE = 0x20000;
/// `O_NOATIME`: do not update access time
const NOATIME = 0x40000;
/// `O_NOCTTY`: do not assign controlling terminal
const NOCTTY = 0x100;
/// `O_NOFOLLOW`: fail if the path does not point to a regular file
#[cfg(target_arch = "x86_64")]
const NOFOLLOW = 0x20000;
#[cfg(target_arch = "aarch64")]
const NOFOLLOW = 0x8000;
/// `O_NDELAY`: non-blocking mode (same as NONBLOCK)
const NDELAY = 0x800;
/// `O_NONBLOCK`: non-blocking mode (same as NDELAY)
Expand All @@ -249,7 +261,10 @@ bitflags! {
/// integrity completion provided by `O_DSYNC`.)
const SYNC = 0x101000;
/// `O_TMPFILE`: create an unnamed temporary file
#[cfg(target_arch = "x86_64")]
const TMPFILE = 0x410000;
#[cfg(target_arch = "aarch64")]
const TMPFILE = 0x404000;
/// `O_TRUNC`: truncate the file to zero length
const TRUNC = 0x200;
/// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
Expand Down
135 changes: 133 additions & 2 deletions litebox/src/mm/exception_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,50 @@ pub unsafe fn memcpy_fallible(dst: *mut u8, src: *const u8, size: usize) -> Resu
fault = label { return Err(Fault) }
}
}
#[cfg(target_arch = "aarch64")]
unsafe {
// Bulk-copy 16 bytes at a time via ldp/stp, then a byte tail for the
// remaining 0..15 bytes. aarch64 permits unaligned access on normal
// memory, so no alignment prologue is needed.
//
// Every faulting load/store must be covered by the [2:, 3:) exception
// table range; non-memory instructions (cmp/sub/branch) within the
// range never fault. On fault, the partially-copied destination is
// observable to the caller, matching the x86_64 `rep movsb` behavior.
core::arch::asm! {
"2:",
"cmp {size}, #16",
"b.lo 20f",
// 16-byte chunk loop.
"10:",
"ldp {t1}, {t2}, [{src}], #16",
"stp {t1}, {t2}, [{dst}], #16",
"sub {size}, {size}, #16",
"cmp {size}, #16",
"b.hs 10b",
// Byte tail (0..15 bytes remaining).
"20:",
"cbz {size}, 3f",
"21:",
"ldrb {t1:w}, [{src}], #1",
"strb {t1:w}, [{dst}], #1",
"subs {size}, {size}, #1",
"b.ne 21b",
"3:",
ex_table_entry!("2b", "3b", "{fault}"),
dst = inout(reg) dst => _,
src = inout(reg) src => _,
size = inout(reg) size => _,
t1 = out(reg) _,
t2 = out(reg) _,
fault = label { return Err(Fault) }
}
}

Ok(())
}

#[cfg(target_arch = "x86_64")]
macro_rules! read_fn {
($name:ident, $ty:ty, $mov_instr:expr) => {
/// Reads a value from the given `src` pointer in a fallible manner.
Expand Down Expand Up @@ -121,12 +161,57 @@ macro_rules! read_fn {
};
}

#[cfg(target_arch = "x86_64")]
read_fn!(read_u8_fallible, u8, "movzx {dest:e}, byte ptr [{src}]");
#[cfg(target_arch = "x86_64")]
read_fn!(read_u16_fallible, u16, "movzx {dest:e}, word ptr [{src}]");
#[cfg(target_arch = "x86_64")]
read_fn!(read_u32_fallible, u32, "mov {dest:e}, dword ptr [{src}]");
#[cfg(target_pointer_width = "64")]
#[cfg(target_arch = "x86_64")]
read_fn!(read_u64_fallible, u64, "mov {dest:r}, qword ptr [{src}]");

#[cfg(target_arch = "aarch64")]
macro_rules! read_fn {
($name:ident, $ty:ty, $load_instr:expr) => {
/// Reads a value from the given `src` pointer in a fallible manner.
///
/// # Safety
/// `src` must be valid for reads or a pointer that's guaranteed to be
/// in non-Rust memory.
pub unsafe fn $name(src: *const $ty) -> Result<$ty, Fault> {
let value: usize;
let failed: u32;
unsafe {
core::arch::asm! {
"2:",
$load_instr,
"mov {failed:w}, wzr",
"3:",
ex_table_entry!("2b", "3b", "3b"),
src = in(reg) src,
dest = out(reg) value,
failed = inout(reg) 1u32 => failed,
}
}
if failed == 0 {
Ok((value as u64).trunc())
} else {
Err(Fault)
}
}
};
}

#[cfg(target_arch = "aarch64")]
read_fn!(read_u8_fallible, u8, "ldrb {dest:w}, [{src}]");
#[cfg(target_arch = "aarch64")]
read_fn!(read_u16_fallible, u16, "ldrh {dest:w}, [{src}]");
#[cfg(target_arch = "aarch64")]
read_fn!(read_u32_fallible, u32, "ldr {dest:w}, [{src}]");
#[cfg(target_arch = "aarch64")]
read_fn!(read_u64_fallible, u64, "ldr {dest}, [{src}]");

#[cfg(target_arch = "x86_64")]
macro_rules! write_fn {
($name:ident, $ty:ty, $mov_instr:expr) => {
/// Writes a value to the given `dest` pointer in a fallible manner.
Expand Down Expand Up @@ -155,11 +240,57 @@ macro_rules! write_fn {

#[cfg(target_arch = "x86_64")]
write_fn!(write_u8_fallible, u8, "mov byte ptr [{dest}], {src:l}");
#[cfg(target_arch = "x86_64")]
write_fn!(write_u16_fallible, u16, "mov word ptr [{dest}], {src:x}");
#[cfg(target_arch = "x86_64")]
write_fn!(write_u32_fallible, u32, "mov dword ptr [{dest}], {src:e}");
#[cfg(target_pointer_width = "64")]
#[cfg(target_arch = "x86_64")]
write_fn!(write_u64_fallible, u64, "mov qword ptr [{dest}], {src:r}");

#[cfg(target_arch = "aarch64")]
macro_rules! write_fn {
($name:ident, $ty:ty, $store_instr:expr, $convert:expr) => {
/// Writes a value to the given `dest` pointer in a fallible manner.
///
/// # Safety
/// `dest` must be valid for writes or a pointer that's guaranteed to be
/// in non-Rust memory.
pub unsafe fn $name(dest: *mut $ty, value: $ty) -> Result<(), Fault> {
unsafe {
core::arch::asm! {
"2:",
$store_instr,
"3:",
ex_table_entry!("2b", "3b", "{fault}"),
src = in(reg) $convert(value),
dest = in(reg) dest,
fault = label { return Err(Fault) }
}
}
Ok(())
}
};
}

#[cfg(target_arch = "aarch64")]
write_fn!(write_u8_fallible, u8, "strb {src:w}, [{dest}]", u32::from);
#[cfg(target_arch = "aarch64")]
write_fn!(write_u16_fallible, u16, "strh {src:w}, [{dest}]", u32::from);
#[cfg(target_arch = "aarch64")]
write_fn!(
write_u32_fallible,
u32,
"str {src:w}, [{dest}]",
core::convert::identity
);
#[cfg(target_arch = "aarch64")]
write_fn!(
write_u64_fallible,
u64,
"str {src}, [{dest}]",
core::convert::identity
);

/// Exception table entry with relative offsets
#[repr(C)]
#[derive(Clone, Copy, Debug)]
Expand Down
2 changes: 2 additions & 0 deletions litebox/src/mm/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ impl crate::platform::PageManagementProvider<PAGE_SIZE> for DummyVmemBackend {
const TASK_ADDR_MIN: usize = 0x1_0000; // default linux config
#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
const TASK_ADDR_MAX: usize = 0x7FFF_FFFF_F000; // (1 << 47) - PAGE_SIZE;
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
const TASK_ADDR_MAX: usize = 0xFFFF_FFFF_F000; // 48-bit VA space

fn allocate_pages(
&self,
Expand Down
3 changes: 3 additions & 0 deletions litebox/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,9 @@ where
// safe (it probably is fine, but the following sequence of steps ensures we are
// staying in a very safe subset).
let bytes: *mut [c_char] = Box::into_raw(bytes);
// SAFETY: c_char and u8 have the same size and alignment. The cast is a no-op on
// targets where c_char is u8 (e.g. aarch64), hence the `unnecessary_cast` allow.
#[allow(clippy::unnecessary_cast)]
let bytes: *mut [u8] = bytes as *mut [u8];
let bytes: Box<[u8]> = unsafe { Box::from_raw(bytes) };
let bytes: Vec<u8> = Vec::from(bytes);
Expand Down
38 changes: 38 additions & 0 deletions litebox/src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,20 @@ pub struct ExceptionInfo {
pub kernel_mode: bool,
}

/// Information about a hardware exception on aarch64.
#[cfg(target_arch = "aarch64")]
#[derive(Copy, Clone, Debug)]
pub struct ExceptionInfo {
/// The aarch64 exception class from ESR_EL1[31:26].
pub exception: Exception,
/// The fault address (FAR_EL1).
pub fault_address: usize,
/// The exception syndrome register value (ESR_EL1).
pub esr: u64,
/// Whether the exception occurred in kernel mode.
pub kernel_mode: bool,
}

/// An x86 exception type.
#[cfg(target_arch = "x86_64")]
#[repr(transparent)]
Expand All @@ -133,3 +147,27 @@ impl Exception {
/// #PF
pub const PAGE_FAULT: Self = Self(14);
}

/// An aarch64 exception class from ESR_EL1[31:26].
#[cfg(target_arch = "aarch64")]
#[repr(transparent)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Exception(pub u8);

#[cfg(target_arch = "aarch64")]
impl Exception {
/// Breakpoint exception from a lower exception level.
pub const BREAKPOINT_LOWER_EL: Self = Self(0x30);
/// Breakpoint exception taken without a change in exception level.
pub const BREAKPOINT_CURRENT_EL: Self = Self(0x31);
/// BRK instruction trap from AArch64 state.
pub const BRK64: Self = Self(0x3c);
/// Instruction abort taken without a change in exception level.
pub const INSTRUCTION_ABORT_CURRENT_EL: Self = Self(0x21);
/// Instruction abort taken from a lower exception level.
pub const INSTRUCTION_ABORT_LOWER_EL: Self = Self(0x20);
/// Data abort taken without a change in exception level.
pub const DATA_ABORT_CURRENT_EL: Self = Self(0x25);
/// Data abort taken from a lower exception level.
pub const DATA_ABORT_LOWER_EL: Self = Self(0x24);
}
Loading
Loading