Skip to content

Commit

Permalink
Merge pull request #20 from LedgerHQ/ecalls
Browse files Browse the repository at this point in the history
Ecalls general framework + IDLE, XSEND, XRECV, EXIT and FATAL. Basic LRU cache for pages.
  • Loading branch information
bigspider authored Sep 18, 2024
2 parents d8b2afd + e45b1f9 commit 677c26c
Show file tree
Hide file tree
Showing 27 changed files with 1,857 additions and 281 deletions.
4 changes: 4 additions & 0 deletions app-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ version = "0.1.0"
edition = "2021"

[dependencies]
common = { path = "../common" }
critical-section = "1.1.2"
ctor = "0.2.8"
embedded-alloc = "0.5.1"

[target.'cfg(target_arch = "x86_64")'.dependencies]
hex = "0.4.3"
2 changes: 0 additions & 2 deletions app-sdk/src/ecall_constants.rs

This file was deleted.

19 changes: 17 additions & 2 deletions app-sdk/src/ecalls.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@

#[cfg(target_arch = "riscv32")]
use crate::ecalls_riscv as ecalls_module;

#[cfg(not(target_arch = "riscv32"))]
use crate::ecalls_native as ecalls_module;

pub(crate) trait EcallsInterface {
/// Shows the idle screen of the V-App
fn ux_idle();

/// Exits the V-App with the given status code
fn exit(status: i32) -> !;

/// Prints a fatal error message and exits the V-App
fn fatal(msg: *const u8, size: usize) -> !;

/// Sends a buffer to the host
fn xsend(buffer: *const u8, size: usize);

/// Receives a buffer of at most `max_size` bytes from the host
fn xrecv(buffer: *mut u8, max_size: usize) -> usize;
}

pub use ecalls_module::*;
pub(crate) use ecalls_module::*;
63 changes: 62 additions & 1 deletion app-sdk/src/ecalls_native.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,63 @@
pub fn ecall_ux_idle() {
use std::io;
use std::io::Write;

use crate::ecalls::EcallsInterface;

pub struct Ecall;

impl EcallsInterface for Ecall {
fn ux_idle() {}

fn exit(status: i32) -> ! {
std::process::exit(status);
}

fn fatal(msg: *const u8, size: usize) -> ! {
// print the message as a panic
let slice = unsafe { std::slice::from_raw_parts(msg, size) };
let msg = std::str::from_utf8(slice).unwrap();
panic!("{}", msg);
}

fn xsend(buffer: *const u8, size: usize) {
let slice = unsafe { std::slice::from_raw_parts(buffer, size) };
for byte in slice {
print!("{:02x}", byte);
}
print!("\n");
io::stdout().flush().expect("Failed to flush stdout");
}

fn xrecv(buffer: *mut u8, max_size: usize) -> usize {
// Request a hex string from the user; repeat until the input is valid
// and at most max_size bytes long
let (n_bytes_to_copy, bytes) = loop {
let mut input = String::new();
io::stdout().flush().expect("Failed to flush stdout");
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");

let input = input.trim();

let Ok(bytes) = hex::decode(input) else {
println!(
"Input too large, max size is {} bytes, please try again.",
max_size
);
continue;
};
if bytes.len() <= max_size {
break (bytes.len(), bytes);
}
println!("Input too large, please try again.");
};

// copy to the destination buffer
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer, n_bytes_to_copy);
}

return n_bytes_to_copy;
}
}
181 changes: 172 additions & 9 deletions app-sdk/src/ecalls_riscv.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,176 @@
use core::arch::asm;

use crate::ecall_constants::*;

