From bbb2998ffba82eccecb20b62a134d326bc3fab5b Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Fri, 10 Oct 2025 14:45:35 -0700 Subject: [PATCH 1/2] Implement keyboard service --- Cargo.lock | 74 +++++ Cargo.toml | 1 + keyboard-service/Cargo.toml | 40 +++ keyboard-service/src/gpio_kb.rs | 477 ++++++++++++++++++++++++++++++++ keyboard-service/src/hid_kb.rs | 407 +++++++++++++++++++++++++++ keyboard-service/src/lib.rs | 170 ++++++++++++ 6 files changed, 1169 insertions(+) create mode 100644 keyboard-service/Cargo.toml create mode 100644 keyboard-service/src/gpio_kb.rs create mode 100644 keyboard-service/src/hid_kb.rs create mode 100644 keyboard-service/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c9738dd94..23ffa6f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1218,6 +1218,46 @@ dependencies = [ "winnow 0.6.24", ] +[[package]] +name = "keyberon" +version = "0.2.0" +source = "git+https://github.com/TeXitoi/keyberon#362c209f729d05505abb1984a0ddb321ec9ebf53" +dependencies = [ + "arraydeque", + "either", + "embedded-hal 1.0.0", + "heapless", + "keyberon-macros", + "usb-device", +] + +[[package]] +name = "keyberon-macros" +version = "0.1.0" +source = "git+https://github.com/TeXitoi/keyberon#362c209f729d05505abb1984a0ddb321ec9ebf53" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", +] + +[[package]] +name = "keyboard-service" +version = "0.1.0" +dependencies = [ + "defmt 0.3.100", + "embassy-executor", + "embassy-futures", + "embassy-sync", + "embassy-time", + "embedded-hal 1.0.0", + "embedded-services", + "hid-service", + "keyberon", + "log", + "static_cell", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1640,6 +1680,30 @@ dependencies = [ "log", ] +[[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-macro-error-attr2" version = "2.0.0" @@ -2196,6 +2260,16 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + [[package]] name = "uuid" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 0b105298a..90071957f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "power-policy-service", "type-c-service", "debug-service", + "keyboard-service", ] exclude = ["examples/*"] diff --git a/keyboard-service/Cargo.toml b/keyboard-service/Cargo.toml new file mode 100644 index 000000000..14ec33dc4 --- /dev/null +++ b/keyboard-service/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "keyboard-service" +version = "0.1.0" +edition = "2024" +description = "Keyboard service implementation" +repository = "https://github.com/OpenDevicePartnership/embedded-services" +rust-version = "1.85" +license = "MIT" + +[dependencies] +defmt = { workspace = true, optional = true } +log = { workspace = true, optional = true } +embassy-executor.workspace = true +embassy-futures.workspace = true +embassy-sync.workspace = true +embassy-time.workspace = true +embedded-services.workspace = true +embedded-hal.workspace = true +static_cell.workspace = true +hid-service = { path = "../hid-service" } +keyberon = { git = "https://github.com/TeXitoi/keyberon" } + +[features] +default = [] +defmt = [ + "dep:defmt", + "embedded-services/defmt", + "embassy-time/defmt", + "embassy-sync/defmt", + "embassy-executor/defmt", + "hid-service/defmt", +] +log = [ + "dep:log", + "embedded-services/log", + "embassy-time/log", + "embassy-sync/log", + "embassy-executor/log", + "hid-service/log", +] diff --git a/keyboard-service/src/gpio_kb.rs b/keyboard-service/src/gpio_kb.rs new file mode 100644 index 000000000..586f2c59f --- /dev/null +++ b/keyboard-service/src/gpio_kb.rs @@ -0,0 +1,477 @@ +//! A configurable GPIO keyboard which can be used for the keyboard service. +//! If this does not meets the user's needs, the user can implement the `HidKeyboard` trait +//! for their own specific use case. +//! +//! Currently there is no software-implemented deghosting, relying on that to be done +//! in hardware (e.g. diode per switch). Will need to investigate more if there are ways to create +//! a configurable software-implemented deghosting strategy. +use super::HidKeyboard; +use embassy_sync::signal::Signal; +use embassy_time::Timer; +use embedded_hal::digital::{InputPin, OutputPin}; +use embedded_services::GlobalRawMutex; +use embedded_services::hid; +use embedded_services::{error, warn}; +use keyberon::debounce::Debouncer; +use keyberon::key_code::KbHidReport; +use keyberon::layout::Layout; +pub use keyberon::layout::{Layers, layout}; +use keyberon::matrix::Matrix; + +// Currently hard cap this to 6 since Keyberon only supports 6 keys +// If move away from Keyberon this can be changed and allow user to configure +const KRO: usize = 6; + +// A single byte represents the state of 8 key modifiers +const KEYMOD_SZ: usize = 1; + +// Don't like how this still needs knowledge of i2c representation +// May need to consider letting hid back end create the hid descriptor +const INPUT_MAX_LEN: usize = super::hid_kb::I2C_REPORT_HEADER_SZ + KEYMOD_SZ + KRO; + +// An input report +const REPORT_ID: u8 = 1; + +// This is a basic report descriptor that defines a single keyboard report with 6 keys +// Revisit: Could also allow user to pass in a custom report descriptor +// Will also need to be expanded to support output reports like LEDs +// Revisit: Investigate a struct representation of report descriptors, +// but may prove challenging due to the fact that a strict ordering and length is not defined. +#[rustfmt::skip] +const REPORT_DESCRIPTOR: &[u8] = &[ + // Usage Page (Generic Desktop Ctrls) + 0x05, 0x01, + // Usage (Keyboard) + 0x09, 0x06, + // Collection (Application) + 0xA1, 0x01, + // Report ID (1) + 0x85, REPORT_ID, + // Usage Page (Keypad) + 0x05, 0x07, + // Usage Minimum (0xE0) + 0x19, 0xE0, + // Usage Maximum (0xE7) + 0x29, 0xE7, + // Logical Minimum (0) + 0x15, 0x00, + // Logical Maximum (1) + 0x25, 0x01, + // Report Size (1) + 0x75, 0x01, + // Report Count (8) (8 modifier keys represented by single bit) + 0x95, 0x08, + // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x81, 0x02, + // Usage Minimum (0x00) + 0x19, 0x00, + // Usage Maximum (0x91) + 0x29, 0x91, + // Logical Maximum (255) + 0x26, 0xFF, 0x00, + // Report Size (8) + 0x75, 0x08, + // Report Count (6) (Keyberon only supports 6 keys) + 0x95, 0x06, + // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x81, 0x00, + // End Collection + // Revisit: LED output reports and consumer reports... but can we make that generic? + 0xC0, +]; + +// Note: This is not defined at top-level because operations on const generics is not yet stable +// E.g. `struct HidReport([u8; KRO + 1])` is not currently possible +#[derive(Default)] +struct HidReport([u8; KRO + KEYMOD_SZ]); + +impl HidReport { + fn as_slice(&self) -> super::HidReportSlice<'_> { + super::HidReportSlice(&self.0) + } +} + +impl From for HidReport { + fn from(keyberon: KbHidReport) -> Self { + // Note: Keyberon uses boot/usb protocol which is [0:modifers, 1:reserved, 2..8: usage codes] + let keyberon = keyberon.as_bytes(); + + let mut buf = [0; KRO + KEYMOD_SZ]; + buf[0] = keyberon[0]; + buf[1..1 + KRO].copy_from_slice(&keyberon[2..2 + KRO]); + + HidReport(buf) + } +} + +/// GPIO keyboard configuration. +pub struct KeyboardConfig< + const NCOLS: usize, + const NROWS: usize, + const NLAYERS: usize, + E, + INPUT: InputPin, + OUTPUT: OutputPin, + DELAY: FnMut(), +> { + /// An array of input pins representing each row. + pub rows: [INPUT; NROWS], + /// An array of output pins representing each column. + pub cols: [OUTPUT; NCOLS], + /// A keyberon layers implementation which maps coordinates to keys. + pub layers: &'static Layers, + /// The interval in milliseconds between each scan. + pub poll_ms: u64, + /// The number of times an event (e.g. a key press) needs to be seen to actually register. + pub nb_bounce: u16, + /// A function that provides some blocking delay implementation. + /// This is used during scan between driving a row and reading a column. + pub delay: DELAY, + /// If enabled, the scanner will perform ghosting detection, + /// and report an error to host if detected. + /// + /// This will also discard false positives, so for a full NKRO/diode-per-switch keyboard, + /// it is best to leave this disabled. + pub deghost: bool, +} + +// Internal keyberon configuration which the public KeyboardConfig gets converted to +struct KeyberonConfig< + const NCOLS: usize, + const NROWS: usize, + const NLAYERS: usize, + E, + INPUT: InputPin, + OUTPUT: OutputPin, + DELAY: FnMut(), +> { + matrix: Matrix, + debouncer: Debouncer<[[bool; NROWS]; NCOLS]>, + layout: Layout, + poll_ms: u64, + delay: DELAY, + deghost: bool, +} + +impl< + const NCOLS: usize, + const NROWS: usize, + const NLAYERS: usize, + E, + INPUT: InputPin, + OUTPUT: OutputPin, + DELAY: FnMut(), +> TryFrom> + for KeyberonConfig +{ + type Error = E; + + fn try_from(cfg: KeyboardConfig) -> Result { + Ok(Self { + // Keyberon expects colums as input and rows as output, but most platforms seem opposite? + // So we swap them, and during scan perform a transform to reverse coordinates. + // + // Revisit: See if there is an easy way to support both formats generically + matrix: Matrix::new(cfg.rows, cfg.cols)?, + debouncer: keyberon::debounce::Debouncer::new( + [[false; NROWS]; NCOLS], + [[false; NROWS]; NCOLS], + cfg.nb_bounce, + ), + layout: Layout::new(cfg.layers), + poll_ms: cfg.poll_ms, + delay: cfg.delay, + deghost: cfg.deghost, + }) + } +} + +/// Keyboard HID configuration. +pub struct HidConfig { + /// Vendor ID + pub vid: u16, + /// Product ID + pub pid: u16, +} + +fn has_ghost(pressed: &[[bool; NROWS]; NCOLS]) -> bool { + // First convert rows represented as an array of bools into packed bits + // This is likely more efficient than doing a triple nested loop below, + // since this allows us to quickly check bits + // Chose u128 as it's the largest primitive and it's very unlikely a keyboard will have more than 128 rows + let mut pressed_bits = [0u128; NCOLS]; + let mut count = 0; + for (c, row) in pressed.iter().enumerate() { + for (r, &key) in row.iter().enumerate() { + if key { + count += 1; + pressed_bits[c] |= 1 << r; + } + } + } + + // Ghosting is only possible when >2 keys are simultaneously pressed + if count <= 2 { + return false; + } + + // Compare every column against every other column. + // + // If bitwise and between two columns has >= 2 bits set, + // at least two pressed keys share same row and column, + // which means a one of those keys reported as pressed is very likely a ghost. + // + // This can report false positives however, as the user might actually be pressing + // 4 keys forming the corners of a rectangle. This is unlikely however, as keypads are typically + // wired to make this improbable, so the usual response is to discard the input regardless + // and report rollover error to the host. + // + // Also note this is sufficient only on a complete post-scan result. + // There are tricks mid-scan to detect 3 keys in L-shape (which would cause ghost later on in the scan) + // and bail early, but that would require modifiying keyberon. + // + // So we essentially complete a scan, check for ghosts, THEN pass into debouncer. + for (i, c1) in pressed_bits.iter().enumerate() { + for c2 in pressed_bits[i + 1..].iter() { + if (c1 & c2).count_ones() >= 2 { + return true; + } + } + } + + false +} + +/// A HID-aware GPIO keyboard ready to be used by the Keyboard Service. +pub struct GpioKeyboard< + const NCOLS: usize, + const NROWS: usize, + const NLAYERS: usize, + E, + INPUT: InputPin, + OUTPUT: OutputPin, + DELAY: FnMut(), +> { + kb_cfg: KeyberonConfig, + hid_cfg: HidConfig, + report: HidReport, + power_state: hid::PowerState, + scan_signal: Signal, + report_freq: hid::ReportFreq, +} + +impl< + const NCOLS: usize, + const NROWS: usize, + const NLAYERS: usize, + E, + INPUT: InputPin, + OUTPUT: OutputPin, + DELAY: FnMut(), +> GpioKeyboard +{ + /// Create a new instance of a GPIO Keyboard with given configuration. + /// + /// # Panics + /// + /// If `deghosting` is enabled in `kb_cfg`, panics if `NROWS > 128`. + pub fn new( + kb_cfg: KeyboardConfig, + hid_cfg: HidConfig, + ) -> Result { + // We can only support upto 128 rows for deghosting + if kb_cfg.deghost { + assert!(NROWS <= 128); + } + + Ok(Self { + kb_cfg: KeyberonConfig::try_from(kb_cfg)?, + hid_cfg, + report: HidReport::default(), + power_state: hid::PowerState::Sleep, + scan_signal: Signal::new(), + report_freq: hid::ReportFreq::Infinite, + }) + } +} + +impl< + const NCOLS: usize, + const NROWS: usize, + const NLAYERS: usize, + E, + INPUT: InputPin, + OUTPUT: OutputPin, + DELAY: FnMut(), +> HidKeyboard for GpioKeyboard +{ + fn register_file(&self) -> hid::RegisterFile { + // Don't need anything special so use the default + hid::RegisterFile::default() + } + + fn hid_descriptor(&self) -> hid::Descriptor { + const VERSION: u16 = 0x0100; + + hid::Descriptor { + w_hid_desc_length: hid::DESCRIPTOR_LEN as u16, + bcd_version: VERSION, + w_report_desc_length: REPORT_DESCRIPTOR.len() as u16, + w_report_desc_register: self.register_file().report_desc_reg, + w_input_register: self.register_file().input_reg, + w_max_input_length: INPUT_MAX_LEN as u16, + w_output_register: self.register_file().output_reg, + // We don't currently support output reports with this keyboard + w_max_output_length: 0, + w_command_register: self.register_file().command_reg, + w_data_register: self.register_file().data_reg, + w_vendor_id: self.hid_cfg.vid, + w_product_id: self.hid_cfg.pid, + w_version_id: VERSION, + } + } + + fn report_descriptor(&self) -> &'static [u8] { + REPORT_DESCRIPTOR + } + + async fn scan(&mut self) -> Result, super::KeyboardError> { + // Wait until we are told to power on before scanning + if self.power_state == hid::PowerState::Sleep { + self.scan_signal.wait().await; + } + + // Determine the idle rate + let idle = if let hid::ReportFreq::Msecs(ms) = self.report_freq { + Timer::after_millis(ms as u64) + } else { + // If set to 'infinite', set a timer very far in the future (effectively infinite) + Timer::after_secs(1_000_000) + }; + + // Polling scan loop + let scan = async { + loop { + // Scan for keys currently pressed + if let Ok(pressed) = self.kb_cfg.matrix.get_with_delay(&mut self.kb_cfg.delay) { + // If ghosting detected, break and report error + if self.kb_cfg.deghost && has_ghost(&pressed) { + warn!("Key ghosting detected"); + break Err(super::KeyboardError::Ghosting); + } + + // Run the scan through the debouncer, applying a coordinate transform if provided + // Note: Keyberon expects cols as input and rows as output, but we are the opposite so swap them for proper coordinate + let events = self + .kb_cfg + .debouncer + .events(pressed) + .map(|e| e.transform(|x, y| (y, x))); + + // Processes each event, notifiying the layout of state change + // If there was any event, we know we have a new report to produce + let mut changed = false; + for event in events { + self.kb_cfg.layout.event(event); + self.kb_cfg.layout.tick(); + changed = true; + } + + // We only want to send a report once on press, and once on release + // No need to continuously send reports while the key is held down + if changed { + // Keyberon layout will convert event coordinates to HID usage codes + // But keyberon's format follows boot/usb protocol, so we convert it + // to a contiguous modifer byte + usage codes array + self.report = self.kb_cfg.layout.keycodes().collect::().into(); + break Ok(()); + } + } else { + error!("Failed to scan keyboard!"); + break Err(super::KeyboardError::Scan); + } + + // If no events, sleep then scan again + // Revisit: Instead of periodic polling which could waste power, could wait for interrupt + // from any row input. + Timer::after_millis(self.kb_cfg.poll_ms).await; + } + }; + + match embassy_futures::select::select(idle, scan).await { + // Hit the idle limit? Return the most recent report + embassy_futures::select::Either::First(_) => Ok(self.report.as_slice()), + + // Have a fresh report? Return it + // Note: We don't return report slice in the loop above as this causes lifetime issues + embassy_futures::select::Either::Second(Ok(())) => Ok(self.report.as_slice()), + + // Error? Let the HID backend convert it to report for us + embassy_futures::select::Either::Second(Err(e)) => Err(e), + } + } + + async fn reset(&mut self) -> Result<(), super::KeyboardError> { + self.report_freq = hid::ReportFreq::Infinite; + Ok(()) + } + + async fn set_power_state(&mut self, power_state: hid::PowerState) -> Result<(), super::KeyboardError> { + self.power_state = power_state; + + // Signal to scanner it can start now + if power_state == hid::PowerState::On { + self.scan_signal.signal(()); + } + + Ok(()) + } + + async fn set_idle( + &mut self, + _report_id: hid::ReportId, + report_freq: hid::ReportFreq, + ) -> Result<(), super::KeyboardError> { + self.report_freq = report_freq; + Ok(()) + } + + fn get_idle(&self, _report_id: hid::ReportId) -> hid::ReportFreq { + self.report_freq + } + + async fn set_protocol(&mut self, _protocol: hid::Protocol) -> Result<(), super::KeyboardError> { + // NOP + // Only support Report protocol + Ok(()) + } + + fn get_protocol(&self) -> hid::Protocol { + hid::Protocol::Report + } + + async fn vendor_cmd(&mut self) -> Result<(), super::KeyboardError> { + // NOP + // No vendor-defined commands for this implementation + Ok(()) + } + + async fn set_report( + &mut self, + _report_type: hid::ReportType, + _report_id: hid::ReportId, + _buf: &embedded_services::buffer::SharedRef<'static, u8>, + ) -> Result<(), super::KeyboardError> { + // NOP + // Do not currently support Output/Feature reports + Ok(()) + } + + fn get_report(&self, report_type: hid::ReportType, _report_id: hid::ReportId) -> super::HidReportSlice<'_> { + match report_type { + hid::ReportType::Input => self.report.as_slice(), + // We don't currently support feature reports + _ => super::HidReportSlice(&[0x00]), + } + } +} diff --git a/keyboard-service/src/hid_kb.rs b/keyboard-service/src/hid_kb.rs new file mode 100644 index 000000000..2c4943ac3 --- /dev/null +++ b/keyboard-service/src/hid_kb.rs @@ -0,0 +1,407 @@ +//! Handles the backend HID communication with host for the keyboard +use super::HidKeyboard; +use core::borrow::BorrowMut; +use embassy_sync::channel::Channel; +use embassy_sync::once_lock::OnceLock; +use embassy_sync::signal::Signal; +use embedded_hal::digital::OutputPin; +use embedded_services::GlobalRawMutex; +use embedded_services::buffer::SharedRef; +use embedded_services::comms; +use embedded_services::error; +use embedded_services::hid; +use embedded_services::ipc::deferred as ipc; +use hid_service::i2c::I2cSlaveAsync; +use static_cell::StaticCell; + +// Revisit: Figure out the best way to make these caller configurable +// According to spec input reports can be upto u16 max, but we don't want a queue +// with 65k bytes * queue size, so need to investigate smarter way of supporting theoretical max +// efficiently. +const INPUT_MAX: usize = 16; +const REPORT_DESC_MAX: usize = 256; +const REPORT_QUEUE_MAX: usize = 10; + +// The size of a HID i2c report header +pub const I2C_REPORT_HEADER_SZ: usize = REPORT_LEN_SZ + REPORT_ID_SZ; + +// Max size of a HID report not including i2c header +const REPORT_MAX_SZ: usize = INPUT_MAX - I2C_REPORT_HEADER_SZ; + +// I2C input reports begin with 2 byte length of the report +const REPORT_LEN_SZ: usize = 2; + +// A input report +const REPORT_ID: u8 = 1; + +// Indicates the report ID (a single device like a keyboard might have multiple report types) +// If only a single type, can be omitted. But include it anyway for future-proofing. +const REPORT_ID_SZ: usize = 1; + +type Report = [u8; INPUT_MAX]; +type ReportQueue = Channel; +type CmdIpc = ipc::Channel, Option>>; +type ReportIpc = ipc::Channel, ()>; + +// A HID input report in the format HID over i2c expects +#[derive(Default)] +struct HidI2cReport([u8; INPUT_MAX]); + +impl HidI2cReport { + // Conenience for the raw bytes + fn to_bytes(&self) -> [u8; INPUT_MAX] { + self.0 + } + + fn from_report_slice(report: super::HidReportSlice, max_len: u16) -> Self { + let mut buf = Self::default().0; + let bytes = report.as_bytes(); + + // Report length + // This always needs to be set to the max_input_len field from the HID descriptor + // Why the need for this redundancy? Who knows. + buf[0..REPORT_LEN_SZ].copy_from_slice(&max_len.to_le_bytes()); + + // Report type/id + buf[2] = REPORT_ID; + + // Modifer keys byte and usage codes + // + // Revisit: Discards bytes from the report greater than we can handle + // Not great, so will change once I have a better idea of allowing + // max buffer sizes to be configurable + let len = bytes.len().min(REPORT_MAX_SZ); + buf[3..3 + len].copy_from_slice(&bytes[..len]); + + Self(buf) + } + + fn from_error(error: super::KeyboardError, max_len: u16) -> Self { + const ERROR_ROLL_OVER: u8 = 0x01; + const ERROR_UNDEFINED: u8 = 0x03; + + let err = match error { + super::KeyboardError::Ghosting | super::KeyboardError::Rollover => [ERROR_ROLL_OVER; REPORT_MAX_SZ], + super::KeyboardError::Scan | super::KeyboardError::Command => [ERROR_UNDEFINED; REPORT_MAX_SZ], + }; + + HidI2cReport::from_report_slice(super::HidReportSlice(&err), max_len) + } +} + +// Shared between tasks for communication and synchronization +struct Context { + report_queue: ReportQueue, + report_ipc: ReportIpc, + cmd_ipc: CmdIpc, + send_complete: Signal, +} +static CONTEXT: OnceLock = OnceLock::new(); + +// Sets up the context, report descriptor buffer, and HID device +pub(crate) async fn init( + spawner: embassy_executor::Spawner, + hid_descriptor: hid::Descriptor, + report_descriptor: &'static [u8], + reg_file: hid::RegisterFile, +) { + // Initialize interprocess comms/synchronization context + let context = Context { + report_queue: ReportQueue::new(), + report_ipc: ReportIpc::new(), + cmd_ipc: CmdIpc::new(), + send_complete: Signal::new(), + }; + CONTEXT + .init(context) + .map_err(|_| ()) + .expect("Keyboard service already initialized"); + + // Initialize the HID device + static DEVICE: StaticCell = StaticCell::new(); + let device = hid::Device::new(super::HID_KB_ID, reg_file); + let device = DEVICE.init(device); + hid::register_device(device) + .await + .expect("Device must not already be registered"); + + // Spawn device request handling task + // Other tasks are spawned by user due to need for macro to implement them because of generics + spawner.must_spawn(device_requests_task(device, hid_descriptor, report_descriptor)); +} + +// This task handles receiving HID requests from the host, +// forwarding them to the keyboard task to process, then sending a response back to host +#[embassy_executor::task] +async fn device_requests_task( + device: &'static hid::Device, + hid_descriptor: hid::Descriptor, + report_descriptor: &'static [u8], +) { + let context = CONTEXT.get().await; + + // Buffer holding hid descriptor + embedded_services::define_static_buffer!(hid_desc_buf, u8, [0u8; hid::DESCRIPTOR_LEN]); + { + let mut buf = hid_desc_buf::get_mut() + .expect("Must not already be borrowed mutably") + .borrow_mut(); + let buf: &mut [u8] = buf.borrow_mut(); + hid_descriptor + .encode_into_slice(buf) + .expect("Src and dst buffers must be same length"); + } + + // Buffer holding report descriptor + embedded_services::define_static_buffer!(report_desc_buf, u8, [0u8; REPORT_DESC_MAX]); + { + let mut buf = report_desc_buf::get_mut() + .expect("Must not already be borrowed mutably") + .borrow_mut(); + let buf: &mut [u8] = buf.borrow_mut(); + buf[..report_descriptor.len()].copy_from_slice(report_descriptor); + } + + loop { + let request = device.wait_request().await; + match request { + // For descriptors, we simply pass references to respective buffers + // These are static and never change, so don't need to do much else + hid::Request::Descriptor => { + let response = hid_desc_buf::get(); + let response = Some(hid::Response::Descriptor(response)); + device.send_response(response).await.expect("Infallible"); + } + hid::Request::ReportDescriptor => { + let response = report_desc_buf::get().slice(0..report_descriptor.len()); + let response = Some(hid::Response::ReportDescriptor(response)); + device.send_response(response).await.expect("Infallible"); + } + + // We won't receive this request unless keyboard told host we have reports available (via interrupt assert) + hid::Request::InputReport => { + // Wait for the keyboard to give us the report + let ipc = context.report_ipc.receive().await; + let report = ipc.command.clone(); + let response = Some(hid::Response::InputReport( + report.slice(0..hid_descriptor.w_max_input_length as usize), + )); + + // Then send it to the host + device.send_response(response).await.expect("Infallible"); + + // Finally tell keyboard we've sent the report so it can deassert interrupt + ipc.respond(()); + } + + // Treat this as a SET_REPORT(Output) command + // It is unclear if the behavior is meant to be different, or just different ways + // of transporting the same request. + hid::Request::OutputReport(id, buf) => { + let response = context + .cmd_ipc + .execute(hid::Command::SetReport( + hid::ReportType::Output, + id.unwrap_or(hid::ReportId(1)), + buf, + )) + .await; + device.send_response(response).await.expect("Infallible"); + } + + // Tell the keyboard to execute the requested command, waiting for it to give us a response to send to host + hid::Request::Command(cmd) => { + let response = context.cmd_ipc.execute(cmd).await; + device.send_response(response).await.expect("Infallible"); + } + } + } +} + +/// This task handles calling the keyboard `scan` in a loop, while also listening for commands +/// from the HID request handler task. To minimize delay between scan loops, we quickly process commands +/// and let the HID request handler task handle forwarding the response to the host. +pub async fn handle_keyboard(mut hid_kb: T) { + let context = CONTEXT.get().await; + + // Buffer holding immediate report requests + embedded_services::define_static_buffer!(report_buf, u8, [0u8; INPUT_MAX]); + let owned_buf = report_buf::get_mut().expect("Must not already be borrowed mutably"); + let max_input_len = hid_kb.hid_descriptor().w_max_input_length; + + loop { + // Wait for either a command request or input report to become available + match embassy_futures::select::select(hid_kb.scan(), context.cmd_ipc.receive()).await { + // If we got a keyboard report, queue it up to be sent out + embassy_futures::select::Either::First(report) => { + let i2c_report = match report { + Ok(report) => { + // Revisit: Look into ways to avoid multiple copies (even if reports are small) + // But, difficult to store slices/references in queue with all the lifetime management that entails + // May need some form of ring buffer if really need to squeeze performance? + HidI2cReport::from_report_slice(report, max_input_len).to_bytes() + } + Err(e) => HidI2cReport::from_error(e, max_input_len).to_bytes(), + }; + + context.report_queue.send(i2c_report).await; + } + + // Otherwise if we are instructed to perform a command, do it quickly then respond + // Revisit: For commands that are fallible, realistically what can we do other than print an error? + embassy_futures::select::Either::Second(request) => match request.command { + // A reset is handled similarly to an input report. + // When we receive a reset command, we must place reset sentinel value ([0x00, 0x00]) + // into report buffer, then assert interrupt so host can read it after we've reset the keyboard. + hid::Command::Reset => { + if hid_kb.reset().await.is_ok() { + // Spec says device should enter power on state after reset + if hid_kb.set_power_state(hid::PowerState::On).await.is_err() { + error!("Failed to set keyboard powerstate to ON"); + } + + context.report_queue.send([0x00; INPUT_MAX]).await; + request.respond(None); + } else { + error!("Failed to reset keyboard"); + } + } + + // Instructs the keyboard to immediately return the latest input/feature report + hid::Command::GetReport(report_type, report_id) => { + { + let report = hid_kb.get_report(report_type, report_id); + let report = HidI2cReport::from_report_slice(report, max_input_len).to_bytes(); + let mut buf = owned_buf.borrow_mut(); + let buf: &mut [u8] = buf.borrow_mut(); + buf[..report.len()].copy_from_slice(&report); + } + request.respond(Some(hid::Response::InputReport(report_buf::get()))); + } + + // Instructs the keyboard to immediately set the output/feature report + hid::Command::SetReport(report_type, report_id, ref buf) => { + if hid_kb.set_report(report_type, report_id, buf).await.is_ok() { + request.respond(None); + } else { + error!("Failed to set keyboard report"); + } + } + + // Gets the keyboard's idle time before sending a report even if no changes + // Not typically used by modern hosts, but we support it anyway + hid::Command::GetIdle(report_id) => { + let freq = hid_kb.get_idle(report_id); + request.respond(Some(hid::Response::Command(hid::CommandResponse::GetIdle(freq)))); + } + + // Sets the keyboard's idle time before sending a report even if no changes + // Not typically used by modern hosts, but we support it anyway + hid::Command::SetIdle(report_id, report_freq) => { + if hid_kb.set_idle(report_id, report_freq).await.is_ok() { + request.respond(None); + } else { + error!("Failed to set keyboard idle"); + } + } + + // Gets the keyboard protocol (Boot or Report) + hid::Command::GetProtocol => { + let protocol = hid_kb.get_protocol(); + request.respond(Some(hid::Response::Command(hid::CommandResponse::GetProtocol( + protocol, + )))); + } + + // Sets the keyboard protocol (Boot or Report) + hid::Command::SetProtocol(protocol) => { + if hid_kb.set_protocol(protocol).await.is_ok() { + request.respond(None); + } else { + error!("Failed to set keyboard protocol"); + } + } + + // Sets the power state of the keyboard (On or Sleep) + hid::Command::SetPower(power_state) => { + if hid_kb.set_power_state(power_state).await.is_ok() { + request.respond(None); + } else { + error!("Failed to set keyboard power state"); + } + } + + // Vendor defined command + hid::Command::Vendor => { + if hid_kb.vendor_cmd().await.is_ok() { + request.respond(None); + } else { + error!("Failed to execute vendor keyboard command"); + } + } + }, + } + } +} + +/// This task handles queueing up input reports as they are generated, asserting interrupts to the host, +/// and synchronizing with the device request handler to ensure they are sent to the host properly. +/// +/// This is a separate task because we want the main `scan` loop to quickly fire off an available report +/// without it being blocked waiting for communication with the host. We also use a queue in case multiple reports +/// are available before one is fully processed to prevent any lost key events. +pub async fn handle_reports(mut kb_int: impl OutputPin) { + let context = CONTEXT.get().await; + + embedded_services::define_static_buffer!(input_buf, u8, [0u8; INPUT_MAX]); + let owned_buf = input_buf::get_mut().expect("Must not already be borrowed immutably"); + + loop { + // Wait for keyboard to push a report to the queue + let report = context.report_queue.receive().await; + + // Wait for previous sends to complete + // Necessary since `handle_host_requests` is borrowing input_buf during duration of send + context.send_complete.wait().await; + + // Once we have one, copy it to outgoing buffer + { + let mut buf = owned_buf.borrow_mut(); + let buf: &mut [u8] = buf.borrow_mut(); + buf.copy_from_slice(&report); + } + + // Then assert interrupt so host knows to send us a read command + if kb_int.set_low().is_err() { + error!("Failed to set keyboard interrupt pin low! Canceling report."); + continue; + } + + // Send the buffer reference to request handler, waiting for it to tell us it finished sending the report + context.report_ipc.execute(input_buf::get()).await; + + // Finally deassert interrupt + if kb_int.set_high().is_err() { + error!("Failed to set keyboard interrupt pin high! Host may not respond properly."); + } + } +} + +/// This task handles listening for raw i2c commands from the host, detecting what kind of request it is, +/// then forwarding that request to the device request listener. +pub async fn handle_host_requests(host: &'static mut hid_service::i2c::Host) { + let context = CONTEXT.get().await; + + comms::register_endpoint(host, &host.tp) + .await + .expect("Host must not already be registered."); + + loop { + let res = host.process().await; + match res { + Ok(()) => context.send_complete.signal(()), + Err(hid_service::Error::Bus(_)) => error!("Host I2C bus error"), + Err(hid_service::Error::Hid(e)) => error!("Host HID error: {:?}", e), + } + } +} diff --git a/keyboard-service/src/lib.rs b/keyboard-service/src/lib.rs new file mode 100644 index 000000000..5303dc3df --- /dev/null +++ b/keyboard-service/src/lib.rs @@ -0,0 +1,170 @@ +//! Keyboard Service +//! +//! For users with basic GPIO key matrix needs, consider using the provided `GpioKeyboard`. +//! +//! Otherwise, users may manually implement the `HidKeyboard` trait for custom scanning logic +//! or hardware-implemented key scanners. +#![no_std] + +pub mod gpio_kb; +pub mod hid_kb; + +use embedded_services::buffer::SharedRef; +use embedded_services::hid; + +pub const HID_KB_ID: hid::DeviceId = hid::DeviceId(0); + +/// HID keyboard error. +pub enum KeyboardError { + /// Rollover occurred + Rollover, + /// Scan error (e.g. failed to drive GPIO) + Scan, + /// Ghosting detected + Ghosting, + /// Command error + Command, +} + +/// A slice of a HID report. +/// +/// This should only contain a single key modifiers byte followed by KRO usage codes. +/// The HID backend will add the appropriate header for underlying protocol (e.g. HID over i2c header). +pub struct HidReportSlice<'a>(&'a [u8]); + +impl HidReportSlice<'_> { + /// Returns the HID report as a raw byte slice. + pub fn as_bytes(&self) -> &[u8] { + self.0 + } +} + +/// Represents a HID-aware keyboard. +/// +/// This should be implemented on a struct and passed to the keyboard service initialization +/// if not using the provided `GpioKeyboard`. +pub trait HidKeyboard { + /// Returns the HID descriptor for the keyboard. + fn hid_descriptor(&self) -> hid::Descriptor; + + /// Returns the report descriptor for the keyboard as a static byte slice. + // + // Revisit: To support this not being static would require a bit of refactoring, + // since the slice gets passed to one task while Self gets passed to another, which would + // require a bit of lifetime management if this were not static. + fn report_descriptor(&self) -> &'static [u8]; + + /// Returns the register file for the keyboard. + fn register_file(&self) -> hid::RegisterFile; + + /// Performs a key scan, yielding when a report is available. + /// + /// If no report is available, this should not yield unless dictated by the idle frequency. + /// + /// The format of the report depends on the protocol (Boot vs Report) as well as underlying + /// transport protocol (I2C vs USB, for example). + /// + /// # Cancel Safety + /// + /// The implementation MUST be cancel safe as the HID backend may cancel to service an incoming command. + fn scan(&mut self) -> impl core::future::Future, KeyboardError>>; + + /// Resets the keyboard to initial state. + fn reset(&mut self) -> impl core::future::Future>; + + /// Sets the power state of the keyboard. + /// + /// In sleep state, keyboard should not yield new input reports. + fn set_power_state( + &mut self, + power_state: hid::PowerState, + ) -> impl core::future::Future>; + + /// Sets the frequency the keyboard should yield reports even if no new events have occurred. + /// + /// A frequency of `ReportFreq::Infinite` should result in the keyboard ONLY yielding reports + /// when new events have occurred. + fn set_idle( + &mut self, + report_id: hid::ReportId, + report_freq: hid::ReportFreq, + ) -> impl core::future::Future>; + + /// Gets the idle frequency of the keyboard. + fn get_idle(&self, report_id: hid::ReportId) -> hid::ReportFreq; + + /// Sets the protocol (Boot vs Report) of the keyboard. + fn set_protocol( + &mut self, + protocol: hid::Protocol, + ) -> impl core::future::Future>; + + /// Gets the protocol (Boot vs Report) of the keyboard. + fn get_protocol(&self) -> hid::Protocol; + + /// Perform a vendor-defined keyboard command. + fn vendor_cmd(&mut self) -> impl core::future::Future>; + + /// Sets output or feature report for the keyboard with the given report ID. + fn set_report( + &mut self, + report_type: hid::ReportType, + report_id: hid::ReportId, + buf: &SharedRef<'static, u8>, + ) -> impl core::future::Future>; + + /// Gets input or feature report for the keyboard with the given report ID. + fn get_report(&self, report_type: hid::ReportType, report_id: hid::ReportId) -> HidReportSlice<'_>; +} + +/// Initialize the keyboard service given keyboard's HID configuration. +/// +/// The user must also ensure the `impl_hid_kb_tasks!` macro is called to implement additional generic +/// tasks and then manually spawn them. E.g.: +/// +/// ```rust,ignore +/// impl_hid_kb_tasks!(MyKeyboardType, MyI2cSlaveType, MyInterruptPinType); +/// spawner.must_spawn(keyboard_task(my_keyboard)); +/// spawner.must_spawn(reports_task(my_interrupt_pin)); +/// spawner.must_spawn(host_requests_task(my_i2c_slave)); +/// ``` +pub async fn init( + spawner: embassy_executor::Spawner, + hid_descriptor: hid::Descriptor, + report_descriptor: &'static [u8], + reg_file: hid::RegisterFile, +) { + embedded_services::hid::init(); + hid_kb::init(spawner, hid_descriptor, report_descriptor, reg_file).await +} + +// Since tasks cannot be generic, rely on this user called macro to supply the explicit type information needed +#[macro_export] +macro_rules! impl_hid_kb_tasks { + ($hid_kb_ty:ty, $i2c_slave_ty:ty, $kb_int_ty:ty) => { + #[embassy_executor::task] + pub async fn keyboard_task(hid_kb: $hid_kb_ty) { + keyboard_service::hid_kb::handle_keyboard(hid_kb).await + } + + #[embassy_executor::task] + pub async fn reports_task(kb_int: $kb_int_ty) { + keyboard_service::hid_kb::handle_reports(kb_int).await + } + + #[embassy_executor::task] + async fn host_requests_task(kb_i2c: $i2c_slave_ty) { + // Revisit: Make this buffer size configurable? + embedded_services::define_static_buffer!(hid_buf, u8, [0u8; 256]); + let buf = hid_buf::get_mut().expect("Must not already be borrowed mutably"); + + // In this macro since static items cannot be generic either + static HOST: ::static_cell::StaticCell> = + ::static_cell::StaticCell::new(); + let host = hid_service::i2c::Host::new(keyboard_service::HID_KB_ID, kb_i2c, buf); + let host = HOST.init(host); + + keyboard_service::hid_kb::handle_host_requests(host).await; + } + }; +} From 3f0b6beab340fbc056f837ba3da7775e663a68cb Mon Sep 17 00:00:00 2001 From: Jerry Xie Date: Fri, 17 Oct 2025 17:31:45 -0500 Subject: [PATCH 2/2] cargo-vet audit update: * add audits for proc-macro-error and usb-device * set audit-as-crates-io as false for keyberon * prune the imports.lock --- supply-chain/audits.toml | 10 +++ supply-chain/config.toml | 47 +------------ supply-chain/imports.lock | 139 ++++++++++++++++++++++++++++---------- 3 files changed, 117 insertions(+), 79 deletions(-) diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index b20b827b1..e35a78ddc 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -232,6 +232,11 @@ who = "Robert Zieba " criteria = "safe-to-run" version = "1.11.1" +[[audits.proc-macro-error]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.4" + [[audits.rtt-target]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -307,6 +312,11 @@ who = "Jerry Xie " criteria = "safe-to-deploy" version = "0.1.0" +[[audits.usb-device]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.2" + [[audits.uuid]] who = "Jerry Xie " criteria = "safe-to-deploy" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 3f994495c..b76513c3d 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -25,6 +25,9 @@ url = "https://raw.githubusercontent.com/zcash/rust-ecosystem/main/supply-chain/ [policy.embassy-imxrt] audit-as-crates-io = false +[policy.keyberon] +audit-as-crates-io = false + [[exemptions.ahash]] version = "0.8.12" criteria = "safe-to-deploy" @@ -41,10 +44,6 @@ criteria = "safe-to-deploy" version = "0.14.0" criteria = "safe-to-deploy" -[[exemptions.atomic-polyfill]] -version = "1.0.3" -criteria = "safe-to-deploy" - [[exemptions.az]] version = "1.2.1" criteria = "safe-to-deploy" @@ -89,10 +88,6 @@ criteria = "safe-to-deploy" version = "0.10.1" criteria = "safe-to-deploy" -[[exemptions.bitflags]] -version = "2.9.4" -criteria = "safe-to-deploy" - [[exemptions.bitvec]] version = "1.0.1" criteria = "safe-to-deploy" @@ -109,10 +104,6 @@ criteria = "safe-to-deploy" version = "0.4.40" criteria = "safe-to-deploy" -[[exemptions.cobs]] -version = "0.3.0" -criteria = "safe-to-deploy" - [[exemptions.convert_case]] version = "0.6.0" criteria = "safe-to-deploy" @@ -245,10 +236,6 @@ criteria = "safe-to-deploy" version = "1.0.0" criteria = "safe-to-deploy" -[[exemptions.embedded-io]] -version = "0.6.1" -criteria = "safe-to-deploy" - [[exemptions.embedded-io-async]] version = "0.6.1" criteria = "safe-to-deploy" @@ -273,10 +260,6 @@ criteria = "safe-to-deploy" version = "0.8.5" criteria = "safe-to-deploy" -[[exemptions.hash32]] -version = "0.2.1" -criteria = "safe-to-deploy" - [[exemptions.hash32]] version = "0.3.1" criteria = "safe-to-deploy" @@ -285,18 +268,10 @@ criteria = "safe-to-deploy" version = "0.14.5" criteria = "safe-to-deploy" -[[exemptions.hashbrown]] -version = "0.15.5" -criteria = "safe-to-deploy" - [[exemptions.hashlink]] version = "0.9.1" criteria = "safe-to-deploy" -[[exemptions.heapless]] -version = "0.7.17" -criteria = "safe-to-deploy" - [[exemptions.heapless]] version = "0.8.0" criteria = "safe-to-deploy" @@ -317,10 +292,6 @@ criteria = "safe-to-deploy" version = "0.4.2" criteria = "safe-to-deploy" -[[exemptions.lock_api]] -version = "0.4.13" -criteria = "safe-to-deploy" - [[exemptions.maitake-sync]] version = "0.2.2" criteria = "safe-to-deploy" @@ -373,10 +344,6 @@ criteria = "safe-to-deploy" version = "1.1.10" criteria = "safe-to-deploy" -[[exemptions.postcard]] -version = "1.1.3" -criteria = "safe-to-deploy" - [[exemptions.proc-macro-error-attr2]] version = "2.0.0" criteria = "safe-to-deploy" @@ -397,10 +364,6 @@ criteria = "safe-to-run" version = "0.2.3" criteria = "safe-to-deploy" -[[exemptions.scopeguard]] -version = "1.2.0" -criteria = "safe-to-deploy" - [[exemptions.semver]] version = "0.9.0" criteria = "safe-to-deploy" @@ -417,10 +380,6 @@ criteria = "safe-to-deploy" version = "0.4.11" criteria = "safe-to-run" -[[exemptions.spin]] -version = "0.9.8" -criteria = "safe-to-deploy" - [[exemptions.tokio]] version = "1.47.1" criteria = "safe-to-run" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index d2af47d55..4018cfd13 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -322,6 +322,52 @@ criteria = "safe-to-deploy" version = "2.0.0" notes = "Fork of the original `adler` crate, zero unsfae code, works in `no_std`, does what it says on th tin." +[[audits.bytecode-alliance.audits.bitflags]] +who = "Jamey Sharp " +criteria = "safe-to-deploy" +delta = "2.1.0 -> 2.2.1" +notes = """ +This version adds unsafe impls of traits from the bytemuck crate when built +with that library enabled, but I believe the impls satisfy the documented +safety requirements for bytemuck. The other changes are minor. +""" + +[[audits.bytecode-alliance.audits.bitflags]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "2.3.2 -> 2.3.3" +notes = """ +Nothing outside the realm of what one would expect from a bitflags generator, +all as expected. +""" + +[[audits.bytecode-alliance.audits.bitflags]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "2.4.1 -> 2.6.0" +notes = """ +Changes in how macros are invoked and various bits and pieces of macro-fu. +Otherwise no major changes and nothing dealing with `unsafe`. +""" + +[[audits.bytecode-alliance.audits.bitflags]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "2.7.0 -> 2.9.4" +notes = "Tweaks to the macro, nothing out of order." + +[[audits.bytecode-alliance.audits.embedded-io]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "0.4.0" +notes = "No `unsafe` code and only uses `std` in ways one would expect the crate to do so." + +[[audits.bytecode-alliance.audits.embedded-io]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.4.0 -> 0.6.1" +notes = "Major updates, but almost all safe code. Lots of pruning/deletions, nothing out of the ordrinary." + [[audits.bytecode-alliance.audits.futures-core]] who = "Pat Hickey " criteria = "safe-to-deploy" @@ -343,6 +389,11 @@ who = "Pat Hickey " criteria = "safe-to-deploy" delta = "0.3.28 -> 0.3.31" +[[audits.bytecode-alliance.audits.hashbrown]] +who = "Chris Fallin " +criteria = "safe-to-deploy" +delta = "0.14.5 -> 0.15.2" + [[audits.bytecode-alliance.audits.itertools]] who = "Nick Fitzgerald " criteria = "safe-to-deploy" @@ -429,12 +480,6 @@ a few `unsafe` blocks related to utf-8 validation which are locally verifiable as correct and otherwise this crate is good to go. """ -[[audits.bytecode-alliance.audits.semver]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "1.0.17" -notes = "plenty of unsafe pointer and vec tricks, but in well-structured and commented code that appears to be correct" - [[audits.bytecode-alliance.audits.sharded-slab]] who = "Pat Hickey " criteria = "safe-to-deploy" @@ -684,6 +729,12 @@ delta = "0.2.9 -> 0.2.13" notes = "Audited at https://fxrev.dev/946396" aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" +[[audits.google.audits.proc-macro-error-attr]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +version = "1.0.4" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + [[audits.google.audits.quote]] who = "Lukasz Anforowicz " criteria = "safe-to-deploy" @@ -1092,6 +1143,47 @@ criteria = "safe-to-deploy" version = "0.5.1" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.bitflags]] +who = "Alex Franchuk " +criteria = "safe-to-deploy" +delta = "1.3.2 -> 2.0.2" +notes = "Removal of some unsafe code/methods. No changes to externals, just some refactoring (mostly internal)." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.bitflags]] +who = "Nicolas Silva " +criteria = "safe-to-deploy" +delta = "2.0.2 -> 2.1.0" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.bitflags]] +who = "Teodor Tanasoaia " +criteria = "safe-to-deploy" +delta = "2.2.1 -> 2.3.2" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.bitflags]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "2.3.3 -> 2.4.0" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.bitflags]] +who = "Jan-Erik Rediger " +criteria = "safe-to-deploy" +delta = "2.4.0 -> 2.4.1" +notes = "Only allowing new clippy lints" +aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" + +[[audits.mozilla.audits.bitflags]] +who = [ + "Teodor Tanasoaia ", + "Erich Gubler ", +] +criteria = "safe-to-deploy" +delta = "2.6.0 -> 2.7.0" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.chrono]] who = "Lars Eggert " criteria = "safe-to-deploy" @@ -1170,6 +1262,12 @@ criteria = "safe-to-deploy" delta = "1.8.3 -> 2.5.0" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.hashbrown]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "0.15.2 -> 0.15.5" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.percent-encoding]] who = "Valentin Gosu " criteria = "safe-to-deploy" @@ -1218,22 +1316,6 @@ delta = "1.1.0 -> 2.1.1" notes = "Simple hashing crate, no unsafe code." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" -[[audits.mozilla.audits.rustc_version]] -who = "Nika Layzell " -criteria = "safe-to-deploy" -version = "0.4.0" -notes = """ -Use of powerful capabilities is limited to invoking `rustc -vV` to get version -information for parsing version information. -""" -aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" - -[[audits.mozilla.audits.semver]] -who = "Jan-Erik Rediger " -criteria = "safe-to-deploy" -delta = "1.0.17 -> 1.0.25" -aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" - [[audits.mozilla.audits.sharded-slab]] who = "Mark Hammond " criteria = "safe-to-deploy" @@ -1334,19 +1416,6 @@ was being selected by the target OS instead of the host OS. """ aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" -[[audits.zcash.audits.rustc_version]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "0.4.0 -> 0.4.1" -notes = "Changes to `Command` usage are to add support for `RUSTC_WRAPPER`." -aggregated-from = "https://raw.githubusercontent.com/zcash/zcash/master/qa/supply-chain/audits.toml" - -[[audits.zcash.audits.semver]] -who = "Jack Grigg " -criteria = "safe-to-deploy" -delta = "1.0.25 -> 1.0.26" -aggregated-from = "https://raw.githubusercontent.com/zcash/wallet/main/supply-chain/audits.toml" - [[audits.zcash.audits.thread_local]] who = "Jack Grigg " criteria = "safe-to-deploy"