staging: comedi: skel: do auto-attachment of PCI devices
authorIan Abbott <abbotti@mev.co.uk>
Thu, 1 Nov 2012 14:02:20 +0000 (14:02 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 1 Nov 2012 15:46:02 +0000 (08:46 -0700)
This driver is an example of how to write a Comedi driver and includes
some code for handling PCI devices, but not very much.  It calls
`comedi_pci_auto_config()` at PCI probe time.  That normally expects to
call the comedi driver's `auto_attach()` hook (or the deprecated
`attach_pci()` hook), but will fall back to using the `attach()` hook
that should only be used for attaching devices "manually" via the
`COMEDI_DEVCONFIG` ioctl.

Add an `auto_attach()` hook (`skel_auto_attach()`) to handle
auto-attachment of supported PCI devices.  Add comments to the
`attach()` hook (`skel_attach()`) to indicate that it shouldn't
generally allow PCI devices to be attached that way (and probably isn't
needed at all for PCI-only Comedi drivers).  Add code to the `detach()`
hook (`skel_detach()`) to disable PCI devices enabled by the
`auto_attach()` hook.

`skel_auto_attach()` calls new function `skel_find_pci_board()` to find
a matching element in `skel_boards[]`.  PCI device ID information has
been added to `skel_boards[]` to give the function something to look
for.

Remove the `pci_dev` member of `struct skel_private` as drivers now use
the `hw_dev` member of `struct comedi_device` to get at the PCI device.

Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/comedi/drivers/skel.c

