Commit cc839bef authored by Koichiro Den's avatar Koichiro Den Committed by Bjorn Helgaas
Browse files

PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU



Extend dw_pcie_ep_set_bar() to support inbound mappings for BAR
subranges using Address Match Mode IB iATU when pci_epf_bar.num_submap
is non-zero.

Rename the existing BAR-match helper into dw_pcie_ep_ib_atu_bar() and
introduce dw_pcie_ep_ib_atu_addr() for Address Match Mode. When
num_submap is non-zero, read the assigned BAR base address and program
one inbound iATU window per subrange. Validate the submap array before
programming:
- each subrange is aligned to pci->region_align
- subranges cover the whole BAR (no gaps and no overlaps)

Track Address Match Mode mappings and tear them down on clear_bar() and
on set_bar() error paths to avoid leaving half-programmed state or
untranslated BAR holes.

Advertise this capability by extending the common feature bit
initializer macro (DWC_EPC_COMMON_FEATURES).

This enables multiple inbound windows within a single BAR, which is
useful on platforms where usable BARs are scarce but EPFs need multiple
inbound regions.

Signed-off-by: default avatarKoichiro Den <den@valinux.co.jp>
Signed-off-by: default avatarManivannan Sadhasivam <mani@kernel.org>
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
Reviewed-by: default avatarFrank Li <Frank.Li@nxp.com>
Reviewed-by: default avatarNiklas Cassel <cassel@kernel.org>
Link: https://patch.msgid.link/20260124145012.2794108-5-den@valinux.co.jp
parent c0f1506f
Loading
Loading
Loading
Loading
+203 −10
Original line number Diff line number Diff line
@@ -100,7 +100,8 @@ static int dw_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
	return 0;
}

static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, u8 func_no, int type,
/* BAR Match Mode inbound iATU mapping */
static int dw_pcie_ep_ib_atu_bar(struct dw_pcie_ep *ep, u8 func_no, int type,
				 dma_addr_t parent_bus_addr, enum pci_barno bar,
				 size_t size)
{
@@ -135,6 +136,179 @@ static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, u8 func_no, int type,
	return 0;
}

static void dw_pcie_ep_clear_ib_maps(struct dw_pcie_ep *ep, enum pci_barno bar)
{
	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
	struct device *dev = pci->dev;
	unsigned int i, num;
	u32 atu_index;
	u32 *indexes;

	/* Tear down the BAR Match Mode mapping, if any. */
	if (ep->bar_to_atu[bar]) {
		atu_index = ep->bar_to_atu[bar] - 1;
		dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, atu_index);
		clear_bit(atu_index, ep->ib_window_map);
		ep->bar_to_atu[bar] = 0;
	}

	/* Tear down all Address Match Mode mappings, if any. */
	indexes = ep->ib_atu_indexes[bar];
	num = ep->num_ib_atu_indexes[bar];
	ep->ib_atu_indexes[bar] = NULL;
	ep->num_ib_atu_indexes[bar] = 0;
	if (!indexes)
		return;
	for (i = 0; i < num; i++) {
		dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, indexes[i]);
		clear_bit(indexes[i], ep->ib_window_map);
	}
	devm_kfree(dev, indexes);
}

static u64 dw_pcie_ep_read_bar_assigned(struct dw_pcie_ep *ep, u8 func_no,
					enum pci_barno bar, int flags)
{
	u32 reg = PCI_BASE_ADDRESS_0 + (4 * bar);
	u32 lo, hi;
	u64 addr;

	lo = dw_pcie_ep_readl_dbi(ep, func_no, reg);

	if (flags & PCI_BASE_ADDRESS_SPACE)
		return lo & PCI_BASE_ADDRESS_IO_MASK;

	addr = lo & PCI_BASE_ADDRESS_MEM_MASK;
	if (!(flags & PCI_BASE_ADDRESS_MEM_TYPE_64))
		return addr;

	hi = dw_pcie_ep_readl_dbi(ep, func_no, reg + 4);
	return addr | ((u64)hi << 32);
}

static int dw_pcie_ep_validate_submap(struct dw_pcie_ep *ep,
				      const struct pci_epf_bar_submap *submap,
				      unsigned int num_submap, size_t bar_size)
{
	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
	u32 align = pci->region_align;
	size_t off = 0;
	unsigned int i;
	size_t size;

	if (!align || !IS_ALIGNED(bar_size, align))
		return -EINVAL;

	/*
	 * The submap array order defines the BAR layout (submap[0] starts
	 * at offset 0 and each entry immediately follows the previous
	 * one). Here, validate that it forms a strict, gapless
	 * decomposition of the BAR:
	 *  - each entry has a non-zero size
	 *  - sizes, implicit offsets and phys_addr are aligned to
	 *    pci->region_align
	 *  - each entry lies within the BAR range
	 *  - the entries exactly cover the whole BAR
	 *
	 * Note: dw_pcie_prog_inbound_atu() also checks alignment for the
	 * PCI address and the target phys_addr, but validating up-front
	 * avoids partially programming iATU windows in vain.
	 */
	for (i = 0; i < num_submap; i++) {
		size = submap[i].size;

		if (!size)
			return -EINVAL;

		if (!IS_ALIGNED(size, align) || !IS_ALIGNED(off, align))
			return -EINVAL;

		if (!IS_ALIGNED(submap[i].phys_addr, align))
			return -EINVAL;

		if (off > bar_size || size > bar_size - off)
			return -EINVAL;

		off += size;
	}
	if (off != bar_size)
		return -EINVAL;

	return 0;
}

