[PATCH 6/9] [Xilinx] xilinx_pcipr: Added Xilinx reconfigurable PCI endpoint driver.

Grant Likely grant.likely at secretlab.ca
Wed Aug 11 04:14:13 EST 2010


On Fri, Jul 23, 2010 at 2:57 PM, Stephen Neuendorffer
<stephen.neuendorffer at xilinx.com> wrote:
> This device has an internal bus which contains multiple devices, which
> are described in a device tree.
>
> Signed-off-by: Stephen Neuendorffer <stephen.neuendorffer at xilinx.com>

You need to cc: the pci mailing list on the next submission.

>
> ----
>
> This is probably still preliminary, although it actually works now.
> 1) It should probably correctly deallocate and free all the contained
> devices.
> 2) It should probably handle different device trees for multiple boards
> in the same system.  How to do this?
> ---
>  drivers/pci/Kconfig        |    8 ++
>  drivers/pci/Makefile       |    2 +
>  drivers/pci/xilinx_pcipr.c |  197 ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 207 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/pci/xilinx_pcipr.c
>
> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> index 34ef70d..69616b6 100644
> --- a/drivers/pci/Kconfig
> +++ b/drivers/pci/Kconfig
> @@ -65,3 +65,11 @@ config PCI_IOAPIC
>        depends on ACPI
>        depends on HOTPLUG
>        default y
> +
> +config XILINX_PCIPR
> +       tristate "Xilinx OF-based PCI endpoint"
> +       depends on PCI
> +       select OF
> +       select OF_FLATTREE
> +       help
> +         Enable support for Xilinx PCIPR endpoint
> diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
> index 0b51857..ec44c21 100644
> --- a/drivers/pci/Makefile
> +++ b/drivers/pci/Makefile
> @@ -62,6 +62,8 @@ obj-$(CONFIG_PCI_SYSCALL) += syscall.o
>
>  obj-$(CONFIG_PCI_STUB) += pci-stub.o
>
> +obj-$(CONFIG_XILINX_PCIPR) += xilinx_pcipr.o
> +
>  ifeq ($(CONFIG_PCI_DEBUG),y)
>  EXTRA_CFLAGS += -DDEBUG
>  endif
> diff --git a/drivers/pci/xilinx_pcipr.c b/drivers/pci/xilinx_pcipr.c
> new file mode 100644
> index 0000000..80f8f1a
> --- /dev/null
> +++ b/drivers/pci/xilinx_pcipr.c
> @@ -0,0 +1,197 @@
> +/*

Always good practice to include a one line description of what this
file is for in the header block.

> + *  Copyright 2010 Xilinx, Inc.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <linux/kernel.h>
> +#include <linux/pci.h>
> +#include <linux/ide.h>
> +#include <linux/init.h>
> +#include <linux/dmi.h>
> +#include <linux/firmware.h>
> +
> +#include <linux/of.h>
> +#include <linux/of_fdt.h>
> +#include <linux/of_platform.h>
> +
> +#include <linux/io.h>
> +
> +#define DRV_NAME "xilinx_pcipr"

unused

> +
> +struct xilinx_pcipr_drvdata {
> +       struct device_node *child_nodes;
> +       struct firmware *fw_entry;
> +};
> +
> +/**
> + *     xilinx_pcipr_probe - Setup the endpoint
> + *     @dev: PCI device to set up
> + */
> +static int __devinit xilinx_pcipr_probe(struct pci_dev *pdev,
> +                                       const struct pci_device_id *id)
> +{
> +       int ret;
> +       struct xilinx_pcipr_drvdata *drvdata;
> +       resource_size_t start, len;
> +       struct property *ranges_prop;
> +       unsigned long *value;
> +       struct device_node *bus_node;
> +
> +       dev_dbg(&pdev->dev, "xilinx_pcipr_probe\n");
> +
> +       drvdata = kzalloc(sizeof(struct xilinx_pcipr_drvdata), GFP_KERNEL);
> +       if (!drvdata) {
> +               dev_err(&pdev->dev,
> +                       "Couldn't allocate device private record\n");
> +               ret = -ENOMEM;
> +               goto out;
> +       }
> +
> +       dev_set_drvdata(&pdev->dev, (void *)drvdata);
> +
> +       /*
> +        * Do some basic sanity checking..
> +        */
> +       if (pci_enable_device(pdev)) {
> +               ret = -EBUSY;
> +               goto free;
> +       }
> +
> +       /* Enable the board to bus master. */
> +       pci_set_master(pdev);
> +
> +       if (request_firmware(&drvdata->fw_entry,
> +                           "xilinx_pcipr.dtb", &pdev->dev)) {
> +               dev_err(&pdev->dev, "Couldn't get dtb\n");
> +               ret = -ENODEV;
> +               goto disable;
> +       }
> +
> +
> +       ret = pci_request_regions(pdev, "xilinx_pcipr");
> +       if (ret)
> +               goto release_dtb;
> +
> +       if (!pci_resource_start(pdev, 0)) {
> +               dev_printk(KERN_ERR, &pdev->dev, "No cardbus resource!\n");
> +               ret = -ENODEV;
> +               goto release;
> +       }
> +
> +
> +       /*
> +        * report the subsystem vendor and device for help debugging
> +        * the irq stuff...
> +        */
> +       dev_printk(KERN_INFO, &pdev->dev,
> +                  "Xilinx PCIPR bridge found [%04x:%04x]\n",
> +                  pdev->subsystem_vendor, pdev->subsystem_device);
> +
> +       /* Stuff the ranges property into the toplevel bus of the device
> +          tree, according to how the BARs are programmed */
> +       start = pci_resource_start(pdev, 0);
> +       len = pci_resource_len(pdev, 0);
> +
> +       dev_printk(KERN_INFO, &pdev->dev,
> +                  "Xilinx PCIPR bridge range %llx %llx",
> +                  (unsigned long long) start, (unsigned long long) len);

dev_info()

> +
> +       ranges_prop = kzalloc(sizeof(struct property), GFP_KERNEL);

if (!ranges_prop) ...

> +       ranges_prop->value = kzalloc(sizeof(unsigned long) * 3, GFP_KERNEL);

if (!ranges_prop->value) ...

> +       ranges_prop->name = "ranges";
> +       ranges_prop->length = sizeof(unsigned long) * 3;
> +       value = (unsigned long *)ranges_prop->value;
> +       /* FIXME: gotta get this from the bridge */
> +       value[0] = cpu_to_be32(0x80000000);
> +       value[1] = cpu_to_be32(start);
> +       value[2] = cpu_to_be32(len);

This won't work if the PCI BARs gets mapped above 4GiB.  It will need
to handle the case where #address-cells and #size-cells on the root
are 2.

> +
> +       unflatten_partial_device_tree((unsigned long *)drvdata->fw_entry->data,
> +                                     &drvdata->child_nodes);
> +       of_node_get(drvdata->child_nodes);
> +       bus_node = of_find_node_by_name(drvdata->child_nodes, "plb");
> +       prom_add_property(bus_node, ranges_prop);

Heh; clever hack.  Not exactly pretty, but it eliminates the need to
have a translation hook and it doesn't look actively dangerous so I'm
okay with it for the time being.  It would be nice to have something
cleaner though.

> +
> +       /* Generate child devices from the device tree */
> +       of_platform_bus_probe(drvdata->child_nodes, NULL, &pdev->dev);
> +
> +       pci_release_regions(pdev);
> +
> +       goto out; /* Success */
> +
> + release:
> +       pci_release_regions(pdev);
> +
> + release_dtb:
> +       release_firmware(drvdata->fw_entry);
> +
> + disable:
> +       pci_disable_device(pdev);
> + free:
> +       kfree(drvdata);
> +       dev_set_drvdata(&pdev->dev, NULL);

set the drvdata to NULL before freeing the pointer.  (probably will
never cause a problem, but it doesn't hurt to be strict).

> + out:
> +       return ret;
> +}
> +
> +static int __devexit xilinx_pcipr_remove(struct pci_dev *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct xilinx_pcipr_drvdata *drvdata = dev_get_drvdata(dev);
> +
> +       /* FIXME: Release the contained devices. There should probably
> +be a device-tree method to free a whole tree of devices, essentially
> +the inverse of of_platform_bus_probe
> +       */

The driver model tracks this for you.  It isn't even device tree
specific.  The solution to this is to walk the children of
pci_dev->dev.  (Actually, not entirely true; as you still need to call
of_put on the device_node before freeing the child device.

> +
> +       release_firmware(drvdata->fw_entry);
> +       pci_disable_device(pdev);
> +       kfree(drvdata);
> +       dev_set_drvdata(dev, NULL);
> +       return 0;
> +}
> +
> +
> +static const struct pci_device_id xilinx_pcipr_ids[] = {
> +       { PCI_VDEVICE(XILINX, 0x0505), 0 },
> +       { 0, },
> +};
> +MODULE_DEVICE_TABLE(pci, xilinx_pcipr_ids);
> +
> +pci_ers_result_t *xilinx_pcipr_error_detected(struct pci_dev *pdev,
> +                                             enum pci_channel_state error) {
> +       dev_printk(KERN_INFO, &pdev->dev, "Error detected!\n");
> +       return PCI_ERS_RESULT_NEED_RESET;
> +}
> +
> +static struct pci_error_handlers xilinx_pcipr_error_handlers = {
> +       /* PCI bus error detected on this device */
> +       .error_detected = xilinx_pcipr_error_detected,
> +};
> +
> +static struct pci_driver  xilinx_pcipr_driver = {
> +       .name           = "XILINX_PCIPR",
> +       .id_table       = xilinx_pcipr_ids,
> +       .probe          = xilinx_pcipr_probe,
> +       .remove         = __devexit_p(xilinx_pcipr_remove),
> +       .err_handler    = &xilinx_pcipr_error_handlers,
> +};
> +
> +static int __init xilinx_pcipr_init(void)
> +{
> +       return ide_pci_register_driver(&xilinx_pcipr_driver);
> +}
> +
> +static void __exit xilinx_pcipr_exit(void)
> +{
> +       pci_unregister_driver(&xilinx_pcipr_driver);
> +}
> +
> +module_init(xilinx_pcipr_init);

module_init should appear immediately after xilinx_pcipr_init()

> +module_exit(xilinx_pcipr_exit);
> +
> +MODULE_AUTHOR("Xilinx Research Labs");
> +MODULE_DESCRIPTION("PCI driver for PCI reconfigurable endpoint");
> +MODULE_LICENSE("GPL");
> --
> 1.5.6.6
>
>
>
> This email and any attachments are intended for the sole use of the named recipient(s) and contain(s) confidential information that may be proprietary, privileged or copyrighted under applicable law. If you are not the intended recipient, do not read, copy, or forward this email message or any attachments. Delete this email message and any attachments immediately.
>
>
>



-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.


More information about the devicetree-discuss mailing list