From nobody Sat Sep 26 20:50:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=kernel.org ARC-Seal: i=1; a=rsa-sha256; t=1789320346; cv=none; d=zohomail.com; s=zohoarc; b=Fo0USlc+PQ2KXuAmgbkIN9BhZloo4xWCsAinnEcCeQl3e0wCW44umM5RIgoCBPKk1921gFybOgGxwP35+znzZTy+jIecI6K2SidHEKB/ZEQ5t/U2Hibfn/qLnGXYg6/qFuihD0GmxvlrlQBkx/UxnYdSO6c+2En+D6lsxP6d13Y= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1789320346; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=o7utxavKpIFUW01FDjLkzTq/r3iL9g9em50W9YCGwG0=; b=Y4rtMo2YiJG2Ia1NodMMJoOYENcx+uDx0/63MxVeuVOVhrZpHH1ReE0Vd1J/IEJNqe4X14zlrYcgI8k7QiWCM0LoRgVe26ZARfRckDgNWu8ucxA5QmkHnt6EXwBFEq9yBchO+ear1uw83DtO0iqzsi+NZNWSYwljbR3yPg20lh0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789320346126777.244196388431; Sun, 13 Sep 2026 10:25:46 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x5nxF-0003Tr-IC; Sun, 13 Sep 2026 13:25:28 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5nUK-000091-CN; Sun, 13 Sep 2026 12:55:32 -0400 Received: from tor.source.kernel.org ([172.105.4.254]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5nTz-0004yi-KQ; Sun, 13 Sep 2026 12:55:32 -0400 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) by tor.source.kernel.org (Postfix) with ESMTP id 31DC460E81; Sun, 13 Sep 2026 16:55:03 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id C39AF1F000FF; Sun, 13 Sep 2026 16:55:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789318502; bh=o7utxavKpIFUW01FDjLkzTq/r3iL9g9em50W9YCGwG0=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=BmKnMCUk95qG+c2MZk26tdmfh80tl7Iopt9rOaxwHPHSg6/ahdwK/mY0Iupprtk2a OJ1Vjk1uDrTt2D1fHa4sejgfab609cKjnv+UZiplf3mCVBq+AJMC41oIx7ES9DO4e+ MDF8YyVN5usqagkGwnQqXZyrEI1+ryhcouU3mIsqIvJgNAMWu24bWZiaQUMxVXGH72 E+A7E+M+7fvp/UtO4Qft9vL88Gs74PLK8PBmnmvyjKc6+onxW1l7+8X78BCf3V7ZEa VLC+KZOlXSZnvOgoSBY00IVKWtPoFh+LTcgINx2wjD/m4HSWH5D+iYC8Hyrh2/T1v8 lHnZ1RLGlXZjg== From: Manivannan Sadhasivam Date: Sun, 13 Sep 2026 18:54:51 +0200 Subject: [PATCH 1/4] docs/system/devices: Add PCIe Endpoint emulation documentation MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260913-pcie-ep-emulation-v1-1-b8e8f74a5842@kernel.org> References: <20260913-pcie-ep-emulation-v1-0-b8e8f74a5842@kernel.org> In-Reply-To: <20260913-pcie-ep-emulation-v1-0-b8e8f74a5842@kernel.org> To: qemu-devel@nongnu.org Cc: Pierrick Bouvier , "Michael S. Tsirkin" , Paolo Bonzini , Peter Maydell , qemu-arm@nongnu.org, Manivannan Sadhasivam X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=8557; i=mani@kernel.org; h=from:subject:message-id; bh=+znO2n9Ef03QY6xyCPLProxiqj8u4AxSMuiMlvR8VEo=; b=owEBbQGS/pANAwAKAVWfEeb+kc71AcsmYgBqptVgM4/tN7FFgayu6SXIvmzh06q0b6xcKZ4SV qS/GvMZriCJATMEAAEKAB0WIQRnpUMqgUjL2KRYJ5dVnxHm/pHO9QUCaqbVYAAKCRBVnxHm/pHO 9d+QB/9b3wiIo/WnAZG2mK7LhCuj3OTsyosRrOuPkz7tdmwImLlBO0mRyBXnAJXEjkYlxUIhZfO 7L0wAPW1WsbDd8iTE5U08ivD2HcnhbJKksA+hvmw6l5n9vDO40tkhOGm540VqWFvmiQEhsedMcp FvTDo/FaJWyz8OCXsVm94/NATE0ZUcGGHk7oOWM92CkgmvdzU8UxpuDtE80QakJ3SG1/StdNvH5 S4FFsZJQeNi/jEKffKbgPFSpEIDIzO6Yq1qxI5TXad9jSGQa0+j67ouO7wE9MUZHlmPSt5X6c1z Lr6gwP1PcxBQuO3oMr0K/fPzSzigzjqW+qglphHyALn4ZqHm X-Developer-Key: i=mani@kernel.org; a=openpgp; fpr=C668AEC3C3188E4C611465E7488550E901166008 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=172.105.4.254; envelope-from=mani@kernel.org; helo=tor.source.kernel.org X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sun, 13 Sep 2026 13:24:48 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @kernel.org) X-ZM-MESSAGEID: 1789320347981158500 From: Manivannan Sadhasivam Document the PCIe Endpoint Controller (pcie-ep-ctrl) and the Endpoint Function (pcie-ep-generic), including how they share a single guest address space. The documentation walks through the QEMU invocation and the configfs bring-up of an Endpoint Function. It also covers the kernel configuration needed for a single guest to act as both the Endpoint Controller and the PCI host that enumerates the Function. The current limitations are listed at the end. Signed-off-by: Manivannan Sadhasivam --- MAINTAINERS | 5 ++ docs/system/device-emulation.rst | 1 + docs/system/devices/pcie-ep.rst | 140 +++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 146 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2b5b581e17..e241f206bc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2170,6 +2170,11 @@ F: docs/pci* F: docs/specs/*pci* F: docs/system/sriov.rst =20 +PCIe Endpoint Emulation +M: Manivannan Sadhasivam +S: Maintained +F: docs/system/devices/pcie-ep.rst + ARM PCI Hotplug M: Gustavo Romero L: qemu-arm@nongnu.org diff --git a/docs/system/device-emulation.rst b/docs/system/device-emulatio= n.rst index 40054bb7df..841ce35511 100644 --- a/docs/system/device-emulation.rst +++ b/docs/system/device-emulation.rst @@ -95,6 +95,7 @@ Emulated Devices devices/keyboard.rst devices/net.rst devices/nvme.rst + devices/pcie-ep.rst devices/scsi/index.rst devices/usb-u2f.rst devices/usb.rst diff --git a/docs/system/devices/pcie-ep.rst b/docs/system/devices/pcie-ep.= rst new file mode 100644 index 0000000000..0e661234f7 --- /dev/null +++ b/docs/system/devices/pcie-ep.rst @@ -0,0 +1,140 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +PCIe Endpoint Emulation +----------------------- + +QEMU can emulate a PCIe Endpoint Controller (EPC) that works with the +Linux PCI Endpoint framework (``drivers/pci/endpoint/``). This allows +running a Linux Endpoint Function driver, such as ``pci-epf-test``, and +exercising it against the matching host-side PCI driver. + +Everything runs inside a single QEMU instance. One guest kernel acts as +both sides. It drives the Endpoint Controller through the +``pci-ep-generic`` platform driver and, when the Link comes up, it +enumerates the resulting Function as a PCI host. Because there is only +one guest, both sides share a single physical address space, so the +Endpoint BARs and the outbound DMA window are plain aliases of guest RAM +with no copying and no inter-process communication. + +Two devices cooperate: + +``pcie-ep-ctrl`` + A SysBus device on the ``virt`` machine's platform bus that exposes an + MMIO based Endpoint Controller (EPC) controlled by the ``pci-ep-generic= `` + Linux EPC driver. The guest configures Endpoint Functions via configfs + and triggers operations (set_bar, raise_irq, map_addr, and so on) + through register writes. When the Link is brought up, the controller + creates a ``pcie-ep-generic`` Function and hotplugs it onto a Root Port. + +``pcie-ep-generic`` + The PCI Endpoint Function. It is created programmatically by + ``pcie-ep-ctrl`` and cannot be instantiated with ``-device``. Its PCI + identity, BAR layout and interrupt capabilities come from the register + file the EPC driver programmed. Each BAR aliases guest RAM at the + address the controller was told to use, so the host and the Function + share the same memory. + +Architecture +^^^^^^^^^^^^ + +:: + + +------------------------- one QEMU / one kernel ----------------------= ---+ + | = | + | EPF driver (pci-epf-test) Host driver (pci-endpoint-te= st)| + | | ^ = | + | | configfs | /dev/ = | + | v | = | + | EPC driver (pci-ep-generic) PCIe subsystem (pciehp) = | + | | ^ = | + | | MMIO | enumerate/hotplug= | + | v | = | + | +----------------+ create + hotplug +---------------------+ = | + | | pcie-ep-ctrl |--------------------->| pcie-ep-generic | = | + | +----------------+ | (on Root Port) | = | + | | +---------------------+ = | + | | alias | alias = | + | v v = | + | +-------------------------------------------------------------+ = | + | | guest RAM (single address space) | = | + | | outbound DMA window aliases + Endpoint BAR backing | = | + | +-------------------------------------------------------------+ = | + +----------------------------------------------------------------------= ---+ + +Outbound maps add an alias of the target RAM into the controller's +outbound window at the offset the driver chose, and unmaps remove it. +Endpoint BAR accesses reach the same RAM the Function was given for its +BAR backing buffers. No data is copied and no synchronization points are +needed, because both sides address the same memory. + +Usage +^^^^^ + +A single invocation instantiates a Root Port and the Endpoint +Controller, pointing the controller at the Root Port with ``target-bus``: + +.. parsed-literal:: + + |qemu_system_aarch64| -machine virt -cpu cortex-a57 -m 1G \\ + -kernel Image -initrd rootfs.cpio.gz \\ + -device pcie-root-port,id=3Drp0,bus=3Dpcie.0,chassis=3D1,slot=3D1,h= otplug=3Don \\ + -device pcie-ep-ctrl,target-bus=3Drp0 \\ + -append "console=3DttyAMA0" + +pcie-ep-ctrl properties +^^^^^^^^^^^^^^^^^^^^^^^^ + +``target-bus=3DID`` + Device ID of the PCIe Root Port the Endpoint Function is hotplugged + onto when the Link comes up. Required. + +``outbound-size=3DSIZE`` + Size of the outbound DMA window (default 16 MiB). + +Guest configuration +^^^^^^^^^^^^^^^^^^^^ + +Configure the Endpoint Function via configfs (using ``pci-epf-test`` as an +example): + +.. code-block:: sh + + mount -t configfs configfs /sys/kernel/config + # The controller directory is named after the address the platform bus + # assigned to the device; discover it rather than hardcoding: + ctrl=3D$(ls /sys/kernel/config/pci_ep/controllers/ | head -n 1) + mkdir /sys/kernel/config/pci_ep/functions/pci_epf_test/func0 + echo 0x104c > /sys/kernel/config/pci_ep/functions/pci_epf_test/func0/ve= ndorid + echo 0xb00d > /sys/kernel/config/pci_ep/functions/pci_epf_test/func0/de= viceid + echo 1 > /sys/kernel/config/pci_ep/functions/pci_epf_test/func0/msi_int= errupts + echo 1 > /sys/kernel/config/pci_ep/functions/pci_epf_test/func0/msix_in= terrupts + ln -s /sys/kernel/config/pci_ep/functions/pci_epf_test/func0 \ + /sys/kernel/config/pci_ep/controllers/$ctrl/ + echo 1 > /sys/kernel/config/pci_ep/controllers/$ctrl/start + +The ``start`` write brings the Link up and triggers the hotplug. The same +kernel then enumerates the Endpoint on the Root Port as a standard PCI +device, which the host driver binds to. + +Kernel requirements +^^^^^^^^^^^^^^^^^^^^ + +The single guest kernel needs both the Endpoint and the host support: + +- ``CONFIG_PCI_ENDPOINT=3Dy`` +- ``CONFIG_PCI_EP_GENERIC=3Dy`` (the ``pci-ep-generic`` platform driver) +- EPF driver for your use case (e.g. ``CONFIG_PCI_EPF_TEST=3Dy``) +- Host driver matching the EPF's vendor/device ID (e.g. + ``CONFIG_PCI_ENDPOINT_TEST=3Dy`` for ``pci-epf-test``) +- PCIe hotplug (``pciehp``) so the enumerated Endpoint appears at runtime + +Limitations +^^^^^^^^^^^ + +- No DMA engine emulation. Transfers use the CPU, so the Endpoint + framework's DMA-backed tests do not complete. +- Legacy INTx delivery is not routed on ARM ``virt``. Use MSI or MSI-X. +- The doorbell path is not implemented. +- Migration is not supported, as the controller mirrors Endpoint state + built at runtime. +- Currently wired only for the ARM ``virt`` machine. --=20 2.43.0 From nobody Sat Sep 26 20:50:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=kernel.org ARC-Seal: i=1; a=rsa-sha256; t=1789320380; cv=none; d=zohomail.com; s=zohoarc; b=ajiE4e9gBwIeaIPu7/vNa1R5kmXF+YLvpqE0oW+Rbjx2twwSsral6k/v0E0Hg7CDTCgk52mqwNUSmGdCFAg8zoEMBiRcudMucoNzCYNuFtZOlckT6vLpbCKQvIDvVtayjZVQwdh4Bel4BUheKrAwZu3s0LuLTJBRcImGlDWKks0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1789320380; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=5NyVIXNG3+KNNQU0msAuaxe/FwKciiptg8/fqS8RKxg=; b=kqGupg7Md2ysWsxD1S6uakFnMxz0INiT8kqXebF6+ao18sPxijuGiRbbhlb1vWQrLEiAGBPOvmFf3RJV9pdl+EJ5l6tzhM7S9Cy1efDyoO/j/CtuOVhAGmz9vxaEanOBiwBJ5T7U1QF6i6zeGLJYQE5uVUruJJWWgXwl9QLBtwY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789320380932672.1092928370207; Sun, 13 Sep 2026 10:26:20 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x5nwt-0003JZ-F4; Sun, 13 Sep 2026 13:25:04 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5nUO-00009X-Js; Sun, 13 Sep 2026 12:55:36 -0400 Received: from sea.source.kernel.org ([2600:3c0a:e001:78e:0:1991:8:25]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5nU3-000585-G0; Sun, 13 Sep 2026 12:55:36 -0400 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) by sea.source.kernel.org (Postfix) with ESMTP id BE0B443206; Sun, 13 Sep 2026 16:55:05 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 81F5E1F000FF; Sun, 13 Sep 2026 16:55:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789318505; bh=5NyVIXNG3+KNNQU0msAuaxe/FwKciiptg8/fqS8RKxg=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=S/9vEqYmUNqhSm+kgQwB0Z348+0vvDKjsYFmeT9+kPVtLSdodklxY6HQLeKD49HP2 mnkKP4/arGU8wgK997abA7TBcH0QZp4QfxqSuGI8z0y2kyrt4LUwFJ36hbqvmh2xgV BvVJAUhgAL/qJnlVJiT05K05MaiXbjFjNB83Ij3aCXnMSrpNn5ckZx0Ww59zVaHbmA 2KZhNJC8G9BBHXr6ieats975KPisoc6FAMZ8eEfdUJdrYvtONVLjx+MFTB6uPfxyxD yJYcVRVRvkuCP+g4C3jAmfvS39LDaBgj8ZN3lbAZ8jJqtkR8z9vewDb3rDto5/Z85z hhPF+mbkdeOBQ== From: Manivannan Sadhasivam Date: Sun, 13 Sep 2026 18:54:52 +0200 Subject: [PATCH 2/4] hw/pci: Add PCIe Endpoint Function device (pcie-ep-generic) MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260913-pcie-ep-emulation-v1-2-b8e8f74a5842@kernel.org> References: <20260913-pcie-ep-emulation-v1-0-b8e8f74a5842@kernel.org> In-Reply-To: <20260913-pcie-ep-emulation-v1-0-b8e8f74a5842@kernel.org> To: qemu-devel@nongnu.org Cc: Pierrick Bouvier , "Michael S. Tsirkin" , Paolo Bonzini , Peter Maydell , qemu-arm@nongnu.org, Manivannan Sadhasivam X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=14146; i=mani@kernel.org; h=from:subject:message-id; bh=I53d1G4GXWlUzGEeJ8C77L2YAMKyb9xnDeAutxGDkM8=; b=owEBbQGS/pANAwAKAVWfEeb+kc71AcsmYgBqptVg9/1zQ3mYw1fpvNLZFnBJRT9vYzcAfTECA mpKAsAtz4uJATMEAAEKAB0WIQRnpUMqgUjL2KRYJ5dVnxHm/pHO9QUCaqbVYAAKCRBVnxHm/pHO 9YjjB/9EFe/RwFnCATGfh8MMzuPXSLz/YwKNJNFsB+5cRXSQ/gq5PO9B6rVYVZyyK8tsSMQTczg s0TvQuAOx9WEyG6YXwUGfOYQ8ws5RMepsUVM196WWhN2+Q5axZy2piyLN5yLDtsEEiQxOPAwB6G JIN1zhEu7w7+eBXleOxg9ASqYt4mhv2+hQHOcHr7Y6KSAWYWBUQ/lxaGQnzodyJQYYYdv6DMxKi 50gHAyUqgslZBQJo9jL8LlthSV3tbCt5biBWVp/K/cEkuyKtafpJtm4bUBw/moH4YDKkwDQOt5W 018bC4qUiPLSGU2rRcl/gAkte+CTP+V1dZuZ6NgWmfO8+y1S X-Developer-Key: i=mani@kernel.org; a=openpgp; fpr=C668AEC3C3188E4C611465E7488550E901166008 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2600:3c0a:e001:78e:0:1991:8:25; envelope-from=mani@kernel.org; helo=sea.source.kernel.org X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sun, 13 Sep 2026 13:24:48 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @kernel.org) X-ZM-MESSAGEID: 1789320382006158500 From: Manivannan Sadhasivam Add the PCIe Endpoint Function device driven by the pcie-ep-ctrl Endpoint Controller. The device is created programmatically by the controller and hotplugged onto a PCIe Root Port when the Link comes up. It is not instantiable with -device. Its PCI identity, BAR layout and interrupt capabilities all come from the controller, which programs them from the register writes issued by the pci-ep-generic Linux EPC driver. Each enabled BAR is a memory region alias onto guest RAM at the address the controller was given. This way, the Endpoint Function and the host share the same backing memory without any copying. Interrupts raised by the controller are delivered to the host through msi_notify(), msix_notify() or a legacy INTx toggle. Signed-off-by: Manivannan Sadhasivam --- MAINTAINERS | 2 + hw/pci/Kconfig | 4 + hw/pci/meson.build | 1 + hw/pci/pcie-ep-generic.c | 255 +++++++++++++++++++++++++++++++++++= ++++ include/hw/pci/pcie-ep-generic.h | 71 +++++++++++ 5 files changed, 333 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index e241f206bc..498569bfe1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2174,6 +2174,8 @@ PCIe Endpoint Emulation M: Manivannan Sadhasivam S: Maintained F: docs/system/devices/pcie-ep.rst +F: hw/pci/pcie-ep-generic.c +F: include/hw/pci/pcie-ep-generic.h =20 ARM PCI Hotplug M: Gustavo Romero diff --git a/hw/pci/Kconfig b/hw/pci/Kconfig index fe70902cd8..c5719ec937 100644 --- a/hw/pci/Kconfig +++ b/hw/pci/Kconfig @@ -11,6 +11,10 @@ config PCI_DEVICES config PCIE_DEVICES bool =20 +config PCIE_EP_GENERIC + bool + select PCI_EXPRESS + config MSI_NONBROKEN # selected by interrupt controllers that do not support MSI, # or support it and have a good implementation. See commit diff --git a/hw/pci/meson.build b/hw/pci/meson.build index a6cbd89c0a..e1c1081c98 100644 --- a/hw/pci/meson.build +++ b/hw/pci/meson.build @@ -17,6 +17,7 @@ pci_ss.add(files( pci_ss.add(files('pcie.c', 'pcie_aer.c')) pci_ss.add(files('pcie_doe.c')) system_ss.add(when: 'CONFIG_PCI_EXPRESS', if_true: files('pcie_port.c', 'p= cie_host.c')) +system_ss.add(when: 'CONFIG_PCIE_EP_GENERIC', if_true: files('pcie-ep-gene= ric.c')) system_ss.add_all(when: 'CONFIG_PCI', if_true: pci_ss) =20 stub_ss.add(files('pci-stub.c')) diff --git a/hw/pci/pcie-ep-generic.c b/hw/pci/pcie-ep-generic.c new file mode 100644 index 0000000000..5a1929486c --- /dev/null +++ b/hw/pci/pcie-ep-generic.c @@ -0,0 +1,255 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * PCIe Endpoint Function device (pcie-ep-generic). + * + * Created programmatically by pcie-ep-ctrl and hotplugged onto a PCIe Root + * Port when the EP driver brings the Link up. Its PCI identity, BAR layout + * and interrupt capabilities come from a PCIeEPConfig handed over + * before realize. Because it is realised after machine initialisation, + * dev->hotplugged is true and pcie_cap_slot_plug_cb() fires the PDC inter= rupt + * so pciehp on the RC guest enumerates it. + * + * Each BAR aliases normal guest RAM at the address the Endpoint Controller + * programmed (the Function's dma_alloc_coherent buffer), so the RC host a= nd + * the Endpoint Function share one address space with no copying. The BAR = is + * a small container holding a low-priority RAM alias, which lets msix_ini= t() + * overlay its table and PBA on top for the MSI-X BAR. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/log.h" +#include "hw/pci/pci.h" +#include "hw/pci/pci_device.h" +#include "hw/pci/pcie.h" +#include "hw/pci/msi.h" +#include "hw/pci/msix.h" +#include "hw/pci/pcie-ep-generic.h" +#include "system/address-spaces.h" +#include "migration/vmstate.h" + +/* PCIe / MSI capability offsets in the Endpoint's Configuration Space. */ +#define EP_PCIE_CAP_OFFSET 0x80 +#define EP_MSI_CAP_OFFSET 0x60 + +OBJECT_DECLARE_SIMPLE_TYPE(PCIeEPState, PCIE_EP_ENDPOINT) + +struct PCIeEPState { + PCIDevice parent_obj; + + PCIeEPConfig cfg; + + /* + * Each BAR is a container holding a RAM alias (and, on the MSI-X BAR,= the + * msix table/PBA subregions added by msix_init()). + */ + MemoryRegion bar_mr[PCI_STD_NUM_BARS]; + MemoryRegion bar_alias[PCI_STD_NUM_BARS]; +}; + +void pcie_ep_endpoint_set_config(DeviceState *dev, + const PCIeEPConfig *cfg) +{ + PCIeEPState *ep =3D PCIE_EP_ENDPOINT(dev); + + ep->cfg =3D *cfg; +} + +void pcie_ep_endpoint_raise_irq(DeviceState *dev, uint32_t irq_type, + uint32_t irq_num) +{ + PCIDevice *pdev =3D PCI_DEVICE(dev); + unsigned vector =3D irq_num ? irq_num - 1 : 0; + + switch (irq_type) { + case PCIE_EP_IRQ_INTX: + pci_set_irq(pdev, 1); + pci_set_irq(pdev, 0); + break; + case PCIE_EP_IRQ_MSI: + if (msi_enabled(pdev)) { + msi_notify(pdev, vector); + } + break; + case PCIE_EP_IRQ_MSIX: + if (msix_enabled(pdev)) { + msix_notify(pdev, vector); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-generic: unknown IRQ type %u\n", irq_type); + break; + } +} + +void pcie_ep_endpoint_get_msi_state(DeviceState *dev, PCIeEPMsiState *out) +{ + PCIDevice *pdev =3D PCI_DEVICE(dev); + + memset(out, 0, sizeof(*out)); + + if (msi_enabled(pdev) && pdev->msi_cap) { + uint16_t ctl =3D pci_get_word(pdev->config + pdev->msi_cap + + PCI_MSI_FLAGS); + uint8_t mme =3D (ctl & PCI_MSI_FLAGS_QSIZE) >> 4; + MSIMessage m =3D msi_get_message(pdev, 0); + + out->msi_vectors =3D 1u << mme; + out->msi_addr_lo =3D (uint32_t)m.address; + out->msi_addr_hi =3D (uint32_t)(m.address >> 32); + out->msi_data =3D m.data; + } + + if (msix_enabled(pdev)) { + out->msix_vectors =3D pdev->msix_entries_nr; + } +} + +static void pcie_ep_endpoint_realize(PCIDevice *pdev, Error **errp) +{ + PCIeEPState *ep =3D PCIE_EP_ENDPOINT(pdev); + PCIeEPConfig *cfg =3D &ep->cfg; + int i; + + /* Apply PCI identity from the staged Header. */ + pci_config_set_vendor_id(pdev->config, cfg->vendor_id); + pci_config_set_device_id(pdev->config, cfg->device_id); + pci_config_set_revision(pdev->config, cfg->rev_id); + pci_config_set_class(pdev->config, (uint16_t)(cfg->base_class << 8) | + cfg->sub_class); + pci_config_set_prog_interface(pdev->config, cfg->progif); + pci_set_word(pdev->config + PCI_SUBSYSTEM_VENDOR_ID, cfg->subsys_vendo= r_id); + pci_set_word(pdev->config + PCI_SUBSYSTEM_ID, cfg->subsys_id); + pci_config_set_interrupt_pin(pdev->config, + cfg->interrupt_pin ? cfg->interrupt_pin := 1); + + if (pcie_endpoint_cap_init(pdev, EP_PCIE_CAP_OFFSET) < 0) { + error_setg(errp, "pcie-ep-generic: failed to init PCIe capability"= ); + return; + } + + if (cfg->num_msi > 0) { + if (msi_init(pdev, EP_MSI_CAP_OFFSET, cfg->num_msi, + true, false, errp) < 0) { + goto err_pcie_cap; + } + } + + /* + * BARs alias guest RAM at the controller-programmed address. Use a + * container with the RAM alias at low priority so msix_init() can ove= rlay + * the MSI-X table and PBA on the designated BAR. + */ + for (i =3D 0; i < PCI_STD_NUM_BARS; i++) { + char name[32]; + + if (!cfg->bar[i].enabled || cfg->bar[i].size =3D=3D 0) { + continue; + } + + snprintf(name, sizeof(name), "pcie-ep-bar%d", i); + memory_region_init(&ep->bar_mr[i], OBJECT(ep), name, cfg->bar[i].s= ize); + + snprintf(name, sizeof(name), "pcie-ep-bar%d-ram", i); + memory_region_init_alias(&ep->bar_alias[i], OBJECT(ep), name, + get_system_memory(), cfg->bar[i].phys, + cfg->bar[i].size); + memory_region_add_subregion_overlap(&ep->bar_mr[i], 0, + &ep->bar_alias[i], -1); + + pci_register_bar(pdev, i, + cfg->bar[i].flags & (PCI_BASE_ADDRESS_MEM_TYPE_64= | + PCI_BASE_ADDRESS_MEM_PREFETC= H | + PCI_BASE_ADDRESS_SPACE), + &ep->bar_mr[i]); + } + + /* MSI-X Capability, table lives in the designated BAR */ + if (cfg->num_msix > 0 && cfg->msix_bar < PCI_STD_NUM_BARS + && cfg->bar[cfg->msix_bar].enabled) { + uint32_t pba_offset =3D cfg->msix_offset + + cfg->num_msix * PCI_MSIX_ENTRY_SIZE; + if (msix_init(pdev, cfg->num_msix, + &ep->bar_mr[cfg->msix_bar], cfg->msix_bar, + cfg->msix_offset, + &ep->bar_mr[cfg->msix_bar], cfg->msix_bar, + pba_offset, 0, errp) < 0) { + goto err_msi; + } + } + + return; + + /* + * QEMU does not call ->exit on realize failure, so undo the capabilit= ies + * here. BAR regions are owned by this object and freed on finalise. + */ +err_msi: + msi_uninit(pdev); +err_pcie_cap: + pcie_cap_exit(pdev); +} + +static void pcie_ep_endpoint_exit(PCIDevice *pdev) +{ + PCIeEPState *ep =3D PCIE_EP_ENDPOINT(pdev); + PCIeEPConfig *cfg =3D &ep->cfg; + + if (cfg->num_msix > 0 && cfg->msix_bar < PCI_STD_NUM_BARS + && cfg->bar[cfg->msix_bar].enabled) { + msix_uninit(pdev, + &ep->bar_mr[cfg->msix_bar], + &ep->bar_mr[cfg->msix_bar]); + } + msi_uninit(pdev); + pcie_cap_exit(pdev); +} + +/* + * The Endpoint is created programmatically at runtime and mirrors live EP + * state, so it is not migratable. + */ +static const VMStateDescription vmstate_pcie_ep_endpoint =3D { + .name =3D "pcie-ep-generic", + .unmigratable =3D 1, +}; + +static void pcie_ep_endpoint_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(oc); + PCIDeviceClass *k =3D PCI_DEVICE_CLASS(oc); + + k->realize =3D pcie_ep_endpoint_realize; + k->exit =3D pcie_ep_endpoint_exit; + /* Placeholder identity, overwritten in realize() from staged config */ + k->vendor_id =3D PCI_VENDOR_ID_REDHAT; + k->device_id =3D 0x0001; + k->class_id =3D PCI_CLASS_OTHERS; + + dc->desc =3D "PCIe Endpoint Function (dynamically configured)"; + dc->vmsd =3D &vmstate_pcie_ep_endpoint; + /* + * Created programmatically by pcie-ep-ctrl at link-up, not via -devic= e: + * its PCI identity and BAR layout are only known at runtime. + */ + dc->user_creatable =3D false; +} + +static const TypeInfo pcie_ep_endpoint_info =3D { + .name =3D TYPE_PCIE_EP_ENDPOINT, + .parent =3D TYPE_PCI_DEVICE, + .instance_size =3D sizeof(PCIeEPState), + .class_init =3D pcie_ep_endpoint_class_init, + .interfaces =3D (const InterfaceInfo[]) { + { INTERFACE_PCIE_DEVICE }, + { } + }, +}; + +static void pcie_ep_generic_register_types(void) +{ + type_register_static(&pcie_ep_endpoint_info); +} +type_init(pcie_ep_generic_register_types) diff --git a/include/hw/pci/pcie-ep-generic.h b/include/hw/pci/pcie-ep-gene= ric.h new file mode 100644 index 0000000000..4560852080 --- /dev/null +++ b/include/hw/pci/pcie-ep-generic.h @@ -0,0 +1,71 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * PCIe Endpoint Function device definitions. + */ + +#ifndef HW_PCI_PCIE_EP_GENERIC_H +#define HW_PCI_PCIE_EP_GENERIC_H + +#include "hw/pci/pci.h" +#include "hw/core/qdev.h" + +#define TYPE_PCIE_EP_ENDPOINT "pcie-ep-generic" + +/* + * IRQ type codes written by the EP driver to the controller's IRQ_TYPE + * register. They match PCI_IRQ_INTX / _MSI / _MSIX in include/linux/pci.h. + */ +#define PCIE_EP_IRQ_INTX 1 +#define PCIE_EP_IRQ_MSI 2 +#define PCIE_EP_IRQ_MSIX 4 + +/* Per-BAR description handed to the Endpoint at creation. */ +typedef struct PCIeEPBarConfig { + bool enabled; + uint64_t phys; /* guest-RAM address the BAR aliases */ + uint64_t size; + uint32_t flags; /* PCI_BASE_ADDRESS_* flags */ +} PCIeEPBarConfig; + +/* Full Endpoint identity and resources, populated by pcie-ep-ctrl. */ +typedef struct PCIeEPConfig { + uint16_t vendor_id; + uint16_t device_id; + uint16_t subsys_vendor_id; + uint16_t subsys_id; + uint8_t base_class; + uint8_t sub_class; + uint8_t progif; + uint8_t rev_id; + uint8_t interrupt_pin; + + PCIeEPBarConfig bar[PCI_STD_NUM_BARS]; + + uint8_t num_msi; + uint16_t num_msix; + uint8_t msix_bar; + uint32_t msix_offset; +} PCIeEPConfig; + +/* Live MSI/MSI-X negotiation state read back from the RC. */ +typedef struct PCIeEPMsiState { + uint32_t msi_vectors; /* 0 if MSI not enabled by the RC */ + uint32_t msix_vectors; /* 0 if MSI-X not enabled by the RC */ + uint32_t msi_addr_lo; + uint32_t msi_addr_hi; + uint32_t msi_data; +} PCIeEPMsiState; + +/* Hand staged config to a freshly created Endpoint before realize. */ +void pcie_ep_endpoint_set_config(DeviceState *dev, + const PCIeEPConfig *cfg); + +/* Deliver an interrupt (type =3D PCIE_EP_IRQ_*) from the Function to the = RC. */ +void pcie_ep_endpoint_raise_irq(DeviceState *dev, uint32_t irq_type, + uint32_t irq_num); + +/* Read the live MSI/MSI-X negotiation state for the readback registers. */ +void pcie_ep_endpoint_get_msi_state(DeviceState *dev, PCIeEPMsiState *out); + +#endif /* HW_PCI_PCIE_EP_GENERIC_H */ --=20 2.43.0 From nobody Sat Sep 26 20:50:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=kernel.org ARC-Seal: i=1; a=rsa-sha256; t=1789320372; cv=none; d=zohomail.com; s=zohoarc; b=Kp5vFbN9D0Vp/gQg/I7klUGgm3Sy+qIM2P3ZR0ArveiTO1QR8vDsufH6niCQ7RmeTXlivEFYHPGFWpsh5zCFwWn1K9+Rk/t/bmSyB1CVozotQwkh7NgB/o5IE6r00Yjh5mef73XsxFOmN8x1GtW0M7UAkf9zMyTTKSz59lihWfE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1789320372; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=/f8fJN8D/L2bVeRYiiTfC0o/SyyYyO/TPZro9450q1w=; b=ZwJ+Mgkc1Izdy30y/IxPzkexPfmcYmxQmlxUehvKkILO5phdxh4CtRlTRLf7K5H5GaO2MytSLFfo4KJe38J9/IODoeSnAdSmDx+/N8LBYm9bV4K/5OVmRzOV1M9a9xF2kuduFbO/x7hi6ZV4M1BpD/eERMuWttRkdZ150MI2FfI= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789320372628912.7185645114338; Sun, 13 Sep 2026 10:26:12 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x5nx1-0003KY-Tv; Sun, 13 Sep 2026 13:25:11 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5nUJ-00008h-QO; Sun, 13 Sep 2026 12:55:31 -0400 Received: from tor.source.kernel.org ([172.105.4.254]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5nTy-00058z-JZ; Sun, 13 Sep 2026 12:55:31 -0400 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) by tor.source.kernel.org (Postfix) with ESMTP id A71EA60E83; Sun, 13 Sep 2026 16:55:08 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 469C61F000FF; Sun, 13 Sep 2026 16:55:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789318508; bh=/f8fJN8D/L2bVeRYiiTfC0o/SyyYyO/TPZro9450q1w=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=kps9P8HZiIvg5FKXJyvFV8pwP6WuMIRWkxkSNncQvdXAvgW6JURbOL/cXRc+LmSev zBDjhCHUjFLwAx1xkF/aZ4GhPc64wbTTDxDYnaOQdt2m9Rr15/1v4tjtYCl7qT/+yb 7X6IzMIL+jfD7YRpy4JMf+UKHFFrd7s6ivNlGIfj4dVE6lCfkTNYp9wIG6io58b0bm 9MK37PapHrUAfykIH8ipUD56kXcscU3KU+iFS0iiAdt7lnL3Z2rpuNpZSXVaUhUu7A SmLJ86ooAHkKSxvZUYlnAwsMQAOjrFsTHz+mZoA96oG4VE92EZkbWC5OL19HQXWtse KlhhXCd2LYsIQ== From: Manivannan Sadhasivam Date: Sun, 13 Sep 2026 18:54:53 +0200 Subject: [PATCH 3/4] hw/misc: Add PCIe Endpoint Controller emulation (pcie-ep-ctrl) MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260913-pcie-ep-emulation-v1-3-b8e8f74a5842@kernel.org> References: <20260913-pcie-ep-emulation-v1-0-b8e8f74a5842@kernel.org> In-Reply-To: <20260913-pcie-ep-emulation-v1-0-b8e8f74a5842@kernel.org> To: qemu-devel@nongnu.org Cc: Pierrick Bouvier , "Michael S. Tsirkin" , Paolo Bonzini , Peter Maydell , qemu-arm@nongnu.org, Manivannan Sadhasivam X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=27273; i=mani@kernel.org; h=from:subject:message-id; bh=TCB97PZx7M6wYpJUrwTEDqBLQ2VW59yu7HFjkeAXRFY=; b=owEBbQGS/pANAwAKAVWfEeb+kc71AcsmYgBqptVgPpWvg1slQhmJsbEZv48kGfgOxnyQ2ao7n AwPky2CfpuJATMEAAEKAB0WIQRnpUMqgUjL2KRYJ5dVnxHm/pHO9QUCaqbVYAAKCRBVnxHm/pHO 9esiCACg0V4QSqg5pPdVwJLl1ibc1fbg9+JEbbSiKjD0PToNaXiTRgU23U7+GZPrfXiSjgfh7ZC gwhVv9oJWSWOYJ7vTR+7zcW9jI/kq0hFSVYtJhwXMvgZPiCBivEWUzvy2VRABT71p9myk1vzcXd OHBehbmCfV23y2wt2no47Bkan4n3s/fRTSAiyM7lbjQM73AoIesVORh8bbDp+0Ybz5zv28QOGV3 lTq7z85EXRhotnaRzC0MvkCKD0m5ixyxtfmtsdL1ZjpVinzlr5ZbXJ9Feg7D5502SHPDszEUZeS B40DnJRwm3x6uBVg6dOCjkKzynGuMkEFU9PDjTKN2j8bIxEl X-Developer-Key: i=mani@kernel.org; a=openpgp; fpr=C668AEC3C3188E4C611465E7488550E901166008 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=172.105.4.254; envelope-from=mani@kernel.org; helo=tor.source.kernel.org X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sun, 13 Sep 2026 13:24:47 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @kernel.org) X-ZM-MESSAGEID: 1789320374192158500 From: Manivannan Sadhasivam Add a SysBus platform device that emulates a PCIe Endpoint Controller (EPC) for use with the Linux PCI Endpoint framework. It exposes an MMIO register interface compatible with the pci-ep-generic Linux EPC driver. The register file is a plain scratchpad. A command register at offset 0xf0 accepts opcodes (WRITE_HEADER, SET_BAR, CLEAR_BAR, MAP, UNMAP, SET_MSI, SET_MSIX, RAISE_IRQ, START, STOP) and the controller only acts when the driver writes an opcode to it. The driver stages every field it needs and issues one command write to commit. A small set of read-only registers (CAPS, MAX_FUNC, LINKUP, MSI/MSI-X readback and MSI address/data) reports firmware-configured or live-negotiated state. The controller also acts as the orchestrator for the Endpoint Function. On CMD_START it builds a pcie-ep-generic Function from the staged registers and hotplugs it onto the Root Port named by the target-bus property. CMD_STOP unplugs it again. Because qdev_realize must not run inline from an MMIO write handler, the hotplug work is deferred to a bottom half. Everything runs inside a single QEMU instance. The controller drives the Endpoint with direct in-process calls instead of any inter-process protocol. Outbound address maps are memory region aliases onto guest RAM. Interrupts are raised straight on the Endpoint Function. This allows one guest kernel to act as both the Endpoint Controller and the PCI host that enumerates the resulting Function. Signed-off-by: Manivannan Sadhasivam --- MAINTAINERS | 2 + hw/misc/Kconfig | 4 + hw/misc/meson.build | 1 + hw/misc/pcie-ep-ctrl.c | 662 +++++++++++++++++++++++++++++++++++++= ++++ include/hw/misc/pcie-ep-ctrl.h | 68 +++++ 5 files changed, 737 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 498569bfe1..eb0fe6b0a7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2176,6 +2176,8 @@ S: Maintained F: docs/system/devices/pcie-ep.rst F: hw/pci/pcie-ep-generic.c F: include/hw/pci/pcie-ep-generic.h +F: hw/misc/pcie-ep-ctrl.c +F: include/hw/misc/pcie-ep-ctrl.h =20 ARM PCI Hotplug M: Gustavo Romero diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 1543ee6653..b80c81281e 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -224,6 +224,10 @@ config SIFIVE_U_PRCI config VIRT_CTRL bool =20 +config PCIE_EP_CTRL + bool + select PCIE_EP_GENERIC + config LASI bool select LASI_82596 diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 23265f6035..428d51fd90 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -26,6 +26,7 @@ system_ss.add(when: 'CONFIG_IOSB', if_true: files('iosb.c= ')) =20 # virt devices system_ss.add(when: 'CONFIG_VIRT_CTRL', if_true: files('virt_ctrl.c')) +system_ss.add(when: 'CONFIG_PCIE_EP_CTRL', if_true: files('pcie-ep-ctrl.c'= )) =20 # RISC-V devices system_ss.add(when: 'CONFIG_MCHP_PFSOC_DMC', if_true: files('mchp_pfsoc_dm= c.c')) diff --git a/hw/misc/pcie-ep-ctrl.c b/hw/misc/pcie-ep-ctrl.c new file mode 100644 index 0000000000..d0b5a78cb4 --- /dev/null +++ b/hw/misc/pcie-ep-ctrl.c @@ -0,0 +1,662 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * QEMU PCIe Endpoint Controller device. + * + * Platform device that gives the guest an MMIO register interface for an + * Endpoint Controller. The Linux pci-ep-generic.c EPC driver binds to it = and + * drives the usual Endpoint framework flow. It writes the PCI Header, siz= es + * the BARs, programs MSI/MSI-X and finally brings the Link up. + * + * Everything happens inside a single QEMU instance. When the driver brings + * the Link up this controller creates a pcie-ep-generic PCI Function and + * hotplugs it onto a Root Port named by the "target-bus" property, so the= same + * guest that runs the controller also enumerates the Endpoint as a host. = The + * Endpoint BARs and the outbound DMA window are plain aliases into guest = RAM, + * so both sides share one address space with no copying and no IPC. + * + * Register layout matches the PCI_EPC_GEN_* offsets in + * drivers/pci/controller/pci-ep-generic.c. + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/units.h" +#include "qemu/main-loop.h" +#include "hw/core/qdev-properties.h" +#include "hw/misc/pcie-ep-ctrl.h" +#include "hw/pci/pci.h" +#include "hw/pci/pci_bus.h" +#include "hw/pci/pci_bridge.h" +#include "hw/pci/pcie-ep-generic.h" +#include "hw/core/hotplug.h" +#include "system/address-spaces.h" +#include "migration/vmstate.h" +#include "qapi/error.h" + +/* + * Register offsets (must match PCI_EPC_GEN_* in pci-ep-generic.c) + * + * The register file is a plain scratchpad. Nothing happens until the driv= er + * writes an opcode to EPCTRL_CMD, at which point the controller acts on t= he + * staged values. Ordering between staging writes does not matter. + */ + +/* Firmware-initialised, read-only from the driver's perspective */ +#define EPCTRL_CAPS 0x00 +#define EPCTRL_CAP_MSI (1u << 1) +#define EPCTRL_CAP_MSIX (1u << 2) +#define EPCTRL_CAP_DYN_INBOUND_MAP (1u << 4) +#define EPCTRL_CAP_SUBRANGE (1u << 5) +#define EPCTRL_MAX_FUNC 0x04 + +/* Link state, read-only status reflecting the last CMD_START / CMD_STOP */ +#define EPCTRL_LINKUP 0x10 + +/* Scratchpad registers staged by the driver */ +#define EPCTRL_FUNC_NO 0x18 +#define EPCTRL_BAR_NO 0x1c +#define EPCTRL_IRQ_TYPE 0x20 +#define EPCTRL_IRQ_NUM 0x24 +#define EPCTRL_HDR_VID 0x30 +#define EPCTRL_HDR_DID 0x34 +#define EPCTRL_HDR_SVID 0x38 +#define EPCTRL_HDR_SSID 0x3c +#define EPCTRL_HDR_CLASS 0x40 /* [23:16] base, [15:8] sub, [7:0] progi= f */ +#define EPCTRL_HDR_REV 0x44 +#define EPCTRL_HDR_INTPIN 0x48 +#define EPCTRL_BAR_PHYS_LO 0x50 +#define EPCTRL_BAR_PHYS_HI 0x54 +#define EPCTRL_BAR_SIZE_LO 0x58 +#define EPCTRL_BAR_SIZE_HI 0x5c +#define EPCTRL_BAR_FLAGS 0x60 +#define EPCTRL_MAP_PHYS_LO 0x70 +#define EPCTRL_MAP_PHYS_HI 0x74 +#define EPCTRL_MAP_PCI_LO 0x78 +#define EPCTRL_MAP_PCI_HI 0x7c +#define EPCTRL_MAP_SIZE_LO 0x80 +#define EPCTRL_MAP_SIZE_HI 0x84 +#define EPCTRL_MSI_COUNT 0x90 +#define EPCTRL_MSIX_BAR 0x94 +#define EPCTRL_MSIX_OFFSET 0x98 +/* Negotiated values reported back to the driver, computed live on read */ +#define EPCTRL_MSI_READBACK 0xa0 +#define EPCTRL_MSIX_READBACK 0xa4 +#define EPCTRL_MSI_ADDR_LO 0xb0 +#define EPCTRL_MSI_ADDR_HI 0xb4 +#define EPCTRL_MSI_DATA 0xb8 + +/* Command register: the driver writes an opcode here to commit staged sta= te */ +#define EPCTRL_CMD 0xf0 +#define EPCTRL_CMD_WRITE_HEADER 0x01 +#define EPCTRL_CMD_SET_BAR 0x02 +#define EPCTRL_CMD_CLEAR_BAR 0x03 +#define EPCTRL_CMD_MAP 0x04 +#define EPCTRL_CMD_UNMAP 0x05 +#define EPCTRL_CMD_SET_MSI 0x06 +#define EPCTRL_CMD_SET_MSIX 0x07 +#define EPCTRL_CMD_RAISE_IRQ 0x08 +#define EPCTRL_CMD_START 0x09 +#define EPCTRL_CMD_STOP 0x0a + +#define EPCTRL_CTRL_SIZE 0x1000 /* 4 KB register file */ + +/* Capabilities advertised to the driver */ +#define EPCTRL_CAPS_ALL (EPCTRL_CAP_MSI | \ + EPCTRL_CAP_MSIX | \ + EPCTRL_CAP_DYN_INBOUND_MAP | EPCTRL_CAP_SUBRANGE) + +static inline uint32_t epctrl_reg_read(PCIeEPCtrlState *s, unsigned off) +{ + return s->regs[off / 4]; +} + +static inline void epctrl_reg_write(PCIeEPCtrlState *s, unsigned off, + uint32_t val) +{ + s->regs[off / 4] =3D val; +} + +static void epctrl_ob_unmap_slot(PCIeEPCtrlState *s, int slot) +{ + MemoryRegion *alias =3D s->ob_map[slot].alias; + + if (!alias) { + return; + } + + memory_region_del_subregion(&s->ob_mr, alias); + object_unparent(OBJECT(alias)); + g_free(alias); + s->ob_map[slot].alias =3D NULL; + s->ob_map[slot].win_off =3D 0; +} + +static void epctrl_ob_map(PCIeEPCtrlState *s, uint64_t win_off, + uint64_t pci_addr, uint64_t size) +{ + MemoryRegion *alias; + char *name; + int slot =3D -1; + int i; + + /* Reuse a slot already mapping this window offset, replacing its alia= s. */ + for (i =3D 0; i < EPCTRL_OB_MAP_MAX; i++) { + if (s->ob_map[i].alias && s->ob_map[i].win_off =3D=3D win_off) { + epctrl_ob_unmap_slot(s, i); + slot =3D i; + break; + } + } + /* Otherwise take the first free slot. */ + if (slot < 0) { + for (i =3D 0; i < EPCTRL_OB_MAP_MAX; i++) { + if (!s->ob_map[i].alias) { + slot =3D i; + break; + } + } + } + if (slot < 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-ctrl: outbound map table full\n"); + return; + } + + alias =3D g_new0(MemoryRegion, 1); + name =3D g_strdup_printf("pcie-ep-ob-map%d", slot); + memory_region_init_alias(alias, OBJECT(s), name, get_system_memory(), + pci_addr, size); + g_free(name); + memory_region_add_subregion(&s->ob_mr, win_off, alias); + + s->ob_map[slot].alias =3D alias; + s->ob_map[slot].win_off =3D win_off; +} + +static void epctrl_ob_unmap(PCIeEPCtrlState *s, uint64_t win_off) +{ + int i; + + for (i =3D 0; i < EPCTRL_OB_MAP_MAX; i++) { + if (s->ob_map[i].alias && s->ob_map[i].win_off =3D=3D win_off) { + epctrl_ob_unmap_slot(s, i); + return; + } + } +} + +static void epctrl_ob_unmap_all(PCIeEPCtrlState *s) +{ + int i; + + for (i =3D 0; i < EPCTRL_OB_MAP_MAX; i++) { + epctrl_ob_unmap_slot(s, i); + } +} + +static PCIBus *epctrl_find_bus(const char *id, Error **errp) +{ + PCIDevice *pdev; + + if (pci_qdev_find_device(id, &pdev) < 0) { + error_setg(errp, "pcie-ep-ctrl: target-bus '%s' not found", id); + return NULL; + } + if (!object_dynamic_cast(OBJECT(pdev), TYPE_PCI_BRIDGE)) { + error_setg(errp, "pcie-ep-ctrl: target-bus '%s' is not a PCI bridg= e", + id); + return NULL; + } + return pci_bridge_get_sec_bus(PCI_BRIDGE(pdev)); +} + +static void epctrl_build_config(PCIeEPCtrlState *s, PCIeEPConfig *cfg) +{ + uint32_t cls =3D epctrl_reg_read(s, EPCTRL_HDR_CLASS); + uint32_t msi =3D epctrl_reg_read(s, EPCTRL_MSI_COUNT); + int i; + + memset(cfg, 0, sizeof(*cfg)); + + cfg->vendor_id =3D epctrl_reg_read(s, EPCTRL_HDR_VID); + cfg->device_id =3D epctrl_reg_read(s, EPCTRL_HDR_DID); + cfg->subsys_vendor_id =3D epctrl_reg_read(s, EPCTRL_HDR_SVID); + cfg->subsys_id =3D epctrl_reg_read(s, EPCTRL_HDR_SSID); + cfg->rev_id =3D epctrl_reg_read(s, EPCTRL_HDR_REV); + cfg->interrupt_pin =3D epctrl_reg_read(s, EPCTRL_HDR_INTPIN); + + cfg->base_class =3D (cls >> 16) & 0xff; + cfg->sub_class =3D (cls >> 8) & 0xff; + cfg->progif =3D cls & 0xff; + + for (i =3D 0; i < EPCTRL_MAX_BARS && i < PCI_STD_NUM_BARS; i++) { + if (s->bar_size[i] =3D=3D 0) { + continue; + } + cfg->bar[i].enabled =3D true; + cfg->bar[i].phys =3D s->bar_phys[i]; + cfg->bar[i].size =3D s->bar_size[i]; + cfg->bar[i].flags =3D s->bar_flags[i]; + } + + /* + * The driver writes both counts through MSI_COUNT (see + * pci_ep_generic_set_msi and pci_ep_generic_set_msix), so the same va= lue + * seeds MSI and MSI-X. + */ + cfg->num_msi =3D msi & 0xff; + cfg->num_msix =3D msi & 0xffff; + cfg->msix_bar =3D epctrl_reg_read(s, EPCTRL_MSIX_BAR) & 0xff; + cfg->msix_offset =3D epctrl_reg_read(s, EPCTRL_MSIX_OFFSET); +} + +static void epctrl_do_hotplug(PCIeEPCtrlState *s) +{ + PCIeEPConfig cfg; + Error *local_err =3D NULL; + DeviceState *dev; + PCIBus *bus; + + if (s->endpoint) { + return; + } + + bus =3D epctrl_find_bus(s->target_bus, &local_err); + if (!bus) { + error_report_err(local_err); + return; + } + + epctrl_build_config(s, &cfg); + + dev =3D qdev_new(TYPE_PCIE_EP_ENDPOINT); + dev->hotplugged =3D true; + pcie_ep_endpoint_set_config(dev, &cfg); + + if (!qdev_realize_and_unref(dev, &bus->qbus, &local_err)) { + error_reportf_err(local_err, + "pcie-ep-ctrl: endpoint realize failed: "); + return; + } + + s->endpoint =3D dev; +} + +static void epctrl_do_unplug(PCIeEPCtrlState *s) +{ + HotplugHandler *hp; + Error *local_err =3D NULL; + + if (!s->endpoint) { + return; + } + + hp =3D qdev_get_hotplug_handler(s->endpoint); + if (hp) { + hotplug_handler_unplug_request(hp, s->endpoint, &local_err); + if (local_err) { + error_reportf_err(local_err, + "pcie-ep-ctrl: endpoint unplug failed: "); + } + } + + s->endpoint =3D NULL; + epctrl_ob_unmap_all(s); +} + +/* + * Link up/down runs qdev_realize, which must not happen inline from the M= MIO + * write handler, so CMD_START / CMD_STOP schedule this bottom half instea= d. + */ +static void epctrl_link_bh(void *opaque) +{ + PCIeEPCtrlState *s =3D opaque; + + if (s->started && !s->endpoint) { + epctrl_do_hotplug(s); + } else if (!s->started && s->endpoint) { + epctrl_do_unplug(s); + } + + /* Reflect live link state in the read-only LINKUP register */ + epctrl_reg_write(s, EPCTRL_LINKUP, s->endpoint ? 1 : 0); +} + +static void epctrl_dispatch_cmd(PCIeEPCtrlState *s, uint32_t cmd) +{ + switch (cmd) { + case EPCTRL_CMD_WRITE_HEADER: + /* + * Header fields are consumed from the register file when the + * Endpoint is materialised at CMD_START. Nothing to do here. + */ + break; + + case EPCTRL_CMD_SET_BAR: { + uint32_t bar_no =3D epctrl_reg_read(s, EPCTRL_BAR_NO); + + if (bar_no >=3D EPCTRL_MAX_BARS) { + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-ctrl: CMD_SET_BAR bad bar_no %u\n", bar= _no); + break; + } + s->bar_phys[bar_no] =3D + (uint64_t)epctrl_reg_read(s, EPCTRL_BAR_PHYS_HI) << 32 | + epctrl_reg_read(s, EPCTRL_BAR_PHYS_LO); + s->bar_size[bar_no] =3D + (uint64_t)epctrl_reg_read(s, EPCTRL_BAR_SIZE_HI) << 32 | + epctrl_reg_read(s, EPCTRL_BAR_SIZE_LO); + s->bar_flags[bar_no] =3D epctrl_reg_read(s, EPCTRL_BAR_FLAGS); + break; + } + + case EPCTRL_CMD_CLEAR_BAR: { + uint32_t bar_no =3D epctrl_reg_read(s, EPCTRL_BAR_NO); + + if (bar_no >=3D EPCTRL_MAX_BARS) { + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-ctrl: CMD_CLEAR_BAR bad bar_no %u\n", + bar_no); + break; + } + s->bar_phys[bar_no] =3D 0; + s->bar_size[bar_no] =3D 0; + s->bar_flags[bar_no] =3D 0; + break; + } + + case EPCTRL_CMD_MAP: { + uint64_t local_phys =3D + (uint64_t)epctrl_reg_read(s, EPCTRL_MAP_PHYS_HI) << 32 | + epctrl_reg_read(s, EPCTRL_MAP_PHYS_LO); + uint64_t pci_addr =3D + (uint64_t)epctrl_reg_read(s, EPCTRL_MAP_PCI_HI) << 32 | + epctrl_reg_read(s, EPCTRL_MAP_PCI_LO); + uint64_t map_size =3D + (uint64_t)epctrl_reg_read(s, EPCTRL_MAP_SIZE_HI) << 32 | + epctrl_reg_read(s, EPCTRL_MAP_SIZE_LO); + uint64_t win_off; + + if (map_size =3D=3D 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-ctrl: CMD_MAP with zero size\n"); + break; + } + if (local_phys < s->ob_base || + local_phys >=3D s->ob_base + s->ob_size) { + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-ctrl: outbound map addr 0x%" PRIx64 + " outside window [0x%" PRIx64 "+0x%" PRIx64 "]\n= ", + local_phys, s->ob_base, s->ob_size); + break; + } + win_off =3D local_phys - s->ob_base; + if (map_size > s->ob_size - win_off) { + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-ctrl: outbound map [0x%" PRIx64 "+0x%" + PRIx64 "] exceeds window [0x%" PRIx64 "+0x%" + PRIx64 "]\n", + local_phys, map_size, s->ob_base, s->ob_size); + break; + } + epctrl_ob_map(s, win_off, pci_addr, map_size); + break; + } + + case EPCTRL_CMD_UNMAP: { + uint64_t local_phys =3D + (uint64_t)epctrl_reg_read(s, EPCTRL_MAP_PHYS_HI) << 32 | + epctrl_reg_read(s, EPCTRL_MAP_PHYS_LO); + uint64_t win_off; + + if (local_phys < s->ob_base || + local_phys >=3D s->ob_base + s->ob_size) { + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-ctrl: outbound unmap addr 0x%" PRIx64 + " outside window [0x%" PRIx64 "+0x%" PRIx64 "]\n= ", + local_phys, s->ob_base, s->ob_size); + break; + } + win_off =3D local_phys - s->ob_base; + epctrl_ob_unmap(s, win_off); + break; + } + + case EPCTRL_CMD_SET_MSI: + case EPCTRL_CMD_SET_MSIX: { + /* + * MSI and MSI-X counts are consumed at CMD_START. Seed the readba= ck + * so an early get_msi() before the RC enables MSI still returns a + * plausible count. Once the Endpoint exists, the read handler + * overrides this with the live negotiated value. + */ + uint32_t n =3D epctrl_reg_read(s, EPCTRL_MSI_COUNT); + + epctrl_reg_write(s, EPCTRL_MSI_READBACK, n); + epctrl_reg_write(s, EPCTRL_MSIX_READBACK, n); + break; + } + + case EPCTRL_CMD_RAISE_IRQ: + if (s->endpoint) { + pcie_ep_endpoint_raise_irq(s->endpoint, + epctrl_reg_read(s, EPCTRL_IRQ_TYPE), + epctrl_reg_read(s, EPCTRL_IRQ_NUM)); + } + break; + + case EPCTRL_CMD_START: + s->started =3D true; + qemu_bh_schedule(s->link_bh); + break; + + case EPCTRL_CMD_STOP: + s->started =3D false; + qemu_bh_schedule(s->link_bh); + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pcie-ep-ctrl: unknown command 0x%"PRIx32"\n", cmd); + break; + } +} + +static uint64_t pcie_ep_ctrl_read(void *opaque, hwaddr addr, unsigned size) +{ + PCIeEPCtrlState *s =3D opaque; + PCIeEPMsiState st; + + if ((addr & 3) || addr / 4 >=3D EPCTRL_REG_COUNT) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad read offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return 0; + } + + /* + * MSI/MSI-X negotiation state is owned by the Endpoint's Configuration + * Space, so compute it live from the Endpoint when it exists. Before = the + * Endpoint is created (or if the RC has not enabled MSI yet), fall th= rough + * to the value the MSI_COUNT write seeded. + */ + if (s->endpoint) { + pcie_ep_endpoint_get_msi_state(s->endpoint, &st); + + switch (addr) { + case EPCTRL_MSI_READBACK: + if (st.msi_vectors) { + return st.msi_vectors; + } + break; + case EPCTRL_MSIX_READBACK: + if (st.msix_vectors) { + return st.msix_vectors; + } + break; + case EPCTRL_MSI_ADDR_LO: + if (st.msi_vectors) { + return st.msi_addr_lo; + } + break; + case EPCTRL_MSI_ADDR_HI: + if (st.msi_vectors) { + return st.msi_addr_hi; + } + break; + case EPCTRL_MSI_DATA: + if (st.msi_vectors) { + return st.msi_data; + } + break; + default: + break; + } + } + + return epctrl_reg_read(s, addr); +} + +static void pcie_ep_ctrl_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + PCIeEPCtrlState *s =3D opaque; + + if ((addr & 3) || addr / 4 >=3D EPCTRL_REG_COUNT) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: bad write offset 0x%"HWADDR_PRIx"\n", + __func__, addr); + return; + } + + /* Read-only registers */ + switch (addr) { + case EPCTRL_CAPS: + case EPCTRL_MAX_FUNC: + case EPCTRL_LINKUP: + case EPCTRL_MSI_READBACK: + case EPCTRL_MSIX_READBACK: + case EPCTRL_MSI_ADDR_LO: + case EPCTRL_MSI_ADDR_HI: + case EPCTRL_MSI_DATA: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only register 0x%"HWADDR_PRIx"\n", + __func__, addr); + return; + } + + if (addr =3D=3D EPCTRL_CMD) { + epctrl_dispatch_cmd(s, (uint32_t)value); + return; + } + + /* Everything else is plain scratchpad, consumed at CMD_* dispatch tim= e */ + epctrl_reg_write(s, addr, (uint32_t)value); +} + +static const MemoryRegionOps pcie_ep_ctrl_ops =3D { + .read =3D pcie_ep_ctrl_read, + .write =3D pcie_ep_ctrl_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, + .impl =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void pcie_ep_ctrl_reset(DeviceState *dev) +{ + PCIeEPCtrlState *s =3D PCIE_EP_CTRL(dev); + + memset(s->regs, 0, sizeof(s->regs)); + epctrl_reg_write(s, EPCTRL_CAPS, EPCTRL_CAPS_ALL); + epctrl_reg_write(s, EPCTRL_MAX_FUNC, 1); +} + +static void pcie_ep_ctrl_realize(DeviceState *dev, Error **errp) +{ + PCIeEPCtrlState *s =3D PCIE_EP_CTRL(dev); + + if (!s->target_bus) { + error_setg(errp, "pcie-ep-ctrl: 'target-bus' property is required"= ); + return; + } + + memory_region_init_io(&s->ctrl_mr, OBJECT(s), &pcie_ep_ctrl_ops, s, + "pcie-ep-ctrl", EPCTRL_CTRL_SIZE); + + memory_region_init(&s->ob_mr, OBJECT(s), "pcie-ep-ob", s->ob_size); + + epctrl_reg_write(s, EPCTRL_CAPS, EPCTRL_CAPS_ALL); + epctrl_reg_write(s, EPCTRL_MAX_FUNC, 1); + + s->link_bh =3D qemu_bh_new_guarded(epctrl_link_bh, s, + &dev->mem_reentrancy_guard); +} + +static void pcie_ep_ctrl_unrealize(DeviceState *dev) +{ + PCIeEPCtrlState *s =3D PCIE_EP_CTRL(dev); + + epctrl_ob_unmap_all(s); + if (s->link_bh) { + qemu_bh_delete(s->link_bh); + s->link_bh =3D NULL; + } +} + +static void pcie_ep_ctrl_instance_init(Object *obj) +{ + SysBusDevice *sbd =3D SYS_BUS_DEVICE(obj); + PCIeEPCtrlState *s =3D PCIE_EP_CTRL(obj); + + sysbus_init_mmio(sbd, &s->ctrl_mr); + sysbus_init_mmio(sbd, &s->ob_mr); +} + +/* + * The controller mirrors live Endpoint state that is built at runtime, so= it + * is not migratable. + */ +static const VMStateDescription vmstate_pcie_ep_ctrl =3D { + .name =3D "pcie-ep-ctrl", + .unmigratable =3D 1, +}; + +static const Property pcie_ep_ctrl_props[] =3D { + DEFINE_PROP_UINT64("outbound-size", PCIeEPCtrlState, ob_size, 16 * MiB= ), + DEFINE_PROP_STRING("target-bus", PCIeEPCtrlState, target_bus), +}; + +static void pcie_ep_ctrl_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(oc); + + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + dc->realize =3D pcie_ep_ctrl_realize; + dc->unrealize =3D pcie_ep_ctrl_unrealize; + dc->vmsd =3D &vmstate_pcie_ep_ctrl; + dc->user_creatable =3D true; + device_class_set_legacy_reset(dc, pcie_ep_ctrl_reset); + device_class_set_props(dc, pcie_ep_ctrl_props); +} + +static const TypeInfo pcie_ep_ctrl_info =3D { + .name =3D TYPE_PCIE_EP_CTRL, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(PCIeEPCtrlState), + .instance_init =3D pcie_ep_ctrl_instance_init, + .class_init =3D pcie_ep_ctrl_class_init, +}; + +static void pcie_ep_ctrl_register_types(void) +{ + type_register_static(&pcie_ep_ctrl_info); +} +type_init(pcie_ep_ctrl_register_types) diff --git a/include/hw/misc/pcie-ep-ctrl.h b/include/hw/misc/pcie-ep-ctrl.h new file mode 100644 index 0000000000..de7f370ffc --- /dev/null +++ b/include/hw/misc/pcie-ep-ctrl.h @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * QEMU PCIe Endpoint Controller device + */ + +#ifndef HW_MISC_PCIE_EP_CTRL_H +#define HW_MISC_PCIE_EP_CTRL_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +/* Maximum number of standard BARs on a PCI Function */ +#define EPCTRL_MAX_BARS 6 + +/* Number of 32-bit registers in the ctrl MMIO region */ +#define EPCTRL_REG_COUNT (0x100 / 4) + +/* Maximum number of concurrent outbound mappings */ +#define EPCTRL_OB_MAP_MAX 16 + +#define TYPE_PCIE_EP_CTRL "pcie-ep-ctrl" +OBJECT_DECLARE_SIMPLE_TYPE(PCIeEPCtrlState, PCIE_EP_CTRL) + +/* One active outbound mapping: an alias into system RAM added to ob_mr. */ +typedef struct EPCtrlObMap { + MemoryRegion *alias; /* NULL when the slot is free */ + uint64_t win_off; /* byte offset within the outbound window */ +} EPCtrlObMap; + +struct PCIeEPCtrlState { + SysBusDevice parent_obj; + + /* ctrl MMIO register file */ + MemoryRegion ctrl_mr; + /* outbound DMA window (container of per-mapping aliases) */ + MemoryRegion ob_mr; + + /* Properties */ + uint64_t ob_size; + char *target_bus; /* id of the RC Root Port to hotplug the EP ont= o */ + + /* + * Absolute CPU address where the machine mapped the outbound window. + * Set after placement (the arm virt platform bus assigns it), used to + * translate guest-programmed addresses into window offsets. + */ + uint64_t ob_base; + + /* Active outbound mappings */ + EPCtrlObMap ob_map[EPCTRL_OB_MAP_MAX]; + + /* The hotplugged PCI Endpoint, NULL until CMD_START */ + DeviceState *endpoint; + QEMUBH *link_bh; + + /* BAR configuration tracked as the EP driver programs it */ + uint64_t bar_phys[EPCTRL_MAX_BARS]; + uint64_t bar_size[EPCTRL_MAX_BARS]; + uint32_t bar_flags[EPCTRL_MAX_BARS]; + + bool started; /* set when CMD_START has been issued (not migrated) */ + + /* Register state */ + uint32_t regs[EPCTRL_REG_COUNT]; +}; + +#endif /* HW_MISC_PCIE_EP_CTRL_H */ --=20 2.43.0 From nobody Sat Sep 26 20:50:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=kernel.org ARC-Seal: i=1; a=rsa-sha256; t=1789320355; cv=none; d=zohomail.com; s=zohoarc; b=Kge55xJFhGyY6IzTxDh/+AIhvfYOQBgoA1mYbMSbciyCgysqQf2FLFRIpDhNO7ZLiutw7v5RPfkUzlqEP4In5OeWJ+DIzxYbuwqSLiKRdw6J6HVQXHEuZs0E5mNXKGcUN/yZYDdVQG+53nZ9b/1zVjOI0XzOs368WE9ITf6FrOk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1789320355; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=t3eABMYx10xOCPmuL2/oN9NpnfYFZjUwGHohBGog74I=; b=bjoO1RV0Nj/dcNbvAhPY4FUfnrHOu1cS24izd43FyBIbn86ECnCJWewyRj8e2Xf72Q2zB2nGknTyIhFCepbtXdZBZxuG3vRujxV5XvkMDlK0NrRphzUXTTYNNjGfjD4DOesNDsF9zdcAZDYz6uS1NDC5TQyXR2FDZ+CGKyvvaxc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789320355329272.4843084034919; Sun, 13 Sep 2026 10:25:55 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x5nwr-0003J3-8J; Sun, 13 Sep 2026 13:25:02 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5nUM-00009H-2Z; Sun, 13 Sep 2026 12:55:34 -0400 Received: from tor.source.kernel.org ([172.105.4.254]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x5nU0-0005Av-LM; Sun, 13 Sep 2026 12:55:33 -0400 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) by tor.source.kernel.org (Postfix) with ESMTP id 647DB60E85; Sun, 13 Sep 2026 16:55:11 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 034D81F000FF; Sun, 13 Sep 2026 16:55:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789318511; bh=t3eABMYx10xOCPmuL2/oN9NpnfYFZjUwGHohBGog74I=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=eizgEX5g+LzHDzxDI0ZYEurk8CmMC5Xc/oZdKCk8us7oZqvu5Lb1dlqkiIUa9gudg em8avjIi6H7cNCemJR19aQoX1WEw4wS/ZDEco9Rp5l26WdJrKJnG4Hi2/W6skdNoLC X9P0+Txz5q2OyKMpwNA1Ny0n++z4YVtEfPObKHUjxfNFW8Bmaqfhg8q0i6CquICgt2 wV/8zQEyx2/fZixGnICgKXeIT2NZ1PcOoU61m+oVx/DEyqSXOqkJPv4wX+eyGTJpQy k3HK4Y3pR7EmeJuEMq4xw9qhWNhsoEExDssmTTxZogNzEnsHZH27rkcXyAgYlLb7b1 6E+D76EJ42y2Q== From: Manivannan Sadhasivam Date: Sun, 13 Sep 2026 18:54:54 +0200 Subject: [PATCH 4/4] hw/arm/virt: Add PCIe Endpoint Controller support MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260913-pcie-ep-emulation-v1-4-b8e8f74a5842@kernel.org> References: <20260913-pcie-ep-emulation-v1-0-b8e8f74a5842@kernel.org> In-Reply-To: <20260913-pcie-ep-emulation-v1-0-b8e8f74a5842@kernel.org> To: qemu-devel@nongnu.org Cc: Pierrick Bouvier , "Michael S. Tsirkin" , Paolo Bonzini , Peter Maydell , qemu-arm@nongnu.org, Manivannan Sadhasivam X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=6182; i=mani@kernel.org; h=from:subject:message-id; bh=4wOqO45KFOwtM58aoP89+yBny+rNvXOMQO9Jja7LR44=; b=owEBbQGS/pANAwAKAVWfEeb+kc71AcsmYgBqptVhKHyh+8Nty6VEkecocuTJaI/LzjuYreeK2 YuMQxx0vTiJATMEAAEKAB0WIQRnpUMqgUjL2KRYJ5dVnxHm/pHO9QUCaqbVYQAKCRBVnxHm/pHO 9aclB/sFfVrcq0V4YoyqGcZTA/YSLMaPX+M7FuZLmaZHNls7FA3Iod7qhENeZzqaPWi+Uz8HTsa ErgNVquWZoxwMhv67NYq21QPyvbcol+JR9YZ+PxhuD/TcIU460k0y5COCiYsKgGFJtl/G3uCoUV 7fZIxbqZrmRae/DAMJ+pfPjGo8Dq0QVFQkHD8UymqjYIiKgg4lxwJeT4X8097Z7V/JbyYYV8uh2 nmRXH87sEfo0XhW38vQHob3mhLeMhjPuySQeqhZ5sgPrGW5n+wx0UOk2LtkshUfTyFmU66kEXil tzHpxU38E2E4MF2LJ0rU3kI/oqYhRBL+nQAHW0NbvNt2HHTF X-Developer-Key: i=mani@kernel.org; a=openpgp; fpr=C668AEC3C3188E4C611465E7488550E901166008 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=172.105.4.254; envelope-from=mani@kernel.org; helo=tor.source.kernel.org X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sun, 13 Sep 2026 13:24:48 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @kernel.org) X-ZM-MESSAGEID: 1789320356009158500 From: Manivannan Sadhasivam Wire the pcie-ep-ctrl controller into the ARM virt machine as a dynamic sysbus device on the platform bus. -device pcie-ep-ctrl,target-bus=3DID The platform bus assigns the MMIO windows, so the machine emits the device tree node (compatible "pci-ep-generic") with the resolved addresses. The node is also marked dma-coherent. This allows the Endpoint Controller driver to allocate coherent DMA memory from system RAM for the Endpoint Function BARs. Allow the device on the platform bus with machine_class_allow_dynamic_sysbus_dev(). Also register a no_fdt_node binding in hw/core/sysbus-fdt.c so the generic platform bus code leaves the node to the machine. The target-bus property names the PCIe Root Port the Endpoint Function is hotplugged onto when the Link comes up. A single guest kernel then drives the controller through the pci-ep-generic EPC driver. In the same kernel, it enumerates the resulting Function as a PCI host. This allows running Linux EPF drivers such as pci-epf-test against the matching host driver without a second QEMU instance. Signed-off-by: Manivannan Sadhasivam --- hw/arm/Kconfig | 1 + hw/arm/virt.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++= ++++ hw/core/sysbus-fdt.c | 3 +++ 3 files changed, 61 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index fb798ccbee..0c8266d403 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -36,6 +36,7 @@ config ARM_VIRT select VIRTIO_MEM_SUPPORTED select ACPI_CXL select ACPI_HMAT + select PCIE_EP_CTRL =20 config CUBIEBOARD bool diff --git a/hw/arm/virt.c b/hw/arm/virt.c index b090233893..25091d2639 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -94,6 +94,7 @@ #include "hw/core/cpu.h" #include "hw/cxl/cxl.h" #include "hw/cxl/cxl_host.h" +#include "hw/misc/pcie-ep-ctrl.h" #include "qemu/guest-random.h" =20 static GlobalProperty arm_virt_compat_defaults[] =3D { @@ -1569,6 +1570,57 @@ static void create_rtc(const VirtMachineState *vms) g_free(nodename); } =20 +/* + * Emit the device tree node for a pcie-ep-ctrl instantiated on the platfo= rm + * bus and hand the device the absolute addresses the bus assigned to its + * regions. Called from the plug handler once platform_bus_link_device() h= as + * placed the MMIO regions, mirroring how the SMMUv3 device builds its own + * node in create_smmuv3_dev_dtb(). + */ +static void create_pcie_ep_ctrl_dtb(VirtMachineState *vms, DeviceState *de= v) +{ + PlatformBusDevice *pbus =3D PLATFORM_BUS_DEVICE(vms->platform_bus_dev); + SysBusDevice *sbdev =3D SYS_BUS_DEVICE(dev); + PCIeEPCtrlState *ec =3D PCIE_EP_CTRL(dev); + MachineState *ms =3D MACHINE(vms); + hwaddr pbus_base =3D vms->memmap[VIRT_PLATFORM_BUS].base; + hwaddr ctrl_base, ob_base; + hwaddr ctrl_size, ob_size; + char *nodename; + static const char compat[] =3D "pci-ep-generic"; + static const char reg_names[] =3D "ctrl\0outbound"; + + /* Absolute base of each region as the platform bus mapped it. */ + ctrl_base =3D pbus_base + platform_bus_get_mmio_addr(pbus, sbdev, 0); + ob_base =3D pbus_base + platform_bus_get_mmio_addr(pbus, sbdev, 1); + + ctrl_size =3D memory_region_size(sysbus_mmio_get_region(sbdev, 0)); + ob_size =3D memory_region_size(sysbus_mmio_get_region(sbdev, 1)); + + /* + * The EP guest programs outbound window addresses using these absolut= e CPU + * addresses, so the device must know where the platform bus placed the + * region to translate them back into window offsets. + */ + ec->ob_base =3D ob_base; + + nodename =3D g_strdup_printf("/pcie-ep@%" PRIx64, ctrl_base); + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop(ms->fdt, nodename, "compatible", + compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", + 2, ctrl_base, 2, ctrl_size, + 2, ob_base, 2, ob_size); + qemu_fdt_setprop(ms->fdt, nodename, "reg-names", + reg_names, sizeof(reg_names)); + /* + * Mark the controller DMA-coherent so the EPC driver's dma_alloc_cohe= rent + * returns cacheable coherent RAM for the Endpoint BAR backing buffers. + */ + qemu_fdt_setprop(ms->fdt, nodename, "dma-coherent", NULL, 0); + g_free(nodename); +} + static DeviceState *gpio_key_dev; static void virt_powerdown_req(Notifier *n, void *opaque) { @@ -3843,6 +3895,10 @@ static void virt_machine_device_plug_cb(HotplugHandl= er *hotplug_dev, if (device_is_dynamic_sysbus(mc, dev)) { platform_bus_link_device(PLATFORM_BUS_DEVICE(vms->platform_bus= _dev), SYS_BUS_DEVICE(dev)); + + if (object_dynamic_cast(OBJECT(dev), TYPE_PCIE_EP_CTRL)) { + create_pcie_ep_ctrl_dtb(vms, dev); + } } } =20 @@ -4091,6 +4147,7 @@ static void virt_machine_class_init(ObjectClass *oc, = const void *data) #ifdef CONFIG_TPM machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS); #endif + machine_class_allow_dynamic_sysbus_dev(mc, TYPE_PCIE_EP_CTRL); mc->block_default_type =3D IF_VIRTIO; mc->no_cdrom =3D 1; mc->pci_allow_0_address =3D true; diff --git a/hw/core/sysbus-fdt.c b/hw/core/sysbus-fdt.c index 89d0c46445..60586cc9b2 100644 --- a/hw/core/sysbus-fdt.c +++ b/hw/core/sysbus-fdt.c @@ -36,6 +36,7 @@ #include "hw/display/ramfb.h" #include "hw/uefi/var-service-api.h" #include "hw/arm/fdt.h" +#include "hw/misc/pcie-ep-ctrl.h" =20 /* * internal struct that contains the information to create dynamic @@ -140,6 +141,8 @@ static const BindingEntry bindings[] =3D { TYPE_BINDING(TYPE_ARM_SMMUV3, no_fdt_node), TYPE_BINDING(TYPE_RAMFB_DEVICE, no_fdt_node), TYPE_BINDING(TYPE_UEFI_VARS_SYSBUS, add_uefi_vars_node), + /* Node emitted by the arm virt machine (create_pcie_ep_ctrl_dtb) */ + TYPE_BINDING(TYPE_PCIE_EP_CTRL, no_fdt_node), TYPE_BINDING("", NULL), /* last element */ }; =20 --=20 2.43.0