Skip to content
Merged
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
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 14 additions & 7 deletions dev_tools/qemu_test_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
# Licensed under the MIT license.

# Shared by the dev_tools/run_optee_on_*.sh scripts; source it. Reads QEMU,
# QEMU_ACCEL, TIMEOUT, LITEBOX_LOG and CARGO_BUILD (see the scripts), makes a
# relative CARGO_TARGET_DIR absolute, and creates $WORK, removed on exit.
# QEMU_ACCEL, QEMU_CPU, TIMEOUT, LITEBOX_LOG and CARGO_BUILD (see the scripts),
# makes a relative CARGO_TARGET_DIR absolute, and creates $WORK, removed on
# exit.

QEMU=${QEMU:-qemu-system-x86_64}
if [[ -z ${QEMU_ACCEL:-} ]]; then
if [[ -r /dev/kvm && -w /dev/kvm ]]; then QEMU_ACCEL=kvm; else QEMU_ACCEL=tcg; fi
fi
case $QEMU_ACCEL in
kvm) CPU=host ;;
tcg) CPU=max ;;
# invtsc (not migratable) lets QEMU publish the TSC frequency to the guest.
kvm) CPU=${QEMU_CPU:-host,+invtsc} ;;
tcg) CPU=${QEMU_CPU:-max} ;;
*) echo "error: unknown QEMU_ACCEL '$QEMU_ACCEL'" >&2; exit 2 ;;
esac
TIMEOUT=${TIMEOUT:-120}
Expand Down Expand Up @@ -52,10 +54,13 @@ pass=0
fail=0
failed=()

# run_vm NAME KERNEL PAYLOAD: boots KERNEL with PAYLOAD as its initrd and
# records the outcome; with $verbose set, prints passing logs too.
# run_vm NAME KERNEL PAYLOAD [QEMU_ARG]...: boots KERNEL with PAYLOAD as its
# initrd and records the outcome; with $verbose set, prints passing logs too.
# If $check names a function, a guest pass also needs `$check NAME` to
# succeed; it prints why not otherwise.
run_vm() {
local name=$1 kernel=$2 payload=$3 log="$WORK/$1.log" status why
shift 3
set +e
# shellcheck disable=SC2086 # QEMU is a command line
timeout "$TIMEOUT" $QEMU \
Expand All @@ -64,16 +69,18 @@ run_vm() {
-kernel "$kernel" -initrd "$payload" -append "litebox.log=${LITEBOX_LOG:-info}" \
-serial stdio -display none -no-reboot \
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
"$@" \
</dev/null >"$log" 2>&1
status=$?
set -e
# isa-debug-exit: QEMU exits with (value << 1) | 1; 33 = pass.
if [[ $status -eq 33 ]]; then
if [[ $status -eq 33 ]] && { [[ -z ${check:-} ]] || why=$($check "$name"); }; then
echo "PASS $name"
pass=$((pass + 1))
[[ ${verbose:-0} -eq 0 ]] || cat "$log"
else
case $status in
33) ;;
65) why="guest reported failure" ;;
124) why="timed out after ${TIMEOUT}s" ;;
*) why="exit status $status" ;;
Expand Down
1 change: 1 addition & 0 deletions dev_tools/run_optee_on_qemu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# Environment:
# QEMU QEMU binary/command (default: qemu-system-x86_64)
# QEMU_ACCEL kvm or tcg (default: kvm if /dev/kvm is usable, else tcg)
# QEMU_CPU QEMU CPU model (default: host,+invtsc for kvm, max for tcg)
# TIMEOUT per-run timeout in seconds (default: 120)
# LITEBOX_LOG guest log level, e.g. debug (default: info)
# CARGO_BUILD command that builds the runner, run in its directory
Expand Down
37 changes: 35 additions & 2 deletions dev_tools/run_optee_on_vm_userland.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,22 @@
# open_session commands name the TAs it needs in their "ta" fields, or a TA
# with a *-cmds.json of its own.
#
# Runner processes' standard streams are a virtio console, backed by files:
# TA output must reach it, and in debug builds, whose runners echo standard
# input, so must the line fed to it.
#
# Usage: dev_tools/run_optee_on_vm_userland.sh [-t <test>]... [-u] [-r] [-v]
# -t test to run, e.g. ta-death or hello-ta; append @default to a TA to
# omit its cmds.json (repeatable; default: every scenario, plus
# hello-ta@default for the default-commands path)
# -u use unmodified ldelf and TAs; the kernel reflects their syscalls
# -r build and run the release kernel
# -v print the full guest log for passing runs too
# -v print the full guest log and console output for passing runs too
#
# Environment:
# QEMU QEMU binary/command (default: qemu-system-x86_64)
# QEMU_ACCEL kvm or tcg (default: kvm if /dev/kvm is usable, else tcg)
# QEMU_CPU QEMU CPU model (default: host,+invtsc for kvm, max for tcg)
# TIMEOUT per-run timeout in seconds (default: 120)
# LITEBOX_LOG guest log level, e.g. debug (default: info)
# CARGO_BUILD command that builds the runners (and the rewriter unless
Expand Down Expand Up @@ -93,6 +98,33 @@ prepare() {
fi
}

