[PATCH v2 7/7] arm: a37xx: pci: Fix configuring PCIe resources
Stefan Roese
sr at denx.de
Thu May 27 08:24:46 CEST 2021
On 26.05.21 17:59, Pali Rohár wrote:
> 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>
Reviewed-by: Stefan Roese <sr at denx.de>
Thanks,
Stefan
> ---
> 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 |
>
Viele Grüße,
Stefan
--
DENX Software Engineering GmbH, Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: (+49)-8142-66989-51 Fax: (+49)-8142-66989-80 Email: sr at denx.de
More information about the U-Boot
mailing list