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(dev: bdev->dev, subdev: bdev->subdev,
101 mask: b_write_mask, bits: &b_data_bits,
102 base_channel: 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(dev: bdev->dev, subdev: bdev->subdev, chan, io: data[0]);
155 break;
156 case INSN_CONFIG_DIO_QUERY:
157 ret = comedi_dio_get_config(dev: bdev->dev, subdev: bdev->subdev, chan,
158 io: &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(nr: minor, addr: devs_opened)) {
200 dev_err(dev->class_dev,
201 "Minor %d specified more than once!\n", minor);
202 return -EINVAL;
203 }
204
205 snprintf(buf: file, size: sizeof(file), fmt: "/dev/comedi%d", minor);
206 file[sizeof(file) - 1] = 0;
207
208 d = comedi_open(path: 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(dev: d, type: COMEDI_SUBD_DIO,
218 subd: sdev + 1)) > -1) {
219 nchans = comedi_get_n_channels(dev: d, subdevice: 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(size: 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(objp: devpriv->devs,
243 new_size: (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(objp: 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, size: sizeof(buf), fmt: "%u:%u ",
258 bdev->minor, bdev->subdev);
259 strlcat(p: devpriv->name, q: buf,
260 avail: 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, size: 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, num_subdevices: 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(nr: bdev->minor, addr: devs_closed))
329 comedi_close(dev: bdev->dev);
330 kfree(objp: bdev);
331 }
332 kfree(objp: 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

source code of linux/drivers/comedi/drivers/comedi_bond.c