Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 220858b

Browse files
Annotated logs, stdout cleanup, and headless display (#32)
* Bring headless and non-headless modes closer to each other * Annotate logs with there log type and strip ANSI control chars from stdout * Reset the serial device after testing: we need it! * Use a cleaner interface to settable control bits
1 parent fa0206f commit 220858b

File tree

4 files changed

+124
-25
lines changed

4 files changed

+124
-25
lines changed

‎src/proto/console/serial.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,25 @@ impl Serial {
6464

6565
/// Sets the device's new control bits.
6666
///
67-
/// Not all bits can be modified with this function.
68-
/// Only the folowing can be set:
69-
///
70-
/// - DATA_TERMINAL_READY
71-
/// - REQUEST_TO_SEND
72-
/// - HARDWARE_LOOPBACK_ENABLE
73-
/// - SOFTWARE_LOOPBACK_ENABLE
74-
/// - HARDWARE_FLOW_CONTROL_ENABLE
67+
/// Not all bits can be modified with this function. You can use the
68+
/// settable_control_bits() method to query which of them are.
7569
pub fn set_control_bits(&mut self, bits: ControlBits) -> Result<()> {
7670
(self.set_control_bits)(self, bits).into()
7771
}
7872

73+
74+
/// Get a bitmask of the control bits that can be set.
75+
///
76+
/// TODO: To be replaced with a constant once Rust has const generics
77+
pub fn settable_control_bits() -> ControlBits {
78+
// Up to date as of UEFI 2.7 / Serial protocol v1
79+
ControlBits::DATA_TERMINAL_READY
80+
| ControlBits::REQUEST_TO_SEND
81+
| ControlBits::HARDWARE_LOOPBACK_ENABLE
82+
| ControlBits::SOFTWARE_LOOPBACK_ENABLE
83+
| ControlBits::HARDWARE_FLOW_CONTROL_ENABLE
84+
}
85+
7986
/// Retrieve the device's current control bits.
8087
pub fn get_control_bits(&self) -> Result<ControlBits> {
8188
let mut bits = ControlBits::empty();

‎uefi-logger/src/lib.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use uefi::proto::console::text::Output;
2222
extern crate log;
2323

2424
use core::cell::UnsafeCell;
25+
use core::fmt::{self, Write};
2526

2627
mod writer;
2728
use self::writer::OutputWriter;
@@ -46,11 +47,10 @@ impl log::Log for Logger {
4647
}
4748

4849
fn log(&self, record: &log::Record) {
49-
let args = record.args();
50-
5150
let writer = unsafe { &mut *self.writer.get() };
52-
use core::fmt::Write;
53-
writeln!(writer, "{}", args).unwrap();
51+
DecoratedLog::write(writer,
52+
record.level(),
53+
record.args()).unwrap();
5454
}
5555

5656
fn flush(&self) {
@@ -61,3 +61,68 @@ impl log::Log for Logger {
6161
// The logger is not thread-safe, but the UEFI boot environment only uses one processor.
6262
unsafe impl Sync for Logger {}
6363
unsafe impl Send for Logger {}
64+
65+
66+
/// Writer wrapper which prints a log level in front of every line of text
67+
///
68+
/// This is less easy than it sounds because...
69+
///
70+
/// 1. The fmt::Arguments is a rather opaque type, the ~only thing you can do
71+
/// with it is to hand it to an fmt::Write implementation.
72+
/// 2. Without using memory allocation, the easy cop-out of writing everything
73+
/// to a String then post-processing is not available.
74+
///
75+
/// Therefore, we need to inject ourselves in the middle of the fmt::Write
76+
/// machinery and intercept the strings that it sends to the Writer.
77+
///.
78+
struct DecoratedLog<'a, W: fmt::Write> {
79+
backend: &'a mut W,
80+
log_level: log::Level,
81+
at_line_start: bool,
82+
}
83+
//
84+
impl<'a, W: fmt::Write> DecoratedLog<'a, W> {
85+
// Call this method to print a level-annotated log
86+
fn write(writer: &'a mut W,
87+
level: log::Level,
88+
args: &fmt::Arguments) -> fmt::Result {
89+
let mut decorated_writer = Self {
90+
backend: writer,
91+
log_level: level,
92+
at_line_start: true,
93+
};
94+
writeln!(decorated_writer, "{}", *args)
95+
}
96+
}
97+
//
98+
impl<'a, W: fmt::Write> fmt::Write for DecoratedLog<'a, W> {
99+
fn write_str(&mut self, s: &str) -> fmt::Result {
100+
// Split the input string into lines
101+
let mut lines = s.lines();
102+
103+
// The beginning of the input string may actually fall in the middle of
104+
// a line of output. We only print the log level if it truly is at the
105+
// beginning of a line of output.
106+
let first = lines.next().unwrap_or("");
107+
if self.at_line_start {
108+
write!(self.backend, "{}: ", self.log_level);
109+
self.at_line_start = false;
110+
}
111+
write!(self.backend, "{}", first)?;
112+
113+
// For the remainder of the line iterator (if any), we know that we are
114+
// truly at the beginning of lines of output.
115+
for line in lines {
116+
write!(self.backend, "\n{}: {}", self.log_level, line);
117+
}
118+
119+
// If the string ends with a newline character, we must 1/propagate it
120+
// to the output (it was swallowed by the iteration) and 2/prepare to
121+
// write the log level of the beginning of the next line (if any).
122+
if let Some('\n') = s.chars().next_back() {
123+
writeln!(self.backend);
124+
self.at_line_start = true;
125+
}
126+
Ok(())
127+
}
128+
}

‎uefi-test-runner/build.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import argparse
66
import os
77
from pathlib import Path
8+
import re
89
import shutil
910
import subprocess as sp
1011
import sys
@@ -101,27 +102,24 @@ def run_qemu(headless):
101102

102103
# Mount the built examples directory.
103104
'-drive', f'format=raw,file=fat:rw:{examples_dir}',
105+
106+
# Connect the serial port to the host. OVMF is kind enough to connect
107+
# the UEFI stdout and stdin to that port too.
108+
'-serial', 'stdio',
104109
]
105110

106-
# When running in headless mode we don't have video
111+
# When running in headless mode we don't have video, but we can still have
112+
# QEMU emulate a display and take screenshots from it.
113+
qemu_flags.extend(['-vga', 'std'])
107114
if headless:
108-
# Disable window
109-
qemu_flags.append('-nographic')
110-
111-
# Redirect all output to stdio
112-
qemu_flags.extend(['-serial', 'stdio'])
113-
else:
114-
# Use a standard VGA for graphics.
115-
qemu_flags.extend(['-vga', 'std'])
115+
# Do not attach a window to QEMU's display
116+
qemu_flags.extend(['-display', 'none'])
116117

117118
# Add other devices
118119
qemu_flags.extend([
119120
# Map the QEMU exit signal to port f4
120121
'-device', 'isa-debug-exit,iobase=0xf4,iosize=0x04',
121122

122-
# Add a null serial device for testing with loop-back
123-
'-serial', 'null',
124-
125123
# OVMF debug builds can output information to a serial `debugcon`.
126124
# Only enable when debugging UEFI boot:
127125
#'-debugcon', 'file:debug.log', '-global', 'isa-debugcon.iobase=0x402',
@@ -132,7 +130,29 @@ def run_qemu(headless):
132130
if VERBOSE:
133131
print(' '.join(cmd))
134132

135-
sp.run(cmd).check_returncode()
133+
# This regex can be used to detect and strip ANSI escape codes when
134+
# analyzing the output of the test runner.
135+
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
136+
137+
# Start QEMU
138+
qemu = sp.Popen(cmd, stdout=sp.PIPE, universal_newlines=True)
139+
140+
# Iterate over stdout...
141+
for line in qemu.stdout:
142+
# Strip ending and trailing whitespace + ANSI escape codes for analysis
143+
stripped = ansi_escape.sub('', line.strip())
144+
145+
# Skip empty lines
146+
if not stripped:
147+
continue
148+
149+
# Print out the processed QEMU output to allow logging & inspection
150+
print(stripped)
151+
152+
# Wait for QEMU to finish, then abort if that fails
153+
status = qemu.wait()
154+
if status != 0:
155+
raise sp.CalledProcessError(cmd=cmd, returncode=status)
136156

137157
def main():
138158
'Runs the user-requested actions.'

‎uefi-test-runner/src/proto/console/serial.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ pub fn test(bt: &BootServices) {
66
if let Some(mut serial) = bt.find_protocol::<Serial>() {
77
let serial = unsafe { serial.as_mut() };
88

9+
let old_ctrl_bits = serial.get_control_bits()
10+
.expect("Failed to get device control bits");
911
let mut ctrl_bits = ControlBits::empty();
1012

1113
// For the purposes of testing, we're _not_ going to implement
@@ -35,6 +37,11 @@ pub fn test(bt: &BootServices) {
3537
assert_eq!(len, msg_len, "Serial port read timed-out!");
3638

3739
assert_eq!(&output[..], &input[..msg_len]);
40+
41+
// Clean up after ourselves
42+
serial.reset().expect("Could not reset the serial device");
43+
serial.set_control_bits(old_ctrl_bits & Serial::settable_control_bits())
44+
.expect("Could not restore the serial device state");
3845
} else {
3946
warn!("No serial device found");
4047
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /