[PATCH 8/8] staging: comedi: skel: do auto-attachment of PCI devices

Ian Abbott abbotti at mev.co.uk
Thu Nov 1 14:02:20 UTC 2012


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 at mev.co.uk>
---
 drivers/staging/comedi/drivers/skel.c | 222 +++++++++++++++++++++++++++++-----
 1 file changed, 195 insertions(+), 27 deletions(-)

diff --git a/drivers/staging/comedi/drivers/skel.c b/drivers/staging/comedi/drivers/skel.c
index ccc1e9b..162bf26 100644
--- a/drivers/staging/comedi/drivers/skel.c
+++ b/drivers/staging/comedi/drivers/skel.c
@@ -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)
@@ -505,6 +498,146 @@ static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 }
 
 /*
+ * _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.
  * This function is also called when _attach() fails, so it should be
@@ -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 */
-- 
1.7.12.4




More information about the devel mailing list