[RFC PATCH] PCI/ACPI: xgene: Add ECAM quirk for X-Gene PCIe controller
Duc Dang
dhdang at apm.com
Mon Sep 19 18:07:37 PDT 2016
Hi Bjorn,
Thanks for reviewing my RFC patch.
On Mon, Sep 19, 2016 at 1:06 PM, Bjorn Helgaas <helgaas at kernel.org> wrote:
> Hi Duc,
>
> On Sat, Sep 17, 2016 at 07:24:38AM -0700, Duc Dang wrote:
>> PCIe controller in X-Gene SoCs is not ECAM compliant: software
>> needs to configure additional concontroller register to address
>> device at bus:dev:function.
>>
>> This patch depends on "ECAM quirks handling for ARM64 platforms"
>> series (http://www.spinics.net/lists/arm-kernel/msg530692.html)
>> to address the limitation above for X-Gene PCIe controller.
>>
>> The quirk will only be applied for X-Gene PCIe MCFG table with
>> OEM revison 1, 2, 3 or 4 (PCIe controller v1 and v2 on X-Gene SoCs).
>>
>> Signed-off-by: Duc Dang <dhdang at apm.com>
>> ---
>> drivers/acpi/pci_mcfg.c | 32 +++++
>> drivers/pci/host/Makefile | 2 +-
>> drivers/pci/host/pci-xgene-ecam.c | 280 ++++++++++++++++++++++++++++++++++++++
>> include/linux/pci-ecam.h | 5 +
>> 4 files changed, 318 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/pci/host/pci-xgene-ecam.c
>
> This adds a bunch of stuff, but doesn't remove anything. So I assume
> it's adding new functionality that didn't exist before. What is it?
This patch only adds the ability for X-Gene PCIe controller to be
probable in ACPI boot mode. All the 'quirk' code added is for ECAM to
work with X-Gene.
>
> I sort of expected this to also remove, for example, the seemingly
> identical xgene_pcie_config_read32() in drivers/pci/host/pci-xgene.c.
> Actually, a bunch of this code seems to be duplicated from there. It
> doesn't seem like we should end up with all this duplicated code.
>
> I'd really like it better if all this could get folded into
> pci-xgene.c so we don't end up with more files.
I am still debating whether to put this X-Gene ECAM quirk code into
drivers/acpi or keep it here in drivers/pci/host. But given the
direction of other quirk patches for ThunderX and HiSi, seem like the
quirk will stay in drivers/pci/host. I can definitely fold the new
quirk code into pci-xgene.c as you suggested and eliminate the
identical one.
>
>> diff --git a/drivers/acpi/pci_mcfg.c b/drivers/acpi/pci_mcfg.c
>> index ddf338b..adce35f 100644
>> --- a/drivers/acpi/pci_mcfg.c
>> +++ b/drivers/acpi/pci_mcfg.c
>> @@ -123,6 +123,38 @@ static struct mcfg_fixup mcfg_quirks[] = {
>> { "CAVIUM", "THUNDERX", 2, 13, MCFG_BUS_ANY, &pci_thunder_ecam_ops,
>> MCFG_RES_EMPTY},
>> #endif
>> +#ifdef CONFIG_PCI_XGENE
>> + {"APM ", "XGENE ", 1, 0, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 1, 1, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 1, 2, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 1, 3, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 1, 4, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 2, 0, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 2, 1, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 2, 2, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 2, 3, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 2, 4, MCFG_BUS_ANY,
>> + &xgene_v1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 3, 0, MCFG_BUS_ANY,
>> + &xgene_v2_1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 3, 1, MCFG_BUS_ANY,
>> + &xgene_v2_1_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 4, 0, MCFG_BUS_ANY,
>> + &xgene_v2_2_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 4, 1, MCFG_BUS_ANY,
>> + &xgene_v2_2_pcie_ecam_ops, MCFG_RES_EMPTY},
>> + {"APM ", "XGENE ", 4, 2, MCFG_BUS_ANY,
>> + &xgene_v2_2_pcie_ecam_ops, MCFG_RES_EMPTY},
>
> Most of these are the same. Let's add a macro that fills in the
> boilerplate so each entry only contains the variable parts. I'm going
> to propose the same for the ThunderX quirks.
I will follow up on this.
>
>> +#endif
>> };
>>
>> static char mcfg_oem_id[ACPI_OEM_ID_SIZE];
>> diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
>> index 8843410..af4f505 100644
>> --- a/drivers/pci/host/Makefile
>> +++ b/drivers/pci/host/Makefile
>> @@ -15,7 +15,7 @@ obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o
>> obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o
>> obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o
>> obj-$(CONFIG_PCIE_XILINX_NWL) += pcie-xilinx-nwl.o
>> -obj-$(CONFIG_PCI_XGENE) += pci-xgene.o
>> +obj-$(CONFIG_PCI_XGENE) += pci-xgene.o pci-xgene-ecam.o
>> obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o
>> obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o
>> obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o
>> diff --git a/drivers/pci/host/pci-xgene-ecam.c b/drivers/pci/host/pci-xgene-ecam.c
>> new file mode 100644
>> index 0000000..b66a04f
>> --- /dev/null
>> +++ b/drivers/pci/host/pci-xgene-ecam.c
>> @@ -0,0 +1,280 @@
>> +/*
>> + * APM X-Gene PCIe ECAM fixup driver
>> + *
>> + * Copyright (c) 2016, Applied Micro Circuits Corporation
>> + * Author:
>> + * Duc Dang <dhdang at apm.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_pci.h>
>> +#include <linux/pci-acpi.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pci-ecam.h>
>> +
>> +#ifdef CONFIG_ACPI
>> +#define RTDID 0x160
>> +#define ROOT_CAP_AND_CTRL 0x5C
>> +
>> +/* PCIe IP version */
>> +#define XGENE_PCIE_IP_VER_UNKN 0
>> +#define XGENE_PCIE_IP_VER_1 1
>> +#define XGENE_PCIE_IP_VER_2 2
>
> We only use XGENE_PCIE_IP_VER_1, so I think the others should be
> removed. I think it would be nicer to have a "crs_broken:1" bit set
> by the probe path, and get rid of the version field altogether.
We do use XGENE_PCIE_IP_VER_2 for X-Gene v2 PCIe controller, but your
idea about using crs_broken is much better. I will make change
following that. This will affect DT booting too.
>
>> +#define XGENE_CSR_LENGTH 0x10000
>> +
>> +struct xgene_pcie_acpi_root {
>> + void __iomem *csr_base;
>> + u32 version;
>> +};
>
> I think this should be folded into struct xgene_pcie_port so we don't
> have to allocate and manage it separately.
I will need to look into this more. When booting with ACPI mode, the
code in pci-xgene.c is not used (except the cfg read/write functions
that are shared with ECAM quirk code), so puting these into
xgene_pcie_port will force ECAM quirk code to allocate this structure
as well.
>
>> +static int xgene_v1_pcie_ecam_init(struct pci_config_window *cfg)
>> +{
>> + struct xgene_pcie_acpi_root *xgene_root;
>> + struct device *dev = cfg->parent;
>> + u32 csr_base;
>> +
>> + xgene_root = devm_kzalloc(dev, sizeof(*xgene_root), GFP_KERNEL);
>> + if (!xgene_root)
>> + return -ENOMEM;
>> +
>> + switch (cfg->res.start) {
>> + case 0xE0D0000000ULL:
>> + csr_base = 0x1F2B0000;
>> + break;
>> + case 0xD0D0000000ULL:
>> + csr_base = 0x1F2C0000;
>> + break;
>> + case 0x90D0000000ULL:
>> + csr_base = 0x1F2D0000;
>> + break;
>> + case 0xA0D0000000ULL:
>> + csr_base = 0x1F500000;
>> + break;
>> + case 0xC0D0000000ULL:
>> + csr_base = 0x1F510000;
>> + break;
>
> Ugh. What in the world is going on here? Apparently we're testing a
> host bridge resource against this hard-coded list of random values,
> and based on that, we know about this *other* list of hard-coded CSR
> ranges? This is not the way resource discovery normally works ;)
Yes, this is very ugly :(
But discovering CSR regions by using acpi_walk_resource for each root
port is not a preferred solution because of the concern that it may
open a backdoor for continuously abusive usage of the quirk for new
version of SoCs (https://patchwork.kernel.org/patch/9178791/, last 2
comments from Lorenzo and Ard)
>
>> + default:
>> + return -ENODEV;
>> + }
>> +
>> + xgene_root->csr_base = ioremap(csr_base, XGENE_CSR_LENGTH);
>
> There should be a request_region() somewhere, too. Ideal would be to
> use devm_ioremap_resource(), but I don't know where this apparent
> resource is coming from.
Yes, I will use request_region/devm_ioremap_resource here.
>
>> + if (!xgene_root->csr_base) {
>> + kfree(xgene_root);
>> + return -ENODEV;
>> + }
>> +
>> + xgene_root->version = XGENE_PCIE_IP_VER_1;
>> +
>> + cfg->priv = xgene_root;
>> +
>> + return 0;
>> +}
>> +
>> +static int xgene_v2_1_pcie_ecam_init(struct pci_config_window *cfg)
>> +{
>> + struct xgene_pcie_acpi_root *xgene_root;
>> + struct device *dev = cfg->parent;
>> + resource_size_t csr_base;
>> +
>> + xgene_root = devm_kzalloc(dev, sizeof(*xgene_root), GFP_KERNEL);
>> + if (!xgene_root)
>> + return -ENOMEM;
>> +
>> + switch (cfg->res.start) {
>> + case 0xC0D0000000ULL:
>> + csr_base = 0x1F2B0000;
>> + break;
>> + case 0xA0D0000000ULL:
>> + csr_base = 0x1F2C0000;
>> + break;
>> + default:
>> + return -ENODEV;
>> + }
>> +
>> + xgene_root->csr_base = ioremap(csr_base, XGENE_CSR_LENGTH);
>> + if (!xgene_root->csr_base) {
>> + kfree(xgene_root);
>> + return -ENODEV;
>> + }
>> +
>> + xgene_root->version = XGENE_PCIE_IP_VER_2;
>> +
>> + cfg->priv = xgene_root;
>> +
>> + return 0;
>> +}
>> +
>> +static int xgene_v2_2_pcie_ecam_init(struct pci_config_window *cfg)
>> +{
>> + struct xgene_pcie_acpi_root *xgene_root;
>> + struct device *dev = cfg->parent;
>> + resource_size_t csr_base;
>> +
>> + xgene_root = devm_kzalloc(dev, sizeof(*xgene_root), GFP_KERNEL);
>> + if (!xgene_root)
>> + return -ENOMEM;
>> +
>> + switch (cfg->res.start) {
>> + case 0xE0D0000000ULL:
>> + csr_base = 0x1F2B0000;
>> + break;
>> + case 0xA0D0000000ULL:
>> + csr_base = 0x1F500000;
>> + break;
>> + case 0x90D0000000ULL:
>> + csr_base = 0x1F2D0000;
>> + break;
>> + default:
>> + return -ENODEV;
>> + }
>> +
>> + xgene_root->csr_base = ioremap(csr_base, XGENE_CSR_LENGTH);
>> + if (!xgene_root->csr_base) {
>> + kfree(xgene_root);
>> + return -ENODEV;
>> + }
>> +
>> + xgene_root->version = XGENE_PCIE_IP_VER_2;
>> +
>> + cfg->priv = xgene_root;
>> +
>> + return 0;
>> +}
>> +/*
>> + * For Configuration request, RTDID register is used as Bus Number,
>> + * Device Number and Function number of the header fields.
>> + */
>> +static void xgene_pcie_set_rtdid_reg(struct pci_bus *bus, uint devfn)
>> +{
>> + struct pci_config_window *cfg = bus->sysdata;
>> + struct xgene_pcie_acpi_root *port = cfg->priv;
>> + unsigned int b, d, f;
>> + u32 rtdid_val = 0;
>> +
>> + b = bus->number;
>> + d = PCI_SLOT(devfn);
>> + f = PCI_FUNC(devfn);
>> +
>> + if (!pci_is_root_bus(bus))
>> + rtdid_val = (b << 8) | (d << 3) | f;
>> +
>> + writel(rtdid_val, port->csr_base + RTDID);
>> + /* read the register back to ensure flush */
>> + readl(port->csr_base + RTDID);
>> +}
>> +
>> +/*
>> + * X-Gene PCIe port uses BAR0-BAR1 of RC's configuration space as
>> + * the translation from PCI bus to native BUS. Entire DDR region
>> + * is mapped into PCIe space using these registers, so it can be
>> + * reached by DMA from EP devices. The BAR0/1 of bridge should be
>> + * hidden during enumeration to avoid the sizing and resource allocation
>> + * by PCIe core.
>> + */
>> +static bool xgene_pcie_hide_rc_bars(struct pci_bus *bus, int offset)
>> +{
>> + if (pci_is_root_bus(bus) && ((offset == PCI_BASE_ADDRESS_0) ||
>> + (offset == PCI_BASE_ADDRESS_1)))
>> + return true;
>> +
>> + return false;
>> +}
>> +
>> +void __iomem *xgene_pcie_ecam_map_bus(struct pci_bus *bus,
>> + unsigned int devfn, int where)
>> +{
>> + struct pci_config_window *cfg = bus->sysdata;
>> + unsigned int busn = bus->number;
>> + void __iomem *base;
>> +
>> + if (busn < cfg->busr.start || busn > cfg->busr.end)
>> + return NULL;
>> +
>> + if ((pci_is_root_bus(bus) && devfn != 0) ||
>> + xgene_pcie_hide_rc_bars(bus, where))
>> + return NULL;
>> +
>> + xgene_pcie_set_rtdid_reg(bus, devfn);
>> +
>> + if (busn > cfg->busr.start)
>> + base = cfg->win + (1 << cfg->ops->bus_shift);
>> + else
>> + base = cfg->win;
>> +
>> + return base + where;
>> +}
>> +
>> +static int xgene_pcie_config_read32(struct pci_bus *bus, unsigned int devfn,
>> + int where, int size, u32 *val)
>> +{
>> + struct pci_config_window *cfg = bus->sysdata;
>> + struct xgene_pcie_acpi_root *port = cfg->priv;
>> +
>> + if (pci_generic_config_read32(bus, devfn, where & ~0x3, 4, val) !=
>> + PCIBIOS_SUCCESSFUL)
>> + return PCIBIOS_DEVICE_NOT_FOUND;
>> +
>> + /*
>> + * The v1 controller has a bug in its Configuration Request
>> + * Retry Status (CRS) logic: when CRS is enabled and we read the
>> + * Vendor and Device ID of a non-existent device, the controller
>> + * fabricates return data of 0xFFFF0001 ("device exists but is not
>> + * ready") instead of 0xFFFFFFFF ("device does not exist"). This
>> + * causes the PCI core to retry the read until it times out.
>> + * Avoid this by not claiming to support CRS.
>> + */
>> + if (pci_is_root_bus(bus) && (port->version == XGENE_PCIE_IP_VER_1) &&
>> + ((where & ~0x3) == ROOT_CAP_AND_CTRL))
>> + *val &= ~(PCI_EXP_RTCAP_CRSVIS << 16);
>> +
>> + if (size <= 2)
>> + *val = (*val >> (8 * (where & 3))) & ((1 << (size * 8)) - 1);
>> +
>> + return PCIBIOS_SUCCESSFUL;
>> +}
>> +
>> +struct pci_ecam_ops xgene_v1_pcie_ecam_ops = {
>> + .bus_shift = 16,
>> + .init = xgene_v1_pcie_ecam_init,
>> + .pci_ops = {
>> + .map_bus = xgene_pcie_ecam_map_bus,
>> + .read = xgene_pcie_config_read32,
>> + .write = pci_generic_config_write,
>> + }
>> +};
>> +
>> +struct pci_ecam_ops xgene_v2_1_pcie_ecam_ops = {
>> + .bus_shift = 16,
>> + .init = xgene_v2_1_pcie_ecam_init,
>> + .pci_ops = {
>> + .map_bus = xgene_pcie_ecam_map_bus,
>> + .read = xgene_pcie_config_read32,
>> + .write = pci_generic_config_write,
>> + }
>> +};
>> +
>> +struct pci_ecam_ops xgene_v2_2_pcie_ecam_ops = {
>> + .bus_shift = 16,
>> + .init = xgene_v2_2_pcie_ecam_init,
>> + .pci_ops = {
>> + .map_bus = xgene_pcie_ecam_map_bus,
>> + .read = xgene_pcie_config_read32,
>> + .write = pci_generic_config_write,
>> + }
>> +};
>> +#endif
>> diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h
>> index 35f0e81..40da3e7 100644
>> --- a/include/linux/pci-ecam.h
>> +++ b/include/linux/pci-ecam.h
>> @@ -65,6 +65,11 @@ extern struct pci_ecam_ops pci_thunder_pem_ops;
>> #ifdef CONFIG_PCI_HOST_THUNDER_ECAM
>> extern struct pci_ecam_ops pci_thunder_ecam_ops;
>> #endif
>> +#ifdef CONFIG_PCI_XGENE
>> +extern struct pci_ecam_ops xgene_v1_pcie_ecam_ops;
>> +extern struct pci_ecam_ops xgene_v2_1_pcie_ecam_ops;
>> +extern struct pci_ecam_ops xgene_v2_2_pcie_ecam_ops;
>> +#endif
>>
>> #ifdef CONFIG_PCI_HOST_GENERIC
>> /* for DT-based PCI controllers that support ECAM */
>> --
>> 1.9.1
>>
Regards,
Duc Dang.
More information about the linux-arm-kernel
mailing list