-
-
Notifications
You must be signed in to change notification settings - Fork 186
uefi: serial: various improvements to doc, read(), and write() #1852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
19e0b16 to
b70296c
Compare
|
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. |
bba5e4f to
9218d96
Compare
uefi/src/proto/console/serial.rs
Outdated
| #[cfg(feature = "alloc")] | ||
| pub fn read_to_end(&mut self) -> Result<Vec<u8>> { | ||
| let mut vec = Vec::new(); | ||
| let mut buf = [0; 2048]; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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()), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
10c84c2 to
b54e7fe
Compare
|
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.
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.
This is the more expected and natural behavior.
nicholasbishop
left a comment
There was a problem hiding this 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong docstring here
| REVISION_0 = 0x00010000, | ||
| /// Version 1.1. | ||
| REVISION_1P1 = 0x00010001, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| //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, |
There was a problem hiding this comment.
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.
Yes, I thought so as well. This PR grew larger than anticipated. |
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