STDIN_LINE="hello from the host"

# The virtio console's files for test $1.
console_args() {
printf '%s\n' "$STDIN_LINE" >"$WORK/$1.stdin"
: >"$WORK/$1.stdout"
echo -device virtio-serial-pci,id=vs0,disable-legacy=on \
-chardev "file,id=con0,path=$WORK/$1.stdout,input-path=$WORK/$1.stdin" \
-device virtconsole,chardev=con0,bus=vs0.0
}

# Run check: TA output, and the echoed input, on the console.
check_console() {
local out="$WORK/$1.stdout"
if [[ ${verbose:-0} -ne 0 ]]; then
echo "--- console ($1) ---" >&2
cat "$out" >&2
echo "---" >&2
fi
grep -q "/TA:" "$out" || { echo "no TA output on the console"; return 1; }
if [[ $profile == debug ]] && ! grep -qF "[stdin] $STDIN_LINE" "$out"; then
echo "standard input was not echoed on the console"
return 1
fi
}
check=check_console

for ta in "${tas[@]}"; do
name=${ta%@default}
if [[ -f "$VM_TESTS_DIR/$name-cmds.json" ]]; then
Expand All @@ -118,6 +150,7 @@ for ta in "${tas[@]}"; do
files+=(cmds.json)
fi
tar --format=ustar -cf "$WORK/$ta.tar" -C "$WORK/$ta" "${files[@]}"
run_vm "$ta" "$KERNEL" "$WORK/$ta.tar"
# shellcheck disable=SC2046 # console_args prints separate arguments
run_vm "$ta" "$KERNEL" "$WORK/$ta.tar" $(console_args "$ta")
done
summary
24 changes: 12 additions & 12 deletions litebox_bootloader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ use layout::{
heap_start_address, rodata_end_address, rodata_start_address, text_end_address,
text_start_address,
};
use litebox_platform_vm_kernel::{BootConfig, KERNEL_OFFSET, VmKernel, clock::TscClock};
use litebox_hal::clock::Clock;
use litebox_platform_vm_kernel::{BootConfig, KERNEL_OFFSET, VmKernel};
use x86_64::PhysAddr;

const PAGE_SIZE: u64 = 4096;
Expand All @@ -43,7 +44,7 @@ pub struct Kernel {
/// Boot modules stay mapped at `PA + KERNEL_OFFSET`, in kernel-managed
/// RAM, for the kernel's lifetime.
pub boot_info: &'static BootInfo,
pub tsc_khz: u64,
pub clock: Clock,
}

/// Declares the kernel's `fn(Kernel) -> !`, run on the kernel stack once the
Expand All @@ -70,15 +71,15 @@ unsafe extern "Rust" {
safe fn __litebox_kernel_logger() -> &'static dyn log::Log;
}

