What an IOMMU group is
The IOMMU finds a device's tables by its requester ID (bus, device
and function, such as 03:00.0; see
request identity). It can only keep
two devices apart if their DMA arrives with different requester IDs
and actually arrives. Linux's VFIO documentation puts it
this way: a group is "a set of devices which is isolatable from all
other devices in the system", and groups are "the unit of ownership
used by VFIO". A VM can get a group; it cannot get half of one.
One requester, or two
Several things make two devices look like one requester:
-
A PCIe-to-PCI bridge. Conventional PCI has no
requester IDs on the wire, so the bridge sends its devices' DMA as
its own: Linux's
pci_for_each_dma_aliasuses the bridge's secondary bus with device and function 0. Intel's VT-d says such devices "can only be collectively assigned to a single domain" (§3.12.2). The lab's bridge machine shows it: both cards may appear as02:00.0. -
A DMA alias. Some devices send DMA with another
function's number. Linux records it with
pci_add_dma_alias; for examplequirk_dma_func1_aliasnotes that the Marvell 88SE9123 "uses function 1 as the requester ID for DMA". The IOMMU must accept both IDs, so both functions go in one group. - Phantom functions. A PCIe device may use unused function numbers to have more requests outstanding; the IOMMU then needs an entry for each (VT-d §3.12.5). The lab does not model them.
SR-IOV goes the other way: each virtual function (VF) has its own
requester ID (VT-d §3.12.6), and Linux treats a VF as a
single-function device (iov.c sets
multifunction = 0). So with ACS above the card, each
VF gets its own group and can go to a different VM.
Peer-to-peer DMA, and what ACS changes
A device's DMA does not have to go up to the root complex. A PCIe switch routes by address: a write from one port to a memory range below another port can turn around inside the switch. Two functions of one card may talk inside the card, and devices on a conventional PCI bus all share one bus. In each case the IOMMU never sees the write.
Access Control Services (ACS) is the PCIe capability that makes
ports send such traffic upstream instead. The controls, with their
bits in the ACS Control register (pci_regs.h):
| Control | Bit | What it is for | Linux's grouping |
|---|---|---|---|
| Source Validation (SV) | 0x0001 | Checks the requester ID of requests from below, so that, in Linux's words, "devices cannot spoof their requester ID" (VT-d §3.12.3 recommends ACS on root ports for this) | Required |
| Translation Blocking (TB) | 0x0002 | Blocks memory requests whose address type is not "untranslated" (VT-d §4.2.2) | Not required; Linux turns it on for external-facing or untrusted ports, or with ATS off |
| P2P Request Redirect (RR) | 0x0004 | Sends peer-to-peer requests upstream instead of across | Required |
| P2P Completion Redirect (CR) | 0x0008 | The same for completions (the answers to reads) | Required |
| Upstream Forwarding (UF) | 0x0010 | Linux: "all transactions are forwarded upstream, even as it passes through a bridge where the target device is downstream" | Required |
| P2P Egress Control (EC) | 0x0020 | Controls peer traffic port by port, through an Egress Control Vector; Linux counts it, with RR and CR, as a redirect control | Not required |
| Direct Translated P2P (DT) | 0x0040 | Concerns peer requests that carry a translated address | Not required; Linux does not turn it on by default |
Linux's own peer-to-peer DMA code agrees on the effect: if RR, CR or
EC is set on a port on the path, "traffic between the devices will
go through the host bridge" (p2pdma.c). The lab models
a write: RR decides at the port where it enters the switch, and UF
at a port that would send it straight back down to another function
of the same card.
How Linux builds groups
pci_device_group() in drivers/iommu/iommu.c
runs for every PCI device, bridges first. For one device it:
-
Follows its DMA aliases up the tree
(
pci_for_each_dma_alias). Behind a PCIe-to-PCI bridge it continues from the bridge, and joins the bridge's group. -
Walks up, one bridge at a time, until the whole path from that
bridge to the root has ACS (
pci_acs_path_enabledwithREQ_ACS_FLAGS, which is SV, RR, CR and UF). Every bridge it passes without ACS pulls the device into that bridge's group. -
Joins the group of any device it is a DMA alias of
(
get_pci_alias_group), then of any other function in the same slot when neither has ACS (get_pci_function_alias_group). - Otherwise it gets a new group.
Which devices "have ACS" follows the PCIe type
(pci_acs_enabled): root ports and switch downstream
ports need the capability; a single-function endpoint passes without
one, since it has no peer inside it to reach; a function of a
multifunction device needs it; conventional PCI devices and
PCIe-to-PCI bridges never pass. A control a port does not implement
counts as hard-wired on. Some chipsets isolate without the standard
capability, and quirks (pci_dev_acs_enabled[] in
quirks.c) answer for them.
A known gap. The walk checks the path above each device, not its neighbours. Choose the switch machine in the lab: without ACS on the downstream ports, Linux 7.3 puts the network card and the NVMe drive in different groups, although a write between them turns around inside the switch. Jason Gunthorpe's patch series (September 2025) reports exactly this topology and proposes a rework; the grouping code in Linux 7.3 is still the version described here. Until then, treat a switch without ACS as one group, whatever sysfs says.
What VFIO needs, and why the ACS override is unsafe
VFIO takes ownership of the whole group
(iommu_group_claim_dma_owner). It refuses while any
device in the group is bound to a driver that does its own DMA
through the kernel. Every device must have no driver, or one marked
driver_managed_dma: vfio-pci,
pci-stub, or pcieport on bridges. In the
VFIO documentation's example, assigning 06:0d.0 means
also binding 06:0d.1, behind the same PCIe-to-PCI
bridge. A graphics card's HDMI audio function is the everyday case.
The "ACS override" is a kernel patch proposed in 2013 that adds a
pcie_acs_override= option to assume ACS where the
hardware has none. It was never merged, but patched kernels that
carry it are still passed around for passthrough. It splits the groups and VFIO accepts them, but the
switch still turns the write around: the VM's device can reach a
device the host or another VM still uses, and the IOMMU never
checks it. On the list, Bjorn Helgaas worried it was "an easy way to
pretend isolation exists". Mainline's
pci=config_acs= changes real ACS controls on ports that
implement them; the kernel documentation warns that turning them off
"may put more devices in an IOMMU group".
On a real machine
Not run by this site. Read-only commands; check their output against your own kernel's version.
ls /sys/kernel/iommu_groups/
ls /sys/bus/pci/devices/0000:03:00.0/iommu_group/devices
sudo lspci -vvv -s 00:01.0 | grep -A2 'Access Control'
The first lists the groups, the second the devices sharing
03:00.0's group, and the third the root port's ACS
capability (ACSCap) and controls (ACSCtl),
if it has one.
Where this fits
Request identity and the PCIe setup come first: request identity covers requester IDs and aliases as test cases, and PCIe setup what a device needs before it may send DMA at all. Next, shared page tables and the virtual IOMMU assume a device already assigned to a VM, which is the group this page gets you.
Sources
-
Linux 7.3, read at commit
80a4e3ad(these files are unchanged in 7.3-rc5):drivers/iommu/iommu.c(pci_device_group,REQ_ACS_FLAGS,iommu_group_claim_dma_owner),drivers/pci/pci.c(pci_acs_enabled,pci_acs_path_enabled,pci_std_enable_acs),drivers/pci/search.c,drivers/pci/quirks.c,drivers/pci/iov.c,drivers/pci/p2pdma.c,drivers/pci/pci-driver.c(pci_dma_configure) andinclude/uapi/linux/pci_regs.h. -
VFIO, Linux kernel documentation:
groups, devices and IOMMUs; and the
kernel parameters
pci=config_acs=anddisable_acs_redir=. - Intel Virtualization Technology for Directed I/O, revision 5.0, D51397-017 (publisher PDF): §3.12 identifying the origin of DMA requests, §3.17 root-complex peer to peer, §4.2.2 Translation Blocking.
- Jason Gunthorpe, [PATCH v3 00/11] Fix incorrect iommu_groups with PCIe ACS, 5 September 2025.
- Re: [PATCH] pci: Enable overrides for missing ACS capabilities, linux-kernel, June 2013.
- ACS itself is defined in the PCI Express Base Specification (section 6.12 in revision 3.0, the section Linux's comments cite). This page did not use the specification directly: the controls are described from Linux and VT-d.