Xen on arm SystemReady: ACPI, SMMU Passthrough, and Graviton

Xen Now Runs on arm SystemReady Platforms

Server-class arm is called SystemReady. Graviton, Ampere, Layerscape: ACPI and UEFI, firmware tables, and no device tree anywhere. Xen's arm port grew up on device-tree, and its ACPI support sat behind CONFIG_UNSUPPORTED from the day it was written. On a SystemReady SR machine, Xen did not boot at all.

It does now, in our Xen tree, with upstreaming to follow. It brings up the GIC and the ITS from the MADT, the SMMUv3 from the IORT, runs a real dom0, and hands a PCI device to a guest with the SMMU translating for it. Boot is verified on a SolidRun HoneyComb, a reference SR platform. No Linux changes were needed anywhere; the dom0 kernel is a stock distribution linux-lts.

Most of what stood in the way was not a missing feature. It was code that had never been pointed at real firmware.

Xen Rejected Firmware Tables For Being Too New

Two of the blockers were in MADT parsing. Xen checked the length of each GICC subtable for exact equality against the ACPI 5.1 and 6.0 values:

#define ACPI_MADT_GICC_LENGTH  (acpi_gbl_FADT.header.revision < 6 ? 76 : 80)

The subtable has kept growing since. Firmware built against a newer spec emits longer, perfectly valid entries, and Xen threw all of them out. What the operator sees is not a version complaint but acpi_smp_init_cpus() reporting a missing boot CPU and gicv3_acpi_init() panicking with no valid GICC entries. Linux relaxed the same macro to a minimum-length check years ago.

The second was narrower and worse. Firmware is allowed to describe CPUs that aren't enabled, and those routinely carry a zero GICR base. Xen's counting handler returned -EINVAL for any zero GICR base without checking whether the CPU was enabled, and acpi_parse_entries() aborts the whole scan on a non-zero return, so one disabled CPU turned a count into an error and panicked the hypervisor.

Xen Gave Up Before Reaching the Probe It Already Had

gic_acpi_preinit() dispatches on the MADT distributor's version field, matched for exact equality against the drivers Xen registers for GICv2, v3 and v4. ACPI also defines ACPI_MADT_GIC_VERSION_NONE, meaning the version isn't specified and should be discovered from the hardware, and firmware uses it routinely. Xen matched nothing and panicked.

Xen already knows how to do that discovery. It reads GICD_PIDR2 eleven lines into gicv3_init(). The probe simply sat downstream of a dispatch that had already given up. Worth knowing if you go looking for this yourself: the panic fires two lines before uart_init(), so without CONFIG_EARLY_PRINTK you get a hang and no output whatsoever.

Neoverse V2 Had No Spectre-BHB Mitigation, and Said Nothing About It

While auditing we found Xen's Spectre-BHB errata table stops at Neoverse V1. Neoverse V2 is absent entirely.

The shape of that bug matters more than the missing row. enable_spectre_bhb_workaround() only ever runs for an entry whose MIDR matched, so a core Xen doesn't name gets no mitigation and no warning. It is indistinguishable from a core that was checked and found safe. Linux takes the opposite position: anything not reporting CSV2.3 and not on an explicit safe list is treated as vulnerable, unknown MIDRs included.

Neoverse V2 needs a 132-iteration loop and Xen had sequences for 8, 24 and 32. We added 38 and 132, brought the MIDR lists in line with upstream, and made cores advertising FEAT_CLRBHB use it whether or not their MIDR is listed, so parts newer than the table aren't silently skipped.

A second bug fell out of that work. The loop capabilities are system-wide, so on a mixed system more than one is set, and the old code tested for 8 iterations first. A big.LITTLE pairing of Cortex-A72 with Cortex-A76 would have selected 8 where the A76 asks for 24. It now tests longest-first.

Passthrough Needed the SMMU, and the SMMU Needed the IORT

assign_device() returns -EXDEV without an IOMMU, so nothing could be handed to a guest until the SMMUv3 came up. Four things were in the way.

The driver couldn't be built with ACPI at all: arm_SMMU_V3 was gated on (!ACPI || BROKEN), and the BROKEN was earned, because the probe reached for a platform device layer Xen doesn't have and read its base address and interrupts from the device tree regardless of how the SMMU had been found. Nothing parsed the IORT, the ACPI counterpart of the device tree's iommu-map and msi-map, so there was nothing to find the SMMU with and no StreamID to program once it was up. Xen also passes through every table it doesn't regenerate, so dom0 saw the firmware's IORT, found the SMMUs and bound its own driver to them, which is harmless right up until Xen is driving them itself. Xen now rebuilds the IORT without the SMMU nodes, mapping each root complex straight at its ITS. Dropping the table isn't an option: without an IORT, Linux has no MSI domain for a PCI device and MSIs stop working entirely.

We wrote a minimal IORT parser rather than porting Linux's, which is mostly platform-device and OF glue with no counterpart in Xen. Every node is bounds-checked against the table before being followed, including the mapping array, and the walk is bounded by the node count, so a table whose output references form a loop terminates rather than spins. None of this is trusted input.

_CRS Doesn't Need an AML Interpreter

With the SMMU up and devices attached, passthrough still failed:

(XEN) 0000:00:01.0: not mapping BAR [10040, 10040] invalid position

pci_check_bar() decides whether a BAR lies inside a region its host bridge actually decodes, and it reads those regions from the device tree's ranges. An ACPI bridge has no such node, so every BAR was rejected. The regions live in the bridge's _CRS, which MCFG doesn't carry, and _CRS is AML. Xen has no interpreter, deliberately: executing firmware bytecode in the hypervisor is an attack surface x86 has always left to dom0.

You only need to execute _CRS if it's a Method. For a PCI host bridge it is normally a Name holding a fixed ResourceTemplate, and extracting that needs a DSDT walk, not an interpreter. Xen now walks the DSDT itself, understanding only the constructs required to reach that Name. Anything else stops the walk of the term list it's in, which costs the windows for that bridge and nothing more, and the check then rejects BARs exactly as before, refusing passthrough rather than allowing it on unvalidated addresses.

The result: Secure PCI Passthrough on ACPI-only arm Hosts

A guest on an ACPI-only arm host now gets a real PCI device assigned to it, binds its own unmodified driver to that device, and brings the interface up. The SMMU translates every DMA that device issues, so it can only reach memory belonging to the guest that owns it. That containment is the reason passthrough is worth having, and it is what none of the pieces above worked without.

None of this is specific to one machine or one vendor. It is firmware-table handling that every SystemReady SR platform needs, and it is the difference between arm servers being a platform Xen tolerates and one it runs on. It lives in our tree today.

The most immediate consequence is AWS Graviton. Our upcoming release supports it on bare-metal instances, and only those: a hypervisor needs EL2, and Graviton's virtualized instance types don't hand it out. Graviton4 is a Neoverse V2 part, which is what makes the missing Spectre-BHB mitigation above a live concern rather than a curiosity.

Want to see how Edera's arm64 support fits into your infrastructure? Get in touch with our team to talk through your Graviton deployment plans, bare-metal capacity needs, or device assignment requirements.

Cute cartoon axolotl with a light blue segmented body, big eyes, and dark gray external gills.

You know you wanna

Let’s solve this together