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