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: Koichiro Den <den@valinux.co.jp>
Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Niklas Cassel <cassel@kernel.org>
Link: https://patch.msgid.link/20260124145012.2794108-5-den@valinux.co.jp
This commit is contained in:
Koichiro Den 2026-01-24 23:50:08 +09:00 committed by Bjorn Helgaas
parent c0f1506f63
commit cc839bef77
2 changed files with 209 additions and 11 deletions

View file

@ -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,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 @@ config_atu:
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;

View file

@ -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;