mirror of
https://github.com/torvalds/linux.git
synced 2026-03-09 18:16:36 +01:00
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:
parent
c0f1506f63
commit
cc839bef77
2 changed files with 209 additions and 11 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue