[PATCH v2 2/3] PCI: host: new PCI host controller driver for Aardvark
Thomas Petazzoni
thomas.petazzoni at free-electrons.com
Thu Jun 30 02:26:11 PDT 2016
Bjorn,
Thanks a lot for your thorough review! I've addressed the comments, but
I wanted to give some feedback on a few of them. See below.
On Wed, 22 Jun 2016 12:25:02 -0500, Bjorn Helgaas wrote:
> > +config PCI_AARDVARK
> > + bool "Aardvark PCIe controller"
> > + depends on ARCH_MVEBU && ARM64
> > + depends on OF
> > + select PCI_MSI
>
> I'm guessing this should be "depends on PCI_MSI_IRQ_DOMAIN" per Arnd's
> recent patch: http://lkml.kernel.org/r/8531046.3ceRqUA835@wuerfel
>
> I have merged Arnd's patch, so it will be in v4.8.
OK. I've cherry-picked this patch, and changed the dependency, and
things seems to be alright (I can select my driver, and PCI MSI is
forced enabled). So sounds good.
> > +/* PCIe core registers */
> > +#define PCIE_CORE_CMD_STATUS_REG 0x4
> > +#define PCIE_CORE_CMD_IO_ACCESS_EN BIT(0)
> > +#define PCIE_CORE_CMD_MEM_ACCESS_EN BIT(1)
> > +#define PCIE_CORE_CMD_MEM_IO_REQ_EN BIT(2)
> > +#define PCIE_CORE_DEV_CTRL_STATS_REG 0xC8
>
> Please use either upper- or lower-case in hex constants consistently.
> Most of the ones below are lower-case.
Fixed.
> > +#define OB_PCIE_CONFIG0 0x8
> > +#define OB_PCIE_CONFIG1 0x9
> > +#define OB_PCIE_MSG 0xc
> > +#define OB_PCIE_MSG_VENDOR 0xd
>
> Some of these definitions (ISR bits above, these window types,
> probably others) are not actually used. I usually omit unused ones on
> the theory that they add bulk and opportunities for transcription
> errors. But if they're useful to you, I'm OK with keeping them.
I've removed a good number of the unused definitions.
> > +#define PIO_TIMEOUT_MS (1)
> > +#define PCIE_LINKUP_TIMEOUT_MS (10)
>
> Bare numbers do not require parentheses.
Fixed.
> > +static bool advk_pcie_link_up(struct advk_pcie *pcie)
> > +{
> > + unsigned long timeout;
> > + u32 ltssm_state;
> > + u32 val;
> > +
> > + timeout = jiffies + msecs_to_jiffies(PCIE_LINKUP_TIMEOUT_MS);
> > +
> > + while (time_before(jiffies, timeout)) {
> > + val = advk_readl(pcie, CFG_REG);
> > + ltssm_state = (val >> LTSSM_SHIFT) & LTSSM_MASK;
> > + if (ltssm_state >= LTSSM_L0)
> > + return true;
> > + }
>
> To try to improve consistency with other similar drivers, please make
> advk_pcie_link_up() a simple function that contains one register read
> and no loop.
>
> Then make a second advk_wait_for_link() function with a loop and
> timeout. Please use the same timeout/usleep structure used in
> dw_pcie_wait_for_link() and nwl_wait_for_link().
I've fixed that. However, notice that the model used by those other
functions is a number of retries, which I was doing originally, but
Arnd suggested to change it to a time_before() based loop.
> > + /* Point PCIe unit MBUS decode windows to DRAM space. */
>
> Tidy up the comments here by making them all one-line (when they fit)
> and consistently omitting (or keeping) the periods.
Fixed.
> > +static int advk_pcie_wait_pio(struct advk_pcie *pcie)
> > +{
> > + unsigned long timeout;
> > + u32 reg, is_done;
> > +
> > + timeout = jiffies + msecs_to_jiffies(PIO_TIMEOUT_MS);
> > +
> > + while (time_before(jiffies, timeout)) {
> > + reg = advk_readl(pcie, PIO_START);
> > + is_done = advk_readl(pcie, PIO_ISR);
> > + if ((!reg) && is_done)
> > + return 0;
> > + }
>
> This busy-loop could use usleep_range() similar to what
> dw_pcie_wait_for_link() does.
No, using usleep_range() is not possible here. This function is called
with the pci_lock held (since it's used when reading/writing the
configuration space), and usleep_range() schedules.
> > + dev_err(&pcie->pdev->dev, "config read/write timed out\n");
> > + return -ETIME;
> > +}
> > +
> > +static int advk_pcie_rd_conf(struct pci_bus *bus, u32 devfn,
> > + int where, int size, u32 *val)
> > +{
> > + struct advk_pcie *pcie = bus->sysdata;
> > + u32 reg;
> > + int ret;
> > +
> > + if (PCI_SLOT(devfn) != 0) {
> > + *val = 0xffffffff;
> > + return PCIBIOS_DEVICE_NOT_FOUND;
> > + }
> > +
> > + advk_pcie_enable_axi_addr_gen(pcie, false);
>
> This AXI enable/disable requires a mutex to prevent another thread
> from performing a simulataneous config access, so please add a comment
> about which mutex you are relying on. I think there's a PCI global
> one, but I'd just like to make it explicit that we need it here,
> because many config accessors don't actually require the mutex.
>
> I assume the AXI enable/disable does not affect MMIO accesses by
> drivers to areas mapped by PCIe device BARs. If it did, that would be
> a pretty serious problem because it would be hard to ensure the mutual
> exclusion.
The AXI enable/disable was affecting MMIO accesses, so it was in fact
bogus to do this. We've switched to a different mechanism to access the
configuration space, which doesn't involve enabling/disabling AXI.
The configuration space read/write themselves are protected by the
pci_lock spinlock.
> > + /* Start PIO */
> > + advk_writel(pcie, 0, PIO_START);
> > + advk_writel(pcie, 1, PIO_ISR);
> > +
> > + /* Program the control register */
> > + if (bus->number == 0)
> > + advk_writel(pcie, PCIE_CONFIG_RD_TYPE0, PIO_CTRL);
>
> Your DT documentation requires the bus range, and
> of_pci_get_host_bridge_resources() parses it, so you probably should
> save that bus range from the DT in advk_pcie and use it here instead
> of assuming zero.
Good point, fixed.
> > + if (where % size) {
> > + advk_pcie_enable_axi_addr_gen(pcie, true);
> > + return PCIBIOS_SET_FAILED;
>
> This could be checked earlier, before fiddling with AXI and touching
> PIO_START.
Ditto, fixed.
> > +static void advk_pcie_free_msi(struct advk_pcie *pcie, int hwirq)
> > +{
> > + mutex_lock(&pcie->msi_used_lock);
> > + if (!test_bit(hwirq, pcie->msi_irq_in_use))
> > + pr_err("trying to free unused MSI#%d\n", hwirq);
>
> Use dev_err().
Indeed, fixed.
> > + pcie->irq_domain =
> > + irq_domain_add_linear(pcie_intc_node, LEGACY_IRQ_NUM,
> > + &advk_pcie_irq_domain_ops, pcie);
> > + if (!pcie->irq_domain) {
> > + dev_err(dev, "Failed to get a INTx IRQ domain\n");
> > + return PTR_ERR(pcie->irq_domain);
> > + }
>
> Can you rename this to advk_pcie_init_irq_domain() and possibly split
> to advk_pcie_init_msi_irq_domain() so it looks more similar to the
> other drivers, e.g., nwl_pcie_init_irq_domain() or
> xilinx_pcie_init_irq_domain()?
I've done two functions:
advk_pcie_init_irq_domain()
advk_pcie_init_msi_irq_domain()
Both are called from probe(). In some other drivers, I've noticed that
the function initializing the legacy interrupt irq_domain also calls
the function initializing the MSI irq_domain, but I find this rather
weird since they are really independent. To me, it makes a lot more
sense that probe() calls each of them independently.
Also, while doing this, I noticed we were leaking a device_node
reference count, so I fixed that up as well.
> > +static void advk_pcie_release_of_pci_ranges(struct advk_pcie *pcie)
> > +{
> > + pci_free_resource_list(&pcie->resources);
>
> You can inline this call below; I know other drivers wrap it too, but
> I don't know why.
Done.
> > +static const struct of_device_id advk_pcie_of_match_table[] = {
> > + { .compatible = "marvell,armada-3700-pcie", },
> > + {},
> > +};
> > +MODULE_DEVICE_TABLE(of, advk_pcie_of_match_table);
>
> Since this driver is currently bool in Kconfig, can you omit the
> MODULE_*() uses? It would be ideal to make this modular, of course,
> but if it's not modular, let's make it so it doesn't *look* like a
> module.
MODULE_DEVICE_TABLE() removed.
> > +static struct platform_driver advk_pcie_driver = {
> > + .driver = {
> > + .name = "advk-pcie",
> > + .of_match_table = advk_pcie_of_match_table,
> > + /* Driver unloading/unbinding currently not supported */
> > + .suppress_bind_attrs = true,
>
> I see some other drivers (mvebu, rcar, tegra, altera, xilinx) also use
> suppress_bind_attrs = true, but I don't know what's special about
> them. Do you know? Do we really need this here? What would it take
> to change this driver so we could omit it?
My understanding is that for the unbinding to work, you need a
->remove() hook. But the PCI infrastructure doesn't provide any
functionality to "unregister" a PCI controller. It's exactly the same
reason for which we can't make such drivers modules: because there is
no way to implement a ->remove() function that unregisters the PCI host
controller from the PCI core.
Is this a topic that is being worked on in the PCI core?
In any case, I've kept the 'suppress_bind_attrs = true' for now, since
changing the PCI core to support controller unregistration is well
beyond the addition of a single controller driver.
I'm going to send the v3 soonish, which takes into account all your
comments, as mentioned above.
Thanks again for the good review!
Thomas
--
Thomas Petazzoni, CTO, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com
More information about the linux-arm-kernel
mailing list