Skip to content

Conversation

@phip1611
Copy link
Member

@phip1611 phip1611 commented Dec 27, 2025

This improves multiple things for the serial IO protocol. Please review commit by commit.

I tested this in a VM as well as on real hardware. The changes make the Serial IO protocol more usable and also help to better cope with non-spec compliant implementations.

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
#[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?

Copy link
Member Author

Choose a reason for hiding this comment

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

I refactored that part - I just learned about https://doc.rust-lang.org/std/vec/struct.Vec.html#method.spare_capacity_mut which did the trick

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?

Copy link
Member Author

@phip1611 phip1611 Jan 24, 2026

Choose a reason for hiding this comment

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

I decided to keep the assert. I actually upgraded the debug asserts to runtime asserts. If the protocol impl behaves unexpectedly, the program should at least notice this

@phip1611 phip1611 force-pushed the serial-read branch 3 times, most recently from 10c84c2 to b54e7fe Compare January 1, 2026 22:19
@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.

We are logically modifying state, so this should be mut.
@phip1611 phip1611 marked this pull request as draft January 24, 2026 11:41
@phip1611 phip1611 changed the title uefi: serial: improve read() uefi: serial: various improvements to doc, read(), and write() Jan 24, 2026
@phip1611 phip1611 marked this pull request as ready for review January 24, 2026 17:04
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.
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.

Adding a few more comments. I haven't fully checked the rest though; I think it would be good to try to split this PR up since there are a lot of changes to consider here. Perhaps one PR for the uefi-raw changes, one for the docstring improvements to existing functions, one each for the changes to read and write, and a couple more for read_to_vec/write_exact.

}

newtype_enum! {
/// Type of queues an NVMe command can be placed into
Copy link
Member

Choose a reason for hiding this comment

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

Wrong docstring here

Comment on lines +86 to +88
REVISION_0 = 0x00010000,
/// Version 1.1.
REVISION_1P1 = 0x00010001,
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
REVISION_0 = 0x00010000,
/// Version 1.1.
REVISION_1P1 = 0x00010001,
REVISION_1_0 = 0x00010000,
/// Version 1.1.
REVISION_1_1 = 0x00010001,

I recognize this drifts a little from the spec names, but it matches how we've defined such constants elsewhere. example


// 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.

Suggested change
//serial.write(request.as_bytes()).discard_errdata()?;

pub read: unsafe extern "efiapi" fn(*mut Self, *mut usize, *mut u8) -> Status,
pub mode: *const SerialIoMode,
// Revision 1.1
pub device_type_guid: *const Guid,
Copy link
Member

Choose a reason for hiding this comment

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

Something to consider here is potential UB from the way we use this type in the uefi crate. We wrap SerialIoProtocol into the Serial type. Via ScopedProtocol we allow creating a &Serial. If the underlying C protocol revision is 1.0, this field won't exist, so we'll have a reference to a type that is larger than the underlying C allocation. Even if we never read from the device_type_guid field (i.e. by first checking the revision), I'm pretty sure that's still UB.

@phip1611
Copy link
Member Author

phip1611 commented Jan 25, 2026

; I think it would be good to try to split this PR up since there are a lot of changes to consider here.

Yes, I thought so as well. This PR grew larger than anticipated.
Closing this in favor of multiple smaller ones.

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