##### 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 ================= .. doxygenenum:: IOMMUDMADomainType .. doxygendefine:: IOMMU_DMA_DOMAIN_PASSTHROUGH_RESTRICTED .. doxygendefine:: IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED_PASSTHROUGH .. doxygendefine:: IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED_RESTRICTED .. doxygendefine:: IOMMU_DMA_DOMAIN_DOUBLE_TRANSLATED_NO_PASID_AS_PASID_0 .. doxygenfunction:: iommu_dma_domain_create .. doxygenfunction:: iommu_dma_domain_group_add .. doxygenfunction:: iommu_dma_domain_group_del .. doxygenfunction:: iommu_dma_domain_destroy .. doxygenfunction:: iommu_dma_domain_passthrough_restrict .. doxygenfunction:: iommu_dma_domain_passthrough_unrestrict .. doxygenfunction:: iommu_dma_domain_translated_map .. doxygenfunction:: iommu_dma_domain_translated_unmap .. doxygenfunction:: iommu_dma_domain_double_translated_map .. doxygenfunction:: iommu_dma_domain_double_translated_unmap .. doxygenfunction:: iommu_dma_domain_double_translated_passthrough .. doxygenfunction:: iommu_dma_domain_double_translated_pasid_page_directory_set .. doxygenfunction:: iommu_dma_domain_double_translated_pasid_page_directory_remove .. doxygenfunction:: iommu_dma_domain_double_translated_flush =========== Private API =========== .. doxygenstruct:: iommu_ops :members: .. doxygenstruct:: iommu :members: .. doxygenstruct:: iommu_dma_domain :members: .. doxygenstruct:: iommu_dma_domain_ops :members: