diff --git a/Cargo.lock b/Cargo.lock index 9b0c28ae..7f93b76e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,7 @@ dependencies = [ "mcp3221", "microchip-24aa02e48", "minimq", + "panic-persist", "postcard", "serde", "serde-json-core", @@ -476,6 +477,14 @@ dependencies = [ "syn", ] +[[package]] +name = "panic-persist" +version = "0.2.1" +source = "git+https://github.com/jamesmunns/panic-persist?branch=master#3f892297e97adc6a415145193000e3749b1959df" +dependencies = [ + "cortex-m", +] + [[package]] name = "postcard" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 66ecc22e..9f174de4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ shared-bus = { version = "0.2.0-alpha.1", features = ["cortex-m"] } usb-device = "0.2.6" postcard = "0.5.1" crc-any = { version = "2.3.5", default-features = false } +panic-persist = { git = "https://github.com/jamesmunns/panic-persist", branch = "master", features = ["custom-panic-handler"] } [dependencies.minimq] git = "https://github.com/quartiq/minimq" diff --git a/README.md b/README.md index 874c9b4e..eb70b976 100644 --- a/README.md +++ b/README.md @@ -232,3 +232,13 @@ Bootloader USB interface. ``` dfu-util -a 0 -s 0x08000000:leave --download booster.bin ``` + +# Generating Releases + +When a release is ready, `develop` must be merged into `master`. + +The corresponding merge commit is then tagged with the version in the form of `vX.Y.Z`, where X, Y, +and Z are the semantic version major, minor, and patch versions. The tag must be pushed using `git +push origin vX.Y.Z`. This will automatically trigger CI to generate the release. + +After the tag is generated, `master` must be merged back into `develop`. diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..013a7ec7 --- /dev/null +++ b/build.rs @@ -0,0 +1,55 @@ +//! Booster NGFW build script +//! +//! # Copyright +//! Copyright (C) 2020 QUARTIQ GmbH - All Rights Reserved +//! Unauthorized usage, editing, or copying is strictly prohibited. +//! Proprietary and confidential. +//! +//! # Note +//! This build script is run immediately before the build is completed and is used to inject +//! environment variables into the build. +use std::process::Command; + +fn main() { + // Inject the git revision into an environment variable for compilation. + let dirty_flag = if !Command::new("git") + .args(&["diff", "--quiet"]) + .status() + .unwrap() + .success() + { + "-dirty" + } else { + "" + }; + + let output = Command::new("git") + .args(&["rev-parse", "HEAD"]) + .output() + .unwrap(); + let revision = String::from_utf8(output.stdout).unwrap(); + println!( + "cargo:rustc-env=GIT_REVISION={}{}", + revision.trim(), + dirty_flag + ); + + let output = Command::new("git") + .args(&["describe", "--tags"]) + .output() + .unwrap(); + let version = String::from_utf8(output.stdout).unwrap(); + println!("cargo:rustc-env=VERSION={}", version.trim()); + + // Collect all of the enabled features and inject them as an environment variable. + let mut features: Vec = Vec::new(); + for (key, _) in std::env::vars() { + let strings: Vec<&str> = key.split("CARGO_FEATURE_").collect(); + if strings.len() > 1 { + println!("{}", strings[1]); + features.push(String::from(strings[1])); + } + } + + println!("cargo:rustc-env=ALL_FEATURES={}", features.join(", ")); +} diff --git a/memory.x b/memory.x index 5e740eb5..8a7feef6 100644 --- a/memory.x +++ b/memory.x @@ -2,9 +2,13 @@ MEMORY { /* NOTE 1 K = 1 KiBi = 1024 bytes */ FLASH : ORIGIN = 0x08000000, LENGTH = 1M - RAM : ORIGIN = 0x20000000, LENGTH = 128K + RAM : ORIGIN = 0x20000000, LENGTH = 127K + PANDUMP : ORIGIN = 0x2001FC00, LENGTH = 1K } +_panic_dump_start = ORIGIN(PANDUMP); +_panic_dump_end = ORIGIN(PANDUMP) + LENGTH(PANDUMP); + /* This is where the call stack will be allocated. */ /* The stack is of the full descending type. */ /* You may want to use this variable to locate the call stack and static diff --git a/src/main.rs b/src/main.rs index 19f2e7f1..e159a60c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,8 @@ #[macro_use] extern crate log; +use panic_persist as _; + use core::fmt::Write; use enum_iterator::IntoEnumIterator; @@ -477,9 +479,6 @@ const APP: () = { c.schedule.usb(c.start).unwrap(); c.schedule.fans(c.start).unwrap(); - // Clear the reset flags now that initialization has completed. - platform::clear_reset_flags(); - init::LateResources { // Note that these share a resource because they both exist on the same I2C bus. main_bus: MainBus { fans, channels }, diff --git a/src/platform.rs b/src/platform.rs index 8afa6a18..a473fb33 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -13,12 +13,15 @@ use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin}; pub const MAXIMUM_REFLECTED_POWER_DBM: f32 = 30.0; #[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { cortex_m::interrupt::disable(); // Shutdown all of the RF channels. shutdown_channels(); + // Write panic info to RAM. + panic_persist::report_panic_info(info); + // Reset the device in `release` configuration. #[cfg(not(debug_assertions))] cortex_m::peripheral::SCB::sys_reset(); diff --git a/src/serial_terminal.rs b/src/serial_terminal.rs index 43a4ef65..0b5aefc8 100644 --- a/src/serial_terminal.rs +++ b/src/serial_terminal.rs @@ -57,6 +57,9 @@ enum Token { #[regex(r"[a-zA-Z0-9]+")] DeviceIdentifier, + + #[token("service")] + ServiceInfo, } #[derive(PartialEq)] @@ -74,6 +77,7 @@ pub enum Request { ResetBootloader, Help, Read(Property), + ServiceInfo, WriteIpAddress(Property, Ipv4Addr), WriteIdentifier(String), } @@ -152,6 +156,61 @@ impl SerialTerminal { ); } + Request::ServiceInfo => { + let mut msg: String = String::new(); + write!(&mut msg, "{:<20}: {}\n", "Version", env!("VERSION")).unwrap_or_else( + |_| { + msg = String::from("Version: too long"); + }, + ); + self.write(msg.as_bytes()); + + msg.clear(); + write!( + &mut msg, + "{:<20}: {}\n", + "Git revision", + env!("GIT_REVISION") + ) + .unwrap_or_else(|_| { + msg = String::from("Git revision: too long"); + }); + self.write(msg.as_bytes()); + + msg.clear(); + write!(&mut msg, "{:<20}: {}\n", "Features", env!("ALL_FEATURES")) + .unwrap_or_else(|_| { + msg = String::from("Features: too long"); + }); + self.write(msg.as_bytes()); + + msg.clear(); + // Note(unwrap): The msg size is long enough to always contain the provided + // string. + write!(&mut msg, "{:<20}: ", "Panic Info").unwrap(); + self.write(msg.as_bytes()); + self.write( + panic_persist::get_panic_message_bytes().unwrap_or("None".as_bytes()), + ); + self.write("\n".as_bytes()); + + msg.clear(); + // Note(unwrap): The msg size is long enough to be sufficient for all possible + // formats. + write!( + &mut msg, + "{:<20}: {}\n", + "Watchdog Detected", + platform::watchdog_detected() + ) + .unwrap(); + self.write(msg.as_bytes()); + + // Reading the panic message above clears the panic message, so similarly, we + // should also clear the watchdog once read. + platform::clear_reset_flags(); + } + Request::WriteIpAddress(prop, addr) => match prop { Property::SelfAddress => self.settings.set_ip_address(addr), Property::BrokerAddress => self.settings.set_broker(addr), @@ -312,6 +371,7 @@ gateway, netmask] netmask, gateway] and must be an IP address (e.g. 192.168.1.1) * `write id ` - Write the MQTT client ID of the device. must be 23 or less ASCII \ characters. +* `service` - Read the service information. Service infromation clears once read. ".as_bytes(), ); } @@ -335,6 +395,7 @@ characters. Token::Reset => Request::Reset, Token::Dfu => Request::ResetBootloader, Token::Help => Request::Help, + Token::ServiceInfo => Request::ServiceInfo, Token::Read => { // Validate that there is one valid token following. let property_token = lex.next().ok_or("Malformed command")?;