[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