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                                dev_err(dev->class_dev, "Out of memory\n");
 250                                return 0;
 251                        }
 252                        bdev->dev = d;
 253                        bdev->minor = minor;
 254                        bdev->subdev = sdev;
 255                        bdev->subdev_type = COMEDI_SUBD_DIO;
 256                        bdev->nchans = nchans;
 257                        bdev->chanid_offset = devpriv->nchans;
 258
 259                        /* map channel id's to BondedDevice * pointer.. */
 260                        while (nchans--)
 261                                devpriv->chanIdDevMap[devpriv->nchans++] = bdev;
 262
 263                        /* Now put bdev pointer at end of devpriv->devs array
 264                         * list.. */
 265
 266                        /* ergh.. ugly.. we need to realloc :(  */
 267                        tmp = devpriv->ndevs * sizeof(bdev);
 268                        devpriv->devs =
 269                            Realloc(devpriv->devs,
 270                                    ++devpriv->ndevs * sizeof(bdev), tmp);
 271                        if (!devpriv->devs) {
 272                                dev_err(dev->class_dev,
 273                                        "Could not allocate memory. Out of memory?\n");
 274                                return 0;
 275                        }
 276
 277                        devpriv->devs[devpriv->ndevs - 1] = bdev;
 278                        {
 279        /** Append dev:subdev to devpriv->name */
 280                                char buf[20];
 281                                int left =
 282                                    MAX_BOARD_NAME - strlen(devpriv->name) - 1;
 283                                snprintf(buf, sizeof(buf), "%d:%d ", dev->minor,
 284                                         bdev->subdev);
 285                                buf[sizeof(buf) - 1] = 0;
 286                                strncat(devpriv->name, buf, left);
 287                        }
 288
 289                }
 290        }
 291
 292        if (!devpriv->nchans) {
 293                dev_err(dev->class_dev, "No channels found!\n");
 294                return 0;
 295        }
 296
 297        return 1;
 298}
 299
 300static int bonding_attach(struct comedi_device *dev,
 301                          struct comedi_devconfig *it)
 302{
 303        struct comedi_bond_private *devpriv;
 304        struct comedi_subdevice *s;
 305        int ret;
 306
 307        devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
 308        if (!devpriv)
 309                return -ENOMEM;
 310        dev->private = devpriv;
 311
 312        /*
 313         * Setup our bonding from config params.. sets up our private struct..
 314         */
 315        if (!doDevConfig(dev, it))
 316                return -EINVAL;
 317
 318        dev->board_name = devpriv->name;
 319
 320        ret = comedi_alloc_subdevices(dev, 1);
 321        if (ret)
 322                return ret;
 323
 324        s = &dev->subdevices[0];
 325        s->type = COMEDI_SUBD_DIO;
 326        s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
 327        s->n_chan = devpriv->nchans;
 328        s->maxdata = 1;
 329        s->range_table = &range_digital;
 330        s->insn_bits = bonding_dio_insn_bits;
 331        s->insn_config = bonding_dio_insn_config;
 332
 333        dev_info(dev->class_dev,
 334                "%s: %s attached, %u channels from %u devices\n",
 335                dev->driver->driver_name, dev->board_name,
 336                devpriv->nchans, devpriv->ndevs);
 337
 338        return 1;
 339}
 340
 341static void bonding_detach(struct comedi_device *dev)
 342{
 343        struct comedi_bond_private *devpriv = dev->private;
 344        unsigned long devs_closed = 0;
 345
 346        if (devpriv) {
 347                while (devpriv->ndevs-- && devpriv->devs) {
 348                        struct BondedDevice *bdev;
 349
 350                        bdev = devpriv->devs[devpriv->ndevs];
 351                        if (!bdev)
 352                                continue;
 353                        if (!(devs_closed & (0x1 << bdev->minor))) {
 354                                comedi_close(bdev->dev);
 355                                devs_closed |= (0x1 << bdev->minor);
 356                        }
 357                        kfree(bdev);
 358                }
 359                kfree(devpriv->devs);
 360                devpriv->devs = NULL;
 361                kfree(devpriv);
 362                dev->private = NULL;
 363        }
 364}
 365
 366static struct comedi_driver bonding_driver = {
 367        .driver_name    = "comedi_bond",
 368        .module         = THIS_MODULE,
 369        .attach         = bonding_attach,
 370        .detach         = bonding_detach,
 371};
 372module_comedi_driver(bonding_driver);
 373
 374MODULE_AUTHOR("Calin A. Culianu");
 375MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI "
 376                   "devices together as one.  In the words of John Lennon: "
 377                   "'And the world will live as one...'");
 378MODULE_LICENSE("GPL");
 379