Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a userpsace low-level debug API for Tock 2.0 #345

Merged
merged 5 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 14 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,24 @@ __internal_disable_timer_in_integration_test = []
[dependencies]
libtock_core = { path = "core" }
libtock_codegen = { path = "codegen" }
futures = { version = "0.3.1", default-features = false, features = ["unstable", "cfg-target-has-atomic"] }
futures = { version = "0.3.1", default-features = false, features = [
"unstable",
"cfg-target-has-atomic",
] }

[dev-dependencies]
corepack = { version = "0.4.0", default-features = false, features = ["alloc"] }
# We pin the serde version because newer serde versions may not be compatible
# with the nightly toolchain used by libtock-rs.
serde = { version = "=1.0.114", default-features = false, features = ["derive"] }
serde = { version = "=1.0.114", default-features = false, features = [
"derive",
] }
ctap2-authenticator = { git = "https://gitlab.com/ctap2-authenticator/ctap2-authenticator.git" }
p256 = { version = "0.7" , default-features = false, features = ["arithmetic", "ecdsa", "ecdsa-core"] }
p256 = { version = "0.7", default-features = false, features = [
"arithmetic",
"ecdsa",
"ecdsa-core",
] }
# p256 depends transitively on bitvec 0.18. bitvec 0.18.5 depends on radium
# 0.3.0, which does not work on platforms that lack atomics. This prevents cargo
# from selecting bitvec 0.18.5, which avoids the radium 0.3.0 issue.
Expand Down Expand Up @@ -82,8 +91,9 @@ lto = true
debug = true

