The Linux PCI Endpoint framework lets a Linux system act as the
peripheral side of a PCI link instead of the host. It runs on SoCs whose
PCIe controller supports Endpoint mode, with Linux running on the
Endpoint device itself. The kernel there uses the framework to set up the
PCI config space (vendor and device IDs, class), expose BARs backed by
local memory, negotiate MSI and MSI-X, map host memory, and raise
interrupts to the host. The personality the Endpoint presents is provided
by an Endpoint Function driver, for example pci-epf-test or pci-epf-nvme,
bound to an Endpoint Controller (EPC) driver through configfs. This is how
a Linux SoC can be turned into a device such as an NVMe drive or a test
function that a separate host enumerates as an ordinary PCI device.
Working on this code today requires real Endpoint-capable hardware and a
second host machine connected over a physical PCIe link. Such setups are
not widely available and are impractical for continuous integration, so
the framework, EPC drivers, and Endpoint Function drivers cannot be
exercised in a plain QEMU or CI environment.
This series closes that gap by emulating a PCIe Endpoint Controller and a
PCIe Endpoint Function in QEMU. It lets developers run the Linux Endpoint
stack, both EPC drivers and Endpoint Function drivers such as
pci-epf-test, entirely inside QEMU without real Endpoint hardware.
Two devices are added:
- pcie-ep-ctrl (hw/misc): a SysBus platform device that emulates the
Endpoint Controller. It exposes an MMIO register interface
compatible with the pci-ep-generic Linux EPC driver and orchestrates
the Endpoint Function.
- pcie-ep-generic (hw/pci): the PCIe Endpoint Function. The controller
builds it from the staged registers and hotplugs it onto a Root Port
when the Endpoint driver starts the link.
Everything runs in a single QEMU instance. One guest kernel acts as both
the Endpoint Controller and the PCI host that enumerates the resulting
Function, sharing one guest address space. Outbound maps are memory
region aliases onto guest RAM and interrupts are raised directly on the
Function, so no second QEMU instance or inter-process protocol is
needed.
The controller is wired into the ARM virt machine, which emits the
device tree node the pci-ep-generic EPC driver binds to.
Testing
-------
The pci-ep-generic EPC driver that binds to this controller is not
upstream yet. The kernel used for testing includes this out-of-tree
commit:
https://github.com/Mani-Sadhasivam/linux/commit/d23bc106b79579ddb979be6b141e71712a70acfa
Built for aarch64 and run under TCG on the virt machine with a single
guest kernel that provides both Endpoint and host support
(CONFIG_PCI_EP_GENERIC, CONFIG_PCI_EPF_TEST, CONFIG_PCI_ENDPOINT_TEST):
qemu-system-aarch64 -machine virt -cpu cortex-a57 -m 1G \
-kernel Image -initrd rootfs.cpio.gz -display none -no-reboot \
-device pcie-root-port,id=rp0,bus=pcie.0,chassis=1,slot=1,hotplug=on \
-device pcie-ep-ctrl,target-bus=rp0 \
-append "console=ttyAMA0"
Bring up the Function through configfs, binding pci_epf_test to the
controller, then start the link:
cd /sys/kernel/config/pci_ep
CTRL=$(ls controllers/ | head -n 1)
mkdir functions/pci_epf_test/ep0
echo 0x104c > functions/pci_epf_test/ep0/vendorid
echo 0xb00d > functions/pci_epf_test/ep0/deviceid
echo 1 > functions/pci_epf_test/ep0/msi_interrupts
echo 1 > functions/pci_epf_test/ep0/msix_interrupts
ln -s functions/pci_epf_test/ep0 controllers/$CTRL/
echo 1 > controllers/$CTRL/start
Once /dev/pci-endpoint-test.0 appears, run the kernel selftest at
tools/testing/selftests/pci_endpoint/pci_endpoint_test. Result on this
setup:
Totals: pass:10 fail:10 skip:3
The passing cases include all six BAR tests and the memcpy READ, WRITE
and COPY transfers, which exercise the memory-aliasing data path. The
failures are environmental: the DMA subtests need a DMA engine that TCG
does not emulate, INTx is not routed on the virt machine, and the
doorbell test is not implemented.
Signed-off-by: Manivannan Sadhasivam <[email protected]>
---
Manivannan Sadhasivam (4):
docs/system/devices: Add PCIe Endpoint emulation documentation
hw/pci: Add PCIe Endpoint Function device (pcie-ep-generic)
hw/misc: Add PCIe Endpoint Controller emulation (pcie-ep-ctrl)
hw/arm/virt: Add PCIe Endpoint Controller support
MAINTAINERS | 9 +
docs/system/device-emulation.rst | 1 +
docs/system/devices/pcie-ep.rst | 140 +++++++++
hw/arm/Kconfig | 1 +
hw/arm/virt.c | 57 ++++
hw/core/sysbus-fdt.c | 3 +
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 1 +
hw/misc/pcie-ep-ctrl.c | 662 +++++++++++++++++++++++++++++++++++++++
hw/pci/Kconfig | 4 +
hw/pci/meson.build | 1 +
hw/pci/pcie-ep-generic.c | 255 +++++++++++++++
include/hw/misc/pcie-ep-ctrl.h | 68 ++++
include/hw/pci/pcie-ep-generic.h | 71 +++++
14 files changed, 1277 insertions(+)
---
base-commit: 193963218accbcf65f28ab1efa773ac6d1cc634c
change-id: 20260913-pcie-ep-emulation-d7d0722677eb
Best regards,
--
மணிவண்ணன் சதாசிவம்