[PATCH] GX bus support

Milton Miller miltonm at bga.com
Tue Nov 15 04:20:38 EST 2005


On Mon Nov 14 19:16:40 EST 2005,  Benjamin Herrenschmidt sent

> This patch adds the necessary core bus support used by device drivers
> that sit on the IBM GX bus on modern pSeries machines like the Galaxy
> infiniband for example. It provide transparent DMA ops (the low level
> driver works with virtual addresses directly) along with a simple bus
> layer using the Open Firmware matching routines.


> Index: linux-work/arch/ppc64/kernel/dma.c
> ===================================================================
> --- linux-work.orig/arch/ppc64/kernel/dma.c     2005-11-14 
> 19:08:12.000000000 +1100
> +++ linux-work/arch/ppc64/kernel/dma.c  2005-11-14 19:08:14.000000000 
> +1100
> @@ -10,6 +10,7 @@
>  /* Include the busses we support */
>  #include <linux/pci.h>
>  #include <asm/vio.h>
> +#include <asm/ebus.h>
>  #include <asm/scatterlist.h>
>  #include <asm/bug.h>
>
> @@ -23,6 +24,10 @@
>         if (dev->bus == &vio_bus_type)
>                 return &vio_dma_ops;
>  #endif
> +#ifdef CONFIG_IBMEBUS
> +       if (dev->bus == &ebus_bus_type)
> +               return &ebus_dma_ops;
> +#endif
>         return NULL;
>  }
>
> @@ -46,7 +51,11 @@
>  #ifdef CONFIG_IBMVIO
>         if (dev->bus == &vio_bus_type)
>                 return -EIO;
> -#endif /* CONFIG_IBMVIO */
> +#endif
> +#ifdef CONFIG_IBMEBUS
> +       if (dev->bus == &ebus_bus_type)
> +               return -EIO;
> +#endif
>         BUG();
>         return 0;
>  }
> Index: linux-work/arch/powerpc/platforms/pseries/ebus.c
> ===================================================================
> --- /dev/null   1970-01-01 00:00:00.000000000 +0000
> +++ linux-work/arch/powerpc/platforms/pseries/ebus.c    2005-11-14 
> 19:11:08.000000000 +1100
> @@ -0,0 +1,372 @@
> +/*
> + * IBM PowerPC eBus Infrastructure Support.
> + *
> + * Copyright (c) 2005 IBM Corporation
> + *  Heiko J Schick <schickhj at de.ibm.com>
> + *
> + * All rights reserved.
> + *
> + * This source code is distributed under a dual license of GPL v2.0 
> and OpenIB
> + * BSD.
> + *
> + * OpenIB BSD License
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions 
> are met:
> + *
> + * Redistributions of source code must retain the above copyright 
> notice, this
> + * list of conditions and the following disclaimer.
> + *
> + * Redistributions in binary form must reproduce the above copyright 
> notice,
> + * this list of conditions and the following disclaimer in the 
> documentation
> + * and/or other materials
> + * provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
> CONTRIBUTORS "AS IS"
> + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
> TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
> PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
> CONTRIBUTORS BE
> + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
> OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
> + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
> LIABILITY, WHETHER
> + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
> OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
> ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/console.h>
> +#include <linux/kobject.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <asm/ebus.h>
> +#include <asm/abs_addr.h>
> +
> +static struct ebus_dev ebus_bus_device = { /* fake "parent" device */
> +       .name = ebus_bus_device.ofdev.dev.bus_id,
> +       .ofdev.dev.bus_id = "ebus",
> +       .ofdev.dev.bus    = &ebus_bus_type,
> +};
> +
> +static void *ebus_alloc_coherent(struct device *dev,
> +                                size_t size,
> +                                dma_addr_t *dma_handle,
> +                                unsigned int __nocast flag)
> +{
> +       return NULL;
> +}
> +
> +static void ebus_free_coherent(struct device *dev,
> +                              size_t size, void *vaddr, dma_addr_t 
> dma_handle)
> +{
> +       return;
> +}
> +
> +static dma_addr_t ebus_map_single(struct device *dev,
> +                                 void *ptr,
> +                                 size_t size,
> +                                 enum dma_data_direction direction)
> +{
> +       return (dma_addr_t)(ptr);
> +}
> +
> +static void ebus_unmap_single(struct device *dev,
> +                             dma_addr_t dma_addr,
> +                             size_t size, enum dma_data_direction 
> direction)
> +{
> +       return;
> +}
> +
> +static int ebus_map_sg(struct device *dev,
> +                      struct scatterlist *sg,
> +                      int nents, enum dma_data_direction direction)
> +{
> +       int i;
> +
> +       for (i = 0; i < nents; i++) {
> +               sg[i].dma_address = 
> (dma_addr_t)page_address(sg[i].page)
> +                       + sg[i].offset;
> +       }
> +
> +       return nents;
> +}
> +
> +static void ebus_unmap_sg(struct device *dev,
> +                         struct scatterlist *sg,
> +                         int nents, enum dma_data_direction direction)
> +{
> +       return;
> +}
> +
> +static int ebus_dma_supported(struct device *dev, u64 mask)
> +{
> +       return 1;
> +}
> +
> +struct dma_mapping_ops ebus_dma_ops = {
> +       .alloc_coherent = ebus_alloc_coherent,
> +       .free_coherent  = ebus_free_coherent,
> +       .map_single     = ebus_map_single,
> +       .unmap_single   = ebus_unmap_single,
> +       .map_sg         = ebus_map_sg,
> +       .unmap_sg       = ebus_unmap_sg,
> +       .dma_supported  = ebus_dma_supported,
> +};
> +
> +static int ebus_bus_probe(struct device *dev)
> +{
> +       struct ebus_dev *ebusdev    = to_ebus_dev(dev);
> +       struct ebus_driver *ebusdrv = to_ebus_driver(dev->driver);
> +       const struct of_device_id *id;
> +       int error = -ENODEV;
> +
> +       if (!ebusdrv->probe)
> +               return error;
> +
> +       id = of_match_device(ebusdrv->id_table, &ebusdev->ofdev);
> +       if (id) {
> +               error = ebusdrv->probe(ebusdev, id);
> +       }
> +
> +       return error;
> +}
> +
> +static int ebus_bus_remove(struct device *dev)
> +{
> +       struct ebus_dev *ebusdev    = to_ebus_dev(dev);
> +       struct ebus_driver *ebusdrv = to_ebus_driver(dev->driver);
> +
> +       if (ebusdrv->remove) {
> +               return ebusdrv->remove(ebusdev);
> +       }
> +
> +       return 1;
> +}
> +

what does the 1 mean?  just return 0.
(hmm... bus remove should probably be void)


> +static void __devinit ebus_dev_release(struct device *dev)
> +{
> +       of_node_put(dev->platform_data);
to_ebus_dev(dev)->ofdev.node ?

buses don't own platform_data
> +       kfree(to_ebus_dev(dev));
> +}
> +
> +static ssize_t ebusdev_show_name(struct device *dev,
> +                                struct device_attribute *attr, char 
> *buf)
> +{
> +       return sprintf(buf, "%s\n", to_ebus_dev(dev)->name);
> +}
> +static DEVICE_ATTR(name, S_IRUSR | S_IRGRP | S_IROTH, 
> ebusdev_show_name, NULL);

We need this because the id can be truncated i guess.

We should also create a devspec file with the devicetree path
like we do for pci devices.  Then use the list of attributes
helper.


> +
> +static struct ebus_dev* __devinit ebus_register_device_common(
> +       struct ebus_dev *ebusdev, char *name)
> +{
> +       ebusdev->name = name;
> +       ebusdev->ofdev.dev.parent  = &ebus_bus_device.ofdev.dev;
> +       ebusdev->ofdev.dev.bus     = &ebus_bus_type;
> +       ebusdev->ofdev.dev.release = ebus_dev_release;
> +
> +       if (of_device_register(&ebusdev->ofdev) != 0) {

wait, calling into another buses' functions?  I thought we were a bus
driver.   If that is a helper it should be clearly marked, not be hidden
in the middle of another bus' implementation and registration function.

Looking at that function, checking if the linux,device is NULL or
non-existant will give us use-once too.

> +               printk(KERN_ERR "%s: failed to register device %s\n",
> +                      __FUNCTION__, ebusdev->ofdev.dev.bus_id);
> +               return NULL;
> +       }
> +
> +       device_create_file(&ebusdev->ofdev.dev, &dev_attr_name);
> +
> +       return ebusdev;
> +}
> +
> +struct ebus_dev* __devinit ebus_register_device_node(struct 
> device_node *dn)
> +{
> +       struct ebus_dev *ebusdev;
> +       char *loc_code;
> +       int length;
> +
> +       loc_code = (char *)get_property(dn, "ibm,loc-code", NULL);
> +       if (!loc_code) {
> +                printk(KERN_WARNING "%s: node %s missing 
> 'ibm,loc-code'\n",
> +                      __FUNCTION__, dn->name ? dn->name : 
> "<unknown>");
> +               return NULL;
> +        }
> +
> +       if (strlen(loc_code) == 0) {
> +               printk(KERN_WARNING "%s: 'ibm,loc-code' is invalid\n",
> +                      __FUNCTION__);
> +               return NULL;
> +       }
> +
> +       ebusdev = kmalloc(sizeof(struct ebus_dev), GFP_KERNEL);
> +       if (!ebusdev) {
> +               return NULL;
> +       }
> +       memset(ebusdev, 0, sizeof(struct ebus_dev));
> +
> +       ebusdev->ofdev.node = of_node_get(dn);
> +
> +       length = strlen(loc_code);
> +       strncpy(ebusdev->ofdev.dev.bus_id, loc_code
> +               + (strlen(loc_code) - min(length, BUS_ID_SIZE)), 
> BUS_ID_SIZE);

length - min(length, BUS_ID_SIZE)
Hmm.. wonder if there are bugs if we actually use BUS_ID_SIZE 
characters?  Maybe that min should be BUS_ID_SIZE-1

> +
> +       /* register with generic device framework */
> +       if (ebus_register_device_common(ebusdev, dn->name) == NULL) {
>
leaking of_node reference

> +               kfree(ebusdev);
> +               return NULL;
> +       }
> +
> +       return ebusdev;
> +}
> +
> +static void probe_bus(char* name)
> +{
> +       struct device_node *dn = NULL;
> +
> +       while ((dn = of_find_node_by_name(dn, name))) {
> +               ebus_register_device_node(dn);
> +       }
> +
> +       of_node_put(dn);
> +}
> +
> +static int ebus_unregister_device(struct device *dev)
> +{
> +       device_remove_file(dev, &dev_attr_name);
> +       of_device_unregister(to_of_device(dev));
> +
> +       return 0;
> +}
> +
> +static int ebus_match_helper(struct device *dev, void *data)
> +{
> +       if (strcmp((char*)data, to_ebus_dev(dev)->name) == 0)
> +               return 1;

So we add and remove on strncmp name instead of of_match_device
like we bind the driver?

> +
> +       return 0;
> +}
> +
> +int ebus_register_driver(struct ebus_driver *ebusdrv)
> +{
> +       struct of_device_id *idt;
> +       struct device *dev;
> +
> +       ebusdrv->driver.name   = ebusdrv->name;
> +       ebusdrv->driver.bus    = &ebus_bus_type;
> +       ebusdrv->driver.probe  = ebus_bus_probe;
> +       ebusdrv->driver.remove = ebus_bus_remove;
> +
> +       /* check if a driver for that device name is already loaded */
> +       idt = ebusdrv->id_table;
> +       while (strlen(idt->name) > 0) {
> +               dev = bus_find_device(&ebus_bus_type, NULL, 
> (void*)idt->name,
> +                                     ebus_match_helper);
> +               if (dev) {
> +                       printk(KERN_ERR
> +                              "%s: driver for device name %s already 
> loaded\n",
> +                              __FUNCTION__, idt->name);
> +                       return -EPERM;
> +               }
> +               idt++;
> +       }
> +
> +       idt = ebusdrv->id_table;
> +       while (strlen(idt->name) > 0) {
> +               probe_bus(idt->name);
> +               idt++;
> +       }

Lets seperate out this magic device discovery into a seperate function.
It is a logically seperate concept.

We really shouldn't need to verify the bus ids are unique, because
the device registration will fail if the device shows up twice
(kobject registration conflict).  However, unloding either driver
would remove the device from the matched driver...

> +
> +       return driver_register(&ebusdrv->driver);
> +}
> +EXPORT_SYMBOL(ebus_register_driver);
> +
> +void ebus_unregister_driver(struct ebus_driver *ebusdrv)
> +{
> +       struct of_device_id *idt;
> +       struct device *dev;
> +
> +       driver_unregister(&ebusdrv->driver);
> +
> +       idt = ebusdrv->id_table;
> +       while (strlen(idt->name) > 0) {
> +               while ((dev = bus_find_device(&ebus_bus_type, NULL,
> +                                            (void*)idt->name,
> +                                            ebus_match_helper))) {
> +                       ebus_unregister_device(dev);
> +               }
> +               idt++;
> +
> +       }

Again, device removal hidden in driver unregister ... seperate function
please.

Oh, and an extra blank line.

> +}
> +EXPORT_SYMBOL(ebus_unregister_driver);
> +
> +int ebus_request_irq(u32 ist,
> +                    irqreturn_t (*handler)(int, void*, struct pt_regs 
> *),
> +                    unsigned long irq_flags, const char * devname,
> +                    void *dev_id)
> +{
> +       unsigned int irq = virt_irq_create_mapping(ist);
> +
> +       if (irq == NO_IRQ)
> +               return -EINVAL;
> +
> +       irq = irq_offset_up(irq);
> +
> +       return request_irq(irq, handler,
> +                          irq_flags, devname, dev_id);

should we not at least pass in the ebus device, even if we don't
check the irq requested against it today?

> +}
> +EXPORT_SYMBOL(ebus_request_irq);
> +
> +void ebus_free_irq(u32 ist, void *dev_id)
> +{
> +       unsigned int irq = virt_irq_create_mapping(ist);

free_irq calls create_mapping ???
And doesn't check for NO_IRQ?

how about we store the "linux" irq in ebusdevice->irq?

> +
> +       irq = irq_offset_up(irq);
> +       free_irq(irq, dev_id);
> +
> +       return;
> +}
> +EXPORT_SYMBOL(ebus_free_irq);
> +
> +static int ebus_bus_match(struct device *dev, struct device_driver 
> *drv)
> +{
> +       const struct ebus_dev *ebus_dev = to_ebus_dev(dev);
> +       struct ebus_driver *ebus_drv    = to_ebus_driver(drv);
> +       const struct of_device_id *ids  = ebus_drv->id_table;
> +       const struct of_device_id *found_id;
> +
> +       if (!ids)
> +               return 0;
> +
> +       found_id = of_match_device(ids, &ebus_dev->ofdev);
> +       if (found_id)
> +               return 1;
> +
> +       return 0;
> +}
> +
> +struct bus_type ebus_bus_type = {
> +       .name = "ebus",
> +       .match = ebus_bus_match,
> +};
> +EXPORT_SYMBOL(ebus_bus_type);
> +
> +static int __init ebus_bus_init(void)
> +{
> +       int err;
> +
> +       printk(KERN_INFO "eBus Device Driver\n");
> +
> +       err = bus_register(&ebus_bus_type);
> +       if (err) {
> +               printk(KERN_ERR "failed to register eBus\n");
> +               return err;
> +       }
> +
> +       err = device_register(&ebus_bus_device.ofdev.dev);
> +       if (err) {
> +               printk(KERN_WARNING "%s: device_register returned 
> %i\n",
> +                      __FUNCTION__, err);
> +               return err;
> +       }

If the device register fails we need to unregister the bus.

> +
> +       return 0;
> +}
> +__initcall(ebus_bus_init);
> Index: linux-work/include/asm-powerpc/ebus.h
> ===================================================================
> --- /dev/null   1970-01-01 00:00:00.000000000 +0000
> +++ linux-work/include/asm-powerpc/ebus.h       2005-11-14 
> 19:08:14.000000000 +1100
> @@ -0,0 +1,87 @@
> +/*
> + * IBM PowerPC eBus Infrastructure Support.
> + *
> + * Copyright (c) 2005 IBM Corporation
> + *  Heiko J Schick <schickhj at de.ibm.com>
> + *
> + * All rights reserved.
> + *
> + * This source code is distributed under a dual license of GPL v2.0 
> and OpenIB
> + * BSD.
> + *
> + * OpenIB BSD License
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions 
> are met:
> + *
> + * Redistributions of source code must retain the above copyright 
> notice, this
> + * list of conditions and the following disclaimer.
> + *
> + * Redistributions in binary form must reproduce the above copyright 
> notice,
> + * this list of conditions and the following disclaimer in the 
> documentation
> + * and/or other materials
> + * provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
> CONTRIBUTORS "AS IS"
> + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
> TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
> PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
> CONTRIBUTORS BE
> + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
> OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
> + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
> LIABILITY, WHETHER
> + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
> OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
> ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#ifndef _ASM_EBUS_H
> +#define _ASM_EBUS_H
> +
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/mod_devicetable.h>
> +#include <asm/of_device.h>
> +
> +extern struct dma_mapping_ops ebus_dma_ops;
> +extern struct bus_type ebus_bus_type;
> +
> +struct ebus_dev {
> +       char *name;
> +       u64 unit_address;
> +       struct of_device ofdev;
> +};

unit_address isn't used anywhere ...

> +
> +struct ebus_driver {
> +       struct list_head node;
> +       char *name;
> +       struct of_device_id *id_table;
> +       int (*probe) (struct ebus_dev *dev, const struct of_device_id 
> *id);
> +       int (*remove) (struct ebus_dev *dev);
> +       unsigned long driver_data;

Usually we would use the generic device's driver_data field.
Oh, and add ebus_set_driver_data / ebus_get_driver_data

> +
> +       struct device_driver driver;
> +};
> +
> +int ebus_register_driver(struct ebus_driver *ebusdrv);
> +void ebus_unregister_driver(struct ebus_driver *ebusdrv);
> +
> +int ebus_request_irq(u32 ist,
> +                    irqreturn_t (*handler)(int, void*, struct pt_regs 
> *),
> +                    unsigned long irq_flags, const char * devname,
> +                    void *dev_id);
> +
> +void ebus_free_irq(u32 ist, void *dev_id);
> +
> +static inline struct ebus_driver *to_ebus_driver(struct device_driver 
> *drv)
> +{
> +       return container_of(drv, struct ebus_driver, driver);
> +}
> +
> +static inline struct ebus_dev *to_ebus_dev(struct device *dev)
> +{
> +       return container_of(dev, struct ebus_dev, ofdev.dev);
> +}
> +
> +
> +#endif /* _ASM_EBUS_H */
> Index: linux-work/arch/powerpc/Kconfig
> ===================================================================
> --- linux-work.orig/arch/powerpc/Kconfig        2005-11-14 
> 19:08:12.000000000 +1100
> +++ linux-work/arch/powerpc/Kconfig     2005-11-14 19:08:14.000000000 
> +1100
> @@ -384,6 +384,13 @@
>         bool
>         default y
>
> +config IBMEBUS
> +       depends on PPC_PSERIES
> +       bool "Support for GX bus based adapters"
> +       default y
> +       help
> +         Bus device driver for GX bus based adapters.
> +
>  config PPC_MPC106
>         bool
>         default n
> Index: linux-work/arch/powerpc/platforms/pseries/Makefile
> ===================================================================
> --- linux-work.orig/arch/powerpc/platforms/pseries/Makefile     
> 2005-11-14 19:08:12.000000000 +1100
> +++ linux-work/arch/powerpc/platforms/pseries/Makefile  2005-11-14 
> 19:08:14.000000000 +1100
> @@ -4,4 +4,5 @@
>  obj-$(CONFIG_IBMVIO)   += vio.o
>  obj-$(CONFIG_XICS)     += xics.o
>  obj-$(CONFIG_SCANLOG)  += scanlog.o
> -obj-$(CONFIG_EEH)    += eeh.o eeh_event.o
> +obj-$(CONFIG_EEH)      += eeh.o eeh_event.o
> +obj-$(CONFIG_IBMEBUS)  += ebus.o




More information about the Linuxppc64-dev mailing list