[PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU

Koichiro Den posted 5 patches 2 weeks, 4 days ago
There is a newer version of this series
[PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU
Posted by Koichiro Den 2 weeks, 4 days ago
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)
- subranges are sorted in ascending order by offset

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.

Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
 .../pci/controller/dwc/pcie-designware-ep.c   | 203 +++++++++++++++++-
 drivers/pci/controller/dwc/pcie-designware.h  |   7 +-
 2 files changed, 199 insertions(+), 11 deletions(-)

diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index cfd59899c7b8..0567552b784c 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -100,9 +100,10 @@ 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,
-				  dma_addr_t parent_bus_addr, enum pci_barno bar,
-				  size_t size)
+/* 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)
 {
 	int ret;
 	u32 free_win;
@@ -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,6 +503,13 @@ 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.
@@ -369,8 +548,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;
 
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index be47f34b49ca..938dcb541ce3 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -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;
-- 
2.51.0
Re: [PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU
Posted by Niklas Cassel 2 weeks, 4 days ago
On Thu, Jan 22, 2026 at 05:49:08PM +0900, Koichiro Den wrote:
> 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)
> - subranges are sorted in ascending order by offset
> 
> 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.
> 
> Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---


> @@ -331,6 +503,13 @@ 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.
> @@ -369,8 +548,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 someone calls set_bar() with a submap, without having called set_bar() first
without a submap, we will still call dw_pcie_ep_ib_atu_addr() here.

To make sure that dw_pcie_ep_ib_atu_addr() cannot be called without already
having a BAR configured, to we perhaps want something like:

diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 0567552b784c..fe26b7f7b212 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -487,6 +487,9 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
        if ((flags & PCI_BASE_ADDRESS_MEM_TYPE_64) && (bar & 1))
                return -EINVAL;
 
+       if (!ep->epf_bar[bar] && epf_bar->num_submap)
+               return -EINVAL;
+
        /*
         * Certain EPF drivers dynamically change the physical address of a BAR
         * (i.e. they call set_bar() twice, without ever calling clear_bar(), as


or


diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
index 0567552b784c..8aeaa6fe53f9 100644
--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -475,6 +475,7 @@ static int dw_pcie_ep_set_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;
+       bool use_addr_match_mode = false;
        size_t size = epf_bar->size;
        enum pci_epc_bar_type bar_type;
        int flags = epf_bar->flags;
@@ -510,6 +511,9 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
                if (ep->epf_bar[bar]->num_submap || epf_bar->num_submap)
                        dw_pcie_ep_clear_ib_maps(ep, bar);
 
+               if (epf_bar->num_submap)
+                       use_addr_match_mode = true;
+
                /*
                 * When dynamically changing a BAR, skip writing the BAR reg, as
                 * that would clear the BAR's PCI address assigned by the host.
@@ -548,7 +552,7 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
        else
                type = PCIE_ATU_TYPE_IO;
 
-       if (epf_bar->num_submap)
+       if (use_addr_match_mode)
                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,
Re: [PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU
Posted by Koichiro Den 2 weeks, 4 days ago
On Thu, Jan 22, 2026 at 10:23:03AM +0100, Niklas Cassel wrote:
> On Thu, Jan 22, 2026 at 05:49:08PM +0900, Koichiro Den wrote:
> > 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)
> > - subranges are sorted in ascending order by offset
> > 
> > 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.
> > 
> > Reviewed-by: Frank Li <Frank.Li@nxp.com>
> > Signed-off-by: Koichiro Den <den@valinux.co.jp>
> > ---
> 
> 
> > @@ -331,6 +503,13 @@ 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.
> > @@ -369,8 +548,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 someone calls set_bar() with a submap, without having called set_bar() first
> without a submap, we will still call dw_pcie_ep_ib_atu_addr() here.
> 
> To make sure that dw_pcie_ep_ib_atu_addr() cannot be called without already
> having a BAR configured, to we perhaps want something like:

Thanks for the review.
Isn't the existing guard in dw_pcie_ep_ib_atu_addr sufficient?

        [...]
        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;
        }

Koichiro

> 
> diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
> index 0567552b784c..fe26b7f7b212 100644
> --- a/drivers/pci/controller/dwc/pcie-designware-ep.c
> +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
> @@ -487,6 +487,9 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
>         if ((flags & PCI_BASE_ADDRESS_MEM_TYPE_64) && (bar & 1))
>                 return -EINVAL;
>  
> +       if (!ep->epf_bar[bar] && epf_bar->num_submap)
> +               return -EINVAL;
> +
>         /*
>          * Certain EPF drivers dynamically change the physical address of a BAR
>          * (i.e. they call set_bar() twice, without ever calling clear_bar(), as
> 
> 
> or
> 
> 
> diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c
> index 0567552b784c..8aeaa6fe53f9 100644
> --- a/drivers/pci/controller/dwc/pcie-designware-ep.c
> +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
> @@ -475,6 +475,7 @@ static int dw_pcie_ep_set_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;
> +       bool use_addr_match_mode = false;
>         size_t size = epf_bar->size;
>         enum pci_epc_bar_type bar_type;
>         int flags = epf_bar->flags;
> @@ -510,6 +511,9 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
>                 if (ep->epf_bar[bar]->num_submap || epf_bar->num_submap)
>                         dw_pcie_ep_clear_ib_maps(ep, bar);
>  
> +               if (epf_bar->num_submap)
> +                       use_addr_match_mode = true;
> +
>                 /*
>                  * When dynamically changing a BAR, skip writing the BAR reg, as
>                  * that would clear the BAR's PCI address assigned by the host.
> @@ -548,7 +552,7 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
>         else
>                 type = PCIE_ATU_TYPE_IO;
>  
> -       if (epf_bar->num_submap)
> +       if (use_addr_match_mode)
>                 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,
Re: [PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU
Posted by Niklas Cassel 2 weeks, 4 days ago
On 22 January 2026 15:29:02 CET, Koichiro Den <den@valinux.co.jp> wrote:
>
>> To make sure that dw_pcie_ep_ib_atu_addr() cannot be called without already
>> having a BAR configured, to we perhaps want something like:
>
>Thanks for the review.
>Isn't the existing guard in dw_pcie_ep_ib_atu_addr sufficient?
>
>        [...]
>        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;
>        }
>

Well, for a driver that does not call dw_pcie_ep_reset_bar() in their .init() to disable all BARs that are enabled in the controller by default, the host side will assign an PCI address even if no EPF has called set_bar() on that BAR.

See e.g.
https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git/commit/drivers/pci/controller/dwc/pcie-tegra194.c?h=controller/dwc&id=42f9c66a6d0cc45758dab77233c5460e1cf003df

There might be other EPC drivers that don't disable all BARs in their .init(), so I would say that simply checking if the BAR has an address is not sufficient to guarantee that an EPF driver has called set_bar().


I think the safest option is my second suggestion because then we know that we will only call
dw_pcie_ep_ib_atu_addr()

When:

1) If ep->epf_bar[bar] is set:
https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L363


2) All the other requirements to dynamically update a BAR is also met:

https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L368-L370



Kind regards,
Niklas
Re: [PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU
Posted by Koichiro Den 2 weeks, 4 days ago
On Thu, Jan 22, 2026 at 05:59:18PM +0100, Niklas Cassel wrote:
> On 22 January 2026 15:29:02 CET, Koichiro Den <den@valinux.co.jp> wrote:
> >
> >> To make sure that dw_pcie_ep_ib_atu_addr() cannot be called without already
> >> having a BAR configured, to we perhaps want something like:
> >
> >Thanks for the review.
> >Isn't the existing guard in dw_pcie_ep_ib_atu_addr sufficient?
> >
> >        [...]
> >        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;
> >        }
> >
> 
> Well, for a driver that does not call dw_pcie_ep_reset_bar() in their .init() to disable all BARs that are enabled in the controller by default, the host side will assign an PCI address even if no EPF has called set_bar() on that BAR.

Thanks for the explanation.

> 
> See e.g.
> https://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git/commit/drivers/pci/controller/dwc/pcie-tegra194.c?h=controller/dwc&id=42f9c66a6d0cc45758dab77233c5460e1cf003df
> 
> There might be other EPC drivers that don't disable all BARs in their .init(), so I would say that simply checking if the BAR has an address is not sufficient to guarantee that an EPF driver has called set_bar().
> 

Even if an EPC driver does not reset the BAR in their .init() and some
default translation is left exposed, wouldn't it be safe as long as
dw_pcie_ep_ib_atu_addr() succeeds in programming inbound mappings for the
entire BAR?

That said, such usage apparently contradicts the documented usage (1st
set_bar with no submap, then with submap) described in the docs and commit
messages in this series, and allowing it would make things unnecessarily
complicated. So I agree that adding such a safeguard is the right approach.

> 
> I think the safest option is my second suggestion because then we know that we will only call
> dw_pcie_ep_ib_atu_addr()
> 
> When:
> 
> 1) If ep->epf_bar[bar] is set:
> https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L363
> 
> 
> 2) All the other requirements to dynamically update a BAR is also met:
> 
> https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L368-L370
> 

That makes sense, and it ensures that the behavior always accords with the
docs and commit messages in this series.

Thanks a lot for the careful review,
Koichiro

> 
> 
> Kind regards,
> Niklas
>
Re: [PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU
Posted by Niklas Cassel 2 weeks, 3 days ago
On Fri, Jan 23, 2026 at 10:16:21AM +0900, Koichiro Den wrote:
> >
> > There might be other EPC drivers that don't disable all BARs in their .init(), so I would say that simply checking if the BAR has an address is not sufficient to guarantee that an EPF driver has called set_bar().
> >
>
> Even if an EPC driver does not reset the BAR in their .init() and some
> default translation is left exposed, wouldn't it be safe as long as
> dw_pcie_ep_ib_atu_addr() succeeds in programming inbound mappings for the
> entire BAR?

For e.g. on RK3588, the default HW configuration of the DWC controller has
all 5 BARs as enabled, with a size of 1 GB.

There is no inbound address translation for these BARs by default.

So for it to be safe, the size of the set_bar() call would have to
match the current size of the BAR, but how should the EPF driver know
that when it has not called set_bar() yet?

dw_pcie_ep_read_bar_assigned() does not return the current size of the
BAR. So you can't verify that the set_bar() call has the same size as
the BARs "default size".


>
> That said, such usage apparently contradicts the documented usage (1st
> set_bar with no submap, then with submap) described in the docs and commit
> messages in this series, and allowing it would make things unnecessarily
> complicated. So I agree that adding such a safeguard is the right approach.
>
> >
> > I think the safest option is my second suggestion because then we know that we will only call
> > dw_pcie_ep_ib_atu_addr()
> >
> > When:
> >
> > 1) If ep->epf_bar[bar] is set:
> > https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L363
> >
> >
> > 2) All the other requirements to dynamically update a BAR is also met:
> >
> > https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L368-L370
> >
>
> That makes sense, and it ensures that the behavior always accords with the
> docs and commit messages in this series.

I think it makes most sense to put the "use_addr_translation = true"

after the check:

		/*
		 * We can only dynamically change a BAR if the new BAR size and
		 * BAR flags do not differ from the existing configuration.
		 */
		if (ep->epf_bar[bar]->barno != bar ||
		    ep->epf_bar[bar]->size != size ||
		    ep->epf_bar[bar]->flags != flags)
			return -EINVAL;


