[PATCH v5 0/4] PCI: Add DOE support for endpoint

Frank Li Frank.li at oss.nxp.com
Thu Jun 11 13:47:18 PDT 2026


On Wed, Jun 10, 2026 at 03:32:52PM +0530, Aksh Garg wrote:
> This patch series introduces the framework for supporting the Data
> Object Exchange (DOE) feature for PCIe endpoint devices. Please refer
> to the documentation added in patch 4 for details on the feature and
> implementation architecture.
>
> The implementation provides a common framework for all PCIe endpoint
> controllers, not specific to any particular SoC vendor.
>

General question, does DOE generate irq when received msg for HOST? I have
not related irq handle code.

Any program to test it? such as pci_endpoint_test, need at least one real
user to use it.

Frank

> The changes since v1 are documented in the respective patch descriptions.
>
> v4: https://lore.kernel.org/all/20260522052434.802034-1-a-garg7@ti.com/
> v3: https://lore.kernel.org/all/20260427051725.223704-1-a-garg7@ti.com/
> v2: https://lore.kernel.org/all/20260401073022.215805-1-a-garg7@ti.com/
> v1 (RFC): https://lore.kernel.org/all/20260213123603.420941-1-a-garg7@ti.com/
>
> Below is a code demonstration showing the integration of DOE-EP APIs with
> EPC drivers.
>
> Note: The provided code is just to show how an EPC driver is expected to
>       utilize the pci_ep_doe_process_request() and pci_ep_doe_abort() APIs,
>       and might not cover all the corner cases. The below implementation
>       also expects the EPC hardware to have some memory buffer to store the
>       data from(for) write_mailbox(read_mailbox) DOE capability registers.
>
> ============================================================================
>
> /* ========== DOE Completion Callback (invoked by DOE-EP core) ========== */
>
> static void doe_completion_cb(struct pci_epc *epc, u8 func_no, u16 cap_offset,
> 			       int status, u16 vendor, u8 type,
> 			       void *response_pl, size_t response_pl_sz)
> {
> 	struct epc_driver *drv = epc_get_drvdata(epc);
> 	u32 *response = (u32 *)response_pl;
> 	u32 header1, header2;
> 	int payload_dw, i;
>
> 	if (readl(drv->base + PF_DOE_CTRL_REG(func_no, cap_offset)) & DOE_CTRL_ABORT) {
> 		/* Aborted: do not send response */
> 		goto free;
> 	}
>
> 	if (status < 0) {
> 		/* Error: set ERROR bit in DOE Status register */
> 		writel(1 << DOE_STATUS_ERROR,
> 		       drv->base + PF_DOE_STATUS_REG(func_no, cap_offset));
> 		goto free;
> 	}
>
> 	/* Success: write DOE headers first, then response to the read memory */
>
> 	/* Header 1: Vendor ID (bits 15:0) | Type (bits 23:16) */
> 	header1 = (type << 16) | vendor;
> 	writel(header1, drv->base + PF_DOE_RD_MEMORY_WR_REG(func_no, cap_offset));
>
> 	/* Header 2: Length in DW (including 2 DW of headers + payload) */
> 	payload_dw = DIV_ROUND_UP(response_pl_sz, sizeof(u32));
> 	header2 = 2 + payload_dw;  /* 2 header DWs + payload */
> 	writel(header2, drv->base + PF_DOE_RD_MEMORY_WR_REG(func_no, cap_offset));
>
> 	/* Set READY bit to signal response ready */
> 	writel(1 << DOE_STATUS_READY,
> 	       drv->base + PF_DOE_STATUS_REG(func_no, cap_offset));
>
> 	/* Write response payload DWORDs to Read memory */
> 	for (i = 0; i < payload_dw; i++)
> 		writel(response[i],
> 		       drv->base + PF_DOE_RD_MEMORY_WR_REG(func_no, cap_offset));
>
> 	/* Wait for the memory to empty before clearing the READY bit */
> 	while (!RD_MEMORY_EMPTY()) {/* wait */}
>
> 	writel(0 << DOE_STATUS_READY,
> 	       drv->base + PF_DOE_STATUS_REG(func_no, cap_offset));
>
> free:
> 	/* unset BUSY bit */
> 	writel(0 << DOE_STATUS_BUSY,
> 	       drv->base + PF_DOE_STATUS_REG(func_no, cap_offset));
>
> 	kfree(response_pl);
> }
>
> /* ========== DOE Interrupt Handler (triggered on GO bit from root complex) ========== */
>
> static irqreturn_t doe_interrupt_handler(int irq, void *priv)
> {
> 	struct epc_driver *drv = priv;
> 	u16 cap_offset = extract_cap_offset_from_irq(irq);
> 	u8 func_no = extract_func_from_irq(irq);
> 	u32 header1, header2, length_dw, *request;
> 	u16 vendor;
> 	u8 type;
> 	int i, ret;
>
> 	/* Read first header DWORD: Vendor ID (bits 15:0) | Type (bits 23:16) */
> 	header1 = readl(drv->base + PF_DOE_WR_MEMORY_RD_REG(func_no, cap_offset));
> 	vendor = header1 & 0xFFFF;
> 	type = (header1 >> 16) & 0xFF;
>
> 	/* Read second header DWORD: Length in DW (includes 2 DW of headers) */
> 	header2 = readl(drv->base + PF_DOE_WR_MEMORY_RD_REG(func_no, cap_offset));
> 	length_dw = header2 & 0x3FFFF;  /* Bits 17:0 */
>
> 	if (!length_dw)
> 		length_dw = PCI_DOE_MAX_LENGTH;
>
> 	length_dw -= 2;  /* Subtract 2 DW of headers to get payload length */
> 	/* Allocate buffer for complete request (headers + payload) */
> 	request = kzalloc(length_dw * sizeof(u32), GFP_ATOMIC);
> 	if (!request) {
> 		writel(1 << DOE_STATUS_ERROR,
> 		       drv->base + PF_DOE_STATUS_REG(func_no, cap_offset));
> 		return IRQ_HANDLED;
> 	}
>
> 	/* Read remaining payload DWORDs from Write memory */
> 	for (i = 0; i < length_dw; i++) {
> 		while (WR_MEMORY_EMPTY()) { /* wait */ }
> 		request[i] = readl(drv->base + PF_DOE_WR_MEMORY_RD_REG(func_no, cap_offset));
> 	}
>
> 	mutex_lock(&lock);
> 	/* Check the ABORT bit, if set then return */
> 	if (readl(drv->base + PF_DOE_CTRL_REG(func_no, cap_offset)) & DOE_CTRL_ABORT) {
> 		kfree(request);
> 		mutex_unlock(&lock);
> 		return IRQ_HANDLED;
> 	}
>
> 	/* Set BUSY bit */
> 	writel(1 << DOE_STATUS_BUSY,
> 	       drv->base + PF_DOE_STATUS_REG(func_no, cap_offset));
> 	mutex_unlock(&lock);
>
> 	/* Hand off to DOE-EP core for asynchronous processing */
> 	ret = pci_ep_doe_process_request(drv->epc, func_no, cap_offset,
> 					 vendor, type, (void *)request,
> 					 length_dw * sizeof(u32),
> 					 doe_completion_cb);
> 	if (ret) {
> 		writel(1 << DOE_STATUS_ERROR,
> 		       drv->base + PF_DOE_STATUS_REG(func_no, cap_offset));
> 		kfree(request);
> 	}
>
> 	return IRQ_HANDLED;
> }
>
> /* ========== Abort Handler (triggered on ABORT bit from root complex) ========== */
>
> static irqreturn_t doe_abort_handler(int irq, void *priv)
> {
> 	struct epc_driver *drv = priv;
> 	u16 cap_offset = extract_cap_offset_from_irq(irq);
> 	u8 func_no = extract_func_from_irq(irq);
>
> 	mutex_lock(&lock);
>
> 	/* call abort API only if BUSY bit set (pci_ep_doe_process_request() called) */
> 	if (readl(drv->base + PF_DOE_STATUS_REG(func_no, cap_offset)) & DOE_STATUS_BUSY)
> 		pci_ep_doe_abort(drv->epc, func_no, cap_offset);
>
> 	mutex_unlock(&lock);
>
> 	/* Discard Write memory contents */
> 	writel(DOE_WR_MEMORY_CTRL_DISCARD,
> 	       drv->base + PF_DOE_WR_MEMORY_CTRL_REG(func_no, cap_offset));
>
> 	/* Clear status bits */
> 	writel((0 << DOE_STATUS_ERROR) | (0 << DOE_STATUS_READY),
> 	       drv->base + PF_DOE_STATUS_REG(func_no, cap_offset));
>
> 	return IRQ_HANDLED;
> }
>
> ====================================================================================
>
> Aksh Garg (4):
>   PCI/DOE: Move common definitions to the header file
>   PCI: endpoint: Add DOE mailbox support for endpoint functions
>   PCI: endpoint: Add support for DOE initialization and setup in EPC
>     core
>   Documentation: PCI: Add documentation for DOE endpoint support
>
>  Documentation/PCI/endpoint/index.rst          |   1 +
>  .../PCI/endpoint/pci-endpoint-doe.rst         | 333 ++++++++++
>  drivers/pci/doe.c                             |  11 -
>  drivers/pci/endpoint/Kconfig                  |  14 +
>  drivers/pci/endpoint/Makefile                 |   1 +
>  drivers/pci/endpoint/pci-ep-doe.c             | 594 ++++++++++++++++++
>  drivers/pci/endpoint/pci-epc-core.c           | 104 +++
>  drivers/pci/pci.h                             |  48 ++
>  include/linux/pci-doe.h                       |   8 +
>  include/linux/pci-epc.h                       |   9 +
>  10 files changed, 1112 insertions(+), 11 deletions(-)
>  create mode 100644 Documentation/PCI/endpoint/pci-endpoint-doe.rst
>  create mode 100644 drivers/pci/endpoint/pci-ep-doe.c
>
> --
> 2.34.1
>



More information about the linux-arm-kernel mailing list