[workspace]
exclude = [ "tock" ]
exclude = ["tock"]
members = [
"apis/low_level_debug",
"codegen",
"core",
"libtock2",
Expand Down
14 changes: 14 additions & 0 deletions apis/low_level_debug/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "libtock_low_level_debug"
version = "0.1.0"
authors = ["Tock Project Developers <[email protected]>"]
license = "MIT/Apache-2.0"
edition = "2018"
repository = "https://www.github.com/tock/libtock-rs"
description = "libtock low-level debug drivers"

[dependencies]
libtock_platform = { path = "../../platform" }

[dev-dependencies]
libtock_unittest = { path = "../../unittest" }
73 changes: 73 additions & 0 deletions apis/low_level_debug/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#![no_std]

use libtock_platform::{CommandReturn, Syscalls};

/// The low-level debug API provides tools to diagnose userspace issues that make normal debugging workflows (e.g. printing to the console) difficult.
///
/// It allows libraries to print alert codes and apps to print numeric information using only the command system call.
///
/// # Example
/// ```ignore
/// use libtock_runtime::TockSyscalls;
///
/// // Uses the real syscall implementation.
/// type LowLevelDebug = libtock_low_level_debug::LowLevelDebug<TockSyscalls>;
///
/// // Prints 0x45 and the app which called it.
/// LowLevelDebug::print_1(0x45);
/// ```
pub struct LowLevelDebug<S: Syscalls>(S);

impl<S: Syscalls> LowLevelDebug<S> {
/// Run a check against the low-level debug capsule to ensure it is working.
#[inline(always)]
pub fn driver_check() -> CommandReturn {
S::command(DRIVER_ID, DRIVER_CHECK, 0, 0)
}

/// Print one of the predefined alerts in [`AlertCode`].
#[inline(always)]
pub fn print_alert_code(code: AlertCode) -> CommandReturn {
S::command(DRIVER_ID, PRINT_ALERT_CODE, code as u32, 0)
}

/// Print a single number. The number will be printed in hexadecimal.
///
/// In general, this should only be added temporarily for debugging and should not be called by released library code.
#[inline(always)]
pub fn print_1(x: u32) -> CommandReturn {
S::command(DRIVER_ID, PRINT_1, x, 0)
}

/// Print two numbers. The numbers will be printed in hexadecimal.
///
/// Like `print_1`, this is intended for temporary debugging and should not be called by released library code.
/// If you want to print multiple values, it is often useful to use the first argument to indicate what value is being printed.
#[inline(always)]
pub fn print_2(x: u32, y: u32) -> CommandReturn {
S::command(DRIVER_ID, PRINT_2, x, y)
}
}

/// A predefined alert code, for use with [`LowLevelDebug::print_alert_code`].
///
/// Predefined alert codes are intended for use in library code, and are defined here to avoid collisions between projects.
#[repr(u32)]
pub enum AlertCode {
/// Application panic (e.g. `panic!()` called in Rust code).
Panic = 0x01,

/// A statically-linked app was not installed in the correct location in flash.
WrongLocation = 0x02,
}

const DRIVER_ID: u32 = 8;

// Command IDs
const DRIVER_CHECK: u32 = 0;
const PRINT_ALERT_CODE: u32 = 1;
const PRINT_1: u32 = 2;
const PRINT_2: u32 = 3;

#[cfg(test)]
mod tests;
80 changes: 80 additions & 0 deletions apis/low_level_debug/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use super::*;
use libtock_platform::ErrorCode;
use libtock_unittest::{command_return, fake, ExpectedSyscall};

#[test]
fn driver_check() {
// Create a new fake kernel for the current thread, replacing avy previous one.
let kernel = fake::Kernel::new();
let driver = fake::LowLevelDebug::new();
kernel.add_driver(&driver);

assert!(LowLevelDebug::<fake::Syscalls>::driver_check().is_success());
assert_eq!(driver.take_messages(), []);
}

#[test]
fn print_alert_code() {
// Create a new fake kernel for the current thread, replacing avy previous one.
let kernel = fake::Kernel::new();
let driver = fake::LowLevelDebug::new();
kernel.add_driver(&driver);

assert!(LowLevelDebug::<fake::Syscalls>::print_alert_code(AlertCode::Panic).is_success());
assert!(
LowLevelDebug::<fake::Syscalls>::print_alert_code(AlertCode::WrongLocation).is_success()
);
assert_eq!(
driver.take_messages(),
[
fake::Message::AlertCode(0x01),
fake::Message::AlertCode(0x02)
]
);
}

#[test]
fn print_1() {
// Create a new fake kernel for the current thread, replacing avy previous one.
let kernel = fake::Kernel::new();
let driver = fake::LowLevelDebug::new();
kernel.add_driver(&driver);

assert!(LowLevelDebug::<fake::Syscalls>::print_1(42).is_success());
assert_eq!(driver.take_messages(), [fake::Message::Print1(42)]);
}

#[test]
fn print_2() {
// Create a new fake kernel for the current thread, replacing avy previous one.
let kernel = fake::Kernel::new();
let driver = fake::LowLevelDebug::new();
kernel.add_driver(&driver);

assert!(LowLevelDebug::<fake::Syscalls>::print_2(42, 27).is_success());
assert!(LowLevelDebug::<fake::Syscalls>::print_2(29, 43).is_success());
assert_eq!(
driver.take_messages(),
[fake::Message::Print2(42, 27), fake::Message::Print2(29, 43)]
);
}

#[test]
fn failed_print() {
// Create a new fake kernel for the current thread, replacing avy previous one.
let kernel = fake::Kernel::new();
let driver = fake::LowLevelDebug::new();
kernel.add_driver(&driver);
kernel.add_expected_syscall(ExpectedSyscall::Command {
driver_id: DRIVER_ID,
command_id: PRINT_1,
argument0: 72,
argument1: 0,
override_return: Some(command_return::failure(ErrorCode::Fail)),
});

assert!(LowLevelDebug::<fake::Syscalls>::print_1(72).is_failure());

// The fake driver still receives the command even if a fake error is injected.
assert_eq!(driver.take_messages(), [fake::Message::Print1(72)]);
}