static BOOT: spin::Once<(BootInfo, u64)> = spin::Once::new();
static BOOT: spin::Once<(BootInfo, Clock)> = spin::Once::new();

fn early_console_init() {
let _ = log::set_logger(__litebox_kernel_logger());
log::set_max_level(log::LevelFilter::Info);

litebox_hal::console_println!("=====================");
litebox_hal::console_println!(" Hello from LiteBox! ");
litebox_hal::console_println!("=====================");
litebox_hal::serial_println!("=====================");
litebox_hal::serial_println!(" Hello from LiteBox! ");
litebox_hal::serial_println!("=====================");
}

/// The front ends' entry; see [`handoff`].
Expand All @@ -101,10 +102,9 @@ fn kernel_start(boot_info: impl FnOnce() -> BootInfo) -> ! {
.collect();
let to_pa = |va: u64| VmKernel::va_to_pa(x86_64::VirtAddr::new(va));
litebox_hal::interrupt::init_legacy_pics();
let tsc_khz = litebox_hal::clock::calibrate_tsc_khz();
litebox_util_log::info!(mhz:% = tsc_khz / 1000; "TSC calibrated against the PIT");
BOOT.call_once(|| (info, tsc_khz));
let clock = alloc::boxed::Box::leak(alloc::boxed::Box::new(TscClock::new(tsc_khz)));
let clock = Clock::measure();
BOOT.call_once(|| (info, clock));
let clock = alloc::boxed::Box::leak(alloc::boxed::Box::new(clock));
// Safety: the front end established the direct mapping with IRQs off on one
// CPU. The heap contains only `ram`, text bounds come from the linker, and
// no live resource needs the boot stack after the handoff.
Expand All @@ -124,7 +124,7 @@ fn kernel_start(boot_info: impl FnOnce() -> BootInfo) -> ! {
}

fn kernel_main(platform: &'static VmKernel) -> ! {
let (boot_info, tsc_khz) = BOOT.get().expect("boot info captured");
let (boot_info, clock) = BOOT.get().expect("boot info captured");
for m in &boot_info.modules {
assert!(
platform.contains_ram(m.clone()),
Expand All @@ -136,7 +136,7 @@ fn kernel_main(platform: &'static VmKernel) -> ! {
__litebox_kernel_main(Kernel {
platform,
boot_info,
tsc_khz: *tsc_khz,
clock: *clock,
})
}

Expand Down
40 changes: 40 additions & 0 deletions litebox_broker_host/src/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,24 @@ pub fn publish_readiness<Channel: HostNotificationChannel>(
}
}

/// Publishes every pending readiness update, then returns; for deployments
/// without a publisher thread, which call this whenever updates may be
/// pending.
///
/// The channel must not block: a send that cannot proceed (e.g., a full
/// notification ring) fails, which leaves its update publishable for the next
/// call and returns the error.
pub fn publish_pending_readiness<Channel: HostNotificationChannel>(
publisher: &ReadinessPublisher,
channel: &mut Channel,
) -> Result<(), Channel::Error> {
while let Some(pending) = publisher.take_pending() {
channel.send_notification(&BrokerNotification::Readiness(pending.notification()))?;
pending.confirm();
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -402,6 +420,28 @@ mod tests {
drained
}

#[test]
fn pending_readiness_survives_a_failed_nonblocking_send() {
struct Full(bool);
impl HostNotificationChannel for Full {
type Error = ();
fn send_notification(&mut self, _: &BrokerNotification) -> Result<(), ()> {
if self.0 { Err(()) } else { Ok(()) }
}
}
let publisher = ReadinessPublisher::new();
publish(&publisher, HANDLE, ReadinessFlags::READ);
assert_eq!(
publish_pending_readiness(&publisher, &mut Full(true)),
Err(())
);
assert_eq!(
publish_pending_readiness(&publisher, &mut Full(false)),
Ok(())
);
assert!(publisher.take_pending().is_none());
}

#[test]
fn repeating_known_readiness_publishes_nothing() {
let publisher = ReadinessPublisher::new();
Expand Down
Loading
Loading