IOMMU¶
Introduction¶
An IOMMU is a device which lies in the interconnect between the CPUs, main memory, and devices, tipically those behind a hub such as a PCIe Root Complex. It is a cornerstone piece for device virtualization, because it allows to configure how devices access memory and generate interrupts.
In systems without an IOMMU, DMA capable devices are able to access system memory and other devices freely. This is a problem if it is desired to expose physical devices to virtual machines, because the guests utilizes guest physical addresses, which are mapped to system physical addresses by the CPU using the nested page tables, but the devices themselves are not aware of this map, so they won’t be accessing to memory correctly.
IOMMUs allow to specify for devices the permissions they have for interacting with main memory, and how such accesses will be translated. Therefore, when a device is passed through to a virtual machine, the memory used by the virtual machine is pinned to memory (that is, it will be mapped from the beginning, and won’t be swapped out, because very few devices support faulting on unmapped pages and doing that from the OS requires special support), and the IOMMU will be given a page table directory with the exact same maps as the nested page tables used by the CPU.
Moreover, IOMMUs allows remapping interrupts. This is mostly of interest in x86, where MSIs are contiguously allocated by devices, and for making them work with X2APICs with IDs greater than 255, although we don’t make use of this mechanism.
x86 IOMMUs¶
The IOMMUs that can be found in x86 platforms are the Intel IOMMU (Intel VT-d), the AMD IOMMU (AMD-VI), and the virtio IOMMU (virtio-iommu).
In x86 platforms, IOMMUs can also be programmed to perform double-translation for devices. While in passthrough mode, the Device Physical Addresses (DPA) are equal to System Physical Addresses (SPA), and in single-translation mode discussed previously, DPAs are translated to SPAs. However, in double-translation, Device Virtual Addresses (DVA) are translated to DPAs using page tables similar to those of the host CPU, and then DPAs are passed through to SPAs or translated to SPAs using the IOMMU native page tables. While in order to make use of this feature, the device has to be able to issue memory requests upstream using an identifier named the PASID, which selects which double-translation map to use, some IOMMUs (AMD IOMMU) allow to treat the lack of PASIDs (which is the most common case for memory requests) as equal to having a PASID = 0, which allows for some neat tricks (e.g. setting a task’s page directory to a device, or sharing the nested page tables with the device).
x86 IOMMUs also support the concept of interrupt posting, which is useful when passing through a device to a virtual machine. Without interrupt posting,
The device generates an IRQ
The hypervisor handles the IRQ (implies that, if running a guest, it has to exit)
Handle the IRQ, modify the APIC fields, enqueue an IRQ for the virtual CPU
When the virtual CPU is about to execute the guest, inject the interrupt
But, with interrupt posting,
The IOMMU is provided the virtual APIC page
The device generates an IRQ
The IOMMU receives the IRQ, modifies the APIC fields in the virtual APIC page
If the virtual CPU is running, don’t do anything at all, otherwise, write the IRQ in a log
Device isolation¶
It is important to take into account that the IOMMU can only process upstream requests which pass through it. However, there are two problems:
Devices can interact with each other directly, and those requests never make it to the IOMMU (Peer-to-peer transactions)
Per-device IOMMU configuration requires a device identifier (for IOMMUs that act as Translation Agents for PCIe Root Complexes, this is just the PCI BDF). However, a set of devices can be using the same identifier (e.g. devices behind a PCIe-to-PCI bridge).
Therefore, the operating system cannot work directly with devices, but with groups of IOMMU devices. For simplicity, we assume that in IOMMU groups, both effects occur.
In the case of PCI, devices interact peer-to-peer freely by default, e.g. when a device issues a memory request for another device by targeting its BAR. With the PCIe ACS (Access Control Services) capability, the operating system can discover and configure whether functions of a multi-function can do P2P requests or redirect them upstream, whether root ports must redirect upstream P2P requests from its device to another device, and whether downstream ports must redirect upstream P2P requests between its ports and to other devices, along with other options.
With this, taratos creates the smallest possible IOMMU groups, where P2P between the devices is allowed, and the requests received by the IOMMU might have the device identifier aliased.
When passing through a device to a virtual machine or to a DMA domain (see below), its entire IOMMU group must be considered.
DMA Domains¶
Most of the times, more than 1 IOMMU group has to be assigned to a virtual machine, or, more generally, it is desired a set of IOMMU groups to share the same memory translations. A DMA domain is such a set.
API¶
Public facing API¶
-
enum IOMMUDMADomainType¶
An IOMMU DMA domain is a set of IOMMU groups that share the same DMA memory translations.
IOMMU_DMA_DOMAIN_PASSTHROUGH: device physical address == system physical address
IOMMU_DMA_DOMAIN_TRANSLATED: device physical address is translated to a system physical address
IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED: address of a device memory request that includes a PASID is treated as a device virtual address, will be translated to a device physical address using supplied CPU page tables and then to a system physical address using the IOMMU page tables. If the IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED_NO_PASID_AS_PASID_0 flag is set, the IOMMU can treat the lack of a PASID as PASID = 0.
Values:
-
enumerator IOMMU_DMA_DOMAIN_PASSTHROUGH¶
-
enumerator IOMMU_DMA_DOMAIN_TRANSLATED¶
-
enumerator IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED¶
-
enumerator IOMMU_DMA_DOMAIN_PASSTHROUGH¶
-
IOMMU_DMA_DOMAIN_PASSTHROUGH_RESTRICTED¶
(Passthrough domains only) DMA domain is restricted
-
IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED_PASSTHROUGH¶
(Double translated domains only) Passthrough mode, DPA = SPA
-
IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED_RESTRICTED¶
(Double translated domains only) Restricted mode in passthrough (no reads or writes)
-
IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED_NO_PASID_AS_PASID_0¶
(Double translated domains only) Treat no PASID as PASID = 0
-
struct iommu_dma_domain *iommu_dma_domain_create(enum IOMMUDMADomainType type, unsigned int flags, struct iommu *iommu)¶
Create a DMA domain.
Use
iommu_dma_domain_group_addto associate IOMMU groups with this domain, andiommu_dma_domain_group_delto disassociate them.- Parameters:
type – type of the DMA domain
flags – flags, depend on the DMA domain
iommu – the IOMMU to which to associate the DMA domain
- Returns:
A pointer to the DMA domain, or if the IOMMU does not support the - combination, if could not allocate the DMA domain, any error code if
iommu_ops.dma_domain_initfails.
-
int iommu_dma_domain_group_add(struct iommu_dma_domain *dma_domain, struct iommu_group *group)¶
Add an IOMMU group to an IOMMU domain.
- Parameters:
dma_domain – the IOMMU DMA domain
group – the IOMMU group
- Returns:
0 on success, otherwise, error code.
-
int iommu_dma_domain_group_del(struct iommu_group *group)¶
Remove an IOMMU group from its IOMMU domain.
- Parameters:
group – the IOMMU group
- Returns:
0 on success, error code on failure
-
void iommu_dma_domain_destroy(struct iommu_dma_domain *dma_domain)¶
Destroy an IOMMU DMA domain.
The caller is responsible for making sure there’s no IOMMU groups in the domain, and that it won’t be used anymore.
- Parameters:
dma_domain – the IOMMU DMA domain
-
int iommu_dma_domain_passthrough_restrict(struct iommu_dma_domain *dma_domain)¶
(For passthrough domains) Restrict access to memory for devices in the domain.
- Parameters:
dma_domain – The DMA domain
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_passthrough_unrestrict(struct iommu_dma_domain *dma_domain)¶
(For passthrough domains) Allow access to memory for devices in the domain.
- Parameters:
dma_domain – The DMA domain
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_translated_map(struct iommu_dma_domain *dma_domain, unsigned long long dpstart, paddr_t pstart, bool read, bool write, bool huge)¶
(For translated domains) Map a range of Device Physical Addresses to a range of System Physical Addresses.
- Parameters:
dma_domain – The DMA domain
dpstart – Start of the Device Physical Address range
pstart – Start of the Physical Address range
read – Whether reads are allowed
write – Whether writes are allowed
huge – If true, map a huge page.
dpstartandpstartmust be huge page aligned. Otherwise, map a regular page.
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_translated_unmap(struct iommu_dma_domain *dma_domain, unsigned long long dpstart, size_t size)¶
(For translated domains) Unmap a range of Device Physical Addresses.
The IOMMU’s TLB and the remote IOTLBs, if any, will be flushed for the specified range.
- Parameters:
dma_domain – The DMA domain
dpstart – Start of the Device Physical Address range
size – Size of the range
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_double_translated_map(struct iommu_dma_domain *dma_domain, unsigned long long dpstart, paddr_t pstart, bool read, bool write, bool huge)¶
(For doubly translated domains) Map a range of Device Physical Addresses to a range of System Physical Addresses.
- Parameters:
dma_domain – The DMA domain
dpstart – Start of the Device Physical Address range
pstart – Start of the Physical Address range
read – Whether reads are allowed
write – Whether writes are allowed
huge – If true, map a huge page.
dpstartandpstartmust be huge page aligned. Otherwise, map a regular page.
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_double_translated_unmap(struct iommu_dma_domain *dma_domain, unsigned long long dpstart, size_t size)¶
(For doubly translated domains) Unmap a range of Device Physical Addresses.
The IOMMU’s TLB and the remote IOTLBs, if any, will be flushed for the specified range.
- Parameters:
dma_domain – The DMA domain
dpstart – Start of the Device Physical Address range
size – Size of the range
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_double_translated_passthrough(struct iommu_dma_domain *dma_domain, bool passthrough, bool restricted)¶
(For doubly translated domains) Set up whether to do passthrough for Device Physical Addresses to System Physical Addresses.
Maps won’t be lost.
- Parameters:
dma_domain – the IOMMU DMA domain
passthrough – Whether to passthrough or not.
restricted – If
passthroughis true, do not allow reads not writes
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_double_translated_pasid_page_directory_set(struct iommu_dma_domain *dma_domain, unsigned int pasid, paddr_t page_directory)¶
(For doubly translated domains) Set a page directory for a given PASID.
- Parameters:
dma_domain – the IOMMU DMA domain
pasid – the PASID
page_directory – the page directory root
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_double_translated_pasid_page_directory_remove(struct iommu_dma_domain *dma_domain, unsigned int pasid)¶
(For doubly translated domains) Remove a page directory for a given PASID.
- Parameters:
dma_domain – the IOMMU DMA domain
pasid – the PASID
- Returns:
0 on success, error code on failure
-
int iommu_dma_domain_double_translated_flush(struct iommu_dma_domain *dma_domain, unsigned int pasid, unsigned long long dvstart, size_t size, bool invalidate_directories)¶
(For doubly translated domains) Flush a range of Device Virtual Addresses.
- Parameters:
dma_domain – the IOMMU DMA domain
pasid – the PASID
dvstart – start of the DVA range
size – size of the DVA range
invalidate_directories – whether to invalidate upper-level page tables
- Returns:
0 on success, error code on failure
Private API¶
-
struct iommu_ops¶
Public Members
-
int (*device_check)(struct iommu *iommu, struct device *device)¶
Check whether the device belongs to the IOMMU.
The implementation can use
device#busto match the device to a bus.- Param iommu:
the IOMMU
- Param device:
The device
- Return:
0 if match
-
int (*device_setup)(struct device *device)¶
Set up a device.
- Param device:
the device
- Return:
0 on success, error code on failure
-
int (*group_setup)(struct iommu_group *group)¶
Set up an IOMMU group.
- Param group:
the IOMMU group
- Return:
0 on success, error code on failure
-
bool (*dma_domain_supported)(struct iommu *iommu, enum IOMMUDMADomainType type, unsigned int flags)¶
- Param iommu:
the IOMMU
- Param type:
the DMA domain type
- Param flags:
the DMA domain flags
- Return:
if the DMA domain is supported, otherwise
-
int (*dma_domain_init)(struct iommu_dma_domain *dma_domain)¶
Initialize the DMA domain.
The IOMMU implementation must set
iommu_dma_domain.ops.- Param dma_domain:
the DMA domain
- Return:
0 on success, error code on failure
-
int (*dma_domain_destroy)(struct iommu_dma_domain *dma_domain)¶
Destroy the DMA domain.
- Param dma_domain:
the DMA domain
-
int (*device_check)(struct iommu *iommu, struct device *device)¶
-
struct iommu¶
Represents an IOMMU.
Public Members
-
const char *name¶
Name of the IOMMU.
-
void *private_data¶
Private data of the IOMMU.
-
bool initialized¶
When initialized = true, the IOMMU is ready to set up groups and devices, Otherwise, those will be initialized by the device layer, but their real initialization by their IOMMU driver will be deferred for later.
-
const char *name¶
-
struct iommu_dma_domain¶
Public Members
-
enum IOMMUDMADomainType type¶
Type of the domain.
-
unsigned int flags¶
Flags of the domain.
-
struct iommu_dma_domain_ops *ops¶
Operations of the domain.
-
void *iommu_private_data¶
Private data for the IOMMU implementation.
-
enum IOMMUDMADomainType type¶
-
struct iommu_dma_domain_ops¶
IOMMU DMA domain operations.
Public Members
-
int (*dma_domain_passthrough_group_add)(struct iommu_dma_domain *dma_domain, struct iommu_group *group)¶
(For passthrough DMA domains) Add the group into the domain.
- Param dma_domain:
the IOMMU DMA domain
- Param group:
the IOMMU group
- Return:
0 on success, error code on failure
-
int (*dma_domain_passthrough_group_del)(struct iommu_dma_domain *dma_domain, struct iommu_group *group)¶
(For passthrough DMA domains) Remove the group from the domain.
- Param dma_domain:
the IOMMU DMA domain
- Param group:
the IOMMU group
- Return:
0 on success, error code on failure
-
int (*dma_domain_passthrough_restrict)(struct iommu_dma_domain *dma_domain)¶
(For passthrough DMA domains) Disallow reads and writes from and to memory for the devices in the domain.
- Param dma_domain:
The DMA domain
- Return:
0 on success, error code on failure
-
int (*dma_domain_passthrough_unrestrict)(struct iommu_dma_domain *dma_domain)¶
(For passthrough DMA domains) Allow reads and writes from and to memory for the devices in the domain.
- Param dma_domain:
The DMA domain
- Return:
0 on success, error code on failure
-
int (*dma_domain_translated_group_add)(struct iommu_dma_domain *dma_domain, struct iommu_group *group)¶
(For translated DMA domains) Add the group into the domain.
- Param dma_domain:
the IOMMU DMA domain
- Param group:
the IOMMU group
- Return:
0 on success, error code on failure
-
int (*dma_domain_translated_group_del)(struct iommu_dma_domain *dma_domain, struct iommu_group *group)¶
(For translated DMA domains) Remove the group from the domain.
- Param dma_domain:
the IOMMU DMA domain
- Param group:
the IOMMU group
- Return:
0 on success, error code on failure
-
int (*dma_domain_translated_map)(struct iommu_dma_domain *dma_domain, unsigned long long dpstart, paddr_t pstart, bool read, bool write, bool huge)¶
(For translated DMA domains) Set up a Device Physical Address to System Physical Address translation.
Caller must make sure there is no overlapping with previously mapped ranges.
- Param dma_domain:
the IOMMU DMA domain
- Param dpstart:
Start of device physical address
- Param pstart:
Start of system physical address
- Param read:
Page is readable
- Param write:
Page is writable
- Param huge:
If true, map a huge page, otherwise, map a normal page
- Return:
0 on success, error code on failure
-
int (*dma_domain_translated_unmap)(struct iommu_dma_domain *dma_domain, unsigned long long dpstart, size_t size)¶
(For translated DMA domains) Remove a Device Physical Address translation range.
- Param dma_domain:
the IOMMU DMA domain
- Param dpstart:
Start of device physical address
- Param size:
Size of the region
- Return:
0 on success, error code on failure
-
int (*dma_domain_double_translated_group_add)(struct iommu_dma_domain *dma_domain, struct iommu_group *group)¶
(For double translated DMA domains) Add the group into the domain.
- Param dma_domain:
the IOMMU DMA domain
- Param group:
the IOMMU group
- Return:
0 on success, error code on failure
-
int (*dma_domain_double_translated_group_del)(struct iommu_dma_domain *dma_domain, struct iommu_group *group)¶
(For double translated DMA domains) Remove the group from the domain.
- Param dma_domain:
the IOMMU DMA domain
- Param group:
the IOMMU group
- Return:
0 on success, error code on failure
-
int (*dma_domain_double_translated_map)(struct iommu_dma_domain *dma_domain, unsigned long long dpstart, paddr_t pstart, bool read, bool write, bool huge)¶
(For double translated DMA domains) Set up a Device Physical Address to System Physical Address translation.
Caller must make sure there is no overlapping with previously mapped ranges.
- Param dma_domain:
the IOMMU DMA domain
- Param dpstart:
Start of device physical address
- Param pstart:
Start of system physical address
- Param read:
Page is readable
- Param write:
Page is writable
- Param huge:
If true, map a huge page, otherwise, map a normal page
- Return:
0 on success, error code on failure
-
int (*dma_domain_double_translated_unmap)(struct iommu_dma_domain *dma_domain, unsigned long long dpstart, size_t size)¶
(For double translated DMA domains) Remove a Device Physical Address translation range.
- Param dma_domain:
the IOMMU DMA domain
- Param dpstart:
Start of device physical address
- Param size:
Size of the region
- Return:
0 on success, error code on failure
-
int (*dma_domain_double_translated_passthrough)(struct iommu_dma_domain *dma_domain, bool passthrough, bool restricted)¶
(For double translated DMA domains) Set up the passthrough settings.
- Param dma_domain:
the IOMMU DMA domain
- Param passthrough:
if true, set up DPA = SPA, otherwise, use the maps
- Param restricted:
if true and
is true, read and write is not allowed, otherwise, both are allowed- Return:
0 on success, error code on failure
-
int (*dma_domain_double_translated_pasid_page_directory_set)(struct iommu_dma_domain *dma_domain, unsigned int pasid, paddr_t page_directory)¶
(For double translated DMA domains) Set up a CPU page directory for a PASID.
- Param dma_domain:
the IOMMU DMA domain
- Param pasid:
the PASID
- Param page_directory:
the page directory
- Return:
0 on success, error code on failure
-
int (*dma_domain_double_translated_pasid_page_directory_remove)(struct iommu_dma_domain *dma_domain, unsigned int pasid)¶
(For double translated DMA domains) Remove a CPU page directory for a pasid.
- Param dma_domain:
the IOMMU DMA domain
- Param pasid:
the PASID
- Return:
0 on success, error code on failure
-
int (*dma_domain_double_translated_flush)(struct iommu_dma_domain *dma_domain, unsigned int pasid, unsigned long long dvstart, size_t size, bool invalidate_directories)¶
(For double translated DMA domains) Flush a range of Device Virtual Addresses.
- Param dma_domain:
the IOMMU DMA domain
- Param pasid:
the PASID
- Param dvstart:
start of the DVA range
- Param size:
size of the range
- Param invalidate_directories:
whether to invalidate directories
- Return:
0 on success, error code on failure
-
int (*dma_domain_passthrough_group_add)(struct iommu_dma_domain *dma_domain, struct iommu_group *group)¶