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()), + } +}