Skip to content

Commit d1e3c27

Browse files
alexandruaglauralt
authored andcommitted
serial: define the SerialEvents trait
Also changed `vmm-sys-util` to a dev-dependency. Signed-off-by: Alexandru Agache <[email protected]>
1 parent 1f2f059 commit d1e3c27

File tree

3 files changed

+105
-7
lines changed

3 files changed

+105
-7
lines changed

Cargo.toml

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ authors = ["rust-vmm AWS maintainers <[email protected]>"]
99
license = "Apache-2.0 OR BSD-3-Clause"
1010
edition = "2018"
1111

12-
[dependencies]
13-
vmm-sys-util = ">=0.6.1"
14-
1512
[dev-dependencies]
1613
libc = ">=0.2.39"
14+
vmm-sys-util = ">=0.7.0"

coverage_config_x86_64.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"coverage_score": 97.7,
2+
"coverage_score": 97.9,
33
"exclude_path": "",
44
"crate_features": ""
55
}

src/serial.rs

+103-3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,33 @@ const DEFAULT_MODEM_CONTROL: u8 = MCR_OUT2_BIT;
9999
const DEFAULT_MODEM_STATUS: u8 = MSR_DSR_BIT | MSR_CTS_BIT | MSR_DCD_BIT;
100100
const DEFAULT_SCRATCH: u8 = 0x00;
101101

102+
/// Defines a series of callbacks that are invoked in response to the occurrence of specific
103+
/// events as part of the serial emulation logic (for example, when the driver reads data). The
104+
/// methods below can be implemented by a backend that keeps track of such events by incrementing
105+
/// metrics, logging messages, or any other action.
106+
///
107+
/// We're using a trait to avoid constraining the concrete characteristics of the backend in
108+
/// any way, enabling zero-cost abstractions and use case-specific implementations.
109+
// TODO: The events defined below are just some examples for now to validate the approach. If
110+
// things look good, we can move on to establishing the initial list. It's also worth mentioning
111+
// the methods can have extra parameters that provide additional information about the event.
112+
pub trait SerialEvents {
113+
/// The driver reads data from the input buffer.
114+
fn buffer_read(&self);
115+
/// The driver successfully wrote one byte to serial output.
116+
fn out_byte(&self);
117+
}
118+
119+
/// Provides a no-op implementation of `SerialEvents` which can be used in situations that
120+
/// do not require logging or otherwise doing anything in response to the events defined
121+
/// as part of `SerialEvents`.
122+
pub struct NoEvents;
123+
124+
impl SerialEvents for NoEvents {
125+
fn buffer_read(&self) {}
126+
fn out_byte(&self) {}
127+
}
128+
102129
/// The serial console emulation is done by emulating a serial COM port.
103130
///
104131
/// Each serial COM port (COM1-4) has an associated Port I/O address base and
@@ -159,7 +186,7 @@ const DEFAULT_SCRATCH: u8 = 0x00;
159186
/// serial.enqueue_raw_bytes(input).unwrap();
160187
/// }
161188
/// ```
162-
pub struct Serial<T: Trigger, W: Write> {
189+
pub struct Serial<T: Trigger, EV: SerialEvents, W: Write> {
163190
// Some UART registers.
164191
baud_divisor_low: u8,
165192
baud_divisor_high: u8,
@@ -178,6 +205,7 @@ pub struct Serial<T: Trigger, W: Write> {
178205

179206
// Used for notifying the driver about some in/out events.
180207
interrupt_evt: T,
208+
serial_evts: EV,
181209
out: W,
182210
}
183211

@@ -192,7 +220,7 @@ pub enum Error<E> {
192220
FullFifo,
193221
}
194222

195-
impl<T: Trigger, W: Write> Serial<T, W> {
223+
impl<T: Trigger, W: Write> Serial<T, NoEvents, W> {
196224
/// Creates a new `Serial` instance which writes the guest's output to
197225
/// `out` and uses `trigger` object to notify the driver about new
198226
/// events.
@@ -209,7 +237,27 @@ impl<T: Trigger, W: Write> Serial<T, W> {
209237
///
210238
/// You can see an example of how to use this function in the
211239
/// [`Example` section from `Serial`](struct.Serial.html#example).
212-
pub fn new(trigger: T, out: W) -> Serial<T, W> {
240+
pub fn new(trigger: T, out: W) -> Serial<T, NoEvents, W> {
241+
Self::with_events(trigger, NoEvents, out)
242+
}
243+
}
244+
245+
impl<T: Trigger, EV: SerialEvents, W: Write> Serial<T, EV, W> {
246+
/// Creates a new `Serial` instance which writes the guest's output to
247+
/// `out`, uses `trigger` object to notify the driver about new
248+
/// events, and invokes the `serial_evts` implementation of `SerialEvents`
249+
/// during operation.
250+
///
251+
/// # Arguments
252+
/// * `trigger` - The `Trigger` object that will be used to notify the driver
253+
/// about events.
254+
/// * `serial_evts` - The `SerialEvents` implementation used to track the occurrence
255+
/// of significant events in the serial operation logic.
256+
/// * `out` - An object for writing guest's output to. In case the output
257+
/// is not of interest,
258+
/// [std::io::Sink](https://doc.rust-lang.org/std/io/struct.Sink.html)
259+
/// can be used here.
260+
pub fn with_events(trigger: T, serial_evts: EV, out: W) -> Self {
213261
Serial {
214262
baud_divisor_low: DEFAULT_BAUD_DIVISOR_LOW,
215263
baud_divisor_high: DEFAULT_BAUD_DIVISOR_HIGH,
@@ -222,6 +270,7 @@ impl<T: Trigger, W: Write> Serial<T, W> {
222270
scratch: DEFAULT_SCRATCH,
223271
in_buffer: VecDeque::new(),
224272
interrupt_evt: trigger,
273+
serial_evts,
225274
out,
226275
}
227276
}
@@ -331,6 +380,8 @@ impl<T: Trigger, W: Write> Serial<T, W> {
331380
} else {
332381
self.out.write_all(&[value]).map_err(Error::IOError)?;
333382
self.out.flush().map_err(Error::IOError)?;
383+
self.serial_evts.out_byte();
384+
334385
self.thr_empty_interrupt().map_err(Error::Trigger)?;
335386
}
336387
}
@@ -371,6 +422,8 @@ impl<T: Trigger, W: Write> Serial<T, W> {
371422
if self.in_buffer.len() <= 1 {
372423
self.clear_lsr_rda_bit();
373424
}
425+
426+
self.serial_evts.buffer_read();
374427
self.in_buffer.pop_front().unwrap_or_default()
375428
}
376429
IER_OFFSET => self.interrupt_enable,
@@ -464,9 +517,13 @@ impl<T: Trigger, W: Write> Serial<T, W> {
464517
#[cfg(test)]
465518
mod tests {
466519
use super::*;
520+
467521
use std::io::{sink, Result};
522+
use std::sync::atomic::AtomicU64;
523+
use std::sync::Arc;
468524

469525
use vmm_sys_util::eventfd::EventFd;
526+
use vmm_sys_util::metric::Metric;
470527

471528
const RAW_INPUT_BUF: [u8; 3] = [b'a', b'b', b'c'];
472529

@@ -478,6 +535,25 @@ mod tests {
478535
}
479536
}
480537

538+
#[derive(Default)]
539+
struct ExampleSerialMetrics {
540+
read_count: AtomicU64,
541+
out_byte_count: AtomicU64,
542+
}
543+
544+
// We define this for `Arc<ExampleSerialMetrics>` because we expect to share a reference to
545+
// the same object with other threads.
546+
impl SerialEvents for Arc<ExampleSerialMetrics> {
547+
fn buffer_read(&self) {
548+
self.read_count.inc();
549+
// We can also log a message here, or as part of any of the other methods.
550+
}
551+
552+
fn out_byte(&self) {
553+
self.out_byte_count.inc();
554+
}
555+
}
556+
481557
#[test]
482558
fn test_serial_output() {
483559
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
@@ -698,4 +774,28 @@ mod tests {
698774
assert_eq!(written_bytes, 1);
699775
assert_eq!(serial.in_buffer.len(), FIFO_SIZE);
700776
}
777+
778+
#[test]
779+
fn test_serial_events() {
780+
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
781+
let metrics = Arc::new(ExampleSerialMetrics::default());
782+
let mut serial = Serial::with_events(intr_evt, metrics, sink());
783+
784+
// Check everything is equal to 0 at the beginning.
785+
assert_eq!(serial.serial_evts.read_count.count(), 0);
786+
assert_eq!(serial.serial_evts.out_byte_count.count(), 0);
787+
788+
// This DATA read should cause the `SerialEvents::buffer_read` method to be invoked.
789+
serial.read(DATA_OFFSET);
790+
assert_eq!(serial.serial_evts.read_count.count(), 1);
791+
792+
// This DATA write should cause `SerialEvents::out_byte` to be called.
793+
serial.write(DATA_OFFSET, 1).unwrap();
794+
assert_eq!(serial.serial_evts.out_byte_count.count(), 1);
795+
796+
// Check that every metric has the expected value at the end, to ensure we didn't
797+
// unexpectedly invoked any extra callbacks.
798+
assert_eq!(serial.serial_evts.read_count.count(), 1);
799+
assert_eq!(serial.serial_evts.out_byte_count.count(), 1);
800+
}
701801
}

0 commit comments

Comments
 (0)