diff options
Diffstat (limited to 'drivers/staging/comedi/drivers/comedi_bond.c')
-rw-r--r-- | drivers/staging/comedi/drivers/comedi_bond.c | 367 |
1 files changed, 176 insertions, 191 deletions
diff --git a/drivers/staging/comedi/drivers/comedi_bond.c b/drivers/staging/comedi/drivers/comedi_bond.c index 1a51866be6f7..51a59e5b8ec5 100644 --- a/drivers/staging/comedi/drivers/comedi_bond.c +++ b/drivers/staging/comedi/drivers/comedi_bond.c @@ -1,130 +1,131 @@ /* - comedi/drivers/comedi_bond.c - A Comedi driver to 'bond' or merge multiple drivers and devices as one. - - COMEDI - Linux Control and Measurement Device Interface - Copyright (C) 2000 David A. Schleef <ds@schleef.org> - Copyright (C) 2005 Calin A. Culianu <calin@ajvar.org> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. -*/ -/* -Driver: comedi_bond -Description: A driver to 'bond' (merge) multiple subdevices from multiple - devices together as one. -Devices: -Author: ds -Updated: Mon, 10 Oct 00:18:25 -0500 -Status: works - -This driver allows you to 'bond' (merge) multiple comedi subdevices -(coming from possibly difference boards and/or drivers) together. For -example, if you had a board with 2 different DIO subdevices, and -another with 1 DIO subdevice, you could 'bond' them with this driver -so that they look like one big fat DIO subdevice. This makes writing -applications slightly easier as you don't have to worry about managing -different subdevices in the application -- you just worry about -indexing one linear array of channel id's. - -Right now only DIO subdevices are supported as that's the personal itch -I am scratching with this driver. If you want to add support for AI and AO -subdevs, go right on ahead and do so! - -Commands aren't supported -- although it would be cool if they were. - -Configuration Options: - List of comedi-minors to bond. All subdevices of the same type - within each minor will be concatenated together in the order given here. -*/ + * comedi_bond.c + * A Comedi driver to 'bond' or merge multiple drivers and devices as one. + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <ds@schleef.org> + * Copyright (C) 2005 Calin A. Culianu <calin@ajvar.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Driver: comedi_bond + * Description: A driver to 'bond' (merge) multiple subdevices from multiple + * devices together as one. + * Devices: + * Author: ds + * Updated: Mon, 10 Oct 00:18:25 -0500 + * Status: works + * + * This driver allows you to 'bond' (merge) multiple comedi subdevices + * (coming from possibly difference boards and/or drivers) together. For + * example, if you had a board with 2 different DIO subdevices, and + * another with 1 DIO subdevice, you could 'bond' them with this driver + * so that they look like one big fat DIO subdevice. This makes writing + * applications slightly easier as you don't have to worry about managing + * different subdevices in the application -- you just worry about + * indexing one linear array of channel id's. + * + * Right now only DIO subdevices are supported as that's the personal itch + * I am scratching with this driver. If you want to add support for AI and AO + * subdevs, go right on ahead and do so! + * + * Commands aren't supported -- although it would be cool if they were. + * + * Configuration Options: + * List of comedi-minors to bond. All subdevices of the same type + * within each minor will be concatenated together in the order given here. + */ + +#include <linux/module.h> #include <linux/string.h> #include <linux/slab.h> #include "../comedi.h" #include "../comedilib.h" #include "../comedidev.h" -/* The maxiumum number of channels per subdevice. */ -#define MAX_CHANS 256 - -struct BondedDevice { +struct bonded_device { struct comedi_device *dev; unsigned minor; unsigned subdev; - unsigned subdev_type; unsigned nchans; - unsigned chanid_offset; /* The offset into our unified linear - channel-id's of chanid 0 on this - subdevice. */ }; -/* this structure is for data unique to this hardware driver. If - several hardware drivers keep similar information in this structure, - feel free to suggest moving the variable to the struct comedi_device struct. */ struct comedi_bond_private { # define MAX_BOARD_NAME 256 char name[MAX_BOARD_NAME]; - struct BondedDevice **devs; + struct bonded_device **devs; unsigned ndevs; - struct BondedDevice *chanIdDevMap[MAX_CHANS]; unsigned nchans; }; -/* DIO devices are slightly special. Although it is possible to - * implement the insn_read/insn_write interface, it is much more - * useful to applications if you implement the insn_bits interface. - * This allows packed reading/writing of the DIO channels. The - * comedi core can convert between insn_bits and insn_read/write */ static int bonding_dio_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct comedi_bond_private *devpriv = dev->private; -#define LSAMPL_BITS (sizeof(unsigned int)*8) - unsigned nchans = LSAMPL_BITS, num_done = 0, i; - - if (devpriv->nchans < nchans) - nchans = devpriv->nchans; - - /* The insn data is a mask in data[0] and the new data - * in data[1], each channel cooresponding to a bit. */ - for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) { - struct BondedDevice *bdev = devpriv->devs[i]; - /* Grab the channel mask and data of only the bits corresponding - to this subdevice.. need to shift them to zero position of - course. */ - /* Bits corresponding to this subdev. */ - unsigned int subdevMask = ((1 << bdev->nchans) - 1); - unsigned int writeMask, dataBits; - - /* Argh, we have >= LSAMPL_BITS chans.. take all bits */ - if (bdev->nchans >= LSAMPL_BITS) - subdevMask = (unsigned int)(-1); - - writeMask = (data[0] >> num_done) & subdevMask; - dataBits = (data[1] >> num_done) & subdevMask; - - /* Read/Write the new digital lines */ - if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask, - &dataBits) != 2) - return -EINVAL; - - /* Make room for the new bits in data[1], the return value */ - data[1] &= ~(subdevMask << num_done); - /* Put the bits in the return value */ - data[1] |= (dataBits & subdevMask) << num_done; - /* Save the new bits to the saved state.. */ - s->state = data[1]; - - num_done += bdev->nchans; - } + unsigned int n_left, n_done, base_chan; + unsigned int write_mask, data_bits; + struct bonded_device **devs; + + write_mask = data[0]; + data_bits = data[1]; + base_chan = CR_CHAN(insn->chanspec); + /* do a maximum of 32 channels, starting from base_chan. */ + n_left = devpriv->nchans - base_chan; + if (n_left > 32) + n_left = 32; + + n_done = 0; + devs = devpriv->devs; + do { + struct bonded_device *bdev = *devs++; + + if (base_chan < bdev->nchans) { + /* base channel falls within bonded device */ + unsigned int b_chans, b_mask, b_write_mask, b_data_bits; + int ret; + + /* + * Get num channels to do for bonded device and set + * up mask and data bits for bonded device. + */ + b_chans = bdev->nchans - base_chan; + if (b_chans > n_left) + b_chans = n_left; + b_mask = (1U << b_chans) - 1; + b_write_mask = (write_mask >> n_done) & b_mask; + b_data_bits = (data_bits >> n_done) & b_mask; + /* Read/Write the new digital lines. */ + ret = comedi_dio_bitfield2(bdev->dev, bdev->subdev, + b_write_mask, &b_data_bits, + base_chan); + if (ret < 0) + return ret; + /* Place read bits into data[1]. */ + data[1] &= ~(b_mask << n_done); + data[1] |= (b_data_bits & b_mask) << n_done; + /* + * Set up for following bonded device (if still have + * channels to read/write). + */ + base_chan = 0; + n_done += b_chans; + n_left -= b_chans; + } else { + /* Skip bonded devices before base channel. */ + base_chan -= bdev->nchans; + } + } while (n_left); return insn->n; } @@ -134,99 +135,91 @@ static int bonding_dio_insn_config(struct comedi_device *dev, struct comedi_insn *insn, unsigned int *data) { struct comedi_bond_private *devpriv = dev->private; - int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits; - unsigned int io; - struct BondedDevice *bdev; + unsigned int chan = CR_CHAN(insn->chanspec); + int ret; + struct bonded_device *bdev; + struct bonded_device **devs; - if (chan < 0 || chan >= devpriv->nchans) - return -EINVAL; - bdev = devpriv->chanIdDevMap[chan]; + /* + * Locate bonded subdevice and adjust channel. + */ + devs = devpriv->devs; + for (bdev = *devs++; chan >= bdev->nchans; bdev = *devs++) + chan -= bdev->nchans; - /* The input or output configuration of each digital line is + /* + * The input or output configuration of each digital line is * configured by a special insn_config instruction. chanspec * contains the channel to be changed, and data[0] contains the - * value COMEDI_INPUT or COMEDI_OUTPUT. */ + * configuration instruction INSN_CONFIG_DIO_OUTPUT, + * INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_QUERY. + * + * Note that INSN_CONFIG_DIO_OUTPUT == COMEDI_OUTPUT, + * and INSN_CONFIG_DIO_INPUT == COMEDI_INPUT. This is deliberate ;) + */ switch (data[0]) { case INSN_CONFIG_DIO_OUTPUT: - io = COMEDI_OUTPUT; /* is this really necessary? */ - io_bits |= 1 << chan; - break; case INSN_CONFIG_DIO_INPUT: - io = COMEDI_INPUT; /* is this really necessary? */ - io_bits &= ~(1 << chan); + ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, data[0]); break; case INSN_CONFIG_DIO_QUERY: - data[1] = - (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; - return insn->n; + ret = comedi_dio_get_config(bdev->dev, bdev->subdev, chan, + &data[1]); break; default: - return -EINVAL; + ret = -EINVAL; break; } - /* 'real' channel id for this subdev.. */ - chan -= bdev->chanid_offset; - ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io); - if (ret != 1) - return -EINVAL; - /* Finally, save the new io_bits values since we didn't get - an error above. */ - s->io_bits = io_bits; - return insn->n; + if (ret >= 0) + ret = insn->n; + return ret; } -static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen) -{ - void *newmem = kmalloc(newlen, GFP_KERNEL); - - if (newmem && oldmem) - memcpy(newmem, oldmem, min(oldlen, newlen)); - kfree(oldmem); - return newmem; -} - -static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it) +static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it) { struct comedi_bond_private *devpriv = dev->private; + DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS); int i; - struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS]; - memset(devs_opened, 0, sizeof(devs_opened)); + memset(&devs_opened, 0, sizeof(devs_opened)); devpriv->name[0] = 0; - /* Loop through all comedi devices specified on the command-line, - building our device list */ + /* + * Loop through all comedi devices specified on the command-line, + * building our device list. + */ for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) { - char file[] = "/dev/comediXXXXXX"; + char file[sizeof("/dev/comediXXXXXX")]; int minor = it->options[i]; struct comedi_device *d; - int sdev = -1, nchans, tmp; - struct BondedDevice *bdev = NULL; + int sdev = -1, nchans; + struct bonded_device *bdev; + struct bonded_device **devs; if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) { dev_err(dev->class_dev, "Minor %d is invalid!\n", minor); - return 0; + return -EINVAL; } if (minor == dev->minor) { dev_err(dev->class_dev, "Cannot bond this driver to itself!\n"); - return 0; + return -EINVAL; } - if (devs_opened[minor]) { + if (test_and_set_bit(minor, devs_opened)) { dev_err(dev->class_dev, "Minor %d specified more than once!\n", minor); - return 0; + return -EINVAL; } snprintf(file, sizeof(file), "/dev/comedi%u", minor); file[sizeof(file) - 1] = 0; - d = devs_opened[minor] = comedi_open(file); + d = comedi_open(file); if (!d) { dev_err(dev->class_dev, "Minor %u could not be opened\n", minor); - return 0; + return -ENODEV; } /* Do DIO, as that's all we support now.. */ @@ -237,45 +230,41 @@ static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it) dev_err(dev->class_dev, "comedi_get_n_channels() returned %d on minor %u subdev %d!\n", nchans, minor, sdev); - return 0; + return -EINVAL; } bdev = kmalloc(sizeof(*bdev), GFP_KERNEL); if (!bdev) - return 0; + return -ENOMEM; bdev->dev = d; bdev->minor = minor; bdev->subdev = sdev; - bdev->subdev_type = COMEDI_SUBD_DIO; bdev->nchans = nchans; - bdev->chanid_offset = devpriv->nchans; + devpriv->nchans += nchans; - /* map channel id's to BondedDevice * pointer.. */ - while (nchans--) - devpriv->chanIdDevMap[devpriv->nchans++] = bdev; - - /* Now put bdev pointer at end of devpriv->devs array - * list.. */ + /* + * Now put bdev pointer at end of devpriv->devs array + * list.. + */ /* ergh.. ugly.. we need to realloc :( */ - tmp = devpriv->ndevs * sizeof(bdev); - devpriv->devs = - Realloc(devpriv->devs, - ++devpriv->ndevs * sizeof(bdev), tmp); - if (!devpriv->devs) { + devs = krealloc(devpriv->devs, + (devpriv->ndevs + 1) * sizeof(*devs), + GFP_KERNEL); + if (!devs) { dev_err(dev->class_dev, "Could not allocate memory. Out of memory?\n"); - return 0; + return -ENOMEM; } - - devpriv->devs[devpriv->ndevs - 1] = bdev; + devpriv->devs = devs; + devpriv->devs[devpriv->ndevs++] = bdev; { - /** Append dev:subdev to devpriv->name */ + /* Append dev:subdev to devpriv->name */ char buf[20]; int left = MAX_BOARD_NAME - strlen(devpriv->name) - 1; - snprintf(buf, sizeof(buf), "%d:%d ", dev->minor, - bdev->subdev); + snprintf(buf, sizeof(buf), "%d:%d ", + bdev->minor, bdev->subdev); buf[sizeof(buf) - 1] = 0; strncat(devpriv->name, buf, left); } @@ -285,10 +274,10 @@ static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it) if (!devpriv->nchans) { dev_err(dev->class_dev, "No channels found!\n"); - return 0; + return -EINVAL; } - return 1; + return 0; } static int bonding_attach(struct comedi_device *dev, @@ -298,16 +287,16 @@ static int bonding_attach(struct comedi_device *dev, struct comedi_subdevice *s; int ret; - devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); if (!devpriv) return -ENOMEM; - dev->private = devpriv; /* * Setup our bonding from config params.. sets up our private struct.. */ - if (!doDevConfig(dev, it)) - return -EINVAL; + ret = do_dev_config(dev, it); + if (ret) + return ret; dev->board_name = devpriv->name; @@ -329,31 +318,29 @@ static int bonding_attach(struct comedi_device *dev, dev->driver->driver_name, dev->board_name, devpriv->nchans, devpriv->ndevs); - return 1; + return 0; } static void bonding_detach(struct comedi_device *dev) { struct comedi_bond_private *devpriv = dev->private; - unsigned long devs_closed = 0; - if (devpriv) { - while (devpriv->ndevs-- && devpriv->devs) { - struct BondedDevice *bdev; + if (devpriv && devpriv->devs) { + DECLARE_BITMAP(devs_closed, COMEDI_NUM_BOARD_MINORS); + + memset(&devs_closed, 0, sizeof(devs_closed)); + while (devpriv->ndevs--) { + struct bonded_device *bdev; bdev = devpriv->devs[devpriv->ndevs]; if (!bdev) continue; - if (!(devs_closed & (0x1 << bdev->minor))) { + if (!test_and_set_bit(bdev->minor, devs_closed)) comedi_close(bdev->dev); - devs_closed |= (0x1 << bdev->minor); - } kfree(bdev); } kfree(devpriv->devs); devpriv->devs = NULL; - kfree(devpriv); - dev->private = NULL; } } @@ -366,7 +353,5 @@ static struct comedi_driver bonding_driver = { module_comedi_driver(bonding_driver); MODULE_AUTHOR("Calin A. Culianu"); -MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI " - "devices together as one. In the words of John Lennon: " - "'And the world will live as one...'"); +MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one."); MODULE_LICENSE("GPL"); |