|
2 | 2 |
|
3 | 3 | //! Abstraction over byte stream devices, also known as serial I/O devices. |
4 | 4 |
|
| 5 | +pub use uefi_raw::protocol::console::serial::{ |
| 6 | + ControlBits, Parity, SerialIoMode as IoMode, StopBits, |
| 7 | +}; |
| 8 | + |
5 | 9 | use crate::proto::unsafe_protocol; |
6 | | -use crate::{Error, Result, Status, StatusExt}; |
7 | | -use core::fmt; |
| 10 | +use crate::{Error, Result, ResultExt, Status, StatusExt, boot}; |
| 11 | +use core::time::Duration; |
| 12 | +use core::{cmp, fmt}; |
| 13 | +use log::error; |
8 | 14 | use uefi_raw::protocol::console::serial::{ |
9 | 15 | SerialIoProtocol, SerialIoProtocol_1_1, SerialIoProtocolRevision, |
10 | 16 | }; |
11 | 17 | use uguid::Guid; |
12 | 18 |
|
13 | | -pub use uefi_raw::protocol::console::serial::{ |
14 | | - ControlBits, Parity, SerialIoMode as IoMode, StopBits, |
15 | | -}; |
| 19 | +/// Returns the estimated time it takes to write a single byte. |
| 20 | +/// |
| 21 | +/// This is conservative: it accounts for the actual serial mode settings |
| 22 | +/// (data bits, parity, stop bits) and rounds up to avoid underestimating. |
| 23 | +fn duration_per_byte_estimate(mode: &IoMode) -> Duration { |
| 24 | + if mode.baud_rate == 0 { |
| 25 | + // Baud rate unknown; assume a very slow link to be safe. |
| 26 | + return Duration::from_millis(100); |
| 27 | + } |
| 28 | + |
| 29 | + // Count the number of bits per character conservatively: |
| 30 | + // - 1 start bit (always) |
| 31 | + // - data bits (use actual setting, fall back to maximum of 8) |
| 32 | + // - parity bit, if any |
| 33 | + // - stop bits (round up: ONE_FIVE and TWO both become 2) |
| 34 | + let data_bits = if mode.data_bits == 0 { |
| 35 | + 8 |
| 36 | + } else { |
| 37 | + mode.data_bits |
| 38 | + }; |
| 39 | + |
| 40 | + let parity_bits: u32 = if mode.parity == Parity::NONE || mode.parity == Parity::DEFAULT { |
| 41 | + 0 |
| 42 | + } else { |
| 43 | + 1 |
| 44 | + }; |
| 45 | + |
| 46 | + // Be conservative with stop bits: treat ONE_FIVE as 2. |
| 47 | + let stop_bits: u32 = match mode.stop_bits { |
| 48 | + StopBits::ONE => 1, |
| 49 | + StopBits::DEFAULT | StopBits::ONE_FIVE | StopBits::TWO => 2, |
| 50 | + // Unknown future variant: assume worst case. |
| 51 | + _ => 2, |
| 52 | + }; |
| 53 | + |
| 54 | + let bits_per_char = 1 + data_bits + parity_bits + stop_bits; |
| 55 | + |
| 56 | + // Compute microseconds per bit, rounding up to avoid underestimating. |
| 57 | + let us_per_bit = 1_000_000_u64.div_ceil(mode.baud_rate); |
| 58 | + |
| 59 | + // Total microseconds per byte/character. |
| 60 | + let us_per_byte = us_per_bit * (bits_per_char as u64); |
| 61 | + |
| 62 | + Duration::from_micros(us_per_byte) |
| 63 | +} |
| 64 | + |
| 65 | +/// Returns the estimated duration it takes to clear/transmit the UARTs internal |
| 66 | +/// FIFO. |
| 67 | +/// |
| 68 | +/// This assumes the UART has each a transmit and a receive FIFO with the same |
| 69 | +/// size, which is the case for UART 16550 devices - the de-facto standard |
| 70 | +/// serial device. |
| 71 | +fn duration_fifo_estimate(mode: &IoMode, remaining: usize) -> Duration { |
| 72 | + let remaining = u32::try_from(remaining).unwrap_or(u32::MAX); |
| 73 | + |
| 74 | + // default: depth = 1. |
| 75 | + let depth = mode.receive_fifo_depth.max(1); |
| 76 | + let remaining = cmp::min(depth, remaining); |
| 77 | + duration_per_byte_estimate(mode) * remaining |
| 78 | +} |
16 | 79 |
|
17 | 80 | /// Serial IO [`Protocol`]. Provides access to a serial I/O device. |
18 | 81 | /// |
@@ -163,6 +226,67 @@ impl Serial { |
163 | 226 | ) |
164 | 227 | } |
165 | 228 |
|
| 229 | + /// Reads bytes into the provided buffer, blocking until it is full. |
| 230 | + /// |
| 231 | + /// Retries automatically on [`Status::TIMEOUT`], stalling briefly between |
| 232 | + /// attempts based on the current baud rate. A maximum retry limit prevents |
| 233 | + /// spinning forever in the unlikely event of a hardware fault. |
| 234 | + /// |
| 235 | + /// # Arguments |
| 236 | + /// |
| 237 | + /// - `buffer`: buffer to fill |
| 238 | + /// |
| 239 | + /// # Tips |
| 240 | + /// |
| 241 | + /// Consider setting non-default properties via [`Self::set_attributes`] |
| 242 | + /// and [`Self::set_control_bits`] matching your use-case. For more info, |
| 243 | + /// please read the general [documentation](Self) of the protocol. |
| 244 | + /// |
| 245 | + /// # Errors |
| 246 | + /// |
| 247 | + /// - [`Status::DEVICE_ERROR`]: serial device reported an error |
| 248 | + /// - [`Status::TIMEOUT`]: This timeout happens if the underlying device |
| 249 | + /// seem to stopped its normal operation and is only reported to prevent |
| 250 | + /// an endless loop. |
| 251 | + pub fn read_exact(&mut self, buffer: &mut [u8]) -> Result<()> { |
| 252 | + // Chosen at will, tested on real hardware. |
| 253 | + const MAX_ZERO_PROGRESS: usize = 16; |
| 254 | + |
| 255 | + let mut remaining_buffer = buffer; |
| 256 | + let mut zero_progress_count = 0; |
| 257 | + |
| 258 | + // Retry until all bytes were written with endless loop protection. |
| 259 | + while !remaining_buffer.is_empty() { |
| 260 | + match self.read(remaining_buffer) { |
| 261 | + // All data read, buffer is full. |
| 262 | + Ok(_) => return Ok(()), |
| 263 | + Err(err) if err.status() == Status::TIMEOUT => { |
| 264 | + let n = *err.data(); |
| 265 | + if n == 0 { |
| 266 | + zero_progress_count += 1; |
| 267 | + if zero_progress_count >= MAX_ZERO_PROGRESS { |
| 268 | + return Err(Error::from(Status::TIMEOUT)); |
| 269 | + } |
| 270 | + } else { |
| 271 | + zero_progress_count = 0; |
| 272 | + } |
| 273 | + |
| 274 | + remaining_buffer = &mut remaining_buffer[n..]; |
| 275 | + |
| 276 | + // Give FIFO time to fill up. Without that protection, we |
| 277 | + // might get TIMEOUT too often and return too early. |
| 278 | + let fifo_stall_duration = |
| 279 | + duration_fifo_estimate(self.io_mode(), remaining_buffer.len()); |
| 280 | + boot::stall(fifo_stall_duration); |
| 281 | + } |
| 282 | + err => { |
| 283 | + return Err(Error::from(err.status())); |
| 284 | + } |
| 285 | + } |
| 286 | + } |
| 287 | + Ok(()) |
| 288 | + } |
| 289 | + |
166 | 290 | /// Writes data to this device. This function has the raw semantics of the |
167 | 291 | /// underlying UEFI protocol. |
168 | 292 | /// |
@@ -195,6 +319,65 @@ impl Serial { |
195 | 319 | ) |
196 | 320 | } |
197 | 321 |
|
| 322 | + /// Writes all provided bytes, blocking until every byte has been sent. |
| 323 | + /// |
| 324 | + /// Retries automatically on [`Status::TIMEOUT`], stalling briefly between |
| 325 | + /// attempts based on the current baud rate. A maximum retry limit prevents |
| 326 | + /// spinning forever in the unlikely event of a hardware fault. |
| 327 | + /// |
| 328 | + /// # Arguments |
| 329 | + /// |
| 330 | + /// - `data`: bytes to write |
| 331 | + /// |
| 332 | + /// # Tips |
| 333 | + /// |
| 334 | + /// Consider setting non-default properties via [`Self::set_attributes`] |
| 335 | + /// and [`Self::set_control_bits`] matching your use-case. For more info, |
| 336 | + /// please read the general [documentation](Self) of the protocol. |
| 337 | + /// |
| 338 | + /// # Errors |
| 339 | + /// |
| 340 | + /// - [`Status::DEVICE_ERROR`]: serial device reported an error |
| 341 | + /// - [`Status::TIMEOUT`]: This timeout happens if the underlying device |
| 342 | + /// seem to stopped its normal operation and is only reported to prevent |
| 343 | + /// an endless loop. |
| 344 | + pub fn write_exact(&mut self, data: &[u8]) -> Result<()> { |
| 345 | + // Chosen at will, tested on real hardware. |
| 346 | + const MAX_ZERO_PROGRESS: usize = 16; |
| 347 | + |
| 348 | + let mut remaining_bytes = data; |
| 349 | + let mut zero_progress_count = 0; |
| 350 | + |
| 351 | + // Retry until all bytes were written with endless loop protection. |
| 352 | + while !remaining_bytes.is_empty() { |
| 353 | + match self.write(remaining_bytes) { |
| 354 | + // All data written, no data left to send. |
| 355 | + Ok(_) => return Ok(()), |
| 356 | + Err(err) if err.status() == Status::TIMEOUT => { |
| 357 | + let n = *err.data(); |
| 358 | + if n == 0 { |
| 359 | + zero_progress_count += 1; |
| 360 | + if zero_progress_count >= MAX_ZERO_PROGRESS { |
| 361 | + return Err(Error::from(Status::TIMEOUT)); |
| 362 | + } |
| 363 | + } else { |
| 364 | + zero_progress_count = 0; |
| 365 | + } |
| 366 | + |
| 367 | + remaining_bytes = &remaining_bytes[n..]; |
| 368 | + |
| 369 | + // Give FIFO time to drain. Without that protection, we |
| 370 | + // might get TIMEOUT too often and return too early. |
| 371 | + let fifo_stall_duration = |
| 372 | + duration_fifo_estimate(self.io_mode(), remaining_bytes.len()); |
| 373 | + boot::stall(fifo_stall_duration); |
| 374 | + } |
| 375 | + Err(err) => return Err(Error::from(err.status())), |
| 376 | + } |
| 377 | + } |
| 378 | + Ok(()) |
| 379 | + } |
| 380 | + |
198 | 381 | /// Pointer to a GUID identifying the device connected to the serial port. |
199 | 382 | /// |
200 | 383 | /// This is either `Ok` if [`Self::revision`] is at least |
@@ -238,6 +421,116 @@ impl Serial { |
238 | 421 |
|
239 | 422 | impl fmt::Write for Serial { |
240 | 423 | fn write_str(&mut self, s: &str) -> fmt::Result { |
241 | | - self.write(s.as_bytes()).map(|_| ()).map_err(|_| fmt::Error) |
| 424 | + // We retry on Status::TIMEOUT but propagate other errors |
| 425 | + self.write_exact(s.as_bytes()).map_err(|e| { |
| 426 | + let msg = "failed to write to serial device"; |
| 427 | + // Simple check to prevent endless recursion if a logger |
| 428 | + // implementation uses the serial protocol |
| 429 | + if !s.contains(msg) { |
| 430 | + error!("{msg}: {e}"); |
| 431 | + } |
| 432 | + fmt::Error |
| 433 | + }) |
| 434 | + } |
| 435 | +} |
| 436 | + |
| 437 | +#[cfg(test)] |
| 438 | +mod tests { |
| 439 | + use super::*; |
| 440 | + |
| 441 | + fn make_mode(baud_rate: u64, data_bits: u32, parity: Parity, stop_bits: StopBits) -> IoMode { |
| 442 | + IoMode { |
| 443 | + control_mask: ControlBits::empty(), |
| 444 | + timeout: 0, |
| 445 | + baud_rate, |
| 446 | + receive_fifo_depth: 0, |
| 447 | + data_bits, |
| 448 | + parity, |
| 449 | + stop_bits, |
| 450 | + } |
| 451 | + } |
| 452 | + |
| 453 | + #[test] |
| 454 | + fn unknown_baud_rate_returns_large_fallback() { |
| 455 | + let mode = make_mode(0, 8, Parity::NONE, StopBits::ONE); |
| 456 | + let duration = duration_per_byte_estimate(&mode); |
| 457 | + assert!( |
| 458 | + duration >= Duration::from_millis(50), |
| 459 | + "fallback should be at least 100ms, got {duration:?}" |
| 460 | + ); |
| 461 | + } |
| 462 | + |
| 463 | + #[test] |
| 464 | + fn higher_baud_rate_gives_shorter_duration() { |
| 465 | + let slow = make_mode(9_600, 8, Parity::NONE, StopBits::ONE); |
| 466 | + let fast = make_mode(115_200, 8, Parity::NONE, StopBits::ONE); |
| 467 | + assert!( |
| 468 | + duration_per_byte_estimate(&slow) > duration_per_byte_estimate(&fast), |
| 469 | + "9600 baud should take longer per byte than 115200 baud" |
| 470 | + ); |
| 471 | + } |
| 472 | + |
| 473 | + #[test] |
| 474 | + fn parity_bit_increases_duration() { |
| 475 | + let no_parity = make_mode(9_600, 8, Parity::NONE, StopBits::ONE); |
| 476 | + let with_parity = make_mode(9_600, 8, Parity::EVEN, StopBits::ONE); |
| 477 | + assert!( |
| 478 | + duration_per_byte_estimate(&with_parity) > duration_per_byte_estimate(&no_parity), |
| 479 | + "a parity bit should increase the estimated duration" |
| 480 | + ); |
| 481 | + } |
| 482 | + |
| 483 | + #[test] |
| 484 | + fn two_stop_bits_increases_duration() { |
| 485 | + let one_stop = make_mode(9_600, 8, Parity::NONE, StopBits::ONE); |
| 486 | + let two_stop = make_mode(9_600, 8, Parity::NONE, StopBits::TWO); |
| 487 | + assert!( |
| 488 | + duration_per_byte_estimate(&two_stop) > duration_per_byte_estimate(&one_stop), |
| 489 | + "two stop bits should increase the estimated duration" |
| 490 | + ); |
| 491 | + } |
| 492 | + |
| 493 | + #[test] |
| 494 | + fn one_five_stop_bits_same_as_two() { |
| 495 | + // ONE_FIVE is rounded up conservatively to 2, same as TWO. |
| 496 | + let one_five = make_mode(9_600, 8, Parity::NONE, StopBits::ONE_FIVE); |
| 497 | + let two = make_mode(9_600, 8, Parity::NONE, StopBits::TWO); |
| 498 | + assert_eq!( |
| 499 | + duration_per_byte_estimate(&one_five), |
| 500 | + duration_per_byte_estimate(&two), |
| 501 | + "ONE_FIVE should be treated as 2 stop bits" |
| 502 | + ); |
| 503 | + } |
| 504 | + |
| 505 | + #[test] |
| 506 | + fn more_data_bits_increases_duration() { |
| 507 | + let seven = make_mode(9_600, 7, Parity::NONE, StopBits::ONE); |
| 508 | + let eight = make_mode(9_600, 8, Parity::NONE, StopBits::ONE); |
| 509 | + assert!( |
| 510 | + duration_per_byte_estimate(&eight) > duration_per_byte_estimate(&seven), |
| 511 | + "more data bits should increase the estimated duration" |
| 512 | + ); |
| 513 | + } |
| 514 | + |
| 515 | + #[test] |
| 516 | + fn default_stop_bits_conservative() { |
| 517 | + // DEFAULT is unknown, so it should be treated at least as generously as TWO. |
| 518 | + let default_stop = make_mode(9_600, 8, Parity::NONE, StopBits::DEFAULT); |
| 519 | + let two_stop = make_mode(9_600, 8, Parity::NONE, StopBits::TWO); |
| 520 | + assert!( |
| 521 | + duration_per_byte_estimate(&default_stop) >= duration_per_byte_estimate(&two_stop), |
| 522 | + "DEFAULT stop bits should be at least as conservative as TWO" |
| 523 | + ); |
| 524 | + } |
| 525 | + |
| 526 | + #[test] |
| 527 | + fn default_parity_conservative() { |
| 528 | + // DEFAULT parity is unknown, so assume a parity bit may be present. |
| 529 | + let default_parity = make_mode(9_600, 8, Parity::DEFAULT, StopBits::ONE); |
| 530 | + let no_parity = make_mode(9_600, 8, Parity::NONE, StopBits::ONE); |
| 531 | + assert!( |
| 532 | + duration_per_byte_estimate(&default_parity) >= duration_per_byte_estimate(&no_parity), |
| 533 | + "DEFAULT parity should be at least as conservative as a known parity bit" |
| 534 | + ); |
242 | 535 | } |
243 | 536 | } |
0 commit comments