Skip to content

Conversation

@phip1611
Copy link
Member

The old way is not just very inflexible but also incorrect and error prone.

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 Dec 27, 2025
@phip1611 phip1611 changed the title uefi: serial: fix read() uefi: serial: improve read() Dec 28, 2025
@phip1611
Copy link
Member Author

Please review again, @nicholasbishop! I changed the PR since your last review quite significantly.

PS: I'm working on a small fun project with the serial device in UEFI.

@phip1611 phip1611 force-pushed the serial-read branch 2 times, most recently from bba5e4f to 9218d96 Compare December 28, 2025 11:49
// UEFI was either not able to fill the whole buffer in the specified
// timeout, but nevertheless read some data, or there was too much
// data.
Status::TIMEOUT => Ok(buffer_size),
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure we should swallow the Timeout error like this, end users might have good reason to handle timeout differently.

It's a bit ugly, but what if we instead added an output arg like num_bytes_read: &mut usize, and document that it will be set correctly in both the success and error cases?

Copy link
Member Author

@phip1611 phip1611 Jan 1, 2026

Choose a reason for hiding this comment

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

end users might have good reason to handle timeout differently.

Fair concern, there might be use-cases for that. Let me think about it again.

(I was working on a fun project over the holidays and I adapted it to mainly work for my use-case)

UPDATE: New solution up.

#[cfg(feature = "alloc")]
pub fn read_to_end(&mut self) -> Result<Vec<u8>> {
let mut vec = Vec::new();
let mut buf = [0; 2048];
Copy link
Member

Choose a reason for hiding this comment

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

Rather than reading into this temporary buffer and copying it to the vec, could we write directly to the vec?

Copy link
Member Author

Choose a reason for hiding this comment

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

good question! no we could not unfortunately, because then we would lose the ability to grow the vector dynamically.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not clear why. Couldn't we write the vec, resize if needed, and then write at an offset for the next iteration, and so on?

pub fn write(&mut self, data: &[u8]) -> Result<(), usize> {
let mut buffer_size = data.len();
unsafe { (self.0.write)(&mut self.0, &mut buffer_size, data.as_ptr()) }.to_result_with(
|| debug_assert_eq!(buffer_size, data.len()),
Copy link
Member

Choose a reason for hiding this comment

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

Is removing this debug_assert intentional? I don't see anything wrong with it.

Copy link
Member Author

@phip1611 phip1611 Jan 1, 2026

Choose a reason for hiding this comment

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

I decided to remove that intentionally as the implementation may decide to only write some bytes and one must call the function again afterwards. https://uefi.org/specs/UEFI/2.10/12_Protocols_Console_Support.html#efi-serial-io-protocol-write

Copy link
Member

Choose a reason for hiding this comment

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

That would seem to conflict with this function's docstring: "This operation will block until the data has been fully written or an error occurs." If the docstring is correct, than the assert can stay, otherwise we should fix the docstring as well. I find the UEFI spec a little ambiguous in its language here. "EFI_SUCCESS -- The data was written" could be taken to mean all the data was written, but I'm not sure. Did you observe that calling write multiple times is needed in some real-world test?

@phip1611 phip1611 force-pushed the serial-read branch 3 times, most recently from 10c84c2 to b54e7fe Compare January 1, 2026 22:19
We didn't handle partial reads correctly. The serial protocol will
return Status::TIMEOUT in case it could not read as many bytes as
the caller asked for. This is however still the good case and just
a normal partial read. Therefore, I made the abstraction more
convenient and natural to use by handling this properly.
This helps to read everything that is currently available on the
device.
@phip1611
Copy link
Member Author

phip1611 commented Jan 1, 2026

I refactored the PR and hope that I now improve the situation in a more flexible way.

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.

3 participants