1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the THIRD-PARTY file.
//! Provides emulation for a super minimal i8042 controller.
//!
//! This emulates just the CPU reset command.
use std::result::Result;
use crate::Trigger;
// Offset of the command register, for write accesses (port 0x64). The same
// offset can be used, in case of read operations, to access the status
// register (in which we are not interested for an i8042 that only knows
// about reset).
const COMMAND_OFFSET: u8 = 4;
// Reset CPU command.
const CMD_RESET_CPU: u8 = 0xFE;
/// An i8042 PS/2 controller that emulates just enough to shutdown the machine.
///
/// A [`Trigger`](../trait.Trigger.html) object is used for notifying the VMM
/// about the CPU reset event.
///
/// # Example
///
/// ```rust
/// # use std::io::{Error, Result};
/// # use std::ops::Deref;
/// # use vm_superio::Trigger;
/// # use vm_superio::I8042Device;
/// # use vmm_sys_util::eventfd::EventFd;
///
/// struct EventFdTrigger(EventFd);
/// impl Trigger for EventFdTrigger {
/// type E = Error;
///
/// fn trigger(&self) -> Result<()> {
/// self.write(1)
/// }
/// }
/// impl Deref for EventFdTrigger {
/// type Target = EventFd;
/// fn deref(&self) -> &Self::Target {
/// &self.0
/// }
/// }
/// impl EventFdTrigger {
/// pub fn new(flag: i32) -> Self {
/// EventFdTrigger(EventFd::new(flag).unwrap())
/// }
/// }
///
/// let reset_evt = EventFdTrigger::new(libc::EFD_NONBLOCK);
/// let mut i8042 = I8042Device::new(reset_evt);
///
/// // Check read/write operations.
/// assert_eq!(i8042.read(0), 0);
/// i8042.write(4, 0xFE).unwrap();
/// ```
#[derive(Debug)]
pub struct I8042Device<T: Trigger> {
/// CPU reset event object. We will trigger this event when the guest issues
/// the reset CPU command.
reset_evt: T,
}
impl<T: Trigger> I8042Device<T> {
/// Constructs an i8042 device that will signal the given event when the
/// guest requests it.
///
/// # Arguments
/// * `reset_evt` - A Trigger object that will be used to notify the driver
/// about the reset event.
///
/// # Example
///
/// You can see an example of how to use this function in the
/// [`Example` section from `I8042Device`](struct.I8042Device.html#example).
pub fn new(reset_evt: T) -> I8042Device<T> {
I8042Device { reset_evt }
}
/// Provides a reference to the reset event object.
pub fn reset_evt(&self) -> &T {
&self.reset_evt
}
/// Handles a read request from the driver at `_offset` offset from the
/// base I/O address.
///
/// Returns the read value, which at this moment is 0x00, since we're not
/// interested in an i8042 operation other than CPU reset.
///
/// # Arguments
/// * `_offset` - The offset that will be added to the base address
/// for writing to a specific register.
///
/// # Example
///
/// You can see an example of how to use this function in the
/// [`Example` section from `I8042Device`](struct.I8042Device.html#example).
pub fn read(&mut self, _offset: u8) -> u8 {
0x00
}
/// Handles a write request from the driver at `offset` offset from the
/// base I/O address.
///
/// # Arguments
/// * `offset` - The offset that will be added to the base address
/// for writing to a specific register.
/// * `value` - The byte that should be written.
///
/// # Example
///
/// You can see an example of how to use this function in the
/// [`Example` section from `I8042Device`](struct.I8042Device.html#example).
pub fn write(&mut self, offset: u8, value: u8) -> Result<(), T::E> {
match offset {
COMMAND_OFFSET if value == CMD_RESET_CPU => {
// Trigger the exit event.
self.reset_evt.trigger()
}
_ => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use vmm_sys_util::eventfd::EventFd;
#[test]
fn test_i8042_valid_ops() {
let reset_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
let mut i8042 = I8042Device::new(reset_evt.try_clone().unwrap());
assert_eq!(i8042.read(0), 0);
// Check if reset works.
i8042.write(COMMAND_OFFSET, CMD_RESET_CPU).unwrap();
assert_eq!(reset_evt.read().unwrap(), 1);
}
#[test]
fn test_i8042_invalid_reset() {
let reset_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
let mut i8042 = I8042Device::new(reset_evt.try_clone().unwrap());
// Write something different than CPU reset and check that the reset event
// was not triggered. For this we have to write 1 to the reset event fd, so
// that read doesn't block.
assert!(reset_evt.write(1).is_ok());
i8042.write(COMMAND_OFFSET, CMD_RESET_CPU + 1).unwrap();
assert_eq!(reset_evt.read().unwrap(), 1);
// Write the CPU reset to a different offset than COMMAND_OFFSET and check that
// the reset event was not triggered.
// For such accesses we should increment some metric (COMMAND_OFFSET + 1 is not
// even a valid i8042 offset), see
// [tracking issue](https://github.com/rust-vmm/vm-superio/issues/13).
assert!(reset_evt.write(1).is_ok());
i8042.write(COMMAND_OFFSET + 1, CMD_RESET_CPU).unwrap();
assert_eq!(reset_evt.read().unwrap(), 1);
}
}