blob: 7d0116bcb9ff1a6acc94d1473e2c335175f64131 [file] [log] [blame]
David Schleefed9eccb2008-11-04 20:29:31 -08001/*
2 comedi/comedi_compat32.c
3 32-bit ioctl compatibility for 64-bit comedi kernel module.
4
5 Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
6 Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
7
8 COMEDI - Linux Control and Measurement Device Interface
9 Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
10
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24
25*/
26
27#define __NO_VERSION__
28#include "comedi.h"
29#include <linux/smp_lock.h>
30#include <asm/uaccess.h>
31
32#include "comedi_compat32.h"
33
34#ifdef CONFIG_COMPAT
35
36#ifndef HAVE_COMPAT_IOCTL
37#include <linux/ioctl32.h> /* for (un)register_ioctl32_conversion */
38#endif
39
40#define COMEDI32_CHANINFO _IOR(CIO,3,comedi32_chaninfo)
41#define COMEDI32_RANGEINFO _IOR(CIO,8,comedi32_rangeinfo)
42/* N.B. COMEDI32_CMD and COMEDI_CMD ought to use _IOWR, not _IOR.
43 * It's too late to change it now, but it only affects the command number. */
44#define COMEDI32_CMD _IOR(CIO,9,comedi32_cmd)
45/* N.B. COMEDI32_CMDTEST and COMEDI_CMDTEST ought to use _IOWR, not _IOR.
46 * It's too late to change it now, but it only affects the command number. */
47#define COMEDI32_CMDTEST _IOR(CIO,10,comedi32_cmd)
48#define COMEDI32_INSNLIST _IOR(CIO,11,comedi32_insnlist)
49#define COMEDI32_INSN _IOR(CIO,12,comedi32_insn)
50
51typedef struct comedi32_chaninfo_struct {
52 unsigned int subdev;
53 compat_uptr_t maxdata_list; /* 32-bit 'lsampl_t *' */
54 compat_uptr_t flaglist; /* 32-bit 'unsigned int *' */
55 compat_uptr_t rangelist; /* 32-bit 'unsigned int *' */
56 unsigned int unused[4];
57} comedi32_chaninfo;
58
59typedef struct comedi32_rangeinfo_struct {
60 unsigned int range_type;
61 compat_uptr_t range_ptr; /* 32-bit 'void *' */
62} comedi32_rangeinfo;
63
64typedef struct comedi32_cmd_struct {
65 unsigned int subdev;
66 unsigned int flags;
67 unsigned int start_src;
68 unsigned int start_arg;
69 unsigned int scan_begin_src;
70 unsigned int scan_begin_arg;
71 unsigned int convert_src;
72 unsigned int convert_arg;
73 unsigned int scan_end_src;
74 unsigned int scan_end_arg;
75 unsigned int stop_src;
76 unsigned int stop_arg;
77 compat_uptr_t chanlist; /* 32-bit 'unsigned int *' */
78 unsigned int chanlist_len;
79 compat_uptr_t data; /* 32-bit 'sampl_t *' */
80 unsigned int data_len;
81} comedi32_cmd;
82
83typedef struct comedi32_insn_struct {
84 unsigned int insn;
85 unsigned int n;
86 compat_uptr_t data; /* 32-bit 'lsampl_t *' */
87 unsigned int subdev;
88 unsigned int chanspec;
89 unsigned int unused[3];
90} comedi32_insn;
91
92typedef struct comedi32_insnlist_struct {
93 unsigned int n_insns;
94 compat_uptr_t insns; /* 32-bit 'comedi_insn *' */
95} comedi32_insnlist;
96
97/* Handle translated ioctl. */
98static int translated_ioctl(struct file *file, unsigned int cmd,
99 unsigned long arg)
100{
101 if (!file->f_op) {
102 return -ENOTTY;
103 }
104#ifdef HAVE_UNLOCKED_IOCTL
105 if (file->f_op->unlocked_ioctl) {
106 int rc = (int)(*file->f_op->unlocked_ioctl)(file, cmd, arg);
107 if (rc == -ENOIOCTLCMD) {
108 rc = -ENOTTY;
109 }
110 return rc;
111 }
112#endif
113 if (file->f_op->ioctl) {
114 int rc;
115 lock_kernel();
116 rc = (*file->f_op->ioctl)(file->f_dentry->d_inode,
117 file, cmd, arg);
118 unlock_kernel();
119 return rc;
120 }
121 return -ENOTTY;
122}
123
124/* Handle 32-bit COMEDI_CHANINFO ioctl. */
125static int compat_chaninfo(struct file *file, unsigned long arg)
126{
127 comedi_chaninfo __user *chaninfo;
128 comedi32_chaninfo __user *chaninfo32;
129 int err;
130 union {
131 unsigned int uint;
132 compat_uptr_t uptr;
133 } temp;
134
135 chaninfo32 = compat_ptr(arg);
136 chaninfo = compat_alloc_user_space(sizeof(*chaninfo));
137
138 /* Copy chaninfo structure. Ignore unused members. */
139 if (!access_ok(VERIFY_READ, chaninfo32, sizeof(*chaninfo32))
140 || !access_ok(VERIFY_WRITE, chaninfo,
141 sizeof(*chaninfo))) {
142 return -EFAULT;
143 }
144 err = 0;
145 err |= __get_user(temp.uint, &chaninfo32->subdev);
146 err |= __put_user(temp.uint, &chaninfo->subdev);
147 err |= __get_user(temp.uptr, &chaninfo32->maxdata_list);
148 err |= __put_user(compat_ptr(temp.uptr), &chaninfo->maxdata_list);
149 err |= __get_user(temp.uptr, &chaninfo32->flaglist);
150 err |= __put_user(compat_ptr(temp.uptr), &chaninfo->flaglist);
151 err |= __get_user(temp.uptr, &chaninfo32->rangelist);
152 err |= __put_user(compat_ptr(temp.uptr), &chaninfo->rangelist);
153 if (err) {
154 return -EFAULT;
155 }
156
157 return translated_ioctl(file, COMEDI_CHANINFO, (unsigned long)chaninfo);
158}
159
160/* Handle 32-bit COMEDI_RANGEINFO ioctl. */
161static int compat_rangeinfo(struct file *file, unsigned long arg)
162{
163 comedi_rangeinfo __user *rangeinfo;
164 comedi32_rangeinfo __user *rangeinfo32;
165 int err;
166 union {
167 unsigned int uint;
168 compat_uptr_t uptr;
169 } temp;
170
171 rangeinfo32 = compat_ptr(arg);
172 rangeinfo = compat_alloc_user_space(sizeof(*rangeinfo));
173
174 /* Copy rangeinfo structure. */
175 if (!access_ok(VERIFY_READ, rangeinfo32, sizeof(*rangeinfo32))
176 || !access_ok(VERIFY_WRITE, rangeinfo,
177 sizeof(*rangeinfo))) {
178 return -EFAULT;
179 }
180 err = 0;
181 err |= __get_user(temp.uint, &rangeinfo32->range_type);
182 err |= __put_user(temp.uint, &rangeinfo->range_type);
183 err |= __get_user(temp.uptr, &rangeinfo32->range_ptr);
184 err |= __put_user(compat_ptr(temp.uptr), &rangeinfo->range_ptr);
185 if (err) {
186 return -EFAULT;
187 }
188
189 return translated_ioctl(file, COMEDI_RANGEINFO,
190 (unsigned long)rangeinfo);
191}
192
193/* Copy 32-bit cmd structure to native cmd structure. */
194static int get_compat_cmd(comedi_cmd __user *cmd,
195 comedi32_cmd __user *cmd32)
196{
197 int err;
198 union {
199 unsigned int uint;
200 compat_uptr_t uptr;
201 } temp;
202
203 /* Copy cmd structure. */
204 if (!access_ok(VERIFY_READ, cmd32, sizeof(*cmd32))
205 || !access_ok(VERIFY_WRITE, cmd, sizeof(*cmd))) {
206 return -EFAULT;
207 }
208 err = 0;
209 err |= __get_user(temp.uint, &cmd32->subdev);
210 err |= __put_user(temp.uint, &cmd->subdev);
211 err |= __get_user(temp.uint, &cmd32->flags);
212 err |= __put_user(temp.uint, &cmd->flags);
213 err |= __get_user(temp.uint, &cmd32->start_src);
214 err |= __put_user(temp.uint, &cmd->start_src);
215 err |= __get_user(temp.uint, &cmd32->start_arg);
216 err |= __put_user(temp.uint, &cmd->start_arg);
217 err |= __get_user(temp.uint, &cmd32->scan_begin_src);
218 err |= __put_user(temp.uint, &cmd->scan_begin_src);
219 err |= __get_user(temp.uint, &cmd32->scan_begin_arg);
220 err |= __put_user(temp.uint, &cmd->scan_begin_arg);
221 err |= __get_user(temp.uint, &cmd32->convert_src);
222 err |= __put_user(temp.uint, &cmd->convert_src);
223 err |= __get_user(temp.uint, &cmd32->convert_arg);
224 err |= __put_user(temp.uint, &cmd->convert_arg);
225 err |= __get_user(temp.uint, &cmd32->scan_end_src);
226 err |= __put_user(temp.uint, &cmd->scan_end_src);
227 err |= __get_user(temp.uint, &cmd32->scan_end_arg);
228 err |= __put_user(temp.uint, &cmd->scan_end_arg);
229 err |= __get_user(temp.uint, &cmd32->stop_src);
230 err |= __put_user(temp.uint, &cmd->stop_src);
231 err |= __get_user(temp.uint, &cmd32->stop_arg);
232 err |= __put_user(temp.uint, &cmd->stop_arg);
233 err |= __get_user(temp.uptr, &cmd32->chanlist);
234 err |= __put_user(compat_ptr(temp.uptr), &cmd->chanlist);
235 err |= __get_user(temp.uint, &cmd32->chanlist_len);
236 err |= __put_user(temp.uint, &cmd->chanlist_len);
237 err |= __get_user(temp.uptr, &cmd32->data);
238 err |= __put_user(compat_ptr(temp.uptr), &cmd->data);
239 err |= __get_user(temp.uint, &cmd32->data_len);
240 err |= __put_user(temp.uint, &cmd->data_len);
241 return err ? -EFAULT : 0;
242}
243
244/* Copy native cmd structure to 32-bit cmd structure. */
245static int put_compat_cmd(comedi32_cmd __user *cmd32, comedi_cmd __user *cmd)
246{
247 int err;
248 unsigned int temp;
249
250 /* Copy back most of cmd structure. */
251 /* Assume the pointer values are already valid. */
252 /* (Could use ptr_to_compat() to set them, but that wasn't implemented
253 * until kernel version 2.6.11.) */
254 if (!access_ok(VERIFY_READ, cmd, sizeof(*cmd))
255 || !access_ok(VERIFY_WRITE, cmd32, sizeof(*cmd32))) {
256 return -EFAULT;
257 }
258 err = 0;
259 err |= __get_user(temp, &cmd->subdev);
260 err |= __put_user(temp, &cmd32->subdev);
261 err |= __get_user(temp, &cmd->flags);
262 err |= __put_user(temp, &cmd32->flags);
263 err |= __get_user(temp, &cmd->start_src);
264 err |= __put_user(temp, &cmd32->start_src);
265 err |= __get_user(temp, &cmd->start_arg);
266 err |= __put_user(temp, &cmd32->start_arg);
267 err |= __get_user(temp, &cmd->scan_begin_src);
268 err |= __put_user(temp, &cmd32->scan_begin_src);
269 err |= __get_user(temp, &cmd->scan_begin_arg);
270 err |= __put_user(temp, &cmd32->scan_begin_arg);
271 err |= __get_user(temp, &cmd->convert_src);
272 err |= __put_user(temp, &cmd32->convert_src);
273 err |= __get_user(temp, &cmd->convert_arg);
274 err |= __put_user(temp, &cmd32->convert_arg);
275 err |= __get_user(temp, &cmd->scan_end_src);
276 err |= __put_user(temp, &cmd32->scan_end_src);
277 err |= __get_user(temp, &cmd->scan_end_arg);
278 err |= __put_user(temp, &cmd32->scan_end_arg);
279 err |= __get_user(temp, &cmd->stop_src);
280 err |= __put_user(temp, &cmd32->stop_src);
281 err |= __get_user(temp, &cmd->stop_arg);
282 err |= __put_user(temp, &cmd32->stop_arg);
283 /* Assume chanlist pointer is unchanged. */
284 err |= __get_user(temp, &cmd->chanlist_len);
285 err |= __put_user(temp, &cmd32->chanlist_len);
286 /* Assume data pointer is unchanged. */
287 err |= __get_user(temp, &cmd->data_len);
288 err |= __put_user(temp, &cmd32->data_len);
289 return err ? -EFAULT : 0;
290}
291
292/* Handle 32-bit COMEDI_CMD ioctl. */
293static int compat_cmd(struct file *file, unsigned long arg)
294{
295 comedi_cmd __user *cmd;
296 comedi32_cmd __user *cmd32;
297 int rc;
298
299 cmd32 = compat_ptr(arg);
300 cmd = compat_alloc_user_space(sizeof(*cmd));
301
302 rc = get_compat_cmd(cmd, cmd32);
303 if (rc) {
304 return rc;
305 }
306
307 return translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd);
308}
309
310/* Handle 32-bit COMEDI_CMDTEST ioctl. */
311static int compat_cmdtest(struct file *file, unsigned long arg)
312{
313 comedi_cmd __user *cmd;
314 comedi32_cmd __user *cmd32;
315 int rc, err;
316
317 cmd32 = compat_ptr(arg);
318 cmd = compat_alloc_user_space(sizeof(*cmd));
319
320 rc = get_compat_cmd(cmd, cmd32);
321 if (rc) {
322 return rc;
323 }
324
325 rc = translated_ioctl(file, COMEDI_CMDTEST, (unsigned long)cmd);
326 if (rc < 0) {
327 return rc;
328 }
329
330 err = put_compat_cmd(cmd32, cmd);
331 if (err) {
332 rc = err;
333 }
334 return rc;
335}
336
337/* Copy 32-bit insn structure to native insn structure. */
338static int get_compat_insn(comedi_insn __user *insn,
339 comedi32_insn __user *insn32)
340{
341 int err;
342 union {
343 unsigned int uint;
344 compat_uptr_t uptr;
345 } temp;
346
347 /* Copy insn structure. Ignore the unused members. */
348 err = 0;
349 if (!access_ok(VERIFY_READ, insn32, sizeof(*insn32))
350 || !access_ok(VERIFY_WRITE, insn, sizeof(*insn))) {
351 return -EFAULT;
352 }
353 err |= __get_user(temp.uint, &insn32->insn);
354 err |= __put_user(temp.uint, &insn->insn);
355 err |= __get_user(temp.uint, &insn32->n);
356 err |= __put_user(temp.uint, &insn->n);
357 err |= __get_user(temp.uptr, &insn32->data);
358 err |= __put_user(compat_ptr(temp.uptr), &insn->data);
359 err |= __get_user(temp.uint, &insn32->subdev);
360 err |= __put_user(temp.uint, &insn->subdev);
361 err |= __get_user(temp.uint, &insn32->chanspec);
362 err |= __put_user(temp.uint, &insn->chanspec);
363 return err ? -EFAULT : 0;
364}
365
366/* Handle 32-bit COMEDI_INSNLIST ioctl. */
367static int compat_insnlist(struct file *file, unsigned long arg)
368{
369 struct combined_insnlist {
370 comedi_insnlist insnlist;
371 comedi_insn insn[1];
372 } __user *s;
373 comedi32_insnlist __user *insnlist32;
374 comedi32_insn __user *insn32;
375 compat_uptr_t uptr;
376 unsigned int n_insns, n;
377 int err, rc;
378
379 insnlist32 = compat_ptr(arg);
380
381 /* Get 32-bit insnlist structure. */
382 if (!access_ok(VERIFY_READ, insnlist32, sizeof(*insnlist32))) {
383 return -EFAULT;
384 }
385 err = 0;
386 err |= __get_user(n_insns, &insnlist32->n_insns);
387 err |= __get_user(uptr, &insnlist32->insns);
388 insn32 = compat_ptr(uptr);
389 if (err) {
390 return -EFAULT;
391 }
392
393 /* Allocate user memory to copy insnlist and insns into. */
394 s = compat_alloc_user_space(offsetof(struct combined_insnlist,
395 insn[n_insns]));
396
397 /* Set native insnlist structure. */
398 if (!access_ok(VERIFY_WRITE, &s->insnlist, sizeof(s->insnlist))) {
399 return -EFAULT;
400 }
401 err |= __put_user(n_insns, &s->insnlist.n_insns);
402 err |= __put_user(&s->insn[0], &s->insnlist.insns);
403 if (err) {
404 return -EFAULT;
405 }
406
407 /* Copy insn structures. */
408 for (n = 0; n < n_insns; n++) {
409 rc = get_compat_insn(&s->insn[n], &insn32[n]);
410 if (rc) {
411 return rc;
412 }
413 }
414
415 return translated_ioctl(file, COMEDI_INSNLIST,
416 (unsigned long)&s->insnlist);
417}
418
419/* Handle 32-bit COMEDI_INSN ioctl. */
420static int compat_insn(struct file *file, unsigned long arg)
421{
422 comedi_insn __user *insn;
423 comedi32_insn __user *insn32;
424 int rc;
425
426 insn32 = compat_ptr(arg);
427 insn = compat_alloc_user_space(sizeof(*insn));
428
429 rc = get_compat_insn(insn, insn32);
430 if (rc) {
431 return rc;
432 }
433
434 return translated_ioctl(file, COMEDI_INSN, (unsigned long)insn);
435}
436
437/* Process untranslated ioctl. */
438/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */
439static inline int raw_ioctl(struct file *file, unsigned int cmd,
440 unsigned long arg)
441{
442 int rc;
443
444 switch (cmd) {
445 case COMEDI_DEVCONFIG:
446 case COMEDI_DEVINFO:
447 case COMEDI_SUBDINFO:
448 case COMEDI_BUFCONFIG:
449 case COMEDI_BUFINFO:
450 /* Just need to translate the pointer argument. */
451 arg = (unsigned long)compat_ptr(arg);
452 rc = translated_ioctl(file, cmd, arg);
453 break;
454 case COMEDI_LOCK:
455 case COMEDI_UNLOCK:
456 case COMEDI_CANCEL:
457 case COMEDI_POLL:
458 /* No translation needed. */
459 rc = translated_ioctl(file, cmd, arg);
460 break;
461 case COMEDI32_CHANINFO:
462 rc = compat_chaninfo(file, arg);
463 break;
464 case COMEDI32_RANGEINFO:
465 rc = compat_rangeinfo(file, arg);
466 break;
467 case COMEDI32_CMD:
468 rc = compat_cmd(file, arg);
469 break;
470 case COMEDI32_CMDTEST:
471 rc = compat_cmdtest(file, arg);
472 break;
473 case COMEDI32_INSNLIST:
474 rc = compat_insnlist(file, arg);
475 break;
476 case COMEDI32_INSN:
477 rc = compat_insn(file, arg);
478 break;
479 default:
480 rc = -ENOIOCTLCMD;
481 break;
482 }
483 return rc;
484}
485
486#ifdef HAVE_COMPAT_IOCTL /* defined in <linux/fs.h> 2.6.11 onwards */
487
488/* compat_ioctl file operation. */
489/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */
490long comedi_compat_ioctl(struct file *file, unsigned int cmd,
491 unsigned long arg)
492{
493 return raw_ioctl(file, cmd, arg);
494}
495
496#else /* HAVE_COMPAT_IOCTL */
497
498/*
499 * Brain-dead ioctl compatibility for 2.6.10 and earlier.
500 *
501 * It's brain-dead because cmd numbers need to be unique system-wide!
502 * The comedi driver could end up attempting to execute ioctls for non-Comedi
503 * devices because it registered the system-wide cmd code first. Similarly,
504 * another driver could end up attempting to execute ioctls for a Comedi
505 * device because it registered the cmd code first. Chaos ensues.
506 */
507
508/* Handler for all 32-bit ioctl codes registered by this driver. */
509static int mapped_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg,
510 struct file *file)
511{
512 int rc;
513
514 /* Make sure we are dealing with a Comedi device. */
515 if (imajor(file->f_dentry->d_inode) != COMEDI_MAJOR) {
516 return -ENOTTY;
517 }
518 rc = raw_ioctl(file, cmd, arg);
519 /* Do not return -ENOIOCTLCMD. */
520 if (rc == -ENOIOCTLCMD) {
521 rc = -ENOTTY;
522 }
523 return rc;
524}
525
526struct ioctl32_map {
527 unsigned int cmd;
528 int (*handler)(unsigned int, unsigned int, unsigned long,
529 struct file *);
530 int registered;
531};
532
533static struct ioctl32_map comedi_ioctl32_map[] = {
534 { COMEDI_DEVCONFIG, mapped_ioctl, 0 },
535 { COMEDI_DEVINFO, mapped_ioctl, 0 },
536 { COMEDI_SUBDINFO, mapped_ioctl, 0 },
537 { COMEDI_BUFCONFIG, mapped_ioctl, 0 },
538 { COMEDI_BUFINFO, mapped_ioctl, 0 },
539 { COMEDI_LOCK, mapped_ioctl, 0 },
540 { COMEDI_UNLOCK, mapped_ioctl, 0 },
541 { COMEDI_CANCEL, mapped_ioctl, 0 },
542 { COMEDI_POLL, mapped_ioctl, 0 },
543 { COMEDI32_CHANINFO, mapped_ioctl, 0 },
544 { COMEDI32_RANGEINFO, mapped_ioctl, 0 },
545 { COMEDI32_CMD, mapped_ioctl, 0 },
546 { COMEDI32_CMDTEST, mapped_ioctl, 0 },
547 { COMEDI32_INSNLIST, mapped_ioctl, 0 },
548 { COMEDI32_INSN, mapped_ioctl, 0 },
549};
550
551#define NUM_IOCTL32_MAPS ARRAY_SIZE(comedi_ioctl32_map)
552
553/* Register system-wide 32-bit ioctl handlers. */
554void comedi_register_ioctl32(void)
555{
556 int n, rc;
557
558 for (n = 0; n < NUM_IOCTL32_MAPS; n++) {
559 rc = register_ioctl32_conversion(comedi_ioctl32_map[n].cmd,
560 comedi_ioctl32_map[n].handler);
561 if (rc) {
562 printk(KERN_WARNING
563 "comedi: failed to register 32-bit "
564 "compatible ioctl handler for 0x%X - "
565 "expect bad things to happen!\n",
566 comedi_ioctl32_map[n].cmd);
567 }
568 comedi_ioctl32_map[n].registered = !rc;
569 }
570}
571
572/* Unregister system-wide 32-bit ioctl translations. */
573void comedi_unregister_ioctl32(void)
574{
575 int n, rc;
576
577 for (n = 0; n < NUM_IOCTL32_MAPS; n++) {
578 if (comedi_ioctl32_map[n].registered) {
579 rc = unregister_ioctl32_conversion(
580 comedi_ioctl32_map[n].cmd,
581 comedi_ioctl32_map[n].handler);
582 if (rc) {
583 printk(KERN_ERR
584 "comedi: failed to unregister 32-bit "
585 "compatible ioctl handler for 0x%X - "
586 "expect kernel Oops!\n",
587 comedi_ioctl32_map[n].cmd);
588 } else {
589 comedi_ioctl32_map[n].registered = 0;
590 }
591 }
592 }
593}
594
595#endif /* HAVE_COMPAT_IOCTL */
596
597#endif /* CONFIG_COMPAT */