Repository: tock/libtock-rs Branch: master Commit: 0766d8cf65b2 Files: 290 Total size: 973.9 KB Directory structure: gitextract_5tcgoj5o/ ├── .cargo/ │ └── config.toml ├── .github/ │ └── workflows/ │ ├── artifacts.yml │ ├── ci.yml │ ├── mac-os.yml │ └── size-diff.yml ├── .gitignore ├── .gitmodules ├── .ignore ├── .vscode/ │ └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── Targets.mk ├── apis/ │ ├── display/ │ │ └── screen/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── tests.rs │ ├── interface/ │ │ ├── buttons/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── buzzer/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── console/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ └── leds/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── tests.rs │ ├── kernel/ │ │ └── low_level_debug/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── tests.rs │ ├── net/ │ │ └── ieee802154/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── rx.rs │ │ └── tests.rs │ ├── peripherals/ │ │ ├── adc/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── alarm/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── gpio/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── i2c_master/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── i2c_master_slave/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── rng/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── spi_controller/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── sensors/ │ │ ├── air_quality/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── ambient_light/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── ninedof/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── proximity/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ ├── sound_pressure/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── lib.rs │ │ │ └── tests.rs │ │ └── temperature/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── tests.rs │ └── storage/ │ └── key_value/ │ ├── Cargo.toml │ └── src/ │ ├── lib.rs │ └── tests.rs ├── build.rs ├── build_scripts/ │ ├── Cargo.toml │ ├── README.md │ ├── libtock_layout.ld │ └── src/ │ └── lib.rs ├── demos/ │ ├── embedded_graphics/ │ │ ├── buttons/ │ │ │ ├── Cargo.toml │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── build.rs │ │ │ └── src/ │ │ │ └── main.rs │ │ └── spin/ │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── README.md │ │ ├── build.rs │ │ └── src/ │ │ └── main.rs │ ├── st7789/ │ │ ├── Cargo.toml │ │ ├── Makefile │ │ ├── build.rs │ │ └── src/ │ │ └── main.rs │ └── st7789-slint/ │ ├── Cargo.toml │ ├── Makefile │ ├── build.rs │ ├── src/ │ │ └── main.rs │ └── ui/ │ └── appwindow.slint ├── doc/ │ ├── CargoFeatures.md │ ├── CodeReview.md │ ├── Dependencies.md │ ├── DesignConsiderations.md │ ├── FaultDebuggingExample.md │ ├── MiriTips.md │ ├── Overview.md │ ├── PlatformDesignStory.md │ ├── Startup.md │ ├── Style.md │ ├── Testing.md │ └── UnitTestOwnership.md ├── examples/ │ ├── adc.rs │ ├── ambient_light.rs │ ├── blink.rs │ ├── buttons.rs │ ├── console.rs │ ├── gpio.rs │ ├── i2c_master_write_read.rs │ ├── i2c_slave_send_recv.rs │ ├── ieee802154_raw.rs │ ├── ieee802154_rx_raw.rs │ ├── ieee802154_rx_tx_raw.rs │ ├── ieee802154_tx_raw.rs │ ├── kv.rs │ ├── leds.rs │ ├── low_level_debug.rs │ ├── music.rs │ ├── ninedof.rs │ ├── proximity.rs │ ├── rng.rs │ ├── rng_async.rs │ ├── screen.rs │ ├── sound_pressure.rs │ ├── spi_controller_write_read.rs │ ├── temperature.rs │ └── usb_i2c_mctp.rs ├── libraries/ │ └── embedded_graphics_libtock/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── lib.rs │ └── tock_screen.rs ├── nightly/ │ └── rust-toolchain.toml ├── panic_handlers/ │ ├── debug_panic/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ └── small_panic/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── platform/ │ ├── Cargo.toml │ └── src/ │ ├── allow_ro.rs │ ├── allow_rw.rs │ ├── command_return.rs │ ├── command_return_tests.rs │ ├── constants.rs │ ├── default_config.rs │ ├── error_code.rs │ ├── error_code_tests.rs │ ├── exit_on_drop.rs │ ├── lib.rs │ ├── raw_syscalls.rs │ ├── register.rs │ ├── return_variant.rs │ ├── share/ │ │ ├── handle.rs │ │ ├── mod.rs │ │ ├── tests.rs │ │ └── tuple_impls.rs │ ├── subscribe.rs │ ├── syscalls.rs │ ├── syscalls_impl.rs │ ├── termination.rs │ └── yield_types.rs ├── runner/ │ ├── Cargo.toml │ └── src/ │ ├── elf2tab.rs │ ├── main.rs │ ├── output_processor.rs │ ├── qemu.rs │ └── tockloader.rs ├── runtime/ │ ├── Cargo.toml │ └── src/ │ ├── lib.rs │ ├── startup/ │ │ ├── asm_arm.s │ │ ├── asm_riscv32.s │ │ ├── asm_x86.s │ │ ├── mod.rs │ │ └── start_prototype.rs │ ├── syscalls_impl_arm.rs │ ├── syscalls_impl_riscv.rs │ └── syscalls_impl_x86.rs ├── rust-toolchain.toml ├── rustfmt.toml ├── src/ │ ├── lib.rs │ └── spi_controller.rs ├── syscalls_tests/ │ ├── Cargo.toml │ └── src/ │ ├── allow_ro.rs │ ├── allow_rw.rs │ ├── command_tests.rs │ ├── exit_on_drop.rs │ ├── lib.rs │ ├── memop_tests.rs │ ├── subscribe_tests.rs │ └── yield_tests.rs ├── tools/ │ └── print_sizes/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── ufmt/ │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── macros/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── src/ │ │ ├── helpers.rs │ │ ├── impls/ │ │ │ ├── array.rs │ │ │ ├── core.rs │ │ │ ├── ixx.rs │ │ │ ├── nz.rs │ │ │ ├── ptr.rs │ │ │ ├── std.rs │ │ │ ├── tuple.rs │ │ │ └── uxx.rs │ │ ├── impls.rs │ │ ├── lib.rs │ │ └── macros.rs │ ├── tests/ │ │ └── vs-std-write.rs │ ├── utils/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ └── write/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs └── unittest/ ├── Cargo.toml └── src/ ├── allow_db.rs ├── allow_db_test.rs ├── command_return.rs ├── driver_info.rs ├── exit_test/ │ ├── mod.rs │ └── tests.rs ├── expected_syscall.rs ├── fake/ │ ├── adc/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── air_quality/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── alarm/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── ambient_light/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── buttons/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── buzzer/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── console/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── gpio/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── ieee802154/ │ │ └── mod.rs │ ├── kernel.rs │ ├── kernel_tests.rs │ ├── key_value/ │ │ └── mod.rs │ ├── leds/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── low_level_debug/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── mod.rs │ ├── ninedof/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── proximity/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── screen/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── sound_pressure/ │ │ ├── mod.rs │ │ └── tests.rs │ ├── syscall_driver.rs │ ├── syscalls/ │ │ ├── allow_ro_impl.rs │ │ ├── allow_ro_impl_tests.rs │ │ ├── allow_rw_impl.rs │ │ ├── allow_rw_impl_tests.rs │ │ ├── command_impl.rs │ │ ├── command_impl_tests.rs │ │ ├── exit_impl.rs │ │ ├── exit_impl_tests.rs │ │ ├── memop_impl.rs │ │ ├── memop_impl_tests.rs │ │ ├── mod.rs │ │ ├── raw_syscalls_impl.rs │ │ ├── raw_syscalls_impl_tests.rs │ │ ├── subscribe_impl.rs │ │ ├── subscribe_impl_tests.rs │ │ ├── yield_impl.rs │ │ └── yield_impl_tests.rs │ └── temperature/ │ ├── mod.rs │ └── tests.rs ├── kernel_data.rs ├── lib.rs ├── share_data.rs ├── syscall_log.rs └── upcall.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] rriscv32imac = "run --release --target=riscv32imac-unknown-none-elf --example" rrv32imac = "rriscv32imac" rriscv32imc = "run --release --target=riscv32imc-unknown-none-elf --example" rrv32imc = "rriscv32imc" rthumbv7em = "run --release --target=thumbv7em-none-eabi --example" rtv7em = "rthumbv7em" # Common settings for all embedded targets [target.'cfg(any(target_arch = "arm", target_arch = "riscv32", target_arch = "x86"))'] rustflags = [ "-C", "relocation-model=static", "-C", "link-arg=-icf=all", ] runner = ["cargo", "run", "-p", "runner", "--release"] ================================================ FILE: .github/workflows/artifacts.yml ================================================ name: Artifacts on: push: branches: [master] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 - name: Install dependencies run: | cargo install elf2tab - name: Build LEDs run: | make -j2 EXAMPLE=leds apollo3 make -j2 EXAMPLE=leds hail make -j2 EXAMPLE=leds imix make -j2 EXAMPLE=leds nucleo_f429zi make -j2 EXAMPLE=leds nucleo_f446re make -j2 EXAMPLE=leds nrf52840 make -j2 EXAMPLE=leds opentitan make -j2 EXAMPLE=leds hifive1 make -j2 EXAMPLE=leds nrf52 - name: Build Low Level Debug run: | make -j2 EXAMPLE=low_level_debug apollo3 make -j2 EXAMPLE=low_level_debug hail make -j2 EXAMPLE=low_level_debug imix make -j2 EXAMPLE=low_level_debug nucleo_f429zi make -j2 EXAMPLE=low_level_debug nucleo_f446re make -j2 EXAMPLE=low_level_debug nrf52840 make -j2 EXAMPLE=low_level_debug opentitan make -j2 EXAMPLE=low_level_debug hifive1 make -j2 EXAMPLE=low_level_debug nrf52 - name: Archive artifacts uses: actions/upload-artifact@v4 with: name: libtock-rs examples path: target/tbf ================================================ FILE: .github/workflows/ci.yml ================================================ # This workflow provides automated testing. It builds and runs tests on each PR. name: ci # We want to run CI on all pull requests. Additionally, GitHub actions merge # queue needs workflows to run on the `merge_queue` trigger to block merges on # them. on: pull_request: merge_group: jobs: ci: # Using ubuntu-latest can cause breakage when ubuntu-latest is updated to # point at a new Ubuntu version. Instead, explicitly specify the version, so # we can update when we need to. This *could* break if we don't update it # until support for this version is dropped, but it is likely we'll have a # reason to update to a newer Ubuntu before then anyway. runs-on: ubuntu-22.04 steps: # Clones a single commit from the libtock-rs repository. The commit cloned # is a merge commit between the PR's target branch and the PR's source. # Note that we checkout submodules so that we can invoke Tock's CI setup # scripts, but we do not recursively checkout submodules as we need Tock's # makefile to set up the qemu submodule itself. - name: Clone repository uses: actions/checkout@v3 with: submodules: true # The main test step. We let the makefile do most of the work because the # makefile can be tested locally. We experimentally determined that -j2 is # optimal for the Azure Standard_DS2_v2 VM, which is the VM type used by # GitHub Actions at the time of this writing. # # We have to append the "-D warnings" flag to .cargo/config.toml rather # than using the RUSTFLAGS environment variable because if we set # RUSTFLAGS cargo will ignore the rustflags config in .cargo/config, # breaking relocation. - name: Build and Test run: | sudo apt-get install ninja-build cd "${GITHUB_WORKSPACE}" echo "[target.'cfg(all())']" >> .cargo/config.toml echo 'rustflags = ["-D", "warnings"]' >> .cargo/config.toml make -j2 setup make -j2 test make demos ================================================ FILE: .github/workflows/mac-os.yml ================================================ # This workflow verifies libtock-rs is usable on Mac OS. name: ci-mac-os # We run this workflow during pull request review, but not as a required status # for GitHub actions merge-queue merges. We can change this if the workflow is # reasonably quick and reliable. on: pull_request jobs: ci-mac-os: runs-on: macos-latest steps: # Clones a single commit from the libtock-rs repository. The commit cloned # is a merge commit between the PR's target branch and the PR's source. - name: Clone repository uses: actions/checkout@v3 - name: Build and Test run: | cd "${GITHUB_WORKSPACE}" LIBTOCK_PLATFORM=nrf52 cargo build -p libtock \ --target=thumbv7em-none-eabi LIBTOCK_PLATFORM=hifive1 cargo build -p libtock \ --target=riscv32imac-unknown-none-elf ================================================ FILE: .github/workflows/size-diff.yml ================================================ # Calculates the size diffs for the each example binary. Runs when a pull # request is created or modified. name: size-diff # We want to run this on all pull requests. Additionally, GitHub actions merge # queue needs workflows to run on the `merge_queue` trigger to block merges on # them. on: pull_request: merge_group: jobs: size-diff: # Using ubuntu-latest can cause breakage when ubuntu-latest is updated to # point at a new Ubuntu version. Instead, explicitly specify the version, so # we can update when we need to. This *could* break if we don't update it # until support for this version is dropped, but it is likely we'll have a # reason to update to a newer Ubuntu before then anyway. runs-on: ubuntu-22.04 steps: # Clones a single commit from the libtock-rs repository. The commit cloned # is a merge commit between the PR's target branch and the PR's source. # We'll later add another commit (the pre-merge target branch) to the # repository. - name: Clone repository uses: actions/checkout@v3 # The main diff script. Stores the sizes of the example binaries for both # the merge commit and the target branch. We display the diff in a # separate step to make it easy to navigate to in the GitHub Actions UI. # # If the build on master doesn't work (`make -j2 examples` fails), we # output a warning message and ignore the error. Ignoring the error # prevents this workflow from blocking PRs that fix a broken build in # master. - name: Compute sizes run: | UPSTREAM_REMOTE_NAME="${UPSTREAM_REMOTE_NAME:-origin}" GITHUB_BASE_REF="${GITHUB_BASE_REF:-master}" cd "${GITHUB_WORKSPACE}" make -j2 examples # The VM this runs on has 2 logical cores. cargo run --release -p print_sizes >'${{runner.temp}}/merge-sizes' git remote set-branches "${UPSTREAM_REMOTE_NAME}" "${GITHUB_BASE_REF}" git fetch --depth=1 "${UPSTREAM_REMOTE_NAME}" "${GITHUB_BASE_REF}" git checkout "${UPSTREAM_REMOTE_NAME}/${GITHUB_BASE_REF}" make -j2 examples && \ cargo run --release -p print_sizes >'${{runner.temp}}/base-sizes' || \ echo 'Broken build on the master branch.' # Computes and displays the size diff. diff returns a nonzero status code # if the files differ, and GitHub interprets a nonzero status code as an # error. To avoid GitHub interpreting a difference as an error, we add # || exit 0 to the command. This also prevents the workflow from failing # if the master build is broken and we didn't generate base-sizes. - name: Size diff run: diff '${{runner.temp}}/base-sizes' '${{runner.temp}}/merge-sizes' || exit 0 ================================================ FILE: .gitignore ================================================ /Cargo.lock /nightly/target /target /demos/*/target /demos/*/*/target ================================================ FILE: .gitmodules ================================================ [submodule "tock"] path = tock url = https://github.com/tock/tock.git ================================================ FILE: .ignore ================================================ # Cargo and GitHub Actions keep their configs in hidden directories. Some search # tools skip hidden directories by default. This overrides that, and makes them # search .cargo and .github. !.cargo/ !.github/ # Tell search tools to ignore the Tock submodule. Usually when someone wants to # search this repository they want to search libtock-rs' codebase, not the Tock # kernel. /tock/ ================================================ FILE: .vscode/settings.json ================================================ { "editor.formatOnSave": true, "rust.all_targets": true, "rust.clippy_preference": "on" } ================================================ FILE: CHANGELOG.md ================================================ # Releases ## 0.2.0 (WIP) ### Comprehensive Changes - Many functions are asynchronous - To create an `async` main function you can use the attribute `#[libtock::main]` - To retrieve the value of an asynchronous `value`, use `value.await` - This is only possible within an `async fn`, so either - Make the caller `fn` of `.await` an `async fn` - Not recommended: Use `libtock::executor::block_on(value)` to retrieve the `value` - Most API functions, including `main()`, return a `Result` - All drivers can exclusively be retrieved by `retrieve_drivers` which returns a `Drivers` singleton. Drivers can be shared between different tasks only if it is safe to do so. - The low-level functions have been moved to a new crate called `libtock-core`. This crate is intended to be less experimental and more stable. ### Changed APIs - The basic APIs have been made consistent. They are initialized via driver factories and no longer require a `WithCallback` object, s.t. the callback subscription is more intuitive. The affected APIs are: - LEDs - Buttons - GPIO - Temperature - ADC (partially) - The timer API now supports concurrent sleep operations ### Syscalls - `syscalls::subscribe` is actually usable - `syscalls::yieldk_for` is no longer available - Yielding manually is discouraged as it conflicts with Rust's safety guarantees. If you need to wait for a condition, use `futures::wait_until` and `.await`. - `syscalls::yieldk` has become `unsafe` for the same reason - `syscalls::command` is no longer `unsafe` - The low-level syscalls have been moved to `syscalls::raw` - `syscalls::subscribe_ptr` becomes `syscalls::raw::subscribe` - `syscalls::allow_ptr` becomes `syscalls::raw::allow` ### Miscellaneous - Flashing examples is no longer restricted to the nRF52 DK board - `./run_example.sh` has been deleted - Instead, use `PLATFORM= cargo r `. This will build the app for your CPU architecture and platform-specific memory layout and flash it via J-Link to your board - Targets without support for atomics can be built - The `TockAllocator` is no longer included by default and needs to to be opted-in via `--features=alloc` - `hardware_test.rs` is now called `libtock_test.rs` to make clear that the intent is to test the correctness of `libtock-rs`, not the hardware or the kernel - The panic handler can now be customized using the `custom_panic_handler` feature - The error alloc handler can now be customized using the `custom_alloc_error_handler` feature ## a8bb4fa9be504517d5533511fd8e607ea61f1750 (0.1.0) - First and highly experimental `libtock-rs` API ================================================ FILE: CONTRIBUTING.md ================================================ # Tests Our aim is to provide a number of tests to be safe from regression. ## Compilation `libtock-rs` currently has the following compilation targets - `riscv32imac-unknown-none-elf` - `riscv32imc-unknown-none-elf` - `thumbv7em-none-eabi` You can trigger a test build of the library and the examples using `make test`. You can run the library's test suite using `make test`. # PR Review Workflow Our code review practices are documented in our [Code Review](doc/CodeReview.md) document. ================================================ FILE: Cargo.toml ================================================ [package] authors = ["Tock Project Developers "] categories = ["embedded", "no-std", "os"] description = """Tock Rust userspace library collection. Provides all the \ tools needed to create a Tock Rust process binary.""" edition = "2021" license = "Apache-2.0 OR MIT" name = "libtock" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true version = "0.1.0" [workspace.package] # This must be kept in sync with rust-toolchain.toml; please see that file for # more information. rust-version = "1.87" [features] rust_embedded = [ "embedded-hal", "libtock_platform/rust_embedded", "libtock_gpio/rust_embedded", ] [dependencies] libtock_adc = { path = "apis/peripherals/adc" } libtock_air_quality = { path = "apis/sensors/air_quality" } libtock_alarm = { path = "apis/peripherals/alarm" } libtock_ambient_light = { path = "apis/sensors/ambient_light" } libtock_buttons = { path = "apis/interface/buttons" } libtock_buzzer = { path = "apis/interface/buzzer" } libtock_console = { path = "apis/interface/console" } libtock_debug_panic = { path = "panic_handlers/debug_panic" } libtock_gpio = { path = "apis/peripherals/gpio" } libtock_i2c_master = { path = "apis/peripherals/i2c_master" } libtock_ieee802154 = { path = "apis/net/ieee802154" } libtock_i2c_master_slave = { path = "apis/peripherals/i2c_master_slave" } libtock_key_value = { path = "apis/storage/key_value" } libtock_leds = { path = "apis/interface/leds" } libtock_low_level_debug = { path = "apis/kernel/low_level_debug" } libtock_ninedof = { path = "apis/sensors/ninedof" } libtock_platform = { path = "platform" } libtock_proximity = { path = "apis/sensors/proximity" } libtock_screen = { path = "apis/display/screen" } libtock_rng = { path = "apis/peripherals/rng" } libtock_runtime = { path = "runtime" } libtock_small_panic = { path = "panic_handlers/small_panic" } libtock_sound_pressure = { path = "apis/sensors/sound_pressure" } libtock_spi_controller = { path = "apis/peripherals/spi_controller" } libtock_temperature = { path = "apis/sensors/temperature" } embedded-hal = { version = "1.0", optional = true } [build-dependencies] libtock_build_scripts = { path = "build_scripts" } [profile.dev] debug = true lto = true panic = "abort" [profile.release] debug = true lto = true panic = "abort" opt-level = "z" codegen-units = 1 [workspace] exclude = ["tock"] members = [ "apis/interface/buttons", "apis/interface/buzzer", "apis/interface/console", "apis/interface/leds", "apis/kernel/low_level_debug", "apis/peripherals/adc", "apis/peripherals/alarm", "apis/peripherals/gpio", "apis/peripherals/i2c_master", "apis/peripherals/i2c_master_slave", "apis/peripherals/rng", "apis/sensors/air_quality", "apis/sensors/ambient_light", "apis/sensors/ninedof", "apis/display/screen", "apis/sensors/proximity", "apis/sensors/temperature", "apis/storage/key_value", "libraries/embedded_graphics_libtock", "panic_handlers/debug_panic", "panic_handlers/small_panic", "platform", "runner", "runtime", "syscalls_tests", "tools/print_sizes", "ufmt", "unittest", ] ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2016 The Tock Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # Make uses /bin/sh by default, which is a different shell on different OSes. # Specify Bash instead so we don't have to test against a variety of shells. SHELL := /usr/bin/env bash # By default, let's print out some help .PHONY: usage usage: @echo "$$(tput bold)Welcome to libtock-rs!$$(tput sgr0)" @echo @echo "First things first, if you haven't yet, check out Tocks's doc/Getting_Started." @echo "After that read the README from libtock-rs" @echo "You'll need to install a few requirements before we get going." @echo @echo "The next step is to choose a board to build Tock for. Mainline" @echo "libtock-rs currently includes support for the following platforms:" @echo " - apollo3" @echo " - clue_nrf52840" @echo " - esp32_c3_devkitm_1" @echo " - hail" @echo " - hifive1" @echo " - imxrt1050" @echo " - microbit_v2" @echo " - nrf52" @echo " - nrf52840" @echo " - nucleo_f429zi" @echo " - nucleo_f446re" @echo " - opentitan" @echo " - qemu_rv32_virt" @echo " - stm32f3discovery" @echo " - stm32f412gdiscovery" @echo " - esp32_c3_devkitm_1" @echo " - clue_nrf52840" @echo " - raspberry_pi_pico" @echo " - pico_explorer_base" @echo @echo "Run 'make setup' to setup Rust to build libtock-rs." @echo "Run 'make EXAMPLE=<>' to build EXAMPLE for that board." @echo "Run 'make flash- EXAMPLE=<>' to flash EXAMPLE to a tockloader-supported board." @echo "Run 'make qemu-example EXAMPLE=<>' to run EXAMPLE in QEMU" @echo "Run 'make test' to test any local changes you have made" @echo "Run 'make print-sizes' to print size data for the example binaries" ifdef FEATURES features=--features=$(FEATURES) endif ifndef DEBUG release=--release artifact_dir=release else artifact_dir=debug endif # Rustup currently lacks the locking needed for concurrent use: # https://github.com/rust-lang/rustup/issues/988. In particular, running # concurrent cargo commands with a missing toolchain results in parallel rustup # instances installing the same toolchain, corrupting that toolchain. To # mitigate that issue, every target that uses the main (MSRV) toolchain should # depend transitively on the `toolchain` target, so that the toolchain is # installed before it is invoked concurrently. Note that we don't need to do # this for the nightly toolchain because the nightly toolchain is only used by # the `test` target, so this Makefile won't invoke it concurrently. .PHONY: toolchain toolchain: cargo -V .PHONY: setup setup: setup-qemu toolchain cargo install elf2tab # Sets up QEMU in the tock/ directory. We use Tock's QEMU which may contain # patches to better support boards that Tock supports. .PHONY: setup-qemu setup-qemu: CI=true $(MAKE) -C tock ci-setup-qemu # Builds a Tock 2.0 kernel for the HiFive board for use by QEMU tests. .PHONY: kernel-hifive kernel-hifive: $(MAKE) -C tock/boards/hifive1 \ $(CURDIR)/tock/target/riscv32imac-unknown-none-elf/release/hifive1.elf # Builds a Tock kernel for the OpenTitan board on the cw310 FPGA for use by QEMU # tests. .PHONY: kernel-opentitan kernel-opentitan: CARGO_TARGET_RISCV32IMC_UNKNOWN_NONE_ELF_RUNNER="[]" \ $(MAKE) -C tock/boards/opentitan/earlgrey-cw310 \ $(CURDIR)/tock/target/riscv32imc-unknown-none-elf/release/earlgrey-cw310.elf # Prints out the sizes of the example binaries. .PHONY: print-sizes print-sizes: examples toolchain cargo run --release -p print_sizes # Runs a libtock example in QEMU on a simulated HiFive board. .PHONY: qemu-example qemu-example: kernel-hifive toolchain LIBTOCK_PLATFORM="hifive1" cargo run --example "$(EXAMPLE)" -p libtock \ --release --target=riscv32imac-unknown-none-elf -- --deploy qemu # Build the examples on both a RISC-V target and an ARM target. We pick # opentitan as the RISC-V target because it lacks atomics. .PHONY: examples examples: toolchain LIBTOCK_PLATFORM=nrf52 cargo build --examples --release \ --target=thumbv7em-none-eabi LIBTOCK_PLATFORM=opentitan cargo build --examples --release \ --target=riscv32imc-unknown-none-elf # Arguments to pass to cargo to exclude crates that require a Tock runtime. # This is largely libtock_runtime and crates that depend on libtock_runtime. # Used when we need to build a crate for the host OS, as libtock_runtime only # supports running on Tock. EXCLUDE_RUNTIME := --exclude libtock --exclude libtock_runtime \ --exclude libtock_debug_panic --exclude libtock_small_panic --exclude embedded_graphics_libtock # Arguments to pass to cargo to exclude crates that cannot be tested by Miri. In # addition to excluding libtock_runtime, Miri also cannot test proc macro crates # (and in fact will generate broken data that causes cargo test to fail). EXCLUDE_MIRI := $(EXCLUDE_RUNTIME) --exclude ufmt-macros # Arguments to pass to cargo to exclude `std` and crates that depend on it. Used # when we build a crate for an embedded target, as those targets lack `std`. EXCLUDE_STD := --exclude libtock_unittest --exclude print_sizes \ --exclude runner --exclude syscalls_tests \ --exclude libtock_build_scripts .PHONY: test test: examples cargo test $(EXCLUDE_RUNTIME) --workspace LIBTOCK_PLATFORM=nrf52 cargo fmt --all -- --check cargo clippy --all-targets $(EXCLUDE_RUNTIME) --workspace LIBTOCK_PLATFORM=nrf52 cargo clippy $(EXCLUDE_STD) \ --target=thumbv7em-none-eabi --workspace LIBTOCK_PLATFORM=hifive1 cargo clippy $(EXCLUDE_STD) \ --target=riscv32imac-unknown-none-elf --workspace cd nightly && \ MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-symbolic-alignment-check" \ cargo miri test $(EXCLUDE_MIRI) --manifest-path=../Cargo.toml \ --target-dir=target --workspace echo '[ SUCCESS ] libtock-rs tests pass' include Targets.mk $(ELF_TARGETS): toolchain LIBTOCK_LINKER_FLASH=$(F) LIBTOCK_LINKER_RAM=$(R) cargo build --example $(EXAMPLE) $(features) --target=$(T) $(release) @mkdir -p target/$(A).$(F).$(R)/ @cp target/$(T)/$(artifact_dir)/examples/$(EXAMPLE) target/$(A).$(F).$(R)/ $(eval ELF_LIST += target/$(A).$(F).$(R)/$(EXAMPLE),$(A).$(F).$(R)) # This target (`make tab`) is not parallel-safe .PHONY: tab tab: $(ELF_TARGETS) mkdir -p target/tab elf2tab --kernel-major 2 --kernel-minor 1 -n $(EXAMPLE) -o target/tab/$(EXAMPLE).tab --stack 1024 --minimum-footer-size 256 $(ELF_LIST) # Creates the `make EXAMPLE=` targets. Arguments: # 1) The name of the platform to build for. # 2) The target architecture the platform uses. # # A different --target-dir is passed for each platform to prevent race # conditions between concurrent cargo run invocations. See # https://github.com/tock/libtock-rs/issues/366 for more information. define platform_build .PHONY: $(1) $(1): toolchain LIBTOCK_PLATFORM=$(1) cargo run --example $(EXAMPLE) $(features) \ $(release) --target=$(2) --target-dir=target/$(1) mkdir -p target/tbf/$(1) cp target/$(1)/$(2)/release/examples/$(EXAMPLE).{tab,tbf} \ target/tbf/$(1) endef # Creates the `make flash- EXAMPLE=` targets. Arguments: # 1) The name of the platform to flash for. define platform_flash .PHONY: flash-$(1) flash-$(1): toolchain LIBTOCK_PLATFORM=$(1) cargo run --example $(EXAMPLE) $(features) \ $(release) --target=$(2) --target-dir=target/flash-$(1) -- \ --deploy=tockloader endef $(eval $(call platform_build,apollo3,thumbv7em-none-eabi)) $(eval $(call platform_build,esp32_c3_devkitm_1,riscv32imc-unknown-none-elf)) $(eval $(call platform_build,hail,thumbv7em-none-eabi)) $(eval $(call platform_flash,hail,thumbv7em-none-eabi)) $(eval $(call platform_build,imix,thumbv7em-none-eabi)) $(eval $(call platform_flash,imix,thumbv7em-none-eabi)) $(eval $(call platform_build,microbit_v2,thumbv7em-none-eabi)) $(eval $(call platform_flash,microbit_v2,thumbv7em-none-eabi)) $(eval $(call platform_build,nucleo_f429zi,thumbv7em-none-eabi)) $(eval $(call platform_build,nucleo_f446re,thumbv7em-none-eabi)) $(eval $(call platform_build,nrf52840,thumbv7em-none-eabi)) $(eval $(call platform_flash,nrf52840,thumbv7em-none-eabi)) $(eval $(call platform_build,raspberry_pi_pico,thumbv6m-none-eabi)) $(eval $(call platform_build,pico_explorer_base,thumbv6m-none-eabi)) $(eval $(call platform_build,nano33ble,thumbv6m-none-eabi)) $(eval $(call platform_build,nano_rp2040_connect,thumbv6m-none-eabi)) $(eval $(call platform_build,qemu_rv32_virt,riscv32imac-unknown-none-elf)) $(eval $(call platform_build,stm32f3discovery,thumbv7em-none-eabi)) $(eval $(call platform_build,stm32f412gdiscovery,thumbv7em-none-eabi)) $(eval $(call platform_build,opentitan,riscv32imc-unknown-none-elf)) $(eval $(call platform_build,hifive1,riscv32imac-unknown-none-elf)) $(eval $(call platform_build,nrf52,thumbv7em-none-eabi)) $(eval $(call platform_flash,nrf52,thumbv7em-none-eabi)) $(eval $(call platform_build,imxrt1050,thumbv7em-none-eabi)) $(eval $(call platform_build,msp432,thumbv7em-none-eabi)) $(eval $(call platform_build,clue_nrf52840,thumbv7em-none-eabi)) $(eval $(call platform_flash,clue_nrf52840,thumbv7em-none-eabi)) .PHONY: demos demos: $(MAKE) -C demos/embedded_graphics/spin $(MAKE) -C demos/embedded_graphics/buttons $(MAKE) -C demos/st7789 $(MAKE) -C demos/st7789-slint # clean cannot safely be invoked concurrently with other actions, so we don't # need to depend on toolchain. We also manually remove the nightly toolchain's # target directory, in case the user doesn't want to install the nightly # toolchain. .PHONY: clean clean: cargo clean rm -fr nightly/target/ cd demos/st7789 && cargo clean $(MAKE) -C tock clean ================================================ FILE: README.md ================================================ ![Build Status](https://github.com/tock/libtock-rs/workflows/ci/badge.svg) # libtock-rs Rust userland library for Tock Generally this library was tested with Tock [Release 2.1.1](https://github.com/tock/tock/releases/tag/release-2.1.1). The library should work on all Tock boards, but currently apps must be compiled for the flash and RAM address they are executed at. See [Fix relocation](https://github.com/tock/libtock-rs/issues/28) for more details. You may either compile a process binary especially for your board and use only a single application written in rust at a time, or use the `make tab` target that builds examples for a series of likely useful flash and RAM addresses. ## Getting Started 1. Ensure you have [rustup](https://www.rustup.rs/) installed. 1. Clone the repository: ```shell git clone --recursive https://github.com/tock/libtock-rs cd libtock-rs ``` 1. Install the dependencies: ```shell make setup ``` 1. Use `make` to build examples ```shell make nrf52 EXAMPLE=console # Builds the console example for the nrf52 ``` ## Using libtock-rs The easiest way to start using libtock-rs is adding an example to the `examples/` folder. We recommend starting by copying the `console` example, as it is a simple example that shows you how to perform normal debug prints. ### Building for a specific board To build your example for your board you can use ```shell make EXAMPLE= ``` An example can be flashed to your board after the build process by running: ```shell make flash- EXAMPLE= ``` This script does the following steps for you: - cross-compile your program - create a TAB (tock application bundle) - if you have a J-Link compatible board connected: flash this TAB to your board (using tockloader) ### Enabling rust-embedded support libtock-rs can be built to be compatible with the rust-embedded [embedded_hal](https://docs.rs/embedded-hal/1.0.0/embedded_hal/index.html) by including the following when running `make` ```shell FEATURES=rust_embedded ``` If using libtock-rs or a sub-crate as a cargo dependency the `rust_embedded` can also be enabled via Cargo. ### Building a generic TAB (Tock Application Bundle) file To build your example for a variety of boards you can use ```shell make tab EXAMPLE= ``` To install the tab use tockloader ```shell tockloader install target/tab/ ``` Tockloader will determine which compiled version with the correct flash and RAM addresses to use. ## License libtock-rs is licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. Submodules, as well as the code in the `ufmt` directory, have their own licenses. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. The contribution guidelines can be found here: [contribution guidelines](CONTRIBUTING.md) ================================================ FILE: Targets.mk ================================================ # Helper makefile to define build location targets for many common tock # platforms. # # To use: # # include Targets.mk # Helper functions to define make targets to build for specific (flash, ram, # target) compilation tuples. # # Inspiration from these answers: # - https://stackoverflow.com/a/50357925 # - https://stackoverflow.com/a/9458230 # # To create a compilation target for a specific architecture with specific flash # and RAM addresses, use `fixed-target`: # # ``` # $(call fixed-target, F=0x00030000 R=0x20008000 T=thumbv7em-none-eabi A=cortex-m4) # ``` # # The "arguments" if you will are: # - F = Flash Address: The address in flash the app is compiled for. # - R = RAM Address: The address in RAM the app is compiled for. # - T = Target: The cargo target to compile for. # - A = Architecture: The Tock architecture name the target corresponds to. # # This technique uses two make variables internally to keep track of state: # - `ELF_TARGETS`: This is the list of unique targets for each compilation # tuple. Each target invokes `cargo build` with the specified settings. # - `ELF_LIST`: The is a list of .elf paths of the generated elfs (one per # compilation tuple). This is passed to `elf2tab` to generate the output .tab # file. # # Internally, what `fixed-target` does is define a new make target named the # join of all of the F/R/T/A variables (with the `=` characters removed) and # then assigns target variables to that new target to represent the compilation # tuple values. concat = $(subst =,,$(subst $(eval ) ,,$1)) fixed-target = $(foreach A,$1,$(eval $(call concat,$1): $A)) $(eval ELF_TARGETS += $(call concat,$1)) $(call fixed-target, F=0x00030000 R=0x20008000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00038000 R=0x20010000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00040000 R=0x10002000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00048000 R=0x1000a000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00040000 R=0x20008000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00042000 R=0x2000a000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00048000 R=0x20010000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00080000 R=0x20006000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x00088000 R=0x2000e000 T=thumbv7em-none-eabi A=cortex-m4) $(call fixed-target, F=0x403b0000 R=0x3fca2000 T=riscv32imc-unknown-none-elf A=rv32imc) $(call fixed-target, F=0x40440000 R=0x3fcaa000 T=riscv32imc-unknown-none-elf A=rv32imc) $(call fixed-target, F=0x80100000 R=0x80300000 T=riscv32imac-unknown-none-elf A=rv32imac) $(call fixed-target, F=0x80110000 R=0x80310000 T=riscv32imac-unknown-none-elf A=rv32imac) $(call fixed-target, F=0x80130000 R=0x80330000 T=riscv32imac-unknown-none-elf A=rv32imac) $(call fixed-target, F=0x80180000 R=0x80380000 T=riscv32imac-unknown-none-elf A=rv32imac) $(call fixed-target, F=0x10020000 R=0x20004000 T=thumbv6m-none-eabi A=cortex-m0) $(call fixed-target, F=0x10028000 R=0x2000c000 T=thumbv6m-none-eabi A=cortex-m0) $(call fixed-target, F=0x10040000 R=0x20020000 T=thumbv8m.main-none-eabi A=cortex-m33) $(call fixed-target, F=0x10060000 R=0x20028000 T=thumbv8m.main-none-eabi A=cortex-m33) ================================================ FILE: apis/display/screen/Cargo.toml ================================================ [package] name = "libtock_screen" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock display driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/display/screen/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::allow_ro::AllowRo; use libtock_platform::share; use libtock_platform::subscribe::Subscribe; use libtock_platform::{self as platform, DefaultConfig}; use libtock_platform::{ErrorCode, Syscalls}; pub struct Screen(S, C); impl Screen { /// Check if the Screen driver exists pub fn exists() -> Result<(), ErrorCode> { let val = S::command(DRIVER_NUM, command::EXISTS, 0, 0).is_success(); if val { Ok(()) } else { Err(ErrorCode::Fail) } } /// Perform initial screen setup pub fn screen_setup() -> Result { S::command(DRIVER_NUM, command::SCREEN_SETUP, 0, 0).to_result() } /// Turn on Screen power pub fn set_power(value: usize) -> Result<(), ErrorCode> { if value != 0 { S::command(DRIVER_NUM, command::SET_POWER, value as u32, 0).to_result() } else { Err(ErrorCode::Invalid) } } /// Set screen brightness, wait for completion via subscribe pub fn set_brightness(value: usize) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::SET_BRIGHTNESS, value as u32, 0) .to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Turn on screen color inversion pub fn set_invert_on() -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::SET_INVERT_ON, 0, 0).to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Turn off screen color inversion pub fn set_invert_off() -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::SET_INVERT_OFF, 0, 0).to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Set inversion using a numeric value (non-zero = on) pub fn set_invert(value: usize) -> Result<(), ErrorCode> { if value != 0 { S::command(DRIVER_NUM, command::SET_INVERT, value as u32, 0).to_result() } else { Err(ErrorCode::Invalid) } } /// Get the number of supported resolution modes pub fn get_resolution_modes_count() -> Result { S::command(DRIVER_NUM, command::GET_RESOLUTION_MODES_COUNT, 0, 0).to_result() } /// Get width and height for a given resolution index pub fn get_resolution_width_height(index: usize) -> Result<(u32, u32), ErrorCode> { S::command( DRIVER_NUM, command::GET_RESOLUTION_WIDTH_HEIGHT, index as u32, 0, ) .to_result() } /// Get the number of supported pixel modes pub fn pixel_modes_count() -> Result { S::command(DRIVER_NUM, command::PIXEL_MODES_COUNT, 0, 0).to_result() } /// Get the pixel format at a specific index pub fn pixel_format(index: usize) -> Result { S::command(DRIVER_NUM, command::PIXEL_FORMAT, index as u32, 0).to_result() } /// Get the current rotation of the screen pub fn get_rotation() -> Result { let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; let val = S::command(DRIVER_NUM, command::GET_ROTATION, 0, 0).to_result(); val?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return val; } } }) } /// Set the screen rotation pub fn set_rotation(rotation: usize) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::SET_ROTATION, rotation as u32, 0) .to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Get the currently set screen resolution pub fn get_resolution() -> Result<(u32, u32), ErrorCode> { S::command(DRIVER_NUM, command::GET_RESOLUTION, 0, 0).to_result() } /// Set the screen resolution pub fn set_resolution(width: usize, height: usize) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command( DRIVER_NUM, command::SET_RESOLUTION, width as u32, height as u32, ) .to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Get the currently set pixel format pub fn get_pixel_format() -> Result { S::command(DRIVER_NUM, command::GET_PIXEL_FORMAT, 0, 0).to_result() } /// Set the pixel format pub fn set_pixel_format(format: usize) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::SET_PIXEL_FORMAT, format as u32, 0) .to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Define the region of the screen that will be written to pub fn set_write_frame(x: u32, y: u32, width: u32, height: u32) -> Result<(), ErrorCode> { let data1: u32 = ((x & 0xFFFF) << 16_u8) | (y & 0xFFFF); let data2: u32 = ((width & 0xFFFF) << 16_u8) | (height & 0xFFFF); let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::SET_WRITE_FRAME, data1, data2).to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Write data to the screen using the given buffer pub fn write(s: &[u8]) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope::< ( AllowRo<_, DRIVER_NUM, { allow_ro::WRITE_BUFFER_ID }>, Subscribe<_, DRIVER_NUM, { subscribe::WRITE }>, ), _, _, >(|handle| { let (allow_ro, subscribe) = handle.split(); S::allow_ro::(allow_ro, s)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::WRITE, s.len() as u32, 0).to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Fill the screen pub fn fill(s: &mut [u8], color: u16) -> Result<(), ErrorCode> { if s.len() >= 2 { s[0] = ((color >> 8) & 0xFF) as u8; s[1] = (color & 0xFF) as u8; let called: Cell> = Cell::new(None); share::scope::< ( AllowRo<_, DRIVER_NUM, { allow_ro::WRITE_BUFFER_ID }>, Subscribe<_, DRIVER_NUM, { subscribe::WRITE }>, ), _, _, >(|handle| { let (allow_ro, subscribe) = handle.split(); S::allow_ro::(allow_ro, s)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::FILL, 0, 0).to_result::<(), _>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } else { Err(ErrorCode::Fail) } } } pub trait Config: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config { } impl Config for T { } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x90001; // Command IDs #[allow(unused)] mod command { pub const EXISTS: u32 = 0; pub const SCREEN_SETUP: u32 = 1; pub const SET_POWER: u32 = 2; pub const SET_BRIGHTNESS: u32 = 3; pub const SET_INVERT_ON: u32 = 4; pub const SET_INVERT_OFF: u32 = 5; pub const SET_INVERT: u32 = 6; pub const GET_RESOLUTION_MODES_COUNT: u32 = 11; pub const GET_RESOLUTION_WIDTH_HEIGHT: u32 = 12; pub const PIXEL_MODES_COUNT: u32 = 13; pub const PIXEL_FORMAT: u32 = 14; pub const GET_ROTATION: u32 = 21; pub const SET_ROTATION: u32 = 22; pub const GET_RESOLUTION: u32 = 23; pub const SET_RESOLUTION: u32 = 24; pub const GET_PIXEL_FORMAT: u32 = 25; pub const SET_PIXEL_FORMAT: u32 = 26; pub const SET_WRITE_FRAME: u32 = 100; pub const WRITE: u32 = 200; pub const FILL: u32 = 300; } // Subscribe ID used for callbacks mod subscribe { pub const WRITE: u32 = 0; } // Allow-readonly buffer ID mod allow_ro { pub const WRITE_BUFFER_ID: u32 = 0; } ================================================ FILE: apis/display/screen/src/tests.rs ================================================ use libtock_platform::ErrorCode; use libtock_unittest::{fake, ExpectedSyscall}; type Screen = super::Screen; #[test] // Test fails when no screen driver is registered. fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(Screen::exists(), Err(ErrorCode::Fail)) } #[test] // Test passes when screen driver is present. fn exists() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::exists(), Ok(())); } #[test] // Tests basic screen setup returns expected value (3). fn screen_setup() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::screen_setup(), Ok(3)); } #[test] // Tests setting screen power to ON. fn set_power() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::set_power(1), Ok(())); } #[test] // Tests setting brightness. fn set_brightness() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_brightness(90), Ok(())); } #[test] // Turns inversion ON. fn set_invert_on() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_invert_on(), Ok(())); } #[test] // Turns inversion OFF. fn set_invert_off() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_invert_off(), Ok(())); } #[test] // Tests invert value. fn set_invert() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::set_invert(2), Ok(())); } #[test] // Checks number of supported resolution modes. fn get_resolution_modes_count() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::get_resolution_modes_count(), Ok(2)); } #[test] // Tests fetching resolutions for indices 0-2. fn get_resolution_width_height() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::get_resolution_width_height(0), Ok((1920, 1080))); assert_eq!(Screen::get_resolution_width_height(1), Ok((2560, 1440))); assert_eq!(Screen::get_resolution_width_height(2), Ok((1280, 720))); } #[test] // Invalid resolution index returns error. fn get_resolution_width_height_fail() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!( Screen::get_resolution_width_height(3), Err(ErrorCode::Invalid) ); } #[test] // Checks total available pixel modes. fn pixel_modes_count() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::pixel_modes_count(), Ok(5)); } #[test] // Gets pixel format for valid indices. fn get_screen_pixel_format_success() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::pixel_format(0), Ok(332)); assert_eq!(Screen::pixel_format(1), Ok(565)); } #[test] // Invalid index returns error. fn get_screen_pixel_format_fail() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::pixel_format(8), Err(ErrorCode::Invalid)); } #[test] // Sets screen rotation. fn set_rotation_success() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_rotation(30), Ok(())); } #[test] // Invalid rotation (>=360 degrees) fails. fn set_rotation_fail() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::set_rotation(360), Err(ErrorCode::Invalid)); } #[test] // Sets and then reads rotation. fn get_rotation_success() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_rotation(30), Ok(())); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::get_rotation(), Ok(30)); } #[test] // Gets default rotation (0) without prior set. fn get_rotation_fail() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::get_rotation(), Ok(0)); } #[test] // Sets resolution to custom dimensions. fn set_resolution() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_resolution(360, 720), Ok(())); } #[test] // Verifies resolution was correctly set and retrieved. fn get_resolution_sucess() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_resolution(360, 720), Ok(())); assert_eq!(Screen::get_resolution(), Ok((360, 720))); } #[test] // Returns (0,0) when no resolution was set. fn get_resolution_fail() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::get_resolution(), Ok((0, 0))); } #[test] // Sets pixel format. fn set_pixel_format() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_pixel_format(2), Ok(())); } #[test] // Retrieves the pixel format that was previously set. fn get_pixel_format_success() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::set_pixel_format(2), Ok(())); assert_eq!(Screen::get_pixel_format(), Ok(2)); } #[test] // Returns 0 if format was never set. fn get_pixel_format_fail() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::get_pixel_format(), Ok(0)); } #[test] // Sets the region of screen to write pixels to. fn set_write_frame() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); assert_eq!(Screen::set_write_frame(10, 20, 30, 30), Ok(())); } #[test] // Writes pixel data to screen using a buffer. fn write_buffer() { let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); let _ = Screen::set_pixel_format(2); let buffer = [0u8; 4]; kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: 0x90001, buffer_num: 0, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::write(&buffer), Ok(())); } #[test] fn fill_success() { // Fills buffer with a color and writes to screen. let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); let _ = Screen::set_pixel_format(2); let mut buffer = [0u8; 2]; let color = 0xF800; kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: 0x90001, buffer_num: 0, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::fill(&mut buffer, color), Ok(())); } #[test] fn fill_fail() { // Buffer too small causes fill failure. let kernel = fake::Kernel::new(); let driver = fake::Screen::new(); kernel.add_driver(&driver); let _ = Screen::set_pixel_format(2); let mut buffer = [0u8; 1]; let color = 0xF800; kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: 0x90001, buffer_num: 0, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 0x90001, subscribe_num: 0, skip_with_error: None, }); assert_eq!(Screen::fill(&mut buffer, color), Err(ErrorCode::Fail)); } ================================================ FILE: apis/interface/buttons/Cargo.toml ================================================ [package] name = "libtock_buttons" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2018" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock buttons driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/interface/buttons/src/lib.rs ================================================ #![no_std] use libtock_platform::{ share::Handle, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; /// The Buttons driver /// /// # Example /// ```ignore /// use libtock::Buttons; /// /// // Read button state /// Buttons::is_pressed(0); /// /// // Register for events /// /// let listener = ButtonListener(|button, state| { /// // make use of the button's state /// }); /// /// share::scope(|subscribe| { /// if let Ok(()) = Buttons::register_listener(&listener, subscribe) { /// // yield /// } /// }); /// ``` pub struct Buttons(S); #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ButtonState { Pressed, Released, } impl From for ButtonState { fn from(value: u32) -> ButtonState { match value { 0 => ButtonState::Released, _ => ButtonState::Pressed, } } } impl Buttons { /// Run a check against the buttons capsule to ensure it is present. /// /// Returns `Ok(number_of_buttons)` if the driver was present. This does not necessarily mean /// that the driver is working. pub fn count() -> Result { S::command(DRIVER_NUM, BUTTONS_COUNT, 0, 0).to_result() } /// Read the state of a button pub fn read(button: u32) -> Result { let button_state: u32 = S::command(DRIVER_NUM, BUTTONS_READ, button, 0).to_result()?; Ok(button_state.into()) } /// Returns `true` if a button is pressed /// /// This function returns `false` if: /// - the button is released /// - the button number is invalid /// - there is an error pub fn is_pressed(button: u32) -> bool { Self::read(button) .map(|state| state == ButtonState::Pressed) .unwrap_or(false) } /// Returns `true` if a button is released /// /// This function returns `false` if: /// - the button is pressed /// - the button number is invalid /// - there is an error pub fn is_released(button: u32) -> bool { Self::read(button) .map(|state| state == ButtonState::Released) .unwrap_or(false) } /// Enable events (interrupts) for a button pub fn enable_interrupts(button: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, BUTTONS_ENABLE_INTERRUPTS, button, 0).to_result() } /// Disable events (interrupts) for a button pub fn disable_interrupts(button: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, BUTTONS_DISABLE_INTERRUPTS, button, 0).to_result() } /// Register an events listener /// /// There can be only one single listener registered at a time. /// Each time this function is used, it will replace the /// previously registered listener. pub fn register_listener<'share, F: Fn(u32, ButtonState)>( listener: &'share ButtonListener, subscribe: Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the events listener /// /// This function may be used even if there was no /// previously registered listener. pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } } /// A wrapper around a closure to be registered and called when /// a button event occurs. /// /// ```ignore /// let listener = ButtonListener(|button, state| { /// // make use of the button's state /// }); /// ``` pub struct ButtonListener(pub F); impl Upcall> for ButtonListener { fn upcall(&self, button_index: u32, state: u32, _arg2: u32) { self.0(button_index, state.into()) } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x3; // Command IDs const BUTTONS_COUNT: u32 = 0; const BUTTONS_ENABLE_INTERRUPTS: u32 = 1; const BUTTONS_DISABLE_INTERRUPTS: u32 = 2; const BUTTONS_READ: u32 = 3; ================================================ FILE: apis/interface/buttons/src/tests.rs ================================================ use core::cell::Cell; use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; use crate::{ButtonListener, ButtonState}; type Buttons = super::Buttons; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(Buttons::count(), Err(ErrorCode::NoDevice)); } #[test] fn num_buttons() { let kernel = fake::Kernel::new(); let driver = fake::Buttons::<10>::new(); kernel.add_driver(&driver); assert_eq!(Buttons::count(), Ok(10)); } #[test] fn read() { let kernel = fake::Kernel::new(); let driver = fake::Buttons::<10>::new(); kernel.add_driver(&driver); assert_eq!(driver.set_pressed(0, true), Ok(())); assert_eq!(Buttons::read(0), Ok(ButtonState::Pressed)); assert_eq!(driver.set_pressed(0, false), Ok(())); assert_eq!(Buttons::read(0), Ok(ButtonState::Released)); assert_eq!(Buttons::read(11), Err(ErrorCode::Invalid)); } #[test] fn interrupts() { let kernel = fake::Kernel::new(); let driver = fake::Buttons::<10>::new(); kernel.add_driver(&driver); assert_eq!(Buttons::enable_interrupts(0), Ok(())); assert!(driver.get_button_state(0).unwrap().interrupt_enabled); assert_eq!(Buttons::disable_interrupts(0), Ok(())); assert!(!driver.get_button_state(0).unwrap().interrupt_enabled); assert_eq!(Buttons::enable_interrupts(11), Err(ErrorCode::Invalid)); assert_eq!(Buttons::disable_interrupts(11), Err(ErrorCode::Invalid)); } #[test] fn subscribe() { let kernel = fake::Kernel::new(); let driver = fake::Buttons::<10>::new(); kernel.add_driver(&driver); let pressed_interrupt_count: Cell = Cell::new(false); let listener = ButtonListener(|button, state| { assert_eq!(button, 0); assert_eq!(state, ButtonState::Pressed); pressed_interrupt_count.set(true); }); assert_eq!(Buttons::enable_interrupts(0), Ok(())); share::scope(|subscribe| { assert_eq!(Buttons::register_listener(&listener, subscribe), Ok(())); assert_eq!(driver.set_pressed(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }); assert!(pressed_interrupt_count.get()); let pressed_interrupt_count: Cell = Cell::new(0); let expected_button_state: Cell = Cell::new(ButtonState::Released); let listener = ButtonListener(|button, state| { assert_eq!(button, 1); assert_eq!(state, expected_button_state.get()); pressed_interrupt_count.set(pressed_interrupt_count.get() + 1); }); share::scope(|subscribe| { assert_eq!(Buttons::enable_interrupts(1), Ok(())); assert_eq!(Buttons::register_listener(&listener, subscribe), Ok(())); expected_button_state.set(ButtonState::Pressed); assert_eq!(driver.set_pressed(1, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(driver.set_pressed(1, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); expected_button_state.set(ButtonState::Released); assert_eq!(driver.set_pressed(1, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(driver.set_pressed(1, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(Buttons::disable_interrupts(1), Ok(())); assert_eq!(driver.set_pressed(1, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(driver.set_pressed(1, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); assert_eq!(pressed_interrupt_count.get(), 2); let pressed_interrupt_count: Cell = Cell::new(false); let listener = ButtonListener(|_, _| { pressed_interrupt_count.set(true); }); share::scope(|subscribe| { assert_eq!(Buttons::enable_interrupts(0), Ok(())); assert_eq!(Buttons::register_listener(&listener, subscribe), Ok(())); Buttons::unregister_listener(); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); assert!(!pressed_interrupt_count.get()); } ================================================ FILE: apis/interface/buzzer/Cargo.toml ================================================ [package] name = "libtock_buzzer" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock buzzer driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/interface/buzzer/src/lib.rs ================================================ //! Implementation started by : https://github.com/teodorobert //! Continued and modified by : https://github.com/SheepSeb #![no_std] use core::cell::Cell; use core::time::Duration; use libtock_platform::{ share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; pub struct Buzzer(S); impl Buzzer { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working. pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } /// Initiate a tone pub fn tone(freq: u32, duration: Duration) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, BUZZER_ON, freq, duration.as_millis() as u32).to_result() } /// Register an events listener pub fn register_listener<'share, F: Fn(u32)>( listener: &'share BuzzerListener, subscribe: share::Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Initiate a synchronous tone /// Returns Ok() if the operation was successful pub fn tone_sync(freq: u32, duration: Duration) -> Result<(), ErrorCode> { let buzzer_cell: Cell> = Cell::new(None); let listener = BuzzerListener(|buzzer_val| { buzzer_cell.set(Some(buzzer_val)); }); share::scope(|subscribe| { Self::register_listener(&listener, subscribe)?; Self::tone(freq, duration)?; while buzzer_cell.get().is_none() { S::yield_wait(); } match buzzer_cell.get() { None => Err(ErrorCode::Fail), Some(_) => Ok(()), } }) } } pub struct BuzzerListener(pub F); impl Upcall> for BuzzerListener { fn upcall(&self, _arg0: u32, _arg1: u32, _arg2: u32) { (self.0)(_arg0); } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x90000; // Command IDs const EXISTS: u32 = 0; const BUZZER_ON: u32 = 1; /// The notes that can be played by the buzzer #[allow(unused)] #[repr(u32)] #[derive(Copy, Clone, Debug)] pub enum Note { B0 = 31, C1 = 33, CS1 = 35, D1 = 37, DS1 = 39, E1 = 41, F1 = 44, FS1 = 46, G1 = 49, GS1 = 52, A1 = 55, AS1 = 58, B1 = 62, C2 = 65, CS2 = 69, D2 = 73, DS2 = 78, E2 = 82, F2 = 87, FS2 = 93, G2 = 98, GS2 = 104, A2 = 110, AS2 = 117, B2 = 123, C3 = 131, CS3 = 139, D3 = 147, DS3 = 156, E3 = 165, F3 = 175, FS3 = 185, G3 = 196, GS3 = 208, A3 = 220, AS3 = 233, B3 = 247, C4 = 262, CS4 = 277, D4 = 294, DS4 = 311, E4 = 330, F4 = 349, FS4 = 370, G4 = 392, GS4 = 415, A4 = 440, AS4 = 466, B4 = 494, C5 = 523, CS5 = 554, D5 = 587, DS5 = 622, E5 = 659, F5 = 698, FS5 = 740, G5 = 784, GS5 = 831, A5 = 880, AS5 = 932, B5 = 988, C6 = 1047, CS6 = 1109, D6 = 1175, DS6 = 1245, E6 = 1319, F6 = 1397, FS6 = 1480, G6 = 1568, GS6 = 1661, A6 = 1760, AS6 = 1865, B6 = 1976, C7 = 2093, CS7 = 2217, D7 = 2349, DS7 = 2489, E7 = 2637, F7 = 2794, FS7 = 2960, G7 = 3136, GS7 = 3322, A7 = 3520, AS7 = 3729, B7 = 3951, C8 = 4186, CS8 = 4435, D8 = 4699, DS8 = 4978, } ================================================ FILE: apis/interface/buzzer/src/tests.rs ================================================ use core::time::Duration; use libtock_platform::ErrorCode; use libtock_unittest::fake; type Buzzer = super::Buzzer; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(Buzzer::exists(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::Buzzer::new(); kernel.add_driver(&driver); assert_eq!(Buzzer::exists(), Ok(())); } #[test] fn tone() { let kernel = fake::Kernel::new(); let driver = fake::Buzzer::new(); kernel.add_driver(&driver); let duration = Duration::from_millis(100); assert_eq!(Buzzer::tone(1000, duration), Ok(())); assert!(driver.is_busy()); assert_eq!(Buzzer::tone(1000, duration), Err(ErrorCode::Busy)); } #[test] fn tone_sync() { let kernel = fake::Kernel::new(); let driver = fake::Buzzer::new(); kernel.add_driver(&driver); let duration = Duration::from_millis(100); driver.set_tone_sync(1000, 100); assert_eq!(Buzzer::tone_sync(1000, duration), Ok(())); } ================================================ FILE: apis/interface/console/Cargo.toml ================================================ [package] name = "libtock_console" version = "0.1.0" authors = [ "Tock Project Developers ", "dcz ", ] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock console driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/interface/console/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use core::fmt; use core::marker::PhantomData; use libtock_platform as platform; use libtock_platform::allow_ro::AllowRo; use libtock_platform::allow_rw::AllowRw; use libtock_platform::share; use libtock_platform::subscribe::Subscribe; use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; /// The console driver. /// /// It allows libraries to pass strings to the kernel's console driver. /// /// # Example /// ```ignore /// use libtock::Console; /// /// // Writes "foo", followed by a newline, to the console /// let mut writer = Console::writer(); /// writeln!(writer, foo).unwrap(); /// ``` pub struct Console(S, C); impl Console { /// Run a check against the console capsule to ensure it is present. /// /// Returns `true` if the driver was present. This does not necessarily mean /// that the driver is working, as it may still fail to allocate grant /// memory. #[inline(always)] pub fn exists() -> bool { S::command(DRIVER_NUM, command::EXISTS, 0, 0).is_success() } /// Writes bytes. /// This is an alternative to `fmt::Write::write` /// because this can actually return an error code. pub fn write(s: &[u8]) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope::< ( AllowRo<_, DRIVER_NUM, { allow_ro::WRITE }>, Subscribe<_, DRIVER_NUM, { subscribe::WRITE }>, ), _, _, >(|handle| { let (allow_ro, subscribe) = handle.split(); S::allow_ro::(allow_ro, s)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, command::WRITE, s.len() as u32, 0) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((_,)) = called.get() { return Ok(()); } } }) } /// Reads bytes /// Reads from the device and writes to `buf`, starting from index 0. /// No special guarantees about when the read stops. /// Returns count of bytes written to `buf`. pub fn read(buf: &mut [u8]) -> (usize, Result<(), ErrorCode>) { let called: Cell> = Cell::new(None); let mut bytes_received = 0; let r = share::scope::< ( AllowRw<_, DRIVER_NUM, { allow_rw::READ }>, Subscribe<_, DRIVER_NUM, { subscribe::READ }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); let len = buf.len(); S::allow_rw::(allow_rw, buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::READ }>(subscribe, &called)?; // When this fails, `called` is guaranteed unmodified, // because upcalls are never processed until we call `yield`. S::command(DRIVER_NUM, command::READ, len as u32, 0).to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((status, bytes_pushed_count)) = called.get() { bytes_received = bytes_pushed_count as usize; return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }); (bytes_received, r) } pub fn writer() -> ConsoleWriter { ConsoleWriter { syscalls: Default::default(), } } } pub struct ConsoleWriter { syscalls: PhantomData, } impl fmt::Write for ConsoleWriter { fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { Console::::write(s.as_bytes()).map_err(|_e| fmt::Error) } } /// System call configuration trait for `Console`. pub trait Config: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config { } impl Config for T { } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x1; // Command IDs #[allow(unused)] mod command { pub const EXISTS: u32 = 0; pub const WRITE: u32 = 1; pub const READ: u32 = 2; pub const ABORT: u32 = 3; } #[allow(unused)] mod subscribe { pub const WRITE: u32 = 1; pub const READ: u32 = 2; } mod allow_ro { pub const WRITE: u32 = 1; } mod allow_rw { pub const READ: u32 = 1; } ================================================ FILE: apis/interface/console/src/tests.rs ================================================ use super::*; use core::fmt::Write; use libtock_platform::ErrorCode; use libtock_unittest::{command_return, fake, ExpectedSyscall}; type Console = super::Console; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert!(!Console::exists()); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::Console::new(); kernel.add_driver(&driver); assert!(Console::exists()); assert_eq!(driver.take_bytes(), &[]); } #[test] fn write_bytes() { let kernel = fake::Kernel::new(); let driver = fake::Console::new(); kernel.add_driver(&driver); Console::write(b"foo").unwrap(); Console::write(b"bar").unwrap(); assert_eq!(driver.take_bytes(), b"foobar",); } #[test] fn write_str() { let kernel = fake::Kernel::new(); let driver = fake::Console::new(); kernel.add_driver(&driver); write!(Console::writer(), "foo").unwrap(); assert_eq!(driver.take_bytes(), b"foo"); } #[test] fn read_bytes_short() { let kernel = fake::Kernel::new(); let driver = fake::Console::new_with_input(b"Hello"); kernel.add_driver(&driver); let mut buf = [0; 10]; let (count, res) = Console::read(&mut buf); res.unwrap(); assert_eq!(&buf[..count], b"Hello"); } #[test] fn read_bytes_alot() { let kernel = fake::Kernel::new(); let driver = fake::Console::new_with_input(b"Hello Alot!"); kernel.add_driver(&driver); let mut buf = [0; 5]; let (count, res) = Console::read(&mut buf); res.unwrap(); assert_eq!(&buf[..count], b"Hello"); let (count, res) = Console::read(&mut buf); res.unwrap(); assert_eq!(&buf[..count], b" Alot"); } #[test] fn failed_print() { let kernel = fake::Kernel::new(); let driver = fake::Console::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::WRITE, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: DRIVER_NUM, subscribe_num: subscribe::WRITE, skip_with_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: DRIVER_NUM, command_id: command::WRITE, argument0: 5, argument1: 0, override_return: Some(command_return::failure(ErrorCode::Fail)), }); assert_eq!(Console::write(b"abcde"), Err(ErrorCode::Fail)); // The fake driver still receives the command even if a fake error is injected. assert_eq!(driver.take_bytes(), b"abcde"); } #[test] fn failed_read() { let kernel = fake::Kernel::new(); let driver = fake::Console::new_with_input(b"bugxxxx"); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::AllowRw { driver_num: DRIVER_NUM, buffer_num: allow_rw::READ, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: DRIVER_NUM, subscribe_num: subscribe::READ, skip_with_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: DRIVER_NUM, command_id: command::READ, argument0: 3, argument1: 0, override_return: Some(command_return::failure(ErrorCode::Fail)), }); let mut buf = [0; 3]; let (count, res) = Console::read(&mut buf); assert_eq!(res, Err(ErrorCode::Fail)); assert_eq!(count, 0); } ================================================ FILE: apis/interface/leds/Cargo.toml ================================================ [package] name = "libtock_leds" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock leds driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/interface/leds/src/lib.rs ================================================ #![no_std] use libtock_platform::{ErrorCode, Syscalls}; /// The LEDs driver /// /// # Example /// ```ignore /// use libtock::Leds; /// /// // Turn on led 0 /// let _ = Leds::on(0); /// ``` pub struct Leds(S); impl Leds { /// Run a check against the leds capsule to ensure it is present. /// /// Returns `Ok(number_of_leds)` if the driver was present. This does not necessarily mean /// that the driver is working, as it may still fail to allocate grant /// memory. pub fn count() -> Result { S::command(DRIVER_NUM, LEDS_COUNT, 0, 0).to_result() } pub fn on(led: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, LED_ON, led, 0).to_result() } pub fn off(led: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, LED_OFF, led, 0).to_result() } pub fn toggle(led: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, LED_TOGGLE, led, 0).to_result() } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x2; // Command IDs const LEDS_COUNT: u32 = 0; const LED_ON: u32 = 1; const LED_OFF: u32 = 2; const LED_TOGGLE: u32 = 3; ================================================ FILE: apis/interface/leds/src/tests.rs ================================================ use libtock_platform::ErrorCode; use libtock_unittest::fake; type Leds = super::Leds; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(Leds::count(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::Leds::<10>::new(); kernel.add_driver(&driver); assert_eq!(Leds::count(), Ok(10)); for led in 0..10 { assert_eq!(driver.get_led(led), Some(false)); } } #[test] fn num_leds() { let kernel = fake::Kernel::new(); let driver = fake::Leds::<10>::new(); kernel.add_driver(&driver); assert_eq!(Leds::count().unwrap_or_default(), 10); } #[test] fn on() { let kernel = fake::Kernel::new(); let driver = fake::Leds::<10>::new(); kernel.add_driver(&driver); assert_eq!(Leds::on(0), Ok(())); assert_eq!(driver.get_led(0), Some(true)); } #[test] fn off() { let kernel = fake::Kernel::new(); let driver = fake::Leds::<10>::new(); kernel.add_driver(&driver); assert_eq!(Leds::off(0), Ok(())); assert_eq!(driver.get_led(0), Some(false)); } #[test] fn toggle() { let kernel = fake::Kernel::new(); let driver = fake::Leds::<10>::new(); kernel.add_driver(&driver); assert_eq!(Leds::toggle(0), Ok(())); assert_eq!(driver.get_led(0), Some(true)); assert_eq!(Leds::toggle(0), Ok(())); assert_eq!(driver.get_led(0), Some(false)); } #[test] fn on_off() { let kernel = fake::Kernel::new(); let driver = fake::Leds::<10>::new(); kernel.add_driver(&driver); assert_eq!(Leds::on(0), Ok(())); assert_eq!(driver.get_led(0), Some(true)); assert_eq!(Leds::off(0), Ok(())); assert_eq!(driver.get_led(0), Some(false)); } #[test] fn no_led() { let kernel = fake::Kernel::new(); let driver = fake::Leds::<10>::new(); kernel.add_driver(&driver); assert_eq!(Leds::on(11), Err(ErrorCode::Invalid)); for led in 0..Leds::count().unwrap_or_default() { assert_eq!(driver.get_led(led), Some(false)); } } ================================================ FILE: apis/kernel/low_level_debug/Cargo.toml ================================================ [package] name = "libtock_low_level_debug" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock low-level debug drivers" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/kernel/low_level_debug/src/lib.rs ================================================ #![no_std] use libtock_platform::Syscalls; /// The low-level debug API provides tools to diagnose userspace issues that /// make normal debugging workflows (e.g. printing to the console) difficult. /// /// It allows libraries to print alert codes and apps to print numeric /// information using only the command system call. /// /// # Example /// ```ignore /// use libtock::LowLevelDebug; /// /// // Prints 0x45 and the app which called it. /// LowLevelDebug::print_1(0x45); /// ``` pub struct LowLevelDebug(S); impl LowLevelDebug { /// Run a check against the low-level debug capsule to ensure it is present. /// /// Returns `true` if the driver was present. This does not necessarily mean /// that the driver is working, as it may still fail to allocate grant /// memory. #[inline(always)] pub fn exists() -> bool { S::command(DRIVER_NUM, EXISTS, 0, 0).is_success() } /// Print one of the predefined alerts in [`AlertCode`]. #[inline(always)] pub fn print_alert_code(code: AlertCode) { let _ = S::command(DRIVER_NUM, PRINT_ALERT_CODE, code as u32, 0); } /// Print a single number. The number will be printed in hexadecimal. /// /// In general, this should only be added temporarily for debugging and /// should not be called by released library code. #[inline(always)] pub fn print_1(x: u32) { let _ = S::command(DRIVER_NUM, PRINT_1, x, 0); } /// Print two numbers. The numbers will be printed in hexadecimal. /// /// Like `print_1`, this is intended for temporary debugging and should not /// be called by released library code. If you want to print multiple /// values, it is often useful to use the first argument to indicate what /// value is being printed. #[inline(always)] pub fn print_2(x: u32, y: u32) { let _ = S::command(DRIVER_NUM, PRINT_2, x, y); } } /// A predefined alert code, for use with [`LowLevelDebug::print_alert_code`]. pub enum AlertCode { /// Application panic (e.g. `panic!()` called in Rust code). Panic = 0x01, /// A statically-linked app was not installed in the correct location in /// flash. WrongLocation = 0x02, } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x8; // Command IDs const EXISTS: u32 = 0; const PRINT_ALERT_CODE: u32 = 1; const PRINT_1: u32 = 2; const PRINT_2: u32 = 3; ================================================ FILE: apis/kernel/low_level_debug/src/tests.rs ================================================ use super::*; use libtock_platform::ErrorCode; use libtock_unittest::{command_return, fake, ExpectedSyscall}; type LowLevelDebug = super::LowLevelDebug; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert!(!LowLevelDebug::exists()); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::LowLevelDebug::new(); kernel.add_driver(&driver); assert!(LowLevelDebug::exists()); assert_eq!(driver.take_messages(), []); } #[test] fn print_alert_code() { let kernel = fake::Kernel::new(); let driver = fake::LowLevelDebug::new(); kernel.add_driver(&driver); LowLevelDebug::print_alert_code(AlertCode::Panic); LowLevelDebug::print_alert_code(AlertCode::WrongLocation); assert_eq!( driver.take_messages(), [ fake::Message::AlertCode(0x01), fake::Message::AlertCode(0x02) ] ); } #[test] fn print_1() { let kernel = fake::Kernel::new(); let driver = fake::LowLevelDebug::new(); kernel.add_driver(&driver); LowLevelDebug::print_1(42); assert_eq!(driver.take_messages(), [fake::Message::Print1(42)]); } #[test] fn print_2() { let kernel = fake::Kernel::new(); let driver = fake::LowLevelDebug::new(); kernel.add_driver(&driver); LowLevelDebug::print_2(42, 27); LowLevelDebug::print_2(29, 43); assert_eq!( driver.take_messages(), [fake::Message::Print2(42, 27), fake::Message::Print2(29, 43)] ); } #[test] fn failed_print() { let kernel = fake::Kernel::new(); let driver = fake::LowLevelDebug::new(); kernel.add_driver(&driver); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: DRIVER_NUM, command_id: PRINT_1, argument0: 72, argument1: 0, override_return: Some(command_return::failure(ErrorCode::Fail)), }); // The error is explicitly silenced, and cannot be detected. LowLevelDebug::print_1(72); // The fake driver still receives the command even if a fake error is injected. assert_eq!(driver.take_messages(), [fake::Message::Print1(72)]); } ================================================ FILE: apis/net/ieee802154/Cargo.toml ================================================ [package] name = "libtock_ieee802154" version = "0.1.0" authors = [ "Tock Project Developers ", ] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock raw IEEE 802.15.4 stack driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/net/ieee802154/src/lib.rs ================================================ //! The raw IEEE 802.15.4 stack driver. #![no_std] use core::cell::Cell; use libtock_platform as platform; use libtock_platform::allow_ro::AllowRo; use libtock_platform::allow_rw::AllowRw; use libtock_platform::share; use libtock_platform::subscribe::Subscribe; use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; /// The raw IEEE 802.15.4 stack driver. /// /// It allows libraries to pass frames to and from kernel's 802.15.4 driver. /// /// # Example /// ```ignore /// use libtock::ieee802154::{Ieee802154, RxOperator, RxRingBuffer, RxSingleBufferOperator}; /// /// // Configure the radio /// let pan: u16 = 0xcafe; /// let addr_short: u16 = 0xdead; /// let addr_long: u64 = 0xdead_dad; /// let tx_power: i8 = -0x42; /// let channel: u8 = 0xff; /// /// Ieee802154::set_pan(pan); /// Ieee802154::set_address_short(addr_short); /// Ieee802154::set_address_long(addr_long); /// Ieee802154::set_tx_power(tx_power).unwrap(); /// Ieee802154::set_channel(channel).unwrap(); /// /// // Don't forget to commit the config! /// Ieee802154::commit_config(); /// /// Ieee802154::radio_on()?; /// /// // Transmit a frame /// Ieee802154::transmit_frame(b"foobar").unwrap(); /// /// // Receive frames /// let mut buf = RxRingBuffer::<2>::new(); /// let mut operator = RxSingleBufferOperator::new(&mut buf); /// /// let frame = operator.receive_frame()?; /// // Access frame data here: /// let _body_len = frame.payload_len; /// let _first_body_byte = frame.body[0]; /// /// ``` pub struct Ieee802154(S, C); // Existence check impl Ieee802154 { /// Run a check against the console capsule to ensure it is present. /// /// Returns `true` if the driver was present. This does not necessarily mean /// that the driver is working, as it may still fail to allocate grant /// memory. #[inline(always)] pub fn exists() -> bool { S::command(DRIVER_NUM, command::EXISTS, 0, 0).is_success() } } // Power management impl Ieee802154 { #[inline(always)] pub fn is_on() -> bool { S::command(DRIVER_NUM, command::STATUS, 0, 0).is_success() } #[inline(always)] pub fn radio_on() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, command::TURN_ON, 0, 0).to_result() } #[inline(always)] pub fn radio_off() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, command::TURN_OFF, 0, 0).to_result() } } // Configuration impl Ieee802154 { #[inline(always)] pub fn set_address_short(short_addr: u16) { // Setting short address can't fail, so no need to check the return value. let _ = S::command( DRIVER_NUM, command::SET_SHORT_ADDR, // Driver expects 1 added to make the value positive. short_addr as u32 + 1, 0, ); } #[inline(always)] pub fn set_address_long(long_addr: u64) { // Setting long address can't fail, so no need to check the return value. let addr_lower: u32 = long_addr as u32; let addr_upper: u32 = (long_addr >> 32) as u32; let _ = S::command(DRIVER_NUM, command::SET_LONG_ADDR, addr_lower, addr_upper); } #[inline(always)] pub fn set_pan(pan: u16) { // Setting PAN can't fail, so no need to check the return value. let _ = S::command( DRIVER_NUM, command::SET_PAN, pan as u32 + 1, // Driver expects 1 added to make the value positive. 0, ); } #[inline(always)] pub fn set_channel(chan: u8) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, command::SET_CHAN, chan as u32, 0).to_result() } #[inline(always)] pub fn set_tx_power(power: i8) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, command::SET_TX_PWR, power as i32 as u32, 0).to_result() } #[inline(always)] pub fn commit_config() { // Committing config can't fail, so no need to check the return value. let _ = S::command(DRIVER_NUM, command::COMMIT_CFG, 0, 0); } #[inline(always)] pub fn get_address_short() -> Result { S::command(DRIVER_NUM, command::GET_SHORT_ADDR, 0, 0) .to_result::() // Driver adds 1 to make the value positive. .map(|addr| addr as u16 - 1) } #[inline(always)] pub fn get_address_long() -> Result { S::command(DRIVER_NUM, command::GET_LONG_ADDR, 0, 0).to_result() } #[inline(always)] pub fn get_pan() -> Result { S::command(DRIVER_NUM, command::GET_PAN, 0, 0) .to_result::() // Driver adds 1 to make the value positive. .map(|pan| pan as u16 - 1) } #[inline(always)] pub fn get_channel() -> Result { S::command(DRIVER_NUM, command::GET_CHAN, 0, 0) .to_result::() .map(|chan| chan as u8) } #[inline(always)] pub fn get_tx_power() -> Result { S::command(DRIVER_NUM, command::GET_TX_PWR, 0, 0) .to_result::() .map(|power| power as i32 as i8) } } // Transmission impl Ieee802154 { /// Transmit a frame using the IEEE 802.15.4 Phy Driver. pub fn transmit_frame_raw(frame: &[u8]) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope::< ( AllowRo<_, DRIVER_NUM, { allow_ro::WRITE }>, Subscribe<_, DRIVER_NUM, { subscribe::FRAME_TRANSMITTED }>, ), _, _, >(|handle| { let (allow_ro, subscribe) = handle.split(); S::allow_ro::(allow_ro, frame)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::FRAME_TRANSMITTED }>( subscribe, &called, )?; S::command(DRIVER_NUM, command::TRANSMIT_RAW, 0, 0).to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if called.get().is_some() { return Ok(()); } } }) } } mod rx; pub use rx::{Frame, RxOperator, RxRingBuffer, RxSingleBufferOperator}; /// System call configuration trait for `Ieee802154`. pub trait Config: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config { } impl Config for T { } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x30001; // Command IDs /// - `0`: Driver existence check. /// - `1`: Return radio status. Ok(())/OFF = on/off. /// - `2`: Set short address. /// - `4`: Set PAN ID. /// - `5`: Set channel. /// - `6`: Set transmission power. /// - `7`: Commit any configuration changes. /// - `8`: Get the short MAC address. /// - `10`: Get the PAN ID. /// - `11`: Get the channel. /// - `12`: Get the transmission power. /// - `27`: Transmit a frame. The frame must be stored in the write RO allow /// buffer 0. The allowed buffer must be the length of the frame. The /// frame includes the PDSU (i.e., the MAC payload) _without_ the MFR /// (i.e., CRC) bytes. /// - `28`: Set long address. /// - `29`: Get the long MAC address. /// - `30`: Turn the radio on. /// - `31`: Turn the radio off. mod command { pub const EXISTS: u32 = 0; pub const STATUS: u32 = 1; pub const SET_SHORT_ADDR: u32 = 2; pub const SET_PAN: u32 = 4; pub const SET_CHAN: u32 = 5; pub const SET_TX_PWR: u32 = 6; pub const COMMIT_CFG: u32 = 7; pub const GET_SHORT_ADDR: u32 = 8; pub const GET_PAN: u32 = 10; pub const GET_CHAN: u32 = 11; pub const GET_TX_PWR: u32 = 12; pub const TRANSMIT_RAW: u32 = 27; pub const SET_LONG_ADDR: u32 = 28; pub const GET_LONG_ADDR: u32 = 29; pub const TURN_ON: u32 = 30; pub const TURN_OFF: u32 = 31; } mod subscribe { /// Frame is received pub const FRAME_RECEIVED: u32 = 0; /// Frame is transmitted pub const FRAME_TRANSMITTED: u32 = 1; } /// Ids for read-only allow buffers mod allow_ro { /// Write buffer. Contains the frame payload to be transmitted. pub const WRITE: u32 = 0; } /// Ids for read-write allow buffers mod allow_rw { /// Read buffer. Will contain the received frame. pub const READ: u32 = 0; } ================================================ FILE: apis/net/ieee802154/src/rx.rs ================================================ use core::marker::PhantomData; use super::*; /// Maximum length of a MAC frame. const MAX_MTU: usize = 127; #[derive(Debug)] #[repr(C)] pub struct Frame { pub header_len: u8, pub payload_len: u8, pub mic_len: u8, pub body: [u8; MAX_MTU], } const EMPTY_FRAME: Frame = Frame { header_len: 0, payload_len: 0, mic_len: 0, body: [0; MAX_MTU], }; /// The ring buffer that is shared with kernel using allow-rw syscall, with kernel acting /// as a producer of frames and we acting a consumer. /// /// The `N` parameter specifies the capacity of the buffer in number of frames. /// Unfortunately, due to a design flaw of the ring buffer, it can never be fully utilised, /// as it's impossible to distinguish an empty buffer from a full one. The kernel code /// actually uses up to `N - 1` slots, and then starts overwriting old frames with /// new ones. Remember to specify `N` as `F + 1`, where `F` is the maximum expected number /// of frames received in short succession. /// /// Given the non-deterministic nature of upcalls, the userprocess must carefully /// handle receiving upcalls. There exists a risk of dropping 15.4 packets while /// reading from the ring buffer (as the ring buffer is unallowed while reading). /// This could be handled by utilizing two ring buffers and alternating which /// belongs to the kernel and which is being read from. However, efforts to implement that /// failed on Miri level - we couldn't find a sound way to achieve that. /// Alternatively, the user can also utilize a single ring buffer if dropped frames may be permissible. /// This is done by [RxSingleBufferOperator]. #[derive(Debug)] #[repr(C)] pub struct RxRingBuffer { /// From where the next frame will be read by process. /// Updated by process only. read_index: u8, /// Where the next frame will be written by kernel. /// Updated by kernel only. write_index: u8, /// Slots for received frames. frames: [Frame; N], } impl Default for RxRingBuffer { fn default() -> Self { Self::new() } } impl RxRingBuffer { /// Creates a new [RxRingBuffer] that can be used to receive frames into. pub const fn new() -> Self { Self { read_index: 0, write_index: 0, frames: [EMPTY_FRAME; N], } } fn as_mut_byte_slice(&mut self) -> &mut [u8] { // SAFETY: any byte value is valid for any byte of Self, // as well as for any byte of [u8], so casts back and forth // cannot break the type system. unsafe { core::slice::from_raw_parts_mut( self as *mut Self as *mut u8, core::mem::size_of::(), ) } } fn has_frame(&self) -> bool { self.read_index != self.write_index } fn next_frame(&mut self) -> &mut Frame { let frame = self.frames.get_mut(self.read_index as usize).unwrap(); self.read_index = (self.read_index + 1) % N as u8; frame } } pub trait RxOperator { /// Receive one new frame. /// /// Logically pop one frame out of the ring buffer and provide mutable access to it. /// If no frame is ready for reception, yield_wait to kernel until one is available. fn receive_frame(&mut self) -> Result<&mut Frame, ErrorCode>; } /// Safe encapsulation that can receive frames from the kernel using a single ring buffer. /// See [RxRingBuffer] for more information. /// /// This operator can lose some frames: if a frame is received in the kernel when /// the app is examining its received frames (and hence has its buffer unallowed), /// then the frame can be lost. Unfortunately, no alternative at the moment due to /// soundness issues in tried implementation. pub struct RxSingleBufferOperator<'buf, const N: usize, S: Syscalls, C: Config = DefaultConfig> { buf: &'buf mut RxRingBuffer, s: PhantomData, c: PhantomData, } impl<'buf, const N: usize, S: Syscalls, C: Config> RxSingleBufferOperator<'buf, N, S, C> { /// Creates a new [RxSingleBufferOperator] that can be used to receive frames. pub fn new(buf: &'buf mut RxRingBuffer) -> Self { Self { buf, s: PhantomData, c: PhantomData, } } } impl RxOperator for RxSingleBufferOperator<'_, N, S, C> { fn receive_frame(&mut self) -> Result<&mut Frame, ErrorCode> { if self.buf.has_frame() { Ok(self.buf.next_frame()) } else { // If no frame is there, wait until one comes, then return it. Ieee802154::::receive_frame_single_buf(self.buf)?; // Safety: kernel schedules an upcall iff a new frame becomes available, // i.e. when it increments `read_index`. Ok(self.buf.next_frame()) } } } // Reception impl Ieee802154 { fn receive_frame_single_buf( buf: &mut RxRingBuffer, ) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope::< ( AllowRw<_, DRIVER_NUM, { allow_rw::READ }>, Subscribe<_, DRIVER_NUM, { subscribe::FRAME_RECEIVED }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); S::allow_rw::(allow_rw, buf.as_mut_byte_slice())?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::FRAME_RECEIVED }>(subscribe, &called)?; loop { S::yield_wait(); if let Some((_lqi,)) = called.get() { // At least one frame was received. return Ok(()); } } }) } } ================================================ FILE: apis/net/ieee802154/src/tests.rs ================================================ use libtock_platform::{RawSyscalls, Register}; use libtock_unittest::fake::{self, ieee802154::Frame as FakeFrame, Ieee802154Phy}; /// The Ieee8021514Phy userspace driver calls yield_wait() immediately after subscribe(). /// Normally, it would wait for the kernel to receive a frame and then asynchronously /// schedule an upcall, but in this testing framework it is required to schedule /// an upcall before yield_wait(), because otherwise a panic is raised. /// /// HACK: This wraps around fake::Syscalls to hook subscribe::FRAME_RECEIVED /// so that immediately after subscribing for the upcall, frames are received /// by the kernel driver and the corresponding upcall is scheduled. struct FakeSyscalls; unsafe impl RawSyscalls for FakeSyscalls { unsafe fn yield1([r0]: [Register; 1]) { libtock_unittest::fake::Syscalls::yield1([r0]) } unsafe fn yield2([r0, r1]: [Register; 2]) { libtock_unittest::fake::Syscalls::yield2([r0, r1]) } unsafe fn syscall1([r0]: [Register; 1]) -> [Register; 2] { libtock_unittest::fake::Syscalls::syscall1::([r0]) } unsafe fn syscall2([r0, r1]: [Register; 2]) -> [Register; 2] { libtock_unittest::fake::Syscalls::syscall2::([r0, r1]) } unsafe fn syscall4([r0, r1, r2, r3]: [Register; 4]) -> [Register; 4] { let trigger_rx_upcall = match CLASS { libtock_platform::syscall_class::SUBSCRIBE => { let driver_num: u32 = r0.try_into().unwrap(); let subscribe_num: u32 = r1.try_into().unwrap(); let len: usize = r3.into(); assert_eq!(driver_num, DRIVER_NUM); subscribe_num == subscribe::FRAME_RECEIVED && len > 0 } _ => false, }; let ret = libtock_unittest::fake::Syscalls::syscall4::([r0, r1, r2, r3]); if trigger_rx_upcall { if let Some(driver) = Ieee802154Phy::instance() { driver.driver_receive_pending_frames(); if driver.has_pending_rx_frames() { driver.trigger_rx_upcall(); } } } ret } } use crate::{subscribe, DRIVER_NUM}; use super::{RxOperator, RxRingBuffer}; type Ieee802154 = super::Ieee802154; type RxSingleBufferOperator<'buf, const N: usize> = super::RxSingleBufferOperator<'buf, N, FakeSyscalls>; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert!(!Ieee802154::exists()); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::Ieee802154Phy::new(); kernel.add_driver(&driver); assert!(Ieee802154::exists()); } #[test] fn configure() { let kernel = fake::Kernel::new(); let driver = fake::Ieee802154Phy::new(); kernel.add_driver(&driver); let pan: u16 = 0xcafe; let addr_short: u16 = 0xdead; let addr_long: u64 = 0xdeaddad; let tx_power: i8 = -0x42; let channel: u8 = 0xff; Ieee802154::set_pan(pan); Ieee802154::set_address_short(addr_short); Ieee802154::set_address_long(addr_long); Ieee802154::set_tx_power(tx_power).unwrap(); Ieee802154::set_channel(channel).unwrap(); Ieee802154::commit_config(); assert_eq!(Ieee802154::get_pan().unwrap(), pan); assert_eq!(Ieee802154::get_address_short().unwrap(), addr_short); assert_eq!(Ieee802154::get_address_long().unwrap(), addr_long); assert_eq!(Ieee802154::get_channel().unwrap(), channel); assert_eq!(Ieee802154::get_tx_power().unwrap(), tx_power); } #[test] fn transmit_frame() { let kernel = fake::Kernel::new(); let driver = fake::Ieee802154Phy::new(); kernel.add_driver(&driver); Ieee802154::transmit_frame_raw(b"foo").unwrap(); Ieee802154::transmit_frame_raw(b"bar").unwrap(); assert_eq!( driver.take_transmitted_frames(), &[&b"foo"[..], &b"bar"[..]], ); } mod rx { use super::*; fn test_with_driver(test: impl FnOnce(&Ieee802154Phy)) { let kernel = fake::Kernel::new(); let driver = fake::Ieee802154Phy::new(); kernel.add_driver(&driver); test(&driver) } fn test_with_single_buf_operator( driver: &Ieee802154Phy, test: impl Fn(&Ieee802154Phy, &mut dyn RxOperator), ) { let mut buf = RxRingBuffer::::new(); let mut operator = RxSingleBufferOperator::new(&mut buf); test(driver, &mut operator) } fn no_frame_comes(_driver: &Ieee802154Phy, operator: &mut dyn RxOperator) { // No frame is available, so we expect to panic in tests, // because yield_wait is called without pending upcalls. // THIS PANICS let _ = operator.receive_frame(); } #[test] #[should_panic = "yield-wait called with no queued upcall"] fn no_frame_comes_single_buf() { test_with_driver(|driver| { const SUPPORTED_FRAMES: usize = 2; test_with_single_buf_operator::(driver, no_frame_comes); }); } #[test] fn receive_frame() { test_with_driver(|driver| { const SUPPORTED_FRAMES: usize = 2; test_with_single_buf_operator::(driver, |driver, operator| { let frame1 = b"alamakota"; driver.radio_receive_frame(FakeFrame::with_body(frame1)); // Now one frame is available. let got_frame1 = operator.receive_frame().unwrap(); assert_eq!(got_frame1.payload_len as usize, frame1.len()); assert_eq!( &got_frame1.body[..got_frame1.payload_len as usize], &frame1[..] ); }); }); } fn only_one_frame_comes(driver: &Ieee802154Phy, operator: &mut dyn RxOperator) { let frame1 = b"alamakota"; // Now one frame is available. driver.radio_receive_frame(FakeFrame::with_body(frame1)); let got_frame1 = operator.receive_frame().unwrap(); assert_eq!(got_frame1.payload_len as usize, frame1.len()); assert_eq!(&got_frame1.body[..frame1.len()], frame1); // But only one! // THIS PANICS let _ = operator.receive_frame(); } #[test] #[should_panic = "yield-wait called with no queued upcall"] fn receive_frame_only_one_single_buf() { test_with_driver(|driver| { const SUPPORTED_FRAMES: usize = 2; test_with_single_buf_operator::(driver, only_one_frame_comes); }); } #[test] fn receive_many_frames() { test_with_driver(|driver| { const SUPPORTED_FRAMES: usize = 3; test_with_single_buf_operator::<{ SUPPORTED_FRAMES + 1 }>( driver, |driver, operator| { for (times, frame) in [1, 2, 3, 10] .iter() .copied() .zip([&b"one"[..], b"two", b"three", b"ten"]) { for _ in 0..times { driver.radio_receive_frame(FakeFrame::with_body(frame)); } for _ in 0..core::cmp::min(times, SUPPORTED_FRAMES) { let got_frame = operator.receive_frame().unwrap(); let expected_frame = frame; assert_eq!(got_frame.payload_len as usize, expected_frame.len()); assert_eq!( &got_frame.body[..got_frame.payload_len as usize], expected_frame ); } } }, ); }); } #[test] fn receive_various_frames() { test_with_driver(|driver| { const SUPPORTED_FRAMES: usize = 3; test_with_single_buf_operator::<{ SUPPORTED_FRAMES + 1 }>( driver, |driver, operator| { let frame1 = b"alamakota"; let frame2 = b"ewamamewe"; let frame3 = b"wojciechmalaptop"; let frames: [&[u8]; 3] = [frame1, frame2, frame3]; let order = [0, 1, 2, 2, 1, 0, 2, 2, 1, 0, 2]; for idx in order { let times = idx + 1; for _ in 0..times { driver.radio_receive_frame(FakeFrame::with_body(frames[idx])); } for _ in 0..core::cmp::min(times, SUPPORTED_FRAMES) { let got_frame = operator.receive_frame().unwrap(); let expected_frame = frames[idx]; assert_eq!(got_frame.payload_len as usize, expected_frame.len()); assert_eq!( &got_frame.body[..got_frame.payload_len as usize], expected_frame ); } } }, ); }); } } ================================================ FILE: apis/peripherals/adc/Cargo.toml ================================================ [package] name = "libtock_adc" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock adc driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/peripherals/adc/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::{ share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; pub struct Adc(S); impl Adc { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working. pub fn exists() -> Result<(), ErrorCode> { // TODO(Tock 3.0): The "exists" command should return directly return // `Result<(), ErrorCode>` (i.e. with no `.and()` call), but the // current ADC driver in the kernel returns the number of ADC channels // instead of just success. This will be fixed in a future release of // Tock, but for now we workaround this issue. // // https://github.com/tock/tock/issues/3375 S::command(DRIVER_NUM, EXISTS, 0, 0) .to_result::() .and(Ok(())) } // Initiate a sample reading pub fn read_single_sample() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 0).to_result() } // Register a listener to be called when the ADC conversion is finished pub fn register_listener<'share, F: Fn(u16)>( listener: &'share ADCListener, subscribe: share::Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Initiates a synchronous ADC conversion /// Returns the converted ADC value or an error pub fn read_single_sample_sync() -> Result { let sample: Cell> = Cell::new(None); let listener = ADCListener(|adc_val| { sample.set(Some(adc_val)); }); share::scope(|subscribe| { Self::register_listener(&listener, subscribe)?; Self::read_single_sample()?; while sample.get().is_none() { S::yield_wait(); } match sample.get() { None => Err(ErrorCode::Busy), Some(adc_val) => Ok(adc_val), } }) } /// Returns the number of ADC resolution bits pub fn get_resolution_bits() -> Result { S::command(DRIVER_NUM, GET_RES_BITS, 0, 0).to_result() } /// Returns the reference voltage in millivolts (mV) pub fn get_reference_voltage_mv() -> Result { S::command(DRIVER_NUM, GET_VOLTAGE_REF, 0, 0).to_result() } } pub struct ADCListener(pub F); impl Upcall> for ADCListener { fn upcall(&self, adc_val: u32, _arg1: u32, _arg2: u32) { self.0(adc_val as u16) } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x5; // Command IDs const EXISTS: u32 = 0; const SINGLE_SAMPLE: u32 = 1; // const REPEAT_SINGLE_SAMPLE: u32 = 2; // const MULTIPLE_SAMPLE: u32 = 3; // const CONTINUOUS_BUFF_SAMPLE: u32 = 4; // const STOP_SAMPLE: u32 = 5; const GET_RES_BITS: u32 = 101; const GET_VOLTAGE_REF: u32 = 102; ================================================ FILE: apis/peripherals/adc/src/tests.rs ================================================ use core::cell::Cell; use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; type Adc = super::Adc; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(Adc::exists(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::Adc::new(); kernel.add_driver(&driver); assert_eq!(Adc::exists(), Ok(())); } #[test] fn read_single_sample() { let kernel = fake::Kernel::new(); let driver = fake::Adc::new(); kernel.add_driver(&driver); assert_eq!(Adc::read_single_sample(), Ok(())); assert!(driver.is_busy()); assert_eq!(Adc::read_single_sample(), Err(ErrorCode::Busy)); assert_eq!(Adc::read_single_sample_sync(), Err(ErrorCode::Busy)); } #[test] fn register_unregister_listener() { let kernel = fake::Kernel::new(); let driver = fake::Adc::new(); kernel.add_driver(&driver); let sample: Cell> = Cell::new(None); let listener = crate::ADCListener(|adc_val| { sample.set(Some(adc_val)); }); share::scope(|subscribe| { assert_eq!(Adc::read_single_sample(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(Adc::register_listener(&listener, subscribe), Ok(())); assert_eq!(Adc::read_single_sample(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(sample.get(), Some(100)); Adc::unregister_listener(); assert_eq!(Adc::read_single_sample(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); } #[test] fn read_single_sample_sync() { let kernel = fake::Kernel::new(); let driver = fake::Adc::new(); kernel.add_driver(&driver); driver.set_value_sync(1000); assert_eq!(Adc::read_single_sample_sync(), Ok(1000)); } ================================================ FILE: apis/peripherals/alarm/Cargo.toml ================================================ [package] name = "libtock_alarm" version = "0.1.0" authors = [ "Tock Project Developers ", "dcz ", ] license = "Apache-2.0 OR MIT" edition = "2018" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock alarm driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/peripherals/alarm/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform as platform; use libtock_platform::share; use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; /// The alarm driver /// /// # Example /// ```ignore /// use libtock2::Alarm; /// /// // Wait for timeout /// Alarm::sleep(Alarm::Milliseconds(2500)); /// ``` pub struct Alarm(S, C); #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Hz(pub u32); pub trait Convert { /// Converts a time unit by rounding up. fn to_ticks(self, freq: Hz) -> Ticks; } #[derive(Copy, Clone, Debug)] pub struct Ticks(pub u32); impl Convert for Ticks { fn to_ticks(self, _freq: Hz) -> Ticks { self } } #[derive(Copy, Clone)] pub struct Milliseconds(pub u32); impl Convert for Milliseconds { fn to_ticks(self, freq: Hz) -> Ticks { // Saturating multiplication will top out at about 1 hour at 1MHz. // It's large enough for an alarm, and much simpler than failing // or losing precision for short sleeps. /// u32::div_ceil is still unstable. fn div_ceil(a: u32, other: u32) -> u32 { let d = a / other; let m = a % other; if m == 0 { d } else { d + 1 } } Ticks(div_ceil(self.0.saturating_mul(freq.0), 1000)) } } impl Alarm { /// Run a check against the console capsule to ensure it is present. #[inline(always)] pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, command::EXISTS, 0, 0).to_result() } pub fn get_frequency() -> Result { S::command(DRIVER_NUM, command::FREQUENCY, 0, 0) .to_result() .map(Hz) } pub fn get_ticks() -> Result { S::command(DRIVER_NUM, command::TIME, 0, 0).to_result() } pub fn get_milliseconds() -> Result { let ticks = Self::get_ticks()? as u64; let freq = (Self::get_frequency()?).0 as u64; Ok(ticks.saturating_div(freq / 1000)) } pub fn sleep_for(time: T) -> Result<(), ErrorCode> { let freq = Self::get_frequency()?; let ticks = time.to_ticks(freq); let called: Cell> = Cell::new(None); share::scope(|subscribe| { S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::CALLBACK }>(subscribe, &called)?; S::command(DRIVER_NUM, command::SET_RELATIVE, ticks.0, 0) .to_result() .map(|_when: u32| ())?; loop { S::yield_wait(); if let Some((_when, _ref)) = called.get() { return Ok(()); } } }) } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x0; // Command IDs #[allow(unused)] mod command { pub const EXISTS: u32 = 0; pub const FREQUENCY: u32 = 1; pub const TIME: u32 = 2; pub const STOP: u32 = 3; pub const SET_RELATIVE: u32 = 5; pub const SET_ABSOLUTE: u32 = 6; } #[allow(unused)] mod subscribe { pub const CALLBACK: u32 = 0; } ================================================ FILE: apis/peripherals/alarm/src/tests.rs ================================================ use libtock_unittest::fake; use crate::{Hz, Milliseconds, Ticks}; type Alarm = crate::Alarm; #[test] fn get_freq() { let kernel = fake::Kernel::new(); let driver = fake::Alarm::new(1000); kernel.add_driver(&driver); assert_eq!(Alarm::get_frequency(), Ok(Hz(1000))); } #[test] fn sleep() { let kernel = fake::Kernel::new(); let driver = fake::Alarm::new(1000); kernel.add_driver(&driver); assert_eq!(Alarm::sleep_for(Ticks(0)), Ok(())); assert_eq!(Alarm::sleep_for(Ticks(1000)), Ok(())); assert_eq!(Alarm::sleep_for(Milliseconds(1000)), Ok(())); } ================================================ FILE: apis/peripherals/gpio/Cargo.toml ================================================ [package] name = "libtock_gpio" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2018" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock gpio driver" [features] rust_embedded = ["embedded-hal"] [dependencies] libtock_platform = { path = "../../../platform" } embedded-hal = { version = "1.0", optional = true } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/peripherals/gpio/src/lib.rs ================================================ #![no_std] use core::marker::PhantomData; use libtock_platform::{ share::Handle, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; /// The GPIO driver. /// /// # Example /// ```ignore /// use libtock::gpio; /// /// // Set pin to high. /// let pin = gpio::Gpio::get_pin(0).unwrap().make_output().unwrap(); /// let _ = pin.set(); /// ``` #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum GpioState { Low = 0, High = 1, } pub enum PinInterruptEdge { Either = 0, Rising = 1, Falling = 2, } pub enum Error { Invalid, Failed, } pub trait Pull { const MODE: u32; } pub struct PullUp; impl Pull for PullUp { const MODE: u32 = 1; } pub struct PullDown; impl Pull for PullDown { const MODE: u32 = 2; } pub struct PullNone; impl Pull for PullNone { const MODE: u32 = 0; } pub struct Gpio(S); impl Gpio { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working, as it may still fail to allocate grant /// memory. pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } pub fn count() -> Result { S::command(DRIVER_NUM, GPIO_COUNT, 0, 0).to_result() } pub fn get_pin(pin: u32) -> Result, ErrorCode> { Self::disable(pin)?; Ok(Pin { pin_number: pin, _syscalls: PhantomData, }) } /// Register an interrupt listener /// /// There can be only one single listener registered at a time. /// Each time this function is used, it will replace the /// previously registered listener. pub fn register_listener<'share, F: Fn(u32, GpioState)>( listener: &'share GpioInterruptListener, subscribe: Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the interrupt listener /// /// This function may be used even if there was no /// previously registered listener. pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } } /// A wrapper around a closure to be registered and called when /// a gpio interrupt occurs. /// /// ```ignore /// let listener = GpioInterruptListener(|gpio, interrupt_edge| { /// // make use of the button's state /// }); /// ``` pub struct GpioInterruptListener(pub F); impl Upcall> for GpioInterruptListener { fn upcall(&self, gpio_index: u32, value: u32, _arg2: u32) { self.0(gpio_index, value.into()) } } impl From for GpioState { fn from(original: u32) -> GpioState { match original { 0 => GpioState::Low, _ => GpioState::High, } } } pub struct Pin { pin_number: u32, _syscalls: PhantomData, } impl Pin { pub fn make_output(&mut self) -> Result, ErrorCode> { Gpio::::enable_gpio_output(self.pin_number)?; Ok(OutputPin { pin: self }) } pub fn make_input(&self) -> Result, ErrorCode> { Gpio::::enable_gpio_input(self.pin_number, P::MODE)?; Ok(InputPin { pin: self, _pull: PhantomData, }) } } pub struct OutputPin<'a, S: Syscalls> { pin: &'a Pin, } impl OutputPin<'_, S> { pub fn toggle(&mut self) -> Result<(), ErrorCode> { Gpio::::toggle(self.pin.pin_number) } pub fn set(&mut self) -> Result<(), ErrorCode> { Gpio::::write(self.pin.pin_number, GpioState::High) } pub fn clear(&mut self) -> Result<(), ErrorCode> { Gpio::::write(self.pin.pin_number, GpioState::Low) } } pub struct InputPin<'a, S: Syscalls, P: Pull> { pin: &'a Pin, _pull: PhantomData

, } impl InputPin<'_, S, P> { pub fn read(&self) -> Result { Gpio::::read(self.pin.pin_number) } pub fn enable_interrupts(&self, edge: PinInterruptEdge) -> Result<(), ErrorCode> { Gpio::::enable_interrupts(self.pin.pin_number, edge) } pub fn disable_interrupts(&self) -> Result<(), ErrorCode> { Gpio::::disable_interrupts(self.pin.pin_number) } } impl Drop for OutputPin<'_, S> { fn drop(&mut self) { let _ = Gpio::::disable(self.pin.pin_number); } } impl Drop for InputPin<'_, S, P> { fn drop(&mut self) { let _ = Gpio::::disable(self.pin.pin_number); } } // ----------------------------------------------------------------------------- // Implementation details below // ----------------------------------------------------------------------------- impl Gpio { fn enable_gpio_output(pin: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, GPIO_ENABLE_OUTPUT, pin, 0).to_result() } fn enable_gpio_input(pin: u32, mode: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, GPIO_ENABLE_INPUT, pin, mode).to_result() } fn write(pin: u32, state: GpioState) -> Result<(), ErrorCode> { let action = match state { GpioState::Low => GPIO_CLEAR, _ => GPIO_SET, }; S::command(DRIVER_NUM, action, pin, 0).to_result() } fn read(pin: u32) -> Result { let pin_state: u32 = S::command(DRIVER_NUM, GPIO_READ_INPUT, pin, 0).to_result()?; Ok(pin_state.into()) } fn toggle(pin: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, GPIO_TOGGLE, pin, 0).to_result() } fn disable(pin: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, GPIO_DISABLE, pin, 0).to_result() } fn enable_interrupts(pin: u32, edge: PinInterruptEdge) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, GPIO_ENABLE_INTERRUPTS, pin, edge as u32).to_result() } fn disable_interrupts(pin: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, GPIO_DISABLE_INTERRUPTS, pin, 0).to_result() } } #[cfg(feature = "rust_embedded")] impl embedded_hal::digital::ErrorType for OutputPin<'_, S> { type Error = ErrorCode; } #[cfg(feature = "rust_embedded")] impl embedded_hal::digital::OutputPin for OutputPin<'_, S> { fn set_low(&mut self) -> Result<(), Self::Error> { self.clear() } fn set_high(&mut self) -> Result<(), Self::Error> { self.set() } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x4; // Command IDs const EXISTS: u32 = 0; const GPIO_ENABLE_OUTPUT: u32 = 1; const GPIO_SET: u32 = 2; const GPIO_CLEAR: u32 = 3; const GPIO_TOGGLE: u32 = 4; const GPIO_ENABLE_INPUT: u32 = 5; const GPIO_READ_INPUT: u32 = 6; const GPIO_ENABLE_INTERRUPTS: u32 = 7; const GPIO_DISABLE_INTERRUPTS: u32 = 8; const GPIO_DISABLE: u32 = 9; const GPIO_COUNT: u32 = 10; ================================================ FILE: apis/peripherals/gpio/src/tests.rs ================================================ use core::cell::Cell; use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake::{self, GpioMode, InterruptEdge, PullMode}; use crate::{GpioInterruptListener, GpioState, PinInterruptEdge, PullDown, PullNone, PullUp}; type Gpio = super::Gpio; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(Gpio::count(), Err(ErrorCode::NoDevice)); } #[test] fn num_gpio() { let kernel = fake::Kernel::new(); let driver = fake::Gpio::<10>::new(); kernel.add_driver(&driver); assert_eq!(Gpio::count(), Ok(10)); } // Tests the OutputPin implementation. #[test] fn output() { let kernel = fake::Kernel::new(); let driver = fake::Gpio::<10>::new(); driver.set_missing_gpio(1); kernel.add_driver(&driver); assert_eq!(Gpio::count(), Ok(10)); assert!(core::matches!(Gpio::get_pin(11), Err(ErrorCode::Invalid))); assert!(core::matches!(Gpio::get_pin(1), Err(ErrorCode::NoDevice))); let pin_0 = Gpio::get_pin(0); assert!(pin_0.is_ok()); let _ = pin_0.map(|mut pin| { let output_pin = pin.make_output(); assert!(output_pin.is_ok()); assert_eq!(driver.get_gpio_state(0).unwrap().mode, GpioMode::Output); let _ = output_pin.map(|mut pin| { assert_eq!(pin.set(), Ok(())); assert!(driver.get_gpio_state(0).unwrap().value); assert_eq!(pin.clear(), Ok(())); assert!(!driver.get_gpio_state(0).unwrap().value); assert_eq!(pin.toggle(), Ok(())); assert!(driver.get_gpio_state(0).unwrap().value); assert_eq!(pin.toggle(), Ok(())); assert!(!driver.get_gpio_state(0).unwrap().value); }); assert_eq!(driver.get_gpio_state(0).unwrap().mode, GpioMode::Disable); }); } // Tests the InputPin implementation #[test] fn input() { let kernel = fake::Kernel::new(); let driver = fake::Gpio::<10>::new(); driver.set_missing_gpio(1); kernel.add_driver(&driver); assert_eq!(Gpio::count(), Ok(10)); assert!(core::matches!(Gpio::get_pin(11), Err(ErrorCode::Invalid))); assert!(core::matches!(Gpio::get_pin(1), Err(ErrorCode::NoDevice))); let pin_0 = Gpio::get_pin(0); assert!(pin_0.is_ok()); let _ = pin_0.map(|pin| { let input_pin = pin.make_input::(); assert!(input_pin.is_ok()); assert_eq!( driver.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullNone) ); let input_pin = pin.make_input::(); assert!(input_pin.is_ok()); assert_eq!( driver.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullUp) ); let input_pin = pin.make_input::(); assert!(input_pin.is_ok()); assert_eq!( driver.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullDown) ); let _ = input_pin.map(|pin| { assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(pin.read(), Ok(GpioState::High)); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(pin.read(), Ok(GpioState::Low)); }); assert_eq!(driver.get_gpio_state(0).unwrap().mode, GpioMode::Disable); }); } // Tests the pin interrupts implementation #[test] fn interrupts() { let kernel = fake::Kernel::new(); let driver = fake::Gpio::<10>::new(); driver.set_missing_gpio(1); kernel.add_driver(&driver); assert_eq!(Gpio::count(), Ok(10)); let gpio_state = Cell::>::new(None); let listener = GpioInterruptListener(|gpio, state| { assert_eq!(gpio, 0); gpio_state.set(Some(state)); }); assert_eq!(Gpio::enable_interrupts(0, PinInterruptEdge::Either), Ok(())); share::scope(|subscribe| { assert_eq!(Gpio::register_listener(&listener, subscribe), Ok(())); assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio_state.get(), Some(GpioState::High)); }); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(core::matches!(Gpio::get_pin(11), Err(ErrorCode::Invalid))); assert!(core::matches!(Gpio::get_pin(1), Err(ErrorCode::NoDevice))); let pin_0 = Gpio::get_pin(0); assert!(pin_0.is_ok()); let _ = pin_0.map(|pin| { // Either let input_pin = pin.make_input::(); assert!(input_pin.is_ok()); assert_eq!( driver.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullNone) ); let _ = input_pin.map(|pin| { assert_eq!( pin.enable_interrupts(crate::PinInterruptEdge::Either), Ok(()) ); assert_eq!( driver.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Either) ); assert_eq!(driver.set_value(0, false), Ok(())); let gpio_state = Cell::>::new(None); let listener = GpioInterruptListener(|gpio, state| { assert_eq!(gpio, 0); gpio_state.set(Some(state)); }); share::scope(|subscribe| { assert_eq!(Gpio::register_listener(&listener, subscribe), Ok(())); assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio_state.get(), Some(GpioState::High)); gpio_state.set(None); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio_state.get(), Some(GpioState::Low)); assert_eq!(pin.disable_interrupts(), Ok(())); assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); }); // Rising let input_pin = pin.make_input::(); assert!(input_pin.is_ok()); assert_eq!( driver.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullNone) ); let _ = input_pin.map(|pin| { assert_eq!( pin.enable_interrupts(crate::PinInterruptEdge::Rising), Ok(()) ); assert_eq!( driver.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Rising) ); assert_eq!(driver.set_value(0, false), Ok(())); let gpio_state = Cell::>::new(None); let listener = GpioInterruptListener(|gpio, state| { assert_eq!(gpio, 0); gpio_state.set(Some(state)); }); share::scope(|subscribe| { assert_eq!(Gpio::register_listener(&listener, subscribe), Ok(())); assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio_state.get(), Some(GpioState::High)); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(pin.disable_interrupts(), Ok(())); assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); }); // Falling let input_pin = pin.make_input::(); assert!(input_pin.is_ok()); assert_eq!( driver.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullNone) ); let _ = input_pin.map(|pin| { assert_eq!( pin.enable_interrupts(crate::PinInterruptEdge::Falling), Ok(()) ); assert_eq!( driver.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Falling) ); assert_eq!(driver.set_value(0, false), Ok(())); let gpio_state = Cell::>::new(None); let listener = GpioInterruptListener(|gpio, state| { assert_eq!(gpio, 0); gpio_state.set(Some(state)); }); share::scope(|subscribe| { assert_eq!(Gpio::register_listener(&listener, subscribe), Ok(())); assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio_state.get(), Some(GpioState::Low)); assert_eq!(pin.disable_interrupts(), Ok(())); assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); }); }); } // Tests the pin event subcribe implementation #[test] fn subscribe() { let kernel = fake::Kernel::new(); let driver = fake::Gpio::<10>::new(); driver.set_missing_gpio(1); kernel.add_driver(&driver); assert_eq!(Gpio::count(), Ok(10)); let gpio_state = Cell::>::new(None); let listener = GpioInterruptListener(|gpio, state| { assert_eq!(gpio, 0); gpio_state.set(Some(state)); }); assert_eq!(Gpio::enable_interrupts(0, PinInterruptEdge::Either), Ok(())); share::scope(|subscribe| { assert_eq!(Gpio::register_listener(&listener, subscribe), Ok(())); assert_eq!(driver.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio_state.get(), Some(GpioState::High)); }); assert_eq!(driver.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); } ================================================ FILE: apis/peripherals/i2c_master/Cargo.toml ================================================ [package] name = "libtock_i2c_master" version = "0.1.0" authors = [ "Tock Project Developers ", "Alistair Francis ", ] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock I2C master driver" [dependencies] libtock_platform = { path = "../../../platform" } ================================================ FILE: apis/peripherals/i2c_master/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform as platform; use libtock_platform::allow_rw::AllowRw; use libtock_platform::share; use libtock_platform::subscribe::Subscribe; use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; pub struct I2CMaster(S, C); impl I2CMaster { pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, i2c_master_cmd::EXISTS, 0, 0).to_result() } /// # Summary /// /// Perform an I2C write followed by a read. /// /// TODO: Add async support /// /// # Parameter /// /// * `addr`: Slave device address /// * `buf`: Buffer /// * `w_len`: Number of bytes to write from @w_buf /// * `r_len`: Number of bytes to read into @r_buf /// /// # Returns /// On success: Returns Ok(()) /// On failure: Err(ErrorCode) pub fn i2c_master_write_read_sync( addr: u16, buf: &mut [u8], w_len: u16, r_len: u16, ) -> Result<(), ErrorCode> { if w_len as usize > buf.len() || r_len as usize > buf.len() { return Err(ErrorCode::NoMem); } let called: Cell> = Cell::new(None); let cmd_arg0: u32 = (w_len as u32) << 8 | addr as u32; share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::MASTER }>, Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); S::allow_rw::(allow_rw, buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_READ_WRITE }>( subscribe, &called, )?; S::command( DRIVER_NUM, i2c_master_cmd::MASTER_WRITE, cmd_arg0, r_len.into(), ) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, status, _)) = called.get() { assert_eq!(r0, 0); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }) } /// # Summary /// /// Write to an I2C device the data from the buffer pointed by @buf. This function is /// synchronous and returns only when the operation has completed. /// /// TODO: Add async support /// /// # Parameter /// /// * `addr`: Slave device address /// * `buf`: Storage buffer, this should be bigger than @len /// * `len`: Number of bytes to read into @buf /// /// # Returns /// On success: Returns Ok(()) /// On failure: Err(ErrorCode) pub fn i2c_master_write_sync(addr: u16, buf: &mut [u8], len: u16) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::MASTER }>, Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); S::allow_rw::(allow_rw, buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_WRITE }>(subscribe, &called)?; S::command( DRIVER_NUM, i2c_master_cmd::MASTER_WRITE, addr.into(), len.into(), ) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, status, _)) = called.get() { assert_eq!(r0, 0); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }) } /// # Summary /// /// Read from an I2C device the data to the buffer pointed by @buf. This function is /// synchronous and returns only when the operation has completed. /// /// TODO: Add async support /// /// # Parameter /// /// * `addr`: Slave device address /// * `buf`: Storage buffer, this should be bigger than @len /// * `len`: Number of bytes to read into @buf /// /// # Returns /// On success: Returns Ok(()) /// On failure: Err(ErrorCode) pub fn i2c_master_read_sync(addr: u16, buf: &mut [u8], len: u16) -> Result<(), ErrorCode> { let called: Cell> = Cell::new(None); share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::MASTER }>, Subscribe<_, DRIVER_NUM, { subscribe::MASTER_READ }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); S::allow_rw::(allow_rw, buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_READ }>(subscribe, &called)?; S::command( DRIVER_NUM, i2c_master_cmd::MASTER_READ, addr.into(), len.into(), ) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, status, _)) = called.get() { assert_eq!(r0, 0); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }) } } /// System call configuration trait for `I2CMaster`. pub trait Config: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config { } impl Config for T { } // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x20003; #[allow(unused)] mod subscribe { pub const MASTER_READ: u32 = 0; pub const MASTER_WRITE: u32 = 0; pub const MASTER_READ_WRITE: u32 = 0; } /// Ids for read-write allow buffers #[allow(unused)] mod rw_allow { pub const MASTER: u32 = 1; } #[allow(unused)] mod i2c_master_cmd { pub const EXISTS: u32 = 0; pub const MASTER_WRITE: u32 = 1; pub const MASTER_READ: u32 = 2; pub const MASTER_WRITE_READ: u32 = 3; } ================================================ FILE: apis/peripherals/i2c_master_slave/Cargo.toml ================================================ [package] name = "libtock_i2c_master_slave" version = "0.1.0" authors = [ "Tock Project Developers ", "Wilfred Mallawa ", ] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock I2C master-slave driver" [dependencies] libtock_platform = { path = "../../../platform" } ================================================ FILE: apis/peripherals/i2c_master_slave/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform as platform; use libtock_platform::allow_ro::AllowRo; use libtock_platform::allow_rw::AllowRw; use libtock_platform::share; use libtock_platform::subscribe::Subscribe; use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; pub struct I2CMasterSlave(S, C); impl I2CMasterSlave { pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, i2c_master_slave_cmd::EXISTS, 0, 0).to_result() } /// # Summary /// /// Perform an I2C write to the slave device on @addr. /// /// # Parameter /// /// * `addr`: Slave device address /// * `buf`: Storage buffer, this should be bigger than @len /// * `len`: Number of bytes to write from @buf /// /// # Returns /// On success: Returns Ok(()), @len bytes were written from @buf. /// On failure: Err(ErrorCode), with failure ErrorCode. pub fn i2c_master_slave_write_sync( addr: u16, buffer: &[u8], len: u16, ) -> Result<(), ErrorCode> { // We could write just the buffer length, but this may lead to // ambiguities for the caller. So Err out early. if len as usize > buffer.len() { return Err(ErrorCode::NoMem); } let called: Cell> = Cell::new(None); // The kernel will split this argument into upper length and lower address. let cmd_arg0: u32 = (len as u32) << 16 | addr as u32; share::scope::< ( AllowRo<_, DRIVER_NUM, { ro_allow::MASTER_TX }>, Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE }>, ), _, _, >(|handle| { let (allow_ro, subscribe) = handle.split(); S::allow_ro::(allow_ro, buffer)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_WRITE }>(subscribe, &called)?; S::command(DRIVER_NUM, i2c_master_slave_cmd::MASTER_WRITE, cmd_arg0, 0) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, status, _r1)) = called.get() { // Kernel uses a different cmd number for this... assert_eq!(r0, 0); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }) } /// # Summary /// /// Perform an I2C read from the the slave device with the slave address of @addr. /// /// # Parameter /// /// * `addr`: Slave device address /// * `buf`: Storage buffer, this should be bigger than @len /// * `len`: Number of bytes to read into @buf /// /// # Returns /// On success: Returns Ok(()) with @bytes_received valid. /// On failure: Err(ErrorCode), Failure ErrorCode and @bytes_received is invalid. /// /// Note: @bytes_received is the first return tuple index (valid only on success). pub fn i2c_master_slave_read_sync( addr: u16, buf: &mut [u8], len: u16, ) -> (usize, Result<(), ErrorCode>) { if len as usize > buf.len() { return (0, Err(ErrorCode::NoMem)); } // This is the total amount of bytes read if the operation was a success. // Otherwise, it is invalid. let mut bytes_received: usize = core::cmp::min(buf.len(), len as usize); let called: Cell> = Cell::new(None); // The kernel will split this argument into upper length and lower address. let cmd_arg0: u32 = (len as u32) << 16 | addr as u32; let r = share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::MASTER_RX }>, Subscribe<_, DRIVER_NUM, { subscribe::MASTER_READ }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); S::allow_rw::(allow_rw, buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_READ }>(subscribe, &called)?; // When this fails, `called` is guaranteed unmodified, // because upcalls are never processed until we call `yield`. S::command(DRIVER_NUM, i2c_master_slave_cmd::MASTER_READ, cmd_arg0, 0) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, _read_len, status)) = called.get() { // TODO: The kernel I2C api does not currently return the read_len, so this // will be invalid. We should keep track, likely assume the transfer was // done if no error. See: tock@capsules/core/src/i2c_master_slave_driver.rs:129 // see: https://github.com/tock/tock/issues/3735 // Kernel uses a different cmd number for this... assert_eq!(r0, 1); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }); // If the operation failed, make bytes received zero so that the caller isn't confused in case // the error is not handled properly. That is, in case of an error, we cannot guarantee the // number of bytes received. if r.is_err() { bytes_received = 0; } (bytes_received, r) } /// # Summary /// /// Perform an I2C write followed by a read. /// /// Note: The kernel uses the TX buffer for both actions, such that if you request a /// a read that exceeds the buffer length of @w_buf, the read will be /// limited to the capacity of @w_buf. This API will detect such a case /// and error to avoid ambiguities until we have a better solution in the kernel. /// /// # Parameter /// /// * `addr`: Slave device address /// * `w_buf`: Write buffer /// * `r_buf`: Read buffer /// * `w_len`: Number of bytes to write from @w_buf /// * `r_len`: Number of bytes to read into @r_buf /// /// # Returns /// On success: Returns Ok(()) with @bytes_received valid. /// On failure: Err(ErrorCode), Failure ErrorCode and @bytes_received is invalid. /// /// Note: @bytes_received is the first return tuple index (valid only on success). pub fn i2c_master_slave_write_read_sync( addr: u16, w_buf: &mut [u8], r_buf: &mut [u8], w_len: u16, r_len: u16, ) -> (usize, Result<(), ErrorCode>) { if w_len as usize > w_buf.len() || r_len as usize > r_buf.len() { return (0, Err(ErrorCode::NoMem)); } // TODO: Kernel uses the TX Buffer to perform both RX/TX for a write_read, so if // the @w_buff is smaller than @r_len. The subsequent read will stop prematurely. // So let's error here until that is addressed. if r_len as usize > w_buf.len() { return (0, Err(ErrorCode::NoMem)); } // This is the total amount of bytes read if the operation was a success. // Otherwise, it is invalid. let mut bytes_received: usize = core::cmp::min(r_buf.len(), r_len as usize); let called: Cell> = Cell::new(None); let cmd_arg0: u32 = (w_len as u32) << 16 | (r_len as u32) << 8 | addr as u32; let r = share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::MASTER_RX }>, AllowRo<_, DRIVER_NUM, { ro_allow::MASTER_TX }>, Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE_READ }>, ), _, _, >(|handle| { let (allow_rw, allow_ro, subscribe) = handle.split(); S::allow_rw::(allow_rw, r_buf)?; S::allow_ro::(allow_ro, w_buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_WRITE_READ }>( subscribe, &called, )?; // When this fails, `called` is guaranteed unmodified, // because upcalls are never processed until we call `yield`. S::command( DRIVER_NUM, i2c_master_slave_cmd::MASTER_WRITE_READ, cmd_arg0, 0, ) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, _read_len, status)) = called.get() { // TODO: The kernel I2C api does not currently return the read_len, so this // will be invalid. We should keep track, likely assume the transfer was // done if no error. See: tock@capsules/core/src/i2c_master_slave_driver.rs:129 // see: https://github.com/tock/tock/issues/3735 assert_eq!(r0, i2c_master_slave_cmd::MASTER_WRITE_READ); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }); // If the operation failed, make bytes received zero so that the caller isn't confused in case // the error is not handled properly. That is, in case of an error, we cannot guarantee the // number of bytes received. if r.is_err() { bytes_received = 0; } (bytes_received, r) } /// # Summary /// /// Set the slave address for this device for slave mode operation. The IP should respond /// to @addr. /// /// # Parameter /// /// * `addr`: Slave device address to set /// /// # Returns /// On success: Returns Ok(()) /// On failure: Err(ErrorCode) pub fn i2c_master_slave_set_slave_address(addr: u8) -> Result<(), ErrorCode> { // We do not count the R/W bit as part of the address, so the // valid range is 0x00-0x7f if addr > 0x7f { return Err(ErrorCode::Invalid); } S::command( DRIVER_NUM, i2c_master_slave_cmd::SLAVE_SET_ADDR, addr as u32, 0, ) .to_result() } /// # Summary /// /// Expect a write from master into the buffer pointed by @buf. This function is /// synchronous and returns only when the operation has completed. /// /// TODO: Add async support /// /// Note: As we do not know the size of data to be sent from a master device, /// it is suggested to allocated a large buffer to accommodate bigger transfers. /// /// # Parameter /// /// * `buf`: Buffer into which to copy data from master /// /// # Returns /// On success: Returns (bytes_read, Ok(())) /// On failure: (0, Err(ErrorCode)) pub fn i2c_master_slave_write_recv_sync(buf: &mut [u8]) -> (usize, Result<(), ErrorCode>) { let called: Cell> = Cell::new(None); let mut bytes_recvd_ret: u32 = 0; let r = share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::SLAVE_RX }>, Subscribe<_, DRIVER_NUM, { subscribe::SLAVE_WRITE_RECV }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); S::allow_rw::(allow_rw, buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::SLAVE_READ }>(subscribe, &called)?; S::command(DRIVER_NUM, i2c_master_slave_cmd::SLAVE_START_LISTEN, 0, 0) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, bytes_recvd, status)) = called.get() { // TODO: Ensure we are returning from the correct upcall and not from an unexpected `read_expect` // Everything in this module subscribes to `0`. Which can be problematic from an async context. assert_eq!(r0, i2c_master_slave_cmd::SLAVE_START_LISTEN); return match status { 0 => { bytes_recvd_ret = bytes_recvd; Ok(()) } e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }); (bytes_recvd_ret as usize, r) } /// # Summary /// /// Expect a write from master into the buffer pointed by @buf. This function is /// synchronous and returns only when the operation has completed. /// /// TODO: Add async support /// /// # Parameter /// /// * `buf`: Buffer from which to transfer data from /// * `len`: max number of bytes from buffer to transfer /// /// # Returns /// On success: Returns (bytes_sent, Ok(())) /// On failure: (0, Err(ErrorCode)) pub fn i2c_master_slave_read_send_sync( buf: &[u8], len: usize, ) -> (usize, Result<(), ErrorCode>) { if len > buf.len() { return (0, Err(ErrorCode::Invalid)); } let called: Cell> = Cell::new(None); let mut bytes_sent_ret: u32 = 0; let r = share::scope::< ( AllowRo<_, DRIVER_NUM, { ro_allow::SLAVE_TX }>, Subscribe<_, DRIVER_NUM, { subscribe::SLAVE_READ_SEND }>, ), _, _, >(|handle| { let (allow_ro, subscribe) = handle.split(); S::allow_ro::(allow_ro, buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::SLAVE_READ }>(subscribe, &called)?; S::command( DRIVER_NUM, i2c_master_slave_cmd::SLAVE_READ_SEND, len as u32, 0, ) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, bytes_sent, status)) = called.get() { // TODO: Ensure we are returning from the correct upcall and not from an unexpected `read_expect` // Everything in this module subscribes to `0`. Which can be problematic from an async context. assert_eq!(r0, i2c_master_slave_cmd::SLAVE_READ_SEND); return match status { 0 => { bytes_sent_ret = bytes_sent; Ok(()) } e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }); (bytes_sent_ret as usize, r) } } /// System call configuration trait for `I2CMaster`. pub trait Config: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config { } impl Config for T { } // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x20006; #[allow(unused)] mod subscribe { // TODO: It seems like only 0 is supported by the i2c_master_slave capsule currently // would be nice to improve this. pub const MASTER_WRITE: u32 = 0; pub const MASTER_WRITE_READ: u32 = 0; pub const MASTER_READ: u32 = 0; pub const SLAVE_READ: u32 = 0; pub const SLAVE_WRITE_RECV: u32 = 0; pub const SLAVE_READ_SEND: u32 = 0; } /// Ids for read-only allow buffers #[allow(unused)] mod ro_allow { pub const MASTER_TX: u32 = 0; pub const SLAVE_TX: u32 = 2; /// The number of allow buffers the kernel stores for this grant pub const COUNT: u8 = 3; } /// Ids for read-write allow buffers #[allow(unused)] mod rw_allow { pub const MASTER_RX: u32 = 1; pub const SLAVE_RX: u32 = 3; } #[allow(unused)] mod i2c_buffers { pub const MASTER_WRITE: u32 = 0; pub const MASTER_READ: u32 = 1; pub const SLAVE_READ: u32 = 2; pub const SLAVE_WRITE: u32 = 3; } #[allow(unused)] mod i2c_master_slave_cmd { pub const EXISTS: u32 = 0; pub const MASTER_WRITE: u32 = 1; pub const MASTER_READ: u32 = 2; pub const SLAVE_START_LISTEN: u32 = 3; pub const SLAVE_READ_SEND: u32 = 4; pub const SLAVE_SET_ADDR: u32 = 6; pub const MASTER_WRITE_READ: u32 = 7; } ================================================ FILE: apis/peripherals/rng/Cargo.toml ================================================ [package] name = "libtock_rng" version = "0.1.0" authors = [ "Tock Project Developers ", "Cosmin Gabriel Georgescu ", ] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock rng driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/peripherals/rng/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::{ share, subscribe::OneId, AllowRw, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; pub struct Rng(S); impl Rng { /// Check if the RNG kernel driver exists pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } /// Request `n` bytes of randomness in an asynchronous way. /// Users must first share a buffer slice with the kernel and register an Rng listener pub fn get_bytes_async(n: u32) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, GET_BYTES, n, 0).to_result() } /// Share a buffer slice with the kernel. /// Must be used in conjunction with the `share::scope` function pub fn allow_buffer<'share>( buf: &'share mut [u8], allow_rw: share::Handle>, ) -> Result<(), ErrorCode> { S::allow_rw::(allow_rw, buf) } pub fn unallow_buffer() { S::unallow_rw(DRIVER_NUM, 0) } /// Register an Rng listener to be called when an upcall is serviced /// Must be used in conjunction with the `share::scope` function pub fn register_listener<'share, F: Fn(u32)>( listener: &'share RngListener, subscribe: share::Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Ask to fill the provided `buf` with `n` random bytes. /// If `n > buf.len()`, it will simply fill the whole buffer. pub fn get_bytes_sync(buf: &mut [u8], n: u32) -> Result<(), ErrorCode> { let called = Cell::new(false); share::scope::<(AllowRw, Subscribe), _, _>(|handle| { let (allow_rw, subscribe) = handle.split(); // Share the provided buffer with the kernel S::allow_rw::(allow_rw, buf)?; // Subscribe for an upcall with the kernel S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &called)?; // Send the command to the kernel driver to fill the allowed_readwrite buffer S::command(DRIVER_NUM, GET_BYTES, n, 0).to_result::<(), ErrorCode>()?; // Wait for a callback to happen while !called.get() { S::yield_wait(); } Ok(()) }) } } /// The provided listener to be called. /// Interior function operates on the number of random bytes filled into the buffer pub struct RngListener(pub F); impl Upcall> for RngListener { fn upcall(&self, _: u32, arg1: u32, _: u32) { (self.0)(arg1) } } // ------------- // DRIVER NUMBER // ------------- const DRIVER_NUM: u32 = 0x40001; // --------------- // COMMAND NUMBERS // --------------- const EXISTS: u32 = 0; const GET_BYTES: u32 = 1; ================================================ FILE: apis/peripherals/spi_controller/Cargo.toml ================================================ [package] name = "libtock_spi_controller" version = "0.1.0" authors = [ "Tock Project Developers ", "Alistair Francis ", ] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock SPI controller driver" [dependencies] libtock_platform = { path = "../../../platform" } ================================================ FILE: apis/peripherals/spi_controller/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform as platform; use libtock_platform::allow_rw::AllowRw; use libtock_platform::share; use libtock_platform::subscribe::Subscribe; use libtock_platform::AllowRo; use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; pub struct SpiController(S, C); impl SpiController { pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, spi_controller_cmd::EXISTS, 0, 0).to_result() } /// # Summary /// /// Perform an I2C write followed by a read. /// /// TODO: Add async support /// /// # Parameter /// /// * `addr`: Slave device address /// * `buf`: Buffer /// * `w_len`: Number of bytes to write from @w_buf /// * `r_len`: Number of bytes to read into @r_buf /// /// # Returns /// On success: Returns Ok(()) /// On failure: Err(ErrorCode) pub fn spi_controller_write_read_sync( w_buf: &[u8], r_buf: &mut [u8], len: u32, ) -> Result<(), ErrorCode> { if len as usize > w_buf.len() || len as usize > r_buf.len() { return Err(ErrorCode::NoMem); } let called: Cell> = Cell::new(None); share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::READ }>, AllowRo<_, DRIVER_NUM, { ro_allow::WRITE }>, Subscribe<_, DRIVER_NUM, { subscribe::COMPLETE }>, ), _, _, >(|handle| { let (allow_rw, allow_ro, subscribe) = handle.split(); S::allow_rw::(allow_rw, r_buf)?; S::allow_ro::(allow_ro, w_buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::COMPLETE }>(subscribe, &called)?; S::command(DRIVER_NUM, spi_controller_cmd::READ_WRITE_BYTES, len, 0) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, status, _)) = called.get() { assert_eq!(r0, len); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }) } pub fn spi_controller_write_sync(w_buf: &[u8], len: u32) -> Result<(), ErrorCode> { if len as usize > w_buf.len() { return Err(ErrorCode::NoMem); } let called: Cell> = Cell::new(None); share::scope::< ( AllowRo<_, DRIVER_NUM, { ro_allow::WRITE }>, Subscribe<_, DRIVER_NUM, { subscribe::COMPLETE }>, ), _, _, >(|handle| { let (allow_ro, subscribe) = handle.split(); S::allow_ro::(allow_ro, w_buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::COMPLETE }>(subscribe, &called)?; S::command(DRIVER_NUM, spi_controller_cmd::READ_WRITE_BYTES, len, 0) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, status, _)) = called.get() { assert_eq!(r0, len); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }) } pub fn spi_controller_read_sync(r_buf: &mut [u8], len: u32) -> Result<(), ErrorCode> { if len as usize > r_buf.len() { return Err(ErrorCode::NoMem); } let called: Cell> = Cell::new(None); share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::READ }>, Subscribe<_, DRIVER_NUM, { subscribe::COMPLETE }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); S::allow_rw::(allow_rw, r_buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::COMPLETE }>(subscribe, &called)?; S::command(DRIVER_NUM, spi_controller_cmd::READ_BYTES, len, 0) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, status, _)) = called.get() { assert_eq!(r0, len); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }) } pub fn spi_controller_inplace_write_read_sync( r_buf: &mut [u8], len: u32, ) -> Result<(), ErrorCode> { if len as usize > r_buf.len() { return Err(ErrorCode::NoMem); } let called: Cell> = Cell::new(None); share::scope::< ( AllowRw<_, DRIVER_NUM, { rw_allow::READ }>, Subscribe<_, DRIVER_NUM, { subscribe::COMPLETE }>, ), _, _, >(|handle| { let (allow_rw, subscribe) = handle.split(); S::allow_rw::(allow_rw, r_buf)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::COMPLETE }>(subscribe, &called)?; S::command( DRIVER_NUM, spi_controller_cmd::INPLACE_READ_WRITE_BYTES, len, 0, ) .to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some((r0, status, _)) = called.get() { assert_eq!(r0, len); return match status { 0 => Ok(()), e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), }; } } }) } } /// System call configuration trait for `SpiController`. pub trait Config: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config { } impl Config for T { } // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x20001; #[allow(unused)] mod subscribe { pub const COMPLETE: u32 = 0; } #[allow(unused)] mod ro_allow { pub const WRITE: u32 = 0; } #[allow(unused)] mod rw_allow { pub const READ: u32 = 0; } #[allow(unused)] mod spi_controller_cmd { pub const EXISTS: u32 = 0; pub const READ_WRITE_BYTES: u32 = 2; pub const SET_BAUD: u32 = 5; pub const GET_BAUD: u32 = 6; pub const SET_PHASE: u32 = 7; pub const GET_PHASE: u32 = 8; pub const SET_POLARITY: u32 = 9; pub const GET_POLARITY: u32 = 10; pub const READ_BYTES: u32 = 11; pub const INPLACE_READ_WRITE_BYTES: u32 = 12; } ================================================ FILE: apis/sensors/air_quality/Cargo.toml ================================================ [package] name = "libtock_air_quality" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock air quality driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/sensors/air_quality/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::subscribe::OneId; use libtock_platform::{ share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; use Value::{Tvoc, CO2}; enum Value { CO2 = READ_CO2 as isize, Tvoc = READ_TVOC as isize, } pub struct AirQuality(S); impl AirQuality { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working. pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } /// Register an events listener pub fn register_listener<'share, F: Fn(u32)>( listener: &'share AirQualityListener, subscribe: Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Initiate a CO2 measurement. /// /// This function is used both for synchronous and asynchronous readings pub fn read_co2() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_CO2, 0, 0).to_result() } /// Initiate a TVOC measurement. /// /// This function is used both for synchronous and asynchronous readings pub fn read_tvoc() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_TVOC, 0, 0).to_result() } /// Public wrapper for `read_data_sync` for CO2 synchronous measurement pub fn read_co2_sync() -> Result { Self::read_data_sync(CO2) } /// Public wrapper for `read_data_sync` for TVOC synchronous measurement pub fn read_tvoc_sync() -> Result { Self::read_data_sync(Tvoc) } /// Read both CO2 and TVOC values synchronously pub fn read_sync() -> Result<(u32, u32), ErrorCode> { match (Self::read_data_sync(CO2), Self::read_data_sync(Tvoc)) { (Ok(co2_value), Ok(tvoc_value)) => Ok((co2_value, tvoc_value)), (Err(co2_error), _) => Err(co2_error), (_, Err(tvoc_error)) => Err(tvoc_error), } } /// Initiate a synchronous CO2 or TVOC measurement, based on the `read_type`. /// Returns Ok(value) if the operation was successful fn read_data_sync(read_type: Value) -> Result { let data_cell: Cell> = Cell::new(None); let listener = AirQualityListener(|data_val| { data_cell.set(Some(data_val)); }); scope(|subscribe| { Self::register_listener(&listener, subscribe)?; match read_type { CO2 => { Self::read_co2()?; while data_cell.get().is_none() { S::yield_wait(); } match data_cell.get() { None => Err(ErrorCode::Fail), Some(co2_value) => Ok(co2_value), } } Tvoc => { Self::read_tvoc()?; while data_cell.get().is_none() { S::yield_wait(); } match data_cell.get() { None => Err(ErrorCode::Fail), Some(tvoc_value) => Ok(tvoc_value), } } } }) } } pub struct AirQualityListener(pub F); impl Upcall> for AirQualityListener { fn upcall(&self, data_val: u32, _arg1: u32, _arg2: u32) { self.0(data_val) } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60007; // Command IDs const EXISTS: u32 = 0; const READ_CO2: u32 = 2; const READ_TVOC: u32 = 3; ================================================ FILE: apis/sensors/air_quality/src/tests.rs ================================================ use crate::AirQualityListener; use core::cell::Cell; use libtock_platform::{share::scope, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; type AirQuality = super::AirQuality; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(AirQuality::exists(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::AirQuality::new(); kernel.add_driver(&driver); assert_eq!(AirQuality::exists(), Ok(())); } #[test] fn read_co2() { let kernel = fake::Kernel::new(); let driver = fake::AirQuality::new(); kernel.add_driver(&driver); assert_eq!(AirQuality::read_co2(), Ok(())); assert!(driver.is_busy()); assert_eq!(AirQuality::read_co2(), Err(ErrorCode::Busy)); assert_eq!(AirQuality::read_co2_sync(), Err(ErrorCode::Busy)); } #[test] fn read_tvoc() { let kernel = fake::Kernel::new(); let driver = fake::AirQuality::new(); kernel.add_driver(&driver); assert_eq!(AirQuality::read_tvoc(), Ok(())); assert!(driver.is_busy()); assert_eq!(AirQuality::read_tvoc(), Err(ErrorCode::Busy)); assert_eq!(AirQuality::read_tvoc_sync(), Err(ErrorCode::Busy)); } #[test] fn register_unregister_listener() { let kernel = fake::Kernel::new(); let driver = fake::AirQuality::new(); kernel.add_driver(&driver); let data_cell: Cell> = Cell::new(None); let listener = AirQualityListener(|data_val| { data_cell.set(Some(data_val)); }); scope(|subscribe| { assert_eq!(AirQuality::read_co2(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(AirQuality::read_tvoc(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(AirQuality::register_listener(&listener, subscribe), Ok(())); assert_eq!(AirQuality::read_co2(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(data_cell.get(), Some(100)); assert_eq!(AirQuality::read_tvoc(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(data_cell.get(), Some(100)); AirQuality::unregister_listener(); assert_eq!(AirQuality::read_co2(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(AirQuality::read_tvoc(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); } #[test] fn read_co2_sync() { let kernel = fake::Kernel::new(); let driver = fake::AirQuality::new(); kernel.add_driver(&driver); driver.set_value_sync(100); assert_eq!(AirQuality::read_co2_sync(), Ok(100)); } #[test] fn read_tvoc_sync() { let kernel = fake::Kernel::new(); let driver = fake::AirQuality::new(); kernel.add_driver(&driver); driver.set_value_sync(100); assert_eq!(AirQuality::read_tvoc_sync(), Ok(100)); } #[test] fn read_sync() { let kernel = fake::Kernel::new(); let driver = fake::AirQuality::new(); kernel.add_driver(&driver); driver.set_values_sync(100, 200); assert_eq!(AirQuality::read_sync(), Ok((100, 200))) } ================================================ FILE: apis/sensors/ambient_light/Cargo.toml ================================================ [package] name = "libtock_ambient_light" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock ambient light driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/sensors/ambient_light/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::{ share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; pub struct AmbientLight(S); impl AmbientLight { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working. pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } /// Initiate a light intensity reading. pub fn read_intensity() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_INTENSITY, 0, 0).to_result() } /// Register an events listener pub fn register_listener<'share, F: Fn(u32)>( listener: &'share IntensityListener, subscribe: share::Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Initiate a synchronous light intensity measurement. /// Returns Ok(intensity_value) if the operation was successful /// intensity_value is returned in lux pub fn read_intensity_sync() -> Result { let intensity_cell: Cell> = Cell::new(None); let listener = IntensityListener(|intensity_val| { intensity_cell.set(Some(intensity_val)); }); share::scope(|subscribe| { Self::register_listener(&listener, subscribe)?; Self::read_intensity()?; while intensity_cell.get().is_none() { S::yield_wait(); } match intensity_cell.get() { None => Err(ErrorCode::Busy), Some(intensity_val) => Ok(intensity_val), } }) } } /// A wrapper around a closure to be registered and called when /// a luminance reading is done. /// /// ```ignore /// let listener = IntensityListener(|intensity_val| { /// // make use of the intensity value /// }); /// ``` pub struct IntensityListener(pub F); impl Upcall> for IntensityListener { fn upcall(&self, intensity: u32, _arg1: u32, _arg2: u32) { self.0(intensity) } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60002; // Command IDs const EXISTS: u32 = 0; const READ_INTENSITY: u32 = 1; ================================================ FILE: apis/sensors/ambient_light/src/tests.rs ================================================ use core::cell::Cell; use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; use crate::IntensityListener; type AmbientLight = super::AmbientLight; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(AmbientLight::exists(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::AmbientLight::new(); kernel.add_driver(&driver); assert_eq!(AmbientLight::exists(), Ok(())); } #[test] fn read_temperature() { let kernel = fake::Kernel::new(); let driver = fake::AmbientLight::new(); kernel.add_driver(&driver); assert_eq!(AmbientLight::read_intensity(), Ok(())); assert!(driver.is_busy()); assert_eq!(AmbientLight::read_intensity(), Err(ErrorCode::Busy)); assert_eq!(AmbientLight::read_intensity_sync(), Err(ErrorCode::Busy)); } #[test] fn register_unregister_listener() { let kernel = fake::Kernel::new(); let driver = fake::AmbientLight::new(); kernel.add_driver(&driver); let intensity_cell: Cell> = Cell::new(None); let listener = IntensityListener(|val| { intensity_cell.set(Some(val)); }); share::scope(|subscribe| { assert_eq!(AmbientLight::read_intensity(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!( AmbientLight::register_listener(&listener, subscribe), Ok(()) ); assert_eq!(AmbientLight::read_intensity(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(intensity_cell.get(), Some(100)); AmbientLight::unregister_listener(); assert_eq!(AmbientLight::read_intensity(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); } #[test] fn read_temperature_sync() { let kernel = fake::Kernel::new(); let driver = fake::AmbientLight::new(); kernel.add_driver(&driver); driver.set_value_sync(1000); assert_eq!(AmbientLight::read_intensity_sync(), Ok(1000)); } ================================================ FILE: apis/sensors/ninedof/Cargo.toml ================================================ [package] name = "libtock_ninedof" version = "0.1.0" authors = [ "Tock Project Developers " ] license = "Apache-2.0 OR MIT" edition = "2018" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock nine degrees of freedom driver" [dependencies] libtock_platform = { path = "../../../platform" } libm = "0.2.7" [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/sensors/ninedof/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::{ share, share::Handle, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; pub struct NineDof(S); #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct NineDofData { pub x: i32, pub y: i32, pub z: i32, } impl NineDof { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working. pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } /// Initiate a accelerometer measurement. /// This function is used both for synchronous and asynchronous readings pub fn read_accelerometer() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_ACCELEROMETER, 0, 0).to_result() } /// Initiate a magnetometer measurement. /// This function is used both for synchronous and asynchronous readings pub fn read_magnetometer() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_MAGNETOMETER, 0, 0).to_result() } /// Initiate a gyroscope measurement. /// This function is used both for synchronous and asynchronous readings pub fn read_gyro() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_GYRO, 0, 0).to_result() } /// Unregister an events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Register an events listener pub fn register_listener<'share, F: Fn(NineDofData)>( listener: &'share NineDofListener, subscribe: Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Initiate a synchronous accelerometer measurement. /// Returns Ok(accelerometer_value) if the operation was successful /// Returns Err(ErrorCode) if the operation was unsuccessful pub fn read_accelerometer_sync() -> Result { let data_cell: Cell> = Cell::new(None); let listener = NineDofListener(|data| { data_cell.set(Some(data)); }); share::scope(|subscribe| { Self::register_listener(&listener, subscribe)?; Self::read_accelerometer()?; while data_cell.get().is_none() { S::yield_wait(); } match data_cell.get() { None => Err(ErrorCode::Fail), Some(data) => Ok(data), } }) } /// Initiate a synchronous magnetometer measurement. /// Returns Ok(data) if the operation was successful /// Returns Err(ErrorCode) if the operation was unsuccessful pub fn read_magnetometer_sync() -> Result { let data_cell: Cell> = Cell::new(None); let listener = NineDofListener(|data| { data_cell.set(Some(data)); }); share::scope(|subscribe| { Self::register_listener(&listener, subscribe)?; Self::read_magnetometer()?; while data_cell.get().is_none() { S::yield_wait(); } match data_cell.get() { None => Err(ErrorCode::Fail), Some(data) => Ok(data), } }) } /// Initiate a synchronous gyroscope measurement. /// Returns Ok(data) as NineDofData if the operation was successful /// Returns Err(ErrorCode) if the operation was unsuccessful pub fn read_gyroscope_sync() -> Result { let data_cell: Cell> = Cell::new(None); let listener = NineDofListener(|data| { data_cell.set(Some(data)); }); share::scope(|subscribe| { Self::register_listener(&listener, subscribe)?; Self::read_gyro()?; while data_cell.get().is_none() { S::yield_wait(); } match data_cell.get() { None => Err(ErrorCode::Fail), Some(data) => Ok(data), } }) } /// Calculate the magnitude of the accelerometer reading /// Returns value of magnitude if the operation was successful /// Returns 0.0 if the operation was unsuccessful pub fn read_accelerometer_mag() -> f64 { let data = Self::read_accelerometer_sync(); match data { Ok(data) => { let x = data.x as f64; let y = data.y as f64; let z = data.z as f64; libm::sqrt(x * x + y * y + z * z) } Err(_) => 0.0, } } } pub struct NineDofListener(pub F); impl Upcall> for NineDofListener { fn upcall(&self, arg0: u32, arg1: u32, arg2: u32) { (self.0)(NineDofData { x: arg0 as i32, y: arg1 as i32, z: arg2 as i32, }) } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60004; // Command IDs const EXISTS: u32 = 0; const READ_ACCELEROMETER: u32 = 1; const READ_MAGNETOMETER: u32 = 100; const READ_GYRO: u32 = 200; ================================================ FILE: apis/sensors/ninedof/src/tests.rs ================================================ use core::cell::Cell; use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; use crate::NineDofData; type NineDof = super::NineDof; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(NineDof::exists(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); assert_eq!(NineDof::exists(), Ok(())); } #[test] fn driver_busy() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); assert_eq!(NineDof::read_accelerometer(), Ok(())); assert!(driver.is_busy()); assert_eq!(NineDof::read_accelerometer(), Err(ErrorCode::Busy)); assert_eq!(NineDof::read_accelerometer_sync(), Err(ErrorCode::Busy)); } #[test] fn read_accelerometer() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); let acceleration_listener: Cell> = Cell::new(None); let acceleration_listener = crate::NineDofListener(|data| { acceleration_listener.set(Some(data)); }); share::scope(|subscribe| { assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!( NineDof::register_listener(&acceleration_listener, subscribe), Ok(()) ); assert_eq!(NineDof::read_accelerometer(), Ok(())); driver.set_value(fake::NineDofData { x: 1, y: 2, z: 3 }); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }); } #[test] fn read_magnetometer() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); let magnetometer_listener: Cell> = Cell::new(None); let magnetometer_listener = crate::NineDofListener(|data| { magnetometer_listener.set(Some(data)); }); share::scope(|subscribe| { assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!( NineDof::register_listener(&magnetometer_listener, subscribe), Ok(()) ); assert_eq!(NineDof::read_accelerometer(), Ok(())); driver.set_value(fake::NineDofData { x: 1, y: 2, z: 3 }); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }); } #[test] fn read_gyro() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); let gyro_listener: Cell> = Cell::new(None); let gyro_listener = crate::NineDofListener(|data| { gyro_listener.set(Some(data)); }); share::scope(|subscribe| { assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!( NineDof::register_listener(&gyro_listener, subscribe), Ok(()) ); assert_eq!(NineDof::read_accelerometer(), Ok(())); driver.set_value(fake::NineDofData { x: 1, y: 2, z: 3 }); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }); } #[test] fn register_unregister_listener() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); let acceleration_listener: Cell> = Cell::new(None); let acceleration_listener = crate::NineDofListener(|data| { acceleration_listener.set(Some(data)); }); let magnetometer_listener: Cell> = Cell::new(None); let magnetometer_listener = crate::NineDofListener(|data| { magnetometer_listener.set(Some(data)); }); let gyro_listener: Cell> = Cell::new(None); let gyro_listener = crate::NineDofListener(|data| { gyro_listener.set(Some(data)); }); share::scope(|subscribe| { assert_eq!(NineDof::read_accelerometer(), Ok(())); driver.set_value(fake::NineDofData { x: 1, y: 2, z: 3 }); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!( NineDof::register_listener(&acceleration_listener, subscribe), Ok(()) ); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(NineDof::read_gyro(), Ok(())); driver.set_value(fake::NineDofData { x: 4, y: 5, z: 6 }); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!( NineDof::register_listener(&gyro_listener, subscribe), Ok(()) ); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(NineDof::read_magnetometer(), Ok(())); driver.set_value(fake::NineDofData { x: 7, y: 8, z: 9 }); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!( NineDof::register_listener(&magnetometer_listener, subscribe), Ok(()) ); }) } #[test] fn read_accelerometer_sync() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); driver.set_value_sync(fake::NineDofData { x: 1, y: 2, z: 3 }); let data = NineDof::read_accelerometer_sync(); assert_eq!(data, Ok(NineDofData { x: 1, y: 2, z: 3 })); } #[test] fn read_magnetometer_sync() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); driver.set_value_sync(fake::NineDofData { x: 1, y: 2, z: 3 }); let data = NineDof::read_magnetometer_sync(); assert_eq!(data, Ok(NineDofData { x: 1, y: 2, z: 3 })); } #[test] fn read_gyro_sync() { let kernel = fake::Kernel::new(); let driver = fake::NineDof::new(); kernel.add_driver(&driver); driver.set_value_sync(fake::NineDofData { x: 1, y: 2, z: 3 }); let value = NineDof::read_gyroscope_sync(); assert_eq!(value, Ok(NineDofData { x: 1, y: 2, z: 3 })); } ================================================ FILE: apis/sensors/proximity/Cargo.toml ================================================ [package] name = "libtock_proximity" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock proximity driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/sensors/proximity/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::{share, DefaultConfig, ErrorCode, Subscribe, Syscalls}; pub struct Proximity(S); impl Proximity { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working. pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } /// Register an events listener pub fn register_listener<'share>( listener: &'share Cell>, subscribe: share::Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Initiate a proximity measurement pub fn read() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ, 0, 0).to_result() } /// Initiate a synchronous proximity measurement. /// Returns Ok(proximity_value) if the operation was successful /// proximity_value is in [0, 255] range, /// where '255' indicates the closest measurable distance and '0' that no object is detected pub fn read_sync() -> Result { let listener: Cell> = Cell::new(None); share::scope(|subscribe| { if let Ok(()) = Self::register_listener(&listener, subscribe) { if let Ok(()) = Self::read() { S::yield_wait(); } } }); match listener.get() { None => Err(ErrorCode::Busy), Some(proximity) => Ok(proximity.0 as u8), } } /// Initiate an on_interrupt proximity measurement /// The sensor reads values continuously and executes the callback only if /// proximity_value < lower or proximity_value > upper /// lower - lower interrupt threshold for sensor --> range is [0,255] /// upper - upper interrupt threshold for sensor --> range is [0,255] /// lower <= upper pub fn read_on_interrupt(lower: u8, upper: u8) -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_ON_INT, lower as u32, upper as u32).to_result() } /// Initiate a synchronous on_interrupt proximity measurement. /// Returns Ok(proximity_value) if the operation was successful /// proximity_value is in [0, 255] range, /// where '255' indicates the closest measurable distance and '0' that no object is detected /// Returns only when proximity_value < lower or proximity_value > upper /// lower - lower interrupt threshold for sensor --> range is [0,255] /// upper - upper interrupt threshold for sensor --> range is [0,255] /// lower <= upper pub fn wait_for_value_between(lower: u8, upper: u8) -> Result { if lower > upper { return Err(ErrorCode::Invalid); } let listener: Cell> = Cell::new(None); share::scope(|subscribe| { if let Ok(()) = Self::register_listener(&listener, subscribe) { if let Ok(()) = Self::read_on_interrupt(lower, upper) { while listener.get().is_none() { S::yield_wait(); } } } }); match listener.get() { None => Err(ErrorCode::Busy), Some(proximity) => Ok(proximity.0 as u8), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60005; // Command IDs const EXISTS: u32 = 0; const READ: u32 = 1; const READ_ON_INT: u32 = 2; ================================================ FILE: apis/sensors/proximity/src/tests.rs ================================================ use core::cell::Cell; use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; type Proximity = super::Proximity; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(Proximity::exists(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::Proximity::new(); kernel.add_driver(&driver); assert_eq!(Proximity::exists(), Ok(())); } #[test] fn busy_driver() { let kernel = fake::Kernel::new(); let driver = fake::Proximity::new(); kernel.add_driver(&driver); assert_eq!(Proximity::read(), Ok(())); assert_eq!(Proximity::read(), Err(ErrorCode::Busy)); assert_eq!(Proximity::read_on_interrupt(0, 0), Err(ErrorCode::Busy)); driver.set_value(100); assert_eq!(Proximity::read_on_interrupt(0, 0), Ok(())); assert_eq!(Proximity::read(), Err(ErrorCode::Busy)); } #[test] fn async_readings() { let kernel = fake::Kernel::new(); let driver = fake::Proximity::new(); kernel.add_driver(&driver); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!(Proximity::read(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(Proximity::register_listener(&listener, subscribe), Ok(())); assert_eq!(Proximity::read(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); assert_eq!(Proximity::read_on_interrupt(100, 200), Ok(())); driver.set_value(150); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); driver.set_value(99); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((99,))); }) } #[test] fn sync_readings() { let kernel = fake::Kernel::new(); let driver = fake::Proximity::new(); kernel.add_driver(&driver); driver.set_value_sync(100); assert_eq!(Proximity::read_sync(), Ok(100)); driver.set_value_sync(250); assert_eq!(Proximity::wait_for_value_between(100, 200), Ok(250)); } #[test] fn bad_arguments() { assert_eq!( Proximity::wait_for_value_between(200, 100), Err(ErrorCode::Invalid) ); } ================================================ FILE: apis/sensors/sound_pressure/Cargo.toml ================================================ [package] name = "libtock_sound_pressure" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2018" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock sound pressure driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/sensors/sound_pressure/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::{ share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; pub struct SoundPressure(S); impl SoundPressure { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working. pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } /// Initiate a pressure measurement. /// This function is used both for synchronous and asynchronous readings pub fn read() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_PRESSURE, 0, 0).to_result() } /// Register an events listener pub fn register_listener<'share, F: Fn(u32)>( listener: &'share SoundPressureListener, subscribe: share::Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Enable sound pressure measurement pub fn enable() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, 2, 0, 0).to_result() } /// Disable sound pressure measurement pub fn disable() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, 3, 0, 0).to_result() } /// Initiate a synchronous pressure measurement. /// Returns Ok(pressure_value) if the operation was successful /// pressure_value is between 0 and 255 pub fn read_sync() -> Result { let pressure_cell: Cell> = Cell::new(None); let listener = SoundPressureListener(|pressure_val| { pressure_cell.set(Some(pressure_val)); }); share::scope(|subscribe| { Self::register_listener(&listener, subscribe)?; Self::read()?; while pressure_cell.get().is_none() { S::yield_wait(); } match pressure_cell.get() { None => Err(ErrorCode::Fail), Some(pressure_val) => { if !(0..=256).contains(&pressure_val) { Err(ErrorCode::Invalid) } else { Ok(pressure_val as u8) } } } }) } } pub struct SoundPressureListener(pub F); impl Upcall> for SoundPressureListener { fn upcall(&self, pressure_val: u32, _arg1: u32, _arg2: u32) { (self.0)(pressure_val); } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60006; // Command IDs const EXISTS: u32 = 0; const READ_PRESSURE: u32 = 1; ================================================ FILE: apis/sensors/sound_pressure/src/tests.rs ================================================ use core::cell::Cell; use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; type SoundPressure = super::SoundPressure; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(SoundPressure::exists(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::SoundPressure::new(); kernel.add_driver(&driver); assert_eq!(SoundPressure::exists(), Ok(())); } #[test] fn driver_busy() { let kernel = fake::Kernel::new(); let driver = fake::SoundPressure::new(); kernel.add_driver(&driver); assert_eq!(SoundPressure::read(), Ok(())); assert!(driver.is_busy()); assert_eq!(SoundPressure::read(), Err(ErrorCode::Busy)); assert_eq!(SoundPressure::read_sync(), Err(ErrorCode::Busy)); } #[test] fn read_pressure() { let kernel = fake::Kernel::new(); let driver = fake::SoundPressure::new(); kernel.add_driver(&driver); let pressure_cell: Cell> = Cell::new(None); let listener = crate::SoundPressureListener(|pressure_val| { pressure_cell.set(Some(pressure_val)); }); share::scope(|subscribe| { assert_eq!(SoundPressure::read(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!( SoundPressure::register_listener(&listener, subscribe), Ok(()) ); assert_eq!(SoundPressure::read(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(pressure_cell.get(), Some(100)); SoundPressure::unregister_listener(); assert_eq!(SoundPressure::read(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); } #[test] fn read_pressure_sync() { let kernel = fake::Kernel::new(); let driver = fake::SoundPressure::new(); kernel.add_driver(&driver); driver.set_value_sync(100); assert_eq!(SoundPressure::read_sync(), Ok(100)); } ================================================ FILE: apis/sensors/temperature/Cargo.toml ================================================ [package] name = "libtock_temperature" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock temperature driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/sensors/temperature/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform::{ share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, }; pub struct Temperature(S); impl Temperature { /// Returns Ok() if the driver was present.This does not necessarily mean /// that the driver is working. pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } /// Initiate a temperature measurement. /// /// This function is used both for synchronous and asynchronous readings pub fn read_temperature() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_TEMP, 0, 0).to_result() } /// Register an events listener pub fn register_listener<'share, F: Fn(i32)>( listener: &'share TemperatureListener, subscribe: share::Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } /// Unregister the events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } /// Initiate a synchronous temperature measurement. /// Returns Ok(temperature_value) if the operation was successful /// temperature_value is returned in hundreds of centigrades pub fn read_temperature_sync() -> Result { let temperature_cell: Cell> = Cell::new(None); let listener = TemperatureListener(|temp_val| { temperature_cell.set(Some(temp_val)); }); share::scope(|subscribe| { if let Ok(()) = Self::register_listener(&listener, subscribe) { if let Ok(()) = Self::read_temperature() { while temperature_cell.get().is_none() { S::yield_wait(); } } } }); match temperature_cell.get() { None => Err(ErrorCode::Busy), Some(temp_val) => Ok(temp_val), } } } pub struct TemperatureListener(pub F); impl Upcall> for TemperatureListener { fn upcall(&self, temp_val: u32, _arg1: u32, _arg2: u32) { self.0(temp_val as i32) } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60000; // Command IDs const EXISTS: u32 = 0; const READ_TEMP: u32 = 1; ================================================ FILE: apis/sensors/temperature/src/tests.rs ================================================ use core::cell::Cell; use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; type Temperature = super::Temperature; #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert_eq!(Temperature::exists(), Err(ErrorCode::NoDevice)); } #[test] fn exists() { let kernel = fake::Kernel::new(); let driver = fake::Temperature::new(); kernel.add_driver(&driver); assert_eq!(Temperature::exists(), Ok(())); } #[test] fn read_temperature() { let kernel = fake::Kernel::new(); let driver = fake::Temperature::new(); kernel.add_driver(&driver); assert_eq!(Temperature::read_temperature(), Ok(())); assert!(driver.is_busy()); assert_eq!(Temperature::read_temperature(), Err(ErrorCode::Busy)); assert_eq!(Temperature::read_temperature_sync(), Err(ErrorCode::Busy)); } #[test] fn register_unregister_listener() { let kernel = fake::Kernel::new(); let driver = fake::Temperature::new(); kernel.add_driver(&driver); let temperature_cell: Cell> = Cell::new(None); let listener = crate::TemperatureListener(|temp_val| { temperature_cell.set(Some(temp_val)); }); share::scope(|subscribe| { assert_eq!(Temperature::read_temperature(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(Temperature::register_listener(&listener, subscribe), Ok(())); assert_eq!(Temperature::read_temperature(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(temperature_cell.get(), Some(100)); Temperature::unregister_listener(); assert_eq!(Temperature::read_temperature(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); } #[test] fn read_temperature_sync() { let kernel = fake::Kernel::new(); let driver = fake::Temperature::new(); kernel.add_driver(&driver); driver.set_value_sync(1000); assert_eq!(Temperature::read_temperature_sync(), Ok(1000)); } #[test] fn negative_value() { let kernel = fake::Kernel::new(); let driver = fake::Temperature::new(); kernel.add_driver(&driver); driver.set_value_sync(-1000); assert_eq!(Temperature::read_temperature_sync(), Ok(-1000)); } ================================================ FILE: apis/storage/key_value/Cargo.toml ================================================ [package] name = "libtock_key_value" version = "0.1.0" authors = [ "Tock Project Developers ", ] license = "MIT/Apache-2.0" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" description = "libtock key-value driver" [dependencies] libtock_platform = { path = "../../../platform" } [dev-dependencies] libtock_unittest = { path = "../../../unittest" } ================================================ FILE: apis/storage/key_value/src/lib.rs ================================================ #![no_std] use core::cell::Cell; use libtock_platform as platform; use libtock_platform::allow_ro::AllowRo; use libtock_platform::allow_rw::AllowRw; use libtock_platform::share; use libtock_platform::subscribe::Subscribe; use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; /// The key-value driver. /// /// It provides access to a key-value store. pub struct KeyValue(S, C); impl KeyValue { /// Run a check against the key-value capsule to ensure it is present. #[inline(always)] pub fn exists() -> bool { S::command(DRIVER_NUM, command::DRIVER_CHECK, 0, 0).is_success() } /// Get a key-value object from the `key`. pub fn get(key: &[u8], value: &mut [u8]) -> Result { let called: Cell>> = Cell::new(None); share::scope::< ( AllowRo<_, DRIVER_NUM, { allow_ro::KEY }>, AllowRw<_, DRIVER_NUM, { allow_rw::VALUE_READ }>, Subscribe<_, DRIVER_NUM, { subscribe::CALLBACK }>, ), _, _, >(|handle| { let (allow_key, allow_value, subscribe) = handle.split(); S::allow_ro::(allow_key, key)?; S::allow_rw::(allow_value, value)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::CALLBACK }>(subscribe, &called)?; S::command(DRIVER_NUM, command::GET, 0, 0).to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some(ret) = called.get() { return ret.map(|(arg0,)| arg0); } } }) } /// Set a key-value object for the `key`. fn insert(command_num: u32, key: &[u8], value: &[u8]) -> Result<(), ErrorCode> { let called: Cell>> = Cell::new(None); share::scope::< ( AllowRo<_, DRIVER_NUM, { allow_ro::KEY }>, AllowRo<_, DRIVER_NUM, { allow_ro::VALUE_WRITE }>, Subscribe<_, DRIVER_NUM, { subscribe::CALLBACK }>, ), _, _, >(|handle| { let (allow_key, allow_value, subscribe) = handle.split(); S::allow_ro::(allow_key, key)?; S::allow_ro::(allow_value, value)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::CALLBACK }>(subscribe, &called)?; S::command(DRIVER_NUM, command_num, 0, 0).to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some(ret) = called.get() { return ret; } } }) } /// Set a key-value object for the `key`. pub fn set(key: &[u8], value: &[u8]) -> Result<(), ErrorCode> { Self::insert(command::SET, key, value) } /// Set a key-value object for the `key`. pub fn add(key: &[u8], value: &[u8]) -> Result<(), ErrorCode> { Self::insert(command::ADD, key, value) } /// Set a key-value object for the `key`. pub fn update(key: &[u8], value: &[u8]) -> Result<(), ErrorCode> { Self::insert(command::UPDATE, key, value) } /// Delete a key-value object by `key`. pub fn delete(key: &[u8]) -> Result<(), ErrorCode> { let called: Cell>> = Cell::new(None); share::scope::< ( AllowRo<_, DRIVER_NUM, { allow_ro::KEY }>, Subscribe<_, DRIVER_NUM, { subscribe::CALLBACK }>, ), _, _, >(|handle| { let (allow_key, subscribe) = handle.split(); S::allow_ro::(allow_key, key)?; S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::CALLBACK }>(subscribe, &called)?; S::command(DRIVER_NUM, command::DELETE, 0, 0).to_result::<(), ErrorCode>()?; loop { S::yield_wait(); if let Some(ret) = called.get() { return ret; } } }) } } /// System call configuration trait for `KeyValue`. pub trait Config: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config { } impl Config for T { } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x50003; // Command IDs #[allow(unused)] mod command { pub const DRIVER_CHECK: u32 = 0; pub const GET: u32 = 1; pub const SET: u32 = 2; pub const DELETE: u32 = 3; pub const ADD: u32 = 4; pub const UPDATE: u32 = 5; } #[allow(unused)] mod subscribe { pub const CALLBACK: u32 = 0; } mod allow_ro { pub const KEY: u32 = 0; pub const VALUE_WRITE: u32 = 1; } mod allow_rw { pub const VALUE_READ: u32 = 0; } ================================================ FILE: apis/storage/key_value/src/tests.rs ================================================ use super::*; use libtock_platform::ErrorCode; use libtock_unittest::{command_return, fake, ExpectedSyscall}; type Kv = super::KeyValue; fn _get(kernel: &fake::Kernel) -> Result { kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::KEY, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::AllowRw { driver_num: DRIVER_NUM, buffer_num: allow_rw::VALUE_READ, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: DRIVER_NUM, subscribe_num: subscribe::CALLBACK, skip_with_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: DRIVER_NUM, command_id: command::GET, argument0: 0, argument1: 0, override_return: Some(command_return::success()), }); let mut buf = [0; 3]; Kv::get("mykey".as_bytes(), &mut buf) } fn _set(kernel: &fake::Kernel) -> Result<(), ErrorCode> { kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::KEY, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::VALUE_WRITE, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: DRIVER_NUM, subscribe_num: subscribe::CALLBACK, skip_with_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: DRIVER_NUM, command_id: command::SET, argument0: 0, argument1: 0, override_return: Some(command_return::success()), }); Kv::set("mykey".as_bytes(), b"hooray") } fn _add(kernel: &fake::Kernel) -> Result<(), ErrorCode> { kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::KEY, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::VALUE_WRITE, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: DRIVER_NUM, subscribe_num: subscribe::CALLBACK, skip_with_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: DRIVER_NUM, command_id: command::ADD, argument0: 0, argument1: 0, override_return: Some(command_return::success()), }); Kv::add("mykey".as_bytes(), b"hooray2") } fn _update(kernel: &fake::Kernel) -> Result<(), ErrorCode> { kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::KEY, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::VALUE_WRITE, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: DRIVER_NUM, subscribe_num: subscribe::CALLBACK, skip_with_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: DRIVER_NUM, command_id: command::UPDATE, argument0: 0, argument1: 0, override_return: Some(command_return::success()), }); Kv::update("mykey".as_bytes(), b"hooray3") } fn _delete(kernel: &fake::Kernel) -> Result<(), ErrorCode> { kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: DRIVER_NUM, buffer_num: allow_ro::KEY, return_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: DRIVER_NUM, subscribe_num: subscribe::CALLBACK, skip_with_error: None, }); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: DRIVER_NUM, command_id: command::DELETE, argument0: 0, argument1: 0, override_return: Some(command_return::success()), }); Kv::delete("mykey".as_bytes()) } #[test] fn no_driver() { let _kernel = fake::Kernel::new(); assert!(!Kv::exists()); } #[test] fn driver_check() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert!(Kv::exists()); } #[test] fn get_fail() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert_eq!(_get(&kernel), Err(ErrorCode::NoSupport)); } #[test] fn set_get() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert_eq!(_set(&kernel), Ok(())); assert_eq!(_get(&kernel), Ok(6)); } #[test] fn add() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert_eq!(_add(&kernel), Ok(())); } #[test] fn add_fail() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert_eq!(_set(&kernel), Ok(())); assert_eq!(_add(&kernel), Err(ErrorCode::NoSupport)); } #[test] fn update() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert_eq!(_set(&kernel), Ok(())); assert_eq!(_update(&kernel), Ok(())); } #[test] fn update_fail() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert_eq!(_update(&kernel), Err(ErrorCode::NoSupport)); } #[test] fn delete() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert_eq!(_set(&kernel), Ok(())); assert_eq!(_delete(&kernel), Ok(())); } #[test] fn delete_fail() { let kernel = fake::Kernel::new(); let driver = fake::KeyValue::new(); kernel.add_driver(&driver); assert_eq!(_delete(&kernel), Err(ErrorCode::NoSupport)); } ================================================ FILE: build.rs ================================================ fn main() { libtock_build_scripts::auto_layout(); } ================================================ FILE: build_scripts/Cargo.toml ================================================ [package] authors = ["Tock Project Developers "] categories = ["embedded", "no-std", "os"] description = """Helper functions for compiling libtock-rs apps.""" edition = "2021" license = "Apache-2.0 OR MIT" name = "libtock_build_scripts" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true version = "0.1.0" ================================================ FILE: build_scripts/README.md ================================================ Libtock Build Scripts Support Crate =================================== This crate provides helpers for building libtock-rs apps. Usage ----- There are three general steps to use this crate. 1. This crate should be included as a build dependency in the app's Cargo.toml file: ```toml # Cargo.toml ... [build-dependencies] libtock_build_scripts = { git = "https://github.com/tock/libtock-rs"} ``` This will ensure the crate and the contained linker scripts are available for the app build. 2. This crate provides a helper function which can used from the libtock-rs app's build.rs file. In the common case, you just call the provided function from the build.rs file in your crate's root: ```rs // build.rs fn main() { libtock_build_scripts::auto_layout(); } ``` This will allow cargo to setup linker scripts and paths for the linker when your app is built. 3. When calling `cargo build` you need to instruct the build.rs on where in memory to compile your app for. This crate supports two mechanisms to do this. You can only use one. 1. Set the `LIBTOCK_PLATFORM` environment variable which specifies the name of the linker script in `/layouts` to be used. So for example, if you are using the microbit_v2 you might run: ```bash $ LIBTOCK_PLATFORM=microbit_v2 cargo build --target thumbv7em-none-eabi --release ``` 2. Set the `LIBTOCK_LINKER_FLASH` and `LIBTOCK_LINKER_RAM` environment variables which specify the starting addresses of flash and RAM memory, respectively. This allows you to customize where exactly the compiled app must be placed in flash and RAM. For example, to build for common Cortex-M4 platforms you might run: ```bash $ LIBTOCK_LINKER_FLASH=0x00040000 LIBTOCK_LINKER_RAM=0x20008000 cargo build --target thumbv7em-none-eabi --release ``` ================================================ FILE: build_scripts/libtock_layout.ld ================================================ /* Layout file for Tock process binaries that use libtock-rs. This currently * implements static linking, because we do not have a working * position-independent relocation solution. This layout works for all * platforms libtock-rs supports (ARM and RISC-V). * * This layout should be included by a script that defines the FLASH and RAM * regions for the board as well as TBF_HEADER_SIZE. Here is a an example * process binary linker script to get started: * * TBF_HEADER_SIZE = 0x60; * * FLASH_START = 0x10000; * FLASH_LENGTH = 0x10000; * * RAM_START = 0x20000; * RAM_LENGTH = 0x10000; * * INCLUDE ../libtock-rs/layout.ld * * FLASH refers to the area the process binary occupies in flash (including the * TBF headers, so FLASH_START refers to the TBF load address). RAM refers to * the area the process will have access to in memory. STACK_SIZE is the size of * the process' stack (this layout file may round the stack size up for * alignment purposes). * * This places the flash sections in the following order: * 1. .rt_header -- Constants used by runtime initialization. * 2. .text -- Executable code. * 3. .rodata -- Read-only global data (e.g. most string constants). * 4. .data -- Read-write data, copied to RAM at runtime. * * This places the RAM sections in the following order: * 1. .stack -- The stack grows downward. Putting it first gives us * MPU-based overflow detection. * 2. .data -- Read-write data, initialized by copying from flash. * 3. .bss -- Zero-initialized read-write global data. * 4. Heap -- The heap (optional) comes after .bss and grows upwards to * the process break. * * TBF_HEADER_SIZE is further used internally in the included `layout.ld` file * to set the `tbf_protected_region_size` symbol. elf2tab will thus prepend TBF * headers and an optional padding such that the final binary matches the * intended app load address (0x10000 in the above example). */ /* TODO: Should TBF_HEADER_SIZE be configured via a similar mechanism to the * stack size? We should see if that is possible. */ /* Rust's default linker (llvm-ld) as used with the Rust toolchain versions of * at least 2022-06-10, 2023-01-26, and 2023-06-27 can produce broken ELF * binaries when the RAM region's start address is not well-aligned to a 4kB * boundary. Unfortunately, this behavior is rather tricky to debug: instead of * refusing to produce a binary or producing a corrupt output, it generates an * ELF file which includes a segment that points to the ELF file's header * itself. elf2tab will include this segment in the final binary (as it is set * to be loaded), producing broken TBFs. This (overrideable) check is designed * to warn users that the linker may be misbehaved under these conditions. */ PROVIDE(LIBTOCKRS_OVERRIDE_RAM_ORIGIN_CHECK = 0); ASSERT(LIBTOCKRS_OVERRIDE_RAM_ORIGIN_CHECK == 1 || RAM_START % 0x1000 == 0, " Start of RAM region must be well-aligned to a 4kB boundary for LLVM's lld to work. Refer to https://github.com/tock/libtock-rs/pull/477 for more information. Set LIBTOCKRS_OVERRIDE_RAM_ORIGIN_CHECK = 1 to override this check (e.g., when using a different linker)."); MEMORY { FLASH (X) : ORIGIN = FLASH_START + TBF_HEADER_SIZE, LENGTH = FLASH_LENGTH - TBF_HEADER_SIZE RAM (W) : ORIGIN = RAM_START , LENGTH = RAM_LENGTH } /* GNU LD looks for `start` as an entry point by default, while LLVM's LLD looks * for `_start`. To be compatible with both, we manually specify an entry point. */ ENTRY(start) SECTIONS { /* The FLASH memory section is placed at a TBF_HEADER_SIZE offset, to give * elf2tab room to prepend the TBF headers. Communicate this reservation to * elf2tab, such that it fills up the space after the TBF headers (if any) * as part of the protected region trailer: */ tbf_protected_region_size = TBF_HEADER_SIZE; /* Sections located in FLASH at runtime. */ /* Runtime header. Contains values the linker knows that the runtime needs * to look up. */ . = ALIGN(4); .start : { /* We combine rt_header and _start into a single section. If we don't, * elf2tab does not parse the ELF file correctly for unknown reasons. */ rt_header = .; LONG(start & 0xFFFFFFFE); /* .start w/ Thumb bit unset */ LONG(ADDR(.bss) + SIZEOF(.bss)); /* Initial process break */ LONG(_stack_top); LONG(SIZEOF(.data)); LONG(LOADADDR(.data)); LONG(ADDR(.data)); LONG(SIZEOF(.bss)); LONG(ADDR(.bss)); *(.start) } > FLASH /* Text section -- the application's code. */ .text ALIGN(4) : { *(.text.*) } > FLASH /* Read-only data section. Contains strings and other global constants. */ .rodata ALIGN(4) : { *(.rodata.*) /* Generated by GCC in libraries for variables that still have relocations but are constant * at runtime. See https://www.airs.com/blog/archives/189 for background. */ *(.data.rel.ro.*) } > FLASH /* Sections located in RAM at runtime. */ /* Reserve space for the stack. Aligned to a multiple of 16 bytes for the * RISC-V calling convention: * https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf */ .stack (NOLOAD) : { /* _sram_origin is used by elf2tab: * https://github.com/tock/elf2tab/blob/master/src/main.rs#L301 */ _sram_origin = .; KEEP(*(.stack_buffer)) . = ALIGN(16); _stack_top = .; /* Used in rt_header */ } > RAM AT > FLASH /* Read-write data section. This is deployed as part of FLASH but is copied * into RAM at runtime. */ .data ALIGN(4) : { data_ram_start = .; /* .sdata is the RISC-V small data section */ *(.sdata .data*) /* Pad to word alignment so the relocation loop can use word-sized * copies. */ . = ALIGN(4); } > RAM AT > FLASH /* BSS section. These are zero-initialized static variables. This section is * not copied from FLASH into RAM but rather directly initialized, and is * mainly put in this linker script so that we get an error if it overflows * the RAM region. */ .bss ALIGN(4) (NOLOAD) : { /* .sbss is the RISC-V small data section */ *(.sbss .bss.*) } > RAM AT > FLASH _heap_start = ADDR(.bss) + SIZEOF(.bss); /* Used by rt_header */ /* Sections we do not need. */ /DISCARD/ : { *(.ARM.exidx .eh_frame) } } /* Check that the linker thinks our start of flash is page aligned. Now, the * linker doesn't actually know the size of pages, and for our purposes we don't * necessarily care that these are aligned, but the linker will generate * segments which are aligned to what it thinks the page size is. This will * cause the linker to insert segments _before_ `FLASH_START`, which is not what * we intend. To ensure more valid-looking .elf files, we check that * `FLASH_START` is aligned to what the linker thinks is the page size. * * If this check fails, it is likely the linker is using a page size of 0x10000 * (based on observations in Aug 2023). The linker argument `-z * max-page-size=4096` changes this setting. We recommend adding * `println!("cargo:rustc-link-arg=-zmax-page-size=4096");` to your build.rs * file to fix this issue. */ ASSERT(FLASH_START % CONSTANT(MAXPAGESIZE)==0, " FLASH_START not page aligned according to the linker. This will cause issues with generated .elf segments. The linker is probably assuming too large of a page size. Add `println!('cargo:rustc-link-arg=-zmax-page-size=4096');` to build.rs to fix.") ================================================ FILE: build_scripts/src/lib.rs ================================================ //! Utility functions for implementing build.rs files for libtock-rs apps. /// List of known `LIBTOCK_PLATFORM` values. #[rustfmt::skip] const PLATFORMS: &[(&str, &str, &str, &str, &str)] = &[ // Name | Flash start | Flash len | RAM start | RAM length ("apollo3" , "0x00040000", "0x00BE000", "0x10004000", "0x03000"), ("clue_nrf52840" , "0x00080000", "512K" , "0x20006000", "216K" ), ("esp32_c3_devkitm_1" , "0x403B0000", "0x0030000", "0x3FCA2000", "0x2E000"), ("hail" , "0x00030000", "0x0040000", "0x20008000", "62K" ), ("hifive1" , "0x20040000", "32M" , "0x80003000", "0x01000"), ("imix" , "0x00040000", "0x0040000", "0x20008000", "62K" ), ("imxrt1050" , "0x63002000", "0x1000000", "0x20004000", "112K" ), ("microbit_v2" , "0x00040000", "256K" , "0x20004000", "112K" ), ("msp432" , "0x00020000", "0x0020000", "0x20004000", "0x02000"), ("nano_rp2040_connect", "0x10020000", "256K" , "0x20004000", "248K" ), ("nrf52" , "0x00030000", "0x0060000", "0x20004000", "62K" ), ("nrf52840" , "0x00040000", "768K" , "0x20010000", "128k" ), ("nucleo_f429zi" , "0x08040000", "255K" , "0x20004000", "112K" ), ("nucleo_f446re" , "0x08040000", "255K" , "0x20004000", "176K" ), ("opentitan" , "0x20030000", "32M" , "0x10006000", "126K" ), ("pico_explorer_base" , "0x10040000", "256K" , "0x20012000", "192K" ), ("qemu_rv32_virt" , "0x80100000", "0x0100000", "0x80300000", "1024K" ), ("raspberry_pi_pico" , "0x10040000", "256K" , "0x20012000", "192K" ), ("stm32f3discovery" , "0x08020000", "0x0020000", "0x20004000", "48K" ), ("stm32f412gdiscovery", "0x08030000", "256K" , "0x20004000", "112K" ), ("nano33ble" , "0x00050000", "704K" , "0x20005000", "240K" ), ]; /// Helper function to configure cargo to use suitable linker scripts for /// linking libtock-rs apps. /// /// `auto_layout` function does a few things: /// /// 1. Copies `libtock_layout.ld` into the linker's search path. /// 2. Generates a linker script that specifies which flash and RAM addresses /// the app should be compiled for. This linker script depends on the /// previously-mentioned `libtock_layout.ld`. /// 3. Passes the `-T` argument to the linker to make it use /// the generated linker script. /// /// `auto_layout` supports two mechanisms for specifying the flash and RAM /// address ranges: /// /// 1. Passing the `LIBTOCK_PLATFORM` environment variable, specifying one of a /// hardcoded list of known platforms. See the `PLATFORMS` variable above for /// the list of supported platforms. /// 2. Passing the `LIBTOCK_LINKER_FLASH` and `LIBTOCK_LINKER_RAM` environment /// variables which specify the starting addresses of flash and RAM memory, /// respectively. /// /// Programs passing `LIBTOCK_LINKER_FLASH` and `LIBTOCK_LINKER_RAM` may /// additionally pass `LIBTOCK_TBF_HEADER_SIZE`, `LIBTOCK_LINKER_FLASH_LENGTH`, /// and/or `LIBTOCK_LINKER_RAM_LENGTH`. If not specified, this function will /// assume some default values for those variables. pub fn auto_layout() { use std::env::var; use std::fs::File; use std::io::Write; use std::ops::Deref; use std::path::PathBuf; const LIBTOCK_LAYOUT_NAME: &str = "libtock_layout.ld"; const LINKER_FLASH_VAR: &str = "LIBTOCK_LINKER_FLASH"; const LINKER_FLASH_LEN_VAR: &str = "LIBTOCK_LINKER_FLASH_LENGTH"; const LINKER_RAM_VAR: &str = "LIBTOCK_LINKER_RAM"; const LINKER_RAM_LEN_VAR: &str = "LIBTOCK_LINKER_RAM_LENGTH"; const PLATFORM_VAR: &str = "LIBTOCK_PLATFORM"; const TBF_HEADER_SIZE_VAR: &str = "LIBTOCK_TBF_HEADER_SIZE"; // Note: we need to print these rerun-if commands before using the variable // or file, so that if the build script fails cargo knows when to re-run it. println!("cargo:rerun-if-env-changed={LINKER_FLASH_VAR}"); println!("cargo:rerun-if-env-changed={LINKER_FLASH_LEN_VAR}"); println!("cargo:rerun-if-env-changed={LINKER_RAM_VAR}"); println!("cargo:rerun-if-env-changed={LINKER_RAM_LEN_VAR}"); println!("cargo:rerun-if-env-changed={PLATFORM_VAR}"); println!("cargo:rerun-if-env-changed={TBF_HEADER_SIZE_VAR}"); let platform = get_env_var(PLATFORM_VAR); let flash_start = get_env_var(LINKER_FLASH_VAR); let ram_start = get_env_var(LINKER_RAM_VAR); let flash_len; let ram_len; // Determine the flash and RAM address ranges. This detects whether // LIBTOCK_PLATFORM was specified or whether the flash and RAM ranges were // specified directly. let (flash_start, flash_len, ram_start, ram_len) = match (platform, &flash_start, &ram_start) { (None, Some(flash_start), Some(ram_start)) => { // The flash and RAM ranges were specified directly. flash_len = get_env_var(LINKER_FLASH_LEN_VAR); ram_len = get_env_var(LINKER_RAM_LEN_VAR); ( flash_start.deref(), flash_len.as_deref().unwrap_or("0xD0000"), ram_start.deref(), ram_len.as_deref().unwrap_or("46K"), ) } (Some(platform), None, None) => { // LIBTOCK_PLATFORM was specified. match PLATFORMS .iter() .find(|&&(name, _, _, _, _)| name == platform) { None => panic!("Unknown platform: {platform}"), Some(&(_, flash_start, flash_len, ram_start, ram_len)) => { (flash_start, flash_len, ram_start, ram_len) } } } _ => panic!( "Must specify either {PLATFORM_VAR} or both {LINKER_FLASH_VAR} and {LINKER_RAM_VAR}; please see \ libtock_build_scripts' documentation for more information." ), }; let tbf_header_size; let tbf_header_size = match get_env_var(TBF_HEADER_SIZE_VAR) { None => "0x80", Some(value) => { tbf_header_size = value; &tbf_header_size } }; // Note: cargo fails if run in a path that is not valid Unicode, so this // script doesn't need to handle non-Unicode paths. Also, OUT_DIR cannot be // in a location with a newline in it, or we have no way to pass // rustc-link-search to cargo. let out_dir = &*var("OUT_DIR").expect("Unable to read OUT_DIR"); assert!( !out_dir.contains('\n'), "Build path contains a newline, which is unsupported" ); // Create a valid linker file with the specified flash and ram locations. // // ``` // TBF_HEADER_SIZE = 0x80; // FLASH_START = 0x00040000; // FLASH_LENGTH = 0x00040000; // RAM_START = 0x20008000; // RAM_LENGTH = 62K; // INCLUDE libtock_layout.ld // ``` let layout_name = format!("{flash_start}.{flash_len}.{ram_start}.{ram_len}.ld"); let layout_path: PathBuf = [out_dir, &layout_name].iter().collect(); let mut layout_file = File::create(&layout_path).unwrap_or_else(|e| panic!("Could not open layout file: {e}")); writeln!( layout_file, "\ TBF_HEADER_SIZE = {tbf_header_size};\n\ FLASH_START = {flash_start};\n\ FLASH_LENGTH = {flash_len};\n\ RAM_START = {ram_start};\n\ RAM_LENGTH = {ram_len};\n\ INCLUDE {LIBTOCK_LAYOUT_NAME};" ) .expect("Failed to write layout file"); drop(layout_file); // Compile the contents of `libtock_layout.ld` into this library as a // string, and copy those contents into out_dir at runtime. let libtock_layout_path: PathBuf = [out_dir, LIBTOCK_LAYOUT_NAME].iter().collect(); let mut libtock_layout_file = File::create(libtock_layout_path) .unwrap_or_else(|e| panic!("Could not open {LIBTOCK_LAYOUT_NAME}: {e}")); write!( libtock_layout_file, "{}", include_str!("../libtock_layout.ld") ) .expect("Failed to write libtock_layout.ld"); drop(libtock_layout_file); // Tell rustc which linker script to use and where to find it. println!("cargo:rustc-link-arg=-T{}", layout_path.display()); println!("cargo:rustc-link-search={out_dir}"); // Configure the alignment size for the linker. This prevents the linker // from assuming very large pages (i.e. 65536 bytes) and unnecessarily // inserting additional padding into the output ELF. println!("cargo:rustc-link-arg=-zmax-page-size=4096"); } // Retrieves an environment variable as a String. Returns None if the variable // is not specified and panics if the variable is not valid Unicode. fn get_env_var(name: &str) -> Option { use std::env::{var, VarError}; match var(name) { Ok(value) => Some(value), Err(VarError::NotPresent) => None, Err(VarError::NotUnicode(value)) => panic!("Non-Unicode value in {name}: {value:?}"), } } ================================================ FILE: demos/embedded_graphics/buttons/Cargo.toml ================================================ [package] name = "buttons" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version = "1.87" description = "libtock-rs button app with embedded graphics" [dependencies] embedded-graphics = "0.8.1" libm = "0.2.15" libtock = { path = "../../../", version = "0.1.0" } libtock_platform = { path = "../../../platform" } embedded_graphics_libtock = { path = "../../../libraries/embedded_graphics_libtock", version = "0.1.0" } [build-dependencies] libtock_build_scripts = { path = "../../../build_scripts" } [workspace] ================================================ FILE: demos/embedded_graphics/buttons/Makefile ================================================ # Makefile for the demo app. # Crate name of the demo app DEMO := buttons all: tab include ../../../Targets.mk $(ELF_TARGETS): LIBTOCK_LINKER_FLASH=$(F) LIBTOCK_LINKER_RAM=$(R) cargo build --target=$(T) --release @mkdir -p target/$(A).$(F).$(R)/ @cp target/$(T)/release/$(DEMO) target/$(A).$(F).$(R)/ $(eval ELF_LIST += target/$(A).$(F).$(R)/$(DEMO),$(A).$(F).$(R)) # This target (`make tab`) is not parallel-safe .PHONY: tab tab: $(ELF_TARGETS) @mkdir -p target/tab elf2tab --kernel-major 2 --kernel-minor 1 -n $(DEMO) -o target/tab/$(DEMO).tab --stack 1024 --minimum-footer-size 256 $(ELF_LIST) ================================================ FILE: demos/embedded_graphics/buttons/README.md ================================================ Buttons Demo Using Embedded Graphics ==================================== Draws buttons as circles on the screen. When a button is pressed the corresponding circle is filled in. ================================================ FILE: demos/embedded_graphics/buttons/build.rs ================================================ fn main() { libtock_build_scripts::auto_layout(); } ================================================ FILE: demos/embedded_graphics/buttons/src/main.rs ================================================ #![no_main] #![no_std] use core::cell::Cell; use core::fmt::Write; use libtock::buttons::{ButtonListener, ButtonState, Buttons}; use libtock::console::Console; use libtock::runtime::{set_main, stack_size, TockSyscalls}; use libtock_platform::share; use libtock_platform::ErrorCode; use libtock_platform::Syscalls; use embedded_graphics_libtock::tock_screen::TockMonochrome8BitPage128x64Screen; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::prelude::Point; use embedded_graphics::prelude::Primitive; use embedded_graphics::primitives::{Circle, PrimitiveStyle}; use embedded_graphics::Drawable; set_main! {main} stack_size! {4000} fn run() -> Result<(), ErrorCode> { let mut screen = TockMonochrome8BitPage128x64Screen::new(); let width = screen.get_width(); let height = screen.get_height(); let button_count = Buttons::count()?; writeln!(Console::writer(), "[BUTTONS] Count: {}", button_count).unwrap(); // Calculate where the buttons should be drawn. let button_padding_px = (button_count - 1) * 2; let max_x = (width - button_padding_px) / button_count; let max_y = height - 2; let diameter = core::cmp::min(max_x, max_y); let buttons_width = (diameter * button_count) + button_padding_px; let padding_left_px = (width - buttons_width) / 2; let y = (height / 2) - (diameter / 2); // Draw the initial button outlines. for i in 0..button_count { let x = padding_left_px + ((diameter + 2) * i); let _ = Circle::new(Point::new(x as i32, y as i32), diameter) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) .draw(&mut screen); } let _ = screen.flush(); // Now wait for button presses. Record what happened in the upcall. let buttons: [Cell; 10] = [const { Cell::new(ButtonState::Released) }; 10]; let changed: Cell = Cell::new(false); let listener = ButtonListener(|button, state| { // If the button state changed, record it. if buttons[button as usize].get() != state { buttons[button as usize].set(state); changed.set(true); } }); share::scope(|subscribe| { // Subscribe to the button callback. Buttons::register_listener(&listener, subscribe).unwrap(); // Enable interrupts for each button press. for i in 0..button_count { Buttons::enable_interrupts(i).unwrap(); } // Wait for buttons to be pressed. loop { TockSyscalls::yield_wait(); // If a button state changed, re-draw the buttons. if changed.get() { changed.set(false); let mut screen = TockMonochrome8BitPage128x64Screen::new(); // Draw Circles for i in 0..button_count { let x = padding_left_px + ((diameter + 2) * i); // Draw outer circle let _ = Circle::new(Point::new(x as i32, y as i32), diameter) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) .draw(&mut screen); match buttons[i as usize].get() { ButtonState::Pressed => { let _ = Circle::new(Point::new(x as i32 + 1, y as i32 + 1), diameter - 2) .into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) .draw(&mut screen); } ButtonState::Released => { let _ = Circle::new(Point::new(x as i32 + 1, y as i32 + 1), diameter - 2) .into_styled(PrimitiveStyle::with_fill(BinaryColor::Off)) .draw(&mut screen); } } } let _ = screen.flush(); } } }); Ok(()) } fn main() { match run() { Ok(()) => {} Err(_e) => { writeln!(Console::writer(), "[BUTTONS] Err could not run app").unwrap(); } } } ================================================ FILE: demos/embedded_graphics/spin/Cargo.toml ================================================ [package] name = "spin" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2021" repository = "https://www.github.com/tock/libtock-rs" rust-version = "1.87" description = "libtock-rs spin app with embedded graphics" [dependencies] embedded-graphics = "0.8.1" libm = "0.2.15" libtock = { path = "../../../", version = "0.1.0" } embedded_graphics_libtock = { path = "../../../libraries/embedded_graphics_libtock", version = "0.1.0" } [build-dependencies] libtock_build_scripts = { path = "../../../build_scripts" } [workspace] ================================================ FILE: demos/embedded_graphics/spin/Makefile ================================================ # Makefile for the demo app. # Crate name of the demo app DEMO := spin all: tab include ../../../Targets.mk $(ELF_TARGETS): LIBTOCK_LINKER_FLASH=$(F) LIBTOCK_LINKER_RAM=$(R) cargo build --target=$(T) --release @mkdir -p target/$(A).$(F).$(R)/ @cp target/$(T)/release/$(DEMO) target/$(A).$(F).$(R)/ $(eval ELF_LIST += target/$(A).$(F).$(R)/$(DEMO),$(A).$(F).$(R)) # This target (`make tab`) is not parallel-safe .PHONY: tab tab: $(ELF_TARGETS) @mkdir -p target/tab elf2tab --kernel-major 2 --kernel-minor 1 -n $(DEMO) -o target/tab/$(DEMO).tab --stack 1024 --minimum-footer-size 256 $(ELF_LIST) ================================================ FILE: demos/embedded_graphics/spin/README.md ================================================ Spin Demo Using Embedded Graphics ================================= This demo spins a line on a screen. It was tested using the SSD1306 screen on the nRF52840dk. ================================================ FILE: demos/embedded_graphics/spin/build.rs ================================================ fn main() { libtock_build_scripts::auto_layout(); } ================================================ FILE: demos/embedded_graphics/spin/src/main.rs ================================================ #![no_main] #![no_std] use libtock::alarm::Alarm; use libtock::alarm::Milliseconds; use libtock::runtime::{set_main, stack_size}; use embedded_graphics_libtock::tock_screen::TockMonochrome8BitPage128x64Screen; use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::pixelcolor::BinaryColor; use embedded_graphics::prelude::Point; use embedded_graphics::prelude::Primitive; use embedded_graphics::primitives::{Line, PrimitiveStyle}; use embedded_graphics::Drawable; set_main! {main} stack_size! {4000} fn main() { let mut screen = TockMonochrome8BitPage128x64Screen::new(); let width = screen.get_width() as i32; let height = screen.get_height() as i32; let center_x = width / 2; let center_y = height / 2; let radius = if width < height { center_x - 1 } else { center_y - 1 }; let mut rot: usize = 0; loop { let _ = screen.clear(BinaryColor::Off); let angle = (rot as f32 / 100.0) * (2.0 * core::f32::consts::PI); let x = (center_x as f32 + (radius as f32 * libm::cosf(angle))) as i32; let y = (center_y as f32 + (radius as f32 * libm::sinf(angle))) as i32; let _ = Line::new(Point::new(center_x, center_y), Point::new(x, y)) .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) .draw(&mut screen); let _ = screen.flush(); Alarm::sleep_for(Milliseconds(200)).unwrap(); rot = (rot + 1) % 100; } } ================================================ FILE: demos/st7789/Cargo.toml ================================================ [package] name = "st7789" version = "0.1.0" edition = "2021" rust-version = "1.87" authors = ["Alistair Francis "] description = """A demo to drive a ST7789 display via SPI using libtock-rs.""" license = "Apache-2.0 OR MIT" [dependencies] libtock = { path = "../../", features = ["rust_embedded"] } embedded-hal = "1.0" mipidsi = "0.8.0" display-interface-spi = "0.5" embedded-graphics = "0.8" [build-dependencies] libtock_build_scripts = { path = "../../build_scripts" } [workspace] ================================================ FILE: demos/st7789/Makefile ================================================ # Makefile for the demo app. # Crate name of the demo app DEMO := st7789 all: tab include ../../Targets.mk $(ELF_TARGETS): LIBTOCK_LINKER_FLASH=$(F) LIBTOCK_LINKER_RAM=$(R) cargo build --target=$(T) --release @mkdir -p target/$(A).$(F).$(R)/ @cp target/$(T)/release/$(DEMO) target/$(A).$(F).$(R)/ $(eval ELF_LIST += target/$(A).$(F).$(R)/$(DEMO),$(A).$(F).$(R)) # This target (`make tab`) is not parallel-safe .PHONY: tab tab: $(ELF_TARGETS) @mkdir -p target/tab elf2tab --kernel-major 2 --kernel-minor 1 -n $(DEMO) -o target/tab/$(DEMO).tab --stack 1024 --minimum-footer-size 256 $(ELF_LIST) ================================================ FILE: demos/st7789/build.rs ================================================ fn main() { libtock_build_scripts::auto_layout(); } ================================================ FILE: demos/st7789/src/main.rs ================================================ //! This sample demonstrates displaying text on a ST7789 display //! using a rust-embedded based crate #![no_main] #![no_std] use core::fmt::Write; use libtock::alarm::{Alarm, Milliseconds}; use libtock::console::Console; use libtock::gpio::Gpio; use libtock::platform::Syscalls; use libtock::runtime::{set_main, stack_size, TockSyscalls}; use libtock::spi_controller::EmbeddedHalSpi; use display_interface_spi::SPIInterface; use embedded_graphics::{ mono_font::{ascii::FONT_10X20, MonoTextStyle}, pixelcolor::Rgb565, prelude::*, text::Text, }; use mipidsi::{models::ST7789, options::ColorInversion, Builder}; set_main! {main} stack_size! {0x1000} // Display const W: i32 = 240; const H: i32 = 240; struct Delay; impl embedded_hal::delay::DelayNs for Delay { fn delay_ns(&mut self, ns: u32) { Alarm::sleep_for(Milliseconds(ns / (1000 * 1000))).unwrap(); } } fn main() { writeln!(Console::writer(), "st7789: example\r").unwrap(); let mut gpio_dc = Gpio::get_pin(0).unwrap(); let dc = gpio_dc.make_output().unwrap(); let mut gpio_reset = Gpio::get_pin(1).unwrap(); let reset = gpio_reset.make_output().unwrap(); let di = SPIInterface::new(EmbeddedHalSpi, dc); let mut delay = Delay; let mut display = Builder::new(ST7789, di) .display_size(W as u16, H as u16) .invert_colors(ColorInversion::Inverted) .reset_pin(reset) .init(&mut delay) .unwrap(); // Text let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); let text = "Hello World ^_^;"; let text_x = W; let text_y = H / 2; // Clear the display initially display.clear(Rgb565::BLUE).unwrap(); writeln!(Console::writer(), "Clear complete\r").unwrap(); // Draw text Text::new(text, Point::new(text_x, text_y), text_style) .draw(&mut display) .unwrap(); loop { TockSyscalls::yield_wait(); } } ================================================ FILE: demos/st7789-slint/Cargo.toml ================================================ [package] name = "st7789-slint" version = "0.1.0" edition = "2021" rust-version = "1.87" authors = ["Alistair Francis "] description = """A demo to use the Slint GUI library with a ST7789 display via SPI using libtock-rs.""" license = "Apache-2.0 OR MIT" [dependencies] libtock = { path = "../../", features = ["rust_embedded"] } embedded-hal = "1.0" mipidsi = "0.8.0" display-interface-spi = "0.5" embedded-graphics = "0.8" # The heap allocator and portable atomics embedded-alloc = "0.5.1" critical-section = "1.0" slint = { git = "https://github.com/slint-ui/slint", default-features = false, features = ["libm", "unsafe-single-threaded"] } mcu-board-support = { git = "https://github.com/slint-ui/slint" } display-interface = "0.5" [build-dependencies] libtock_build_scripts = { path = "../../build_scripts" } slint-build = { git = "https://github.com/slint-ui/slint" } [workspace] resolver = "3" ================================================ FILE: demos/st7789-slint/Makefile ================================================ # Makefile for the demo app. # Crate name of the demo app DEMO := st7789-slint all: tab include ../../Targets.mk $(ELF_TARGETS): LIBTOCK_LINKER_FLASH=$(F) LIBTOCK_LINKER_RAM=$(R) cargo build --target=$(T) --release @mkdir -p target/$(A).$(F).$(R)/ @cp target/$(T)/release/$(DEMO) target/$(A).$(F).$(R)/ $(eval ELF_LIST += target/$(A).$(F).$(R)/$(DEMO),$(A).$(F).$(R)) # This target (`make tab`) is not parallel-safe .PHONY: tab tab: $(ELF_TARGETS) @mkdir -p target/tab elf2tab --kernel-major 2 --kernel-minor 1 -n $(DEMO) -o target/tab/$(DEMO).tab --stack 1024 --minimum-footer-size 256 $(ELF_LIST) ================================================ FILE: demos/st7789-slint/build.rs ================================================ fn main() { libtock_build_scripts::auto_layout(); let config = slint_build::CompilerConfiguration::new() .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer); slint_build::compile_with_config("ui/appwindow.slint", config).unwrap(); slint_build::print_rustc_flags().unwrap(); } ================================================ FILE: demos/st7789-slint/src/main.rs ================================================ //! This sample demonstrates displaying a slint GUI on a ST7789 display //! using a rust-embedded based crate #![no_main] #![no_std] extern crate alloc; use core::fmt::Write; use libtock::alarm::{Alarm, Milliseconds}; use libtock::console::Console; use libtock::gpio::Gpio; use libtock::platform::ErrorCode; use libtock::runtime::{set_main, stack_size}; use libtock::spi_controller::EmbeddedHalSpi; use critical_section::RawRestoreState; use embedded_alloc::Heap; use display_interface_spi::SPIInterface; use embedded_hal::digital::OutputPin; use mipidsi::{models::ST7789, options::ColorInversion, Builder, Display}; slint::include_modules!(); set_main! {main} stack_size! {0x1400} #[global_allocator] static HEAP: Heap = Heap::empty(); struct MyCriticalSection; critical_section::set_impl!(MyCriticalSection); unsafe impl critical_section::Impl for MyCriticalSection { unsafe fn acquire() -> RawRestoreState { // Tock is single threaded, so this can only be preempted by interrupts // The kernel won't schedule anything from our app unless we yield // so as long as we don't yield we won't concurrently run with // other critical sections from our app. // The kernel might schedule itself or other applications, but there // is nothing we can do about that. } unsafe fn release(_token: RawRestoreState) {} } // Setup the heap and the global allocator. unsafe fn setup_heap() { use core::mem::MaybeUninit; const HEAP_SIZE: usize = 1024 * 6; static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) } } // Display const W: i32 = 240; const H: i32 = 240; struct Delay; impl embedded_hal::delay::DelayNs for Delay { fn delay_ns(&mut self, ns: u32) { Alarm::sleep_for(Milliseconds(ns / (1000 * 1000))).unwrap(); } } #[mcu_board_support::entry] fn main() { writeln!(Console::writer(), "st7789-slint: example\r").unwrap(); unsafe { setup_heap(); } // Configure platform for Slint let window = slint::platform::software_renderer::MinimalSoftwareWindow::new( slint::platform::software_renderer::RepaintBufferType::ReusedBuffer, ); window.set_size(slint::PhysicalSize::new(240, 240)); slint::platform::set_platform(alloc::boxed::Box::new(SlintPlatform { window: window.clone(), })) .unwrap(); MainWindow::new().unwrap().run().unwrap(); } struct SlintPlatform { window: alloc::rc::Rc, } impl slint::platform::Platform for SlintPlatform { fn create_window_adapter( &self, ) -> Result, slint::PlatformError> { Ok(self.window.clone()) } fn duration_since_start(&self) -> core::time::Duration { core::time::Duration::from_millis(Alarm::get_milliseconds().unwrap()) } fn debug_log(&self, arguments: core::fmt::Arguments) { writeln!(Console::writer(), "{arguments}\r").unwrap(); } fn run_event_loop(&self) -> Result<(), slint::PlatformError> { let mut gpio_dc = Gpio::get_pin(0).unwrap(); let dc = gpio_dc.make_output().unwrap(); let mut gpio_reset = Gpio::get_pin(1).unwrap(); let reset = gpio_reset.make_output().unwrap(); let di = SPIInterface::new(EmbeddedHalSpi, dc); let mut delay = Delay; let display = Builder::new(ST7789, di) .display_size(W as u16, H as u16) .invert_colors(ColorInversion::Inverted) .reset_pin(reset) .init(&mut delay) .unwrap(); let mut buffer_provider = DrawBuffer { display, buffer: &mut [slint::platform::software_renderer::Rgb565Pixel(100); 240], }; writeln!(Console::writer(), "Setup platform, running loop\r").unwrap(); loop { slint::platform::update_timers_and_animations(); self.window.draw_if_needed(|renderer| { renderer.render_by_line(&mut buffer_provider); }); if self.window.has_active_animations() { continue; } if let Some(duration) = slint::platform::duration_until_next_timer_update() { Alarm::sleep_for(Milliseconds(duration.as_millis() as u32)).unwrap(); } } } } struct DrawBuffer<'a, Display> { display: Display, buffer: &'a mut [slint::platform::software_renderer::Rgb565Pixel], } impl> slint::platform::software_renderer::LineBufferProvider for &mut DrawBuffer<'_, Display> { type TargetPixel = slint::platform::software_renderer::Rgb565Pixel; fn process_line( &mut self, line: usize, range: core::ops::Range, render_fn: impl FnOnce(&mut [slint::platform::software_renderer::Rgb565Pixel]), ) { let buffer = &mut self.buffer[range.clone()]; render_fn(buffer); // We send empty data just to get the device in the right window self.display .set_pixels( range.start as u16, line as _, range.end as u16, line as u16, buffer .iter() .map(|x| embedded_graphics::pixelcolor::raw::RawU16::new(x.0).into()), ) .unwrap(); } } ================================================ FILE: demos/st7789-slint/ui/appwindow.slint ================================================ import {Button, AboutSlint} from "std-widgets.slint"; export component MainWindow inherits Window { width: 240px; height: 240px; VerticalLayout { AboutSlint { } } } ================================================ FILE: doc/CargoFeatures.md ================================================ Cargo Features ============== On the surface, `cargo`'s [features](https://doc.rust-lang.org/cargo/reference/features.html) mechanism looks great for `libtock-rs`. Process binary crates can depend on `libtock-rs`, and use `cargo` features to specify which optional `libtock-rs` functionality they want. However, `cargo` assumes that it can compile a crate with features that a binary does not need. This isn't necessarily true for embedded crates, where process binary authors care about the app size. ## Process Binaries in a Workspace When used with workspaces, `cargo` features can result in excess code being built into a repository. For example, suppose that `libtock` exposes a `malloc` feature, which adds a dynamic memory allocator. Then if a `libtock` user creates a `cargo` workspace with the following process binaries in it: 1. `no_malloc`, which depends on `libtock` and does not depend on the `malloc` feature. 2. `malloc`, which depends on `libtock` and the `malloc` feature. With this setup, `malloc` will always work correctly. Also, if you run `cargo build -p no_malloc`, then `no_malloc` will correctly build without a memory allocation. Seems optimal, right? It's not: if you build the entire workspace with `cargo build --workspace`, `libtock` will only be built once, with the `malloc` feature! That will result in a `no_malloc` process binary that contains a memory allocator, which is not what the author desired. ## Alternative to `cargo` Features In many cases, `cargo` features can be replaced by splitting a crate up into smaller crates, and letting process binaries choose what to depend on. For example, memory allocation can be in a separate crate that process binaries depend on if and only if they need memory allocation. Here are some questions to help guide the decision between using a `cargo` feature and separate crates: 1. Do you forsee a single user writing multiple process binaries, some of which use this feature? If yes, then maybe it should not be a `cargo` feature. 2. Will the compiler optimize the feature away entirely if it is included but unused? If yes, then making it a `cargo` feature is probably fine. In many cases, it will make sense to add a `cargo` feature to `libtock` but use optional crates to implement the feature internally. This way, users with multiple process binaries in a workspace can choose whether each process binary depends on the optimal crate. ================================================ FILE: doc/CodeReview.md ================================================ Code Review =========== ## Code Review Practices PR to `libtock-rs` can be divided into two categories: 1. **Upkeep pull requests** are self-contained changes that do not introduce significant code churn, and are unlikely to cause major merge conflicts. 1. **Significant pull requests** are pull requests that are too substantial to be considered upkeep pull requests. Significant pull requests may represent a significant change in `libtock-rs`'s design or include large refactorings that are likely to cause merge conflicts for other pull requests. The owners of `libtock-rs` (listed [below](#owners)) determine whether a PR is an upkeep PR or a significant PR. PRs should be merged by the `libtock-rs` owners rather than the PR's author. In general, PRs should be merged using a `bors r+` command rather than the GitHub UI (see the [bors documentation](https://bors.tech/documentation/) for more information on bors). A PR may only be merged when all of the following are true: 1. At least one `libtock-rs` owner (who is not the PR author) has approved the PR. 1. All outstanding review discussions have been resolved. 1. If the pull request is significant, a 7 day waiting period has passed since the PR was opened. We recommend that authors of significant PRs comment on the PR when they believe the above criteria have been satisfied (including the waiting period). This is primarily to remind the owners to merge the PR. Secondarily, it should help identify confusion about a PR review's status. ## Owners The owners of `libtock-rs` are: * The [Tock Core Working Group](https://github.com/tock/tock/tree/master/doc/wg/core#members). * Alistair Francis, [alistair23](https://github.com/alistair23), Western Digital ================================================ FILE: doc/Dependencies.md ================================================ Third Party Dependencies ======================== This document is about dependencies that are required to build applications using `libtock-rs`. These dependencies are not contained in the libtock-rs repository, but are used by libtock-rs when libtock-rs is used as a dependency of an application. Dependencies required to run `libtock-rs`' tests (such as `make`) are outside the scope of this document. ## Unaudited Required Dependencies `libtock-rs` has the following required build dependencies, none of which are currently audited: * The Rust toolchain, including [`cargo`](https://github.com/rust-lang/cargo), [`rustc`](https://github.com/rust-lang/rust/tree/master/src/rustc), and [`libcore`](https://github.com/rust-lang/rust/tree/master/src/libcore). The specific toolchain version used is specified by the `rust-toolchain` file at the root of the repository. * [`syn`](https://crates.io/crates/syn), pulled in by `libtock_codegen`. * [`quote`](https://crates.io/crates/quote), pulled in by `libtock_codegen`. * [`proc-macro2`](https://crates.io/crates/proc-macro2), pulled in by `libtock_codegen`. ## Avoiding Optional Dependencies To avoid pulling in optional dependencies, users should use `libtock_core` instead of `libtock`. `libtock_core` is in the `core/` directory. ================================================ FILE: doc/DesignConsiderations.md ================================================ Design Considerations ===================== This document describes several of the factors that constrain the design of `libtock_core`. ## Size impact Tock is designed to run on hardware with limited program storage and limited RAM. On Google's H1 chip, the Tock kernel and apps are limited to a 256 KiB flash region. The HiFive1 Rev B board has only 16 KiB of RAM. Note that a Tock system may have multiple process binaries, each of which uses `libtock_core`, so `libtock_core` must minimize its size impact to the extent possible. A process binary is generally stored in non-volatile memory and contains: * Runtime headers (the `.crt0_header` section) * Executable code (the `.text` section) * Read-only data (the `.rodata` section) * Non-zero-initialized read-write data (the `.data` section) A process' memory section is in RAM and contains: * Heap memory (if a dynamic memory allocator is present) * Zero-initialized read-write data (the `.bss` section) * Non-zero-initialized read-write data (the `.data` section) * Stack memory `libtock_core` developers should consider the space usage of their code, and where the space usage is (non-volatile memory or RAM). Important note: the `.data` section consumes space in both non-volatile memory and RAM! Most apps will not use all functionality in `libtock_core`. The size impact of using `libtock_core` in an app should be commensurate with the `libtock_core` functionality that app uses. If done correctly, this will allow `libtock_core`'s users to build Tock systems with multiple small, single-purpose apps without excessive code duplication. ## Testability Programmatic tests are important to verify that new functionality works correctly and to keep existing functionality working through nontrivial refactorings. Different types of tests offer different capabilities: * **Host-based unit tests** run on a non-Tock host OS (such as Linux). They can only be used with portable `libtock_core` code, as the code they test must run on both Tock and the host OS. These tests must either test code that does not directly use system calls, or must direct the system calls to a fake Tock kernel. These tests can be fast (compile and run in seconds), and can easily simulate a variety of failure modes (e.g. kernel errors) that may be difficult to generate with a real Tock kernel. * **Emulation tests** run on an emulated Tock system. Currently, emulation tests run in QEMU on an emulated HiFive1 Rev B board. These test `libtock_core`'s interaction with a real Tock kernel, albeit with limited hardware access. * **Hardware tests** run on physical hardware with a real Tock kernel. These are end-to-end tests that test not only `libtock_core`'s functionality but also the Tock kernel's interaction with the real hardware. However, automating these tests is difficult, and currently we do not have a way to run hardware tests in CI. If `libtock_core` is functional on real hardware with a real kernel, it should be able to support emulation tests and hardware tests. However, host-based unit tests require more work. In its intended use case, `libtock_core` is not supposed to be portable, supporting only the Tock kernel. Hoever, host-based unit tests are very valuable, both for rapid development and for testing error handling code. To support host-based tests, `libtock_core` must be built out of portable pieces that can be unit tested, even though the library as a whole is not portable. Testing pieces of `libtock_core` against a fake kernel can be done using dependency injection, but most dependency injection techniques have considerable code size and RAM usage costs. `libtock_core` needs either a dependency injection technique that has minimal size impact or an alternative mechanism for testing `libtock_core`'s system call error handling logic. ## Support multiple asynchronous programming models. The general Rust ecosystem has converged on futures as its building block for interoperable asynchronous APIs, but futures have a [size cost](https://github.com/tock/design-explorations/tree/master/size_comparison) that makes them impractical for some use cases of `libtock_core`. Although `libtock_core` is still a work in progress (see [issue 217](https://github.com/tock/libtock-rs/issues/217)), it will probably use an asynchronous API design based on the [Asynchronous Components using Zero Sized Type Pointers](https://github.com/tock/design-explorations/tree/master/zst_pointer_async) exploration. In order to interoperate with other libraries as well as the `async`/`await` functionality built in to Rust, we need `libtock_core` to interface well with futures. In addition, there are several use cases of `libtock_core` that consist primarily of synchronous code, so we need to be able to use `libtock_core`'s APIs in a synchronous manner. The `libtock-c` developers are already familiar with some of the complications that come up when synchronous code is interfaced to asynchronous code. For example, calling `printf` -- an extremely common debugging tool -- causes callbacks to run in the background. This causes surprising and hard-to-debug reentrancy issues. ## Dependency tree minimization Some of users of `libtock_core` intend to use it in applications that will undergo code audits for security certifications. To make this auditing practical, it is important to allow users to use `libtock_core` without pulling in a large dependency tree. As a general rule, a `libtock_core` should avoid having required dependencies unless those dependencies are minimal (that is, avoiding the dependency would require reimplementing nearly all of it). Dependencies included in the Rust toolchain (such as the `core` crate and its dependencies) are an exception, as they are a part of the language itself. ## No hard `alloc` dependency It should be possible to use `libtock_core` without bringing in the `alloc`. Of course, it is fine for `libtock_core` to offer a memory allocator as an optional feature. ================================================ FILE: doc/FaultDebuggingExample.md ================================================ Fault Debugging Example ======================= This document shows the debugging process I (@jrvanwhy) used to find the cause of an illegal instruction error. I wrote this document because the debugging process demonstrates the use of valuable debugging tools (such as `objdump`) as well as the thought process I use to debug low-level issues. ## The failure When I attempt to run the `low_level_debug` example, I get the following app fault (note: the early debug messages are from a work-in-progress PR, but should be self-explanatory): ``` Package name: "low_level_debug" TAB path: target/riscv32imac-unknown-none-elf/release/examples/low_level_debug.tab Protected region size: 72 Found .stack section, size: 256 ELF file: "target/riscv32imac-unknown-none-elf/release/examples/low_level_debug" TBF path: target/riscv32imac-unknown-none-elf/release/examples/low_level_debug.tbf elf2tab command: "elf2tab" "-n" "low_level_debug" "-o" "target/riscv32imac-unknown-none-elf/release/examples/low_level_debug.tab" "--protected-region-size" "72" "--stack" "256" "target/riscv32imac-unknown-none-elf/release/examples/low_level_debug" "-v" Spawning elf2tab Creating "target/riscv32imac-unknown-none-elf/release/examples/low_level_debug.tbf" Min RAM size from segments in ELF: 0 bytes Number of writeable flash regions: 0 Entry point is in .start section Adding .start section. Offset: 72 (0x48). Length: 118 (0x76) bytes. Adding .text section. Offset: 190 (0xbe). Length: 64 (0x40) bytes. Warning! Placing section .text at 0xbe, which is not 4-byte aligned. Searching for .rel.X sections to add. TBF Header: version: 2 0x2 header_size: 64 0x40 total_size: 512 0x200 flags: 1 0x1 init_fn_offset: 40 0x28 protected_size: 8 0x8 minimum_ram_size: 2304 0x900 start_process_ram: 2147493888 0x80002800 start_process_flash: 537133128 0x20040048 elf2tab finished. exit status: 0 QEMU command: "tock2/tools/qemu/build/qemu-system-riscv32" "-M" "sifive_e,revb=true" "-kernel" "tock2/target/riscv32imac-unknown-none-elf/release/hifive1" "-nographic" "-device" "loader,file=target/riscv32imac-unknown-none-elf/release/examples/low_level_debug.tbf,addr=0x20040000" Spawning QEMU HiFive1 initialization complete. Entering main loop. panicked at 'Process low_level_debug had a fault', kernel/src/process.rs:1037:17 Kernel version release-1.6-954-g17e698e8f ---| No debug queue found. You can set it with the DebugQueue component. ---| RISC-V Machine State |--- Last cause (mcause): Illegal instruction (interrupt=0, exception code=0x00000002) Last value (mtval): 0x00000000 System register dump: mepc: 0x20012948 mstatus: 0x00000088 mcycle: 0xFF33A41C minstret: 0xFF33CBEA mtvec: 0x20010100 mstatus: 0x00000088 uie: false upie: false sie: false spie: false mie: true mpie: true spp: false mie: 0x00000888 mip: 0x00000000 usoft: false false ssoft: false false msoft: true false utimer: false false stimer: false false mtimer: true false uext: false false sext: false false mext: true false ---| App Status |--- App: low_level_debug - [Faulted] Events Queued: 0 Syscall Count: 1 Dropped Callback Count: 0 Restart Count: 0 Last Syscall: Some(Memop { operand: 0, arg0: 2147494144 }) ╔═══════════╤══════════════════════════════════════════╗ ║ Address │ Region Name Used | Allocated (bytes) ║ ╚0x800037C0═╪══════════════════════════════════════════╝ │ ▼ Grant 960 | 960 0x80003400 ┼─────────────────────────────────────────── │ Unused 0x80002900 ┼─────────────────────────────────────────── │ ▲ Heap ? | ? S ?????????? ┼─────────────────────────────────────────── R │ Data ? | ? A ?????????? ┼─────────────────────────────────────────── M │ ▼ Stack ? | ? 0x80002900 ┼─────────────────────────────────────────── │ Unused 0x80002800 ┴─────────────────────────────────────────── ..... 0x20040200 ┬─────────────────────────────────────────── F │ App Flash 440 L 0x20040048 ┼─────────────────────────────────────────── A │ Protected 72 S 0x20040000 ┴─────────────────────────────────────────── H R0 : 0x00000000 R16: 0x00000000 R1 : 0x200400BE R17: 0x00000000 R2 : 0x80002900 R18: 0x00000000 R3 : 0x00000000 R19: 0x00000000 R4 : 0x00000000 R20: 0x00000000 R5 : 0x00000000 R21: 0x00000000 R6 : 0x00000000 R22: 0x00000000 R7 : 0x00000000 R23: 0x00000000 R8 : 0x20040068 R24: 0x00000000 R9 : 0x20040068 R25: 0x00000000 R10: 0x00000000 R26: 0x00000000 R11: 0x80002900 R27: 0x00000000 R12: 0x00000FC0 R28: 0x00000000 R13: 0x80003400 R29: 0x00000000 R14: 0x00000005 R30: 0x00000000 R15: 0x20040048 R31: 0x00000000 PC : 0x200400F6 SP : 0x80002900 mcause: 0x00000002 (Illegal instruction) mtval: 0x00000000 PMP regions: [0]: addr=0x20040000, size=0x00000200, cfg=0xD (r-x) [1]: addr=0x80002800, size=0x00000100, cfg=0xB (rw-) To debug, run `make lst` in the app's folder and open the arch.0x20040048.0x80002800.lst file. ``` ## Debugging process When I see an illegal instruction error, my first thought is "did the code just jump to an address that is not code?". The kernel's output indicates that `pc` is `0x200400F6`. To see if `0x200400F6` is code, we disassemble the binary: ``` jrvanwhy@penguin:~/libtock-rs$ riscv64-unknown-elf-objdump -d -j .start -j .text target/riscv32imac-unknown-none-elf/release/examples/low_level_debug target/riscv32imac-unknown-none-elf/release/examples/low_level_debug: file format elf32-littleriscv Disassembly of section .start: 20040048 : 20040048: 0068 addi a0,sp,12 2004004a: 2004 fld fs1,0(s0) 2004004c: 2900 fld fs0,16(a0) 2004004e: 8000 0x8000 20040050: 2900 fld fs0,16(a0) 20040052: 8000 0x8000 20040054: 0000 unimp 20040056: 0000 unimp 20040058: 2900 fld fs0,16(a0) 2004005a: 8000 0x8000 2004005c: 2900 fld fs0,16(a0) 2004005e: 8000 0x8000 20040060: 0000 unimp 20040062: 0000 unimp 20040064: 2900 fld fs0,16(a0) 20040066: 8000 0x8000 20040068 : 20040068: 00000417 auipc s0,0x0 2004006c: 87aa mv a5,a0 2004006e: 4384 lw s1,0(a5) 20040070: 00940c63 beq s0,s1,20040088 20040074: 4521 li a0,8 20040076: 4585 li a1,1 20040078: 4609 li a2,2 2004007a: 4709 li a4,2 2004007c: 00000073 ecall 20040080: 4501 li a0,0 20040082: 4719 li a4,6 20040084: 00000073 ecall 20040088: 4501 li a0,0 2004008a: 43cc lw a1,4(a5) 2004008c: 4715 li a4,5 2004008e: 00000073 ecall 20040092: 0087a103 lw sp,8(a5) 20040096: 47c8 lw a0,12(a5) 20040098: c909 beqz a0,200400aa 2004009a: 4b8c lw a1,16(a5) 2004009c: 4bd0 lw a2,20(a5) 2004009e: 4194 lw a3,0(a1) 200400a0: c214 sw a3,0(a2) 200400a2: 1571 addi a0,a0,-4 200400a4: 0591 addi a1,a1,4 200400a6: 0611 addi a2,a2,4 200400a8: f97d bnez a0,2004009e 200400aa: 4f88 lw a0,24(a5) 200400ac: c519 beqz a0,200400ba 200400ae: 4fcc lw a1,28(a5) 200400b0: 00058023 sb zero,0(a1) 200400b4: 157d addi a0,a0,-1 200400b6: 0585 addi a1,a1,1 200400b8: fd65 bnez a0,200400b0 200400ba: 03c000ef jal ra,200400f6 Disassembly of section .text: 200400c0 <_ZN15low_level_debug4main17h52d4e61b1cb7ceefE>: 200400c0: 4709 li a4,2 200400c2: 4521 li a0,8 200400c4: 4605 li a2,1 200400c6: 4589 li a1,2 200400c8: 4681 li a3,0 200400ca: 00000073 ecall 200400ce: 4709 li a4,2 200400d0: 468d li a3,3 200400d2: 4521 li a0,8 200400d4: 4609 li a2,2 200400d6: 458d li a1,3 200400d8: 00000073 ecall 200400dc: 8082 ret 200400de : 200400de: 1141 addi sp,sp,-16 200400e0: c606 sw ra,12(sp) 200400e2: 00000097 auipc ra,0x0 200400e6: fde080e7 jalr -34(ra) # 200400c0 <_ZN15low_level_debug4main17h52d4e61b1cb7ceefE> 200400ea: 4719 li a4,6 200400ec: 4501 li a0,0 200400ee: 4581 li a1,0 200400f0: 00000073 ecall ... 200400f6 : 200400f6: 00000097 auipc ra,0x0 200400fa: fe8080e7 jalr -24(ra) # 200400de ... ``` In the ELF file, `0x200400f6` is the beginning of a valid instruction. So maybe something is going wrong in the ELF to TBF conversion, or in the deployment? ## Address checking The [RISC-V entry point](https://github.com/tock/libtock-rs/blob/b0f8593c1c5dc2a4ded1305809841202107d7c75/runtime/asm/asm_riscv32.S) has logic to verify the program counter is incorrect. If that logic executed, then we know the TBF file was deployed to the correct location in flash. But how do we know it executed, rather than the kernel trying to start execution at `0x200400f6` immediately? We can see some evidence of that in the kernel's output: ``` Last Syscall: Some(Memop { operand: 0, arg0: 2147494144 }) ``` The only place that Memop calls occur in `low_level_debug` is in `asm_riscv32.S`: ``` .section .start, "ax" .globl start start: /* First, verify the process binary was loaded at the correct address. The * check is performed by comparing the program counter at the start to the * address of `start`, which is stored in rt_header. */ auipc s0, 0 /* s0 = pc */ mv a5, a0 /* Save rt_header so syscalls don't overwrite it */ lw s1, 0(a5) /* s1 = rt_header.start */ beq s0, s1, .Lset_brk /* Skip error handling code if pc is correct */ /* If the beq on the previous line did not jump, then the binary is not at * the correct location. Report the error via LowLevelDebug then exit. */ li a0, 8 /* LowLevelDebug driver number */ li a1, 1 /* Command: Print alert code */ li a2, 2 /* Alert code 2 (incorrect location) */ li a4, 2 /* `command` class */ ecall li a0, 0 /* exit-terminate */ /* TODO: Set a completion code, once completion codes are decided */ li a4, 6 /* `exit` class */ ecall .Lset_brk: /* memop(): set brk to rt_header's initial break value */ li a0, 0 /* operation: set break */ lw a1, 4(a5) /* rt_header's initial process break */ li a4, 5 /* `memop` class */ ecall ``` Notably, the initial process break is loaded from `4(a5)`, and `a5` is set *before* the `pc` check runs. So if the memory break is correct, then we can be fairly confident the `pc` check ran. `2147494144` is `0x80002900`, which is 256 bytes past the beginning of the process binary's flash region (from `runtime/layouts/hifive1.ld`): ``` MEMORY { /* Note that the SRAM address may need to be changed depending on * the kernel binary, check for the actual address of APP_MEMORY! */ FLASH (X) : ORIGIN = 0x20040000, LENGTH = 32M RAM (W) : ORIGIN = 0x80002800, LENGTH = 0x1800 } ``` That seems completely reasonable. Next, I suspect an issue in the ELF -> TBF conversion. ## Decoding the TBF file This is the point where the tiny size of `low_level_debug` becomes hugely beneficial. We hexdump the entirety of the TBF file: ``` jrvanwhy@penguin:~/libtock-rs$ hexdump -C target/riscv32imac-unknown-none-elf/release/examples/low_level_debug.tbf | tee ~/decoded_tbf 00000000 02 00 40 00 00 02 00 00 01 00 00 00 62 03 4d ff |..@.........b.M.| 00000010 01 00 0c 00 28 00 00 00 08 00 00 00 00 09 00 00 |....(...........| 00000020 03 00 0f 00 6c 6f 77 5f 6c 65 76 65 6c 5f 64 65 |....low_level_de| 00000030 62 75 67 00 05 00 08 00 00 28 00 80 48 00 04 20 |bug......(..H.. | 00000040 00 00 00 00 00 00 00 00 68 00 04 20 00 29 00 80 |........h.. .)..| 00000050 00 29 00 80 00 00 00 00 00 29 00 80 00 29 00 80 |.).......)...)..| 00000060 00 00 00 00 00 29 00 80 17 04 00 00 aa 87 84 43 |.....).........C| 00000070 63 0c 94 00 21 45 85 45 09 46 09 47 73 00 00 00 |c...!E.E.F.Gs...| 00000080 01 45 19 47 73 00 00 00 01 45 cc 43 15 47 73 00 |.E.Gs....E.C.Gs.| 00000090 00 00 03 a1 87 00 c8 47 09 c9 8c 4b d0 4b 94 41 |.......G...K.K.A| 000000a0 14 c2 71 15 91 05 11 06 7d f9 88 4f 19 c5 cc 4f |..q.....}..O...O| 000000b0 23 80 05 00 7d 15 85 05 65 fd ef 00 c0 03 09 47 |#...}...e......G| 000000c0 21 45 05 46 89 45 81 46 73 00 00 00 09 47 8d 46 |!E.F.E.Fs....G.F| 000000d0 21 45 09 46 8d 45 73 00 00 00 82 80 41 11 06 c6 |!E.F.Es.....A...| 000000e0 97 00 00 00 e7 80 e0 fd 19 47 01 45 81 45 73 00 |.........G.E.Es.| 000000f0 00 00 00 00 97 00 00 00 e7 80 80 fe 00 00 00 00 |................| 00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000200 ``` I then use a text editor to remove the last two lines (which represent padding space), and change the high bits of the addresses to match the addresses the TBF file is deployed to: ``` 20040000 02 00 40 00 00 02 00 00 01 00 00 00 62 03 4d ff |..@.........b.M.| 20040010 01 00 0c 00 28 00 00 00 08 00 00 00 00 09 00 00 |....(...........| 20040020 03 00 0f 00 6c 6f 77 5f 6c 65 76 65 6c 5f 64 65 |....low_level_de| 20040030 62 75 67 00 05 00 08 00 00 28 00 80 48 00 04 20 |bug......(..H.. | 20040040 00 00 00 00 00 00 00 00 68 00 04 20 00 29 00 80 |........h.. .)..| 20040050 00 29 00 80 00 00 00 00 00 29 00 80 00 29 00 80 |.).......)...)..| 20040060 00 00 00 00 00 29 00 80 17 04 00 00 aa 87 84 43 |.....).........C| 20040070 63 0c 94 00 21 45 85 45 09 46 09 47 73 00 00 00 |c...!E.E.F.Gs...| 20040080 01 45 19 47 73 00 00 00 01 45 cc 43 15 47 73 00 |.E.Gs....E.C.Gs.| 20040090 00 00 03 a1 87 00 c8 47 09 c9 8c 4b d0 4b 94 41 |.......G...K.K.A| 200400a0 14 c2 71 15 91 05 11 06 7d f9 88 4f 19 c5 cc 4f |..q.....}..O...O| 200400b0 23 80 05 00 7d 15 85 05 65 fd ef 00 c0 03 09 47 |#...}...e......G| 200400c0 21 45 05 46 89 45 81 46 73 00 00 00 09 47 8d 46 |!E.F.E.Fs....G.F| 200400d0 21 45 09 46 8d 45 73 00 00 00 82 80 41 11 06 c6 |!E.F.Es.....A...| 200400e0 97 00 00 00 e7 80 e0 fd 19 47 01 45 81 45 73 00 |.........G.E.Es.| 200400f0 00 00 00 00 97 00 00 00 e7 80 80 fe 00 00 00 00 |................| 20040100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ``` At this point, I start going through the TBF file byte-by-byte, using the [TBF documentation](https://github.com/tock/tock/blob/master/doc/TockBinaryFormat.md) to decode it and add notes describing what each byte means. Once I make it past the headers, I also compare the bytes against the disassembly, looking for a difference. Eventually, I find one: ``` TBF headers: 20040000 02 00 40 00 00 02 00 00 Version 2, header len 64, TBF len 512 20040008 01 00 00 00 62 03 4d ff Process enabled, checksum 20040010 01 00 0c 00 28 00 00 00 Main element, Data len 12, _start offset 0x28 20040018 08 00 00 00 00 09 00 00 Protected size 8, Minimum RAM size 2304 20040020 03 00 0f 00 Package name, len 15 20040024 6c 6f 77 5f 6c 65 76 65 6c 5f 64 65 62 75 67 00 "low_level_debug", padding 20040034 05 00 08 00 00 28 00 80 48 00 04 20 Fixed addrs, Len 8, RAM 0x80002800, Flash 0x20040048 20040040 00 00 00 00 00 00 00 00 Post-header padding rt_header: 20040048 68 00 04 20 00 29 00 80 00 29 00 80 00 00 00 00 rt_header (first 16 bytes) 20040058 00 29 00 80 00 29 00 80 00 00 00 00 00 29 00 80 rt_header (second 16 bytes) start: 20040068 17 04 00 00 aa 87 84 43 20040070 63 0c 94 00 21 45 85 45 09 46 09 47 73 00 00 00 20040080 01 45 19 47 73 00 00 00 01 45 cc 43 15 47 73 00 20040090 00 00 03 a1 87 00 c8 47 09 c9 8c 4b d0 4b 94 41 200400a0 14 c2 71 15 91 05 11 06 7d f9 88 4f 19 c5 cc 4f 200400b0 23 80 05 00 7d 15 85 05 65 fd ef 00 c0 03 .text (misplaced!): 200400be 09 47 200400c0 21 45 05 46 89 45 81 46 73 00 00 00 09 47 8d 46 200400d0 21 45 09 46 8d 45 73 00 00 00 82 80 41 11 06 c6 200400e0 97 00 00 00 e7 80 e0 fd 19 47 01 45 81 45 73 00 200400f0 00 00 00 00 97 00 00 00 e7 80 80 fe 00 00 00 00 20040100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20040190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 200401a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 200401b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 200401c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 200401d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 200401e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 200401f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ``` `.text` in the TBF starts at `0x200400be`, but in the ELF file it starts at `0x200400c0`! There should be 2 more bytes of padding between `.start` and `.text`, so that `.text` is located at a multiple of 4 bytes. ## The fix It turns out this is a known issue in `elf2tab`, and was fixed in https://github.com/tock/elf2tab/pull/35. I resolved the fault by updating `elf2tab`. ================================================ FILE: doc/MiriTips.md ================================================ Miri Tips ========= `libtock-rs` runs most of its unit tests under [Miri](https://github.com/rust-lang/miri) to detect undefined behavior. However, Miri does not detect all undefined behavior encountered. This document lists techniques for maximizing the amount of undefined behavior identified by Miri. ## Miri flags Miri has [configuration flags](https://github.com/rust-lang/miri#miri--z-flags-and-environment-variables), which can be set using the `MIRIFLAGS` environment variable. We run the tests both with and without `-Zmiri-track-raw-pointers`, as both versions of Stacked Borrows has undefined behavior the other does not (see [this discussion](https://github.com/rust-lang/miri/pull/1748) for more information). On the run with `-Zmiri-track-raw-pointers`, we also set `-Zmiri-symbolic-alignment-check` to make Miri's alignment check more pendantic. ## Detecting invalid values Miri does not always detect undefined behavior when an invalid value is created but not used. Here are some cases that Miri currently accepts: 1. Unused reference to an invalid value (https://github.com/rust-lang/miri/issues/1638) 1. Unused uninitialized value (https://github.com/rust-lang/miri/issues/1340) 1. Slices pointing to invalid values (https://github.com/rust-lang/miri/issues/1240) Note that copying the value around (including passing it to functions) does *not* count as a use for the purpose of this check. For types that implement `core::fmt::Debug` (i.e. most types), you can check a value is valid by attempting to debug-print it: ```rust format!("{:?}", value); ``` If `value` is invalid, then Miri will identify undefined behavior in the above code. ================================================ FILE: doc/Overview.md ================================================ Overview ====== This document gives an overview of the crates in this repository, and is intended to be useful to `libtock-rs` newcomers. ## `libtock` `libtock` provides the default `libtock-rs` experience. It re-exports all of the drivers `libtock-rs` provides, and provides usable defaults for panic handling and memory allocation. It should be easy to build a Tock application that only has one direct dependency: `libtock`. In order to be easy to use, `libtock` has a hard dependency on `libtock_runtime`, and therefore cannot be used in a unit test environment. If you want to unit test code that uses `libtock-rs`, you should depend on the individual libraries rather than `libtock`. ## Naming convention note Although these crates have yet to be uploaded to crates.io, they likely will be uploaded in the future. Therefore, to avoid name collisions, most crates in this repository have a name that begins with `libtock`. Crates that are only intended for internal use (e.g. `syscall_tests`, which tests code internal to `libtock_platform`) do not have the `libtock_` prefix. The directory names of `libtock_` crates do not contain the `libtock_` prefix. ## Core abstractions: `libtock_platform` In order to unit test `libtock-rs` code, we need a way to run `libtock-rs` on our development machines (and in CI). Ironically, that means most crates in `libtock-rs` are platform independent. `libtock_platform` provides the tools that allow code to run in both a unit test environment and in real Tock apps. It consists primarily of the `Syscalls` trait and supporting machinery. ## Syscall implementations: `libtock_runtime` and `libtock_unittest` In order to run `libtock-rs` code, you need a `libtock_platform::Syscalls` implementation. Multiple implementations exist that work in different environments: * `libtock_runtime` provides a syscall interface that uses a real Tock kernel. This is the crate to use in Tock process binaries. * `libtock_unittest` provides a fake kernel for use in unit tests. In addition, `libtock_runtime` provides the linker script and Rust runtime needed to start a Tock process binary. `libtock_unittest` relies on `std` to provide a runtime. ## Panic handler crates Each Rust binary must have exactly one panic handler (note that `std` provides a panic handler for binaries that depend on it). The following crates provide a `#[panic_handler]` implementation for Tock process binaries: * `libtock_panic_debug` provides useful diagnostics in the event of a panic, at the expense of code size. This is the panic handler used by `libtock`. ## Driver crates Driver crates provide interfaces to specific Tock APIs in the `/apis` directory. ================================================ FILE: doc/PlatformDesignStory.md ================================================ `libtock_platform` Design Story =============================== `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 { 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>(data: usize) { libtock_runtime::subscribe(DRIVER, WRITE_DONE, write_complete::, data); } extern "C" fn write_complete>( 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::(0); libtock_console::set_write_buffer(&GREETING); libtock_console::start_write(GREETING.len()); loop { libtock_runtime::yieldk(); } } struct App; impl libtock_platform::FreeCallback 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>( syscalls: S, data: usize) { syscalls.subscribe(1, 1, write_complete::, data); } extern "C" fn write_complete>( bytes: usize, _: usize, _: usize, data: usize) { Callback::call(WriteCompleted { bytes, data }); } pub fn set_write_buffer(syscalls: S, buffer: &'static [u8]) { syscalls.const_allow(1, 1, buffer); } pub fn start_write(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>, output: core::cell::Cell>, write_buffer: core::cell::Cell>, write_callback: core::cell::Cell>, write_data: core::cell::Cell, } 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 { 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 { response: core::cell::Cell>, syscalls: Syscalls, } impl SyncAdapter { pub const fn new(syscalls: Syscalls) -> SyncAdapter { SyncAdapter { response: core::cell::Cell::new(None), syscalls } } } impl SyncAdapter { pub fn wait(&self) -> AsyncResponse { loop { match self.response.take() { Some(response) => return response, None => self.syscalls.yieldk(), } } } } impl MethodCallback for SyncAdapter { 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> = const {std::cell::Cell::new(None)}) GLOBAL.with(|g| g.set(Some($name))); struct $link; impl libtock_platform::FreeCallback for $link where $comp: libtock_platform::MethodCallback { 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 = 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 libtock_platform::FreeCallback for $link where $comp: libtock_platform::MethodCallback { 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 } impl App { pub const fn new() -> App { App { done: core::cell::Cell::new(false) } } } impl MethodCallback 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. ================================================ FILE: doc/Startup.md ================================================ Startup ======= This document describes the `libtock_runtime` startup process, up until the process binary's `main` starts executing. ## Step 1: `start` assembly The first code to start executing is in a symbol called `start`, which is written in handwritten assembly. These implementations are specific to each architecture, and live in `runtime/asm`. This assembly does the following: 1. Checks the initial program counter value against the correct `start` address. This verifies the process was deployed at the direct address in non-volatile storage. This is necessary because `libtock-rs` apps are statically-linked, and an incorrect location would cause undefined behavior. If this check fails, an error may be reported (if the `low_level_debug` capsule is present) and the process terminates. 1. Moves the process break to make room for the stack, `.data`, and `.bss`. The process break is the top of the process-accessible RAM. The process break is initially moved to be shortly after the end of the `.bss` section (depending on alignment constraints). 1. Initialize the stack. The initial stack pointer value is provided by the linker script, which calculates it using a symbol called `STACK_MEMORY` in the `.stack_buffer` section. 1. Copies `.data` from non-volatile storage into RAM. The .data section contains read-write global variables (e.g. `static mut` values) that have nonzero initial values. 1. Zeroes out `.bss`. `.bss` contains read-write global variables that have zero initial values. 1. Calls `rust_start`. ## Step 2: `rust_start` `rust_start` is the first Rust code to execute in a process. It is defined in the `libtock_runtime::startup` module. It runs some higher-level initialization, such as giving debug information (stack and heap addresses) to the kernel. `rust_start` then calls `libtock_unsafe_main`. ## Step 3: `libtock_unsafe_main` `libtock_unsafe_main` is a shim used to direct execution from `libtock_runtime` to the process binary's `main` function. It is generated by the `libtock_runtime::set_main!` macro. `libtock_unsafe_main` just calls the user-provided `main` function, then calls `Termination::complete()` on the value returned from `main`. ## Step 4: `main` At this point, the user's `main` starts executing, and `libtock_runtime` is no longer in control. Main is required to return either `!`, or a type which implements the `Termination` trait, which includes a `complete()` method that never returns. Implementations of this trait are provided for `()` and `Result<(), ErrorCode>` which call exit-terminate. If a process wishes to loop forever to service callbacks, it should call the yield-wait system call in a loop. Process binaries are free to call the `exit` system call themselves if needed. ## Appendix: Why `#![no_main]`? Writing a `#![no_std]` `bin` crate currently requires using either `#![no_main]` or the `start` unstable feature. Because we want to move `libtock-rs` to stable Rust eventually (hopefully soon after `asm` is stabilized), `libtock-rs` expects process binaries to be `#![no_main]`. ================================================ FILE: doc/Style.md ================================================ Coding Style ============ ## List Ordering Source code tends to contain many lists whose order is unimportant, such as dependency lists in `Cargo.toml` and `mod` declarations in `.rs` files. When there isn't a better reason to prefer a particular order, these lists should be in alphabetical order. Benefits: 1. When lists become long, this makes it easier to search for a particular entry. 2. Always adding new entries at the bottom of the list results in merge conflicts whenever two PRs add entries to the same list. Putting them in alphabetical order decreases the probability of merge conflicts. ## Naming Conventions Many things in `libtock-rs` live outside Rust's namespacing system, and can therefore collide with other libraries. Examples include: 1. `cargo` package names 2. Environment variables 3. Linker script file names For example, if `libtock-rs` were to contain a `cargo` package called `adc`, that package would likely collide with another package if we were to try to upload it to crates.io. To prevent these collisions, things in `libtock-rs` that can experience external name collisions should have a `libtock` prefix (with suitable capitalization). For example: 1. The runtime crate is called `libtock_runtime` 2. The build platform environment variable is `LIBTOCK_PLATFORM` 3. The linker script is called `libtock_layout.ld` However, this prefix should be omitted when it is unnecessary, to avoid naming everything in the repository `libtock_*`. For example: 1. `libtock_` is omitted from directory names. `libtock_runtime` lives in the `/runtime/` directory. 2. Cargo packages that are not used externally, such as the `print_sizes` tool (which is only used for developing `libtock-rs` itself), do not have the `libtock_` prefix. ================================================ FILE: doc/Testing.md ================================================ Testing ======= This document gives an introduction to the tests in this repository, and is intended to be useful to `libtock-rs` contributors. ## Unit Tests Cargo packages in this repository that are platform-independent (i.e. that do not depend on `libtock_runtime`) have unit tests that use Rust's builtin test harness. These unit tests run on a fully-featured host OS (Linux, Mac OS, Windows, etc...) and therefore do not depend on a real Tock kernel. Instead, these unit tests use the fake kernel provided by `libtock_unittest`. As a result, most `libtock-rs` crates have `libtock_unittest` in their `[dev-dependencies]`. `libtock_platform` is a bit of an exception to this rule. If all of `libtock_platform`'s unit tests were in `libtock_platform`, the following would occur: 1. `cargo` builds `libtock_platform` with `cfg(test)` enabled to make the test binary. 2. `cargo` builds `libtock_unittest` because it is a `[dev-dependency]` of `libtock_platform` 3. `cargo` builds `libtock_platform` without `cfg(test)` because it is a dependency of `libtock_unittest` Both copies of `libtock_platform` end up in the final binary. These contain incompatible instances of the `Syscalls` trait, resulting in hard-to-understand errors. To solve this, some of `libtock_platform`'s unit tests (namely, those that require `libtock_unittest`) were moved to a `platform_test` crate. ## Integration Tests `libtock-rs`'s integration tests are Tock process binaries that can run on an emulated or real Tock system. They live in `libtock`'s `tests/` directory. TODO: Figure out a test runner strategy for automatically running all the integration tests, and document it here. ================================================ FILE: doc/UnitTestOwnership.md ================================================ Unit Test Ownership Design ========================== **TODO: This document describes a not-yet-implemented design for `libtock_unittest`. This TODO will be removed when `libtock_unittest` is refactored to use the new design.** This document examines the component-level call graph for unit tests, including the code under test, fake kernel, and fake syscall drivers. The call graph contains both shared references and circular references, so representing it via Rust's ownership system is nontrivial. This document derives an ownership strategy for the fake kernel and fake syscall drivers. ## The Call Graph For our current purposes, we are looking at the interactions between the following components: 1. **Unit test:** The `#[test]` function, which sets up the test environment and drives the test execution. 2. **Code under test:** The code which we are testing, which should not depend on `libtock_unittest` (as this code should run on Tock as well as the host system). 3. **`fake::Kernel`:** A single instance of `fake::Kernel` that handles system calls. 4. **`fake::SyscallDriver`:** 0 or more `fake::SyscallDriver` instances that implement system call interfaces. The function calls we anticipate are as follows: 1. **Unit test -> Code under test:** The test case will instantiate components to test and call into them to execute the test. 2. **Unit test -> `fake::Kernel`:** The test case will create a fake kernel and call into it to configure it, including registering `fake::SyscallDriver`s with it. 3. **Unit test -> `fake::SyscallDriver`:** The test case may talk to fake syscall drivers to configure them or inspect their state (e.g. to verify whether the code under test performed particular actions). 4. **Code under test -> `fake::Kernel`:** The code under test will execute syscalls, which call into the fake kernel. 5. **`fake::Kernel` -> Code under test:** When the code under test invokes the Yield system call, the fake kernel may call back into the code under test to execute an upcall. 6. **`fake::Kernel` -> `fake::SyscallDriver`:** The fake kernel will call into fake syscall drivers as part of registering them, and to invoke Command system calls (and perhaps other system calls as well). 7. **`fake::SyscallDriver` -> `fake::Kernel`:** The fake syscall drivers will call into the fake kernel to queue upcalls and access Allow buffers. The **`fake::Kernel` -> Code under test** calls always occur via function pointer, with the function pointer passed via the Subscribe system call, so we do not need to represent them directly in Rust's ownership system. Therefore I will represent them via a dotted line. I'll use the term "share access" to refer to fake syscall drivers queueing upcalls and accessing Allow buffers, as `libtock_platform` uses that terminology for that same functionality. This gives the following call graph: ``` +----------------------------------------+ | Unit test case | +----------------------------------------+ | | | V V V +-------+ RawSyscalls +--------+ Command +=========+ | Code |-------------->| Fake |---------->| Fake | | under | | kernel | | syscall | | test |< - - - - - - -| |<----------| drivers | +-------+ Upcall +--------+ Share +=========+ invocation access ``` There are a few things to note here: 1. The `fake::Kernel` is shared between the unit test case and the code under test. 2. The `fake::SyscallDriver` instances are shared between the unit test case and the `fake::Kernel`. 3. There is a circular dependency between the `fake::Kernel` and the `fake::SyscallDriver`s. Note that we can ignore the shared access to the code under test, because that is handled by `libtock_platform`'s syscall API design. ## Handling the `fake::Kernel` <-> `fake::SyscallDriver` circular dependency We cannot do both of the following: 1. Store `&dyn fake::SyscallDriver` references in the `fake::Kernel`. 2. Store `&fake::Kernel` references in the `fake::SyscallDriver`s. because those references types require lifetime parameters, and drop check will not pass (both `fake::Kernel` and the `fake::SyscallDriver`s use dynamic memory allocation). Therefore, we need to give up on one of the above. Fortunately, number 2 (storing references to the kernel inside the fake syscall drivers) isn't quite what we want anyway. Fake syscall drivers should have access to share data associated with their driver number, but not other drivers. So it makes more sense to give them a handle type, which we can call `DriverShareRef`, which only gives them access to their own shares. To avoid the drop check issues, `DriverShareRef` cannot have a lifetime parameter, so the share data itself needs to be pulled out into a separate object: ``` +----------------------------------------+ | Unit test case | +----------------------------------------+ | | | V V V +-------+ RawSyscalls +--------+ Command +=========+ | Code |-------------->| Fake |---------->| Fake | | under | | kernel | | syscall | | test |< - - - - - - -| | | drivers | +-------+ Upcall +--------+ +=========+ invocation | | | Rc<> | DriverShareRef V V +---------------+ | ShareData | +---------------+ ``` `ShareData` would contain data common to all `fake::SyscallDriver`s (such as the upcall queue). `DriverShareRef`'s API will allow the `fake::SyscallDriver`s to read Allow buffers shared with them as well as queue upcalls. As such, it will need to contain the following data: ```rust struct DriverShareRef { driver_num: u32, share_data: Rc, } ``` To give the `DriverShareRef` to the `fake::SyscallDriver`s, we need to add a registration function to `fake::SyscallDriver`: ```rust trait SyscallDriver { /* ... */ fn register(&self, share_ref: DriverShareRef); } ``` ## Remaining details With the above design for `fake::Kernel` and `fake::SyscallDriver`, the unit test case can directly own both the `fake::Kernel` and the `fake::SyscallDriver`s. The `fake::Kernel` can hold `&dyn fake::SyscallDriver`s. If we implement `RawSyscalls` on `&fake::Kernel`, then we can avoid using thread-local storage. This will require the `fake::Kernel` and `fake::SyscallDriver`s to use interior mutability. ================================================ FILE: examples/adc.rs ================================================ //! A simple libtock-rs example. Checks for adc driver //! and samples the sensor every 2 seconds. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::adc::Adc; use libtock::alarm::{Alarm, Milliseconds}; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x200} fn main() { if Adc::exists().is_err() { writeln!(Console::writer(), "adc driver unavailable").unwrap(); return; } loop { match Adc::read_single_sample_sync() { Ok(adc_val) => writeln!(Console::writer(), "Sample: {adc_val}\n").unwrap(), Err(_) => writeln!(Console::writer(), "error while reading sample",).unwrap(), } Alarm::sleep_for(Milliseconds(2000)).unwrap(); } } ================================================ FILE: examples/ambient_light.rs ================================================ //! A simple libtock-rs example. Checks for ambient light driver //! and samples the sensor every 2 seconds. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::alarm::{Alarm, Milliseconds}; use libtock::ambient_light::AmbientLight; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x200} fn main() { if AmbientLight::exists().is_err() { writeln!(Console::writer(), "ambient light driver unavailable").unwrap(); return; } loop { match AmbientLight::read_intensity_sync() { Ok(intensity_val) => { writeln!(Console::writer(), "Light intensity: {intensity_val} lux\n").unwrap() } Err(_) => writeln!(Console::writer(), "error while reading light intensity",).unwrap(), } Alarm::sleep_for(Milliseconds(2000)).unwrap(); } } ================================================ FILE: examples/blink.rs ================================================ //! The blink app. #![no_main] #![no_std] use libtock::alarm::{Alarm, Milliseconds}; use libtock::leds::Leds; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x200} fn main() { let mut count = 0; if let Ok(leds_count) = Leds::count() { loop { for led_index in 0..leds_count { if count & (1 << led_index) > 0 { let _ = Leds::on(led_index); } else { let _ = Leds::off(led_index); } } Alarm::sleep_for(Milliseconds(250)).unwrap(); count += 1; } } } ================================================ FILE: examples/buttons.rs ================================================ //! An extremely simple libtock-rs example. Register button events. #![no_main] #![no_std] use core::fmt::Write; use libtock::buttons::{ButtonListener, Buttons}; use libtock::console::Console; use libtock::leds::Leds; use libtock::runtime::{set_main, stack_size}; use libtock_platform::{share, Syscalls}; use libtock_runtime::TockSyscalls; set_main! {main} stack_size! {0x1000} fn main() { let listener = ButtonListener(|button, state| { let _ = Leds::toggle(button); writeln!(Console::writer(), "button {button:?}: {state:?}").unwrap(); }); if let Ok(buttons_count) = Buttons::count() { writeln!(Console::writer(), "button count: {buttons_count}").unwrap(); share::scope(|subscribe| { // Subscribe to the button callback. Buttons::register_listener(&listener, subscribe).unwrap(); // Enable interrupts for each button press. for i in 0..buttons_count { Buttons::enable_interrupts(i).unwrap(); } // Wait for buttons to be pressed. loop { TockSyscalls::yield_wait(); } }); } } ================================================ FILE: examples/console.rs ================================================ //! An extremely simple libtock-rs example. Just prints out a message //! using the Console capsule, then terminates. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x100} fn main() { writeln!(Console::writer(), "Hello world!").unwrap(); } ================================================ FILE: examples/gpio.rs ================================================ //! A simple GPIO example for getting GPIO interrupts. //! //! This will configure GPIO 0 to be a rising-edge triggered interrupt and print //! a message when the interrupt is triggered. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::gpio; use libtock::gpio::Gpio; use libtock::runtime::{set_main, stack_size}; use libtock_platform::{share, Syscalls}; use libtock_runtime::TockSyscalls; set_main! {main} stack_size! {0x1000} fn main() { let listener = gpio::GpioInterruptListener(|gpio_index, state| { writeln!(Console::writer(), "GPIO[{gpio_index}]: {state:?}").unwrap(); }); if !Gpio::count().is_ok_and(|c| c > 0) { writeln!(Console::writer(), "No GPIO pins on this board.").unwrap(); return; } // Configure pin 0 as an input and enable rising interrupts let pin = Gpio::get_pin(0).unwrap(); let input_pin = pin.make_input::().unwrap(); let _ = input_pin.enable_interrupts(gpio::PinInterruptEdge::Rising); // Wait for callbacks. share::scope(|subscribe| { Gpio::register_listener(&listener, subscribe).unwrap(); loop { TockSyscalls::yield_wait(); } }); } ================================================ FILE: examples/i2c_master_write_read.rs ================================================ //! This sample demonstrates setting up the i2c ip (assuming board has support) //! for master mode. In the event loop, we write some bytes to the target, then //! attempt to read some bytes from the target. //! //! This sample is tested with `i2c_slave_send_recv.rs` sample running on the //! slave device. That sample uses the synchronous slave api, so the order of operations //! is important to ensure we don't cause the slave to stretch clocks if it hasn't setup //! send buffers in time. #![no_main] #![no_std] use core::fmt::Write; use libtock::alarm::{Alarm, Milliseconds}; use libtock::console::Console; use libtock::i2c_master_slave::I2CMasterSlave; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x400} pub const SLAVE_DEVICE_ADDR: u16 = 0x69; fn main() { let addr = SLAVE_DEVICE_ADDR; // 7-bit addressing assert!(addr <= 0x7f); let mut tx_buf: [u8; 4] = [0; 4]; // Write 4 bytes to the slave let tx_len = 4; let mut rx_buf: [u8; 2] = [0; 2]; // Attempt to read 2 bytes from the slave let rx_len = 2; writeln!(Console::writer(), "i2c-master: write-read sample\r").unwrap(); writeln!(Console::writer(), "i2c-master: slave address 0x{addr:x}!\r").unwrap(); let mut i: u32 = 0; loop { writeln!( Console::writer(), "i2c-master: write-read operation {i:?}\r" ) .unwrap(); // Change up the data in tx-buffer tx_buf[0] = tx_buf[0].wrapping_add(2); tx_buf[1] = tx_buf[1].wrapping_add(4); tx_buf[2] = tx_buf[2].wrapping_add(6); tx_buf[3] = tx_buf[3].wrapping_add(8); if let Err(why) = I2CMasterSlave::i2c_master_slave_write_sync(addr, &tx_buf, tx_len) { writeln!( Console::writer(), "i2c-master: write operation failed {why:?}" ) .unwrap(); } else { // This sample target the i2c_slave_send_recv.rs sample, which is synchronous. // so allow some time for it to setup 'send' buffer. Alarm::sleep_for(Milliseconds(200)).unwrap(); let r = I2CMasterSlave::i2c_master_slave_read_sync(addr, &mut rx_buf, rx_len); match r.1 { Ok(()) => { writeln!( Console::writer(), "{:} bytes read from slave | data received (0h): {:x?}\r\n", r.0, rx_buf ) .unwrap(); } Err(why) => { writeln!( Console::writer(), "i2c-master: read operation failed {why:?}" ) .unwrap(); } } i += 1; } Alarm::sleep_for(Milliseconds(1000)).unwrap(); } } ================================================ FILE: examples/i2c_slave_send_recv.rs ================================================ //! This sample demonstrates setting up the i2c ip (assuming board has support) //! for target mode. In the event loop, we first expect the master to write some data //! then we setup a response packet. //! //! NOTE: The device (based on hwip) may stretch clocks by holding the SCL line low if the master attempts to //! read data before we have setup the read data buffers. //! //! This sample is tested with `i2c_master_write_read.rs` sample running on the //! master device. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::i2c_master_slave::I2CMasterSlave; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x400} pub const SLAVE_DEVICE_ADDR: u8 = 0x69; fn main() { let mut rx_buf: [u8; 8] = [0; 8]; let mut tx_buf: [u8; 8] = [0; 8]; let addr: u8 = SLAVE_DEVICE_ADDR; // 7-bit addressing assert!(addr <= 0x7f); writeln!(Console::writer(), "i2c-slave: setting up\r").unwrap(); writeln!(Console::writer(), "i2c-slave: address 0x{addr:x}!\r").unwrap(); I2CMasterSlave::i2c_master_slave_set_slave_address(addr).expect("i2c-target: Failed to listen"); let mut i: u32 = 0; loop { writeln!(Console::writer(), "i2c-slave: operation {i:?}\r").unwrap(); // Expect a write, if the master reads here, the IP may stretch clocks! let r = I2CMasterSlave::i2c_master_slave_write_recv_sync(&mut rx_buf); if let Err(why) = r.1 { writeln!( Console::writer(), "i2c-slave: error to receiving data {why:?}\r" ) .unwrap(); } else { writeln!( Console::writer(), "{:} bytes received from master | buf: {:x?}\r", r.0, rx_buf ) .unwrap(); // Note: The master should allow a little delay when communicating with this slave // as we are doing everything synchronously. // Expect a 2 byte read by master and let's keep changing the values tx_buf[0] = tx_buf[0].wrapping_add(1); tx_buf[1] = tx_buf[1].wrapping_add(5); let r = I2CMasterSlave::i2c_master_slave_read_send_sync(&tx_buf, tx_buf.len()); match r.1 { Ok(()) => { writeln!( Console::writer(), "{:} bytes read by master | data sent: {:x?}\r", r.0, tx_buf ) .unwrap(); i += 1; } Err(why) => { writeln!( Console::writer(), "i2c-slave: error setting up read_send {why:?}\r" ) .unwrap(); } } } } } ================================================ FILE: examples/ieee802154_raw.rs ================================================ //! An example showing use of IEEE 802.15.4 networking. //! //! The kernel contains a standard and phy 15.4 driver. This example //! expects the kernel to be configured with the phy 15.4 driver to //! allow direct access to the radio and the ability to send "raw" //! frames. An example board file using this driver is provided at //! `boards/tutorials/nrf52840dk-thread-tutorial`. //! //! "No Support" Errors for setting the channel/tx power are a telltale //! sign that the kernel is not configured with the phy 15.4 driver. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::ieee802154::{Ieee802154, RxOperator as _, RxRingBuffer, RxSingleBufferOperator}; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x600} fn main() { // Configure the radio let pan: u16 = 0xcafe; let addr_short: u16 = 0xdead; let addr_long: u64 = 0xdead_dad; let tx_power: i8 = -3; let channel: u8 = 11; writeln!(Console::writer(), "Configuring IEEE 802.15.4 radio...\n").unwrap(); Ieee802154::set_pan(pan); writeln!(Console::writer(), "Set PAN to {:#06x}\n", pan).unwrap(); Ieee802154::set_address_short(addr_short); writeln!( Console::writer(), "Set short address to {:#06x}\n", addr_short ) .unwrap(); Ieee802154::set_address_long(addr_long); writeln!( Console::writer(), "Set long address to {:#018x}\n", addr_long ) .unwrap(); Ieee802154::set_tx_power(tx_power).unwrap(); writeln!(Console::writer(), "Set TX power to {}\n", tx_power).unwrap(); Ieee802154::set_channel(channel).unwrap(); writeln!(Console::writer(), "Set channel to {}\n", channel).unwrap(); // Don't forget to commit the config! Ieee802154::commit_config(); writeln!(Console::writer(), "Committed radio configuration!\n").unwrap(); // Turn the radio on Ieee802154::radio_on().unwrap(); assert!(Ieee802154::is_on()); // Transmit a frame Ieee802154::transmit_frame_raw(b"foobar").unwrap(); Console::write(b"Transmitted frame!\n").unwrap(); // Showcase receiving to a single buffer - there is a risk of losing some frames. // See [RxSingleBufferOperator] docs for more details. rx_single_buffer(); } fn rx_single_buffer() { let mut buf = RxRingBuffer::<2>::new(); let mut operator = RxSingleBufferOperator::new(&mut buf); let frame1 = operator.receive_frame().unwrap(); // Access frame1 data here: let _body_len = frame1.payload_len; let _first_body_byte = frame1.body[0]; let _frame2 = operator.receive_frame().unwrap(); // Access frame2 data here } ================================================ FILE: examples/ieee802154_rx_raw.rs ================================================ //! An example showing use of IEEE 802.15.4 networking. //! It infinitely received a frame and prints its content to Console. //! //! The kernel contains a standard and phy 15.4 driver. This example //! expects the kernel to be configured with the phy 15.4 driver to //! allow direct access to the radio and the ability to send "raw" //! frames. An example board file using this driver is provided at //! `boards/tutorials/nrf52840dk-thread-tutorial`. //! //! "No Support" Errors for setting the channel/tx power are a telltale //! sign that the kernel is not configured with the phy 15.4 driver. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::ieee802154::{Ieee802154, RxOperator as _, RxRingBuffer, RxSingleBufferOperator}; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x600} fn main() { // Configure the radio let pan: u16 = 0xcafe; let addr_short: u16 = 0xdead; let addr_long: u64 = 0xdead_dad; let tx_power: i8 = 4; let channel: u8 = 11; writeln!(Console::writer(), "Configuring IEEE 802.15.4 radio...\n").unwrap(); Ieee802154::set_pan(pan); writeln!(Console::writer(), "Set PAN to {:#06x}\n", pan).unwrap(); Ieee802154::set_address_short(addr_short); writeln!( Console::writer(), "Set short address to {:#06x}\n", addr_short ) .unwrap(); Ieee802154::set_address_long(addr_long); writeln!( Console::writer(), "Set long address to {:#018x}\n", addr_long ) .unwrap(); Ieee802154::set_tx_power(tx_power).unwrap(); writeln!(Console::writer(), "Set TX power to {}\n", tx_power).unwrap(); Ieee802154::set_channel(channel).unwrap(); writeln!(Console::writer(), "Set channel to {}\n", channel).unwrap(); // Don't forget to commit the config! Ieee802154::commit_config(); writeln!(Console::writer(), "Committed radio configuration!\n").unwrap(); // Turn the radio on Ieee802154::radio_on().unwrap(); assert!(Ieee802154::is_on()); writeln!(Console::writer(), "Radio is on!\n").unwrap(); let mut buf = RxRingBuffer::<2>::new(); let mut operator = RxSingleBufferOperator::new(&mut buf); loop { let frame = operator.receive_frame().unwrap(); let body_len = frame.payload_len; // Parse the counter. let text = &frame.body[..body_len as usize - core::mem::size_of::()]; let counter_bytes = &frame.body[body_len as usize - core::mem::size_of::()..body_len as usize]; let counter = usize::from_be_bytes(counter_bytes.try_into().unwrap()); writeln!( Console::writer(), "Received frame with body of len {}: \"{} {}\"\n", body_len, core::str::from_utf8(text).unwrap_or("-- error decoding utf8 string --"), counter, ) .unwrap(); } } ================================================ FILE: examples/ieee802154_rx_tx_raw.rs ================================================ //! An example showing use of IEEE 802.15.4 networking. //! It infinitely sends a frame with a constantly incremented counter, //! and after each send receives a frame and prints it to Console. //! //! The kernel contains a standard and phy 15.4 driver. This example //! expects the kernel to be configured with the phy 15.4 driver to //! allow direct access to the radio and the ability to send "raw" //! frames. An example board file using this driver is provided at //! //! "No Support" Errors for setting the channel/tx power are a telltale //! sign that the kernel is not configured with the phy 15.4 driver. //! `boards/tutorials/nrf52840dk-thread-tutorial`. #![no_main] #![no_std] use core::fmt::Write; use libtock::alarm::{Alarm, Milliseconds}; use libtock::console::Console; use libtock::ieee802154::{Ieee802154, RxOperator as _, RxRingBuffer, RxSingleBufferOperator}; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x600} fn main() { // Configure the radio let pan: u16 = 0xcafe; let addr_short: u16 = 0xdead; let addr_long: u64 = 0xdead_dad; let tx_power: i8 = 4; let channel: u8 = 11; writeln!(Console::writer(), "Configuring IEEE 802.15.4 radio...\n").unwrap(); Ieee802154::set_pan(pan); writeln!(Console::writer(), "Set PAN to {:#06x}\n", pan).unwrap(); Ieee802154::set_address_short(addr_short); writeln!( Console::writer(), "Set short address to {:#06x}\n", addr_short ) .unwrap(); Ieee802154::set_address_long(addr_long); writeln!( Console::writer(), "Set long address to {:#018x}\n", addr_long ) .unwrap(); Ieee802154::set_tx_power(tx_power).unwrap(); writeln!(Console::writer(), "Set TX power to {}\n", tx_power).unwrap(); Ieee802154::set_channel(channel).unwrap(); writeln!(Console::writer(), "Set channel to {}\n", channel).unwrap(); // Don't forget to commit the config! Ieee802154::commit_config(); writeln!(Console::writer(), "Committed radio configuration!\n").unwrap(); // Turn the radio on Ieee802154::radio_on().unwrap(); assert!(Ieee802154::is_on()); writeln!(Console::writer(), "Radio is on!\n").unwrap(); let mut buf = RxRingBuffer::<2>::new(); let mut operator = RxSingleBufferOperator::new(&mut buf); let mut counter = 0_usize; let mut buf = [ b'f', b'r', b'a', b'm', b'e', b' ', b'n', b'.', b'o', b'.', b' ', b'\0', b'\0', b'\0', b'\0', ]; fn set_buf_cnt(buf: &mut [u8], counter: &mut usize) { let buf_len = buf.len(); let buf_cnt = &mut buf[buf_len - core::mem::size_of_val(&counter)..]; buf_cnt.copy_from_slice(&counter.to_be_bytes()); } loop { Alarm::sleep_for(Milliseconds(1000)).unwrap(); set_buf_cnt(&mut buf, &mut counter); // Transmit a frame Ieee802154::transmit_frame_raw(&buf).unwrap(); writeln!(Console::writer(), "Transmitted frame {counter}!\n").unwrap(); let frame = operator.receive_frame().unwrap(); let body_len = frame.payload_len; // Parse the counter. let text = &frame.body[..body_len as usize - core::mem::size_of::()]; let counter_bytes = &frame.body[body_len as usize - core::mem::size_of::()..body_len as usize]; let received_counter = usize::from_be_bytes(counter_bytes.try_into().unwrap()); writeln!( Console::writer(), "Received frame with body of len {}: \"{} {}\"\n", body_len, core::str::from_utf8(text).unwrap_or("-- error decoding utf8 string --"), received_counter, ) .unwrap(); counter += 1; } } ================================================ FILE: examples/ieee802154_tx_raw.rs ================================================ //! An example showing use of IEEE 802.15.4 networking. //! It infinitely sends a frame with a constantly incremented counter. //! //! The kernel contains a standard and phy 15.4 driver. This example //! expects the kernel to be configured with the phy 15.4 driver to //! allow direct access to the radio and the ability to send "raw" //! frames. An example board file using this driver is provided at //! `boards/tutorials/nrf52840dk-thread-tutorial`. //! //! "No Support" Errors for setting the channel/tx power are a telltale //! sign that the kernel is not configured with the phy 15.4 driver. #![no_main] #![no_std] use core::fmt::Write; use libtock::alarm::{Alarm, Milliseconds}; use libtock::console::Console; use libtock::ieee802154::Ieee802154; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x600} fn main() { // Configure the radio let pan: u16 = 0xcafe; let addr_short: u16 = 0xdead; let addr_long: u64 = 0xdeaddad; let tx_power: i8 = 4; let channel: u8 = 11; writeln!(Console::writer(), "Configuring IEEE 802.15.4 radio...\n").unwrap(); Ieee802154::set_pan(pan); writeln!(Console::writer(), "Set PAN to {:#06x}\n", pan).unwrap(); Ieee802154::set_address_short(addr_short); writeln!( Console::writer(), "Set short address to {:#06x}\n", addr_short ) .unwrap(); Ieee802154::set_address_long(addr_long); writeln!( Console::writer(), "Set long address to {:#018x}\n", addr_long ) .unwrap(); Ieee802154::set_tx_power(tx_power).unwrap(); writeln!(Console::writer(), "Set TX power to {}\n", tx_power).unwrap(); Ieee802154::set_channel(channel).unwrap(); writeln!(Console::writer(), "Set channel to {}\n", channel).unwrap(); // Don't forget to commit the config! Ieee802154::commit_config(); writeln!(Console::writer(), "Committed radio configuration!\n").unwrap(); // Turn the radio on Ieee802154::radio_on().unwrap(); assert!(Ieee802154::is_on()); writeln!(Console::writer(), "Radio is on!\n").unwrap(); let mut counter = 0_usize; let mut buf = [ b'f', b'r', b'a', b'm', b'e', b' ', b'n', b'.', b'o', b'.', b' ', b'\0', b'\0', b'\0', b'\0', ]; fn set_buf_cnt(buf: &mut [u8], counter: &mut usize) { let buf_len = buf.len(); let buf_cnt = &mut buf[buf_len - core::mem::size_of_val(&counter)..]; buf_cnt.copy_from_slice(&counter.to_be_bytes()); } loop { Alarm::sleep_for(Milliseconds(1000)).unwrap(); set_buf_cnt(&mut buf, &mut counter); // Transmit a frame Ieee802154::transmit_frame_raw(&buf).unwrap(); writeln!(Console::writer(), "Transmitted frame {counter}!\n").unwrap(); counter += 1; } } ================================================ FILE: examples/kv.rs ================================================ //! A key-value store example. Gets and sets key-value objects. #![no_main] #![no_std] use core::fmt::Write; use core::str; use libtock::console::Console; use libtock::key_value::KeyValue; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x200} /// Retrieve a key and insert the value into `value`. Then print the value /// contents as a UTF-8 string or as hex values. fn get_and_print(key: &[u8], value: &mut [u8]) { match KeyValue::get(key, value) { Ok(val_length) => { let val_length: usize = val_length as usize; writeln!(Console::writer(), "Got value len: {val_length}").unwrap(); match str::from_utf8(&value[0..val_length]) { Ok(val_str) => writeln!(Console::writer(), "Value: {val_str}").unwrap(), Err(_) => { write!(Console::writer(), "Value: ").unwrap(); for val in &value[0..val_length] { write!(Console::writer(), "{val:02x}").unwrap(); } writeln!(Console::writer()).unwrap(); } } } Err(_) => writeln!(Console::writer(), "error KV::get()",).unwrap(), } } fn main() { // Check if there is key-value support on this board. if KeyValue::exists() { writeln!(Console::writer(), "KV available!").unwrap() } else { writeln!(Console::writer(), "ERR! KV unavailable").unwrap(); } // Do a test query for key: a. let key = "a"; let mut value: [u8; 64] = [0; 64]; get_and_print(key.as_bytes(), &mut value); // Now set, get, delete, then get the key: libtock-rs. let set_key = "libtock-rs"; let set_val = "kv test app"; match KeyValue::set(set_key.as_bytes(), set_val.as_bytes()) { Ok(()) => { writeln!(Console::writer(), "Successfully set the key-value").unwrap(); } Err(_) => writeln!(Console::writer(), "error KV::set()",).unwrap(), } get_and_print(set_key.as_bytes(), &mut value); match KeyValue::delete(set_key.as_bytes()) { Ok(()) => { writeln!(Console::writer(), "Successfully deleted the key-value").unwrap(); } Err(_) => writeln!(Console::writer(), "error KV::delete()",).unwrap(), } get_and_print(set_key.as_bytes(), &mut value); } ================================================ FILE: examples/leds.rs ================================================ //! A simple libtock-rs example. Just blinks all the LEDs. #![no_main] #![no_std] use libtock::alarm::{Alarm, Milliseconds}; use libtock::leds::Leds; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x200} fn main() { if let Ok(leds_count) = Leds::count() { loop { for led_index in 0..leds_count { let _ = Leds::toggle(led_index); } Alarm::sleep_for(Milliseconds(250)).unwrap(); } } } ================================================ FILE: examples/low_level_debug.rs ================================================ //! An extremely simple libtock-rs example. Just prints out a few numbers using //! the LowLevelDebug capsule then terminates. #![no_main] #![no_std] use libtock::low_level_debug::LowLevelDebug; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x100} fn main() { LowLevelDebug::print_1(1); LowLevelDebug::print_2(2, 3); } ================================================ FILE: examples/music.rs ================================================ //! Implementation done by : https://github.com/teodorobert //! A simple libtock-rs example. Plays Ode of Joy using the buzzer. #![no_main] #![no_std] use core::fmt::Write; use core::time::Duration; use libtock::buzzer::{Buzzer, Note}; use libtock::console::Console; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x800} // Adapted from https://github.com/robsoncouto/arduino-songs // Notes in the form of (note_frequency, note_delay in musical terms) const MELODY: [(Note, i32); 62] = [ (Note::E4, 4), (Note::E4, 4), (Note::F4, 4), (Note::G4, 4), (Note::G4, 4), (Note::F4, 4), (Note::E4, 4), (Note::D4, 4), (Note::C4, 4), (Note::C4, 4), (Note::D4, 4), (Note::E4, 4), (Note::E4, -4), (Note::D4, 8), (Note::D4, 2), (Note::E4, 4), (Note::E4, 4), (Note::F4, 4), (Note::G4, 4), (Note::G4, 4), (Note::F4, 4), (Note::E4, 4), (Note::D4, 4), (Note::C4, 4), (Note::C4, 4), (Note::D4, 4), (Note::E4, 4), (Note::D4, -4), (Note::C4, 8), (Note::C4, 2), (Note::D4, 4), (Note::D4, 4), (Note::E4, 4), (Note::C4, 4), (Note::D4, 4), (Note::E4, 8), (Note::F4, 8), (Note::E4, 4), (Note::C4, 4), (Note::D4, 4), (Note::E4, 8), (Note::F4, 8), (Note::E4, 4), (Note::D4, 4), (Note::C4, 4), (Note::D4, 4), (Note::G3, 2), (Note::E4, 4), (Note::E4, 4), (Note::F4, 4), (Note::G4, 4), (Note::G4, 4), (Note::F4, 4), (Note::E4, 4), (Note::D4, 4), (Note::C4, 4), (Note::C4, 4), (Note::D4, 4), (Note::E4, 4), (Note::D4, -4), (Note::C4, 8), (Note::C4, 2), ]; const TEMPO: u32 = 114; const WHOLE_NOTE: u32 = (60000 * 4) / TEMPO; fn main() { if Buzzer::exists().is_err() { writeln!(Console::writer(), "There is no available buzzer").unwrap(); return; } writeln!(Console::writer(), "Ode to Joy").unwrap(); for (frequency, duration) in MELODY.iter() { let mut note_duration: Duration = Duration::from_millis((WHOLE_NOTE / duration.unsigned_abs()) as u64); // let mut note_duration = WHOLE_NOTE / duration.unsigned_abs(); if duration < &0 { note_duration = note_duration * 15 / 10; } let note_duration = note_duration * 9 / 10; Buzzer::tone_sync(*frequency as u32 * 3, note_duration).unwrap(); } } ================================================ FILE: examples/ninedof.rs ================================================ //! Libtock-rs example for the ninedof sensor. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::alarm::{Alarm, Milliseconds}; use libtock::ninedof::NineDof; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x2000} fn main() { if NineDof::exists().is_err() { writeln!(Console::writer(), "NineDof driver unavailable").unwrap(); return; } writeln!(Console::writer(), "NineDof driver available").unwrap(); loop { let accelerometer_data = NineDof::read_accelerometer_sync(); let magnetomer_data = NineDof::read_magnetometer_sync(); let gyroscope_data = NineDof::read_gyroscope_sync(); match accelerometer_data { Ok(data) => { writeln!( Console::writer(), "Accelerometer: x: {}, y: {}, z: {}", data.x, data.y, data.z ) .unwrap(); } Err(_) => writeln!(Console::writer(), "error while reading accelerometer").unwrap(), } match magnetomer_data { Ok(data) => { writeln!( Console::writer(), "Magnetometer: x: {}, y: {}, z: {}", data.x, data.y, data.z ) .unwrap(); } Err(_) => writeln!(Console::writer(), "error while reading magnetometer").unwrap(), } match gyroscope_data { Ok(data) => { writeln!( Console::writer(), "Gyroscope: x: {}, y: {}, z: {}", data.x, data.y, data.z ) .unwrap(); } Err(_) => writeln!(Console::writer(), "error while reading gyroscope").unwrap(), } Alarm::sleep_for(Milliseconds(700)).unwrap(); } } ================================================ FILE: examples/proximity.rs ================================================ //! A simple libtock-rs example. Checks for proximity driver //! and samples the sensor every 2 seconds. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::alarm::{Alarm, Milliseconds}; use libtock::proximity::Proximity; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x200} fn main() { if Proximity::exists().is_err() { writeln!(Console::writer(), "proximity driver unavailable").unwrap(); return; } writeln!(Console::writer(), "proximity driver available").unwrap(); loop { match Proximity::read_sync() { Ok(prox_val) => writeln!(Console::writer(), "Proximity: {prox_val}\n").unwrap(), Err(_) => writeln!(Console::writer(), "error while reading proximity",).unwrap(), } Alarm::sleep_for(Milliseconds(2000)).unwrap(); } } ================================================ FILE: examples/rng.rs ================================================ #![no_std] #![no_main] use core::fmt::Write; use libtock::alarm::{Alarm, Milliseconds}; use libtock::console::Console; use libtock::rng::Rng; use libtock::runtime::{set_main, stack_size}; stack_size! {0x300} set_main! {main} struct Randomness<'a, const N: usize>(&'a [u8; N]); impl<'a, const N: usize> core::fmt::Display for Randomness<'a, N> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let bytes = self.0.iter(); for &byte in bytes { write!(f, "{byte:02x}")?; } Ok(()) } } fn main() { if let Err(e) = Rng::exists() { writeln!(Console::writer(), "RNG DRIVER ERROR: {e:?}").unwrap(); return; } let mut console_writer = Console::writer(); let mut buffer: [u8; 32] = Default::default(); let n: u32 = 32; loop { match Rng::get_bytes_sync(&mut buffer, n) { Ok(()) => { let _ = writeln!(console_writer, "Randomness: {}", Randomness(&buffer)); } Err(e) => { let _ = writeln!(console_writer, "Error while getting bytes {e:?}"); } } let _ = Alarm::sleep_for(Milliseconds(2000)); } } ================================================ FILE: examples/rng_async.rs ================================================ #![no_std] #![no_main] use core::fmt::Write; use libtock::alarm::{Alarm, Milliseconds}; use libtock::rng::RngListener; use libtock::{console::Console, rng::Rng}; use libtock_platform::{share, Syscalls}; use libtock_runtime::{set_main, stack_size, TockSyscalls}; stack_size! {0x300} set_main! {main} fn main() { if let Err(e) = Rng::exists() { writeln!(Console::writer(), "RNG DRIVER ERROR: {e:?}").unwrap(); return; } let mut console_writer = Console::writer(); let rng_listener = RngListener(|_| write!(Console::writer(), "Randomness: ").unwrap()); let mut buffer: [u8; 32] = Default::default(); let n: u32 = 32; loop { share::scope(|allow_rw| { Rng::allow_buffer(&mut buffer, allow_rw).unwrap(); share::scope(|subscribe| { Rng::register_listener(&rng_listener, subscribe).unwrap(); Rng::get_bytes_async(n).unwrap(); TockSyscalls::yield_wait(); }); }); buffer.iter().for_each(|&byte| { let _ = write!(console_writer, "{byte:02x}"); }); let _ = writeln!(console_writer); let _ = Alarm::sleep_for(Milliseconds(2000)); } } ================================================ FILE: examples/screen.rs ================================================ #![no_main] #![no_std] use core::fmt::Write; use libtock::alarm::Alarm; use libtock::alarm::Milliseconds; use libtock::console::Console; use libtock::display::Screen; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {11*1024} fn main() { // Output a message to the console let _ = writeln!(Console::writer(), "available resolutions\n"); // Query the number of supported Screen resolution modes let resolutions = match Screen::get_resolution_modes_count() { Ok(val) => val, Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); 0 } }; // Exit if no resolutions found if resolutions == 0 { assert_eq!(0, 1); } let _ = writeln!(Console::writer(), "{resolutions}\n"); // Iterate over each resolution mode and print the width and height for index in 0..resolutions { let (width, height) = match Screen::get_resolution_width_height(index as usize) { Ok((width, height)) => (width, height), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); (0, 0) } }; let width = (width, height).0; let height = (width, height).1; // Abort if invalid resolution if width == 0 && height == 0 { assert_eq!(0, 1); } let _ = writeln!(Console::writer(), " {width} x {height} \n"); } // Print supported color depths let _ = writeln!(Console::writer(), "available colors depths\n"); let pixel_format = match Screen::get_pixel_format() { Ok(val) => val, Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); 0 } }; if pixel_format == 0 { assert_eq!(0, 1); } // List each supported color format for index in 0..pixel_format { let format = match Screen::pixel_format(index as usize) { Ok(val) => val, Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); 0 } }; let _ = writeln!(Console::writer(), " {format} bpp\n"); } const BUFFER_SIZE: usize = 10 * 1024; // Initialize the Screen screen buffer let mut buffer = [0u8; BUFFER_SIZE]; // Set Screen brightness to 100% match Screen::set_brightness(100) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; // Get current screen resolution let (width, height) = match Screen::get_resolution() { Ok((width, height)) => (width, height), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); (0, 0) } }; // Unwrap width and height let width = (width, height).0; let height = (width, height).1; if width == 0 && height == 0 { assert_eq!(0, 1); }; // Set full-screen write frame and clear screen match Screen::set_write_frame(0, 0, width, height) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; match Screen::fill(&mut buffer, 0x0) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; // Animation loop: cycle through rotations and color block updates let mut invert = false; for i in 0.. { // Every 4 iterations, toggle Screen inversion if i % 4 == 3 { invert = !invert; if invert { let _ = Screen::set_invert_on(); } else { let _ = Screen::set_invert_off(); } } // Set Screen rotation (0 to 3) match Screen::set_rotation(i % 4) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; // Draw a red square at (10, 20) match Screen::set_write_frame(10, 20, 30, 30) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; match Screen::fill(&mut buffer, 0xF800) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; // Draw a black square at (88, 20) match Screen::set_write_frame(88, 20, 30, 30) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; match Screen::fill(&mut buffer, 0x0) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; // Wait 1 second Alarm::sleep_for(Milliseconds(1000)).unwrap(); // Clear the red square match Screen::set_write_frame(10, 20, 30, 30) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; match Screen::fill(&mut buffer, 0x0) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; // Draw a green square at (88, 20) match Screen::set_write_frame(88, 20, 30, 30) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; match Screen::fill(&mut buffer, 0x07F0) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; // Wait 1 second Alarm::sleep_for(Milliseconds(1000)).unwrap(); // Clear screen match Screen::set_write_frame(0, 0, width, height) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; match Screen::fill(&mut buffer, 0x0) { Ok(()) => (), Err(e) => { let _ = writeln!(Console::writer(), "{e:?}\n"); } }; } } ================================================ FILE: examples/sound_pressure.rs ================================================ //! This example shows how to use the sound pressure driver. //! It checks for the sound pressure driver and samples the sensor every second. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::alarm::{Alarm, Milliseconds}; use libtock::runtime::{set_main, stack_size}; use libtock::sound_pressure::SoundPressure; set_main! {main} stack_size! {0x200} fn main() { if SoundPressure::exists().is_err() { writeln!(Console::writer(), "Sound pressure driver not found").unwrap(); return; } writeln!(Console::writer(), "Sound pressure driver found").unwrap(); let enable = SoundPressure::enable(); match enable { Ok(()) => { writeln!(Console::writer(), "Sound pressure driver enabled").unwrap(); loop { match SoundPressure::read_sync() { Ok(sound_pressure_val) => { writeln!(Console::writer(), "Sound Pressure: {sound_pressure_val}\n") .unwrap() } Err(_) => { writeln!(Console::writer(), "error while reading sound pressure",).unwrap() } } Alarm::sleep_for(Milliseconds(1000)).unwrap(); } } Err(_e) => writeln!(Console::writer(), "Sound pressure driver enable failed",).unwrap(), } } ================================================ FILE: examples/spi_controller_write_read.rs ================================================ //! This sample demonstrates setting up the SPI controller (assuming board has support) #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::runtime::{set_main, stack_size}; use libtock::spi_controller::SpiController; set_main! {main} stack_size! {0x400} const OPERATION_LEN: usize = 0x08; fn main() { let tx_buf: [u8; OPERATION_LEN] = [0x12; OPERATION_LEN]; let mut rx_buf: [u8; OPERATION_LEN] = [0; OPERATION_LEN]; writeln!(Console::writer(), "spi-controller: write-read\r").unwrap(); if let Err(why) = SpiController::spi_controller_write_read_sync(&tx_buf, &mut rx_buf, OPERATION_LEN as u32) { writeln!( Console::writer(), "spi-controller: write-read operation failed {why:?}\r" ) .unwrap(); } else { writeln!( Console::writer(), "spi-controller: write-read: wrote {tx_buf:x?}: read {rx_buf:x?}\r" ) .unwrap(); } writeln!(Console::writer(), "spi-controller: write\r").unwrap(); if let Err(why) = SpiController::spi_controller_write_sync(&tx_buf, OPERATION_LEN as u32) { writeln!( Console::writer(), "spi-controller: write operation failed {why:?}\r" ) .unwrap(); } else { writeln!(Console::writer(), "spi-controller: wrote {tx_buf:x?}\r").unwrap(); } writeln!(Console::writer(), "spi-controller: read\r").unwrap(); if let Err(why) = SpiController::spi_controller_read_sync(&mut rx_buf, OPERATION_LEN as u32) { writeln!( Console::writer(), "spi-controller: read operation failed {why:?}\r" ) .unwrap(); } else { writeln!(Console::writer(), "spi-controller: read {rx_buf:x?}\r").unwrap(); } writeln!(Console::writer(), "spi-controller: inplace write-read\r").unwrap(); if let Err(why) = SpiController::spi_controller_inplace_write_read_sync(&mut rx_buf, OPERATION_LEN as u32) { writeln!( Console::writer(), "spi-controller: inplace write-read operation failed {why:?}\r" ) .unwrap(); } else { writeln!( Console::writer(), "spi-controller: inplace write-read: wrote {tx_buf:x?}: read {rx_buf:x?}\r" ) .unwrap(); } } ================================================ FILE: examples/temperature.rs ================================================ //! A simple libtock-rs example. Checks for temperature driver //! and samples the sensor every 2 seconds. #![no_main] #![no_std] use core::fmt::Write; use libtock::console::Console; use libtock::alarm::{Alarm, Milliseconds}; use libtock::runtime::{set_main, stack_size}; use libtock::temperature::Temperature; set_main! {main} stack_size! {0x200} fn main() { match Temperature::exists() { Ok(()) => writeln!(Console::writer(), "temperature driver available").unwrap(), Err(_) => { writeln!(Console::writer(), "temperature driver unavailable").unwrap(); return; } } loop { match Temperature::read_temperature_sync() { Ok(temp_val) => writeln!( Console::writer(), "Temperature: {}{}.{}*C\n", if temp_val > 0 { "" } else { "-" }, i32::abs(temp_val) / 100, i32::abs(temp_val) % 100 ) .unwrap(), Err(_) => writeln!(Console::writer(), "error while reading temperature",).unwrap(), } Alarm::sleep_for(Milliseconds(2000)).unwrap(); } } ================================================ FILE: examples/usb_i2c_mctp.rs ================================================ //! A sample app that implements MCTP messages to be transceived from a host //! machine without exposed SMBus/I2C capabilities to a target endpoint using //! UART and I2C. //! //! The following topology is used: //! //! [HOST MACHINE] <--UART--> [USB_I2C_BRIDGE_DEVICE] <--I2C/SmBus--> [TARGET_ENDPOiNT] //! //! The host machine will issue a message to the USB_I2C_BRIDGE_DEVICE, which runs this app. //! The app then determines the end point target address based on the received packet header, //! and forwards this data to the target endpoint. As we are supporting MCTP, there are no //! I2C reads, only write. Thus, the endpoint must then master the bus and write a response //! back. This message is forwarded to the host again via UART. //! //! The host application must append a small packet header of the following format //! for any messages being send to this device. //! //! host_tx[0] = 0xAA // Preamble //! host_tx[1] = XX // Endpoint target address //! host_tx[2] = YY // MSB of 16bit data length //! host_tx[3] = ZZ // LSB of 16bit data length //! //! For reception, the device (this app) first sends a packet header of the following format //! //! device_tx[0] = 0xBB // Preamble //! device_tx[1] = 0xFF // Unused //! device_tx[2] = YY // MSB of 16bit data length //! device_tx[3] = ZZ // LSB of 16bit data length //! //! Based on the data length, the host can read the next ((YY << 8) | ZZ ) //! as data message. //! //! Required Kernel Configuration: //! //! This application requires that the kernel console buffer sizes are increased //! as well as i2c-master-slave driver buffers. This is because we are doing transfers //! of sizes greater than what the upstream kernel is allowed to do. //! //! The following reference can be used to prepare the kernel for the uart/console buffers. //! The size specified here must be >= RX_BUF_LEN (as specified below). //! //! ``` //! diff --git a/capsules/core/src/console.rs b/capsules/core/src/console.rs //! index a3b3af6cf..4eaf401a9 100644 //! --- a/capsules/core/src/console.rs //! +++ b/capsules/core/src/console.rs //! @@ -54,7 +54,7 @@ pub const DRIVER_NUM: usize = driver::NUM::Console as usize; //! //! /// Default size for the read and write buffers used by the console. //! /// Boards may pass different-size buffers if needed. //! -pub const DEFAULT_BUF_SIZE: usize = 64; //! +pub const DEFAULT_BUF_SIZE: usize = 132; //! //! /// IDs for subscribed upcalls. //! mod upcall { //! ``` //! //! ``` //! diff --git a/capsules/core/src/virtualizers/virtual_uart.rs b/capsules/core/src/virtualizers/virtual_uart.rs //! index 0d39fe024..59e0435a1 100644 //! --- a/capsules/core/src/virtualizers/virtual_uart.rs //! +++ b/capsules/core/src/virtualizers/virtual_uart.rs //! @@ -54,7 +54,7 @@ use kernel::hil::uart; //! use kernel::utilities::cells::{OptionalCell, TakeCell}; //! use kernel::ErrorCode; //! //! -pub const RX_BUF_LEN: usize = 64; //! +pub const RX_BUF_LEN: usize = 132; //! //! pub struct MuxUart<'a> { //! uart: &'a dyn uart::Uart<'a>, //! ``` //! //! For i2c-master-slave buffers, use a buffer size that is >= MAX_DLEN //! (as specified below). //! #![no_main] #![no_std] use libtock::console::Console; use libtock::i2c_master_slave::I2CMasterSlave; use libtock::leds::Leds; use libtock::runtime::{set_main, stack_size}; set_main! {main} stack_size! {0x900} /// The address to which we listen for in slave/target mode. pub const MY_ID: u8 = 0x34; /// Contains packet metadata pub const HEADER_LEN: usize = 4; /// Max data message length pub const MAX_DLEN: usize = 128; /// Total amount of bytes we can receive in a single UART RX pub const RX_BUF_LEN: usize = HEADER_LEN + MAX_DLEN; /// Debug LED config, change these based on the board config pub const PANIC_LED: u32 = 0; /// Triggered when waiting for RX pub const RX_LED: u32 = 1; /// Triggered when TX in progress pub const TX_LED: u32 = 2; /// # Summary /// /// A helper function to append the packet metadata do the outgoing buffer /// pointed to by @buf. /// /// # Parameter /// /// * `buf`: the buffer in which to append the metadata to. /// * `n_bytes`: length of data message body of the next message this is /// stored in the packet header, so that the host can /// determine the data message read length. /// /// # Returns /// /// Ok(n) returns the total size of bytes to send from this buffer (buf[0..n]). /// /// # Panics /// /// If the buffer capacity cannot fit the packet metadata /// fn prepare_tx_header(buf: &mut [u8], n_bytes: usize) -> Result { let total_bytes_to_send = HEADER_LEN; if total_bytes_to_send > buf.len() { return Err(()); } // Setup Header buf[0] = 0xBB; buf[1] = 0xFF; // Upper 8-bits buf[2..=3].copy_from_slice(&u16::to_be_bytes(n_bytes as u16)); Ok(total_bytes_to_send) } fn main() { let led_count = Leds::count().unwrap_or(0); // Using the led number, set it on iff it's available let led_on = |led_num| { if led_num < led_count { Leds::on(led_num).unwrap() } }; // Using the led number, set it off iff it's available let led_off = |led_num| { if led_num < led_count { Leds::off(led_num).unwrap() } }; // RX Buffer layout // [0] = Preamble // [1] = TargetID // [2] = Length Upper Byte // [3] = Length Lower Byte let mut rx_buf: [u8; RX_BUF_LEN] = [0x00; RX_BUF_LEN]; let mut msg_len: u16; let mut target_id: u16; loop { led_on(RX_LED); let (_, err) = Console::read(&mut rx_buf); led_off(RX_LED); if err.is_err() { led_on(PANIC_LED); panic!("Failed to read from host {:?}", err); } // If we don't get a matching preamble, then the rest of the data is unreliable. assert_eq!(rx_buf[0], 0xAA); // Target in 7-bit address range? assert!(rx_buf[1] <= 0x7F); target_id = rx_buf[1] as u16; // Data length should be non-zero, otherwise why are we here? just to suffer? msg_len = u16::from_be_bytes([rx_buf[2], rx_buf[3]]); assert!(msg_len as usize <= MAX_DLEN); assert_ne!(msg_len, 0); if let Err(why) = I2CMasterSlave::i2c_master_slave_write_sync( target_id, &mut rx_buf[HEADER_LEN..HEADER_LEN + msg_len as usize], msg_len, ) { led_on(PANIC_LED); panic!("i2c-master: write operation failed {:?}", why); } I2CMasterSlave::i2c_master_slave_set_slave_address(MY_ID) .expect("i2c-target: Failed to set slave address"); // Expect a write, if the master reads here, the IP may stretch clocks! let r = I2CMasterSlave::i2c_master_slave_write_recv_sync(&mut rx_buf[HEADER_LEN..]); if let Err(why) = r.1 { led_on(PANIC_LED); panic!("i2c-slave: error to receiving data {:?}\r", why); } let mut header: [u8; HEADER_LEN] = [0; HEADER_LEN]; let mut tx_len = 0; if let Ok(n) = prepare_tx_header(&mut header, r.0) { tx_len = n; } assert_eq!(tx_len, HEADER_LEN); // Write header first, this allows the host to know how many bytes to // expect in the following data message. led_on(TX_LED); if Console::write(&mut header).is_err() { led_on(PANIC_LED); } // Data message body. if Console::write(&mut rx_buf[HEADER_LEN..HEADER_LEN + r.0]).is_err() { led_on(PANIC_LED); } led_off(TX_LED); } } ================================================ FILE: libraries/embedded_graphics_libtock/Cargo.toml ================================================ [package] name = "embedded_graphics_libtock" version = "0.1.0" authors = ["Tock Project Developers "] license = "Apache-2.0 OR MIT" edition = "2018" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true description = "libtock-rs port of embedded graphics" [dependencies] embedded-graphics = "0.8.1" libtock = { path = "../../" } libtock_platform = { path = "../../platform" } ================================================ FILE: libraries/embedded_graphics_libtock/README.md ================================================ Embedded Graphics - Libtock =========================== This crate connects the [Embedded Graphics library](https://crates.io/crates/embedded-graphics) to libtock-rs. Specifically, this implements the [DrawTarget trait](https://docs.rs/embedded-graphics/latest/embedded_graphics/draw_target/trait.DrawTarget.html) using the Tock `screen` systemcall. ================================================ FILE: libraries/embedded_graphics_libtock/src/lib.rs ================================================ //! Interface library for using Embedded Graphics with libtock-rs //! //! This library implements `embedded_graphics::draw_target::DrawTarget` from //! the [Embedded Graphics](https://crates.io/crates/embedded-graphics) graphics //! library using the screen system call driver in Tock. //! //! ## Example Usage //! //! Using Embedded Graphics to draw a circle on the screen might look like:DrawTarget //! //! ```rust //! use embedded_graphics_libtock::tock_screen::TockMonochrome8BitPage128x64Screen; //! //! use embedded_graphics::pixelcolor::BinaryColor; //! use embedded_graphics::prelude::Point; //! use embedded_graphics::primitives::{Circle, PrimitiveStyle}; //! //! let mut screen = TockMonochrome8BitPage128x64Screen::new(); //! //! let x = 50; //! let y = 50; //! let diameter = 40; //! let _ = Circle::new(Point::new(x as i32, y as i32), diameter) //! .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1)) //! .draw(&mut screen); //! } //! let _ = screen.flush(); //! ``` #![no_std] pub mod tock_screen; ================================================ FILE: libraries/embedded_graphics_libtock/src/tock_screen.rs ================================================ //! Implementations of `DrawTarget` using the screen system call. use libtock::display::Screen; use libtock_platform::ErrorCode; /// An implementation of a `DrawTarget` for monochromatic, 128x64 pixel screens /// where the pixels in each byte are vertical on the screen. /// /// This corresponds to the `Mono_8BitPage` pixel format documented /// [here](https://github.com/tock/tock/blob/master/doc/syscalls/90001_screen.md#command-number-25). pub struct TockMonochrome8BitPage128x64Screen { /// The framebuffer for the max supported screen size (128x64). Each pixel /// is a bit. framebuffer: [u8; (128 * 64) / 8], width: u32, height: u32, } impl Default for TockMonochrome8BitPage128x64Screen { fn default() -> Self { Self::new() } } impl TockMonochrome8BitPage128x64Screen { pub fn new() -> Self { let (width, height) = Screen::get_resolution().unwrap_or((0, 0)); // Because this is a specific type of screen with a specific pixel // format, we tell the kernel that is the pixel format we expect. let mono_8_bit_page = 6; let _ = Screen::set_pixel_format(mono_8_bit_page); Self { framebuffer: [0; 1024], width, height, } } pub fn get_width(&self) -> u32 { self.width } pub fn get_height(&self) -> u32 { self.height } /// Updates the screen from the framebuffer. pub fn flush(&self) -> Result<(), ErrorCode> { Screen::set_write_frame(0, 0, self.width, self.height)?; Screen::write(&self.framebuffer)?; Ok(()) } } impl embedded_graphics::draw_target::DrawTarget for TockMonochrome8BitPage128x64Screen { type Color = embedded_graphics::pixelcolor::BinaryColor; type Error = core::convert::Infallible; fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator>, { for embedded_graphics::Pixel(coord, color) in pixels.into_iter() { if coord.x >= 0 && coord.x < self.width as i32 && coord.y >= 0 && coord.y < self.height as i32 { const X_FACTOR: usize = 1; const Y_FACTOR: usize = 8; const X_COLS: usize = 128; let x = coord.x as usize; let y = coord.y as usize; let byte_index = (x / X_FACTOR) + ((y / Y_FACTOR) * X_COLS); let bit_index = y % Y_FACTOR; if color.is_on() { self.framebuffer[byte_index] |= 1 << bit_index; } else { self.framebuffer[byte_index] &= !(1 << bit_index); } } } Ok(()) } } impl embedded_graphics::geometry::OriginDimensions for TockMonochrome8BitPage128x64Screen { fn size(&self) -> embedded_graphics::geometry::Size { embedded_graphics::geometry::Size::new(128, 64) } } ================================================ FILE: nightly/rust-toolchain.toml ================================================ # This is the nightly Rust toolchain used by `make test`. [toolchain] channel = "nightly-2025-09-11" components = ["miri", "rust-src"] ================================================ FILE: panic_handlers/debug_panic/Cargo.toml ================================================ [package] name = "libtock_debug_panic" authors = ["Tock Project Developers "] version = "0.1.0" description = """Debug panic handler for libtock. Prints panic info to console and terminates.""" edition = "2021" license = "Apache-2.0 OR MIT" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true [dependencies] libtock_console = { path = "../../apis/interface/console" } libtock_low_level_debug = { path = "../../apis/kernel/low_level_debug" } libtock_platform = { path = "../../platform" } libtock_runtime = { path = "../../runtime" } ================================================ FILE: panic_handlers/debug_panic/src/lib.rs ================================================ #![no_std] use core::fmt::Write; use libtock_console::Console; use libtock_low_level_debug::{AlertCode, LowLevelDebug}; use libtock_platform::{ErrorCode, Syscalls}; use libtock_runtime::TockSyscalls; /// This handler requires some 0x400 bytes of stack #[panic_handler] fn panic_handler(info: &core::panic::PanicInfo) -> ! { // Signal a panic using the LowLevelDebug capsule (if available). LowLevelDebug::::print_alert_code(AlertCode::Panic); let mut writer = Console::::writer(); // If this printing fails, we can't panic harder, and we can't print it either. let _ = writeln!(writer, "{info}"); // Exit with a non-zero exit code to indicate failure. TockSyscalls::exit_terminate(ErrorCode::Fail as u32); } ================================================ FILE: panic_handlers/small_panic/Cargo.toml ================================================ [package] name = "libtock_small_panic" authors = ["Tock Project Developers "] version = "0.1.0" description = """Small and basic panic handler for libtock. Calls low-level debug on panic and terminates.""" edition = "2021" license = "Apache-2.0 OR MIT" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true [dependencies] libtock_low_level_debug = { path = "../../apis/kernel/low_level_debug" } libtock_platform = { path = "../../platform" } libtock_runtime = { path = "../../runtime" } ================================================ FILE: panic_handlers/small_panic/src/lib.rs ================================================ #![no_std] use libtock_low_level_debug::{AlertCode, LowLevelDebug}; use libtock_platform::{ErrorCode, Syscalls}; use libtock_runtime::TockSyscalls; #[panic_handler] fn panic_handler(_info: &core::panic::PanicInfo) -> ! { // Signal a panic using the LowLevelDebug capsule (if available). LowLevelDebug::::print_alert_code(AlertCode::Panic); // Exit with a non-zero exit code to indicate failure. // TODO(kupiakos@google.com): Make this logic consistent with tock/tock#2914 // when it is merged. TockSyscalls::exit_terminate(ErrorCode::Fail as u32); } ================================================ FILE: platform/Cargo.toml ================================================ [package] authors = ["Tock Project Developers "] categories = ["embedded", "no-std", "os"] description = """libtock-rs platform layer. Provides the Platform abstraction, an abstraction that extends Tock's system calls to form the basis for libtock-rs' asynchronous APIs. libtock_platform is intended for use in both TBF binaries and unit tests that run on Linux.""" edition = "2021" license = "Apache-2.0 OR MIT" name = "libtock_platform" repository = "https://www.github.com/tock/libtock/rs" rust-version.workspace = true version = "0.1.0" [features] rust_embedded = ["embedded-hal"] [dependencies] embedded-hal = { version = "1.0", optional = true } ================================================ FILE: platform/src/allow_ro.rs ================================================ use crate::share::List; use crate::Syscalls; use core::marker::PhantomData; // ----------------------------------------------------------------------------- // `AllowRo` struct // ----------------------------------------------------------------------------- /// A `share::Handle` instance allows safe code to call Tock's /// Read-Only Allow system call, by guaranteeing the buffer will be revoked /// before 'share ends. It is intended for use with the `share::scope` function, /// which offers a safe interface for constructing `share::Handle` /// instances. pub struct AllowRo<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> { _syscalls: PhantomData, // Make this struct invariant with respect to the 'share lifetime. // // If AllowRo were covariant with respect to 'share, then an // `AllowRo<'static, ...>` could be used to share a buffer that has a // shorter lifetime. The capsule would still have access to the memory after // the buffer is deallocated and the memory re-used (e.g. if the buffer is // on the stack), likely leaking data the process binary does not want to // share. Therefore, AllowRo cannot be covariant with respect to 'share. // Contravariance would not have this issue, but would still be confusing // and would be unexpected. // // Additionally, this makes AllowRo !Sync, which is probably desirable, as // Sync would allow for races between threads sharing buffers with the // kernel. _share: PhantomData>, } // We can't derive(Default) because S is not Default, and derive(Default) // generates a Default implementation that requires S to be Default. Instead, we // manually implement Default. impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> Default for AllowRo<'share, S, DRIVER_NUM, BUFFER_NUM> { fn default() -> Self { Self { _syscalls: PhantomData, _share: PhantomData, } } } impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> Drop for AllowRo<'share, S, DRIVER_NUM, BUFFER_NUM> { fn drop(&mut self) { S::unallow_ro(DRIVER_NUM, BUFFER_NUM); } } impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> List for AllowRo<'share, S, DRIVER_NUM, BUFFER_NUM> { } // ----------------------------------------------------------------------------- // `Config` trait // ----------------------------------------------------------------------------- /// `Config` configures the behavior of the Read-Only Allow system call. It /// should generally be passed through by drivers, to allow application code to /// configure error handling. pub trait Config { /// Called if a Read-Only Allow call succeeds and returns a non-zero buffer. /// In some applications, this may indicate unexpected reentrance. By /// default, the non-zero buffer is ignored. fn returned_nonzero_buffer(_driver_num: u32, _buffer_num: u32) {} } ================================================ FILE: platform/src/allow_rw.rs ================================================ use crate::share::List; use crate::Syscalls; use core::marker::PhantomData; // ----------------------------------------------------------------------------- // `AllowRw` struct // ----------------------------------------------------------------------------- /// A `share::Handle` instance allows safe code to call Tock's /// Read-Write Allow system call, by guaranteeing the buffer will be revoked /// before 'share ends. It is intended for use with the `share::scope` function, /// which offers a safe interface for constructing `share::Handle` /// instances. pub struct AllowRw<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> { _syscalls: PhantomData, // Make this struct invariant with respect to the 'share lifetime. // // If AllowRw were covariant with respect to 'share, then an // `AllowRw<'static, ...>` could be used to share a buffer that has a // shorter lifetime. The capsule would still have access to the memory after // the buffer is deallocated and the memory re-used (e.g. if the buffer is // on the stack), allowing it to cause undefined behavior in the process. // Therefore, AllowRw cannot be covariant with respect to 'share. // Contravariance would not have this issue, but would still be confusing // and would be unexpected. // // Additionally, this makes AllowRw !Sync, which is probably desirable, as // Sync would allow for races between threads sharing buffers with the // kernel. _share: PhantomData>, } // We can't derive(Default) because S is not Default, and derive(Default) // generates a Default implementation that requires S to be Default. Instead, we // manually implement Default. impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> Default for AllowRw<'share, S, DRIVER_NUM, BUFFER_NUM> { fn default() -> Self { Self { _syscalls: PhantomData, _share: PhantomData, } } } impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> Drop for AllowRw<'share, S, DRIVER_NUM, BUFFER_NUM> { fn drop(&mut self) { S::unallow_rw(DRIVER_NUM, BUFFER_NUM); } } impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> List for AllowRw<'share, S, DRIVER_NUM, BUFFER_NUM> { } // ----------------------------------------------------------------------------- // `Config` trait // ----------------------------------------------------------------------------- /// `Config` configures the behavior of the Read-Write Allow system call. It /// should generally be passed through by drivers, to allow application code to /// configure error handling. pub trait Config { /// Called if a Read-Write Allow call succeeds and returns a non-zero /// buffer. In some applications, this may indicate unexpected reentrance. /// By default, the non-zero buffer is ignored. fn returned_nonzero_buffer(_driver_num: u32, _buffer_num: u32) {} } ================================================ FILE: platform/src/command_return.rs ================================================ use crate::{return_variant, ErrorCode, ReturnVariant}; use core::mem::transmute; /// The response type from the [`command`](crate::Syscalls::command) syscall. /// Can represent a success or a failure with or without associated data. /// /// After a syscall is made, registers `r1`-`r3` contain the output as /// described by [TRD 104][trd-104]. Some syscalls only return success/failure, /// while others provide associated data. This is done by placing the _return /// variant_ in `r0`, which specifies how the output registers should be /// interpreted. For syscalls other than `command`, the possible output /// variants are fixed; you always know which variants are expected given the /// syscall class. /// /// However, the `command` syscall is flexible - there must be one success /// variant and one failure variant for a given driver/command ID, but /// which variants those are, and what data is expected, cannot be checked /// statically. Capsules and userspace APIs must agree on the expected /// variants for success and failure. /// /// # Example /// /// This uses the [`to_result`] method to implicitly check variants and convert /// to a `Result`. /// /// ```ignore /// let res: Result<(u32, u32), ErrorCode> = Syscalls::command(314, 1, 1, 2).to_result(); /// match res { /// Ok((val1, val2)) => { /// // Success with associated data in val1, val2. /// } /// Err(ErrorCode::BadRVal) => { /// // Incorrect return variant! We may choose to handle this /// // explicitly or propagate upwards without branching. /// } /// Err(ec) => { /// // The driver returned an error (or it doesn't exist). /// } /// } /// ``` /// /// This uses the `get_*` methods to check the variants explicitly and extract /// the associated data. /// /// ```ignore /// let command_return = Syscalls::command(314, 1, 1, 2); /// if let Some((val1, val2)) = command_return.get_success_2_u32() { /// // If there was a success, there is an associated data (u32, u32). /// } else if let Some(error_code) = command_return.get_failure() { /// // If there was a failure, there's no associated data and we only /// // have an error code. /// } else { /// // Incorrect return variant! If this occurs, your capsule and userspace /// // API do not agree on what the return variants should be. /// // An application may want to panic in this case to catch this early. /// } /// ``` /// /// [trd-104]: https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#32-return-values #[must_use = "this `CommandReturn` may represent an error, which should be handled"] #[derive(Clone, Copy, Debug)] pub struct CommandReturn { return_variant: ReturnVariant, // Safety invariant on r1: If return_variant is failure variant, r1 must be // a valid ErrorCode. r1: u32, r2: u32, r3: u32, } impl CommandReturn { /// # Safety /// If return_variant is a failure variant, r1 must be a valid ErrorCode. pub unsafe fn new(return_variant: ReturnVariant, r1: u32, r2: u32, r3: u32) -> Self { CommandReturn { return_variant, r1, r2, r3, } } /// Returns true if this CommandReturn is of type Failure. Note that this /// does not return true for other failure types, such as Failure with u32. pub fn is_failure(&self) -> bool { self.return_variant == return_variant::FAILURE } /// Returns true if this CommandReturn is of type Failure with u32. pub fn is_failure_u32(&self) -> bool { self.return_variant == return_variant::FAILURE_U32 } /// Returns true if this CommandReturn is of type Failure with 2 u32. pub fn is_failure_2_u32(&self) -> bool { self.return_variant == return_variant::FAILURE_2_U32 } /// Returns true if this CommandReturn is of type Failure with u64. pub fn is_failure_u64(&self) -> bool { self.return_variant == return_variant::FAILURE_U64 } /// Returns true if this CommandReturn is of type Success. Note that this /// does not return true for other success types, such as Success with u32. pub fn is_success(&self) -> bool { self.return_variant == return_variant::SUCCESS } /// Returns true if this CommandReturn is of type Success with u32. pub fn is_success_u32(&self) -> bool { self.return_variant == return_variant::SUCCESS_U32 } /// Returns true if this CommandReturn is of type Success with 2 u32. pub fn is_success_2_u32(&self) -> bool { self.return_variant == return_variant::SUCCESS_2_U32 } /// Returns true if this CommandReturn is of type Success with u64. pub fn is_success_u64(&self) -> bool { self.return_variant == return_variant::SUCCESS_U64 } /// Returns true if this CommandReturn is of type Success with 3 u32. pub fn is_success_3_u32(&self) -> bool { self.return_variant == return_variant::SUCCESS_3_U32 } /// Returns true if this CommandReturn is of type Success with u32 and u64. pub fn is_success_u32_u64(&self) -> bool { self.return_variant == return_variant::SUCCESS_U32_U64 } /// Returns the error code if this CommandReturn is of type Failure. pub fn get_failure(&self) -> Option { if !self.is_failure() { return None; } Some(unsafe { transmute::(self.r1) }) } /// Returns the error code and value if this CommandReturn is of type /// Failure with u32. pub fn get_failure_u32(&self) -> Option<(ErrorCode, u32)> { if !self.is_failure_u32() { return None; } Some((unsafe { transmute::(self.r1) }, self.r2)) } /// Returns the error code and return values if this CommandReturn is of /// type Failure with 2 u32. pub fn get_failure_2_u32(&self) -> Option<(ErrorCode, u32, u32)> { if !self.is_failure_2_u32() { return None; } Some(( unsafe { transmute::(self.r1) }, self.r2, self.r3, )) } /// Returns the error code and return value if this CommandReturn is of type /// Failure with u64. pub fn get_failure_u64(&self) -> Option<(ErrorCode, u64)> { if !self.is_failure_u64() { return None; } Some(( unsafe { transmute::(self.r1) }, self.r2 as u64 + ((self.r3 as u64) << 32), )) } /// Returns the value if this CommandReturn is of type Success with u32. pub fn get_success_u32(&self) -> Option { if !self.is_success_u32() { return None; } Some(self.r1) } /// Returns the values if this CommandReturn is of type Success with 2 u32. pub fn get_success_2_u32(&self) -> Option<(u32, u32)> { if !self.is_success_2_u32() { return None; } Some((self.r1, self.r2)) } /// Returns the value if this CommandReturn is of type Success with u64. pub fn get_success_u64(&self) -> Option { if !self.is_success_u64() { return None; } Some(self.r1 as u64 + ((self.r2 as u64) << 32)) } /// Returns the values if this CommandReturn is of type Success with 3 u32. pub fn get_success_3_u32(&self) -> Option<(u32, u32, u32)> { if !self.is_success_3_u32() { return None; } Some((self.r1, self.r2, self.r3)) } /// Returns the values if this CommandReturn is of type Success with u32 and /// u64. pub fn get_success_u32_u64(&self) -> Option<(u32, u64)> { if !self.is_success_u32_u64() { return None; } Some((self.r1, self.r2 as u64 + ((self.r3 as u64) << 32))) } /// Returns the register values used to create this command. pub fn raw_values(&self) -> (ReturnVariant, u32, u32, u32) { (self.return_variant, self.r1, self.r2, self.r3) } /// Returns the return variant of this command. pub fn return_variant(&self) -> ReturnVariant { self.return_variant } /// Interprets this `CommandReturn` as a `Result`, checking the success and /// failure variants, as well as extracting the relevant data. /// /// If neither the success or failure variants match what is required by /// `T` and `E`, this function will return `Err(ErrorCode::BadRVal)`. /// If `E` contains non-`ErrorCode` data in this case, the data will be 0. /// /// It is recommended to use type ascription or `::<>` to make the types /// for `T` and `E` explicit at call-site. pub fn to_result(self) -> Result where T: SuccessData, E: FailureData, { let (return_variant, r1, mut r2, mut r3) = self.raw_values(); if return_variant == T::RETURN_VARIANT { return Ok(T::from_raw_values(r1, r2, r3)); } let ec: ErrorCode = if return_variant == E::RETURN_VARIANT { // Safety: E::RETURN_VARIANT must be a failure variant, and // failure variants must contain a valid ErrorCode in r1. unsafe { transmute::(r1) } } else { r2 = 0; r3 = 0; ErrorCode::BadRVal }; Err(E::from_raw_values(ec, r2, r3)) } } mod sealed { pub trait Sealed {} } /// Output from a successful `command` syscall. /// /// This trait is [sealed], meaning foreign implementations cannot be defined, /// even though it can be referenced by foreign crates. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub trait SuccessData: sealed::Sealed { /// The return variant for this success type, stored in `r0` after /// performing a `command` syscall. const RETURN_VARIANT: ReturnVariant; /// Constructs the success data given the raw register values. fn from_raw_values(r1: u32, r2: u32, r3: u32) -> Self; } impl sealed::Sealed for () {} impl SuccessData for () { const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS; fn from_raw_values(_r1: u32, _r2: u32, _r3: u32) -> Self {} } impl sealed::Sealed for u32 {} impl SuccessData for u32 { const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_U32; fn from_raw_values(r1: u32, _r2: u32, _r3: u32) -> Self { r1 } } impl sealed::Sealed for u64 {} impl SuccessData for u64 { const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_U64; fn from_raw_values(r1: u32, r2: u32, _r3: u32) -> Self { r1 as u64 | ((r2 as u64) << 32) } } impl sealed::Sealed for (u32, u32) {} impl SuccessData for (u32, u32) { const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_2_U32; fn from_raw_values(r1: u32, r2: u32, _r3: u32) -> Self { (r1, r2) } } impl sealed::Sealed for (u32, u64) {} impl SuccessData for (u32, u64) { const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_U32_U64; fn from_raw_values(r1: u32, r2: u32, r3: u32) -> Self { (r1, r2 as u64 | ((r3 as u64) << 32)) } } impl sealed::Sealed for (u32, u32, u32) {} impl SuccessData for (u32, u32, u32) { const RETURN_VARIANT: ReturnVariant = return_variant::SUCCESS_3_U32; fn from_raw_values(r1: u32, r2: u32, r3: u32) -> Self { (r1, r2, r3) } } /// Output from a failed `command` syscall. /// /// This trait is [sealed], meaning foreign implementations cannot be defined, /// even though it can be referenced by foreign crates. /// /// # Safety /// [`RETURN_VARIANT`] must represent a failure variant, such that `r1` will /// always be a valid [`ErrorCode`]. /// /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed pub unsafe trait FailureData: sealed::Sealed { /// The return variant for this failure type, stored in `r0` after /// performing a `command` syscall. const RETURN_VARIANT: ReturnVariant; /// Constructs the error data given the raw register values. fn from_raw_values(r1: ErrorCode, r2: u32, r3: u32) -> Self; } impl sealed::Sealed for ErrorCode {} unsafe impl FailureData for ErrorCode { const RETURN_VARIANT: ReturnVariant = return_variant::FAILURE; fn from_raw_values(r1: ErrorCode, _r2: u32, _r3: u32) -> Self { r1 } } impl sealed::Sealed for (ErrorCode, u32) {} unsafe impl FailureData for (ErrorCode, u32) { const RETURN_VARIANT: ReturnVariant = return_variant::FAILURE_U32; fn from_raw_values(r1: ErrorCode, r2: u32, _r3: u32) -> Self { (r1, r2) } } impl sealed::Sealed for (ErrorCode, u32, u32) {} unsafe impl FailureData for (ErrorCode, u32, u32) { const RETURN_VARIANT: ReturnVariant = return_variant::FAILURE_2_U32; fn from_raw_values(r1: ErrorCode, r2: u32, r3: u32) -> Self { (r1, r2, r3) } } impl sealed::Sealed for (ErrorCode, u64) {} unsafe impl FailureData for (ErrorCode, u64) { const RETURN_VARIANT: ReturnVariant = return_variant::FAILURE_U64; fn from_raw_values(r1: ErrorCode, r2: u32, r3: u32) -> Self { (r1, r2 as u64 | ((r3 as u64) << 32)) } } ================================================ FILE: platform/src/command_return_tests.rs ================================================ use crate::{return_variant, CommandReturn, ErrorCode}; #[test] fn failure() { let command_return = unsafe { CommandReturn::new( return_variant::FAILURE, ErrorCode::Reserve as u32, 1002, 1003, ) }; assert!(command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), Some(ErrorCode::Reserve)); assert_eq!(command_return.get_failure_u32(), None); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::FAILURE, 5, 1002, 1003) ); assert_eq!(command_return.return_variant(), return_variant::FAILURE); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::Reserve) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u32)>(), Err((ErrorCode::BadRVal, 0)) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u32, u32)>(), Err((ErrorCode::BadRVal, 0, 0)) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u64)>(), Err((ErrorCode::BadRVal, 0)) ); } #[test] fn failure_u32() { let command_return = unsafe { CommandReturn::new( return_variant::FAILURE_U32, ErrorCode::Off as u32, 1002, 1003, ) }; assert!(!command_return.is_failure()); assert!(command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!( command_return.get_failure_u32(), Some((ErrorCode::Off, 1002)) ); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::FAILURE_U32, 4, 1002, 1003) ); assert_eq!(command_return.return_variant(), return_variant::FAILURE_U32); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u32)>(), Err((ErrorCode::Off, 1002)) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u32, u32)>(), Err((ErrorCode::BadRVal, 0, 0)) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u64)>(), Err((ErrorCode::BadRVal, 0)) ); } #[test] fn failure_2_u32() { let command_return = unsafe { CommandReturn::new( return_variant::FAILURE_2_U32, ErrorCode::Already as u32, 1002, 1003, ) }; assert!(!command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!(command_return.get_failure_u32(), None); assert_eq!( command_return.get_failure_2_u32(), Some((ErrorCode::Already, 1002, 1003)) ); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::FAILURE_2_U32, 3, 1002, 1003) ); assert_eq!( command_return.return_variant(), return_variant::FAILURE_2_U32 ); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u32)>(), Err((ErrorCode::BadRVal, 0)) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u32, u32)>(), Err((ErrorCode::Already, 1002, 1003)) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u64)>(), Err((ErrorCode::BadRVal, 0)) ); } #[test] fn failure_u64() { let command_return = unsafe { CommandReturn::new( return_variant::FAILURE_U64, ErrorCode::Busy as u32, 0x1002, 0x1003, ) }; assert!(!command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!(command_return.get_failure_u32(), None); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!( command_return.get_failure_u64(), Some((ErrorCode::Busy, 0x0000_1003_0000_1002)) ); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::FAILURE_U64, 2, 0x1002, 0x1003) ); assert_eq!(command_return.return_variant(), return_variant::FAILURE_U64); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u32)>(), Err((ErrorCode::BadRVal, 0)) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u32, u32)>(), Err((ErrorCode::BadRVal, 0, 0)) ); assert_eq!( command_return.to_result::<(), (ErrorCode, u64)>(), Err((ErrorCode::Busy, 0x0000_1003_0000_1002)) ); } #[test] fn success() { let command_return = unsafe { CommandReturn::new(return_variant::SUCCESS, 1001, 1002, 1003) }; assert!(!command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!(command_return.get_failure_u32(), None); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::SUCCESS, 1001, 1002, 1003) ); assert_eq!(command_return.return_variant(), return_variant::SUCCESS); assert_eq!(command_return.to_result::<(), ErrorCode>(), Ok(())); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u64), ErrorCode>(), Err(ErrorCode::BadRVal) ); } #[test] fn success_u32() { let command_return = unsafe { CommandReturn::new(return_variant::SUCCESS_U32, 1001, 1002, 1003) }; assert!(!command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!(command_return.get_failure_u32(), None); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), Some(1001)); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::SUCCESS_U32, 1001, 1002, 1003) ); assert_eq!(command_return.return_variant(), return_variant::SUCCESS_U32); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!(command_return.to_result::(), Ok(1001)); assert_eq!( command_return.to_result::<(u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u64), ErrorCode>(), Err(ErrorCode::BadRVal) ); } #[test] fn success_2_u32() { let command_return = unsafe { CommandReturn::new(return_variant::SUCCESS_2_U32, 1001, 1002, 1003) }; assert!(!command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!(command_return.get_failure_u32(), None); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), Some((1001, 1002))); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::SUCCESS_2_U32, 1001, 1002, 1003) ); assert_eq!( command_return.return_variant(), return_variant::SUCCESS_2_U32 ); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32), ErrorCode>(), Ok((1001, 1002)) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u64), ErrorCode>(), Err(ErrorCode::BadRVal) ); } #[test] fn success_u64() { let command_return = unsafe { CommandReturn::new(return_variant::SUCCESS_U64, 0x1001, 0x1002, 1003) }; assert!(!command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!(command_return.get_failure_u32(), None); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!( command_return.get_success_u64(), Some(0x0000_1002_0000_1001) ); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::SUCCESS_U64, 0x1001, 0x1002, 1003) ); assert_eq!(command_return.return_variant(), return_variant::SUCCESS_U64); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Ok(0x0000_1002_0000_1001) ); assert_eq!( command_return.to_result::<(u32, u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u64), ErrorCode>(), Err(ErrorCode::BadRVal) ); } #[test] fn success_3_u32() { let command_return = unsafe { CommandReturn::new(return_variant::SUCCESS_3_U32, 1001, 1002, 1003) }; assert!(!command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(command_return.is_success_3_u32()); assert!(!command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!(command_return.get_failure_u32(), None); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), Some((1001, 1002, 1003))); assert_eq!(command_return.get_success_u32_u64(), None); assert_eq!( command_return.raw_values(), (return_variant::SUCCESS_3_U32, 1001, 1002, 1003) ); assert_eq!( command_return.return_variant(), return_variant::SUCCESS_3_U32 ); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32, u32), ErrorCode>(), Ok((1001, 1002, 1003)) ); assert_eq!( command_return.to_result::<(u32, u64), ErrorCode>(), Err(ErrorCode::BadRVal) ); } #[test] fn success_u32_u64() { let command_return = unsafe { CommandReturn::new(return_variant::SUCCESS_U32_U64, 1001, 0x1002, 0x1003) }; assert!(!command_return.is_failure()); assert!(!command_return.is_failure_u32()); assert!(!command_return.is_failure_2_u32()); assert!(!command_return.is_failure_u64()); assert!(!command_return.is_success()); assert!(!command_return.is_success_u32()); assert!(!command_return.is_success_2_u32()); assert!(!command_return.is_success_u64()); assert!(!command_return.is_success_3_u32()); assert!(command_return.is_success_u32_u64()); assert_eq!(command_return.get_failure(), None); assert_eq!(command_return.get_failure_u32(), None); assert_eq!(command_return.get_failure_2_u32(), None); assert_eq!(command_return.get_failure_u64(), None); assert_eq!(command_return.get_success_u32(), None); assert_eq!(command_return.get_success_2_u32(), None); assert_eq!(command_return.get_success_u64(), None); assert_eq!(command_return.get_success_3_u32(), None); assert_eq!( command_return.get_success_u32_u64(), Some((1001, 0x0000_1003_0000_1002)) ); assert_eq!( command_return.raw_values(), (return_variant::SUCCESS_U32_U64, 1001, 0x1002, 0x1003) ); assert_eq!( command_return.return_variant(), return_variant::SUCCESS_U32_U64 ); assert_eq!( command_return.to_result::<(), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u32, u32), ErrorCode>(), Err(ErrorCode::BadRVal) ); assert_eq!( command_return.to_result::<(u32, u64), ErrorCode>(), Ok((1001, 0x0000_1003_0000_1002)) ); } ================================================ FILE: platform/src/constants.rs ================================================ //! Defines constants shared between multiple `libtock-rs` crates. pub mod exit_id { pub const TERMINATE: u32 = 0; pub const RESTART: u32 = 1; } pub mod syscall_class { pub const SUBSCRIBE: usize = 1; pub const COMMAND: usize = 2; pub const ALLOW_RW: usize = 3; pub const ALLOW_RO: usize = 4; pub const MEMOP: usize = 5; pub const EXIT: usize = 6; } pub mod yield_id { pub const NO_WAIT: u32 = 0; pub const WAIT: u32 = 1; } ================================================ FILE: platform/src/default_config.rs ================================================ /// A general purpose syscall configuration, which drivers should use as their /// default syscall config. pub struct DefaultConfig; impl crate::allow_ro::Config for DefaultConfig {} impl crate::allow_rw::Config for DefaultConfig {} impl crate::subscribe::Config for DefaultConfig {} ================================================ FILE: platform/src/error_code.rs ================================================ use core::{convert::TryFrom, fmt, mem::transmute}; // TODO: Add a ufmt debug implementation for process binaries to use. /// An error code that libtock-rs APIs may return, as specified in /// [TRD 104][error-codes]. Note that while `BADRVAL` can never be produced by /// the kernel, it can be produced by userspace APIs. /// /// [error-codes]: https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#33-error-codes #[derive(Clone, Copy, PartialEq, Eq)] // Explicit repr to use `transmute`. A word-sized error code results in // significant code size savings in typical use when compared to `#[repr(u16)]`. #[repr(u32)] #[rustfmt::skip] pub enum ErrorCode { Fail = 1, Busy = 2, Already = 3, Off = 4, Reserve = 5, Invalid = 6, Size = 7, Cancel = 8, NoMem = 9, NoSupport = 10, NoDevice = 11, Uninstalled = 12, NoAck = 13, BadRVal = 1024, // Error codes reserved for future use. We have to include these for future // compatibility -- this allows process binaries compiled with this version // of libtock-rs to run on future kernel versions that may return a larger // variety of error codes. N00014 = 14, N00015 = 15, N00016 = 16, N00017 = 17, N00018 = 18, N00019 = 19, N00020 = 20, N00021 = 21, N00022 = 22, N00023 = 23, N00024 = 24, N00025 = 25, N00026 = 26, N00027 = 27, N00028 = 28, N00029 = 29, N00030 = 30, N00031 = 31, N00032 = 32, N00033 = 33, N00034 = 34, N00035 = 35, N00036 = 36, N00037 = 37, N00038 = 38, N00039 = 39, N00040 = 40, N00041 = 41, N00042 = 42, N00043 = 43, N00044 = 44, N00045 = 45, N00046 = 46, N00047 = 47, N00048 = 48, N00049 = 49, N00050 = 50, N00051 = 51, N00052 = 52, N00053 = 53, N00054 = 54, N00055 = 55, N00056 = 56, N00057 = 57, N00058 = 58, N00059 = 59, N00060 = 60, N00061 = 61, N00062 = 62, N00063 = 63, N00064 = 64, N00065 = 65, N00066 = 66, N00067 = 67, N00068 = 68, N00069 = 69, N00070 = 70, N00071 = 71, N00072 = 72, N00073 = 73, N00074 = 74, N00075 = 75, N00076 = 76, N00077 = 77, N00078 = 78, N00079 = 79, N00080 = 80, N00081 = 81, N00082 = 82, N00083 = 83, N00084 = 84, N00085 = 85, N00086 = 86, N00087 = 87, N00088 = 88, N00089 = 89, N00090 = 90, N00091 = 91, N00092 = 92, N00093 = 93, N00094 = 94, N00095 = 95, N00096 = 96, N00097 = 97, N00098 = 98, N00099 = 99, N00100 = 100, N00101 = 101, N00102 = 102, N00103 = 103, N00104 = 104, N00105 = 105, N00106 = 106, N00107 = 107, N00108 = 108, N00109 = 109, N00110 = 110, N00111 = 111, N00112 = 112, N00113 = 113, N00114 = 114, N00115 = 115, N00116 = 116, N00117 = 117, N00118 = 118, N00119 = 119, N00120 = 120, N00121 = 121, N00122 = 122, N00123 = 123, N00124 = 124, N00125 = 125, N00126 = 126, N00127 = 127, N00128 = 128, N00129 = 129, N00130 = 130, N00131 = 131, N00132 = 132, N00133 = 133, N00134 = 134, N00135 = 135, N00136 = 136, N00137 = 137, N00138 = 138, N00139 = 139, N00140 = 140, N00141 = 141, N00142 = 142, N00143 = 143, N00144 = 144, N00145 = 145, N00146 = 146, N00147 = 147, N00148 = 148, N00149 = 149, N00150 = 150, N00151 = 151, N00152 = 152, N00153 = 153, N00154 = 154, N00155 = 155, N00156 = 156, N00157 = 157, N00158 = 158, N00159 = 159, N00160 = 160, N00161 = 161, N00162 = 162, N00163 = 163, N00164 = 164, N00165 = 165, N00166 = 166, N00167 = 167, N00168 = 168, N00169 = 169, N00170 = 170, N00171 = 171, N00172 = 172, N00173 = 173, N00174 = 174, N00175 = 175, N00176 = 176, N00177 = 177, N00178 = 178, N00179 = 179, N00180 = 180, N00181 = 181, N00182 = 182, N00183 = 183, N00184 = 184, N00185 = 185, N00186 = 186, N00187 = 187, N00188 = 188, N00189 = 189, N00190 = 190, N00191 = 191, N00192 = 192, N00193 = 193, N00194 = 194, N00195 = 195, N00196 = 196, N00197 = 197, N00198 = 198, N00199 = 199, N00200 = 200, N00201 = 201, N00202 = 202, N00203 = 203, N00204 = 204, N00205 = 205, N00206 = 206, N00207 = 207, N00208 = 208, N00209 = 209, N00210 = 210, N00211 = 211, N00212 = 212, N00213 = 213, N00214 = 214, N00215 = 215, N00216 = 216, N00217 = 217, N00218 = 218, N00219 = 219, N00220 = 220, N00221 = 221, N00222 = 222, N00223 = 223, N00224 = 224, N00225 = 225, N00226 = 226, N00227 = 227, N00228 = 228, N00229 = 229, N00230 = 230, N00231 = 231, N00232 = 232, N00233 = 233, N00234 = 234, N00235 = 235, N00236 = 236, N00237 = 237, N00238 = 238, N00239 = 239, N00240 = 240, N00241 = 241, N00242 = 242, N00243 = 243, N00244 = 244, N00245 = 245, N00246 = 246, N00247 = 247, N00248 = 248, N00249 = 249, N00250 = 250, N00251 = 251, N00252 = 252, N00253 = 253, N00254 = 254, N00255 = 255, N00256 = 256, N00257 = 257, N00258 = 258, N00259 = 259, N00260 = 260, N00261 = 261, N00262 = 262, N00263 = 263, N00264 = 264, N00265 = 265, N00266 = 266, N00267 = 267, N00268 = 268, N00269 = 269, N00270 = 270, N00271 = 271, N00272 = 272, N00273 = 273, N00274 = 274, N00275 = 275, N00276 = 276, N00277 = 277, N00278 = 278, N00279 = 279, N00280 = 280, N00281 = 281, N00282 = 282, N00283 = 283, N00284 = 284, N00285 = 285, N00286 = 286, N00287 = 287, N00288 = 288, N00289 = 289, N00290 = 290, N00291 = 291, N00292 = 292, N00293 = 293, N00294 = 294, N00295 = 295, N00296 = 296, N00297 = 297, N00298 = 298, N00299 = 299, N00300 = 300, N00301 = 301, N00302 = 302, N00303 = 303, N00304 = 304, N00305 = 305, N00306 = 306, N00307 = 307, N00308 = 308, N00309 = 309, N00310 = 310, N00311 = 311, N00312 = 312, N00313 = 313, N00314 = 314, N00315 = 315, N00316 = 316, N00317 = 317, N00318 = 318, N00319 = 319, N00320 = 320, N00321 = 321, N00322 = 322, N00323 = 323, N00324 = 324, N00325 = 325, N00326 = 326, N00327 = 327, N00328 = 328, N00329 = 329, N00330 = 330, N00331 = 331, N00332 = 332, N00333 = 333, N00334 = 334, N00335 = 335, N00336 = 336, N00337 = 337, N00338 = 338, N00339 = 339, N00340 = 340, N00341 = 341, N00342 = 342, N00343 = 343, N00344 = 344, N00345 = 345, N00346 = 346, N00347 = 347, N00348 = 348, N00349 = 349, N00350 = 350, N00351 = 351, N00352 = 352, N00353 = 353, N00354 = 354, N00355 = 355, N00356 = 356, N00357 = 357, N00358 = 358, N00359 = 359, N00360 = 360, N00361 = 361, N00362 = 362, N00363 = 363, N00364 = 364, N00365 = 365, N00366 = 366, N00367 = 367, N00368 = 368, N00369 = 369, N00370 = 370, N00371 = 371, N00372 = 372, N00373 = 373, N00374 = 374, N00375 = 375, N00376 = 376, N00377 = 377, N00378 = 378, N00379 = 379, N00380 = 380, N00381 = 381, N00382 = 382, N00383 = 383, N00384 = 384, N00385 = 385, N00386 = 386, N00387 = 387, N00388 = 388, N00389 = 389, N00390 = 390, N00391 = 391, N00392 = 392, N00393 = 393, N00394 = 394, N00395 = 395, N00396 = 396, N00397 = 397, N00398 = 398, N00399 = 399, N00400 = 400, N00401 = 401, N00402 = 402, N00403 = 403, N00404 = 404, N00405 = 405, N00406 = 406, N00407 = 407, N00408 = 408, N00409 = 409, N00410 = 410, N00411 = 411, N00412 = 412, N00413 = 413, N00414 = 414, N00415 = 415, N00416 = 416, N00417 = 417, N00418 = 418, N00419 = 419, N00420 = 420, N00421 = 421, N00422 = 422, N00423 = 423, N00424 = 424, N00425 = 425, N00426 = 426, N00427 = 427, N00428 = 428, N00429 = 429, N00430 = 430, N00431 = 431, N00432 = 432, N00433 = 433, N00434 = 434, N00435 = 435, N00436 = 436, N00437 = 437, N00438 = 438, N00439 = 439, N00440 = 440, N00441 = 441, N00442 = 442, N00443 = 443, N00444 = 444, N00445 = 445, N00446 = 446, N00447 = 447, N00448 = 448, N00449 = 449, N00450 = 450, N00451 = 451, N00452 = 452, N00453 = 453, N00454 = 454, N00455 = 455, N00456 = 456, N00457 = 457, N00458 = 458, N00459 = 459, N00460 = 460, N00461 = 461, N00462 = 462, N00463 = 463, N00464 = 464, N00465 = 465, N00466 = 466, N00467 = 467, N00468 = 468, N00469 = 469, N00470 = 470, N00471 = 471, N00472 = 472, N00473 = 473, N00474 = 474, N00475 = 475, N00476 = 476, N00477 = 477, N00478 = 478, N00479 = 479, N00480 = 480, N00481 = 481, N00482 = 482, N00483 = 483, N00484 = 484, N00485 = 485, N00486 = 486, N00487 = 487, N00488 = 488, N00489 = 489, N00490 = 490, N00491 = 491, N00492 = 492, N00493 = 493, N00494 = 494, N00495 = 495, N00496 = 496, N00497 = 497, N00498 = 498, N00499 = 499, N00500 = 500, N00501 = 501, N00502 = 502, N00503 = 503, N00504 = 504, N00505 = 505, N00506 = 506, N00507 = 507, N00508 = 508, N00509 = 509, N00510 = 510, N00511 = 511, N00512 = 512, N00513 = 513, N00514 = 514, N00515 = 515, N00516 = 516, N00517 = 517, N00518 = 518, N00519 = 519, N00520 = 520, N00521 = 521, N00522 = 522, N00523 = 523, N00524 = 524, N00525 = 525, N00526 = 526, N00527 = 527, N00528 = 528, N00529 = 529, N00530 = 530, N00531 = 531, N00532 = 532, N00533 = 533, N00534 = 534, N00535 = 535, N00536 = 536, N00537 = 537, N00538 = 538, N00539 = 539, N00540 = 540, N00541 = 541, N00542 = 542, N00543 = 543, N00544 = 544, N00545 = 545, N00546 = 546, N00547 = 547, N00548 = 548, N00549 = 549, N00550 = 550, N00551 = 551, N00552 = 552, N00553 = 553, N00554 = 554, N00555 = 555, N00556 = 556, N00557 = 557, N00558 = 558, N00559 = 559, N00560 = 560, N00561 = 561, N00562 = 562, N00563 = 563, N00564 = 564, N00565 = 565, N00566 = 566, N00567 = 567, N00568 = 568, N00569 = 569, N00570 = 570, N00571 = 571, N00572 = 572, N00573 = 573, N00574 = 574, N00575 = 575, N00576 = 576, N00577 = 577, N00578 = 578, N00579 = 579, N00580 = 580, N00581 = 581, N00582 = 582, N00583 = 583, N00584 = 584, N00585 = 585, N00586 = 586, N00587 = 587, N00588 = 588, N00589 = 589, N00590 = 590, N00591 = 591, N00592 = 592, N00593 = 593, N00594 = 594, N00595 = 595, N00596 = 596, N00597 = 597, N00598 = 598, N00599 = 599, N00600 = 600, N00601 = 601, N00602 = 602, N00603 = 603, N00604 = 604, N00605 = 605, N00606 = 606, N00607 = 607, N00608 = 608, N00609 = 609, N00610 = 610, N00611 = 611, N00612 = 612, N00613 = 613, N00614 = 614, N00615 = 615, N00616 = 616, N00617 = 617, N00618 = 618, N00619 = 619, N00620 = 620, N00621 = 621, N00622 = 622, N00623 = 623, N00624 = 624, N00625 = 625, N00626 = 626, N00627 = 627, N00628 = 628, N00629 = 629, N00630 = 630, N00631 = 631, N00632 = 632, N00633 = 633, N00634 = 634, N00635 = 635, N00636 = 636, N00637 = 637, N00638 = 638, N00639 = 639, N00640 = 640, N00641 = 641, N00642 = 642, N00643 = 643, N00644 = 644, N00645 = 645, N00646 = 646, N00647 = 647, N00648 = 648, N00649 = 649, N00650 = 650, N00651 = 651, N00652 = 652, N00653 = 653, N00654 = 654, N00655 = 655, N00656 = 656, N00657 = 657, N00658 = 658, N00659 = 659, N00660 = 660, N00661 = 661, N00662 = 662, N00663 = 663, N00664 = 664, N00665 = 665, N00666 = 666, N00667 = 667, N00668 = 668, N00669 = 669, N00670 = 670, N00671 = 671, N00672 = 672, N00673 = 673, N00674 = 674, N00675 = 675, N00676 = 676, N00677 = 677, N00678 = 678, N00679 = 679, N00680 = 680, N00681 = 681, N00682 = 682, N00683 = 683, N00684 = 684, N00685 = 685, N00686 = 686, N00687 = 687, N00688 = 688, N00689 = 689, N00690 = 690, N00691 = 691, N00692 = 692, N00693 = 693, N00694 = 694, N00695 = 695, N00696 = 696, N00697 = 697, N00698 = 698, N00699 = 699, N00700 = 700, N00701 = 701, N00702 = 702, N00703 = 703, N00704 = 704, N00705 = 705, N00706 = 706, N00707 = 707, N00708 = 708, N00709 = 709, N00710 = 710, N00711 = 711, N00712 = 712, N00713 = 713, N00714 = 714, N00715 = 715, N00716 = 716, N00717 = 717, N00718 = 718, N00719 = 719, N00720 = 720, N00721 = 721, N00722 = 722, N00723 = 723, N00724 = 724, N00725 = 725, N00726 = 726, N00727 = 727, N00728 = 728, N00729 = 729, N00730 = 730, N00731 = 731, N00732 = 732, N00733 = 733, N00734 = 734, N00735 = 735, N00736 = 736, N00737 = 737, N00738 = 738, N00739 = 739, N00740 = 740, N00741 = 741, N00742 = 742, N00743 = 743, N00744 = 744, N00745 = 745, N00746 = 746, N00747 = 747, N00748 = 748, N00749 = 749, N00750 = 750, N00751 = 751, N00752 = 752, N00753 = 753, N00754 = 754, N00755 = 755, N00756 = 756, N00757 = 757, N00758 = 758, N00759 = 759, N00760 = 760, N00761 = 761, N00762 = 762, N00763 = 763, N00764 = 764, N00765 = 765, N00766 = 766, N00767 = 767, N00768 = 768, N00769 = 769, N00770 = 770, N00771 = 771, N00772 = 772, N00773 = 773, N00774 = 774, N00775 = 775, N00776 = 776, N00777 = 777, N00778 = 778, N00779 = 779, N00780 = 780, N00781 = 781, N00782 = 782, N00783 = 783, N00784 = 784, N00785 = 785, N00786 = 786, N00787 = 787, N00788 = 788, N00789 = 789, N00790 = 790, N00791 = 791, N00792 = 792, N00793 = 793, N00794 = 794, N00795 = 795, N00796 = 796, N00797 = 797, N00798 = 798, N00799 = 799, N00800 = 800, N00801 = 801, N00802 = 802, N00803 = 803, N00804 = 804, N00805 = 805, N00806 = 806, N00807 = 807, N00808 = 808, N00809 = 809, N00810 = 810, N00811 = 811, N00812 = 812, N00813 = 813, N00814 = 814, N00815 = 815, N00816 = 816, N00817 = 817, N00818 = 818, N00819 = 819, N00820 = 820, N00821 = 821, N00822 = 822, N00823 = 823, N00824 = 824, N00825 = 825, N00826 = 826, N00827 = 827, N00828 = 828, N00829 = 829, N00830 = 830, N00831 = 831, N00832 = 832, N00833 = 833, N00834 = 834, N00835 = 835, N00836 = 836, N00837 = 837, N00838 = 838, N00839 = 839, N00840 = 840, N00841 = 841, N00842 = 842, N00843 = 843, N00844 = 844, N00845 = 845, N00846 = 846, N00847 = 847, N00848 = 848, N00849 = 849, N00850 = 850, N00851 = 851, N00852 = 852, N00853 = 853, N00854 = 854, N00855 = 855, N00856 = 856, N00857 = 857, N00858 = 858, N00859 = 859, N00860 = 860, N00861 = 861, N00862 = 862, N00863 = 863, N00864 = 864, N00865 = 865, N00866 = 866, N00867 = 867, N00868 = 868, N00869 = 869, N00870 = 870, N00871 = 871, N00872 = 872, N00873 = 873, N00874 = 874, N00875 = 875, N00876 = 876, N00877 = 877, N00878 = 878, N00879 = 879, N00880 = 880, N00881 = 881, N00882 = 882, N00883 = 883, N00884 = 884, N00885 = 885, N00886 = 886, N00887 = 887, N00888 = 888, N00889 = 889, N00890 = 890, N00891 = 891, N00892 = 892, N00893 = 893, N00894 = 894, N00895 = 895, N00896 = 896, N00897 = 897, N00898 = 898, N00899 = 899, N00900 = 900, N00901 = 901, N00902 = 902, N00903 = 903, N00904 = 904, N00905 = 905, N00906 = 906, N00907 = 907, N00908 = 908, N00909 = 909, N00910 = 910, N00911 = 911, N00912 = 912, N00913 = 913, N00914 = 914, N00915 = 915, N00916 = 916, N00917 = 917, N00918 = 918, N00919 = 919, N00920 = 920, N00921 = 921, N00922 = 922, N00923 = 923, N00924 = 924, N00925 = 925, N00926 = 926, N00927 = 927, N00928 = 928, N00929 = 929, N00930 = 930, N00931 = 931, N00932 = 932, N00933 = 933, N00934 = 934, N00935 = 935, N00936 = 936, N00937 = 937, N00938 = 938, N00939 = 939, N00940 = 940, N00941 = 941, N00942 = 942, N00943 = 943, N00944 = 944, N00945 = 945, N00946 = 946, N00947 = 947, N00948 = 948, N00949 = 949, N00950 = 950, N00951 = 951, N00952 = 952, N00953 = 953, N00954 = 954, N00955 = 955, N00956 = 956, N00957 = 957, N00958 = 958, N00959 = 959, N00960 = 960, N00961 = 961, N00962 = 962, N00963 = 963, N00964 = 964, N00965 = 965, N00966 = 966, N00967 = 967, N00968 = 968, N00969 = 969, N00970 = 970, N00971 = 971, N00972 = 972, N00973 = 973, N00974 = 974, N00975 = 975, N00976 = 976, N00977 = 977, N00978 = 978, N00979 = 979, N00980 = 980, N00981 = 981, N00982 = 982, N00983 = 983, N00984 = 984, N00985 = 985, N00986 = 986, N00987 = 987, N00988 = 988, N00989 = 989, N00990 = 990, N00991 = 991, N00992 = 992, N00993 = 993, N00994 = 994, N00995 = 995, N00996 = 996, N00997 = 997, N00998 = 998, N00999 = 999, N01000 = 1000, N01001 = 1001, N01002 = 1002, N01003 = 1003, N01004 = 1004, N01005 = 1005, N01006 = 1006, N01007 = 1007, N01008 = 1008, N01009 = 1009, N01010 = 1010, N01011 = 1011, N01012 = 1012, N01013 = 1013, N01014 = 1014, N01015 = 1015, N01016 = 1016, N01017 = 1017, N01018 = 1018, N01019 = 1019, N01020 = 1020, N01021 = 1021, N01022 = 1022, N01023 = 1023, } /// The provided value is not a recognized TRD 104 error code. #[derive(PartialEq, Eq, Debug)] pub struct NotAnErrorCode; impl ErrorCode { /// Represent this error code as a string, if defined. fn as_str(self) -> Option<&'static str> { match self { Self::Fail => Some("FAIL"), Self::Busy => Some("BUSY"), Self::Already => Some("ALREADY"), Self::Off => Some("OFF"), Self::Reserve => Some("RESERVE"), Self::Invalid => Some("INVALID"), Self::Size => Some("SIZE"), Self::Cancel => Some("CANCEL"), Self::NoMem => Some("NOMEM"), Self::NoSupport => Some("NOSUPPORT"), Self::NoDevice => Some("NODEVICE"), Self::Uninstalled => Some("UNINSTALLED"), Self::NoAck => Some("NOACK"), Self::BadRVal => Some("BADRVAL"), _ => None, } } } impl fmt::Debug for ErrorCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.as_str() { Some(s) => write!(f, "{s}"), None => write!(f, "code {}", *self as u16), } } } impl TryFrom for ErrorCode { type Error = NotAnErrorCode; fn try_from(value: u32) -> Result { if (1..=1024).contains(&value) { Ok(unsafe { transmute::(value) }) } else { Err(NotAnErrorCode) } } } #[cfg(feature = "rust_embedded")] impl embedded_hal::digital::Error for ErrorCode { fn kind(&self) -> embedded_hal::digital::ErrorKind { use embedded_hal::digital::ErrorKind; ErrorKind::Other } } #[cfg(feature = "rust_embedded")] impl embedded_hal::spi::Error for ErrorCode { fn kind(&self) -> embedded_hal::spi::ErrorKind { use embedded_hal::spi::ErrorKind; ErrorKind::Other } } ================================================ FILE: platform/src/error_code_tests.rs ================================================ use core::convert::TryInto; use crate::{error_code::NotAnErrorCode, ErrorCode}; // Verifies that `ErrorCode` represents every valid value in the range // [1, 1024]. #[cfg(miri)] #[test] fn error_code_range() { for value in 1..=1024u32 { let _ = unsafe { *(&value as *const u32 as *const ErrorCode) }; } } #[test] fn error_code_try_into() { assert_eq!(TryInto::::try_into(0u32), Err(NotAnErrorCode)); for value in 1..=1024u32 { assert_eq!(value.try_into().map(|e: ErrorCode| e as u32), Ok(value)); } assert_eq!(TryInto::::try_into(1025u32), Err(NotAnErrorCode)); } ================================================ FILE: platform/src/exit_on_drop.rs ================================================ /// Calls the Exit system call when dropped. Used to catch panic unwinding from /// a `#![no_std]` context. The caller should `core::mem::forget` the /// `ExitOnDrop` when it no longer needs to catch unwinding. /// /// /// # Example /// ``` /// use libtock_platform::exit_on_drop::ExitOnDrop; /// fn function_that_must_not_unwind() { /// let exit_on_drop: ExitOnDrop:: = Default::default(); /// /* Do something that might unwind here. */ /// core::mem::forget(exit_on_drop); /// } /// ``` pub struct ExitOnDrop(core::marker::PhantomData); impl Default for ExitOnDrop { fn default() -> ExitOnDrop { ExitOnDrop(core::marker::PhantomData) } } impl Drop for ExitOnDrop { fn drop(&mut self) { S::exit_terminate(0); } } ================================================ FILE: platform/src/lib.rs ================================================ #![cfg_attr(not(test), no_std)] #![warn(unsafe_op_in_unsafe_fn)] pub mod allow_ro; pub mod allow_rw; pub mod command_return; mod constants; mod default_config; mod error_code; pub mod exit_on_drop; mod raw_syscalls; mod register; pub mod return_variant; pub mod share; pub mod subscribe; mod syscalls; mod syscalls_impl; mod termination; mod yield_types; pub use allow_ro::AllowRo; pub use allow_rw::AllowRw; pub use command_return::CommandReturn; pub use constants::{exit_id, syscall_class, yield_id}; pub use default_config::DefaultConfig; pub use error_code::ErrorCode; pub use raw_syscalls::RawSyscalls; pub use register::Register; pub use return_variant::ReturnVariant; pub use subscribe::{Subscribe, Upcall}; pub use syscalls::Syscalls; pub use termination::Termination; pub use yield_types::YieldNoWaitReturn; #[cfg(test)] mod command_return_tests; #[cfg(test)] mod error_code_tests; ================================================ FILE: platform/src/raw_syscalls.rs ================================================ use crate::Register; /// `RawSyscalls` allows a fake Tock kernel to be injected into components for /// unit testing. It is implemented by `libtock_runtime::TockSyscalls` and /// `libtock_unittest::fake::Kernel`. **Components should not use `RawSyscalls` /// directly; instead, use the `Syscalls` trait, which provides higher-level /// interfaces to the system calls.** /// /// # Safety /// `RawSyscalls` is unsafe because `unsafe` code depends on its methods to /// return the correct register values. // The RawSyscalls trait is designed to minimize the complexity and size of its // implementation, as its implementation is difficult to test (it cannot be used // in unit tests, with sanitizers, or in Miri). It is also designed to minimize // the number of unnecessary instructions it generates. // // Convention: This file uses the same register naming conventions as the Tock // 2.0 syscall TRD. Registers r0-r4 correspond to ARM registers r0-r4 and RISC-V // registers a0-a4. // // Theoretically, RawSyscalls could consist of a single raw system call. To // start, something like this should work: // // unsafe fn syscall([Reg; 4]) -> [Reg; 4]; // // Note: Reg is an abbreviation of Register. // // Using a single system call has a major inefficiency. The single raw system // call would need to clobber every register that any system call can clobber. // Yield has a far longer clobber list than most system calls, so this would be // inefficient for the majority of system calls. As a result, we can split yield // out into its own function, giving the following API: // // unsafe fn yield([Reg; 4]) -> [Reg; 4]; // unsafe fn syscall([Reg; 4]) -> [Reg; 4]; // // There is one significant inefficiency remaining. Many system calls, such as // memop's "get RAM start address" operation, do not need to set all four // arguments. The compiler cannot optimize away this inefficiency, so to remove // it we need to split the system calls up based on the number of arguments they // take: // // unsafe fn yield0([Reg; 0]) -> [Reg; 4]; // unsafe fn yield1([Reg; 1]) -> [Reg; 4]; // unsafe fn yield2([Reg; 2]) -> [Reg; 4]; // unsafe fn yield3([Reg; 3]) -> [Reg; 4]; // unsafe fn yield4([Reg; 4]) -> [Reg; 4]; // unsafe fn syscall0([Reg; 0]) -> [Reg; 4]; // unsafe fn syscall1([Reg; 1]) -> [Reg; 4]; // unsafe fn syscall2([Reg; 2]) -> [Reg; 4]; // unsafe fn syscall3([Reg; 3]) -> [Reg; 4]; // unsafe fn syscall4([Reg; 4]) -> [Reg; 4]; // // However, not all of these are used! If we remove the system calls that are // unused, we are left with the following: // // unsafe fn yield1([Reg; 1]) -> [Reg; 4]; // unsafe fn yield2([Reg; 2]) -> [Reg; 4]; // unsafe fn syscall1([Reg; 1]) -> [Reg; 4]; // unsafe fn syscall2([Reg; 2]) -> [Reg; 4]; // unsafe fn syscall4([Reg; 4]) -> [Reg; 4]; // // These system calls are refined further individually, which is documented on // a per-function basis. pub unsafe trait RawSyscalls: Sized { // yield1 can only be used to call `yield-wait`, which does not have a // return value. To simplify the assembly implementation, we remove its // return value. // // yield1 should: // 1. Call syscall class 0 // 2. Pass in r0 as an inlateout register. // 3. Mark all caller-saved registers as lateout clobbers. // 4. NOT provide any of the following options: // pure (yield has side effects) // nomem (a callback can read + write globals) // readonly (a callback can write globals) // preserves_flags (a callback can change flags) // noreturn (yield is expected to return) // nostack (a callback needs the stack) /// `yield1` should only be called by `libtock_platform`. /// # Safety /// yield1 may only be used for yield operations that do not return a value. /// It is exactly as safe as the underlying system call. unsafe fn yield1(_: [Register; 1]); // yield2 can only be used to call `yield-no-wait`. `yield-no-wait` does not // return any values, so to simplify the assembly we omit return arguments. // // yield2 should: // 1. Call syscall class 0 // 2. Pass in r0 and r1 as inlateout registers. // 3. Mark all caller-saved registers as lateout clobbers. // 4. NOT provide any of the following options: // pure (yield has side effects) // nomem (a callback can read + write globals) // readonly (a callback can write globals) // preserves_flags (a callback can change flags) // noreturn (yield is expected to return) // nostack (a callback needs the stack) /// `yield2` should only be called by `libtock_platform`. /// # Safety /// yield2 may only be used for yield operations that do not return a value. /// It has the same safety invariants as the underlying system call. unsafe fn yield2(_: [Register; 2]); // syscall1 is only used to invoke Memop operations. Because there are no // Memop commands that set r2 or r3, raw_syscall1 only needs to return r0 // and r1. // // Memop commands may panic in the unit test environment, as not all memop // calls can be sensibly implemented in that environment. // // syscall1 should: // 1. Call the syscall class specified by CLASS. // 2. Pass r0 as an inlateout register. // 3. Specify r1 as a lateout register and return its value. // 4. Not mark any registers as clobbered. // 5. Have all of the following options: // preserves_flags // nostack // nomem (it is okay for the compiler to cache globals // across memop calls) // 6. NOT have any of the following options: // pure (two invocations of the same memop can return // different values) // readonly (incompatible with nomem) // noreturn /// `syscall1` should only be called by `libtock_platform`. /// # Safety /// This directly makes a system call. It can only be used for Memop calls /// that accept 1 argument and only overwrite r0 and r1 on return. It is /// unsafe any time the underlying system call is unsafe. unsafe fn syscall1(_: [Register; 1]) -> [Register; 2]; // syscall2 is used to invoke Exit as well as Memop operations that take an // argument. Memop does not currently use more than 2 registers for its // return value, and Exit does not return, so syscall2 only returns 2 // values. // // syscall2 should: // 1. Call the syscall class specified by CLASS. // 2. Pass r0 and r1 as inlateout registers. // 3. Not mark any registers as clobbered. // 4. Have all of the following options: // preserves_flags // nostack // nomem (the compiler can cache globals across memop // calls) // 5. NOT have any of the following options: // pure Two invocations of sbrk can return different values // readonly Incompatible with nomem // noreturn /// `syscall2` should only be called by `libtock_platform`. /// # Safety /// `syscall2` directly makes a system call. It can only be used for core /// kernel system calls that accept 2 arguments and only overwrite r0 and r1 /// on return. It is unsafe any time the underlying system call is unsafe. unsafe fn syscall2(_: [Register; 2]) -> [Register; 2]; // syscall4 should: // 1. Call the syscall class specified by CLASS. // 2. Pass r0-r3 in the corresponding registers as inlateout registers. // 3. Not mark any registers as clobbered. // 4. Have all of the following options: // preserves_flags (these system calls do not touch flags) // nostack (these system calls do not touch the stack) // 5. NOT have any of the following options: // pure (these system calls have side effects) // nomem (the compiler needs to write to globals before allow) // readonly (rw allow can modify memory) // noreturn (all these system calls are expected to return) // // For subscribe(), the callback pointer should be either 0 (for the null // callback) or an `unsafe extern fn(u32, u32, u32, Register)`. /// `syscall4` should only be called by `libtock_platform`. /// /// # Safety /// `syscall4` must NOT be used to invoke yield. It inherits all safety /// invariants from the underlying system call as described in TRD 104, /// which varies depending on the system call class. /// /// For the Allow system calls, there are some invariants that are stricter /// than TRD 104. These invariants are explicitly allowed by TRD 104. /// /// For Read-Only Allow, the aliasing invariants on the buffer are /// equivalent to passing a `&[u8]` reference across the system call /// boundary. In particular, that means there MUST NOT be a `&mut [u8]` /// reference overlapping the passed buffer, until the buffer has been /// returned by a Read-Only Allow call. /// /// For Read-Write Allow, the aliasing invariants on the buffer are /// equivalent to passing a `&mut [u8]` reference across the system call /// boundary. In particular, that means there MUST NOT be a reference /// overlapping the passed buffer, until the buffer has been returned by a /// Read-Write Allow call. unsafe fn syscall4(_: [Register; 4]) -> [Register; 4]; } ================================================ FILE: platform/src/register.rs ================================================ /// `Register` represents the value of a register used in Tock's syscall ABI. It /// can contain integer values as well as pointer values. /// /// `Register` currently wraps a raw pointer, but that is not a stable guarantee /// and users should not rely on it. However, `Register` does guarantee that the /// type it wraps is a valid operand type for inline assembly. /// /// If a pointer is converted to a `Register`, that `Register` has that /// pointer's provenance. The provenance is not exposed. If an integer is /// converted to a `Register`, that `Register` has no provenance. When a /// `Register` with provenance is converted into a pointer, that pointer carries /// the `Register`'s provenance. When a `Register` without provenance is /// converted into a pointer, that pointer has no provenance. // Register is repr(transparent) so that an upcall's application data can be // soundly passed as a Register. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct Register(pub *mut ()); // ----------------------------------------------------------------------------- // Conversions to Register // ----------------------------------------------------------------------------- impl From for Register { fn from(value: crate::ErrorCode) -> Register { (value as usize).into() } } impl From for Register { fn from(value: u32) -> Register { (value as usize).into() } } impl From for Register { fn from(value: i32) -> Register { (value as usize).into() } } impl From for Register { fn from(value: usize) -> Register { // TODO(Rust 1.84): We want to convert using the same semantics as // core::ptr::without_provenance. However, until our MSRV is >= 1.84, we // have to use an `as` cast instead. This may result in this Register // converting into a pointer with provenance later on, but that // shouldn't break any users of Register in practice. #[cfg(not(miri))] { Register(value as *mut ()) } // However, on Miri, we cannot do the conversion using an `as` cast. // Fortunately, since Miri runs on nightly Rust, we can use // `without_provenance_mut`. #[cfg(miri)] { Register(core::ptr::without_provenance_mut(value)) } } } impl From<*mut T> for Register { fn from(value: *mut T) -> Register { Register(value.cast()) } } impl From<*const T> for Register { fn from(value: *const T) -> Register { Register(value as *mut ()) } } // ----------------------------------------------------------------------------- // Infallible conversions from Register // ----------------------------------------------------------------------------- // If we implement From on Register, then we automatically get a // TryFrom implementation, which conflicts with our fallible // TryFrom implementation. We could choose to not implement TryFrom and instead // add a fallible accessor (something like "expect_u32"), but that seems // confusing. Instead, we use an inherent method for the Register -> u32 // infallible conversion. impl Register { /// Casts this register to a u32, truncating it if it is larger than /// u32::MAX. This conversion should be avoided in host-based test code; use /// the `TryFrom for u32` implementation instead. pub fn as_u32(self) -> u32 { self.0 as u32 } /// Casts this register to a i32, truncating it if it is larger than /// 32 bits. This conversion should be avoided in host-based test code; use /// the `TryFrom for i32` implementation instead. pub fn as_i32(self) -> i32 { self.0 as i32 } } impl From for usize { fn from(register: Register) -> usize { // TODO(Rust 1.84): We want to convert using the same semantics as // .addr(). Until our MSRV is >= 1.84, we have to convert using an `as` // cast instead. This exposes the provenance of the pointer, which is // not correct but shouldn't break any users in practice. register.0 as usize } } impl From for *mut T { fn from(register: Register) -> *mut T { register.0.cast() } } impl From for *const T { fn from(register: Register) -> *const T { register.0 as *const T } } // ----------------------------------------------------------------------------- // Fallible conversions from Register // ----------------------------------------------------------------------------- /// Converts a `Register` to a `u32`. Returns an error if the `Register`'s value /// is larger than `u32::MAX`. This is intended for use in host-based tests; in /// Tock process binary code, use Register::as_u32 instead. impl TryFrom for u32 { type Error = core::num::TryFromIntError; fn try_from(register: Register) -> Result { usize::from(register).try_into() } } ================================================ FILE: platform/src/return_variant.rs ================================================ // TODO: Re-evaluate whether u32 is the correct type to wrap. Maybe it should // wrap a Register instead? Also, double-check that ReturnVariant is providing // useful type-safety. /// `ReturnVariant` describes what value type the kernel has returned. // ReturnVariant is not an enum so that it can be converted from a u32 for free. // TODO: Add a ufmt debug implementation for use by process binaries. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ReturnVariant(u32); impl From for ReturnVariant { fn from(value: u32) -> ReturnVariant { ReturnVariant(value) } } impl From for crate::Register { fn from(return_variant: ReturnVariant) -> crate::Register { return_variant.0.into() } } impl From for u32 { fn from(return_variant: ReturnVariant) -> u32 { return_variant.0 } } pub const FAILURE: ReturnVariant = ReturnVariant(0); pub const FAILURE_U32: ReturnVariant = ReturnVariant(1); pub const FAILURE_2_U32: ReturnVariant = ReturnVariant(2); pub const FAILURE_U64: ReturnVariant = ReturnVariant(3); pub const SUCCESS: ReturnVariant = ReturnVariant(128); pub const SUCCESS_U32: ReturnVariant = ReturnVariant(129); pub const SUCCESS_2_U32: ReturnVariant = ReturnVariant(130); pub const SUCCESS_U64: ReturnVariant = ReturnVariant(131); pub const SUCCESS_3_U32: ReturnVariant = ReturnVariant(132); pub const SUCCESS_U32_U64: ReturnVariant = ReturnVariant(133); ================================================ FILE: platform/src/share/handle.rs ================================================ use crate::share::List; use core::marker::PhantomData; /// A `Handle`'s existence indicates two things: /// /// 1. The specified List exists. /// 2. The specified List will be cleaned up (via `Drop::drop`) rather than /// leaked or forgotten. /// /// `Handle`s are used to call system calls, and should generally be created by /// using `share::scope`. pub struct Handle<'handle, L: List> { // Handle acts like a &'handle L, so set its variance accordingly. Note that // the most important lifetime -- the lifetime the share can live for -- is // a parameter of L, not Handle. // // Additionally, _list is a private member, which prevents code outside this // module from constructing a Handle without calling Handle's constructors. _list: PhantomData<&'handle L>, } // We can't #[derive(Clone, Copy)] because derive's implementations of Clone and // Copy have `L: Clone` and `L: Copy` constraints, respectively. We don't want // those constraints, so we manually implement Clone and Copy. impl<'handle, L: List> Clone for Handle<'handle, L> { fn clone(&self) -> Self { *self } } impl<'handle, L: List> Copy for Handle<'handle, L> {} impl<'handle, L: List> Handle<'handle, L> { /// Constructs a new `Handle`, typing its lifetime to the provided list. /// /// # Safety /// The calling code must guarantee that `Drop::drop` is called on `_list`'s /// pointee before `_list`'s pointee becomes invalid. In other words, /// `_list`'s pointee may not be forgotten or leaked. pub unsafe fn new(_list: &'handle L) -> Self { Handle { _list: PhantomData } } /// Converts this `Handle` to a `Handle` of a different type. Used to create /// a handle to a sub-object of this handle's `L`. /// /// # Safety /// The calling code must guarantee that an `Other` exists, and that /// `Drop::drop` will be called on the `Other` before the `Other` becomes /// invalid. In general, the `Other` should be contained inside the `L`, so /// `Drop::drop` is executed on the `Other` when the `L` is dropped. pub unsafe fn change_type(self) -> Handle<'handle, Other> { Handle { _list: PhantomData } } /// Splits this `Handle` into a list of handles to sub-lists of `L`. Used /// when `L` is a tuple of shares, to obtain handles to the individual /// shares. pub fn split(self) -> L::SplitHandles where L: SplittableHandle<'handle>, { L::split(self) } } /// A trait implemented by `share::List`s that can be divided into sub-lists. /// `SplittableHandle` should not be used directly; instead, callers should use /// `Handle::split`. pub trait SplittableHandle<'handle>: List { /// SplitHandles should be a tuple of Handle types, i.e. (Handle<>, /// Handle<>, ...). type SplitHandles; /// Split the specified handle into sub-handles. Implementations of `split` /// should use `Handle::change_type` to create the sub-handles. fn split(handle: Handle<'handle, Self>) -> Self::SplitHandles; } ================================================ FILE: platform/src/share/mod.rs ================================================ //! `share` contains tools for safely sharing objects (such as buffers and //! upcalls) with the Tock kernel. mod handle; mod tuple_impls; pub use handle::{Handle, SplittableHandle}; /// Creates a scope in which objects may safely be shared with the kernel. pub fn scope) -> Output>(fcn: F) -> Output { let list = Default::default(); // Safety: We do not move the L out of the `list` variable. The `list` // variable will be dropped at the end of the scope, immediately before the // L becomes invalid. fcn(unsafe { Handle::new(&list) }) } /// A list of objects that may be shared with the kernel. `List` is implemented /// for system call types such as `Subscribe`, as well as (potentially-nested) /// tuples of such types. pub trait List: Default {} #[cfg(test)] mod tests; ================================================ FILE: platform/src/share/tests.rs ================================================ use crate::share::{scope, Handle, List}; std::thread_local! {static INSTANCE_COUNT: core::cell::Cell = const {core::cell::Cell::new(0)}} // InstanceCounter increments INSTANCE_COUNT when it is constructed and // decrements INSTANCE_COUNT when it is dropped. struct InstanceCounter { _private: (), } impl Default for InstanceCounter { fn default() -> Self { INSTANCE_COUNT.with(|cell| cell.set(cell.get() + 1)); Self { _private: () } } } impl Drop for InstanceCounter { fn drop(&mut self) { INSTANCE_COUNT.with(|cell| cell.set(cell.get() - 1)); } } impl List for InstanceCounter {} #[test] fn list_lifetime() { // INSTANCE_COUNT *should* be 0 here, but make sure it's zero in case // another test case leaked an instance. INSTANCE_COUNT.with(|cell| cell.set(0)); scope(|_counter: Handle| { assert_eq!(INSTANCE_COUNT.with(|cell| cell.get()), 1); }); assert_eq!(INSTANCE_COUNT.with(|cell| cell.get()), 0); } // This test will only compile if the correct trait impls exist for tuples. #[test] fn tuple_impls() { #[allow(clippy::let_unit_value)] scope(|list: Handle<()>| { let _empty: () = list.split(); }); scope(|list: Handle<((),)>| { let (_empty_handle,): (Handle<()>,) = list.split(); }); scope(|list: Handle<((), InstanceCounter)>| { let (_empty, _counter): (Handle<()>, Handle) = list.split(); }); // Tests a size-12 tuple with varying List types inside it. #[allow(clippy::type_complexity)] scope( |list: Handle<( (), (), (), (), (), (), (), (), (), (), ((), ()), InstanceCounter, )>| { let ( _empty1, _empty2, _empty3, _empty4, _empty5, _empty6, _empty7, _empty8, _empty9, _empty10, pair, _counter, ): ( Handle<()>, Handle<()>, Handle<()>, Handle<()>, Handle<()>, Handle<()>, Handle<()>, Handle<()>, Handle<()>, Handle<()>, Handle<((), ())>, Handle, ) = list.split(); let (_empty11, _empty12): (Handle<()>, Handle<()>) = pair.split(); }, ); } ================================================ FILE: platform/src/share/tuple_impls.rs ================================================ /// Implements `share::List` and `share::SplittableHandle` on tuples of various /// sizes. use crate::share::{Handle, List, SplittableHandle}; // Implement List and SplittableHandle on empty tuples, because tuple_impl only // works for tuples of size 1 or greater. Empty lists may be useful in generic // and/or macro contexts. impl List for () {} impl<'handle> SplittableHandle<'handle> for () { type SplitHandles = (); fn split(_handle: Handle<'handle, ()>) {} } // Provides `share::List` and `share::SplittableHandle` impls for tuples of a // specific size. tuple_impls! must be provided with a list of names, and will // generate impls for tuples of the given length. macro_rules! tuple_impls { ($($name:ident),*) => { impl<$($name: List),*> List for ($($name),*,) {} impl<'handle, $($name: List + 'handle),*> SplittableHandle<'handle> for ($($name),*,) { type SplitHandles = ($(Handle<'handle, $name>),*,); fn split(handle: Handle<'handle, Self>) -> Self::SplitHandles { // Safety: handle guarantees that an instance of Self exists and // will be cleaned up before it becomes invalid. Self is a // tuple, and the types we are changing handle into are elements // of that tuple, so when the tuple is cleaned up they will be // cleaned up as well. ($(unsafe { handle.change_type::<$name>() }),*,) } } } } // Recursively calls tuple_impls for all tuples of a given length or shorter // (except the empty tuple, which tuple_impls doesn't support). macro_rules! impl_recursive { // Base case: if provided no names, do nothing. () => {}; // Recursive case. Calls tuple_impls, then call ourselves with one less // name. ($head_name:ident$(, $($tail_names:ident),*)?) => { tuple_impls!($head_name$(, $($tail_names),*)?); impl_recursive!($($($tail_names),*)?); }; } // Because List depends on Default, which is only implemented for tuples of <= // 12 elements, we can only implement List for tuples of up to length 12. impl_recursive!(A, B, C, D, E, F, G, H, I, J, K, L); ================================================ FILE: platform/src/subscribe.rs ================================================ use crate::share::List; use crate::ErrorCode; use crate::Syscalls; // ----------------------------------------------------------------------------- // `Subscribe` struct // ----------------------------------------------------------------------------- /// A `Subscribe` instance allows safe code to call Tock's Subscribe system /// call, by guaranteeing the upcall will be cleaned up before 'share ends. It /// is generally used with the `share::scope` function, which offers a safe /// interface for constructing `Subscribe` instances. pub struct Subscribe<'share, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> { _syscalls: core::marker::PhantomData, // Make this struct invariant with respect to the 'share lifetime. // // Covariance would be unsound, as that would allow code with a // `Subscribe<'static, ...>` to register an upcall that lasts for a shorter // lifetime, resulting in use-after-free if the upcall is invoked. // Contravariance would be sound, but is not necessary and may be confusing. // // Additionally, we want to have at least one private member of this struct // so that code outside this module cannot construct a `Subscribe` without // calling `ShareList::new`. _scope: core::marker::PhantomData>, } // We can't derive(Default) because S is not Default, and derive(Default) // generates a Default implementation that requires S to be Default. Instead, we // manually implement Default. impl<'share, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> Default for Subscribe<'share, S, DRIVER_NUM, SUBSCRIBE_NUM> { fn default() -> Self { Self { _syscalls: Default::default(), _scope: Default::default(), } } } impl<'share, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> Drop for Subscribe<'share, S, DRIVER_NUM, SUBSCRIBE_NUM> { fn drop(&mut self) { S::unsubscribe(DRIVER_NUM, SUBSCRIBE_NUM); } } impl<'share, S: Syscalls, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32> List for Subscribe<'share, S, DRIVER_NUM, SUBSCRIBE_NUM> { } // ----------------------------------------------------------------------------- // `Upcall` trait // ----------------------------------------------------------------------------- /// A Tock kernel upcall. Upcalls are registered using the Subscribe system /// call, and are invoked during Yield calls. /// /// Each `Upcall` supports one or more subscribe IDs, which are indicated by the /// `SupportedIds` parameter. The types `AnySubscribeId` and `OneSubscribeId` /// are provided to use as `SupportedIds` parameters in `Upcall` /// implementations. pub trait Upcall { fn upcall(&self, arg0: u32, arg1: u32, arg2: u32); } pub trait SupportsId {} pub struct AnyId; impl SupportsId for AnyId { } pub struct OneId; impl SupportsId for OneId { } // ----------------------------------------------------------------------------- // Upcall implementations that simply store their arguments // ----------------------------------------------------------------------------- /// An implementation of `Upcall` that sets the contained boolean value to /// `true` when the upcall is invoked. impl Upcall for core::cell::Cell { fn upcall(&self, _: u32, _: u32, _: u32) { self.set(true); } } /// Implemented for consistency with the other `Cell>` `Upcall` /// impls. Most users would prefer the `Cell` implementation over this /// impl, but this may be useful in a generic or macro context. impl Upcall for core::cell::Cell> { fn upcall(&self, _: u32, _: u32, _: u32) { self.set(Some(())); } } /// An `Upcall` implementation that stores its first argument when called. impl Upcall for core::cell::Cell> { fn upcall(&self, arg0: u32, _: u32, _: u32) { self.set(Some((arg0,))); } } /// An `Upcall` implementation that stores its first two arguments when called. impl Upcall for core::cell::Cell> { fn upcall(&self, arg0: u32, arg1: u32, _: u32) { self.set(Some((arg0, arg1))); } } /// An `Upcall` implementation that stores its arguments when called. impl Upcall for core::cell::Cell> { fn upcall(&self, arg0: u32, arg1: u32, arg2: u32) { self.set(Some((arg0, arg1, arg2))); } } /// An `Upcall` implementation that interprets its first argument as a return /// code. impl Upcall for core::cell::Cell>> { fn upcall(&self, arg0: u32, _: u32, _: u32) { self.set(Some(match arg0 { 0 => Ok(()), _ => Err(arg0.try_into().unwrap_or(ErrorCode::Fail)), })); } } /// An `Upcall` implementation that interprets its first argument as a return /// code and stores its second argument when called. impl Upcall for core::cell::Cell>> { fn upcall(&self, arg0: u32, arg1: u32, _: u32) { self.set(Some(match arg0 { 0 => Ok((arg1,)), _ => Err(arg0.try_into().unwrap_or(ErrorCode::Fail)), })); } } /// An `Upcall` implementation that interprets its first argument as a return /// code and stores its second argument when called. impl Upcall for core::cell::Cell>> { fn upcall(&self, arg0: u32, arg1: u32, arg2: u32) { self.set(Some(match arg0 { 0 => Ok((arg1, arg2)), _ => Err(arg0.try_into().unwrap_or(ErrorCode::Fail)), })); } } #[cfg(test)] #[test] fn upcall_impls() { let cell_bool = core::cell::Cell::new(false); cell_bool.upcall(1, 2, 3); assert!(cell_bool.get()); let cell_empty = core::cell::Cell::new(None); cell_empty.upcall(1, 2, 3); assert_eq!(cell_empty.get(), Some(())); let cell_one = core::cell::Cell::new(None); cell_one.upcall(1, 2, 3); assert_eq!(cell_one.get(), Some((1,))); let cell_two = core::cell::Cell::new(None); cell_two.upcall(1, 2, 3); assert_eq!(cell_two.get(), Some((1, 2))); let cell_three = core::cell::Cell::new(None); cell_three.upcall(1, 2, 3); assert_eq!(cell_three.get(), Some((1, 2, 3))); let cell_returncode_empty_success: core::cell::Cell>> = core::cell::Cell::new(None); cell_returncode_empty_success.upcall(0, 2, 3); assert_eq!(cell_returncode_empty_success.get(), Some(Ok(()))); let cell_returncode_empty_fail: core::cell::Cell>> = core::cell::Cell::new(None); cell_returncode_empty_fail.upcall(1, 2, 3); assert_eq!(cell_returncode_empty_fail.get(), Some(Err(ErrorCode::Fail))); let cell_returncode_one_success: core::cell::Cell>> = core::cell::Cell::new(None); cell_returncode_one_success.upcall(0, 2, 3); assert_eq!(cell_returncode_one_success.get(), Some(Ok((2,)))); let cell_returncode_one_fail: core::cell::Cell>> = core::cell::Cell::new(None); cell_returncode_one_fail.upcall(1, 2, 3); assert_eq!(cell_returncode_one_fail.get(), Some(Err(ErrorCode::Fail))); let cell_returncode_two_success: core::cell::Cell>> = core::cell::Cell::new(None); cell_returncode_two_success.upcall(0, 2, 3); assert_eq!(cell_returncode_two_success.get(), Some(Ok((2, 3)))); let cell_returncode_two_fail: core::cell::Cell>> = core::cell::Cell::new(None); cell_returncode_two_fail.upcall(1, 2, 3); assert_eq!(cell_returncode_two_fail.get(), Some(Err(ErrorCode::Fail))); } // ----------------------------------------------------------------------------- // `Config` trait // ----------------------------------------------------------------------------- /// `Config` configures the behavior of the Subscribe system call. It should /// generally be passed through by drivers, to allow application code to /// configure error handling. pub trait Config { /// Called if a Subscribe call succeeds and returns a non-null upcall. In /// some applications, this may indicate unexpected reentrance. By default, /// the non-null upcall is ignored. fn returned_nonnull_upcall(_driver_num: u32, _subscribe_num: u32) {} } ================================================ FILE: platform/src/syscalls.rs ================================================ use crate::{ allow_ro, allow_rw, share, subscribe, AllowRo, AllowRw, CommandReturn, ErrorCode, RawSyscalls, Subscribe, Upcall, YieldNoWaitReturn, }; /// `Syscalls` provides safe abstractions over Tock's system calls. It is /// implemented for `libtock_runtime::TockSyscalls` and /// `libtock_unittest::fake::Kernel` (by way of `RawSyscalls`). pub trait Syscalls: RawSyscalls + Sized { // ------------------------------------------------------------------------- // Yield // ------------------------------------------------------------------------- /// Runs the next pending callback, if a callback is pending. Unlike /// `yield_wait`, `yield_no_wait` returns immediately if no callback is /// pending. fn yield_no_wait() -> YieldNoWaitReturn; /// Puts the process to sleep until a callback becomes pending, invokes the /// callback, then returns. fn yield_wait(); // ------------------------------------------------------------------------- // Subscribe // ------------------------------------------------------------------------- /// Registers an upcall with the kernel. fn subscribe< 'share, IDS: subscribe::SupportsId, U: Upcall, CONFIG: subscribe::Config, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32, >( subscribe: share::Handle>, upcall: &'share U, ) -> Result<(), ErrorCode>; /// Unregisters the upcall with the given ID. If no upcall is registered /// with the given ID, `unsubscribe` does nothing. fn unsubscribe(driver_num: u32, subscribe_num: u32); // ------------------------------------------------------------------------- // Command // ------------------------------------------------------------------------- fn command(driver_id: u32, command_id: u32, argument0: u32, argument1: u32) -> CommandReturn; // ------------------------------------------------------------------------- // Read-Write Allow // ------------------------------------------------------------------------- /// Shares a read-write buffer with the kernel. fn allow_rw<'share, CONFIG: allow_rw::Config, const DRIVER_NUM: u32, const BUFFER_NUM: u32>( allow_rw: share::Handle>, buffer: &'share mut [u8], ) -> Result<(), ErrorCode>; /// Revokes the kernel's access to the buffer with the given ID, overwriting /// it with a zero buffer. If no buffer is shared with the given ID, /// `unallow_rw` does nothing. fn unallow_rw(driver_num: u32, buffer_num: u32); // ------------------------------------------------------------------------- // Read-Only Allow // ------------------------------------------------------------------------- /// Shares a read-only buffer with the kernel. fn allow_ro<'share, CONFIG: allow_ro::Config, const DRIVER_NUM: u32, const BUFFER_NUM: u32>( allow_ro: share::Handle>, buffer: &'share [u8], ) -> Result<(), ErrorCode>; /// Revokes the kernel's access to the buffer with the given ID, overwriting /// it with a zero buffer. If no buffer is shared with the given ID, /// `unallow_ro` does nothing. fn unallow_ro(driver_num: u32, buffer_num: u32); // ------------------------------------------------------------------------- // Memop // ------------------------------------------------------------------------- /// Changes the location of the program break to the specified address and /// returns an error if it fails to do so. /// /// # Safety /// Callers of this function must ensure that they do not pass an /// address below any address that includes a currently reachable object. unsafe fn memop_brk(addr: *const u8) -> Result<(), ErrorCode>; /// Changes the location of the program break by the passed increment, /// and returns the previous break address. /// /// # Safety /// Callers of this function must ensure that they do not pass an /// increment that would deallocate memory containing any currently /// reachable object. unsafe fn memop_sbrk(incr: i32) -> Result<*const u8, ErrorCode>; /// Increments the program break by the passed increment, /// and returns the previous break address. fn memop_increment_brk(incr: u32) -> Result<*const u8, ErrorCode>; /// Gets the address of the start of this application's RAM allocation. fn memop_app_ram_start() -> Result<*const u8, ErrorCode>; /// Tells the kernel where the start of the app stack is, to support /// debugging. fn memop_debug_stack_start(stack_top: *const u8) -> Result<(), ErrorCode>; /// Tells the kernel the initial program break, to support debugging. fn memop_debug_heap_start(initial_break: *const u8) -> Result<(), ErrorCode>; // TODO: Add remaining memop() methods (3-9). // ------------------------------------------------------------------------- // Exit // ------------------------------------------------------------------------- fn exit_terminate(exit_code: u32) -> !; fn exit_restart(exit_code: u32) -> !; } ================================================ FILE: platform/src/syscalls_impl.rs ================================================ //! Implements `Syscalls` for all types that implement `RawSyscalls`. use crate::{ allow_ro, allow_rw, exit_id, exit_on_drop, return_variant, share, subscribe, syscall_class, yield_id, AllowRo, AllowRw, CommandReturn, ErrorCode, RawSyscalls, Register, ReturnVariant, Subscribe, Syscalls, Upcall, YieldNoWaitReturn, }; impl Syscalls for S { // ------------------------------------------------------------------------- // Yield // ------------------------------------------------------------------------- fn yield_no_wait() -> YieldNoWaitReturn { let mut flag = core::mem::MaybeUninit::::uninit(); unsafe { // Flag can be uninitialized here because the kernel promises to // only write to it, not read from it. MaybeUninit guarantees that // it is safe to write a YieldNoWaitReturn into it. Self::yield2([yield_id::NO_WAIT.into(), flag.as_mut_ptr().into()]); // yield-no-wait guarantees it sets (initializes) flag before // returning. flag.assume_init() } } fn yield_wait() { // Safety: yield-wait does not return a value, which satisfies yield1's // requirement. The yield-wait system call cannot trigger undefined // behavior on its own in any other way. unsafe { Self::yield1([yield_id::WAIT.into()]); } } // ------------------------------------------------------------------------- // Subscribe // ------------------------------------------------------------------------- fn subscribe< 'share, IDS: subscribe::SupportsId, U: Upcall, CONFIG: subscribe::Config, const DRIVER_NUM: u32, const SUBSCRIBE_NUM: u32, >( _subscribe: share::Handle>, upcall: &'share U, ) -> Result<(), ErrorCode> { // The upcall function passed to the Tock kernel. // // Safety: data must be a reference to a valid instance of U. unsafe extern "C" fn kernel_upcall>( arg0: u32, arg1: u32, arg2: u32, data: Register, ) { let exit: exit_on_drop::ExitOnDrop = Default::default(); let upcall: *const U = data.into(); unsafe { &*upcall }.upcall(arg0, arg1, arg2); core::mem::forget(exit); } // Inner function that does the majority of the work. This is not // monomorphized over DRIVER_NUM and SUBSCRIBE_NUM to keep code size // small. // // Safety: upcall_fcn must be kernel_upcall and upcall_data // must be a reference to an instance of U that will remain valid as // long as the 'scope lifetime is alive. Can only be called if a // Subscribe<'scope, S, driver_num, subscribe_num> exists. unsafe fn inner( driver_num: u32, subscribe_num: u32, upcall_fcn: Register, upcall_data: Register, ) -> Result<(), ErrorCode> { // Safety: syscall4's documentation indicates it can be used to call // Subscribe. These arguments follow TRD104. kernel_upcall has the // required signature. This function's preconditions mean that // upcall is a reference to an instance of U that will remain valid // until the 'scope lifetime is alive The existence of the // Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM> guarantees // that if this Subscribe succeeds then the upcall will be cleaned // up before the 'scope lifetime ends, guaranteeing that upcall is // still alive when kernel_upcall is invoked. let [r0, r1, _, _] = unsafe { S::syscall4::<{ syscall_class::SUBSCRIBE }>([ driver_num.into(), subscribe_num.into(), upcall_fcn, upcall_data, ]) }; let return_variant: ReturnVariant = r0.as_u32().into(); // TRD 104 guarantees that Subscribe returns either Success with 2 // U32 or Failure with 2 U32. We check the return variant by // comparing against Failure with 2 U32 for 2 reasons: // // 1. On RISC-V with compressed instructions, it generates smaller // code. FAILURE_2_U32 has value 2, which can be loaded into a // register with a single compressed instruction, whereas // loading SUCCESS_2_U32 uses an uncompressed instruction. // 2. In the event the kernel malfuctions and returns a different // return variant, the success path is actually safer than the // failure path. The failure path assumes that r1 contains an // ErrorCode, and produces UB if it has an out of range value. // Incorrectly assuming the call succeeded will not generate // unsoundness, and will likely lead to the application // hanging. if return_variant == return_variant::FAILURE_2_U32 { // Safety: TRD 104 guarantees that if r0 is Failure with 2 U32, // then r1 will contain a valid error code. ErrorCode is // designed to be safely transmuted directly from a kernel error // code. return Err(unsafe { core::mem::transmute::(r1.as_u32()) }); } // r0 indicates Success with 2 u32s. Confirm the null upcall was // returned, and it if wasn't then call the configured function. // We're relying on the optimizer to remove this branch if // returned_nonnull_upcall is a no-op. // Note: TRD 104 specifies that the null upcall has address 0, // not necessarily a null pointer. let returned_upcall: usize = r1.into(); if returned_upcall != 0usize { CONFIG::returned_nonnull_upcall(driver_num, subscribe_num); } Ok(()) } let upcall_fcn = (kernel_upcall:: as *const ()).into(); let upcall_data = (upcall as *const U).into(); // Safety: upcall's type guarantees it is a reference to a U that will // remain valid for at least the 'scope lifetime. _subscribe is a // reference to a Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>, // proving one exists. upcall_fcn and upcall_data are derived in ways // that satisfy inner's requirements. unsafe { inner::(DRIVER_NUM, SUBSCRIBE_NUM, upcall_fcn, upcall_data) } } fn unsubscribe(driver_num: u32, subscribe_num: u32) { unsafe { // syscall4's documentation indicates it can be used to call // Subscribe. The upcall pointer passed is the null upcall, which // cannot cause undefined behavior on its own. Self::syscall4::<{ syscall_class::SUBSCRIBE }>([ driver_num.into(), subscribe_num.into(), 0usize.into(), 0usize.into(), ]); } } // ------------------------------------------------------------------------- // Command // ------------------------------------------------------------------------- fn command(driver_id: u32, command_id: u32, argument0: u32, argument1: u32) -> CommandReturn { unsafe { // syscall4's documentation indicates it can be used to call // Command. The Command system call cannot trigger undefined // behavior on its own. let [r0, r1, r2, r3] = Self::syscall4::<{ syscall_class::COMMAND }>([ driver_id.into(), command_id.into(), argument0.into(), argument1.into(), ]); // Because r0 and r1 are returned directly from the kernel, we are // guaranteed that if r0 represents a failure variant then r1 is an // error code. CommandReturn::new(r0.as_u32().into(), r1.as_u32(), r2.as_u32(), r3.as_u32()) } } // ------------------------------------------------------------------------- // Read-Write Allow // ------------------------------------------------------------------------- fn allow_rw<'share, CONFIG: allow_rw::Config, const DRIVER_NUM: u32, const BUFFER_NUM: u32>( _allow_rw: share::Handle>, buffer: &'share mut [u8], ) -> Result<(), ErrorCode> { // Inner function that does the majority of the work. This is not // monomorphized over DRIVER_NUM and BUFFER_NUM to keep code size small. // // Safety: A share::Handle> // must exist, and `buffer` must last for at least the 'share lifetime. unsafe fn inner( driver_num: u32, buffer_num: u32, buffer: &mut [u8], ) -> Result<(), ErrorCode> { // Safety: syscall4's documentation indicates it can be used to call // Read-Write Allow. These arguments follow TRD104. let [r0, r1, r2, _] = unsafe { S::syscall4::<{ syscall_class::ALLOW_RW }>([ driver_num.into(), buffer_num.into(), buffer.as_mut_ptr().into(), buffer.len().into(), ]) }; let return_variant: ReturnVariant = r0.as_u32().into(); // TRD 104 guarantees that Read-Write Allow returns either Success // with 2 U32 or Failure with 2 U32. We check the return variant by // comparing against Failure with 2 U32 for 2 reasons: // // 1. On RISC-V with compressed instructions, it generates smaller // code. FAILURE_2_U32 has value 2, which can be loaded into a // register with a single compressed instruction, whereas // loading SUCCESS_2_U32 uses an uncompressed instruction. // 2. In the event the kernel malfuctions and returns a different // return variant, the success path is actually safer than the // failure path. The failure path assumes that r1 contains an // ErrorCode, and produces UB if it has an out of range value. // Incorrectly assuming the call succeeded will not generate // unsoundness, and will likely lead to the application // panicing. if return_variant == return_variant::FAILURE_2_U32 { // Safety: TRD 104 guarantees that if r0 is Failure with 2 U32, // then r1 will contain a valid error code. ErrorCode is // designed to be safely transmuted directly from a kernel error // code. return Err(unsafe { core::mem::transmute::(r1.as_u32()) }); } // r0 indicates Success with 2 u32s. Confirm a zero buffer was // returned, and it if wasn't then call the configured function. // We're relying on the optimizer to remove this branch if // returned_nozero_buffer is a no-op. let returned_buffer: (usize, usize) = (r1.into(), r2.into()); if returned_buffer != (0, 0) { CONFIG::returned_nonzero_buffer(driver_num, buffer_num); } Ok(()) } // Safety: The presence of the share::Handle> // guarantees that an AllowRw exists and will clean up this Allow ID // before the 'share lifetime ends. unsafe { inner::(DRIVER_NUM, BUFFER_NUM, buffer) } } fn unallow_rw(driver_num: u32, buffer_num: u32) { unsafe { // syscall4's documentation indicates it can be used to call // Read-Write Allow. The buffer passed has 0 length, which cannot // cause undefined behavior on its own. Self::syscall4::<{ syscall_class::ALLOW_RW }>([ driver_num.into(), buffer_num.into(), 0usize.into(), 0usize.into(), ]); } } // ------------------------------------------------------------------------- // Read-Only Allow // ------------------------------------------------------------------------- fn allow_ro<'share, CONFIG: allow_ro::Config, const DRIVER_NUM: u32, const BUFFER_NUM: u32>( _allow_ro: share::Handle>, buffer: &'share [u8], ) -> Result<(), ErrorCode> { // Inner function that does the majority of the work. This is not // monomorphized over DRIVER_NUM and BUFFER_NUM to keep code size small. // // Security note: The syscall driver will retain read-only access to // `*buffer` until this Allow ID is unallowed or overwritten via another // Allow call. Therefore the caller must ensure the Allow ID is // unallowed or overwritten before `*buffer` is deallocated, to avoid // leaking newly-allocated information at the same address as `*buffer`. fn inner( driver_num: u32, buffer_num: u32, buffer: &[u8], ) -> Result<(), ErrorCode> { // Safety: syscall4's documentation indicates it can be used to call // Read-Only Allow. These arguments follow TRD104. let [r0, r1, r2, _] = unsafe { S::syscall4::<{ syscall_class::ALLOW_RO }>([ driver_num.into(), buffer_num.into(), buffer.as_ptr().into(), buffer.len().into(), ]) }; let return_variant: ReturnVariant = r0.as_u32().into(); // TRD 104 guarantees that Read-Only Allow returns either Success // with 2 U32 or Failure with 2 U32. We check the return variant by // comparing against Failure with 2 U32 for 2 reasons: // // 1. On RISC-V with compressed instructions, it generates smaller // code. FAILURE_2_U32 has value 2, which can be loaded into a // register with a single compressed instruction, whereas // loading SUCCESS_2_U32 uses an uncompressed instruction. // 2. In the event the kernel malfuctions and returns a different // return variant, the success path is actually safer than the // failure path. The failure path assumes that r1 contains an // ErrorCode, and produces UB if it has an out of range value. // Incorrectly assuming the call succeeded will not generate // unsoundness, and will likely lead to the application // panicing. if return_variant == return_variant::FAILURE_2_U32 { // Safety: TRD 104 guarantees that if r0 is Failure with 2 U32, // then r1 will contain a valid error code. ErrorCode is // designed to be safely transmuted directly from a kernel error // code. return Err(unsafe { core::mem::transmute::(r1.as_u32()) }); } // r0 indicates Success with 2 u32s. Confirm a zero buffer was // returned, and it if wasn't then call the configured function. // We're relying on the optimizer to remove this branch if // returned_nozero_buffer is a no-op. let returned_buffer: (usize, usize) = (r1.into(), r2.into()); if returned_buffer != (0, 0) { CONFIG::returned_nonzero_buffer(driver_num, buffer_num); } Ok(()) } // Security: The presence of the share::Handle> // guarantees that an AllowRo exists and will clean up this Allow ID // before the 'share lifetime ends. inner::(DRIVER_NUM, BUFFER_NUM, buffer) } fn unallow_ro(driver_num: u32, buffer_num: u32) { unsafe { // syscall4's documentation indicates it can be used to call // Read-Only Allow. The buffer passed has 0 length, which cannot // cause undefined behavior on its own. Self::syscall4::<{ syscall_class::ALLOW_RO }>([ driver_num.into(), buffer_num.into(), 0usize.into(), 0usize.into(), ]); } } // ------------------------------------------------------------------------- // Memop // ------------------------------------------------------------------------- // Safety: Callers of this function must ensure that they do not pass an // address below any address that includes a currently reachable object. unsafe fn memop_brk(addr: *const u8) -> Result<(), ErrorCode> { // Safety: syscall2's documentation indicates it can be used to call Memop. let [r0, r1] = unsafe { Self::syscall2::<{ syscall_class::MEMOP }>([0u32.into(), addr.into()]) }; let return_variant: ReturnVariant = r0.as_u32().into(); // TRD 104 guarantees that memop 0, 10, and 11 return either Success // or Failure. We check the return variant by comparing against Failure // for 2 reasons: // // 1. On RISC-V with compressed instructions, it generates smaller // code. FAILURE has value 0, which can be loaded into a // register with a single compressed instruction, whereas // loading SUCCESS uses an uncompressed instruction. // 2. In the event the kernel malfunctions and returns a different // return variant, the success path is actually safer than the // failure path. The failure path assumes that r1 contains an // ErrorCode, and produces UB if it has an out of range value. if return_variant == return_variant::FAILURE { // Safety: TRD 104 guarantees that if r0 is Failure, // then r1 will contain a valid error code. ErrorCode is // designed to be safely transmuted directly from a kernel error // code. Err(unsafe { core::mem::transmute::(r1.as_u32()) }) } else { Ok(()) } } // Safety: Callers of this function must ensure that they do not pass an // increment that would deallocate memory containing any currently // reachable object. unsafe fn memop_sbrk(incr: i32) -> Result<*const u8, ErrorCode> { // Safety: syscall2's documentation indicates it can be used to call Memop. let [r0, r1] = unsafe { Self::syscall2::<{ syscall_class::MEMOP }>([1u32.into(), incr.into()]) }; let return_variant: ReturnVariant = r0.as_u32().into(); // TRD 104 guarantees that memop 1, returns either Success with U32 // or Failure. We check the return variant by comparing against Failure // for 1 reason: // // 1. On RISC-V with compressed instructions, it generates smaller // code. FAILURE has value 0, which can be loaded into a // register with a single compressed instruction, whereas // loading SUCCESS_U32 uses an uncompressed instruction. if return_variant == return_variant::FAILURE { // Safety: TRD 104 guarantees that if r0 is Failure, // then r1 will contain a valid error code. ErrorCode is // designed to be safely transmuted directly from a kernel error // code. Err(unsafe { core::mem::transmute::(r1.as_u32()) }) } else { Ok(r1.into()) } } fn memop_increment_brk(incr: u32) -> Result<*const u8, ErrorCode> { // Safety: memop_sbrk is safe if the passed increment is positive unsafe { Self::memop_sbrk(i32::try_from(incr).map_err(|_| ErrorCode::Invalid)?) } } fn memop_app_ram_start() -> Result<*const u8, ErrorCode> { // Safety: syscall1's documentation indicates it can be used to call Memop operations // that only accept a memop operation number. let [r0, r1] = unsafe { Self::syscall1::<{ syscall_class::MEMOP }>([2u32.into()]) }; let return_variant: ReturnVariant = r0.as_u32().into(); // TRD 104 guarantees that memop 2 returns either Success // or Failure. We check the return variant by comparing against Failure // for 1 reason: // // 1. On RISC-V with compressed instructions, it generates smaller // code. FAILURE has value 0, which can be loaded into a // register with a single compressed instruction, whereas // loading SUCCESS_U32 uses an uncompressed instruction. if return_variant == return_variant::FAILURE { // Safety: TRD 104 guarantees that if r0 is Failure, // then r1 will contain a valid error code. ErrorCode is // designed to be safely transmuted directly from a kernel error // code. Err(unsafe { core::mem::transmute::(r1.as_u32()) }) } else { Ok(r1.into()) } } fn memop_debug_stack_start(stack_top: *const u8) -> Result<(), ErrorCode> { // Safety: syscall2's documentation indicates it can be used to call Memop. let [r0, r1] = unsafe { Self::syscall2::<{ syscall_class::MEMOP }>([10u32.into(), stack_top.into()]) }; let return_variant: ReturnVariant = r0.as_u32().into(); // TRD 104 guarantees that memop 0, 10, and 11 return either Success // or Failure. We check the return variant by comparing against Failure // for 2 reasons: // // 1. On RISC-V with compressed instructions, it generates smaller // code. FAILURE has value 0, which can be loaded into a // register with a single compressed instruction, whereas // loading SUCCESS uses an uncompressed instruction. // 2. In the event the kernel malfunctions and returns a different // return variant, the success path is actually safer than the // failure path. The failure path assumes that r1 contains an // ErrorCode, and produces UB if it has an out of range value. if return_variant == return_variant::FAILURE { // Safety: TRD 104 guarantees that if r0 is Failure, // then r1 will contain a valid error code. ErrorCode is // designed to be safely transmuted directly from a kernel error // code. Err(unsafe { core::mem::transmute::(r1.as_u32()) }) } else { Ok(()) } } fn memop_debug_heap_start(initial_break: *const u8) -> Result<(), ErrorCode> { // Safety: syscall2's documentation indicates it can be used to call Memop. let [r0, r1] = unsafe { Self::syscall2::<{ syscall_class::MEMOP }>([11u32.into(), initial_break.into()]) }; let return_variant: ReturnVariant = r0.as_u32().into(); // TRD 104 guarantees that memop 0, 10, and 11 return either Success // or Failure. We check the return variant by comparing against Failure // for 2 reasons: // // 1. On RISC-V with compressed instructions, it generates smaller // code. FAILURE has value 0, which can be loaded into a // register with a single compressed instruction, whereas // loading SUCCESS uses an uncompressed instruction. // 2. In the event the kernel malfunctions and returns a different // return variant, the success path is actually safer than the // failure path. The failure path assumes that r1 contains an // ErrorCode, and produces UB if it has an out of range value. if return_variant == return_variant::FAILURE { // Safety: TRD 104 guarantees that if r0 is Failure, // then r1 will contain a valid error code. ErrorCode is // designed to be safely transmuted directly from a kernel error // code. Err(unsafe { core::mem::transmute::(r1.as_u32()) }) } else { Ok(()) } } // ------------------------------------------------------------------------- // Exit // ------------------------------------------------------------------------- fn exit_terminate(exit_code: u32) -> ! { unsafe { // syscall2's documentation indicates it can be used to call Exit. // The exit system call cannot trigger undefined behavior on its // own. Self::syscall2::<{ syscall_class::EXIT }>([ exit_id::TERMINATE.into(), exit_code.into(), ]); // TRD104 indicates that exit-terminate MUST always succeed and so // never return. core::hint::unreachable_unchecked() } } fn exit_restart(exit_code: u32) -> ! { unsafe { // syscall2's documentation indicates it can be used to call Exit. // The exit system call cannot trigger undefined behavior on its // own. Self::syscall2::<{ syscall_class::EXIT }>([exit_id::RESTART.into(), exit_code.into()]); // TRD104 indicates that exit-restart MUST always succeed and so // never return. core::hint::unreachable_unchecked() } } } ================================================ FILE: platform/src/termination.rs ================================================ //! Definition of the Termination trait. The main() function (set using set_main!()) //! must return a type that implements Termination. use crate::{ErrorCode, Syscalls}; pub trait Termination { fn complete(self) -> !; } impl Termination for () { fn complete(self) -> ! { S::exit_terminate(0) } } impl Termination for Result<(), ErrorCode> { fn complete(self) -> ! { let exit_code = match self { Ok(()) => 0, Err(ec) => ec as u32, }; S::exit_terminate(exit_code); } } ================================================ FILE: platform/src/yield_types.rs ================================================ /// The return value from a yield_no_wait call. // Calling yield-no-wait passes a *mut YieldNoWaitReturn to the kernel, which // the kernel writes to. We cannot safely pass a `*mut bool` to the kernel, // because the representation of `bool` in Rust is undefined (although it is // likely false == 0, true == 1, based on `bool`'s conversions). Using *mut // YieldNoWaitReturn rather than a *mut u8 allows the compiler to assume the // kernel will never write a value other than 0 or 1 into the pointee. Assuming // the likely representation of `bool`, this makes the conversion into `bool` // free. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[repr(u8)] pub enum YieldNoWaitReturn { NoUpcall = 0, Upcall = 1, } ================================================ FILE: runner/Cargo.toml ================================================ [package] authors = ["Tock Project Developers "] description = """Tool used to run libtock-rs process binaries.""" edition = "2021" license = "Apache-2.0 OR MIT" name = "runner" publish = false repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true version = "0.1.0" [dependencies] clap = { features = ["derive"], version = "3.2.6" } elf = "0.0.10" libc = "0.2.113" termion = "1.5.6" ================================================ FILE: runner/src/elf2tab.rs ================================================ use super::Cli; use std::fs::{metadata, remove_file}; use std::io::ErrorKind; use std::path::PathBuf; use std::process::Command; fn get_platform_architecture(platform: &str) -> Option<&'static str> { match platform { "raspberry_pi_pico" | "pico_explorer_base" | "nano_rp2040_connect" => Some("cortex-m0"), "apollo3" | "clue_nrf52840" | "hail" | "imix" | "microbit_v2" | "msp432" | "nano33ble" | "nrf52" | "nrf52840" | "nucleo_f429zi" | "nucleo_f446re" | "stm32f3discovery" | "stm32f412gdiscovery" => Some("cortex-m4"), "imxrt1050" | "teensy40" => Some("cortex-m7"), "opentitan" | "esp32_c3_devkitm_1" => Some("riscv32imc"), "hifive1" | "qemu_rv32_virt" => Some("riscv32imac"), _ => None, } } // Converts the ELF file specified on the command line into TBF and TAB files, // and returns the paths to those files. pub fn convert_elf(cli: &Cli, platform: &str) -> OutFiles { let package_name = cli.elf.file_stem().expect("ELF must be a file"); let mut tab_path = cli.elf.clone(); tab_path.set_extension("tab"); if cli.verbose { println!("Package name: {package_name:?}"); println!("TAB path: {}", tab_path.display()); } let stack_size = read_stack_size(cli); let elf = cli.elf.as_os_str(); let mut tbf_path = cli.elf.clone(); tbf_path.set_extension("tbf"); let architecture = get_platform_architecture(platform).expect("Failed to determine ELF's architecture"); if cli.verbose { println!("ELF file: {elf:?}"); println!("TBF path: {}", tbf_path.display()); } // If elf2tab returns a successful status but does not write to the TBF // file, then we run the risk of using an outdated TBF file, creating a // hard-to-debug situation. Therefore, we delete the TBF file, forcing // elf2tab to create it, and later verify that it exists. if let Err(io_error) = remove_file(&tbf_path) { // Ignore file-no-found errors, panic on any other error. if io_error.kind() != ErrorKind::NotFound { panic!("Unable to remove the TBF file. Error: {io_error}"); } } let mut command = Command::new("elf2tab"); #[rustfmt::skip] command.args([ // TODO: libtock-rs' crates are designed for Tock 2.1's Allow interface, // so we should increment this as soon as the Tock kernel will accept a // 2.1 app. "--kernel-major".as_ref(), "2".as_ref(), "--kernel-minor".as_ref(), "0".as_ref(), "-n".as_ref(), package_name, "-o".as_ref(), tab_path.as_os_str(), "--stack".as_ref(), stack_size.as_ref(), format!("{},{}", elf.to_str().unwrap(), architecture).as_ref(), ]); if cli.verbose { command.arg("-v"); println!("elf2tab command: {command:?}"); println!("Spawning elf2tab"); } let mut child = command.spawn().expect("failed to spawn elf2tab"); let status = child.wait().expect("failed to wait for elf2tab"); if cli.verbose { println!("elf2tab finished. {status}"); } assert!(status.success(), "elf2tab returned an error. {status}"); // Verify that elf2tab created the TBF file, and that it is a file. match metadata(&tbf_path) { Err(io_error) => { if io_error.kind() == ErrorKind::NotFound { panic!("elf2tab did not create {}", tbf_path.display()); } panic!( "Unable to query metadata for {}: {}", tbf_path.display(), io_error ); } Ok(metadata) => { assert!(metadata.is_file(), "{} is not a file", tbf_path.display()); } } OutFiles { tab_path, tbf_path } } // Paths to the files output by elf2tab. pub struct OutFiles { pub tab_path: PathBuf, pub tbf_path: PathBuf, } // Reads the stack size, and returns it as a String for use on elf2tab's command // line. fn read_stack_size(cli: &Cli) -> String { let file = elf::File::open_path(&cli.elf).expect("Unable to open ELF"); for section in file.sections { // This section name comes from runtime/libtock_layout.ld, and it // matches the size (and location) of the process binary's stack. if section.shdr.name == ".stack" { let stack_size = section.shdr.size.to_string(); if cli.verbose { println!("Found .stack section, size: {stack_size}"); } return stack_size; } } panic!("Unable to find the .stack section in {}", cli.elf.display()); } ================================================ FILE: runner/src/main.rs ================================================ mod elf2tab; mod output_processor; mod qemu; mod tockloader; use clap::{Parser, ValueEnum}; use std::env::{var, VarError}; use std::path::PathBuf; /// Converts ELF binaries into Tock Binary Format binaries and runs them on a /// Tock system. #[derive(Debug, Parser)] pub struct Cli { /// Where to deploy the process binary. If not specified, runner will only /// make a TBF file and not attempt to run it. #[clap(action, long, short, value_enum)] deploy: Option, /// The executable to convert into Tock Binary Format and run. #[clap(action)] elf: PathBuf, /// Whether to output verbose debugging information to the console. #[clap(long, short, action)] verbose: bool, } #[derive(ValueEnum, Clone, Copy, Debug)] pub enum Deploy { Qemu, Tockloader, } fn main() { let cli = Cli::parse(); let platform = match var("LIBTOCK_PLATFORM") { Err(VarError::NotPresent) => { panic!("LIBTOCK_PLATFORM must be specified to deploy") } Err(VarError::NotUnicode(platform)) => { panic!("Non-UTF-8 LIBTOCK_PLATFORM value: {platform:?}") } Ok(platform) => platform, }; if cli.verbose { println!("Detected platform {platform}"); } let paths = elf2tab::convert_elf(&cli, &platform); let deploy = match cli.deploy { None => return, Some(deploy) => deploy, }; let child = match deploy { Deploy::Qemu => qemu::deploy(&cli, platform, paths.tbf_path), Deploy::Tockloader => tockloader::deploy(&cli, platform, paths.tab_path), }; output_processor::process(&cli, child); } ================================================ FILE: runner/src/output_processor.rs ================================================ use super::Cli; use libc::{kill, pid_t, SIGINT}; use std::io::{stderr, stdin, stdout, BufRead, BufReader, ErrorKind, Stdout, Write}; use std::process::Child; use std::thread::spawn; use termion::raw::{IntoRawMode, RawTerminal}; /// Reads the console messages from `child`'s standard output, sending SIGTERM /// to the child when the process is terminated. pub fn process(cli: &Cli, mut child: Child) { let raw_mode = forward_stdin_if_piped(&mut child); forward_stderr_if_piped(&mut child, raw_mode.is_some()); let mut to_print = Vec::new(); let mut reader = BufReader::new(child.stdout.as_mut().expect("Child's stdout not piped.")); loop { let buffer = reader .fill_buf() .expect("Unable to read from child process."); if buffer.is_empty() { // The child process has closed its stdout, likely by exiting. break; } // Print the bytes received over stdout. If the terminal is in raw mode, // translate '\n' into '\r\n'. for &byte in buffer { if raw_mode.is_some() && byte == b'\n' { to_print.push(b'\r'); } to_print.push(byte); } let stdout = stdout(); let mut lock = stdout.lock(); lock.write_all(&to_print) .expect("Unable to echo child's stdout."); let _ = lock.flush(); drop(lock); to_print.clear(); let buffer_len = buffer.len(); reader.consume(buffer_len); } if cli.verbose { println!("Waiting for child process.\r"); } let status = child.wait().expect("Unable to wait for child process"); drop(raw_mode); assert!( status.success(), "Child process did not exit successfully. {status}" ); } // If child's stdin is piped, this sets the terminal to raw mode and spawns a // thread that forwards our stdin to child's stdin. The thread sends SIGINT to // the child if Ctrl+C is pressed. Returns a RawTerminal, which reverts the // terminal to its previous configuration on drop. fn forward_stdin_if_piped(child: &mut Child) -> Option> { let mut child_stdin = child.stdin.take()?; let child_id = child.id(); spawn(move || { let our_stdin = stdin(); let mut our_stdin = our_stdin.lock(); loop { let buffer = our_stdin.fill_buf().expect("Failed to read stdin."); if buffer.is_empty() { // Our stdin was closed. We interpret this as a signal to exit, // because pressing Ctrl+C to trigger an exit is no longer // possible. break; } // In raw mode, pressing Ctrl+C will send a '3' byte to stdin ("end // of message" ASCII value) instead of sending SIGINT. Identify that // case, and exit if it occurs. if buffer.contains(&3) { break; } match child_stdin.write(buffer) { // A BrokenPipe error occurs when the child has exited. Exit // without sending SIGINT. Err(error) if error.kind() == ErrorKind::BrokenPipe => return, Err(error) => panic!("Failed to forward stdin: {error}"), Ok(bytes) => our_stdin.consume(bytes), } } // Send SIGINT to the child, telling it to exit. After the child exits, // the main loop will detect the exit and we will shut down cleanly. // // Safety: Sending SIGINT to a process is a safe operation -- kill is // marked unsafe because it is a FFI function. unsafe { kill(child_id as pid_t, SIGINT); } }); Some( stdout() .into_raw_mode() .expect("Failed to set terminal to raw mode."), ) } // Forwards child's stderr to our stderr if child's stderr is piped, converting // line endings to CRLF if raw_mode is true. fn forward_stderr_if_piped(child: &mut Child, raw_mode: bool) { let child_stderr = match child.stderr.take() { None => return, Some(child_stderr) => child_stderr, }; spawn(move || { let mut to_print = Vec::new(); let mut reader = BufReader::new(child_stderr); loop { let buffer = reader.fill_buf().expect("Unable to read child's stderr."); if buffer.is_empty() { return; } for &byte in buffer { if raw_mode && byte == b'\n' { to_print.push(b'\r'); } to_print.push(byte); } stderr() .write_all(&to_print) .expect("Unable to echo child's stderr."); to_print.clear(); let buffer_len = buffer.len(); reader.consume(buffer_len); } }); } ================================================ FILE: runner/src/qemu.rs ================================================ use super::Cli; use std::path::PathBuf; use std::process::{Child, Command, Stdio}; // Spawns a QEMU VM with a simulated Tock system and the process binary. Returns // the handle for the spawned QEMU process. pub fn deploy(cli: &Cli, platform: String, tbf_path: PathBuf) -> Child { let platform_args = get_platform_args(platform); let device = format!( "loader,file={},addr={}", tbf_path .into_os_string() .into_string() .expect("Non-UTF-8 path"), platform_args.process_binary_load_address, ); let mut qemu = Command::new("tock/tools/qemu/build/qemu-system-riscv32"); qemu.args(["-device", &device, "-nographic", "-serial", "mon:stdio"]); qemu.args(platform_args.fixed_args); // If we let QEMU inherit its stdin from us, it will set it to raw mode, // which prevents Ctrl+C from generating SIGINT. QEMU will not exit when // Ctrl+C is entered, making our runner hard to close. Instead, we forward // stdin to QEMU ourselves -- see output_processor.rs for more details. qemu.stdin(Stdio::piped()); qemu.stdout(Stdio::piped()); // Because we set the terminal to raw mode while running QEMU, but QEMU's // stdin is not connected to a terminal, QEMU does not know it needs to use // CRLF line endings when printing to stderr. To convert, we also pipe // QEMU's stderr through us and output_processor converts the line endings. qemu.stderr(Stdio::piped()); if cli.verbose { println!("QEMU command: {qemu:?}"); println!("Spawning QEMU") } qemu.spawn().expect("failed to spawn QEMU") } // Returns the command line arguments for the given platform to qemu. Panics if // an unknown platform is passed. fn get_platform_args(platform: String) -> PlatformConfig { match platform.as_str() { "hifive1" => PlatformConfig { #[rustfmt::skip] fixed_args: &[ "-kernel", "tock/target/riscv32imac-unknown-none-elf/release/hifive1", "-M", "sifive_e,revb=true", ], process_binary_load_address: "0x20040000", }, "opentitan" => PlatformConfig { #[rustfmt::skip] fixed_args: &[ "-bios", "tock/tools/qemu-runner/opentitan-boot-rom.elf", "-kernel", "tock/target/riscv32imc-unknown-none-elf/release/earlgrey-cw310", "-M", "opentitan", ], process_binary_load_address: "0x20030000", }, _ => panic!("Cannot deploy to platform {platform} via QEMU."), } } // QEMU configuration information that is specific to each platform. struct PlatformConfig { fixed_args: &'static [&'static str], process_binary_load_address: &'static str, } ================================================ FILE: runner/src/tockloader.rs ================================================ use super::Cli; use std::path::PathBuf; use std::process::{Child, Command, Stdio}; // Uses tockloader to deploy the provided TAB file to a Tock system. Returns the // handle for the spawned 'tockloader listen' process. // Note: This function is untested, as its author does not have hardware that // works with tockloader. If you use it, please report back on how it works so // we can fix it or remove this notice! pub fn deploy(cli: &Cli, platform: String, tab_path: PathBuf) -> Child { let flags: &[_] = match platform.as_str() { "clue_nrf52840" => &[], "hail" | "imix" => &[], "microbit_v2" => &["--bundle-apps"], "nrf52" | "nrf52840" => &[ "--jlink", "--arch", "cortex-m4", "--board", "nrf52dk", "--jtag-device", "nrf52", ], _ => panic!("Cannot deploy to platform {platform} via tockloader"), }; if cli.verbose { println!("Tockloader flags: {flags:?}"); } // Tockloader listen's ability to receive every message from the Tock system // varies from platform to platform. We look up the platform, and if it is // not satisfactorily reliable we output a warning for the user. let reliable_listen = match platform.as_str() { // tockloader listen will reset the Hail/Imix, allowing it to capture all // printed messages. "hail" | "imix" => true, // Microbit uses CDC over USB, which buffers messages so that tockloader // listen can receive messages sent before it was started. As long as // tockloader listen launches before the timeout, there will not be // dropped messages. This is good enough for our purposes. "microbit_v2" => true, // tockloader listen doesn't reset the nrf52, and there's no message // queueing mechanism. Therefore, tockloader listen will likely miss // messages printed quickly after the process binary is deployed. "nrf52" | "nrf52840" => false, // We shouldn't hit this case, because the flag determination code above // should error out on unknown platforms. _ => panic!("Unknown reliability for {platform}"), }; if !reliable_listen { println!("Warning: tockloader listen may miss early messages on platform {platform}"); } // Invoke tockloader uninstall to remove the process binary, if present. let mut uninstall = Command::new("tockloader"); uninstall.arg("uninstall"); uninstall.args(flags); if cli.verbose { println!("tockloader uninstall command: {uninstall:?}"); } let mut child = uninstall .spawn() .expect("failed to spawn tockloader uninstall"); let status = child .wait() .expect("failed to wait for tockloader uninstall"); if cli.verbose { println!("tockloader uninstall finished. {status}"); } // Invoke tockloader install to deploy the new process binary. let mut install = Command::new("tockloader"); install.arg("install"); install.args(flags); install.arg(tab_path); if cli.verbose { println!("tockloader install command: {install:?}"); } let mut child = install.spawn().expect("failed to spawn tockloader install"); let status = child.wait().expect("failed to wait for tockloader install"); if cli.verbose { println!("tockloader install finished. {status}"); } assert!( status.success(), "tockloader install returned unsuccessful status {status}" ); // Invoke tockloader listen to receive messages from the Tock system. let mut listen = Command::new("tockloader"); listen.arg("listen"); listen.args(flags); listen.stdout(Stdio::piped()); if cli.verbose { println!("tockloader listen command: {listen:?}"); } listen.spawn().expect("failed to spawn tockloader listen") } ================================================ FILE: runtime/Cargo.toml ================================================ [package] authors = ["Tock Project Developers "] categories = ["embedded", "no-std", "os"] description = """libtock-rs runtime. Provides raw system call implementations \ and language items necessary for Tock apps.""" edition = "2021" license = "Apache-2.0 OR MIT" name = "libtock_runtime" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true version = "0.1.0" [dependencies] libtock_platform = { path = "../platform" } [features] # By default, libtock_runtime calls Memop to tell the Tock kernel where the # stack and heap begin. The kernel uses those addresses to specify the stack and # heap address ranges if the process faults. Those calls cost 22 bytes on ARM # and 28 bytes on RISC-V. To remove them (for the purpose of minimizing code # size), enable the no_debug_memop feature. no_debug_memop = [] ================================================ FILE: runtime/src/lib.rs ================================================ //! `libtock_runtime` provides the runtime for Tock process binaries written in //! Rust as well as interfaces to Tock's system calls. //! //! `libtock_runtime` is designed for statically-compiled binaries, and needs to //! know the location (in non-volatile memory and RAM) at which the process will //! execute. It reads the `LIBTOCK_PLATFORM` variable to determine what location //! to build for (see the `layouts/` directory to see what platforms are //! available). It expects the following cargo config options to be set (e.g. in //! `.cargo/config.toml`): //! ``` //! [build] //! rustflags = [ //! "-C", "relocation-model=static", //! "-C", "link-arg=-Tlayout.ld", //! ] //! ``` //! If a process binary wants to support another platform, it can set the //! `no_auto_layout` feature on `libtock_runtime` to disable this functionality //! and provide its own layout file. #![no_std] #![warn(unsafe_op_in_unsafe_fn)] pub mod startup; /// TockSyscalls implements `libtock_platform::Syscalls`. pub struct TockSyscalls; #[cfg(target_arch = "arm")] mod syscalls_impl_arm; #[cfg(target_arch = "riscv32")] mod syscalls_impl_riscv; #[cfg(target_arch = "x86")] mod syscalls_impl_x86; ================================================ FILE: runtime/src/startup/asm_arm.s ================================================ /* rt_header is defined by the general linker script (libtock_layout.ld). It has * the following layout: * * Field | Offset * ------------------------------------ * Address of the start symbol | 0 * Initial process break | 4 * Top of the stack | 8 * Size of .data | 12 * Start of .data in flash | 16 * Start of .data in ram | 20 * Size of .bss | 24 * Start of .bss in ram | 28 */ /* start is the entry point -- the first code executed by the kernel. The kernel * passes arguments through 4 registers: * * r0 Pointer to beginning of the process binary's code. The linker script * locates rt_header at this address. * * r1 Address of the beginning of the process's usable memory region. * r2 Size of the process' allocated memory region (including grant region) * r3 Process break provided by the kernel. * * We currently only use the value in r0. It is copied into r5 early on because * r0 is needed to invoke system calls. * * To be compatible with ARMv6 Thumb-1, we the cmp and beq instructions * instead of cbz in two places. This increases the code size with 4 bytes, * but allows us to use it on Cortex-M0+ processors. */ .section .start, "ax" .global start .thumb_func start: /* First, verify the process binary was loaded at the correct address. The * check is performed by comparing the program counter at the start to the * address of `start`, which is stored in rt_header. */ mov r4, pc /* r4 = address of .start + 4 (Thumb bit unset) */ mov r5, r0 /* Save rt_header; we use r0 for syscalls */ ldr r0, [r5, #0] /* r0 = rt_header.start */ adds r0, #4 /* r0 = rt_header.start + 4 */ cmp r0, r4 /* Skip error handling if pc correct */ beq .Lset_brk /* If the beq on the previous line did not jump, then the binary is not at * the correct location. Report the error via LowLevelDebug then exit. */ movs r0, #8 /* LowLevelDebug driver number */ movs r1, #1 /* Command: print alert code */ movs r2, #2 /* Alert code 2 (incorrect location */ svc 2 /* Execute `command` */ movs r0, #0 /* Operation: exit-terminate */ movs r1, #1 /* Completion code: FAIL */ svc 6 /* Execute `exit` */ .Lset_brk: /* memop(): set brk to rt_header's initial break value */ movs r0, #0 /* operation: set break */ ldr r1, [r5, #4] /* rt_header`s initial process break */ svc 5 /* call `memop` */ /* Set the stack pointer */ ldr r0, [r5, #8] /* r0 = rt_header._stack_top */ mov sp, r0 /* Copy .data into place */ ldr r0, [r5, #12] /* remaining = rt_header.data_size */ cmp r0, #0 /* Jump to zero_bss if remaining == 0 */ beq .Lzero_bss ldr r1, [r5, #16] /* src = rt_header.data_flash_start */ ldr r2, [r5, #20] /* dest = rt_header.data_ram_start */ .Ldata_loop_body: ldr r3, [r1] /* r3 = *src */ str r3, [r2] /* *(dest) = r3 */ subs r0, #4 /* remaining -= 4 */ adds r1, #4 /* src += 4 */ adds r2, #4 /* dest += 4 */ cmp r0, #0 bne .Ldata_loop_body /* Iterate again if remaining != 0 */ .Lzero_bss: ldr r0, [r5, #24] /* remaining = rt_header.bss_size */ cmp r0, #0 /* Jump to call_rust_start if remaining == 0 */ beq .Lcall_rust_start ldr r1, [r5, #28] /* dest = rt_header.bss_start */ movs r2, #0 /* r2 = 0 */ .Lbss_loop_body: strb r2, [r1] /* *(dest) = r2 = 0 */ subs r0, #1 /* remaining -= 1 */ adds r1, #1 /* dest += 1 */ cmp r0, #0 bne .Lbss_loop_body /* Iterate again if remaining != 0 */ .Lcall_rust_start: bl rust_start ================================================ FILE: runtime/src/startup/asm_riscv32.s ================================================ /* rt_header is defined by the general linker script (libtock_layout.ld). It has * the following layout: * * Field | Offset * ------------------------------------ * Address of the start symbol | 0 * Initial process break | 4 * Top of the stack | 8 * Size of .data | 12 * Start of .data in flash | 16 * Start of .data in ram | 20 * Size of .bss | 24 * Start of .bss in ram | 28 */ /* start is the entry point -- the first code executed by the kernel. The kernel * passes arguments through 4 registers: * * a0 Pointer to beginning of the process binary's code. The linker script * locates rt_header at this address. * * a1 Address of the beginning of the process's usable memory region. * a2 Size of the process' allocated memory region (including grant region) * a3 Process break provided by the kernel. * * We currently only use the value in a0. It is copied into a5 early on because * a0-a4 are needed to invoke system calls. */ .section .start, "ax" .globl start start: /* First, verify the process binary was loaded at the correct address. The * check is performed by comparing the program counter at the start to the * address of `start`, which is stored in rt_header. */ auipc s0, 0 /* s0 = pc */ mv a5, a0; /* Save rt_header so syscalls don't overwrite it */ lw s1, 0(a5) /* s1 = rt_header.start */ beq s0, s1, .Lset_brk /* Skip error handling code if pc is correct */ /* If the beq on the previous line did not jump, then the binary is not at * the correct location. Report the error via LowLevelDebug then exit. */ li a0, 8 /* LowLevelDebug driver number */ li a1, 1 /* Command: Print alert code */ li a2, 2 /* Alert code 2 (incorrect location) */ li a4, 2 /* `command` class */ ecall li a0, 0 /* exit-terminate */ li a1, 1 /* Completion code: FAIL */ li a4, 6 /* `exit` class */ ecall .Lset_brk: /* memop(): set brk to rt_header's initial break value */ li a0, 0 /* operation: set break */ lw a1, 4(a5) /* rt_header's initial process break */ li a4, 5 /* `memop` class */ ecall /* Set the stack pointer */ lw sp, 8(a5) /* sp = rt_header._stack_top */ /* Copy .data into place. */ lw a0, 12(a5) /* remaining = rt_header.data_size */ beqz a0, .Lzero_bss /* Jump to zero_bss if remaining is zero */ lw a1, 16(a5) /* src = rt_header.data_flash_start */ lw a2, 20(a5) /* dest = rt_header.data_ram_start */ .Ldata_loop_body: lw a3, 0(a1) /* a3 = *src */ sw a3, 0(a2) /* *dest = a3 */ addi a0, a0, -4 /* remaining -= 4 */ addi a1, a1, 4 /* src += 4 */ addi a2, a2, 4 /* dest += 4 */ bnez a0, .Ldata_loop_body /* Iterate again if remaining != 0 */ .Lzero_bss: lw a0, 24(a5) /* remaining = rt_header.bss_size */ beqz a0, .Lcall_rust_start /* Jump to call_Main if remaining is zero */ lw a1, 28(a5) /* dest = rt_header.bss_start */ .Lbss_loop_body: sb zero, 0(a1) /* *dest = zero */ addi a0, a0, -1 /* remaining -= 1 */ addi a1, a1, 1 /* dest += 1 */ bnez a0, .Lbss_loop_body /* Iterate again if remaining != 0 */ .Lcall_rust_start: /* Note: rust_start must be a diverging function (i.e. return `!`) */ jal rust_start ================================================ FILE: runtime/src/startup/asm_x86.s ================================================ /* rt_header is defined by the general linker script (libtock_layout.ld). It has * the following layout: * * Field | Offset * ------------------------------------ * Address of the start symbol | 0 * Initial process break | 4 * Top of the stack | 8 * Size of .data | 12 * Start of .data in flash | 16 * Start of .data in ram | 20 * Size of .bss | 24 * Start of .bss in ram | 28 */ /* start is the entry point -- the first code executed by the kernel. The kernel * passes the following arguments onto the stack: * * esp+4 Pointer to beginning of the process binary's code. The linker script * locates rt_header at this address. * * +8 Address of the beginning of the process's usable memory region. * +12 Size of the process' allocated memory region (including grant region) * +16 Process break provided by the kernel. * * We currently only use the value in esp+4. */ /* int 0x03 is used to trigger a breakpoint which is promoted to a hard fault in the absence of a debugger. This is useful to fault at failure cases where there is no recovery path. */ /* Specify that the start code is allocated and executable (ax), * and that it should be placed in the .start section of the binary. */ .section .start, "ax" .globl start start: /* * Verify that the binary was loaded to the correct * address. We can do this by using the call command * and grabbing the EIP off of the stack. The eip * will be the "length of the call instruction" (5 bytes) * ahead of the actual start of the program. */ call .Lget_eip // 1 byte for the opcode + 4 bytes for the relative offset // = 5 byte long instruction .Lget_eip: popl %eax // eax = eip subl $5, %eax // eax = eip - 5 byte instruction movl 4(%esp), %ebx // ebx = rt_header (top of memory) movl 0(%ebx), %ecx // ecx = rt_header.start cmpl %ecx, %eax je .Lset_brk /* If the binary is not at the correct location, report the error via LowLevelDebug * then exit. */ pushl %eax // eip, not consumed by the syscall, but is seen in trace pushl $2 // Code 0x02 (app was not installed in the correct location) pushl $1 // Minor number: Alert code pushl $8 // Major number: LowLevelDebug driver mov $2, %eax // Command syscall int $0x40 addl $16, %esp pushl $0 pushl $0 pushl $1 // Completion code: FAIL pushl $0 // exit-terminate mov $6, %eax // Exit syscall int $0x40 addl $16, %esp int $0x03 // If we return, trigger a fault /* Set brk to rt_header initial break value */ .Lset_brk: movl 4(%ebx), %ecx // ecx = initial process break pushl $0 pushl $0 pushl %ecx // push initial process break pushl $0 movl $5, %eax // memop int $0x40 /* Set the stack pointer */ mov 8(%ebx), %esp .Lzero_bss: /* Zero out .bss */ movl 24(%ebx), %ecx // ecx = remaining = rt_header.bss_size cmpl $0, %ecx je .Lcopy_data // If there is no .bss, jump to copying .data movl 28(%ebx), %edi // edi = dst = rt_header.bss_start shrl $2, %ecx // ecx = remaining / 4 = number of words to zero cld // Clear the direction flag xorl %eax, %eax // eax = 0, value to set .bss to rep stosl // Zero out the .bss_size movl 24(%ebx), %ecx // ecx = remaining = rt_header.bss_size andl $3, %ecx // ecx = remaining % 4 = number of bytes to zero rep stosb // Zero out the remaining bytes .Lcopy_data: /* Copy .data into place */ movl 12(%ebx), %ecx // ecx = rt_header.data_size cmpl $0, %ecx je .Lcall_rust_start movl 16(%ebx), %esi // esi = src = rt_header.data_flash_start movl 20(%ebx), %edi // edi = dst = rt_header.data_ram_start shrl $2, %ecx // ecx = rt_header.data_size / 4 = number of words to copy cld // Clear the direction flag rep movsl // Copy data from flash to ram movl 12(%ebx), %ecx // ecx = rt_header.data_size andl $3, %ecx // ecx = rt_header.data_size % 4 = number of bytes to copy rep movsb // Copy the remaining bytes .Lcall_rust_start: jmp rust_start int $0x03 // If we return, trigger a fault ================================================ FILE: runtime/src/startup/mod.rs ================================================ //! Runtime components related to process startup. use crate::TockSyscalls; use libtock_platform::{Syscalls, Termination}; // Include the correct `start` symbol (the program entry point) for the // architecture. #[cfg(target_arch = "arm")] core::arch::global_asm!(include_str!("asm_arm.s")); #[cfg(target_arch = "riscv32")] core::arch::global_asm!(include_str!("asm_riscv32.s")); #[cfg(target_arch = "x86")] core::arch::global_asm!(include_str!("asm_x86.s"), options(att_syntax)); /// `set_main!` is used to tell `libtock_runtime` where the process binary's /// `main` function is. The process binary's `main` function must have the /// signature `FnOnce() -> T`, where T is some concrete type that implements /// `libtock_platform::Termination`. /// /// # Example /// ``` /// libtock_runtime::set_main!{main}; /// /// fn main() -> () { /* Omitted */ } /// ``` // set_main! generates a function called `libtock_unsafe_main`, which is called // by `rust_start`. The function has `unsafe` in its name because implementing // it is `unsafe` (it *must* have the signature `libtock_unsafe_main() -> !`), // but there is no way to enforce the use of `unsafe` through the type system. // This function calls the client-provided function, which enforces its type // signature. #[macro_export] macro_rules! set_main { {$name:ident} => { #[no_mangle] fn libtock_unsafe_main() -> ! { #[allow(unreachable_code)] // so that fn main() -> ! does not produce a warning. $crate::startup::handle_main_return($name()) } } } /// Executables must specify their stack size by using the `stack_size!` macro. /// It takes a single argument, the desired stack size in bytes. Example: /// ``` /// stack_size!{0x400} /// ``` // stack_size works by putting a symbol equal to the size of the stack in the // .stack_buffer section. The linker script uses the .stack_buffer section to // size the stack. flash.sh looks for the symbol by name (hence #[no_mangle]) to // determine the size of the stack to pass to elf2tab. #[macro_export] macro_rules! stack_size { {$size:expr} => { #[no_mangle] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; $size] = [0; $size]; } } /// This is public for the sake of making `set_main!` usable in other crates. /// It doesn't have another function. pub fn handle_main_return(result: T) -> ! { Termination::complete::(result) } // The runtime header, which is generated by the linker script and placed at the // beginning of the app binary. #[repr(C)] struct RtHeader { start: usize, initial_break: *mut (), stack_top: *mut (), data_size: usize, data_flash_start: *const u8, data_ram_start: *mut u8, bss_size: usize, bss_start: *mut u8, } // rust_start is the first Rust code to execute in the process. It is called // from start, which is written directly in assembly. #[no_mangle] extern "C" fn rust_start() -> ! { extern "Rust" { fn libtock_unsafe_main() -> !; static rt_header: RtHeader; } #[cfg(not(feature = "no_debug_memop"))] // Safety: rt_header is defined in the linker script, valid for its type, // and not modified anywhere unsafe { let _ = TockSyscalls::memop_debug_stack_start(rt_header.stack_top as *const u8); let _ = TockSyscalls::memop_debug_heap_start(rt_header.initial_break as *const u8); } // Safety: libtock_unsafe_main is defined by the set_main! macro, and its // signature matches the signature in the `extern` block in this function. unsafe { libtock_unsafe_main(); } } /// Function which an allocator can call to learn the initial /// start of the heap region pub fn get_heap_start() -> *mut () { extern "Rust" { static rt_header: RtHeader; } // Safety: rt_header is defined in the linker script, valid for its type, // and not modified anywhere unsafe { rt_header.initial_break } } ================================================ FILE: runtime/src/startup/start_prototype.rs ================================================ // This file is not compiled or tested! It is kept in this repository in case // future libtock_runtime developers want to use it. To use this file, add // `mod start_prototype;` to mod.rs. // The `start` symbol must be written purely in assembly, because it has an ABI // that the Rust compiler doesn't know (e.g. it does not expect the stack to be // set up). One way to write a correct `start` implementation is to write it in // Rust using the C ABI, compile that implementation, then tweak the assembly by // hand. This is a Rust version of `start` for developers who are working on // `start`. use super::RtHeader; use core::arch::asm; #[link_section = ".start"] #[no_mangle] extern "C" fn start_prototype( rt_header: &RtHeader, _memory_start: usize, _memory_len: usize, _app_break: usize, ) -> ! { use crate::TockSyscalls; use libtock_platform::{syscall_class, RawSyscalls}; let pc: usize; unsafe { #[cfg(target_arch = "arm")] asm!("mov {}, pc", lateout(reg) pc, options(nomem, nostack, preserves_flags)); #[cfg(target_arch = "riscv32")] asm!("auipc {}, 0", lateout(reg) pc, options(nomem, nostack, preserves_flags)); } if pc != rt_header.start { // Binary is in an incorrect location: report an error via // LowLevelDebug then exit. unsafe { TockSyscalls::syscall4::<{ syscall_class::COMMAND }>([ 8u32.into(), 1u32.into(), 2u32.into(), 0u32.into(), ]); TockSyscalls::syscall2::<{ syscall_class::EXIT }>([0u32.into(), 0u32.into()]); } } // Set the app break. // TODO: Replace with Syscalls::memop_brk() when that is implemented. unsafe { TockSyscalls::syscall2::<{ syscall_class::MEMOP }>([ 0u32.into(), rt_header.initial_break.into(), ]); } // Set the stack pointer. unsafe { #[cfg(target_arch = "arm")] asm!("mov sp, {}", in(reg) rt_header.stack_top, options(nomem, preserves_flags)); #[cfg(target_arch = "riscv32")] asm!("mv sp, {}", in(reg) rt_header.stack_top, options(nomem, preserves_flags)); } // Copy .data into place. Uses a manual loop rather than // `core::ptr::copy*()` to avoid relying on `memcopy` or `memmove`. let mut remaining = rt_header.data_size; let mut src = rt_header.data_flash_start as *const u32; let mut dest = rt_header.data_ram_start as *mut u32; while remaining > 0 { unsafe { core::ptr::write(dest, *(src)); src = src.add(1); dest = dest.add(1); } remaining -= 4; } // Zero .bss. Uses a manual loop and volatile write to avoid relying on // `memset`. let mut remaining = rt_header.bss_size; let mut dest = rt_header.bss_start; while remaining > 0 { unsafe { core::ptr::write_volatile(dest, 0); dest = dest.add(1); } remaining -= 1; } extern "C" { fn rust_start() -> !; } unsafe { rust_start(); } } ================================================ FILE: runtime/src/syscalls_impl_arm.rs ================================================ use core::arch::asm; use libtock_platform::{RawSyscalls, Register}; unsafe impl RawSyscalls for crate::TockSyscalls { unsafe fn yield1([Register(r0)]: [Register; 1]) { // Safety: This matches the invariants required by the documentation on // RawSyscalls::yield1 // the use of `clobber_abi` allows us this to run on both Thumb-1 and Thumb-2 unsafe { asm!("svc 0", inlateout("r0") r0 => _, // a1 // r4-r8 are callee-saved. // r9 is platform-specific. We don't use it in libtock_runtime, // so it is either unused or used as a callee-saved register. // r10 and r11 are callee-saved. // r13 is the stack pointer and must be restored by the callee. // r15 is the program counter. clobber_abi("C"), // a2, a3, a4, ip (r12), lr (r14) ); } } unsafe fn yield2([Register(r0), Register(r1)]: [Register; 2]) { // Safety: This matches the invariants required by the documentation on // RawSyscalls::yield2 // the use of `clobber_abi` allows us this to run on both Thumb-1 and Thumb-2 unsafe { asm!("svc 0", inlateout("r0") r0 => _, // a1 inlateout("r1") r1 => _, // a2 // r4-r8 are callee-saved. // r9 is platform-specific. We don't use it in libtock_runtime, // so it is either unused or used as a callee-saved register. // r10 and r11 are callee-saved. // r13 is the stack pointer and must be restored by the callee. // r15 is the program counter. clobber_abi("C"), // a3, a4, ip (r12), lr (r14) ); } } unsafe fn syscall1( [Register(mut r0)]: [Register; 1], ) -> [Register; 2] { let r1; // Safety: This matches the invariants required by the documentation on // RawSyscalls::syscall1 #[allow(clippy::pointers_in_nomem_asm_block)] unsafe { asm!( "svc {SYSCALL_CLASS_NUMBER}", inlateout("r0") r0, lateout("r1") r1, options(preserves_flags, nostack, nomem), SYSCALL_CLASS_NUMBER = const SYSCALL_CLASS_NUMBER, ); } [Register(r0), Register(r1)] } unsafe fn syscall2( [Register(mut r0), Register(mut r1)]: [Register; 2], ) -> [Register; 2] { // Safety: This matches the invariants required by the documentation on // RawSyscalls::syscall2 #[allow(clippy::pointers_in_nomem_asm_block)] unsafe { asm!( "svc {SYSCALL_CLASS_NUMBER}", inlateout("r0") r0, inlateout("r1") r1, options(preserves_flags, nostack, nomem), SYSCALL_CLASS_NUMBER = const SYSCALL_CLASS_NUMBER, ); } [Register(r0), Register(r1)] } unsafe fn syscall4( [Register(mut r0), Register(mut r1), Register(mut r2), Register(mut r3)]: [Register; 4], ) -> [Register; 4] { // Safety: This matches the invariants required by the documentation on // RawSyscalls::syscall4 unsafe { asm!( "svc {SYSCALL_CLASS_NUMBER}", inlateout("r0") r0, inlateout("r1") r1, inlateout("r2") r2, inlateout("r3") r3, options(preserves_flags, nostack), SYSCALL_CLASS_NUMBER = const SYSCALL_CLASS_NUMBER, ); } [Register(r0), Register(r1), Register(r2), Register(r3)] } } ================================================ FILE: runtime/src/syscalls_impl_riscv.rs ================================================ use core::arch::asm; use libtock_platform::{RawSyscalls, Register}; unsafe impl RawSyscalls for crate::TockSyscalls { // This yield implementation is currently limited to RISC-V versions without // floating-point registers, as it does not mark them clobbered. #[cfg(not(any(target_feature = "d", target_feature = "f")))] unsafe fn yield1([Register(r0)]: [Register; 1]) { // Safety: This matches the invariants required by the documentation on // RawSyscalls::yield1 unsafe { asm!("ecall", // x0 is the zero register. lateout("x1") _, // Return address // x2-x4 are stack, global, and thread pointers. sp is // callee-saved. lateout("x5") _, // t0 lateout("x6") _, // t1 lateout("x7") _, // t2 // x8 and x9 are s0 and s1 and are callee-saved. inlateout("x10") r0 => _, // a0 lateout("x11") _, // a1 lateout("x12") _, // a2 lateout("x13") _, // a3 inlateout("x14") 0 => _, // a4 lateout("x15") _, // a5 lateout("x16") _, // a6 lateout("x17") _, // a7 // x18-27 are s2-s11 and are callee-saved lateout("x28") _, // t3 lateout("x29") _, // t4 lateout("x30") _, // t5 lateout("x31") _, // t6 ); } } // This yield implementation is currently limited to RISC-V versions without // floating-point registers, as it does not mark them clobbered. #[cfg(not(any(target_feature = "d", target_feature = "f")))] unsafe fn yield2([Register(r0), Register(r1)]: [Register; 2]) { // Safety: This matches the invariants required by the documentation on // RawSyscalls::yield2 unsafe { asm!("ecall", // x0 is the zero register. lateout("x1") _, // Return address // x2-x4 are stack, global, and thread pointers. sp is // callee-saved. lateout("x5") _, // t0 lateout("x6") _, // t1 lateout("x7") _, // t2 // x8 and x9 are s0 and s1 and are callee-saved. inlateout("x10") r0 => _, // a0 inlateout("x11") r1 => _, // a1 lateout("x12") _, // a2 lateout("x13") _, // a3 inlateout("x14") 0 => _, // a4 lateout("x15") _, // a5 lateout("x16") _, // a6 lateout("x17") _, // a7 // x18-27 are s2-s11 and are callee-saved lateout("x28") _, // t3 lateout("x29") _, // t4 lateout("x30") _, // t5 lateout("x31") _, // t6 ); } } unsafe fn syscall1([Register(mut r0)]: [Register; 1]) -> [Register; 2] { let r1; // Safety: This matches the invariants required by the documentation on // RawSyscalls::syscall1 #[allow(clippy::pointers_in_nomem_asm_block)] unsafe { asm!("ecall", inlateout("a0") r0, lateout("a1") r1, in("a4") CLASS, options(preserves_flags, nostack, nomem), ); } [Register(r0), Register(r1)] } unsafe fn syscall2( [Register(mut r0), Register(mut r1)]: [Register; 2], ) -> [Register; 2] { // Safety: This matches the invariants required by the documentation on // RawSyscalls::syscall2 #[allow(clippy::pointers_in_nomem_asm_block)] unsafe { asm!("ecall", inlateout("a0") r0, inlateout("a1") r1, in("a4") CLASS, options(preserves_flags, nostack, nomem) ); } [Register(r0), Register(r1)] } unsafe fn syscall4( [Register(mut r0), Register(mut r1), Register(mut r2), Register(mut r3)]: [Register; 4], ) -> [Register; 4] { // Safety: This matches the invariants required by the documentation on // RawSyscalls::syscall4 unsafe { asm!("ecall", inlateout("a0") r0, inlateout("a1") r1, inlateout("a2") r2, inlateout("a3") r3, in("a4") CLASS, options(preserves_flags, nostack), ); } [Register(r0), Register(r1), Register(r2), Register(r3)] } } ================================================ FILE: runtime/src/syscalls_impl_x86.rs ================================================ use core::arch::asm; use libtock_platform::{syscall_class, RawSyscalls, Register}; unsafe impl RawSyscalls for crate::TockSyscalls { // Yield 1 is used for yield_wait unsafe fn yield1([Register(r0)]: [Register; 1]) { unsafe { asm!( "pushl $0", "pushl $0", "pushl $0", "pushl {0}", // r0 "movl $0, %eax", "int $0x40", "addl $16, %esp", in(reg) r0, // The following registers are clobbered by the syscall out("eax") _, out("ecx") _, out("edx") _, options(att_syntax), ); } } // Yield 2 is used for yield_no_wait unsafe fn yield2([Register(r0), Register(r1)]: [Register; 2]) { unsafe { asm!( "pushl $0", "pushl $0", "pushl {0}", // r1 "pushl {1}", // r0 "movl $0, %eax", "int $0x40", "addl $16, %esp", in(reg) r1, in(reg) r0, // The following registers are clobbered by the syscall out("eax") _, out("ecx") _, out("edx") _, options(att_syntax) ); } } unsafe fn syscall1([Register(mut r0)]: [Register; 1]) -> [Register; 2] { // This is memop, the only syscall class that syscall1 supports let r1; unsafe { asm!( "push $0", "push $0", "push $0", "push {0}", // r0 "movl $5, %eax", "int $0x40", "popl {0:e}", // r1 "popl {1:e}", // r0 "addl $8, %esp", inlateout(reg) r0, out(reg) r1, // The following registers are clobbered by the syscall out("eax") _, options(att_syntax), ); } [Register(r0), Register(r1)] } unsafe fn syscall2( [Register(mut r0), Register(mut r1)]: [Register; 2], ) -> [Register; 2] { let cmd: u32 = match CLASS { syscall_class::MEMOP => 5, syscall_class::EXIT => 6, _ => unreachable!(), }; unsafe { asm!( "pushl $0", "pushl $0", "pushl {0}", // r1 "pushl {1}", // r0 "movl {2}, %eax", // cmd "int $0x40", "popl {1:e}", // r0 "popl {0:e}", // r1 "addl $8, %esp", inlateout(reg) r1, inlateout(reg) r0, in(reg) cmd, // The following registers are clobbered by the syscall out("eax") _, options(att_syntax), ); } [Register(r0), Register(r1)] } unsafe fn syscall4( [Register(mut r0), Register(mut r1), Register(mut r2), Register(mut r3)]: [Register; 4], ) -> [Register; 4] { let cmd: u32 = match CLASS { syscall_class::SUBSCRIBE => 1, syscall_class::COMMAND => 2, syscall_class::ALLOW_RW => 3, syscall_class::ALLOW_RO => 4, _ => unreachable!(), }; unsafe { asm!( "pushl {3}", // r3 "pushl {2}", // r2 "pushl {1}", // r1 "pushl {0}", // r0 "movl {4:e}, %eax", "int $0x40", "popl {0:e}", // r0 "popl {1:e}", // r1 "popl {2:e}", // r2 "popl {3:e}", // r3 inlateout(reg) r0, inlateout(reg) r1, inlateout(reg) r2, inlateout(reg) r3, in(reg) cmd, // The following registers are clobbered by the syscall out("eax") _, options(att_syntax), ); } [Register(r0), Register(r1), Register(r2), Register(r3)] } } ================================================ FILE: rust-toolchain.toml ================================================ [toolchain] # This is libtock-rs' Minimum Supported Rust Version (MSRV). # # Update policy: Update this if doing so allows you to use a Rust feature that # you'd like to use. When you do so, update this to the first Rust version that # includes that feature. Whenever this value is updated, the rust-version field # in Cargo.toml must be updated as well. channel = "1.88" components = ["clippy", "rustfmt", "rust-analyzer"] targets = [ "thumbv6m-none-eabi", "thumbv7em-none-eabi", "thumbv8m.main-none-eabi", "riscv32imac-unknown-none-elf", "riscv32imc-unknown-none-elf", ] ================================================ FILE: rustfmt.toml ================================================ use_field_init_shorthand = true ================================================ FILE: src/lib.rs ================================================ #![forbid(unsafe_code)] #![no_std] #[cfg(debug_assertions)] extern crate libtock_debug_panic; #[cfg(not(debug_assertions))] extern crate libtock_small_panic; pub use libtock_platform as platform; pub use libtock_runtime as runtime; pub mod adc { use libtock_adc as adc; pub type Adc = adc::Adc; pub use adc::ADCListener; } pub mod display { use libtock_screen as screen; pub type Screen = screen::Screen; } pub mod air_quality { use libtock_air_quality as air_quality; pub type AirQuality = air_quality::AirQuality; pub use air_quality::AirQualityListener; } pub mod alarm { use libtock_alarm as alarm; pub type Alarm = alarm::Alarm; pub use alarm::{Convert, Hz, Milliseconds, Ticks}; } pub mod ambient_light { use libtock_ambient_light as ambient_light; pub type AmbientLight = ambient_light::AmbientLight; pub use ambient_light::IntensityListener; } pub mod buttons { use libtock_buttons as buttons; pub type Buttons = buttons::Buttons; pub use buttons::{ButtonListener, ButtonState}; } pub mod buzzer { use libtock_buzzer as buzzer; pub type Buzzer = buzzer::Buzzer; pub use buzzer::Note; } pub mod console { use libtock_console as console; pub type Console = console::Console; pub use console::ConsoleWriter; } pub mod gpio { use libtock_gpio as gpio; pub type Gpio = gpio::Gpio; pub use gpio::{ Error, GpioInterruptListener, GpioState, InputPin, OutputPin, PinInterruptEdge, Pull, PullDown, PullNone, PullUp, }; } pub mod i2c_master { use libtock_i2c_master as i2c_master; pub type I2CMaster = i2c_master::I2CMaster; } pub mod i2c_master_slave { use libtock_i2c_master_slave as i2c_master_slave; pub type I2CMasterSlave = i2c_master_slave::I2CMasterSlave; } pub mod ieee802154 { use libtock_ieee802154 as ieee802154; pub type Ieee802154 = ieee802154::Ieee802154; pub use ieee802154::{Frame, RxOperator, RxRingBuffer}; pub type RxSingleBufferOperator<'buf, const N: usize> = ieee802154::RxSingleBufferOperator<'buf, N, super::runtime::TockSyscalls>; } pub mod leds { use libtock_leds as leds; pub type Leds = leds::Leds; } pub mod low_level_debug { use libtock_low_level_debug as lldb; pub type LowLevelDebug = lldb::LowLevelDebug; pub use lldb::AlertCode; } pub mod ninedof { use libtock_ninedof as ninedof; pub type NineDof = ninedof::NineDof; pub use ninedof::NineDofListener; } pub mod proximity { use libtock_proximity as proximity; pub type Proximity = proximity::Proximity; } pub mod rng { use libtock_rng as rng; pub type Rng = rng::Rng; pub use rng::RngListener; } pub mod sound_pressure { use libtock_sound_pressure as sound_pressure; pub type SoundPressure = sound_pressure::SoundPressure; } #[cfg(feature = "rust_embedded")] pub mod spi_controller; #[cfg(not(feature = "rust_embedded"))] pub mod spi_controller { use libtock_spi_controller as spi_controller; pub type SpiController = spi_controller::SpiController; } pub mod temperature { use libtock_temperature as temperature; pub type Temperature = temperature::Temperature; pub use temperature::TemperatureListener; } pub mod key_value { use libtock_key_value as key_value; pub type KeyValue = key_value::KeyValue; } ================================================ FILE: src/spi_controller.rs ================================================ use crate::alarm::{Alarm, Milliseconds}; use crate::platform::ErrorCode; use libtock_spi_controller as spi_controller; pub type SpiController = spi_controller::SpiController; pub struct EmbeddedHalSpi; impl embedded_hal::spi::ErrorType for EmbeddedHalSpi { type Error = ErrorCode; } impl embedded_hal::spi::SpiDevice for EmbeddedHalSpi { fn transaction( &mut self, operations: &mut [embedded_hal::spi::Operation<'_, u8>], ) -> Result<(), Self::Error> { for operation in operations { match operation { embedded_hal::spi::Operation::Read(read_buf) => { SpiController::spi_controller_read_sync(read_buf, read_buf.len() as u32)? } embedded_hal::spi::Operation::Write(write_buf) => { // writeln!(Console::writer(), "Write: write_buf: {:x?}\r", write_buf).unwrap(); SpiController::spi_controller_write_sync(write_buf, write_buf.len() as u32) .unwrap(); } embedded_hal::spi::Operation::Transfer(read_buf, write_buf) => { // writeln!(Console::writer(), "Transfer: write_buf: {:x?}\r", write_buf).unwrap(); SpiController::spi_controller_write_read_sync( write_buf, read_buf, write_buf.len() as u32, )? } embedded_hal::spi::Operation::TransferInPlace(read_write_buf) => { // writeln!(Console::writer(), "TransferInPlace: read_write_buf: {:x?}\r", read_write_buf).unwrap(); SpiController::spi_controller_inplace_write_read_sync( read_write_buf, read_write_buf.len() as u32, )? } embedded_hal::spi::Operation::DelayNs(time) => { Alarm::sleep_for(Milliseconds(*time / 1000)).unwrap(); } } } Ok(()) } } ================================================ FILE: syscalls_tests/Cargo.toml ================================================ [package] edition = "2021" name = "syscalls_tests" rust-version.workspace = true version = "0.1.0" [dependencies] libtock_platform = { path = "../platform" } libtock_unittest = { path = "../unittest" } ================================================ FILE: syscalls_tests/src/allow_ro.rs ================================================ use libtock_platform::{allow_ro, share, CommandReturn, ErrorCode, Syscalls}; use libtock_unittest::{command_return, fake, DriverInfo, RoAllowBuffer, SyscallLogEntry}; use std::cell::Cell; use std::rc::Rc; use std::thread_local; #[derive(Default)] struct TestDriver { buffer_0: Cell, } impl fake::SyscallDriver for TestDriver { fn info(&self) -> DriverInfo { DriverInfo::new(42) } fn command(&self, _command_num: u32, _argument0: u32, _argument1: u32) -> CommandReturn { command_return::failure(ErrorCode::NoSupport) } fn allow_readonly( &self, buffer_num: u32, buffer: RoAllowBuffer, ) -> Result { if buffer_num != 0 { return Err((buffer, ErrorCode::NoSupport)); } Ok(self.buffer_0.replace(buffer)) } } struct TestConfig; // CALLED is set to true when returned_nonzero_buffer is called. thread_local! {static CALLED: Cell = const {Cell::new(false)} } impl allow_ro::Config for TestConfig { fn returned_nonzero_buffer(driver_num: u32, buffer_num: u32) { assert_eq!(driver_num, 42); assert_eq!(buffer_num, 0); CALLED.with(|cell| cell.set(true)); } } #[test] fn allow_ro() { let kernel = fake::Kernel::new(); let driver = Rc::new(TestDriver::default()); kernel.add_driver(&driver); let buffer1 = [1, 2, 3, 4]; let buffer2 = [5, 6]; share::scope(|allow_ro| { // Tests a call that should fail because it has an incorrect buffer // number. let result = fake::Syscalls::allow_ro::(allow_ro, &buffer1); assert!(!CALLED.with(|c| c.get())); assert_eq!(result, Err(ErrorCode::NoSupport)); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRo { driver_num: 42, buffer_num: 1, len: 4, }] ); }); // Verify that share::scope unallowed the buffer. assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRo { driver_num: 42, buffer_num: 1, len: 0, }] ); share::scope(|allow_ro| { // Tests a call that should succeed and return a zero buffer. let result = fake::Syscalls::allow_ro::(allow_ro, &buffer1); assert!(!CALLED.with(|c| c.get())); assert_eq!(result, Ok(())); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRo { driver_num: 42, buffer_num: 0, len: 4, }] ); // Tests a call that should succeed and return a nonzero buffer. let result = fake::Syscalls::allow_ro::(allow_ro, &buffer2); assert!(CALLED.with(|c| c.get())); assert_eq!(result, Ok(())); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRo { driver_num: 42, buffer_num: 0, len: 2, }] ); }); // Verify that share::scope unallowed the buffer, but only once. assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRo { driver_num: 42, buffer_num: 0, len: 0, }] ); } ================================================ FILE: syscalls_tests/src/allow_rw.rs ================================================ use libtock_platform::{allow_rw, share, CommandReturn, ErrorCode, Syscalls}; use libtock_unittest::{command_return, fake, DriverInfo, RwAllowBuffer, SyscallLogEntry}; use std::cell::Cell; use std::rc::Rc; use std::thread_local; #[derive(Default)] struct TestDriver { buffer_0: Cell, } impl fake::SyscallDriver for TestDriver { fn info(&self) -> DriverInfo { DriverInfo::new(42) } fn command(&self, _command_num: u32, _argument0: u32, _argument1: u32) -> CommandReturn { command_return::failure(ErrorCode::NoSupport) } fn allow_readwrite( &self, buffer_num: u32, buffer: RwAllowBuffer, ) -> Result { if buffer_num != 0 { return Err((buffer, ErrorCode::NoSupport)); } Ok(self.buffer_0.replace(buffer)) } } struct TestConfig; // CALLED is set to true when returned_nonzero_buffer is called. thread_local! {static CALLED: Cell = const {Cell::new(false)} } impl allow_rw::Config for TestConfig { fn returned_nonzero_buffer(driver_num: u32, buffer_num: u32) { assert_eq!(driver_num, 42); assert_eq!(buffer_num, 0); CALLED.with(|cell| cell.set(true)); } } #[test] fn allow_rw() { let kernel = fake::Kernel::new(); let driver = Rc::new(TestDriver::default()); kernel.add_driver(&driver); let mut buffer1 = [1, 2, 3, 4]; let mut buffer2 = [5, 6]; share::scope(|allow_rw| { // Tests a call that should fail because it has an incorrect buffer // number. let result = fake::Syscalls::allow_rw::(allow_rw, &mut buffer1); assert!(!CALLED.with(|c| c.get())); assert_eq!(result, Err(ErrorCode::NoSupport)); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRw { driver_num: 42, buffer_num: 1, len: 4, }] ); }); // Verify that share::scope unallowed the buffer. assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRw { driver_num: 42, buffer_num: 1, len: 0, }] ); share::scope(|allow_rw| { // Tests a call that should succeed and return a zero buffer. let result = fake::Syscalls::allow_rw::(allow_rw, &mut buffer1); assert!(!CALLED.with(|c| c.get())); assert_eq!(result, Ok(())); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRw { driver_num: 42, buffer_num: 0, len: 4, }] ); // Tests a call that should succeed and return a nonzero buffer. let result = fake::Syscalls::allow_rw::(allow_rw, &mut buffer2); assert!(CALLED.with(|c| c.get())); assert_eq!(result, Ok(())); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRw { driver_num: 42, buffer_num: 0, len: 2, }] ); // Mutate the buffer, which under Miri will verify the buffer has been // shared with the kernel properly. let mut buffer = driver.buffer_0.take(); buffer[1] = 31; driver.buffer_0.set(buffer); }); // Verify that share::scope unallowed the buffer, but only once. assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRw { driver_num: 42, buffer_num: 0, len: 0, }] ); // Verify the buffer write occurred. assert_eq!(buffer2, [5, 31]); } ================================================ FILE: syscalls_tests/src/command_tests.rs ================================================ //! Tests for the Command system call implementation in //! `libtock_platform::Syscalls`. use libtock_platform::Syscalls; use libtock_unittest::{command_return, fake, ExpectedSyscall, SyscallLogEntry}; #[test] fn command() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: 1, command_id: 2, argument0: 3, argument1: 4, override_return: Some(command_return::success_3_u32(1, 2, 3)), }); assert_eq!( fake::Syscalls::command(1, 2, 3, 4).get_success_3_u32(), Some((1, 2, 3)) ); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Command { driver_id: 1, command_id: 2, argument0: 3, argument1: 4, }] ); } ================================================ FILE: syscalls_tests/src/exit_on_drop.rs ================================================ use libtock_platform::exit_on_drop::ExitOnDrop; use libtock_unittest::fake; // Unwinds if `unwind` is true, otherwise just returns. fn maybe_unwind(unwind: bool) { if unwind { panic!("Triggering stack unwinding."); } } #[cfg(not(miri))] #[test] fn exit() { let exit = libtock_unittest::exit_test("exit_on_drop::exit", || { let exit_on_drop: ExitOnDrop = Default::default(); maybe_unwind(true); core::mem::forget(exit_on_drop); }); assert_eq!(exit, libtock_unittest::ExitCall::Terminate(0)); } #[test] fn no_exit() { let exit_on_drop: ExitOnDrop = Default::default(); maybe_unwind(false); core::mem::forget(exit_on_drop); } ================================================ FILE: syscalls_tests/src/lib.rs ================================================ //! This crate contains tests for `libtock_platform` functionality that use the //! `Syscalls` implementation. //! //! These tests are not in `libtock_platform` because adding them to //! `libtock_platform causes two incompatible copies of `libtock_platform` to be //! compiled: //! 1. The `libtock_platform` with `cfg(test)` enabled //! 2. The `libtock_platform` that `libtock_unittest` depends on, which has //! `cfg(test)` disabled. //! //! Mixing types from the two `libtock_platform` instantiations in tests results //! in confusing error messages, so instead those tests live in this crate. #[cfg(test)] mod allow_ro; #[cfg(test)] mod allow_rw; #[cfg(test)] mod command_tests; #[cfg(test)] mod exit_on_drop; // TODO: Add Exit. #[cfg(test)] mod memop_tests; #[cfg(test)] mod subscribe_tests; #[cfg(test)] mod yield_tests; ================================================ FILE: syscalls_tests/src/memop_tests.rs ================================================ //! Tests for the Memop system call implementation in //! `libtock_platform::Syscalls`. use libtock_platform::{ErrorCode, Syscalls}; use libtock_unittest::{fake, ExpectedSyscall, SyscallLogEntry}; #[test] fn memop() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 1, argument0: 3.into(), return_error: Some(ErrorCode::NoMem), }); assert_eq!( unsafe { fake::Syscalls::memop_sbrk(3) }, Err(ErrorCode::NoMem) ); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Memop { memop_num: 1, argument0: 3.into(), }] ); } #[test] fn brk_test() { let kernel = fake::Kernel::new(); let fake_mem_buf = [0; 8]; kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 0, argument0: fake_mem_buf.as_ptr().into(), return_error: None, }); assert_eq!( unsafe { fake::Syscalls::memop_brk(fake_mem_buf.as_ptr()) }, Ok(()) ); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Memop { memop_num: 0, argument0: fake_mem_buf.as_ptr().into(), }] ); } #[test] fn sbrk_test() { let kernel = fake::Kernel::new(); let fake_mem_buf = [0; 8]; kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 0, argument0: fake_mem_buf.as_ptr().into(), return_error: None, }); assert_eq!( unsafe { fake::Syscalls::memop_brk(fake_mem_buf.as_ptr()) }, Ok(()) ); kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 1, argument0: 4.into(), return_error: None, }); assert_eq!( unsafe { fake::Syscalls::memop_sbrk(4) }, Ok((&fake_mem_buf[4]) as *const u8) ); } #[test] fn increment_brk_test() { let kernel = fake::Kernel::new(); let fake_mem_buf = [0; 8]; kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 0, argument0: fake_mem_buf.as_ptr().into(), return_error: None, }); assert_eq!( unsafe { fake::Syscalls::memop_brk(fake_mem_buf.as_ptr()) }, Ok(()) ); kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 1, argument0: 4.into(), return_error: None, }); assert_eq!( fake::Syscalls::memop_increment_brk(4), Ok((&fake_mem_buf[4]) as *const u8) ); } #[test] fn app_ram_start_test() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 2, argument0: 0.into(), return_error: None, }); assert!(fake::Syscalls::memop_app_ram_start().is_ok()); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Memop { memop_num: 2, argument0: 0.into(), }] ); } #[test] fn debug_stack_start_test() { let kernel = fake::Kernel::new(); let fake_stack = [0; 8]; kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 10, argument0: fake_stack.as_ptr().into(), return_error: None, }); assert!(fake::Syscalls::memop_debug_stack_start(fake_stack.as_ptr()).is_ok()); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Memop { memop_num: 10, argument0: fake_stack.as_ptr().into(), }] ); } #[test] fn debug_heap_start_test() { let kernel = fake::Kernel::new(); let fake_heap = [0; 8]; kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 11, argument0: fake_heap.as_ptr().into(), return_error: None, }); assert!(fake::Syscalls::memop_debug_heap_start(fake_heap.as_ptr()).is_ok()); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Memop { memop_num: 11, argument0: fake_heap.as_ptr().into(), }] ); } ================================================ FILE: syscalls_tests/src/subscribe_tests.rs ================================================ use libtock_platform::{ share, subscribe, CommandReturn, DefaultConfig, ErrorCode, Syscalls, YieldNoWaitReturn, }; use libtock_unittest::{command_return, fake, DriverInfo, DriverShareRef, SyscallLogEntry}; use std::{cell::Cell, rc::Rc}; // Fake driver that accepts an upcall. #[derive(Default)] struct MockDriver { share_ref: DriverShareRef, } impl fake::SyscallDriver for MockDriver { fn info(&self) -> DriverInfo { DriverInfo::new(1).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, _: u32, _: u32, _: u32) -> CommandReturn { command_return::failure(ErrorCode::NoSupport) } } #[test] fn config() { // Thread local used by TestConfig to indicate that returned_nonnull_upcall // has been called. std::thread_local! {static CALLED: core::cell::Cell> = const {Cell::new(None)} } struct TestConfig; impl subscribe::Config for TestConfig { fn returned_nonnull_upcall(driver_num: u32, subscribe_num: u32) { CALLED.with(|cell| cell.set(Some((driver_num, subscribe_num)))); } } let kernel = fake::Kernel::new(); kernel.add_driver(&std::rc::Rc::new(MockDriver::default())); let called = core::cell::Cell::new(false); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, TestConfig, 1, 0>(subscribe, &called), Ok(()) ); assert_eq!(CALLED.with(|cell| cell.get()), None); // Repeat the subscribe, which will make the kernel return the previous // upcall. subscribe should invoke TestConfig::returned_nonnull_upcall. assert_eq!( fake::Syscalls::subscribe::<_, _, TestConfig, 1, 0>(subscribe, &called), Ok(()) ); assert_eq!(CALLED.with(|cell| cell.get()), Some((1, 0))); }); } #[test] fn failed() { let _kernel = fake::Kernel::new(); let done = core::cell::Cell::new(false); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, 1, 2>(subscribe, &done), Err(ErrorCode::NoDevice) ); }); } #[test] fn success() { let driver = Rc::new(MockDriver::default()); let kernel = fake::Kernel::new(); kernel.add_driver(&driver); let called = core::cell::Cell::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, 1, 0>(subscribe, &called), Ok(()) ); assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Subscribe { driver_num: 1, subscribe_num: 0 }] ); driver.share_ref.schedule_upcall(0, (2, 3, 4)).unwrap(); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(called.get(), Some((2, 3, 4))); // Clear the syscall log. kernel.take_syscall_log(); }); // Verify the upcall was cleaned up correctly. assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Subscribe { driver_num: 1, subscribe_num: 0 }] ); driver.share_ref.schedule_upcall(0, (2, 3, 4)).unwrap(); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); } #[cfg(not(miri))] #[test] fn unwinding_upcall() { struct BadUpcall; impl libtock_platform::Upcall for BadUpcall { fn upcall(&self, _: u32, _: u32, _: u32) { panic!("Beginning stack unwinding"); } } let exit = libtock_unittest::exit_test("subscribe_tests::unwinding_upcall", || { let driver = Rc::new(MockDriver::default()); let kernel = fake::Kernel::new(); kernel.add_driver(&driver); let upcall = BadUpcall; share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, 1, 0>(subscribe, &upcall), Ok(()) ); driver.share_ref.schedule_upcall(0, (2, 3, 4)).unwrap(); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }); }); assert_eq!(exit, libtock_unittest::ExitCall::Terminate(0)); } ================================================ FILE: syscalls_tests/src/yield_tests.rs ================================================ //! Tests for implementations of Yield system calls in //! `libtock_platform::Syscalls`. use libtock_platform::{Syscalls, YieldNoWaitReturn}; use libtock_unittest::{fake, ExpectedSyscall, SyscallLogEntry}; // Tests yield_no_wait with an upcall executed. #[test] fn no_wait_upcall() { use YieldNoWaitReturn::Upcall; let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::YieldNoWait { override_return: Some(Upcall), }); assert_eq!(fake::Syscalls::yield_no_wait(), Upcall); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldNoWait]); } // Tests yield_no_wait with no upcall executed. #[test] fn no_wait_no_upcall() { use YieldNoWaitReturn::NoUpcall; let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::YieldNoWait { override_return: Some(NoUpcall), }); assert_eq!(fake::Syscalls::yield_no_wait(), NoUpcall); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldNoWait]); } // Tests yield_wait. #[test] fn wait() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::YieldWait { skip_upcall: true }); fake::Syscalls::yield_wait(); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldWait]); } ================================================ FILE: tools/print_sizes/Cargo.toml ================================================ # Finds all the libtock_core and libtock examples and prints the sizes of # several of their sections. Searches the target/$ARCH/release directory. Note # that print_sizes will not build the examples; that is done by the # `print-sizes` Makefile action. [package] authors = ["Tock Project Developers "] edition = "2018" name = "print_sizes" rust-version.workspace = true version = "0.1.0" [dependencies] elf = "0.0.10" ================================================ FILE: tools/print_sizes/src/main.rs ================================================ // Architectures that we expect the examples to be built for. const ARCHITECTURES: [&str; 2] = ["riscv32imc-unknown-none-elf", "thumbv7em-none-eabi"]; // The order of these fields actually matters, because it affects the derived // Ord impl. I have a suspicion that when I introduce size diffs into the CI, // this order will make the eventual diffs easier to understand than other // orderings. #[derive(Eq, PartialEq, PartialOrd, Ord)] struct Example { name: String, arch: &'static str, path: std::path::PathBuf, } // Finds the example binaries and returns a list of their paths. fn find_examples() -> Vec { // Find target/ using std::env::current_exe(). let exe_dir = std::env::current_exe().expect("Unable to find executable location"); let target_dir = exe_dir .parent() .expect("Unable to find target/ directory") .parent() .expect("Unable to find target/ directory"); let mut examples = Vec::new(); for arch in &ARCHITECTURES { // Set examples_dir to target/$ARCH/examples/ let mut examples_dir = target_dir.to_path_buf(); examples_dir.push(arch); examples_dir.push("release"); examples_dir.push("examples"); // If the architecture's examples directory exists, iterate through the // files through it and search for examples. If the directory doesn't // exist we skip this architecture. if let Ok(read_dir) = examples_dir.read_dir() { for file in read_dir.filter_map(Result::ok) { use std::os::unix::ffi::OsStrExt; // Skip entries that are not files. If file_type() returns // Err(_) we skip the entry as well. if !file.file_type().is_ok_and(|t| t.is_file()) { continue; } // Skip files with dots (*.d files) and hyphens (-$HASH) in // them. if file.file_name().as_bytes().contains(&b'.') || file.file_name().as_bytes().contains(&b'-') { continue; } examples.push(Example { name: file.file_name().to_string_lossy().into_owned(), arch, path: file.path(), }); } } } examples } struct ElfSizes { bss: u64, data: u64, rodata: u64, text: u64, } fn get_sizes(path: &std::path::Path) -> ElfSizes { let file = elf::File::open_path(path).expect("Unable to open example binary"); let mut sizes = ElfSizes { bss: 0, data: 0, rodata: 0, text: 0, }; for section in file.sections { match section.shdr.name.as_ref() { ".bss" => sizes.bss = section.shdr.size, ".data" => sizes.data = section.shdr.size, ".rodata" => sizes.rodata = section.shdr.size, ".text" => sizes.text = section.shdr.size, _ => {} } } sizes } struct ExampleData { name: String, arch: &'static str, sizes: ElfSizes, } fn main() { let mut examples = find_examples(); examples.sort_unstable(); let example_data: Vec<_> = examples .drain(..) .map(|example| ExampleData { name: example.name, arch: example.arch, sizes: get_sizes(&example.path), }) .collect(); let name_width = 20; let arch_width = example_data .iter() .map(|a| a.arch.len()) .max() .expect("No examples found"); let section_width = 7; println!( "{0:1$} {2:3$} {4:>8$} {5:>8$} {6:>8$} {7:>8$}", "Example", name_width, "Architecture", arch_width, ".bss", ".data", ".text", ".rodata", section_width ); for data in example_data { println!( "{0:1$} {2:3$} {4:8$} {5:8$} {6:8$} {7:8$}", data.name, name_width, data.arch, arch_width, data.sizes.bss, data.sizes.data, data.sizes.text, data.sizes.rodata, section_width ); } } ================================================ FILE: ufmt/Cargo.toml ================================================ [package] authors = ["Jorge Aparicio "] categories = ["embedded", "no-std"] description = "A (6-40x) smaller, (2-9x) faster and panic-free alternative to `core::fmt`" documentation = "https://docs.rs/ufmt" edition = "2021" keywords = ["Debug", "Display", "Write", "format"] license = "MIT OR Apache-2.0" name = "ufmt" readme = "README.md" repository = "https://github.com/japaric/ufmt" version = "0.1.0" [dependencies] proc-macro-hack = "0.5.11" ufmt-macros = { path = "macros", version = "0.1.0" } ufmt-write = { path = "write", version = "0.1.0" } # NOTE do NOT add an `alloc` feature before the alloc crate can be used in # no-std BINARIES [features] # NOTE do NOT turn `std` into a default feature; this is a no-std first crate std = ["ufmt-write/std"] [[test]] name = "vs-std-write" required-features = ["std"] ================================================ FILE: ufmt/LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: ufmt/LICENSE-MIT ================================================ Copyright (c) 2019 Jorge Aparicio Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: ufmt/README.md ================================================ # Tock fork of `ufmt` **This is a fork of the [ufmt](https://github.com/japaric/ufmt) crate.** We are currently evaluating whether `libtock-rs` should use `ufmt` for its debug formatting functionality. Because `ufmt` has unfixed bugs (and appears to be unmaintained), we have forked it here and are making bugfixes. This fork is temporary and will be removed when one of the following happens: 1. We decide that `ufmt` is the right solution for `libtock-rs`. If this happens, we will work to restore the maintenance of `ufmt`. 2. We decide that `ufmt` is not the right solution for `libtock-rs`, at which point we will replace it with an alternative. We did the following to create this fork: 1. `wget 'https://github.com/japaric/ufmt/archive/fe817a3cd5d1a3f4edaf8828193519069f2901ec.zip'` 1. `unzip fe817a3cd5d1a3f4edaf8828193519069f2901ec.zip` 1. `mv ufmt-fe817a3cd5d1a3f4edaf8828193519069f2901ec ufmt` 1. Changed the wording of the License section of `libtock-rs`' `README.md` to mention that `ufmt` has its own license. 1. cd `ufmt` 1. Removed CI infrastructure and tools that aren't used now that this is in a subdirectory of `libtock-rs`: `rm -r cg.png ci .github .gitignore nopanic .travis.yml` 1. Committed, creating commit [5163051a2d7fefbe4c9b1c6ba62b79fa07c324e7](https://github.com/tock/libtock-rs/commit/5163051a2d7fefbe4c9b1c6ba62b79fa07c324e7). Further changes beyond the commit mentioned above are in separate commits so their diffs are readable. # `μfmt` > A (6-40x) smaller, (2-9x) faster and panic-free alternative to `core::fmt` ![Call graph of formatting structs](cg.png) Call graph of a program that formats some structs (generated using [`cargo-call-stack`]). Source code can be found at the bottom of this file. The program was compiled with `-C opt-level=z`. [`cargo-call-stack`]: https://crates.io/crates/cargo-call-stack ## [API docs](https://docs.rs/ufmt) ## Design goals From highest priority to lowest priority - Optimized for binary size and speed (rather than for compilation time) - No dynamic dispatch in generated code - No panicking branches in generated code, when optimized - No recursion where possible ## Features - `Debug` and `Display`-like traits - `core::write!`-like macro - A generic `Formatter<'_, impl uWrite>` instead of a single `core::Formatter`; the `uWrite` trait has an associated error type so each writer can choose its error type. For example, the implementation for `std::String` uses `Infallible` as its error type. - `core::fmt::Formatter::debug_struct`-like API - `#[derive(uDebug)]` - Pretty formatting (`{:#?}`) for `uDebug` # Minimum Supported Rust Version (MSRV) This crate is guaranteed to compile on stable Rust 1.34 and up. It *might* compile on older versions but that may change in any new patch release. ## License All source code (including code snippets) is licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [https://www.apache.org/licenses/LICENSE-2.0][L1]) - MIT license ([LICENSE-MIT](LICENSE-MIT) or [https://opensource.org/licenses/MIT][L2]) [L1]: https://www.apache.org/licenses/LICENSE-2.0 [L2]: https://opensource.org/licenses/MIT at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions. ## Appendix ### Formatting structs (snippet) Full source code in [nopanic/examples/struct.rs](nopanic/examples/struct.rs). ``` rust // .. #[derive(Clone, Copy, uDebug)] struct Pair { x: i32, y: i32, } static X: AtomicI32 = AtomicI32::new(0); static Y: AtomicI32 = AtomicI32::new(0); #[exception] fn PendSV() { let x = X.load(Ordering::Relaxed); let y = Y.load(Ordering::Relaxed); uwrite!(&mut W, "{:?}", Braces {}).unwrap(); uwrite!(&mut W, "{:#?}", Braces {}).unwrap(); uwrite!(&mut W, "{:?}", Parens()).unwrap(); uwrite!(&mut W, "{:#?}", Parens()).unwrap(); uwrite!(&mut W, "{:?}", I32(x)).unwrap(); uwrite!(&mut W, "{:#?}", I32(x)).unwrap(); uwrite!(&mut W, "{:?}", Tuple(x, y)).unwrap(); uwrite!(&mut W, "{:#?}", Tuple(x, y)).unwrap(); let pair = Pair { x, y }; uwrite!(&mut W, "{:?}", pair).unwrap(); uwrite!(&mut W, "{:#?}", pair).unwrap(); let first = pair; let second = pair; uwrite!(&mut W, "{:?}", Nested { first, second }).unwrap(); uwrite!(&mut W, "{:#?}", Nested { first, second }).unwrap(); } // .. ``` ================================================ FILE: ufmt/macros/Cargo.toml ================================================ [package] authors = ["Jorge Aparicio "] categories = ["embedded", "no-std"] description = "`μfmt` macros" edition = "2021" keywords = ["Debug", "Display", "Write", "format"] license = "MIT OR Apache-2.0" name = "ufmt-macros" repository = "https://github.com/japaric/ufmt" version = "0.1.1" [lib] proc-macro = true [dependencies] proc-macro-hack = "0.5.11" proc-macro2 = "1" quote = "1" regex = "1.5.4" lazy_static = "1.4.0" [dependencies.syn] features = ["full"] version = "1" ================================================ FILE: ufmt/macros/src/lib.rs ================================================ //! `μfmt` macros // Added when we vendored ufmt into libtock-rs to avoid needing to immediately // make many changes. #![allow(clippy::all)] #![deny(warnings)] extern crate proc_macro; use core::mem; use proc_macro::TokenStream; use std::borrow::Cow; use lazy_static::lazy_static; use proc_macro2::{Literal, Span}; use proc_macro_hack::proc_macro_hack; use quote::quote; use syn::{ parse::{self, Parse, ParseStream}, parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Data, DeriveInput, Expr, Fields, GenericParam, Ident, LitStr, Token, }; /// Automatically derive the `uDebug` trait for a `struct` or `enum` /// /// Supported items /// /// - all kind of `struct`-s /// - all kind of `enum`-s /// /// `union`-s are not supported #[proc_macro_derive(uDebug)] pub fn debug(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut generics = input.generics; for param in &mut generics.params { if let GenericParam::Type(type_param) = param { type_param.bounds.push(parse_quote!(ufmt::uDebug)); } } let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let ident = &input.ident; let ts = match input.data { Data::Struct(data) => { let ident_s = ident.to_string(); let body = match data.fields { Fields::Named(fields) => { let fields = fields .named .iter() .map(|field| { let ident = field.ident.as_ref().expect("UNREACHABLE"); let name = ident.to_string(); quote!(field(#name, &self.#ident)?) }) .collect::>(); quote!(f.debug_struct(#ident_s)?#(.#fields)*.finish()) } Fields::Unnamed(fields) => { let fields = (0..fields.unnamed.len()) .map(|i| { let i = Literal::u64_unsuffixed(i as u64); quote!(field(&self.#i)?) }) .collect::>(); quote!(f.debug_tuple(#ident_s)?#(.#fields)*.finish()) } Fields::Unit => quote!(f.write_str(#ident_s)), }; quote!( impl #impl_generics ufmt::uDebug for #ident #ty_generics #where_clause { fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> core::result::Result<(), W::Error> where W: ufmt::uWrite + ?Sized, { #body } } ) } Data::Enum(data) => { let arms = data .variants .iter() .map(|var| { let variant = &var.ident; let variant_s = variant.to_string(); match &var.fields { Fields::Named(fields) => { let mut pats = Vec::with_capacity(fields.named.len()); let mut methods = Vec::with_capacity(fields.named.len()); for field in &fields.named { let ident = field.ident.as_ref().unwrap(); let ident_s = ident.to_string(); pats.push(quote!(#ident)); methods.push(quote!(field(#ident_s, #ident)?)); } quote!( #ident::#variant { #(#pats),* } => { f.debug_struct(#variant_s)?#(.#methods)*.finish() } ) } Fields::Unnamed(fields) => { let pats = &(0..fields.unnamed.len()) .map(|i| Ident::new(&format!("_{}", i), Span::call_site())) .collect::>(); quote!( #ident::#variant(#(#pats),*) => { f.debug_tuple(#variant_s)?#(.field(#pats)?)*.finish() } ) } Fields::Unit => quote!( #ident::#variant => { f.write_str(#variant_s) } ), } }) .collect::>(); quote!( impl #impl_generics ufmt::uDebug for #ident #ty_generics #where_clause { fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> core::result::Result<(), W::Error> where W: ufmt::uWrite + ?Sized, { match self { #(#arms),* } } } ) } Data::Union(..) => { return parse::Error::new(Span::call_site(), "this trait cannot be derived for unions") .to_compile_error() .into(); } }; ts.into() } #[proc_macro_hack] pub fn uwrite(input: TokenStream) -> TokenStream { write(input, false) } #[proc_macro_hack] pub fn uwriteln(input: TokenStream) -> TokenStream { write(input, true) } fn write(input: TokenStream, newline: bool) -> TokenStream { let input = parse_macro_input!(input as Input); let formatter = &input.formatter; let literal = input.literal; let mut format = literal.value(); if newline { format.push('\n'); } let pieces = match parse(&format, literal.span()) { Err(e) => return e.to_compile_error().into(), Ok(pieces) => pieces, }; let required_args = pieces.iter().filter(|piece| !piece.is_str()).count(); let supplied_args = input.args.len(); if supplied_args < required_args { return parse::Error::new( literal.span(), &format!( "format string requires {} arguments but {} {} supplied", required_args, supplied_args, if supplied_args == 1 { "was" } else { "were" } ), ) .to_compile_error() .into(); } else if supplied_args > required_args { return parse::Error::new( input.args[required_args].span(), &format!("argument never used"), ) .to_compile_error() .into(); } let mut args = vec![]; let mut pats = vec![]; let mut exprs = vec![]; let mut i = 0; for piece in pieces { if let Piece::Str(s) = piece { exprs.push(quote!(f.write_str(#s)?;)) } else { let pat = mk_ident(i); let arg = &input.args[i]; i += 1; args.push(quote!(&(#arg))); pats.push(quote!(#pat)); match piece { Piece::Display { pretty, hex, width, pad, } => { exprs.push(if let Some(w) = width { if let Some(case) = hex { if case == Hex::Lower { quote!(f.fixed_width(#pretty, Some(true), Some(#w), #pad, |f| ufmt::uDisplay::fmt(#pat, f))?;) } else { quote!(f.fixed_width(#pretty, Some(false), Some(#w), #pad, |f| ufmt::uDisplay::fmt(#pat, f))?;) } } else { quote!(f.fixed_width(#pretty, None, Some(#w), #pad, |f| ufmt::uDisplay::fmt(#pat, f))?;) } } else if hex == Some(Hex::Lower) && pretty { quote!(f.hex(true, true, |f| ufmt::uDisplay::fmt(#pat, f))?;) } else if hex == Some(Hex::Upper) && pretty { quote!(f.hex(false, true, |f| ufmt::uDisplay::fmt(#pat, f))?;) } else if hex == Some(Hex::Lower) { quote!(f.hex(true, false, |f| ufmt::uDisplay::fmt(#pat, f))?;) } else if hex == Some(Hex::Upper) { quote!(f.hex(false, false, |f| ufmt::uDisplay::fmt(#pat, f))?;) } else if pretty { quote!(f.pretty(|f| ufmt::uDisplay::fmt(#pat, f))?;) } else { quote!(ufmt::uDisplay::fmt(#pat, f)?;) }); } Piece::Debug { pretty } => { exprs.push(if pretty { quote!(f.pretty(|f| ufmt::uDebug::fmt(#pat, f))?;) } else { quote!(ufmt::uDebug::fmt(#pat, f)?;) }); } _ => unreachable!(), } } } quote!(match (#(#args),*) { (#(#pats),*) => { use ufmt::UnstableDoAsFormatter as _; (#formatter).do_as_formatter(|f| { #(#exprs)* Ok(()) }) } }) .into() } struct Input { formatter: Expr, _comma: Token![,], literal: LitStr, _comma2: Option, args: Punctuated, } impl Parse for Input { fn parse(input: ParseStream) -> parse::Result { let formatter = input.parse()?; let _comma = input.parse()?; let literal = input.parse()?; if input.is_empty() { Ok(Input { formatter, _comma, literal, _comma2: None, args: Punctuated::new(), }) } else { Ok(Input { formatter, _comma, literal, _comma2: input.parse()?, args: Punctuated::parse_terminated(input)?, }) } } } #[derive(Debug, PartialEq)] pub(crate) enum Hex { Upper, Lower, } #[derive(Debug, PartialEq)] enum Piece<'a> { Debug { pretty: bool, }, Display { pretty: bool, hex: Option, width: Option, pad: char, }, Str(Cow<'a, str>), } impl Piece<'_> { fn is_str(&self) -> bool { match self { Piece::Str(_) => true, _ => false, } } } fn mk_ident(i: usize) -> Ident { Ident::new(&format!("__{}", i), Span::call_site()) } // `}}` -> `}` fn unescape<'l>(mut literal: &'l str, span: Span) -> parse::Result> { if literal.contains('}') { let mut buf = String::new(); while literal.contains('}') { const ERR: &str = "format string contains an unmatched right brace"; let mut parts = literal.splitn(2, '}'); match (parts.next(), parts.next()) { (Some(left), Some(right)) => { const ESCAPED_BRACE: &str = "}"; if right.starts_with(ESCAPED_BRACE) { buf.push_str(left); buf.push('}'); literal = &right[ESCAPED_BRACE.len()..]; } else { return Err(parse::Error::new(span, ERR)); } } _ => unreachable!(), } } buf.push_str(literal); Ok(buf.into()) } else { Ok(Cow::Borrowed(literal)) } } fn parse<'l>(mut literal: &'l str, span: Span) -> parse::Result>> { let mut pieces = vec![]; let mut buf = String::new(); loop { let mut parts = literal.splitn(2, '{'); match (parts.next(), parts.next()) { // empty string literal (None, None) => break, // end of the string literal (Some(s), None) => { if buf.is_empty() { if !s.is_empty() { pieces.push(Piece::Str(unescape(s, span)?)); } } else { buf.push_str(&unescape(s, span)?); pieces.push(Piece::Str(Cow::Owned(buf))); } break; } (head, Some(tail)) => { const DEBUG: &str = ":?}"; const DEBUG_PRETTY: &str = ":#?}"; const DISPLAY: &str = "}"; const ESCAPED_BRACE: &str = "{"; use regex::Regex; lazy_static! { static ref WIDTH_REGEX: Regex = Regex::new(r"^:(#?)(0?)([0-9]*)([x|X]?)}").unwrap(); } let head = head.unwrap_or(""); if tail.starts_with(DEBUG) || tail.starts_with(DEBUG_PRETTY) || tail.starts_with(DISPLAY) || WIDTH_REGEX.is_match(tail) // for width specifiers { if buf.is_empty() { if !head.is_empty() { pieces.push(Piece::Str(unescape(head, span)?)); } } else { buf.push_str(&unescape(head, span)?); pieces.push(Piece::Str(Cow::Owned(mem::replace( &mut buf, String::new(), )))); } if tail.starts_with(DEBUG) { pieces.push(Piece::Debug { pretty: false }); literal = &tail[DEBUG.len()..]; } else if tail.starts_with(DEBUG_PRETTY) { pieces.push(Piece::Debug { pretty: true }); literal = &tail[DEBUG_PRETTY.len()..]; } else if let Some(cap) = WIDTH_REGEX.captures(tail) { let leading_pound = cap[1].eq("#"); let leading_zero = cap[2].eq("0"); let width = if cap[3].len() > 0 { Some(cap[3].parse::().map_err(|_| { parse::Error::new( span, "invalid width specifier: expected valid u8", ) })?) } else { None }; // 18 is the smallest buffer used by any numeric uDebug impl // By increasing that buffer size, we could increase this maximum value, // at the cost of cycles and a small amount of size if let Some(w) = width { if w > 18 { return Err(parse::Error::new( span, "invalid width specifier: maximum allowed specifier is 18", )); } } let hex = if cap[4].eq("x") { Some(Hex::Lower) } else if cap[4].eq("X") { Some(Hex::Upper) } else { None }; pieces.push(Piece::Display { pretty: leading_pound, hex, width, pad: if leading_zero { '0' } else { ' ' }, }); // first capture is entire match literal = &tail[cap[0].len()..]; } else { pieces.push(Piece::Display { pretty: false, hex: None, width: None, pad: ' ', }); literal = &tail[DISPLAY.len()..]; } } else if tail.starts_with(ESCAPED_BRACE) { buf.push_str(&unescape(head, span)?); buf.push('{'); literal = &tail[ESCAPED_BRACE.len()..]; } else { return Err(parse::Error::new( span, "invalid format string: expected `{{`, `{}`, `{:?}`, `{:#?}`, `{:x}`, `{:#x}`, or a width specifier}", )); } } } } Ok(pieces) } #[cfg(test)] mod tests { use std::borrow::Cow; use proc_macro2::Span; use crate::Hex; use crate::Piece; #[test] fn pieces() { let span = Span::call_site(); // string interpolation assert_eq!( super::parse("The answer is {}", span).ok(), Some(vec![ Piece::Str(Cow::Borrowed("The answer is ")), Piece::Display { pretty: false, hex: None, width: None, pad: ' ' } ]), ); assert_eq!( super::parse("{:?}", span).ok(), Some(vec![Piece::Debug { pretty: false }]), ); assert_eq!( super::parse("{:#?}", span).ok(), Some(vec![Piece::Debug { pretty: true }]), ); assert_eq!( super::parse("{:x}", span).ok(), Some(vec![Piece::Display { pretty: false, hex: Some(Hex::Lower), width: None, pad: ' ', }]), ); assert_eq!( super::parse("{:X}", span).ok(), Some(vec![Piece::Display { pretty: false, hex: Some(Hex::Upper), width: None, pad: ' ', }]), ); assert_eq!( super::parse("{:10x}", span).ok(), Some(vec![Piece::Display { pretty: false, hex: Some(Hex::Lower), width: Some(10), pad: ' ', }]), ); assert_eq!( super::parse("{:08x}", span).ok(), Some(vec![Piece::Display { pretty: false, hex: Some(Hex::Lower), width: Some(8), pad: '0', }]), ); assert_eq!( super::parse("{:#08x}", span).ok(), Some(vec![Piece::Display { pretty: true, hex: Some(Hex::Lower), width: Some(8), pad: '0', }]), ); // escaped braces assert_eq!( super::parse("{{}} is not an argument", span).ok(), Some(vec![Piece::Str(Cow::Borrowed("{} is not an argument"))]), ); // left brace & junk assert!(super::parse("{", span).is_err()); assert!(super::parse(" {", span).is_err()); assert!(super::parse("{ ", span).is_err()); assert!(super::parse("{ {", span).is_err()); } #[test] fn unescape() { let span = Span::call_site(); // no right brace assert_eq!(super::unescape("", span).ok(), Some(Cow::Borrowed(""))); assert_eq!( super::unescape("Hello", span).ok(), Some(Cow::Borrowed("Hello")) ); // unmatched right brace assert!(super::unescape(" }", span).is_err()); assert!(super::unescape("} ", span).is_err()); assert!(super::unescape("}", span).is_err()); // escaped right brace assert_eq!(super::unescape("}}", span).ok(), Some(Cow::Borrowed("}"))); assert_eq!(super::unescape("}} ", span).ok(), Some(Cow::Borrowed("} "))); } } ================================================ FILE: ufmt/src/helpers.rs ================================================ use crate::{uDebug, uWrite, Formatter}; impl<'w, W> Formatter<'w, W> where W: uWrite + ?Sized, { /// Creates a `DebugList` builder designed to assist with creation of `uDebug` implementations /// for list-like structures. pub fn debug_list(&mut self) -> Result, W::Error> { self.write_str("[")?; if self.pretty { self.indentation += 1; } Ok(DebugList { first: true, formatter: self, }) } /// Creates a `DebugMap` builder designed to assist with creation of `uDebug` implementations /// for map-like structures. pub fn debug_map(&mut self) -> Result, W::Error> { self.write_str("{")?; if self.pretty { self.indentation += 1; } Ok(DebugMap { first: true, formatter: self, }) } /// Creates a `DebugSet` builder designed to assist with creation of `uDebug` implementations /// for set-like structures. pub fn debug_set(&mut self) -> Result, W::Error> { self.write_str("{")?; if self.pretty { self.indentation += 1; } Ok(DebugSet { first: true, formatter: self, }) } /// Creates a `DebugStruct` builder designed to assist with creation of `uDebug` implementations /// for structs. pub fn debug_struct(&mut self, name: &str) -> Result, W::Error> { self.write_str(name)?; if self.pretty { self.indentation += 1; } Ok(DebugStruct { first: true, formatter: self, }) } /// Creates a `DebugTuple` builder designed to assist with creation of `uDebug` implementations /// for tuple structs. pub fn debug_tuple(&mut self, name: &str) -> Result, W::Error> { self.write_str(name)?; if self.pretty { self.indentation += 1; } Ok(DebugTuple { fields: 0, first: true, formatter: self, unnamed: name.is_empty(), }) } } /// A struct to help with [`uDebug`] implementations. /// /// This is useful when you wish to output a formatted list of items as a part of your /// [`uDebug::fmt`] implementation. /// /// This can be constructed by the [`Formatter::debug_list`] method. pub struct DebugList<'f, 'w, W> where W: uWrite + ?Sized, { first: bool, formatter: &'f mut Formatter<'w, W>, } impl DebugList<'_, '_, W> where W: uWrite + ?Sized, { /// Adds a new entry to the list output. pub fn entry(&mut self, entry: &impl uDebug) -> Result<&mut Self, W::Error> { if self.first { self.first = false; if self.formatter.pretty { self.formatter.write_str("\n")?; } } else if !self.formatter.pretty { self.formatter.write_str(", ")?; } if self.formatter.pretty { self.formatter.indent()?; } entry.fmt(self.formatter)?; if self.formatter.pretty { self.formatter.write_str(",\n")?; } Ok(self) } /// Adds the contents of an iterator of entries to the list output. pub fn entries( &mut self, entries: impl IntoIterator, ) -> Result<&mut Self, W::Error> { for entry in entries { self.entry(&entry)?; } Ok(self) } /// Finishes output pub fn finish(&mut self) -> Result<(), W::Error> { if self.formatter.pretty { self.formatter.indentation -= 1; self.formatter.indent()?; } self.formatter.write_str("]") } } /// A struct to help with [`uDebug`] implementations. /// /// This is useful when you wish to output a formatted map as a part of your [`uDebug::fmt`] /// implementation. /// /// This can be constructed by the [`Formatter::debug_map`] method. pub struct DebugMap<'f, 'w, W> where W: uWrite + ?Sized, { first: bool, formatter: &'f mut Formatter<'w, W>, } impl DebugMap<'_, '_, W> where W: uWrite + ?Sized, { /// Adds a new entry to the map output. pub fn entry(&mut self, key: &impl uDebug, value: &impl uDebug) -> Result<&mut Self, W::Error> { if self.first { self.first = false; if self.formatter.pretty { self.formatter.write_str("\n")?; } } else if !self.formatter.pretty { self.formatter.write_str(", ")?; } if self.formatter.pretty { self.formatter.indent()?; } key.fmt(self.formatter)?; self.formatter.write_str(": ")?; value.fmt(self.formatter)?; if self.formatter.pretty { self.formatter.write_str(",\n")?; } Ok(self) } /// Adds the contents of an iterator of entries to the map output. pub fn entries( &mut self, entries: impl IntoIterator, ) -> Result<&mut Self, W::Error> { for (k, v) in entries.into_iter() { self.entry(&k, &v)?; } Ok(self) } /// Finishes output pub fn finish(&mut self) -> Result<(), W::Error> { self.formatter.write_str("}") } } /// A struct to help with [`uDebug`] implementations. /// /// This is useful when you wish to output a formatted set of items as a part of your /// [`uDebug::fmt`] implementation. /// /// This can be constructed by the [`Formatter::debug_set`] method. pub struct DebugSet<'f, 'w, W> where W: uWrite + ?Sized, { first: bool, formatter: &'f mut Formatter<'w, W>, } impl DebugSet<'_, '_, W> where W: uWrite + ?Sized, { /// Adds a new entry to the set output. pub fn entry(&mut self, entry: &impl uDebug) -> Result<&mut Self, W::Error> { if self.first { self.first = false; if self.formatter.pretty { self.formatter.write_str("\n")?; } } else if !self.formatter.pretty { self.formatter.write_str(", ")?; } if self.formatter.pretty { self.formatter.indent()?; } entry.fmt(self.formatter)?; if self.formatter.pretty { self.formatter.write_str(",\n")?; } Ok(self) } /// Adds the contents of an iterator of entries to the set output. pub fn entries( &mut self, entries: impl IntoIterator, ) -> Result<&mut Self, W::Error> { for entry in entries { self.entry(&entry)?; } Ok(self) } /// Finishes output pub fn finish(&mut self) -> Result<(), W::Error> { self.formatter.write_str("}") } } /// A struct to help with [`uDebug`] implementations. /// /// This is useful when you wish to output a formatted struct as a part of your [`uDebug::fmt`] /// implementation. /// /// This can be constructed by the [`Formatter::debug_struct`] method. pub struct DebugStruct<'f, 'w, W> where W: uWrite + ?Sized, { first: bool, formatter: &'f mut Formatter<'w, W>, } impl DebugStruct<'_, '_, W> where W: uWrite + ?Sized, { /// Adds a new field to the generated struct output. pub fn field(&mut self, name: &str, value: &impl uDebug) -> Result<&mut Self, W::Error> { if self.first { self.first = false; self.formatter.write_str(" {")?; if self.formatter.pretty { self.formatter.write_str("\n")?; } else { self.formatter.write_str(" ")?; } } else if !self.formatter.pretty { self.formatter.write_str(", ")?; } if self.formatter.pretty { self.formatter.indent()?; } self.formatter.write_str(name)?; self.formatter.write_str(": ")?; value.fmt(self.formatter)?; if self.formatter.pretty { self.formatter.write_str(",\n")?; } Ok(self) } /// Finishes output pub fn finish(&mut self) -> Result<(), W::Error> { if self.formatter.pretty { self.formatter.indentation -= 1; } if !self.first { if self.formatter.pretty { self.formatter.indent()?; } else { self.formatter.write_str(" ")?; } self.formatter.write_str("}")?; } Ok(()) } } /// A struct to help with [`uDebug`] implementations. /// /// This is useful when you wish to output a formatted tuple as a part of your [`uDebug::fmt`] /// implementation. /// /// This can be constructed by the [`Formatter::debug_tuple`] method. pub struct DebugTuple<'f, 'w, W> where W: uWrite + ?Sized, { fields: u8, first: bool, formatter: &'f mut Formatter<'w, W>, unnamed: bool, } impl DebugTuple<'_, '_, W> where W: uWrite + ?Sized, { /// Adds a new field to the generated tuple struct output. pub fn field(&mut self, value: &impl uDebug) -> Result<&mut Self, W::Error> { self.fields += 1; if self.first { self.first = false; self.formatter.write_str("(")?; if self.formatter.pretty { self.formatter.write_str("\n")?; } } else if !self.formatter.pretty { self.formatter.write_str(", ")?; } if self.formatter.pretty { self.formatter.indent()?; } value.fmt(self.formatter)?; if self.formatter.pretty { self.formatter.write_str(",\n")?; } Ok(self) } /// Finishes output pub fn finish(&mut self) -> Result<(), W::Error> { if self.formatter.pretty { self.formatter.indentation -= 1; } if !self.first { if self.formatter.pretty { self.formatter.indent()?; } else if self.unnamed && self.fields == 1 { // this is a one-element tuple so we need a trailing comma self.formatter.write_str(",")?; } self.formatter.write_str(")")?; } Ok(()) } } ================================================ FILE: ufmt/src/impls/array.rs ================================================ use crate::{uDebug, uWrite, Formatter}; macro_rules! array { ($($N:expr),+) => { $( impl uDebug for [T; $N] where T: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { <[T] as uDebug>::fmt(self, f) } } )+ } } array!( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 ); ================================================ FILE: ufmt/src/impls/core.rs ================================================ use crate::{uDebug, uDisplay, uWrite, Formatter}; impl uDebug for bool { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { if *self { f.write_str("true") } else { f.write_str("false") } } } impl uDisplay for bool { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } // FIXME this (`escape_debug`) contains a panicking branch // impl uDebug for char { // fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> // where // W: uWrite + ?Sized, // { // f.write_str("'")?; // for c in self.escape_debug() { // f.write_char(c)? // } // f.write_str("'") // } // } impl uDisplay for char { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.write_char(*self) } } impl uDebug for [T] where T: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.debug_list()?.entries(self)?.finish() } } // Below borrowed from https://github.com/japaric/ufmt/pull/25 impl uDebug for str { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.write_str("\"")?; let mut from = 0; for (i, c) in self.char_indices() { let esc = c.escape_debug(); // If char needs escaping, flush backlog so far and write, else skip if esc.len() != 1 { // SAFETY: `char_indices()` guarantees that `i` is always the index of utf-8 boundary of `c`. // In the first iteration `from` is zero and therefore also the index of the bounardy of `c`. // In the following iterations `from` either keeps its value or is set to `i + c.len_utf8()` // (with last rounds `i` and `c`), which means `from` is again `i` (this round), the index // of this rounds `c`. Notice that this also implies `from <= i`. f.write_str(unsafe { self.get_unchecked(from..i) })?; for c in esc { f.write_char(c)?; } from = i + c.len_utf8(); } } // SAFETY: As seen above, `from` is the index of an utf-8 boundary in `self`. // This also means that from is in bounds of `self`: `from <= self.len()`. f.write_str(unsafe { self.get_unchecked(from..) })?; f.write_str("\"") } } impl uDisplay for str { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.write_str(self) } } impl uDebug for &'_ T where T: uDebug + ?Sized, { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDisplay for &'_ T where T: uDisplay + ?Sized, { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for &'_ mut T where T: uDebug + ?Sized, { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDisplay for &'_ mut T where T: uDisplay + ?Sized, { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for Option where T: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { match self { None => f.write_str("None"), Some(x) => f.debug_tuple("Some")?.field(x)?.finish(), } } } impl uDebug for Result where T: uDebug, E: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { match self { Err(e) => f.debug_tuple("Err")?.field(e)?.finish(), Ok(x) => f.debug_tuple("Ok")?.field(x)?.finish(), } } } ================================================ FILE: ufmt/src/impls/ixx.rs ================================================ use core::str; use crate::{uDebug, uDisplay, uWrite, Formatter}; macro_rules! ixx_pad { ($uxx:ty, $n:expr, $w:expr, $p:expr, $buf:expr) => {{ let n = $n; let len = $buf.len(); let negative = n.is_negative(); let mut n = if negative { match n.checked_abs() { Some(n) => n as $uxx, None => <$uxx>::max_value() / 2 + 1, } } else { n as $uxx }; let mut i = $buf.len() - 1; loop { *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = (n % 10) as u8 + b'0'; n = n / 10; if n == 0 { break; } else { i -= 1; } } // Now fill in padding up to the prescribed width. // We do not support widths shorter than the value being // printed, like core::fmt::format!() // Also, we currently only support ' ' and '0' padding. match ($w, $p) { // For now, we default to left padding for all int-like values (Some(mut w), pad) => { // for space padding, pad before negative sign // for 0 padding, pad after negative sign if negative && pad == ' ' { i -= 1; *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = b'-'; } else if negative { w -= 1; } while i > (len - (w as usize)) { i -= 1; let byte = match pad { '0' => b'0', _ => b' ', }; *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = byte; } } _ => {} } if negative && ($w.is_none() || $p != ' ') { i -= 1; *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = b'-'; } unsafe { str::from_utf8_unchecked($buf.get(i..).unwrap_or_else(|| assume_unreachable!())) } }}; } fn isize_pad(n: isize, width: Option, pad: char, buf: &mut [u8]) -> &str { ixx_pad!(usize, n, width, pad, buf) } impl uDebug for i8 { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { if f.hex.is_some() { ::fmt(&(*self as u8), f) } else { let mut buf: [u8; 18] = [0; 18]; f.write_str(isize_pad(isize::from(*self), f.width, f.pad, &mut buf)) } } } impl uDisplay for i8 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for i16 { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { if f.hex.is_some() { ::fmt(&(*self as u16), f) } else { let mut buf: [u8; 18] = [0; 18]; f.write_str(isize_pad(isize::from(*self), f.width, f.pad, &mut buf)) } } } impl uDisplay for i16 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for i32 { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { if f.hex.is_some() { ::fmt(&(*self as u32), f) } else { let mut buf: [u8; 21] = [0; 21]; f.write_str(isize_pad(*self as isize, f.width, f.pad, &mut buf)) } } } impl uDisplay for i32 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for i64 { #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { if f.hex.is_some() { ::fmt(&(*self as u64), f) } else { let mut buf: [u8; 30] = [0; 30]; let s = ixx_pad!(u64, *self, f.width, f.pad, buf); f.write_str(s) } } #[cfg(target_pointer_width = "64")] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { if f.hex.is_some() { ::fmt(&(*self as u64), f) } else { let mut buf: [u8; 30] = [0; 30]; f.write_str(isize_pad(*self as isize, f.width, f.pad, &mut buf)) } } } impl uDisplay for i64 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for i128 { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { if f.hex.is_some() { ::fmt(&(*self as u128), f) } else { let mut buf: [u8; 50] = [0; 50]; let s = ixx_pad!(u128, *self, f.width, f.pad, buf); f.write_str(s) } } } impl uDisplay for i128 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for isize { #[cfg(target_pointer_width = "16")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as i16), f) } #[cfg(target_pointer_width = "32")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as i32), f) } #[cfg(target_pointer_width = "64")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as i64), f) } } impl uDisplay for isize { #[cfg(target_pointer_width = "16")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as i16), f) } #[cfg(target_pointer_width = "32")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as i32), f) } #[cfg(target_pointer_width = "64")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as i64), f) } } ================================================ FILE: ufmt/src/impls/nz.rs ================================================ use core::num::{ NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, }; use crate::{uDebug, uDisplay, uWrite, Formatter}; macro_rules! nz { ($($NZ:ident : $inner:ident,)*) => { $( impl uDebug for $NZ { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { <$inner as uDebug>::fmt(&self.get(), f) } } impl uDisplay for $NZ { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { <$inner as uDisplay>::fmt(&self.get(), f) } } )* } } nz!( NonZeroI16: i16, NonZeroI32: i32, NonZeroI64: i64, NonZeroI8: i8, NonZeroIsize: isize, NonZeroU16: u16, NonZeroU32: u32, NonZeroU64: u64, NonZeroU8: u8, NonZeroUsize: usize, ); ================================================ FILE: ufmt/src/impls/ptr.rs ================================================ use core::str; use crate::{uDebug, uWrite, Formatter}; macro_rules! hex { ($self:expr, $f:expr, $N:expr) => {{ let mut buf: [u8; $N] = [0; $N]; let i = hex(*$self as usize, &mut buf); unsafe { $f.write_str(str::from_utf8_unchecked( buf.get(i..).unwrap_or_else(|| assume_unreachable!()), )) } }}; } fn hex(mut n: usize, buf: &mut [u8]) -> usize { let mut i = buf.len() - 1; loop { let d = (n % 16) as u8; *buf.get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = if d < 10 { d + b'0' } else { (d - 10) + b'a' }; n = n / 16; i -= 1; if n == 0 { break; } } *buf.get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = b'x'; i -= 1; *buf.get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = b'0'; i } impl uDebug for *const T { #[cfg(target_pointer_width = "16")] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { hex!(self, f, 6) } #[cfg(target_pointer_width = "32")] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { hex!(self, f, 10) } #[cfg(target_pointer_width = "64")] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { hex!(self, f, 18) } } impl uDebug for *mut T { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { (*self as *const T).fmt(f) } } ================================================ FILE: ufmt/src/impls/std.rs ================================================ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use crate::{uDebug, uDisplay, uWrite, Formatter}; impl uDebug for Box where T: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDisplay for Box where T: uDisplay, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for BTreeMap where K: uDebug, V: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.debug_map()?.entries(self)?.finish() } } impl uDebug for BTreeSet where T: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.debug_set()?.entries(self)?.finish() } } impl uDebug for HashMap where K: uDebug, V: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.debug_map()?.entries(self)?.finish() } } impl uDebug for HashSet where T: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.debug_set()?.entries(self)?.finish() } } impl uDebug for String { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDisplay for String { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for Vec where T: uDebug, { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { <[T] as uDebug>::fmt(self, f) } } ================================================ FILE: ufmt/src/impls/tuple.rs ================================================ use crate::{uDebug, uWrite, Formatter}; macro_rules! tuple { ($($T:ident),*; $($i:tt),*) => { impl<$($T,)*> uDebug for ($($T,)*) where $($T: uDebug,)* { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.debug_tuple("")?$(.field(&self.$i)?)*.finish() } } } } impl uDebug for () { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { f.write_str("()") } } tuple!(A; 0); tuple!(A, B; 0, 1); tuple!(A, B, C; 0, 1, 2); tuple!(A, B, C, D; 0, 1, 2, 3); tuple!(A, B, C, D, E; 0, 1, 2, 3, 4); tuple!(A, B, C, D, E, F; 0, 1, 2, 3, 4, 5); tuple!(A, B, C, D, E, F, G; 0, 1, 2, 3, 4, 5, 6); tuple!(A, B, C, D, E, F, G, H; 0, 1, 2, 3, 4, 5, 6, 7); tuple!(A, B, C, D, E, F, G, H, I; 0, 1, 2, 3, 4, 5, 6, 7, 8); tuple!(A, B, C, D, E, F, G, H, I, J; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); tuple!(A, B, C, D, E, F, G, H, I, J, K; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); tuple!(A, B, C, D, E, F, G, H, I, J, K, L; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); ================================================ FILE: ufmt/src/impls/uxx.rs ================================================ use core::str; use crate::{uDebug, uDisplay, uWrite, Formatter}; macro_rules! uxx_hex_pad { ($n:expr, $w:expr, $p:expr, $buf:expr, $pretty:expr, $lower:expr) => {{ let mut n = $n; let len = $buf.len(); let mut i = len - 1; loop { *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = { let d = (n as u8) & 0xf; if d < 10 { d + b'0' } else { if $lower { (d - 10) + b'a' } else { (d - 10) + b'A' } } }; n = n >> 4; if n == 0 { break; } else { i -= 1; } } // Now fill in padding up to the prescribed width. // We do not support widths shorter than the value being // printed, like core::fmt::format!() // Also, we currently only support ' ' and '0' padding. match ($w, $p) { // For now, we default to left padding for all int-like values (Some(mut w), pad) => { // for space padding, pad before 0x. For 0 padding, // pad after 0x if $pretty && pad == ' ' { i -= 2; *$buf .get_mut(i + 1) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = b'x'; *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = b'0'; } else if $pretty { w -= 2; } while i > (len - (w as usize)) { i -= 1; let byte = match pad { '0' => b'0', _ => b' ', }; *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = byte; } } _ => {} } if $pretty && ($w.is_none() || $p != ' ') { i -= 2; *$buf .get_mut(i + 1) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = b'x'; *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = b'0'; } unsafe { str::from_utf8_unchecked($buf.get(i..).unwrap_or_else(|| assume_unreachable!())) } }}; } macro_rules! uxx_pad { ($n:expr, $w: expr, $p:expr, $buf:expr) => {{ let mut n = $n; let len = $buf.len(); let mut i = len - 1; loop { *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = (n % 10) as u8 + b'0'; n = n / 10; if n == 0 { break; } else { i -= 1; } } // Now fill in padding up to the prescribed width. // We do not support widths shorter than the value being // printed, like core::fmt::format!() // Also, we currently only support ' ' and '0' padding. match ($w, $p) { // For now, we default to left padding for all int-like values (Some(w), pad) => { while i > (len - (w as usize)) { i -= 1; let byte = match pad { '0' => b'0', _ => b' ', }; *$buf .get_mut(i) .unwrap_or_else(|| unsafe { assume_unreachable!() }) = byte; } } _ => {} } unsafe { str::from_utf8_unchecked($buf.get(i..).unwrap_or_else(|| assume_unreachable!())) } }}; } fn usize_pad(n: usize, width: Option, pad: char, buf: &mut [u8]) -> &str { uxx_pad!(n, width, pad, buf) } fn usize_hex_pad( n: usize, width: Option, pad: char, buf: &mut [u8], pretty: bool, lower: bool, ) -> &str { uxx_hex_pad!(n, width, pad, buf, pretty, lower) } impl uDebug for u8 { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { let mut buf: [u8; 18] = [0; 18]; if let Some(lower) = f.hex { f.write_str(usize_hex_pad( usize::from(*self), f.width, f.pad, &mut buf, f.pretty, lower, )) } else { f.write_str(usize_pad(usize::from(*self), f.width, f.pad, &mut buf)) } } } impl uDisplay for u8 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for u16 { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { let mut buf: [u8; 18] = [0; 18]; if let Some(lower) = f.hex { f.write_str(usize_hex_pad( usize::from(*self), f.width, f.pad, &mut buf, f.pretty, lower, )) } else { f.write_str(usize_pad(usize::from(*self), f.width, f.pad, &mut buf)) } } } impl uDisplay for u16 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for u32 { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { let mut buf: [u8; 20] = [0; 20]; if let Some(lower) = f.hex { f.write_str(usize_hex_pad( *self as usize, f.width, f.pad, &mut buf, f.pretty, lower, )) } else { f.write_str(usize_pad(*self as usize, f.width, f.pad, &mut buf)) } } } impl uDisplay for u32 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for u64 { #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { let mut buf: [u8; 28] = [0; 28]; let s = if let Some(lower) = f.hex { uxx_hex_pad!(*self, f.width, f.pad, &mut buf, f.pretty, lower) } else { uxx_pad!(*self, f.width, f.pad, &mut buf) }; f.write_str(s) } #[cfg(target_pointer_width = "64")] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { let mut buf: [u8; 30] = [0; 30]; if let Some(lower) = f.hex { f.write_str(usize_hex_pad( *self as usize, f.width, f.pad, &mut buf, f.pretty, lower, )) } else { f.write_str(usize_pad(*self as usize, f.width, f.pad, &mut buf)) } } } impl uDisplay for u64 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for u128 { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { let mut buf: [u8; 49] = [0; 49]; let s = if let Some(lower) = f.hex { uxx_hex_pad!(*self, f.width, f.pad, buf, f.pretty, lower) } else { uxx_pad!(*self, f.width, f.pad, buf) }; f.write_str(s) } } impl uDisplay for u128 { #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(self, f) } } impl uDebug for usize { #[cfg(target_pointer_width = "16")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as u16), f) } #[cfg(target_pointer_width = "32")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as u32), f) } #[cfg(target_pointer_width = "64")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as u64), f) } } impl uDisplay for usize { #[cfg(target_pointer_width = "16")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as u16), f) } #[cfg(target_pointer_width = "32")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as u32), f) } #[cfg(target_pointer_width = "64")] #[inline(always)] fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { ::fmt(&(*self as u64), f) } } ================================================ FILE: ufmt/src/impls.rs ================================================ mod array; mod core; mod ixx; mod nz; mod ptr; #[cfg(feature = "std")] mod std; mod tuple; mod uxx; ================================================ FILE: ufmt/src/lib.rs ================================================ //! `μfmt`, a (6-40x) smaller, (2-9x) faster and panic-free alternative to `core::fmt` //! //! # Design goals //! //! From highest priority to lowest priority //! //! - Optimized for binary size and speed (rather than for compilation time) //! - No dynamic dispatch in generated code //! - No panicking branches in generated code, when optimized //! - No recursion where possible //! //! # Features //! //! - [`Debug`] and [`Display`]-like traits //! - [`core::write!`][uwrite]-like macro //! - A generic [`Formatter<'_, impl uWrite>`][formatter] instead of a single `core::Formatter`; the //! [`uWrite`] trait has an associated error type so each writer can choose its error type. For //! example, the implementation for `std::String` uses [`Infallible`] as its error type. //! - [`core::fmt::Formatter::debug_struct`][debug_struct]-like API //! - [`#[derive(uDebug)]`][derive] //! - Pretty formatting (`{:#?}`) for `uDebug` //! - Hex formatting (`{:x}`) for numbers //! - Pretty hex formatting (`{:#x}`) for numbers //! - Fixed width 0-and-space-left-padded formatting (`{:08}`, `{:8}`) for numbers //! - Fixed width space-right-padded formatting (`{:8}`) for `uDisplay` types //! - Support for padding characters other than 0 and space is not currently implemented //! //! [`Debug`]: trait.uDebug.html //! [`Display`]: trait.uDisplay.html //! [uwrite]: index.html#reexports //! [formatter]: struct.Formatter.html //! [`uWrite`]: trait.uWrite.html //! [`Infallible`]: https://doc.rust-lang.org/core/convert/enum.Infallible.html //! [debug_struct]: file:///home/japaric/rust/ufmt/target/doc/ufmt/struct.Formatter.html#method.debug_list //! [derive]: derive/index.html //! //! # Non-features //! //! These are out of scope //! //! - Alignment and other advanced formatting options //! - Formatting floating point numbers //! //! # Examples //! //! - `uwrite!` / `uwriteln!` //! //! ```ignore //! use ufmt::{derive::uDebug, uwrite}; //! //! #[derive(uDebug)] //! struct Pair { x: u32, y: u32 } //! //! let mut s = String::new(); //! let pair = Pair { x: 1, y: 2 }; //! uwrite!(s, "{:?}", pair).unwrap(); //! assert_eq!(s, "Pair { x: 1, y: 2 }"); //! ``` //! //! - implementing `uWrite` //! //! When implementing the `uWrite` trait you should prefer the `ufmt_write::uWrite` crate over the //! `ufmt::uWrite` crate for better forward compatibility. //! //! ``` //! use core::convert::Infallible; //! //! use ufmt_write::uWrite; //! //! struct MyWriter; //! //! impl uWrite for MyWriter { //! type Error = Infallible; //! //! fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { //! // .. //! Ok(()) //! } //! } //! ``` //! //! - writing a `macro_rules!` macro that uses `uwrite!` (or `uwriteln!`). //! //! Both `ufmt` macros are implemented using [`proc-macro-hack`]; care is needed to avoid running //! into the compiler bug [#43081](https://github.com/rust-lang/rust/issues/43081). See also //! [dtolnay/proc-macro-hack#46][pmh-46]. //! //! [`proc-macro-hack`]: https://github.com/dtolnay/proc-macro-hack //! [pmh-46]: https://github.com/dtolnay/proc-macro-hack/issues/46 //! //! ``` //! // like `std::format!` it returns a `std::String` but uses `uwrite!` instead of `write!` //! macro_rules! uformat { //! // IMPORTANT use `tt` fragments instead of `expr` fragments (i.e. `$($exprs:expr),*`) //! ($($tt:tt)*) => {{ //! let mut s = String::new(); //! match ufmt::uwrite!(&mut s, $($tt)*) { //! Ok(_) => Ok(s), //! Err(e) => Err(e), //! } //! }} //! } //! ``` //! //! # Benchmarks //! //! The benchmarks ran on a ARM Cortex-M3 chip (`thumbv7m-none-eabi`). //! //! The benchmarks were compiled with `nightly-2019-05-01`, `-C opt-level=3`, `lto = true`, //! `codegen-units = 1`. //! //! In all benchmarks `x = i32::MIN` and `y = i32::MIN` plus some obfuscation was applied to //! prevent const-propagation of the `*write!` arguments. //! //! The unit of time is one core clock cycle: 125 ns (8 MHz) //! //! The `.text` and `.rodata` columns indicate the delta (in bytes) when commenting out the //! `*write!` statement. //! //! |Code |Time|% |`.text`|% |`.rodata`|% | //! |------------------------------------------|----|---------|-------|---------|---------|--------| //! |`write!("Hello, world!")` |154 |~ |1906 |~ |248 |~ | //! |`uwrite!("Hello, world!")` |20 |**13.0%**|34 |**1.8%** |16 |**6.5%**| //! |`write!(w, "{}", 0i32)` |256 |~ |1958 |~ |232 |~ | //! |`uwrite!(w, "{}", 0i32)` |37 |**14.5%**|288 |**14.7%**|0 |**0%** | //! |`write!(w, "{}", x)` |381 |~ | //! |`uwrite!(w, "{}", x)` |295 |77.4% | //! |`write!(w, "{:?}", Pair { x: 0, y: 0 })` |996 |~ |4704 |~ |312 |~ | //! |`uwrite!(w, "{:?}", Pair { x: 0, y: 0 })` |254 |**25.5%**|752 |**16.0%**|24 |**7.7%**| //! |`write!(w, "{:?}", Pair { x, y })` |1264|~ | //! |`uwrite!(w, "{:?}", Pair { x, y })` |776 |61.4% | //! |`write!(w, "{:#?}", Pair { x: 0, y: 0 })` |2853|~ |4710 |~ |348 |~ | //! |`uwrite!(w, "{:#?}", Pair { x: 0, y: 0 })`|301 |**10.6%**|754 |**16.0%**|24 |**6.9%**| //! |`write!(w, "{:#?}", Pair { x, y })` |3693|~ | //! |`uwrite!(w, "{:#?}", Pair { x, y })` |823 |**22.3%**| //! //! //! Benchmark program: //! //! ``` ignore //! static X: AtomicI32 = AtomicI32::new(i32::MIN); // or `0` //! static Y: AtomicI32 = AtomicI32::new(i32::MIN); // or `0` //! //! #[exception] //! fn PendSV() { //! // read DWT.CYCCNT here //! //! let x = X.load(Ordering::Relaxed); //! let y = Y.load(Ordering::Relaxed); //! //! let p = Pair { x, y }; //! //! uwrite!(&mut W, "{:#?}", p).ok(); //! //! // write!(&mut W, "{:#?}", p).ok(); //! //! asm::bkpt(); // read DWT.CYCCNT here //! } //! ``` //! //! Writer used in the benchmarks: //! //! ``` //! use core::{convert::Infallible, fmt, ptr}; //! //! use ufmt::uWrite; //! //! struct W; //! //! impl uWrite for W { //! type Error = Infallible; //! //! fn write_str(&mut self, s: &str) -> Result<(), Infallible> { //! s.as_bytes() //! .iter() //! .for_each(|b| unsafe { drop(ptr::read_volatile(b)) }); //! //! Ok(()) //! } //! } //! //! impl fmt::Write for W { //! fn write_str(&mut self, s: &str) -> fmt::Result { //! s.as_bytes() //! .iter() //! .for_each(|b| unsafe { drop(ptr::read_volatile(b)) }); //! //! Ok(()) //! } //! } //! ``` //! //! # Minimum Supported Rust Version (MSRV) //! //! This crate is guaranteed to compile on stable Rust 1.34 and up. It *might* compile on older //! versions but that may change in any new patch release. // Added when we vendored ufmt into libtock-rs to avoid needing to immediately // make many changes. #![allow(clippy::all)] #![cfg_attr(not(feature = "std"), no_std)] #![deny(missing_docs)] #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] #![deny(warnings)] // this lets us use `uwrite!` in the test suite #[allow(unused_extern_crates)] #[cfg(test)] extern crate self as ufmt; use core::str; use proc_macro_hack::proc_macro_hack; pub use ufmt_write::uWrite; /// Write formatted data into a buffer /// /// This macro accepts a format string, a list of arguments, and a 'writer'. Arguments will be /// formatted according to the specified format string and the result will be passed to the writer. /// The writer must have type `[&mut] impl uWrite` or `[&mut] ufmt::Formatter<'_, impl uWrite>`. The /// macro returns the associated `Error` type of the `uWrite`-r. /// /// The syntax is similar to [`core::write!`] but only a handful of argument types are accepted: /// /// [`core::write!`]: https://doc.rust-lang.org/core/macro.write.html /// /// - `{}` - `uDisplay` /// - `{:?}` - `uDebug` /// - `{:#?}` - "pretty" `uDebug` /// - `{:x}` - lowercase hexadecimal for numbers /// - `{:X}` - uppercase hexadecimal for numbers /// - `{:8}` - space padded width specifier. left pad spaces for numbers, right pad otherwise /// - `{:08}` - zero padded width specifier. left pads zeroes for numbers /// /// Named parameters and "specified" positional parameters (`{0}`) are not supported. /// /// `{{` and `}}` can be used to escape braces. #[proc_macro_hack] pub use ufmt_macros::uwrite; /// Write formatted data into a buffer, with a newline appended /// /// See [`uwrite!`](macro.uwrite.html) for more details #[proc_macro_hack] pub use ufmt_macros::uwriteln; pub use crate::helpers::{DebugList, DebugMap, DebugStruct, DebugTuple}; #[macro_use] mod macros; mod helpers; mod impls; /// Derive macros pub mod derive { pub use ufmt_macros::uDebug; } /// Just like `core::fmt::Debug` #[allow(non_camel_case_types)] pub trait uDebug { /// Formats the value using the given formatter fn fmt(&self, _: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized; } /// Just like `core::fmt::Display` #[allow(non_camel_case_types)] pub trait uDisplay { /// Formats the value using the given formatter fn fmt(&self, _: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized; } /// Configuration for formatting #[allow(non_camel_case_types)] pub struct Formatter<'w, W> where W: uWrite + ?Sized, { indentation: u8, pretty: bool, hex: Option, // Some(true) = lowercase width: Option, pad: char, bytes_written: usize, // used internally for width padding writer: &'w mut W, } impl<'w, W> Formatter<'w, W> where W: uWrite + ?Sized, { /// Creates a formatter from the given writer pub fn new(writer: &'w mut W) -> Self { Self { indentation: 0, pretty: false, hex: None, width: None, pad: ' ', bytes_written: 0, writer, } } /// Execute the closure with pretty-printing enabled pub fn pretty( &mut self, f: impl FnOnce(&mut Self) -> Result<(), W::Error>, ) -> Result<(), W::Error> { let pretty = self.pretty; self.pretty = true; f(self)?; self.pretty = pretty; Ok(()) } /// Execute the closure with hex-printing enabled pub fn hex( &mut self, lower: bool, pretty: bool, f: impl FnOnce(&mut Self) -> Result<(), W::Error>, ) -> Result<(), W::Error> { let old_hex = self.hex; let old_pretty = self.pretty; self.pretty = pretty; self.hex = Some(lower); f(self)?; self.hex = old_hex; self.pretty = old_pretty; Ok(()) } /// Execute the closure with specified width / pad values pub fn fixed_width( &mut self, pretty: bool, hex: Option, width: Option, pad: char, f: impl FnOnce(&mut Self) -> Result<(), W::Error>, ) -> Result<(), W::Error> { self.bytes_written = 0; let old_hex = self.hex; let old_pretty = self.pretty; let old_width = self.width; let old_pad = self.pad; self.pretty = pretty; self.hex = hex; self.width = width; self.pad = pad; f(self)?; // The trick here is that if the internal implementation // already padded to the appropriate length, we don't have // to do anything -- this is the case for all numeric types. // Otherwise, we only support space padding to the right of // the object, so just fill that in here. if let Some(w) = self.width { if (w as usize) > self.bytes_written { // we don't support non-space padding here if self.pad == ' ' { for _i in 0..(w as usize - self.bytes_written) { self.write_str(" ")?; } } } } self.hex = old_hex; self.pretty = old_pretty; self.width = old_width; self.pad = old_pad; Ok(()) } /// Writes a character to the underlying buffer contained within this formatter. pub fn write_char(&mut self, c: char) -> Result<(), W::Error> { let err = self.writer.write_char(c); if err.is_ok() { self.bytes_written += c.len_utf8(); } err } /// Writes a string slice to the underlying buffer contained within this formatter. pub fn write_str(&mut self, s: &str) -> Result<(), W::Error> { let err = self.writer.write_str(s); if err.is_ok() { self.bytes_written += s.len(); } err } /// Write whitespace according to the current `self.indentation` fn indent(&mut self) -> Result<(), W::Error> { for _ in 0..self.indentation { self.write_str(" ")?; } Ok(()) } } // Implementation detail of the `uwrite*!` macros #[doc(hidden)] pub trait UnstableDoAsFormatter { type Writer: uWrite + ?Sized; fn do_as_formatter( &mut self, f: impl FnOnce(&mut Formatter<'_, Self::Writer>) -> Result<(), ::Error>, ) -> Result<(), ::Error>; } impl UnstableDoAsFormatter for W where W: uWrite + ?Sized, { type Writer = W; fn do_as_formatter( &mut self, f: impl FnOnce(&mut Formatter<'_, W>) -> Result<(), W::Error>, ) -> Result<(), W::Error> { f(&mut Formatter::new(self)) } } impl UnstableDoAsFormatter for Formatter<'_, W> where W: uWrite + ?Sized, { type Writer = W; fn do_as_formatter( &mut self, f: impl FnOnce(&mut Formatter<'_, W>) -> Result<(), W::Error>, ) -> Result<(), W::Error> { f(self) } } ================================================ FILE: ufmt/src/macros.rs ================================================ macro_rules! assume_unreachable { () => { if cfg!(debug_assertions) { unreachable!() } else { core::hint::unreachable_unchecked() } }; } ================================================ FILE: ufmt/tests/vs-std-write.rs ================================================ use core::convert::Infallible; use std::collections::{BTreeMap, BTreeSet}; use ufmt::{derive::uDebug, uDebug, uWrite, uwrite, uwriteln, Formatter}; macro_rules! uformat { ($($tt:tt)*) => {{ let mut s = String::new(); #[allow(unreachable_code)] match ufmt::uwrite!(&mut s, $($tt)*) { Ok(_) => Ok(s), Err(e) => Err(e), } }}; } macro_rules! cmp { ($($tt:tt)*) => { assert_eq!( uformat!($($tt)*), Ok(format!($($tt)*)), ) } } #[test] fn core() { cmp!("{:?}", None::); cmp!("{:#?}", None::); cmp!("{:?}", Some(0)); cmp!("{:#?}", Some(0)); cmp!("{:?}", Ok::<_, ()>(1)); cmp!("{:#?}", Ok::<_, ()>(1)); cmp!("{:?}", Err::<(), _>(2)); cmp!("{:#?}", Err::<(), _>(2)); } #[test] fn recursion() { #[derive(uDebug, Debug)] struct Node { value: i32, next: Option>, } fn x() -> Node { let tail = Node { value: 0, next: None, }; Node { value: 1, next: Some(Box::new(tail)), } } cmp!("{:?}", x()); cmp!("{:#?}", x()); } #[test] fn uxx() { cmp!("{}", 0u8); cmp!("{}", 10u8); cmp!("{}", 100u8); // extreme values cmp!("{}", u8::max_value()); cmp!("{}", u16::max_value()); cmp!("{}", u32::max_value()); cmp!("{}", u64::max_value()); cmp!("{}", u128::max_value()); cmp!("{}", usize::max_value()); } #[test] fn ixx() { // sanity check cmp!("{}", 0i8); cmp!("{}", 10i8); cmp!("{}", 100i8); // extreme values cmp!("{}", i8::min_value()); cmp!("{}", i8::max_value()); cmp!("{}", i16::min_value()); cmp!("{}", i16::max_value()); cmp!("{}", i32::min_value()); cmp!("{}", i32::max_value()); cmp!("{}", i64::min_value()); cmp!("{}", i64::max_value()); cmp!("{}", i128::min_value()); cmp!("{}", i128::max_value()); cmp!("{}", isize::min_value()); cmp!("{}", isize::max_value()); } #[test] fn hex() { cmp!("{:x}", 0i8); cmp!("{:x}", 10i8); cmp!("{:x}", 100i8); cmp!("{:x}", i8::min_value()); cmp!("{:x}", i8::max_value()); cmp!("{:x}", i16::min_value()); cmp!("{:x}", i16::max_value()); cmp!("{:x}", i32::min_value()); cmp!("{:x}", i32::max_value()); cmp!("{:x}", i64::min_value()); cmp!("{:x}", i64::max_value()); cmp!("{:x}", i128::min_value()); cmp!("{:x}", i128::max_value()); cmp!("{:x}", isize::min_value()); cmp!("{:x}", isize::max_value()); cmp!("{:x}", 0u8); cmp!("{:x}", 10u8); cmp!("{:x}", 100u8); cmp!("{:x}", u8::max_value()); cmp!("{:x}", u16::max_value()); cmp!("{:x}", u32::max_value()); cmp!("{:x}", u64::max_value()); cmp!("{:x}", u128::max_value()); cmp!("{:x}", usize::max_value()); } #[test] fn special_hex() { cmp!("{:X}", 0i8); cmp!("{:X}", 10i8); cmp!("{:X}", 100i8); cmp!("{:X}", u32::max_value()); cmp!("{:X}", u64::max_value()); cmp!("{:#X}", 0i8); cmp!("{:#X}", 10i8); cmp!("{:#X}", u32::max_value()); cmp!("{:#X}", u64::max_value()); cmp!("{:#x}", 0i8); cmp!("{:#x}", 10i8); cmp!("{:#x}", u32::max_value()); cmp!("{:#x}", u64::max_value()); cmp!("{:#x}", 0i8); cmp!("{:#x}", 10i8); cmp!("{:#9x}", u32::max_value()); cmp!("{:#09x}", u64::max_value()); cmp!("{:#9X}", u32::max_value()); cmp!("{:#09X}", u64::max_value()); } // Verify padding, with spaces or zeroes, up to width 10, for any // numeric value. macro_rules! width_test { ($tt:expr) => {{ cmp!("{:1}", $tt); cmp!("{:3}", $tt); cmp!("{:5}", $tt); cmp!("{:7}", $tt); cmp!("{:9}", $tt); cmp!("{:10}", $tt); cmp!("{:02}", $tt); cmp!("{:04}", $tt); cmp!("{:06}", $tt); cmp!("{:08}", $tt); cmp!("{:010}", $tt); cmp!("{:018}", $tt); cmp!("lead{:018}follow", $tt); cmp!("lead{:10}follow", $tt); }}; } // Verify space padding for non-numeric values macro_rules! width_test_non_numeric { ($tt:expr) => {{ cmp!("{:1}", $tt); cmp!("{:3}", $tt); cmp!("{:5}", $tt); cmp!("{:7}", $tt); cmp!("{:9}", $tt); cmp!("{:10}", $tt); cmp!("{:18}", $tt); cmp!("lead{:18}follow", $tt); cmp!("lead{:5}follow", $tt); }}; } #[test] fn width_format_numbers() { width_test!(0i8); width_test!(10i8); width_test!(100i8); width_test!(i8::min_value()); width_test!(i8::max_value()); width_test!(i16::min_value()); width_test!(i16::max_value()); width_test!(i32::min_value()); width_test!(i32::max_value()); width_test!(i64::min_value()); width_test!(i64::max_value()); width_test!(i128::min_value()); width_test!(i128::max_value()); width_test!(isize::min_value()); width_test!(isize::max_value()); width_test!(0u8); width_test!(10u8); width_test!(100u8); width_test!(u8::max_value()); width_test!(u16::max_value()); width_test!(u32::max_value()); width_test!(u64::max_value()); width_test!(u128::max_value()); width_test!(usize::max_value()); width_test!(0i8); width_test!(10i8); width_test!(100i8); width_test!(i8::min_value()); width_test!(i8::max_value()); width_test!(i16::min_value()); width_test!(i16::max_value()); width_test!(i32::min_value()); width_test!(i32::max_value()); width_test!(i64::min_value()); width_test!(i64::max_value()); width_test!(i128::min_value()); width_test!(i128::max_value()); width_test!(isize::min_value()); width_test!(isize::max_value()); width_test!(0u8); width_test!(10u8); width_test!(100u8); width_test!(u8::max_value()); width_test!(u16::max_value()); width_test!(u32::max_value()); width_test!(u64::max_value()); width_test!(u128::max_value()); width_test!(usize::max_value()); } #[test] fn width_non_numbers() { width_test_non_numeric!(false); width_test_non_numeric!("hi"); width_test_non_numeric!('P'); } #[test] fn fmt() { cmp!("Hello, world!"); cmp!("The answer is {}", 42); } #[test] fn map() { fn x() -> BTreeMap { let mut m = BTreeMap::new(); m.insert(1, 2); m.insert(3, 4); m } cmp!("{:?}", BTreeMap::<(), ()>::new()); cmp!("{:?}", x()); cmp!("{:#?}", BTreeMap::<(), ()>::new()); cmp!("{:#?}", x()); } #[test] fn set() { fn x() -> BTreeSet { let mut m = BTreeSet::new(); m.insert(1); m.insert(3); m } cmp!("{:?}", BTreeSet::<()>::new()); cmp!("{:?}", x()); cmp!("{:#?}", BTreeSet::<()>::new()); cmp!("{:#?}", x()); } #[test] fn struct_() { #[derive(Debug, uDebug)] struct Braces {} #[derive(Debug, uDebug)] struct Parens(); #[derive(Debug, Default, uDebug)] struct I32(i32); #[derive(Debug, Default, uDebug)] struct Tuple(i32, i32); #[derive(Debug, Default, uDebug)] struct Pair { x: i32, y: i32, } #[derive(Debug, Default, uDebug)] struct Nested { first: Pair, second: Pair, } cmp!("{:?}", Braces {}); cmp!("{:?}", Parens()); cmp!("{:?}", I32::default()); cmp!("{:?}", Tuple::default()); cmp!("{:?}", Pair::default()); cmp!("{:?}", Nested::default()); cmp!("{:#?}", Braces {}); cmp!("{:#?}", Parens()); cmp!("{:#?}", I32::default()); cmp!("{:#?}", Tuple::default()); cmp!("{:#?}", Pair::default()); cmp!("{:#?}", Nested::default()); } #[test] fn enum_() { #[derive(Debug, uDebug)] enum X { A, B(u8, u16), C { x: u8, y: u16 }, } cmp!("{:?}", X::A); cmp!("{:?}", X::B(0, 1)); cmp!("{:?}", X::C { x: 0, y: 1 }); cmp!("{:#?}", X::A); cmp!("{:#?}", X::B(0, 1)); cmp!("{:#?}", X::C { x: 0, y: 1 }); } #[test] fn ptr() { cmp!("{:?}", 1 as *const u8); cmp!("{:?}", 0xf as *const u8); cmp!("{:?}", 0xff as *const u8); cmp!("{:?}", 0xfff as *const u8); cmp!("{:?}", 0xffff as *const u8); cmp!("{:?}", 0xfffff as *const u8); cmp!("{:?}", 0xffffff as *const u8); cmp!("{:?}", 0xfffffff as *const u8); cmp!("{:?}", 0xffffffff as *const u8); #[cfg(target_pointer_width = "64")] cmp!("{:?}", 0xfffffffff as *const u8); } #[test] fn tuples() { cmp!("{:?}", ()); cmp!("{:?}", (1,)); cmp!("{:?}", (1, 2)); cmp!("{:?}", (1, 2, 3)); cmp!("{:?}", (1, 2, 3, 4)); cmp!("{:?}", (1, 2, 3, 4, 5)); cmp!("{:?}", (1, 2, 3, 4, 5, 6)); cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7)); cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8)); cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8, 9)); cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); cmp!("{:?}", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); cmp!("{:#?}", ()); cmp!("{:#?}", (1,)); cmp!("{:#?}", (1, 2)); } #[test] fn slice() { cmp!("{:?}", [0; 0]); cmp!("{:?}", [0]); cmp!("{:?}", [0, 1]); cmp!("{:#?}", [0; 0]); cmp!("{:#?}", [0]); cmp!("{:#?}", [0, 1]); } #[test] fn uwriteln() { let mut s = String::new(); uwriteln!(&mut s, "Hello").unwrap(); uwriteln!(&mut s, "World",).unwrap(); assert_eq!(s, "Hello\nWorld\n"); } #[test] fn formatter_uwrite() { #[derive(uDebug)] struct X; struct Y; impl uDebug for Y { fn fmt(&self, f: &mut Formatter<'_, W>) -> Result<(), W::Error> where W: uWrite + ?Sized, { uwrite!(f, "{:?}", X) } } assert_eq!(uformat!("{:?}", Y).unwrap(), "X") } #[test] fn generic() { #[derive(uDebug, Debug)] struct X(T); cmp!("{:?}", X(0)); #[derive(uDebug, Debug)] enum Y { Z(T), } cmp!("{:?}", Y::Z(0)); } // compile-pass test #[allow(dead_code)] fn static_lifetime(x: &'static mut u32) { fn foo(x: &'static mut u32) -> *mut u32 { x as *mut u32 } uwrite!(&mut String::new(), "{:?}", foo(x)).ok(); } // test dynamically sized writer #[test] fn dst() { #[allow(rust_2018_idioms)] // false positive? struct Cursor where B: ?Sized, { pos: usize, buffer: B, } impl Cursor { fn new(buffer: B) -> Self { Cursor { pos: 0, buffer } } } impl uWrite for Cursor<[u8]> { type Error = Infallible; fn write_str(&mut self, s: &str) -> Result<(), Infallible> { let bytes = s.as_bytes(); let len = bytes.len(); let start = self.pos; if let Some(buffer) = self.buffer.get_mut(start..start + len) { buffer.copy_from_slice(bytes); self.pos += len; } Ok(()) } } let mut cursor = Cursor::new([0; 256]); let cursor: &mut Cursor<[u8]> = &mut cursor; uwrite!(cursor, "The answer is {}", 42).ok(); let msg = b"The answer is 42"; assert_eq!(&cursor.buffer[..msg.len()], msg); } ================================================ FILE: ufmt/utils/Cargo.toml ================================================ [package] authors = ["Jorge Aparicio "] categories = ["embedded", "no-std"] description = "`μfmt` utilities" documentation = "https://docs.rs/ufmt-utils" edition = "2021" keywords = ["Debug", "Display", "Write", "format"] license = "MIT OR Apache-2.0" name = "ufmt-utils" repository = "https://github.com/japaric/ufmt" version = "0.1.1" [dependencies] heapless = "0.5.0" ufmt-write = { version = "0.1.0", path = "../write" } [dev-dependencies] ufmt = { version = "0.1.0", path = ".." } ================================================ FILE: ufmt/utils/src/lib.rs ================================================ //! `μfmt` utilities //! //! # Minimum Supported Rust Version (MSRV) //! //! This crate is guaranteed to compile on stable Rust 1.36 and up. It *might* compile on older //! versions but that may change in any new patch release. #![deny(missing_docs)] #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] #![deny(warnings)] #![no_std] use core::{convert::Infallible, str, fmt}; pub use heapless::consts; use heapless::{ArrayLength, String}; use ufmt_write::uWrite; macro_rules! assume_unreachable { () => { if cfg!(debug_assertions) { panic!() } else { core::hint::unreachable_unchecked() } }; } /// A write adapter that ignores all errors pub struct Ignore where W: uWrite, { writer: W, } impl Ignore where W: uWrite, { /// Creates a new `Ignore` adapter pub fn new(writer: W) -> Self { Self { writer } } /// Destroys the adapter and returns the underlying writer pub fn free(self) -> W { self.writer } } impl uWrite for Ignore where W: uWrite, { type Error = Infallible; fn write_str(&mut self, s: &str) -> Result<(), Infallible> { let _ = self.writer.write_str(s); Ok(()) } } /// A write adapter that buffers writes and automatically flushes on newlines pub struct LineBuffered where N: ArrayLength, W: uWrite, { buffer: String, writer: W, } impl LineBuffered where N: ArrayLength, W: uWrite, { /// Creates a new `LineBuffered` adapter pub fn new(writer: W) -> Self { Self { buffer: String::new(), writer, } } /// Flushes the contents of the buffer pub fn flush(&mut self) -> Result<(), W::Error> { let ret = self.writer.write_str(&self.buffer); self.buffer.clear(); ret } /// Destroys the adapter and returns the underlying writer pub fn free(self) -> W { self.writer } fn push_str(&mut self, s: &str) -> Result<(), W::Error> { let len = s.as_bytes().len(); if self.buffer.len() + len > self.buffer.capacity() { self.flush()?; } if len > self.buffer.capacity() { self.writer.write_str(s)?; } else { self.buffer .push_str(s) .unwrap_or_else(|_| unsafe { assume_unreachable!() }) } Ok(()) } } impl uWrite for LineBuffered where N: ArrayLength, W: uWrite, { type Error = W::Error; fn write_str(&mut self, mut s: &str) -> Result<(), W::Error> { while let Some(pos) = s.as_bytes().iter().position(|b| *b == b'\n') { let line = s .get(..pos + 1) .unwrap_or_else(|| unsafe { assume_unreachable!() }); self.push_str(line)?; self.flush()?; s = s .get(pos + 1..) .unwrap_or_else(|| unsafe { assume_unreachable!() }); } self.push_str(s) } } /// An adapter struct allowing to use `ufmt` on types which implement `core::fmt::Write` /// /// For example: /// /// ``` /// use ufmt::uwrite; /// use ufmt_write::uWrite; /// use ufmt_utils::WriteAdapter; /// /// let fancy_number: u8 = 42; /// /// let mut s = String::new(); /// uwrite!(WriteAdapter(&mut s), "{:?}", fancy_number); /// ``` pub struct WriteAdapter(pub W) where W: fmt::Write; impl uWrite for WriteAdapter where W: fmt::Write { type Error = fmt::Error; fn write_char(&mut self, c: char) -> Result<(), Self::Error> { self.0.write_char(c) } fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { self.0.write_str(s) } } ================================================ FILE: ufmt/write/Cargo.toml ================================================ [package] authors = ["Jorge Aparicio "] categories = ["embedded", "no-std"] description = "`μfmt`'s `uWrite` trait" edition = "2021" keywords = ["Debug", "Display", "Write", "format"] license = "MIT OR Apache-2.0" name = "ufmt-write" repository = "https://github.com/japaric/ufmt" version = "0.1.0" # NOTE do NOT add an `alloc` feature before the alloc crate can be used in # no-std BINARIES [features] # NOTE do NOT turn `std` into a default feature; this is a no-std first crate std = [] ================================================ FILE: ufmt/write/src/lib.rs ================================================ //! `μfmt`'s `uWrite` trait #![cfg_attr(not(feature = "std"), no_std)] #![deny(missing_docs)] #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] #![deny(warnings)] #[cfg(feature = "std")] use core::convert::Infallible; /// A collection of methods that are required / used to format a message into a stream. #[allow(non_camel_case_types)] pub trait uWrite { /// The error associated to this writer type Error; /// Writes a string slice into this writer, returning whether the write succeeded. /// /// This method can only succeed if the entire string slice was successfully written, and this /// method will not return until all data has been written or an error occurs. fn write_str(&mut self, s: &str) -> Result<(), Self::Error>; /// Writes a [`char`] into this writer, returning whether the write succeeded. /// /// A single [`char`] may be encoded as more than one byte. This method can only succeed if the /// entire byte sequence was successfully written, and this method will not return until all /// data has been written or an error occurs. fn write_char(&mut self, c: char) -> Result<(), Self::Error> { let mut buf: [u8; 4] = [0; 4]; self.write_str(c.encode_utf8(&mut buf)) } } #[cfg(feature = "std")] impl uWrite for String { type Error = Infallible; fn write_str(&mut self, s: &str) -> Result<(), Infallible> { self.push_str(s); Ok(()) } } ================================================ FILE: unittest/Cargo.toml ================================================ [package] authors = ["Tock Project Developers "] description = """libtock-rs unit test support. Provides a fake Tock kernel as \ well as the test_component! macro.""" edition = "2021" license = "Apache-2.0 OR MIT" name = "libtock_unittest" repository = "https://www.github.com/tock/libtock-rs" rust-version.workspace = true version = "0.1.0" [dependencies] libtock_platform = { path = "../platform" } thiserror = "1.0.44" ================================================ FILE: unittest/src/allow_db.rs ================================================ use core::num::NonZeroUsize; use libtock_platform::Register; /// `AllowDb` stores the currently-active Allow buffers, and is responsible for /// preventing overlapping Allow buffers. /// /// Functionally, it receives raw register data from the Allow system calls /// implementations, verifies the new buffer does not overlap any active buffer, /// and converts the register values into usable reference types (such as /// `&'static mut [u8]`). When a buffer reference is returned, it removes it /// from its database and returns the raw register values. // TRD 104 (Tock's system call ABI) says that allow buffers only overlap if they // have a memory address in common, so zero-sized buffers cannot overlap. // // Read-Write Allow and Read-Only Allow are invoked through // RawSyscalls::syscall4, which is unsafe, and requires its caller to pass // arguments that are valid for the system call. Those requirements require that // either the length field is zero, or the address and length field represent a // valid slice. Several of the steps in this file require that property. // // Therefore AllowDb does not need to check for overlaps with zero-sized // buffers. #[derive(Default)] pub struct AllowDb { // List of all active buffers, excluding zero-sized buffers. Contains both // read-only buffers and read-write buffers. // Key: address of the buffer. // Value: length of the buffer. // Invariant: These buffers never overlap, and represent valid slices // (although they can't be converted to slices because fake drivers may have // a &mut [u8] pointing at the buffer). buffers: std::collections::BTreeMap<*mut u8, NonZeroUsize>, } impl AllowDb { // Adds a new buffer, or returns an error if it overlaps with any existing // buffers. Requires that address and len represent a valid slice. unsafe fn insert_raw( &mut self, address: *mut u8, len: NonZeroUsize, ) -> Result<(), OverlapError> { // The new buffer spans the address range [address, address + len - 1], // so the highest-address buffer it overlaps with starts at // address + len - 1. It can overlap with a buffer that starts with any // address less than that (if that buffer's length is large enough), so // we don't give a lower bound for the range. // // If the last buffer in this range does NOT overlap the new buffer, // then that buffer's memory range is strictly less than the new buffer. // Because buffers in self.buffers cannot overlap, all of the buffers in // the range would need to be strictly less than the new buffer. // Therefore we only need to check for overlap with the last buffer in // this range. // // range_end is one past the end of the range to check. let range_end = unsafe { // Safety: The function's preconditions require that address and len // represent a valid slice, which guarantees that this sum does not // oveflow. address.add(len.get()) }; if let Some(existing_buffer) = self.buffers.range(..range_end).next_back() { let (&existing_address, &existing_len) = existing_buffer; // Check if existing_buffer overlaps with the new buffer. Note that // existing_address.add(existing_len) generates a pointer one past // the end of the existing buffer, which is why the inequality is // strict. // Safety: self.buffers has an invariant that its values represent // slices, so this sum cannot overflow. if unsafe { existing_address.add(existing_len.get()) } > address { return Err(OverlapError); } } self.buffers.insert(address, len); Ok(()) } /// Adds a read-only buffer to the database, and returns it as a /// `RoAllowBuffer`. /// /// # Safety /// `address` and `len` must be valid as specified in TRD 104: either `len` /// is 0 or `address` and `len` represent a valid slice. pub unsafe fn insert_ro_buffer( &mut self, address: Register, len: Register, ) -> Result { let len: usize = len.into(); if let Some(nonzero_len) = NonZeroUsize::new(len) { // The buffer is not zero-sized. Add it to the database (checking it // does not overlap an existing buffer). // Safety: `len` is nonzero, so by this function's precondition // `address` and `len` represent a valid slice. unsafe { self.insert_raw(address.into(), nonzero_len) }?; } Ok(RoAllowBuffer { address: address.into(), len, }) } /// Adds a read-write buffer to the database, and returns it as a /// `RwAllowBuffer`. /// /// # Safety /// `address` and `len` must be valid as specified in TRD 104: either `len` /// is 0 or `address` and `len` represent a valid slice. pub unsafe fn insert_rw_buffer( &mut self, address: Register, len: Register, ) -> Result { let address: *mut u8 = address.into(); let len: usize = len.into(); if let Some(nonzero_len) = NonZeroUsize::new(len) { // The buffer is not zero-sized. Add it to the database (checking it // does not overlap an existing buffer). // Safety: `len` is nonzero, so by this function's precondition // `address` and `len` represent a valid slice. unsafe { self.insert_raw(address, nonzero_len) }?; } Ok(RwAllowBuffer { address, len }) } /// Removes a read-only buffer from the database and returns its raw /// register values. /// /// The returned value is the tuple (address, len) passed into the /// insert_ro_buffer call that created the RoAllowBuffer. pub fn remove_ro_buffer(&mut self, buffer: RoAllowBuffer) -> (Register, Register) { self.buffers.remove(&(buffer.address as *mut u8)); (buffer.address.into(), buffer.len.into()) } /// Removes a read-write buffer from the database and returns its raw /// register values. /// /// The returned value is the tuple (address, len) passed into the /// insert_rw_buffer call that created the RwAllowBuffer. pub fn remove_rw_buffer(&mut self, buffer: RwAllowBuffer) -> (Register, Register) { self.buffers.remove(&buffer.address); (buffer.address.into(), buffer.len.into()) } } #[derive(Debug, Eq, PartialEq, thiserror::Error)] #[error("allow buffers overlap")] pub struct OverlapError; /// A read-only reference to a buffer that has been shared via the Allow system /// call. This reference is non-Copy, so `AllowDb` can determine when all /// references to the buffer have been destroyed. #[derive(Debug)] pub struct RoAllowBuffer { // Safety invariant: Either length is 0, or address and length can be // soundly converted to a &'static [u8]. Note: that means that no &mut [u8] // references may overlap the slice described by address and len. address: *const u8, len: usize, } impl Default for RoAllowBuffer { fn default() -> RoAllowBuffer { RoAllowBuffer { address: core::ptr::null(), len: 0, } } } // Allows access to the pointed-to-buffer. The returned reference has the same // lifetime as the &self reference, so the caller can't keep the reference for // longer than it has access to the RoAllowBuffer. impl std::ops::Deref for RoAllowBuffer { type Target = [u8]; fn deref(&self) -> &[u8] { match self.len { 0 => &[], // Safety: Because length is nonzero, the safety invariant on // address and len says this conversion is sound. _ => unsafe { core::slice::from_raw_parts(self.address, self.len) }, } } } /// A read-write reference to a buffer that has been shared via the Allow system /// call. This reference is non-Copy, so `AllowDb` can determine when all /// references to the buffer have been destroyed. #[derive(Debug)] pub struct RwAllowBuffer { // Safety invariant: Either length is 0, or address and length can be // soundly converted to a &'static mut [u8]. Note: that means that no // references may overlap the slice described by address and len. address: *mut u8, len: usize, } impl Default for RwAllowBuffer { fn default() -> RwAllowBuffer { RwAllowBuffer { address: core::ptr::null_mut(), len: 0, } } } // Allows access to the pointed-to-buffer. The returned reference has the same // lifetime as the &self reference, so the caller can't keep the reference for // longer than it has access to the RwAllowBuffer. impl std::ops::Deref for RwAllowBuffer { type Target = [u8]; fn deref(&self) -> &[u8] { match self.len { 0 => &[], // Safety: Because length is nonzero, the safety invariant on // address and len says this conversion is sound. _ => unsafe { core::slice::from_raw_parts(self.address, self.len) }, } } } // Same purpose as the Deref implementation, but for mut references. impl std::ops::DerefMut for RwAllowBuffer { fn deref_mut(&mut self) -> &mut [u8] { match self.len { 0 => &mut [], // Safety: Because length is nonzero, the safety invariant on // address and len says this conversion is sound. _ => unsafe { core::slice::from_raw_parts_mut(self.address, self.len) }, } } } ================================================ FILE: unittest/src/allow_db_test.rs ================================================ //! Unit test cases for functionality in allow_db. use crate::allow_db::*; use core::cell::Cell; // Utility to call insert_ro_buffer with a slice. // Safety: insert_ro_slice does not prevent RoAllowBuffer from outliving slice. // Instead, the caller must make sure its use patterns don't cause invalid // accesses. unsafe fn insert_ro_slice( db: &mut AllowDb, slice: &[Cell], ) -> Result { // Safety: The address and len arguments are derived directly from a slice, // and therefore satisfy insert_ro_buffer's precondition. unsafe { db.insert_ro_buffer(slice.as_ptr().into(), slice.len().into()) } } // Utility to call insert_rw_buffer with a slice. // Safety: insert_ro_slice does not prevent RoAllowBuffer from outliving slice. // Instead, the caller must make sure its use patterns don't cause invalid // accesses. unsafe fn insert_rw_slice( db: &mut AllowDb, slice: &[Cell], ) -> Result { // Safety: The address and len arguments are derived directly frwm a slice, // and therefore satisfy insert_rw_buffer's precondition. unsafe { db.insert_rw_buffer(slice.as_ptr().into(), slice.len().into()) } } // Utility to return a RoAllowBuffer and verify the returned register values // match the provided slice. fn remove_ro_check(db: &mut AllowDb, buffer: RoAllowBuffer, slice: &[Cell]) { let (address, len) = db.remove_ro_buffer(buffer); let address: *const u8 = address.into(); assert_eq!(address, slice.as_ptr() as *const u8); let len: usize = len.into(); assert_eq!(len, slice.len()); } // Utility to return a RwAllowBuffer and verify the returned register values // match the prwvided slice. fn remove_rw_check(db: &mut AllowDb, buffer: RwAllowBuffer, slice: &[Cell]) { let (address, len) = db.remove_rw_buffer(buffer); let address: *mut u8 = address.into(); assert_eq!(address, slice.as_ptr() as *mut u8); let len: usize = len.into(); assert_eq!(len, slice.len()); } #[test] fn allow_db() { let mut db: AllowDb = Default::default(); let fake_memory: &mut [u8] = &mut [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; let fake_memory = Cell::from_mut(fake_memory).as_slice_of_cells(); // Safety: Big unsafe block because insert_ro_slice and insert_rw_slice do // not protect lifetimes. We have to return all slices before they become // invalid, which happens after this blocks ends (when we check // fake_memory's value). unsafe { let ro_buffer_2_5 = insert_ro_slice(&mut db, &fake_memory[2..=5]).unwrap(); // A zero-sized buffer should not alias. let ro_buffer_3_empty = insert_ro_slice(&mut db, &fake_memory[3..3]).unwrap(); let rw_buffer_3_empty = insert_rw_slice(&mut db, &fake_memory[3..3]).unwrap(); // Generate an overlapping allow, which should return an error. insert_rw_slice(&mut db, &fake_memory[4..=7]).unwrap_err(); // Generate a variety of overlaps: overlapping just the first byte, the last // byte, a larger range, a smaller range, and an identical range. insert_rw_slice(&mut db, &fake_memory[0..=2]).unwrap_err(); insert_rw_slice(&mut db, &fake_memory[5..=10]).unwrap_err(); insert_ro_slice(&mut db, &fake_memory[1..=7]).unwrap_err(); insert_ro_slice(&mut db, &fake_memory[3..=4]).unwrap_err(); insert_ro_slice(&mut db, &fake_memory[2..=5]).unwrap_err(); // Add a second buffer, and make sure we can still add a buffer in the // middle. let mut rw_buffer_8_12 = insert_rw_slice(&mut db, &fake_memory[8..=12]).unwrap(); let ro_buffer_6_7 = insert_ro_slice(&mut db, &fake_memory[6..=7]).unwrap(); // Check the Deref implementations on the read-only buffers. For the // nonempty read-write buffers, we mutate the buffers as well. assert_eq!(*ro_buffer_2_5, [2, 3, 4, 5]); assert_eq!(*ro_buffer_3_empty, []); assert_eq!(*rw_buffer_3_empty, []); assert_eq!(*rw_buffer_8_12, [8, 9, 10, 11, 12]); rw_buffer_8_12.copy_from_slice(&[20, 21, 22, 23, 24]); assert_eq!(*ro_buffer_6_7, [6, 7]); // Remove a buffer, and check it can be re-added. remove_ro_check(&mut db, ro_buffer_6_7, &fake_memory[6..=7]); let rw_buffer_6_7 = insert_rw_slice(&mut db, &fake_memory[6..=7]).unwrap(); // Clean up all the buffers. remove_ro_check(&mut db, ro_buffer_2_5, &fake_memory[2..=5]); remove_ro_check(&mut db, ro_buffer_3_empty, &fake_memory[3..3]); remove_rw_check(&mut db, rw_buffer_3_empty, &fake_memory[3..3]); remove_rw_check(&mut db, rw_buffer_8_12, &fake_memory[8..=12]); remove_rw_check(&mut db, rw_buffer_6_7, &fake_memory[6..=7]); } // Verify the values were correctly written into fake_memory. let expected: &mut [u8] = &mut [0, 1, 2, 3, 4, 5, 6, 7, 20, 21, 22, 23, 24, 13, 14, 15]; assert_eq!(fake_memory, Cell::from_mut(expected).as_slice_of_cells()); } ================================================ FILE: unittest/src/command_return.rs ================================================ //! Safe constructors for `libtock_platform::CommandReturn` variants. use libtock_platform::{return_variant, CommandReturn, ErrorCode}; pub fn failure(error_code: ErrorCode) -> CommandReturn { // Safety: return_variant is a failure, so r1 must be a valid ErrorCode, // which is enforced by error_code's type. unsafe { CommandReturn::new(return_variant::FAILURE, error_code as u32, 0, 0) } } pub fn failure_u32(error_code: ErrorCode, value: u32) -> CommandReturn { // Safety: return_variant is a failure, so r1 must be a valid ErrorCode, // which is enforced by error_code's type. unsafe { CommandReturn::new(return_variant::FAILURE_U32, error_code as u32, value, 0) } } pub fn failure_2_u32(error_code: ErrorCode, value0: u32, value1: u32) -> CommandReturn { unsafe { // Safety: return_variant is a failure, so r1 must be a valid ErrorCode, // which is enforced by error_code's type. CommandReturn::new( return_variant::FAILURE_2_U32, error_code as u32, value0, value1, ) } } pub fn failure_u64(error_code: ErrorCode, value: u64) -> CommandReturn { unsafe { // Safety: return_variant is a failure, so r1 must be a valid ErrorCode, // which is enforced by error_code's type. CommandReturn::new( return_variant::FAILURE_U64, error_code as u32, value as u32, (value >> 32) as u32, ) } } pub fn success() -> CommandReturn { // Safety: return_variant is a success so there are no other invariants to // maintain. unsafe { CommandReturn::new(return_variant::SUCCESS, 0, 0, 0) } } pub fn success_u32(value: u32) -> CommandReturn { // Safety: return_variant is a success so there are no other invariants to // maintain. unsafe { CommandReturn::new(return_variant::SUCCESS_U32, value, 0, 0) } } pub fn success_2_u32(value0: u32, value1: u32) -> CommandReturn { // Safety: return_variant is a success so there are no other invariants to // maintain. unsafe { CommandReturn::new(return_variant::SUCCESS_2_U32, value0, value1, 0) } } pub fn success_u64(value: u64) -> CommandReturn { unsafe { // Safety: return_variant is a success so there are no other invariants // to maintain. CommandReturn::new( return_variant::SUCCESS_U64, value as u32, (value >> 32) as u32, 0, ) } } pub fn success_3_u32(value0: u32, value1: u32, value2: u32) -> CommandReturn { // Safety: return_variant is a success so there are no other invariants to // maintain. unsafe { CommandReturn::new(return_variant::SUCCESS_3_U32, value0, value1, value2) } } pub fn success_u32_u64(value0: u32, value1: u64) -> CommandReturn { unsafe { // Safety: return_variant is a success so there are no other invariants // to maintain. CommandReturn::new( return_variant::SUCCESS_U32_U64, value0, value1 as u32, (value1 >> 32) as u32, ) } } #[cfg(test)] mod tests { use super::*; #[test] fn failure_test() { assert_eq!( failure(ErrorCode::Fail).get_failure(), Some(ErrorCode::Fail) ); } #[test] fn failure_u32_test() { assert_eq!( failure_u32(ErrorCode::Busy, 42).get_failure_u32(), Some((ErrorCode::Busy, 42)) ); } #[test] fn failure_2_u32_test() { assert_eq!( failure_2_u32(ErrorCode::Off, 31, 27).get_failure_2_u32(), Some((ErrorCode::Off, 31, 27)) ); } #[test] fn failure_u64_test() { assert_eq!( failure_u64(ErrorCode::Size, 0x1111_2222_3333_4444).get_failure_u64(), Some((ErrorCode::Size, 0x1111_2222_3333_4444)) ); } #[test] fn success_test() { assert!(success().is_success()); } #[test] fn success_u32_test() { assert_eq!(success_u32(1618).get_success_u32(), Some(1618)); } #[test] fn success_2_u32_test() { assert_eq!(success_2_u32(1, 2).get_success_2_u32(), Some((1, 2))); } #[test] fn success_u64_test() { assert_eq!( success_u64(0x1111_2222_3333_4444).get_success_u64(), Some(0x1111_2222_3333_4444) ); } #[test] fn success_3_u32_test() { assert_eq!(success_3_u32(3, 5, 8).get_success_3_u32(), Some((3, 5, 8))); } #[test] fn success_u32_u64_test() { assert_eq!( success_u32_u64(13, 0x1111_2222_3333_4444).get_success_u32_u64(), Some((13, 0x1111_2222_3333_4444)) ); } } ================================================ FILE: unittest/src/driver_info.rs ================================================ /// Information that a `fake::SyscallDriver` provides to the `fake::Kernel` /// during registration. This may be expanded over time as new features are /// added to Tock. #[non_exhaustive] pub struct DriverInfo { // All constructors of DriverInfo require the driver to specify // `driver_num`. pub(crate) driver_num: u32, /// The maximum number of subscriptions to support. The maximum subscribe /// number supported will be one less than `upcall_count`. pub upcall_count: u32, } impl DriverInfo { /// Creates a new `DriverInfo` with the given driver number. `upcall_count` /// will be initialized to zero. pub fn new(driver_num: u32) -> Self { Self { driver_num, upcall_count: 0, } } /// Sets `upcall_count` and returns `self`. Used similar to a builder. /// /// # Example /// ``` /// use libtock_platform::CommandReturn; /// use libtock_unittest::{DriverInfo, fake}; /// struct FooDriver; /// impl fake::SyscallDriver for FooDriver { /// fn info(&self) -> DriverInfo { /// DriverInfo::new(3).upcall_count(2) /// } /// fn command(&self, _: u32, _: u32, _: u32) -> CommandReturn { /// unimplemented!("Example code"); /// } /// } /// ``` pub fn upcall_count(mut self, upcall_count: u32) -> Self { self.upcall_count = upcall_count; self } } ================================================ FILE: unittest/src/exit_test/mod.rs ================================================ //! Tools for testing code that calls the Exit system call. //! //! This module is not compatible with Miri because it requires the ability to //! spawn external processes, which Miri does not support by default. Therefore //! it is only available for non-Miri tests. #[cfg(test)] mod tests; use std::panic::{catch_unwind, Location, UnwindSafe}; /// Utility for testing code that is expected to call the Exit system call. It /// is used as follows (inside a unit test case): /// /// ``` /// // Note: exit_test is not available in Miri /// #[cfg(miri)] /// fn main() {} /// /// #[cfg(not(miri))] /// fn main() { /// use libtock_platform::Syscalls; /// let _kernel = libtock_unittest::fake::Kernel::new(); /// let exit = libtock_unittest::exit_test("tests::foo", || { /// libtock_unittest::fake::Syscalls::exit_terminate(0); /// }); /// assert_eq!(exit, libtock_unittest::ExitCall::Terminate(0)); /// } /// ``` /// /// `exit_test` will panic (to fail the test case) if the code does not call /// Exit, or if the parameters to exit do not match `expected_exit`. /// /// `test_name` must match the name of the test case, as is used in Rust's test /// framework's filter syntax. /// /// `exit_test` is a hack, and the user should understand how it works to /// understand its limitations. When the above test case is executed, the /// following happens: /// /// 1. The first test process (the one started by the user, e.g. through /// `cargo test`) executes the `foo()` test case, which calls `exit_test`. /// We'll call this process A, as it was the first test process to start. /// 2. `exit_test` spawns a second process, B, by invoking the same test binary /// as process A. When it does, it passes a filter to process B telling it /// to only invoke `foo()` (this is the purpose of the `test_name` argument). /// It also sets an environment variable telling process B that `exit_test` /// launched it. /// 3. Process B runs the `foo()` test case, which invokes `exit_test` a second /// time. /// 4. `exit_test` in process B uses the environment variable to detect that it /// is the subprocess version, and it runs closure `fcn`. If `fcn` does not /// call Exit, it panics. `exit_test` will not return from process B. /// 5. `exit_test` in process A waits until process B terminates. /// 6. `exit_test` in process A reads the output of process B to determine /// whether Exit was called, and if so what arguments were called. /// 7. `exit_test` in process A returns a value indicating what happened in /// process B, which `foo()` can make assertions on. #[track_caller] pub fn exit_test(test_name: &str, fcn: F) -> ExitCall { if let Some(signal_var) = std::env::var_os(SIGNAL_VAR) { // We are process B, run the test function. run_test(signal_var, fcn) } else { // We are process A, spawn process B. spawn_test(test_name) } } /// Indicates what type of Exit call was performed, and what completion code was /// provided. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ExitCall { Terminate(u32), Restart(u32), } // ----------------------------------------------------------------------------- // Public API above, implementation details below. // ----------------------------------------------------------------------------- // Prints a message telling exit_test the Exit system call was called. pub(crate) fn signal_exit(exit_call: ExitCall) { signal_message(ExitMessage::ExitCall(exit_call)); } #[doc(hidden)] impl std::fmt::Display for ExitCall { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { match self { ExitCall::Terminate(code) => write!(f, "exit-terminate({code})"), ExitCall::Restart(code) => write!(f, "exit-restart({code})"), } } } #[doc(hidden)] impl std::str::FromStr for ExitCall { type Err = ParseExitError; fn from_str(s: &str) -> Result { // Strip off the trailing ), leaving the name and ( let s = s.strip_suffix(')').ok_or(ParseExitError)?; if let Some(s) = s.strip_prefix("exit-terminate(") { Ok(ExitCall::Terminate(s.parse().or(Err(ParseExitError))?)) } else if let Some(s) = s.strip_prefix("exit-restart(") { Ok(ExitCall::Restart(s.parse().or(Err(ParseExitError))?)) } else { Err(ParseExitError) } } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[doc(hidden)] pub struct ParseExitError; // The name of the environment variable used by process A to tell process B that // it is process B. The value of the environment variable is the location where // exit_test was called (this location is used to help verify that test_name is // correct). const SIGNAL_VAR: &str = "LIBTOCK_UNITTEST_EXIT_TEST"; // This string is printed by process B to tell process A how it exited. It is // followed by the Display string for a ExitMessage. const EXIT_STRING: &str = "LIBTOCK_UNITTEST_EXIT_TEST_RESULT: "; #[derive(Debug, Eq, PartialEq)] enum ExitMessage { ExitCall(ExitCall), WrongCase, DidNotExit, } impl std::fmt::Display for ExitMessage { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { match self { ExitMessage::ExitCall(exit_call) => write!(f, "ExitCall({exit_call})"), ExitMessage::WrongCase => write!(f, "WrongCase"), ExitMessage::DidNotExit => write!(f, "DidNotExit"), } } } impl std::str::FromStr for ExitMessage { type Err = ParseExitError; fn from_str(s: &str) -> Result { if let Some(s) = s.strip_prefix("ExitCall(") { let s = s.strip_suffix(')').ok_or(ParseExitError)?; Ok(ExitMessage::ExitCall(s.parse()?)) } else if s == "WrongCase" { Ok(ExitMessage::WrongCase) } else if s == "DidNotExit" { Ok(ExitMessage::DidNotExit) } else { Err(ParseExitError) } } } // Implements process A's behavior for exit_test: spawns this test again as a // subprocess, only executing the test specified by test_name. #[track_caller] fn spawn_test(test_name: &str) -> ExitCall { let current_exe = std::env::current_exe().expect("Unable to find test executable"); let output = std::process::Command::new(current_exe) .args(std::env::args_os()) .arg("--nocapture") .arg("--exact") .arg(test_name) .envs(std::env::vars_os()) .env(SIGNAL_VAR, format!("{}", Location::caller())) .output() .expect("Subprocess exec failed"); let stdout = String::from_utf8(output.stdout).expect("Subprocess produced invalid UTF-8"); println!("{test_name} subprocess stdout:\n{stdout}"); let stderr = String::from_utf8(output.stderr).expect("Subprocess produced invalid UTF-8"); println!("{test_name} subprocess stderr:\n{stderr}"); // Search for the exit message in stdout. for line in stdout.lines() { if let Some(message) = line.strip_prefix(EXIT_STRING) { match message .parse::() .expect("Failed to parse exit message") { ExitMessage::ExitCall(exit_call) => return exit_call, ExitMessage::WrongCase => panic!( "Subprocess executed the wrong test case. Perhaps test_name is incorrect?" ), ExitMessage::DidNotExit => panic!("Subprocess did not call Exit."), } } } panic!("Subprocess did not indicate why it exited. Perhaps test_name is incorrect?"); } // Used by process B to send a message to process A. fn signal_message(message: ExitMessage) { println!("{EXIT_STRING}{message}"); } // Implements process B's behavior for exit_test. Verifies the test case was // specified correctly, runs the test function, and prints an error if the test // function did not call Exit. #[track_caller] fn run_test(signal_var: std::ffi::OsString, fcn: F) -> ! { let signal_var = signal_var.to_str().expect("Invalid signal variable value"); if format!("{}", Location::caller()) != signal_var { signal_message(ExitMessage::WrongCase); std::process::exit(1); } println!("exit_test: closure return value {:?}", catch_unwind(fcn)); signal_message(ExitMessage::DidNotExit); std::process::exit(1); } ================================================ FILE: unittest/src/exit_test/tests.rs ================================================ use super::*; #[test] fn exitcall_display() { assert_eq!(format!("{}", ExitCall::Terminate(3)), "exit-terminate(3)"); assert_eq!(format!("{}", ExitCall::Restart(14)), "exit-restart(14)"); } #[test] fn exitcall_parse() { assert_eq!("exit-terminate(3)".parse(), Ok(ExitCall::Terminate(3))); assert_eq!("exit-restart(14)".parse(), Ok(ExitCall::Restart(14))); assert_eq!("exit-unknown(3)".parse::(), Err(ParseExitError)); assert_eq!( "exit-restart(not-an-int)".parse::(), Err(ParseExitError) ); assert_eq!("no-parens".parse::(), Err(ParseExitError)); assert_eq!("".parse::(), Err(ParseExitError)); } #[test] fn exitmessage_display() { assert_eq!( format!("{}", ExitMessage::ExitCall(ExitCall::Restart(1))), "ExitCall(exit-restart(1))" ); assert_eq!(format!("{}", ExitMessage::WrongCase), "WrongCase"); assert_eq!(format!("{}", ExitMessage::DidNotExit), "DidNotExit"); } #[test] fn exitmessage_parse() { assert_eq!("".parse::(), Err(ParseExitError)); assert_eq!("ExitCall()".parse::(), Err(ParseExitError)); assert_eq!( "ExitCall(error)".parse::(), Err(ParseExitError) ); assert_eq!( "ExitCall(exit-restart(5))".parse::(), Ok(ExitMessage::ExitCall(ExitCall::Restart(5))) ); assert_eq!( "WrongCase".parse::(), Ok(ExitMessage::WrongCase) ); assert_eq!( "DidNotExit".parse::(), Ok(ExitMessage::DidNotExit) ); } #[should_panic(expected = "did not call Exit")] #[test] fn exit_test_did_not_exit() { exit_test("exit_test::tests::exit_test_did_not_exit", || {}); } #[should_panic(expected = "did not indicate why it exited")] #[test] fn exit_test_did_not_signal() { exit_test("exit_test::tests::exit_test_did_not_signal", || { std::process::exit(1) }); } #[test] fn exit_test_signal_terminate() { let result = exit_test("exit_test::tests::exit_test_signal_terminate", || { signal_exit(ExitCall::Terminate(159)); std::process::exit(1); }); assert_eq!(result, ExitCall::Terminate(159)); } #[test] fn exit_test_signal_restart() { let result = exit_test("exit_test::tests::exit_test_signal_restart", || { signal_exit(ExitCall::Restart(0)); std::process::exit(1); }); assert_eq!(result, ExitCall::Restart(0)); } #[should_panic(expected = "executed the wrong test case")] #[test] fn exit_test_wrong_case() { // Intentionally-incorrect test case name. exit_test("exit_test::tests::exit_test_signal_restart", || { signal_exit(ExitCall::Restart(0)); std::process::exit(1); }); } ================================================ FILE: unittest/src/expected_syscall.rs ================================================ use libtock_platform::Register; /// Unit tests can use `ExpectedSyscall` to alter `fake::Kernel`'s behavior for /// a particular system call. An example use case is error injection: unit tests /// can add a `ExpectedSyscall` to the fake kernel's queue to insert errors in /// order to test error handling code. #[derive(Clone, Copy, Debug)] pub enum ExpectedSyscall { // ------------------------------------------------------------------------- // Yield // ------------------------------------------------------------------------- YieldNoWait { /// If not `None`, `yield-no-wait` will set the return value to the /// specified value. If `None`, `yield-no-wait` will set the return /// value based on whether or not an upcall was run. override_return: Option, }, YieldWait { /// If true, yield_wait will skip executing a upcall. skip_upcall: bool, }, // ------------------------------------------------------------------------- // Subscribe // ------------------------------------------------------------------------- Subscribe { driver_num: u32, subscribe_num: u32, /// If not None, the Subscribe call will be skipped and the provided /// error code will be returned (along with the passed upcall). skip_with_error: Option, }, // ------------------------------------------------------------------------- // Command // ------------------------------------------------------------------------- Command { // Matched values: the command must give the specified driver_id, // command_id, argument0, and argument1 values. driver_id: u32, command_id: u32, argument0: u32, argument1: u32, // If not None, the output of the driver will be replaced with the given // return value. override_return: Option, }, // ------------------------------------------------------------------------- // Read-Only Allow // ------------------------------------------------------------------------- AllowRo { driver_num: u32, buffer_num: u32, // If set to Some(_), the driver's allow_readonly method will not be // invoked and the provided error will be returned instead. return_error: Option, }, // ------------------------------------------------------------------------- // Read-Write Allow // ------------------------------------------------------------------------- AllowRw { driver_num: u32, buffer_num: u32, // If set to Some(_), the driver's allow_readwrite method will not be // invoked and the provided error will be returned instead. return_error: Option, }, // ------------------------------------------------------------------------- // Memop // ------------------------------------------------------------------------- Memop { memop_num: u32, argument0: Register, // Necessary for Miri ptr provenance tests of brk // If set to Some(_), the driver's memop method will not be // invoked and the provided error will be returned instead. return_error: Option, }, // TODO: Add Exit. } impl ExpectedSyscall { // Panics with a message describing that the named system call was called // instead of the expected system call. Used by fake::Kernel to report // incorrect system calls. pub(crate) fn panic_wrong_call(&self, called: &str) -> ! { // TODO: Implement Display for ExpectedSyscall and replace {:?} with {} panic!("Expected system call {self:?}, but {called} was called instead."); } } ================================================ FILE: unittest/src/fake/adc/mod.rs ================================================ //! Fake implementation of the Adc API, documented here: //! //! Like the real API, `Adc` controls a fake Adc sensor. It provides //! a function `set_value` used to immediately call an upcall with a Adc value read by the sensor //! and a function 'set_value_sync' used to call the upcall when the read command is received. use crate::{DriverInfo, DriverShareRef}; use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::Cell; // The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when read command is received, // or None otherwise. It was needed for testing `read_sync` library function which simulates a synchronous Adc read, // because it was impossible to schedule an upcall during the `synchronous` read in other ways. pub struct Adc { busy: Cell, upcall_on_command: Cell>, share_ref: DriverShareRef, } impl Adc { pub fn new() -> std::rc::Rc { std::rc::Rc::new(Adc { busy: Cell::new(false), upcall_on_command: Cell::new(None), share_ref: Default::default(), }) } pub fn is_busy(&self) -> bool { self.busy.get() } pub fn set_value(&self, value: i32) { if self.busy.get() { self.share_ref .schedule_upcall(0, (value as u32, 0, 0)) .expect("Unable to schedule upcall"); self.busy.set(false); } } pub fn set_value_sync(&self, value: i32) { self.upcall_on_command.set(Some(value)); } } impl crate::fake::SyscallDriver for Adc { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_id { EXISTS => crate::command_return::success_u32(1), SINGLE_SAMPLE => { if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_command.take() { self.set_value(val); } crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x5; // Command IDs const EXISTS: u32 = 0; const SINGLE_SAMPLE: u32 = 1; ================================================ FILE: unittest/src/fake/adc/tests.rs ================================================ use crate::fake::{self, SyscallDriver}; use fake::adc::*; use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; //Test the command implementation #[test] fn command() { let adc = Adc::new(); assert!(adc.command(EXISTS, 1, 2).is_success_u32()); assert!(adc.command(SINGLE_SAMPLE, 0, 0).is_success()); assert_eq!( adc.command(SINGLE_SAMPLE, 0, 0).get_failure(), Some(ErrorCode::Busy) ); adc.set_value(100); assert!(adc.command(SINGLE_SAMPLE, 0, 1).is_success()); adc.set_value(100); adc.set_value_sync(100); assert!(adc.command(SINGLE_SAMPLE, 0, 1).is_success()); assert!(adc.command(SINGLE_SAMPLE, 0, 1).is_success()); } // Integration test that verifies Adc works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let adc = Adc::new(); kernel.add_driver(&adc); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success_u32()); assert!(fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 0).get_failure(), Some(ErrorCode::Busy) ); adc.set_value(100); assert!(fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 1).is_success()); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); adc.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); adc.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 1).is_success()); adc.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); adc.set_value_sync(200); assert!(fake::Syscalls::command(DRIVER_NUM, SINGLE_SAMPLE, 0, 1).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }); } ================================================ FILE: unittest/src/fake/air_quality/mod.rs ================================================ use crate::{DriverInfo, DriverShareRef}; use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::Cell; pub struct AirQuality { busy: Cell, co2_available: Cell, tvoc_available: Cell, upcall_on_read: Cell>, upcall_on_tuple_read: Cell>, share_ref: DriverShareRef, } impl AirQuality { pub fn new() -> std::rc::Rc { std::rc::Rc::new(AirQuality { busy: Cell::new(false), co2_available: Cell::new(true), tvoc_available: Cell::new(true), upcall_on_read: Cell::new(None), upcall_on_tuple_read: Cell::new(None), share_ref: Default::default(), }) } pub fn set_co2_available(&self, co2_available: bool) { self.co2_available.set(co2_available); } pub fn set_tvoc_available(&self, tvoc_available: bool) { self.tvoc_available.set(tvoc_available); } pub fn is_busy(&self) -> bool { self.busy.get() } pub fn set_value(&self, value: u32) { if self.busy.get() { self.share_ref .schedule_upcall(0, (value, 0, 0)) .expect("Unable to schedule upcall"); self.busy.set(false); } } pub fn set_value_sync(&self, value: u32) { self.upcall_on_read.set(Some(value)); } pub fn set_values_sync(&self, co2_value: u32, tvoc_value: u32) { self.upcall_on_tuple_read.set(Some((co2_value, tvoc_value))); } } impl crate::fake::SyscallDriver for AirQuality { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_id { EXISTS => crate::command_return::success(), READ_CO2 => { if !self.co2_available.get() { return crate::command_return::failure(ErrorCode::NoSupport); } if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_read.take() { self.set_value(val); } if let Some((co2_val, _)) = self.upcall_on_tuple_read.get() { self.set_value(co2_val); } crate::command_return::success() } READ_TVOC => { if !self.tvoc_available.get() { return crate::command_return::failure(ErrorCode::NoSupport); } if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_read.take() { self.set_value(val); } if let Some((_, tvoc_val)) = self.upcall_on_tuple_read.take() { self.set_value(tvoc_val); } crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60007; // Command IDs const EXISTS: u32 = 0; const READ_CO2: u32 = 2; const READ_TVOC: u32 = 3; ================================================ FILE: unittest/src/fake/air_quality/tests.rs ================================================ use crate::fake::{self, SyscallDriver}; use fake::air_quality::*; use libtock_platform::{share::scope, DefaultConfig, YieldNoWaitReturn}; //Test the `command` implementation #[test] fn command() { let driver = AirQuality::new(); assert!(driver.command(EXISTS, 0, 0).is_success()); driver.set_co2_available(false); assert_eq!( driver.command(READ_CO2, 0, 0).get_failure(), Some(ErrorCode::NoSupport) ); driver.set_tvoc_available(false); assert_eq!( driver.command(READ_TVOC, 0, 0).get_failure(), Some(ErrorCode::NoSupport) ); driver.set_co2_available(true); driver.set_tvoc_available(true); assert!(driver.command(READ_CO2, 0, 0).is_success()); assert_eq!( driver.command(READ_CO2, 0, 0).get_failure(), Some(ErrorCode::Busy) ); driver.set_value(100); assert!(driver.command(READ_CO2, 0, 0).is_success()); driver.set_value(100); assert!(driver.command(READ_TVOC, 0, 0).is_success()); assert_eq!( driver.command(READ_TVOC, 0, 0).get_failure(), Some(ErrorCode::Busy) ); driver.set_value(100); assert!(driver.command(READ_TVOC, 0, 0).is_success()); driver.set_value(100); driver.set_value_sync(100); assert!(driver.command(READ_CO2, 0, 0).is_success()); assert!(driver.command(READ_TVOC, 0, 0).is_success()); } // Integration test that verifies Temperature works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let driver = AirQuality::new(); kernel.add_driver(&driver); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 0, 0).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).get_failure(), Some(ErrorCode::Busy) ); driver.set_value(100); assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); driver.set_value(100); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).get_failure(), Some(ErrorCode::Busy) ); driver.set_value(100); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).is_success()); let listener = Cell::>::new(None); scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); driver.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(listener.get(), Some((100,))); assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); driver.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((200,))); driver.set_value_sync(200); assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); driver.set_value_sync(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); driver.set_values_sync(100, 200); assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); } ================================================ FILE: unittest/src/fake/alarm/mod.rs ================================================ //! Fake implementation of the Alarm API. //! //! Supports frequency and set_relative. //! Will schedule the upcall immediately. use core::cell::Cell; use core::num::Wrapping; use libtock_platform::{CommandReturn, ErrorCode}; use crate::{DriverInfo, DriverShareRef}; pub struct Alarm { frequency_hz: u32, now: Cell>, share_ref: DriverShareRef, } impl Alarm { pub fn new(frequency_hz: u32) -> std::rc::Rc { std::rc::Rc::new(Alarm { frequency_hz, now: Cell::new(Wrapping(0)), share_ref: Default::default(), }) } } impl crate::fake::SyscallDriver for Alarm { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_number: u32, argument0: u32, _argument1: u32) -> CommandReturn { match command_number { command::FREQUENCY => crate::command_return::success_u32(self.frequency_hz), command::SET_RELATIVE => { // We're not actually sleeping, just ticking the timer. // The semantics of sleeping aren't clear, // so we're assuming that all future times are equal, // and waking immediately. let relative = argument0; let wake = self.now.get() + Wrapping(relative); self.share_ref .schedule_upcall(subscribe::CALLBACK, (wake.0, 0, 0)) .expect("schedule_upcall failed"); self.now.set(wake); crate::command_return::success_u32(wake.0) } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x0; // Command IDs #[allow(unused)] pub mod command { pub const EXISTS: u32 = 0; pub const FREQUENCY: u32 = 1; pub const TIME: u32 = 2; pub const STOP: u32 = 3; pub const SET_RELATIVE: u32 = 5; pub const SET_ABSOLUTE: u32 = 6; } #[allow(unused)] pub mod subscribe { pub const CALLBACK: u32 = 0; } ================================================ FILE: unittest/src/fake/alarm/tests.rs ================================================ use crate::fake; use fake::alarm::*; // Tests the command implementation. #[test] fn command() { use fake::SyscallDriver; let alarm = Alarm::new(10); assert_eq!( alarm.command(command::FREQUENCY, 1, 2).get_success_u32(), Some(10) ); } ================================================ FILE: unittest/src/fake/ambient_light/mod.rs ================================================ //! Fake implementation of the Ambient Light API, documented here: //! https://github.com/tock/tock/blob/master/doc/syscalls/60002_luminance.md //! //! Like the real API, `AmbientLight` controls a fake ambient light sensor. It provides //! a function `set_value` used to immediately call an upcall with a intensity value read by the sensor //! and a function 'set_value_sync' used to call the upcall when the read command is received. use crate::{DriverInfo, DriverShareRef}; use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::Cell; // The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when read command is received, // or None otherwise. It was needed for testing `read_sync` library function which simulates a synchronous temperature read, // because it was impossible to schedule an upcall during the `synchronous` read in other ways. pub struct AmbientLight { busy: Cell, upcall_on_command: Cell>, share_ref: DriverShareRef, } impl AmbientLight { pub fn new() -> std::rc::Rc { std::rc::Rc::new(AmbientLight { busy: Cell::new(false), upcall_on_command: Cell::new(None), share_ref: Default::default(), }) } pub fn is_busy(&self) -> bool { self.busy.get() } pub fn set_value(&self, value: u32) { if self.busy.get() { self.share_ref .schedule_upcall(0, (value, 0, 0)) .expect("Unable to schedule upcall"); self.busy.set(false); } } pub fn set_value_sync(&self, value: u32) { self.upcall_on_command.set(Some(value)); } } impl crate::fake::SyscallDriver for AmbientLight { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_id { EXISTS => crate::command_return::success(), READ_INTENSITY => { if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_command.take() { self.set_value(val); } crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60002; // Command IDs const EXISTS: u32 = 0; const READ_INTENSITY: u32 = 1; ================================================ FILE: unittest/src/fake/ambient_light/tests.rs ================================================ use crate::fake::{self, SyscallDriver}; use fake::ambient_light::*; use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; //Test the command implementation #[test] fn command() { let amb = AmbientLight::new(); assert!(amb.command(EXISTS, 1, 2).is_success()); assert!(amb.command(READ_INTENSITY, 0, 0).is_success()); assert_eq!( amb.command(READ_INTENSITY, 0, 0).get_failure(), Some(ErrorCode::Busy) ); amb.set_value(100); assert!(amb.command(READ_INTENSITY, 0, 1).is_success()); amb.set_value(100); amb.set_value_sync(100); assert!(amb.command(READ_INTENSITY, 0, 1).is_success()); assert!(amb.command(READ_INTENSITY, 0, 1).is_success()); } // Integration test that verifies AmbientLight works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let ambient_light = AmbientLight::new(); kernel.add_driver(&ambient_light); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 0).get_failure(), Some(ErrorCode::Busy) ); ambient_light.set_value(100); assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 1).is_success()); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); ambient_light.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); ambient_light.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 1).is_success()); ambient_light.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); ambient_light.set_value_sync(200); assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 1).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }); } ================================================ FILE: unittest/src/fake/buttons/mod.rs ================================================ //! Fake implementation of the Buttons API, documented here: //! https://github.com/tock/tock/blob/master/doc/syscalls/00003_buttons.md //! //! Like the real API, `Buttons` controls a set of fake buttons. It provides //! a function `get_button_state` used to retrieve the state and interrupt //! status of a button. //! //! It also provides the function `set_pressed` that set the button's state. use core::cell::Cell; use libtock_platform::{CommandReturn, ErrorCode}; use crate::{DriverInfo, DriverShareRef}; #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct ButtonState { pub pressed: bool, pub interrupt_enabled: bool, } pub struct Buttons { buttons: [Cell; NUM_BUTTONS], share_ref: DriverShareRef, } impl Buttons { pub fn new() -> std::rc::Rc> { #[allow(clippy::declare_interior_mutable_const)] const OFF: Cell = Cell::new(ButtonState { pressed: false, interrupt_enabled: false, }); std::rc::Rc::new(Buttons { buttons: [OFF; NUM_BUTTONS], share_ref: Default::default(), }) } pub fn set_pressed(&self, button: u32, pressed: bool) -> Result<(), ErrorCode> { self.buttons .get(button as usize) .map(|button_state| { let original_button_state = button_state.get(); button_state.set(ButtonState { pressed, ..original_button_state }); if original_button_state.interrupt_enabled && original_button_state.pressed != pressed { self.share_ref .schedule_upcall(0, (button, pressed as u32, 0)) .expect("Unable to schedule upcall {}"); } }) .ok_or(ErrorCode::Invalid) } pub fn get_button_state(&self, button: u32) -> Option { self.buttons.get(button as usize).map(|button| button.get()) } } impl crate::fake::SyscallDriver for Buttons { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_number: u32, argument0: u32, _argument1: u32) -> CommandReturn { match command_number { BUTTONS_COUNT => crate::command_return::success_u32(NUM_BUTTONS as u32), BUTTONS_ENABLE_INTERRUPTS => { if argument0 < NUM_BUTTONS as u32 { let button = self.buttons[argument0 as usize].get(); self.buttons[argument0 as usize].set(ButtonState { interrupt_enabled: true, ..button }); crate::command_return::success() } else { crate::command_return::failure(ErrorCode::Invalid) } } BUTTONS_DISABLE_INTERRUPTS => { if argument0 < NUM_BUTTONS as u32 { let button = self.buttons[argument0 as usize].get(); self.buttons[argument0 as usize].set(ButtonState { interrupt_enabled: false, ..button }); crate::command_return::success() } else { crate::command_return::failure(ErrorCode::Invalid) } } BUTTONS_READ => { if argument0 < NUM_BUTTONS as u32 { crate::command_return::success_u32( self.buttons[argument0 as usize].get().pressed as u32, ) } else { crate::command_return::failure(ErrorCode::Invalid) } } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x3; // Command IDs const BUTTONS_COUNT: u32 = 0; const BUTTONS_ENABLE_INTERRUPTS: u32 = 1; const BUTTONS_DISABLE_INTERRUPTS: u32 = 2; const BUTTONS_READ: u32 = 3; ================================================ FILE: unittest/src/fake/buttons/tests.rs ================================================ use crate::fake; use fake::buttons::*; use libtock_platform::ErrorCode; // Tests the command implementation. #[test] fn command() { use fake::SyscallDriver; let buttons = Buttons::<10>::new(); assert_eq!( buttons.command(BUTTONS_COUNT, 1, 2).get_success_u32(), Some(10) ); assert_eq!( buttons.command(BUTTONS_READ, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( buttons .command(BUTTONS_ENABLE_INTERRUPTS, 11, 0) .get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( buttons .command(BUTTONS_DISABLE_INTERRUPTS, 11, 0) .get_failure(), Some(ErrorCode::Invalid) ); for button_index in 0..10 { assert_eq!( buttons.get_button_state(button_index), Some(ButtonState { pressed: false, interrupt_enabled: false }) ); assert!(buttons .command(BUTTONS_ENABLE_INTERRUPTS, button_index, 0) .is_success()); assert_eq!( buttons.get_button_state(button_index), Some(ButtonState { pressed: false, interrupt_enabled: true }) ); assert!(buttons .command(BUTTONS_DISABLE_INTERRUPTS, button_index, 0) .is_success()); assert_eq!( buttons.get_button_state(button_index), Some(ButtonState { pressed: false, interrupt_enabled: false }) ); assert_eq!(buttons.set_pressed(button_index, true), Ok(())); assert_eq!( buttons.get_button_state(button_index), Some(ButtonState { pressed: true, interrupt_enabled: false }) ); assert_eq!(buttons.set_pressed(button_index, false), Ok(())); assert_eq!( buttons.get_button_state(button_index), Some(ButtonState { pressed: false, interrupt_enabled: false }) ); } } // Integration test that verifies Buttons works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let buttons = Buttons::<10>::new(); kernel.add_driver(&buttons); let value = fake::Syscalls::command(DRIVER_NUM, BUTTONS_COUNT, 1, 2); assert_eq!(value.get_success_u32(), Some(10)); assert_eq!( fake::Syscalls::command(DRIVER_NUM, BUTTONS_READ, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert!(fake::Syscalls::command(DRIVER_NUM, BUTTONS_READ, 0, 0).is_success_u32()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, BUTTONS_ENABLE_INTERRUPTS, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert!(fake::Syscalls::command(DRIVER_NUM, BUTTONS_ENABLE_INTERRUPTS, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, BUTTONS_DISABLE_INTERRUPTS, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert!(fake::Syscalls::command(DRIVER_NUM, BUTTONS_DISABLE_INTERRUPTS, 0, 0).is_success()); } ================================================ FILE: unittest/src/fake/buzzer/mod.rs ================================================ //! Fake implementation of the Buzzer API, documented here: //! //! Like the real API, `Buzzer` controls a fake buzzer. It provides //! a function `set_tone` used to immediately call an upcall with a tone set by the buzzer //! and a function 'set_tone_sync' used to call the upcall when the tone command is received. use crate::{DriverInfo, DriverShareRef}; use core::time::Duration; use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::Cell; // The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when tone command is received, // or None otherwise. It was needed for testing `tone_sync` library function which simulates a synchronous tone set, // because it was impossible to schedule an upcall during the `synchronous` tone set in other ways. pub struct Buzzer { busy: Cell, upcall_on_command: [Cell>; 2], share_ref: DriverShareRef, } impl Buzzer { pub fn new() -> std::rc::Rc { std::rc::Rc::new(Buzzer { busy: Cell::new(false), upcall_on_command: [Cell::new(None), Cell::new(None)], share_ref: Default::default(), }) } pub fn is_busy(&self) -> bool { self.busy.get() } pub fn set_tone(&self, freq: i32, duration: Duration) { if self.busy.get() { self.share_ref .schedule_upcall(0, (freq as u32, duration.as_millis() as u32, 0)) .expect("Unable to schedule upcall"); self.busy.set(false); } } pub fn set_tone_sync(&self, freq: i32, duration: i32) { self.upcall_on_command[0].set(Some(freq)); self.upcall_on_command[1].set(Some(duration)); } } impl crate::fake::SyscallDriver for Buzzer { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_num: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_num { EXISTS => crate::command_return::success(), TONE => { if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(freq) = self.upcall_on_command[0].take() { if let Some(duration) = self.upcall_on_command[1].take() { self.set_tone(freq, Duration::from_millis(duration as u64)); } } crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x90000; // Command IDs const EXISTS: u32 = 0; const TONE: u32 = 1; ================================================ FILE: unittest/src/fake/buzzer/tests.rs ================================================ use crate::fake::{self, SyscallDriver}; use fake::buzzer::*; use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; #[test] fn command() { let buzzer = Buzzer::new(); assert!(buzzer.command(EXISTS, 1, 2).is_success()); assert!(buzzer.command(TONE, 0, 0).is_success()); assert_eq!( buzzer.command(TONE, 0, 0).get_failure(), Some(ErrorCode::Busy) ); buzzer.set_tone(100, Duration::from_millis(100)); assert!(buzzer.command(TONE, 0, 1).is_success()); buzzer.set_tone(100, Duration::from_millis(100)); buzzer.set_tone_sync(100, 100); assert!(buzzer.command(TONE, 0, 1).is_success()); assert!(buzzer.command(TONE, 0, 1).is_success()); } #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let buzzer = Buzzer::new(); kernel.add_driver(&buzzer); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, TONE, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, TONE, 0, 0).get_failure(), Some(ErrorCode::Busy) ); buzzer.set_tone(100, Duration::from_millis(100)); assert!(fake::Syscalls::command(DRIVER_NUM, TONE, 0, 1).is_success()); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); buzzer.set_tone(100, Duration::from_millis(100)); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); buzzer.set_tone(200, Duration::from_millis(100)); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, TONE, 0, 1).is_success()); buzzer.set_tone(200, Duration::from_millis(100)); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((200,))); }); } ================================================ FILE: unittest/src/fake/console/mod.rs ================================================ //! Fake implementation of the Console API, documented here: //! https://github.com/tock/tock/blob/master/doc/syscalls/00001_console.md //! //! Like the real API, `Console` stores each message written to it. //! The resulting byte stream can be retrieved via `take_bytes` //! for use in unit tests. use core::cell::{Cell, RefCell}; use core::cmp; use libtock_platform::{CommandReturn, ErrorCode}; use crate::{DriverInfo, DriverShareRef, RoAllowBuffer, RwAllowBuffer}; pub struct Console { messages: Cell>, buffer: Cell, read_buffer: RefCell, /// To be returned on read input: Cell>, share_ref: DriverShareRef, } impl Console { pub fn new() -> std::rc::Rc { Self::new_with_input(b"") } pub fn new_with_input(inputs: &[u8]) -> std::rc::Rc { std::rc::Rc::new(Console { messages: Default::default(), buffer: Default::default(), read_buffer: Default::default(), input: Cell::new(Vec::from(inputs)), share_ref: Default::default(), }) } /// Returns the bytes that have been submitted so far, /// and clears them. pub fn take_bytes(&self) -> Vec { self.messages.take() } } impl crate::fake::SyscallDriver for Console { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(3) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn allow_readonly( &self, buffer_num: u32, buffer: RoAllowBuffer, ) -> Result { if buffer_num == ALLOW_WRITE { Ok(self.buffer.replace(buffer)) } else { Err((buffer, ErrorCode::Invalid)) } } fn allow_readwrite( &self, buffer_num: u32, buffer: RwAllowBuffer, ) -> Result { if buffer_num == ALLOW_READ { Ok(self.read_buffer.replace(buffer)) } else { Err((buffer, ErrorCode::Invalid)) } } fn command(&self, command_num: u32, argument0: u32, _argument1: u32) -> CommandReturn { match command_num { EXISTS => {} WRITE => { let mut bytes = self.messages.take(); let buffer = self.buffer.take(); let size = cmp::min(buffer.len(), argument0 as usize); bytes.extend_from_slice(&(*buffer)[..size]); self.buffer.set(buffer); self.messages.set(bytes); self.share_ref .schedule_upcall(SUBSCRIBE_WRITE, (size as u32, 0, 0)) .expect("Unable to schedule upcall {}"); } READ => { let count_wanted = argument0 as usize; let bytes = self.input.take(); let count_wanted = cmp::min(count_wanted, bytes.len()); let to_send = &bytes[..count_wanted]; let to_keep = &bytes[count_wanted..]; self.input.set(Vec::from(to_keep)); let count_available = to_send.len(); self.read_buffer.borrow_mut()[..count_wanted].copy_from_slice(to_send); self.share_ref .schedule_upcall(SUBSCRIBE_READ, (0, count_available as u32, 0)) .expect("Unable to schedule upcall {}"); } _ => return crate::command_return::failure(ErrorCode::NoSupport), } crate::command_return::success() } } // ----------------------------------------------------------------------------- // Implementation details below // ----------------------------------------------------------------------------- #[cfg(test)] mod tests; const DRIVER_NUM: u32 = 0x1; // Command numbers const EXISTS: u32 = 0; const WRITE: u32 = 1; const READ: u32 = 2; //const ABORT: u32 = 3; const SUBSCRIBE_WRITE: u32 = 1; const SUBSCRIBE_READ: u32 = 2; const ALLOW_WRITE: u32 = 1; const ALLOW_READ: u32 = 1; ================================================ FILE: unittest/src/fake/console/tests.rs ================================================ use crate::fake; use crate::{RoAllowBuffer, RwAllowBuffer}; use libtock_platform::share; use libtock_platform::DefaultConfig; // Tests the command implementation. #[test] fn command() { use fake::SyscallDriver; let console = fake::Console::new(); assert!(console.command(fake::console::EXISTS, 1, 2).is_success()); assert!(console.allow_readonly(1, RoAllowBuffer::default()).is_ok()); assert!(console.allow_readonly(2, RoAllowBuffer::default()).is_err()); assert!(console.allow_readwrite(1, RwAllowBuffer::default()).is_ok()); assert!(console .allow_readwrite(2, RwAllowBuffer::default()) .is_err()); } // Integration test that verifies Console works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let console = fake::Console::new(); kernel.add_driver(&console); assert!( fake::Syscalls::command(fake::console::DRIVER_NUM, fake::console::EXISTS, 1, 2) .is_success() ); share::scope(|allow_ro| { fake::Syscalls::allow_ro::< DefaultConfig, { fake::console::DRIVER_NUM }, { fake::console::ALLOW_WRITE }, >(allow_ro, b"abcd") .unwrap(); assert!( fake::Syscalls::command(fake::console::DRIVER_NUM, fake::console::WRITE, 3, 0) .is_success() ); }); assert_eq!(console.take_bytes(), b"abc"); assert_eq!(console.take_bytes(), b""); let mut buf = [0; 4]; share::scope(|allow_rw| { fake::Syscalls::allow_rw::< DefaultConfig, { fake::console::DRIVER_NUM }, { fake::console::ALLOW_READ }, >(allow_rw, &mut buf) .unwrap(); assert!( fake::Syscalls::command(fake::console::DRIVER_NUM, fake::console::READ, 3, 0) .is_success() ); }); } ================================================ FILE: unittest/src/fake/gpio/mod.rs ================================================ //! Fake implementation of the GPIO API, documented here: //! https://github.com/tock/tock/blob/master/doc/syscalls/00004_gpio.md //! //! Like the real API, `Gpio` controls a set of fake gpios. It provides //! a function `get_button_state` used to retrieve the state and interrupt //! status of a button. //! //! It also provides the function `set_pressed` that set the button's state. use core::cell::Cell; use libtock_platform::{CommandReturn, ErrorCode}; use std::convert::TryFrom; use crate::{DriverInfo, DriverShareRef}; #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum GpioMode { Output, Input(PullMode), Disable, } #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum PullMode { PullNone = 0, PullUp = 1, PullDown = 2, } impl TryFrom for PullMode { type Error = ErrorCode; fn try_from(value: u32) -> Result { match value { 0 => Ok(PullMode::PullNone), 1 => Ok(PullMode::PullUp), 2 => Ok(PullMode::PullDown), _ => Err(ErrorCode::Invalid), } } } #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum InterruptEdge { Either, Rising, Falling, } impl TryFrom for InterruptEdge { type Error = ErrorCode; fn try_from(value: u32) -> Result { match value { 0 => Ok(InterruptEdge::Either), 1 => Ok(InterruptEdge::Rising), 2 => Ok(InterruptEdge::Falling), _ => Err(ErrorCode::Invalid), } } } #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct GpioState { pub value: bool, pub mode: GpioMode, pub interrupt_enabled: Option, } pub struct Gpio { gpios: [Cell>; NUM_GPIOS], share_ref: DriverShareRef, } impl Gpio { pub fn new() -> std::rc::Rc> { #[allow(clippy::declare_interior_mutable_const)] const OFF: Cell> = Cell::new(Some(GpioState { value: false, mode: GpioMode::Input(PullMode::PullNone), interrupt_enabled: None, })); std::rc::Rc::new(Gpio { gpios: [OFF; NUM_GPIOS], share_ref: Default::default(), }) } pub fn set_missing_gpio(&self, gpio: usize) { if let Some(state) = self.gpios.get(gpio) { state.set(None); } } pub fn set_value(&self, pin: u32, value: bool) -> Result<(), ErrorCode> { self.gpios .get(pin as usize) .map(|gpio| { if let Some(gpio_state) = gpio.get() { let original_value = gpio_state.value; gpio.set(Some(GpioState { value, ..gpio_state })); if original_value != value { if value { if gpio_state.interrupt_enabled == Some(InterruptEdge::Either) || gpio_state.interrupt_enabled == Some(InterruptEdge::Rising) { self.share_ref .schedule_upcall(0, (pin, value as u32, 0)) .expect("Unable to schedule upcall"); } } else if gpio_state.interrupt_enabled == Some(InterruptEdge::Falling) || gpio_state.interrupt_enabled == Some(InterruptEdge::Either) { self.share_ref .schedule_upcall(0, (pin, value as u32, 0)) .expect("Unable to schedule upcall"); } } Ok(()) } else { Err(ErrorCode::NoDevice) } }) .ok_or(ErrorCode::Invalid) .and_then(|value| value) } pub fn get_gpio_state(&self, button: u32) -> Option { self.gpios .get(button as usize) .map(|button| button.get()) .and_then(|value| value) } } impl crate::fake::SyscallDriver for Gpio { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_number: u32, argument0: u32, argument1: u32) -> CommandReturn { if command_number == EXISTS { crate::command_return::success() } else if command_number == GPIO_COUNT { crate::command_return::success_u32(NUM_GPIOS as u32) } else if argument0 < NUM_GPIOS as u32 { if self.gpios[argument0 as usize].get().is_some() { let gpio = self.gpios[argument0 as usize].get().unwrap(); match command_number { GPIO_ENABLE_OUTPUT => { self.gpios[argument0 as usize].set(Some(GpioState { mode: GpioMode::Output, ..gpio })); crate::command_return::success() } GPIO_SET => { if let GpioMode::Output = gpio.mode { self.gpios[argument0 as usize].set(Some(GpioState { value: true, ..gpio })); } crate::command_return::success() } GPIO_CLEAR => { if let GpioMode::Output = gpio.mode { self.gpios[argument0 as usize].set(Some(GpioState { value: false, ..gpio })); } crate::command_return::success() } GPIO_TOGGLE => { if let GpioMode::Output = gpio.mode { self.gpios[argument0 as usize].set(Some(GpioState { value: !gpio.value, ..gpio })); } crate::command_return::success() } GPIO_ENABLE_INPUT => { let pull_mode = PullMode::try_from(argument1); match pull_mode { Ok(mode) => { self.gpios[argument0 as usize].set(Some(GpioState { mode: GpioMode::Input(mode), ..gpio })); crate::command_return::success() } Err(error) => crate::command_return::failure(error), } } GPIO_READ_INPUT => { if let GpioMode::Input(_) = gpio.mode { crate::command_return::success_u32(gpio.value as u32) } else { crate::command_return::success_u32(0) } } GPIO_ENABLE_INTERRUPTS => { let edge = InterruptEdge::try_from(argument1); match edge { Ok(interrupt_edge) => { self.gpios[argument0 as usize].set(Some(GpioState { interrupt_enabled: Some(interrupt_edge), ..gpio })); crate::command_return::success() } Err(error) => crate::command_return::failure(error), } } GPIO_DISABLE_INTERRUPTS => { self.gpios[argument0 as usize].set(Some(GpioState { interrupt_enabled: None, ..gpio })); crate::command_return::success() } GPIO_DISABLE => { self.gpios[argument0 as usize].set(Some(GpioState { mode: GpioMode::Disable, ..gpio })); crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } else { crate::command_return::failure(ErrorCode::NoDevice) } } else { crate::command_return::failure(ErrorCode::Invalid) } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x4; // Command IDs const EXISTS: u32 = 0; const GPIO_ENABLE_OUTPUT: u32 = 1; const GPIO_SET: u32 = 2; const GPIO_CLEAR: u32 = 3; const GPIO_TOGGLE: u32 = 4; const GPIO_ENABLE_INPUT: u32 = 5; const GPIO_READ_INPUT: u32 = 6; const GPIO_ENABLE_INTERRUPTS: u32 = 7; const GPIO_DISABLE_INTERRUPTS: u32 = 8; const GPIO_DISABLE: u32 = 9; const GPIO_COUNT: u32 = 10; ================================================ FILE: unittest/src/fake/gpio/tests.rs ================================================ use crate::fake; use fake::gpio::*; use libtock_platform::{share, DefaultConfig, ErrorCode, YieldNoWaitReturn}; // Tests the command implementation. #[test] fn command() { use fake::SyscallDriver; let gpio = Gpio::<10>::new(); gpio.set_missing_gpio(1); assert_eq!(gpio.command(GPIO_COUNT, 1, 2).get_success_u32(), Some(10)); // Enable Output assert_eq!( gpio.command(GPIO_ENABLE_OUTPUT, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_ENABLE_OUTPUT, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(gpio.command(GPIO_ENABLE_OUTPUT, 0, 0).is_success()); // Gpio Set assert_eq!( gpio.command(GPIO_SET, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_SET, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(gpio.command(GPIO_SET, 0, 0).is_success(),); assert!(gpio.get_gpio_state(0).unwrap().value); // Gpio Clear assert_eq!( gpio.command(GPIO_CLEAR, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_CLEAR, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(gpio.command(GPIO_CLEAR, 0, 0).is_success(),); assert!(!gpio.get_gpio_state(0).unwrap().value); // Gpio Toggle assert_eq!( gpio.command(GPIO_TOGGLE, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_TOGGLE, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(gpio.command(GPIO_TOGGLE, 0, 0).is_success(),); assert!(gpio.get_gpio_state(0).unwrap().value); assert!(gpio.command(GPIO_TOGGLE, 0, 0).is_success(),); assert!(!gpio.get_gpio_state(0).unwrap().value); // Enable Input assert_eq!( gpio.command(GPIO_ENABLE_INPUT, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_ENABLE_INPUT, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert_eq!( gpio.command(GPIO_ENABLE_INPUT, 0, 3).get_failure(), Some(ErrorCode::Invalid) ); assert!(gpio.command(GPIO_ENABLE_INPUT, 0, 0).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullNone) ); assert!(gpio.command(GPIO_ENABLE_INPUT, 0, 1).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullUp) ); assert!(gpio.command(GPIO_ENABLE_INPUT, 0, 2).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullDown) ); // Gpio Read assert_eq!( gpio.command(GPIO_READ_INPUT, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_READ_INPUT, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert_eq!(gpio.set_value(0, true), Ok(())); assert_eq!( gpio.command(GPIO_READ_INPUT, 0, 0).get_success_u32(), Some(1) ); assert_eq!(gpio.set_value(0, false), Ok(())); assert_eq!( gpio.command(GPIO_READ_INPUT, 0, 0).get_success_u32(), Some(0) ); // Enable Interrupts assert_eq!( gpio.command(GPIO_ENABLE_INTERRUPTS, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_ENABLE_INTERRUPTS, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert_eq!( gpio.command(GPIO_ENABLE_INTERRUPTS, 0, 3).get_failure(), Some(ErrorCode::Invalid) ); assert!(gpio.command(GPIO_ENABLE_INTERRUPTS, 0, 0).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Either) ); assert!(gpio.command(GPIO_ENABLE_INTERRUPTS, 0, 1).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Rising) ); assert!(gpio.command(GPIO_ENABLE_INTERRUPTS, 0, 2).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Falling) ); // Disable Interrupts assert_eq!( gpio.command(GPIO_DISABLE_INTERRUPTS, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_DISABLE_INTERRUPTS, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(gpio.command(GPIO_DISABLE_INTERRUPTS, 0, 0).is_success()); assert_eq!(gpio.get_gpio_state(0).unwrap().interrupt_enabled, None); // Disable Pin assert_eq!( gpio.command(GPIO_DISABLE, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( gpio.command(GPIO_DISABLE, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(gpio.command(GPIO_DISABLE, 0, 0).is_success()); assert_eq!(gpio.get_gpio_state(0).unwrap().mode, GpioMode::Disable); } // Integration test that verifies Gpio works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let gpio = Gpio::<10>::new(); gpio.set_missing_gpio(1); kernel.add_driver(&gpio); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_COUNT, 1, 2).get_success_u32(), Some(10) ); // Enable Output assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_OUTPUT, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_OUTPUT, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_OUTPUT, 0, 0).is_success()); // Gpio Set assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_SET, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_SET, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_SET, 0, 0).is_success(),); assert!(gpio.get_gpio_state(0).unwrap().value); // Gpio Clear assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_CLEAR, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_CLEAR, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_CLEAR, 0, 0).is_success(),); assert!(!gpio.get_gpio_state(0).unwrap().value); // Gpio Toggle assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_TOGGLE, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_TOGGLE, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_TOGGLE, 0, 0).is_success(),); assert!(gpio.get_gpio_state(0).unwrap().value); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_TOGGLE, 0, 0).is_success(),); assert!(!gpio.get_gpio_state(0).unwrap().value); // Enable Input assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INPUT, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INPUT, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INPUT, 0, 3).get_failure(), Some(ErrorCode::Invalid) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INPUT, 0, 0).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullNone) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INPUT, 0, 1).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullUp) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INPUT, 0, 2).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().mode, GpioMode::Input(PullMode::PullDown) ); // Gpio Read assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_READ_INPUT, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_READ_INPUT, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert_eq!(gpio.set_value(0, true), Ok(())); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_READ_INPUT, 0, 0).get_success_u32(), Some(1) ); assert_eq!(gpio.set_value(0, false), Ok(())); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_READ_INPUT, 0, 0).get_success_u32(), Some(0) ); // Enable Interrupts assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INTERRUPTS, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INTERRUPTS, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INTERRUPTS, 0, 3).get_failure(), Some(ErrorCode::Invalid) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INTERRUPTS, 0, 0).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Either) ); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); assert_eq!(gpio.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(gpio.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INTERRUPTS, 0, 1).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Rising) ); assert_eq!(gpio.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(gpio.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_ENABLE_INTERRUPTS, 0, 2).is_success()); assert_eq!( gpio.get_gpio_state(0).unwrap().interrupt_enabled, Some(InterruptEdge::Falling) ); assert_eq!(gpio.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(gpio.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(gpio.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); // Disable Interrupts assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_DISABLE_INTERRUPTS, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_DISABLE_INTERRUPTS, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_DISABLE_INTERRUPTS, 0, 0).is_success()); assert_eq!(gpio.get_gpio_state(0).unwrap().interrupt_enabled, None); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); assert_eq!(gpio.set_value(0, true), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert_eq!(gpio.set_value(0, false), Ok(())); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); // Disable Pin assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_DISABLE, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GPIO_DISABLE, 1, 0).get_failure(), Some(ErrorCode::NoDevice) ); assert!(fake::Syscalls::command(DRIVER_NUM, GPIO_DISABLE, 0, 0).is_success()); assert_eq!(gpio.get_gpio_state(0).unwrap().mode, GpioMode::Disable); } ================================================ FILE: unittest/src/fake/ieee802154/mod.rs ================================================ //! Fake implementation of the raw IEEE 802.15.4 API. use core::cell::Cell; use libtock_platform::{CommandReturn, ErrorCode}; use std::{ cell::RefCell, collections::VecDeque, convert::TryFrom, rc::{self, Rc}, }; use crate::{command_return, DriverInfo, DriverShareRef, RoAllowBuffer, RwAllowBuffer}; /// Maximum length of a MAC frame. const MAX_MTU: usize = 127; const PSDU_OFFSET: usize = 2; #[derive(Debug)] #[repr(C)] pub struct Frame { pub header_len: u8, pub payload_len: u8, pub mic_len: u8, pub body: [u8; MAX_MTU], } impl Frame { pub fn with_body(body: &[u8]) -> Self { let mut frame = Self { header_len: 0, payload_len: u8::try_from(body.len()).unwrap(), mic_len: 0, body: [0_u8; 127], }; frame.body[PSDU_OFFSET..PSDU_OFFSET + body.len()].copy_from_slice(body); frame } } /// Fake IEEE 802.15.4 PHY driver implementation. pub struct Ieee802154Phy { pan: Cell, addr_short: Cell, addr_long: Cell, chan: Cell, tx_power: Cell, radio_on: Cell, tx_buf: Cell, rx_buf: RefCell, transmitted_frames: Cell>>, frames_to_be_received: RefCell>, share_ref: DriverShareRef, } // Needed for scheduling an receive upcall immediately after subscribing to it. // Without that, thread_local!(pub(crate) static DRIVER: RefCell> = const { RefCell::new(rc::Weak::new()) }); impl Ieee802154Phy { pub fn instance() -> Option> { DRIVER.with_borrow(|driver| driver.upgrade()) } pub fn new() -> Rc { let new = Self::new_with_frames_to_be_received(std::iter::empty()); DRIVER.with_borrow_mut(|inner| *inner = Rc::downgrade(&new)); new } pub fn new_with_frames_to_be_received( frames_to_be_received: impl IntoIterator, ) -> Rc { Rc::new(Self { pan: Default::default(), addr_short: Default::default(), addr_long: Default::default(), chan: Default::default(), tx_power: Default::default(), radio_on: Default::default(), tx_buf: Default::default(), rx_buf: Default::default(), transmitted_frames: Default::default(), frames_to_be_received: RefCell::new(frames_to_be_received.into_iter().collect()), share_ref: Default::default(), }) } pub fn take_transmitted_frames(&self) -> Vec> { self.transmitted_frames.take() } pub fn has_pending_rx_frames(&self) -> bool { let rx_buf = self.rx_buf.borrow(); #[allow(clippy::get_first)] let read_index = rx_buf.get(0); let write_index = rx_buf.get(1); matches!((read_index, write_index), (Some(r), Some(w)) if r != w) } pub fn radio_receive_frame(&self, frame: Frame) { self.frames_to_be_received.borrow_mut().push_back(frame); } pub fn driver_receive_pending_frames(&self) { for frame in self.frames_to_be_received.borrow_mut().drain(..) { self.driver_receive_frame(&frame.body[..frame.payload_len as usize + PSDU_OFFSET]); } } fn driver_receive_frame(&self, frame: &[u8]) { let mut rx_buf = self.rx_buf.borrow_mut(); Self::phy_driver_receive_frame(&mut rx_buf, frame); } // Code taken and adapted from capsules/extra/src/ieee802154/phy_driver.rs. fn phy_driver_receive_frame(rbuf: &mut [u8], frame: &[u8]) { let frame_len = frame.len() - PSDU_OFFSET; //////////////////////////////////////////////////////// // NOTE: context for the ring buffer and assumptions // regarding the ring buffer format and usage can be // found in the detailed comment at the top of this // file. // // Ring buffer format: // | read | write | user_frame | user_frame |...| user_frame | // | index | index | 0 | 1 | | n | // // user_frame format: // | header_len | payload_len | mic_len | 15.4 frame | // //////////////////////////////////////////////////////// const PSDU_OFFSET: usize = 2; // 2 bytes for the readwrite buffer metadata (read and // write index). const RING_BUF_METADATA_SIZE: usize = 2; /// 3 byte metadata (offset, len, mic_len) const USER_FRAME_METADATA_SIZE: usize = 3; /// 3 byte metadata + 127 byte max payload const USER_FRAME_MAX_SIZE: usize = USER_FRAME_METADATA_SIZE + MAX_MTU; // Confirm the availability of the buffer. A buffer of // len 0 is indicative of the userprocess not allocating // a readwrite buffer. We must also confirm that the // userprocess correctly formatted the buffer to be of // length 2 + n * USER_FRAME_MAX_SIZE, where n is the // number of user frames that the buffer can store. We // combine checking the buffer's non-zero length and the // case of the buffer being shorter than the // `RING_BUF_METADATA_SIZE` as an invalid buffer (e.g. // of length 1) may otherwise errantly pass the second // conditional check (due to unsigned integer // arithmetic). assert!(rbuf.len() > RING_BUF_METADATA_SIZE); assert!((rbuf.len() - RING_BUF_METADATA_SIZE) % USER_FRAME_MAX_SIZE == 0); let mut read_index = rbuf[0] as usize; let mut write_index = rbuf[1] as usize; let max_pending_rx = (rbuf.len() - RING_BUF_METADATA_SIZE) / USER_FRAME_MAX_SIZE; // Confirm user modifiable metadata is valid (i.e. // within bounds of the provided buffer). assert!(read_index < max_pending_rx && write_index < max_pending_rx); // We don't parse the received packet, so we don't know // how long all of the pieces are. let mic_len = 0; let header_len = 0; // Start in the buffer where we are going to write this // incoming packet. let offset = RING_BUF_METADATA_SIZE + (write_index * USER_FRAME_MAX_SIZE); // Copy the entire frame over to userland, preceded by // three metadata bytes: the header length, the data // length, and the MIC length. let dst_start = offset + USER_FRAME_METADATA_SIZE; let dst_end = dst_start + frame_len; let src_start = PSDU_OFFSET; let src_end = src_start + frame_len; rbuf[dst_start..dst_end].copy_from_slice(&frame[src_start..src_end]); rbuf[offset] = header_len as u8; rbuf[offset + 1] = frame_len as u8; rbuf[offset + 2] = mic_len as u8; // Prepare the ring buffer for the next write. The // current design favors newness; newly received packets // will begin to overwrite the oldest data in the event // of the buffer becoming full. The read index must // always point to the "oldest" data. If we have // overwritten the oldest data, the next oldest data is // now at the read index + 1. We must update the read // index to reflect this. write_index = (write_index + 1) % max_pending_rx; if write_index == read_index { read_index = (read_index + 1) % max_pending_rx; rbuf[0] = read_index as u8; } // Update write index metadata since we have added a // frame. rbuf[1] = write_index as u8; } pub fn trigger_rx_upcall(&self) { self.share_ref .schedule_upcall(subscribe::FRAME_RECEIVED, (0, 0, 0)) .expect("Unable to schedule upcall {}"); } } impl crate::fake::SyscallDriver for Ieee802154Phy { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(2) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_number: u32, argument0: u32, argument1: u32) -> CommandReturn { match command_number { command::EXISTS => command_return::success(), command::STATUS => { if self.radio_on.get() { command_return::success() } else { command_return::failure(ErrorCode::Off) } } command::SET_SHORT_ADDR => { self.addr_short.set(u16::try_from(argument0).unwrap()); command_return::success() } command::SET_PAN => { self.pan.set(u16::try_from(argument0).unwrap()); command_return::success() } command::SET_CHAN => { self.chan.set(u8::try_from(argument0).unwrap()); command_return::success() } command::SET_TX_PWR => { self.tx_power.set(i8::try_from(argument0 as i32).unwrap()); command_return::success() } command::COMMIT_CFG => command_return::success(), command::GET_SHORT_ADDR => command_return::success_u32(self.addr_short.get() as u32), command::GET_PAN => command_return::success_u32(self.pan.get() as u32), command::GET_CHAN => command_return::success_u32(self.chan.get() as u32), command::GET_TX_PWR => command_return::success_u32(self.tx_power.get() as i32 as u32), command::SET_LONG_ADDR => { self.addr_long .set(argument0 as u64 | (argument1 as u64) << 32); command_return::success() } command::GET_LONG_ADDR => command_return::success_u64(self.addr_long.get()), command::TURN_ON => { self.radio_on.set(true); command_return::success() } command::TURN_OFF => { self.radio_on.set(false); command_return::success() } command::TRANSMIT => { let mut transmitted_frames = self.transmitted_frames.take(); let tx_buf = self.tx_buf.take(); transmitted_frames.push(Vec::from(tx_buf.as_ref())); self.tx_buf.set(tx_buf); self.transmitted_frames.set(transmitted_frames); self.share_ref .schedule_upcall(subscribe::FRAME_TRANSMITTED, (2137, 0, 0)) .expect("Unable to schedule upcall {}"); command_return::success() } _ => command_return::failure(ErrorCode::Invalid), } } fn allow_readonly( &self, buffer_num: u32, buffer: crate::RoAllowBuffer, ) -> Result { if buffer_num == allow_ro::WRITE { Ok(self.tx_buf.replace(buffer)) } else { Err((buffer, ErrorCode::Invalid)) } } fn allow_readwrite( &self, buffer_num: u32, buffer: crate::RwAllowBuffer, ) -> Result { if buffer_num == allow_rw::READ { Ok(self.rx_buf.replace(buffer)) } else { Err((buffer, ErrorCode::Invalid)) } } } // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x30001; // Command IDs /// - `0`: Driver existence check. /// - `1`: Return radio status. Ok(())/OFF = on/off. /// - `2`: Set short address. /// - `4`: Set PAN ID. /// - `5`: Set channel. /// - `6`: Set transmission power. /// - `7`: Commit any configuration changes. /// - `8`: Get the short MAC address. /// - `10`: Get the PAN ID. /// - `11`: Get the channel. /// - `12`: Get the transmission power. /// - `27`: Transmit a frame. The frame must be stored in the write RO allow /// buffer 0. The allowed buffer must be the length of the frame. The /// frame includes the PDSU (i.e., the MAC payload) _without_ the MFR /// (i.e., CRC) bytes. /// - `28`: Set long address. /// - `29`: Get the long MAC address. /// - `30`: Turn the radio on. /// - `31`: Turn the radio off. mod command { pub const EXISTS: u32 = 0; pub const STATUS: u32 = 1; pub const SET_SHORT_ADDR: u32 = 2; pub const SET_PAN: u32 = 4; pub const SET_CHAN: u32 = 5; pub const SET_TX_PWR: u32 = 6; pub const COMMIT_CFG: u32 = 7; pub const GET_SHORT_ADDR: u32 = 8; pub const GET_PAN: u32 = 10; pub const GET_CHAN: u32 = 11; pub const GET_TX_PWR: u32 = 12; pub const TRANSMIT: u32 = 27; pub const SET_LONG_ADDR: u32 = 28; pub const GET_LONG_ADDR: u32 = 29; pub const TURN_ON: u32 = 30; pub const TURN_OFF: u32 = 31; } mod subscribe { /// Frame is received pub const FRAME_RECEIVED: u32 = 0; /// Frame is transmitted pub const FRAME_TRANSMITTED: u32 = 1; } /// Ids for read-only allow buffers mod allow_ro { /// Write buffer. Contains the frame payload to be transmitted. pub const WRITE: u32 = 0; } /// Ids for read-write allow buffers mod allow_rw { /// Read buffer. Will contain the received frame. pub const READ: u32 = 0; } ================================================ FILE: unittest/src/fake/kernel.rs ================================================ use crate::kernel_data::{with_kernel_data, DriverData, KernelData, KERNEL_DATA}; use crate::{DriverShareRef, ExpectedSyscall, SyscallLogEntry}; use std::cell::Cell; /// A fake implementation of the Tock kernel. Used with `fake::Syscalls`, which /// provides system calls that are routed to this kernel. `fake::SyscallDriver`s /// may be attached to a `fake::Kernel`, and the `fake::Kernel` will route /// system calls to the correct fake driver. /// /// Note that there can only be one `fake::Kernel` instance per thread, as /// `fake::Syscalls` uses a thread-local variable to locate the `fake::Kernel`. // Note: The kernel's data is actually stored in // crate::kernel_data::KERNEL_DATA. See the kernel_data module comment for an // explanation. pub struct Kernel { // Prevents user code from constructing a Kernel directly, in order to force // construction via new(). _private: (), } impl Kernel { /// Creates a `Kernel` for this thread and returns it. The returned `Kernel` /// should be dropped at the end of the test, before this thread creates /// another `Kernel`. // Clippy suggests we implement Default instead of having new. However, // using Default implies the newly-created instance isn't special, but it is // (as only one can exist per thread). Also, Default::default is not // #[track_caller]. #[allow(clippy::new_without_default)] #[track_caller] pub fn new() -> Kernel { let old_option = KERNEL_DATA.with(|kernel_data| { kernel_data.replace(Some(KernelData { allow_db: Default::default(), create_location: std::panic::Location::caller(), drivers: Default::default(), expected_syscalls: Default::default(), syscall_log: Vec::new(), upcall_queue: Default::default(), memory_break: core::ptr::null(), })) }); if let Some(old_kernel_data) = old_option { panic!( "New fake::Kernel created before the previous fake::Kernel \ was dropped. The previous fake::Kernel was created at {}.", old_kernel_data.create_location ); } Kernel { _private: () } } /// Adds a `fake::SyscallDriver` to this `fake::Kernel`. After the call, /// system calls with this driver's ID will be routed to the driver. // TODO: It's kind of weird to implicitly clone the RC by default. Instead, // we should probably take the Rc by value. Also, after making that change, // maybe we can take a Rc instead of using // generics? // TODO: Add a test for add_driver. pub fn add_driver(&self, driver: &std::rc::Rc) { let info = driver.info(); let driver_data = DriverData { driver: driver.clone(), num_upcalls: info.upcall_count, upcalls: std::collections::HashMap::with_capacity(info.upcall_count as usize), }; let insert_return = with_kernel_data(|kernel_data| { kernel_data .unwrap() .drivers .insert(info.driver_num, driver_data) }); assert!( insert_return.is_none(), "Duplicate driver with number {}", info.driver_num ); driver.register(DriverShareRef { driver_num: Cell::new(info.driver_num), }); } /// Adds an ExpectedSyscall to the expected syscall queue. /// /// # What is the expected syscall queue? /// /// In addition to routing system calls to drivers, `Kernel` supports /// injecting artificial system call responses. The primary use case for /// this feature is to simulate errors without having to implement error /// simulation in each `fake::SyscallDriver`. /// /// The expected syscall queue is a FIFO queue containing anticipated /// upcoming system calls. It starts empty, and as long as it is empty, the /// expected syscall functionality does nothing. When the queue is nonempty /// and a system call is made, the system call is compared with the next /// queue entry. If the system call matches, then the action defined by the /// expected syscall is taken. If the call does not match, the call panics /// (to make the unit test fail). pub fn add_expected_syscall(&self, expected_syscall: ExpectedSyscall) { with_kernel_data(|kernel_data| { kernel_data .unwrap() .expected_syscalls .push_back(expected_syscall) }); } /// Returns the system call log and empties it. pub fn take_syscall_log(&self) -> Vec { with_kernel_data(|kernel_data| std::mem::take(&mut kernel_data.unwrap().syscall_log)) } /// Returns true if the specified driver installed. pub fn is_driver_present(driver_num: u32) -> bool { with_kernel_data(|kernel_data| { kernel_data.is_some_and(|kernel| kernel.drivers.contains_key(&driver_num)) }) } /// Returns true if there are any pending upcalls. pub fn is_upcall_pending() -> bool { with_kernel_data(|kernel_data| { kernel_data.is_some_and(|kernel| !kernel.upcall_queue.is_empty()) }) } } impl Drop for Kernel { fn drop(&mut self) { KERNEL_DATA.with(|kernel_data| kernel_data.replace(None)); } } ================================================ FILE: unittest/src/fake/kernel_tests.rs ================================================ use crate::kernel_data::with_kernel_data; use crate::{fake, ExpectedSyscall, SyscallLogEntry}; #[test] fn expected_syscall_queue() { use libtock_platform::YieldNoWaitReturn::Upcall; use std::matches; use ExpectedSyscall::YieldNoWait; let kernel = fake::Kernel::new(); with_kernel_data(|kernel_data| assert!(kernel_data.unwrap().expected_syscalls.is_empty())); kernel.add_expected_syscall(YieldNoWait { override_return: None, }); kernel.add_expected_syscall(YieldNoWait { override_return: Some(Upcall), }); with_kernel_data(|kernel_data| { let expected_syscalls = &mut kernel_data.unwrap().expected_syscalls; assert!(matches!( expected_syscalls.pop_front(), Some(YieldNoWait { override_return: None }) )); assert!(matches!( expected_syscalls.pop_front(), Some(YieldNoWait { override_return: Some(Upcall) }) )); assert!(expected_syscalls.is_empty()); }); } #[test] fn syscall_log() { use SyscallLogEntry::{YieldNoWait, YieldWait}; let kernel = fake::Kernel::new(); assert_eq!(kernel.take_syscall_log(), []); with_kernel_data(|kernel_data| { let syscall_log = &mut kernel_data.unwrap().syscall_log; syscall_log.push(YieldNoWait); syscall_log.push(YieldWait); }); assert_eq!(kernel.take_syscall_log(), [YieldNoWait, YieldWait]); assert_eq!(kernel.take_syscall_log(), []); } ================================================ FILE: unittest/src/fake/key_value/mod.rs ================================================ use libtock_platform::{CommandReturn, ErrorCode}; use core::cell::{Cell, RefCell}; use std::collections::HashMap; use std::str; use crate::{DriverInfo, DriverShareRef, RoAllowBuffer, RwAllowBuffer}; pub struct KeyValue { buffer_in_key: Cell, buffer_in_val: Cell, buffer_out_val: RefCell, share_ref: DriverShareRef, database: RefCell>, } impl KeyValue { pub fn new() -> std::rc::Rc { std::rc::Rc::new(KeyValue { buffer_in_key: Default::default(), buffer_in_val: Default::default(), buffer_out_val: Default::default(), share_ref: Default::default(), database: Default::default(), }) } } impl crate::fake::SyscallDriver for KeyValue { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn allow_readonly( &self, buffer_num: u32, buffer: RoAllowBuffer, ) -> Result { match buffer_num { RO_ALLOW_KEY => Ok(self.buffer_in_key.replace(buffer)), RO_ALLOW_VAL => Ok(self.buffer_in_val.replace(buffer)), _ => Err((buffer, ErrorCode::Invalid)), } } fn allow_readwrite( &self, buffer_num: u32, buffer: RwAllowBuffer, ) -> Result { match buffer_num { RW_ALLOW_VAL => Ok(self.buffer_out_val.replace(buffer)), _ => Err((buffer, ErrorCode::Invalid)), } } fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_id { CMD_DRIVER_CHECK => crate::command_return::success(), CMD_GET => { let k = self.buffer_in_key.take(); let k_str = str::from_utf8(&k).unwrap(); let db = self.database.take(); match db.get(k_str) { Some(val) => { let cp_len = core::cmp::min(self.buffer_out_val.borrow().len(), val.len()); self.buffer_out_val.borrow_mut()[..cp_len] .copy_from_slice(&val.as_bytes()[..cp_len]); self.share_ref .schedule_upcall(SUB_CALLBACK, (0, val.len() as u32, 0)) .expect("Unable to schedule upcall {}"); } _ => { self.share_ref .schedule_upcall(SUB_CALLBACK, (ErrorCode::NoSupport as u32, 0, 0)) .expect("Unable to schedule upcall {}"); } } self.buffer_in_key.set(k); self.database.replace(db); crate::command_return::success() } CMD_SET => { let k = self.buffer_in_key.take(); let k_str = str::from_utf8(&k).unwrap(); let v = self.buffer_in_val.take(); let v_str = str::from_utf8(&v).unwrap(); let mut db = self.database.take(); db.insert(k_str.to_string(), v_str.to_string()); self.buffer_in_key.set(k); self.buffer_in_val.set(v); self.database.replace(db); self.share_ref .schedule_upcall(SUB_CALLBACK, (0, 0, 0)) .expect("Unable to schedule upcall {}"); crate::command_return::success() } CMD_ADD => { let k = self.buffer_in_key.take(); let k_str = str::from_utf8(&k).unwrap(); let v = self.buffer_in_val.take(); let v_str = str::from_utf8(&v).unwrap(); let mut db = self.database.take(); let mut found = false; if let Some(_val) = db.get(k_str) { self.share_ref .schedule_upcall(SUB_CALLBACK, (ErrorCode::NoSupport as u32, 0, 0)) .expect("Unable to schedule upcall {}"); found = true; } if !found { db.insert(k_str.to_string(), v_str.to_string()); self.share_ref .schedule_upcall(SUB_CALLBACK, (0, 0, 0)) .expect("Unable to schedule upcall {}"); } self.buffer_in_key.set(k); self.buffer_in_val.set(v); self.database.replace(db); crate::command_return::success() } CMD_UPDATE => { let k = self.buffer_in_key.take(); let k_str = str::from_utf8(&k).unwrap(); let v = self.buffer_in_val.take(); let v_str = str::from_utf8(&v).unwrap(); let mut db = self.database.take(); let mut found = false; match db.get(k_str) { Some(_val) => { found = true; } _ => { self.share_ref .schedule_upcall(SUB_CALLBACK, (ErrorCode::NoSupport as u32, 0, 0)) .expect("Unable to schedule upcall {}"); } } if found { db.insert(k_str.to_string(), v_str.to_string()); self.share_ref .schedule_upcall(SUB_CALLBACK, (0, 0, 0)) .expect("Unable to schedule upcall {}"); } self.buffer_in_key.set(k); self.buffer_in_val.set(v); self.database.replace(db); crate::command_return::success() } CMD_DELETE => { let k = self.buffer_in_key.take(); let k_str = str::from_utf8(&k).unwrap(); let mut db = self.database.take(); match db.remove(k_str) { Some(_val) => { self.share_ref .schedule_upcall(SUB_CALLBACK, (0, 0, 0)) .expect("Unable to schedule upcall {}"); } _ => { self.share_ref .schedule_upcall(SUB_CALLBACK, (ErrorCode::NoSupport as u32, 0, 0)) .expect("Unable to schedule upcall {}"); } } self.buffer_in_key.set(k); self.database.replace(db); crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x50003; // Command IDs const CMD_DRIVER_CHECK: u32 = 0; const CMD_GET: u32 = 1; const CMD_SET: u32 = 2; const CMD_DELETE: u32 = 3; const CMD_ADD: u32 = 4; const CMD_UPDATE: u32 = 5; const RO_ALLOW_KEY: u32 = 0; const RO_ALLOW_VAL: u32 = 1; const RW_ALLOW_VAL: u32 = 0; const SUB_CALLBACK: u32 = 0; ================================================ FILE: unittest/src/fake/leds/mod.rs ================================================ //! Fake implementation of the LEDs API, documented here: //! https://github.com/tock/tock/blob/master/doc/syscalls/00002_leds.md //! //! Like the real API, `Leds` controls a set of fake LEDs. It provides //! a function `get_led` used to retrieve the state of an LED. use crate::DriverInfo; use core::cell::Cell; use libtock_platform::{CommandReturn, ErrorCode}; pub struct Leds { leds: [Cell; LEDS_COUNT], } impl Leds { pub fn new() -> std::rc::Rc> { #[allow(clippy::declare_interior_mutable_const)] const OFF: Cell = Cell::new(false); std::rc::Rc::new(Leds { leds: [OFF; LEDS_COUNT], }) } pub fn get_led(&self, led: u32) -> Option { self.leds.get(led as usize).map(|led| led.get()) } } impl crate::fake::SyscallDriver for Leds { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM) } fn command(&self, command_num: u32, argument0: u32, _argument1: u32) -> CommandReturn { match command_num { EXISTS => crate::command_return::success_u32(LEDS_COUNT as u32), LED_ON => { if argument0 < LEDS_COUNT as u32 { self.leds[argument0 as usize].set(true); crate::command_return::success() } else { crate::command_return::failure(ErrorCode::Invalid) } } LED_OFF => { if argument0 < LEDS_COUNT as u32 { self.leds[argument0 as usize].set(false); crate::command_return::success() } else { crate::command_return::failure(ErrorCode::Invalid) } } LED_TOGGLE => { if argument0 < LEDS_COUNT as u32 { self.leds[argument0 as usize].set(!self.leds[argument0 as usize].get()); crate::command_return::success() } else { crate::command_return::failure(ErrorCode::Invalid) } } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Implementation details below // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x2; // Command numbers const EXISTS: u32 = 0; const LED_ON: u32 = 1; const LED_OFF: u32 = 2; const LED_TOGGLE: u32 = 3; ================================================ FILE: unittest/src/fake/leds/tests.rs ================================================ use crate::fake; use fake::leds::*; use libtock_platform::ErrorCode; // Tests the command implementation. #[test] fn command() { use fake::SyscallDriver; let leds = Leds::<10>::new(); let value = leds.command(EXISTS, 1, 2); assert_eq!(value.get_success_u32(), Some(10)); assert_eq!( leds.command(LED_ON, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!(leds.get_led(0), Some(false)); assert!(leds.command(LED_ON, 0, 0).is_success()); assert_eq!(leds.get_led(0), Some(true)); assert!(leds.command(LED_OFF, 0, 0).is_success()); assert_eq!(leds.get_led(0), Some(false)); assert!(leds.command(LED_TOGGLE, 0, 0).is_success()); assert_eq!(leds.get_led(0), Some(true)); assert!(leds.command(LED_TOGGLE, 0, 0).is_success()); assert_eq!(leds.get_led(0), Some(false)); } // Integration test that verifies Leds works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let leds = Leds::<10>::new(); kernel.add_driver(&leds); let value = fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2); assert!(value.is_success_u32()); assert_eq!(value.get_success_u32(), Some(10)); assert_eq!( fake::Syscalls::command(DRIVER_NUM, LED_ON, 11, 0).get_failure(), Some(ErrorCode::Invalid) ); assert_eq!(leds.get_led(0), Some(false)); assert!(fake::Syscalls::command(DRIVER_NUM, LED_ON, 0, 0).is_success()); assert_eq!(leds.get_led(0), Some(true)); assert!(fake::Syscalls::command(DRIVER_NUM, LED_OFF, 0, 0).is_success()); assert_eq!(leds.get_led(0), Some(false)); assert!(fake::Syscalls::command(DRIVER_NUM, LED_TOGGLE, 0, 0).is_success()); assert_eq!(leds.get_led(0), Some(true)); assert!(fake::Syscalls::command(DRIVER_NUM, LED_TOGGLE, 0, 0).is_success()); assert_eq!(leds.get_led(0), Some(false)); } ================================================ FILE: unittest/src/fake/low_level_debug/mod.rs ================================================ //! Fake implementation of the LowLevelDebug API, documented here: //! https://github.com/tock/tock/blob/master/doc/syscalls/00008_low_level_debug.md //! //! Like the real API, `LowLevelDebug` prints each message it is commanded to //! print. It also keeps a log of the messages as `Message` instances, which can //! be retrieved via `take_messages` for use in unit tests. use crate::DriverInfo; use libtock_platform::{CommandReturn, ErrorCode}; pub struct LowLevelDebug { messages: core::cell::Cell>, } impl LowLevelDebug { pub fn new() -> std::rc::Rc { std::rc::Rc::new(LowLevelDebug { messages: Default::default(), }) } /// Returns the messages that have been printed, and clears the message /// queue. pub fn take_messages(&self) -> Vec { self.messages.take() } } impl crate::fake::SyscallDriver for LowLevelDebug { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM) } fn command(&self, command_num: u32, argument0: u32, argument1: u32) -> CommandReturn { match command_num { EXISTS => {} PRINT_ALERT_CODE => self.handle_message(Message::AlertCode(argument0)), PRINT_1 => self.handle_message(Message::Print1(argument0)), PRINT_2 => self.handle_message(Message::Print2(argument0, argument1)), _ => return crate::command_return::failure(ErrorCode::NoSupport), } crate::command_return::success() } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Message { AlertCode(u32), Print1(u32), Print2(u32, u32), } impl core::fmt::Display for Message { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { match *self { Message::AlertCode(code) => { write!(f, "alert code 0x{:x} ({})", code, alert_description(code)) } Message::Print1(arg0) => write!(f, "prints 0x{arg0:x}"), Message::Print2(arg0, arg1) => write!(f, "prints 0x{arg0:x} 0x{arg1:x}"), } } } // ----------------------------------------------------------------------------- // Implementation details below // ----------------------------------------------------------------------------- #[cfg(test)] mod tests; const DRIVER_NUM: u32 = 0x8; // Command numbers const EXISTS: u32 = 0; const PRINT_ALERT_CODE: u32 = 1; const PRINT_1: u32 = 2; const PRINT_2: u32 = 3; // Predefined alert codes const PANIC: u32 = 0x01; const WRONG_LOCATION: u32 = 0x02; impl LowLevelDebug { fn handle_message(&self, message: Message) { // Format the message the same way as the real LowLevelDebug. // `libtock_unittest` doesn't support multiple processes, so we pretend // this is the first process (number 0). println!("LowLevelDebug: App 0x0 {message}"); let mut messages = self.messages.take(); messages.push(message); self.messages.set(messages); } } // Returns a description of a predefined alert code. fn alert_description(code: u32) -> &'static str { match code { PANIC => "panic", WRONG_LOCATION => "wrong location", _ => "unknown", } } ================================================ FILE: unittest/src/fake/low_level_debug/tests.rs ================================================ use crate::fake; use fake::low_level_debug::*; // Tests the command implementation. #[test] fn command() { use fake::SyscallDriver; let low_level_debug = LowLevelDebug::new(); assert!(low_level_debug.command(EXISTS, 1, 2).is_success()); assert!(low_level_debug.command(PRINT_ALERT_CODE, 3, 4).is_success()); assert_eq!(low_level_debug.take_messages(), [Message::AlertCode(3)]); assert!(low_level_debug.command(PRINT_1, 5, 6).is_success()); assert!(low_level_debug.command(PRINT_2, 7, 8).is_success()); assert_eq!( low_level_debug.take_messages(), [Message::Print1(5), Message::Print2(7, 8)] ); } // Integration test that verifies LowLevelDebug works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let low_level_debug = LowLevelDebug::new(); kernel.add_driver(&low_level_debug); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, PRINT_ALERT_CODE, 3, 4).is_success()); assert_eq!(low_level_debug.take_messages(), [Message::AlertCode(3)]); assert!(fake::Syscalls::command(DRIVER_NUM, PRINT_1, 5, 6).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, PRINT_2, 7, 8).is_success()); assert_eq!( low_level_debug.take_messages(), [Message::Print1(5), Message::Print2(7, 8)] ); } // Tests the Display implementation on Message. #[test] fn message_display() { use Message::*; assert_eq!(format!("{}", AlertCode(0x0)), "alert code 0x0 (unknown)"); assert_eq!(format!("{}", AlertCode(0x1)), "alert code 0x1 (panic)"); assert_eq!( format!("{}", AlertCode(0x2)), "alert code 0x2 (wrong location)" ); assert_eq!(format!("{}", AlertCode(0x3)), "alert code 0x3 (unknown)"); assert_eq!(format!("{}", Print1(0x31)), "prints 0x31"); assert_eq!(format!("{}", Print2(0x41, 0x59)), "prints 0x41 0x59"); } ================================================ FILE: unittest/src/fake/mod.rs ================================================ //! `fake` contains fake implementations of Tock kernel components. Fake //! components emulate the behavior of the real Tock kernel components, but in //! the unit test environment. They generally have additional testing features, //! such as error injection functionality. //! //! These components are exposed under the `fake` module because otherwise their //! names would collide with the corresponding drivers (e.g. the fake Console //! would collide with the Console driver in unit tests). Tests should generally //! `use libtock_unittest::fake` and refer to the type with the `fake::` prefix //! (e.g. `fake::Console`). mod adc; mod air_quality; mod alarm; mod ambient_light; mod buttons; mod buzzer; mod console; mod gpio; pub mod ieee802154; mod kernel; mod key_value; mod leds; mod low_level_debug; mod ninedof; mod proximity; mod screen; mod sound_pressure; mod syscall_driver; mod syscalls; mod temperature; pub use adc::Adc; pub use air_quality::AirQuality; pub use alarm::Alarm; pub use ambient_light::AmbientLight; pub use buttons::Buttons; pub use buzzer::Buzzer; pub use console::Console; pub use gpio::{Gpio, GpioMode, InterruptEdge, PullMode}; pub use ieee802154::Ieee802154Phy; pub use kernel::Kernel; pub use key_value::KeyValue; pub use leds::Leds; pub use low_level_debug::{LowLevelDebug, Message}; pub use ninedof::{NineDof, NineDofData}; pub use proximity::Proximity; pub use screen::Screen; pub use sound_pressure::SoundPressure; pub use syscall_driver::SyscallDriver; pub use syscalls::Syscalls; pub use temperature::Temperature; #[cfg(test)] mod kernel_tests; ================================================ FILE: unittest/src/fake/ninedof/mod.rs ================================================ //! Fake implementation of the NineDof API, documented here: //! //! Like the real API, `NineDof` controls a fake 9dof sensor. It provides //! a function `set_value` used to immediately call an upcall with a 9dof value read by the sensor //! and a function 'set_value_sync' used to call the upcall when the read command is received. use crate::{DriverInfo, DriverShareRef}; use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::Cell; pub struct NineDof { busy: Cell, upcall_on_command: Cell>, share_ref: DriverShareRef, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct NineDofData { pub x: i32, pub y: i32, pub z: i32, } impl NineDof { pub fn new() -> std::rc::Rc { std::rc::Rc::new(NineDof { busy: Cell::new(false), upcall_on_command: Cell::new(None), share_ref: Default::default(), }) } pub fn is_busy(&self) -> bool { self.busy.get() } pub fn set_value(&self, value: NineDofData) { if self.busy.get() { self.share_ref .schedule_upcall(0, (value.x as u32, value.y as u32, value.z as u32)) .expect("Unable to schedule upcall"); self.busy.set(false); } } pub fn set_value_sync(&self, value: NineDofData) { self.upcall_on_command.set(Some(value)); } } impl crate::fake::SyscallDriver for NineDof { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_id { EXISTS => crate::command_return::success(), READ_ACCELEROMETER => { if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_command.take() { self.set_value(val); } crate::command_return::success() } READ_MAGNETOMETER => { if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_command.take() { self.set_value(val); } crate::command_return::success() } READ_GYRO => { if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_command.take() { self.set_value(val); } crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60004; // Command IDs const EXISTS: u32 = 0; const READ_ACCELEROMETER: u32 = 1; const READ_MAGNETOMETER: u32 = 100; const READ_GYRO: u32 = 200; ================================================ FILE: unittest/src/fake/ninedof/tests.rs ================================================ use crate::fake::{self, SyscallDriver}; use fake::ninedof::*; use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; //Test the command implementation #[test] fn command() { let ninedof = NineDof::new(); assert!(ninedof.command(EXISTS, 1, 2).is_success()); assert!(ninedof.command(READ_ACCELEROMETER, 0, 0).is_success()); assert_eq!( ninedof.command(READ_ACCELEROMETER, 0, 0).get_failure(), Some(ErrorCode::Busy) ); let payload: NineDofData = NineDofData { x: 1, y: 2, z: 3 }; ninedof.set_value(payload); assert!(ninedof.command(READ_ACCELEROMETER, 0, 1).is_success()); ninedof.set_value(payload); ninedof.set_value_sync(payload); assert!(ninedof.command(READ_ACCELEROMETER, 0, 1).is_success()); assert!(ninedof.command(READ_ACCELEROMETER, 0, 1).is_success()); } // Integration test that verifies NineDof works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let ninedof = NineDof::new(); kernel.add_driver(&ninedof); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, READ_ACCELEROMETER, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, READ_ACCELEROMETER, 0, 0).get_failure(), Some(ErrorCode::Busy) ); let payload: NineDofData = NineDofData { x: 1, y: 2, z: 3 }; ninedof.set_value(payload); assert!(fake::Syscalls::command(DRIVER_NUM, READ_ACCELEROMETER, 0, 1).is_success()); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); ninedof.set_value(payload); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((1, 2, 3))); ninedof.set_value(payload); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ_ACCELEROMETER, 0, 1).is_success()); ninedof.set_value(payload); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((1, 2, 3))); }); } ================================================ FILE: unittest/src/fake/proximity/mod.rs ================================================ //! Fake implementation of the Proximity API. //! //! Like the real API, `Proximity` controls a fake proximity sensor. //! It provides a function `set_value` used to immediately simulate a sensor reading, //! and a function `set_value_sync` used to simulate a sensor reading when one //! of the read commands is received. use std::cell::Cell; use crate::{DriverInfo, DriverShareRef}; use libtock_platform::{CommandReturn, ErrorCode}; #[derive(Clone, Copy, PartialEq, Eq)] pub enum ProximityCommand { ReadProximity = 1, ReadProximityOnInterrupt = 2, NoCommand = 3, } #[derive(Default, Clone, Copy)] pub struct Thresholds { lower: u8, upper: u8, } // The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when a read command is received, // or None otherwise. It was needed for testing `read_sync` library functions which simulates a synchronous sensor read, // because it was impossible to schedule an upcall during the `synchronous` read in other ways. pub struct Proximity { current_command: Cell, thresholds: Cell, upcall_on_command: Cell>, share_ref: DriverShareRef, } impl Proximity { pub fn new() -> std::rc::Rc { std::rc::Rc::new(Proximity { current_command: Cell::new(ProximityCommand::NoCommand), thresholds: Cell::new(Thresholds { lower: 0, upper: 0 }), upcall_on_command: Cell::new(None), share_ref: Default::default(), }) } pub fn set_value(&self, value: u8) { //should not schedule an upcall if no reading was initiated or interrupt conditions are not true match self.current_command.get() { ProximityCommand::NoCommand => return, ProximityCommand::ReadProximityOnInterrupt => { if value >= self.thresholds.get().lower && value <= self.thresholds.get().upper { return; } } ProximityCommand::ReadProximity => {} } self.share_ref .schedule_upcall(0, (value as u32, 0, 0)) .expect("Unable to schedule upcall"); self.current_command.set(ProximityCommand::NoCommand); self.upcall_on_command.set(None); } pub fn set_value_sync(&self, value: u8) { self.upcall_on_command.set(Some(value)); } } impl crate::fake::SyscallDriver for Proximity { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_id: u32, argument0: u32, argument1: u32) -> CommandReturn { match command_id { EXISTS => crate::command_return::success(), READ => { if self.current_command.get() != ProximityCommand::NoCommand { return crate::command_return::failure(ErrorCode::Busy); } self.current_command.set(ProximityCommand::ReadProximity); if let Some(val) = self.upcall_on_command.get() { self.set_value(val); } crate::command_return::success() } READ_ON_INT => { if self.current_command.get() != ProximityCommand::NoCommand { return crate::command_return::failure(ErrorCode::Busy); } self.current_command .set(ProximityCommand::ReadProximityOnInterrupt); self.thresholds.set(Thresholds { lower: argument0 as u8, upper: argument1 as u8, }); if let Some(val) = self.upcall_on_command.get() { self.set_value(val); } crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60005; // Command IDs const EXISTS: u32 = 0; const READ: u32 = 1; const READ_ON_INT: u32 = 2; ================================================ FILE: unittest/src/fake/proximity/tests.rs ================================================ use crate::fake::{self, SyscallDriver}; use fake::proximity::*; use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; //Test the command implementation #[test] fn command() { let proximity = Proximity::new(); assert!(proximity.command(EXISTS, 0, 0).is_success()); assert!(proximity.command(READ, 0, 0).is_success()); assert_eq!( proximity.command(READ, 0, 0).get_failure(), Some(ErrorCode::Busy) ); proximity.set_value(0); assert!(proximity.command(READ_ON_INT, 10, 20).is_success()); //should still be busy, value does not meet conditions proximity.set_value(15); assert_eq!( proximity.command(READ, 0, 0).get_failure(), Some(ErrorCode::Busy) ); proximity.set_value(50); assert!(proximity.command(READ, 0, 0).is_success()); proximity.set_value(50); proximity.set_value_sync(10); assert!(proximity.command(READ, 0, 0).is_success()); //upcall was called after command, should not be busy assert!(proximity.command(READ, 0, 0).is_success()); } // Integration test that verifies Proximity works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let proximity = Proximity::new(); kernel.add_driver(&proximity); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 0, 0).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, READ, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, READ, 0, 0).get_failure(), Some(ErrorCode::Busy) ); proximity.set_value(100); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); //should not call an upcall as no command was given proximity.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ, 0, 0).is_success()); //now it should call an upcall proximity.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); assert!(fake::Syscalls::command(DRIVER_NUM, READ_ON_INT, 50, 150).is_success()); proximity.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); proximity.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); proximity.set_value_sync(100); assert!(fake::Syscalls::command(DRIVER_NUM, READ, 0, 0).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }) } ================================================ FILE: unittest/src/fake/screen/mod.rs ================================================ use crate::{command_return, DriverInfo, DriverShareRef, RoAllowBuffer}; use core::cell::Cell; use libtock_platform::{CommandReturn, ErrorCode}; pub struct Screen { screen_setup: Option, // Optional screen setup state resolution_modes: Option, // Number of supported resolution modes invert: Cell, // Current invert state (true = inverted) screen_resolution_width_height: [Option<(u16, u16)>; 3], // Predefined resolutions resolution_width_height: [Cell; 2], // Current resolution (width, height) pixel_modes: Option, // Number of pixel formats supported screen_pixel_format: [u16; 2], // Predefined pixel formats pixel_format: Cell, // Currently selected pixel format brightness: Cell, // Current brightness level rotation: Cell, // Current screen rotation write_frame: [Cell; 2], // Coordinates for the write frame power: Cell, // Power state share_ref: DriverShareRef, // Handle for kernel-user communication write_buffer: Cell>, // Optional buffer for write operations messages: Cell>, } impl Screen { pub fn new() -> std::rc::Rc { #[allow(clippy::declare_interior_mutable_const)] const VALUE_U16: Cell = Cell::new(0); #[allow(clippy::declare_interior_mutable_const)] const VALUE_U32: Cell = Cell::new(0); std::rc::Rc::new(Screen { screen_setup: Some(3), screen_pixel_format: [332, 565], // Example pixel formats screen_resolution_width_height: [ Some((1920, 1080)), Some((2560, 1440)), Some((1280, 720)), ], pixel_format: VALUE_U32, resolution_modes: Some(2), resolution_width_height: [VALUE_U16, VALUE_U16], invert: Cell::new(false), brightness: VALUE_U16, pixel_modes: Some(5), rotation: VALUE_U16, write_frame: [VALUE_U16, VALUE_U16], power: VALUE_U16, share_ref: Default::default(), write_buffer: Cell::new(None), messages: Default::default(), }) } pub fn take_bytes(&self) -> Vec { self.messages.take() } // Checks if the buffer size is compatible with the pixel format fn is_buffer_length_valid(&self, buffer_len: usize) -> bool { let bytes_per_pixel = match self.pixel_format.get() { 1 => 1, // Monochrome 2 => 2, // RGB_565 3 => 3, // RGB_888 4 => 4, // ARGB_8888 _ => return false, // Unknown/unsupported format }; buffer_len % bytes_per_pixel == 0 } // Simulates writing to the screen fn write(&self, buffer: &[u8]) -> Result<(), ErrorCode> { if !self.is_buffer_length_valid(buffer.len()) { return Err(ErrorCode::Invalid); } self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall"); Ok(()) } // Simulates filling the screen with a color fn fill(&self, _color: u16) -> Result<(), ErrorCode> { self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall"); Ok(()) } } impl crate::fake::SyscallDriver for Screen { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(3) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn allow_readonly( &self, buffer_num: u32, buffer: RoAllowBuffer, ) -> Result { if buffer_num == WRITE_BUFFER_ID { let old_buffer = self.write_buffer.replace(Some(buffer)); Ok(old_buffer.unwrap_or_default()) } else { Err((buffer, ErrorCode::Invalid)) } } fn command(&self, command_num: u32, argument0: u32, argument1: u32) -> CommandReturn { match command_num { EXISTS => command_return::success(), SCREEN_SETUP => command_return::success_u32(self.screen_setup.unwrap() as u32), SET_POWER => { self.power.set(1); command_return::success() } GET_POWER => command_return::success_u32(self.power.get() as u32), SET_BRIGHTNESS => { self.brightness.set(argument0 as u16); self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); crate::command_return::success() } GET_BRIGHTNESS => crate::command_return::success_u32(self.brightness.get() as u32), SET_INVERT_ON => { self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); self.invert.set(argument0 != 0); crate::command_return::success() } SET_INVERT_OFF => { self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); self.invert.set(argument0 != 0); command_return::success() } SET_INVERT => { self.invert.set(argument0 != 0); command_return::success() } GET_INVERT => command_return::success_u32(self.invert.get() as u32), GET_RESOLUTION_MODES_COUNT => { if Option::is_some(&self.screen_setup) { crate::command_return::success_u32(self.resolution_modes.unwrap() as u32) } else { command_return::failure(ErrorCode::NoSupport) } } GET_RESOLUTION_WIDTH_HEIGHT => { if argument0 < self.screen_resolution_width_height.len() as u32 { if Option::is_some(&self.screen_setup) { self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); crate::command_return::success_2_u32( self.screen_resolution_width_height[argument0 as usize] .unwrap() .0 as u32, self.screen_resolution_width_height[argument0 as usize] .unwrap() .1 as u32, ) } else { command_return::failure(ErrorCode::NoSupport) } } else { command_return::failure(ErrorCode::Invalid) } } PIXEL_MODES_COUNT => { if self.screen_setup.is_some() { command_return::success_u32(self.pixel_modes.unwrap() as u32) } else { command_return::failure(ErrorCode::NoSupport) } } PIXEL_FORMAT => { if argument0 < self.screen_pixel_format.len() as u32 { if Option::is_some(&self.screen_setup) { self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); crate::command_return::success_u32( self.screen_pixel_format[argument0 as usize] as u32, ) } else { crate::command_return::failure(ErrorCode::NoSupport) } } else { command_return::failure(ErrorCode::Invalid) } } GET_ROTATION => { self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); crate::command_return::success_u32(self.rotation.get() as u32) } SET_ROTATION => { if argument0 > 359 { command_return::failure(ErrorCode::Invalid) } else { self.rotation.set(argument0 as u16); self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); command_return::success() } } GET_RESOLUTION => command_return::success_2_u32( self.resolution_width_height[0].get() as u32, self.resolution_width_height[1].get() as u32, ), SET_RESOLUTION => { self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); self.resolution_width_height[0].set(argument0 as u16); self.resolution_width_height[1].set(argument1 as u16); command_return::success() } GET_PIXEL_FORMAT => command_return::success_u32(self.pixel_format.get()), SET_PIXEL_FORMAT => { if argument0 < self.pixel_modes.unwrap() as u32 { self.pixel_format.set(argument0); self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); command_return::success() } else { command_return::failure(ErrorCode::Invalid) } } SET_WRITE_FRAME => { self.share_ref .schedule_upcall(0, (0, 0, 0)) .expect("Unable to schedule upcall {}"); self.write_frame[0].set(argument0 as u16); self.write_frame[1].set(argument1 as u16); command_return::success() } GET_WRITE_FRAME => command_return::success_2_u32( self.write_frame[0].get() as u32, self.write_frame[1].get() as u32, ), WRITE => { let buffer_len = argument0 as usize; let buffer = self .write_buffer .take() .expect("No buffer provided for WRITE command"); if buffer.len() != buffer_len || !self.is_buffer_length_valid(buffer_len) { return command_return::failure(ErrorCode::Invalid); } match self.write(buffer.as_ref()) { Ok(()) => command_return::success(), Err(e) => command_return::failure(e), } } FILL => { let color = argument0 as u16; match self.fill(color) { Ok(()) => command_return::success(), Err(e) => command_return::failure(e), } } _ => command_return::failure(ErrorCode::NoSupport), } } } // ----------------------------------------------------------------------------- // Implementation details below // ----------------------------------------------------------------------------- #[cfg(test)] mod tests; const DRIVER_NUM: u32 = 0x90001; const WRITE_BUFFER_ID: u32 = 0; // Buffer ID for write operations // Command IDs #[allow(unused)] pub const EXISTS: u32 = 0; pub const SCREEN_SETUP: u32 = 1; pub const SET_POWER: u32 = 2; pub const SET_BRIGHTNESS: u32 = 3; pub const SET_INVERT_ON: u32 = 4; pub const SET_INVERT_OFF: u32 = 5; pub const SET_INVERT: u32 = 6; pub const GET_RESOLUTION_MODES_COUNT: u32 = 11; pub const GET_RESOLUTION_WIDTH_HEIGHT: u32 = 12; pub const PIXEL_MODES_COUNT: u32 = 13; pub const PIXEL_FORMAT: u32 = 14; pub const GET_ROTATION: u32 = 21; pub const SET_ROTATION: u32 = 22; pub const GET_RESOLUTION: u32 = 23; pub const SET_RESOLUTION: u32 = 24; pub const GET_PIXEL_FORMAT: u32 = 25; pub const SET_PIXEL_FORMAT: u32 = 26; pub const SET_WRITE_FRAME: u32 = 100; pub const WRITE: u32 = 200; pub const FILL: u32 = 300; pub const GET_POWER: u32 = 400; pub const GET_BRIGHTNESS: u32 = 401; pub const GET_INVERT: u32 = 402; pub const GET_WRITE_FRAME: u32 = 403; ================================================ FILE: unittest/src/fake/screen/tests.rs ================================================ use crate::fake; use fake::screen::*; use fake::SyscallDriver; use libtock_platform::share; use libtock_platform::CommandReturn; use libtock_platform::DefaultConfig; use libtock_platform::Syscalls; // Tests the command implementation. #[test] fn command() { let screen = fake::screen::Screen::new(); // Check if the driver exists let value = screen.command(EXISTS, 0, 0); assert!(CommandReturn::is_success(&value)); // Verify screen setup command assert_eq!( screen.command(SCREEN_SETUP, 0, 0).get_success_u32(), Some(3) ); // Test power on/off functionality assert!(screen.command(SET_POWER, 0, 0).is_success()); assert_eq!(screen.command(GET_POWER, 0, 0).get_success_u32(), Some(1)); // Test brightness control assert!(screen.command(SET_BRIGHTNESS, 50, 0).is_success()); assert_eq!( screen.command(GET_BRIGHTNESS, 0, 0).get_success_u32(), Some(50) ); // Pixel mode and format test assert_eq!( screen.command(PIXEL_MODES_COUNT, 0, 0).get_success_u32(), Some(5) ); assert_eq!( screen.command(PIXEL_FORMAT, 0, 0).get_success_u32(), Some(332) ); // Set and get rotation assert!(screen.command(SET_ROTATION, 90, 0).is_success()); assert_eq!( screen.command(GET_ROTATION, 0, 0).get_success_u32(), Some(90) ); // Set and get resolution assert!(screen.command(SET_RESOLUTION, 1280, 720).is_success()); assert_eq!( screen.command(GET_RESOLUTION, 0, 0).get_success_2_u32(), Some((1280, 720)) ); // Test invert mode toggle assert!(screen.command(SET_INVERT, 1, 0).is_success()); assert_eq!(screen.command(GET_INVERT, 0, 0).get_success_u32(), Some(1)); // Set and get write frame area assert!(screen.command(SET_WRITE_FRAME, 360, 720).is_success()); assert_eq!( screen.command(GET_WRITE_FRAME, 0, 0).get_success_2_u32(), Some((360, 720)) ); // Set and validate pixel format assert!(screen.command(SET_PIXEL_FORMAT, 1, 0).is_success()); // Ensure that an invalid pixel format returns an error. assert!(screen.command(SET_PIXEL_FORMAT, 99999, 0).is_failure()); // Kernel setup for screen and buffer simulation let kernel = fake::Kernel::new(); kernel.add_driver(&screen); let buf = [0; 4]; // Share buffer with the driver and test WRITE command share::scope(|allow_ro| { fake::Syscalls::allow_ro::< DefaultConfig, { fake::screen::DRIVER_NUM }, { fake::screen::WRITE_BUFFER_ID }, >(allow_ro, &buf) .unwrap(); // Execute write operation assert!( fake::Syscalls::command(fake::screen::DRIVER_NUM, fake::screen::WRITE, 4, 0) .is_success() ); }); } #[test] fn kernel_integration() { use fake::Kernel; let kernel = Kernel::new(); let screen = Screen::new(); kernel.add_driver(&screen); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, SCREEN_SETUP, 0, 0).get_success_u32(), Some(3) ); assert!(fake::Syscalls::command(DRIVER_NUM, SET_POWER, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GET_POWER, 0, 0).get_success_u32(), Some(1) ); assert!(fake::Syscalls::command(DRIVER_NUM, SET_BRIGHTNESS, 50, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GET_BRIGHTNESS, 0, 0).get_success_u32(), Some(50) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, PIXEL_MODES_COUNT, 0, 0).get_success_u32(), Some(5) ); assert_eq!( fake::Syscalls::command(DRIVER_NUM, PIXEL_FORMAT, 0, 0).get_success_u32(), Some(332) ); assert!(fake::Syscalls::command(DRIVER_NUM, SET_ROTATION, 90, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GET_ROTATION, 0, 0).get_success_u32(), Some(90) ); assert!(fake::Syscalls::command(DRIVER_NUM, SET_RESOLUTION, 1280, 720).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GET_RESOLUTION, 0, 0).get_success_2_u32(), Some((1280, 720)) ); assert!(fake::Syscalls::command(DRIVER_NUM, SET_INVERT, 1, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GET_INVERT, 0, 0).get_success_u32(), Some(1) ); assert!(fake::Syscalls::command(DRIVER_NUM, SET_WRITE_FRAME, 360, 720).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, GET_WRITE_FRAME, 0, 0).get_success_2_u32(), Some((360, 720)) ); } ================================================ FILE: unittest/src/fake/sound_pressure/mod.rs ================================================ //! Fake implementation of the SoundPressure API, documented here: //! //! Like the real API, `SoundPressure` controls a fake sound pressure sensor. It provides //! a function `set_value` used to immediately call an upcall with a sound pressure value read by the sensor //! and a function 'set_value_sync' used to call the upcall when the read command is received. use crate::{DriverInfo, DriverShareRef}; use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::Cell; // The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when read command is received, // or None otherwise. It was needed for testing `read_sync` library function which simulates a synchronous sound pressure read, // because it was impossible to schedule an upcall during the `synchronous` read in other ways. pub struct SoundPressure { busy: Cell, upcall_on_command: Cell>, share_ref: DriverShareRef, } impl SoundPressure { pub fn new() -> std::rc::Rc { std::rc::Rc::new(SoundPressure { busy: Cell::new(false), upcall_on_command: Cell::new(None), share_ref: Default::default(), }) } pub fn is_busy(&self) -> bool { self.busy.get() } pub fn set_value(&self, value: u8) { if self.busy.get() { self.share_ref .schedule_upcall(0, (value as u32, 0, 0)) .expect("Unable to schedule upcall"); self.busy.set(false); } } pub fn set_value_sync(&self, value: u8) { self.upcall_on_command.set(Some(value)); } } impl crate::fake::SyscallDriver for SoundPressure { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_id { EXISTS => crate::command_return::success(), READ_PRESSURE => { if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_command.take() { self.set_value(val); } crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60006; // Command IDs const EXISTS: u32 = 0; const READ_PRESSURE: u32 = 1; ================================================ FILE: unittest/src/fake/sound_pressure/tests.rs ================================================ use crate::fake::{self, SyscallDriver}; use fake::sound_pressure::*; use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; //Test the command implementation #[test] fn command() { let sound_pressure = SoundPressure::new(); assert!(sound_pressure.command(EXISTS, 1, 2).is_success()); assert!(sound_pressure.command(READ_PRESSURE, 0, 0).is_success()); assert_eq!( sound_pressure.command(READ_PRESSURE, 0, 0).get_failure(), Some(ErrorCode::Busy) ); sound_pressure.set_value(100); assert!(sound_pressure.command(READ_PRESSURE, 0, 1).is_success()); sound_pressure.set_value(100); sound_pressure.set_value_sync(100); assert!(sound_pressure.command(READ_PRESSURE, 0, 1).is_success()); assert!(sound_pressure.command(READ_PRESSURE, 0, 1).is_success()); } // Integration test that verifies SoundPressure works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let sound_pressure = SoundPressure::new(); kernel.add_driver(&sound_pressure); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, READ_PRESSURE, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, READ_PRESSURE, 0, 0).get_failure(), Some(ErrorCode::Busy) ); sound_pressure.set_value(100); assert!(fake::Syscalls::command(DRIVER_NUM, READ_PRESSURE, 0, 1).is_success()); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); sound_pressure.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); sound_pressure.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ_PRESSURE, 0, 1).is_success()); sound_pressure.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((200,))); }); } ================================================ FILE: unittest/src/fake/syscall_driver.rs ================================================ use crate::{DriverInfo, DriverShareRef, RoAllowBuffer, RwAllowBuffer}; use libtock_platform::{CommandReturn, ErrorCode}; /// The `fake::SyscallDriver` trait is implemented by fake versions of Tock's /// kernel APIs. It is used by `fake::Kernel` to route system calls to the fake /// kernel APIs. pub trait SyscallDriver: 'static { // ------------------------------------------------------------------------- // Functions called by `fake::Kernel` during driver registration. // ------------------------------------------------------------------------- /// Returns information about this driver, including its driver number. fn info(&self) -> DriverInfo; /// Called by `fake::Kernel` to link this driver to the `fake::Kernel`. /// Passes a reference to data shared with the kernel (e.g. registered /// upcalls). fn register(&self, share_ref: DriverShareRef) { let _ = share_ref; // Silence the unused variable warning. } // ------------------------------------------------------------------------- // Command // ------------------------------------------------------------------------- /// Process a Command system call. Fake drivers should use the methods in /// `libtock_unittest::command_return` to construct the return value. fn command(&self, command_id: u32, argument0: u32, argument1: u32) -> CommandReturn; // ------------------------------------------------------------------------- // Allow // ------------------------------------------------------------------------- /// Process a Read-Only Allow call. Because not all `SyscallDriver` /// implementations need to support Read-Only Allow, a default /// implementation is provided that rejects all Read-Only Allow calls. fn allow_readonly( &self, buffer_num: u32, buffer: RoAllowBuffer, ) -> Result { let _ = buffer_num; // Silences the unused variable warning. Err((buffer, ErrorCode::NoSupport)) } /// Process a Read-Write Allow call. Because not all SyscallDriver /// implementations need to support Read-Write Allow, a default /// implementation is provided that rejects all Read-Write Allow calls. fn allow_readwrite( &self, buffer_num: u32, buffer: RwAllowBuffer, ) -> Result { let _ = buffer_num; // Silences the unused variable warning. Err((buffer, ErrorCode::NoSupport)) } } ================================================ FILE: unittest/src/fake/syscalls/allow_ro_impl.rs ================================================ use crate::kernel_data::with_kernel_data; use crate::{ExpectedSyscall, SyscallLogEntry}; use libtock_platform::{return_variant, ErrorCode, Register}; use std::convert::TryInto; pub(super) unsafe fn allow_ro( driver_num: Register, buffer_num: Register, address: Register, len: Register, ) -> [Register; 4] { let driver_num = driver_num.try_into().expect("Too large driver number"); let buffer_num = buffer_num.try_into().expect("Too large buffer number"); let result = with_kernel_data(|option_kernel_data| { let kernel_data = option_kernel_data.expect("Read-Only Allow called but no fake::Kernel exists"); kernel_data.syscall_log.push(SyscallLogEntry::AllowRo { driver_num, buffer_num, len: len.into(), }); // Check for an expected syscall entry. Returns an error from the lambda // if this syscall was expected and return_error was specified. Panics // if a different syscall was expected. match kernel_data.expected_syscalls.pop_front() { None => {} Some(ExpectedSyscall::AllowRo { driver_num: expected_driver_num, buffer_num: expected_buffer_num, return_error, }) => { assert_eq!( driver_num, expected_driver_num, "expected different driver_num" ); assert_eq!( buffer_num, expected_buffer_num, "expected different buffer_num" ); if let Some(error_code) = return_error { return Err(error_code); } } Some(expected_syscall) => expected_syscall.panic_wrong_call("Read-Only Allow"), }; let driver = match kernel_data.drivers.get(&driver_num) { None => return Err(ErrorCode::NoDevice), Some(driver_data) => driver_data.driver.clone(), }; // Safety: RawSyscall requires the caller to specify address and len as // required by TRD 104. That trivially satisfies the precondition of // insert_ro_buffer, which also requires address and len to follow TRD // 104. let buffer = unsafe { kernel_data.allow_db.insert_ro_buffer(address, len) } .expect("Read-Only Allow called with a buffer that overlaps an already-Allowed buffer"); Ok((driver, buffer)) }); let (driver, buffer) = match result { Ok((driver, buffer)) => (driver, buffer), Err(error_code) => { let r0: u32 = return_variant::FAILURE_2_U32.into(); let r1: u32 = error_code as u32; return [r0.into(), r1.into(), address, len]; } }; let (error_code, buffer_out) = match driver.allow_readonly(buffer_num, buffer) { Ok(buffer_out) => (None, buffer_out), Err((buffer_out, error_code)) => (Some(error_code), buffer_out), }; let (address_out, len_out) = with_kernel_data(|option_kernel_data| { let kernel_data = option_kernel_data .expect("fake::Kernel dropped during fake::SyscallDriver::allow_readonly"); kernel_data.allow_db.remove_ro_buffer(buffer_out) }); match error_code { None => { let r0: u32 = return_variant::SUCCESS_2_U32.into(); // The value of r3 isn't specified in TRD 104, but in practice the // kernel won't change it. This mimics that behavior, for lack of a // better option. [r0.into(), address_out, len_out, len] } Some(error_code) => { let r0: u32 = return_variant::FAILURE_2_U32.into(); let r1: u32 = error_code as u32; [r0.into(), r1.into(), address_out, len_out] } } } ================================================ FILE: unittest/src/fake/syscalls/allow_ro_impl_tests.rs ================================================ use crate::{fake, ExpectedSyscall, SyscallLogEntry}; use fake::syscalls::allow_ro_impl::*; use libtock_platform::{return_variant, ErrorCode}; use std::convert::TryInto; use std::panic::catch_unwind; // TODO: Add a TestDriver, and add tests that use a driver: // 1. A test that passes buffers to the driver and retrieves them. // 2. A test with a driver that doesn't swap buffers (i.e. one that maintains a // longer list of buffers). // 3. Fuzz tests // 4. Test the driver error handling code. // Tests calls that do not match the expected system call. #[test] fn expected_wrong() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: 1, command_id: 2, argument0: 3, argument1: 4, override_return: None, }); assert!(catch_unwind(|| unsafe { allow_ro(1u32.into(), 2u32.into(), 0u32.into(), 0u32.into()) }) .expect_err("failed to catch wrong syscall class") .downcast_ref::() .expect("wrong panic payload type") .contains("but Read-Only Allow was called instead")); kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: 1, buffer_num: 2, return_error: None, }); assert!(catch_unwind(|| unsafe { allow_ro(7u32.into(), 2u32.into(), 0u32.into(), 0u32.into()) }) .expect_err("failed to catch wrong driver number") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different driver_num")); kernel.add_expected_syscall(ExpectedSyscall::AllowRo { driver_num: 1, buffer_num: 2, return_error: None, }); assert!(catch_unwind(|| unsafe { allow_ro(1u32.into(), 7u32.into(), 0u32.into(), 0u32.into()) }) .expect_err("failed to catch wrong buffer number") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different buffer_num")); } #[test] fn no_driver() { let _kernel = fake::Kernel::new(); let [r0, r1, r2, r3] = unsafe { allow_ro(7u32.into(), 1u32.into(), 0u32.into(), 0u32.into()) }; assert_eq!( r0.try_into(), Ok(Into::::into(return_variant::FAILURE_2_U32)) ); assert_eq!(r1.try_into(), Ok(ErrorCode::NoDevice as u32)); assert_eq!(r2.try_into(), Ok(0u32)); assert_eq!(r3.try_into(), Ok(0u32)); } #[test] fn no_kernel() { let result = catch_unwind(|| unsafe { allow_ro(1u32.into(), 1u32.into(), 0u32.into(), 0u32.into()) }); assert!(result .expect_err("failed to catch missing kernel") .downcast_ref::() .expect("wrong panic payload type") .contains("no fake::Kernel exists")); } #[test] fn syscall_log() { let kernel = fake::Kernel::new(); // We want to pass a buffer of nonzero length to verify the length is logged // correctly. let buffer = [0; 3]; unsafe { allow_ro( 1u32.into(), 2u32.into(), buffer.as_ptr().into(), buffer.len().into(), ); } assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRo { driver_num: 1, buffer_num: 2, len: 3, }] ); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_buffer_number() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| unsafe { allow_ro( 1u32.into(), (u32::MAX as usize + 1).into(), 0u32.into(), 0u32.into(), ) }); assert!(result .expect_err("failed to catch too-large buffer number") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large buffer number")); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_driver_number() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| unsafe { allow_ro( (u32::MAX as usize + 1).into(), 1u32.into(), 0u32.into(), 0u32.into(), ) }); assert!(result .expect_err("failed to catch too-large driver number") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large driver number")); } ================================================ FILE: unittest/src/fake/syscalls/allow_rw_impl.rs ================================================ use crate::kernel_data::with_kernel_data; use crate::{ExpectedSyscall, SyscallLogEntry}; use libtock_platform::{return_variant, ErrorCode, Register}; use std::convert::TryInto; pub(super) unsafe fn allow_rw( driver_num: Register, buffer_num: Register, address: Register, len: Register, ) -> [Register; 4] { let driver_num = driver_num.try_into().expect("Too large driver number"); let buffer_num = buffer_num.try_into().expect("Too large buffer number"); let result = with_kernel_data(|option_kernel_data| { let kernel_data = option_kernel_data.expect("Read-Write Allow called but no fake::Kernel exists"); kernel_data.syscall_log.push(SyscallLogEntry::AllowRw { driver_num, buffer_num, len: len.into(), }); // Check for an expected syscall entry. Returns an error from the lambda // if this syscall was expected and return_error was specified. Panics // if a different syscall was expected. match kernel_data.expected_syscalls.pop_front() { None => {} Some(ExpectedSyscall::AllowRw { driver_num: expected_driver_num, buffer_num: expected_buffer_num, return_error, }) => { assert_eq!( driver_num, expected_driver_num, "expected different driver_num" ); assert_eq!( buffer_num, expected_buffer_num, "expected different buffer_num" ); if let Some(error_code) = return_error { return Err(error_code); } } Some(expected_syscall) => expected_syscall.panic_wrong_call("Read-Write Allow"), }; let driver = match kernel_data.drivers.get(&driver_num) { None => return Err(ErrorCode::NoDevice), Some(driver_data) => driver_data.driver.clone(), }; // Safety: RawSyscall requires the caller to specify address and len as // required by TRD 104. That trivially satisfies the precondition of // insert_rw_buffer, which also requires address and len to follow TRD // 104. let buffer = unsafe { kernel_data.allow_db.insert_rw_buffer(address, len) }.expect( "Read-Write Allow called with a buffer that overlaps an already-Allowed buffer", ); Ok((driver, buffer)) }); let (driver, buffer) = match result { Ok((driver, buffer)) => (driver, buffer), Err(error_code) => { let r0: u32 = return_variant::FAILURE_2_U32.into(); let r1: u32 = error_code as u32; return [r0.into(), r1.into(), address, len]; } }; let (error_code, buffer_out) = match driver.allow_readwrite(buffer_num, buffer) { Ok(buffer_out) => (None, buffer_out), Err((buffer_out, error_code)) => (Some(error_code), buffer_out), }; let (address_out, len_out) = with_kernel_data(|option_kernel_data| { let kernel_data = option_kernel_data .expect("fake::Kernel dropped during fake::SyscallDriver::allow_readwrite"); kernel_data.allow_db.remove_rw_buffer(buffer_out) }); match error_code { None => { let r0: u32 = return_variant::SUCCESS_2_U32.into(); // The value of r3 isn't specified in TRD 104, but in practice the // kernel won't change it. This mimics that behavior, for lack of a // better option. [r0.into(), address_out, len_out, len] } Some(error_code) => { let r0: u32 = return_variant::FAILURE_2_U32.into(); let r1: u32 = error_code as u32; [r0.into(), r1.into(), address_out, len_out] } } } ================================================ FILE: unittest/src/fake/syscalls/allow_rw_impl_tests.rs ================================================ use crate::{fake, ExpectedSyscall, SyscallLogEntry}; use fake::syscalls::allow_rw_impl::*; use libtock_platform::{return_variant, ErrorCode}; use std::convert::TryInto; use std::panic::catch_unwind; // TODO: Add a TestDriver, and add tests that use a driver: // 1. A test that passes buffers to the driver and retrieves them. // 2. A test with a driver that doesn't swap buffers (i.e. one that maintains a // longer list of buffers). // 3. Fuzz tests // 4. Test the driver error handling code. // Tests calls that do not match the expected system call. #[test] fn expected_wrong() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: 1, command_id: 2, argument0: 3, argument1: 4, override_return: None, }); assert!(catch_unwind(|| unsafe { allow_rw(1u32.into(), 2u32.into(), 0u32.into(), 0u32.into()) }) .expect_err("failed to catch wrong syscall class") .downcast_ref::() .expect("wrong panic payload type") .contains("but Read-Write Allow was called instead")); kernel.add_expected_syscall(ExpectedSyscall::AllowRw { driver_num: 1, buffer_num: 2, return_error: None, }); assert!(catch_unwind(|| unsafe { allow_rw(7u32.into(), 2u32.into(), 0u32.into(), 0u32.into()) }) .expect_err("failed to catch wrong driver number") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different driver_num")); kernel.add_expected_syscall(ExpectedSyscall::AllowRw { driver_num: 1, buffer_num: 2, return_error: None, }); assert!(catch_unwind(|| unsafe { allow_rw(1u32.into(), 7u32.into(), 0u32.into(), 0u32.into()) }) .expect_err("failed to catch wrong buffer number") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different buffer_num")); } #[test] fn no_driver() { let _kernel = fake::Kernel::new(); let [r0, r1, r2, r3] = unsafe { allow_rw(7u32.into(), 1u32.into(), 0u32.into(), 0u32.into()) }; assert_eq!( r0.try_into(), Ok(Into::::into(return_variant::FAILURE_2_U32)) ); assert_eq!(r1.try_into(), Ok(ErrorCode::NoDevice as u32)); assert_eq!(r2.try_into(), Ok(0u32)); assert_eq!(r3.try_into(), Ok(0u32)); } #[test] fn no_kernel() { let result = catch_unwind(|| unsafe { allow_rw(1u32.into(), 1u32.into(), 0u32.into(), 0u32.into()) }); assert!(result .expect_err("failed to catch missing kernel") .downcast_ref::() .expect("wrong panic payload type") .contains("no fake::Kernel exists")); } #[test] fn syscall_log() { let kernel = fake::Kernel::new(); // We want to pass a buffer of nonzero length to verify the length is logged // correctly. let buffer = [0; 3]; unsafe { allow_rw( 1u32.into(), 2u32.into(), buffer.as_ptr().into(), buffer.len().into(), ); } assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRw { driver_num: 1, buffer_num: 2, len: 3, }] ); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_buffer_number() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| unsafe { allow_rw( 1u32.into(), (u32::MAX as usize + 1).into(), 0u32.into(), 0u32.into(), ) }); assert!(result .expect_err("failed to catch too-large buffer number") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large buffer number")); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_driver_number() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| unsafe { allow_rw( (u32::MAX as usize + 1).into(), 1u32.into(), 0u32.into(), 0u32.into(), ) }); assert!(result .expect_err("failed to catch too-large driver number") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large driver number")); } ================================================ FILE: unittest/src/fake/syscalls/command_impl.rs ================================================ //! `fake::Kernel`'s implementation of the Command system call. use crate::kernel_data::with_kernel_data; use crate::{command_return, ExpectedSyscall, SyscallLogEntry}; use libtock_platform::{ErrorCode, Register}; use std::convert::TryInto; pub(super) fn command( driver_id: Register, command_id: Register, argument0: Register, argument1: Register, ) -> [Register; 4] { let driver_id = driver_id.try_into().expect("Too large driver ID"); let command_id = command_id.try_into().expect("Too large command ID"); let argument0 = argument0.try_into().expect("Too large argument 0"); let argument1 = argument1.try_into().expect("Too large argument 1"); let (driver, override_return) = with_kernel_data(|option_kernel_data| { let kernel_data = option_kernel_data.expect("Command called but no fake::Kernel exists"); kernel_data.syscall_log.push(SyscallLogEntry::Command { driver_id, command_id, argument0, argument1, }); // Check for an expected syscall entry. Sets override_return to None if // the expected syscall queue is empty or if it expected this syscall // but did not specify a return override. Panics if a different syscall // was expected (either a non-Command syscall, or a Command call with // different arguments). let override_return = match kernel_data.expected_syscalls.pop_front() { None => None, Some(ExpectedSyscall::Command { driver_id: expected_driver_id, command_id: expected_command_id, argument0: expected_argument0, argument1: expected_argument1, override_return, }) => { assert_eq!( driver_id, expected_driver_id, "expected different driver_id" ); assert_eq!( command_id, expected_command_id, "expected different command_id" ); assert_eq!( argument0, expected_argument0, "expected different argument0" ); assert_eq!( argument1, expected_argument1, "expected different argument1" ); override_return } Some(expected_syscall) => expected_syscall.panic_wrong_call("Command"), }; let driver = kernel_data .drivers .get(&driver_id) .map(|driver_data| driver_data.driver.clone()); (driver, override_return) }); // Call the driver if one is present. If not, return NoDevice as required by // TRD 104. let driver_return = match driver { Some(driver) => driver.command(command_id, argument0, argument1), None => command_return::failure(ErrorCode::NoDevice), }; // Convert the override return value (or the driver return value if no // override is present) into the representative register values. let (return_variant, r1, r2, r3) = override_return.unwrap_or(driver_return).raw_values(); let r0: u32 = return_variant.into(); [r0.into(), r1.into(), r2.into(), r3.into()] } ================================================ FILE: unittest/src/fake/syscalls/command_impl_tests.rs ================================================ use super::command_impl::*; use crate::{command_return, fake, DriverInfo, ExpectedSyscall, SyscallLogEntry}; use libtock_platform::{ return_variant, syscall_class, CommandReturn, ErrorCode, RawSyscalls, ReturnVariant, }; use std::convert::TryInto; use std::panic::catch_unwind; // TODO: When another system call is implemented, add a test for the case // where a different system call class is expected. #[test] fn driver_support() { let kernel = fake::Kernel::new(); // Call command for a nonexistent driver. let [r0, r1, _, _] = command(42u32.into(), 1u32.into(), 0u32.into(), 0u32.into()); assert_eq!( r0.try_into(), Ok(Into::::into(return_variant::FAILURE)) ); assert_eq!(r1.try_into(), Ok(ErrorCode::NoDevice as u32)); // A mock driver that returns a fixed value. // TODO: This is growing every time we add a new required method to Driver. // Once we have fake driver inside `crate::fake` (e.g. a fake LowLevelDebug // driver), we should remove MockDriver and replace it with the fake driver, // so we have 1 fewer Driver implementations to maintain. struct MockDriver; impl fake::SyscallDriver for MockDriver { fn info(&self) -> DriverInfo { DriverInfo::new(42) } fn command(&self, _command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { command_return::success_3_u32(1, 2, 3) } } // Call command with the mock driver. let driver = std::rc::Rc::new(MockDriver); kernel.add_driver(&driver); let [r0, r1, r2, r3] = command(42u32.into(), 0u32.into(), 0u32.into(), 0u32.into()); assert_eq!( r0.try_into(), Ok(Into::::into(return_variant::SUCCESS_3_U32)) ); assert_eq!(r1.try_into(), Ok(1u32)); assert_eq!(r2.try_into(), Ok(2u32)); assert_eq!(r3.try_into(), Ok(3u32)); } // Tests command with expected syscalls that don't match this command call. #[test] fn expected_wrong_command() { let kernel = fake::Kernel::new(); let expected_syscall = ExpectedSyscall::Command { driver_id: 1, command_id: 1, argument0: 1, argument1: 1, override_return: None, }; kernel.add_expected_syscall(expected_syscall); assert!( catch_unwind(|| command(2u32.into(), 1u32.into(), 1u32.into(), 1u32.into())) .expect_err("failed to catch wrong driver_id") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different driver_id") ); kernel.add_expected_syscall(expected_syscall); assert!( catch_unwind(|| command(1u32.into(), 2u32.into(), 1u32.into(), 1u32.into())) .expect_err("failed to catch wrong command_id") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different command_id") ); kernel.add_expected_syscall(expected_syscall); assert!( catch_unwind(|| command(1u32.into(), 1u32.into(), 2u32.into(), 1u32.into())) .expect_err("failed to catch wrong argument0") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different argument0") ); kernel.add_expected_syscall(expected_syscall); assert!( catch_unwind(|| command(1u32.into(), 1u32.into(), 1u32.into(), 2u32.into())) .expect_err("failed to catch wrong argument1") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different argument1") ); } #[test] fn no_kernel() { let result = catch_unwind(|| command(1u32.into(), 1u32.into(), 0u32.into(), 0u32.into())); assert!(result .expect_err("failed to catch missing kernel") .downcast_ref::() .expect("wrong panic payload type") .contains("no fake::Kernel exists")); } #[test] fn override_return() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Command { driver_id: 1, command_id: 2, argument0: 3, argument1: 4, override_return: Some(command_return::success_3_u32(1, 2, 3)), }); let [r0, r1, r2, r3] = command(1u32.into(), 2u32.into(), 3u32.into(), 4u32.into()); let r0: u32 = r0.try_into().expect("too large r0"); let r1: u32 = r1.try_into().expect("too large r1"); let r2: u32 = r2.try_into().expect("too large r2"); let r3: u32 = r3.try_into().expect("too large r3"); let return_variant: ReturnVariant = r0.into(); assert_eq!(return_variant, return_variant::SUCCESS_3_U32); assert_eq!(r1, 1); assert_eq!(r2, 2); assert_eq!(r3, 3); } // Test that fake::Kernel's implementation of RawSyscalls correctly forwards // a command to `command`. // TODO: Migrate into raw_syscalls_impl.rs when the other system calls are // completed, to avoid git conflicts. #[test] fn syscall4() { let kernel = fake::Kernel::new(); unsafe { fake::Syscalls::syscall4::<{ syscall_class::COMMAND }>([ 1u32.into(), 2u32.into(), 3u32.into(), 4u32.into(), ]); } assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Command { driver_id: 1, command_id: 2, argument0: 3, argument1: 4, }] ); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_argument0() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| { command( 1u32.into(), 1u32.into(), (u32::MAX as usize + 1).into(), 0u32.into(), ) }); assert!(result .expect_err("failed to catch too-large argument0") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large argument 0")); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_argument1() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| { command( 1u32.into(), 1u32.into(), 0u32.into(), (u32::MAX as usize + 1).into(), ) }); assert!(result .expect_err("failed to catch too-large argument1") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large argument 1")); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_command_id() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| { command( 1u32.into(), (u32::MAX as usize + 1).into(), 0u32.into(), 0u32.into(), ) }); assert!(result .expect_err("failed to catch too-large command ID") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large command ID")); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_driver_id() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| { command( (u32::MAX as usize + 1).into(), 1u32.into(), 0u32.into(), 0u32.into(), ) }); assert!(result .expect_err("failed to catch too-large driver ID") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large driver ID")); } ================================================ FILE: unittest/src/fake/syscalls/exit_impl.rs ================================================ use core::convert::TryInto; pub(super) fn exit(r0: libtock_platform::Register, r1: libtock_platform::Register) -> ! { let exit_num: u32 = r0.try_into().expect("Too large exit number"); let completion_code: u32 = r1.try_into().expect("Too large completion code"); match exit_num { libtock_platform::exit_id::TERMINATE => { println!("exit-terminate called with code {completion_code}"); #[cfg(not(miri))] crate::exit_test::signal_exit(crate::ExitCall::Terminate(completion_code)); std::process::exit(1); } libtock_platform::exit_id::RESTART => { println!("exit-restart called with code {completion_code}"); #[cfg(not(miri))] crate::exit_test::signal_exit(crate::ExitCall::Restart(completion_code)); std::process::exit(1); } _ => panic!("Unknown exit number {exit_num} invoked."), } } ================================================ FILE: unittest/src/fake/syscalls/exit_impl_tests.rs ================================================ use super::exit_impl::*; use crate::{exit_test, ExitCall}; #[test] fn exit_restart() { let exit_call = exit_test("fake::syscalls::exit_impl_tests::exit_restart", || { exit(libtock_platform::exit_id::RESTART.into(), 31415u32.into()) }); assert_eq!(exit_call, ExitCall::Restart(31415)); } #[test] fn exit_terminate() { let exit_call = exit_test("fake::syscalls::exit_impl_tests::exit_terminate", || { exit(libtock_platform::exit_id::TERMINATE.into(), 9265u32.into()) }); assert_eq!(exit_call, ExitCall::Terminate(9265)); } ================================================ FILE: unittest/src/fake/syscalls/memop_impl.rs ================================================ //! `fake::Kernel`'s implementation of the Memop system call. use crate::kernel_data::with_kernel_data; use crate::{ExpectedSyscall, SyscallLogEntry}; use libtock_platform::{return_variant, ErrorCode, Register}; use std::convert::TryInto; pub(super) fn memop(memop_num: Register, argument0: Register) -> [Register; 2] { let memop_num = memop_num.try_into().expect("Too large memop num"); let (return_error, memop_return, memop_r1) = with_kernel_data(|option_kernel_data| { let kernel_data = option_kernel_data.expect("Memop called but no fake::Kernel exists"); kernel_data.syscall_log.push(SyscallLogEntry::Memop { memop_num, argument0, }); // Check for an expected syscall entry. Sets return_error to None if // the expected syscall queue is empty or if it expected this syscall // but did not specify a return override. Panics if a different syscall // was expected (either a non-Memop syscall, or a Memop call with // different arguments). let return_error = match kernel_data.expected_syscalls.pop_front() { None => None, Some(ExpectedSyscall::Memop { memop_num: expected_memop_num, argument0: expected_argument0, return_error, }) => { assert_eq!( memop_num, expected_memop_num, "expected different memop_num" ); assert_eq!( usize::from(argument0), usize::from(expected_argument0), "expected different argument0" ); return_error } Some(expected_syscall) => expected_syscall.panic_wrong_call("Memop"), }; // Emulate the memop call // TODO: This emulation could be improved by adding data to kernel_data to allow us to // better track what input arguments might be expected to return errors. let (memop_return, memop_r1) = match memop_num { 0 => { /* brk */ if Into::<*const u8>::into(argument0).is_null() { (return_variant::FAILURE, ErrorCode::Invalid.into()) } else { kernel_data.memory_break = argument0.into(); (return_variant::SUCCESS, 0.into()) } } 1 => { /* sbrk */ let current_brk = kernel_data.memory_break; let new_brk = current_brk.wrapping_byte_offset(argument0.as_i32() as isize); kernel_data.memory_break = new_brk; (return_variant::SUCCESS, kernel_data.memory_break.into()) } 2 => { /* app_ram_start */ // just pick a random number to always return, for now (return_variant::SUCCESS, 0x123400.into()) } 10 => { /* debug_stack_start */ (return_variant::SUCCESS, 0.into()) } 11 => { /* debug_heap_start */ (return_variant::SUCCESS, 0.into()) } _ => { panic!("Memop num not supported by test infrastructure"); } }; (return_error, memop_return, memop_r1) }); // Convert the return value into the representative register values. // If there is an return_error, return a Failure along with that ErrorCode. let (return_variant, r1) = return_error.map_or((memop_return, memop_r1), |override_errcode| { (return_variant::FAILURE, override_errcode.into()) }); let r0: u32 = return_variant.into(); [r0.into(), r1] } ================================================ FILE: unittest/src/fake/syscalls/memop_impl_tests.rs ================================================ use super::memop_impl::*; use crate::{fake, ExpectedSyscall}; use libtock_platform::{return_variant, ErrorCode, ReturnVariant}; use std::convert::TryInto; use std::panic::catch_unwind; // Tests memop with expected syscalls that don't match this memop call. #[test] fn expected_wrong_memop() { let kernel = fake::Kernel::new(); let expected_syscall = ExpectedSyscall::Memop { memop_num: 1, argument0: 1u32.into(), return_error: None, }; kernel.add_expected_syscall(expected_syscall); assert!(catch_unwind(|| memop(0u32.into(), 1u32.into())) .expect_err("failed to catch wrong memop_num") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different memop_num")); kernel.add_expected_syscall(expected_syscall); assert!(catch_unwind(|| memop(1u32.into(), 0u32.into())) .expect_err("failed to catch wrong memop argument0") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different argument0")); } #[test] fn no_kernel() { let result = catch_unwind(|| memop(1u32.into(), 1u32.into())); assert!(result .expect_err("failed to catch missing kernel") .downcast_ref::() .expect("wrong panic payload type") .contains("no fake::Kernel exists")); } #[test] fn return_error() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 1, argument0: 4u32.into(), return_error: Some(ErrorCode::NoMem), }); let [r0, r1] = memop(1u32.into(), 4u32.into()); let r0: u32 = r0.try_into().expect("too large r0"); let r1: u32 = r1.try_into().expect("too large r1"); let return_variant: ReturnVariant = r0.into(); assert_eq!(return_variant, return_variant::FAILURE); assert_eq!(r1, ErrorCode::NoMem as u32); } #[cfg(target_pointer_width = "64")] #[test] fn too_large_memop_num() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| memop((u32::MAX as usize + 1).into(), 1u32.into())); assert!(result .expect_err("failed to catch too-large memop num") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large memop num")); } #[test] fn memop_using_syscall1() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Memop { memop_num: 2, argument0: 0u32.into(), return_error: None, }); let [r0, r1] = memop(2u32.into(), 0u32.into()); let r0: u32 = r0.try_into().expect("too large r0"); let _r1: u32 = r1.try_into().expect("too large r1"); let return_variant: ReturnVariant = r0.into(); assert_eq!(return_variant, return_variant::SUCCESS); // No assertion for return value, could be any value from real kernel. } ================================================ FILE: unittest/src/fake/syscalls/mod.rs ================================================ mod allow_ro_impl; mod allow_rw_impl; mod command_impl; mod exit_impl; mod memop_impl; mod raw_syscalls_impl; mod subscribe_impl; mod yield_impl; /// `fake::Syscalls` implements `libtock_platform::Syscalls` by forwarding the /// system calls to the thread's `fake::Kernel` instance. It is used by unit /// tests to provide the code under test access to Tock's system calls. pub struct Syscalls; #[cfg(test)] mod allow_ro_impl_tests; #[cfg(test)] mod allow_rw_impl_tests; #[cfg(test)] mod command_impl_tests; #[cfg(all(not(miri), test))] mod exit_impl_tests; #[cfg(test)] mod memop_impl_tests; #[cfg(test)] mod raw_syscalls_impl_tests; #[cfg(test)] mod subscribe_impl_tests; #[cfg(test)] mod yield_impl_tests; // Miri does not always check that values are valid (see `doc/MiriTips.md` in // the root of this repository). This function uses a hack to verify a value is // valid. If the value is invalid, Miri will detect undefined behavior when it // executes this. It is used by submodules of fake::syscalls. fn assert_valid(_value: T) { #[cfg(miri)] let _ = format!("{:?}", _value); } ================================================ FILE: unittest/src/fake/syscalls/raw_syscalls_impl.rs ================================================ use libtock_platform::{syscall_class, yield_id, RawSyscalls, Register}; use std::convert::TryInto; unsafe impl RawSyscalls for crate::fake::Syscalls { unsafe fn yield1([r0]: [Register; 1]) { crate::fake::syscalls::assert_valid(r0); match r0.try_into().expect("too-large Yield ID passed") { yield_id::NO_WAIT => panic!("yield-no-wait called without an argument"), yield_id::WAIT => super::yield_impl::yield_wait(), id => panic!("unknown yield ID {id}"), } } unsafe fn yield2([r0, r1]: [Register; 2]) { crate::fake::syscalls::assert_valid((r0, r1)); match r0.try_into().expect("too-large Yield ID passed") { yield_id::NO_WAIT => unsafe { super::yield_impl::yield_no_wait(r1.into()) }, yield_id::WAIT => { // Technically it is acceptable to call yield_wait with an // argument, but it shouldn't be done because it's wasteful so // we fail the test case regardless. panic!("yield-wait called with an argument"); } id => panic!("unknown yield ID {id}"), } } unsafe fn syscall1([r0]: [Register; 1]) -> [Register; 2] { match CLASS { syscall_class::MEMOP => super::memop_impl::memop(r0, 0u32.into()), _ => panic!("Unknown syscall1 call. Class: {CLASS}"), } } unsafe fn syscall2([r0, r1]: [Register; 2]) -> [Register; 2] { crate::fake::syscalls::assert_valid((r0, r1)); match CLASS { syscall_class::MEMOP => super::memop_impl::memop(r0, r1), syscall_class::EXIT => super::exit_impl::exit(r0, r1), _ => panic!("Unknown syscall2 call. Class: {CLASS}"), } } unsafe fn syscall4([r0, r1, r2, r3]: [Register; 4]) -> [Register; 4] { crate::fake::syscalls::assert_valid((r0, r1, r2, r3)); match CLASS { syscall_class::SUBSCRIBE => unsafe { super::subscribe_impl::subscribe(r0, r1, r2, r3) }, syscall_class::COMMAND => super::command_impl::command(r0, r1, r2, r3), syscall_class::ALLOW_RW => unsafe { super::allow_rw_impl::allow_rw(r0, r1, r2, r3) }, syscall_class::ALLOW_RO => unsafe { super::allow_ro_impl::allow_ro(r0, r1, r2, r3) }, _ => panic!("Unknown syscall4 call. Class: {CLASS}"), } } } ================================================ FILE: unittest/src/fake/syscalls/raw_syscalls_impl_tests.rs ================================================ // These tests verify the RawSyscalls implementation routes system calls to the // fake implementations (e.g. command(), yield_wait, etc) correctly. It does not // test the fake syscall implementations themselves, as they have their own unit // tests. use crate::{fake, SyscallLogEntry}; use libtock_platform::{syscall_class, RawSyscalls}; #[test] fn allow_ro() { let kernel = fake::Kernel::new(); unsafe { fake::Syscalls::syscall4::<{ syscall_class::ALLOW_RO }>([ 1u32.into(), 2u32.into(), 0u32.into(), 0u32.into(), ]); } assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRo { driver_num: 1, buffer_num: 2, len: 0, }] ); } #[test] fn allow_rw() { let kernel = fake::Kernel::new(); unsafe { fake::Syscalls::syscall4::<{ syscall_class::ALLOW_RW }>([ 1u32.into(), 2u32.into(), 0u32.into(), 0u32.into(), ]); } assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::AllowRw { driver_num: 1, buffer_num: 2, len: 0, }] ); } // TODO: Move the syscall4 Command test here. // TODO: Implement Exit. #[test] fn memop() { let kernel = fake::Kernel::new(); unsafe { fake::Syscalls::syscall2::<{ syscall_class::MEMOP }>([1u32.into(), 2u32.into()]); fake::Syscalls::syscall1::<{ syscall_class::MEMOP }>([2u32.into()]); } assert_eq!( kernel.take_syscall_log(), [ SyscallLogEntry::Memop { memop_num: 1, argument0: 2.into(), }, SyscallLogEntry::Memop { memop_num: 2, argument0: 0.into(), } ] ); } // TODO: Implement Subscribe. // TODO: Move the yield1 and yield2 tests here. ================================================ FILE: unittest/src/fake/syscalls/subscribe_impl.rs ================================================ use crate::kernel_data::with_kernel_data; use crate::{ExpectedSyscall, SyscallLogEntry}; use libtock_platform::{return_variant, ErrorCode, Register}; use std::convert::TryInto; // Safety: The arguments must represent a valid Subscribe call as specified by // TRD 104. pub(super) unsafe fn subscribe( driver_num: Register, subscribe_num: Register, upcall_fn: Register, data: Register, ) -> [Register; 4] { let driver_num = driver_num.try_into().expect("Too large driver number"); let subscribe_num = subscribe_num .try_into() .expect("Too large subscribe number"); let (skip_with_error, num_upcalls) = with_kernel_data(|option_kernel_data| { let kernel_data = option_kernel_data.expect("Subscribe called but no fake::Kernel exists"); kernel_data.syscall_log.push(SyscallLogEntry::Subscribe { driver_num, subscribe_num, }); // Check for an expected syscall. Panics if an expected syscall exists // and it does not match this syscall. Otherwise sets skip_with_error to // skip_with_error from the expected syscall, or None if none was // provided. let skip_with_error = match kernel_data.expected_syscalls.pop_front() { None => None, Some(ExpectedSyscall::Subscribe { driver_num: expected_driver_num, subscribe_num: expected_subscribe_num, skip_with_error, }) => { assert_eq!( driver_num, expected_driver_num, "expected different driver number" ); assert_eq!( subscribe_num, expected_subscribe_num, "expected different subscribe number" ); skip_with_error } Some(expected_syscall) => expected_syscall.panic_wrong_call("Subscribe"), }; // Retrieve the number of upcalls for this driver, or None if there is // no driver with this number. let num_upcalls = kernel_data .drivers .get(&driver_num) .map(|driver_data| driver_data.num_upcalls); (skip_with_error, num_upcalls) }); // Convenience function to produce an error return. let failure_registers = |error_code: ErrorCode| { [ return_variant::FAILURE_2_U32.into(), error_code.into(), upcall_fn, data, ] }; // If skip_with_error was specified, we skip the remainder of this logic and // return an error directly. if let Some(error_code) = skip_with_error { return failure_registers(error_code); } // Verify the given driver ID was present. If no driver with this ID is // present, the kernel returns NODEVICE. let num_upcalls = match num_upcalls { Some(num_upcalls) => num_upcalls, None => return failure_registers(ErrorCode::NoDevice), }; // If a too-large subscribe number is passed, the kernel returns the Invalid // error code. if subscribe_num >= num_upcalls { return failure_registers(ErrorCode::Invalid); } // At this point, we know the Subscribe call should succeed. let upcall = crate::upcall::Upcall { fn_pointer: match upcall_fn.into() { 0usize => None, // Safety: RawSyscalls guarantees that if upcall_fn is not 0, then // it is a valid unsafe extern fn(u32, u32, u32, Register). We've // already verified upcall_fn is not 0. The niche optimization // guarantees that an unsafe extern fn(u32, u32, u32, Register) can // be transmuted into an Option. _ => unsafe { core::mem::transmute::< Register, std::option::Option, >(upcall_fn) }, }, data, }; let upcall_id = crate::upcall::UpcallId { driver_num, subscribe_num, }; // Go back into the kernel data to update the stored upcall and purge the // previous upcall from the upcall queue (as required by TRD 104). let out_upcall = with_kernel_data(|option_kernel_data| { let kernel_data = option_kernel_data.unwrap(); kernel_data .upcall_queue .retain(|existing_queue_entry| existing_queue_entry.id != upcall_id); kernel_data .drivers .get_mut(&driver_num) .unwrap() .upcalls .insert(subscribe_num, upcall) }); let out_upcall_fn = out_upcall .map_or(0, |out_upcall| match out_upcall.fn_pointer { None => 0, Some(fn_pointer) => fn_pointer as usize, }) .into(); let out_upcall_data = out_upcall.map_or(0usize.into(), |out_upcall| out_upcall.data); // The Success with 2 U32 variant doesn't specify what is returned in r3. In // practice, the kernel will leave that register alone, so we echo data // (passed in via r3) out as r3. [ return_variant::SUCCESS_2_U32.into(), out_upcall_fn, out_upcall_data, data, ] } ================================================ FILE: unittest/src/fake/syscalls/subscribe_impl_tests.rs ================================================ use super::subscribe_impl::*; use crate::{fake, ExpectedSyscall, SyscallLogEntry}; use libtock_platform::{return_variant, syscall_class, ErrorCode, RawSyscalls, Register}; use std::convert::TryInto; use std::panic::catch_unwind; // TODO: Once a fake driver that supports upcalls is added, add the following // test cases: // 1. A test with a subscribe_id that is too large. // 2. A test that should pass -- and verify the upcall is set correctly. // 3. A test that verifies that upcalls are correctly cleared from the queue // when they are replaced by a subsequent Subscribe call. // Tests Subscribe calls that do not match the expected syscall. #[test] fn expected_wrong() { let kernel = fake::Kernel::new(); // Test with a non-Subscribe expected syscall. kernel.add_expected_syscall(ExpectedSyscall::YieldWait { skip_upcall: false }); let result = catch_unwind(|| unsafe { subscribe(1u32.into(), 2u32.into(), 0usize.into(), 0usize.into()) }); assert!(result .expect_err("failed to catch wrong syscall") .downcast_ref::() .expect("wrong panic payload type") .contains("but Subscribe was called instead")); let expected_syscall = ExpectedSyscall::Subscribe { driver_num: 1, subscribe_num: 2, skip_with_error: None, }; // Tests with an incorrect driver number kernel.add_expected_syscall(expected_syscall); let result = catch_unwind(|| unsafe { subscribe(7u32.into(), 2u32.into(), 0usize.into(), 0usize.into()) }); assert!(result .expect_err("failed to catch wrong driver number") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different driver number")); // Tests with an incorrect subscribe number kernel.add_expected_syscall(expected_syscall); let result = catch_unwind(|| unsafe { subscribe(1u32.into(), 7u32.into(), 0usize.into(), 0usize.into()) }); assert!(result .expect_err("failed to catch wrong subscribe number") .downcast_ref::() .expect("wrong panic payload type") .contains("expected different subscribe number")); } // Test Subscribe with a driver number that does not exist. #[test] fn missing_driver() { let _kernel = fake::Kernel::new(); let [r0, r1, r2, r3] = unsafe { subscribe(1u32.into(), 2u32.into(), 0usize.into(), 0usize.into()) }; let (r0, r1, r2, r3): (u32, u32, usize, usize) = ( r0.try_into().expect("too large r0"), r1.try_into().expect("too large r1"), r2.into(), r3.into(), ); assert_eq!(r0, return_variant::FAILURE_2_U32.into()); assert_eq!(r1, ErrorCode::NoDevice as u32); assert_eq!(r2, 0); assert_eq!(r3, 0); } #[test] fn no_kernel() { let result = catch_unwind(|| unsafe { subscribe(1u32.into(), 2u32.into(), 0usize.into(), 0usize.into()) }); assert!(result .expect_err("failed to catch missing kernel") .downcast_ref::() .expect("wrong panic payload type") .contains("no fake::Kernel exists")); } #[test] fn skip_with_error() { let kernel = fake::Kernel::new(); kernel.add_expected_syscall(ExpectedSyscall::Subscribe { driver_num: 1, subscribe_num: 2, skip_with_error: Some(ErrorCode::NoAck), }); unsafe extern "C" fn upcall_fn(_: u32, _: u32, _: u32, _: Register) {} // Convert to a raw pointer to get a stable address. let upcall_fn_ptr = upcall_fn as *const (); let [r0, r1, r2, r3] = unsafe { subscribe( 1u32.into(), 2u32.into(), upcall_fn_ptr.into(), 1234usize.into(), ) }; let (r0, r1, r2, r3): (u32, u32, *const (), usize) = ( r0.try_into().expect("too large r0"), r1.try_into().expect("too large r1"), r2.into(), r3.into(), ); assert_eq!(r0, return_variant::FAILURE_2_U32.into()); assert_eq!(r1, ErrorCode::NoAck as u32); assert_eq!(r2, upcall_fn_ptr); assert_eq!(r3, 1234); } // TODO: Move the syscall4_subscribe test into raw_syscalls_impl_tests.rs, once // raw_syscalls_impl_tests.rs has been created. #[test] fn syscall4_subscribe() { let kernel = fake::Kernel::new(); unsafe { fake::Syscalls::syscall4::<{ syscall_class::SUBSCRIBE }>([ 1u32.into(), 2u32.into(), 0u32.into(), 0u32.into(), ]); } assert_eq!( kernel.take_syscall_log(), [SyscallLogEntry::Subscribe { driver_num: 1, subscribe_num: 2, }] ); } // Tests Subscribe with too large inputs (driver_num and subscribe_num) #[cfg(target_pointer_width = "64")] #[test] fn too_large_inputs() { let _kernel = fake::Kernel::new(); let result = catch_unwind(|| unsafe { subscribe( (u32::MAX as usize + 1).into(), 2u32.into(), 0usize.into(), 0usize.into(), ) }); assert!(result .expect_err("failed to catch too-large driver number") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large driver number")); let result = catch_unwind(|| unsafe { subscribe( 1u32.into(), (u32::MAX as usize + 1).into(), 0usize.into(), 0usize.into(), ) }); assert!(result .expect_err("failed to catch too-large subscribe number") .downcast_ref::() .expect("wrong panic payload type") .contains("Too large subscribe number")); } ================================================ FILE: unittest/src/fake/syscalls/yield_impl.rs ================================================ //! Implementations of Yield system calls. use crate::kernel_data::{with_kernel_data, KERNEL_DATA}; use crate::{ExpectedSyscall, SyscallLogEntry}; /// # Safety /// It must be valid to write a `libtock_platform::YieldNoWaitReturn` into the /// value pointed to by `return_ptr`. When `yield_no_wait` returns, the value /// pointed to by `return_ptr` will be set. pub(super) unsafe fn yield_no_wait(return_ptr: *mut libtock_platform::YieldNoWaitReturn) { let override_return = KERNEL_DATA.with(|refcell| { let mut refmut = refcell.borrow_mut(); let kernel_data = refmut .as_mut() .expect("yield-no-wait called but no fake::Kernel exists"); kernel_data.syscall_log.push(SyscallLogEntry::YieldNoWait); match kernel_data.expected_syscalls.pop_front() { None => None, Some(ExpectedSyscall::YieldNoWait { override_return }) => override_return, Some(expected_syscall) => expected_syscall.panic_wrong_call("yield-no-wait"), } }); let upcall_ran = match invoke_next_upcall() { true => libtock_platform::YieldNoWaitReturn::Upcall, false => libtock_platform::YieldNoWaitReturn::NoUpcall, }; unsafe { core::ptr::write(return_ptr, override_return.unwrap_or(upcall_ran)); } } pub(super) fn yield_wait() { let skip_upcall = KERNEL_DATA.with(|refcell| { let mut refmut = refcell.borrow_mut(); let kernel_data = refmut .as_mut() .expect("yield-wait called but no fake::Kernel exists"); kernel_data.syscall_log.push(SyscallLogEntry::YieldWait); match kernel_data.expected_syscalls.pop_front() { None => false, Some(ExpectedSyscall::YieldWait { skip_upcall }) => skip_upcall, Some(expected_syscall) => expected_syscall.panic_wrong_call("yield-wait"), } }); if skip_upcall { return; } // In a real Tock system, a process that calls yield-wait with no queued // upcalls would be put to sleep until an upcall was queued (e.g. by an // interrupt). However, in this single-threaded test environment, there is // no possibility a new upcall will be enqueued while we wait. Panicing is // friendlier than hanging, so we panic if there's no upcall. assert!( invoke_next_upcall(), "yield-wait called with no queued upcall" ); } // Pops the next upcall off the kernel data's upcall queue and invokes it, or // does nothing if the upcall queue was entry. The return value indicates // whether an upcall was run. Panics if no kernel data is present. fn invoke_next_upcall() -> bool { let option_queue_entry = with_kernel_data(|option_kernel_data| option_kernel_data.unwrap().upcall_queue.pop_front()); match option_queue_entry { None => false, Some(queue_entry) => { unsafe { queue_entry.upcall.invoke(queue_entry.args); } true } } } ================================================ FILE: unittest/src/fake/syscalls/yield_impl_tests.rs ================================================ use crate::kernel_data::with_kernel_data; use crate::upcall::{Upcall, UpcallId, UpcallQueueEntry}; use crate::{fake, ExpectedSyscall, SyscallLogEntry}; use libtock_platform::{RawSyscalls, YieldNoWaitReturn}; use std::panic::catch_unwind; use fake::syscalls::yield_impl::*; // Upcall function that copies its arguments into the [u32; 3] pointed to by // `output`. Used by multiple tests in this file. unsafe extern "C" fn copy_args( arg0: u32, arg1: u32, arg2: u32, output: libtock_platform::Register, ) { let output: *mut [u32; 3] = output.into(); unsafe { *output = [arg0, arg1, arg2]; } } #[test] fn yield_no_wait_test() { // Test calling yield_no_wait with no fake::Kernel present. let result = catch_unwind(|| { let mut return_value = core::mem::MaybeUninit::::uninit(); unsafe { yield_no_wait(return_value.as_mut_ptr()); } }); assert!(result .expect_err("failed to catch missing fake::Kernel") .downcast_ref::() .expect("wrong panic payload type") .contains("no fake::Kernel")); let kernel = fake::Kernel::new(); // Test yield_no_wait with an empty upcall queue and empty expected syscall // queue. let mut return_value = core::mem::MaybeUninit::::uninit(); unsafe { yield_no_wait(return_value.as_mut_ptr()); } let return_value = unsafe { return_value.assume_init() }; fake::syscalls::assert_valid(return_value); assert_eq!(return_value, YieldNoWaitReturn::NoUpcall); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldNoWait]); // Test yield_no_wait with a return override in an expected syscall. kernel.add_expected_syscall(ExpectedSyscall::YieldNoWait { override_return: Some(YieldNoWaitReturn::Upcall), }); let mut return_value = core::mem::MaybeUninit::::uninit(); unsafe { yield_no_wait(return_value.as_mut_ptr()); } let return_value = unsafe { return_value.assume_init() }; fake::syscalls::assert_valid(return_value); assert_eq!(return_value, YieldNoWaitReturn::Upcall); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldNoWait]); // Test yield_no_wait with a mismatched expected syscall. kernel.add_expected_syscall(ExpectedSyscall::YieldWait { skip_upcall: false }); let result = catch_unwind(|| { let mut return_value = core::mem::MaybeUninit::::uninit(); unsafe { yield_no_wait(return_value.as_mut_ptr()); } }); assert!(result .expect_err("failed to catch mismatched expected syscall") .downcast_ref::() .expect("wrong panic payload type") .contains("yield-no-wait was called instead")); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldNoWait]); // Upcall structures for using copy_args. let mut output_array = [0u32; 3]; let upcall_id = UpcallId { driver_num: 1, subscribe_num: 2, }; let upcall = Upcall { fn_pointer: Some(copy_args), data: (&mut output_array as *mut u32).into(), }; // Test yield_no_wait with an upcall queued. with_kernel_data(|option_kernel_data| { option_kernel_data .unwrap() .upcall_queue .push_back(UpcallQueueEntry { args: (1, 2, 3), id: upcall_id, upcall, }); }); let mut return_value = core::mem::MaybeUninit::::uninit(); unsafe { yield_no_wait(return_value.as_mut_ptr()); } assert_eq!(output_array, [1, 2, 3]); let return_value = unsafe { return_value.assume_init() }; fake::syscalls::assert_valid(return_value); assert_eq!(return_value, YieldNoWaitReturn::Upcall); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldNoWait]); // Test yield_no_wait with an upcall queued and a return override in an // expected syscall. kernel.add_expected_syscall(ExpectedSyscall::YieldNoWait { override_return: Some(YieldNoWaitReturn::NoUpcall), }); with_kernel_data(|option_kernel_data| { option_kernel_data .unwrap() .upcall_queue .push_back(UpcallQueueEntry { args: (4, 5, 6), id: upcall_id, upcall, }); }); let mut return_value = core::mem::MaybeUninit::::uninit(); unsafe { yield_no_wait(return_value.as_mut_ptr()); } assert_eq!(output_array, [4, 5, 6]); let return_value = unsafe { return_value.assume_init() }; fake::syscalls::assert_valid(return_value); assert_eq!(return_value, YieldNoWaitReturn::NoUpcall); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldNoWait]); } #[test] fn yield_wait_test() { // Test calling yield_wait with no fake::Kernel present. assert!(catch_unwind(yield_wait) .expect_err("failed to catch missing fake::Kernel") .downcast_ref::() .expect("wrong panic payload type") .contains("no fake::Kernel")); let kernel = fake::Kernel::new(); // Test yield_wait with a mismatched expected syscall. kernel.add_expected_syscall(ExpectedSyscall::YieldNoWait { override_return: None, }); assert!(catch_unwind(yield_wait) .expect_err("failed to catch mismatched expected syscall") .downcast_ref::() .expect("wrong panic payload type") .contains("yield-wait was called instead")); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldWait]); // Upcall structures for using copy_args. let mut output_array = [0u32; 3]; let upcall_id = UpcallId { driver_num: 1, subscribe_num: 2, }; let upcall = Upcall { fn_pointer: Some(copy_args), data: (&mut output_array as *mut u32).into(), }; // Test yield_wait with a skipped upcall in an expected syscall. with_kernel_data(|option_kernel_data| { option_kernel_data .unwrap() .upcall_queue .push_back(UpcallQueueEntry { args: (1, 2, 3), id: upcall_id, upcall, }); }); kernel.add_expected_syscall(ExpectedSyscall::YieldWait { skip_upcall: true }); yield_wait(); assert_eq!(output_array, [0; 3]); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldWait]); // Test that yield_wait correctly invokes a queued upcall. The upcall was // queued for the previous test (which confirmed that skip_upcall works). yield_wait(); assert_eq!(output_array, [1, 2, 3]); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldWait]); } // TODO: Move the yield1 and yield2 tests into a raw_syscalls_impl test module, // once all system calls have been implemented. #[test] fn yield1() { let kernel = fake::Kernel::new(); #[cfg(target_pointer_width = "64")] { let result = catch_unwind(|| unsafe { fake::Syscalls::yield1([(u32::MAX as usize + 1).into()]) }); assert!(result .expect_err("failed to catch too large yield ID") .downcast_ref::() .expect("wrong panic payload type") .contains("too-large Yield ID")); } // Call yield-no-wait through yield1, which is not valid. let result = catch_unwind(|| unsafe { fake::Syscalls::yield1([0u32.into()]) }); assert!(result .expect_err("failed to catch yield-no-wait without arg") .downcast_ref::<&'static str>() .expect("wrong panic payload type") .contains("yield-no-wait called without an argument")); // Test a successful invocation of yield-wait. kernel.add_expected_syscall(ExpectedSyscall::YieldWait { skip_upcall: true }); unsafe { fake::Syscalls::yield1([1u32.into()]); } assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldWait]); // Call yield1 with a yield ID that is unknown but which fits in a u32. let result = catch_unwind(|| unsafe { fake::Syscalls::yield1([2u32.into()]) }); assert!(result .expect_err("failed to catch incorrect yield ID -- new ID added?") .downcast_ref::() .expect("wrong panic payload type") .contains("unknown yield ID")); } // Tests RawSyscalls::yield2's handling of bad yield IDs. #[test] fn yield2() { let kernel = fake::Kernel::new(); #[cfg(target_pointer_width = "64")] { let result = catch_unwind(|| unsafe { fake::Syscalls::yield2([(u32::MAX as usize + 1).into(), 0u32.into()]) }); assert!(result .expect_err("failed to catch too large yield ID") .downcast_ref::() .expect("wrong panic payload type") .contains("too-large Yield ID")); } // Test a successful invocation of yield-no-wait. kernel.add_expected_syscall(ExpectedSyscall::YieldNoWait { override_return: Some(YieldNoWaitReturn::Upcall), }); let mut return_value = core::mem::MaybeUninit::::uninit(); unsafe { fake::Syscalls::yield2([0u32.into(), return_value.as_mut_ptr().into()]); } let return_value = unsafe { return_value.assume_init() }; fake::syscalls::assert_valid(return_value); assert_eq!(kernel.take_syscall_log(), [SyscallLogEntry::YieldNoWait]); assert_eq!(return_value, YieldNoWaitReturn::Upcall); // Call yield-wait through yield2, which should be rejected. let result = catch_unwind(|| unsafe { fake::Syscalls::yield2([1u32.into(), 0u32.into()]) }); assert!(result .expect_err("failed to catch yield-wait with arg") .downcast_ref::<&'static str>() .expect("wrong panic payload type") .contains("yield-wait called with an argument")); // Call yield2 with a yield ID that is unknown but which fits in a u32. let result = catch_unwind(|| unsafe { fake::Syscalls::yield2([2u32.into(), 0u32.into()]) }); assert!(result .expect_err("failed to catch incorrect yield ID -- new ID added?") .downcast_ref::() .expect("wrong panic payload type") .contains("unknown yield ID")); } ================================================ FILE: unittest/src/fake/temperature/mod.rs ================================================ //! Fake implementation of the Temperature API, documented here: //! https://github.com/tock/tock/blob/master/doc/syscalls/60000_ambient_temperature.md //! //! Like the real API, `Temperature` controls a fake temperature sensor. It provides //! a function `set_value` used to immediately call an upcall with a temperature value read by the sensor //! and a function 'set_value_sync' used to call the upcall when the read command is received. use crate::{DriverInfo, DriverShareRef}; use libtock_platform::{CommandReturn, ErrorCode}; use std::cell::Cell; // The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when read command is received, // or None otherwise. It was needed for testing `read_sync` library function which simulates a synchronous temperature read, // because it was impossible to schedule an upcall during the `synchronous` read in other ways. pub struct Temperature { busy: Cell, upcall_on_command: Cell>, share_ref: DriverShareRef, } impl Temperature { pub fn new() -> std::rc::Rc { std::rc::Rc::new(Temperature { busy: Cell::new(false), upcall_on_command: Cell::new(None), share_ref: Default::default(), }) } pub fn is_busy(&self) -> bool { self.busy.get() } pub fn set_value(&self, value: i32) { if self.busy.get() { self.share_ref .schedule_upcall(0, (value as u32, 0, 0)) .expect("Unable to schedule upcall"); self.busy.set(false); } } pub fn set_value_sync(&self, value: i32) { self.upcall_on_command.set(Some(value)); } } impl crate::fake::SyscallDriver for Temperature { fn info(&self) -> DriverInfo { DriverInfo::new(DRIVER_NUM).upcall_count(1) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { match command_id { EXISTS => crate::command_return::success(), READ_TEMP => { if self.busy.get() { return crate::command_return::failure(ErrorCode::Busy); } self.busy.set(true); if let Some(val) = self.upcall_on_command.take() { self.set_value(val); } crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), } } } #[cfg(test)] mod tests; // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- const DRIVER_NUM: u32 = 0x60000; // Command IDs const EXISTS: u32 = 0; const READ_TEMP: u32 = 1; ================================================ FILE: unittest/src/fake/temperature/tests.rs ================================================ use crate::fake::{self, SyscallDriver}; use fake::temperature::*; use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; //Test the command implementation #[test] fn command() { let temp = Temperature::new(); assert!(temp.command(EXISTS, 1, 2).is_success()); assert!(temp.command(READ_TEMP, 0, 0).is_success()); assert_eq!( temp.command(READ_TEMP, 0, 0).get_failure(), Some(ErrorCode::Busy) ); temp.set_value(100); assert!(temp.command(READ_TEMP, 0, 1).is_success()); temp.set_value(100); temp.set_value_sync(100); assert!(temp.command(READ_TEMP, 0, 1).is_success()); assert!(temp.command(READ_TEMP, 0, 1).is_success()); } // Integration test that verifies Temperature works with fake::Kernel and // libtock_platform::Syscalls. #[test] fn kernel_integration() { use libtock_platform::Syscalls; let kernel = fake::Kernel::new(); let temp = Temperature::new(); kernel.add_driver(&temp); assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TEMP, 0, 0).is_success()); assert_eq!( fake::Syscalls::command(DRIVER_NUM, READ_TEMP, 0, 0).get_failure(), Some(ErrorCode::Busy) ); temp.set_value(100); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TEMP, 0, 1).is_success()); let listener = Cell::>::new(None); share::scope(|subscribe| { assert_eq!( fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), Ok(()) ); temp.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert_eq!(listener.get(), Some((100,))); temp.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TEMP, 0, 1).is_success()); temp.set_value(200); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); temp.set_value_sync(200); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TEMP, 0, 1).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); }); } ================================================ FILE: unittest/src/kernel_data.rs ================================================ //! `KernelData` contains the data corresponding to a `fake::Kernel`. It is //! stored in the thread-local variable `KERNEL_DATA`. //! //! The data is stored separately from the `fake::Kernel` because in addition to //! being accessed through the `fake::Kernel`, it is also accessed by //! `fake::Syscalls` and `upcall::schedule`. `fake::Syscalls` is reentrant (a //! Yield invocation can run a callback that executes another system call), //! which easily results in messy code. To keep things understandable, code that //! uses `KERNEL_DATA` should avoid calling user-supplied functions (such as //! upcalls) while holding a reference to `KERNEL_DATA`. use std::cell::RefCell; pub(crate) struct KernelData { pub allow_db: crate::allow_db::AllowDb, // The location of the call to `fake::Kernel::new`. Used in the event a // duplicate `fake::Kernel` is created to tell the user which kernel they // did not clean up in a unit test. pub create_location: &'static std::panic::Location<'static>, pub drivers: std::collections::HashMap, pub expected_syscalls: std::collections::VecDeque, pub syscall_log: Vec, pub upcall_queue: crate::upcall::UpcallQueue, pub memory_break: *const u8, } // KERNEL_DATA is set to Some in `fake::Kernel::new` and set to None when the // `fake::Kernel` is dropped. thread_local!(pub(crate) static KERNEL_DATA: RefCell> = const { RefCell::new(None) }); // Convenience function to get mutable access to KERNEL_DATA. pub(crate) fn with_kernel_data) -> R, R>(f: F) -> R { KERNEL_DATA.with(|refcell| f(refcell.borrow_mut().as_mut())) } // Per-driver data stored in KernelData. pub struct DriverData { pub driver: std::rc::Rc, pub num_upcalls: u32, // Currently-valid upcalls passed to Subscribe. The key is the subscribe // number. pub upcalls: std::collections::HashMap, } ================================================ FILE: unittest/src/lib.rs ================================================ //! `libtock_unittest` provides testing tools needed by `libtock-rs`'s own unit //! tests as well as unit tests of code that uses `libtock-rs`. #![deny(unsafe_op_in_unsafe_fn)] mod allow_db; pub mod command_return; mod driver_info; #[cfg(not(miri))] mod exit_test; mod expected_syscall; pub mod fake; mod kernel_data; mod share_data; mod syscall_log; pub mod upcall; pub use allow_db::{RoAllowBuffer, RwAllowBuffer}; pub use driver_info::DriverInfo; #[cfg(not(miri))] pub use exit_test::{exit_test, ExitCall}; pub use expected_syscall::ExpectedSyscall; pub use share_data::DriverShareRef; pub use syscall_log::SyscallLogEntry; #[cfg(test)] mod allow_db_test; ================================================ FILE: unittest/src/share_data.rs ================================================ use crate::kernel_data::with_kernel_data; use crate::upcall::{UpcallId, UpcallQueueEntry}; use std::cell::Cell; /// A reference used by a `fake::SyscallDriver` to access data shared between it /// and the `fake::Kernel`, such as upcalls. /// /// A `Default`-initialized `DriverShareRef` links to an empty share. It can be /// used as normal, but is generally a no-op. This allows `fake::SyscallDriver` /// implementations to store a `DriverShareRef` directly, rather than having to /// contain a `Cell>`. #[derive(Default)] pub struct DriverShareRef { pub(crate) driver_num: Cell, } impl DriverShareRef { /// Replaces this DriverShareRef with another. `fake:SyscallDrivers` can use /// `replace` to implement `register` to avoid having to store their /// `DriverShareRef` inside a `Cell`. pub fn replace(&self, new: Self) { self.driver_num.set(new.driver_num.get()); } /// Schedules the upcall with the specified subscribe number. Like the real /// kernel, this does nothing if there is no upcall with number /// `subscribe_num` or the upcall is the null upcall. pub fn schedule_upcall( &self, subscribe_num: u32, args: (u32, u32, u32), ) -> Result<(), InvalidSubscribeNum> { with_kernel_data(|kernel_data| { let kernel_data = match kernel_data { Some(kernel_data) => kernel_data, None => return Ok(()), }; let driver_data = kernel_data .drivers .get(&self.driver_num.get()) .expect("DriverShareRef: registered but nonexistent?"); if subscribe_num >= driver_data.num_upcalls { return Err(InvalidSubscribeNum { upcall_count: driver_data.num_upcalls, requested: subscribe_num, }); } let upcall = match driver_data.upcalls.get(&subscribe_num) { Some(&upcall) => upcall, None => return Ok(()), }; // Don't bother queueing a null upcall, as they don't do anything // when invoked anyway, and the core kernel does not queue them // either. if upcall.is_null() { return Ok(()); } kernel_data.upcall_queue.push_back(UpcallQueueEntry { args, id: UpcallId { driver_num: self.driver_num.get(), subscribe_num, }, upcall, }); Ok(()) }) } } #[derive(Debug, Eq, PartialEq, thiserror::Error)] #[error("Upcall number {requested} too large, expected < {upcall_count}.")] pub struct InvalidSubscribeNum { requested: u32, upcall_count: u32, } #[cfg(test)] mod tests { use super::*; use crate::upcall::Upcall; use crate::DriverInfo; use libtock_platform::Register; use std::ptr::fn_addr_eq; use std::rc::Rc; #[derive(Default)] struct MockDriver { share_ref: DriverShareRef, } impl crate::fake::SyscallDriver for MockDriver { fn info(&self) -> DriverInfo { DriverInfo::new(1).upcall_count(10) } fn register(&self, share_ref: DriverShareRef) { self.share_ref.replace(share_ref); } fn command(&self, _: u32, _: u32, _: u32) -> libtock_platform::CommandReturn { crate::command_return::failure(libtock_platform::ErrorCode::NoSupport) } } #[test] fn schedule_errors() { let mock_driver = Rc::new(MockDriver::default()); let kernel = crate::fake::Kernel::new(); kernel.add_driver(&mock_driver); assert_eq!( mock_driver.share_ref.schedule_upcall(10, (3, 4, 5)), Err(InvalidSubscribeNum { upcall_count: 10, requested: 10 }) ); } #[test] fn schedule_success() { let mock_driver = Rc::new(MockDriver::default()); let kernel = crate::fake::Kernel::new(); kernel.add_driver(&mock_driver); // Call schedule with no registered upcall. assert_eq!(mock_driver.share_ref.schedule_upcall(2, (3, 4, 5)), Ok(())); with_kernel_data(|kernel_data| { let kernel_data = kernel_data.unwrap(); // There was no upcall to schedule, so the queue should still be // empty. assert!(kernel_data.upcall_queue.is_empty()); // Register a null upcall. kernel_data.drivers.get_mut(&1).unwrap().upcalls.insert( 2, Upcall { fn_pointer: None, data: 1234u32.into(), }, ); }); // Call schedule again. This should still do nothing, because the upcall // is a null upcall. assert_eq!(mock_driver.share_ref.schedule_upcall(2, (3, 4, 5)), Ok(())); unsafe extern "C" fn upcall(_: u32, _: u32, _: u32, _: libtock_platform::Register) {} // Cast to a pointer to get a stable address. let upcall_ptr = upcall as unsafe extern "C" fn(u32, u32, u32, Register); with_kernel_data(|kernel_data| { let kernel_data = kernel_data.unwrap(); // Verify the upcall was not queued. assert!(kernel_data.upcall_queue.is_empty()); // Register a non-null upcall. kernel_data.drivers.get_mut(&1).unwrap().upcalls.insert( 2, Upcall { fn_pointer: Some(upcall_ptr), data: 1111usize.into(), }, ); }); // Call schedule again. This should schedule the upcall. assert_eq!(mock_driver.share_ref.schedule_upcall(2, (3, 4, 5)), Ok(())); with_kernel_data(|kernel_data| { let kernel_data = kernel_data.unwrap(); // Verify the upcall was queued. assert_eq!(kernel_data.upcall_queue.len(), 1); let upcall_queue_entry = kernel_data.upcall_queue.front().expect("Upcall not queued"); assert_eq!(upcall_queue_entry.args, (3, 4, 5)); assert_eq!( upcall_queue_entry.id, UpcallId { driver_num: 1, subscribe_num: 2 } ); assert!(fn_addr_eq( upcall_queue_entry.upcall.fn_pointer.unwrap(), upcall_ptr )); let data: usize = upcall_queue_entry.upcall.data.into(); assert_eq!(data, 1111); // Register a non-null upcall. kernel_data.drivers.get_mut(&1).unwrap().upcalls.insert( 2, Upcall { fn_pointer: Some(upcall_ptr), data: 2222u32.into(), }, ); }); // Call schedule again. This should schedule another upcall, after the // first. assert_eq!( mock_driver.share_ref.schedule_upcall(2, (30, 40, 50)), Ok(()) ); with_kernel_data(|kernel_data| { let kernel_data = kernel_data.unwrap(); // Very the upcall was queued. assert_eq!(kernel_data.upcall_queue.len(), 2); let front_queue_entry = kernel_data.upcall_queue.front().expect("Upcall not queued"); assert_eq!(front_queue_entry.args, (3, 4, 5)); assert_eq!( front_queue_entry.id, UpcallId { driver_num: 1, subscribe_num: 2 } ); assert!(fn_addr_eq( front_queue_entry.upcall.fn_pointer.unwrap(), upcall_ptr )); let front_data: usize = front_queue_entry.upcall.data.into(); assert_eq!(front_data, 1111); let back_queue_entry = kernel_data.upcall_queue.back().expect("Upcall not queued"); assert_eq!(back_queue_entry.args, (30, 40, 50)); assert_eq!( back_queue_entry.id, UpcallId { driver_num: 1, subscribe_num: 2 } ); assert!(fn_addr_eq( back_queue_entry.upcall.fn_pointer.unwrap(), upcall_ptr )); let back_data: usize = back_queue_entry.upcall.data.into(); assert_eq!(back_data, 2222); }); } } ================================================ FILE: unittest/src/syscall_log.rs ================================================ use libtock_platform::Register; /// SyscallLogEntry represents a system call made during test execution. #[derive(Debug, Eq, PartialEq)] pub enum SyscallLogEntry { // ------------------------------------------------------------------------- // Yield // ------------------------------------------------------------------------- YieldNoWait, YieldWait, // ------------------------------------------------------------------------- // Subscribe // ------------------------------------------------------------------------- Subscribe { driver_num: u32, subscribe_num: u32, }, // ------------------------------------------------------------------------- // Command // ------------------------------------------------------------------------- Command { driver_id: u32, command_id: u32, argument0: u32, argument1: u32, }, // ------------------------------------------------------------------------- // Read-Only Allow // ------------------------------------------------------------------------- AllowRo { driver_num: u32, buffer_num: u32, len: usize, }, // ------------------------------------------------------------------------- // Read-Write Allow // ------------------------------------------------------------------------- AllowRw { driver_num: u32, buffer_num: u32, len: usize, }, // ------------------------------------------------------------------------- // Memop // ------------------------------------------------------------------------- Memop { memop_num: u32, argument0: Register, // Necessary for Miri ptr provenance of brk() }, // TODO: Add Exit. } ================================================ FILE: unittest/src/upcall.rs ================================================ /// Raw upcall data, as it was passed to Subscribe. This upcall is not /// guaranteed to still be valid. #[derive(Clone, Copy)] pub struct Upcall { pub fn_pointer: Option, pub data: libtock_platform::Register, } impl Upcall { /// Returns true if this is a null callback, false otherwise. pub fn is_null(&self) -> bool { self.fn_pointer.is_none() } /// # Safety /// An upcall may only be invoked if it is still active. As described in TRD /// 104, an upcall is still active if it has not been replaced by another /// Subscribe call with the same upcall ID. All upcalls in the upcall queue /// in KernelData are active. pub unsafe fn invoke(&self, args: (u32, u32, u32)) { if let Some(fn_pointer) = self.fn_pointer { unsafe { fn_pointer(args.0, args.1, args.2, self.data); } } } } // The type of the upcall queue in KernelData. Contains queued upcalls, which // are waiting to be invoked during a Yield call. // // Based on this discussion: // https://mailman.stanford.edu/pipermail/tock-version2/2020-November/000025.html // this queue is a FIFO queue. New entries should be pushed to the back of the // queue, and Yield should invoke upcalls starting with the front of the queue. // // A note on performance: When an upcall is replaced via Subscribe and becomes // invalid, Subscribe has to iterate through this queue and remove all instances // of that upcall. That takes linear time in the length of the queue, so it can // be slow if the queue is long. A long queue is unrealistic, so we shouldn't // need a long queue in test cases, so that is acceptable. There are alternative // data structures that avoid that slowdown, but they are more complex and // likely slower in the common case. pub(crate) type UpcallQueue = std::collections::VecDeque; // An entry in the fake kernel's upcall queue. pub(crate) struct UpcallQueueEntry { pub args: (u32, u32, u32), pub id: UpcallId, pub upcall: Upcall, } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub(crate) struct UpcallId { pub driver_num: u32, pub subscribe_num: u32, }