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 = (b_chans < 32) ? ((1 << b_chans) - 1)
105 						: 0xffffffff;
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 
bonding_dio_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)133 static 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 
do_dev_config(struct comedi_device * dev,struct comedi_devconfig * it)178 static 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 
266 				snprintf(buf, sizeof(buf), "%u:%u ",
267 					 bdev->minor, bdev->subdev);
268 				strlcat(devpriv->name, buf,
269 					sizeof(devpriv->name));
270 			}
271 		}
272 	}
273 
274 	if (!devpriv->nchans) {
275 		dev_err(dev->class_dev, "No channels found!\n");
276 		return -EINVAL;
277 	}
278 
279 	return 0;
280 }
281 
bonding_attach(struct comedi_device * dev,struct comedi_devconfig * it)282 static int bonding_attach(struct comedi_device *dev,
283 			  struct comedi_devconfig *it)
284 {
285 	struct comedi_bond_private *devpriv;
286 	struct comedi_subdevice *s;
287 	int ret;
288 
289 	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
290 	if (!devpriv)
291 		return -ENOMEM;
292 
293 	/*
294 	 * Setup our bonding from config params.. sets up our private struct..
295 	 */
296 	ret = do_dev_config(dev, it);
297 	if (ret)
298 		return ret;
299 
300 	dev->board_name = devpriv->name;
301 
302 	ret = comedi_alloc_subdevices(dev, 1);
303 	if (ret)
304 		return ret;
305 
306 	s = &dev->subdevices[0];
307 	s->type = COMEDI_SUBD_DIO;
308 	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
309 	s->n_chan = devpriv->nchans;
310 	s->maxdata = 1;
311 	s->range_table = &range_digital;
312 	s->insn_bits = bonding_dio_insn_bits;
313 	s->insn_config = bonding_dio_insn_config;
314 
315 	dev_info(dev->class_dev,
316 		 "%s: %s attached, %u channels from %u devices\n",
317 		 dev->driver->driver_name, dev->board_name,
318 		 devpriv->nchans, devpriv->ndevs);
319 
320 	return 0;
321 }
322 
bonding_detach(struct comedi_device * dev)323 static void bonding_detach(struct comedi_device *dev)
324 {
325 	struct comedi_bond_private *devpriv = dev->private;
326 
327 	if (devpriv && devpriv->devs) {
328 		DECLARE_BITMAP(devs_closed, COMEDI_NUM_BOARD_MINORS);
329 
330 		memset(&devs_closed, 0, sizeof(devs_closed));
331 		while (devpriv->ndevs--) {
332 			struct bonded_device *bdev;
333 
334 			bdev = devpriv->devs[devpriv->ndevs];
335 			if (!bdev)
336 				continue;
337 			if (!test_and_set_bit(bdev->minor, devs_closed))
338 				comedi_close(bdev->dev);
339 			kfree(bdev);
340 		}
341 		kfree(devpriv->devs);
342 		devpriv->devs = NULL;
343 	}
344 }
345 
346 static struct comedi_driver bonding_driver = {
347 	.driver_name	= "comedi_bond",
348 	.module		= THIS_MODULE,
349 	.attach		= bonding_attach,
350 	.detach		= bonding_detach,
351 };
352 module_comedi_driver(bonding_driver);
353 
354 MODULE_AUTHOR("Calin A. Culianu");
355 MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one.");
356 MODULE_LICENSE("GPL");
357