Skip to content

uefi: serial: improve read() and write(), add read_exact() and write_exact(), add read_to_end()#1875

Open
phip1611 wants to merge 3 commits intomainfrom
uefi-serial-improve-read-write
Open

uefi: serial: improve read() and write(), add read_exact() and write_exact(), add read_to_end()#1875
phip1611 wants to merge 3 commits intomainfrom
uefi-serial-improve-read-write

Conversation

@phip1611
Copy link
Member

This improves read/write convenience for higher levels significantly while retaining all flexibility of the underlying protocol semantics.

Split out from #1852. I can split this into further PRs if desired.

Checklist

  • Sensible git history (for example, squash "typo" or "fix" commits). See the Rewriting History guide for help.
  • Update the changelog (if necessary)

@phip1611 phip1611 self-assigned this Jan 25, 2026
@phip1611 phip1611 force-pushed the uefi-serial-improve-read-write branch from fae721f to da6a8ed Compare January 25, 2026 09:35
@phip1611 phip1611 force-pushed the uefi-serial-improve-read-write branch from da6a8ed to 84787aa Compare January 26, 2026 12:32
@phip1611 phip1611 force-pushed the uefi-serial-improve-read-write branch from 84787aa to cc9c13e Compare February 1, 2026 11:50
Next to documentation improvements, I replaced the debug assertions with
runtime assertions. If the underlying protocol implementation doesn't
behave as expected, we should at least fail hard.

I tested this with OVMF and also on real hardware.
@phip1611 phip1611 force-pushed the uefi-serial-improve-read-write branch from cc9c13e to 465e4d3 Compare February 1, 2026 12:07
This implementation is more correct and closer to what users
typcially correct. Timeout errors can occur quite often on real
hardware, for example when sending data over a UART 16550. However,
in this case, it is more natural to retry the transmission as it
will succeed eventually.

For errors that are not Status::TIMEOUT errors, we will nevertheless
propagate an error.

This is the more expected and natural behavior.
@phip1611 phip1611 force-pushed the uefi-serial-improve-read-write branch from 465e4d3 to df9a189 Compare February 1, 2026 12:09
Copy link
Member

@nicholasbishop nicholasbishop left a comment

Choose a reason for hiding this comment

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

Initial comments in. For ease of review, I'd like to move the read/write changes to a separate PR, those seem independent of the new methods being added.


// Send a screenshot request to the host.
serial.write(request.as_bytes()).discard_errdata()?;
//serial.write(request.as_bytes()).discard_errdata()?;
Copy link
Member

Choose a reason for hiding this comment

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

remove commented-out line

/// requested bytes were read.
///
/// This loops over potential [`Status::TIMEOUT`]s and just tries again. It
/// is unlikely that this causes and endless loop, as in normal operation we
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// is unlikely that this causes and endless loop, as in normal operation we
/// is unlikely that this causes an endless loop, as in normal operation we

pub fn read_exact(&mut self, buffer: &mut [u8]) -> Result<()> {
let mut remaining_buffer = buffer;
// We retry on timeout and only return other errors
while !remaining_buffer.is_empty() {
Copy link
Member

Choose a reason for hiding this comment

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

Potentially-infinite loops always make me a little nervous :) I wonder if it would make sense to add some kind of limit here. E.g. perhaps this could have an "overall timeout" (separate from the underlying timeout configured in the protocol).

For ease of use this wouldn't have to be a configurable parameter; just something like:

let end_time = current_time + 10seconds; // Or whatever you think a reasonable max timeout is
while !remaining_buffer.is_empty() {
    if current_time > end_time {
        break;
    }
}

unsafe { (self.0.write)(&mut self.0, &mut buffer_size, data.as_ptr()) }.to_result_with(
|| debug_assert_eq!(buffer_size, data.len()),
|| {
// By spec: Either reads all requested bytes (and blocks) or
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// By spec: Either reads all requested bytes (and blocks) or
// By spec: Either writes all requested bytes (and blocks) or

pub fn write_exact(&mut self, data: &[u8]) -> Result<()> {
let mut remaining_bytes = data;
// We retry on timeout and only return other errors
while !remaining_bytes.is_empty() {
Copy link
Member

Choose a reason for hiding this comment

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

Same suggestion here as read_exact, I'd like some kind of additional exit condition to ensure that an infinite loop isn't possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants