root/drivers/staging/comedi/drivers/comedi_bond.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. bonding_dio_insn_bits
  2. bonding_dio_insn_config
  3. do_dev_config
  4. bonding_attach
  5. bonding_detach

   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 
  47 struct bonded_device {
  48         struct comedi_device *dev;
  49         unsigned int minor;
  50         unsigned int subdev;
  51         unsigned int nchans;
  52 };
  53 
  54 struct comedi_bond_private {
  55         char name[256];
  56         struct bonded_device **devs;
  57         unsigned int ndevs;
  58         unsigned int nchans;
  59 };
  60 
  61 static 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 
 124 static 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 
 169 static 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 
 273 static 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 
 314 static 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 
 337 static struct comedi_driver bonding_driver = {
 338         .driver_name    = "comedi_bond",
 339         .module         = THIS_MODULE,
 340         .attach         = bonding_attach,
 341         .detach         = bonding_detach,
 342 };
 343 module_comedi_driver(bonding_driver);
 344 
 345 MODULE_AUTHOR("Calin A. Culianu");
 346 MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one.");
 347 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */