Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
comedi_bond.c
Go to the documentation of this file.
1 /*
2  comedi/drivers/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 <[email protected]>
7  Copyright (C) 2005 Calin A. Culianu <[email protected]>
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  You should have received a copy of the GNU General Public License
20  along with this program; if not, write to the Free Software
21  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 
23 */
24 /*
25 Driver: comedi_bond
26 Description: A driver to 'bond' (merge) multiple subdevices from multiple
27  devices together as one.
28 Devices:
29 Author: ds
30 Updated: Mon, 10 Oct 00:18:25 -0500
31 Status: works
32 
33 This driver allows you to 'bond' (merge) multiple comedi subdevices
34 (coming from possibly difference boards and/or drivers) together. For
35 example, if you had a board with 2 different DIO subdevices, and
36 another with 1 DIO subdevice, you could 'bond' them with this driver
37 so that they look like one big fat DIO subdevice. This makes writing
38 applications slightly easier as you don't have to worry about managing
39 different subdevices in the application -- you just worry about
40 indexing one linear array of channel id's.
41 
42 Right now only DIO subdevices are supported as that's the personal itch
43 I am scratching with this driver. If you want to add support for AI and AO
44 subdevs, go right on ahead and do so!
45 
46 Commands aren't supported -- although it would be cool if they were.
47 
48 Configuration Options:
49  List of comedi-minors to bond. All subdevices of the same type
50  within each minor will be concatenated together in the order given here.
51 */
52 
53 #include <linux/string.h>
54 #include <linux/slab.h>
55 #include "../comedi.h"
56 #include "../comedilib.h"
57 #include "../comedidev.h"
58 
59 /* The maxiumum number of channels per subdevice. */
60 #define MAX_CHANS 256
61 
62 struct BondedDevice {
63  struct comedi_device *dev;
64  unsigned minor;
65  unsigned subdev;
66  unsigned subdev_type;
67  unsigned nchans;
68  unsigned chanid_offset; /* The offset into our unified linear
69  channel-id's of chanid 0 on this
70  subdevice. */
71 };
72 
73 /* this structure is for data unique to this hardware driver. If
74  several hardware drivers keep similar information in this structure,
75  feel free to suggest moving the variable to the struct comedi_device struct. */
77 # define MAX_BOARD_NAME 256
79  struct BondedDevice **devs;
80  unsigned ndevs;
82  unsigned nchans;
83 };
84 
85 /* DIO devices are slightly special. Although it is possible to
86  * implement the insn_read/insn_write interface, it is much more
87  * useful to applications if you implement the insn_bits interface.
88  * This allows packed reading/writing of the DIO channels. The
89  * comedi core can convert between insn_bits and insn_read/write */
90 static int bonding_dio_insn_bits(struct comedi_device *dev,
91  struct comedi_subdevice *s,
92  struct comedi_insn *insn, unsigned int *data)
93 {
94  struct comedi_bond_private *devpriv = dev->private;
95 #define LSAMPL_BITS (sizeof(unsigned int)*8)
96  unsigned nchans = LSAMPL_BITS, num_done = 0, i;
97 
98  if (devpriv->nchans < nchans)
99  nchans = devpriv->nchans;
100 
101  /* The insn data is a mask in data[0] and the new data
102  * in data[1], each channel cooresponding to a bit. */
103  for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) {
104  struct BondedDevice *bdev = devpriv->devs[i];
105  /* Grab the channel mask and data of only the bits corresponding
106  to this subdevice.. need to shift them to zero position of
107  course. */
108  /* Bits corresponding to this subdev. */
109  unsigned int subdevMask = ((1 << bdev->nchans) - 1);
110  unsigned int writeMask, dataBits;
111 
112  /* Argh, we have >= LSAMPL_BITS chans.. take all bits */
113  if (bdev->nchans >= LSAMPL_BITS)
114  subdevMask = (unsigned int)(-1);
115 
116  writeMask = (data[0] >> num_done) & subdevMask;
117  dataBits = (data[1] >> num_done) & subdevMask;
118 
119  /* Read/Write the new digital lines */
120  if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask,
121  &dataBits) != 2)
122  return -EINVAL;
123 
124  /* Make room for the new bits in data[1], the return value */
125  data[1] &= ~(subdevMask << num_done);
126  /* Put the bits in the return value */
127  data[1] |= (dataBits & subdevMask) << num_done;
128  /* Save the new bits to the saved state.. */
129  s->state = data[1];
130 
131  num_done += bdev->nchans;
132  }
133 
134  return insn->n;
135 }
136 
137 static int bonding_dio_insn_config(struct comedi_device *dev,
138  struct comedi_subdevice *s,
139  struct comedi_insn *insn, unsigned int *data)
140 {
141  struct comedi_bond_private *devpriv = dev->private;
142  int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits;
143  unsigned int io;
144  struct BondedDevice *bdev;
145 
146  if (chan < 0 || chan >= devpriv->nchans)
147  return -EINVAL;
148  bdev = devpriv->chanIdDevMap[chan];
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  * value COMEDI_INPUT or COMEDI_OUTPUT. */
154  switch (data[0]) {
156  io = COMEDI_OUTPUT; /* is this really necessary? */
157  io_bits |= 1 << chan;
158  break;
160  io = COMEDI_INPUT; /* is this really necessary? */
161  io_bits &= ~(1 << chan);
162  break;
164  data[1] =
165  (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
166  return insn->n;
167  break;
168  default:
169  return -EINVAL;
170  break;
171  }
172  /* 'real' channel id for this subdev.. */
173  chan -= bdev->chanid_offset;
174  ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io);
175  if (ret != 1)
176  return -EINVAL;
177  /* Finally, save the new io_bits values since we didn't get
178  an error above. */
179  s->io_bits = io_bits;
180  return insn->n;
181 }
182 
183 static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen)
184 {
185  void *newmem = kmalloc(newlen, GFP_KERNEL);
186 
187  if (newmem && oldmem)
188  memcpy(newmem, oldmem, min(oldlen, newlen));
189  kfree(oldmem);
190  return newmem;
191 }
192 
193 static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it)
194 {
195  struct comedi_bond_private *devpriv = dev->private;
196  int i;
197  struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS];
198 
199  memset(devs_opened, 0, sizeof(devs_opened));
200  devpriv->name[0] = 0;
201  /* Loop through all comedi devices specified on the command-line,
202  building our device list */
203  for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
204  char file[] = "/dev/comediXXXXXX";
205  int minor = it->options[i];
206  struct comedi_device *d;
207  int sdev = -1, nchans, tmp;
208  struct BondedDevice *bdev = NULL;
209 
210  if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
211  dev_err(dev->class_dev,
212  "Minor %d is invalid!\n", minor);
213  return 0;
214  }
215  if (minor == dev->minor) {
216  dev_err(dev->class_dev,
217  "Cannot bond this driver to itself!\n");
218  return 0;
219  }
220  if (devs_opened[minor]) {
221  dev_err(dev->class_dev,
222  "Minor %d specified more than once!\n", minor);
223  return 0;
224  }
225 
226  snprintf(file, sizeof(file), "/dev/comedi%u", minor);
227  file[sizeof(file) - 1] = 0;
228 
229  d = devs_opened[minor] = comedi_open(file);
230 
231  if (!d) {
232  dev_err(dev->class_dev,
233  "Minor %u could not be opened\n", minor);
234  return 0;
235  }
236 
237  /* Do DIO, as that's all we support now.. */
239  sdev + 1)) > -1) {
240  nchans = comedi_get_n_channels(d, sdev);
241  if (nchans <= 0) {
242  dev_err(dev->class_dev,
243  "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
244  nchans, minor, sdev);
245  return 0;
246  }
247  bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
248  if (!bdev) {
249  dev_err(dev->class_dev, "Out of memory\n");
250  return 0;
251  }
252  bdev->dev = d;
253  bdev->minor = minor;
254  bdev->subdev = sdev;
256  bdev->nchans = nchans;
257  bdev->chanid_offset = devpriv->nchans;
258 
259  /* map channel id's to BondedDevice * pointer.. */
260  while (nchans--)
261  devpriv->chanIdDevMap[devpriv->nchans++] = bdev;
262 
263  /* Now put bdev pointer at end of devpriv->devs array
264  * list.. */
265 
266  /* ergh.. ugly.. we need to realloc :( */
267  tmp = devpriv->ndevs * sizeof(bdev);
268  devpriv->devs =
269  Realloc(devpriv->devs,
270  ++devpriv->ndevs * sizeof(bdev), tmp);
271  if (!devpriv->devs) {
272  dev_err(dev->class_dev,
273  "Could not allocate memory. Out of memory?\n");
274  return 0;
275  }
276 
277  devpriv->devs[devpriv->ndevs - 1] = bdev;
278  {
280  char buf[20];
281  int left =
282  MAX_BOARD_NAME - strlen(devpriv->name) - 1;
283  snprintf(buf, sizeof(buf), "%d:%d ", dev->minor,
284  bdev->subdev);
285  buf[sizeof(buf) - 1] = 0;
286  strncat(devpriv->name, buf, left);
287  }
288 
289  }
290  }
291 
292  if (!devpriv->nchans) {
293  dev_err(dev->class_dev, "No channels found!\n");
294  return 0;
295  }
296 
297  return 1;
298 }
299 
300 static int bonding_attach(struct comedi_device *dev,
301  struct comedi_devconfig *it)
302 {
304  struct comedi_subdevice *s;
305  int ret;
306 
307  ret = alloc_private(dev, sizeof(*devpriv));
308  if (ret)
309  return ret;
310  devpriv = dev->private;
311 
312  /*
313  * Setup our bonding from config params.. sets up our private struct..
314  */
315  if (!doDevConfig(dev, it))
316  return -EINVAL;
317 
318  dev->board_name = devpriv->name;
319 
320  ret = comedi_alloc_subdevices(dev, 1);
321  if (ret)
322  return ret;
323 
324  s = &dev->subdevices[0];
325  s->type = COMEDI_SUBD_DIO;
327  s->n_chan = devpriv->nchans;
328  s->maxdata = 1;
330  s->insn_bits = bonding_dio_insn_bits;
331  s->insn_config = bonding_dio_insn_config;
332 
333  dev_info(dev->class_dev,
334  "%s: %s attached, %u channels from %u devices\n",
335  dev->driver->driver_name, dev->board_name,
336  devpriv->nchans, devpriv->ndevs);
337 
338  return 1;
339 }
340 
341 static void bonding_detach(struct comedi_device *dev)
342 {
343  struct comedi_bond_private *devpriv = dev->private;
344  unsigned long devs_closed = 0;
345 
346  if (devpriv) {
347  while (devpriv->ndevs-- && devpriv->devs) {
348  struct BondedDevice *bdev;
349 
350  bdev = devpriv->devs[devpriv->ndevs];
351  if (!bdev)
352  continue;
353  if (!(devs_closed & (0x1 << bdev->minor))) {
354  comedi_close(bdev->dev);
355  devs_closed |= (0x1 << bdev->minor);
356  }
357  kfree(bdev);
358  }
359  kfree(devpriv->devs);
360  devpriv->devs = NULL;
361  kfree(devpriv);
362  dev->private = NULL;
363  }
364 }
365 
366 static struct comedi_driver bonding_driver = {
367  .driver_name = "comedi_bond",
368  .module = THIS_MODULE,
369  .attach = bonding_attach,
370  .detach = bonding_detach,
371 };
372 module_comedi_driver(bonding_driver);
373 
374 MODULE_AUTHOR("Calin A. Culianu");
375 MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI "
376  "devices together as one. In the words of John Lennon: "
377  "'And the world will live as one...'");
378 MODULE_LICENSE("GPL");