linux/drivers/staging/comedi/drivers/comedi_bond.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * comedi_bond.c
   4 * A Comedi driver to 'bond' or merge multiple drivers and devices as one.
   5 *
   6 * COMEDI - Linux Control and Measurement Device Interface
   7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
   8 * Copyright (C) 2005 Calin A. Culianu <calin@ajvar.org>
   9 */
  10
  11/*
  12 * Driver: comedi_bond
  13 * Description: A driver to 'bond' (merge) multiple subdevices from multiple
  14 * devices together as one.
  15 * Devices:
  16 * Author: ds
  17 * Updated: Mon, 10 Oct 00:18:25 -0500
  18 * Status: works
  19 *
  20 * This driver allows you to 'bond' (merge) multiple comedi subdevices
  21 * (coming from possibly difference boards and/or drivers) together.  For
  22 * example, if you had a board with 2 different DIO subdevices, and
  23 * another with 1 DIO subdevice, you could 'bond' them with this driver
  24 * so that they look like one big fat DIO subdevice.  This makes writing
  25 * applications slightly easier as you don't have to worry about managing
  26 * different subdevices in the application -- you just worry about
  27 * indexing one linear array of channel id's.
  28 *
  29 * Right now only DIO subdevices are supported as that's the personal itch
  30 * I am scratching with this driver.  If you want to add support for AI and AO
  31 * subdevs, go right on ahead and do so!
  32 *
  33 * Commands aren't supported -- although it would be cool if they were.
  34 *
  35 * Configuration Options:
  36 *   List of comedi-minors to bond.  All subdevices of the same type
  37 *   within each minor will be concatenated together in the order given here.
  38 */
  39
  40#include <linux/module.h>
  41#include <linux/string.h>
  42#include <linux/slab.h>
  43#include "../comedi.h"
  44#include "../comedilib.h"
  45#include "../comedidev.h"
  46
  47struct bonded_device {
  48        struct comedi_device *dev;
  49        unsigned int minor;
  50        unsigned int subdev;
  51        unsigned int nchans;
  52};
  53
  54struct comedi_bond_private {
  55        char name[256];
  56        struct bonded_device **devs;
  57        unsigned int ndevs;
  58        unsigned int nchans;
  59};
  60
  61static int bonding_dio_insn_bits(struct comedi_device *dev,
  62                                 struct comedi_subdevice *s,
  63                                 struct comedi_insn *insn, unsigned int *data)
  64{
  65        struct comedi_bond_private *devpriv = dev->private;
  66        unsigned int n_left, n_done, base_chan;
  67        unsigned int write_mask, data_bits;
  68        struct bonded_device **devs;
  69
  70        write_mask = data[0];
  71        data_bits = data[1];
  72        base_chan = CR_CHAN(insn->chanspec);
  73        /* do a maximum of 32 channels, starting from base_chan. */
  74        n_left = devpriv->nchans - base_chan;
  75        if (n_left > 32)
  76                n_left = 32;
  77
  78        n_done = 0;
  79        devs = devpriv->devs;
  80        do {
  81                struct bonded_device *bdev = *devs++;
  82
  83                if (base_chan < bdev->nchans) {
  84                        /* base channel falls within bonded device */
  85                        unsigned int b_chans, b_mask, b_write_mask, b_data_bits;
  86                        int ret;
  87
  88                        /*
  89                         * Get num channels to do for bonded device and set
  90                         * up mask and data bits for bonded device.
  91                         */
  92                        b_chans = bdev->nchans - base_chan;
  93                        if (b_chans > n_left)
  94                                b_chans = n_left;
  95                        b_mask = (b_chans < 32) ? ((1 << b_chans) - 1)
  96                                                : 0xffffffff;
  97                        b_write_mask = (write_mask >> n_done) & b_mask;
  98                        b_data_bits = (data_bits >> n_done) & b_mask;
  99                        /* Read/Write the new digital lines. */
 100                        ret = comedi_dio_bitfield2(bdev->dev, bdev->subdev,
 101                                                   b_write_mask, &b_data_bits,
 102                                                   base_chan);
 103                        if (ret < 0)
 104                                return ret;
 105                        /* Place read bits into data[1]. */
 106                        data[1] &= ~(b_mask << n_done);
 107                        data[1] |= (b_data_bits & b_mask) << n_done;
 108                        /*
 109                         * Set up for following bonded device (if still have
 110                         * channels to read/write).
 111                         */
 112                        base_chan = 0;
 113                        n_done += b_chans;
 114                        n_left -= b_chans;
 115                } else {
 116                        /* Skip bonded devices before base channel. */
 117                        base_chan -= bdev->nchans;
 118                }
 119        } while (n_left);
 120
 121        return insn->n;
 122}
 123
 124static int bonding_dio_insn_config(struct comedi_device *dev,
 125                                   struct comedi_subdevice *s,
 126                                   struct comedi_insn *insn, unsigned int *data)
 127{
 128        struct comedi_bond_private *devpriv = dev->private;
 129        unsigned int chan = CR_CHAN(insn->chanspec);
 130        int ret;
 131        struct bonded_device *bdev;
 132        struct bonded_device **devs;
 133
 134        /*
 135         * Locate bonded subdevice and adjust channel.
 136         */
 137        devs = devpriv->devs;
 138        for (bdev = *devs++; chan >= bdev->nchans; bdev = *devs++)
 139                chan -= bdev->nchans;
 140
 141        /*
 142         * The input or output configuration of each digital line is
 143         * configured by a special insn_config instruction.  chanspec
 144         * contains the channel to be changed, and data[0] contains the
 145         * configuration instruction INSN_CONFIG_DIO_OUTPUT,
 146         * INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_QUERY.
 147         *
 148         * Note that INSN_CONFIG_DIO_OUTPUT == COMEDI_OUTPUT,
 149         * and INSN_CONFIG_DIO_INPUT == COMEDI_INPUT.  This is deliberate ;)
 150         */
 151        switch (data[0]) {
 152        case INSN_CONFIG_DIO_OUTPUT:
 153        case INSN_CONFIG_DIO_INPUT:
 154                ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, data[0]);
 155                break;
 156        case INSN_CONFIG_DIO_QUERY:
 157                ret = comedi_dio_get_config(bdev->dev, bdev->subdev, chan,
 158                                            &data[1]);
 159                break;
 160        default:
 161                ret = -EINVAL;
 162                break;
 163        }
 164        if (ret >= 0)
 165                ret = insn->n;
 166        return ret;
 167}
 168
 169static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it)
 170{
 171        struct comedi_bond_private *devpriv = dev->private;
 172        DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS);
 173        int i;
 174
 175        memset(&devs_opened, 0, sizeof(devs_opened));
 176        devpriv->name[0] = 0;
 177        /*
 178         * Loop through all comedi devices specified on the command-line,
 179         * building our device list.
 180         */
 181        for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
 182                char file[sizeof("/dev/comediXXXXXX")];
 183                int minor = it->options[i];
 184                struct comedi_device *d;
 185                int sdev = -1, nchans;
 186                struct bonded_device *bdev;
 187                struct bonded_device **devs;
 188
 189                if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
 190                        dev_err(dev->class_dev,
 191                                "Minor %d is invalid!\n", minor);
 192                        return -EINVAL;
 193                }
 194                if (minor == dev->minor) {
 195                        dev_err(dev->class_dev,
 196                                "Cannot bond this driver to itself!\n");
 197                        return -EINVAL;
 198                }
 199                if (test_and_set_bit(minor, devs_opened)) {
 200                        dev_err(dev->class_dev,
 201                                "Minor %d specified more than once!\n", minor);
 202                        return -EINVAL;
 203                }
 204
 205                snprintf(file, sizeof(file), "/dev/comedi%d", minor);
 206                file[sizeof(file) - 1] = 0;
 207
 208                d = comedi_open(file);
 209
 210                if (!d) {
 211                        dev_err(dev->class_dev,
 212                                "Minor %u could not be opened\n", minor);
 213                        return -ENODEV;
 214                }
 215
 216                /* Do DIO, as that's all we support now.. */
 217                while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
 218                                                             sdev + 1)) > -1) {
 219                        nchans = comedi_get_n_channels(d, sdev);
 220                        if (nchans <= 0) {
 221                                dev_err(dev->class_dev,
 222                                        "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
 223                                        nchans, minor, sdev);
 224                                return -EINVAL;
 225                        }
 226                        bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
 227                        if (!bdev)
 228                                return -ENOMEM;
 229
 230                        bdev->dev = d;
 231                        bdev->minor = minor;
 232                        bdev->subdev = sdev;
 233                        bdev->nchans = nchans;
 234                        devpriv->nchans += nchans;
 235
 236                        /*
 237                         * Now put bdev pointer at end of devpriv->devs array
 238                         * list..
 239                         */
 240
 241                        /* ergh.. ugly.. we need to realloc :(  */
 242                        devs = krealloc(devpriv->devs,
 243                                        (devpriv->ndevs + 1) * sizeof(*devs),
 244                                        GFP_KERNEL);
 245                        if (!devs) {
 246                                dev_err(dev->class_dev,
 247                                        "Could not allocate memory. Out of memory?\n");
 248                                kfree(bdev);
 249                                return -ENOMEM;
 250                        }
 251                        devpriv->devs = devs;
 252                        devpriv->devs[devpriv->ndevs++] = bdev;
 253                        {
 254                                /* Append dev:subdev to devpriv->name */
 255                                char buf[20];
 256
 257                                snprintf(buf, sizeof(buf), "%u:%u ",
 258                                         bdev->minor, bdev->subdev);
 259                                strlcat(devpriv->name, buf,
 260                                        sizeof(devpriv->name));
 261                        }
 262                }
 263        }
 264
 265        if (!devpriv->nchans) {
 266                dev_err(dev->class_dev, "No channels found!\n");
 267                return -EINVAL;
 268        }
 269
 270        return 0;
 271}
 272
 273static int bonding_attach(struct comedi_device *dev,
 274                          struct comedi_devconfig *it)
 275{
 276        struct comedi_bond_private *devpriv;
 277        struct comedi_subdevice *s;
 278        int ret;
 279
 280        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 281        if (!devpriv)
 282                return -ENOMEM;
 283
 284        /*
 285         * Setup our bonding from config params.. sets up our private struct..
 286         */
 287        ret = do_dev_config(dev, it);
 288        if (ret)
 289                return ret;
 290
 291        dev->board_name = devpriv->name;
 292
 293        ret = comedi_alloc_subdevices(dev, 1);
 294        if (ret)
 295                return ret;
 296
 297        s = &dev->subdevices[0];
 298        s->type = COMEDI_SUBD_DIO;
 299        s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
 300        s->n_chan = devpriv->nchans;
 301        s->maxdata = 1;
 302        s->range_table = &range_digital;
 303        s->insn_bits = bonding_dio_insn_bits;
 304        s->insn_config = bonding_dio_insn_config;
 305
 306        dev_info(dev->class_dev,
 307                 "%s: %s attached, %u channels from %u devices\n",
 308                 dev->driver->driver_name, dev->board_name,
 309                 devpriv->nchans, devpriv->ndevs);
 310
 311        return 0;
 312}
 313
 314static void bonding_detach(struct comedi_device *dev)
 315{
 316        struct comedi_bond_private *devpriv = dev->private;
 317
 318        if (devpriv && devpriv->devs) {
 319                DECLARE_BITMAP(devs_closed, COMEDI_NUM_BOARD_MINORS);
 320
 321                memset(&devs_closed, 0, sizeof(devs_closed));
 322                while (devpriv->ndevs--) {
 323                        struct bonded_device *bdev;
 324
 325                        bdev = devpriv->devs[devpriv->ndevs];
 326                        if (!bdev)
 327                                continue;
 328                        if (!test_and_set_bit(bdev->minor, devs_closed))
 329                                comedi_close(bdev->dev);
 330                        kfree(bdev);
 331                }
 332                kfree(devpriv->devs);
 333                devpriv->devs = NULL;
 334        }
 335}
 336
 337static struct comedi_driver bonding_driver = {
 338        .driver_name    = "comedi_bond",
 339        .module         = THIS_MODULE,
 340        .attach         = bonding_attach,
 341        .detach         = bonding_detach,
 342};
 343module_comedi_driver(bonding_driver);
 344
 345MODULE_AUTHOR("Calin A. Culianu");
 346MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one.");
 347MODULE_LICENSE("GPL");
 348