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 
47 struct bonded_device {
48 	struct comedi_device *dev;
49 	unsigned int minor;
50 	unsigned int subdev;
51 	unsigned int nchans;
52 };
53 
54 struct comedi_bond_private {
55 	char name[256];
56 	struct bonded_device **devs;
57 	unsigned int ndevs;
58 	unsigned int nchans;
59 };
60 
bonding_dio_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)61 static 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 
bonding_dio_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)124 static 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 
do_dev_config(struct comedi_device * dev,struct comedi_devconfig * it)169 static 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 
bonding_attach(struct comedi_device * dev,struct comedi_devconfig * it)273 static 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 
bonding_detach(struct comedi_device * dev)314 static 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 
337 static struct comedi_driver bonding_driver = {
338 	.driver_name	= "comedi_bond",
339 	.module		= THIS_MODULE,
340 	.attach		= bonding_attach,
341 	.detach		= bonding_detach,
342 };
343 module_comedi_driver(bonding_driver);
344 
345 MODULE_AUTHOR("Calin A. Culianu");
346 MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one.");
347 MODULE_LICENSE("GPL");
348