So we know that dw_pcie_ep_ib_atu_addr() is only called when the size is the
same.


Kind regards,
Niklas
Re: [PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU
Posted by Koichiro Den 2 weeks, 3 days ago
On Fri, Jan 23, 2026 at 09:51:19AM +0100, Niklas Cassel wrote:
> On Fri, Jan 23, 2026 at 10:16:21AM +0900, Koichiro Den wrote:
> > >
> > > There might be other EPC drivers that don't disable all BARs in their .init(), so I would say that simply checking if the BAR has an address is not sufficient to guarantee that an EPF driver has called set_bar().
> > >
> >
> > Even if an EPC driver does not reset the BAR in their .init() and some
> > default translation is left exposed, wouldn't it be safe as long as
> > dw_pcie_ep_ib_atu_addr() succeeds in programming inbound mappings for the
> > entire BAR?
> 
> For e.g. on RK3588, the default HW configuration of the DWC controller has
> all 5 BARs as enabled, with a size of 1 GB.
> 
> There is no inbound address translation for these BARs by default.
> 
> So for it to be safe, the size of the set_bar() call would have to
> match the current size of the BAR, but how should the EPF driver know
> that when it has not called set_bar() yet?
> 
> dw_pcie_ep_read_bar_assigned() does not return the current size of the
> BAR. So you can't verify that the set_bar() call has the same size as
> the BARs "default size".

I wasn't considering either of the following cases as unsafe:
- succeeding by chance in programming via a one-shot set_bar() with submaps
- such a set_bar() failing (due to incorrect size recognition)

while as I mentioned in my previous reply, the first case effectively
becomes a loophole that contradicts the docs and git commit messages.

However, since v8, the second case clears any existing mappings, which
could indeed lead to an unsafe situtation.

> 
> 
> >
> > That said, such usage apparently contradicts the documented usage (1st
> > set_bar with no submap, then with submap) described in the docs and commit
> > messages in this series, and allowing it would make things unnecessarily
> > complicated. So I agree that adding such a safeguard is the right approach.
> >
> > >
> > > I think the safest option is my second suggestion because then we know that we will only call
> > > dw_pcie_ep_ib_atu_addr()
> > >
> > > When:
> > >
> > > 1) If ep->epf_bar[bar] is set:
> > > https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L363
> > >
> > >
> > > 2) All the other requirements to dynamically update a BAR is also met:
> > >
> > > https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L368-L370
> > >
> >
> > That makes sense, and it ensures that the behavior always accords with the
> > docs and commit messages in this series.
> 
> I think it makes most sense to put the "use_addr_translation = true"
> 
> after the check:
> 
> 		/*
> 		 * We can only dynamically change a BAR if the new BAR size and
> 		 * BAR flags do not differ from the existing configuration.
> 		 */
> 		if (ep->epf_bar[bar]->barno != bar ||
> 		    ep->epf_bar[bar]->size != size ||
> 		    ep->epf_bar[bar]->flags != flags)
> 			return -EINVAL;
> 
> 
> So we know that dw_pcie_ep_ib_atu_addr() is only called when the size is the
> same.

