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 | |
19 | #include <linux/comedi.h> |
20 | #include <linux/comedi/comedidev.h> |
21 | #include <linux/comedi/comedilib.h> |
22 | |
23 | MODULE_AUTHOR("David Schleef <ds@schleef.org>" ); |
24 | MODULE_DESCRIPTION("Comedi kernel library" ); |
25 | MODULE_LICENSE("GPL" ); |
26 | |
27 | struct comedi_device *comedi_open(const char *filename) |
28 | { |
29 | struct comedi_device *dev, *retval = NULL; |
30 | unsigned int minor; |
31 | |
32 | if (strncmp(filename, "/dev/comedi" , 11) != 0) |
33 | return NULL; |
34 | |
35 | if (kstrtouint(s: filename + 11, base: 0, res: &minor)) |
36 | return NULL; |
37 | |
38 | if (minor >= COMEDI_NUM_BOARD_MINORS) |
39 | return NULL; |
40 | |
41 | dev = comedi_dev_get_from_minor(minor); |
42 | if (!dev) |
43 | return NULL; |
44 | |
45 | down_read(sem: &dev->attach_lock); |
46 | if (dev->attached) |
47 | retval = dev; |
48 | else |
49 | retval = NULL; |
50 | up_read(sem: &dev->attach_lock); |
51 | |
52 | if (!retval) |
53 | comedi_dev_put(dev); |
54 | |
55 | return retval; |
56 | } |
57 | EXPORT_SYMBOL_GPL(comedi_open); |
58 | |
59 | int comedi_close(struct comedi_device *dev) |
60 | { |
61 | comedi_dev_put(dev); |
62 | return 0; |
63 | } |
64 | EXPORT_SYMBOL_GPL(comedi_close); |
65 | |
66 | static int comedi_do_insn(struct comedi_device *dev, |
67 | struct comedi_insn *insn, |
68 | unsigned int *data) |
69 | { |
70 | struct comedi_subdevice *s; |
71 | int ret; |
72 | |
73 | mutex_lock(&dev->mutex); |
74 | |
75 | if (!dev->attached) { |
76 | ret = -EINVAL; |
77 | goto error; |
78 | } |
79 | |
80 | /* a subdevice instruction */ |
81 | if (insn->subdev >= dev->n_subdevices) { |
82 | ret = -EINVAL; |
83 | goto error; |
84 | } |
85 | s = &dev->subdevices[insn->subdev]; |
86 | |
87 | if (s->type == COMEDI_SUBD_UNUSED) { |
88 | dev_err(dev->class_dev, |
89 | "%d not usable subdevice\n" , insn->subdev); |
90 | ret = -EIO; |
91 | goto error; |
92 | } |
93 | |
94 | /* XXX check lock */ |
95 | |
96 | ret = comedi_check_chanlist(s, n: 1, chanlist: &insn->chanspec); |
97 | if (ret < 0) { |
98 | dev_err(dev->class_dev, "bad chanspec\n" ); |
99 | ret = -EINVAL; |
100 | goto error; |
101 | } |
102 | |
103 | if (s->busy) { |
104 | ret = -EBUSY; |
105 | goto error; |
106 | } |
107 | s->busy = dev; |
108 | |
109 | switch (insn->insn) { |
110 | case INSN_BITS: |
111 | ret = s->insn_bits(dev, s, insn, data); |
112 | break; |
113 | case INSN_CONFIG: |
114 | /* XXX should check instruction length */ |
115 | ret = s->insn_config(dev, s, insn, data); |
116 | break; |
117 | default: |
118 | ret = -EINVAL; |
119 | break; |
120 | } |
121 | |
122 | s->busy = NULL; |
123 | error: |
124 | |
125 | mutex_unlock(lock: &dev->mutex); |
126 | return ret; |
127 | } |
128 | |
129 | int comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev, |
130 | unsigned int chan, unsigned int *io) |
131 | { |
132 | struct comedi_insn insn; |
133 | unsigned int data[2]; |
134 | int ret; |
135 | |
136 | memset(&insn, 0, sizeof(insn)); |
137 | insn.insn = INSN_CONFIG; |
138 | insn.n = 2; |
139 | insn.subdev = subdev; |
140 | insn.chanspec = CR_PACK(chan, 0, 0); |
141 | data[0] = INSN_CONFIG_DIO_QUERY; |
142 | data[1] = 0; |
143 | ret = comedi_do_insn(dev, insn: &insn, data); |
144 | if (ret >= 0) |
145 | *io = data[1]; |
146 | return ret; |
147 | } |
148 | EXPORT_SYMBOL_GPL(comedi_dio_get_config); |
149 | |
150 | int comedi_dio_config(struct comedi_device *dev, unsigned int subdev, |
151 | unsigned int chan, unsigned int io) |
152 | { |
153 | struct comedi_insn insn; |
154 | |
155 | memset(&insn, 0, sizeof(insn)); |
156 | insn.insn = INSN_CONFIG; |
157 | insn.n = 1; |
158 | insn.subdev = subdev; |
159 | insn.chanspec = CR_PACK(chan, 0, 0); |
160 | |
161 | return comedi_do_insn(dev, insn: &insn, data: &io); |
162 | } |
163 | EXPORT_SYMBOL_GPL(comedi_dio_config); |
164 | |
165 | int comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev, |
166 | unsigned int mask, unsigned int *bits, |
167 | unsigned int base_channel) |
168 | { |
169 | struct comedi_insn insn; |
170 | unsigned int data[2]; |
171 | unsigned int n_chan; |
172 | unsigned int shift; |
173 | int ret; |
174 | |
175 | base_channel = CR_CHAN(base_channel); |
176 | n_chan = comedi_get_n_channels(dev, subdevice: subdev); |
177 | if (base_channel >= n_chan) |
178 | return -EINVAL; |
179 | |
180 | memset(&insn, 0, sizeof(insn)); |
181 | insn.insn = INSN_BITS; |
182 | insn.chanspec = base_channel; |
183 | insn.n = 2; |
184 | insn.subdev = subdev; |
185 | |
186 | data[0] = mask; |
187 | data[1] = *bits; |
188 | |
189 | /* |
190 | * Most drivers ignore the base channel in insn->chanspec. |
191 | * Fix this here if the subdevice has <= 32 channels. |
192 | */ |
193 | if (n_chan <= 32) { |
194 | shift = base_channel; |
195 | if (shift) { |
196 | insn.chanspec = 0; |
197 | data[0] <<= shift; |
198 | data[1] <<= shift; |
199 | } |
200 | } else { |
201 | shift = 0; |
202 | } |
203 | |
204 | ret = comedi_do_insn(dev, insn: &insn, data); |
205 | *bits = data[1] >> shift; |
206 | return ret; |
207 | } |
208 | EXPORT_SYMBOL_GPL(comedi_dio_bitfield2); |
209 | |
210 | int comedi_find_subdevice_by_type(struct comedi_device *dev, int type, |
211 | unsigned int subd) |
212 | { |
213 | struct comedi_subdevice *s; |
214 | int ret = -ENODEV; |
215 | |
216 | down_read(sem: &dev->attach_lock); |
217 | if (dev->attached) |
218 | for (; subd < dev->n_subdevices; subd++) { |
219 | s = &dev->subdevices[subd]; |
220 | if (s->type == type) { |
221 | ret = subd; |
222 | break; |
223 | } |
224 | } |
225 | up_read(sem: &dev->attach_lock); |
226 | return ret; |
227 | } |
228 | EXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type); |
229 | |
230 | int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice) |
231 | { |
232 | int n; |
233 | |
234 | down_read(sem: &dev->attach_lock); |
235 | if (!dev->attached || subdevice >= dev->n_subdevices) |
236 | n = 0; |
237 | else |
238 | n = dev->subdevices[subdevice].n_chan; |
239 | up_read(sem: &dev->attach_lock); |
240 | |
241 | return n; |
242 | } |
243 | EXPORT_SYMBOL_GPL(comedi_get_n_channels); |
244 | |
245 | static int __init kcomedilib_module_init(void) |
246 | { |
247 | return 0; |
248 | } |
249 | |
250 | static void __exit kcomedilib_module_exit(void) |
251 | { |
252 | } |
253 | |
254 | module_init(kcomedilib_module_init); |
255 | module_exit(kcomedilib_module_exit); |
256 | |