index ccc1e9b..162bf26 100644 (file)
@@ -92,6 +92,7 @@ Configuration Options:
  */
 struct skel_board {
        const char *name;
+       unsigned int devid;
        int ai_chans;
        int ai_bits;
        int have_dio;
@@ -100,12 +101,14 @@ struct skel_board {
 static const struct skel_board skel_boards[] = {
        {
         .name = "skel-100",
+        .devid = 0x100,
         .ai_chans = 16,
         .ai_bits = 12,
         .have_dio = 1,
         },
        {
         .name = "skel-200",
+        .devid = 0x200,
         .ai_chans = 8,
         .ai_bits = 16,
         .have_dio = 0,
@@ -120,9 +123,6 @@ struct skel_private {
 
        int data;
 
-       /* would be useful for a PCI device */
-       struct pci_dev *pci_dev;
-
        /* Used for AO readback */
        unsigned int ao_readback[2];
 };
@@ -421,37 +421,30 @@ static int skel_dio_insn_config(struct comedi_device *dev,
        return insn->n;
 }
 
-/*
- * Attach is called by the Comedi core to configure the driver
- * for a particular board.  If you specified a board_name array
- * in the driver structure, dev->board_ptr contains that
- * address.
- */
-static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+static const struct skel_board *skel_find_pci_board(struct pci_dev *pcidev)
 {
-       const struct skel_board *thisboard;
-       struct skel_private *devpriv;
-       struct comedi_subdevice *s;
-       int ret;
+       unsigned int i;
 
 /*
- * If you can probe the device to determine what device in a series
- * it is, this is the place to do it.  Otherwise, dev->board_ptr
- * should already be initialized.
+ * This example code assumes all the entries in skel_boards[] are PCI boards
+ * and all use the same PCI vendor ID.  If skel_boards[] contains a mixture
+ * of PCI and non-PCI boards, this loop should skip over the non-PCI boards.
  */
-       /* dev->board_ptr = skel_probe(dev, it); */
+       for (i = 0; i < ARRAY_SIZE(skel_boards); i++)
+               if (/* skel_boards[i].bustype == pci_bustype && */
+                   pcidev->device == skel_boards[i].devid)
+                       return &skel_boards[i];
+       return NULL;
+}
 
-       thisboard = comedi_board(dev);
 /*
- * Initialize dev->board_name.
+ * Handle common part of skel_attach() and skel_auto_attach().
  */
-       dev->board_name = thisboard->name;
-
-       /* Allocate the private data */
-       devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
-       if (!devpriv)
-               return -ENOMEM;
-       dev->private = devpriv;
+static int skel_common_attach(struct comedi_device *dev)
+{
+       const struct skel_board *thisboard = comedi_board(dev);
+       struct comedi_subdevice *s;
+       int ret;
 
        ret = comedi_alloc_subdevices(dev, 3);
        if (ret)
@@ -504,6 +497,146 @@ static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it)
        return 0;
 }
 
+/*
+ * _attach is called by the Comedi core to configure the driver
+ * for a particular board in response to the COMEDI_DEVCONFIG ioctl for
+ * a matching board or driver name.  If you specified a board_name array
+ * in the driver structure, dev->board_ptr contains that address.
+ *
+ * Drivers that handle only PCI or USB devices do not usually support
+ * manual attachment of those devices via the COMEDI_DEVCONFIG ioctl, so
+ * those drivers do not have an _attach function; they just have an
+ * _auto_attach function instead.  (See skel_auto_attach() for an example
+ * of such a function.)
+ */
+static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+       const struct skel_board *thisboard;
+       struct skel_private *devpriv;
+
+/*
+ * If you can probe the device to determine what device in a series
+ * it is, this is the place to do it.  Otherwise, dev->board_ptr
+ * should already be initialized.
+ */
+       /* dev->board_ptr = skel_probe(dev, it); */
+
+       thisboard = comedi_board(dev);
+
+/*
+ * Initialize dev->board_name.
+ */
+       dev->board_name = thisboard->name;
+
+       /* Allocate the private data */
+       devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
+       if (!devpriv)
+               return -ENOMEM;
+       dev->private = devpriv;
+
+/*
+ * Supported boards are usually either auto-attached via the
+ * Comedi driver's _auto_attach routine, or manually attached via the
+ * Comedi driver's _attach routine.  In most cases, attempts to
+ * manual attach boards that are usually auto-attached should be
+ * rejected by this function.
+ */
+/*
+ *     if (thisboard->bustype == pci_bustype) {
+ *             dev_err(dev->class_dev,
+ *                     "Manual attachment of PCI board '%s' not supported\n",
+ *                     thisboard->name);
+ *     }
+ */
+
+/*
+ * For ISA boards, get the i/o base address from it->options[],
+ * request the i/o region and set dev->iobase * from it->options[].
+ * If using interrupts, get the IRQ number from it->options[].
+ */
+
+       /*
+        * Call a common function to handle the remaining things to do for
+        * attaching ISA or PCI boards.  (Extra parameters could be added
+        * to pass additional information such as IRQ number.)
+        */
+       return skel_common_attach(dev);
+}
+
+/*
+ * _auto_attach is called via comedi_pci_auto_config() (or
+ * comedi_usb_auto_config(), etc.) to handle devices that can be attached
+ * to the Comedi core automatically without the COMEDI_DEVCONFIG ioctl.
+ *
+ * For PCI devices, comedi_pci_auto_config() is usually called directly from
+ * the struct pci_driver probe() function, so this _auto_attach() function
+ * can be tagged __devinit.
+ *
+ * The context parameter is usually unused, but if the driver called
+ * comedi_auto_config() directly instead of the comedi_pci_auto_config()
+ * wrapper function, this will be a copy of the context passed to
+ * comedi_auto_config().
+ */
+static int __devinit skel_auto_attach(struct comedi_device *dev,
+                                     unsigned long context)
+{
+       struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+       const struct skel_board *thisboard;
+       struct skel_private *devpriv;
+       int ret;
+
+       /* Hack to allow unused code to be optimized out. */
+       if (!IS_ENABLED(CONFIG_COMEDI_PCI_DRIVERS))
+               return -EINVAL;
+
+       /* Find a matching board in skel_boards[]. */
+       thisboard = skel_find_pci_board(pcidev);
+       if (!thisboard) {
+               dev_err(dev->class_dev, "BUG! cannot determine board type!\n");
+               return -EINVAL;
+       }
+
+       /*
+        * Point the struct comedi_device to the matching board info
+        * and set the board name.
+        */
+       dev->board_ptr = thisboard;
+       dev->board_name = thisboard->name;
+
+       /* Allocate the private data */
+       devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
+       if (!devpriv)
+               return -ENOMEM;
+       dev->private = devpriv;
+
+       /* Enable the PCI device. */
+       ret = comedi_pci_enable(pcidev, dev->board_name);
+       if (ret)
+               return ret;
+
+       /*
+        * Record the fact that the PCI device is enabled so that it can
+        * be disabled during _detach().
+        *
+        * For this example driver, we assume PCI BAR 0 is the main I/O
+        * region for the board registers and use dev->iobase to hold the
+        * I/O base address and to indicate that the PCI device has been
+        * enabled.
+        *
+        * (For boards with memory-mapped registers, dev->iobase is not
+        * usually needed for register access, so can just be set to 1
+        * to indicate that the PCI device has been enabled.)
+        */
+       dev->iobase = pci_resource_start(pcidev, 0);
+
+       /*
+        * Call a common function to handle the remaining things to do for
+        * attaching ISA or PCI boards.  (Extra parameters could be added
+        * to pass additional information such as IRQ number.)
+        */
+       return skel_common_attach(dev);
+}
+
 /*
  * _detach is called to deconfigure a device.  It should deallocate
  * resources.
@@ -514,6 +647,40 @@ static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it)
  */
 static void skel_detach(struct comedi_device *dev)
 {
+       const struct skel_board *thisboard = comedi_board(dev);
+       struct skel_private *devpriv = dev->private;
+       struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+
+       if (!thisboard || !devpriv)
+               return;
+
+/*
+ * Do common stuff such as freeing IRQ, unmapping remapped memory
+ * regions, etc., being careful to check that the stuff is valid given
+ * that _detach() is called even when _attach() or _auto_attach() return
+ * an error.
+ */
+
+       if (IS_ENABLED(CONFIG_COMEDI_PCI_DRIVERS) /* &&
+           thisboard->bustype == pci_bustype */) {
+               /*
+                * PCI board
+                *
+                * If PCI device enabled by _auto_attach() (or _attach()),
+                * disable it here.
+                */
+               if (pcidev && dev->iobase)
+                       comedi_pci_disable(pcidev);
+       } else {
+               /*
+                * ISA board
+                *
+                * If I/O regions successfully requested by _attach(),
+                * release them here.
+                */
+               if (dev->iobase)
+                       release_region(dev->iobase, SKEL_SIZE);
+       }
 }
 
 /*
@@ -526,6 +693,7 @@ static struct comedi_driver skel_driver = {
        .driver_name = "dummy",
        .module = THIS_MODULE,
        .attach = skel_attach,
+       .auto_attach = skel_auto_attach,
        .detach = skel_detach,
 /* It is not necessary to implement the following members if you are
  * writing a driver for a ISA PnP or PCI card */