diff --git a/devices/src/legacy/gpio_pl061.rs b/devices/src/legacy/gpio_pl061.rs new file mode 100644 index 000000000..529358454 --- /dev/null +++ b/devices/src/legacy/gpio_pl061.rs @@ -0,0 +1,484 @@ +// Copyright 2021 Arm Limited (or its affiliates). All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +//! ARM PrimeCell General Purpose Input/Output(PL061) +//! +//! This module implements an ARM PrimeCell General Purpose Input/Output(PL061) to support gracefully poweroff microvm from external. +//! + +use crate::{read_le_u32, write_le_u32}; +use anyhow::anyhow; +use std::result; +use std::sync::{Arc, Barrier}; +use std::{fmt, io}; +use vm_device::interrupt::InterruptSourceGroup; +use vm_device::BusDevice; +use vm_migration::{ + Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable, + Transportable, +}; + +const OFS_DATA: u64 = 0x400; // Data Register +const GPIODIR: u64 = 0x400; // Direction Register +const GPIOIS: u64 = 0x404; // Interrupt Sense Register +const GPIOIBE: u64 = 0x408; // Interrupt Both Edges Register +const GPIOIEV: u64 = 0x40c; // Interrupt Event Register +const GPIOIE: u64 = 0x410; // Interrupt Mask Register +const GPIORIE: u64 = 0x414; // Raw Interrupt Status Register +const GPIOMIS: u64 = 0x418; // Masked Interrupt Status Register +const GPIOIC: u64 = 0x41c; // Interrupt Clear Register +const GPIOAFSEL: u64 = 0x420; // Mode Control Select Register + // From 0x424 to 0xFDC => reserved space. + // From 0xFE0 to 0xFFC => Peripheral and PrimeCell Identification Registers which are Read Only registers. + // Thses registers can conceptually be treated as a 32-bit register, and PartNumber[11:0] is used to identify the peripheral. + // We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array. +const GPIO_ID: [u8; 8] = [0x61, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1]; +// ID Margins +const GPIO_ID_LOW: u64 = 0xfe0; +const GPIO_ID_HIGH: u64 = 0x1000; + +const N_GPIOS: u32 = 8; + +#[derive(Debug)] +pub enum Error { + BadWriteOffset(u64), + GPIOInterruptDisabled, + GPIOInterruptFailure(io::Error), + GPIOTriggerKeyFailure(u32), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset), + Error::GPIOInterruptDisabled => write!(f, "GPIO interrupt disabled by guest driver.",), + Error::GPIOInterruptFailure(ref e) => { + write!(f, "Could not trigger GPIO interrupt: {}.", e) + } + Error::GPIOTriggerKeyFailure(key) => { + write!(f, "Invalid GPIO Input key triggerd: {}.", key) + } + } + } +} + +type Result = result::Result; + +/// A GPIO device following the PL061 specification. +pub struct GPIO { + id: String, + // Data Register + data: u32, + old_in_data: u32, + // Direction Register + dir: u32, + // Interrupt Sense Register + isense: u32, + // Interrupt Both Edges Register + ibe: u32, + // Interrupt Event Register + iev: u32, + // Interrupt Mask Register + im: u32, + // Raw Interrupt Status Register + istate: u32, + // Mode Control Select Register + afsel: u32, + // GPIO irq_field + interrupt: Arc>, +} + +#[derive(Serialize, Deserialize)] +pub struct GPIOState { + data: u32, + old_in_data: u32, + dir: u32, + isense: u32, + ibe: u32, + iev: u32, + im: u32, + istate: u32, + afsel: u32, +} + +impl GPIO { + /// Constructs an PL061 GPIO device. + pub fn new(id: String, interrupt: Arc>) -> GPIO { + GPIO { + id, + data: 0, + old_in_data: 0, + dir: 0, + isense: 0, + ibe: 0, + iev: 0, + im: 0, + istate: 0, + afsel: 0, + interrupt, + } + } + + fn state(&self) -> GPIOState { + GPIOState { + data: self.data, + old_in_data: self.old_in_data, + dir: self.dir, + isense: self.isense, + ibe: self.ibe, + iev: self.iev, + im: self.im, + istate: self.istate, + afsel: self.afsel, + } + } + + fn set_state(&mut self, state: &GPIOState) { + self.data = state.data; + self.old_in_data = state.old_in_data; + self.dir = state.dir; + self.isense = state.isense; + self.ibe = state.ibe; + self.iev = state.iev; + self.im = state.im; + self.istate = state.istate; + self.afsel = state.afsel; + } + + fn pl061_internal_update(&mut self) { + // FIXME: + // Missing Output Interrupt Emulation. + + // Input Edging Interrupt Emulation. + let changed = ((self.old_in_data ^ self.data) & !self.dir) as u32; + if changed > 0 { + self.old_in_data = self.data; + for i in 0..N_GPIOS { + let mask = (1 << i) as u32; + if (changed & mask) > 0 { + // Bits set high in GPIOIS(Interrupt sense register) configure the corresponding + // pins to detect levels, otherwise, detect edges. + if (self.isense & mask) == 0 { + if (self.ibe & mask) > 0 { + // Bits set high in GPIOIBE(Interrupt both-edges register) configure the corresponding + // pins to detect both falling and rising edges. + // Clearing a bit configures the pin to be controlled by GPIOIEV. + self.istate |= mask; + } else { + // Bits set to high in GPIOIEV(Interrupt event register) configure the + // corresponding pin to detect rising edges, otherwise, detect falling edges. + self.istate |= !(self.data ^ self.iev) & mask; + } + } + } + } + } + + // Input Level Interrupt Emulation. + self.istate |= !(self.data ^ self.iev) & self.isense; + } + + fn handle_write(&mut self, offset: u64, val: u32) -> Result<()> { + if offset < OFS_DATA { + // In order to write to data register, the corresponding bits in the mask, resulting + // from the offsite[9:2], must be HIGH. otherwise the bit values remain unchanged. + let mask = (offset >> 2) as u32 & self.dir; + self.data = (self.data & !mask) | (val & mask); + } else { + match offset { + GPIODIR => { + /* Direction Register */ + self.dir = val & 0xff; + } + GPIOIS => { + /* Interrupt Sense Register */ + self.isense = val & 0xff; + } + GPIOIBE => { + /* Interrupt Both Edges Register */ + self.ibe = val & 0xff; + } + GPIOIEV => { + /* Interrupt Event Register */ + self.iev = val & 0xff; + } + GPIOIE => { + /* Interrupt Mask Register */ + self.im = val & 0xff; + } + GPIOIC => { + /* Interrupt Clear Register */ + self.istate &= !val; + } + GPIOAFSEL => { + /* Mode Control Select Register */ + self.afsel = val & 0xff; + } + o => { + return Err(Error::BadWriteOffset(o)); + } + } + } + Ok(()) + } + + pub fn trigger_key(&mut self, key: u32) -> Result<()> { + let mask = (1 << key) as u32; + if (!self.dir & mask) > 0 { + // emulate key event + // By default, Input Pin is configured to detect both rising and falling edges. + // So reverse the input pin data to generate a pulse. + self.data |= !(self.data & mask) & mask; + self.pl061_internal_update(); + + match self.trigger_gpio_interrupt() { + Ok(_) | Err(Error::GPIOInterruptDisabled) => return Ok(()), + Err(e) => return Err(e), + } + } + + Err(Error::GPIOTriggerKeyFailure(key)) + } + + fn trigger_gpio_interrupt(&self) -> Result<()> { + // Bits set to high in GPIOIE(Interrupt mask register) allow the corresponding pins to + // trigger their individual interrupts and then the combined GPIOINTR line. + if (self.istate & self.im) == 0 { + warn!("Failed to trigger GPIO input interrupt (disabled by guest OS)"); + return Err(Error::GPIOInterruptDisabled); + } + self.interrupt + .trigger(0) + .map_err(Error::GPIOInterruptFailure)?; + Ok(()) + } +} + +impl BusDevice for GPIO { + fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) { + let value; + let mut read_ok = true; + + if (GPIO_ID_LOW..GPIO_ID_HIGH).contains(&offset) { + let index = ((offset - GPIO_ID_LOW) >> 2) as usize; + value = u32::from(GPIO_ID[index]); + } else if offset < OFS_DATA { + value = self.data & ((offset >> 2) as u32) + } else { + value = match offset { + GPIODIR => self.dir, + GPIOIS => self.isense, + GPIOIBE => self.ibe, + GPIOIEV => self.iev, + GPIOIE => self.im, + GPIORIE => self.istate, + GPIOMIS => self.istate & self.im, + GPIOAFSEL => self.afsel, + _ => { + read_ok = false; + 0 + } + }; + } + + if read_ok && data.len() <= 4 { + write_le_u32(data, value); + } else { + warn!( + "Invalid GPIO PL061 read: offset {}, data length {}", + offset, + data.len() + ); + } + } + + fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { + if data.len() <= 4 { + let value = read_le_u32(&data[..]); + if let Err(e) = self.handle_write(offset, value) { + warn!("Failed to write to GPIO PL061 device: {}", e); + } + } else { + warn!( + "Invalid GPIO PL061 write: offset {}, data length {}", + offset, + data.len() + ); + } + + None + } +} + +impl Snapshottable for GPIO { + fn id(&self) -> String { + self.id.clone() + } + + fn snapshot(&mut self) -> std::result::Result { + let snapshot = + serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?; + + let mut gpio_snapshot = Snapshot::new(self.id.as_str()); + gpio_snapshot.add_data_section(SnapshotDataSection { + id: format!("{}-section", self.id), + snapshot, + }); + + Ok(gpio_snapshot) + } + + fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> { + if let Some(gpio_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) { + let gpio_state = match serde_json::from_slice(&gpio_section.snapshot) { + Ok(state) => state, + Err(error) => { + return Err(MigratableError::Restore(anyhow!( + "Could not deserialize GPIO {}", + error + ))) + } + }; + + self.set_state(&gpio_state); + + return Ok(()); + } + + Err(MigratableError::Restore(anyhow!( + "Could not find the GPIO snapshot section" + ))) + } +} + +impl Pausable for GPIO {} +impl Transportable for GPIO {} +impl Migratable for GPIO {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{read_le_u32, write_le_u32}; + use std::sync::Arc; + use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig}; + use vmm_sys_util::eventfd::EventFd; + + const GPIO_NAME: &str = "gpio"; + const LEGACY_GPIO_MAPPED_IO_START: u64 = 0x0902_0000; + + struct TestInterrupt { + event_fd: EventFd, + } + + impl InterruptSourceGroup for TestInterrupt { + fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> { + self.event_fd.write(1) + } + + fn update( + &self, + _index: InterruptIndex, + _config: InterruptSourceConfig, + ) -> result::Result<(), std::io::Error> { + Ok(()) + } + + fn notifier(&self, _index: InterruptIndex) -> Option { + Some(self.event_fd.try_clone().unwrap()) + } + } + + impl TestInterrupt { + fn new(event_fd: EventFd) -> Self { + TestInterrupt { event_fd } + } + } + + #[test] + fn test_gpio_read_write_and_event() { + let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let mut gpio = GPIO::new( + String::from(GPIO_NAME), + Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))), + ); + let mut data = [0; 4]; + + // Read and write to the GPIODIR register. + // Set pin 0 output pin. + write_le_u32(&mut data, 1); + gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIODIR, &mut data); + gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIODIR, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 1); + + // Read and write to the GPIODATA register. + write_le_u32(&mut data, 1); + // Set pin 0 high. + let offset = 0x00000004 as u64; + gpio.write(LEGACY_GPIO_MAPPED_IO_START, offset, &mut data); + gpio.read(LEGACY_GPIO_MAPPED_IO_START, offset, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 1); + + // Read and write to the GPIOIS register. + // Configure pin 0 detecting level interrupt. + write_le_u32(&mut data, 1); + gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIS, &mut data); + gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIS, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 1); + + // Read and write to the GPIOIBE register. + // Configure pin 1 detecting both falling and rising edges. + write_le_u32(&mut data, 2); + gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIBE, &mut data); + gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIBE, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 2); + + // Read and write to the GPIOIEV register. + // Configure pin 2 detecting both falling and rising edges. + write_le_u32(&mut data, 4); + gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIEV, &mut data); + gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIEV, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 4); + + // Read and write to the GPIOIE register. + // Configure pin 0...2 capable of triggering their individual interrupts + // and then the combined GPIOINTR line. + write_le_u32(&mut data, 7); + gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIE, &mut data); + gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIE, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 7); + + let mask = 0x00000002 as u32; + // emulate an rising pulse in pin 1. + gpio.data |= !(gpio.data & mask) & mask; + gpio.pl061_internal_update(); + // The interrupt line on pin 1 should be on. + // Read the GPIOMIS register. + gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOMIS, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 2); + + // Read and Write to the GPIOIC register. + // clear interrupt in pin 1. + write_le_u32(&mut data, 2); + gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIOIC, &mut data); + gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIOIC, &mut data); + let v = read_le_u32(&data[..]); + assert_eq!(v, 2); + + // Attempts to write beyond the writable space. + write_le_u32(&mut data, 0); + gpio.write(LEGACY_GPIO_MAPPED_IO_START, GPIO_ID_LOW, &mut data); + + let mut data = [0; 4]; + gpio.read(LEGACY_GPIO_MAPPED_IO_START, GPIO_ID_LOW, &mut data); + let index = GPIO_ID_LOW + 3; + assert_eq!(data[0], GPIO_ID[((index - GPIO_ID_LOW) >> 2) as usize]); + } +} diff --git a/devices/src/legacy/mod.rs b/devices/src/legacy/mod.rs index 8deca1670..08eb7a56e 100644 --- a/devices/src/legacy/mod.rs +++ b/devices/src/legacy/mod.rs @@ -9,6 +9,8 @@ mod cmos; #[cfg(feature = "fwdebug")] mod fwdebug; +#[cfg(target_arch = "aarch64")] +mod gpio_pl061; mod i8042; #[cfg(target_arch = "aarch64")] mod rtc_pl031;