From: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>
To: Phil Edworthy <phil.edworthy@renesas.com>, linux-pci@vger.kernel.org
Cc: linux-sh@vger.kernel.org,
LAKML <linux-arm-kernel@lists.infradead.org>,
Bjorn Helgaas <bhelgaas@google.com>,
Valentine Barshak <valentine.barshak@cogentembedded.com>,
Simon Horman <horms@verge.net.au>,
Magnus Damm <magnus.damm@gmail.com>,
Ben Dooks <ben.dooks@codethink.co.uk>,
Jason Gunthorpe <jgunthorpe@obsidianresearch.com>,
Lucas Stach <l.stach@pengutronix.de>
Subject: Re: [PATCH v8 1/3] PCI: host: rcar: Add Renesas R-Car PCIe driver
Date: Thu, 19 Jun 2014 01:51:22 +0400 [thread overview]
Message-ID: <53A209DA.9000000@cogentembedded.com> (raw)
In-Reply-To: <1399892270-25021-2-git-send-email-phil.edworthy@renesas.com>
Hello.
On 05/12/2014 02:57 PM, Phil Edworthy wrote:
I'm investigating an imprecise external abort occurring once userland is
started when I have NetMos PCIe serial card inserted and the '8250_pci' driver
enabled and I have found some issues in this driver, while at it...
> This PCIe Host driver currently does not support MSI, so cards
> fall back to INTx interrupts.
> Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
[...]
> diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c
> new file mode 100644
> index 0000000..3c524b9
> --- /dev/null
> +++ b/drivers/pci/host/pcie-rcar.c
> @@ -0,0 +1,768 @@
[...]
> +#define PCI_MAX_RESOURCES 4
As a side note, this risks collision with <linux/pci*.h>...
> +static void pci_write_reg(struct rcar_pcie *pcie, unsigned long val,
> + unsigned long reg)
> +{
> + writel(val, pcie->base + reg);
> +}
> +
> +static unsigned long pci_read_reg(struct rcar_pcie *pcie, unsigned long reg)
> +{
> + return readl(pcie->base + reg);
> +}
As a side note, these functions are hardly needed, and risk collision too...
> +
> +enum {
> + PCI_ACCESS_READ,
> + PCI_ACCESS_WRITE,
These risk collision too...
> +static void rcar_pcie_setup_window(int win, struct resource *res,
> + struct rcar_pcie *pcie)
As a side note, 'res' parameter is hardly needed here, as the function
always gets
called with the resources contained within 'struct rcar_pcie'...
> +{
> + /* Setup PCIe address space mappings for each resource */
> + resource_size_t size;
> + u32 mask;
> +
> + pci_write_reg(pcie, 0x00000000, PCIEPTCTLR(win));
> +
> + /*
> + * The PAMR mask is calculated in units of 128Bytes, which
> + * keeps things pretty simple.
> + */
> + size = resource_size(res);
> + mask = (roundup_pow_of_two(size) / SZ_128) - 1;
> + pci_write_reg(pcie, mask << 7, PCIEPAMR(win));
> +
> + pci_write_reg(pcie, upper_32_bits(res->start), PCIEPARH(win));
> + pci_write_reg(pcie, lower_32_bits(res->start), PCIEPARL(win));
My investigation showed and printk() here confirmed that instead of a PCI
bus address here we have CPU address written to these registers:
rcar_pcie_setup_window: window 0, resource [io 0xfe100000-0xfe1fffff]
rcar_pcie_setup_window: window 1, resource [mem 0xfe200000-0xfe3fffff]
rcar_pcie_setup_window: window 2, resource [mem 0x30000000-0x37ffffff]
rcar_pcie_setup_window: window 3, resource [mem 0x38000000-0x3fffffff pref]
rcar-pcie fe000000.pcie: PCI host bridge to bus 0000:00
> +
> + /* First resource is for IO */
> + mask = PAR_ENABLE;
> + if (res->flags & IORESOURCE_IO)
> + mask |= IO_SPACE;
For the memory space this works OK as you're identity-mapping the memory
ranges in your device trees. However, for the I/O space this means that it
won't work as the BARs in the PCIe devices get programmed with the PCI bus
addresses but the PCIe window translation register is programmed with a CPU
address which don't at all match (given your device trees) and hence one can't
access the card's I/O mapped registers at all...
> +
> + pci_write_reg(pcie, mask, PCIEPTCTLR(win));
> +}
> +
> +static int rcar_pcie_setup(int nr, struct pci_sys_data *sys)
> +{
> + struct rcar_pcie *pcie = sys_to_pcie(sys);
> + struct resource *res;
> + int i;
> +
> + pcie->root_bus_nr = -1;
> +
> + /* Setup PCI resources */
> + for (i = 0; i < PCI_MAX_RESOURCES; i++) {
> +
> + res = &pcie->res[i];
> + if (!res->flags)
> + continue;
> +
> + rcar_pcie_setup_window(i, res, pcie);
> +
> + if (res->flags & IORESOURCE_IO)
> + pci_ioremap_io(nr * SZ_64K, res->start);
I'm not sure why are you not calling pci_add_resource() for I/O space...
Also, this sets up only 64 KiB of I/O ports while your device tree describes
I/O space 1 MiB is size.
> + else
> + pci_add_resource(&sys->resources, res);
> + }
> + pci_add_resource(&sys->resources, &pcie->busn);
> +
> + return 1;
> +}
[...]
> +static int rcar_pcie_hw_init(struct rcar_pcie *pcie)
> +{
> + int err;
> +
> + /* Begin initialization */
> + pci_write_reg(pcie, 0, PCIETCTLR);
> +
> + /* Set mode */
> + pci_write_reg(pcie, 1, PCIEMSR);
> +
> + /*
> + * Initial header for port config space is type 1, set the device
> + * class to match. Hardware takes care of propagating the IDSETR
> + * settings, so there is no need to bother with a quirk.
> + */
> + pci_write_reg(pcie, PCI_CLASS_BRIDGE_PCI << 16, IDSETR1);
Hm, shouldn't this be a host bridge? I've noticed that the bridge's I/O
and memory base/limit registers are left uninitialized even though the BARs of
the PICe devices behind this bridge are assigned.
> +
> + /*
> + * Setup Secondary Bus Number & Subordinate Bus Number, even though
> + * they aren't used, to avoid bridge being detected as broken.
> + */
> + rcar_rmw32(pcie, RCONF(PCI_SECONDARY_BUS), 0xff, 1);
> + rcar_rmw32(pcie, RCONF(PCI_SUBORDINATE_BUS), 0xff, 1);
> +
> + /* Initialize default capabilities. */
> + rcar_rmw32(pcie, REXPCAP(0), 0, PCI_CAP_ID_EXP);
> + rcar_rmw32(pcie, REXPCAP(PCI_EXP_FLAGS),
> + PCI_EXP_FLAGS_TYPE, PCI_EXP_TYPE_ROOT_PORT << 4);
> + rcar_rmw32(pcie, RCONF(PCI_HEADER_TYPE), 0x7f,
> + PCI_HEADER_TYPE_BRIDGE);
> +
> + /* Enable data link layer active state reporting */
> + rcar_rmw32(pcie, REXPCAP(PCI_EXP_LNKCAP), 0, PCI_EXP_LNKCAP_DLLLARC);
> +
> + /* Write out the physical slot number = 0 */
> + rcar_rmw32(pcie, REXPCAP(PCI_EXP_SLTCAP), PCI_EXP_SLTCAP_PSN, 0);
> +
> + /* Set the completion timer timeout to the maximum 50ms. */
> + rcar_rmw32(pcie, TLCTLR+1, 0x3f, 50);
Missing spaces around '+'...
> +
> + /* Terminate list of capabilities (Next Capability Offset=0) */
> + rcar_rmw32(pcie, RVCCAP(0), 0xfff0, 0);
> +
> + /* Enable MAC data scrambling. */
> + rcar_rmw32(pcie, MACCTLR, SCRAMBLE_DISABLE, 0);
Doesn't the comment contradict the code here?
> +
> + /* Finish initialization - establish a PCI Express link */
> + pci_write_reg(pcie, CFINIT, PCIETCTLR);
> +
> + /* This will timeout if we don't have a link. */
> + err = rcar_pcie_wait_for_dl(pcie);
> + if (err)
> + return err;
> +
> + /* Enable INTx interrupts */
> + rcar_rmw32(pcie, PCIEINTXR, 0, 0xF << 8);
> +
> + /* Enable slave Bus Mastering */
> + rcar_rmw32(pcie, RCONF(PCI_STATUS), PCI_STATUS_DEVSEL_MASK,
> + PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
> + PCI_STATUS_CAP_LIST | PCI_STATUS_DEVSEL_FAST);
Hmm, you're mixing up PCI control/status registers' bits here; they're
two 16-bit registers! So you're writing to 3 reserved LSBs of the PCI status
register...
> +static int rcar_pcie_get_resources(struct platform_device *pdev,
> + struct rcar_pcie *pcie)
> +{
> + struct resource res;
> + int err;
> +
> + err = of_address_to_resource(pdev->dev.of_node, 0, &res);
BTW, you could use platfrom_get_resource() and save on your local variable
and the error check -- devm_ioremap_resource() does it.
> + if (err)
> + return err;
> +
> + pcie->clk = devm_clk_get(&pdev->dev, "pcie");
> + if (IS_ERR(pcie->clk)) {
> + dev_err(pcie->dev, "cannot get platform clock\n");
> + return PTR_ERR(pcie->clk);
> + }
> + err = clk_prepare_enable(pcie->clk);
> + if (err)
> + goto fail_clk;
> +
> + pcie->bus_clk = devm_clk_get(&pdev->dev, "pcie_bus");
> + if (IS_ERR(pcie->bus_clk)) {
> + dev_err(pcie->dev, "cannot get pcie bus clock\n");
> + err = PTR_ERR(pcie->bus_clk);
> + goto fail_clk;
> + }
> + err = clk_prepare_enable(pcie->bus_clk);
> + if (err)
> + goto err_map_reg;
> +
> + pcie->base = devm_ioremap_resource(&pdev->dev, &res);
> + if (IS_ERR(pcie->base)) {
> + err = PTR_ERR(pcie->base);
> + goto err_map_reg;
> + }
> +
> + return 0;
> +
> +err_map_reg:
> + clk_disable_unprepare(pcie->bus_clk);
> +fail_clk:
> + clk_disable_unprepare(pcie->clk);
> +
> + return err;
> +}
> +
> +static int rcar_pcie_inbound_ranges(struct rcar_pcie *pcie,
> + struct of_pci_range *range,
> + int *index)
> +{
> + u64 restype = range->flags;
> + u64 cpu_addr = range->cpu_addr;
> + u64 cpu_end = range->cpu_addr + range->size;
> + u64 pci_addr = range->pci_addr;
> + u32 flags = LAM_64BIT | LAR_ENABLE;
> + u64 mask;
> + u64 size;
> + int idx = *index;
> +
> + if (restype & IORESOURCE_PREFETCH)
> + flags |= LAM_PREFETCH;
> +
> + /*
> + * If the size of the range is larger than the alignment of the start
> + * address, we have to use multiple entries to perform the mapping.
> + */
> + if (cpu_addr > 0) {
> + unsigned long nr_zeros = __ffs64(cpu_addr);
> + u64 alignment = 1ULL << nr_zeros;
Missing newline...
> + size = min(range->size, alignment);
> + } else {
> + size = range->size;
> + }
> + /* Hardware supports max 4GiB inbound region */
> + size = min(size, 1ULL << 32);
> +
> + mask = roundup_pow_of_two(size) - 1;
> + mask &= ~0xf;
> +
> + while (cpu_addr < cpu_end) {
> + /*
> + * Set up 64-bit inbound regions as the range parser doesn't
> + * distinguish between 32 and 64-bit types.
> + */
> + pci_write_reg(pcie, lower_32_bits(pci_addr), PCIEPRAR(idx));
> + pci_write_reg(pcie, lower_32_bits(cpu_addr), PCIELAR(idx));
> + pci_write_reg(pcie, lower_32_bits(mask) | flags, PCIELAMR(idx));
> +
> + pci_write_reg(pcie, upper_32_bits(pci_addr), PCIEPRAR(idx+1));
> + pci_write_reg(pcie, upper_32_bits(cpu_addr), PCIELAR(idx+1));
> + pci_write_reg(pcie, 0, PCIELAMR(idx+1));
Missing spaces around '+'...
> +
> + pci_addr += size;
> + cpu_addr += size;
> + idx += 2;
> +
> + if (idx > MAX_NR_INBOUND_MAPS) {
> + dev_err(pcie->dev, "Failed to map inbound regions!\n");
> + return -EINVAL;
> + }
> + }
> + *index = idx;
> +
> + return 0;
> +}
> +
> +static int pci_dma_range_parser_init(struct of_pci_range_parser *parser,
> + struct device_node *node)
> +{
> + const int na = 3, ns = 2;
> + int rlen;
> +
> + parser->node = node;
> + parser->pna = of_n_addr_cells(node);
> + parser->np = parser->pna + na + ns;
> +
> + parser->range = of_get_property(node, "dma-ranges", &rlen);
> + if (!parser->range)
> + return -ENOENT;
> +
> + parser->end = parser->range + rlen / sizeof(__be32);
> + return 0;
> +}
Erm, AFAIK "dma-ranges" is a standard property, shouldn't its parsing be
placed in some generic place like drivers/of/address.c?
[...]
> +static int rcar_pcie_probe(struct platform_device *pdev)
> +{
> + struct rcar_pcie *pcie;
> + unsigned int data;
> + struct of_pci_range range;
> + struct of_pci_range_parser parser;
> + const struct of_device_id *of_id;
> + int err, win = 0;
> + int (*hw_init_fn)(struct rcar_pcie *);
> +
> + pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL);
> + if (!pcie)
> + return -ENOMEM;
> +
> + pcie->dev = &pdev->dev;
> + platform_set_drvdata(pdev, pcie);
> +
> + /* Get the bus range */
> + if (of_pci_parse_bus_range(pdev->dev.of_node, &pcie->busn)) {
> + dev_err(&pdev->dev, "failed to parse bus-range property\n");
> + return -EINVAL;
> + }
> +
> + if (of_pci_range_parser_init(&parser, pdev->dev.of_node)) {
> + dev_err(&pdev->dev, "missing ranges property\n");
> + return -EINVAL;
> + }
> +
> + err = rcar_pcie_get_resources(pdev, pcie);
> + if (err < 0) {
> + dev_err(&pdev->dev, "failed to request resources: %d\n", err);
> + return err;
> + }
> +
> + for_each_of_pci_range(&parser, &range) {
> + of_pci_range_to_resource(&range, pdev->dev.of_node,
> + &pcie->res[win++]);
This function call is probably no good here as it fetches into the 'start'
field of a 'struct resource' a CPU address instead of a PCI address...
> +
> + if (win > PCI_MAX_RESOURCES)
> + break;
> + }
> +
> + err = rcar_pcie_parse_map_dma_ranges(pcie, pdev->dev.of_node);
> + if (err)
> + return err;
> +
> + of_id = of_match_device(rcar_pcie_of_match, pcie->dev);
> + if (!of_id || !of_id->data)
> + return -EINVAL;
> + hw_init_fn = of_id->data;
> +
> + /* Failure to get a link might just be that no cards are inserted */
> + err = hw_init_fn(pcie);
> + if (err) {
> + dev_info(&pdev->dev, "PCIe link down\n");
> + return 0;
Not quite sure why you exit normally here without enabling the hardware.
I think the internal bridge should be visible regardless of whether link is
detected or not...
> + }
> +
> + data = pci_read_reg(pcie, MACSR);
> + dev_info(&pdev->dev, "PCIe x%d: link up\n", (data >> 20) & 0x3f);
> +
> + rcar_pcie_enable(pcie);
> +
> + return 0;
> +}
[...]
WBR, Sergei
next prev parent reply other threads:[~2014-06-18 21:51 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-05-12 10:57 [PATCH v8 0/3] R-Car Gen2 PCIe host driver Phil Edworthy
2014-05-12 10:57 ` [PATCH v8 1/3] PCI: host: rcar: Add Renesas R-Car PCIe driver Phil Edworthy
2014-06-18 21:51 ` Sergei Shtylyov [this message]
2014-06-23 16:44 ` Phil Edworthy
2014-06-23 21:11 ` Sergei Shtylyov
2014-06-24 10:01 ` Phil Edworthy
2014-06-24 21:19 ` Sergei Shtylyov
2014-06-27 16:40 ` Phil Edworthy
2014-06-20 7:37 ` Gabriel Fernandez
2014-05-12 10:57 ` [PATCH v8 2/3] PCI: host: rcar: Add MSI support Phil Edworthy
2014-05-12 10:57 ` [PATCH v8 3/3] dt-bindings: pci: rcar pcie device tree bindings Phil Edworthy
2014-05-27 23:09 ` [PATCH v8 0/3] R-Car Gen2 PCIe host driver Bjorn Helgaas
2014-05-28 0:41 ` Simon Horman
2014-05-28 2:48 ` Bjorn Helgaas
2014-05-28 3:52 ` Simon Horman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=53A209DA.9000000@cogentembedded.com \
--to=sergei.shtylyov@cogentembedded.com \
--cc=ben.dooks@codethink.co.uk \
--cc=bhelgaas@google.com \
--cc=horms@verge.net.au \
--cc=jgunthorpe@obsidianresearch.com \
--cc=l.stach@pengutronix.de \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-pci@vger.kernel.org \
--cc=linux-sh@vger.kernel.org \
--cc=magnus.damm@gmail.com \
--cc=phil.edworthy@renesas.com \
--cc=valentine.barshak@cogentembedded.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).