Skip to content

hid-service: add timeout for I2C device operations#697

Merged
jerrysxie merged 6 commits intoOpenDevicePartnership:mainfrom
jerrysxie:timeout-i2c-operations
Feb 26, 2026
Merged

hid-service: add timeout for I2C device operations#697
jerrysxie merged 6 commits intoOpenDevicePartnership:mainfrom
jerrysxie:timeout-i2c-operations

Conversation

@jerrysxie
Copy link
Contributor

@jerrysxie jerrysxie commented Jan 28, 2026

This pull request introduces configurable timeout handling for I2C HID device and host operations, replacing hardcoded timeouts with new Config structs. This change allows users to customize operation timeouts and improves error handling by reporting specific timeout errors. The update also modifies all usages of the device and host constructors to accept these new configuration parameters, ensuring consistent timeout management throughout the codebase.

Timeout configuration and usage improvements:

  • Added Config structs for both Device and Host in hid-service/src/i2c/device.rs and hid-service/src/i2c/host.rs, allowing customization of timeouts for device response and data reads. Default values are provided. [1] [2]
  • Updated Device and Host constructors to accept a timeout configuration parameter, and refactored all internal timeout usages to reference these configuration values instead of hardcoded constants. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15]

Error handling enhancements:

  • All I2C operations in Device now use the with_timeout wrapper, returning a specific timeout error if the operation exceeds the configured duration and logging the error for better traceability. [1] [2] [3] [4]

API and macro updates:

  • Updated public exports in hid-service/src/i2c/mod.rs to include the new Config types as DeviceConfig and HostConfig.
  • Modified macros and static initializations in hid-service/src/i2c/passthrough/mod.rs and keyboard-service/src/task.rs to use the new constructors and pass default timeout configurations. [1] [2] [3]

Wrap all I2C master bus operations with timeouts to
prevent indefinite blocking when communicating with HID devices.

- Add DEVICE_RESPONSE_TIMEOUT_MS (200ms) for descriptor reads and commands
- Add DATA_READ_TIMEOUT_MS (50ms) for input reports and feature data
- Return Error::Hid(hid::Error::Timeout) on timeout expiry
@jerrysxie jerrysxie self-assigned this Jan 28, 2026
Copilot AI review requested due to automatic review settings January 28, 2026 23:24
@jerrysxie jerrysxie added the enhancement New feature or request label Jan 28, 2026
@jerrysxie jerrysxie requested a review from RobertZ2011 January 28, 2026 23:26
@jerrysxie jerrysxie linked an issue Jan 28, 2026 that may be closed by this pull request
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds bounded timeouts around all I2C master operations performed by the HID I2C device side, preventing indefinite blocking when devices misbehave or stop responding.

Changes:

  • Introduced DEVICE_RESPONSE_TIMEOUT_MS (200 ms) and DATA_READ_TIMEOUT_MS (50 ms) in the I2C device implementation.
  • Wrapped all bus.write_read, bus.read, and bus.write calls in hid-service/src/i2c/device.rs with embassy_time::with_timeout, mapping timeout expiries to Error::Hid(hid::Error::Timeout).
  • Added per-call logging for timeout vs. underlying bus errors for descriptor reads, report descriptor reads, input reports, and command/host-data transfers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Replace hardcoded timeout constants in both device.rs and host.rs
with configurable structs (DeviceTimeoutConfig, HostTimeoutConfig)
that are passed in at construction time. Both provide Default
impls preserving the original values (200ms for device response,
50ms for data reads).

Update call sites in passthrough macros and keyboard-service to
pass the default configs.
@jerrysxie jerrysxie marked this pull request as ready for review February 22, 2026 21:10
@jerrysxie jerrysxie requested review from a team as code owners February 22, 2026 21:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- rename host/device timeout configs to Config and re-export as
  HostConfig/DeviceConfig
- update passthrough macros and keyboard host task to use new aliases
kurtjd
kurtjd previously approved these changes Feb 24, 2026
tullom
tullom previously approved these changes Feb 24, 2026
Copy link
Contributor

@tullom tullom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two nb comments, otherwise lgtm!

Change Config struct fields in both host and device modules from
raw u64 millisecond values to embassy_time::Duration. This removes
the need for Duration::from_millis() conversions at each call site
and makes the timeout units explicit in the type system.
Copilot AI review requested due to automatic review settings February 24, 2026 18:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

hid-service/src/i2c/host.rs:83

  • Both read_bus and write_bus log the same "Response timeout" message on timeout, which makes it hard to tell whether the host timed out on a write vs read. Consider using distinct messages (or include operation context) so logs are actionable.
    async fn read_bus(&self, timeout: Duration, buffer: &mut [u8]) -> Result<(), Error<B::Error>> {
        let mut bus = self.bus.lock().await;
        with_timeout(timeout, bus.respond_to_write(buffer))
            .await
            .map_err(|_| {
                error!("Response timeout");
                Error::Hid(hid::Error::Timeout)
            })?
            .map_err(|e| {
                error!("Failed to read from bus");
                Error::Bus(e)
            })
    }

    async fn write_bus(&self, timeout: Duration, buffer: &[u8]) -> Result<(), Error<B::Error>> {
        let mut bus = self.bus.lock().await;
        // Send response, timeout if the host doesn't read so we don't get stuck here
        trace!("Sending {} bytes", buffer.len());
        with_timeout(timeout, bus.respond_to_read(buffer))
            .await
            .map_err(|_| {
                error!("Response timeout");
                Error::Hid(hid::Error::Timeout)
            })?

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@williampMSFT williampMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@jerrysxie
Copy link
Contributor Author

Announced API changes on Zulip: #embedded-controller > hid-service API breaking changes @ 💬

@jerrysxie jerrysxie merged commit fb12cc3 into OpenDevicePartnership:main Feb 26, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BREAKING CHANGE Marks breaking changes enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Add timeouts around I2C operations in hid-service

6 participants