Merge #253 253: Update result.rs with remaining ReturnCode values r=jrvanwhy a=MirnaMuhammad98 Adding the remaining ReturnCode values that exist in [tock/returncode.rs](https://github.com/tock/tock/blob/master/kernel/src/returncode.rs) Co-authored-by: Mirna <29131616+MirnaMuhammad98@users.noreply.github.com>
diff --git a/Cargo.toml b/Cargo.toml index caeae02..99543d9 100644 --- a/Cargo.toml +++ b/Cargo.toml
@@ -29,6 +29,7 @@ p256 = { version = "0.5.0" , default-features = false, features = ["arithmetic", "ecdsa", "ecdsa-core"] } subtle = { version = "2.3.0", default-features = false, features = ["i128"] } generic-array = { version = "0.14.3" } +libmctp = { version = "0.1.0" } # We need to override this to allow builds for targets that don't support atomics. # Once a version newer then 0.4.11 is released we can update to use that.
diff --git a/Makefile b/Makefile index e32decb..81829e2 100644 --- a/Makefile +++ b/Makefile
@@ -18,6 +18,7 @@ @echo " - nrf52" @echo " - imxrt1050" @echo " - apollo3" + @echo " - stm32f3discovery" @echo @echo "Run 'make setup' to setup Rust to build libtock-rs." @echo "Run 'make <board>' to build libtock-rs for that board" @@ -132,6 +133,14 @@ flash-nrf52840: PLATFORM=nrf52840 cargo run $(release) --target=thumbv7em-none-eabi --example $(EXAMPLE) $(features) +.PHONY: stm32f3discovery +stm32f3discovery: + PLATFORM=stm32f3discovery cargo build $(release) --target=thumbv7em-none-eabi --examples $(features) + +.PHONY: flash-stm32f3discovery +flash-stm32f3discovery: + PLATFORM=stm32f3discovery cargo run $(release) --target=thumbv7em-none-eabi --example $(EXAMPLE) $(features) + .PHONY: opentitan opentitan: PLATFORM=opentitan cargo build $(release) --target=riscv32imc-unknown-none-elf --examples $(features)
diff --git a/boards/layout_stm32f3discovery.ld b/boards/layout_stm32f3discovery.ld new file mode 100644 index 0000000..9368003 --- /dev/null +++ b/boards/layout_stm32f3discovery.ld
@@ -0,0 +1,11 @@ +/* Layout for the stm32f3discovery board, usable by the examples in this repository. */ + +MEMORY { + /* The application region is 64 bytes (0x40) */ + FLASH (rx) : ORIGIN = 0x08020040, LENGTH = 0x00020000 + SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 48K +} + +MPU_MIN_ALIGN = 8K; + +INCLUDE layout_generic.ld
diff --git a/doc/PlatformDesignStory.md b/doc/PlatformDesignStory.md new file mode 100644 index 0000000..0632494 --- /dev/null +++ b/doc/PlatformDesignStory.md
@@ -0,0 +1,572 @@ +`libtock_platform` Design Story +=============================== + +Note: At the time of this writing, `libtock_platform` is still a work in +progress that exists largely in @jrvanwhy's head. + +`libtock_platform` is a crate that will contain core abstractions that will be +used by `libtock_core`'s drivers. For example, it will contain abstractions for +asynchronous APIs that are lighter weight (in terms of code size and RAM usage) +than Rust's futures. + +This document serves as a justification for, and explanation of, the high-level +design of `libtock_platform`. Instead of describing the various components and +how they interact, it starts with a hello world application and extracts some of +the functionality out into a reusable library. As we do so, we see that +`libtock_core`'s [design considerations](DesignConsiderations.md) lead +incrementally to a design for `libtock_platform`. + +In order to keep things understandable, this document makes several +simplifications. Error handling logic is omitted, although we still structure +the code so as to allow it. We use `unsafe` in places where we should instead +have an additional, safe, abstraction. We use a simplified form of the Tock 2.0 +system calls, which are currently undergoing design revisions. + +## Hello World + +We start with the following example app. In order to show how `libtock_platform` +will handle asynchronous callbacks, it is written in an asynchronous manner. + +```rust +#![no_std] + +static GREETING: [u8; 7] = *b"Hello, "; +static NOUN: [u8; 7] = *b"World!\n"; +static mut DONE: bool = false; + +// Driver, command, allow, and subscription numbers. +const DRIVER: usize = 1; +const WRITE_DONE: usize = 1; +const WRITE_BUFFER: usize = 1; +const START_WRITE: usize = 1; + +fn main() { + libtock_runtime::subscribe(DRIVER, WRITE_DONE, write_complete, 0); + libtock_runtime::const_allow(DRIVER, WRITE_BUFFER, &GREETING); + libtock_runtime::command(DRIVER, START_WRITE, GREETING.len(), 0); + loop { + libtock_runtime::yieldk(); + } +} + +extern "C" fn write_complete(bytes: usize, _: usize, _: usize, _data: usize) { + // Detect if this write completion is a result of writing NOUN (the + // second write). If so, return immediately to avoid an infinite loop. + unsafe { + if DONE { return; } + DONE = true; + } + // At this point, we just finished writing GREETING and need to write NOUN. + libtock_runtime::const_allow(DRIVER, WRITE_BUFFER, &NOUN); + libtock_runtime::command(DRIVER, START_WRITE, NOUN.len(), 0); +} +``` + +Strictly speaking, in this app, `DONE` could be tracked by passing it as the +`data` argument to `subscribe`. However, most applications will need to track +more than 32 bits of persistent state -- and use more than one callback -- so +they will need to manage persistent state themselves. To make this app +representative of most apps, we keep the state in userspace memory. + +This example assumes that the `libtock_runtime` crate exposes system call +implementations that look like the following (these are a simplification of the +Tock 2.0 system calls): + +```rust +pub fn subscribe(driver_num: usize, + subscription_num: usize, + callback: extern "C" fn(usize, usize, usize, usize), + data: usize); + +pub fn const_allow(driver_num: usize, buffer_num: usize, buffer: &'static [u8]); +pub fn command(driver_num: usize, command_num: usize, arg1: usize, arg2: usize); +pub fn yieldk(); +``` + +What do we already get "right" in this example? Except for the two intentional +inefficiencies explained above (performing the write in two steps rather than 1 +and maintaining the `DONE` state in process memory), it is very efficient. It +has only 3 global variables: `GREETING`, `NOUN`, and `DONE`. It has almost zero +bookkeeping overhead; it fairly directly makes 8 system calls (`subscribe`, +`const_allow`, `command`, `yieldk`, `const_allow`, `command`, `yieldk`, +`yieldk`). + +What do we want to improve? It calls system calls directly from application code +-- there should be a reusable `console` library that implements the system calls +instead! The application shouldn't need to know what command number starts a +write, it should just tell the console driver to do that for it. + +## Adding a Console Library + +Let's start taking some of the console-specific parts and moving them into a new +crate. The first system call the app makes is to `subscribe` to the write done +callback, so let's add a function to a new `libtock_console` crate that sets a +write callback using `subscribe`: + +```rust +#![no_std] + +const DRIVER: usize = 1; +const WRITE_DONE: usize = 1; + +fn set_write_callback(callback: fn(bytes: usize, data: usize), data: usize) { + libtock_runtime::subscribe(DRIVER, WRITE_DONE, write_complete, data); + // We need to store `callback` somewhere -- where to do so? +} + +extern "C" fn write_complete(bytes: usize, _: usize, _: usize, data: usize) { + // Hmm, how do we access the callback? +} +``` + +You may notice that the code is not quite complete: `set_write_callback` takes a +callback but doesn't store it anywhere. We don't want to store it in a global +variable because doing so would not be zero-cost: the original app didn't need +to store a function pointer, and we want to do this refactoring without bloating +the app. We could pass it through `data`, but what if the client code needs to +use `data` itself? That pattern isn't extensible: if there is another +asynchronous layer about the console (e.g. a virtualization system), it won't +have access to `data` to pass its callbacks through. + +Instead, we can pass the callback through the type system. We need a new trait +to represent the callback. This trait won't be specific to `libtock_console`, +and we'll later use it in unit tests -- which run on Linux, not Tock, so we'll +put it in the `libtock_platform` crate: + +```rust +pub trait FreeCallback<AsyncResponse> { + fn call(response: AsyncResponse); +} +``` + +We call this `FreeCallback` because it represents a free function as opposed to +a method. (This forshadows `MethodCallback`, which we will need later) + +The reason why we made this a shared generic trait rather than adding a +`libtock_console::Client` trait as the Tock kernel does will be apparent later. + +Using this trait, we can now write `libtock_console::set_write_callback`: + +```rust +#![no_std] + +use libtock_platform::FreeCallback; + +const DRIVER: usize = 1; +const WRITE_DONE: usize = 1; + +pub struct WriteCompleted { + pub bytes: usize, + pub data: usize, +} + +pub fn set_write_callback<Callback: FreeCallback<WriteCompleted>>(data: usize) { + libtock_runtime::subscribe(DRIVER, WRITE_DONE, write_complete::<Callback>, data); +} + +extern "C" fn write_complete<Callback: FreeCallback<WriteCompleted>>( + bytes: usize, _: usize, _: usize, data: usize) +{ + Callback::call(WriteCompleted { bytes, data }); +} +``` + +To finish `libtock_console`, we also need to add `set_write_buffer` (which calls +`allow`) and `start_write` (which calls `command`), which are much simpler: + +```rust +const WRITE_BUFFER: usize = 1; +const START_WRITE: usize = 1; + +pub fn set_write_buffer(buffer: &'static [u8]) { + libtock_runtime::const_allow(DRIVER, WRITE_BUFFER, buffer); +} + +pub fn start_write(bytes: usize) { + libtock_runtime::command(DRIVER, START_WRITE, bytes, 0); +} +``` + +We can then make use of `libtock_console` in our app as follows: + +```rust +#![no_std] + +static GREETING: [u8; 7] = *b"Hello, "; +static NOUN: [u8; 7] = *b"World!\n"; +static mut DONE: bool = false; + +fn main() { + libtock_console::set_write_callback::<App>(0); + libtock_console::set_write_buffer(&GREETING); + libtock_console::start_write(GREETING.len()); + loop { + libtock_runtime::yieldk(); + } +} + +struct App; + +impl libtock_platform::FreeCallback<libtock_console::WriteCompleted> for App { + fn call(_response: WriteCompleted) { + unsafe { + if DONE { return; } + DONE = true; + } + libtock_console::set_write_buffer(&NOUN); + libtock_console::start_write(NOUN.len()); + } +} +``` + +Now we have a reusable console library! However, we still don't have any unit +tests. + +## Adding a Fake Kernel + +A good unit test for the console driver would test not only the driver's +operation with successful system calls but also also test the driver's +error-handling logic. That is difficult to do if we test using a real Tock +kernel in an emulator -- the real kernel has the goal of avoiding system call +errors! Instead of using a real Tock kernel, driver unit tests should use a +"fake kernel" that simulates the kernel's functionality while allowing errors to +be injected. + +To keep this document reasonably short and understandable, we have omitted error +handling, but we can still structure our unit tests in a manner that would allow +a test to inject errors when error handling logic is added. + +To allow the console driver to work with both a real kernel and a fake kernel, +we add a `Syscalls` trait to `libtock_platform`. When compiled into a Tock +process binary, `Syscalls` will be implemented by a zero-sized type, which +avoids wasting non-volatile storage space or RAM area. To avoid passing around +references to a `Syscalls` implementation, we can pass the `Syscalls` by value +rather than by reference (i.e. take `self` rather than `&self`). For practical +use, this requires the `Syscalls` implementations to be `Copy`. + +```rust +pub trait Syscalls: Copy { + fn subscribe(self, driver: usize, minor: usize, + callback: extern "C" fn(usize, usize, usize, usize), + data: usize); + + fn const_allow(self, major: usize, minor: usize, buffer: &'static [u8]); + fn command(self, major: usize, miner: usize, arg1: usize, arg2: usize); + fn yieldk(self); +} +``` + +We then implement `Syscalls` using a real Tock kernel in `libtock_runtime`: + +```rust +#[derive(Clone, Copy)] +pub struct TockSyscalls; + +impl libtock_platform::Syscalls for TockSyscalls { + /* Omitted implementation details */ +} +``` + +We adapt `libtock_console` to use an app-provided `Syscalls` implementation +rather than directly calling into `libtock_runtime`: + +```rust +pub fn set_write_callback<S: Syscalls, Callback: FreeCallback<WriteCompleted>>( + syscalls: S, data: usize) +{ + syscalls.subscribe(1, 1, write_complete::<Callback>, data); +} + +extern "C" fn write_complete<Callback: FreeCallback<WriteCompleted>>( + bytes: usize, _: usize, _: usize, data: usize) +{ + Callback::call(WriteCompleted { bytes, data }); +} + +pub fn set_write_buffer<S: Syscalls>(syscalls: S, buffer: &'static [u8]) { + syscalls.const_allow(1, 1, buffer); +} + +pub fn start_write<S: Syscalls>(syscalls: S, bytes: usize) { + syscalls.command(1, 1, bytes, 0); +} +``` + +We'll create a new crate, `libtock_unittest`, which contains test utilities such +as the fake Tock kernel. The fake kernel, unlike +`libtock_runtime::TockSyscalls`, needs to maintain state, so it cannot be a +zero-sized type. Instead of implementing `Syscalls` on the fake kernel directly, +we implement it on a shared reference: + +```rust +type RawCallback = extern "C" fn(usize, usize, usize, usize); + +pub struct FakeSyscalls { + callback_pending: core::cell::Cell<Option<usize>>, + output: core::cell::Cell<Vec<u8>>, + write_buffer: core::cell::Cell<Option<&'static [u8]>>, + write_callback: core::cell::Cell<Option<RawCallback>>, + write_data: core::cell::Cell<usize>, +} + +impl FakeSyscalls { + pub fn new() -> Self { + FakeSyscalls { + callback_pending: Cell::new(None), + output: Cell::new(Vec::new()), + write_buffer: Cell::new(None), + write_callback: Cell::new(None), + write_data: Cell::new(0), + } + } + + pub fn read_buffer(&self) -> &'static [u8] { + self.write_buffer.take().unwrap_or(&[]) + } +} + +impl libtock_platform::Syscalls for &FakeSyscalls { + fn subscribe(self, driver: usize, minor: usize, callback: RawCallback, data: usize) { + if driver == 1 && minor == 1 { + self.write_callback.set(Some(callback)); + self.write_data.set(data); + } + } + + fn const_allow(self, major: usize, minor: usize, buffer: &'static [u8]) { + if major == 1 && minor == 1 { + self.write_buffer.set(Some(buffer)); + } + } + + fn command(self, major: usize, minor: usize, arg1: usize, _arg2: usize) { + if major != 1 || minor != 1 { return; } + if let Some(buffer) = self.write_buffer.get() { + let mut output = self.output.take(); + let bytes = core::cmp::min(arg1, buffer.len()); + output.extend_from_slice(&buffer[..bytes]); + self.output.set(output); + self.callback_pending.set(Some(bytes)); + } + } + + fn yieldk(self) { + let bytes = match self.callback_pending.take() { + Some(bytes) => bytes, + None => return, + }; + if let Some(callback) = self.write_callback.get() { + callback(bytes, 0, 0, self.write_data.get()); + } + } +} +``` + +## Adding a Synchronous Adapter + +We're still not ready to add unit tests to `libtock_console` yet! +`libtock_console` is asynchronous, which is difficult to work with in a unit +test. `libtock_core` should provide synchronous APIs for apps that do not wish +to be fully asynchronous, so lets go ahead and implement a synchronous API. To +avoid re-implementing synchronous APIs for every driver, let's make it work +generically with all `libtock_core` drivers. This is where we benefit from +making `FreeCallback` a generic trait rather than having a +`libtock_console::Client` trait. + +The synchronous adapter will need to store a copy of an `AsyncResponse`, so its +callback cannot be a free function (it needs access to `self`). Therefore, we +add the `MethodCallback` trait to `libtock_platform`: + +```rust +pub trait MethodCallback<AsyncResponse> { + fn call(&self, response: AsyncResponse); +} +``` + +Using `MethodCallback`, we can now write `SyncAdapter`. We add `SyncAdapter` to +a new crate, `libtock_sync`, as not all Tock apps will want it: + +```rust +use libtock_platform::MethodCallback; + +pub struct SyncAdapter<AsyncResponse, Syscalls> { + response: core::cell::Cell<Option<AsyncResponse>>, + syscalls: Syscalls, +} + +impl<AsyncResponse, Syscalls> SyncAdapter<AsyncResponse, Syscalls> { + pub const fn new(syscalls: Syscalls) -> SyncAdapter<AsyncResponse, Syscalls> { + SyncAdapter { response: core::cell::Cell::new(None), syscalls } + } +} + +impl<AsyncResponse, Syscalls: libtock_platform::Syscalls> SyncAdapter<AsyncResponse, Syscalls> { + pub fn wait(&self) -> AsyncResponse { + loop { + match self.response.take() { + Some(response) => return response, + None => self.syscalls.yieldk(), + } + } + } +} + +impl<AsyncResponse, Syscalls: libtock_platform::Syscalls> +MethodCallback<AsyncResponse> for SyncAdapter<AsyncResponse, Syscalls> { + fn call(&self, response: AsyncResponse) { + self.response.set(Some(response)); + } +} +``` + +## Adding a Unit Test + +Before we write the test itself, we should add one more utility to +`libtock_unittest`. That utility is the `test_component!` macro, which creates a +thread-local instance of a type and provides `FreeCallback` implementations for +every `MethodCallback` implementation the type has: + +```rust +#[macro_export] +macro_rules! test_component { + [$link:ident, $name:ident: $comp:ty = $init:expr] => { + let $name = std::boxed::Box::leak(std::boxed::Box::new($init)) as &$comp; + std::thread_local!(static GLOBAL: std::cell::Cell<Option<&'static $comp>> + = std::cell::Cell::new(None)); + GLOBAL.with(|g| g.set(Some($name))); + struct $link; + impl<T> libtock_platform::FreeCallback<T> for $link + where $comp: libtock_platform::MethodCallback<T> { + fn call(response: T) { + GLOBAL.with(|g| g.get().unwrap()).call(response); + } + } + }; +} +``` + +We can finally add a unit test to `libtock_console`: + +```rust +#[cfg(test)] +mod tests { + #[test] + fn write() { + extern crate std; + + use libtock_platform::MethodCallback; + use libtock_sync::SyncAdapter; + use libtock_unittest::FakeSyscalls; + use std::boxed::Box; + use std::thread_local; + use super::{set_write_buffer, set_write_callback, start_write, WriteCompleted}; + + let syscalls: &_ = Box::leak(Box::new(FakeSyscalls::new())); + libtock_unittest::test_component![SyncAdapterLink, sync_adapter: SyncAdapter<WriteCompleted, &'static FakeSyscalls> + = SyncAdapter::new(syscalls)]; + + set_write_callback::<_, SyncAdapterLink>(syscalls, 1234); + set_write_buffer(syscalls, b"Hello"); + start_write(syscalls, 5); + let response = sync_adapter.wait(); + assert_eq!(response.bytes, 5); + assert_eq!(response.data, 1234); + assert_eq!(syscalls.read_buffer(), b"Hello"); + } +} +``` + +## Adding `static_component!` + +We added `libtock_unittest::test_component!` to make it easy to set up +components in unit tests, but we have no equivalent for apps. Our app still uses +`unsafe` to access its `DONE` variable. Instead, lets hide that unsafety behind +a new macro. This macro is only sound in `libtock_runtime`'s single-threaded +environment, so we add it to `libtock_runtime` directly: + +```rust +#[macro_export] +macro_rules! static_component { + [$link:ident, $name:ident: $comp:ty = $init:expr] => { + static mut COMPONENT: $comp = $init; + struct $link; + impl<T> libtock_platform::FreeCallback<T> for $link + where $comp: libtock_platform::MethodCallback<T> { + fn call(response: T) { + unsafe { &COMPONENT }.call(response); + } + } + }; +} +``` + +We can now use `static_component!` in our Hello World app to instantiate the +`App` struct: + +```rust +#![no_std] + +static GREETING: [u8; 7] = *b"Hello, "; +static NOUN: [u8; 7] = *b"World!\n"; + +fn main() { + libtock_console::set_write_callback::<_, AppLink>(0); + libtock_console::set_write_buffer(&GREETING); + libtock_console::start_write(GREETING.len()); + loop { + libtock_runtime::yieldk(); + } +} + +struct App { + done: core::cell::Cell<bool> +} + +impl App { + pub const fn new() -> App { + App { + done: core::cell::Cell::new(false) + } + } +} + +impl MethodCallback<WriteCompleted> for App { + fn call(&self, _response: WriteCompleted) { + if self.done.get() { return; } + self.done.set(true); + set_write_buffer(TockSyscalls, &NOUN ); + start_write(TockSyscalls, NOUN.len() ); + } +} + +libtock_runtime::static_component![AppLink, APP: App = App::new()]; +``` + +Now our app has no more unsafe! + +## Recap + +We wrote a Hello World application that uses Tock's console system calls and +asynchronous callbacks. We then extracted the console system call interface into +a reusable library, creating the `FreeCallback` trait along the way. + +In order to provide unit tests for the console library, we needed to create +several new abstractions. We created `Syscalls` so that we can direct tho +console driver's system calls to a fake kernel. We created the +`libtock_unittest` crate which contains the fake kernel as well as a +`test_component!` helper macro. We created `SyncAdapter` so that the unit test +can be written in a synchronous manner -- although `SyncAdapter` is not +testing-specific! We created `MethodCallback` because `FreeCallback` is not a +powerful-enough abstraction on its own for `SyncAdapter`. + +We ended up with six Rust crates: + +1. `libtock_console`, which contains the console driver logic and unit test + code. +2. `libtock_platform`, which provides abstractions that can be shared with other + drivers. +3. `libtock_runtime`, which contains non-portable system call implementations. +4. `libtock_sync`, which provides a synchronous interface using + `libtock_platform`'s traits. +5. `libtock_unittest`, which provides a fake Tock kernel and other utilities. +6. The hello world app itself.
diff --git a/examples-features/ctap.rs b/examples-features/ctap.rs index 3a66eff..6ca0611 100644 --- a/examples-features/ctap.rs +++ b/examples-features/ctap.rs
@@ -30,6 +30,8 @@ use p256::{Scalar, SecretKey}; use subtle::{Choice, ConditionallySelectable}; +libtock_core::stack_size! {0x4000} + #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct PrivateKey(Scalar);
diff --git a/examples/libmctp.rs b/examples/libmctp.rs new file mode 100644 index 0000000..8623665 --- /dev/null +++ b/examples/libmctp.rs
@@ -0,0 +1,105 @@ +#![no_std] +/// This is a example of a MCTP master device +use libmctp::smbus::{MCTPSMBusContext, VendorIDFormat}; +use libtock::i2c_master::I2cBuffer; +use libtock::println; +use libtock::result::TockResult; +use libtock::syscalls; + +libtock_core::stack_size! {0x800} + +// The address of this device +const MY_ID: u8 = 0x23; +const DEST_ID: u8 = 0x10; +// Support vendor defined protocol 0x7E +const MSG_TYPES: [u8; 1] = [0x7E]; +// Specify a PCI vendor ID that we support +const VENDOR_IDS: [libmctp::smbus::VendorIDFormat; 1] = [VendorIDFormat { + // PCI Vendor ID + format: 0x00, + // PCI VID + data: 0x1414, + // Extra data + numeric_value: 4, +}]; + +#[libtock::main] +async fn main() -> TockResult<()> { + let mut drivers = libtock::retrieve_drivers()?; + drivers.console.create_console(); + println!("Starting libmctp example"); + let i2c_driver = drivers.i2c.init_driver()?; + + // Check that I2C exists + if i2c_driver.check_present().is_err() { + println!("No I2C device, yielding"); + loop { + unsafe { syscalls::raw::yieldk() }; + } + } + println!("Found the I2C device"); + + println!("Setting callback"); + let mut callback = |_, _| { + println!("I2C Callback"); + }; + + let _subscription = i2c_driver.subscribe(&mut callback)?; + println!("Creating MCTP SMBus Context"); + let ctx = MCTPSMBusContext::new(MY_ID, &MSG_TYPES, &VENDOR_IDS); + + let mut buf: [u8; 32] = [0; 32]; + + println!("Creating the request"); + let len = ctx + .get_request() + .get_vendor_defined_message_support(0xB, 0, &mut buf); + + println!("Creating master write buffer"); + let mut master_write_buffer = I2cBuffer::default(); + // Skip the first byte, as that is the destination address + for (i, d) in buf[1..].iter().enumerate() { + master_write_buffer[i] = *d; + } + let dest_buffer = i2c_driver.init_buffer(&mut master_write_buffer)?; + println!(" done"); + + let _ = i2c_driver.write(DEST_ID as usize, len.unwrap()); + + unsafe { syscalls::raw::yieldk() }; + + // Read 4 bytes for the SMBus header + let _ = i2c_driver.read(DEST_ID as usize, 4); + + unsafe { syscalls::raw::yieldk() }; + + // Copy into a temp buffer + let mut temp_buffer = [0; libtock::hmac::DEST_BUFFER_SIZE]; + dest_buffer.read_bytes(&mut temp_buffer[1..4]); + + // Determine the full length + let bytes = ctx.get_length(&temp_buffer).unwrap(); + + // Read the full packet. The slave will re-send the data, so do + // a full read + let _ = i2c_driver.read(DEST_ID as usize, bytes - 1); + + unsafe { syscalls::raw::yieldk() }; + + // Copy in the full packet, with space for the destination address + dest_buffer.read_bytes(&mut temp_buffer[1..bytes - 1]); + + // Set the destination address as this isn't filled in the buffer from + // the kernel + temp_buffer[0] = DEST_ID << 1; + + // Decode the response + let ret = ctx.decode_packet(&temp_buffer[0..bytes]); + + // Print the outcome of the decode + println!("ret: {:?}", ret); + + loop { + unsafe { syscalls::raw::yieldk() }; + } +}
diff --git a/examples/ninedof.rs b/examples/ninedof.rs new file mode 100644 index 0000000..ec0724c --- /dev/null +++ b/examples/ninedof.rs
@@ -0,0 +1,23 @@ +#![no_std] + +use libtock::println; +use libtock::result::TockResult; +use libtock::timer::Duration; + +libtock_core::stack_size! {0x800} + +#[libtock::main] +async fn main() -> TockResult<()> { + let mut drivers = libtock::retrieve_drivers()?; + + let mut timer_driver = drivers.timer.create_timer_driver(); + let timer_driver = timer_driver.activate()?; + drivers.console.create_console(); + + loop { + println!("Mag: {}\n", drivers.ninedof.read_magnetometer()?); + println!("Accel: {}\n", drivers.ninedof.read_acceleration()?); + println!("Gyro: {}\n", drivers.ninedof.read_gyroscope()?); + timer_driver.sleep(Duration::from_ms(500)).await?; + } +}
diff --git a/src/drivers.rs b/src/drivers.rs index 9b74a28..8604691 100644 --- a/src/drivers.rs +++ b/src/drivers.rs
@@ -4,6 +4,8 @@ use crate::ctap::CtapDriverFactory; use crate::gpio::GpioDriverFactory; use crate::hmac::HmacDriverFactory; +use crate::i2c_master::I2cDriverFactory; +use crate::i2c_master_slave::I2cMSDriverFactory; use crate::leds::LedsDriverFactory; use crate::result::OtherError; use crate::result::TockError; @@ -30,6 +32,8 @@ pub temperature: TemperatureDriverFactory, pub buttons: ButtonsDriverFactory, pub adc: AdcDriverFactory, + pub i2c_ms: I2cMSDriverFactory, + pub i2c: I2cDriverFactory, pub rng: RngDriver, pub ble_advertising: BleAdvertisingDriverFactory, pub ble_scanning: BleScanningDriverFactory, @@ -77,6 +81,8 @@ }, gpio: GpioDriverFactory, hmac: HmacDriverFactory, + i2c_ms: I2cMSDriverFactory, + i2c: I2cDriverFactory, temperature: TemperatureDriverFactory, rng: RngDriver, ambient_light_sensor: AmbientLightSensor,
diff --git a/src/i2c_master.rs b/src/i2c_master.rs new file mode 100644 index 0000000..55f2ee1 --- /dev/null +++ b/src/i2c_master.rs
@@ -0,0 +1,112 @@ +use crate::callback::CallbackSubscription; +use crate::callback::Consumer; +use crate::result::TockResult; +use crate::syscalls; +use core::marker::PhantomData; +use core::ops::Deref; +use core::ops::DerefMut; +use libtock_core::shared_memory::SharedMemory; + +const DRIVER_NUMBER: usize = 0x20003; + +pub const BUFFER_SIZE: usize = 64; + +mod command_nr { + pub const CHECK_PRESENT: usize = 0; + pub const WRITE: usize = 1; + pub const READ: usize = 2; + pub const WRITE_READ: usize = 3; +} + +mod subscribe_nr { + pub const SUBSCRIBE_CALLBACK: usize = 1; +} + +mod allow_nr { + pub const BUFFER: usize = 1; +} + +#[non_exhaustive] +pub struct I2cDriverFactory; + +impl I2cDriverFactory { + pub fn init_driver(&mut self) -> TockResult<I2cDriver> { + let i2c = I2cDriver { + lifetime: PhantomData, + }; + Ok(i2c) + } +} + +struct I2cEventConsumer; + +impl<CB: FnMut(usize, usize)> Consumer<CB> for I2cEventConsumer { + fn consume(callback: &mut CB, _: usize, _: usize, _: usize) { + callback(0, 0); + } +} + +pub struct I2cBuffer { + buffer: [u8; BUFFER_SIZE], +} + +impl Default for I2cBuffer { + fn default() -> Self { + I2cBuffer { + buffer: [0; BUFFER_SIZE], + } + } +} + +impl Deref for I2cBuffer { + type Target = [u8; BUFFER_SIZE]; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl DerefMut for I2cBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} + +pub struct I2cDriver<'a> { + lifetime: PhantomData<&'a ()>, +} + +impl<'a> I2cDriver<'a> { + pub fn init_buffer(&self, buffer: &'a mut I2cBuffer) -> TockResult<SharedMemory> { + syscalls::allow(DRIVER_NUMBER, allow_nr::BUFFER, &mut buffer.buffer).map_err(Into::into) + } + + pub fn subscribe<CB: FnMut(usize, usize)>( + &self, + callback: &'a mut CB, + ) -> TockResult<CallbackSubscription> { + syscalls::subscribe::<I2cEventConsumer, _>( + DRIVER_NUMBER, + subscribe_nr::SUBSCRIBE_CALLBACK, + callback, + ) + .map_err(Into::into) + } + + pub fn check_present(&self) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::CHECK_PRESENT, 0, 0).map_err(Into::into) + } + + pub fn write(&self, address: usize, length: usize) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::WRITE, address, length).map_err(Into::into) + } + + pub fn read(&self, address: usize, length: usize) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::READ, address, length).map_err(Into::into) + } + + pub fn write_read(&self, address: usize, length: usize) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::WRITE_READ, address, length) + .map_err(Into::into) + } +}
diff --git a/src/i2c_master_slave.rs b/src/i2c_master_slave.rs new file mode 100644 index 0000000..0e3cce6 --- /dev/null +++ b/src/i2c_master_slave.rs
@@ -0,0 +1,264 @@ +use crate::callback::CallbackSubscription; +use crate::callback::Consumer; +use crate::result::TockResult; +use crate::syscalls; +use core::marker::PhantomData; +use core::ops::Deref; +use core::ops::DerefMut; +use libtock_core::shared_memory::SharedMemory; + +const DRIVER_NUMBER: usize = 0x20006; + +pub const MASTER_BUFFER_SIZE: usize = 32; +pub const SLAVE_BUFFER_SIZE: usize = 32; + +mod command_nr { + pub const CHECK_PRESENT: usize = 0; + pub const MASTER_WRITE: usize = 1; + pub const MASTER_READ: usize = 2; + pub const SLAVE_LISTEN: usize = 3; + pub const SLAVE_ENABLE_READ: usize = 4; + pub const SLAVE_STOP_LISTEN: usize = 5; + pub const SLAVE_SET_ADDRESS: usize = 6; + pub const MASTER_WRITE_READ: usize = 7; +} + +mod subscribe_nr { + pub const SUBSCRIBE_CALLBACK: usize = 0; +} + +mod allow_nr { + pub const MASTER_WRIE: usize = 0; + pub const MASTER_READ: usize = 1; + pub const SLAVE_READ: usize = 2; + pub const SLAVE_WRITE: usize = 2; +} + +#[non_exhaustive] +pub struct I2cMSDriverFactory; + +impl I2cMSDriverFactory { + pub fn init_driver(&mut self) -> TockResult<I2cMSDriver> { + let i2c_ms = I2cMSDriver { + lifetime: PhantomData, + }; + Ok(i2c_ms) + } +} + +struct I2cMSEventConsumer; + +impl<CB: FnMut(usize, usize)> Consumer<CB> for I2cMSEventConsumer { + fn consume(callback: &mut CB, _: usize, _: usize, _: usize) { + callback(0, 0); + } +} + +pub struct I2cMSMasterWriteBuffer { + buffer: [u8; MASTER_BUFFER_SIZE], +} + +impl Default for I2cMSMasterWriteBuffer { + fn default() -> Self { + I2cMSMasterWriteBuffer { + buffer: [0; MASTER_BUFFER_SIZE], + } + } +} + +impl Deref for I2cMSMasterWriteBuffer { + type Target = [u8; MASTER_BUFFER_SIZE]; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl DerefMut for I2cMSMasterWriteBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} + +pub struct I2cMSMasterReadBuffer { + buffer: [u8; MASTER_BUFFER_SIZE], +} + +impl Default for I2cMSMasterReadBuffer { + fn default() -> Self { + I2cMSMasterReadBuffer { + buffer: [0; MASTER_BUFFER_SIZE], + } + } +} + +impl Deref for I2cMSMasterReadBuffer { + type Target = [u8; MASTER_BUFFER_SIZE]; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl DerefMut for I2cMSMasterReadBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} + +pub struct I2cMSSlaveReadBuffer { + buffer: [u8; SLAVE_BUFFER_SIZE], +} + +impl Default for I2cMSSlaveReadBuffer { + fn default() -> Self { + I2cMSSlaveReadBuffer { + buffer: [0; SLAVE_BUFFER_SIZE], + } + } +} + +impl Deref for I2cMSSlaveReadBuffer { + type Target = [u8; SLAVE_BUFFER_SIZE]; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl DerefMut for I2cMSSlaveReadBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} + +pub struct I2cMSSlaveWriteBuffer { + buffer: [u8; SLAVE_BUFFER_SIZE], +} + +impl Default for I2cMSSlaveWriteBuffer { + fn default() -> Self { + I2cMSSlaveWriteBuffer { + buffer: [0; SLAVE_BUFFER_SIZE], + } + } +} + +impl Deref for I2cMSSlaveWriteBuffer { + type Target = [u8; SLAVE_BUFFER_SIZE]; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl DerefMut for I2cMSSlaveWriteBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} + +pub struct I2cMSDriver<'a> { + lifetime: PhantomData<&'a ()>, +} + +impl<'a> I2cMSDriver<'a> { + pub fn init_master_write_buffer( + &self, + buffer: &'a mut I2cMSMasterWriteBuffer, + ) -> TockResult<SharedMemory> { + syscalls::allow(DRIVER_NUMBER, allow_nr::MASTER_WRIE, &mut buffer.buffer) + .map_err(Into::into) + } + + pub fn init_master_read_buffer( + &self, + buffer: &'a mut I2cMSMasterReadBuffer, + ) -> TockResult<SharedMemory> { + syscalls::allow(DRIVER_NUMBER, allow_nr::MASTER_READ, &mut buffer.buffer) + .map_err(Into::into) + } + + pub fn init_slave_read_buffer( + &self, + buffer: &'a mut I2cMSSlaveReadBuffer, + ) -> TockResult<SharedMemory> { + syscalls::allow(DRIVER_NUMBER, allow_nr::SLAVE_READ, &mut buffer.buffer).map_err(Into::into) + } + + pub fn init_slave_write_buffer( + &self, + buffer: &'a mut I2cMSSlaveWriteBuffer, + ) -> TockResult<SharedMemory> { + syscalls::allow(DRIVER_NUMBER, allow_nr::SLAVE_WRITE, &mut buffer.buffer) + .map_err(Into::into) + } + + pub fn subscribe<CB: FnMut(usize, usize)>( + &self, + callback: &'a mut CB, + ) -> TockResult<CallbackSubscription> { + syscalls::subscribe::<I2cMSEventConsumer, _>( + DRIVER_NUMBER, + subscribe_nr::SUBSCRIBE_CALLBACK, + callback, + ) + .map_err(Into::into) + } + + pub fn check_present(&self) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::CHECK_PRESENT, 0, 0).map_err(Into::into) + } + + pub fn master_write(&self, address: usize, length: usize) -> TockResult<usize> { + syscalls::command( + DRIVER_NUMBER, + command_nr::MASTER_WRITE, + address & 0xFFFF | length << 16, + 0, + ) + .map_err(Into::into) + } + + pub fn master_read(&self, address: usize, length: usize) -> TockResult<usize> { + syscalls::command( + DRIVER_NUMBER, + command_nr::MASTER_READ, + address & 0xFFFF | length << 16, + 0, + ) + .map_err(Into::into) + } + + pub fn slave_listen(&self) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::SLAVE_LISTEN, 0, 0).map_err(Into::into) + } + + pub fn slave_enable_read(&self) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::SLAVE_ENABLE_READ, 0, 0).map_err(Into::into) + } + + pub fn slave_stop_listen(&self) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::SLAVE_STOP_LISTEN, 0, 0).map_err(Into::into) + } + + pub fn slave_set_address(&self, address: usize) -> TockResult<usize> { + syscalls::command(DRIVER_NUMBER, command_nr::SLAVE_SET_ADDRESS, address, 0) + .map_err(Into::into) + } + + pub fn master_write_read( + &self, + address: usize, + read_length: usize, + write_length: usize, + ) -> TockResult<usize> { + syscalls::command( + DRIVER_NUMBER, + command_nr::MASTER_WRITE_READ, + address & 0xFF | read_length << 8 | write_length << 16, + 0, + ) + .map_err(Into::into) + } +}
diff --git a/src/lib.rs b/src/lib.rs index 96d2720..6f5be5a 100644 --- a/src/lib.rs +++ b/src/lib.rs
@@ -13,6 +13,8 @@ pub mod futures; pub mod gpio; pub mod hmac; +pub mod i2c_master; +pub mod i2c_master_slave; pub mod leds; pub mod result; pub mod rng;