[PATCH v2 7/7] arm: a37xx: pci: Fix configuring PCIe resources

Pali Rohár pali at kernel.org
Wed May 26 17:59:40 CEST 2021


The `ranges` DT property of the PCIe node is currently ignored by
Aardvark driver - all entries are used as transparent PCIe MEM, despite
some of them being defined for IO in DT.

This is because the driver does not setup PCIe outbound windows and thus
a default configuration is used.

This can cause an external abort on CPU when a device driver tries to
access non-MEM space.

Setup the PCIe windows according to the `ranges` property for all
non-MEM resources (currently only IO) and also non-transparent MEM
resources.

Because Linux expects that bootloader does not setup Aardvark PCIe
windows, disable them before booting Linux.

Signed-off-by: Pali Rohár <pali at kernel.org>
Reviewed-by: Marek Behún <marek.behun at nic.cz>
---
 drivers/pci/pci-aardvark.c | 158 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 157 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/pci-aardvark.c b/drivers/pci/pci-aardvark.c
index ae1a20551fed..96aa039bdc26 100644
--- a/drivers/pci/pci-aardvark.c
+++ b/drivers/pci/pci-aardvark.c
@@ -99,6 +99,46 @@
 #define     PCIE_CORE_CTRL2_STRICT_ORDER_ENABLE	BIT(5)
 #define     PCIE_CORE_CTRL2_ADDRWIN_MAP_ENABLE	BIT(6)
 
+/* PCIe window configuration */
+#define OB_WIN_BASE_ADDR			0x4c00
+#define OB_WIN_BLOCK_SIZE			0x20
+#define OB_WIN_COUNT				8
+#define OB_WIN_REG_ADDR(win, offset)		(OB_WIN_BASE_ADDR + \
+						 OB_WIN_BLOCK_SIZE * (win) + \
+						 (offset))
+#define OB_WIN_MATCH_LS(win)			OB_WIN_REG_ADDR(win, 0x00)
+#define     OB_WIN_ENABLE			BIT(0)
+#define OB_WIN_MATCH_MS(win)			OB_WIN_REG_ADDR(win, 0x04)
+#define OB_WIN_REMAP_LS(win)			OB_WIN_REG_ADDR(win, 0x08)
+#define OB_WIN_REMAP_MS(win)			OB_WIN_REG_ADDR(win, 0x0c)
+#define OB_WIN_MASK_LS(win)			OB_WIN_REG_ADDR(win, 0x10)
+#define OB_WIN_MASK_MS(win)			OB_WIN_REG_ADDR(win, 0x14)
+#define OB_WIN_ACTIONS(win)			OB_WIN_REG_ADDR(win, 0x18)
+#define OB_WIN_DEFAULT_ACTIONS			(OB_WIN_ACTIONS(OB_WIN_COUNT-1) + 0x4)
+#define     OB_WIN_FUNC_NUM_MASK		GENMASK(31, 24)
+#define     OB_WIN_FUNC_NUM_SHIFT		24
+#define     OB_WIN_FUNC_NUM_ENABLE		BIT(23)
+#define     OB_WIN_BUS_NUM_BITS_MASK		GENMASK(22, 20)
+#define     OB_WIN_BUS_NUM_BITS_SHIFT		20
+#define     OB_WIN_MSG_CODE_ENABLE		BIT(22)
+#define     OB_WIN_MSG_CODE_MASK		GENMASK(21, 14)
+#define     OB_WIN_MSG_CODE_SHIFT		14
+#define     OB_WIN_MSG_PAYLOAD_LEN		BIT(12)
+#define     OB_WIN_ATTR_ENABLE			BIT(11)
+#define     OB_WIN_ATTR_TC_MASK			GENMASK(10, 8)
+#define     OB_WIN_ATTR_TC_SHIFT		8
+#define     OB_WIN_ATTR_RELAXED			BIT(7)
+#define     OB_WIN_ATTR_NOSNOOP			BIT(6)
+#define     OB_WIN_ATTR_POISON			BIT(5)
+#define     OB_WIN_ATTR_IDO			BIT(4)
+#define     OB_WIN_TYPE_MASK			GENMASK(3, 0)
+#define     OB_WIN_TYPE_SHIFT			0
+#define     OB_WIN_TYPE_MEM			0x0
+#define     OB_WIN_TYPE_IO			0x4
+#define     OB_WIN_TYPE_CONFIG_TYPE0		0x8
+#define     OB_WIN_TYPE_CONFIG_TYPE1		0x9
+#define     OB_WIN_TYPE_MSG			0xc
+
 /* LMI registers base address and register offsets */
 #define LMI_BASE_ADDR				0x6000
 #define CFG_REG					(LMI_BASE_ADDR + 0x0)
