Add virtio-input Linux test binary.
The binary reads from the Linux character devices `/dev/input/eventX`
and ensures that a series of keystrokes is received.
Bug: MAC-213, MAC-214
Change-Id: Ic50b70d63a55c8f603917db3546a3310e9e6b164
diff --git a/Cargo.toml b/Cargo.toml
index 55f6868..422772a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,8 @@
[dependencies]
structopt = { path = "rust_crates/vendor/structopt" }
libc = { path = "rust_crates/vendor/libc" }
+clap = { path = "rust_crates/vendor/clap" }
+matches = { path = "rust_crates/vendor/matches" }
[patch.crates-io]
winapi = { path = "rust_crates/tiny_mirrors/winapi" }
diff --git a/build.sh b/build.sh
index e10c3e6..d4313f8 100755
--- a/build.sh
+++ b/build.sh
@@ -48,6 +48,7 @@
mkdir -p ${OUT_DIR}
cp target/${TARGET}/debug/virtio_block_test_util ${OUT_DIR}
+cp target/${TARGET}/debug/virtio_input_test_util ${OUT_DIR}
cp target/${TARGET}/debug/virtio_net_test_util ${OUT_DIR}
cp target/${TARGET}/debug/virtio_rng_test_util ${OUT_DIR}
cp target/${TARGET}/debug/virtio_vsock_test_util ${OUT_DIR}
diff --git a/src/bin/virtio_input_test_util/events.rs b/src/bin/virtio_input_test_util/events.rs
new file mode 100644
index 0000000..ee86c63
--- /dev/null
+++ b/src/bin/virtio_input_test_util/events.rs
@@ -0,0 +1,153 @@
+/// Functionality for parsing events on Linux's `/dev/input/eventX` character
+/// devices.
+use std::fs::OpenOptions;
+use std::io::{Error, Read};
+
+/// Input event types, used in the Linux `input_event` struct.
+///
+/// See Linux `include/uapi/linux/input.h` for details.
+#[repr(u16)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[allow(dead_code)]
+pub enum EventType {
+ Syn = 0x00,
+ Key = 0x01,
+ Rel = 0x02,
+ Abs = 0x03,
+ Msc = 0x04,
+ Sw = 0x05,
+ Led = 0x11,
+ Snd = 0x12,
+ Rep = 0x14,
+ Ff = 0x15,
+ Pwr = 0x16,
+ FfStatus = 0x17,
+}
+
+impl Default for EventType {
+ fn default() -> EventType {
+ // By default, return the value with a zero representation.
+ EventType::Syn
+ }
+}
+
+/// Linux's `struct time_val`.
+///
+/// See Linux `include/uapi/linux/time.h` for details.
+#[repr(C, packed)]
+#[derive(Debug, Clone, Copy, Default)]
+pub struct TimeVal {
+ pub tv_sec: libc::c_long,
+ pub tv_usec: libc::c_long,
+}
+
+/// Linux's `struct input_event`.
+///
+/// This is the format returned by input devices such as `/dev/input/eventX', which provide raw
+/// input events from input devices.
+///
+/// See Linux `include/uapi/linux/input.h` for details.
+#[repr(C, packed)]
+#[derive(Debug, Clone, Copy, Default)]
+pub struct InputEvent {
+ pub time: TimeVal,
+ pub type_: EventType,
+ pub code: u16,
+ pub value: i32,
+}
+
+// Keyboard `value` values.
+#[repr(i32)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[allow(dead_code)]
+pub enum KeyboardValue {
+ KeyDown = 0,
+ KeyUp = 1,
+ KeyRepeat = 2,
+}
+
+// Keyboard `code` values.
+//
+// This is not the complete list, but just a subset needed for tests.
+//
+// See Linux's `include/uapi/linux/input.h` for details.
+#[repr(u16)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum KeyboardCode {
+ A = 30,
+ B = 48,
+ C = 46,
+ LeftShift = 42,
+}
+
+/// An EventReader parses events from the `/dev/input/eventX` interface.
+pub struct EventReader {
+ input: Box<Read + Send>,
+}
+
+impl EventReader {
+ /// Create a new EventReader, parsing bytes from the given reader.
+ pub fn new_from_reader(reader: Box<Read + Send>) -> EventReader {
+ EventReader { input: reader }
+ }
+
+ /// Create a new EventReader from the given file. The input file may be a character special
+ /// file, such as `/dev/input/eventX`.
+ pub fn new_from_path(path: &std::path::Path) -> Result<EventReader, Error> {
+ let input = OpenOptions::new()
+ .read(true)
+ .write(false)
+ .create(false)
+ .open(&path)?;
+ Ok(EventReader::new_from_reader(Box::new(input)))
+ }
+
+ pub fn read(self: &mut EventReader) -> Result<InputEvent, Error> {
+ let mut result: InputEvent = Default::default();
+
+ // Read an InputEvent from the input stream by reading the raw bytes into "result".
+ //
+ // The Linux kernel requires us to perform the read of a full event in a single read()
+ // syscall.
+ unsafe {
+ let raw_bytes = std::slice::from_raw_parts_mut(
+ &mut result as *mut _ as *mut u8,
+ std::mem::size_of::<InputEvent>(),
+ );
+ self.input.read_exact(raw_bytes)?;
+ }
+
+ Ok(result)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use matches::matches;
+
+ #[test]
+ fn test_parse_event() {
+ // Raw bytes of event pulled from /dev/input/event0 from USB Keyboard pressing the "a"
+ // button.
+ #[rustfmt::skip]
+ let raw_event = vec![
+ /* 0x00 */ 0x5b, 0x98, 0xe4, 0x5c, 0x00, 0x00, 0x00, 0x00,
+ /* 0x08 */ 0x33, 0xfd, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x10 */ 0x01, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let mut reader = EventReader::new_from_reader(Box::new(std::io::Cursor::new(raw_event)));
+ let event = reader.read().expect("Could not parse event");
+ assert_eq!({ event.type_ }, EventType::Key);
+ assert_eq!({ event.code }, KeyboardCode::A as u16);
+ assert_eq!({ event.value }, KeyboardValue::KeyDown as i32);
+ }
+
+ #[test]
+ fn test_eof() {
+ let empty_stream = vec![];
+ let mut reader = EventReader::new_from_reader(Box::new(std::io::Cursor::new(empty_stream)));
+ assert!(matches!(reader.read(), Err(_)));
+ }
+}
diff --git a/src/bin/virtio_input_test_util/main.rs b/src/bin/virtio_input_test_util/main.rs
new file mode 100644
index 0000000..739908c
--- /dev/null
+++ b/src/bin/virtio_input_test_util/main.rs
@@ -0,0 +1,90 @@
+mod events;
+
+use events::{EventReader, EventType, InputEvent, KeyboardCode, KeyboardValue};
+use std::path::Path;
+use std::sync::mpsc;
+
+/// Wait for the given key to be pressed on the given `events` interator.
+///
+/// Returns `true` if the keystroke was detected before the iterator finished.
+fn wait_for_key_press<I>(code: KeyboardCode, events: &mut I) -> Result<(), String>
+where
+ I: Iterator<Item = InputEvent>,
+{
+ for e in events {
+ if { e.type_ } == EventType::Key
+ && e.code == (code as u16)
+ && e.value == (KeyboardValue::KeyDown as i32)
+ {
+ return Ok(());
+ }
+ }
+ Err("Device closed while waiting for key press.".to_string())
+}
+
+fn run_keyboard_test(input_devices: &[&Path]) -> Result<(), String> {
+ // Open the set of character devices.
+ let files = input_devices
+ .iter()
+ .map(|x| {
+ EventReader::new_from_path(Path::new(x))
+ .map_err(|e| format!("Failed to open file '{}': {}", x.display(), e))
+ })
+ .collect::<Result<Vec<EventReader>, String>>()?;
+
+ // Create a EventReader object for each input file, all sending to the channel `tx`.
+ let (tx, rx) = mpsc::sync_channel(0);
+ let _threads = files
+ .into_iter()
+ .map(|mut r| {
+ let tx = tx.clone();
+ std::thread::spawn(move || {
+ while let Ok(event) = r.read() {
+ tx.send(event).ok();
+ }
+ })
+ })
+ .collect::<Vec<_>>();
+
+ // Wait for key strokes.
+ println!("Type 'abc<shift>' to pass test.");
+ let mut iter = rx.iter();
+ let codes = [
+ KeyboardCode::A,
+ KeyboardCode::B,
+ KeyboardCode::C,
+ KeyboardCode::LeftShift,
+ ];
+ for code in &codes {
+ println!(" waiting for {:?} ...", code);
+ if let error @ Err(_) = wait_for_key_press(*code, &mut iter) {
+ return error;
+ }
+ }
+ println!("PASS");
+ Ok(())
+}
+
+fn main() -> Result<(), String> {
+ // Parse command line arguments.
+ let matches = clap::App::new("VirtIO Input Tester")
+ .subcommand(
+ clap::SubCommand::with_name("keyboard")
+ .about("runs a keyboard test")
+ .arg(clap::Arg::with_name("files").required(true).min_values(1)),
+ )
+ .get_matches();
+
+ // Run the user-specified command
+ match matches.subcommand() {
+ ("keyboard", Some(keyboard_matches)) => {
+ let files = keyboard_matches
+ .values_of("files")
+ .unwrap()
+ .map(Path::new)
+ .collect::<Vec<&Path>>();
+ run_keyboard_test(&files)
+ }
+ _ => Err("Must provide a subcommand indicating which test to run.".to_string()),
+ }
+}