I'll send v10 with the fix, possibly adding a BAR_SUBRANGE_TEST to pci
endpoint test as well.

Kind regards,
Koichiro

> 
> 
> Kind regards,
> Niklas
Re: [PATCH v9 4/5] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU
Posted by Koichiro Den 2 weeks, 2 days ago
On Fri, Jan 23, 2026 at 11:28:30PM +0900, Koichiro Den wrote:
> On Fri, Jan 23, 2026 at 09:51:19AM +0100, Niklas Cassel wrote:
> > On Fri, Jan 23, 2026 at 10:16:21AM +0900, Koichiro Den wrote:
> > > >
> > > > There might be other EPC drivers that don't disable all BARs in their .init(), so I would say that simply checking if the BAR has an address is not sufficient to guarantee that an EPF driver has called set_bar().
> > > >
> > >
> > > Even if an EPC driver does not reset the BAR in their .init() and some
> > > default translation is left exposed, wouldn't it be safe as long as
> > > dw_pcie_ep_ib_atu_addr() succeeds in programming inbound mappings for the
> > > entire BAR?
> > 
> > For e.g. on RK3588, the default HW configuration of the DWC controller has
> > all 5 BARs as enabled, with a size of 1 GB.
> > 
> > There is no inbound address translation for these BARs by default.
> > 
> > So for it to be safe, the size of the set_bar() call would have to
> > match the current size of the BAR, but how should the EPF driver know
> > that when it has not called set_bar() yet?
> > 
> > dw_pcie_ep_read_bar_assigned() does not return the current size of the
> > BAR. So you can't verify that the set_bar() call has the same size as
> > the BARs "default size".
> 
> I wasn't considering either of the following cases as unsafe:
> - succeeding by chance in programming via a one-shot set_bar() with submaps
> - such a set_bar() failing (due to incorrect size recognition)
> 
> while as I mentioned in my previous reply, the first case effectively
> becomes a loophole that contradicts the docs and git commit messages.
> 
> However, since v8, the second case clears any existing mappings, which
> could indeed lead to an unsafe situtation.
> 
> > 
> > 
> > >
> > > That said, such usage apparently contradicts the documented usage (1st
> > > set_bar with no submap, then with submap) described in the docs and commit
> > > messages in this series, and allowing it would make things unnecessarily
> > > complicated. So I agree that adding such a safeguard is the right approach.
> > >
> > > >
> > > > I think the safest option is my second suggestion because then we know that we will only call
> > > > dw_pcie_ep_ib_atu_addr()
> > > >
> > > > When:
> > > >
> > > > 1) If ep->epf_bar[bar] is set:
> > > > https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L363
> > > >
> > > >
> > > > 2) All the other requirements to dynamically update a BAR is also met:
> > > >
> > > > https://github.com/torvalds/linux/blob/v6.19-rc6/drivers/pci/controller/dwc/pcie-designware-ep.c#L368-L370
> > > >
> > >
> > > That makes sense, and it ensures that the behavior always accords with the
> > > docs and commit messages in this series.
> > 
> > I think it makes most sense to put the "use_addr_translation = true"
> > 
> > after the check:
> > 
> > 		/*
> > 		 * We can only dynamically change a BAR if the new BAR size and
> > 		 * BAR flags do not differ from the existing configuration.
> > 		 */
> > 		if (ep->epf_bar[bar]->barno != bar ||
> > 		    ep->epf_bar[bar]->size != size ||
> > 		    ep->epf_bar[bar]->flags != flags)
> > 			return -EINVAL;
> > 
> > 
> > So we know that dw_pcie_ep_ib_atu_addr() is only called when the size is the
> > same.
> 
> I'll send v10 with the fix, possibly adding a BAR_SUBRANGE_TEST to pci
> endpoint test as well.

