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 <linux/comedi.h> 44#include <linux/comedi/comedilib.h> 45#include <linux/comedi/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