/* Address Match Mode inbound iATU mapping */
static int dw_pcie_ep_ib_atu_addr(struct dw_pcie_ep *ep, u8 func_no, int type,
				  const struct pci_epf_bar *epf_bar)
{
	const struct pci_epf_bar_submap *submap = epf_bar->submap;
	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
	enum pci_barno bar = epf_bar->barno;
	struct device *dev = pci->dev;
	u64 pci_addr, parent_bus_addr;
	u64 size, base, off = 0;
	int free_win, ret;
	unsigned int i;
	u32 *indexes;

	if (!epf_bar->num_submap || !submap || !epf_bar->size)
		return -EINVAL;

	ret = dw_pcie_ep_validate_submap(ep, submap, epf_bar->num_submap,
					 epf_bar->size);
	if (ret)
		return ret;

	base = dw_pcie_ep_read_bar_assigned(ep, func_no, bar, epf_bar->flags);
	if (!base) {
		dev_err(dev,
			"BAR%u not assigned, cannot set up sub-range mappings\n",
			bar);
		return -EINVAL;
	}

	indexes = devm_kcalloc(dev, epf_bar->num_submap, sizeof(*indexes),
			       GFP_KERNEL);
	if (!indexes)
		return -ENOMEM;

	ep->ib_atu_indexes[bar] = indexes;
	ep->num_ib_atu_indexes[bar] = 0;

	for (i = 0; i < epf_bar->num_submap; i++) {
		size = submap[i].size;
		parent_bus_addr = submap[i].phys_addr;

		if (off > (~0ULL) - base) {
			ret = -EINVAL;
			goto err;
		}

		pci_addr = base + off;
		off += size;

		free_win = find_first_zero_bit(ep->ib_window_map,
					       pci->num_ib_windows);
		if (free_win >= pci->num_ib_windows) {
			ret = -ENOSPC;
			goto err;
		}

		ret = dw_pcie_prog_inbound_atu(pci, free_win, type,
					       parent_bus_addr, pci_addr, size);
		if (ret)
			goto err;

		set_bit(free_win, ep->ib_window_map);
		indexes[i] = free_win;
		ep->num_ib_atu_indexes[bar] = i + 1;
	}
	return 0;
err:
	dw_pcie_ep_clear_ib_maps(ep, bar);
	return ret;
}

static int dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep,
				   struct dw_pcie_ob_atu_cfg *atu)
{
@@ -165,17 +339,15 @@ static void dw_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
	struct dw_pcie_ep *ep = epc_get_drvdata(epc);
	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
	enum pci_barno bar = epf_bar->barno;
	u32 atu_index = ep->bar_to_atu[bar] - 1;

	if (!ep->bar_to_atu[bar])
	if (!ep->epf_bar[bar])
		return;

	__dw_pcie_ep_reset_bar(pci, func_no, bar, epf_bar->flags);

	dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, atu_index);
	clear_bit(atu_index, ep->ib_window_map);
	dw_pcie_ep_clear_ib_maps(ep, bar);

	ep->epf_bar[bar] = NULL;
	ep->bar_to_atu[bar] = 0;
}

static unsigned int dw_pcie_ep_get_rebar_offset(struct dw_pcie *pci,
@@ -331,11 +503,28 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
		    ep->epf_bar[bar]->flags != flags)
			return -EINVAL;

		/*
		 * When dynamically changing a BAR, tear down any existing
		 * mappings before re-programming.
		 */
		if (ep->epf_bar[bar]->num_submap || epf_bar->num_submap)
			dw_pcie_ep_clear_ib_maps(ep, bar);

		/*
		 * When dynamically changing a BAR, skip writing the BAR reg, as
		 * that would clear the BAR's PCI address assigned by the host.
		 */
		goto config_atu;
	} else {
		/*
		 * Subrange mapping is an update-only operation.  The BAR
		 * must have been configured once without submaps so that
		 * subsequent set_bar() calls can update inbound mappings
		 * without touching the BAR register (and clobbering the
		 * host-assigned address).
		 */
		if (epf_bar->num_submap)
			return -EINVAL;
	}

	bar_type = dw_pcie_ep_get_bar_type(ep, bar);
@@ -369,8 +558,12 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
	else
		type = PCIE_ATU_TYPE_IO;

	ret = dw_pcie_ep_inbound_atu(ep, func_no, type, epf_bar->phys_addr, bar,
				     size);
	if (epf_bar->num_submap)
		ret = dw_pcie_ep_ib_atu_addr(ep, func_no, type, epf_bar);
	else
		ret = dw_pcie_ep_ib_atu_bar(ep, func_no, type,
					    epf_bar->phys_addr, bar, size);

	if (ret)
		return ret;

+6 −1
Original line number Diff line number Diff line
@@ -306,7 +306,8 @@
#define DMA_LLP_MEM_SIZE		PAGE_SIZE

/* Common struct pci_epc_feature bits among DWC EP glue drivers */
#define DWC_EPC_COMMON_FEATURES		.dynamic_inbound_mapping = true
#define DWC_EPC_COMMON_FEATURES		.dynamic_inbound_mapping = true, \
					.subrange_mapping = true

struct dw_pcie;
struct dw_pcie_rp;
@@ -487,6 +488,10 @@ struct dw_pcie_ep {
	phys_addr_t		msi_mem_phys;
	struct pci_epf_bar	*epf_bar[PCI_STD_NUM_BARS];

	/* Only for Address Match Mode inbound iATU */
	u32			*ib_atu_indexes[PCI_STD_NUM_BARS];
	unsigned int		num_ib_atu_indexes[PCI_STD_NUM_BARS];

	/* MSI outbound iATU state */
	bool			msi_iatu_mapped;
	u64			msi_msg_addr;