uefi: serial: improve read() and write(), add read_exact() and write_exact(), add read_to_end()#1875
uefi: serial: improve read() and write(), add read_exact() and write_exact(), add read_to_end()#1875
Conversation
fae721f to
da6a8ed
Compare
da6a8ed to
84787aa
Compare
84787aa to
cc9c13e
Compare
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.
cc9c13e to
465e4d3
Compare
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.
465e4d3 to
df9a189
Compare
nicholasbishop
left a comment
There was a problem hiding this comment.
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()?; |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
| /// 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() { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
| // 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() { |
There was a problem hiding this comment.
Same suggestion here as read_exact, I'd like some kind of additional exit condition to ensure that an infinite loop isn't possible.
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