mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 03:27:30 -04:00
PCI: endpoint: pci-ep-msi: Add embedded doorbell fallback
Some endpoint platforms cannot use platform MSI / GIC ITS to implement EP-side doorbells. In those cases, EPF drivers cannot provide an interrupt-driven doorbell and often fall back to polling. Add an "embedded" doorbell backend that uses a controller-integrated doorbell target (e.g. DesignWare integrated eDMA interrupt-emulation doorbell). The backend locates the doorbell register and a corresponding Linux IRQ via the EPC aux-resource API. If the doorbell register is already exposed via a fixed BAR mapping, provide BAR+offset. Otherwise provide the DMA address returned by dma_map_resource() (which may be an IOVA when an IOMMU is enabled) so EPF drivers can map it into BAR space. When MSI doorbell allocation fails with -ENODEV, pci_epf_alloc_doorbell() falls back to this embedded backend. Suggested-by: Manivannan Sadhasivam <mani@kernel.org> Signed-off-by: Koichiro Den <den@valinux.co.jp> Signed-off-by: Manivannan Sadhasivam <mani@kernel.org> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Link: https://patch.msgid.link/20260414141514.1341429-8-den@valinux.co.jp
This commit is contained in:
committed by
Bjorn Helgaas
parent
8fda2dd209
commit
e4f2624395
@@ -6,6 +6,8 @@
|
||||
* Author: Frank Li <Frank.Li@nxp.com>
|
||||
*/
|
||||
|
||||
#include <linux/align.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/interrupt.h>
|
||||
@@ -36,6 +38,109 @@ static void pci_epf_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
|
||||
pci_epc_put(epc);
|
||||
}
|
||||
|
||||
static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db)
|
||||
{
|
||||
const struct pci_epc_aux_resource *doorbell = NULL;
|
||||
struct pci_epf_doorbell_msg *msg;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
size_t map_size = 0, off = 0;
|
||||
dma_addr_t iova_base = 0;
|
||||
phys_addr_t phys_base;
|
||||
int count, ret, i;
|
||||
u64 addr;
|
||||
|
||||
count = pci_epc_get_aux_resources_count(epc, epf->func_no,
|
||||
epf->vfunc_no);
|
||||
if (count < 0)
|
||||
return count;
|
||||
if (!count)
|
||||
return -ENODEV;
|
||||
|
||||
struct pci_epc_aux_resource *res __free(kfree) =
|
||||
kcalloc(count, sizeof(*res), GFP_KERNEL);
|
||||
if (!res)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = pci_epc_get_aux_resources(epc, epf->func_no, epf->vfunc_no,
|
||||
res, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* TODO: Support multiple DOORBELL_MMIO resources per EPC. */
|
||||
for (i = 0; i < count; i++) {
|
||||
if (res[i].type != PCI_EPC_AUX_DOORBELL_MMIO)
|
||||
continue;
|
||||
|
||||
doorbell = &res[i];
|
||||
break;
|
||||
}
|
||||
if (!doorbell)
|
||||
return -ENODEV;
|
||||
addr = doorbell->phys_addr;
|
||||
if (!IS_ALIGNED(addr, sizeof(u32)))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Reuse the pre-exposed BAR window if available. Otherwise map the MMIO
|
||||
* doorbell resource here. Any required IOMMU mapping is handled
|
||||
* internally, matching the MSI doorbell semantics.
|
||||
*/
|
||||
if (doorbell->bar == NO_BAR) {
|
||||
phys_base = addr & PAGE_MASK;
|
||||
off = addr - phys_base;
|
||||
map_size = PAGE_ALIGN(off + sizeof(u32));
|
||||
|
||||
iova_base = dma_map_resource(epc->dev.parent, phys_base,
|
||||
map_size, DMA_FROM_DEVICE, 0);
|
||||
if (dma_mapping_error(epc->dev.parent, iova_base))
|
||||
return -EIO;
|
||||
|
||||
addr = iova_base + off;
|
||||
}
|
||||
|
||||
msg = kcalloc(num_db, sizeof(*msg), GFP_KERNEL);
|
||||
if (!msg) {
|
||||
ret = -ENOMEM;
|
||||
goto err_unmap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Embedded doorbell backends (e.g. DesignWare eDMA interrupt emulation)
|
||||
* typically provide a single IRQ and do not offer per-doorbell
|
||||
* distinguishable address/data pairs. The EPC aux resource therefore
|
||||
* exposes one DOORBELL_MMIO entry (u.db_mmio.irq).
|
||||
*
|
||||
* Still, pci_epf_alloc_doorbell() allows requesting multiple doorbells.
|
||||
* For such backends we replicate the same address/data for each entry
|
||||
* and mark the IRQ as shared (IRQF_SHARED). Consumers must treat them
|
||||
* as equivalent "kick" doorbells.
|
||||
*/
|
||||
for (i = 0; i < num_db; i++)
|
||||
msg[i] = (struct pci_epf_doorbell_msg) {
|
||||
.msg.address_lo = (u32)addr,
|
||||
.msg.address_hi = (u32)(addr >> 32),
|
||||
.msg.data = doorbell->u.db_mmio.data,
|
||||
.virq = doorbell->u.db_mmio.irq,
|
||||
.irq_flags = IRQF_SHARED,
|
||||
.type = PCI_EPF_DOORBELL_EMBEDDED,
|
||||
.bar = doorbell->bar,
|
||||
.offset = (doorbell->bar == NO_BAR) ? 0 :
|
||||
doorbell->bar_offset,
|
||||
.iova_base = iova_base,
|
||||
.iova_size = map_size,
|
||||
};
|
||||
|
||||
epf->num_db = num_db;
|
||||
epf->db_msg = msg;
|
||||
return 0;
|
||||
|
||||
err_unmap:
|
||||
if (map_size)
|
||||
dma_unmap_resource(epc->dev.parent, iova_base, map_size,
|
||||
DMA_FROM_DEVICE, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pci_epf_alloc_doorbell_msi(struct pci_epf *epf, u16 num_db)
|
||||
{
|
||||
struct pci_epf_doorbell_msg *msg;
|
||||
@@ -109,18 +214,38 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
dev_err(dev, "Failed to allocate doorbell: %d\n", ret);
|
||||
return ret;
|
||||
/*
|
||||
* Fall back to embedded doorbell only when platform MSI is unavailable
|
||||
* for this EPC.
|
||||
*/
|
||||
if (ret != -ENODEV)
|
||||
return ret;
|
||||
|
||||
ret = pci_epf_alloc_doorbell_embedded(epf, num_db);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to allocate doorbell: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "Using embedded (DMA) doorbell fallback\n");
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_alloc_doorbell);
|
||||
|
||||
void pci_epf_free_doorbell(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_doorbell_msg *msg0;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
|
||||
if (!epf->db_msg)
|
||||
return;
|
||||
|
||||
if (epf->db_msg[0].type == PCI_EPF_DOORBELL_MSI)
|
||||
msg0 = &epf->db_msg[0];
|
||||
if (msg0->type == PCI_EPF_DOORBELL_MSI)
|
||||
platform_device_msi_free_irqs_all(epf->epc->dev.parent);
|
||||
else if (msg0->type == PCI_EPF_DOORBELL_EMBEDDED && msg0->iova_size)
|
||||
dma_unmap_resource(epc->dev.parent, msg0->iova_base,
|
||||
msg0->iova_size, DMA_FROM_DEVICE, 0);
|
||||
|
||||
kfree(epf->db_msg);
|
||||
epf->db_msg = NULL;
|
||||
|
||||
@@ -171,6 +171,12 @@ enum pci_epf_doorbell_type {
|
||||
* (NO_BAR if not)
|
||||
* @offset: offset within @bar for the doorbell target (valid iff
|
||||
* @bar != NO_BAR)
|
||||
* @iova_base: Internal: base DMA address returned by dma_map_resource() for the
|
||||
* embedded doorbell MMIO window (used only for unmapping). Valid
|
||||
* when @type is PCI_EPF_DOORBELL_EMBEDDED and @iova_size is
|
||||
* non-zero.
|
||||
* @iova_size: Internal: size of the dma_map_resource() mapping at @iova_base.
|
||||
* Zero when no mapping was created (e.g. pre-exposed fixed BAR).
|
||||
*/
|
||||
struct pci_epf_doorbell_msg {
|
||||
struct msi_msg msg;
|
||||
@@ -179,6 +185,8 @@ struct pci_epf_doorbell_msg {
|
||||
enum pci_epf_doorbell_type type;
|
||||
enum pci_barno bar;
|
||||
resource_size_t offset;
|
||||
dma_addr_t iova_base;
|
||||
size_t iova_size;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user