From d3445ef583f4070fe04337b21cf06c291e7021c7 Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Tue, 23 Jan 2024 18:15:08 +0000 Subject: [PATCH 1/8] Add dongle BSP and basic puzzle firmware. Still need to integrate puzzlegen into build process for puzzle-fw and set up CI to use a Github secret to store the phrase. --- build.sh | 8 + .../{ => dongle-fw}/dongle/.cargo/config.toml | 0 nrf52-code/boards/dongle-fw/dongle/README.md | 131 +++++ .../dongle/deprecated_hex/README.md | 0 .../deprecated_hex/loopback-nousb11.hex | 0 .../deprecated_hex/loopback-nousb16.hex | 0 .../deprecated_hex/loopback-nousb21.hex | 0 .../deprecated_hex/loopback-nousb26.hex | 0 .../dongle/deprecated_hex/loopback.hex | 0 .../dongle/deprecated_hex/puzzle-nousb11.hex | 0 .../dongle/deprecated_hex/puzzle-nousb16.hex | 0 .../dongle/deprecated_hex/puzzle-nousb21.hex | 0 .../dongle/deprecated_hex/puzzle-nousb26.hex | 0 .../dongle/deprecated_hex/puzzle.hex | 0 .../boards/{ => dongle-fw}/dongle/loopback | Bin .../{ => dongle-fw}/dongle/loopback-2020 | Bin .../{ => dongle-fw}/dongle/loopback-nousb11 | Bin .../{ => dongle-fw}/dongle/loopback-nousb16 | Bin .../{ => dongle-fw}/dongle/loopback-nousb21 | Bin .../{ => dongle-fw}/dongle/loopback-nousb26 | Bin nrf52-code/boards/dongle-fw/dongle/memory.x | 11 + .../boards/{ => dongle-fw}/dongle/puzzle | Bin .../boards/{ => dongle-fw}/dongle/puzzle-2020 | Bin .../{ => dongle-fw}/dongle/puzzle-nousb11 | Bin .../{ => dongle-fw}/dongle/puzzle-nousb16 | Bin .../{ => dongle-fw}/dongle/puzzle-nousb21 | Bin .../{ => dongle-fw}/dongle/puzzle-nousb26 | Bin .../{ => dongle-fw}/dongle/puzzlegen.rs | 0 nrf52-code/boards/dongle/.gitignore | 1 + nrf52-code/boards/dongle/Cargo.toml | 16 + nrf52-code/boards/dongle/README.md | 130 +---- nrf52-code/boards/dongle/build.rs | 12 + nrf52-code/boards/dongle/memory.x | 11 +- nrf52-code/boards/dongle/src/lib.rs | 361 +++++++++++++ nrf52-code/hal-app/Cargo.lock | 10 + nrf52-code/puzzle-fw/.cargo/config.toml | 13 + nrf52-code/puzzle-fw/.gitignore | 1 + nrf52-code/puzzle-fw/Cargo.lock | 506 ++++++++++++++++++ nrf52-code/puzzle-fw/Cargo.toml | 38 ++ nrf52-code/puzzle-fw/build.rs | 11 + nrf52-code/puzzle-fw/src/main.rs | 250 +++++++++ 41 files changed, 1374 insertions(+), 136 deletions(-) rename nrf52-code/boards/{ => dongle-fw}/dongle/.cargo/config.toml (100%) create mode 100644 nrf52-code/boards/dongle-fw/dongle/README.md rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/README.md (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/loopback-nousb11.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/loopback-nousb16.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/loopback-nousb21.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/loopback-nousb26.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/loopback.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/puzzle-nousb11.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/puzzle-nousb16.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/puzzle-nousb21.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/puzzle-nousb26.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/deprecated_hex/puzzle.hex (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/loopback (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/loopback-2020 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/loopback-nousb11 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/loopback-nousb16 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/loopback-nousb21 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/loopback-nousb26 (100%) create mode 100644 nrf52-code/boards/dongle-fw/dongle/memory.x rename nrf52-code/boards/{ => dongle-fw}/dongle/puzzle (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/puzzle-2020 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/puzzle-nousb11 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/puzzle-nousb16 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/puzzle-nousb21 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/puzzle-nousb26 (100%) rename nrf52-code/boards/{ => dongle-fw}/dongle/puzzlegen.rs (100%) create mode 100644 nrf52-code/boards/dongle/.gitignore create mode 100644 nrf52-code/boards/dongle/Cargo.toml create mode 100644 nrf52-code/boards/dongle/build.rs create mode 100644 nrf52-code/boards/dongle/src/lib.rs create mode 100644 nrf52-code/puzzle-fw/.cargo/config.toml create mode 100644 nrf52-code/puzzle-fw/.gitignore create mode 100644 nrf52-code/puzzle-fw/Cargo.lock create mode 100644 nrf52-code/puzzle-fw/Cargo.toml create mode 100644 nrf52-code/puzzle-fw/build.rs create mode 100644 nrf52-code/puzzle-fw/src/main.rs diff --git a/build.sh b/build.sh index 499f6d01..e95f77a9 100755 --- a/build.sh +++ b/build.sh @@ -27,6 +27,10 @@ pushd boards/dk-solution cargo build --target=thumbv7em-none-eabihf cargo fmt --check popd +pushd boards/dongle +cargo build --target=thumbv7em-none-eabihf +cargo fmt --check +popd pushd radio-app cargo build --target=thumbv7em-none-eabihf --release cargo fmt --check @@ -49,6 +53,10 @@ pushd consts cargo build cargo fmt --check popd +pushd puzzle-fw +cargo build --target=thumbv7em-none-eabihf --release +cargo fmt --check +popd popd # Only build the templates (they will panic at run-time due to the use of todo!) diff --git a/nrf52-code/boards/dongle/.cargo/config.toml b/nrf52-code/boards/dongle-fw/dongle/.cargo/config.toml similarity index 100% rename from nrf52-code/boards/dongle/.cargo/config.toml rename to nrf52-code/boards/dongle-fw/dongle/.cargo/config.toml diff --git a/nrf52-code/boards/dongle-fw/dongle/README.md b/nrf52-code/boards/dongle-fw/dongle/README.md new file mode 100644 index 00000000..dd9ac171 --- /dev/null +++ b/nrf52-code/boards/dongle-fw/dongle/README.md @@ -0,0 +1,131 @@ +# `dongle` + +Pre-made applications for the nRF52840 Dongle. + +These applications will be used in the radio workshop. + +## Hardware + +### LEDs + +- The green LED (LD1) is connected to pin P0.6 +- The red channel of the RGB LED (LD2) is connected to pin P0.8 +- The green channel of the RGB LED (LD2) is connected to pin P**1**.9 +- The blue channel of the RGB LED (LD2) is connected to pin P0.12 + +Both LEDs are mounted near the USB connector. + +### Buttons + +- The Reset button is mounted sideways near the edge of the board that's opposite of the USB connector. +- The SW1 button is connected to pin P**1**.06. This round-ish button is right next to the RESET button but closer to the USB connector. + +## Changing the puzzle secret string + +### Create the secret string + +Run the `puzzlegen.rs` program on the host to create a new secret string. + +```console +cargo new --bin puzzlegen +cp puzzlegen.rs puzzlegen/src/main.rs +cd puzzlegen +# or manually modify the Cargo.toml +cargo add rand +# update the plaintext in `src/main.rs` +cargo run +``` + +Take note of the output; it will look like this: + +```text +from: [116, 68, 123, 97, 47, 46, 90, 120, 34, 49, 59, 39, 50, 106, 71, 75, 108, 115, 81, 117, 69, 57, 76, 41, 100, 38, 93, 58, 78, 126, 70, 56, 84, 111, 113, 91, 89, 55, 40, 114, 122, 52, 61, 64, 45, 79, 67, 83, 48, 66, 63, 104, 43, 77, 44, 54, 98, 92, 94, 60, 62, 118, 87, 80, 95, 74, 65, 112, 109, 73, 110, 101, 53, 86, 33, 121, 42, 35, 85, 82, 105, 36, 37, 119, 125, 51, 96, 99, 88, 32, 103, 72, 107, 124, 102] +to: [81, 78, 109, 61, 120, 87, 125, 98, 100, 91, 97, 66, 57, 117, 49, 64, 48, 85, 75, 73, 92, 101, 83, 110, 62, 89, 35, 37, 93, 71, 123, 121, 60, 38, 115, 102, 59, 47, 108, 80, 58, 44, 86, 111, 41, 84, 96, 50, 51, 70, 43, 112, 79, 46, 113, 107, 106, 116, 65, 68, 69, 77, 105, 56, 103, 67, 40, 54, 99, 55, 45, 63, 34, 88, 119, 74, 94, 32, 114, 36, 95, 124, 118, 76, 126, 72, 82, 122, 33, 104, 90, 39, 42, 52, 53] +secret: ">?>h$IUQhL&P*Up&6w" +``` + +### Generate `puzzle` ELF + +```console +git clone --branch dongle-puzzle https://github.com/japaric/embedded2020 +cd embedded2020/firmware/apps +``` + +Find `puzzle.rs` in the `embedded2020/firmware/apps/src/bin` folder. + +Update `puzzle.rs` with the `FROM`, `TO` and `SECRET` data that you got from `puzzlegen` + +````rust +static FROM: &[u8] = &[ + 116, 68, 123, 97, 47, 46, 90, 120, 34, 49, 59, 39, 50, 106, 71, 75, 108, 115, 81, 117, 69, 57, + 76, 41, 100, 38, 93, 58, 78, 126, 70, 56, 84, 111, 113, 91, 89, 55, 40, 114, 122, 52, 61, 64, + 45, 79, 67, 83, 48, 66, 63, 104, 43, 77, 44, 54, 98, 92, 94, 60, 62, 118, 87, 80, 95, 74, 65, + 112, 109, 73, 110, 101, 53, 86, 33, 121, 42, 35, 85, 82, 105, 36, 37, 119, 125, 51, 96, 99, 88, + 32, 103, 72, 107, 124, 102, +]; + +static TO: &[u8] = &[ + 81, 78, 109, 61, 120, 87, 125, 98, 100, 91, 97, 66, 57, 117, 49, 64, 48, 85, 75, 73, 92, 101, + 83, 110, 62, 89, 35, 37, 93, 71, 123, 121, 60, 38, 115, 102, 59, 47, 108, 80, 58, 44, 86, 111, + 41, 84, 96, 50, 51, 70, 43, 112, 79, 46, 113, 107, 106, 116, 65, 68, 69, 77, 105, 56, 103, 67, + 40, 54, 99, 55, 45, 63, 34, 88, 119, 74, 94, 32, 114, 36, 95, 124, 118, 76, 126, 72, 82, 122, + 33, 104, 90, 39, 42, 52, 53, +]; + +// store the secret rather than the plaintext -- otherwise `strings $elf` will reveal the answer +static SECRET: &[u8] = b">?>h$IUQhL&P*Up&6w"; +```` + +Build the program; this will produce an ELF file called `puzzle` (no file ending). + +```console +cargo build --bin puzzle --release +``` + +Copy this ELF file from `embedded2020/firmware/target/thumbv7em-none-eabi/release` to `embedded-trainings-2020/boards/dongle` + +Test the produced `puzzle` file: + +- flash it onto a dongle using `nrfdfu puzzle`. The green LED on the dongle should turn on +- run `cargo xtask serial-term`; you should see the following output. `deviceid` will be different + + ```text + deviceid=d90eedf1978d5fd2 channel=25 TxPower=+8dBm app=puzzle + ``` + +- run the `radio-puzzle-solution` program on a DK; it should be able to decrypt the new secret +- run `cargo xtask change-channel ` to test changing the Dongle's radio channel +- modify and re-run the `radio-puzzle-solution` program on a DK to solve the puzzle using a the channel you set in the previous step + +### Generate `puzzle-nousb-*` + +The procedure is similar to the one for generating the `puzzle` ELF file. The differences are: + +- you build `puzzle-nousb.rs` in the `embedded2020` repository and copy `embedded2020/firmware/target/thumbv7em-none-eabi/release/puzzle-nousb` over +- you also need to change `const CHANNEL` in `puzzle-nousb.rs` +- you need to produce one ELF file per hard-coded radio channel. + +Also test these `nousb` ELF files. Note that the green LED won't turn on when the dongle restarts! The green LED will toggle when a new packet is received and the blue LED will turn on when the decoded secret is received. Also, `cargo xtask change-channel` won't work with the `nousb` variants so you can skip that test. + +## Addendum + +In October 2023, Jonathan changed the USB Vendor ID from 0x2020 to the 0x1209 suggested by https://pid.codes. Rather than find the old +firmware source code and recompile it, it was easier to patch the binaries. + +```console +$ diff <(hexdump -C puzzle) <(hexdump -C puzzle-1209) +2363c2363 +< 000131b0 00 00 02 12 01 00 02 ef 02 01 40 20 20 09 03 00 |..........@ ...| +--- +> 000131b0 00 00 02 12 01 00 02 ef 02 01 40 09 12 09 03 00 |..........@.....| +$ diff <(hexdump -C loopback) <(hexdump -C loopback-1209) +2344c2344 +< 000131b0 00 00 02 12 01 00 02 ef 02 01 40 20 20 09 03 00 |..........@ ...| +--- +> 000131b0 00 00 02 12 01 00 02 ef 02 01 40 09 12 09 03 00 |..........@.....| +$ +``` + +## References + +- [nRF52840 Dongle section on Nordic Semiconductor's info center](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_getting_started%2FUG%2Fgs%2Fdevelop_sw.html&cp=1_0_2) diff --git a/nrf52-code/boards/dongle/deprecated_hex/README.md b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/README.md similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/README.md rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/README.md diff --git a/nrf52-code/boards/dongle/deprecated_hex/loopback-nousb11.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb11.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/loopback-nousb11.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb11.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/loopback-nousb16.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb16.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/loopback-nousb16.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb16.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/loopback-nousb21.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb21.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/loopback-nousb21.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb21.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/loopback-nousb26.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb26.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/loopback-nousb26.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb26.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/loopback.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/loopback.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/puzzle-nousb11.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb11.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/puzzle-nousb11.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb11.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/puzzle-nousb16.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb16.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/puzzle-nousb16.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb16.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/puzzle-nousb21.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb21.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/puzzle-nousb21.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb21.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/puzzle-nousb26.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb26.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/puzzle-nousb26.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb26.hex diff --git a/nrf52-code/boards/dongle/deprecated_hex/puzzle.hex b/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle.hex similarity index 100% rename from nrf52-code/boards/dongle/deprecated_hex/puzzle.hex rename to nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle.hex diff --git a/nrf52-code/boards/dongle/loopback b/nrf52-code/boards/dongle-fw/dongle/loopback similarity index 100% rename from nrf52-code/boards/dongle/loopback rename to nrf52-code/boards/dongle-fw/dongle/loopback diff --git a/nrf52-code/boards/dongle/loopback-2020 b/nrf52-code/boards/dongle-fw/dongle/loopback-2020 similarity index 100% rename from nrf52-code/boards/dongle/loopback-2020 rename to nrf52-code/boards/dongle-fw/dongle/loopback-2020 diff --git a/nrf52-code/boards/dongle/loopback-nousb11 b/nrf52-code/boards/dongle-fw/dongle/loopback-nousb11 similarity index 100% rename from nrf52-code/boards/dongle/loopback-nousb11 rename to nrf52-code/boards/dongle-fw/dongle/loopback-nousb11 diff --git a/nrf52-code/boards/dongle/loopback-nousb16 b/nrf52-code/boards/dongle-fw/dongle/loopback-nousb16 similarity index 100% rename from nrf52-code/boards/dongle/loopback-nousb16 rename to nrf52-code/boards/dongle-fw/dongle/loopback-nousb16 diff --git a/nrf52-code/boards/dongle/loopback-nousb21 b/nrf52-code/boards/dongle-fw/dongle/loopback-nousb21 similarity index 100% rename from nrf52-code/boards/dongle/loopback-nousb21 rename to nrf52-code/boards/dongle-fw/dongle/loopback-nousb21 diff --git a/nrf52-code/boards/dongle/loopback-nousb26 b/nrf52-code/boards/dongle-fw/dongle/loopback-nousb26 similarity index 100% rename from nrf52-code/boards/dongle/loopback-nousb26 rename to nrf52-code/boards/dongle-fw/dongle/loopback-nousb26 diff --git a/nrf52-code/boards/dongle-fw/dongle/memory.x b/nrf52-code/boards/dongle-fw/dongle/memory.x new file mode 100644 index 00000000..7ad752ba --- /dev/null +++ b/nrf52-code/boards/dongle-fw/dongle/memory.x @@ -0,0 +1,11 @@ +MEMORY +{ + /* Bootloader is split in 2 parts: the first part lives in the range + 0..0x1000; the second part lives at the end of the 1 MB Flash. The range + selected here collides with neither */ + FLASH : ORIGIN = 0x1000, LENGTH = 0x7f000 + + /* The bootloader uses the first 8 bytes of RAM to preserve state so don't + touch them */ + RAM : ORIGIN = 0x20000008, LENGTH = 0x3fff8 +} diff --git a/nrf52-code/boards/dongle/puzzle b/nrf52-code/boards/dongle-fw/dongle/puzzle similarity index 100% rename from nrf52-code/boards/dongle/puzzle rename to nrf52-code/boards/dongle-fw/dongle/puzzle diff --git a/nrf52-code/boards/dongle/puzzle-2020 b/nrf52-code/boards/dongle-fw/dongle/puzzle-2020 similarity index 100% rename from nrf52-code/boards/dongle/puzzle-2020 rename to nrf52-code/boards/dongle-fw/dongle/puzzle-2020 diff --git a/nrf52-code/boards/dongle/puzzle-nousb11 b/nrf52-code/boards/dongle-fw/dongle/puzzle-nousb11 similarity index 100% rename from nrf52-code/boards/dongle/puzzle-nousb11 rename to nrf52-code/boards/dongle-fw/dongle/puzzle-nousb11 diff --git a/nrf52-code/boards/dongle/puzzle-nousb16 b/nrf52-code/boards/dongle-fw/dongle/puzzle-nousb16 similarity index 100% rename from nrf52-code/boards/dongle/puzzle-nousb16 rename to nrf52-code/boards/dongle-fw/dongle/puzzle-nousb16 diff --git a/nrf52-code/boards/dongle/puzzle-nousb21 b/nrf52-code/boards/dongle-fw/dongle/puzzle-nousb21 similarity index 100% rename from nrf52-code/boards/dongle/puzzle-nousb21 rename to nrf52-code/boards/dongle-fw/dongle/puzzle-nousb21 diff --git a/nrf52-code/boards/dongle/puzzle-nousb26 b/nrf52-code/boards/dongle-fw/dongle/puzzle-nousb26 similarity index 100% rename from nrf52-code/boards/dongle/puzzle-nousb26 rename to nrf52-code/boards/dongle-fw/dongle/puzzle-nousb26 diff --git a/nrf52-code/boards/dongle/puzzlegen.rs b/nrf52-code/boards/dongle-fw/dongle/puzzlegen.rs similarity index 100% rename from nrf52-code/boards/dongle/puzzlegen.rs rename to nrf52-code/boards/dongle-fw/dongle/puzzlegen.rs diff --git a/nrf52-code/boards/dongle/.gitignore b/nrf52-code/boards/dongle/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/nrf52-code/boards/dongle/.gitignore @@ -0,0 +1 @@ +target diff --git a/nrf52-code/boards/dongle/Cargo.toml b/nrf52-code/boards/dongle/Cargo.toml new file mode 100644 index 00000000..b1de4840 --- /dev/null +++ b/nrf52-code/boards/dongle/Cargo.toml @@ -0,0 +1,16 @@ +[package] +authors = ["Ferrous Systems"] +edition = "2018" +license = "MIT OR Apache-2.0" +name = "dongle" +version = "0.0.0" + +[dependencies] +cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]} +cortex-m-rt = "0.7.2" +cortex-m-semihosting = "0.5.0" +defmt = "0.3.5" +defmt-rtt = "0.4" +embedded-hal = "0.2.7" +hal = { package = "nrf52840-hal", version = "0.16.0" } +panic-probe = { version = "0.3.0", features = ["print-defmt"] } diff --git a/nrf52-code/boards/dongle/README.md b/nrf52-code/boards/dongle/README.md index dd9ac171..f90f583d 100644 --- a/nrf52-code/boards/dongle/README.md +++ b/nrf52-code/boards/dongle/README.md @@ -1,131 +1,5 @@ # `dongle` -Pre-made applications for the nRF52840 Dongle. +Board Support Package (BSP) for the nRF52840 USB Dongle -These applications will be used in the radio workshop. - -## Hardware - -### LEDs - -- The green LED (LD1) is connected to pin P0.6 -- The red channel of the RGB LED (LD2) is connected to pin P0.8 -- The green channel of the RGB LED (LD2) is connected to pin P**1**.9 -- The blue channel of the RGB LED (LD2) is connected to pin P0.12 - -Both LEDs are mounted near the USB connector. - -### Buttons - -- The Reset button is mounted sideways near the edge of the board that's opposite of the USB connector. -- The SW1 button is connected to pin P**1**.06. This round-ish button is right next to the RESET button but closer to the USB connector. - -## Changing the puzzle secret string - -### Create the secret string - -Run the `puzzlegen.rs` program on the host to create a new secret string. - -```console -cargo new --bin puzzlegen -cp puzzlegen.rs puzzlegen/src/main.rs -cd puzzlegen -# or manually modify the Cargo.toml -cargo add rand -# update the plaintext in `src/main.rs` -cargo run -``` - -Take note of the output; it will look like this: - -```text -from: [116, 68, 123, 97, 47, 46, 90, 120, 34, 49, 59, 39, 50, 106, 71, 75, 108, 115, 81, 117, 69, 57, 76, 41, 100, 38, 93, 58, 78, 126, 70, 56, 84, 111, 113, 91, 89, 55, 40, 114, 122, 52, 61, 64, 45, 79, 67, 83, 48, 66, 63, 104, 43, 77, 44, 54, 98, 92, 94, 60, 62, 118, 87, 80, 95, 74, 65, 112, 109, 73, 110, 101, 53, 86, 33, 121, 42, 35, 85, 82, 105, 36, 37, 119, 125, 51, 96, 99, 88, 32, 103, 72, 107, 124, 102] -to: [81, 78, 109, 61, 120, 87, 125, 98, 100, 91, 97, 66, 57, 117, 49, 64, 48, 85, 75, 73, 92, 101, 83, 110, 62, 89, 35, 37, 93, 71, 123, 121, 60, 38, 115, 102, 59, 47, 108, 80, 58, 44, 86, 111, 41, 84, 96, 50, 51, 70, 43, 112, 79, 46, 113, 107, 106, 116, 65, 68, 69, 77, 105, 56, 103, 67, 40, 54, 99, 55, 45, 63, 34, 88, 119, 74, 94, 32, 114, 36, 95, 124, 118, 76, 126, 72, 82, 122, 33, 104, 90, 39, 42, 52, 53] -secret: ">?>h$IUQhL&P*Up&6w" -``` - -### Generate `puzzle` ELF - -```console -git clone --branch dongle-puzzle https://github.com/japaric/embedded2020 -cd embedded2020/firmware/apps -``` - -Find `puzzle.rs` in the `embedded2020/firmware/apps/src/bin` folder. - -Update `puzzle.rs` with the `FROM`, `TO` and `SECRET` data that you got from `puzzlegen` - -````rust -static FROM: &[u8] = &[ - 116, 68, 123, 97, 47, 46, 90, 120, 34, 49, 59, 39, 50, 106, 71, 75, 108, 115, 81, 117, 69, 57, - 76, 41, 100, 38, 93, 58, 78, 126, 70, 56, 84, 111, 113, 91, 89, 55, 40, 114, 122, 52, 61, 64, - 45, 79, 67, 83, 48, 66, 63, 104, 43, 77, 44, 54, 98, 92, 94, 60, 62, 118, 87, 80, 95, 74, 65, - 112, 109, 73, 110, 101, 53, 86, 33, 121, 42, 35, 85, 82, 105, 36, 37, 119, 125, 51, 96, 99, 88, - 32, 103, 72, 107, 124, 102, -]; - -static TO: &[u8] = &[ - 81, 78, 109, 61, 120, 87, 125, 98, 100, 91, 97, 66, 57, 117, 49, 64, 48, 85, 75, 73, 92, 101, - 83, 110, 62, 89, 35, 37, 93, 71, 123, 121, 60, 38, 115, 102, 59, 47, 108, 80, 58, 44, 86, 111, - 41, 84, 96, 50, 51, 70, 43, 112, 79, 46, 113, 107, 106, 116, 65, 68, 69, 77, 105, 56, 103, 67, - 40, 54, 99, 55, 45, 63, 34, 88, 119, 74, 94, 32, 114, 36, 95, 124, 118, 76, 126, 72, 82, 122, - 33, 104, 90, 39, 42, 52, 53, -]; - -// store the secret rather than the plaintext -- otherwise `strings $elf` will reveal the answer -static SECRET: &[u8] = b">?>h$IUQhL&P*Up&6w"; -```` - -Build the program; this will produce an ELF file called `puzzle` (no file ending). - -```console -cargo build --bin puzzle --release -``` - -Copy this ELF file from `embedded2020/firmware/target/thumbv7em-none-eabi/release` to `embedded-trainings-2020/boards/dongle` - -Test the produced `puzzle` file: - -- flash it onto a dongle using `nrfdfu puzzle`. The green LED on the dongle should turn on -- run `cargo xtask serial-term`; you should see the following output. `deviceid` will be different - - ```text - deviceid=d90eedf1978d5fd2 channel=25 TxPower=+8dBm app=puzzle - ``` - -- run the `radio-puzzle-solution` program on a DK; it should be able to decrypt the new secret -- run `cargo xtask change-channel ` to test changing the Dongle's radio channel -- modify and re-run the `radio-puzzle-solution` program on a DK to solve the puzzle using a the channel you set in the previous step - -### Generate `puzzle-nousb-*` - -The procedure is similar to the one for generating the `puzzle` ELF file. The differences are: - -- you build `puzzle-nousb.rs` in the `embedded2020` repository and copy `embedded2020/firmware/target/thumbv7em-none-eabi/release/puzzle-nousb` over -- you also need to change `const CHANNEL` in `puzzle-nousb.rs` -- you need to produce one ELF file per hard-coded radio channel. - -Also test these `nousb` ELF files. Note that the green LED won't turn on when the dongle restarts! The green LED will toggle when a new packet is received and the blue LED will turn on when the decoded secret is received. Also, `cargo xtask change-channel` won't work with the `nousb` variants so you can skip that test. - -## Addendum - -In October 2023, Jonathan changed the USB Vendor ID from 0x2020 to the 0x1209 suggested by https://pid.codes. Rather than find the old -firmware source code and recompile it, it was easier to patch the binaries. - -```console -$ diff <(hexdump -C puzzle) <(hexdump -C puzzle-1209) -2363c2363 -< 000131b0 00 00 02 12 01 00 02 ef 02 01 40 20 20 09 03 00 |..........@ ...| ---- -> 000131b0 00 00 02 12 01 00 02 ef 02 01 40 09 12 09 03 00 |..........@.....| -$ diff <(hexdump -C loopback) <(hexdump -C loopback-1209) -2344c2344 -< 000131b0 00 00 02 12 01 00 02 ef 02 01 40 20 20 09 03 00 |..........@ ...| ---- -> 000131b0 00 00 02 12 01 00 02 ef 02 01 40 09 12 09 03 00 |..........@.....| -$ -``` - -## References - -- [nRF52840 Dongle section on Nordic Semiconductor's info center](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_getting_started%2FUG%2Fgs%2Fdevelop_sw.html&cp=1_0_2) +See diff --git a/nrf52-code/boards/dongle/build.rs b/nrf52-code/boards/dongle/build.rs new file mode 100644 index 00000000..f4ed2c03 --- /dev/null +++ b/nrf52-code/boards/dongle/build.rs @@ -0,0 +1,12 @@ +use std::{env, error::Error, fs, path::PathBuf}; + +fn main() -> Result<(), Box> { + let out_dir = PathBuf::from(env::var("OUT_DIR")?); + + // put memory layout (linker script) in the linker search path + fs::copy("memory.x", out_dir.join("memory.x"))?; + + println!("cargo:rustc-link-search={}", out_dir.display()); + + Ok(()) +} diff --git a/nrf52-code/boards/dongle/memory.x b/nrf52-code/boards/dongle/memory.x index 7ad752ba..d874c334 100644 --- a/nrf52-code/boards/dongle/memory.x +++ b/nrf52-code/boards/dongle/memory.x @@ -1,11 +1,6 @@ MEMORY { - /* Bootloader is split in 2 parts: the first part lives in the range - 0..0x1000; the second part lives at the end of the 1 MB Flash. The range - selected here collides with neither */ - FLASH : ORIGIN = 0x1000, LENGTH = 0x7f000 - - /* The bootloader uses the first 8 bytes of RAM to preserve state so don't - touch them */ - RAM : ORIGIN = 0x20000008, LENGTH = 0x3fff8 + /* Start after the bootloader */ + FLASH : ORIGIN = 0x00001000, LENGTH = 1020K + RAM : ORIGIN = 0x20000000, LENGTH = 256K } diff --git a/nrf52-code/boards/dongle/src/lib.rs b/nrf52-code/boards/dongle/src/lib.rs new file mode 100644 index 00000000..b345e706 --- /dev/null +++ b/nrf52-code/boards/dongle/src/lib.rs @@ -0,0 +1,361 @@ +//! Board Support Package (BSP) for the nRF52840 Dongle +//! +//! See + +#![deny(missing_docs)] +#![deny(warnings)] +#![no_std] + +use core::{ + ops, + sync::atomic::{self, AtomicU32, Ordering}, + time::Duration, +}; + +use cortex_m::peripheral::NVIC; +use cortex_m_semihosting::debug; +use embedded_hal::{ + digital::v2::{OutputPin, StatefulOutputPin}, + timer::CountDown, +}; + +pub use hal::{self, ieee802154}; +use hal::{ + clocks::{self, Clocks}, + gpio::{p0, p1, Level, Output, Pin, Port, PushPull}, + pac::USBD, + rtc::{Rtc, RtcInterrupt}, + timer::OneShot, +}; + +use defmt_rtt as _; // global logger + +/// Exports PAC peripherals +pub mod peripheral { + pub use hal::pac::{interrupt, Interrupt, POWER, USBD}; +} + +use peripheral::interrupt; + +/// Components on the board +pub struct Board { + /// LEDs + pub leds: Leds, + /// Timer + pub timer: Timer, + + /// Radio interface + pub radio: ieee802154::Radio<'static>, + /// USBD (Universal Serial Bus Device) peripheral + pub usbd: USBD, + /// Clocks + pub clocks: &'static Clocks< + clocks::ExternalOscillator, + clocks::ExternalOscillator, + clocks::LfOscStarted, + >, +} + +/// All LEDs on the board +/// +/// See User Manual Table 1 +pub struct Leds { + /// LD1: pin P0.06 + pub ld1: Led, + /// LD2 Red: pin P0.08 + pub ld2_red: Led, + /// LD2 Green: pin P1.09 + pub ld2_green: Led, + /// LD2 Blue: pin P0.12 + pub ld2_blue: Led, +} + +/// A single LED +pub struct Led { + inner: Pin>, +} + +impl Led { + /// Turns on the LED + pub fn on(&mut self) { + defmt::trace!( + "setting P{}.{} low (LED on)", + if self.inner.port() == Port::Port1 { + '1' + } else { + '0' + }, + self.inner.pin() + ); + + // NOTE this operations returns a `Result` but never returns the `Err` variant + let _ = self.inner.set_low(); + } + + /// Turns off the LED + pub fn off(&mut self) { + defmt::trace!( + "setting P{}.{} high (LED off)", + if self.inner.port() == Port::Port1 { + '1' + } else { + '0' + }, + self.inner.pin() + ); + + // NOTE this operations returns a `Result` but never returns the `Err` variant + let _ = self.inner.set_high(); + } + + /// Returns `true` if the LED is in the OFF state + pub fn is_off(&self) -> bool { + self.inner.is_set_high() == Ok(true) + } + + /// Returns `true` if the LED is in the ON state + pub fn is_on(&self) -> bool { + !self.is_off() + } + + /// Toggles the state (on/off) of the LED + pub fn toggle(&mut self) { + if self.is_off() { + self.on(); + } else { + self.off() + } + } +} + +/// A timer for creating blocking delays +pub struct Timer { + inner: hal::Timer, +} + +impl Timer { + /// Blocks program execution for at least the specified `duration` + pub fn wait(&mut self, duration: Duration) { + defmt::trace!("blocking for {:?} ...", duration); + + // 1 cycle = 1 microsecond + let subsec_micros = duration.subsec_micros(); + if subsec_micros != 0 { + self.inner.delay(subsec_micros); + } + + const MICROS_IN_ONE_SEC: u32 = 1_000_000; + // maximum number of seconds that fit in a single `delay` call without overflowing the `u32` + // argument + const MAX_SECS: u32 = u32::MAX / MICROS_IN_ONE_SEC; + let mut secs = duration.as_secs(); + while secs != 0 { + let cycles = if secs > MAX_SECS as u64 { + secs -= MAX_SECS as u64; + MAX_SECS * MICROS_IN_ONE_SEC + } else { + let cycles = secs as u32 * MICROS_IN_ONE_SEC; + secs = 0; + cycles + }; + + self.inner.delay(cycles) + } + + defmt::trace!("... DONE"); + } + + /// Start a timer running for `timeout_us` microseconds + pub fn start(&mut self, timeout_us: u32) { + self.inner.start(timeout_us) + } + + /// Returns `true` if the timer is still running after a call to `start(nnn)` + pub fn is_running(&mut self) -> bool { + self.inner.wait().is_err() + } +} + +impl ops::Deref for Timer { + type Target = hal::Timer; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl ops::DerefMut for Timer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +/// The ways that initialisation can fail +#[derive(Debug, Copy, Clone, defmt::Format)] +pub enum Error { + /// You tried to initialise the board twice + DoubleInit = 1, +} + +/// Initializes the board +/// +/// This return an `Err`or if called more than once +pub fn init() -> Result { + let Some(periph) = hal::pac::Peripherals::take() else { + return Err(Error::DoubleInit); + }; + // NOTE(static mut) this branch runs at most once + static mut CLOCKS: Option< + Clocks, + > = None; + + defmt::debug!("Initializing the board"); + + let clocks = Clocks::new(periph.CLOCK); + let clocks = clocks.enable_ext_hfosc(); + let clocks = clocks.set_lfclk_src_external(clocks::LfOscConfiguration::NoExternalNoBypass); + let clocks = clocks.start_lfclk(); + let _clocks = clocks.enable_ext_hfosc(); + // extend lifetime to `'static` + let clocks = unsafe { CLOCKS.get_or_insert(_clocks) }; + + defmt::debug!("Clocks configured"); + + let mut rtc = Rtc::new(periph.RTC0, 0).unwrap(); + rtc.enable_interrupt(RtcInterrupt::Overflow, None); + rtc.enable_counter(); + // NOTE(unsafe) because this crate defines the `#[interrupt] fn RTC0` interrupt handler, + // RTIC cannot manage that interrupt (trying to do so results in a linker error). Thus it + // is the task of this crate to mask/unmask the interrupt in a safe manner. + // + // Because the RTC0 interrupt handler does *not* access static variables through a critical + // section (that disables interrupts) this `unmask` operation cannot break critical sections + // and thus won't lead to undefined behavior (e.g. torn reads/writes) + // + // the preceding `enable_conuter` method consumes the `rtc` value. This is a semantic move + // of the RTC0 peripheral from this function (which can only be called at most once) to the + // interrupt handler (where the peripheral is accessed without any synchronization + // mechanism) + unsafe { NVIC::unmask(hal::pac::Interrupt::RTC0) }; + + defmt::debug!("RTC started"); + + let p0_pins = p0::Parts::new(periph.P0); + let p1_pins = p1::Parts::new(periph.P1); + + defmt::debug!("I/O pins have been configured for digital output"); + + let timer = hal::Timer::new(periph.TIMER0); + + let radio = { + let mut radio = ieee802154::Radio::init(periph.RADIO, clocks); + + // set TX power to its maximum value + radio.set_txpower(ieee802154::TxPower::Pos8dBm); + defmt::debug!("Radio initialized and configured with TX power set to the maximum value"); + radio + }; + + Ok(Board { + // NOTE LEDs turn on when the pin output level is low + leds: Leds { + ld1: Led { + inner: p0_pins.p0_06.degrade().into_push_pull_output(Level::High), + }, + ld2_red: Led { + inner: p0_pins.p0_08.degrade().into_push_pull_output(Level::High), + }, + ld2_green: Led { + inner: p1_pins.p1_09.degrade().into_push_pull_output(Level::High), + }, + ld2_blue: Led { + inner: p0_pins.p0_12.degrade().into_push_pull_output(Level::High), + }, + }, + radio, + timer: Timer { inner: timer }, + usbd: periph.USBD, + clocks, + }) +} + +// Counter of OVERFLOW events -- an OVERFLOW occurs every (1<<24) ticks +static OVERFLOWS: AtomicU32 = AtomicU32::new(0); + +// NOTE this will run at the highest priority, higher priority than RTIC tasks +#[interrupt] +fn RTC0() { + let curr = OVERFLOWS.load(Ordering::Relaxed); + OVERFLOWS.store(curr + 1, Ordering::Relaxed); + + // clear the EVENT register + unsafe { + core::mem::transmute::<_, hal::pac::RTC0>(()) + .events_ovrflw + .reset() + } +} + +/// Exits the application when the program is executed through the `probe-rs` Cargo runner +pub fn exit() -> ! { + unsafe { + // turn off the USB D+ pull-up before pausing the device with a breakpoint + // this disconnects the nRF device from the USB host so the USB host won't attempt further + // USB communication (and see an unresponsive device). + const USBD_USBPULLUP: *mut u32 = 0x4002_7504 as *mut u32; + USBD_USBPULLUP.write_volatile(0) + } + defmt::println!("`dk::exit()` called; exiting ..."); + // force any pending memory operation to complete before the instruction that follows + atomic::compiler_fence(Ordering::SeqCst); + loop { + debug::exit(debug::ExitStatus::Ok(())) + } +} + +/// Returns the time elapsed since the call to the `dk::init` function +/// +/// The clock that is read to compute this value has a resolution of 30 microseconds. +/// +/// Calling this function before calling `dk::init` will return a value of `0` nanoseconds. +pub fn uptime() -> Duration { + // here we are going to perform a 64-bit read of the number of ticks elapsed + // + // a 64-bit load operation cannot performed in a single instruction so the operation can be + // preempted by the RTC0 interrupt handler (which increases the OVERFLOWS counter) + // + // the loop below will load both the lower and upper parts of the 64-bit value while preventing + // the issue of mixing a low value with an "old" high value -- note that, due to interrupts, an + // arbitrary amount of time may elapse between the `hi1` load and the `low` load + let overflows = &OVERFLOWS as *const AtomicU32 as *const u32; + let ticks = loop { + unsafe { + // NOTE volatile is used to order these load operations among themselves + let hi1 = overflows.read_volatile(); + let low = core::mem::transmute::<_, hal::pac::RTC0>(()) + .counter + .read() + .counter() + .bits(); + let hi2 = overflows.read_volatile(); + + if hi1 == hi2 { + break u64::from(low) | (u64::from(hi1) << 24); + } + } + }; + + // 2**15 ticks = 1 second + let freq = 1 << 15; + let secs = ticks / freq; + // subsec ticks + let ticks = (ticks % freq) as u32; + // one tick is equal to `1e9 / 32768` nanos + // the fraction can be reduced to `1953125 / 64` + // which can be further decomposed as `78125 * (5 / 4) * (5 / 4) * (1 / 4)`. + // Doing the operation this way we can stick to 32-bit arithmetic without overflowing the value + // at any stage + let nanos = + (((ticks % 32768).wrapping_mul(78125) >> 2).wrapping_mul(5) >> 2).wrapping_mul(5) >> 2; + Duration::new(secs, nanos as u32) +} diff --git a/nrf52-code/hal-app/Cargo.lock b/nrf52-code/hal-app/Cargo.lock index b1942178..d64c39eb 100644 --- a/nrf52-code/hal-app/Cargo.lock +++ b/nrf52-code/hal-app/Cargo.lock @@ -86,6 +86,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cortex-m-semihosting" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c23234600452033cc77e4b761e740e02d2c4168e11dbf36ab14a0f58973592b0" +dependencies = [ + "cortex-m", +] + [[package]] name = "critical-section" version = "1.1.2" @@ -146,6 +155,7 @@ version = "0.0.0" dependencies = [ "cortex-m", "cortex-m-rt", + "cortex-m-semihosting", "defmt", "defmt-rtt", "embedded-hal", diff --git a/nrf52-code/puzzle-fw/.cargo/config.toml b/nrf52-code/puzzle-fw/.cargo/config.toml new file mode 100644 index 00000000..bbe3663d --- /dev/null +++ b/nrf52-code/puzzle-fw/.cargo/config.toml @@ -0,0 +1,13 @@ +[target.thumbv7em-none-eabihf] +# set custom cargo runner to flash & run on embedded target when we call `cargo run` +# for more information, check out https://github.com/probe-rs +runner = "probe-rs run --chip nRF52840_xxAA" +rustflags = [ + "-C", "link-arg=-Tlink.x", # use the cortex-m-rt linker script + "-C", "linker=flip-link", # adds stack overflow protection + "-C", "link-arg=-Tdefmt.x", # defmt support +] + +[build] +# cross-compile to this target +target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 with FPU diff --git a/nrf52-code/puzzle-fw/.gitignore b/nrf52-code/puzzle-fw/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/nrf52-code/puzzle-fw/.gitignore @@ -0,0 +1 @@ +target diff --git a/nrf52-code/puzzle-fw/Cargo.lock b/nrf52-code/puzzle-fw/Cargo.lock new file mode 100644 index 00000000..5fd8a657 --- /dev/null +++ b/nrf52-code/puzzle-fw/Cargo.lock @@ -0,0 +1,506 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "consts" +version = "0.1.0" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "critical-section", + "embedded-hal", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cortex-m-semihosting" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c23234600452033cc77e4b761e740e02d2c4168e11dbf36ab14a0f58973592b0" +dependencies = [ + "cortex-m", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "defmt" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "defmt-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609923761264dd99ed9c7d209718cda4631c5fe84668e0f0960124cbb844c49f" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "dongle" +version = "0.0.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "cortex-m-semihosting", + "defmt", + "defmt-rtt", + "embedded-hal", + "nrf52840-hal", + "panic-probe", +] + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "fixed" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c69ce7e7c0f17aa18fdd9d0de39727adb9c6281f2ad12f57cbe54ae6e76e7d" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "half" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nrf-hal-common" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd244c63d588066d75cffdcae8a03299fd5fb272e36bdde4a9f922f3e4bc6e4b" +dependencies = [ + "cast", + "cfg-if", + "cortex-m", + "embedded-dma", + "embedded-hal", + "embedded-storage", + "fixed", + "nb 1.1.0", + "nrf-usbd", + "nrf52840-pac", + "rand_core", + "void", +] + +[[package]] +name = "nrf-usbd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b2907c0b3ec4d264981c1fc5a2321d51c463d5a63d386e573f00e84d5495e6" +dependencies = [ + "cortex-m", + "critical-section", + "usb-device", + "vcell", +] + +[[package]] +name = "nrf52840-hal" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54757ec98f38331889d1d6c535edb5dd543c762751abfe507f2d644b30f6d4f" +dependencies = [ + "embedded-hal", + "nrf-hal-common", + "nrf52840-pac", +] + +[[package]] +name = "nrf52840-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30713f36f1be02e5bc9abefa30eae4a1f943d810f199d4923d3ad062d1be1b3d" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "panic-probe" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "puzzle-fw" +version = "0.0.0" +dependencies = [ + "consts", + "cortex-m", + "cortex-m-rt", + "defmt", + "defmt-rtt", + "dongle", + "embedded-hal", + "heapless", + "panic-probe", + "usb-device", + "usbd-serial", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "usb-device" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" + +[[package]] +name = "usbd-serial" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db75519b86287f12dcf0d171c7cf4ecc839149fe9f3b720ac4cfce52959e1dfe" +dependencies = [ + "embedded-hal", + "nb 0.1.3", + "usb-device", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/nrf52-code/puzzle-fw/Cargo.toml b/nrf52-code/puzzle-fw/Cargo.toml new file mode 100644 index 00000000..2f1b255f --- /dev/null +++ b/nrf52-code/puzzle-fw/Cargo.toml @@ -0,0 +1,38 @@ +[package] +authors = ["Ferrous Systems"] +edition = "2021" +license = "MIT OR Apache-2.0" +name = "puzzle-fw" +version = "0.0.0" + +[dependencies] +cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]} +cortex-m-rt = "0.7" +defmt = "0.3" +defmt-rtt = "0.4" +embedded-hal = "0.2.7" +heapless = "0.8" +panic-probe = { version = "0.3.0", features = ["print-defmt"] } +dongle = { path = "../boards/dongle" } +usb-device = "0.2.7" +usbd-serial = "0.1.0" +consts = { path = "../consts" } + +# optimize code in both profiles +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # ! +incremental = false +lto = "fat" +opt-level = 'z' # ! +overflow-checks = false + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 3 +overflow-checks = false diff --git a/nrf52-code/puzzle-fw/build.rs b/nrf52-code/puzzle-fw/build.rs new file mode 100644 index 00000000..f679b8f8 --- /dev/null +++ b/nrf52-code/puzzle-fw/build.rs @@ -0,0 +1,11 @@ +fn main() { + if std::env::var("SECRET_MESSAGE").is_err() { + println!("cargo:rustc-env=SECRET_MESSAGE=WELLDONE"); + } + if std::env::var("PLAIN_LETTERS").is_err() { + println!("cargo:rustc-env=PLAIN_LETTERS=abcdefghijklmnopqrstuvwxyz"); + } + if std::env::var("CIPHER_LETTERS").is_err() { + println!("cargo:rustc-env=CIPHER_LETTERS=ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + } +} diff --git a/nrf52-code/puzzle-fw/src/main.rs b/nrf52-code/puzzle-fw/src/main.rs new file mode 100644 index 00000000..f7edb7a8 --- /dev/null +++ b/nrf52-code/puzzle-fw/src/main.rs @@ -0,0 +1,250 @@ +//! Firmware for the nRF52840 Dongle, for playing the puzzle game +//! +//! Sets up a USB Serial port and listens for radio packets. + +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::sync::atomic::{AtomicU32, Ordering}; + +use cortex_m_rt::entry; +use panic_probe as _; +use usb_device::class_prelude::UsbBusAllocator; +use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +use dongle::peripheral::interrupt; +use dongle::{hal::usbd, ieee802154::Packet}; + +/// Store the secret. +/// +/// We do this rather than the plaintext -- otherwise `strings $elf` will reveal the answer +static SECRET: &[u8] = env!("SECRET_MESSAGE").as_bytes(); + +/// The plaintext side of the map +static PLAIN_LETTERS: &[u8] = env!("PLAIN_LETTERS").as_bytes(); + +/// The ciphertext side of the map +static CIPHER_LETTERS: &[u8] = env!("CIPHER_LETTERS").as_bytes(); + +/// A 64-byte USB Serial buffer +static RING_BUFFER: Ringbuffer = Ringbuffer { + buffer: heapless::mpmc::Q64::new(), +}; + +/// The USB Device Driver (shared with the interrupt). +static mut USB_DEVICE: Option>> = None; + +/// The USB Bus Driver (shared with the interrupt). +static mut USB_BUS: Option>> = None; + +/// The USB Serial Device Driver (shared with the interrupt). +static mut USB_SERIAL: Option>> = None; + +static RX_COUNT: AtomicU32 = AtomicU32::new(0); + +static ERR_COUNT: AtomicU32 = AtomicU32::new(0); + +struct Ringbuffer { + buffer: heapless::mpmc::Q64, +} + +impl core::fmt::Write for &Ringbuffer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + for b in s.bytes() { + let _ = self.buffer.enqueue(b); + } + Ok(()) + } +} + +#[entry] +fn main() -> ! { + let mut board = dongle::init().unwrap(); + board.usbd.inten.modify(|_r, w| { + w.sof().set_bit(); + w + }); + let usb_bus = UsbBusAllocator::new(usbd::Usbd::new(usbd::UsbPeripheral::new( + board.usbd, + board.clocks, + ))); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet + USB_BUS = Some(usb_bus); + } + // Grab a reference to the USB Bus allocator. We are promising to the + // compiler not to take mutable access to this global variable whilst this + // reference exists! + let bus_ref = unsafe { USB_BUS.as_ref().unwrap() }; + let serial = SerialPort::new(bus_ref); + unsafe { + USB_SERIAL = Some(serial); + } + + let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE); + let usb_dev = UsbDeviceBuilder::new(bus_ref, vid_pid) + .manufacturer("Ferrous Systems") + .product("Dongle Puzzle") + .device_class(USB_CLASS_CDC) + .max_packet_size_0(64) // (makes control transfers 8x faster) + .build(); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet + USB_DEVICE = Some(usb_dev); + } + + board.radio.set_channel(dongle::ieee802154::Channel::_25); + + let mut dict: heapless::LinearMap = heapless::LinearMap::new(); + for (&plain, &cipher) in PLAIN_LETTERS.iter().zip(CIPHER_LETTERS.iter()) { + let _ = dict.insert(plain, cipher); + } + + // Turn on USB interrupts... + // Enable the USB interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(dongle::peripheral::Interrupt::USBD); + }; + + let mut pkt = Packet::new(); + loop { + board.leds.ld1.on(); + match board.radio.recv(&mut pkt) { + Ok(crc) => { + board.leds.ld1.off(); + write!(&RING_BUFFER, "RX CRC {crc:04x}, LQI ").unwrap(); + if pkt.len() > 3 { + let lqi = pkt.lqi(); + writeln!(&RING_BUFFER, "{lqi}").unwrap(); + } else { + writeln!(&RING_BUFFER, "Unknown").unwrap(); + } + + match handle_packet(&pkt, &dict) { + Command::SendSecret => { + pkt.copy_from_slice(&SECRET); + writeln!(&RING_BUFFER, "TX Secret").unwrap(); + board.leds.ld2_blue.on(); + board.leds.ld2_green.off(); + board.leds.ld2_red.off(); + } + Command::MapChar(from, to) => { + pkt.copy_from_slice(&[to]); + writeln!(&RING_BUFFER, "TX Map({from}) => {to}").unwrap(); + board.leds.ld2_blue.off(); + board.leds.ld2_green.on(); + board.leds.ld2_red.off(); + } + Command::Correct => { + pkt.copy_from_slice(b"correct"); + writeln!(&RING_BUFFER, "TX Correct").unwrap(); + board.leds.ld2_blue.on(); + board.leds.ld2_green.on(); + board.leds.ld2_red.on(); + } + Command::Wrong => { + pkt.copy_from_slice(b"incorrect"); + writeln!(&RING_BUFFER, "TX Incorrect").unwrap(); + board.leds.ld2_blue.off(); + board.leds.ld2_green.on(); + board.leds.ld2_red.on(); + } + } + // wait 1ms so they have time to switch to receive mode + board.timer.delay(1000); + // now send it + board.radio.send(&mut pkt); + RX_COUNT.fetch_add(1, Ordering::Relaxed); + } + Err(_crc) => { + ERR_COUNT.fetch_add(1, Ordering::Relaxed); + } + } + } +} + +enum Command { + SendSecret, + MapChar(u8, u8), + Correct, + Wrong, +} + +fn handle_packet(packet: &Packet, dict: &heapless::LinearMap) -> Command { + if packet.len() == 0 { + Command::SendSecret + } else if packet.len() == 1 { + // They want to know how convert from X to Y, and they gave us X + let from = packet[0]; + let to = *dict.get(&from).unwrap_or(&0); + Command::MapChar(from, to) + } else { + let mut correct = packet.len() as usize == SECRET.len(); + // Encrypt every byte of plaintext they give us + for (encrypted, secret) in packet + .iter() + .map(|b| dict.get(b).unwrap_or(&0)) + .zip(SECRET.iter()) + { + if secret != encrypted { + correct = false; + } + } + + if correct { + Command::Correct + } else { + Command::Wrong + } + } +} + +#[interrupt] +unsafe fn USBD() { + // Grab the global objects. This is OK as we only access them under interrupt. + let usb_dev = USB_DEVICE.as_mut().unwrap(); + let serial = USB_SERIAL.as_mut().unwrap(); + + let mut buf = [0u8; 64]; + + // Poll the USB driver with all of our supported USB Classes + if usb_dev.poll(&mut [serial]) { + match serial.read(&mut buf) { + Err(_e) => { + // Do nothing + } + Ok(0) => { + // Do nothing + } + Ok(count) => { + for item in &buf[0..count] { + // Look for question marks + if *item == b'?' { + let _ = writeln!( + &RING_BUFFER, + "{}, {}", + RX_COUNT.load(Ordering::Relaxed), + ERR_COUNT.load(Ordering::Relaxed) + ); + } + } + } + } + } + + // Copy from ring-buffer to USB UART + let mut count = 0; + while count < buf.len() { + if let Some(item) = RING_BUFFER.buffer.dequeue() { + buf[count] = item; + count += 1; + } else { + break; + } + } + let _ = serial.write(&buf[0..count]); +} + +// End of file From a41388294d9c72e2e1f4eef43a3de5f48c810ec6 Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Wed, 24 Jan 2024 12:34:42 +0000 Subject: [PATCH 2/8] Add HID API for channel changes. --- nrf52-code/puzzle-fw/Cargo.lock | 79 +++++++++++- nrf52-code/puzzle-fw/Cargo.toml | 8 +- nrf52-code/puzzle-fw/src/main.rs | 199 +++++++++++++++++++++++++------ 3 files changed, 241 insertions(+), 45 deletions(-) diff --git a/nrf52-code/puzzle-fw/Cargo.lock b/nrf52-code/puzzle-fw/Cargo.lock index 5fd8a657..e5a92a0b 100644 --- a/nrf52-code/puzzle-fw/Cargo.lock +++ b/nrf52-code/puzzle-fw/Cargo.lock @@ -37,9 +37,9 @@ checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" @@ -192,6 +192,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "fixed" version = "1.24.0" @@ -352,13 +358,11 @@ dependencies = [ "consts", "cortex-m", "cortex-m-rt", - "defmt", - "defmt-rtt", "dongle", "embedded-hal", "heapless", - "panic-probe", "usb-device", + "usbd-hid", "usbd-serial", ] @@ -401,6 +405,36 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -467,6 +501,41 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" +[[package]] +name = "usbd-hid" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1" +dependencies = [ + "bitfield", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2" +dependencies = [ + "byteorder", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "usbd-serial" version = "0.1.1" diff --git a/nrf52-code/puzzle-fw/Cargo.toml b/nrf52-code/puzzle-fw/Cargo.toml index 2f1b255f..09ece9d4 100644 --- a/nrf52-code/puzzle-fw/Cargo.toml +++ b/nrf52-code/puzzle-fw/Cargo.toml @@ -6,17 +6,15 @@ name = "puzzle-fw" version = "0.0.0" [dependencies] +consts = { path = "../consts" } cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]} cortex-m-rt = "0.7" -defmt = "0.3" -defmt-rtt = "0.4" +dongle = { path = "../boards/dongle" } embedded-hal = "0.2.7" heapless = "0.8" -panic-probe = { version = "0.3.0", features = ["print-defmt"] } -dongle = { path = "../boards/dongle" } usb-device = "0.2.7" +usbd-hid = "0.6" usbd-serial = "0.1.0" -consts = { path = "../consts" } # optimize code in both profiles [profile.dev] diff --git a/nrf52-code/puzzle-fw/src/main.rs b/nrf52-code/puzzle-fw/src/main.rs index f7edb7a8..c4fedbd0 100644 --- a/nrf52-code/puzzle-fw/src/main.rs +++ b/nrf52-code/puzzle-fw/src/main.rs @@ -6,16 +6,19 @@ #![no_main] use core::fmt::Write; -use core::sync::atomic::{AtomicU32, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use cortex_m_rt::entry; -use panic_probe as _; use usb_device::class_prelude::UsbBusAllocator; use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; +use usbd_hid::hid_class::HIDClass; use usbd_serial::{SerialPort, USB_CLASS_CDC}; use dongle::peripheral::interrupt; -use dongle::{hal::usbd, ieee802154::Packet}; +use dongle::{ + hal::usbd, + ieee802154::{Channel, Packet}, +}; /// Store the secret. /// @@ -33,19 +36,37 @@ static RING_BUFFER: Ringbuffer = Ringbuffer { buffer: heapless::mpmc::Q64::new(), }; -/// The USB Device Driver (shared with the interrupt). -static mut USB_DEVICE: Option>> = None; +/// A short-hand for the nRF52 USB types +type UsbBus<'a> = usbd::Usbd>; + +/// The USB Device Driver (owned by the USBD interrupt). +static mut USB_DEVICE: Option> = None; -/// The USB Bus Driver (shared with the interrupt). -static mut USB_BUS: Option>> = None; +/// The USB Bus Driver (owned by the USBD interrupt). +static mut USB_BUS: Option> = None; -/// The USB Serial Device Driver (shared with the interrupt). -static mut USB_SERIAL: Option>> = None; +/// The USB Serial Device Driver (owned by the USBD interrupt). +static mut USB_SERIAL: Option> = None; +/// The USB Human Interface Device Driver (owned by the USBD interrupt). +static mut USB_HID: Option> = None; + +/// Track how many CRC successes we had receiving radio packets static RX_COUNT: AtomicU32 = AtomicU32::new(0); +/// Track how many CRC failures we had receiving radio packets static ERR_COUNT: AtomicU32 = AtomicU32::new(0); +/// The USB interrupt sets this to < u32::MAX when a new channel is sent over HID. +/// +/// The main loop handles it and sets it back to u32::MAX when processed. +static NEW_CHANNEL: AtomicU32 = AtomicU32::new(u32::MAX); + +/// Set to true when we get a ?. +/// +/// We print some info in response. +static WANT_INFO: AtomicBool = AtomicBool::new(false); + struct Ringbuffer { buffer: heapless::mpmc::Q64, } @@ -83,6 +104,36 @@ fn main() -> ! { USB_SERIAL = Some(serial); } + let desc = &[ + 0x06, 0x00, 0xFF, // Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 + 0x09, 0x01, // Item(Local ): Usage, data= [ 0x01 ] 1 + 0xA1, 0x01, // Item(Main ): Collection, data= [ 0x01 ] 1 + // Application + 0x15, 0x00, // Item(Global): Logical Minimum, data= [ 0x00 ] 0 + 0x26, 0xFF, 0x00, // Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 + 0x75, 0x08, // Item(Global): Report Size, data= [ 0x08 ] 8 + 0x95, 0x40, // Item(Global): Report Count, data= [ 0x40 ] 64 + 0x09, 0x01, // Item(Local ): Usage, data= [ 0x01 ] 1 + 0x81, 0x02, // Item(Main ): Input, data= [ 0x02 ] 2 + // Data Variable Absolute No_Wrap Linear + // Preferred_State No_Null_Position Non_Volatile Bitfield + 0x95, 0x40, // Item(Global): Report Count, data= [ 0x40 ] 64 + 0x09, 0x01, // Item(Local ): Usage, data= [ 0x01 ] 1 + 0x91, 0x02, // Item(Main ): Output, data= [ 0x02 ] 2 + // Data Variable Absolute No_Wrap Linear + // Preferred_State No_Null_Position Non_Volatile Bitfield + 0x95, 0x01, // Item(Global): Report Count, data= [ 0x01 ] 1 + 0x09, 0x01, // Item(Local ): Usage, data= [ 0x01 ] 1 + 0xB1, 0x02, // Item(Main ): Feature, data= [ 0x02 ] 2 + // Data Variable Absolute No_Wrap Linear + // Preferred_State No_Null_Position Non_Volatile Bitfield + 0xC0, // Item(Main ): End Collection, data=none + ]; + let hid = HIDClass::new(bus_ref, desc, 100); + unsafe { + USB_HID = Some(hid); + } + let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE); let usb_dev = UsbDeviceBuilder::new(bus_ref, vid_pid) .manufacturer("Ferrous Systems") @@ -95,6 +146,7 @@ fn main() -> ! { USB_DEVICE = Some(usb_dev); } + let mut current_ch_id = 25; board.radio.set_channel(dongle::ieee802154::Channel::_25); let mut dict: heapless::LinearMap = heapless::LinearMap::new(); @@ -111,56 +163,104 @@ fn main() -> ! { let mut pkt = Packet::new(); loop { board.leds.ld1.on(); - match board.radio.recv(&mut pkt) { + // Wait up to 1 second for a radio packet + match board + .radio + .recv_timeout(&mut pkt, &mut board.timer, 1_000_000) + { Ok(crc) => { board.leds.ld1.off(); - write!(&RING_BUFFER, "RX CRC {crc:04x}, LQI ").unwrap(); - if pkt.len() > 3 { - let lqi = pkt.lqi(); - writeln!(&RING_BUFFER, "{lqi}").unwrap(); - } else { - writeln!(&RING_BUFFER, "Unknown").unwrap(); - } - + let _ = writeln!( + &RING_BUFFER, + "\nRX CRC {:04x}, LQI {}, LEN {}", + crc, + pkt.lqi(), + pkt.len() + ); match handle_packet(&pkt, &dict) { Command::SendSecret => { pkt.copy_from_slice(&SECRET); - writeln!(&RING_BUFFER, "TX Secret").unwrap(); + let _ = writeln!(&RING_BUFFER, "TX Secret"); board.leds.ld2_blue.on(); board.leds.ld2_green.off(); board.leds.ld2_red.off(); } Command::MapChar(from, to) => { pkt.copy_from_slice(&[to]); - writeln!(&RING_BUFFER, "TX Map({from}) => {to}").unwrap(); + let _ = writeln!(&RING_BUFFER, "TX Map({from}) => {to}"); board.leds.ld2_blue.off(); board.leds.ld2_green.on(); board.leds.ld2_red.off(); } Command::Correct => { pkt.copy_from_slice(b"correct"); - writeln!(&RING_BUFFER, "TX Correct").unwrap(); + let _ = writeln!(&RING_BUFFER, "TX Correct"); board.leds.ld2_blue.on(); board.leds.ld2_green.on(); board.leds.ld2_red.on(); } Command::Wrong => { pkt.copy_from_slice(b"incorrect"); - writeln!(&RING_BUFFER, "TX Incorrect").unwrap(); + let _ = writeln!(&RING_BUFFER, "TX Incorrect"); board.leds.ld2_blue.off(); board.leds.ld2_green.on(); board.leds.ld2_red.on(); } } - // wait 1ms so they have time to switch to receive mode - board.timer.delay(1000); // now send it board.radio.send(&mut pkt); RX_COUNT.fetch_add(1, Ordering::Relaxed); } - Err(_crc) => { + Err(dongle::ieee802154::Error::Crc(_)) => { ERR_COUNT.fetch_add(1, Ordering::Relaxed); } + Err(dongle::ieee802154::Error::Timeout) => { + // Show that we are alive + let _ = write!(&RING_BUFFER, "."); + } + } + + // Handle channel changes + let ch_id = NEW_CHANNEL.load(Ordering::Relaxed); + if ch_id != u32::MAX { + NEW_CHANNEL.store(u32::MAX, Ordering::Relaxed); + if let Some(channel) = match ch_id { + 11 => Some(Channel::_11), + 12 => Some(Channel::_12), + 13 => Some(Channel::_13), + 14 => Some(Channel::_14), + 15 => Some(Channel::_15), + 16 => Some(Channel::_16), + 17 => Some(Channel::_17), + 18 => Some(Channel::_18), + 19 => Some(Channel::_19), + 20 => Some(Channel::_20), + 21 => Some(Channel::_21), + 22 => Some(Channel::_22), + 23 => Some(Channel::_23), + 24 => Some(Channel::_24), + 25 => Some(Channel::_25), + 26 => Some(Channel::_26), + _ => None, + } { + board.radio.set_channel(channel); + let _ = writeln!(&RING_BUFFER, "\nChannel {} set", ch_id); + current_ch_id = ch_id; + } else { + let _ = writeln!(&RING_BUFFER, "\nChannel {} invalid", ch_id); + } + } + + // Print help text when ? is pressed + if WANT_INFO.load(Ordering::Relaxed) { + WANT_INFO.store(false, Ordering::Relaxed); + let _ = writeln!( + &RING_BUFFER, + "rx={}, err={}, ch={}, app=puzzle-fw", + RX_COUNT.load(Ordering::Relaxed), + ERR_COUNT.load(Ordering::Relaxed), + current_ch_id, + ); } } } @@ -201,16 +301,21 @@ fn handle_packet(packet: &Packet, dict: &heapless::LinearMap) -> Co } } +/// Handles USB interrupts +/// +/// Polls all the USB devices, and copies bytes from [`RING_BUFFER`] into the +/// USB UART. #[interrupt] -unsafe fn USBD() { +fn USBD() { // Grab the global objects. This is OK as we only access them under interrupt. - let usb_dev = USB_DEVICE.as_mut().unwrap(); - let serial = USB_SERIAL.as_mut().unwrap(); + let usb_dev = unsafe { USB_DEVICE.as_mut().unwrap() }; + let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; + let hid = unsafe { USB_HID.as_mut().unwrap() }; let mut buf = [0u8; 64]; // Poll the USB driver with all of our supported USB Classes - if usb_dev.poll(&mut [serial]) { + if usb_dev.poll(&mut [serial, hid]) { match serial.read(&mut buf) { Err(_e) => { // Do nothing @@ -222,16 +327,29 @@ unsafe fn USBD() { for item in &buf[0..count] { // Look for question marks if *item == b'?' { - let _ = writeln!( - &RING_BUFFER, - "{}, {}", - RX_COUNT.load(Ordering::Relaxed), - ERR_COUNT.load(Ordering::Relaxed) - ); + WANT_INFO.store(true, Ordering::Relaxed); } } } } + let hid_byte = match hid.pull_raw_output(&mut buf) { + Ok(64) => { + // Windows zero-pads the packet + Some(buf[0]) + } + Ok(1) => { + // macOS/Linux sends a single byte + Some(buf[0]) + } + Ok(_n) => { + // Ignore any other size packet + None + } + Err(_e) => None, + }; + if let Some(ch) = hid_byte { + NEW_CHANNEL.store(ch as u32, Ordering::Relaxed); + } } // Copy from ring-buffer to USB UART @@ -247,4 +365,15 @@ unsafe fn USBD() { let _ = serial.write(&buf[0..count]); } +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(&RING_BUFFER, "Panic: {:?}", info); + cortex_m::asm::delay(64_000_000 * 2); + unsafe { + loop { + core::arch::asm!("bkpt 0x00"); + } + } +} + // End of file From bfe5e280ec80779e9b7b297fe33df64502d80fe9 Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Wed, 24 Jan 2024 16:52:17 +0000 Subject: [PATCH 3/8] Moved old dongle firmware. --- .../dongle-fw/{dongle => old}/.cargo/config.toml | 0 .../boards/dongle-fw/{dongle => old}/README.md | 0 .../{dongle => old}/deprecated_hex/README.md | 0 .../deprecated_hex/loopback-nousb11.hex | 0 .../deprecated_hex/loopback-nousb16.hex | 0 .../deprecated_hex/loopback-nousb21.hex | 0 .../deprecated_hex/loopback-nousb26.hex | 0 .../{dongle => old}/deprecated_hex/loopback.hex | 0 .../deprecated_hex/puzzle-nousb11.hex | 0 .../deprecated_hex/puzzle-nousb16.hex | 0 .../deprecated_hex/puzzle-nousb21.hex | 0 .../deprecated_hex/puzzle-nousb26.hex | 0 .../{dongle => old}/deprecated_hex/puzzle.hex | 0 .../boards/dongle-fw/{dongle => old}/loopback | Bin .../boards/dongle-fw/{dongle => old}/loopback-2020 | Bin .../dongle-fw/{dongle => old}/loopback-nousb11 | Bin .../dongle-fw/{dongle => old}/loopback-nousb16 | Bin .../dongle-fw/{dongle => old}/loopback-nousb21 | Bin .../dongle-fw/{dongle => old}/loopback-nousb26 | Bin .../boards/dongle-fw/{dongle => old}/memory.x | 0 nrf52-code/boards/dongle-fw/{dongle => old}/puzzle | Bin .../boards/dongle-fw/{dongle => old}/puzzle-2020 | Bin .../boards/dongle-fw/{dongle => old}/puzzle-nousb11 | Bin .../boards/dongle-fw/{dongle => old}/puzzle-nousb16 | Bin .../boards/dongle-fw/{dongle => old}/puzzle-nousb21 | Bin .../boards/dongle-fw/{dongle => old}/puzzle-nousb26 | Bin .../boards/dongle-fw/{dongle => old}/puzzlegen.rs | 0 27 files changed, 0 insertions(+), 0 deletions(-) rename nrf52-code/boards/dongle-fw/{dongle => old}/.cargo/config.toml (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/README.md (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/README.md (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/loopback-nousb11.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/loopback-nousb16.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/loopback-nousb21.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/loopback-nousb26.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/loopback.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/puzzle-nousb11.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/puzzle-nousb16.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/puzzle-nousb21.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/puzzle-nousb26.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/deprecated_hex/puzzle.hex (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/loopback (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/loopback-2020 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/loopback-nousb11 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/loopback-nousb16 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/loopback-nousb21 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/loopback-nousb26 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/memory.x (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/puzzle (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/puzzle-2020 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/puzzle-nousb11 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/puzzle-nousb16 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/puzzle-nousb21 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/puzzle-nousb26 (100%) rename nrf52-code/boards/dongle-fw/{dongle => old}/puzzlegen.rs (100%) diff --git a/nrf52-code/boards/dongle-fw/dongle/.cargo/config.toml b/nrf52-code/boards/dongle-fw/old/.cargo/config.toml similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/.cargo/config.toml rename to nrf52-code/boards/dongle-fw/old/.cargo/config.toml diff --git a/nrf52-code/boards/dongle-fw/dongle/README.md b/nrf52-code/boards/dongle-fw/old/README.md similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/README.md rename to nrf52-code/boards/dongle-fw/old/README.md diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/README.md b/nrf52-code/boards/dongle-fw/old/deprecated_hex/README.md similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/README.md rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/README.md diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb11.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback-nousb11.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb11.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback-nousb11.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb16.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback-nousb16.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb16.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback-nousb16.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb21.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback-nousb21.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb21.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback-nousb21.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb26.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback-nousb26.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback-nousb26.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback-nousb26.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/loopback.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/loopback.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb11.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle-nousb11.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb11.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle-nousb11.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb16.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle-nousb16.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb16.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle-nousb16.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb21.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle-nousb21.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb21.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle-nousb21.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb26.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle-nousb26.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle-nousb26.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle-nousb26.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle.hex b/nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle.hex similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/deprecated_hex/puzzle.hex rename to nrf52-code/boards/dongle-fw/old/deprecated_hex/puzzle.hex diff --git a/nrf52-code/boards/dongle-fw/dongle/loopback b/nrf52-code/boards/dongle-fw/old/loopback similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/loopback rename to nrf52-code/boards/dongle-fw/old/loopback diff --git a/nrf52-code/boards/dongle-fw/dongle/loopback-2020 b/nrf52-code/boards/dongle-fw/old/loopback-2020 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/loopback-2020 rename to nrf52-code/boards/dongle-fw/old/loopback-2020 diff --git a/nrf52-code/boards/dongle-fw/dongle/loopback-nousb11 b/nrf52-code/boards/dongle-fw/old/loopback-nousb11 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/loopback-nousb11 rename to nrf52-code/boards/dongle-fw/old/loopback-nousb11 diff --git a/nrf52-code/boards/dongle-fw/dongle/loopback-nousb16 b/nrf52-code/boards/dongle-fw/old/loopback-nousb16 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/loopback-nousb16 rename to nrf52-code/boards/dongle-fw/old/loopback-nousb16 diff --git a/nrf52-code/boards/dongle-fw/dongle/loopback-nousb21 b/nrf52-code/boards/dongle-fw/old/loopback-nousb21 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/loopback-nousb21 rename to nrf52-code/boards/dongle-fw/old/loopback-nousb21 diff --git a/nrf52-code/boards/dongle-fw/dongle/loopback-nousb26 b/nrf52-code/boards/dongle-fw/old/loopback-nousb26 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/loopback-nousb26 rename to nrf52-code/boards/dongle-fw/old/loopback-nousb26 diff --git a/nrf52-code/boards/dongle-fw/dongle/memory.x b/nrf52-code/boards/dongle-fw/old/memory.x similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/memory.x rename to nrf52-code/boards/dongle-fw/old/memory.x diff --git a/nrf52-code/boards/dongle-fw/dongle/puzzle b/nrf52-code/boards/dongle-fw/old/puzzle similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/puzzle rename to nrf52-code/boards/dongle-fw/old/puzzle diff --git a/nrf52-code/boards/dongle-fw/dongle/puzzle-2020 b/nrf52-code/boards/dongle-fw/old/puzzle-2020 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/puzzle-2020 rename to nrf52-code/boards/dongle-fw/old/puzzle-2020 diff --git a/nrf52-code/boards/dongle-fw/dongle/puzzle-nousb11 b/nrf52-code/boards/dongle-fw/old/puzzle-nousb11 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/puzzle-nousb11 rename to nrf52-code/boards/dongle-fw/old/puzzle-nousb11 diff --git a/nrf52-code/boards/dongle-fw/dongle/puzzle-nousb16 b/nrf52-code/boards/dongle-fw/old/puzzle-nousb16 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/puzzle-nousb16 rename to nrf52-code/boards/dongle-fw/old/puzzle-nousb16 diff --git a/nrf52-code/boards/dongle-fw/dongle/puzzle-nousb21 b/nrf52-code/boards/dongle-fw/old/puzzle-nousb21 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/puzzle-nousb21 rename to nrf52-code/boards/dongle-fw/old/puzzle-nousb21 diff --git a/nrf52-code/boards/dongle-fw/dongle/puzzle-nousb26 b/nrf52-code/boards/dongle-fw/old/puzzle-nousb26 similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/puzzle-nousb26 rename to nrf52-code/boards/dongle-fw/old/puzzle-nousb26 diff --git a/nrf52-code/boards/dongle-fw/dongle/puzzlegen.rs b/nrf52-code/boards/dongle-fw/old/puzzlegen.rs similarity index 100% rename from nrf52-code/boards/dongle-fw/dongle/puzzlegen.rs rename to nrf52-code/boards/dongle-fw/old/puzzlegen.rs From c091af8f6e3fce4acf17dadceb9b96770176e11a Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Wed, 24 Jan 2024 16:54:31 +0000 Subject: [PATCH 4/8] puzzle-fw takes secret input --- .github/workflows/build.yml | 2 ++ build.sh | 1 + nrf52-code/puzzle-fw/Cargo.lock | 54 ++++++++++++++++++++++++++++++++ nrf52-code/puzzle-fw/Cargo.toml | 3 ++ nrf52-code/puzzle-fw/build.rs | 48 +++++++++++++++++++++++----- nrf52-code/puzzle-fw/src/main.rs | 45 +++++++++++++------------- 6 files changed, 121 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84b0d0bf..02eb79f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,8 @@ jobs: echo "slug=${slug}" >> "${GITHUB_ENV}" - name: Build and test + env: # Or as an environment variable + HIDDEN_MESSAGE: ${{ secrets.HIDDEN_MESSAGE }} run: | ./build.sh "./rust-exercises-${{ env.slug }}" diff --git a/build.sh b/build.sh index e95f77a9..ef42a7e6 100755 --- a/build.sh +++ b/build.sh @@ -86,5 +86,6 @@ cp -r ./exercise-solutions "${OUTPUT_NAME}/" cp -r ./nrf52-code "${OUTPUT_NAME}/" cp -r ./xtask "${OUTPUT_NAME}/" cp -r ./.cargo "${OUTPUT_NAME}/" +cp ./nrf52-code/puzzle-fw/target/thumbv7em-none-eabihf/release/puzzle-fw "${OUTPUT_NAME}/nrf52-code/boards/dongle-fw/puzzle-fw" find "${OUTPUT_NAME}" -name target -type d -print0 | xargs -0 rm -rf zip -r "${OUTPUT_NAME}.zip" "${OUTPUT_NAME}" diff --git a/nrf52-code/puzzle-fw/Cargo.lock b/nrf52-code/puzzle-fw/Cargo.lock index e5a92a0b..534ad960 100644 --- a/nrf52-code/puzzle-fw/Cargo.lock +++ b/nrf52-code/puzzle-fw/Cargo.lock @@ -210,6 +210,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "half" version = "2.3.1" @@ -239,6 +250,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + [[package]] name = "nb" version = "0.1.3" @@ -318,6 +335,12 @@ dependencies = [ "defmt", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -361,6 +384,7 @@ dependencies = [ "dongle", "embedded-hal", "heapless", + "rand", "usb-device", "usbd-hid", "usbd-serial", @@ -375,11 +399,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rustc_version" @@ -573,3 +621,9 @@ checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" dependencies = [ "vcell", ] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/nrf52-code/puzzle-fw/Cargo.toml b/nrf52-code/puzzle-fw/Cargo.toml index 09ece9d4..fe8de4e2 100644 --- a/nrf52-code/puzzle-fw/Cargo.toml +++ b/nrf52-code/puzzle-fw/Cargo.toml @@ -16,6 +16,9 @@ usb-device = "0.2.7" usbd-hid = "0.6" usbd-serial = "0.1.0" +[build-dependencies] +rand = "0.8.5" + # optimize code in both profiles [profile.dev] codegen-units = 1 diff --git a/nrf52-code/puzzle-fw/build.rs b/nrf52-code/puzzle-fw/build.rs index f679b8f8..ad5ccb58 100644 --- a/nrf52-code/puzzle-fw/build.rs +++ b/nrf52-code/puzzle-fw/build.rs @@ -1,11 +1,43 @@ +//! Configure the puzzle firmware + +use rand::prelude::*; + fn main() { - if std::env::var("SECRET_MESSAGE").is_err() { - println!("cargo:rustc-env=SECRET_MESSAGE=WELLDONE"); - } - if std::env::var("PLAIN_LETTERS").is_err() { - println!("cargo:rustc-env=PLAIN_LETTERS=abcdefghijklmnopqrstuvwxyz"); - } - if std::env::var("CIPHER_LETTERS").is_err() { - println!("cargo:rustc-env=CIPHER_LETTERS=ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + // We avoid \ to prevent escaping issues + const PLAIN_LETTERS: &str = r##"0123456789 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"##; + + let maybe_msg = std::env::var("HIDDEN_MESSAGE"); + let plaintext = maybe_msg.as_deref().unwrap_or("This is an example message"); + + let mut rng = rand::thread_rng(); + let mut cipher_letters: Vec = PLAIN_LETTERS.bytes().collect(); + cipher_letters.shuffle(&mut rng); + let cipher_letters_str = std::str::from_utf8(&cipher_letters).unwrap(); + + let mut dict = std::collections::HashMap::new(); + for (from, &to) in PLAIN_LETTERS.bytes().zip(cipher_letters.iter()) { + dict.insert(from, to); } + + println!("from: {:?}", PLAIN_LETTERS); + println!("to: {:?}", cipher_letters_str); + println!("plaintext: {:?}", plaintext); + + let encoded: Vec = plaintext.bytes().map(|byte| dict[&byte]).collect(); + let encoded_str = std::str::from_utf8(&encoded).unwrap(); + println!("secret: {:?}", encoded_str); + + output_data("ENCODED_MESSAGE.txt", encoded_str); + output_data("PLAIN_LETTERS.txt", PLAIN_LETTERS); + output_data("CIPHER_LETTERS.txt", cipher_letters_str); + + println!("cargo:rerun-if-env-changed=HIDDEN_MESSAGE"); +} + +fn output_data(filename: &str, value: &str) { + let out_dir: std::path::PathBuf = std::env::var_os("OUT_DIR").unwrap().into(); + let filepath = out_dir.join(filename); + std::fs::write(filepath, value).unwrap(); } + +// End of file diff --git a/nrf52-code/puzzle-fw/src/main.rs b/nrf52-code/puzzle-fw/src/main.rs index c4fedbd0..ce8da792 100644 --- a/nrf52-code/puzzle-fw/src/main.rs +++ b/nrf52-code/puzzle-fw/src/main.rs @@ -20,16 +20,16 @@ use dongle::{ ieee802154::{Channel, Packet}, }; -/// Store the secret. +/// The secret message, but encoded. /// /// We do this rather than the plaintext -- otherwise `strings $elf` will reveal the answer -static SECRET: &[u8] = env!("SECRET_MESSAGE").as_bytes(); +static ENCODED_MESSAGE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/ENCODED_MESSAGE.txt")); /// The plaintext side of the map -static PLAIN_LETTERS: &[u8] = env!("PLAIN_LETTERS").as_bytes(); +static PLAIN_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/PLAIN_LETTERS.txt")); /// The ciphertext side of the map -static CIPHER_LETTERS: &[u8] = env!("CIPHER_LETTERS").as_bytes(); +static CIPHER_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/CIPHER_LETTERS.txt")); /// A 64-byte USB Serial buffer static RING_BUFFER: Ringbuffer = Ringbuffer { @@ -177,17 +177,17 @@ fn main() -> ! { pkt.lqi(), pkt.len() ); - match handle_packet(&pkt, &dict) { + match handle_packet(&mut pkt, &dict) { Command::SendSecret => { - pkt.copy_from_slice(&SECRET); + pkt.copy_from_slice(&ENCODED_MESSAGE); let _ = writeln!(&RING_BUFFER, "TX Secret"); board.leds.ld2_blue.on(); board.leds.ld2_green.off(); board.leds.ld2_red.off(); } - Command::MapChar(from, to) => { - pkt.copy_from_slice(&[to]); - let _ = writeln!(&RING_BUFFER, "TX Map({from}) => {to}"); + Command::MapChar(plain, cipher) => { + pkt.copy_from_slice(&[cipher]); + let _ = writeln!(&RING_BUFFER, "TX Map({plain}) => {cipher}"); board.leds.ld2_blue.off(); board.leds.ld2_green.on(); board.leds.ld2_red.off(); @@ -272,28 +272,25 @@ enum Command { Wrong, } -fn handle_packet(packet: &Packet, dict: &heapless::LinearMap) -> Command { +fn handle_packet(packet: &mut Packet, dict: &heapless::LinearMap) -> Command { if packet.len() == 0 { Command::SendSecret } else if packet.len() == 1 { - // They want to know how convert from X to Y, and they gave us X - let from = packet[0]; - let to = *dict.get(&from).unwrap_or(&0); - Command::MapChar(from, to) + // They give us plaintext, we give them ciphertext + let plain = packet[0]; + let cipher = *dict.get(&plain).unwrap_or(&0); + Command::MapChar(plain, cipher) } else { - let mut correct = packet.len() as usize == SECRET.len(); + // They give us plaintext, we tell them if it is correct // Encrypt every byte of plaintext they give us - for (encrypted, secret) in packet - .iter() - .map(|b| dict.get(b).unwrap_or(&0)) - .zip(SECRET.iter()) - { - if secret != encrypted { - correct = false; + for slot in packet.iter_mut() { + if let Some(c) = dict.get(slot) { + *slot = *c; + } else { + *slot = 0; } } - - if correct { + if &packet[..] == ENCODED_MESSAGE { Command::Correct } else { Command::Wrong From 4167a910678bada702f2094ba368d1dc9817c273 Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Wed, 24 Jan 2024 17:49:57 +0000 Subject: [PATCH 5/8] Add loopback-fw. --- build.sh | 5 + exercise-book/src/nrf52-radio-dongle.md | 8 +- nrf52-code/boards/dongle/src/lib.rs | 14 + nrf52-code/loopback-fw/.cargo/config.toml | 13 + nrf52-code/loopback-fw/.gitignore | 1 + nrf52-code/loopback-fw/Cargo.lock | 629 ++++++++++++++++++++++ nrf52-code/loopback-fw/Cargo.toml | 39 ++ nrf52-code/loopback-fw/src/main.rs | 328 +++++++++++ xtask/src/tasks.rs | 4 +- 9 files changed, 1035 insertions(+), 6 deletions(-) create mode 100644 nrf52-code/loopback-fw/.cargo/config.toml create mode 100644 nrf52-code/loopback-fw/.gitignore create mode 100644 nrf52-code/loopback-fw/Cargo.lock create mode 100644 nrf52-code/loopback-fw/Cargo.toml create mode 100644 nrf52-code/loopback-fw/src/main.rs diff --git a/build.sh b/build.sh index ef42a7e6..88200325 100755 --- a/build.sh +++ b/build.sh @@ -57,6 +57,10 @@ pushd puzzle-fw cargo build --target=thumbv7em-none-eabihf --release cargo fmt --check popd +pushd loopback-fw +cargo build --target=thumbv7em-none-eabihf --release +cargo fmt --check +popd popd # Only build the templates (they will panic at run-time due to the use of todo!) @@ -87,5 +91,6 @@ cp -r ./nrf52-code "${OUTPUT_NAME}/" cp -r ./xtask "${OUTPUT_NAME}/" cp -r ./.cargo "${OUTPUT_NAME}/" cp ./nrf52-code/puzzle-fw/target/thumbv7em-none-eabihf/release/puzzle-fw "${OUTPUT_NAME}/nrf52-code/boards/dongle-fw/puzzle-fw" +cp ./nrf52-code/loopback-fw/target/thumbv7em-none-eabihf/release/loopback-fw "${OUTPUT_NAME}/nrf52-code/boards/dongle-fw/loopback-fw" find "${OUTPUT_NAME}" -name target -type d -print0 | xargs -0 rm -rf zip -r "${OUTPUT_NAME}.zip" "${OUTPUT_NAME}" diff --git a/exercise-book/src/nrf52-radio-dongle.md b/exercise-book/src/nrf52-radio-dongle.md index 46240061..42c1b71b 100644 --- a/exercise-book/src/nrf52-radio-dongle.md +++ b/exercise-book/src/nrf52-radio-dongle.md @@ -58,7 +58,7 @@ After the device has been programmed it will automatically reset and start runni ```console $ cargo xtask usb-list (..) -Bus 001 Device 020: ID 1209:0309 <- nRF52840 Dongle (loopback.hex) +Bus 001 Device 020: ID 1209:0309 <- nRF52840 Dongle (loopback-fw) ``` The `loopback` app will log messages over the USB interface. To display these messages on the host we have provided a cross-platform tool: `cargo xtask serial-term`. @@ -69,7 +69,7 @@ The `loopback` app will log messages over the USB interface. To display these me ```console $ cargo xtask serial-term -deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback.hex +deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback-fw ``` This line is printed by the `loopback` app on boot. It contains the device ID of the dongle, a 64-bit unique identifier (so everyone will see a different number); the radio channel that the device will use to communicate; and the transmission power of the radio in dBm. @@ -86,7 +86,7 @@ At this point you should *not* get more output from `cargo xtask serial-term`. ```console $ cargo xtask serial-term -deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm +deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback-fw received 7 bytes (CRC=Ok(0x2459), LQI=0) received 5 bytes (CRC=Ok(0xdad9), LQI=0) received 6 bytes (CRC=Ok(0x72bb), LQI=0) @@ -102,7 +102,7 @@ requested channel change to channel 11 Then you should see new output from `cargo xtask serial-term`: ```console -deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm +deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback-fw (..) now listening on channel 11 ``` diff --git a/nrf52-code/boards/dongle/src/lib.rs b/nrf52-code/boards/dongle/src/lib.rs index b345e706..e5b7f235 100644 --- a/nrf52-code/boards/dongle/src/lib.rs +++ b/nrf52-code/boards/dongle/src/lib.rs @@ -359,3 +359,17 @@ pub fn uptime() -> Duration { (((ticks % 32768).wrapping_mul(78125) >> 2).wrapping_mul(5) >> 2).wrapping_mul(5) >> 2; Duration::new(secs, nanos as u32) } + +/// Returns the least-significant bits of the device identifier +pub fn deviceid0() -> u32 { + // NOTE(unsafe) read-only registers, and no other use of the block + let ficr = unsafe { &*hal::pac::FICR::ptr() }; + ficr.deviceid[0].read().deviceid().bits() +} + +/// Returns the most-significant bits of the device identifier +pub fn deviceid1() -> u32 { + // NOTE(unsafe) read-only registers, and no other use of the block + let ficr = unsafe { &*hal::pac::FICR::ptr() }; + ficr.deviceid[1].read().deviceid().bits() +} diff --git a/nrf52-code/loopback-fw/.cargo/config.toml b/nrf52-code/loopback-fw/.cargo/config.toml new file mode 100644 index 00000000..bbe3663d --- /dev/null +++ b/nrf52-code/loopback-fw/.cargo/config.toml @@ -0,0 +1,13 @@ +[target.thumbv7em-none-eabihf] +# set custom cargo runner to flash & run on embedded target when we call `cargo run` +# for more information, check out https://github.com/probe-rs +runner = "probe-rs run --chip nRF52840_xxAA" +rustflags = [ + "-C", "link-arg=-Tlink.x", # use the cortex-m-rt linker script + "-C", "linker=flip-link", # adds stack overflow protection + "-C", "link-arg=-Tdefmt.x", # defmt support +] + +[build] +# cross-compile to this target +target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 with FPU diff --git a/nrf52-code/loopback-fw/.gitignore b/nrf52-code/loopback-fw/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/nrf52-code/loopback-fw/.gitignore @@ -0,0 +1 @@ +target diff --git a/nrf52-code/loopback-fw/Cargo.lock b/nrf52-code/loopback-fw/Cargo.lock new file mode 100644 index 00000000..aa3bbca5 --- /dev/null +++ b/nrf52-code/loopback-fw/Cargo.lock @@ -0,0 +1,629 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "consts" +version = "0.1.0" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "critical-section", + "embedded-hal", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cortex-m-semihosting" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c23234600452033cc77e4b761e740e02d2c4168e11dbf36ab14a0f58973592b0" +dependencies = [ + "cortex-m", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "defmt" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "defmt-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609923761264dd99ed9c7d209718cda4631c5fe84668e0f0960124cbb844c49f" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "dongle" +version = "0.0.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "cortex-m-semihosting", + "defmt", + "defmt-rtt", + "embedded-hal", + "nrf52840-hal", + "panic-probe", +] + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "fixed" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c69ce7e7c0f17aa18fdd9d0de39727adb9c6281f2ad12f57cbe54ae6e76e7d" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "loopback-fw" +version = "0.0.0" +dependencies = [ + "consts", + "cortex-m", + "cortex-m-rt", + "dongle", + "embedded-hal", + "heapless", + "rand", + "usb-device", + "usbd-hid", + "usbd-serial", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nrf-hal-common" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd244c63d588066d75cffdcae8a03299fd5fb272e36bdde4a9f922f3e4bc6e4b" +dependencies = [ + "cast", + "cfg-if", + "cortex-m", + "embedded-dma", + "embedded-hal", + "embedded-storage", + "fixed", + "nb 1.1.0", + "nrf-usbd", + "nrf52840-pac", + "rand_core", + "void", +] + +[[package]] +name = "nrf-usbd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b2907c0b3ec4d264981c1fc5a2321d51c463d5a63d386e573f00e84d5495e6" +dependencies = [ + "cortex-m", + "critical-section", + "usb-device", + "vcell", +] + +[[package]] +name = "nrf52840-hal" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54757ec98f38331889d1d6c535edb5dd543c762751abfe507f2d644b30f6d4f" +dependencies = [ + "embedded-hal", + "nrf-hal-common", + "nrf52840-pac", +] + +[[package]] +name = "nrf52840-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30713f36f1be02e5bc9abefa30eae4a1f943d810f199d4923d3ad062d1be1b3d" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "panic-probe" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "usb-device" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" + +[[package]] +name = "usbd-hid" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1" +dependencies = [ + "bitfield", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2" +dependencies = [ + "byteorder", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + +[[package]] +name = "usbd-serial" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db75519b86287f12dcf0d171c7cf4ecc839149fe9f3b720ac4cfce52959e1dfe" +dependencies = [ + "embedded-hal", + "nb 0.1.3", + "usb-device", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/nrf52-code/loopback-fw/Cargo.toml b/nrf52-code/loopback-fw/Cargo.toml new file mode 100644 index 00000000..e122340d --- /dev/null +++ b/nrf52-code/loopback-fw/Cargo.toml @@ -0,0 +1,39 @@ +[package] +authors = ["Ferrous Systems"] +edition = "2021" +license = "MIT OR Apache-2.0" +name = "loopback-fw" +version = "0.0.0" + +[dependencies] +consts = { path = "../consts" } +cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]} +cortex-m-rt = "0.7" +dongle = { path = "../boards/dongle" } +embedded-hal = "0.2.7" +heapless = "0.8" +usb-device = "0.2.7" +usbd-hid = "0.6" +usbd-serial = "0.1.0" + +[build-dependencies] +rand = "0.8.5" + +# optimize code in both profiles +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # ! +incremental = false +lto = "fat" +opt-level = 'z' # ! +overflow-checks = false + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 3 +overflow-checks = false diff --git a/nrf52-code/loopback-fw/src/main.rs b/nrf52-code/loopback-fw/src/main.rs new file mode 100644 index 00000000..1d571a0a --- /dev/null +++ b/nrf52-code/loopback-fw/src/main.rs @@ -0,0 +1,328 @@ +//! Firmware for the nRF52840 Dongle, for echoing packets in loopback mode +//! +//! Sets up a USB Serial port and listens for radio packets. + +#![no_std] +#![no_main] + +use core::fmt::Write; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; + +use cortex_m_rt::entry; +use usb_device::class_prelude::UsbBusAllocator; +use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; +use usbd_hid::hid_class::HIDClass; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +use dongle::peripheral::interrupt; +use dongle::{ + hal::usbd, + ieee802154::{Channel, Packet}, +}; + +/// A 64-byte USB Serial buffer +static RING_BUFFER: Ringbuffer = Ringbuffer { + buffer: heapless::mpmc::Q64::new(), + force_buffer: AtomicBool::new(true), +}; + +/// A short-hand for the nRF52 USB types +type UsbBus<'a> = usbd::Usbd>; + +/// The USB Device Driver (owned by the USBD interrupt). +static mut USB_DEVICE: Option> = None; + +/// The USB Bus Driver (owned by the USBD interrupt). +static mut USB_BUS: Option> = None; + +/// The USB Serial Device Driver (owned by the USBD interrupt). +static mut USB_SERIAL: Option> = None; + +/// The USB Human Interface Device Driver (owned by the USBD interrupt). +static mut USB_HID: Option> = None; + +/// Track how many CRC successes we had receiving radio packets +static RX_COUNT: AtomicU32 = AtomicU32::new(0); + +/// Track how many CRC failures we had receiving radio packets +static ERR_COUNT: AtomicU32 = AtomicU32::new(0); + +/// The USB interrupt sets this to < u32::MAX when a new channel is sent over HID. +/// +/// The main loop handles it and sets it back to u32::MAX when processed. +static NEW_CHANNEL: AtomicU32 = AtomicU32::new(u32::MAX); + +/// Set to true when we get a ?. +/// +/// We print some info in response. +static WANT_INFO: AtomicBool = AtomicBool::new(false); + +/// Is the UART connected? +static IS_CONNECTED: AtomicBool = AtomicBool::new(false); + +struct Ringbuffer { + buffer: heapless::mpmc::Q64, + force_buffer: AtomicBool, +} + +impl core::fmt::Write for &Ringbuffer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + for b in s.bytes() { + if IS_CONNECTED.load(Ordering::Relaxed) || self.force_buffer.load(Ordering::Relaxed) { + // spin until space in the UART + while let Err(_) = self.buffer.enqueue(b) { + cortex_m::asm::wfi(); + } + } else { + // drop the data - we're not connected + } + } + Ok(()) + } +} + +#[entry] +fn main() -> ! { + let mut board = dongle::init().unwrap(); + board.usbd.inten.modify(|_r, w| { + w.sof().set_bit(); + w + }); + let usb_bus = UsbBusAllocator::new(usbd::Usbd::new(usbd::UsbPeripheral::new( + board.usbd, + board.clocks, + ))); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet + USB_BUS = Some(usb_bus); + } + // Grab a reference to the USB Bus allocator. We are promising to the + // compiler not to take mutable access to this global variable whilst this + // reference exists! + let bus_ref = unsafe { USB_BUS.as_ref().unwrap() }; + let serial = SerialPort::new(bus_ref); + unsafe { + USB_SERIAL = Some(serial); + } + + let desc = &[ + 0x06, 0x00, 0xFF, // Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 + 0x09, 0x01, // Item(Local ): Usage, data= [ 0x01 ] 1 + 0xA1, 0x01, // Item(Main ): Collection, data= [ 0x01 ] 1 + // Application + 0x15, 0x00, // Item(Global): Logical Minimum, data= [ 0x00 ] 0 + 0x26, 0xFF, 0x00, // Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 + 0x75, 0x08, // Item(Global): Report Size, data= [ 0x08 ] 8 + 0x95, 0x40, // Item(Global): Report Count, data= [ 0x40 ] 64 + 0x09, 0x01, // Item(Local ): Usage, data= [ 0x01 ] 1 + 0x81, 0x02, // Item(Main ): Input, data= [ 0x02 ] 2 + // Data Variable Absolute No_Wrap Linear + // Preferred_State No_Null_Position Non_Volatile Bitfield + 0x95, 0x40, // Item(Global): Report Count, data= [ 0x40 ] 64 + 0x09, 0x01, // Item(Local ): Usage, data= [ 0x01 ] 1 + 0x91, 0x02, // Item(Main ): Output, data= [ 0x02 ] 2 + // Data Variable Absolute No_Wrap Linear + // Preferred_State No_Null_Position Non_Volatile Bitfield + 0x95, 0x01, // Item(Global): Report Count, data= [ 0x01 ] 1 + 0x09, 0x01, // Item(Local ): Usage, data= [ 0x01 ] 1 + 0xB1, 0x02, // Item(Main ): Feature, data= [ 0x02 ] 2 + // Data Variable Absolute No_Wrap Linear + // Preferred_State No_Null_Position Non_Volatile Bitfield + 0xC0, // Item(Main ): End Collection, data=none + ]; + let hid = HIDClass::new(bus_ref, desc, 100); + unsafe { + USB_HID = Some(hid); + } + + let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE); + let usb_dev = UsbDeviceBuilder::new(bus_ref, vid_pid) + .manufacturer("Ferrous Systems") + .product("Dongle Puzzle") + .device_class(USB_CLASS_CDC) + .max_packet_size_0(64) // (makes control transfers 8x faster) + .build(); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet + USB_DEVICE = Some(usb_dev); + } + + let mut current_ch_id = 25; + board.radio.set_channel(dongle::ieee802154::Channel::_25); + + // Turn on USB interrupts... + unsafe { + cortex_m::peripheral::NVIC::unmask(dongle::peripheral::Interrupt::USBD); + }; + + let _ = writeln!( + &RING_BUFFER, + "deviceid={:08x}{:08x} channel={} TxPower=+8dBm app=loopback-fw", + dongle::deviceid1(), + dongle::deviceid0(), + current_ch_id + ); + + // Now the sign-up message is done, turn off force buffering + RING_BUFFER.force_buffer.store(false, Ordering::Relaxed); + + board.leds.ld1.on(); + board.leds.ld2_blue.on(); + let mut pkt = Packet::new(); + loop { + // Wait up to 1 second for a radio packet + match board + .radio + .recv_timeout(&mut pkt, &mut board.timer, 1_000_000) + { + Ok(crc) => { + board.leds.ld1.toggle(); + let _ = writeln!( + &RING_BUFFER, + "received {} bytes (CRC=Ok(0x{:04x}), LQI={})", + pkt.len(), + crc, + pkt.lqi() + ); + // now send it back + board.radio.send(&mut pkt); + RX_COUNT.fetch_add(1, Ordering::Relaxed); + } + Err(dongle::ieee802154::Error::Crc(_)) => { + ERR_COUNT.fetch_add(1, Ordering::Relaxed); + } + Err(dongle::ieee802154::Error::Timeout) => { + // do nothing + } + } + + // Handle channel changes + let ch_id = NEW_CHANNEL.load(Ordering::Relaxed); + if ch_id != u32::MAX { + NEW_CHANNEL.store(u32::MAX, Ordering::Relaxed); + if let Some(channel) = match ch_id { + 11 => Some(Channel::_11), + 12 => Some(Channel::_12), + 13 => Some(Channel::_13), + 14 => Some(Channel::_14), + 15 => Some(Channel::_15), + 16 => Some(Channel::_16), + 17 => Some(Channel::_17), + 18 => Some(Channel::_18), + 19 => Some(Channel::_19), + 20 => Some(Channel::_20), + 21 => Some(Channel::_21), + 22 => Some(Channel::_22), + 23 => Some(Channel::_23), + 24 => Some(Channel::_24), + 25 => Some(Channel::_25), + 26 => Some(Channel::_26), + _ => None, + } { + board.radio.set_channel(channel); + let _ = writeln!(&RING_BUFFER, "now listening on channel {}", ch_id); + current_ch_id = ch_id; + } else { + let _ = writeln!(&RING_BUFFER, "Channel {} invalid", ch_id); + } + } + + let connected = IS_CONNECTED.load(Ordering::Relaxed); + if connected { + board.leds.ld2_red.on(); + } else { + board.leds.ld2_red.off(); + } + + // Print help text when ? is pressed + if WANT_INFO.load(Ordering::Relaxed) { + WANT_INFO.store(false, Ordering::Relaxed); + let _ = writeln!( + &RING_BUFFER, + "rx={}, err={}, ch={}, app=loopback-fw", + RX_COUNT.load(Ordering::Relaxed), + ERR_COUNT.load(Ordering::Relaxed), + current_ch_id, + ); + } + } +} + +/// Handles USB interrupts +/// +/// Polls all the USB devices, and copies bytes from [`RING_BUFFER`] into the +/// USB UART. +#[interrupt] +fn USBD() { + // Grab the global objects. This is OK as we only access them under interrupt. + let usb_dev = unsafe { USB_DEVICE.as_mut().unwrap() }; + let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; + let hid = unsafe { USB_HID.as_mut().unwrap() }; + + let mut buf = [0u8; 64]; + + // Poll the USB driver with all of our supported USB Classes + if usb_dev.poll(&mut [serial, hid]) { + match serial.read(&mut buf) { + Err(_e) => { + // Do nothing + } + Ok(0) => { + // Do nothing + } + Ok(count) => { + for item in &buf[0..count] { + // Look for question marks + if *item == b'?' { + WANT_INFO.store(true, Ordering::Relaxed); + } + } + } + } + let hid_byte = match hid.pull_raw_output(&mut buf) { + Ok(64) => { + // Windows zero-pads the packet + Some(buf[0]) + } + Ok(1) => { + // macOS/Linux sends a single byte + Some(buf[0]) + } + Ok(_n) => { + // Ignore any other size packet + None + } + Err(_e) => None, + }; + if let Some(ch) = hid_byte { + NEW_CHANNEL.store(ch as u32, Ordering::Relaxed); + } + } + + // Copy from ring-buffer to USB UART + let mut count = 0; + while count < buf.len() { + if let Some(item) = RING_BUFFER.buffer.dequeue() { + buf[count] = item; + count += 1; + } else { + break; + } + } + let _ = serial.write(&buf[0..count]); + IS_CONNECTED.store(serial.dtr(), Ordering::Relaxed); + cortex_m::asm::sev(); +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(&RING_BUFFER, "Panic: {:?}", info); + cortex_m::asm::delay(64_000_000 * 2); + unsafe { + loop { + core::arch::asm!("bkpt 0x00"); + } + } +} + +// End of file diff --git a/xtask/src/tasks.rs b/xtask/src/tasks.rs index 46aabeb8..8a8db885 100644 --- a/xtask/src/tasks.rs +++ b/xtask/src/tasks.rs @@ -88,8 +88,8 @@ pub fn usb_list() -> color_eyre::Result<()> { let suffix = match (desc.vendor_id(), desc.product_id()) { (0x1366, pid) if (pid >> 8) == 0x10 || (pid >> 8) == 0x01 => " <- J-Link on the nRF52840 Development Kit", (0x1915, 0x521f) => " <- nRF52840 Dongle (in bootloader mode)", - (consts::USB_VID_DEMO, consts::USB_PID_DONGLE_LOOPBACK) => " <- nRF52840 Dongle (loopback.hex)", - (consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE) => " <- nRF52840 Dongle (puzzle.hex)", + (consts::USB_VID_DEMO, consts::USB_PID_DONGLE_LOOPBACK) => " <- nRF52840 Dongle (loopback-fw)", + (consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE) => " <- nRF52840 Dongle (puzzle-fw)", (consts::USB_VID_DEMO, consts::USB_PID_RTIC_DEMO) => " <- nRF52840 on the nRF52840 Development Kit", _ => "", }; From a10566f985fcc283c2027a82bd1ca6d86d68d41e Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Wed, 24 Jan 2024 17:50:20 +0000 Subject: [PATCH 6/8] Clean up names. --- exercise-book/src/nrf52-radio-puzzle-help.md | 2 +- exercise-book/src/nrf52-radio-puzzle.md | 4 +-- exercise-book/src/nrf52-radio-setup.md | 2 +- nrf52-code/boards/dongle-fw/README.md | 27 +++++++++++++++++++ nrf52-code/puzzle-fw/src/main.rs | 1 - .../radio-app/src/bin/radio-puzzle-1.rs | 2 +- .../radio-app/src/bin/radio-puzzle-3.rs | 2 +- .../radio-app/src/bin/radio-puzzle-5.rs | 2 +- .../radio-app/src/bin/radio-puzzle-6.rs | 2 +- .../radio-app/src/bin/radio-puzzle-7.rs | 2 +- .../src/bin/radio-puzzle-solution-2.rs | 2 +- .../src/bin/radio-puzzle-solution.rs | 2 +- nrf52-code/radio-app/src/bin/radio-puzzle.rs | 2 +- 13 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 nrf52-code/boards/dongle-fw/README.md diff --git a/exercise-book/src/nrf52-radio-puzzle-help.md b/exercise-book/src/nrf52-radio-puzzle-help.md index db8cfc42..59bb5043 100644 --- a/exercise-book/src/nrf52-radio-puzzle-help.md +++ b/exercise-book/src/nrf52-radio-puzzle-help.md @@ -40,7 +40,7 @@ Something you will likely run into while solving this exercise are *character* l *IMPORTANT* you do not need to use the `str` or `char` API to solve this problem, other than for printing purposes. Work directly with slices of bytes (`[u8]`) and bytes (`u8`); and only convert those to `str` or `char` when you are about to print them. -> Note: The plaintext string is *not* stored in `puzzle.hex` so running `strings` on it will not give you the answer. Nice try. +> Note: The plaintext string is *not* stored in `puzzle-fw` so running `strings` on it will not give you the answer. Nice try. ## Make sure not to flood the log buffer diff --git a/exercise-book/src/nrf52-radio-puzzle.md b/exercise-book/src/nrf52-radio-puzzle.md index 2df6db4a..e8453980 100644 --- a/exercise-book/src/nrf52-radio-puzzle.md +++ b/exercise-book/src/nrf52-radio-puzzle.md @@ -9,9 +9,9 @@ Your task in this section is to decrypt the [substitution cipher] encrypted *ASC [substitution cipher]: https://en.wikipedia.org/wiki/Substitution_cipher [`heapless`]: https://docs.rs/heapless -✅ Flash the `puzzle.hex` program on the Dongle. Follow the instructions from the "nRF52840 Dongle" section but flash the `puzzle.hex` program instead of the `loopback.hex` one -- don't forget to put the Dongle in bootloader mode before invoking `nrfdfu`. +✅ Flash the `puzzle-fw` program on the Dongle. Follow the instructions from the "nRF52840 Dongle" section but flash the `puzzle-fw` program instead of the `loopback-fw` one -- don't forget to put the Dongle in bootloader mode before invoking `nrfdfu`. -> Note: If you experienced USB issues with `loopback.hex` you can use the `puzzle-nousb*.hex` variants. +> Note: If you experienced USB issues with `loopback-fw` you can use the older `puzzle-nousb*.hex` variants. Like in the previous sections the Dongle will listen for radio packets -- this time over *channel 25* -- while also logging messages over a USB/serial interface. diff --git a/exercise-book/src/nrf52-radio-setup.md b/exercise-book/src/nrf52-radio-setup.md index a73b519f..79ffef95 100644 --- a/exercise-book/src/nrf52-radio-setup.md +++ b/exercise-book/src/nrf52-radio-setup.md @@ -6,7 +6,7 @@ ```console $ cargo xtask serial-term -deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback.hex +deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback-fw received 5 bytes (CRC=Ok(0xdad9), LQI=53) ``` diff --git a/nrf52-code/boards/dongle-fw/README.md b/nrf52-code/boards/dongle-fw/README.md new file mode 100644 index 00000000..230e3668 --- /dev/null +++ b/nrf52-code/boards/dongle-fw/README.md @@ -0,0 +1,27 @@ +# nRF52840 USB Dongle Firmware + +This folder is empty on Github, but in a release .zip archive it will contain: + +* Puzzle Firmware - see <../../puzzle-fw> +* Loopback Firmware - see <../../loopback-fw> + +## Loading Firmware + +You can load one firmware image at a time into your USB dongle. Load the +firmware image with `nrfdfu`: + +```console +$ cargo install nrfdfu +$ # Now press the dongle's 'Reset' button - the red LED should come on... +$ nrfdfu ./nrf52-code/boards/dongle-fw/puzzle-fw +[INFO nrfdfu] Sending init packet... +[INFO nrfdfu] Sending firmware image of size 37568... +[INFO nrfdfu] Done. +``` + +## Compiling Firmware + +Enter the source directory for the firmware, and run `cargo build --release`. +Note that if you compile your own puzzle firmware, you won't have the same +secret message as everyone else because it's not in the source code anywhere +(it's a Github Secret). diff --git a/nrf52-code/puzzle-fw/src/main.rs b/nrf52-code/puzzle-fw/src/main.rs index ce8da792..86600a88 100644 --- a/nrf52-code/puzzle-fw/src/main.rs +++ b/nrf52-code/puzzle-fw/src/main.rs @@ -155,7 +155,6 @@ fn main() -> ! { } // Turn on USB interrupts... - // Enable the USB interrupt unsafe { cortex_m::peripheral::NVIC::unmask(dongle::peripheral::Interrupt::USBD); }; diff --git a/nrf52-code/radio-app/src/bin/radio-puzzle-1.rs b/nrf52-code/radio-app/src/bin/radio-puzzle-1.rs index e44c04aa..c1744278 100644 --- a/nrf52-code/radio-app/src/bin/radio-puzzle-1.rs +++ b/nrf52-code/radio-app/src/bin/radio-puzzle-1.rs @@ -15,7 +15,7 @@ fn main() -> ! { let mut radio = board.radio; let mut timer = board.timer; - // puzzle.hex uses channel 25 by default + // puzzle-fw uses channel 25 by default // NOTE if you ran `change-channel` then you may need to update the channel here radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel diff --git a/nrf52-code/radio-app/src/bin/radio-puzzle-3.rs b/nrf52-code/radio-app/src/bin/radio-puzzle-3.rs index b42ed976..75083011 100644 --- a/nrf52-code/radio-app/src/bin/radio-puzzle-3.rs +++ b/nrf52-code/radio-app/src/bin/radio-puzzle-3.rs @@ -16,7 +16,7 @@ fn main() -> ! { let mut radio = board.radio; let mut timer = board.timer; - // puzzle.hex uses channel 25 by default + // puzzle-fw uses channel 25 by default // NOTE if you ran `change-channel` then you may need to update the channel here radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel diff --git a/nrf52-code/radio-app/src/bin/radio-puzzle-5.rs b/nrf52-code/radio-app/src/bin/radio-puzzle-5.rs index 4b7b2a95..f34d211a 100644 --- a/nrf52-code/radio-app/src/bin/radio-puzzle-5.rs +++ b/nrf52-code/radio-app/src/bin/radio-puzzle-5.rs @@ -18,7 +18,7 @@ fn main() -> ! { let mut radio = board.radio; let mut timer = board.timer; - // puzzle.hex uses channel 25 by default + // puzzle-fw uses channel 25 by default // NOTE if you ran `change-channel` then you may need to update the channel here radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel diff --git a/nrf52-code/radio-app/src/bin/radio-puzzle-6.rs b/nrf52-code/radio-app/src/bin/radio-puzzle-6.rs index 4ce9bcab..60020225 100644 --- a/nrf52-code/radio-app/src/bin/radio-puzzle-6.rs +++ b/nrf52-code/radio-app/src/bin/radio-puzzle-6.rs @@ -18,7 +18,7 @@ fn main() -> ! { let mut radio = board.radio; let mut timer = board.timer; - // puzzle.hex uses channel 25 by default + // puzzle-fw uses channel 25 by default // NOTE if you ran `change-channel` then you may need to update the channel here radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel diff --git a/nrf52-code/radio-app/src/bin/radio-puzzle-7.rs b/nrf52-code/radio-app/src/bin/radio-puzzle-7.rs index b85d870c..d3e1eb3f 100644 --- a/nrf52-code/radio-app/src/bin/radio-puzzle-7.rs +++ b/nrf52-code/radio-app/src/bin/radio-puzzle-7.rs @@ -18,7 +18,7 @@ fn main() -> ! { let mut radio = board.radio; let mut timer = board.timer; - // puzzle.hex uses channel 25 by default + // puzzle-fw uses channel 25 by default // NOTE if you ran `change-channel` then you may need to update the channel here radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel diff --git a/nrf52-code/radio-app/src/bin/radio-puzzle-solution-2.rs b/nrf52-code/radio-app/src/bin/radio-puzzle-solution-2.rs index 01859abf..67438665 100644 --- a/nrf52-code/radio-app/src/bin/radio-puzzle-solution-2.rs +++ b/nrf52-code/radio-app/src/bin/radio-puzzle-solution-2.rs @@ -18,7 +18,7 @@ fn main() -> ! { let mut radio = board.radio; let mut timer = board.timer; - // puzzle.hex uses channel 25 by default + // puzzle-fw uses channel 25 by default // NOTE if you ran `change-channel` then you may need to update the channel here radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel diff --git a/nrf52-code/radio-app/src/bin/radio-puzzle-solution.rs b/nrf52-code/radio-app/src/bin/radio-puzzle-solution.rs index 7c46cd81..a73f3d76 100644 --- a/nrf52-code/radio-app/src/bin/radio-puzzle-solution.rs +++ b/nrf52-code/radio-app/src/bin/radio-puzzle-solution.rs @@ -18,7 +18,7 @@ fn main() -> ! { let mut radio = board.radio; let mut timer = board.timer; - // puzzle.hex uses channel 25 by default + // puzzle-fw uses channel 25 by default // NOTE if you ran `change-channel` then you may need to update the channel here radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel diff --git a/nrf52-code/radio-app/src/bin/radio-puzzle.rs b/nrf52-code/radio-app/src/bin/radio-puzzle.rs index 0c3b1e8f..2dd45b1f 100644 --- a/nrf52-code/radio-app/src/bin/radio-puzzle.rs +++ b/nrf52-code/radio-app/src/bin/radio-puzzle.rs @@ -17,7 +17,7 @@ fn main() -> ! { let mut radio = board.radio; let mut timer = board.timer; - // puzzle.hex uses channel 25 by default + // puzzle-fw uses channel 25 by default // NOTE if you ran `change-channel` then you may need to update the channel here radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel From 82b3e0a530d9fe19022b041eb68aebf5ee19acc2 Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Thu, 25 Jan 2024 13:20:19 +0000 Subject: [PATCH 7/8] Clean up USB Serial code. * dongle BSP provides RingBuffer and UsbBus types. * use critical-section mutexes to handle Usb types, not static mut * Add a 5ms delay before replying - makes it much more reliable --- nrf52-code/boards/dongle/Cargo.toml | 1 + nrf52-code/boards/dongle/src/lib.rs | 41 +++++++ nrf52-code/loopback-fw/Cargo.lock | 3 +- nrf52-code/loopback-fw/Cargo.toml | 2 +- nrf52-code/loopback-fw/src/main.rs | 164 ++++++++++++++-------------- nrf52-code/puzzle-fw/Cargo.lock | 2 + nrf52-code/puzzle-fw/Cargo.toml | 1 + nrf52-code/puzzle-fw/src/main.rs | 164 +++++++++++++++++----------- 8 files changed, 231 insertions(+), 147 deletions(-) diff --git a/nrf52-code/boards/dongle/Cargo.toml b/nrf52-code/boards/dongle/Cargo.toml index b1de4840..05694d37 100644 --- a/nrf52-code/boards/dongle/Cargo.toml +++ b/nrf52-code/boards/dongle/Cargo.toml @@ -13,4 +13,5 @@ defmt = "0.3.5" defmt-rtt = "0.4" embedded-hal = "0.2.7" hal = { package = "nrf52840-hal", version = "0.16.0" } +heapless = "0.8.0" panic-probe = { version = "0.3.0", features = ["print-defmt"] } diff --git a/nrf52-code/boards/dongle/src/lib.rs b/nrf52-code/boards/dongle/src/lib.rs index e5b7f235..480f3812 100644 --- a/nrf52-code/boards/dongle/src/lib.rs +++ b/nrf52-code/boards/dongle/src/lib.rs @@ -35,6 +35,9 @@ pub mod peripheral { pub use hal::pac::{interrupt, Interrupt, POWER, USBD}; } +/// A short-hand for the nRF52 USB types +pub type UsbBus = hal::usbd::Usbd>; + use peripheral::interrupt; /// Components on the board @@ -190,6 +193,44 @@ impl ops::DerefMut for Timer { } } +/// A byte-based ring-buffer that you can writeln! into, and drain under +/// interrupt. +/// +/// Used for buffering serial port output. +/// +/// Stores 128 bytes, maximum. +pub struct Ringbuffer { + buffer: heapless::mpmc::MpMcQueue, +} + +impl Ringbuffer { + /// Construct a new Ringbuffer + pub const fn new() -> Ringbuffer { + Ringbuffer { + buffer: heapless::mpmc::MpMcQueue::new(), + } + } + + /// Take an item from the buffer + pub fn read(&self) -> Option { + self.buffer.dequeue() + } + + /// Add an item to the queue + pub fn write(&self, value: u8) -> Result<(), u8> { + self.buffer.enqueue(value) + } +} + +impl core::fmt::Write for &Ringbuffer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + for b in s.bytes() { + let _ = self.buffer.enqueue(b); + } + Ok(()) + } +} + /// The ways that initialisation can fail #[derive(Debug, Copy, Clone, defmt::Format)] pub enum Error { diff --git a/nrf52-code/loopback-fw/Cargo.lock b/nrf52-code/loopback-fw/Cargo.lock index aa3bbca5..23cdad2d 100644 --- a/nrf52-code/loopback-fw/Cargo.lock +++ b/nrf52-code/loopback-fw/Cargo.lock @@ -163,6 +163,7 @@ dependencies = [ "defmt", "defmt-rtt", "embedded-hal", + "heapless", "nrf52840-hal", "panic-probe", ] @@ -263,9 +264,9 @@ dependencies = [ "consts", "cortex-m", "cortex-m-rt", + "critical-section", "dongle", "embedded-hal", - "heapless", "rand", "usb-device", "usbd-hid", diff --git a/nrf52-code/loopback-fw/Cargo.toml b/nrf52-code/loopback-fw/Cargo.toml index e122340d..0acc7ecf 100644 --- a/nrf52-code/loopback-fw/Cargo.toml +++ b/nrf52-code/loopback-fw/Cargo.toml @@ -9,9 +9,9 @@ version = "0.0.0" consts = { path = "../consts" } cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]} cortex-m-rt = "0.7" +critical-section = "1.1.2" dongle = { path = "../boards/dongle" } embedded-hal = "0.2.7" -heapless = "0.8" usb-device = "0.2.7" usbd-hid = "0.6" usbd-serial = "0.1.0" diff --git a/nrf52-code/loopback-fw/src/main.rs b/nrf52-code/loopback-fw/src/main.rs index 1d571a0a..cba73573 100644 --- a/nrf52-code/loopback-fw/src/main.rs +++ b/nrf52-code/loopback-fw/src/main.rs @@ -5,10 +5,12 @@ #![no_std] #![no_main] +use core::cell::RefCell; use core::fmt::Write; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use cortex_m_rt::entry; +use critical_section::Mutex; use usb_device::class_prelude::UsbBusAllocator; use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; use usbd_hid::hid_class::HIDClass; @@ -18,28 +20,20 @@ use dongle::peripheral::interrupt; use dongle::{ hal::usbd, ieee802154::{Channel, Packet}, + UsbBus, }; -/// A 64-byte USB Serial buffer -static RING_BUFFER: Ringbuffer = Ringbuffer { - buffer: heapless::mpmc::Q64::new(), - force_buffer: AtomicBool::new(true), -}; - -/// A short-hand for the nRF52 USB types -type UsbBus<'a> = usbd::Usbd>; +/// A buffer for holding bytes we want to send to the USB Serial port +static RING_BUFFER: dongle::Ringbuffer = dongle::Ringbuffer::new(); /// The USB Device Driver (owned by the USBD interrupt). -static mut USB_DEVICE: Option> = None; - -/// The USB Bus Driver (owned by the USBD interrupt). -static mut USB_BUS: Option> = None; +static USB_DEVICE: Mutex>>> = Mutex::new(RefCell::new(None)); /// The USB Serial Device Driver (owned by the USBD interrupt). -static mut USB_SERIAL: Option> = None; +static USB_SERIAL: Mutex>>> = Mutex::new(RefCell::new(None)); /// The USB Human Interface Device Driver (owned by the USBD interrupt). -static mut USB_HID: Option> = None; +static USB_HID: Mutex>>> = Mutex::new(RefCell::new(None)); /// Track how many CRC successes we had receiving radio packets static RX_COUNT: AtomicU32 = AtomicU32::new(0); @@ -57,53 +51,33 @@ static NEW_CHANNEL: AtomicU32 = AtomicU32::new(u32::MAX); /// We print some info in response. static WANT_INFO: AtomicBool = AtomicBool::new(false); -/// Is the UART connected? -static IS_CONNECTED: AtomicBool = AtomicBool::new(false); - -struct Ringbuffer { - buffer: heapless::mpmc::Q64, - force_buffer: AtomicBool, -} - -impl core::fmt::Write for &Ringbuffer { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - for b in s.bytes() { - if IS_CONNECTED.load(Ordering::Relaxed) || self.force_buffer.load(Ordering::Relaxed) { - // spin until space in the UART - while let Err(_) = self.buffer.enqueue(b) { - cortex_m::asm::wfi(); - } - } else { - // drop the data - we're not connected - } - } - Ok(()) - } -} - #[entry] fn main() -> ! { + // The USB Bus, statically allocated + static mut USB_BUS: Option> = None; + let mut board = dongle::init().unwrap(); + board.usbd.inten.modify(|_r, w| { w.sof().set_bit(); w }); + let usb_bus = UsbBusAllocator::new(usbd::Usbd::new(usbd::UsbPeripheral::new( board.usbd, board.clocks, ))); - unsafe { - // Note (safety): This is safe as interrupts haven't been started yet - USB_BUS = Some(usb_bus); - } + *USB_BUS = Some(usb_bus); + + // This reference has static lifetime + let bus_ref = USB_BUS.as_ref().unwrap(); + // Grab a reference to the USB Bus allocator. We are promising to the // compiler not to take mutable access to this global variable whilst this // reference exists! - let bus_ref = unsafe { USB_BUS.as_ref().unwrap() }; - let serial = SerialPort::new(bus_ref); - unsafe { - USB_SERIAL = Some(serial); - } + critical_section::with(|cs| { + *USB_SERIAL.borrow(cs).borrow_mut() = Some(SerialPort::new(bus_ref)); + }); let desc = &[ 0x06, 0x00, 0xFF, // Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 @@ -131,24 +105,23 @@ fn main() -> ! { 0xC0, // Item(Main ): End Collection, data=none ]; let hid = HIDClass::new(bus_ref, desc, 100); - unsafe { - USB_HID = Some(hid); - } + critical_section::with(|cs| { + *USB_HID.borrow(cs).borrow_mut() = Some(hid); + }); let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE); let usb_dev = UsbDeviceBuilder::new(bus_ref, vid_pid) .manufacturer("Ferrous Systems") - .product("Dongle Puzzle") + .product("Dongle Loopback") .device_class(USB_CLASS_CDC) .max_packet_size_0(64) // (makes control transfers 8x faster) .build(); - unsafe { - // Note (safety): This is safe as interrupts haven't been started yet - USB_DEVICE = Some(usb_dev); - } + critical_section::with(|cs| { + *USB_DEVICE.borrow(cs).borrow_mut() = Some(usb_dev); + }); - let mut current_ch_id = 25; - board.radio.set_channel(dongle::ieee802154::Channel::_25); + let mut current_ch_id = 20; + board.radio.set_channel(dongle::ieee802154::Channel::_20); // Turn on USB interrupts... unsafe { @@ -163,9 +136,6 @@ fn main() -> ! { current_ch_id ); - // Now the sign-up message is done, turn off force buffering - RING_BUFFER.force_buffer.store(false, Ordering::Relaxed); - board.leds.ld1.on(); board.leds.ld2_blue.on(); let mut pkt = Packet::new(); @@ -184,7 +154,10 @@ fn main() -> ! { crc, pkt.lqi() ); - // now send it back + // send packet after 5ms (we know the client waits for 10ms and + // we want to ensure they are definitely in receive mode by the + // time we send this reply) + board.timer.delay(5000); board.radio.send(&mut pkt); RX_COUNT.fetch_add(1, Ordering::Relaxed); } @@ -227,13 +200,6 @@ fn main() -> ! { } } - let connected = IS_CONNECTED.load(Ordering::Relaxed); - if connected { - board.leds.ld2_red.on(); - } else { - board.leds.ld2_red.off(); - } - // Print help text when ? is pressed if WANT_INFO.load(Ordering::Relaxed) { WANT_INFO.store(false, Ordering::Relaxed); @@ -254,10 +220,33 @@ fn main() -> ! { /// USB UART. #[interrupt] fn USBD() { - // Grab the global objects. This is OK as we only access them under interrupt. - let usb_dev = unsafe { USB_DEVICE.as_mut().unwrap() }; - let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; - let hid = unsafe { USB_HID.as_mut().unwrap() }; + static mut LOCAL_USB_DEVICE: Option> = None; + static mut LOCAL_USB_SERIAL: Option> = None; + static mut LOCAL_USB_HID: Option> = None; + static mut IS_PENDING: Option = None; + + // Grab a reference to our local vars, moving the object out of the global as required... + + let usb_dev = LOCAL_USB_DEVICE.get_or_insert_with(|| { + critical_section::with(|cs| { + // Move USB device here, leaving a None in its place + USB_DEVICE.borrow(cs).replace(None).unwrap() + }) + }); + + let serial = LOCAL_USB_SERIAL.get_or_insert_with(|| { + critical_section::with(|cs| { + // Move USB device here, leaving a None in its place + USB_SERIAL.borrow(cs).replace(None).unwrap() + }) + }); + + let hid = LOCAL_USB_HID.get_or_insert_with(|| { + critical_section::with(|cs| { + // Move USB device here, leaving a None in its place + USB_HID.borrow(cs).replace(None).unwrap() + }) + }); let mut buf = [0u8; 64]; @@ -299,18 +288,31 @@ fn USBD() { } } - // Copy from ring-buffer to USB UART - let mut count = 0; - while count < buf.len() { - if let Some(item) = RING_BUFFER.buffer.dequeue() { - buf[count] = item; - count += 1; - } else { + // Is there a pending byte from last time? + if let Some(n) = IS_PENDING { + match serial.write(core::slice::from_ref(n)) { + Ok(_) => { + // it took our pending byte + *IS_PENDING = None; + } + Err(_) => { + // serial buffer is full + return; + } + } + } + + // Copy some more from the ring-buffer to the USB Serial interface, + // until the serial interface is full. + while let Some(item) = RING_BUFFER.read() { + let s = &[item]; + if serial.write(s).is_err() { + // the USB UART can't take this byte right now + *IS_PENDING = Some(item); break; } } - let _ = serial.write(&buf[0..count]); - IS_CONNECTED.store(serial.dtr(), Ordering::Relaxed); + cortex_m::asm::sev(); } diff --git a/nrf52-code/puzzle-fw/Cargo.lock b/nrf52-code/puzzle-fw/Cargo.lock index 534ad960..5b593381 100644 --- a/nrf52-code/puzzle-fw/Cargo.lock +++ b/nrf52-code/puzzle-fw/Cargo.lock @@ -163,6 +163,7 @@ dependencies = [ "defmt", "defmt-rtt", "embedded-hal", + "heapless", "nrf52840-hal", "panic-probe", ] @@ -381,6 +382,7 @@ dependencies = [ "consts", "cortex-m", "cortex-m-rt", + "critical-section", "dongle", "embedded-hal", "heapless", diff --git a/nrf52-code/puzzle-fw/Cargo.toml b/nrf52-code/puzzle-fw/Cargo.toml index fe8de4e2..31de2856 100644 --- a/nrf52-code/puzzle-fw/Cargo.toml +++ b/nrf52-code/puzzle-fw/Cargo.toml @@ -9,6 +9,7 @@ version = "0.0.0" consts = { path = "../consts" } cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]} cortex-m-rt = "0.7" +critical-section = "1.1.2" dongle = { path = "../boards/dongle" } embedded-hal = "0.2.7" heapless = "0.8" diff --git a/nrf52-code/puzzle-fw/src/main.rs b/nrf52-code/puzzle-fw/src/main.rs index 86600a88..06445c79 100644 --- a/nrf52-code/puzzle-fw/src/main.rs +++ b/nrf52-code/puzzle-fw/src/main.rs @@ -5,10 +5,12 @@ #![no_std] #![no_main] +use core::cell::RefCell; use core::fmt::Write; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use cortex_m_rt::entry; +use critical_section::Mutex; use usb_device::class_prelude::UsbBusAllocator; use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; use usbd_hid::hid_class::HIDClass; @@ -18,6 +20,7 @@ use dongle::peripheral::interrupt; use dongle::{ hal::usbd, ieee802154::{Channel, Packet}, + UsbBus, }; /// The secret message, but encoded. @@ -31,25 +34,17 @@ static PLAIN_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/PLAIN_LE /// The ciphertext side of the map static CIPHER_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/CIPHER_LETTERS.txt")); -/// A 64-byte USB Serial buffer -static RING_BUFFER: Ringbuffer = Ringbuffer { - buffer: heapless::mpmc::Q64::new(), -}; - -/// A short-hand for the nRF52 USB types -type UsbBus<'a> = usbd::Usbd>; +/// A buffer for holding bytes we want to send to the USB Serial port +static RING_BUFFER: dongle::Ringbuffer = dongle::Ringbuffer::new(); /// The USB Device Driver (owned by the USBD interrupt). -static mut USB_DEVICE: Option> = None; - -/// The USB Bus Driver (owned by the USBD interrupt). -static mut USB_BUS: Option> = None; +static USB_DEVICE: Mutex>>> = Mutex::new(RefCell::new(None)); /// The USB Serial Device Driver (owned by the USBD interrupt). -static mut USB_SERIAL: Option> = None; +static USB_SERIAL: Mutex>>> = Mutex::new(RefCell::new(None)); /// The USB Human Interface Device Driver (owned by the USBD interrupt). -static mut USB_HID: Option> = None; +static USB_HID: Mutex>>> = Mutex::new(RefCell::new(None)); /// Track how many CRC successes we had receiving radio packets static RX_COUNT: AtomicU32 = AtomicU32::new(0); @@ -67,42 +62,33 @@ static NEW_CHANNEL: AtomicU32 = AtomicU32::new(u32::MAX); /// We print some info in response. static WANT_INFO: AtomicBool = AtomicBool::new(false); -struct Ringbuffer { - buffer: heapless::mpmc::Q64, -} - -impl core::fmt::Write for &Ringbuffer { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - for b in s.bytes() { - let _ = self.buffer.enqueue(b); - } - Ok(()) - } -} - #[entry] fn main() -> ! { + // The USB Bus, statically allocated + static mut USB_BUS: Option> = None; + let mut board = dongle::init().unwrap(); + board.usbd.inten.modify(|_r, w| { w.sof().set_bit(); w }); + let usb_bus = UsbBusAllocator::new(usbd::Usbd::new(usbd::UsbPeripheral::new( board.usbd, board.clocks, ))); - unsafe { - // Note (safety): This is safe as interrupts haven't been started yet - USB_BUS = Some(usb_bus); - } + *USB_BUS = Some(usb_bus); + + // This reference has static lifetime + let bus_ref = USB_BUS.as_ref().unwrap(); + // Grab a reference to the USB Bus allocator. We are promising to the // compiler not to take mutable access to this global variable whilst this // reference exists! - let bus_ref = unsafe { USB_BUS.as_ref().unwrap() }; - let serial = SerialPort::new(bus_ref); - unsafe { - USB_SERIAL = Some(serial); - } + critical_section::with(|cs| { + *USB_SERIAL.borrow(cs).borrow_mut() = Some(SerialPort::new(bus_ref)); + }); let desc = &[ 0x06, 0x00, 0xFF, // Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 @@ -130,9 +116,9 @@ fn main() -> ! { 0xC0, // Item(Main ): End Collection, data=none ]; let hid = HIDClass::new(bus_ref, desc, 100); - unsafe { - USB_HID = Some(hid); - } + critical_section::with(|cs| { + *USB_HID.borrow(cs).borrow_mut() = Some(hid); + }); let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE); let usb_dev = UsbDeviceBuilder::new(bus_ref, vid_pid) @@ -141,34 +127,43 @@ fn main() -> ! { .device_class(USB_CLASS_CDC) .max_packet_size_0(64) // (makes control transfers 8x faster) .build(); - unsafe { - // Note (safety): This is safe as interrupts haven't been started yet - USB_DEVICE = Some(usb_dev); - } + critical_section::with(|cs| { + *USB_DEVICE.borrow(cs).borrow_mut() = Some(usb_dev); + }); let mut current_ch_id = 25; board.radio.set_channel(dongle::ieee802154::Channel::_25); - let mut dict: heapless::LinearMap = heapless::LinearMap::new(); - for (&plain, &cipher) in PLAIN_LETTERS.iter().zip(CIPHER_LETTERS.iter()) { - let _ = dict.insert(plain, cipher); - } - // Turn on USB interrupts... unsafe { cortex_m::peripheral::NVIC::unmask(dongle::peripheral::Interrupt::USBD); }; + let _ = writeln!( + &RING_BUFFER, + "deviceid={:08x}{:08x} channel={} TxPower=+8dBm app=puzzle-fw", + dongle::deviceid1(), + dongle::deviceid0(), + current_ch_id + ); + + board.leds.ld1.on(); + board.leds.ld2_green.on(); + + let mut dict: heapless::LinearMap = heapless::LinearMap::new(); + for (&plain, &cipher) in PLAIN_LETTERS.iter().zip(CIPHER_LETTERS.iter()) { + let _ = dict.insert(plain, cipher); + } + let mut pkt = Packet::new(); loop { - board.leds.ld1.on(); // Wait up to 1 second for a radio packet match board .radio .recv_timeout(&mut pkt, &mut board.timer, 1_000_000) { Ok(crc) => { - board.leds.ld1.off(); + board.leds.ld1.toggle(); let _ = writeln!( &RING_BUFFER, "\nRX CRC {:04x}, LQI {}, LEN {}", @@ -206,7 +201,10 @@ fn main() -> ! { board.leds.ld2_red.on(); } } - // now send it + // send packet after 5ms (we know the client waits for 10ms and + // we want to ensure they are definitely in receive mode by the + // time we send this reply) + board.timer.delay(5000); board.radio.send(&mut pkt); RX_COUNT.fetch_add(1, Ordering::Relaxed); } @@ -243,10 +241,10 @@ fn main() -> ! { _ => None, } { board.radio.set_channel(channel); - let _ = writeln!(&RING_BUFFER, "\nChannel {} set", ch_id); + let _ = writeln!(&RING_BUFFER, "now listening on channel {}", ch_id); current_ch_id = ch_id; } else { - let _ = writeln!(&RING_BUFFER, "\nChannel {} invalid", ch_id); + let _ = writeln!(&RING_BUFFER, "Channel {} invalid", ch_id); } } @@ -303,10 +301,33 @@ fn handle_packet(packet: &mut Packet, dict: &heapless::LinearMap) - /// USB UART. #[interrupt] fn USBD() { - // Grab the global objects. This is OK as we only access them under interrupt. - let usb_dev = unsafe { USB_DEVICE.as_mut().unwrap() }; - let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; - let hid = unsafe { USB_HID.as_mut().unwrap() }; + static mut LOCAL_USB_DEVICE: Option> = None; + static mut LOCAL_USB_SERIAL: Option> = None; + static mut LOCAL_USB_HID: Option> = None; + static mut IS_PENDING: Option = None; + + // Grab a reference to our local vars, moving the object out of the global as required... + + let usb_dev = LOCAL_USB_DEVICE.get_or_insert_with(|| { + critical_section::with(|cs| { + // Move USB device here, leaving a None in its place + USB_DEVICE.borrow(cs).replace(None).unwrap() + }) + }); + + let serial = LOCAL_USB_SERIAL.get_or_insert_with(|| { + critical_section::with(|cs| { + // Move USB device here, leaving a None in its place + USB_SERIAL.borrow(cs).replace(None).unwrap() + }) + }); + + let hid = LOCAL_USB_HID.get_or_insert_with(|| { + critical_section::with(|cs| { + // Move USB device here, leaving a None in its place + USB_HID.borrow(cs).replace(None).unwrap() + }) + }); let mut buf = [0u8; 64]; @@ -348,17 +369,32 @@ fn USBD() { } } - // Copy from ring-buffer to USB UART - let mut count = 0; - while count < buf.len() { - if let Some(item) = RING_BUFFER.buffer.dequeue() { - buf[count] = item; - count += 1; - } else { + // Is there a pending byte from last time? + if let Some(n) = IS_PENDING { + match serial.write(core::slice::from_ref(n)) { + Ok(_) => { + // it took our pending byte + *IS_PENDING = None; + } + Err(_) => { + // serial buffer is full + return; + } + } + } + + // Copy some more from the ring-buffer to the USB Serial interface, + // until the serial interface is full. + while let Some(item) = RING_BUFFER.read() { + let s = &[item]; + if serial.write(s).is_err() { + // the USB UART can't take this byte right now + *IS_PENDING = Some(item); break; } } - let _ = serial.write(&buf[0..count]); + + cortex_m::asm::sev(); } #[panic_handler] From d45c035448284caa293308ea96c52792fb6a5c47 Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Thu, 25 Jan 2024 16:02:07 +0000 Subject: [PATCH 8/8] Hide the funky Mutex>> types from application code. --- nrf52-code/boards/dongle/Cargo.toml | 1 + nrf52-code/boards/dongle/src/lib.rs | 45 +++++++++++++++++ nrf52-code/loopback-fw/Cargo.lock | 1 + nrf52-code/loopback-fw/src/main.rs | 77 ++++++++++------------------- nrf52-code/puzzle-fw/Cargo.lock | 1 + nrf52-code/puzzle-fw/src/main.rs | 75 +++++++++------------------- 6 files changed, 97 insertions(+), 103 deletions(-) diff --git a/nrf52-code/boards/dongle/Cargo.toml b/nrf52-code/boards/dongle/Cargo.toml index 05694d37..460dc435 100644 --- a/nrf52-code/boards/dongle/Cargo.toml +++ b/nrf52-code/boards/dongle/Cargo.toml @@ -9,6 +9,7 @@ version = "0.0.0" cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]} cortex-m-rt = "0.7.2" cortex-m-semihosting = "0.5.0" +critical-section = "1.1.2" defmt = "0.3.5" defmt-rtt = "0.4" embedded-hal = "0.2.7" diff --git a/nrf52-code/boards/dongle/src/lib.rs b/nrf52-code/boards/dongle/src/lib.rs index 480f3812..4325c66f 100644 --- a/nrf52-code/boards/dongle/src/lib.rs +++ b/nrf52-code/boards/dongle/src/lib.rs @@ -231,6 +231,51 @@ impl core::fmt::Write for &Ringbuffer { } } +/// The global type for sharing things with an interrupt handler +pub struct GlobalIrqState { + inner: critical_section::Mutex>>, +} + +impl GlobalIrqState { + /// Create a new, empty, object + pub const fn new() -> GlobalIrqState { + GlobalIrqState { + inner: critical_section::Mutex::new(core::cell::RefCell::new(None)), + } + } + + /// Load a value into the global + /// + /// Returns the old value, if any + pub fn load(&self, value: T) -> Option { + critical_section::with(|cs| self.inner.borrow(cs).replace(Some(value))) + } +} + +/// The local type for sharing things with an interrupt handler +pub struct LocalIrqState { + inner: Option, +} + +impl LocalIrqState { + /// Create a new, empty, object + pub const fn new() -> LocalIrqState { + LocalIrqState { inner: None } + } + + /// Grab a mutable reference to the contents. + /// + /// If the value is empty, the contents are taken from a mutex-locked global + /// variable. That global must have been initialised before calling this + /// function. If not, this function panics. + pub fn get_or_init_with(&mut self, global: &GlobalIrqState) -> &mut T { + let result = self.inner.get_or_insert_with(|| { + critical_section::with(|cs| global.inner.borrow(cs).replace(None).unwrap()) + }); + result + } +} + /// The ways that initialisation can fail #[derive(Debug, Copy, Clone, defmt::Format)] pub enum Error { diff --git a/nrf52-code/loopback-fw/Cargo.lock b/nrf52-code/loopback-fw/Cargo.lock index 23cdad2d..3f3c5ab1 100644 --- a/nrf52-code/loopback-fw/Cargo.lock +++ b/nrf52-code/loopback-fw/Cargo.lock @@ -160,6 +160,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "cortex-m-semihosting", + "critical-section", "defmt", "defmt-rtt", "embedded-hal", diff --git a/nrf52-code/loopback-fw/src/main.rs b/nrf52-code/loopback-fw/src/main.rs index cba73573..2d03315f 100644 --- a/nrf52-code/loopback-fw/src/main.rs +++ b/nrf52-code/loopback-fw/src/main.rs @@ -5,12 +5,10 @@ #![no_std] #![no_main] -use core::cell::RefCell; use core::fmt::Write; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use cortex_m_rt::entry; -use critical_section::Mutex; use usb_device::class_prelude::UsbBusAllocator; use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; use usbd_hid::hid_class::HIDClass; @@ -20,20 +18,20 @@ use dongle::peripheral::interrupt; use dongle::{ hal::usbd, ieee802154::{Channel, Packet}, - UsbBus, + GlobalIrqState, LocalIrqState, UsbBus, }; /// A buffer for holding bytes we want to send to the USB Serial port static RING_BUFFER: dongle::Ringbuffer = dongle::Ringbuffer::new(); /// The USB Device Driver (owned by the USBD interrupt). -static USB_DEVICE: Mutex>>> = Mutex::new(RefCell::new(None)); +static USB_DEVICE: GlobalIrqState> = GlobalIrqState::new(); /// The USB Serial Device Driver (owned by the USBD interrupt). -static USB_SERIAL: Mutex>>> = Mutex::new(RefCell::new(None)); +static USB_SERIAL: GlobalIrqState> = GlobalIrqState::new(); /// The USB Human Interface Device Driver (owned by the USBD interrupt). -static USB_HID: Mutex>>> = Mutex::new(RefCell::new(None)); +static USB_HID: GlobalIrqState> = GlobalIrqState::new(); /// Track how many CRC successes we had receiving radio packets static RX_COUNT: AtomicU32 = AtomicU32::new(0); @@ -67,17 +65,14 @@ fn main() -> ! { board.usbd, board.clocks, ))); - *USB_BUS = Some(usb_bus); - - // This reference has static lifetime - let bus_ref = USB_BUS.as_ref().unwrap(); + USB_BUS.replace(usb_bus); // Grab a reference to the USB Bus allocator. We are promising to the // compiler not to take mutable access to this global variable whilst this // reference exists! - critical_section::with(|cs| { - *USB_SERIAL.borrow(cs).borrow_mut() = Some(SerialPort::new(bus_ref)); - }); + let bus_ref = USB_BUS.as_ref().unwrap(); + + USB_SERIAL.load(SerialPort::new(bus_ref)); let desc = &[ 0x06, 0x00, 0xFF, // Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 @@ -104,21 +99,17 @@ fn main() -> ! { // Preferred_State No_Null_Position Non_Volatile Bitfield 0xC0, // Item(Main ): End Collection, data=none ]; - let hid = HIDClass::new(bus_ref, desc, 100); - critical_section::with(|cs| { - *USB_HID.borrow(cs).borrow_mut() = Some(hid); - }); - - let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE); - let usb_dev = UsbDeviceBuilder::new(bus_ref, vid_pid) - .manufacturer("Ferrous Systems") - .product("Dongle Loopback") - .device_class(USB_CLASS_CDC) - .max_packet_size_0(64) // (makes control transfers 8x faster) - .build(); - critical_section::with(|cs| { - *USB_DEVICE.borrow(cs).borrow_mut() = Some(usb_dev); - }); + USB_HID.load(HIDClass::new(bus_ref, desc, 100)); + + let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_LOOPBACK); + USB_DEVICE.load( + UsbDeviceBuilder::new(bus_ref, vid_pid) + .manufacturer("Ferrous Systems") + .product("Dongle Loopback") + .device_class(USB_CLASS_CDC) + .max_packet_size_0(64) // (makes control transfers 8x faster) + .build(), + ); let mut current_ch_id = 20; board.radio.set_channel(dongle::ieee802154::Channel::_20); @@ -220,33 +211,15 @@ fn main() -> ! { /// USB UART. #[interrupt] fn USBD() { - static mut LOCAL_USB_DEVICE: Option> = None; - static mut LOCAL_USB_SERIAL: Option> = None; - static mut LOCAL_USB_HID: Option> = None; + static mut LOCAL_USB_DEVICE: LocalIrqState> = LocalIrqState::new(); + static mut LOCAL_USB_SERIAL: LocalIrqState> = LocalIrqState::new(); + static mut LOCAL_USB_HID: LocalIrqState> = LocalIrqState::new(); static mut IS_PENDING: Option = None; // Grab a reference to our local vars, moving the object out of the global as required... - - let usb_dev = LOCAL_USB_DEVICE.get_or_insert_with(|| { - critical_section::with(|cs| { - // Move USB device here, leaving a None in its place - USB_DEVICE.borrow(cs).replace(None).unwrap() - }) - }); - - let serial = LOCAL_USB_SERIAL.get_or_insert_with(|| { - critical_section::with(|cs| { - // Move USB device here, leaving a None in its place - USB_SERIAL.borrow(cs).replace(None).unwrap() - }) - }); - - let hid = LOCAL_USB_HID.get_or_insert_with(|| { - critical_section::with(|cs| { - // Move USB device here, leaving a None in its place - USB_HID.borrow(cs).replace(None).unwrap() - }) - }); + let usb_dev = LOCAL_USB_DEVICE.get_or_init_with(&USB_DEVICE); + let serial = LOCAL_USB_SERIAL.get_or_init_with(&USB_SERIAL); + let hid = LOCAL_USB_HID.get_or_init_with(&USB_HID); let mut buf = [0u8; 64]; diff --git a/nrf52-code/puzzle-fw/Cargo.lock b/nrf52-code/puzzle-fw/Cargo.lock index 5b593381..e73ca560 100644 --- a/nrf52-code/puzzle-fw/Cargo.lock +++ b/nrf52-code/puzzle-fw/Cargo.lock @@ -160,6 +160,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "cortex-m-semihosting", + "critical-section", "defmt", "defmt-rtt", "embedded-hal", diff --git a/nrf52-code/puzzle-fw/src/main.rs b/nrf52-code/puzzle-fw/src/main.rs index 06445c79..2527bfaa 100644 --- a/nrf52-code/puzzle-fw/src/main.rs +++ b/nrf52-code/puzzle-fw/src/main.rs @@ -5,12 +5,10 @@ #![no_std] #![no_main] -use core::cell::RefCell; use core::fmt::Write; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use cortex_m_rt::entry; -use critical_section::Mutex; use usb_device::class_prelude::UsbBusAllocator; use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; use usbd_hid::hid_class::HIDClass; @@ -20,7 +18,7 @@ use dongle::peripheral::interrupt; use dongle::{ hal::usbd, ieee802154::{Channel, Packet}, - UsbBus, + GlobalIrqState, LocalIrqState, UsbBus, }; /// The secret message, but encoded. @@ -38,13 +36,13 @@ static CIPHER_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/CIPHER_ static RING_BUFFER: dongle::Ringbuffer = dongle::Ringbuffer::new(); /// The USB Device Driver (owned by the USBD interrupt). -static USB_DEVICE: Mutex>>> = Mutex::new(RefCell::new(None)); +static USB_DEVICE: GlobalIrqState> = GlobalIrqState::new(); /// The USB Serial Device Driver (owned by the USBD interrupt). -static USB_SERIAL: Mutex>>> = Mutex::new(RefCell::new(None)); +static USB_SERIAL: GlobalIrqState> = GlobalIrqState::new(); /// The USB Human Interface Device Driver (owned by the USBD interrupt). -static USB_HID: Mutex>>> = Mutex::new(RefCell::new(None)); +static USB_HID: GlobalIrqState> = GlobalIrqState::new(); /// Track how many CRC successes we had receiving radio packets static RX_COUNT: AtomicU32 = AtomicU32::new(0); @@ -78,17 +76,14 @@ fn main() -> ! { board.usbd, board.clocks, ))); - *USB_BUS = Some(usb_bus); - - // This reference has static lifetime - let bus_ref = USB_BUS.as_ref().unwrap(); + USB_BUS.replace(usb_bus); // Grab a reference to the USB Bus allocator. We are promising to the // compiler not to take mutable access to this global variable whilst this // reference exists! - critical_section::with(|cs| { - *USB_SERIAL.borrow(cs).borrow_mut() = Some(SerialPort::new(bus_ref)); - }); + let bus_ref = USB_BUS.as_ref().unwrap(); + + USB_SERIAL.load(SerialPort::new(bus_ref)); let desc = &[ 0x06, 0x00, 0xFF, // Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 @@ -115,21 +110,17 @@ fn main() -> ! { // Preferred_State No_Null_Position Non_Volatile Bitfield 0xC0, // Item(Main ): End Collection, data=none ]; - let hid = HIDClass::new(bus_ref, desc, 100); - critical_section::with(|cs| { - *USB_HID.borrow(cs).borrow_mut() = Some(hid); - }); + USB_HID.load(HIDClass::new(bus_ref, desc, 100)); let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE); - let usb_dev = UsbDeviceBuilder::new(bus_ref, vid_pid) - .manufacturer("Ferrous Systems") - .product("Dongle Puzzle") - .device_class(USB_CLASS_CDC) - .max_packet_size_0(64) // (makes control transfers 8x faster) - .build(); - critical_section::with(|cs| { - *USB_DEVICE.borrow(cs).borrow_mut() = Some(usb_dev); - }); + USB_DEVICE.load( + UsbDeviceBuilder::new(bus_ref, vid_pid) + .manufacturer("Ferrous Systems") + .product("Dongle Puzzle") + .device_class(USB_CLASS_CDC) + .max_packet_size_0(64) // (makes control transfers 8x faster) + .build(), + ); let mut current_ch_id = 25; board.radio.set_channel(dongle::ieee802154::Channel::_25); @@ -173,7 +164,7 @@ fn main() -> ! { ); match handle_packet(&mut pkt, &dict) { Command::SendSecret => { - pkt.copy_from_slice(&ENCODED_MESSAGE); + pkt.copy_from_slice(ENCODED_MESSAGE); let _ = writeln!(&RING_BUFFER, "TX Secret"); board.leds.ld2_blue.on(); board.leds.ld2_green.off(); @@ -301,33 +292,15 @@ fn handle_packet(packet: &mut Packet, dict: &heapless::LinearMap) - /// USB UART. #[interrupt] fn USBD() { - static mut LOCAL_USB_DEVICE: Option> = None; - static mut LOCAL_USB_SERIAL: Option> = None; - static mut LOCAL_USB_HID: Option> = None; + static mut LOCAL_USB_DEVICE: LocalIrqState> = LocalIrqState::new(); + static mut LOCAL_USB_SERIAL: LocalIrqState> = LocalIrqState::new(); + static mut LOCAL_USB_HID: LocalIrqState> = LocalIrqState::new(); static mut IS_PENDING: Option = None; // Grab a reference to our local vars, moving the object out of the global as required... - - let usb_dev = LOCAL_USB_DEVICE.get_or_insert_with(|| { - critical_section::with(|cs| { - // Move USB device here, leaving a None in its place - USB_DEVICE.borrow(cs).replace(None).unwrap() - }) - }); - - let serial = LOCAL_USB_SERIAL.get_or_insert_with(|| { - critical_section::with(|cs| { - // Move USB device here, leaving a None in its place - USB_SERIAL.borrow(cs).replace(None).unwrap() - }) - }); - - let hid = LOCAL_USB_HID.get_or_insert_with(|| { - critical_section::with(|cs| { - // Move USB device here, leaving a None in its place - USB_HID.borrow(cs).replace(None).unwrap() - }) - }); + let usb_dev = LOCAL_USB_DEVICE.get_or_init_with(&USB_DEVICE); + let serial = LOCAL_USB_SERIAL.get_or_init_with(&USB_SERIAL); + let hid = LOCAL_USB_HID.get_or_init_with(&USB_HID); let mut buf = [0u8; 64];