1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * kcomedilib/kcomedilib.c 4 * a comedlib interface for kernel modules 5 * 6 * COMEDI - Linux Control and Measurement Device Interface 7 * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> 8 */ 9 10 #include <linux/module.h> 11 12 #include <linux/errno.h> 13 #include <linux/kernel.h> 14 #include <linux/sched.h> 15 #include <linux/fcntl.h> 16 #include <linux/mm.h> 17 #include <linux/io.h> 18 #include <linux/bitmap.h> 19 20 #include <linux/comedi.h> 21 #include <linux/comedi/comedidev.h> 22 #include <linux/comedi/comedilib.h> 23 24 MODULE_AUTHOR("David Schleef <ds@schleef.org>"); 25 MODULE_DESCRIPTION("Comedi kernel library"); 26 MODULE_LICENSE("GPL"); 27 28 static DEFINE_MUTEX(kcomedilib_to_from_lock); 29 30 /* 31 * Row index is the "to" node, column index is the "from" node, element value 32 * is the number of links from the "from" node to the "to" node. 33 */ 34 static unsigned char 35 kcomedilib_to_from[COMEDI_NUM_BOARD_MINORS][COMEDI_NUM_BOARD_MINORS]; 36 37 static bool kcomedilib_set_link_from_to(unsigned int from, unsigned int to) 38 { 39 DECLARE_BITMAP(destinations[2], COMEDI_NUM_BOARD_MINORS); 40 unsigned int cur = 0; 41 bool okay = true; 42 43 /* 44 * Allow "from" node to be out of range (no loop checking), 45 * but require "to" node to be in range. 46 */ 47 if (to >= COMEDI_NUM_BOARD_MINORS) 48 return false; 49 if (from >= COMEDI_NUM_BOARD_MINORS) 50 return true; 51 52 /* 53 * Check that kcomedilib_to_from[to][from] can be made non-zero 54 * without creating a loop. 55 * 56 * Termination of the loop-testing code relies on the assumption that 57 * kcomedilib_to_from[][] does not contain any loops. 58 * 59 * Start with a set destinations set containing "from" as the only 60 * element and work backwards looking for loops. 61 */ 62 bitmap_zero(destinations[cur], COMEDI_NUM_BOARD_MINORS); 63 set_bit(from, destinations[cur]); 64 mutex_lock(&kcomedilib_to_from_lock); 65 do { 66 unsigned int next = 1 - cur; 67 unsigned int t = 0; 68 69 if (test_bit(to, destinations[cur])) { 70 /* Loop detected. */ 71 okay = false; 72 break; 73 } 74 /* Create next set of destinations. */ 75 bitmap_zero(destinations[next], COMEDI_NUM_BOARD_MINORS); 76 while ((t = find_next_bit(destinations[cur], 77 COMEDI_NUM_BOARD_MINORS, 78 t)) < COMEDI_NUM_BOARD_MINORS) { 79 unsigned int f; 80 81 for (f = 0; f < COMEDI_NUM_BOARD_MINORS; f++) { 82 if (kcomedilib_to_from[t][f]) 83 set_bit(f, destinations[next]); 84 } 85 t++; 86 } 87 cur = next; 88 } while (!bitmap_empty(destinations[cur], COMEDI_NUM_BOARD_MINORS)); 89 if (okay) { 90 /* Allow a maximum of 255 links from "from" to "to". */ 91 if (kcomedilib_to_from[to][from] < 255) 92 kcomedilib_to_from[to][from]++; 93 else 94 okay = false; 95 } 96 mutex_unlock(&kcomedilib_to_from_lock); 97 return okay; 98 } 99 100 static void kcomedilib_clear_link_from_to(unsigned int from, unsigned int to) 101 { 102 if (to < COMEDI_NUM_BOARD_MINORS && from < COMEDI_NUM_BOARD_MINORS) { 103 mutex_lock(&kcomedilib_to_from_lock); 104 if (kcomedilib_to_from[to][from]) 105 kcomedilib_to_from[to][from]--; 106 mutex_unlock(&kcomedilib_to_from_lock); 107 } 108 } 109 110 /** 111 * comedi_open_from() - Open a COMEDI device from the kernel with loop checks 112 * @filename: Fake pathname of the form "/dev/comediN". 113 * @from: Device number it is being opened from (if in range). 114 * 115 * Converts @filename to a COMEDI device number and "opens" it if it exists 116 * and is attached to a low-level COMEDI driver. 117 * 118 * If @from is in range, refuse to open the device if doing so would form a 119 * loop of devices opening each other. There is also a limit of 255 on the 120 * number of concurrent opens from one device to another. 121 * 122 * Return: A pointer to the COMEDI device on success. 123 * Return %NULL on failure. 124 */ 125 struct comedi_device *comedi_open_from(const char *filename, int from) 126 { 127 struct comedi_device *dev, *retval = NULL; 128 unsigned int minor; 129 130 if (strncmp(filename, "/dev/comedi", 11) != 0) 131 return NULL; 132 133 if (kstrtouint(filename + 11, 0, &minor)) 134 return NULL; 135 136 if (minor >= COMEDI_NUM_BOARD_MINORS) 137 return NULL; 138 139 dev = comedi_dev_get_from_minor(minor); 140 if (!dev) 141 return NULL; 142 143 down_read(&dev->attach_lock); 144 if (dev->attached && kcomedilib_set_link_from_to(from, minor)) 145 retval = dev; 146 else 147 retval = NULL; 148 up_read(&dev->attach_lock); 149 150 if (!retval) 151 comedi_dev_put(dev); 152 153 return retval; 154 } 155 EXPORT_SYMBOL_GPL(comedi_open_from); 156 157 /** 158 * comedi_close_from() - Close a COMEDI device from the kernel with loop checks 159 * @dev: COMEDI device. 160 * @from: Device number it was opened from (if in range). 161 * 162 * Closes a COMEDI device previously opened by comedi_open_from(). 163 * 164 * If @from is in range, it should be match the one used by comedi_open_from(). 165 * 166 * Returns: 0 167 */ 168 int comedi_close_from(struct comedi_device *dev, int from) 169 { 170 kcomedilib_clear_link_from_to(from, dev->minor); 171 comedi_dev_put(dev); 172 return 0; 173 } 174 EXPORT_SYMBOL_GPL(comedi_close_from); 175 176 static int comedi_do_insn(struct comedi_device *dev, 177 struct comedi_insn *insn, 178 unsigned int *data) 179 { 180 struct comedi_subdevice *s; 181 int ret; 182 183 mutex_lock(&dev->mutex); 184 185 if (!dev->attached) { 186 ret = -EINVAL; 187 goto error; 188 } 189 190 /* a subdevice instruction */ 191 if (insn->subdev >= dev->n_subdevices) { 192 ret = -EINVAL; 193 goto error; 194 } 195 s = &dev->subdevices[insn->subdev]; 196 197 if (s->type == COMEDI_SUBD_UNUSED) { 198 dev_err(dev->class_dev, 199 "%d not usable subdevice\n", insn->subdev); 200 ret = -EIO; 201 goto error; 202 } 203 204 /* XXX check lock */ 205 206 ret = comedi_check_chanlist(s, 1, &insn->chanspec); 207 if (ret < 0) { 208 dev_err(dev->class_dev, "bad chanspec\n"); 209 ret = -EINVAL; 210 goto error; 211 } 212 213 if (s->busy) { 214 ret = -EBUSY; 215 goto error; 216 } 217 s->busy = dev; 218 219 switch (insn->insn) { 220 case INSN_BITS: 221 ret = s->insn_bits(dev, s, insn, data); 222 break; 223 case INSN_CONFIG: 224 /* XXX should check instruction length */ 225 ret = s->insn_config(dev, s, insn, data); 226 break; 227 default: 228 ret = -EINVAL; 229 break; 230 } 231 232 s->busy = NULL; 233 error: 234 235 mutex_unlock(&dev->mutex); 236 return ret; 237 } 238 239 int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev, 240 unsigned int chan, unsigned int *io) 241 { 242 struct comedi_insn insn; 243 unsigned int data[2]; 244 int ret; 245 246 memset(&insn, 0, sizeof(insn)); 247 insn.insn = INSN_CONFIG; 248 insn.n = 2; 249 insn.subdev = subdev; 250 insn.chanspec = CR_PACK(chan, 0, 0); 251 data[0] = INSN_CONFIG_DIO_QUERY; 252 data[1] = 0; 253 ret = comedi_do_insn(dev, &insn, data); 254 if (ret >= 0) 255 *io = data[1]; 256 return ret; 257 } 258 EXPORT_SYMBOL_GPL(comedi_dio_get_config); 259 260 int comedi_dio_config(struct comedi_device *dev, unsigned int subdev, 261 unsigned int chan, unsigned int io) 262 { 263 struct comedi_insn insn; 264 265 memset(&insn, 0, sizeof(insn)); 266 insn.insn = INSN_CONFIG; 267 insn.n = 1; 268 insn.subdev = subdev; 269 insn.chanspec = CR_PACK(chan, 0, 0); 270 271 return comedi_do_insn(dev, &insn, &io); 272 } 273 EXPORT_SYMBOL_GPL(comedi_dio_config); 274 275 int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev, 276 unsigned int mask, unsigned int *bits, 277 unsigned int base_channel) 278 { 279 struct comedi_insn insn; 280 unsigned int data[2]; 281 unsigned int n_chan; 282 unsigned int shift; 283 int ret; 284 285 base_channel = CR_CHAN(base_channel); 286 n_chan = comedi_get_n_channels(dev, subdev); 287 if (base_channel >= n_chan) 288 return -EINVAL; 289 290 memset(&insn, 0, sizeof(insn)); 291 insn.insn = INSN_BITS; 292 insn.chanspec = base_channel; 293 insn.n = 2; 294 insn.subdev = subdev; 295 296 data[0] = mask; 297 data[1] = *bits; 298 299 /* 300 * Most drivers ignore the base channel in insn->chanspec. 301 * Fix this here if the subdevice has <= 32 channels. 302 */ 303 if (n_chan <= 32) { 304 shift = base_channel; 305 if (shift) { 306 insn.chanspec = 0; 307 data[0] <<= shift; 308 data[1] <<= shift; 309 } 310 } else { 311 shift = 0; 312 } 313 314 ret = comedi_do_insn(dev, &insn, data); 315 *bits = data[1] >> shift; 316 return ret; 317 } 318 EXPORT_SYMBOL_GPL(comedi_dio_bitfield2); 319 320 int comedi_find_subdevice_by_type(struct comedi_device *dev, int type, 321 unsigned int subd) 322 { 323 struct comedi_subdevice *s; 324 int ret = -ENODEV; 325 326 down_read(&dev->attach_lock); 327 if (dev->attached) 328 for (; subd < dev->n_subdevices; subd++) { 329 s = &dev->subdevices[subd]; 330 if (s->type == type) { 331 ret = subd; 332 break; 333 } 334 } 335 up_read(&dev->attach_lock); 336 return ret; 337 } 338 EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type); 339 340 int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice) 341 { 342 int n; 343 344 down_read(&dev->attach_lock); 345 if (!dev->attached || subdevice >= dev->n_subdevices) 346 n = 0; 347 else 348 n = dev->subdevices[subdevice].n_chan; 349 up_read(&dev->attach_lock); 350 351 return n; 352 } 353 EXPORT_SYMBOL_GPL(comedi_get_n_channels); 354