Skip to content

Commit

Permalink
devices/gpio: import a PL061-based GPIO
Browse files Browse the repository at this point in the history
Import a PL061-based GPIO to have an interface in the guest to send our
shut down signal. This device, along with its FDT entry, will instruct
Linux to enable a gpio-keys interface to receive key events.

When a consumer of the library writes to the eventfd received from
krun_get_shutdown_eventfd(), this device will send a key press event
followed by a key release event, for the key code KEY_RESTART. We're
using KEY_RESTART instead of KEY_POWEROFF because it's easier to
identify a vCPU exiting for a reboot than for a poweroff.

For the moment we're only implementing support on macOS/aarch64, but
should be possible to work mostly in the same way on Linux/aarch64.

Signed-off-by: Sergio Lopez <slp@redhat.com>
  • Loading branch information
slp committed Feb 13, 2024
1 parent b8979d8 commit ab7e3f8
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 2 deletions.
47 changes: 47 additions & 0 deletions src/arch/src/aarch64/fdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap}
const GIC_PHANDLE: u32 = 1;
// This is a value for uniquely identifying the FDT node containing the clock definition.
const CLOCK_PHANDLE: u32 = 2;
// This is a value for uniquely identifying the FDT node containing the gpio controller.
const GPIO_PHANDLE: u32 = 4;
// Read the documentation specified when appending the root node to the FDT.
const ADDRESS_CELLS: u32 = 0x2;
const SIZE_CELLS: u32 = 0x2;

// System restart
const KEY_RESTART: u32 = 0x198;

// As per kvm tool and
// https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt
// Look for "The 1st cell..."
Expand Down Expand Up @@ -350,6 +355,47 @@ fn create_rtc_node<T: DeviceInfoForFDT + Clone + Debug>(
Ok(())
}

fn create_gpio_node<T: DeviceInfoForFDT + Clone + Debug>(
fdt: &mut FdtWriter,
dev_info: &T,
) -> Result<()> {
let compatible = b"arm,pl061\0arm,primecell\0";

let gpio_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
let irq = generate_prop32(&[
GIC_FDT_IRQ_TYPE_SPI,
dev_info.irq() - 32,
IRQ_TYPE_EDGE_RISING,
]);

let gpio_node = fdt.begin_node(&format!("pl061@{:x}", dev_info.addr()))?;
fdt.property("compatible", compatible)?;

fdt.property("reg", &gpio_reg_prop)?;
fdt.property("interrupts", &irq)?;
fdt.property_null("gpio-controller")?;
fdt.property_u32("#gpio-cells", 2)?;
fdt.property_u32("clocks", CLOCK_PHANDLE)?;
fdt.property_string("clock-names", "apb_pclk")?;
fdt.property_u32("phandle", GPIO_PHANDLE)?;
fdt.end_node(gpio_node)?;

// gpio-keys node
let gpio_keys_node = fdt.begin_node("gpio-keys")?;
fdt.property_string("compatible", "gpio-keys")?;
fdt.property_u32("#size-cells", 0)?;
fdt.property_u32("#address-cells", 1)?;
let gpio_keys_poweroff_node = fdt.begin_node("button@1")?;
fdt.property_string("label", "GPIO Key Poweroff")?;
fdt.property_u32("linux,code", KEY_RESTART)?;
let gpios = [GPIO_PHANDLE, 3, 0];
fdt.property_array_u32("gpios", &gpios)?;
fdt.end_node(gpio_keys_poweroff_node)?;
fdt.end_node(gpio_keys_node)?;

Ok(())
}

fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug>(
fdt: &mut FdtWriter,
dev_info: &HashMap<(DeviceType, String), T>,
Expand All @@ -359,6 +405,7 @@ fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug>(

for ((device_type, _device_id), info) in dev_info {
match device_type {
DeviceType::Gpio => create_gpio_node(fdt, info)?,
DeviceType::RTC => create_rtc_node(fdt, info)?,
DeviceType::Serial => create_serial_node(fdt, info)?,
DeviceType::Virtio(_) => {
Expand Down
3 changes: 3 additions & 0 deletions src/arch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ pub type Result<T> = result::Result<T, Error>;
pub enum DeviceType {
/// Device Type: Virtio.
Virtio(u32),
/// Device Type: GPIO (PL061).
#[cfg(target_arch = "aarch64")]
Gpio,
/// Device Type: Serial.
#[cfg(target_arch = "aarch64")]
Serial,
Expand Down
254 changes: 254 additions & 0 deletions src/devices/src/legacy/aarch64/gpio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// 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 std::fmt;
use std::os::fd::AsRawFd;
use std::result;
use std::sync::{Arc, Mutex};

use polly::event_manager::{EventManager, Subscriber};
use utils::byte_order::{read_le_u32, write_le_u32};
use utils::epoll::{EpollEvent, EventSet};
use utils::eventfd::EventFd;

use crate::bus::BusDevice;
use crate::legacy::Gic;

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.
// These 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;

#[derive(Debug)]
pub enum Error {
BadWriteOffset(u64),
}

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}"),
}
}
}

type Result<T> = result::Result<T, Error>;

/// A GPIO device following the PL061 specification.
pub struct Gpio {
// Data Register
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_evt: EventFd,
intc: Option<Arc<Mutex<Gic>>>,
irq_line: Option<u32>,
shutdown_efd: EventFd,
}

impl Gpio {
/// Constructs an PL061 GPIO device.
pub fn new(shutdown_efd: EventFd, interrupt_evt: EventFd) -> Self {
Self {
data: 0,
dir: 0,
isense: 0,
ibe: 0,
iev: 0,
im: 0,
istate: 0,
afsel: 0,
interrupt_evt,
intc: None,
irq_line: None,
shutdown_efd,
}
}

pub fn set_intc(&mut self, intc: Arc<Mutex<Gic>>) {
self.intc = Some(intc);
}

pub fn set_irq_line(&mut self, irq: u32) {
self.irq_line = Some(irq);
}

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_restart_key(&mut self, press: bool) {
if press {
debug!("Generate a restart key press event");
self.istate = 0x8;
self.data = 0x8;
} else {
debug!("Generate a restart key release event");
self.istate = 0x8;
self.data = 0x0;
}

self.trigger_gpio_interrupt();
}

fn trigger_gpio_interrupt(&self) {
if let Some(intc) = &self.intc {
intc.lock().unwrap().set_irq(self.irq_line.unwrap());
} else if let Err(e) = self.interrupt_evt.write(1) {
error!("Failed to signal used queue: {:?}", e);
}
}
}

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);
if value != 0 {
// Now that the guest has read it, send a key release event.
self.trigger_restart_key(false);
}
} 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]) {
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()
);
}
}
}

impl Subscriber for Gpio {
fn process(&mut self, event: &EpollEvent, _event_manager: &mut EventManager) {
let source = event.fd();

match source {
_ if source == self.shutdown_efd.as_raw_fd() => {
_ = self.shutdown_efd.read();
// Send a key press event.
self.trigger_restart_key(true);
}
_ => warn!("Unexpected gpio event received: {:?}", source),
}
}

fn interest_list(&self) -> Vec<EpollEvent> {
vec![EpollEvent::new(
EventSet::IN,
self.shutdown_efd.as_raw_fd() as u64,
)]
}
}
1 change: 1 addition & 0 deletions src/devices/src/legacy/aarch64/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod gpio;
pub mod serial;
4 changes: 4 additions & 0 deletions src/devices/src/legacy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ use x86_64::serial;
#[cfg(target_arch = "aarch64")]
mod aarch64;
#[cfg(target_arch = "aarch64")]
use aarch64::gpio;
#[cfg(target_arch = "aarch64")]
use aarch64::serial;

#[cfg(target_os = "macos")]
pub use self::gic::Gic;
#[cfg(target_arch = "aarch64")]
pub use self::gpio::Gpio;
pub use self::i8042::Error as I8042DeviceError;
pub use self::i8042::I8042Device;
#[cfg(target_arch = "aarch64")]
Expand Down
11 changes: 9 additions & 2 deletions src/vmm/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -888,8 +888,8 @@ fn attach_legacy_devices(
kernel_cmdline: &mut kernel::cmdline::Cmdline,
intc: Option<Arc<Mutex<Gic>>>,
serial: Option<Arc<Mutex<Serial>>>,
_event_manager: &mut EventManager,
_shutdown_efd: Option<EventFd>,
event_manager: &mut EventManager,
shutdown_efd: Option<EventFd>,
) -> std::result::Result<(), StartMicrovmError> {
if let Some(serial) = serial {
mmio_device_manager
Expand All @@ -908,6 +908,13 @@ fn attach_legacy_devices(
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;

if let Some(shutdown_efd) = shutdown_efd {
mmio_device_manager
.register_mmio_gpio(vm, intc, event_manager, shutdown_efd)
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;
}

Ok(())
}

Expand Down
Loading

0 comments on commit ab7e3f8

Please sign in to comment.