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