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
56 struct bonded_device {
57 struct comedi_device *dev;
58 unsigned minor;
59 unsigned subdev;
60 unsigned nchans;
61 };
62
63 struct comedi_bond_private {
64 char name[256];
65 struct bonded_device **devs;
66 unsigned ndevs;
67 unsigned nchans;
68 };
69
bonding_dio_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)70 static int bonding_dio_insn_bits(struct comedi_device *dev,
71 struct comedi_subdevice *s,
72 struct comedi_insn *insn, unsigned int *data)
73 {
74 struct comedi_bond_private *devpriv = dev->private;
75 unsigned int n_left, n_done, base_chan;
76 unsigned int write_mask, data_bits;
77 struct bonded_device **devs;
78
79 write_mask = data[0];
80 data_bits = data[1];
81 base_chan = CR_CHAN(insn->chanspec);
82 /* do a maximum of 32 channels, starting from base_chan. */
83 n_left = devpriv->nchans - base_chan;
84 if (n_left > 32)
85 n_left = 32;
86
87 n_done = 0;
88 devs = devpriv->devs;
89 do {
90 struct bonded_device *bdev = *devs++;
91
92 if (base_chan < bdev->nchans) {
93 /* base channel falls within bonded device */
94 unsigned int b_chans, b_mask, b_write_mask, b_data_bits;
95 int ret;
96
97 /*
98 * Get num channels to do for bonded device and set
99 * up mask and data bits for bonded device.
100 */
101 b_chans = bdev->nchans - base_chan;
102 if (b_chans > n_left)
103 b_chans = n_left;
104 b_mask = (1U << b_chans) - 1;
105 b_write_mask = (write_mask >> n_done) & b_mask;
106 b_data_bits = (data_bits >> n_done) & b_mask;
107 /* Read/Write the new digital lines. */
108 ret = comedi_dio_bitfield2(bdev->dev, bdev->subdev,
109 b_write_mask, &b_data_bits,
110 base_chan);
111 if (ret < 0)
112 return ret;
113 /* Place read bits into data[1]. */
114 data[1] &= ~(b_mask << n_done);
115 data[1] |= (b_data_bits & b_mask) << n_done;
116 /*
117 * Set up for following bonded device (if still have
118 * channels to read/write).
119 */
120 base_chan = 0;
121 n_done += b_chans;
122 n_left -= b_chans;
123 } else {
124 /* Skip bonded devices before base channel. */
125 base_chan -= bdev->nchans;
126 }
127 } while (n_left);
128
129 return insn->n;
130 }
131
bonding_dio_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)132 static int bonding_dio_insn_config(struct comedi_device *dev,
133 struct comedi_subdevice *s,
134 struct comedi_insn *insn, unsigned int *data)
135 {
136 struct comedi_bond_private *devpriv = dev->private;
137 unsigned int chan = CR_CHAN(insn->chanspec);
138 int ret;
139 struct bonded_device *bdev;
140 struct bonded_device **devs;
141
142 /*
143 * Locate bonded subdevice and adjust channel.
144 */
145 devs = devpriv->devs;
146 for (bdev = *devs++; chan >= bdev->nchans; bdev = *devs++)
147 chan -= bdev->nchans;
148
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 * configuration instruction INSN_CONFIG_DIO_OUTPUT,
154 * INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_QUERY.
155 *
156 * Note that INSN_CONFIG_DIO_OUTPUT == COMEDI_OUTPUT,
157 * and INSN_CONFIG_DIO_INPUT == COMEDI_INPUT. This is deliberate ;)
158 */
159 switch (data[0]) {
160 case INSN_CONFIG_DIO_OUTPUT:
161 case INSN_CONFIG_DIO_INPUT:
162 ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, data[0]);
163 break;
164 case INSN_CONFIG_DIO_QUERY:
165 ret = comedi_dio_get_config(bdev->dev, bdev->subdev, chan,
166 &data[1]);
167 break;
168 default:
169 ret = -EINVAL;
170 break;
171 }
172 if (ret >= 0)
173 ret = insn->n;
174 return ret;
175 }
176
do_dev_config(struct comedi_device * dev,struct comedi_devconfig * it)177 static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it)
178 {
179 struct comedi_bond_private *devpriv = dev->private;
180 DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS);
181 int i;
182
183 memset(&devs_opened, 0, sizeof(devs_opened));
184 devpriv->name[0] = 0;
185 /*
186 * Loop through all comedi devices specified on the command-line,
187 * building our device list.
188 */
189 for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
190 char file[sizeof("/dev/comediXXXXXX")];
191 int minor = it->options[i];
192 struct comedi_device *d;
193 int sdev = -1, nchans;
194 struct bonded_device *bdev;
195 struct bonded_device **devs;
196
197 if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
198 dev_err(dev->class_dev,
199 "Minor %d is invalid!\n", minor);
200 return -EINVAL;
201 }
202 if (minor == dev->minor) {
203 dev_err(dev->class_dev,
204 "Cannot bond this driver to itself!\n");
205 return -EINVAL;
206 }
207 if (test_and_set_bit(minor, devs_opened)) {
208 dev_err(dev->class_dev,
209 "Minor %d specified more than once!\n", minor);
210 return -EINVAL;
211 }
212
213 snprintf(file, sizeof(file), "/dev/comedi%d", minor);
214 file[sizeof(file) - 1] = 0;
215
216 d = comedi_open(file);
217
218 if (!d) {
219 dev_err(dev->class_dev,
220 "Minor %u could not be opened\n", minor);
221 return -ENODEV;
222 }
223
224 /* Do DIO, as that's all we support now.. */
225 while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
226 sdev + 1)) > -1) {
227 nchans = comedi_get_n_channels(d, sdev);
228 if (nchans <= 0) {
229 dev_err(dev->class_dev,
230 "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
231 nchans, minor, sdev);
232 return -EINVAL;
233 }
234 bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
235 if (!bdev)
236 return -ENOMEM;
237
238 bdev->dev = d;
239 bdev->minor = minor;
240 bdev->subdev = sdev;
241 bdev->nchans = nchans;
242 devpriv->nchans += nchans;
243
244 /*
245 * Now put bdev pointer at end of devpriv->devs array
246 * list..
247 */
248
249 /* ergh.. ugly.. we need to realloc :( */
250 devs = krealloc(devpriv->devs,
251 (devpriv->ndevs + 1) * sizeof(*devs),
252 GFP_KERNEL);
253 if (!devs) {
254 dev_err(dev->class_dev,
255 "Could not allocate memory. Out of memory?\n");
256 kfree(bdev);
257 return -ENOMEM;
258 }
259 devpriv->devs = devs;
260 devpriv->devs[devpriv->ndevs++] = bdev;
261 {
262 /* Append dev:subdev to devpriv->name */
263 char buf[20];
264
265 snprintf(buf, sizeof(buf), "%u:%u ",
266 bdev->minor, bdev->subdev);
267 strlcat(devpriv->name, buf,
268 sizeof(devpriv->name));
269 }
270 }
271 }
272
273 if (!devpriv->nchans) {
274 dev_err(dev->class_dev, "No channels found!\n");
275 return -EINVAL;
276 }
277
278 return 0;
279 }
280
bonding_attach(struct comedi_device * dev,struct comedi_devconfig * it)281 static int bonding_attach(struct comedi_device *dev,
282 struct comedi_devconfig *it)
283 {
284 struct comedi_bond_private *devpriv;
285 struct comedi_subdevice *s;
286 int ret;
287
288 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
289 if (!devpriv)
290 return -ENOMEM;
291
292 /*
293 * Setup our bonding from config params.. sets up our private struct..
294 */
295 ret = do_dev_config(dev, it);
296 if (ret)
297 return ret;
298
299 dev->board_name = devpriv->name;
300
301 ret = comedi_alloc_subdevices(dev, 1);
302 if (ret)
303 return ret;
304
305 s = &dev->subdevices[0];
306 s->type = COMEDI_SUBD_DIO;
307 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
308 s->n_chan = devpriv->nchans;
309 s->maxdata = 1;
310 s->range_table = &range_digital;
311 s->insn_bits = bonding_dio_insn_bits;
312 s->insn_config = bonding_dio_insn_config;
313
314 dev_info(dev->class_dev,
315 "%s: %s attached, %u channels from %u devices\n",
316 dev->driver->driver_name, dev->board_name,
317 devpriv->nchans, devpriv->ndevs);
318
319 return 0;
320 }
321
bonding_detach(struct comedi_device * dev)322 static void bonding_detach(struct comedi_device *dev)
323 {
324 struct comedi_bond_private *devpriv = dev->private;
325
326 if (devpriv && devpriv->devs) {
327 DECLARE_BITMAP(devs_closed, COMEDI_NUM_BOARD_MINORS);
328
329 memset(&devs_closed, 0, sizeof(devs_closed));
330 while (devpriv->ndevs--) {
331 struct bonded_device *bdev;
332
333 bdev = devpriv->devs[devpriv->ndevs];
334 if (!bdev)
335 continue;
336 if (!test_and_set_bit(bdev->minor, devs_closed))
337 comedi_close(bdev->dev);
338 kfree(bdev);
339 }
340 kfree(devpriv->devs);
341 devpriv->devs = NULL;
342 }
343 }
344
345 static struct comedi_driver bonding_driver = {
346 .driver_name = "comedi_bond",
347 .module = THIS_MODULE,
348 .attach = bonding_attach,
349 .detach = bonding_detach,
350 };
351 module_comedi_driver(bonding_driver);
352
353 MODULE_AUTHOR("Calin A. Culianu");
354 MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one.");
355 MODULE_LICENSE("GPL");
356