linux/drivers/staging/comedi/drivers/comedi_bond.c
<<
>>
Prefs
   1/*
   2    comedi/drivers/comedi_bond.c
   3    A Comedi driver to 'bond' or merge multiple drivers and devices as one.
   4
   5    COMEDI - Linux Control and Measurement Device Interface
   6    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
   7    Copyright (C) 2005 Calin A. Culianu <calin@ajvar.org>
   8
   9    This program is free software; you can redistribute it and/or modify
  10    it under the terms of the GNU General Public License as published by
  11    the Free Software Foundation; either version 2 of the License, or
  12    (at your option) any later version.
  13
  14    This program is distributed in the hope that it will be useful,
  15    but WITHOUT ANY WARRANTY; without even the implied warranty of
  16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17    GNU General Public License for more details.
  18
  19    You should have received a copy of the GNU General Public License
  20    along with this program; if not, write to the Free Software
  21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  22
  23*/
  24/*
  25Driver: comedi_bond
  26Description: A driver to 'bond' (merge) multiple subdevices from multiple
  27             devices together as one.
  28Devices:
  29Author: ds
  30Updated: Mon, 10 Oct 00:18:25 -0500
  31Status: works
  32
  33This driver allows you to 'bond' (merge) multiple comedi subdevices
  34(coming from possibly difference boards and/or drivers) together.  For
  35example, if you had a board with 2 different DIO subdevices, and
  36another with 1 DIO subdevice, you could 'bond' them with this driver
  37so that they look like one big fat DIO subdevice.  This makes writing
  38applications slightly easier as you don't have to worry about managing
  39different subdevices in the application -- you just worry about
  40indexing one linear array of channel id's.
  41
  42Right now only DIO subdevices are supported as that's the personal itch
  43I am scratching with this driver.  If you want to add support for AI and AO
  44subdevs, go right on ahead and do so!
  45
  46Commands aren't supported -- although it would be cool if they were.
  47
  48Configuration Options:
  49  List of comedi-minors to bond.  All subdevices of the same type
  50  within each minor will be concatenated together in the order given here.
  51*/
  52
  53#include <linux/string.h>
  54#include <linux/slab.h>
  55#include "../comedi.h"
  56#include "../comedilib.h"
  57#include "../comedidev.h"
  58
  59/* The maxiumum number of channels per subdevice. */
  60#define MAX_CHANS 256
  61
  62struct BondedDevice {
  63        struct comedi_device *dev;
  64        unsigned minor;
  65        unsigned subdev;
  66        unsigned subdev_type;
  67        unsigned nchans;
  68        unsigned chanid_offset; /* The offset into our unified linear
  69                                   channel-id's of chanid 0 on this
  70                                   subdevice. */
  71};
  72
  73/* this structure is for data unique to this hardware driver.  If
  74   several hardware drivers keep similar information in this structure,
  75   feel free to suggest moving the variable to the struct comedi_device struct.  */
  76struct comedi_bond_private {
  77# define MAX_BOARD_NAME 256
  78        char name[MAX_BOARD_NAME];
  79        struct BondedDevice **devs;
  80        unsigned ndevs;
  81        struct BondedDevice *chanIdDevMap[MAX_CHANS];
  82        unsigned nchans;
  83};
  84
  85/* DIO devices are slightly special.  Although it is possible to
  86 * implement the insn_read/insn_write interface, it is much more
  87 * useful to applications if you implement the insn_bits interface.
  88 * This allows packed reading/writing of the DIO channels.  The
  89 * comedi core can convert between insn_bits and insn_read/write */
  90static int bonding_dio_insn_bits(struct comedi_device *dev,
  91                                 struct comedi_subdevice *s,
  92                                 struct comedi_insn *insn, unsigned int *data)
  93{
  94        struct comedi_bond_private *devpriv = dev->private;
  95#define LSAMPL_BITS (sizeof(unsigned int)*8)
  96        unsigned nchans = LSAMPL_BITS, num_done = 0, i;
  97
  98        if (devpriv->nchans < nchans)
  99                nchans = devpriv->nchans;
 100
 101        /* The insn data is a mask in data[0] and the new data
 102         * in data[1], each channel cooresponding to a bit. */
 103        for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) {
 104                struct BondedDevice *bdev = devpriv->devs[i];
 105                /* Grab the channel mask and data of only the bits corresponding
 106                   to this subdevice.. need to shift them to zero position of
 107                   course. */
 108                /* Bits corresponding to this subdev. */
 109                unsigned int subdevMask = ((1 << bdev->nchans) - 1);
 110                unsigned int writeMask, dataBits;
 111
 112                /* Argh, we have >= LSAMPL_BITS chans.. take all bits */
 113                if (bdev->nchans >= LSAMPL_BITS)
 114                        subdevMask = (unsigned int)(-1);
 115
 116                writeMask = (data[0] >> num_done) & subdevMask;
 117                dataBits = (data[1] >> num_done) & subdevMask;
 118
 119                /* Read/Write the new digital lines */
 120                if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask,
 121                                        &dataBits) != 2)
 122                        return -EINVAL;
 123
 124                /* Make room for the new bits in data[1], the return value */
 125                data[1] &= ~(subdevMask << num_done);
 126                /* Put the bits in the return value */
 127                data[1] |= (dataBits & subdevMask) << num_done;
 128                /* Save the new bits to the saved state.. */
 129                s->state = data[1];
 130
 131                num_done += bdev->nchans;
 132        }
 133
 134        return insn->n;
 135}
 136
 137static int bonding_dio_insn_config(struct comedi_device *dev,
 138                                   struct comedi_subdevice *s,
 139                                   struct comedi_insn *insn, unsigned int *data)
 140{
 141        struct comedi_bond_private *devpriv = dev->private;
 142        int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits;
 143        unsigned int io;
 144        struct BondedDevice *bdev;
 145
 146        if (chan < 0 || chan >= devpriv->nchans)
 147                return -EINVAL;
 148        bdev = devpriv->chanIdDevMap[chan];
 149
 150        /* The input or output configuration of each digital line is
 151         * configured by a special insn_config instruction.  chanspec
 152         * contains the channel to be changed, and data[0] contains the
 153         * value COMEDI_INPUT or COMEDI_OUTPUT. */
 154        switch (data[0]) {
 155        case INSN_CONFIG_DIO_OUTPUT:
 156                io = COMEDI_OUTPUT;     /* is this really necessary? */
 157                io_bits |= 1 << chan;
 158                break;
 159        case INSN_CONFIG_DIO_INPUT:
 160                io = COMEDI_INPUT;      /* is this really necessary? */
 161                io_bits &= ~(1 << chan);
 162                break;
 163        case INSN_CONFIG_DIO_QUERY:
 164                data[1] =
 165                    (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
 166                return insn->n;
 167                break;
 168        default:
 169                return -EINVAL;
 170                break;
 171        }
 172        /* 'real' channel id for this subdev.. */
 173        chan -= bdev->chanid_offset;
 174        ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io);
 175        if (ret != 1)
 176                return -EINVAL;
 177        /* Finally, save the new io_bits values since we didn't get
 178           an error above. */
 179        s->io_bits = io_bits;
 180        return insn->n;
 181}
 182
 183static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen)
 184{
 185        void *newmem = kmalloc(newlen, GFP_KERNEL);
 186
 187        if (newmem && oldmem)
 188                memcpy(newmem, oldmem, min(oldlen, newlen));
 189        kfree(oldmem);
 190        return newmem;
 191}
 192
 193static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it)
 194{
 195        struct comedi_bond_private *devpriv = dev->private;
 196        int i;
 197        struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS];
 198
 199        memset(devs_opened, 0, sizeof(devs_opened));
 200        devpriv->name[0] = 0;
 201        /* Loop through all comedi devices specified on the command-line,
 202           building our device list */
 203        for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
 204                char file[] = "/dev/comediXXXXXX";
 205                int minor = it->options[i];
 206                struct comedi_device *d;
 207                int sdev = -1, nchans, tmp;
 208                struct BondedDevice *bdev = NULL;
 209
 210                if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
 211                        dev_err(dev->class_dev,
 212                                "Minor %d is invalid!\n", minor);
 213                        return 0;
 214                }
 215                if (minor == dev->minor) {
 216                        dev_err(dev->class_dev,
 217                                "Cannot bond this driver to itself!\n");
 218                        return 0;
 219                }
 220                if (devs_opened[minor]) {
 221                        dev_err(dev->class_dev,
 222                                "Minor %d specified more than once!\n", minor);
 223                        return 0;
 224                }
 225
 226                snprintf(file, sizeof(file), "/dev/comedi%u", minor);
 227                file[sizeof(file) - 1] = 0;
 228
 229                d = devs_opened[minor] = comedi_open(file);
 230
 231                if (!d) {
 232                        dev_err(dev->class_dev,
 233                                "Minor %u could not be opened\n", minor);
 234                        return 0;
 235                }
 236
 237                /* Do DIO, as that's all we support now.. */
 238                while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
 239                                                             sdev + 1)) > -1) {
 240                        nchans = comedi_get_n_channels(d, sdev);
 241                        if (nchans <= 0) {
 242                                dev_err(dev->class_dev,
 243                                        "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
 244                                        nchans, minor, sdev);
 245                                return 0;
 246                        }
 247                        bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
 248                        if (!bdev)
 249                                return 0;
 250
 251                        bdev->dev = d;
 252                        bdev->minor = minor;
 253                        bdev->subdev = sdev;
 254                        bdev->subdev_type = COMEDI_SUBD_DIO;
 255                        bdev->nchans = nchans;
 256                        bdev->chanid_offset = devpriv->nchans;
 257
 258                        /* map channel id's to BondedDevice * pointer.. */
 259                        while (nchans--)
 260                                devpriv->chanIdDevMap[devpriv->nchans++] = bdev;
 261
 262                        /* Now put bdev pointer at end of devpriv->devs array
 263                         * list.. */
 264
 265                        /* ergh.. ugly.. we need to realloc :(  */
 266                        tmp = devpriv->ndevs * sizeof(bdev);
 267                        devpriv->devs =
 268                            Realloc(devpriv->devs,
 269                                    ++devpriv->ndevs * sizeof(bdev), tmp);
 270                        if (!devpriv->devs) {
 271                                dev_err(dev->class_dev,
 272                                        "Could not allocate memory. Out of memory?\n");
 273                                return 0;
 274                        }
 275
 276                        devpriv->devs[devpriv->ndevs - 1] = bdev;
 277                        {
 278        /** Append dev:subdev to devpriv->name */
 279                                char buf[20];
 280                                int left =
 281                                    MAX_BOARD_NAME - strlen(devpriv->name) - 1;
 282                                snprintf(buf, sizeof(buf), "%d:%d ", dev->minor,
 283                                         bdev->subdev);
 284                                buf[sizeof(buf) - 1] = 0;
 285                                strncat(devpriv->name, buf, left);
 286                        }
 287
 288                }
 289        }
 290
 291        if (!devpriv->nchans) {
 292                dev_err(dev->class_dev, "No channels found!\n");
 293                return 0;
 294        }
 295
 296        return 1;
 297}
 298
 299static int bonding_attach(struct comedi_device *dev,
 300                          struct comedi_devconfig *it)
 301{
 302        struct comedi_bond_private *devpriv;
 303        struct comedi_subdevice *s;
 304        int ret;
 305
 306        devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
 307        if (!devpriv)
 308                return -ENOMEM;
 309        dev->private = devpriv;
 310
 311        /*
 312         * Setup our bonding from config params.. sets up our private struct..
 313         */
 314        if (!doDevConfig(dev, it))
 315                return -EINVAL;
 316
 317        dev->board_name = devpriv->name;
 318
 319        ret = comedi_alloc_subdevices(dev, 1);
 320        if (ret)
 321                return ret;
 322
 323        s = &dev->subdevices[0];
 324        s->type = COMEDI_SUBD_DIO;
 325        s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
 326        s->n_chan = devpriv->nchans;
 327        s->maxdata = 1;
 328        s->range_table = &range_digital;
 329        s->insn_bits = bonding_dio_insn_bits;
 330        s->insn_config = bonding_dio_insn_config;
 331
 332        dev_info(dev->class_dev,
 333                "%s: %s attached, %u channels from %u devices\n",
 334                dev->driver->driver_name, dev->board_name,
 335                devpriv->nchans, devpriv->ndevs);
 336
 337        return 1;
 338}
 339
 340static void bonding_detach(struct comedi_device *dev)
 341{
 342        struct comedi_bond_private *devpriv = dev->private;
 343        unsigned long devs_closed = 0;
 344
 345        if (devpriv) {
 346                while (devpriv->ndevs-- && devpriv->devs) {
 347                        struct BondedDevice *bdev;
 348
 349                        bdev = devpriv->devs[devpriv->ndevs];
 350                        if (!bdev)
 351                                continue;
 352                        if (!(devs_closed & (0x1 << bdev->minor))) {
 353                                comedi_close(bdev->dev);
 354                                devs_closed |= (0x1 << bdev->minor);
 355                        }
 356                        kfree(bdev);
 357                }
 358                kfree(devpriv->devs);
 359                devpriv->devs = NULL;
 360                kfree(devpriv);
 361                dev->private = NULL;
 362        }
 363}
 364
 365static struct comedi_driver bonding_driver = {
 366        .driver_name    = "comedi_bond",
 367        .module         = THIS_MODULE,
 368        .attach         = bonding_attach,
 369        .detach         = bonding_detach,
 370};
 371module_comedi_driver(bonding_driver);
 372
 373MODULE_AUTHOR("Calin A. Culianu");
 374MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI "
 375                   "devices together as one.  In the words of John Lennon: "
 376                   "'And the world will live as one...'");
 377MODULE_LICENSE("GPL");
 378