in src/devices/src/legacy/i8042.rs [235:325]
fn write(&mut self, offset: u64, data: &[u8]) {
// All our ports are byte-wide. We don't know how to handle any wider data.
if data.len() != 1 {
METRICS.i8042.missed_write_count.inc();
return;
}
let mut write_ok = true;
match offset {
OFS_STATUS if data[0] == CMD_RESET_CPU => {
// The guest wants to assert the CPU reset line. We handle that by triggering
// our exit event fd. Meaning Firecracker will be exiting as soon as the VMM
// thread wakes up to handle this event.
if let Err(e) = self.reset_evt.write(1) {
error!("Failed to trigger i8042 reset event: {:?}", e);
METRICS.i8042.error_count.inc();
}
METRICS.i8042.reset_count.inc();
}
OFS_STATUS if data[0] == CMD_READ_CTR => {
// The guest wants to read the control register.
// Let's make sure only the control register will be available for reading from
// the data port, for the next inb(0x60).
self.flush_buf();
let control = self.control;
// Buffer is empty, push() will always succeed.
self.push_byte(control).unwrap();
}
OFS_STATUS if data[0] == CMD_WRITE_CTR => {
// The guest wants to write the control register. This is a two-step command:
// 1. port 0x64 < CMD_WRITE_CTR
// 2. port 0x60 < <control reg value>
// Make sure we'll be expecting the control reg value on port 0x60 for the next
// write.
self.flush_buf();
self.status |= SB_I8042_CMD_DATA;
self.cmd = data[0];
}
OFS_STATUS if data[0] == CMD_READ_OUTP => {
// The guest wants to read the output port (for lack of a better name - this is
// just another register on the 8042, that happens to also have its bits connected
// to some output pins of the 8042).
self.flush_buf();
let outp = self.outp;
// Buffer is empty, push() will always succeed.
self.push_byte(outp).unwrap();
}
OFS_STATUS if data[0] == CMD_WRITE_OUTP => {
// Similar to writing the control register, this is a two-step command.
// I.e. write CMD_WRITE_OUTP at port 0x64, then write the actual out port value
// to port 0x60.
self.status |= SB_I8042_CMD_DATA;
self.cmd = data[0];
}
OFS_DATA if (self.status & SB_I8042_CMD_DATA) != 0 => {
// The guest is writing to port 0x60. This byte can either be:
// 1. the payload byte of a CMD_WRITE_CTR or CMD_WRITE_OUTP command, in which case
// the status reg bit SB_I8042_CMD_DATA will be set, or
// 2. a direct command sent to the keyboard
// This match arm handles the first option (when the SB_I8042_CMD_DATA bit is set).
match self.cmd {
CMD_WRITE_CTR => self.control = data[0],
CMD_WRITE_OUTP => self.outp = data[0],
_ => (),
}
self.status &= !SB_I8042_CMD_DATA;
}
OFS_DATA => {
// The guest is sending a command straight to the keyboard (so this byte is not
// addressed to the 8042, but to the keyboard). Since we're emulating a pretty
// dumb keyboard, we can get away with blindly ack-in anything (byte 0xFA).
// Something along the lines of "Yeah, uhm-uhm, yeah, okay, honey, that's great."
self.flush_buf();
// Buffer is empty, push() will always succeed.
self.push_byte(0xFA).unwrap();
if let Err(Error::KbdInterruptFailure(err)) = self.trigger_kbd_interrupt() {
warn!("Failed to trigger i8042 kbd interrupt {:?}", err);
}
}
_ => {
write_ok = false;
}
}
if write_ok {
METRICS.i8042.write_count.inc();
} else {
METRICS.i8042.missed_write_count.inc();
}
}