diff --git a/README.md b/README.md index b102cb2..b98b72b 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,11 @@ When connected the emberOne usbserial firmware will create two serial ports. Usu 3. command bus - always 0x00 4. command page - - I2C: 0x05 - - GPIO: 0x06 - - ADC: 0x07 - - LED: 0x08 + - I2C: 0x05 + - GPIO: 0x06 + - ADC: 0x07 + - LED: 0x08 + - System: 0x09 5. command - varies by command page. See below 6. data @@ -151,3 +152,23 @@ Data: Example: - Set LED Magenta: `09 00 00 00 08 10 FF 00 FF` + +**System** + +Commands require a magic payload to guard against accidental +reboot from line noise or a framing desync. No response is +sent; the host detects success by observing USB re-enumeration. + +Commands: + +- Reboot: 0x01 (magic: `DE AD BE EF`) +- Reboot to bootloader: 0x02 (magic: `B0 07 10 AD`) + +Data: + +- [magic (4 bytes)] + +Examples: + +- Reboot: `0A 00 00 00 09 01 DE AD BE EF` +- Reboot to bootloader: `0A 00 00 00 09 02 B0 07 10 AD` diff --git a/src/control/mod.rs b/src/control/mod.rs index 5da7983..6bd71d8 100644 --- a/src/control/mod.rs +++ b/src/control/mod.rs @@ -22,6 +22,8 @@ const ADC_COMMAND: u8 = 7; pub mod led; const LED_COMMAND: u8 = 8; +pub mod system; +const SYSTEM_COMMAND: u8 = 9; #[derive(defmt::Format)] struct Command { @@ -36,6 +38,7 @@ enum CommandInner { Gpio(gpio::Command), Adc(adc::Command), Led(led::Command), + System(system::Command), Error(CommandError), } @@ -63,6 +66,11 @@ impl Command { bus: buf[1], inner: CommandInner::Led(led::Command::from_bytes(&buf[3..])?), }), + SYSTEM_COMMAND => Ok(Self { + id, + bus: buf[1], + inner: CommandInner::System(system::Command::from_bytes(&buf[3..])?), + }), _ => Err(CommandError::Invalid), } } @@ -126,6 +134,7 @@ impl Controller { CommandInner::Gpio(cmd) => cmd.handle(self).await, CommandInner::Adc(cmd) => cmd.handle(self).await, CommandInner::Led(cmd) => cmd.handle(self).await, + CommandInner::System(cmd) => cmd.handle(self).await, CommandInner::Error(err) => Err(err), }; diff --git a/src/control/system.rs b/src/control/system.rs new file mode 100644 index 0000000..27973db --- /dev/null +++ b/src/control/system.rs @@ -0,0 +1,46 @@ +use defmt::{info, panic}; +use heapless::Vec; + +use super::CommandError; + +// Commands require a magic payload to guard against accidental reboot +// from line noise or a framing desync. +const REBOOT_MAGIC: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; +const REBOOT_TO_BOOTLOADER_MAGIC: [u8; 4] = [0xB0, 0x07, 0x10, 0xAD]; + +#[derive(defmt::Format)] +pub enum Command { + Reboot, + RebootToBootloader, +} + +impl Command { + pub fn from_bytes(buf: &[u8]) -> Result { + match buf { + [0x01, rest @ ..] if rest == REBOOT_MAGIC => Ok(Self::Reboot), + [0x02, rest @ ..] if rest == REBOOT_TO_BOOTLOADER_MAGIC => { + Ok(Self::RebootToBootloader) + } + _ => Err(CommandError::Invalid), + } + } +} + +impl super::ControllerCommand for Command { + async fn handle( + &self, + _controller: &mut super::Controller, + ) -> Result, CommandError> { + match self { + Command::Reboot => { + info!("System: rebooting"); + cortex_m::peripheral::SCB::sys_reset() + } + Command::RebootToBootloader => { + info!("System: rebooting to bootloader"); + embassy_rp::rom_data::reset_to_usb_boot(0, 0); + panic!("reset_to_usb_boot returned"); + } + } + } +}