Implement HID-I2C target service - #926
Conversation
A new service that implements the HID-I2C spec, written against the I2C HAL trait and a new HidDevice trait that is transport-agnostic. This obsoletes the current hid-service, which is really a HID-I2C service that leverages the comms system / message passing and doesn't handle interrupt line management. A future change will introduce an analogue to the impl_odp_mctp_relay_handler macro to aggregate multiple logical HID devices into a single HID device. This should allow clients to write transport-agnostic HID device code that can be reused without modification on other, future physical transports (e.g. I3C), and removes our dependance on the comms system, which imposes lifetime requirements that make testing difficult.
Cargo Vet Audit Passed
|
There was a problem hiding this comment.
Pull request overview
This PR introduces a new hidi2c-target-service crate that implements the HID-over-I2C target/slave side against an async I2C target HAL and a transport-agnostic HidDevice trait. In support of that goal, it refactors embedded-service/src/relay by splitting MCTP relay helpers into relay::mctp and adding a new relay::hid module for shared HID abstractions. It also adds RT685s-EVK mock keyboard/mouse examples and updates supply-chain metadata and workspace dependencies to include generic-array/typenum (used for compile-time report sizing).
Changes:
- Add a new HID-I2C target service crate (runner + control handle, descriptor generation, timeouts, interrupt pin handling).
- Refactor relay helpers into
embedded_services::relay::mctpand introduceembedded_services::relay::hidfor transport-agnostic HID types. - Add RT685s-EVK mock HID keyboard/mouse examples and update workspace + supply-chain metadata for new dependencies.
Step-by-step review guide (for human reviewers)
-
Relay module refactor (MCTP vs HID)
embedded-service/src/relay/mod.rsnow just re-exportshidandmctp; existing MCTP serialization traits moved torelay::mctp.- Multiple relay/message crates update imports accordingly (e.g.,
battery-service-relay,thermal-service-relay,time-alarm-service-relay,debug-service-messages).
-
New HID relay primitives
embedded-service/src/relay/hid.rsaddsHidDevice, report/descriptor helpers, and descriptor scanning (implicit vs explicit report IDs).- This module is intended to be transport-agnostic so both HID-I2C and future transports can share device logic.
-
HID-I2C target service implementation
hidi2c-target-serviceimplements register handling, command processing, device reset handling, and interrupt (“ATTN”) pin management, with per-operation timeouts and bus recovery logic.- Uses typenum-based sizing via
ConstrainedHidDeviceto allocate fixed buffers sized to max report lengths.
-
Examples / mocks
- Adds RT685s-EVK mock mouse/keyboard devices and example binaries wiring up
I2cSlave+ GPIO ATTN pin +hidi2c-target-service.
- Adds RT685s-EVK mock mouse/keyboard devices and example binaries wiring up
Potential issues
| # | Severity | File | Description | Code |
|---|---|---|---|---|
| 1 | High | hidi2c-target-service/src/constrained_hid_device.rs:24-43 |
ConstrainedHidDevice sealing is circular (ConstrainedHidDevice: Sealed but Sealed is only implemented for T: ConstrainedHidDevice), preventing the blanket impl from applying. |
pub trait ConstrainedHidDevice: ... + sealed::Sealed |
| 2 | High | embedded-service/src/relay/hid.rs:221-265 |
item_size() can panic on long items and the descriptor scan doesn’t skip long items, making descriptor parsing unsafe and potentially incorrect. |
panic!("Long items are not yet supported"); |
| 3 | Medium | hidi2c-target-service/src/attn_pin_handler.rs:24-39 |
asserted state is updated before driving the GPIO; on pin I/O error, internal state can diverge from physical state and break the “don’t wait while asserted” logic. |
self.asserted = true; self.attn_pin.set_low()... |
| 4 | Medium | hidi2c-target-service/src/service.rs:341-344 |
Several I2C reads ignore how many bytes were actually received; early-aborted writes can be misinterpreted as valid register/command data. | self.bus.read(&mut reg).await?; |
| 5 | Medium | hidi2c-target-service/src/service.rs:479-483, 562-567 |
Length fields are used with unchecked subtraction, risking underflow/wrap and incorrect buffer slicing/reads for malformed host packets. | ... as usize - header_len |
| 6 | Medium | hidi2c-target-service/src/service.rs:451-452, 550-552 |
write_unterminated()’s “NeedMore” signal is ignored; if the host terminates after the header, the follow-on payload write can time out and trigger recover unnecessarily. |
self.bus.write_unterminated(...); self.bus.write(...); |
| 7 | Low | supply-chain/imports.lock:199-202 |
Duplicate aggregated-from entries add lockfile noise/churn; should be de-duplicated. |
aggregated-from = [ "...", "..." ] |
Reviewed changes
Copilot reviewed 24 out of 29 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| time-alarm-service-relay/src/serialization.rs | Update relay serialization imports to embedded_services::relay::mctp. |
| thermal-service-relay/src/serialization.rs | Update relay serialization imports to embedded_services::relay::mctp. |
| battery-service-relay/src/serialization.rs | Update relay serialization imports to embedded_services::relay::mctp. |
| debug-service-messages/src/lib.rs | Update relay serialization imports to embedded_services::relay::mctp. |
| embedded-service/src/relay/mod.rs | Split relay surface into hid and mctp modules. |
| embedded-service/src/relay/mctp.rs | New home for MCTP relay traits/macros previously in relay/mod.rs. |
| embedded-service/src/relay/hid.rs | Add transport-agnostic HID types + descriptor helper/parser. |
| embedded-service/Cargo.toml | Add deps needed by new relay HID module (generic-array, num_enum). |
| hidi2c-target-service/Cargo.toml | New crate definition and dependencies/features for HID-I2C service. |
| hidi2c-target-service/src/lib.rs | Crate root exports + HID-I2C register enum. |
| hidi2c-target-service/src/service.rs | Core HID-I2C target service runner/control handle implementation. |
| hidi2c-target-service/src/error.rs | New protocol/bus/device error types and conversions. |
| hidi2c-target-service/src/device_descriptor.rs | Construct HID-I2C device descriptor based on HidDevice + HW IDs. |
| hidi2c-target-service/src/constrained_hid_device.rs | Typenum-based sizing helper trait for max report buffer sizes. |
| hidi2c-target-service/src/attn_pin_handler.rs | ATTN GPIO wrapper tracking asserted state. |
| Cargo.toml | Add new crate to workspace; add workspace deps generic-array and typenum. |
| Cargo.lock | Update lockfile for new crate and dependency graph changes. |
| supply-chain/audits.toml | Add audit entry for generic-array. |
| supply-chain/imports.lock | Update imports lock; currently includes duplicated aggregated-from. |
| examples/std/Cargo.lock | Lockfile update for examples std workspace due to new deps. |
| examples/pico-de-gallo/Cargo.lock | Lockfile update due to new deps. |
| examples/rt685s-evk/Cargo.toml | Add hidi2c-target-service and related deps for mock HID examples. |
| examples/rt685s-evk/Cargo.lock | Lockfile update for RT685s-EVK example workspace. |
| examples/rt685s-evk/src/lib.rs | Re-export mocks module for example binaries. |
| examples/rt685s-evk/src/mocks/mod.rs | New module for mock HID sub-devices. |
| examples/rt685s-evk/src/mocks/mouse.rs | Mock HID mouse device + relay adapter. |
| examples/rt685s-evk/src/mocks/keyboard.rs | Mock HID keyboard device + relay adapter. |
| examples/rt685s-evk/src/bin/mock_i2c_mouse.rs | Example wiring I2C slave + ATTN pin + mock mouse HID device. |
| examples/rt685s-evk/src/bin/mock_i2c_keyboard.rs | Example wiring I2C slave + ATTN pin + mock keyboard HID device. |
| } | ||
| } | ||
|
|
||
| impl MockKeyboardService { |
There was a problem hiding this comment.
nit: This should be named something like MockKeyboardDevice or even just MockKeyboard.
There was a problem hiding this comment.
I can do that if you feel strongly about it, but it seems like a departure from the naming convention we have for other things of the same class so I'm not sure I understand why - can you expand on that? Is it because the old examples didn't fit your mental model for what constitutes a service, and if yes, do the new examples? If not, what am I missing?
jerrysxie
left a comment
There was a problem hiding this comment.
I have not reviewed the examples yet. These are my initial thoughts.
A new service that implements the HID-I2C spec, written against the I2C HAL trait and a new HidDevice trait that is transport-agnostic.
This obsoletes the current hid-service, which is really a HID-I2C service that leverages the comms system / message passing and doesn't handle interrupt line management.
A future change will introduce an analogue to the impl_odp_mctp_relay_handler macro to aggregate multiple logical HID devices into a single HID device.
This should allow clients to write transport-agnostic HID device code that can be reused without modification on other, future physical transports (e.g. I3C), and removes our dependance on the comms system, which imposes lifetime requirements that make testing difficult.
Resolves #839