diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 30fd2f94..00000000 --- a/.cargo/config +++ /dev/null @@ -1,7 +0,0 @@ -[target.thumbv7em-none-eabi] -rustflags = [ - "-C", "link-arg=-Tlayout.ld", - "-C", "relocation-model=ropi-rwpi", - "-C", "linker=arm-none-eabi-ld", - "-Z", "linker-flavor=ld", -] diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..537b0c03 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "tock"] + path = tock + url = https://github.com/helena-project/tock +[submodule "xargo"] + path = xargo + url = https://github.com/japaric/xargo diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..45620a30 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: rust +rust: + - nightly-2018-02-23 + +os: + - linux + +cache: + directories: + - $HOME/.cargo + - xargo/target + - tock/userland/tools/elf2tbf/target + +install: + - wget -c https://developer.arm.com/-/media/Files/downloads/gnu-rm/6-2016q4/gcc-arm-none-eabi-6_2-2016q4-20161216-linux.tar.bz2 + - tar -xjf gcc-arm-none-eabi-6_2-2016q4-20161216-linux.tar.bz2 + - rustup component add rust-src + +script: + - export PATH="$PATH:gcc-arm-none-eabi-6_2-2016q4/bin" + - export RUSTFLAGS="$RUSTFLAGS -D warnings" + - cargo test --lib + - ./build_examples.sh diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8c31c0de --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "editor.formatOnSave": true, + "rust-client.channel": "nightly-2018-02-23", +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 0599a45e..70136178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,12 @@ authors = ["Tock Project Developers "] license = "MIT/Apache-2.0" [dependencies] -linked_list_allocator = "0.4.3" +linked_list_allocator = "0.5.0" + +[dev-dependencies] +corepack = { version="~0.3.1", default-features=false, features = ["alloc"] } +serde_derive = { version = "~1.0.10", default-features = false } +serde = { version = "~1.0.10", default-features = false } [profile.dev] panic = "abort" diff --git a/Cross.toml b/Cross.toml deleted file mode 100755 index 764630cd..00000000 --- a/Cross.toml +++ /dev/null @@ -1,12 +0,0 @@ -[target.thumbv7em-none-eabi] -toolchain = "nightly-2017-09-20" -image = "tockimage:latest" - -[target.thumbv7em-tock-eabi] -toolchain = "nightly-2017-09-20" -image = "tockimage:latest" - -[build.env] -passthrough = [ - "TOCK_KERNEL_VERSION" -] diff --git a/README.md b/README.md index 984ba66e..b57a6eca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Build Status](https://travis-ci.org/torfmaster/libtock-rs.svg?branch=master)](https://travis-ci.org/torfmaster/libtock-rs) # libtock-rs Rust userland library for Tock (WIP) @@ -5,23 +6,13 @@ Rust userland library for Tock (WIP) This project is nascent and still under heavy development, but first steps: -1. First, update rustup to ensure you have the latest toolchain available: +1. Ensure you have a working toolchain available: - `rustup update` - - or - - `rustup install nightly` + `rustup install nightly-2017-12-16` -2. Get a copy of the latest nightly, in this repo's root: +2. Get a copy of this toolchain, in this repo's root: - `rustup override set nightly` - - Your rustc should be at least this new: - ``` - $ rustc --version - rustc 1.21.0-nightly (7ac979d8c 2017-08-16) - ``` + `rustup override set nightly-2017-12-16` 3. Need to grab a copy of the rust sources: diff --git a/build_examples.sh b/build_examples.sh new file mode 100755 index 00000000..385a2f0f --- /dev/null +++ b/build_examples.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -eux +export RUST_TARGET_PATH=`pwd` +export CARGO_INCREMENTAL=0 + +cargo run --manifest-path xargo/Cargo.toml -- build --release --target=thumbv7em-tock-eabi --examples diff --git a/examples/ble_ess.rs b/examples/ble_ess.rs index 7f57ea6c..d9d842a0 100644 --- a/examples/ble_ess.rs +++ b/examples/ble_ess.rs @@ -8,6 +8,8 @@ use alloc::fmt::Write; use tock::console::Console; use tock::ipc::ble_ess::{self, ReadingType}; use tock::sensors::*; +use tock::timer; +use tock::timer::Duration; fn main() { let mut console = Console::new(); @@ -17,7 +19,7 @@ fn main() { Ok(ess) => ess, _ => { write!(&mut console, "BLE IPC Service not installed\n").unwrap(); - return + return; } }; write!(&mut console, "Found BLE IPC Service\n").unwrap(); @@ -26,14 +28,13 @@ fn main() { let mut temperature = TemperatureSensor; let mut light = AmbientLightSensor; loop { - // Temperature let temp = temperature.read(); write!(&mut console, "Temperature: {}\n", temp).unwrap(); if let Err(_) = ess.set_reading(ReadingType::Temperature, temp) { write!(&mut console, "Failed to set temperature\n").unwrap_or(()); } - + // Light let lx = light.read(); write!(&mut console, "Light: {}\n", lx).unwrap(); @@ -48,7 +49,6 @@ fn main() { write!(&mut console, "Failed to set temperature\n").unwrap_or(()); } - tock::timer::delay_ms(5000); + timer::sleep(Duration::from_ms(5000)) } } - diff --git a/examples/ble_scanning.rs b/examples/ble_scanning.rs new file mode 100644 index 00000000..db0c4f10 --- /dev/null +++ b/examples/ble_scanning.rs @@ -0,0 +1,49 @@ +#![no_std] +#![feature(alloc)] + +extern crate alloc; +extern crate corepack; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate tock; + +// Macro usages are not detected +#[allow(unused_imports)] +use alloc::*; +use tock::ble_parser; +use tock::led; +use tock::simple_ble::BleDriver; +use tock::syscalls; + +#[derive(Deserialize)] +struct LedCommand { + pub nr: u8, + pub st: bool, +} + +fn main() { + let buffer = [0; tock::simple_ble::BUFFER_SIZE_SCAN]; + BleDriver::start(&buffer, |_: usize, _: usize| { + match ble_parser::find(&buffer, tock::simple_ble::gap_data::SERVICE_DATA as u8) { + Some(payload) => { + let payload: Vec = payload.iter().map(|&x| *x).collect::>(); + let msg: LedCommand = corepack::from_bytes(payload.as_slice()).unwrap(); + let msg_led = led::get(msg.nr as isize); + match msg_led { + Some(msg_led) => if msg.st { + msg_led.on(); + } else { + msg_led.off(); + }, + _ => (), + } + } + None => (), + } + }).unwrap(); + + loop { + syscalls::yieldk(); + } +} diff --git a/examples/blink.rs b/examples/blink.rs index f72b1884..683e5797 100644 --- a/examples/blink.rs +++ b/examples/blink.rs @@ -2,14 +2,15 @@ extern crate tock; -use tock::{led, timer}; +use tock::led; +use tock::timer; +use tock::timer::Duration; fn main() { - let led_count = led::count(); + let led = led::get(0).unwrap(); + loop { - for i in 0..led_count { - led::toggle(i as u32); - timer::delay_ms(500); - } + led.toggle(); + timer::sleep(Duration::from_ms(500)); } } diff --git a/examples/blink_async.rs b/examples/blink_async.rs deleted file mode 100644 index fb33c036..00000000 --- a/examples/blink_async.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_std] - -extern crate tock; - -use tock::{led, timer}; - -extern fn timer_event(_: usize, _: usize, _: usize, _: usize) { - led::toggle(0); -} - -fn main() { - unsafe { - timer::subscribe(timer_event, 0); - } - timer::start_repeating(500); -} diff --git a/examples/button_read.rs b/examples/button_read.rs new file mode 100644 index 00000000..759d44bb --- /dev/null +++ b/examples/button_read.rs @@ -0,0 +1,27 @@ +#![feature(alloc)] +#![no_std] + +extern crate alloc; +extern crate tock; + +use alloc::string::String; +use tock::buttons; +use tock::buttons::ButtonState; +use tock::console::Console; +use tock::timer; +use tock::timer::Duration; + +fn main() { + let mut console = Console::new(); + let mut buttons = buttons::with_callback(|_, _| {}).unwrap(); + let mut button = buttons.iter_mut().next().unwrap(); + let button = button.enable().unwrap(); + + loop { + match button.read() { + ButtonState::Pressed => console.write(String::from("pressed\n")), + ButtonState::Released => console.write(String::from("released\n")), + } + timer::sleep(Duration::from_ms(500)); + } +} diff --git a/examples/button_subscribe.rs b/examples/button_subscribe.rs new file mode 100644 index 00000000..615adeea --- /dev/null +++ b/examples/button_subscribe.rs @@ -0,0 +1,36 @@ +#![feature(alloc)] +#![no_std] + +extern crate alloc; +extern crate tock; + +use alloc::string::String; +use tock::buttons; +use tock::buttons::ButtonState; +use tock::console::Console; +use tock::fmt; +use tock::timer; +use tock::timer::Duration; + +// FIXME: Hangs up when buttons are pressed rapidly - problem in console? +fn main() { + let mut console = Console::new(); + + let mut buttons = buttons::with_callback(|button_num: usize, state| { + console.write(String::from("\nButton: ")); + console.write(fmt::u32_as_hex(button_num as u32)); + console.write(String::from(" - State: ")); + console.write(String::from(match state { + ButtonState::Pressed => "pressed", + ButtonState::Released => "released", + })); + }).unwrap(); + + for mut button in &mut buttons { + button.enable().unwrap(); + } + + loop { + timer::sleep(Duration::from_ms(500)); + } +} diff --git a/examples/gpio.rs b/examples/gpio.rs new file mode 100644 index 00000000..e9933f55 --- /dev/null +++ b/examples/gpio.rs @@ -0,0 +1,20 @@ +#![no_std] + +extern crate tock; + +use tock::gpio::GpioPinUnitialized; +use tock::timer; +use tock::timer::Duration; + +// Example works on P0.03 +fn main() { + let pin = GpioPinUnitialized::new(0); + let pin = pin.open_for_write().unwrap(); + + loop { + pin.set_high(); + timer::sleep(Duration::from_ms(500)); + pin.set_low();; + timer::sleep(Duration::from_ms(500)); + } +} diff --git a/examples/gpio_read.rs b/examples/gpio_read.rs new file mode 100644 index 00000000..997fcde1 --- /dev/null +++ b/examples/gpio_read.rs @@ -0,0 +1,27 @@ +#![feature(alloc)] +#![no_std] + +extern crate alloc; +extern crate tock; + +use alloc::string::String; +use tock::console::Console; +use tock::gpio::{GpioPinUnitialized, InputMode}; +use tock::timer; +use tock::timer::Duration; + +// example works on p0.03 +fn main() { + let mut console = Console::new(); + let pin = GpioPinUnitialized::new(0); + let pin = pin.open_for_read(None, InputMode::PullDown).unwrap(); + + loop { + if pin.read() { + console.write(String::from("true\n")); + } else { + console.write(String::from("false\n")); + } + timer::sleep(Duration::from_ms(500)); + } +} diff --git a/examples/hello.rs b/examples/hello.rs index 6cf86956..fb0c34dd 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,17 +1,22 @@ -#![feature(asm,alloc)] +#![feature(alloc)] #![no_std] extern crate alloc; extern crate tock; -use alloc::fmt::Write; +use alloc::string::String; use tock::console::Console; +use tock::timer; +use tock::timer::Duration; +// TODO: Make format!/alloc::string::ToString work fn main() { let mut console = Console::new(); + for i in 0.. { - write!(&mut console, "Hello world! {}\n", i).unwrap(); - tock::timer::delay_ms(500); + console.write(String::from("Hello world! ")); + console.write(tock::fmt::u32_as_decimal(i)); + console.write(String::from("\n")); + timer::sleep(Duration::from_ms(500)) } } - diff --git a/examples/ipcclient.rs b/examples/ipcclient.rs new file mode 100644 index 00000000..40fed282 --- /dev/null +++ b/examples/ipcclient.rs @@ -0,0 +1,33 @@ +#![feature(alloc)] +#![no_std] +extern crate alloc; +extern crate tock; + +use alloc::boxed::Box; +use alloc::string::String; +use tock::console::Console; +use tock::fmt; +use tock::ipc_cs::*; +use tock::timer; + +#[allow(unreachable_code)] +// Calls the ipc_server and prints result +fn main() { + let mut buf: Box<[u8]> = reserve_shared_buffer(); + timer::sleep(timer::Duration::from_ms(1000)); + + loop { + let mut server = ServerHandle::discover_service(String::from("ipcserver")).unwrap(); + let mut payload: [u8; 32] = [5; 32]; + + server.share(&mut buf, &mut payload); + server.subscribe_callback(|_: usize, _: usize| { + let mut console = Console::new(); + console.write(String::from("Client: \"Payload: ")); + console.write(fmt::u32_as_hex(buf[0] as u32)); + console.write(String::from("\"\n")); + }); + server.notify(); + timer::sleep(timer::Duration::from_ms(1000)); + } +} diff --git a/examples/ipcserver.rs b/examples/ipcserver.rs new file mode 100644 index 00000000..e6182e27 --- /dev/null +++ b/examples/ipcserver.rs @@ -0,0 +1,33 @@ +#![feature(alloc)] +#![no_std] +extern crate alloc; +extern crate tock; + +use alloc::string::String; +use tock::console::Console; +use tock::fmt::*; +use tock::ipc_cs; +use tock::ipc_cs::IpcServerDriver; + +#[allow(unreachable_code)] +// Prints the payload and adds one to the first byte. +fn main() { + let mut console = Console::new(); + console.write(String::from("Start service:\n")); + + let cb = &mut |pid: usize, _: usize, message: &mut [u8; 32]| { + console.write(String::from("Server: \"Payload: ")); + + console.write(u32_as_hex(message[0] as u32)); + console.write(String::from("\"\n")); + message[0] += 1; + ipc_cs::notify_client(pid); + }; + #[allow(unused_variables)] + let server = IpcServerDriver::start(cb); + + loop { + tock::syscalls::yieldk(); + } + server.unwrap(); +} diff --git a/examples/sensors.rs b/examples/sensors.rs index cb951f20..71ceb846 100644 --- a/examples/sensors.rs +++ b/examples/sensors.rs @@ -7,6 +7,8 @@ extern crate tock; use alloc::fmt::Write; use tock::console::Console; use tock::sensors::*; +use tock::timer; +use tock::timer::Duration; fn main() { let mut console = Console::new(); @@ -18,8 +20,11 @@ fn main() { write!(&mut console, "Humidity: {}\n", humidity.read()).unwrap(); write!(&mut console, "Temperature: {}\n", temperature.read()).unwrap(); write!(&mut console, "Light: {}\n", light.read()).unwrap(); - write!(&mut console, "Accel: {}\n", ninedof.read_acceleration()).unwrap(); - tock::timer::delay_ms(500); + write!( + &mut console, + "Accel: {}\n", + ninedof.read_acceleration() + ).unwrap(); + timer::sleep(Duration::from_ms(500)); } } - diff --git a/examples/seven_segment.rs b/examples/seven_segment.rs new file mode 100644 index 00000000..b41d00c3 --- /dev/null +++ b/examples/seven_segment.rs @@ -0,0 +1,40 @@ +#![no_std] + +extern crate tock; + +use tock::electronics::ShiftRegister; +use tock::gpio::GpioPinUnitialized; +use tock::timer; +use tock::timer::Duration; + +fn number_to_bits(n: u8) -> [bool; 8] { + match n { + 1 => [false, false, false, true, false, true, false, false], + 2 => [true, false, true, true, false, false, true, true], + 3 => [true, false, true, true, false, true, true, false], + 4 => [true, true, false, true, false, true, false, false], + 5 => [true, true, true, false, false, true, true, false], + 6 => [true, true, true, false, false, true, true, true], + 7 => [false, false, true, true, false, true, false, false], + 8 => [true, true, true, true, false, true, true, true], + 9 => [true, true, true, true, false, true, true, false], + 0 => [false, true, true, true, false, true, true, true], + _ => [false, false, false, false, true, false, false, false], + } +} + +// Example works on a shift register on P0.03, P0.04, P0.28 +fn main() { + let shift_register = ShiftRegister::new( + GpioPinUnitialized::new(0).open_for_write().unwrap(), + GpioPinUnitialized::new(1).open_for_write().unwrap(), + GpioPinUnitialized::new(2).open_for_write().unwrap(), + ); + + let mut i = 0; + loop { + i = (i + 1) % 11; + shift_register.write_bits(&number_to_bits(i)); + timer::sleep(Duration::from_ms(200)); + } +} diff --git a/examples/simple_ble.rs b/examples/simple_ble.rs new file mode 100644 index 00000000..6be41a03 --- /dev/null +++ b/examples/simple_ble.rs @@ -0,0 +1,42 @@ +#![no_std] +#![feature(alloc)] + +extern crate alloc; +extern crate corepack; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate tock; + +use alloc::String; +use tock::led; +use tock::simple_ble::BleDeviceUninitialized; +use tock::timer; +use tock::timer::Duration; + +#[derive(Serialize)] +struct LedCommand { + pub nr: u8, + pub st: bool, +} + +#[allow(unused_variables)] +fn main() { + let led = led::get(0).unwrap(); + + let name = String::from("Tock!"); + let uuid: [u16; 1] = [0x0018]; + + let mut payload = corepack::to_bytes(LedCommand { nr: 2, st: true }).unwrap(); + + let mut bleuninit = BleDeviceUninitialized::new(100, name, uuid.to_vec(), true, &mut payload); + let mut bleinit = bleuninit.initialize().unwrap(); + let ble = bleinit.start_advertising().unwrap(); + + loop { + led.on(); + timer::sleep(Duration::from_ms(500)); + led.off();; + timer::sleep(Duration::from_ms(500)); + } +} diff --git a/examples/temperature.rs b/examples/temperature.rs new file mode 100644 index 00000000..a8d5eaea --- /dev/null +++ b/examples/temperature.rs @@ -0,0 +1,25 @@ +#![feature(alloc)] +#![no_std] +extern crate alloc; +extern crate tock; + +use alloc::string::String; +use tock::console::Console; +use tock::temperature::TemperatureDriver; + +#[allow(unreachable_code)] +fn main() { + let mut console = Console::new(); + #[allow(unused_variables)] + let temperature = TemperatureDriver::start_measurement(|result: isize| { + console.write(String::from("Temperature:")); + console.write(tock::fmt::i32_as_decimal(result as i32)); + console.write(String::from("\n")); + }); + + loop { + tock::syscalls::yieldk(); + } + // FIXME: Find another solution to prevent the compiler from calling drop too early. + temperature.unwrap(); +} diff --git a/examples/timer_subscribe.rs b/examples/timer_subscribe.rs new file mode 100644 index 00000000..1589192c --- /dev/null +++ b/examples/timer_subscribe.rs @@ -0,0 +1,25 @@ +#![feature(alloc)] +#![no_std] + +extern crate alloc; +extern crate tock; + +use alloc::String; +use tock::console::Console; +use tock::syscalls; +use tock::timer; +use tock::timer::Duration; + +fn main() { + let mut console = Console::new(); + let timer = timer::with_callback(|_, _| { + console.write(String::from( + "This line is printed 2 seconds after the start of the program.", + )) + }).unwrap(); + + timer.set_alarm(Duration::from_ms(2000)).unwrap(); + loop { + syscalls::yieldk(); + } +} diff --git a/layout.ld b/layout.ld index ffc3bb11..82575be0 100644 --- a/layout.ld +++ b/layout.ld @@ -4,6 +4,10 @@ * is not known. Therefore, this script over provisions space on some platforms. */ +STACK_SIZE = 2048; +APP_HEAP_SIZE = 1024; +KERNEL_HEAP_SIZE = 1024; + /* Memory Spaces Definitions, 448K flash, 64K ram */ PROG_LENGTH = 0x00040000; RAM_LENGTH = 0x00010000; @@ -15,7 +19,7 @@ ENTRY(_start) * actual location in flash where the app is placed. */ MEMORY { - FLASH (rx) : ORIGIN = 0x00040030, LENGTH = PROG_LENGTH + FLASH (rx) : ORIGIN = 0x80000000, LENGTH = PROG_LENGTH SRAM (RWX) : ORIGIN = 0x00000000, LENGTH = RAM_LENGTH } @@ -24,43 +28,64 @@ SECTIONS { .text : { _text = .; - KEEP (*(.text._start)) + /** + * Populate the header expected by `crt0`: + * + * + * struct hdr { + * uint32_t got_sym_start; + * uint32_t got_start; + * uint32_t got_size; + * uint32_t data_sym_start; + * uint32_t data_start; + * uint32_t data_size; + * uint32_t bss_start; + * uint32_t bss_size; + * uint32_t reldata_start; + * }; + */ + /* Offset of GOT symbols in flash */ + LONG(LOADADDR(.got) - _text); + /* Offset of GOT section in memory */ + LONG(_got); + /* Size of GOT section */ + LONG(SIZEOF(.got)); + /* Offset of data symbols in flash */ + LONG(LOADADDR(.data) - _text); + /* Offset of data section in memory */ + LONG(_data); + /* Size of data section */ + LONG(SIZEOF(.data)); + /* Offset of BSS section in memory */ + LONG(_bss); + /* Size of BSS section */ + LONG(SIZEOF(.bss)); + /* First address offset after program flash, where elf2tbf places + * .rel.data section */ + LONG(LOADADDR(.endsec) - _text); + + KEEP (*(.start)) *(.text*) *(.rodata*) - - /* C++ exception unwinding information */ - *(.ARM.extab* .gnu.linkonce.armextab.*) + KEEP (*(.syscalls)) + _etext = .; + *(.ARM.extab*) + . = ALIGN(4); /* Make sure we're word-aligned here */ } > FLASH =0xFF -/* ARM Exception support - * - * This contains compiler-generated support for unwinding the stack, - * consisting of key-value pairs of function addresses and information on - * how to unwind stack frames. - * https://wiki.linaro.org/KenWerner/Sandbox/libunwind?action=AttachFile&do=get&target=libunwind-LDS.pdf - * - * .ARM.exidx is sorted, so has to go in its own output section. - */ - PROVIDE_HIDDEN (__exidx_start = .); - .ARM.exidx : - { - /* (C++) Index entries for section unwinding */ - *(.ARM.exidx* .gnu.linkonce.armexidx.*) - } > FLASH - PROVIDE_HIDDEN (__exidx_end = .); -/* Beginning of SRAM */ - _sram_start = .; +/* App state section. Used for persistent app data. */ + .app_state : + { + KEEP (*(.app_state)) + } > FLASH =0xFF /* Global Offset Table */ .got : { _got = .; *(.got*) - _egot = .; - _plt = .; *(.got.plt*) - _eplt = .; } > SRAM AT > FLASH /* Data section, static initialized variables @@ -71,7 +96,6 @@ SECTIONS { { _data = .; KEEP(*(.data*)) - _edata = .; } > SRAM AT > FLASH /* BSS section, static uninitialized variables */ @@ -80,39 +104,52 @@ SECTIONS { _bss = .; KEEP(*(.bss*)) *(COMMON) - _ebss = .; } > SRAM /* - * __NOTE__: The following symbols are used only to pass information - * through the elf -> tbf -> Tock kernel. - * - * The kernel will place the stack at the beginning of the SRAM section so - * that stack overflows run off the end of the memory segment and trigger an - * MPU violation instead of overwriting data/got/bss information. This means - * the actual location of symbols in those sections in memory will be offset - * by STACK_SIZE. + * __NOTE__: The following symbols are used only to hint to elf2tbf how much + * total memory to request from the OS. */ .stack : { - _stack = .; - . += 1024; - _estack = .; + . += STACK_SIZE; } > SRAM .app_heap : { - _app_heap = .; - . += 1024; - _eapp_heap = .; + . += APP_HEAP_SIZE; } > SRAM .kernel_heap : { - _kernel_heap = .; - . += 1024; - _ekernel_heap = .; + . += KERNEL_HEAP_SIZE; } > SRAM _sram_end = .; + .endsec : + { + } > FLASH + +/* ARM Exception support + * + * This contains compiler-generated support for unwinding the stack, + * consisting of key-value pairs of function addresses and information on + * how to unwind stack frames. + * https://wiki.linaro.org/KenWerner/Sandbox/libunwind?action=AttachFile&do=get&target=libunwind-LDS.pdf + * + * .ARM.exidx is sorted, so has to go in its own output section. + * + * __NOTE__: It's at the end because we currently don't actually serialize + * it to the binary in elf2tbf. If it was before the RAM sections, it would + * through off our calculations of the header. + */ + PROVIDE_HIDDEN (__exidx_start = .); + .ARM.exidx : + { + /* (C++) Index entries for section unwinding */ + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + PROVIDE_HIDDEN (__exidx_end = .); + + } diff --git a/run_example.sh b/run_example.sh new file mode 100755 index 00000000..3901858a --- /dev/null +++ b/run_example.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Examples only run on a NRF52DK board + +set -eux + +export RUST_TARGET_PATH=`pwd` +export CARGO_INCREMENTAL=0 + +tab_file_name=metadata.toml +elf_file_name=cortex-m4.elf +bin_file_name=cortex-m4.bin + +cargo run --manifest-path xargo/Cargo.toml -- build --release --target=thumbv7em-tock-eabi --example "$1" +cp target/thumbv7em-tock-eabi/release/examples/"$1" "target/$elf_file_name" +cargo run --manifest-path tock/userland/tools/elf2tbf/Cargo.toml -- -n "$1" -o "target/$bin_file_name" "target/$elf_file_name" + +echo "tab-version = 1" > "target/$tab_file_name" +echo "name = \"$1\"" >> "target/$tab_file_name" +echo "only-for-boards = \"\"" >> "target/$tab_file_name" +echo "build-date = $(date "+%Y-%m-%dT%H:%M:%SZ")" >> "target/$tab_file_name" + +out_file_name="$1".tab +tar -C target -cf "target/$out_file_name" "$bin_file_name" "$tab_file_name" + +if [ "$#" -ge "2" ] +then + if [ "$2" = "--dont-clear-apps" ] + then + echo "do not delete apps from board." + else + tockloader uninstall --jtag --arch cortex-m4 --board nrf52-dk --jtag-device nrf52 --app-address 0x20000 || true + fi +else + tockloader uninstall --jtag --arch cortex-m4 --board nrf52-dk --jtag-device nrf52 --app-address 0x20000 || true +fi +tockloader install --jtag --arch cortex-m4 --board nrf52-dk --jtag-device nrf52 --app-address 0x20000 "target/$out_file_name" diff --git a/run_ipc_example.sh b/run_ipc_example.sh new file mode 100755 index 00000000..7bef4d81 --- /dev/null +++ b/run_ipc_example.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# Examples only run on a NRF52DK board + +set -eux + +yes 0|tockloader uninstall --jtag --arch cortex-m4 --board nrf52-dk --jtag-device nrf52 --app-address 0x20000 || true + +./run_example.sh ipcclient --dont-clear-apps +./run_example.sh ipcserver --dont-clear-apps diff --git a/rust-toolchain b/rust-toolchain index f8d784e0..9c049752 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2017-08-16 +nightly-2018-02-23 \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..3dc0db27 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +reorder_imports = true +reorder_imported_names = true +use_try_shorthand = true diff --git a/src/ble_parser.rs b/src/ble_parser.rs new file mode 100644 index 00000000..cb8869a7 --- /dev/null +++ b/src/ble_parser.rs @@ -0,0 +1,57 @@ +use alloc::*; +use simple_ble::BUFFER_SIZE_SCAN; + +pub fn find(buffer: &[u8; BUFFER_SIZE_SCAN], kind: u8) -> Option> { + let mut iter = buffer[8..BUFFER_SIZE_SCAN].iter(); + + loop { + match iter.next() { + Some(&len) => { + let data_type = iter.next(); + if data_type == Some(&kind) { + return Some(iter.take(len as usize - 1).collect::>()); + } else { + if len > 0 { + for _ in 0..len - 1 { + iter.next(); + } + } else { + return None; + } + } + } + None => return None, + } + } +} +#[cfg(test)] +mod test { + use ble_parser::*; + + #[test] + pub fn extracts_data_for_ids_correctly() { + let mut buf = [0; BUFFER_SIZE_SCAN]; + { + let slice = &mut buf[8..23]; + let data = &[ + 0x02, 0x02, 0x01, 0x02, 0x01, 0x03, 0x03, 0x16, 0x01, 0x02, 0x04, 0xFF, 0x01, 0x02, + 0x03, + ]; + slice.clone_from_slice(data); + } + assert_eq!(find(&buf, 0x02), Some(vec![&0x01])); + assert_eq!(find(&buf, 0x01), Some(vec![&0x03])); + assert_eq!(find(&buf, 0x16), Some(vec![&0x01, &0x02])); + assert_eq!(find(&buf, 0xFF), Some(vec![&0x01, &0x02, &0x03])); + } + + #[test] + pub fn doesnt_panic_for_defect_packets() { + let mut buf = [0; BUFFER_SIZE_SCAN]; + { + let slice = &mut buf[8..18]; + let data = &[0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x0, 0x16, 0x01, 0x02]; + slice.clone_from_slice(data); + } + } +} diff --git a/src/buttons.rs b/src/buttons.rs new file mode 100644 index 00000000..218e9fee --- /dev/null +++ b/src/buttons.rs @@ -0,0 +1,192 @@ +use callback::CallbackSubscription; +use callback::SubscribableCallback; +use result; +use result::TockResult; +use result::TockValue; +use syscalls; +use util::PhantomLifetime; + +const DRIVER_NUMBER: usize = 0x00003; + +mod command_nr { + pub const COUNT: usize = 0; + pub const ENABLE_INTERRUPT: usize = 1; + pub const DISABLE_INTERRUPT: usize = 2; + pub const READ: usize = 3; +} + +mod subscribe_nr { + pub const SUBSCRIBE_CALLBACK: usize = 0; +} + +pub fn with_callback( + callback: CB, +) -> TockResult, ButtonsError> { + let count = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::COUNT, 0, 0) }; + + if count < 1 { + return Err(TockValue::Expected(ButtonsError::NotSupported)); + } + + let (return_code, subscription) = syscalls::subscribe_new(ButtonsCallback { callback }); + + match return_code { + result::SUCCESS => Ok(Buttons { + count: count as usize, + subscription, + }), + result::ENOMEM => Err(TockValue::Expected(ButtonsError::SubscriptionFailed)), + unexpected => Err(TockValue::Unexpected(unexpected)), + } +} + +pub struct Buttons { + count: usize, + #[allow(dead_code)] // Used in drop + subscription: CallbackSubscription>, +} + +#[derive(Copy, Clone, Debug)] +pub enum ButtonsError { + NotSupported, + SubscriptionFailed, +} + +impl Buttons { + pub fn iter_mut(&mut self) -> ButtonIter { + ButtonIter { + curr_button: 0, + button_count: self.count, + lifetime: Default::default(), + } + } +} + +struct ButtonsCallback { + callback: CB, +} + +impl SubscribableCallback for ButtonsCallback { + fn driver_number(&self) -> usize { + DRIVER_NUMBER + } + + fn subscribe_number(&self) -> usize { + subscribe_nr::SUBSCRIBE_CALLBACK + } + + fn call_rust(&mut self, button_num: usize, state: usize, _: usize) { + (self.callback)(button_num, state.into()); + } +} + +#[derive(Copy, Clone, Debug)] +pub enum ButtonState { + Pressed, + Released, +} + +impl From for ButtonState { + fn from(state: usize) -> ButtonState { + match state { + 0 => ButtonState::Released, + 1 => ButtonState::Pressed, + _ => unreachable!(), + } + } +} + +impl<'a, CB: FnMut(usize, ButtonState)> IntoIterator for &'a mut Buttons { + type Item = ButtonHandle<'a>; + type IntoIter = ButtonIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +pub struct ButtonIter<'a> { + curr_button: usize, + button_count: usize, + lifetime: PhantomLifetime<'a>, +} + +impl<'a> Iterator for ButtonIter<'a> { + type Item = ButtonHandle<'a>; + + fn next(&mut self) -> Option { + if self.curr_button < self.button_count { + let item = ButtonHandle { + button_num: self.curr_button, + lifetime: Default::default(), + }; + self.curr_button += 1; + Some(item) + } else { + None + } + } +} + +pub struct ButtonHandle<'a> { + button_num: usize, + lifetime: PhantomLifetime<'a>, +} + +impl<'a> ButtonHandle<'a> { + pub fn enable(&mut self) -> TockResult { + let return_code = unsafe { + syscalls::command( + DRIVER_NUMBER, + command_nr::ENABLE_INTERRUPT, + self.button_num, + 0, + ) + }; + + match return_code { + result::SUCCESS => Ok(Button { handle: self }), + result::ENOMEM => Err(TockValue::Expected(ButtonError::ActivationFailed)), + unexpected => Err(TockValue::Unexpected(unexpected)), + } + } + + pub fn disable(&mut self) -> TockResult<(), ButtonError> { + let return_code = unsafe { + syscalls::command( + DRIVER_NUMBER, + command_nr::DISABLE_INTERRUPT, + self.button_num, + 0, + ) + }; + + match return_code { + result::SUCCESS => Ok(()), + result::ENOMEM => Err(TockValue::Expected(ButtonError::ActivationFailed)), + unexpected => Err(TockValue::Unexpected(unexpected)), + } + } +} + +pub struct Button<'a> { + handle: &'a ButtonHandle<'a>, +} + +#[derive(Copy, Clone, Debug)] +pub enum ButtonError { + ActivationFailed, +} + +impl<'a> Button<'a> { + pub fn read(&self) -> ButtonState { + unsafe { + ButtonState::from(syscalls::command( + DRIVER_NUMBER, + command_nr::READ, + self.handle.button_num, + 0, + ) as usize) + } + } +} diff --git a/src/callback.rs b/src/callback.rs new file mode 100644 index 00000000..36f00f16 --- /dev/null +++ b/src/callback.rs @@ -0,0 +1,12 @@ +pub trait SubscribableCallback { + fn driver_number(&self) -> usize; + + fn subscribe_number(&self) -> usize; + + fn call_rust(&mut self, arg0: usize, arg1: usize, arg2: usize); +} + +pub struct CallbackSubscription { + #[allow(dead_code)] // Used in drop + pub(crate) callback: CB, +} diff --git a/src/console.rs b/src/console.rs index 78193161..e30f317a 100644 --- a/src/console.rs +++ b/src/console.rs @@ -1,11 +1,10 @@ +use alloc::String; +use core::{fmt, mem}; use core::cell::Cell; -use core::{fmt,mem}; use core::result::Result; use syscalls::{self, allow, yieldk_for}; -use alloc::String; - -const DRIVER_NUM: u32 = 0; +const DRIVER_NUM: usize = 1; pub struct Console; @@ -14,6 +13,8 @@ impl Console { Console } + // TODO: Accept borrowed strings (e.g. &str). + // For this, the borrowed referencne must be accessible by the capsule. This is not the case for string literals. pub fn write(&mut self, string: String) { if string.len() <= 0 { return; @@ -23,15 +24,13 @@ impl Console { if putstr_async(&string, Self::cb, &done as *const _ as usize) >= 0 { yieldk_for(|| done.get()) } else { - return + return; } } } - extern fn cb(_: usize, _: usize, _: usize, ptr: usize) { - let done: &Cell = unsafe { - mem::transmute(ptr) - }; + extern "C" fn cb(_: usize, _: usize, _: usize, ptr: usize) { + let done: &Cell = unsafe { mem::transmute(ptr) }; done.set(true); } } @@ -43,21 +42,25 @@ impl fmt::Write for Console { } } -unsafe fn putstr_async(string: &String, cb: extern fn (usize, usize, usize, usize), ud: usize) -> isize { - let mut ret = allow(DRIVER_NUM, 1, string.as_bytes()); - if ret < 0 { - return ret; - } - - ret = syscalls::subscribe(DRIVER_NUM, 1, cb, ud); - if ret < 0 { - return ret; - } - - ret = syscalls::command(DRIVER_NUM, 1, string.len() as isize); - if ret < 0 { - return ret; - } - ret -} +// TODO: Should this function be unsafe on its own? +unsafe fn putstr_async( + string: &String, + cb: extern "C" fn(usize, usize, usize, usize), + ud: usize, +) -> isize { + let mut ret = allow(DRIVER_NUM, 1, string.as_bytes()); + if ret < 0 { + return ret; + } + ret = syscalls::subscribe(DRIVER_NUM, 1, cb, ud); + if ret < 0 { + return ret; + } + + ret = syscalls::command(DRIVER_NUM, 1, string.len(), 0); + if ret < 0 { + return ret; + } + ret +} diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 00000000..d992d296 --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,37 @@ +//! Tempoary formatting functions until format! is fixed +use syscalls::{allow, command}; + +pub fn output_number(value: u32) { + let mut out: [u8; 11] = [32; 11]; + write_u32_into_array(&mut out, value as u32, 0x10_00_00_00, 0x10); + + unsafe { + allow(1, 1, &out); + command(1, 1, 10, 0); + } +} +pub fn write_u32_into_array(result: &mut [u8; 11], value: u32, start: u32, base: u32) { + let mut scanning = start; + let mut remainder = value; + let mut counter = 0; + result[0] = '0' as u8; + result[1] = 'x' as u8; + result[10] = '\n' as u8; + + while scanning > 0 { + let digit = remainder / scanning; + result[counter + 2] = render_digit(digit as u8) as u8; + + remainder = remainder % scanning; + scanning = scanning / base; + counter += 1; + } +} + +fn render_digit(digit: u8) -> char { + if digit < 10 { + ('0' as u8 + digit) as char + } else { + ('a' as u8 + digit - 10) as char + } +} diff --git a/src/electronics/mod.rs b/src/electronics/mod.rs new file mode 100644 index 00000000..44e7cd9e --- /dev/null +++ b/src/electronics/mod.rs @@ -0,0 +1,3 @@ +pub mod shift_register; + +pub use self::shift_register::ShiftRegister; diff --git a/src/electronics/shift_register.rs b/src/electronics/shift_register.rs new file mode 100644 index 00000000..b6209e56 --- /dev/null +++ b/src/electronics/shift_register.rs @@ -0,0 +1,43 @@ +use gpio::GpioPinWrite; + +pub struct ShiftRegister { + data_pin: GpioPinWrite, + clock_pin: GpioPinWrite, + latch_pin: GpioPinWrite, +} + +impl ShiftRegister { + pub fn new( + data_pin: GpioPinWrite, + clock_pin: GpioPinWrite, + latch_pin: GpioPinWrite, + ) -> ShiftRegister { + ShiftRegister { + data_pin, + clock_pin, + latch_pin, + } + } + + pub fn write_bits(&self, values: &[bool]) { + for i in values { + self.push_bit(*i); + } + self.display(); + } + + fn push_bit(&self, value: bool) { + if value { + self.data_pin.set_high(); + } else { + self.data_pin.set_low(); + } + self.clock_pin.set_high(); + self.clock_pin.set_low(); + } + + fn display(&self) { + self.latch_pin.set_high(); + self.latch_pin.set_low(); + } +} diff --git a/src/entry_point.rs b/src/entry_point.rs new file mode 100644 index 00000000..a0c984de --- /dev/null +++ b/src/entry_point.rs @@ -0,0 +1,94 @@ +extern crate linked_list_allocator; + +use self::linked_list_allocator::Heap; +use alloc::allocator::Alloc; +use alloc::allocator::AllocErr; +use alloc::allocator::Layout; +use alloc::string::String; +use console::Console; +use core::mem; +use core::ptr; +use fmt; +use syscalls; + +const HEAP_SIZE: usize = 0x200; +const STACK_SIZE: usize = 0x400; + +// None-threaded heap wrapper based on `r9` register instead of global variable +pub(crate) struct StackOwnedHeap; + +impl StackOwnedHeap { + unsafe fn heap(&self) -> &mut Heap { + let heap: *mut Heap; + asm!("mov $0, r9" : "=r"(heap) : : : "volatile"); + &mut *heap + } + + /// Initializes an empty heap + /// + /// # Unsafety + /// + /// This function must be called at most once. The heap_buffer must remain valid until the end of the process. + #[inline(never)] + unsafe fn init(&mut self, heap_buffer: &mut [u8]) { + let heap_location = heap_buffer.as_ptr() as usize; + asm!("mov r9, $0" : : "r"(heap_location) : : "volatile"); + let heap = heap_location as *mut Heap; + + let heap_bottom = heap_location + mem::size_of::(); + let heap_top = heap_location + heap_buffer.len(); + *heap = Heap::new(heap_bottom, heap_top - heap_bottom); + } +} + +unsafe impl<'a> Alloc for &'a StackOwnedHeap { + unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> { + self.heap().alloc(layout) + } + + unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) { + self.heap().dealloc(ptr, layout) + } +} + +/// Tock programs' entry point +#[doc(hidden)] +#[no_mangle] +#[naked] +#[link_section = ".start"] +pub extern "C" fn _start( + _text_start: usize, + mem_start: usize, + _memory_len: usize, + _app_heap_break: usize, +) -> ! { + unsafe { + asm!("mov sp, $0" : : "r"(mem_start + HEAP_SIZE + STACK_SIZE) : "memory" : "volatile" ); + run_with_new_stack() + } +} + +#[naked] +#[inline(never)] +unsafe fn run_with_new_stack() -> ! { + extern "C" { + // This function is created internally by`rustc`. See `src/lang_items.rs` for more details. + fn main(argc: isize, argv: *const *const u8) -> isize; + } + let mut heap: [u8; HEAP_SIZE as usize] = [0; HEAP_SIZE as usize]; + + StackOwnedHeap.init(&mut heap); + + let mut console = Console::new(); + console.write(String::from( + "\nProcess started\n===============\nHeap position: ", + )); + console.write(fmt::u32_as_hex(heap.as_ptr() as u32)); + console.write(String::from("\n\n")); + + main(0, ptr::null()); + + loop { + syscalls::yieldk(); + } +} diff --git a/src/fmt.rs b/src/fmt.rs new file mode 100644 index 00000000..680ec483 --- /dev/null +++ b/src/fmt.rs @@ -0,0 +1,84 @@ +//! Tempoary formatting functions until format! is fixed + +use alloc::string::String; + +pub fn u32_as_decimal(value: u32) -> String { + let mut result = String::new(); + write_u32_as_any_base(&mut result, value, 1_000_000_000, 10); + result +} + +pub fn i32_as_decimal(value: i32) -> String { + let mut result = String::new(); + let mut value = value; + if value < 0 { + result.push_str("-"); + value = -value; + } + write_u32_as_any_base(&mut result, value as u32, 1_000_000_000, 10); + result +} + +pub fn u32_as_hex(value: u32) -> String { + let mut result = String::new(); + result.push_str("0x"); + write_u32_as_any_base(&mut result, value, 0x1000_0000, 0x10); + result +} + +fn write_u32_as_any_base(result: &mut String, value: u32, start: u32, base: u32) { + let mut scanning = start; + let mut remaining = value; + while scanning > 0 { + let digit = remaining / scanning; + result.push(render_digit(digit as u8)); + + remaining = remaining % scanning; + scanning = scanning / base; + } +} + +fn render_digit(digit: u8) -> char { + if digit < 10 { + ('0' as u8 + digit) as char + } else { + ('a' as u8 + digit - 10) as char + } +} + +pub fn convert_le(number: u16) -> [u8; 2] { + let mut array: [u8; 2] = [0; 2]; + array[0] = number as u8; + array[1] = (number >> 8) as u8; + array +} + +#[cfg(test)] +mod test { + use fmt::*; + + #[test] + pub fn digits_are_correctly_rendered_in_decimal() { + assert_eq!(u32_as_decimal(123), String::from("0000000123")); + assert_eq!(u32_as_decimal(2000000123), String::from("2000000123")); + } + + #[test] + pub fn negative_numbers_are_rendered() { + assert_eq!(i32_as_decimal(-123), String::from("-0000000123")); + } + + #[test] + pub fn digits_are_correctly_rendered_in_hex() { + assert_eq!(u32_as_hex(0x1000_0000), String::from("0x10000000")); + assert_eq!(u32_as_hex(0x1000_3000), String::from("0x10003000")); + assert_eq!(u32_as_hex(0x0000_0000), String::from("0x00000000")); + } + + #[test] + pub fn number_are_rendered_correctly() { + assert_eq!(convert_le(0x00FF), [255, 0]); + assert_eq!(convert_le(0x0100), [0, 1]); + assert_eq!(convert_le(0x01FF), [255, 1]); + } +} diff --git a/src/gpio.rs b/src/gpio.rs new file mode 100644 index 00000000..f1b513fc --- /dev/null +++ b/src/gpio.rs @@ -0,0 +1,186 @@ +use syscalls::{command, subscribe}; + +const DRIVER_NUMBER: usize = 0x00004; +mod gpio_commands { + pub const SUBSCRIBE_CALLBACK: usize = 0; + pub const ENABLE_OUTPUT: usize = 1; + pub const SET_HIGH: usize = 2; + pub const SET_LOW: usize = 3; + pub const TOGGLE: usize = 4; + pub const ENABLE_INPUT: usize = 5; + pub const READ: usize = 6; + pub const ENABLE_INTERRUPT: usize = 7; + pub const DISABLE_INTERRUPT: usize = 8; + pub const DISABLE: usize = 9; +} + +pub enum InputMode { + PullUp, + PullDown, + PullNone, +} + +pub enum IrqMode { + EitherEdge, + RisingEdge, + FallingEdge, +} + +impl InputMode { + fn to_num(self) -> usize { + match self { + InputMode::PullNone => 0, + InputMode::PullUp => 1, + InputMode::PullDown => 2, + } + } +} + +impl IrqMode { + fn to_num(self) -> usize { + match self { + IrqMode::EitherEdge => 0, + IrqMode::RisingEdge => 1, + IrqMode::FallingEdge => 2, + } + } +} + +pub struct GpioPinUnitialized { + number: usize, +} + +pub struct GpioPinWrite { + number: usize, +} + +pub struct GpioPinRead { + number: usize, +} + +impl GpioPinUnitialized { + pub fn new(number: usize) -> GpioPinUnitialized { + GpioPinUnitialized { number } + } + + pub fn open_for_write(self) -> Result { + match unsafe { command(DRIVER_NUMBER, gpio_commands::ENABLE_OUTPUT, self.number, 0) } { + 0 => Ok(GpioPinWrite { + number: self.number, + }), + _ => Err("Could not open pin for writing."), + } + } + + pub fn open_for_read( + self, + callback: Option<(extern "C" fn(usize, usize, usize, usize), IrqMode)>, + input_mode: InputMode, + ) -> Result { + let (callback, irq_mode) = callback.unwrap_or((noop_callback, IrqMode::EitherEdge)); + self.enable_input(input_mode) + .and_then(|pin| pin.subscribe_callback(callback)) + .and_then(|pin| pin.enable_callback(irq_mode)) + } + + fn subscribe_callback( + self, + callback: extern "C" fn(usize, usize, usize, usize), + ) -> Result { + if unsafe { + subscribe( + DRIVER_NUMBER, + gpio_commands::SUBSCRIBE_CALLBACK, + callback, + self.number as usize, + ) + } == 0 + { + Ok(self) + } else { + Err("Could not subscribe callback.") + } + } + + fn enable_input(self, mode: InputMode) -> Result { + if unsafe { + command( + DRIVER_NUMBER, + gpio_commands::ENABLE_INPUT, + self.number, + mode.to_num(), + ) + } == 0 + { + Ok(self) + } else { + Err("Could not enable input.") + } + } + + fn enable_callback(self, irq_mode: IrqMode) -> Result { + if unsafe { + command( + DRIVER_NUMBER, + gpio_commands::ENABLE_INTERRUPT, + self.number, + irq_mode.to_num(), + ) + } == 0 + { + Ok(GpioPinRead { + number: self.number, + }) + } else { + Err("Could not enable callback.") + } + } +} + +impl GpioPinWrite { + pub fn set_low(&self) { + unsafe { + command(DRIVER_NUMBER, gpio_commands::SET_LOW, self.number, 0); + } + } + pub fn set_high(&self) { + unsafe { + command(DRIVER_NUMBER, gpio_commands::SET_HIGH, self.number, 0); + } + } + pub fn toggle(&self) { + unsafe { + command(DRIVER_NUMBER, gpio_commands::TOGGLE, self.number, 0); + } + } +} + +impl GpioPinRead { + pub fn read(&self) -> bool { + unsafe { command(DRIVER_NUMBER, gpio_commands::READ, self.number, 0) == 1 } + } +} + +impl Drop for GpioPinWrite { + fn drop(&mut self) { + unsafe { + command(DRIVER_NUMBER, gpio_commands::DISABLE, self.number, 0); + } + } +} + +impl Drop for GpioPinRead { + fn drop(&mut self) { + unsafe { + command( + DRIVER_NUMBER, + gpio_commands::DISABLE_INTERRUPT, + self.number, + 0, + ); + command(DRIVER_NUMBER, gpio_commands::DISABLE, self.number, 0); + } + } +} + +extern "C" fn noop_callback(_: usize, _: usize, _: usize, _: usize) {} diff --git a/src/ipc/client.rs b/src/ipc/client.rs index 93443278..85901441 100644 --- a/src/ipc/client.rs +++ b/src/ipc/client.rs @@ -1,25 +1,23 @@ -use core::cell::Cell; -use core::mem; -use syscalls; -use alloc::heap::Heap; use alloc::String; +use alloc::allocator::{Alloc, Layout}; use alloc::boxed::Box; +use alloc::heap::Heap; use alloc::raw_vec::RawVec; -use alloc::allocator::{Alloc, Layout}; +use core::cell::Cell; +use core::mem; +use syscalls; -const DRIVER_NUM: u32 = 0xff; +const DRIVER_NUM: usize = 0x10000; pub struct Client { - pid: u32 + pid: usize, } impl Client { pub fn new(pkg_name: String) -> Result { unsafe { let res = discover(pkg_name)?; - Ok(Client { - pid: res as u32 - }) + Ok(Client { pid: res }) } } @@ -28,33 +26,33 @@ impl Client { len = 3; } unsafe { - let shared_val = Heap.alloc_zeroed( - Layout::from_size_align(1 << len, 1 << len).unwrap()); + let shared_val = + Heap.alloc_zeroed(Layout::from_size_align(1 << len, 1 << len).unwrap()); match shared_val { Ok(v) => { share(self.pid, v, 1 << len)?; Ok(RawVec::from_raw_parts(v, 1 << len).into_box()) - }, - _ => { - Err(()) } + _ => Err(()), } } } pub unsafe fn notify_async(&mut self) -> Result<(), ()> { - if syscalls::command(DRIVER_NUM, self.pid, 0) < 0 { - return Err(()) + if syscalls::command(DRIVER_NUM, self.pid, 0, 0) < 0 { + return Err(()); } Ok(()) } - pub unsafe fn subscribe(&mut self, - cb: extern fn(_: usize, _: usize, _: usize, ptr: usize), ud: usize) - -> Result<(), ()> { + pub unsafe fn subscribe( + &mut self, + cb: extern "C" fn(_: usize, _: usize, _: usize, ptr: usize), + ud: usize, + ) -> Result<(), ()> { if syscalls::subscribe(DRIVER_NUM, self.pid, cb, ud) < 0 { - return Err(()) + return Err(()); } Ok(()) } @@ -70,23 +68,22 @@ impl Client { Ok(()) } - extern fn cb(_: usize, _: usize, _: usize, ptr: usize) { + extern "C" fn cb(_: usize, _: usize, _: usize, ptr: usize) { let done: &Cell = unsafe { mem::transmute(ptr) }; done.set(true); - } } -unsafe fn discover(pkg_name: String) -> Result { +unsafe fn discover(pkg_name: String) -> Result { let res = syscalls::allow(DRIVER_NUM, 0, pkg_name.as_bytes()); if res < 0 { Err(()) } else { - Ok(res) + Ok(res as usize) } } -unsafe fn share(pid: u32, base: *mut u8, len: usize) -> Result<(), ()> { +unsafe fn share(pid: usize, base: *mut u8, len: usize) -> Result<(), ()> { use core::slice::from_raw_parts; let res = syscalls::allow(DRIVER_NUM, pid, from_raw_parts(base, len)); if res < 0 { @@ -95,4 +92,3 @@ unsafe fn share(pid: u32, base: *mut u8, len: usize) -> Result<(), ()> { Ok(()) } } - diff --git a/src/ipc_cs/client.rs b/src/ipc_cs/client.rs new file mode 100644 index 00000000..b6897617 --- /dev/null +++ b/src/ipc_cs/client.rs @@ -0,0 +1,85 @@ +use alloc::String; +use alloc::allocator::{Alloc, Layout}; +use alloc::boxed::Box; +use alloc::heap::Heap; +use alloc::raw_vec::RawVec; +use syscalls::{allow, command, subscribe}; + +const DRIVER_NUMBER: usize = 0x10000; + +mod ipc_commands { + pub const DISCOVER_SERVICE: usize = 0; +} + +pub struct ServerHandle { + pid: isize, + callback: Option, +} + +pub trait IPCCallback { + fn callback(&mut self, usize, usize); +} +impl IPCCallback for F { + fn callback(&mut self, pid: usize, len: usize) { + self(pid, len); + } +} + +pub fn reserve_shared_buffer() -> Box<[u8]> { + let shared_val = unsafe { + Heap.alloc_zeroed(Layout::from_size_align(32, 32).unwrap()) + .unwrap() + }; + let v = unsafe { RawVec::from_raw_parts(shared_val, 32).into_box() }; + v +} + +impl ServerHandle { + pub fn share(&mut self, shared_buffer: &mut Box<[u8]>, message: &[u8; 32]) { + shared_buffer.clone_from_slice(message); + + unsafe { + if allow(DRIVER_NUMBER, self.pid as usize, &*shared_buffer) < 0 { + panic!() + }; + } + } + + pub fn notify(&mut self) { + unsafe { command(DRIVER_NUMBER, self.pid as usize, 0, 0) }; + } + + pub fn discover_service(name: String) -> Option> { + let pid = unsafe { + allow( + DRIVER_NUMBER, + ipc_commands::DISCOVER_SERVICE, + &name.as_bytes(), + ) + }; + if pid >= 0 { + Some(ServerHandle { + callback: None, + pid: pid, + }) + } else { + None + } + } + + pub fn subscribe_callback(&mut self, mut callback: CB) { + extern "C" fn cb(result: usize, len: usize, _: usize, ud: usize) { + let callback = unsafe { &mut *(ud as *mut CB) }; + callback.callback(result, len); + } + unsafe { + subscribe( + DRIVER_NUMBER, + self.pid as usize, + cb::, + &mut callback as *mut _ as usize, + ); + } + self.callback = Some(callback); + } +} diff --git a/src/ipc_cs/mod.rs b/src/ipc_cs/mod.rs new file mode 100644 index 00000000..a7b42c19 --- /dev/null +++ b/src/ipc_cs/mod.rs @@ -0,0 +1,5 @@ +pub mod server; +pub mod client; + +pub use self::client::*; +pub use self::server::*; diff --git a/src/ipc_cs/server.rs b/src/ipc_cs/server.rs new file mode 100644 index 00000000..01b94ce1 --- /dev/null +++ b/src/ipc_cs/server.rs @@ -0,0 +1,49 @@ +use callback::CallbackSubscription; +use callback::SubscribableCallback; +use core::marker::PhantomData; +use syscalls; + +const DRIVER_NUMBER: usize = 0x10000; + +mod ipc_commands { + pub const REGISTER_SERVICE: usize = 0; + pub const NOTIFY_CLIENT: usize = 1; +} + +pub struct IpcServerCallback { + callback: CB, + phantom_data: PhantomData, +} + +impl SubscribableCallback for IpcServerCallback { + fn driver_number(&self) -> usize { + DRIVER_NUMBER + } + + fn subscribe_number(&self) -> usize { + ipc_commands::REGISTER_SERVICE + } + + fn call_rust(&mut self, arg0: usize, arg1: usize, arg2: usize) { + let data = unsafe { &mut *(arg2 as *mut S) }; + (self.callback)(arg0, arg1, data); + } +} + +pub fn notify_client(pid: usize) { + unsafe { syscalls::command(DRIVER_NUMBER, pid, ipc_commands::NOTIFY_CLIENT, 0) }; +} + +pub struct IpcServerDriver; + +impl IpcServerDriver { + pub fn start( + callback: CB, + ) -> Result>, ()> { + let (_, subscription) = syscalls::subscribe_new(IpcServerCallback { + callback, + phantom_data: Default::default(), + }); + Ok(subscription) + } +} diff --git a/src/lang_items.rs b/src/lang_items.rs index ba0d0558..2a3b3d81 100644 --- a/src/lang_items.rs +++ b/src/lang_items.rs @@ -6,7 +6,7 @@ // ``` // #[export_name = "main"] // pub extern "C" fn rustc_main(argc: isize, argv: *const *const u8) -> isize { -// start(main) +// start(main, argc, argv) // } // ``` // @@ -16,21 +16,58 @@ // The final piece is that the entry point of our program, _start, has to call // `rustc_main`. That's covered by the `_start` function in the root of this // crate. +use led; +use timer; +use timer::Duration; + #[lang = "start"] -extern "C" fn start( - main: fn(), - _argc: isize, - _argv: *const *const u8, -) -> isize { +extern "C" fn start(main: fn() -> T, _argc: isize, _argv: *const *const u8) -> isize +where + T: Termination, +{ main(); 0 } +#[lang = "termination"] +pub trait Termination { + fn report(self) -> i32; +} + +impl Termination for () { + fn report(self) -> i32 { + 0 + } +} + #[lang = "eh_personality"] -extern "C" fn eh_personality() {} +fn eh_personality() { + cycle_leds(); +} #[lang = "panic_fmt"] -unsafe extern "C" fn rust_begin_unwind() { - loop {} +fn panic_fmt() { + flash_all_leds(); +} + +fn cycle_leds() { + for led in led::all().cycle() { + led.on(); + timer::sleep(Duration::from_ms(100)); + led.off(); + } +} + +fn flash_all_leds() { + loop { + for led in led::all() { + led.on(); + } + timer::sleep(Duration::from_ms(100)); + for led in led::all() { + led.off(); + } + timer::sleep(Duration::from_ms(100)); + } } diff --git a/src/led.rs b/src/led.rs index ea81f107..994670e3 100644 --- a/src/led.rs +++ b/src/led.rs @@ -1,31 +1,85 @@ use syscalls::command; -const COUNT: u32 = 0; -const ON: u32 = 1; -const OFF: u32 = 2; -const TOGGLE: u32 = 3; -const DRIVER_NUMBER: u32 = 2; +const DRIVER_NUMBER: usize = 0x00002; + +mod command_nr { + pub const COUNT: usize = 0; + pub const ON: usize = 1; + pub const OFF: usize = 2; + pub const TOGGLE: usize = 3; +} + +pub struct Led { + led_num: usize, +} pub fn count() -> isize { - unsafe { - command(DRIVER_NUMBER, COUNT, 0) + unsafe { command(DRIVER_NUMBER, command_nr::COUNT, 0, 0) } +} + +pub fn get(led_num: isize) -> Option { + if led_num >= 0 && led_num < count() { + Some(Led { + led_num: led_num as usize, + }) + } else { + None } } -pub fn on(led_num: u32) { - unsafe { - command(DRIVER_NUMBER, ON, led_num as isize); +pub fn all() -> LedIter { + LedIter { + curr_led: 0, + led_count: count() as usize, } } -pub fn off(led_num: u32) { - unsafe { - command(DRIVER_NUMBER, OFF, led_num as isize); +impl Led { + pub fn set_state(&self, state: bool) { + if state { + self.on() + } else { + self.off() + } + } + + pub fn on(&self) { + unsafe { + command(DRIVER_NUMBER, command_nr::ON, self.led_num, 0); + } + } + + pub fn off(&self) { + unsafe { + command(DRIVER_NUMBER, command_nr::OFF, self.led_num, 0); + } + } + + pub fn toggle(&self) { + unsafe { + command(DRIVER_NUMBER, command_nr::TOGGLE, self.led_num, 0); + } } } -pub fn toggle(led_num: u32) { - unsafe { - command(DRIVER_NUMBER, TOGGLE, led_num as isize); +#[derive(Copy, Clone)] +pub struct LedIter { + curr_led: usize, + led_count: usize, +} + +impl Iterator for LedIter { + type Item = Led; + + fn next(&mut self) -> Option { + if self.curr_led < self.led_count { + let item = Led { + led_num: self.curr_led, + }; + self.curr_led += 1; + Some(item) + } else { + None + } } } diff --git a/src/lib.rs b/src/lib.rs index e89586d2..42063f75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,99 +1,39 @@ -#![feature(asm,alloc,allocator_api,compiler_builtins_lib,const_fn,global_allocator,lang_items,naked_functions)] +#![feature(asm, alloc, allocator_api, compiler_builtins_lib, global_allocator, lang_items, + naked_functions)] #![no_std] -pub mod syscalls; -pub mod ipc; -pub mod sensors; -pub mod console; -pub mod timer; -pub mod led; - extern crate alloc; extern crate compiler_builtins; -extern crate linked_list_allocator; -mod lang_items; +mod callback; -use alloc::allocator::{Alloc, Layout, AllocErr}; -use core::ptr; -use core::mem::{align_of, size_of}; -use linked_list_allocator::{align_up, Heap}; - -// None-threaded heap wrapper based on `r9` register instead of global variable -struct BaseHeap; - -impl BaseHeap { - pub unsafe fn heap(&self) -> &mut Heap { - let heap: &mut Heap; - asm!("mov $0, r9" : "=r"(heap) : : : "volatile"); - heap - } - - /// Initializes an empty heap - /// - /// Returns the end of the heap - /// - /// # Unsafety - /// - /// This function must be called at most once and must only be used on an - /// empty heap. - #[inline(never)] - pub unsafe fn init(&mut self, heap_size: usize) -> usize { - let heap_bottom = align_up(self as *mut _ as usize + size_of::(), - align_of::()); - self.heap().init(heap_bottom, heap_size); - heap_bottom + heap_size - } - -} - -unsafe impl<'a> Alloc for &'a BaseHeap { - unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> { - self.heap().allocate_first_fit(layout) - } - - unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout) { - self.heap().deallocate(ptr, layout) - } -} +pub mod ble_parser; +pub mod buttons; +pub mod console; +pub mod debug; +pub mod electronics; +pub mod fmt; +pub mod gpio; +pub mod ipc; +pub mod ipc_cs; +pub mod led; +pub mod result; +pub mod sensors; +pub mod simple_ble; +pub mod temperature; +pub mod timer; +pub mod util; +#[cfg(target_os = "tock")] +pub mod entry_point; +#[cfg(target_os = "tock")] +mod lang_items; +#[cfg(target_os = "tock")] +pub mod syscalls; +#[cfg(not(target_os = "tock"))] +#[path = "syscalls_mock.rs"] +mod syscalls; +#[cfg(target_os = "tock")] #[global_allocator] -static ALLOCATOR : BaseHeap = BaseHeap; - -/// Tock programs' entry point -#[doc(hidden)] -#[no_mangle] -#[naked] -pub extern "C" fn _start(mem_start: usize, _app_heap_break: usize, - _kernel_memory_break: usize) -> ! { - - extern "C" { - // NOTE `rustc` forces this signature on us. See `src/lang_items.rs` - fn main(argc: isize, argv: *const *const u8) -> isize; - } - - unsafe { - // Setup stack - syscalls::memop(0, mem_start + 1024); - - let new_stack = mem_start + 1024; - asm!("mov sp, $0" : : "r"(new_stack) : : "volatile"); - syscalls::memop(10, new_stack); - - // Setup heap - let new_heap = align_up(new_stack, align_of::()); - asm!("mov r9, $0" : : "r"(new_heap) : : "volatile"); - syscalls::memop(11, new_heap); - - let end_of_mem = BaseHeap.init(1024); - syscalls::memop(0, end_of_mem); - - // arguments are not used in Tock applications - main(0, ptr::null()); - } - - loop { - ::syscalls::yieldk(); - } -} +static ALLOCATOR: entry_point::StackOwnedHeap = entry_point::StackOwnedHeap; diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 00000000..046947d1 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,25 @@ +#[derive(Copy, Clone, Debug)] +pub enum TockValue { + Expected(E), + Unexpected(isize), +} + +pub type TockResult = Result>; + +pub trait TockResultExt: Sized { + fn as_expected(self) -> Result; +} + +impl TockResultExt for TockResult { + fn as_expected(self) -> Result { + match self { + Ok(ok) => Ok(ok), + Err(TockValue::Expected(err)) => Err(err), + Err(TockValue::Unexpected(_)) => panic!("Unexpected error"), + } + } +} + +pub const SUCCESS: isize = 0; +pub const EALREADY: isize = -3; +pub const ENOMEM: isize = -9; diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 21a46391..7ad02796 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -8,25 +8,24 @@ mod ninedof; pub use self::ninedof::*; -extern fn cb(x: usize, y: usize, z: usize, ptr: usize) - where Reading: Copy + From<(usize, usize, usize)> { - let res: &Cell> = unsafe { - mem::transmute(ptr) - }; +extern "C" fn cb(x: usize, y: usize, z: usize, ptr: usize) +where + Reading: Copy + From<(usize, usize, usize)>, +{ + let res: &Cell> = unsafe { mem::transmute(ptr) }; res.set(Some(From::from((x, y, z)))); } pub trait Sensor> { - fn driver_num(&self) -> u32; + fn driver_num(&self) -> usize; fn read(&mut self) -> Reading { let res: Cell> = Cell::new(None); let driver_num = self.driver_num(); unsafe { - syscalls::subscribe(driver_num, 0, cb::, - mem::transmute(&res)); - syscalls::command(driver_num, 1, 0); - yieldk_for(|| res.get().is_some() ); + syscalls::subscribe(driver_num, 0, cb::, mem::transmute(&res)); + syscalls::command(driver_num, 1, 0, 0); + yieldk_for(|| res.get().is_some()); res.get().unwrap() } } @@ -52,7 +51,7 @@ macro_rules! single_value_sensor { pub struct $sensor_name; impl Sensor<$type_name> for $sensor_name { - fn driver_num(&self) -> u32 { + fn driver_num(&self) -> usize { $driver_num } } @@ -80,4 +79,3 @@ impl fmt::Display for Temperature { write!(f, "{}.{}\u{00B0}C", self.0 / 100, self.0 % 100) } } - diff --git a/src/sensors/ninedof.rs b/src/sensors/ninedof.rs index fbe95021..72464483 100644 --- a/src/sensors/ninedof.rs +++ b/src/sensors/ninedof.rs @@ -3,7 +3,7 @@ use core::fmt; use core::mem; use syscalls::{self, yieldk_for}; -const DRIVER_NUM: u32 = 11; +const DRIVER_NUM: usize = 0x60004; pub struct Ninedof; @@ -11,7 +11,7 @@ pub struct Ninedof; pub struct NinedofReading { pub x: i32, pub y: i32, - pub z: i32 + pub z: i32, } impl fmt::Display for NinedofReading { @@ -23,7 +23,7 @@ impl fmt::Display for NinedofReading { #[derive(Default)] struct CbData { res: Cell, - ready: Cell + ready: Cell, } impl Ninedof { @@ -36,9 +36,9 @@ impl Ninedof { unsafe { subscribe(Self::cb, mem::transmute(&res)); start_accel_reading(); - yieldk_for(|| res.ready.get() ); + yieldk_for(|| res.ready.get()); } - res.res.get() + res.res.get() } pub fn read_magnetometer(&mut self) -> NinedofReading { @@ -46,31 +46,30 @@ impl Ninedof { unsafe { subscribe(Self::cb, mem::transmute(&res)); start_magnetometer_reading(); - yieldk_for(|| res.ready.get() ); + yieldk_for(|| res.ready.get()); } - res.res.get() + res.res.get() } - extern fn cb(x: usize, y: usize, z: usize, ptr: usize) { - let res: &CbData = unsafe { - mem::transmute(ptr) - }; + extern "C" fn cb(x: usize, y: usize, z: usize, ptr: usize) { + let res: &CbData = unsafe { mem::transmute(ptr) }; res.res.set(NinedofReading { - x: x as i32, y: y as i32, z: z as i32 + x: x as i32, + y: y as i32, + z: z as i32, }); res.ready.set(true); } } -pub unsafe fn subscribe(cb: extern fn(usize, usize, usize, usize), ud: usize) { +pub unsafe fn subscribe(cb: extern "C" fn(usize, usize, usize, usize), ud: usize) { syscalls::subscribe(DRIVER_NUM, 0, cb, ud); } pub unsafe fn start_accel_reading() { - syscalls::command(DRIVER_NUM, 1, 0); + syscalls::command(DRIVER_NUM, 1, 0, 0); } pub unsafe fn start_magnetometer_reading() { - syscalls::command(DRIVER_NUM, 1, 0); + syscalls::command(DRIVER_NUM, 1, 0, 0); } - diff --git a/src/simple_ble.rs b/src/simple_ble.rs new file mode 100644 index 00000000..573d5856 --- /dev/null +++ b/src/simple_ble.rs @@ -0,0 +1,237 @@ +use alloc::String; +use alloc::Vec; +use callback::CallbackSubscription; +use callback::SubscribableCallback; +use syscalls; + +const DRIVER_NUMBER: usize = 0x30000; +pub const MAX_PAYLOAD_SIZE: usize = 9; +pub const BUFFER_SIZE: usize = 39; +pub const BUFFER_SIZE_SCAN: usize = 39; + +mod ble_commands { + pub const START_ADVERTISING: usize = 0; + pub const SET_ADVERTISING_INTERVAL: usize = 3; + pub const ALLOW_ADVERTISMENT_BUFFER: usize = 0x32; + pub const REQ_ADV_ADDRESS: usize = 6; + pub const BLE_PASSIVE_SCAN_SUB: usize = 0; + pub const ALLOW_SCAN_BUFFER: usize = 0x31; + pub const PASSIVE_SCAN: usize = 5; +} + +mod gap_flags { + pub const BLE_DISCOVERABLE: u8 = 0x02; + pub const BLE_NOT_DISCOVERABLE: u8 = 0x01; + pub const ONLY_LE: u8 = 0x04; +} + +pub mod gap_data { + pub const COMPLETE_LIST_16BIT_SERVICE_IDS: usize = 0x03; + pub const COMPLETE_LOCAL_NAME: usize = 0x09; + pub const SET_FLAGS: usize = 1; + pub const SERVICE_DATA: usize = 0x16; +} + +#[allow(dead_code)] +pub struct BleDeviceUninitialized<'a> { + interval: u16, + name: String, + uuid: Vec, + flags: Vec, + buffer: [u8; BUFFER_SIZE], + service_payload: &'a mut Vec, +} + +#[allow(dead_code)] +pub struct BleDeviceInitialized<'a> { + interval: &'a mut u16, + name: &'a mut String, + uuid: &'a mut Vec, + flags: &'a mut Vec, + buffer: &'a mut [u8; 39], + service_payload: &'a mut Vec, +} + +#[allow(dead_code)] +pub struct BleDeviceAdvertising<'a> { + interval: &'a mut u16, + name: &'a mut String, + uuid: &'a mut Vec, + flags: &'a mut Vec, + buffer: &'a mut [u8; 39], + service_payload: &'a mut Vec, +} + +#[allow(dead_code)] +impl<'a> BleDeviceUninitialized<'a> { + pub fn new( + interval: u16, + name: String, + uuid: Vec, + stay_visible: bool, + service_payload: &'a mut Vec, + ) -> BleDeviceUninitialized { + let flags: [u8; 1] = [ + gap_flags::ONLY_LE | (if stay_visible { + gap_flags::BLE_DISCOVERABLE + } else { + gap_flags::BLE_NOT_DISCOVERABLE + }), + ]; + + BleDeviceUninitialized { + interval: interval, + name: name, + uuid: uuid, + flags: flags.to_vec(), + buffer: [0; 39], + service_payload: service_payload, + } + } + pub fn initialize(&'a mut self) -> Result, &'static str> { + Ok(self) + .and_then(|ble| ble.set_advertising_buffer()) + .and_then(|ble| ble.request_address()) + .and_then(|ble| ble.set_advertising_interval()) + .and_then(|ble| ble.set_advertising_flags()) + .and_then(|ble| ble.set_advertising_name()) + .and_then(|ble| ble.set_advertsing_uuid()) + .and_then(|ble| ble.set_service_payload()) + .and_then(|ble| { + Ok(BleDeviceInitialized { + interval: &mut ble.interval, + name: &mut ble.name, + uuid: &mut ble.uuid, + flags: &mut ble.flags, + buffer: &mut ble.buffer, + service_payload: &mut ble.service_payload, + }) + }) + } + + fn set_advertising_interval(&mut self) -> Result<&mut Self, &'static str> { + match unsafe { + syscalls::command( + DRIVER_NUMBER, + ble_commands::SET_ADVERTISING_INTERVAL, + self.interval as usize, + 0, + ) + } { + 0 => Ok(self), + _ => Err(""), + } + } + + fn request_address(&mut self) -> Result<&mut Self, &'static str> { + match unsafe { syscalls::command(DRIVER_NUMBER, ble_commands::REQ_ADV_ADDRESS, 0, 0) } { + 0 => Ok(self), + _ => Err(""), + } + } + + fn set_advertising_name(&mut self) -> Result<&mut Self, &'static str> { + match unsafe { + syscalls::allow( + DRIVER_NUMBER, + gap_data::COMPLETE_LOCAL_NAME, + self.name.as_bytes(), + ) + } { + 0 => Ok(self), + _ => Err(""), + } + } + + fn set_advertsing_uuid(&mut self) -> Result<&mut Self, &'static str> { + match unsafe { + syscalls::allow16( + DRIVER_NUMBER, + gap_data::COMPLETE_LIST_16BIT_SERVICE_IDS, + &self.uuid, + ) + } { + 0 => Ok(self), + _ => Err(""), + } + } + + fn set_advertising_flags(&mut self) -> Result<&mut Self, &'static str> { + match unsafe { syscalls::allow(DRIVER_NUMBER, gap_data::SET_FLAGS, &self.flags) } { + 0 => Ok(self), + _ => Err(""), + } + } + + fn set_advertising_buffer(&mut self) -> Result<&mut Self, &'static str> { + match unsafe { + syscalls::allow( + DRIVER_NUMBER, + ble_commands::ALLOW_ADVERTISMENT_BUFFER, + &self.buffer, + ) + } { + 0 => Ok(self), + _ => Err(""), + } + } + + fn set_service_payload(&mut self) -> Result<&mut Self, &'static str> { + match unsafe { + syscalls::allow(DRIVER_NUMBER, gap_data::SERVICE_DATA, self.service_payload) + } { + 0 => Ok(self), + _ => Err(""), + } + } +} + +impl<'a> BleDeviceInitialized<'a> { + pub fn start_advertising(&mut self) -> Result { + match unsafe { syscalls::command(DRIVER_NUMBER, ble_commands::START_ADVERTISING, 0, 0) } { + 0 => Ok(BleDeviceAdvertising { + interval: &mut self.interval, + name: &mut self.name, + uuid: &mut self.uuid, + flags: &mut self.flags, + buffer: &mut self.buffer, + service_payload: &mut self.service_payload, + }), + _ => Err(""), + } + } +} + +pub struct BleCallback { + callback: CB, +} + +impl SubscribableCallback for BleCallback { + fn driver_number(&self) -> usize { + DRIVER_NUMBER + } + + fn subscribe_number(&self) -> usize { + ble_commands::BLE_PASSIVE_SCAN_SUB + } + + fn call_rust(&mut self, arg0: usize, arg1: usize, _: usize) { + (self.callback)(arg0, arg1); + } +} + +pub struct BleDriver; + +impl BleDriver { + pub fn start( + buffer: &[u8; BUFFER_SIZE_SCAN], + callback: CB, + ) -> Result>, ()> { + let (_, subscription) = syscalls::subscribe_new(BleCallback { callback }); + unsafe { + syscalls::allow(DRIVER_NUMBER, ble_commands::ALLOW_SCAN_BUFFER, buffer); + syscalls::command(DRIVER_NUMBER, ble_commands::PASSIVE_SCAN, 1, 0); + } + Ok(subscription) + } +} diff --git a/src/syscalls.rs b/src/syscalls.rs index d99257d2..cfe5d98e 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -1,3 +1,5 @@ +use callback::CallbackSubscription; +use callback::SubscribableCallback; pub fn yieldk() { // Note: A process stops yielding when there is a callback ready to run, @@ -38,7 +40,7 @@ pub fn yieldk_for bool>(cond: F) { } } -pub unsafe fn allow(major: u32, minor: u32, slice: &[u8]) -> isize { +pub unsafe fn allow(major: usize, minor: usize, slice: &[u8]) -> isize { let res; asm!("svc 3" : "={r0}"(res) : "{r0}"(major) "{r1}"(minor) "{r2}"(slice.as_ptr()) "{r3}"(slice.len()) @@ -47,7 +49,21 @@ pub unsafe fn allow(major: u32, minor: u32, slice: &[u8]) -> isize { res } -pub unsafe fn subscribe(major: u32, minor: u32, cb: extern fn(usize, usize, usize, usize), ud: usize) -> isize { +pub unsafe fn allow16(major: usize, minor: usize, slice: &[u16]) -> isize { + let res; + asm!("svc 3" : "={r0}"(res) + : "{r0}"(major) "{r1}"(minor) "{r2}"(slice.as_ptr()) "{r3}"(slice.len()*2) + : "memory" + : "volatile"); + res +} + +pub unsafe fn subscribe( + major: usize, + minor: usize, + cb: unsafe extern "C" fn(usize, usize, usize, usize), + ud: usize, +) -> isize { let res; asm!("svc 1" : "={r0}"(res) : "{r0}"(major) "{r1}"(minor) "{r2}"(cb) "{r3}"(ud) @@ -56,10 +72,10 @@ pub unsafe fn subscribe(major: u32, minor: u32, cb: extern fn(usize, usize, usiz res } -pub unsafe fn command(major: u32, minor: u32, arg1: isize) -> isize { +pub unsafe fn command(major: usize, minor: usize, arg1: usize, arg2: usize) -> isize { let res; asm!("svc 2" : "={r0}"(res) - : "{r0}"(major) "{r1}"(minor) "{r2}"(arg1) + : "{r0}"(major) "{r1}"(minor) "{r2}"(arg1) "{r3}"(arg2) : "memory" : "volatile"); res @@ -74,3 +90,40 @@ pub unsafe fn memop(major: u32, arg1: usize) -> isize { res } +pub fn subscribe_new( + mut callback: CB, +) -> (isize, CallbackSubscription) { + extern "C" fn c_callback( + arg0: usize, + arg1: usize, + arg2: usize, + userdata: usize, + ) { + let callback = unsafe { &mut *(userdata as *mut CB) }; + callback.call_rust(arg0, arg1, arg2); + } + + let return_code = unsafe { + subscribe( + callback.driver_number(), + callback.subscribe_number(), + c_callback::, + &mut callback as *mut CB as usize, + ) + }; + (return_code, CallbackSubscription { callback }) +} +impl Drop for CallbackSubscription { + fn drop(&mut self) { + extern "C" fn noop_callback(_: usize, _: usize, _: usize, _: usize) {} + + unsafe { + subscribe( + self.callback.driver_number(), + self.callback.subscribe_number(), + noop_callback, + 0, + ); + } + } +} diff --git a/src/syscalls_mock.rs b/src/syscalls_mock.rs new file mode 100644 index 00000000..495c9c2e --- /dev/null +++ b/src/syscalls_mock.rs @@ -0,0 +1,35 @@ +use callback::CallbackSubscription; +use callback::SubscribableCallback; + +pub fn yieldk_for bool>(_: F) { + unimplemented() +} + +pub unsafe fn allow(_: usize, _: usize, _: &[u8]) -> isize { + unimplemented() +} + +pub unsafe fn allow16(_: usize, _: usize, _: &[u16]) -> isize { + unimplemented() +} + +pub unsafe fn subscribe( + _: usize, + _: usize, + _: unsafe extern "C" fn(usize, usize, usize, usize), + _: usize, +) -> isize { + unimplemented() +} + +pub unsafe fn command(_: usize, _: usize, _: usize, _: usize) -> isize { + unimplemented() +} + +pub fn subscribe_new(_: CB) -> (isize, CallbackSubscription) { + unimplemented() +} + +fn unimplemented() -> ! { + unimplemented!("Unimplemented for tests"); +} diff --git a/src/temperature.rs b/src/temperature.rs new file mode 100644 index 00000000..120203e2 --- /dev/null +++ b/src/temperature.rs @@ -0,0 +1,39 @@ +use callback::CallbackSubscription; +use callback::SubscribableCallback; +use syscalls; + +const DRIVER_NUMBER: usize = 0x60000; +const SUBSCRIBE_CALLBACK: usize = 0; +const START_MEASUREMENT: usize = 1; + +pub struct TemperatureCallback { + callback: CB, +} + +impl SubscribableCallback for TemperatureCallback { + fn driver_number(&self) -> usize { + DRIVER_NUMBER + } + + fn subscribe_number(&self) -> usize { + SUBSCRIBE_CALLBACK + } + + fn call_rust(&mut self, arg0: usize, _: usize, _: usize) { + (self.callback)(arg0 as isize); + } +} + +pub struct TemperatureDriver; + +impl TemperatureDriver { + pub fn start_measurement( + callback: CB, + ) -> Result>, ()> { + let (_, subscription) = syscalls::subscribe_new(TemperatureCallback { callback }); + unsafe { + syscalls::command(DRIVER_NUMBER, START_MEASUREMENT, 0, 0); + } + Ok(subscription) + } +} diff --git a/src/timer.rs b/src/timer.rs index bdc138ed..6008ecc6 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,50 +1,225 @@ -use core::mem; +use callback::CallbackSubscription; +use callback::SubscribableCallback; use core::cell::Cell; -use syscalls::{self, command, yieldk_for}; +use core::isize; +use result; +use result::TockResult; +use result::TockValue; +use syscalls; -const DRIVER_NUMBER: u32 = 0; -const GET_CLOCK_FREQUENCY: u32 = 1; -const SET_ALARM_NOTIFICATION: u32 = 4; -const GET_CLOCK_VALUE: u32 = 2; +const DRIVER_NUMBER: usize = 0x00000; -pub unsafe fn subscribe(cb: extern fn(usize, usize, usize, usize), ud: usize) { - syscalls::subscribe(3, 0, cb, ud); +mod command_nr { + pub const IS_DRIVER_AVAILABLE: usize = 0; + pub const GET_CLOCK_FREQUENCY: usize = 1; + pub const GET_CLOCK_VALUE: usize = 2; + pub const STOP_ALARM: usize = 3; + pub const SET_ALARM: usize = 4; } -pub fn set_alarm(ms: u32) { - unsafe { - command(DRIVER_NUMBER, SET_ALARM_NOTIFICATION, ms as isize); +mod subscribe_nr { + pub const SUBSCRIBE_CALLBACK: usize = 0; +} + +pub fn sleep(duration: Duration) { + let expired = Cell::new(false); + + let timer = with_callback(|_, _| expired.set(true)).unwrap(); + + timer.set_alarm(duration).unwrap(); + syscalls::yieldk_for(|| expired.get()); +} + +pub fn with_callback( + callback: CB, +) -> TockResult, TimerError> { + let num_notifications = + unsafe { syscalls::command(DRIVER_NUMBER, command_nr::IS_DRIVER_AVAILABLE, 0, 0) }; + + if num_notifications < 1 { + return Err(TockValue::Expected(TimerError::NotSupported)); + } + + let clock_frequency = + unsafe { syscalls::command(DRIVER_NUMBER, command_nr::GET_CLOCK_FREQUENCY, 0, 0) }; + + if clock_frequency < 1 { + return Err(TockValue::Expected(TimerError::ErroneousClockFrequency( + clock_frequency, + ))); + } + + let (return_code, subscription) = syscalls::subscribe_new(TimerCallback { + callback, + clock_frequency: ClockFrequency { + hz: clock_frequency as usize, + }, + }); + + let timer = Timer { + num_notifications: num_notifications as usize, + clock_frequency: ClockFrequency { + hz: clock_frequency as usize, + }, + subscription, + }; + + match return_code { + result::SUCCESS => Ok(timer), + result::ENOMEM => Err(TockValue::Expected(TimerError::SubscriptionFailed)), + unexpected => Err(TockValue::Unexpected(unexpected)), } } -pub fn start_repeating(ms: u32) { - unsafe { - command(DRIVER_NUMBER, 2, ms as isize); +pub struct Timer { + num_notifications: usize, + clock_frequency: ClockFrequency, + #[allow(dead_code)] // Used in drop + subscription: CallbackSubscription>, +} + +#[derive(Copy, Clone, Debug)] +pub enum TimerError { + NotSupported, + ErroneousClockFrequency(isize), + SubscriptionFailed, +} + +impl Timer { + pub fn num_notifications(&self) -> usize { + self.num_notifications } + + pub fn clock_frequency(&self) -> ClockFrequency { + self.clock_frequency + } + + pub fn get_current_clock(&self) -> ClockValue { + let num_ticks = + unsafe { syscalls::command(DRIVER_NUMBER, command_nr::GET_CLOCK_VALUE, 0, 0) }; + + ClockValue { + num_ticks: num_ticks, + clock_frequency: self.clock_frequency, + } + } + + pub fn stop_alarm(&self, alarm: Alarm) -> TockResult<(), StopAlarmError> { + let return_code = + unsafe { syscalls::command(DRIVER_NUMBER, command_nr::STOP_ALARM, alarm.alarm_id, 0) }; + + match return_code { + result::SUCCESS => Ok(()), + result::EALREADY => Err(TockValue::Expected(StopAlarmError::AlreadyDisabled)), + unexpected => Err(TockValue::Unexpected(unexpected)), + } + } + + pub fn set_alarm(&self, duration: Duration) -> TockResult { + let now = self.get_current_clock(); + let alarm_instant = + now.num_ticks() as usize + (duration.ms() as usize * self.clock_frequency.hz()) / 1000; + + let alarm_id = + unsafe { syscalls::command(DRIVER_NUMBER, command_nr::SET_ALARM, alarm_instant, 0) }; + + match alarm_id { + _ if alarm_id >= 0 => Ok(Alarm { + alarm_id: alarm_id as usize, + }), + result::ENOMEM => Err(TockValue::Expected(SetAlarmError::NoMemoryAvailable)), + unexpected => Err(TockValue::Unexpected(unexpected)), + } + } +} + +struct TimerCallback { + callback: CB, + clock_frequency: ClockFrequency, } -pub fn stop(ms: u32) { - unsafe { - command(DRIVER_NUMBER, 3, ms as isize); +impl SubscribableCallback for TimerCallback { + fn driver_number(&self) -> usize { + DRIVER_NUMBER + } + + fn subscribe_number(&self) -> usize { + subscribe_nr::SUBSCRIBE_CALLBACK + } + + fn call_rust(&mut self, clock_value: usize, alarm_id: usize, _: usize) { + (self.callback)( + ClockValue { + num_ticks: clock_value as isize, + clock_frequency: self.clock_frequency, + }, + Alarm { alarm_id }, + ); } } -pub fn delay_ms(ms: u32) { - extern fn cb(_: usize, _: usize, _: usize, expired_ptr: usize) { - let expired: &Cell = unsafe { - mem::transmute(expired_ptr) - }; - expired.set(true); +#[derive(Copy, Clone, Debug)] +pub struct ClockFrequency { + hz: usize, +} + +impl ClockFrequency { + pub fn hz(&self) -> usize { + self.hz } +} - let f: u32 = unsafe { command(DRIVER_NUMBER, GET_CLOCK_FREQUENCY, 0) as u32 }; - let point: u32 = unsafe { command(DRIVER_NUMBER, GET_CLOCK_VALUE, 0) as u32 } + ms * f / 1000; +#[derive(Copy, Clone, Debug)] +pub struct ClockValue { + num_ticks: isize, + clock_frequency: ClockFrequency, +} - let expired = Cell::new(false); - unsafe { - subscribe(cb, &expired as *const _ as usize); - set_alarm(point); - yieldk_for(|| expired.get() ); +impl ClockValue { + pub fn num_ticks(&self) -> isize { + self.num_ticks + } + + pub fn ms(&self) -> isize { + if self.num_ticks.abs() < isize::MAX / 1000 { + (1000 * self.num_ticks) / self.clock_frequency.hz() as isize + } else { + 1000 * (self.num_ticks / self.clock_frequency.hz() as isize) + } } } +pub struct Alarm { + alarm_id: usize, +} + +impl Alarm { + pub fn alarm_id(&self) -> usize { + self.alarm_id + } +} + +#[derive(Clone, Copy, Debug)] +pub enum StopAlarmError { + AlreadyDisabled, +} + +#[derive(Clone, Copy, Debug)] +pub enum SetAlarmError { + NoMemoryAvailable, +} + +#[derive(Copy, Clone, Debug)] +pub struct Duration { + ms: isize, +} + +impl Duration { + pub fn from_ms(ms: isize) -> Duration { + Duration { ms } + } + + pub fn ms(&self) -> isize { + self.ms + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 00000000..77dbc000 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,3 @@ +use core::marker::PhantomData; + +pub(crate) type PhantomLifetime<'a> = PhantomData<&'a ()>; diff --git a/thumbv7em-tock-eabi.json b/thumbv7em-tock-eabi.json index 82ab22cc..069626d0 100644 --- a/thumbv7em-tock-eabi.json +++ b/thumbv7em-tock-eabi.json @@ -1,22 +1,23 @@ { - "data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64", - "llvm-target": "thumbv7em-none-eabi", - "target-endian": "little", - "target-pointer-width": "32", - "arch": "arm", - "panic": "abort", - "os": "tock", - "morestack": false, - "executables": true, - "no-compiler-rt": false, - "relocation-model": "ropi-rwpi", - "linker": "arm-none-eabi-ld", - "linker-flavor": "ld", - "linker-is-gnu": true, - "disable-redzone": true, - "pre-link-args": { - "ld": [ - "-Tlayout.ld" - ] - } -} + "data-layout": "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64", + "llvm-target": "thumbv7em-none-eabi", + "target-endian": "little", + "target-pointer-width": "32", + "target-c-int-width": "32", + "arch": "arm", + "panic": "abort", + "os": "tock", + "morestack": false, + "executables": true, + "no-compiler-rt": false, + "relocation-model": "ropi-rwpi", + "linker": "arm-none-eabi-ld", + "linker-flavor": "ld", + "linker-is-gnu": true, + "disable-redzone": true, + "pre-link-args": { + "ld": [ + "-Tlayout.ld" + ] + } +} \ No newline at end of file diff --git a/tock b/tock new file mode 160000 index 00000000..d11eadd5 --- /dev/null +++ b/tock @@ -0,0 +1 @@ +Subproject commit d11eadd5a983bb057aec1ec70e525639876102df diff --git a/xargo b/xargo new file mode 160000 index 00000000..3b13437c --- /dev/null +++ b/xargo @@ -0,0 +1 @@ +Subproject commit 3b13437c131e406b599ec49491394d8941c829ff