@@ -522,6 +562,86 @@ static int pcie_advk_wait_for_link(struct pcie_advk *pcie)
 	return -ETIMEDOUT;
 }
 
+/*
+ * Set PCIe address window register which could be used for memory
+ * mapping.
+ */
+static void pcie_advk_set_ob_win(struct pcie_advk *pcie, u8 win_num,
+				 phys_addr_t match, phys_addr_t remap,
+				 phys_addr_t mask, u32 actions)
+{
+	advk_writel(pcie, OB_WIN_ENABLE |
+			  lower_32_bits(match), OB_WIN_MATCH_LS(win_num));
+	advk_writel(pcie, upper_32_bits(match), OB_WIN_MATCH_MS(win_num));
+	advk_writel(pcie, lower_32_bits(remap), OB_WIN_REMAP_LS(win_num));
+	advk_writel(pcie, upper_32_bits(remap), OB_WIN_REMAP_MS(win_num));
+	advk_writel(pcie, lower_32_bits(mask), OB_WIN_MASK_LS(win_num));
+	advk_writel(pcie, upper_32_bits(mask), OB_WIN_MASK_MS(win_num));
+	advk_writel(pcie, actions, OB_WIN_ACTIONS(win_num));
+}
+
+static void pcie_advk_disable_ob_win(struct pcie_advk *pcie, u8 win_num)
+{
+	advk_writel(pcie, 0, OB_WIN_MATCH_LS(win_num));
+	advk_writel(pcie, 0, OB_WIN_MATCH_MS(win_num));
+	advk_writel(pcie, 0, OB_WIN_REMAP_LS(win_num));
+	advk_writel(pcie, 0, OB_WIN_REMAP_MS(win_num));
+	advk_writel(pcie, 0, OB_WIN_MASK_LS(win_num));
+	advk_writel(pcie, 0, OB_WIN_MASK_MS(win_num));
+	advk_writel(pcie, 0, OB_WIN_ACTIONS(win_num));
+}
+
+static void pcie_advk_set_ob_region(struct pcie_advk *pcie, int *wins,
+				    struct pci_region *region, u32 actions)
+{
+	phys_addr_t phys_start = region->phys_start;
+	pci_addr_t bus_start = region->bus_start;
+	pci_size_t size = region->size;
+	phys_addr_t win_mask;
+	u64 win_size;
+
+	if (*wins == -1)
+		return;
+
+	/*
+	 * The n-th PCIe window is configured by tuple (match, remap, mask)
+	 * and an access to address A uses this window it if A matches the
+	 * match with given mask.
+	 * So every PCIe window size must be a power of two and every start
+	 * address must be aligned to window size. Minimal size is 64 KiB
+	 * because lower 16 bits of mask must be zero.
+	 */
+	while (*wins < OB_WIN_COUNT && size > 0) {
+		/* Calculate the largest aligned window size */
+		win_size = (1ULL << (fls64(size) - 1)) |
+			   (phys_start ? (1ULL << __ffs64(phys_start)) : 0);
+		win_size = 1ULL << __ffs64(win_size);
+		if (win_size < 0x10000)
+			break;
+
+		dev_dbg(pcie->dev,
+			"Configuring PCIe window %d: [0x%llx-0x%llx] as 0x%x\n",
+			*wins, (u64)phys_start, (u64)phys_start + win_size,
+			actions);
+		win_mask = ~(win_size - 1) & ~0xffff;
+		pcie_advk_set_ob_win(pcie, *wins, phys_start, bus_start,
+				     win_mask, actions);
+
+		phys_start += win_size;
+		bus_start += win_size;
+		size -= win_size;
+		(*wins)++;
+	}
+
+	if (size > 0) {
+		*wins = -1;
+		dev_err(pcie->dev,
+			"Invalid PCIe region [0x%llx-0x%llx]\n",
+			(u64)region->phys_start,
+			(u64)region->phys_start + region->size);
+	}
+}
+
 /**
  * pcie_advk_setup_hw() - PCIe initailzation
  *
@@ -531,6 +651,8 @@ static int pcie_advk_wait_for_link(struct pcie_advk *pcie)
  */
 static int pcie_advk_setup_hw(struct pcie_advk *pcie)
 {
+	struct pci_region *io, *mem, *pref;
+	int i, wins;
 	u32 reg;
 
 	/* Set to Direct mode */
@@ -597,7 +719,9 @@ static int pcie_advk_setup_hw(struct pcie_advk *pcie)
 	 * configurations (Default User Field: 0xD0074CFC)
 	 * are used to transparent address translation for
 	 * the outbound transactions. Thus, PCIe address
-	 * windows are not required.
+	 * windows are not required for transparent memory
+	 * access when default outbound window configuration
+	 * is set for memory access.
 	 */
 	reg = advk_readl(pcie, PCIE_CORE_CTRL2_REG);
 	reg |= PCIE_CORE_CTRL2_ADDRWIN_MAP_ENABLE;
@@ -613,6 +737,34 @@ static int pcie_advk_setup_hw(struct pcie_advk *pcie)
 	reg |= PIO_CTRL_ADDR_WIN_DISABLE;
 	advk_writel(pcie, reg, PIO_CTRL);
 
+	/*
+	 * Set memory access in Default User Field so it
+	 * is not required to configure PCIe address for
+	 * transparent memory access.
+	 */
+	advk_writel(pcie, OB_WIN_TYPE_MEM, OB_WIN_DEFAULT_ACTIONS);
+
+	/*
+	 * Configure PCIe address windows for non-memory or
+	 * non-transparent access as by default PCIe uses
+	 * transparent memory access.
+	 */
+	wins = 0;
+	pci_get_regions(pcie->dev, &io, &mem, &pref);
+	if (io)
+		pcie_advk_set_ob_region(pcie, &wins, io, OB_WIN_TYPE_IO);
+	if (mem && mem->phys_start != mem->bus_start)
+		pcie_advk_set_ob_region(pcie, &wins, mem, OB_WIN_TYPE_MEM);
+	if (pref && pref->phys_start != pref->bus_start)
+		pcie_advk_set_ob_region(pcie, &wins, pref, OB_WIN_TYPE_MEM);
+
+	/* Disable remaining PCIe outbound windows */
+	for (i = ((wins >= 0) ? wins : 0); i < OB_WIN_COUNT; i++)
+		pcie_advk_disable_ob_win(pcie, i);
+
+	if (wins == -1)
+		return -EINVAL;
+
 	/* Wait for PCIe link up */
 	if (pcie_advk_wait_for_link(pcie))
 		return -ENXIO;
@@ -674,6 +826,10 @@ static int pcie_advk_remove(struct udevice *dev)
 {
 	struct pcie_advk *pcie = dev_get_priv(dev);
 	u32 reg;
+	int i;
+
+	for (i = 0; i < OB_WIN_COUNT; i++)
+		pcie_advk_disable_ob_win(pcie, i);
 
 	reg = advk_readl(pcie, PCIE_CORE_CMD_STATUS_REG);
 	reg &= ~(PCIE_CORE_CMD_MEM_ACCESS_EN |
-- 
2.20.1



More information about the U-Boot mailing list