Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interrupt abstractions #71

Merged
merged 3 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ address range, IRQ number, etc)

## Design

### I/O

The virtual device model is built around four traits, `DevicePio` and
`MutDevicePio` for
[Programmed I/O](https://en.wikipedia.org/wiki/Programmed_input%E2%80%93output)
Expand Down Expand Up @@ -42,8 +44,40 @@ that there’s a virtual device registered on the bus for the requested address,
and finally sends the request to that device.
![vm-device](https://user-images.githubusercontent.com/241037/143853115-b1526028-6836-4845-a311-71cf989c60ef.png)

### Interrupts

Interrupt configuration is built around the `Interrupt` and `InterruptSourceGroup`
traits. These traits allow device control code that is developed in separate
crates from the VMM (which typically implements the interrupt mechanisms) to
configure interrupt delivery to the guest VM without having a dependency on the
implementation of the interrupt mechanism.
The `EdgeInterrupt` and `LevelInterrupt` are traits that provide interrupt
assertion mechanisms and can be used by device implementations directly in order
to inject interrupts in the guest VM.

The `Interrupt` trait provides methods that are used by devices to enable and
disable interrupt generation by registering the interrupt with the VMM. Access
to additional interrupt properties is defined in new super-traits.
`ConfigurableInterrupt` allows for devices to send or receive interrupt
configuration parameters to/from the implementation inside the VMM. This is useful
when devices need to specify custom data that the VMM will use when delivering the
interrupt (e.g. MSI device id, PCI INTx pin etc).
`MaskableInterrupt` is also defined as a super-trait for use with interrupts that
can be masked/unmasked.

An `InterruptSourceGroup` stores a collection of interrupts of the same type. It
is the interface through which a device may request or release interrupts and
perform group related actions like enabling or disabling all interrupts at once.
Each device that generates interrupts can be assigned one or more
`InterruptSourceGroup`s (depending on the types of interrupts it uses or logical
grouping). The following diagram depicts the interaction between the components
that use the interrupt interface:

![vm-device-interrupts](https://user-images.githubusercontent.com/86006646/148783015-fea49a7c-cff8-4ec7-8766-00b0baed41c5.png)

## Usage

### I/O
A device is usually attached to a particular bus and thus needs to implement a
trait of only one type. For example, serial port on x86 is a PIO device, while
VirtIO devices use MMIO. It’s also possible for a device to implement both. Once
Expand All @@ -67,6 +101,44 @@ address range to the device. The requests are dispatched by the client code, for
example when handling VM exits, using `IoManager`'s methods `pio_read`,
`pio_write`, `mmio_read` and `mmio_write`.

### Interrupts

To allow configuration of interrupts, a VMM must implement the `Interrupt` and
`InterruptSourceGroup` traits for the interrupt mechanisms that the device
requires. Implementation is machine or VMM specific and may depend on the types
and number of IRQ chips that the machine has or interrupt delivery mechanisms
(e.g. `EventFd`s).
The device interrupt configuration code generally does not concern itself with
the actual implementation of the interrupts and will be initialized with one or
more `InterruptSourceGroup`s by the VMM.

In order to allow devices to inject interrupt lines in the guest, a VMM must
also implement either the `EdgeInterrupt` or `LevelInterrupt` traits, depending
on the type of interrupt assertion mechanism that is supported. Devices use
objects that have these traits to signal an interrupt to the guest.

The device configuration code may define constraints for the types of interrupts
that it needs by combining the supertraits defined into trait bounds (e.g. if it
needs a `ConfigurableInterrupt` that can receive the `LegacyIrqConfig`
configuration struct). MSI and legacy interrupt traits are added in this crate
for ease of use. Configuration of these interrupt types are standardised.
`MsiInterrupt` can also be used for MSI-X interrupts. These traits only define
the configuration bounds for these interrupt types. `EdgeInterrupt` or
`LevelInterrupt` will still need to be implemented in order to allow devices
to use these interrupts. MSI interrupts are considered edge-triggered while
`LegacyInterrupt` can be either edge-triggered (typically in the case of ISA
interrupts) or level triggered (in the case of INTx interrupts).

In order to have access to the underlying notification mechanisms used by the
hypervisor, the device configuration may use the `AsRefTriggerNotifier` and the
`AsRefResampleNotifier` conversion traits and specify the `NotifierType` associated
type. This type defines the interrupt delivery mechanism and is specific to the
Hypervisor (e.g. KVM irqfd, Xen evtchn etc).

One example of this requirement is the development of a VFIO device. Since VFIO
can trigger a KVM irqfd directly, the VFIO device would need to get access to the
underlying irqfd in order to register it with VFIO.

## Examples

### Implementing a simple log PIO device
Expand Down Expand Up @@ -119,3 +191,4 @@ This project is licensed under either of:

- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0
- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause)

2 changes: 1 addition & 1 deletion coverage_config_x86_64.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"coverage_score": 91.4,
"coverage_score": 87.3,
"exclude_path": "",
"crate_features": ""
}
39 changes: 39 additions & 0 deletions src/interrupt/legacy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (C) 2021 Amazon.com, Inc. or its affiliates.
// All Rights Reserved.

// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause

//! Traits and Structs to manage legacy interrupt sources for devices.
//!
//! Legacy interrupt sources typically include pin based interrupt lines.

use crate::interrupt::ConfigurableInterrupt;

/// Definition for PCI INTx pins.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
pub enum IntXPin {
/// INTA
IntA = 0x1,
/// INTB
IntB = 0x2,
/// INTC
IntC = 0x3,
/// INTD
IntD = 0x4,
}

/// Standard configuration for Legacy interrupts.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct LegacyIrqConfig {
/// Input of the system interrupt controllers the device's interrupt pin is connected to.
/// Implemented by any device that makes use of an interrupt pin.
pub interrupt_line: Option<u32>,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be used by PCI devices to set their interrupt line field or are there other uses? Typically, the interrupt line field in the config space is just a normal R/W field that is written during boot by firmware. So device emulation should not be concerned with this value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a PCI device using an INTx interrupt interrupt_line may not be of significance for device emulation. But this value is of significance for setting up other components like the MPtable.
Also you would use interrupt_line to configure a specific line that you want the interrupt assigned to (see https://github.com/alsrdn/firecracker/blob/ab0c9642162f8c749c516d02b43b144a91baa33e/src/vmm/src/device_manager/legacy.rs#L89).

/// Specifies which interrupt pin the device uses.
pub interrupt_pin: Option<IntXPin>,
}

/// Trait for defining properties of Legacy interrupts.
pub trait LegacyInterrupt: ConfigurableInterrupt<Cfg = LegacyIrqConfig> {}

/// Blanket implementation for Interrupts that use a LegacyIrqConfig.
impl<T> LegacyInterrupt for T where T: ConfigurableInterrupt<Cfg = LegacyIrqConfig> {}
Loading