After thinking again, I believe just the following is the most robust and
safest approach, as it makes subrange mapping strictly update-only and
avoids any silent success on invalid first-time calls.


--- a/drivers/pci/controller/dwc/pcie-designware-ep.c
+++ b/drivers/pci/controller/dwc/pcie-designware-ep.c
@@ -508,20 +508,29 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
                 * 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);
        switch (bar_type) {
        case BAR_FIXED:
                /*
                 * There is no need to write a BAR mask for a fixed BAR (except
                 * to write 1 to the LSB of the BAR mask register, to enable the
                 * BAR). Write the BAR mask regardless. (The fixed bits in the
                 * BAR mask register will be read-only anyway.)


This is close to your first suggestion at:
https://lore.kernel.org/linux-pci/aXHsd7-WWAGyhy_w@ryzen/
but it avoids even performing BAR sizing when set_bar() is called in an invalid manner.

With this, we still guarantee dw_pcie_ep_ib_atu_addr() is only reached when:
  1) ep->epf_bar[bar] is set
  2) All the other requirements to dynamically update a BAR is also met

The resulting behavior matrix becomes:

                           | num_submap > 0           | num_submap == 0          |
  -------------------------+--------------------------+--------------------------+
  ep->epf_bar[bar] == NULL | returns -EINVAL          | always try BAR Match     |
  ep->epf_bar[bar] != NULL | always try Address Match | always try BAR Match     |

By contrast, with the latest idea that relies on the local
"use_addr_translation" variable, the case marked as [1] below possibly
leads to an unexpected success in BAR Match Mode, .submap/.num_submap are
silently ignored, and the caller has no way to notice the mistake.

                           | num_submap > 0           | num_submap == 0          |
  -------------------------+--------------------------+--------------------------+
  ep->epf_bar[bar] == NULL | always try BAR Match [1] | always try BAR Match     |
  ep->epf_bar[bar] != NULL | always try Address Match | always try BAR Match     |


Kind regards,
Koichiro

> 
> Kind regards,
> Koichiro
> 
> > 
> > 
> > Kind regards,
> > Niklas