pub fn ecall_ux_idle() {
unsafe {
asm!(
"li a7, {0}",
"ecall",
const ECALL_UX_IDLE
);
use crate::ecalls::EcallsInterface;
use common::ecall_constants::*;

macro_rules! ecall0v {
// ECALL with no arguments and no return value
($fn_name:ident, $syscall_number:expr) => {
fn $fn_name() {
unsafe {
asm!(
"ecall",
in("t0") $syscall_number, // Pass the syscall number in t0
);
}
}
};
}
macro_rules! ecall0 {
// ECALL with no arguments and returning a value
($fn_name:ident, $syscall_number:expr, $ret_type:ty) => {
fn $fn_name() -> $ret_type {
let ret: $ret_type;
unsafe {
asm!(
"ecall",
in("t0") $syscall_number, // Pass the syscall number in t0
lateout("a0") ret // Return value in a0
);
}
ret
}
};
}
macro_rules! ecall1v {
// ECALL with 1 argument and no return value
($fn_name:ident, $syscall_number:expr, ($arg1:ident: $arg1_type:ty)) => {
fn $fn_name($arg1: $arg1_type) {
unsafe {
asm!(
"ecall",
in("t0") $syscall_number, // Pass the syscall number in t0
in("a0") $arg1 // First argument in a0
);
}
}
};
}
macro_rules! ecall1 {
// ECALL with 1 argument and returning a value
($fn_name:ident, $syscall_number:expr, ($arg1:ident: $arg1_type:ty), $ret_type:ty) => {
fn $fn_name($arg1: $arg1_type) -> $ret_type {
let ret: $ret_type;
unsafe {
asm!(
"ecall",
in("t0") $syscall_number, // Pass the syscall number in t0
in("a0") $arg1, // First argument in a0
lateout("a0") ret // Return value in a0
);
}
ret
}
};
}
macro_rules! ecall2v {
// ECALL with 2 arguments and no return value
($fn_name:ident, $syscall_number:expr,
($arg1:ident: $arg1_type:ty),
($arg2:ident: $arg2_type:ty)) => {
fn $fn_name($arg1: $arg1_type, $arg2: $arg2_type) {
unsafe {
asm!(
"ecall",
in("t0") $syscall_number, // Pass the syscall number in t0
in("a0") $arg1, // First argument in a0
in("a1") $arg2 // Second argument in a1
);
}
}
};
}
macro_rules! ecall2 {
// ECALL with 2 arguments and returning a value
($fn_name:ident, $syscall_number:expr,
($arg1:ident: $arg1_type:ty),
($arg2:ident: $arg2_type:ty), $ret_type:ty) => {
fn $fn_name($arg1: $arg1_type, $arg2: $arg2_type) -> $ret_type {
let ret: $ret_type;
unsafe {
asm!(
"ecall",
in("t0") $syscall_number, // Pass the syscall number in t0
in("a0") $arg1, // First argument in a0
in("a1") $arg2, // Second argument in a1
lateout("a0") ret // Return value in a0
);
}
ret
}
};
}
macro_rules! ecall3v {
// ECALL with 3 arguments and no return value
($fn_name:ident, $syscall_number:expr,
($arg1:ident: $arg1_type:ty),
($arg2:ident: $arg2_type:ty),
($arg3:ident: $arg3_type:ty)) => {
fn $fn_name($arg1: $arg1_type, $arg2: $arg2_type, $arg3: $arg3_type) {
unsafe {
asm!(
"ecall",
in("t0") $syscall_number, // Pass the syscall number in t0
in("a0") $arg1, // First argument in a0
in("a1") $arg2, // Second argument in a1
in("a2") $arg3 // Third argument in a2
);
}
}
};
}
macro_rules! ecall3 {
// ECALL with 3 arguments and returning a value
($fn_name:ident, $syscall_number:expr,
($arg1:ident: $arg1_type:ty),
($arg2:ident: $arg2_type:ty),
($arg3:ident: $arg3_type:ty), $ret_type:ty) => {
fn $fn_name($arg1: $arg1_type, $arg2: $arg2_type, $arg3: $arg3_type) -> $ret_type {
let ret: $ret_type;
unsafe {
asm!(
"ecall",
in("t0") $syscall_number, // Pass the syscall number in t0
in("a0") $arg1, // First argument in a0
in("a1") $arg2, // Second argument in a1
in("a2") $arg3, // Third argument in a2
lateout("a0") ret // Return value in a0
);
}
ret
}
};
}

pub struct Ecall;

impl EcallsInterface for Ecall {
ecall0v!(ux_idle, ECALL_UX_IDLE);

fn exit(status: i32) -> ! {
unsafe {
asm!(
"ecall",
in("t0") ECALL_EXIT,
in("a0") status,
options(noreturn)
);
}
}

// fatal() and exit() are diverging, therefore we can't use the macro
fn fatal(msg: *const u8, size: usize) -> ! {
unsafe {
asm!(
"ecall",
in("t0") ECALL_FATAL,
in("a0") msg,
in("a1") size,
options(noreturn)
);
}
}

ecall2v!(xsend, ECALL_XSEND, (buffer: *const u8), (size: usize));
ecall2!(xrecv, ECALL_XRECV, (buffer: *mut u8), (size: usize), usize);
}
26 changes: 20 additions & 6 deletions app-sdk/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
#![feature(asm_const)]

#![cfg_attr(target_arch = "riscv32", no_main, no_std)]

extern crate alloc;

use alloc::{vec, vec::Vec};

pub mod ux;

mod ecalls;
mod ecall_constants;

#[cfg(target_arch = "riscv32")]
mod ecalls_riscv;

#[cfg(not(target_arch = "riscv32"))]
mod ecalls_native;

use ecalls::{Ecall, EcallsInterface};
use embedded_alloc::Heap;

#[cfg(not(target_arch = "riscv32"))]
Expand All @@ -21,7 +24,6 @@ use ctor;
const HEAP_SIZE: usize = 65536;
static mut HEAP_ALLOC: [u8; HEAP_SIZE] = [0; HEAP_SIZE];


#[global_allocator]
static HEAP: Heap = Heap::empty();

Expand Down Expand Up @@ -64,11 +66,23 @@ pub extern "C" fn rust_init_heap() {
// the initializer is called automatically on native targets
}

pub fn fatal(msg: &str) {
// TODO: placeholder
let _ = msg;
pub fn fatal(msg: &str) -> ! {
Ecall::fatal(msg.as_ptr(), msg.len());
}

pub fn exit(status: i32) -> ! {
Ecall::exit(status);
}

pub fn xrecv(size: usize) -> Vec<u8> {
let mut buffer = vec![0; size];
let recv_size = Ecall::xrecv(buffer.as_mut_ptr(), buffer.len());
buffer[0..recv_size].to_vec()
}

pub fn xsend(buffer: &[u8]) {
Ecall::xsend(buffer.as_ptr(), buffer.len() as usize)
}

#[cfg(test)]
mod tests {
Expand Down
5 changes: 2 additions & 3 deletions app-sdk/src/ux.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

use crate::ecalls::*;
use crate::ecalls::{Ecall, EcallsInterface};

pub fn ux_idle() {
ecall_ux_idle()
Ecall::ux_idle()
}
Loading

0 comments on commit 677c26c

Please sign in to comment.