diff --git a/nrf52-code/boards/dongle/src/lib.rs b/nrf52-code/boards/dongle/src/lib.rs index 4a0dc4c3..eb80b671 100644 --- a/nrf52-code/boards/dongle/src/lib.rs +++ b/nrf52-code/boards/dongle/src/lib.rs @@ -230,51 +230,6 @@ 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/puzzle-fw/Cargo.lock b/nrf52-code/puzzle-fw/Cargo.lock index f6d1b7ae..4d2689a0 100644 --- a/nrf52-code/puzzle-fw/Cargo.lock +++ b/nrf52-code/puzzle-fw/Cargo.lock @@ -29,6 +29,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + [[package]] name = "bitfield" version = "0.13.2" @@ -81,7 +87,7 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ - "bare-metal", + "bare-metal 0.2.5", "bitfield 0.13.2", "critical-section", "embedded-hal 0.2.7", @@ -213,6 +219,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + [[package]] name = "embedded-io" version = "0.6.1" @@ -231,6 +246,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "fixed" version = "1.28.0" @@ -243,6 +264,45 @@ dependencies = [ "typenum", ] +[[package]] +name = "fugit" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17186ad64927d5ac8f02c1e77ccefa08ccd9eaa314d5a4772278aa204a22f7e7" +dependencies = [ + "gcd", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + [[package]] name = "getrandom" version = "0.2.15" @@ -291,21 +351,38 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ + "defmt", "hash32", "stable_deref_trait", ] +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", +] + [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "log" @@ -399,6 +476,18 @@ dependencies = [ "defmt", ] +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.10.0" @@ -450,13 +539,13 @@ name = "puzzle-fw" version = "0.0.0" dependencies = [ "consts", - "cortex-m", - "cortex-m-rt", - "critical-section", + "defmt", + "defmt-rtt", "dongle", - "embedded-hal 1.0.0", "heapless", "rand", + "rtic", + "rtic-monotonics", "usb-device", "usbd-hid", "usbd-serial", @@ -501,6 +590,75 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rtic" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "401961431a1e491124cdd216a313fada2d395aa2b5bee2867c872fc8af7c1bc1" +dependencies = [ + "bare-metal 1.0.0", + "cortex-m", + "critical-section", + "portable-atomic", + "rtic-core", + "rtic-macros", +] + +[[package]] +name = "rtic-common" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786b50b81ef9d2a944a000f60405bb28bf30cd45da2d182f3fe636b2321f35c" +dependencies = [ + "critical-section", +] + +[[package]] +name = "rtic-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9369355b04d06a3780ec0f51ea2d225624db777acbc60abd8ca4832da5c1a42" + +[[package]] +name = "rtic-macros" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac22ab522d80079b48f46ac66ded4d349e1adf81b52430d6a74faa3a7790ed80" +dependencies = [ + "indexmap", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "rtic-monotonics" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1cb90bcfdbbacf3ca37340cdab52ec2de5611c744095ef7889e9c50c233b748" +dependencies = [ + "cfg-if", + "cortex-m", + "fugit", + "portable-atomic", + "rtic-time", +] + +[[package]] +name = "rtic-time" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b1d853fa50dc125695414ce4510567a0d420221e455b1568cfa8c9aece9614" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", + "embedded-hal-async", + "fugit", + "futures-util", + "rtic-common", +] + [[package]] name = "rustc_version" version = "0.2.3" @@ -621,6 +779,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" dependencies = [ + "defmt", "heapless", "portable-atomic", ] @@ -631,6 +790,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" dependencies = [ + "defmt", "serde", "ssmarshal", "usb-device", @@ -653,7 +813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" dependencies = [ "byteorder", - "hashbrown", + "hashbrown 0.13.2", "log", "proc-macro2", "quote", diff --git a/nrf52-code/puzzle-fw/Cargo.toml b/nrf52-code/puzzle-fw/Cargo.toml index 20b7fa37..0669339e 100644 --- a/nrf52-code/puzzle-fw/Cargo.toml +++ b/nrf52-code/puzzle-fw/Cargo.toml @@ -7,34 +7,15 @@ version = "0.0.0" [dependencies] consts = { path = "../consts" } -cortex-m = {version = "0.7.7", features = ["critical-section-single-core"]} -cortex-m-rt = "0.7.5" -critical-section = "1.2.0" +defmt = "0.3.10" +defmt-rtt = "0.4.1" dongle = { path = "../boards/dongle" } -embedded-hal = "1.0" -heapless = "0.8" -usb-device = "0.3" -usbd-hid = "0.8" -usbd-serial = "0.2" +heapless = { version = "0.8.0", features = ["defmt-03"] } +rtic = { version = "2.1.2", features = ["thumbv7-backend"] } +rtic-monotonics = { version = "2.0.3", features = ["cortex-m-systick"] } +usb-device = { version = "0.3.2", features = ["defmt"] } +usbd-hid = { version = "0.8.2", features = ["defmt"] } +usbd-serial = "0.2.2" [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/puzzle-fw/src/main.rs b/nrf52-code/puzzle-fw/src/main.rs index 9bfbc246..2bc3eed5 100644 --- a/nrf52-code/puzzle-fw/src/main.rs +++ b/nrf52-code/puzzle-fw/src/main.rs @@ -2,415 +2,512 @@ //! //! Sets up a USB Serial port and listens for radio packets. -#![no_std] #![no_main] +#![no_std] + +use defmt_rtt as _; + +#[rtic::app(device = dongle, peripherals = false)] +mod app { + use core::mem::MaybeUninit; + use rtic_monotonics::systick::prelude::*; + const QUEUE_LEN: usize = 8; + + /// The secret message, but encoded. + /// + /// We do this rather than the plaintext -- otherwise `strings $elf` will reveal the answer + static ENCODED_MESSAGE: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/ENCODED_MESSAGE.txt")); + + /// The plaintext side of the map + static PLAIN_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/PLAIN_LETTERS.txt")); + + /// The ciphertext side of the map + static CIPHER_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/CIPHER_LETTERS.txt")); + + /// How many address bytes we reflect back + const ADDR_BYTES: usize = 6; + + systick_monotonic!(Mono, 100); + + /// An adapter that lets us writeln! into any closure that takes a byte. + /// + /// This is useful if writing a byte requires taking a lock, and you don't + /// want to hold the lock for the duration of the write. + struct Writer(F) + where + F: FnMut(&[u8]); + + impl core::fmt::Write for Writer + where + F: FnMut(&[u8]), + { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + (self.0)(s.as_bytes()); + Ok(()) + } + } + + #[local] + struct MyLocalResources { + /// The radio subsystem + radio: dongle::ieee802154::Radio<'static>, + /// Which channel are we on + current_channel: u8, + /// Holds one package, for receive or transmit + packet: dongle::ieee802154::Packet, + /// Used to measure elapsed time + timer: dongle::Timer, + /// How many packets have been received OK? + rx_count: u32, + /// How many packets have been received with errors? + err_count: u32, + /// A place to read the message queue + msg_queue_out: heapless::spsc::Consumer<'static, Message, QUEUE_LEN>, + /// A place to write to the message queue + msg_queue_in: heapless::spsc::Producer<'static, Message, QUEUE_LEN>, + /// The status LEDs + leds: dongle::Leds, + } + + #[derive(Debug, defmt::Format, Copy, Clone, PartialEq, Eq)] + enum Message { + ChangeChannel(u8), + WantInfo, + } + + #[shared] + struct MySharedResources { + /// Handles the USB Serial interface, including a ring buffer + usb_serial: usbd_serial::SerialPort<'static, dongle::UsbBus>, + /// Handles the USB HID interface + usb_hid: usbd_hid::hid_class::HIDClass<'static, dongle::UsbBus>, + /// Handles the lower-level USB Device interface + usb_device: usb_device::device::UsbDevice<'static, dongle::UsbBus>, + } -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::{StringDescriptors, UsbDevice, UsbDeviceBuilder, UsbVidPid}; -use usb_device::LangID; -use usbd_hid::hid_class::HIDClass; -use usbd_serial::SerialPort; - -use dongle::peripheral::interrupt; -use dongle::{ - hal::usbd, - ieee802154::{Channel, Packet}, - GlobalIrqState, LocalIrqState, UsbBus, -}; - -/// The secret message, but encoded. -/// -/// We do this rather than the plaintext -- otherwise `strings $elf` will reveal the answer -static ENCODED_MESSAGE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/ENCODED_MESSAGE.txt")); - -/// The plaintext side of the map -static PLAIN_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/PLAIN_LETTERS.txt")); - -/// The ciphertext side of the map -static CIPHER_LETTERS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/CIPHER_LETTERS.txt")); - -/// 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: GlobalIrqState> = GlobalIrqState::new(); - -/// The USB Serial Device Driver (owned by the USBD interrupt). -static USB_SERIAL: GlobalIrqState> = GlobalIrqState::new(); - -/// The USB Human Interface Device Driver (owned by the USBD interrupt). -static USB_HID: GlobalIrqState> = GlobalIrqState::new(); - -/// 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); - -/// How many address bytes we reflect back -const ADDR_BYTES: usize = 6; - -#[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, - ))); - 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! - 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 - 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 - ]; - USB_HID.load(HIDClass::new(bus_ref, desc, 100)); - - let strings = StringDescriptors::new(LangID::EN) - .manufacturer("Ferrous Systems") - .product("Dongle Puzzle"); - - let vid_pid = UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_PUZZLE); - // See https://www.usb.org/sites/default/files/iadclasscode_r10.pdf - // and https://learn.microsoft.com/en-us/windows-hardware/drivers/usbcon/usb-interface-association-descriptor - USB_DEVICE.load( - UsbDeviceBuilder::new(bus_ref, vid_pid) + #[init(local = [ + usb_alloc: MaybeUninit> = MaybeUninit::uninit(), + queue: heapless::spsc::Queue = heapless::spsc::Queue::new(), + ])] + fn init(ctx: init::Context) -> (MySharedResources, MyLocalResources) { + let mut board = dongle::init().unwrap(); + Mono::start(ctx.core.SYST, 64_000_000); + + defmt::debug!("Enabling interrupts..."); + board.usbd.inten.modify(|_r, w| { + w.sof().set_bit(); + w + }); + + defmt::debug!("Building USB allocator..."); + let usbd = dongle::UsbBus::new(dongle::hal::usbd::UsbPeripheral::new( + board.usbd, + board.clocks, + )); + let usb_alloc = ctx + .local + .usb_alloc + .write(usb_device::bus::UsbBusAllocator::new(usbd)); + + defmt::debug!("Creating usb_serial..."); + let usb_serial = usbd_serial::SerialPort::new(usb_alloc); + + defmt::debug!("Creating usb_hid..."); + 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 usb_hid = usbd_hid::hid_class::HIDClass::new(usb_alloc, desc, 100); + + defmt::debug!("Building USB Strings..."); + let strings = usb_device::device::StringDescriptors::new(usb_device::LangID::EN) + .manufacturer("Ferrous Systems") + .product("Test Device"); + + defmt::debug!("Building VID and PID..."); + let vid_pid = + usb_device::device::UsbVidPid(consts::USB_VID_DEMO, consts::USB_PID_DONGLE_LOOPBACK); + + defmt::debug!("Building USB Device..."); + let usb_device = usb_device::device::UsbDeviceBuilder::new(usb_alloc, vid_pid) .composite_with_iads() .strings(&[strings]) - .expect("adding strings") - // (makes control transfers 8x faster) + .expect("Adding strings") .max_packet_size_0(64) - .expect("set packet size") - .build(), - ); - - 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=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); + .expect("set_packet_size") + .build(); + + defmt::debug!("Configuring radio..."); + board.radio.set_channel(dongle::ieee802154::Channel::_25); + let current_channel = 25; + + let (msg_queue_in, msg_queue_out) = ctx.local.queue.split(); + + defmt::debug!("Building structures..."); + let shared = MySharedResources { + usb_serial, + usb_hid, + usb_device, + }; + let local = MyLocalResources { + radio: board.radio, + current_channel, + packet: dongle::ieee802154::Packet::new(), + timer: board.timer, + rx_count: 0, + err_count: 0, + msg_queue_out, + msg_queue_in, + leds: board.leds, + }; + + defmt::debug!("Init Complete!"); + + (shared, local) } - 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, - "\nRX CRC {:04x}, LQI {}, LEN {}", - crc, - pkt.lqi(), - pkt.len() - ); - let mut reply = true; - match handle_packet(&mut pkt, &dict) { - None => { - // not enough bytes - send nothing back - reply = false; + #[idle(local = [radio, current_channel, packet, timer, rx_count, err_count, msg_queue_out, leds], shared = [usb_serial])] + fn idle(mut ctx: idle::Context) -> ! { + use core::fmt::Write as _; + let mut writer = Writer(|b: &[u8]| { + ctx.shared.usb_serial.lock(|usb_serial| { + let _ = usb_serial.write(b); + }) + }); + + defmt::info!( + "deviceid={=u32:08x}{=u32:08x} channel={=u8} TxPower=+8dBm app=puzzle-fw", + dongle::deviceid1(), + dongle::deviceid0(), + ctx.local.current_channel + ); + + ctx.local.leds.ld1.on(); + ctx.local.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); + } + + loop { + while let Some(msg) = ctx.local.msg_queue_out.dequeue() { + match msg { + Message::WantInfo => { + defmt::info!( + "rx={=u32}, err={=u32}, ch={=u8}, app=puzzle-fw", + ctx.local.rx_count, + ctx.local.err_count, + ctx.local.current_channel + ); + let _ = writeln!( + writer, + "\nrx={}, err={}, ch={}, app=puzzle-fw", + ctx.local.rx_count, ctx.local.err_count, ctx.local.current_channel + ); } - Some(Command::SendSecret) => { - pkt.set_len(ENCODED_MESSAGE.len() as u8 + ADDR_BYTES as u8); - for (src, dest) in ENCODED_MESSAGE.iter().zip(&mut pkt[ADDR_BYTES..]) { - *dest = *src; + Message::ChangeChannel(n) => { + defmt::info!("Changing Channel to {}", n); + let _ = writeln!(writer, "\nChanging Channel to {}", n); + match n { + 11 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_11); + } + 12 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_12); + } + 13 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_13); + } + 14 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_14); + } + 15 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_15); + } + 16 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_16); + } + 17 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_17); + } + 18 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_18); + } + 19 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_19); + } + 20 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_20); + } + 21 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_21); + } + 22 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_22); + } + 23 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_23); + } + 24 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_24); + } + 25 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_25); + } + 26 => { + ctx.local + .radio + .set_channel(dongle::ieee802154::Channel::_26); + } + _ => { + defmt::info!("Bad Channel {}!", n); + } } - let _ = writeln!(&RING_BUFFER, "TX Secret"); - board.leds.ld2_blue.on(); - board.leds.ld2_green.off(); - board.leds.ld2_red.off(); - } - Some(Command::MapChar(plain, cipher)) => { - pkt.set_len(1 + ADDR_BYTES as u8); - pkt[ADDR_BYTES] = cipher; - let _ = writeln!(&RING_BUFFER, "TX Map({plain}) => {cipher}"); - board.leds.ld2_blue.off(); - board.leds.ld2_green.on(); - board.leds.ld2_red.off(); } - Some(Command::Correct) => { - let message = b"correct"; - pkt.set_len(message.len() as u8 + ADDR_BYTES as u8); - for (src, dest) in message.iter().zip(&mut pkt[ADDR_BYTES..]) { - *dest = *src; + } + } + + defmt::debug!("Waiting for packet.."); + match ctx + .local + .radio + .recv_timeout(ctx.local.packet, ctx.local.timer, 1_000_000) + { + Ok(crc) => { + ctx.local.leds.ld1.toggle(); + defmt::info!( + "Received {=u8} bytes (CRC=0x{=u16:04x}, LQI={})", + ctx.local.packet.len(), + crc, + ctx.local.packet.lqi(), + ); + let _ = writeln!( + writer, + "\nReceived {} bytes (CRC=0x{:04x}, LQI={})", + ctx.local.packet.len(), + crc, + ctx.local.packet.lqi(), + ); + *ctx.local.rx_count += 1; + let mut reply = true; + match handle_packet(ctx.local.packet, &dict) { + None => { + // not enough bytes - send nothing back + reply = false; } - let _ = writeln!(&RING_BUFFER, "TX Correct"); - board.leds.ld2_blue.on(); - board.leds.ld2_green.on(); - board.leds.ld2_red.on(); - } - Some(Command::Wrong) => { - let message = b"incorrect"; - pkt.set_len(message.len() as u8 + ADDR_BYTES as u8); - for (src, dest) in message.iter().zip(&mut pkt[ADDR_BYTES..]) { - *dest = *src; + Some(Command::SendSecret) => { + ctx.local + .packet + .set_len(ENCODED_MESSAGE.len() as u8 + ADDR_BYTES as u8); + for (src, dest) in ENCODED_MESSAGE + .iter() + .zip(&mut ctx.local.packet[ADDR_BYTES..]) + { + *dest = *src; + } + let _ = writeln!(writer, "TX Secret"); + ctx.local.leds.ld2_blue.on(); + ctx.local.leds.ld2_green.off(); + ctx.local.leds.ld2_red.off(); + } + Some(Command::MapChar(plain, cipher)) => { + ctx.local.packet.set_len(1 + ADDR_BYTES as u8); + ctx.local.packet[ADDR_BYTES] = cipher; + let _ = writeln!(writer, "TX Map({plain}) => {cipher}"); + ctx.local.leds.ld2_blue.off(); + ctx.local.leds.ld2_green.on(); + ctx.local.leds.ld2_red.off(); + } + Some(Command::Correct) => { + let message = b"correct"; + ctx.local + .packet + .set_len(message.len() as u8 + ADDR_BYTES as u8); + for (src, dest) in + message.iter().zip(&mut ctx.local.packet[ADDR_BYTES..]) + { + *dest = *src; + } + let _ = writeln!(writer, "TX Correct"); + ctx.local.leds.ld2_blue.on(); + ctx.local.leds.ld2_green.on(); + ctx.local.leds.ld2_red.on(); + } + Some(Command::Wrong) => { + let message = b"incorrect"; + ctx.local + .packet + .set_len(message.len() as u8 + ADDR_BYTES as u8); + for (src, dest) in + message.iter().zip(&mut ctx.local.packet[ADDR_BYTES..]) + { + *dest = *src; + } + let _ = writeln!(writer, "TX Incorrect"); + ctx.local.leds.ld2_blue.off(); + ctx.local.leds.ld2_green.on(); + ctx.local.leds.ld2_red.on(); } - let _ = writeln!(&RING_BUFFER, "TX Incorrect"); - board.leds.ld2_blue.off(); - board.leds.ld2_green.on(); - board.leds.ld2_red.on(); + } + // send packet after 500us (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) + if reply { + ctx.local.timer.delay(500); + ctx.local.radio.send(ctx.local.packet); } } - // send packet after 500us (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) - if reply { - board.timer.delay(500); - board.radio.send(&mut pkt); + Err(dongle::ieee802154::Error::Crc(_)) => { + defmt::debug!("RX fail!"); + let _ = write!(writer, "!"); + *ctx.local.err_count += 1; + } + Err(dongle::ieee802154::Error::Timeout) => { + defmt::debug!("RX timeout..."); + let _ = write!(writer, "."); } - RX_COUNT.fetch_add(1, Ordering::Relaxed); - } - 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, "now listening on channel {}", ch_id); - current_ch_id = ch_id; - } else { - let _ = writeln!(&RING_BUFFER, "Channel {} 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, - ); - } - } -} - -enum Command { - SendSecret, - MapChar(u8, u8), - Correct, - Wrong, -} - -fn handle_packet(packet: &mut Packet, dict: &heapless::LinearMap) -> Option { - let payload = packet.get_mut(ADDR_BYTES..)?; - if payload.len() == 0 { - Some(Command::SendSecret) - } else if payload.len() == 1 { - // They give us plaintext, we give them ciphertext - let plain = payload[0]; - let cipher = *dict.get(&plain).unwrap_or(&0); - Some(Command::MapChar(plain, cipher)) - } else { - // They give us plaintext, we tell them if it is correct - // Encrypt every byte of plaintext they give us - for slot in payload.iter_mut() { - if let Some(c) = dict.get(slot) { - *slot = *c; - } else { - *slot = 0; } } - if &payload[..] == ENCODED_MESSAGE { - Some(Command::Correct) - } else { - Some(Command::Wrong) - } } -} -/// Handles USB interrupts -/// -/// Polls all the USB devices, and copies bytes from [`RING_BUFFER`] into the -/// USB UART. -#[interrupt] -fn USBD() { - 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_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]; - - // 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); + /// USB Interrupt Handler + /// + /// USB Device is set to fire this whenever there's a Start of Frame from + /// the USB Host. + #[task(binds = USBD, local = [msg_queue_in], shared = [usb_serial, usb_hid, usb_device])] + fn usb_isr(mut ctx: usb_isr::Context) { + let mut all = ( + &mut ctx.shared.usb_serial, + &mut ctx.shared.usb_hid, + &mut ctx.shared.usb_device, + ); + all.lock(|usb_serial, usb_hid, usb_device| { + if usb_device.poll(&mut [usb_serial, usb_hid]) { + let mut buffer = [0u8; 64]; + if let Ok(n) = usb_serial.read(&mut buffer) { + if n > 0 { + for b in &buffer[0..n] { + if *b == b'?' { + // User pressed "?" in the terminal + _ = ctx.local.msg_queue_in.enqueue(Message::WantInfo); + } + } + } + } + if let Ok(n) = usb_hid.pull_raw_output(&mut buffer) { + // Linux sends 1 byte, Windows sends 64 (with 63 zero bytes) + if n == 1 || n == 64 { + _ = ctx + .local + .msg_queue_in + .enqueue(Message::ChangeChannel(buffer[0])); } } } - } - 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); - } + }); } - // 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; - } - } + enum Command { + SendSecret, + MapChar(u8, u8), + Correct, + Wrong, } - // 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; + fn handle_packet( + packet: &mut dongle::ieee802154::Packet, + dict: &heapless::LinearMap, + ) -> Option { + let payload = packet.get_mut(ADDR_BYTES..)?; + if payload.len() == 0 { + Some(Command::SendSecret) + } else if payload.len() == 1 { + // They give us plaintext, we give them ciphertext + let plain = payload[0]; + let cipher = *dict.get(&plain).unwrap_or(&0); + Some(Command::MapChar(plain, cipher)) + } else { + // They give us plaintext, we tell them if it is correct + // Encrypt every byte of plaintext they give us + for slot in payload.iter_mut() { + if let Some(c) = dict.get(slot) { + *slot = *c; + } else { + *slot = 0; + } + } + if &payload[..] == ENCODED_MESSAGE { + Some(Command::Correct) + } else { + Some(Command::Wrong) + } } } - - 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"); - } + if let Some(location) = info.location() { + defmt::error!("Panic at {}:{}", location.file(), location.line()); + } else { + defmt::error!("Panic at unknown location"); + } + loop { + core::hint::spin_loop(); } } -// End of file +defmt::timestamp!("{=u64:tus}", dongle::uptime_us());