comedi_compat32.c revision bf8db92eb3c52ccc3c28ee8d382750a7d4dc758d
1/*
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, (struct comedi32_rangeinfo_struct))
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, (struct comedi32_cmd_struct))
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, (struct comedi32_cmd_struct))
48#define COMEDI32_INSNLIST _IOR(CIO, 11, (struct comedi32_insnlist_struct))
49#define COMEDI32_INSN _IOR(CIO, 12, (struct comedi32_insn_struct))
50
51struct 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};
58
59struct comedi32_rangeinfo_struct {
60	unsigned int range_type;
61	compat_uptr_t range_ptr;	/* 32-bit 'void *' */
62};
63
64struct 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};
82
83struct 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};
91
92struct comedi32_insnlist_struct {
93	unsigned int n_insns;
94	compat_uptr_t insns;		/* 32-bit 'comedi_insn *' */
95};
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		return rc;
110	}
111#endif
112	if (file->f_op->ioctl) {
113		int rc;
114		lock_kernel();
115		rc = (*file->f_op->ioctl)(file->f_dentry->d_inode,
116				file, cmd, arg);
117		unlock_kernel();
118		return rc;
119	}
120	return -ENOTTY;
121}
122
123/* Handle 32-bit COMEDI_CHANINFO ioctl. */
124static int compat_chaninfo(struct file *file, unsigned long arg)
125{
126	struct comedi_chaninfo_struct __user *chaninfo;
127	struct comedi32_chaninfo_struct __user *chaninfo32;
128	int err;
129	union {
130		unsigned int uint;
131		compat_uptr_t uptr;
132	} temp;
133
134	chaninfo32 = compat_ptr(arg);
135	chaninfo = compat_alloc_user_space(sizeof(*chaninfo));
136
137	/* Copy chaninfo structure.  Ignore unused members. */
138	if (!access_ok(VERIFY_READ, chaninfo32, sizeof(*chaninfo32))
139			|| !access_ok(VERIFY_WRITE, chaninfo,
140				sizeof(*chaninfo))) {
141		return -EFAULT;
142	}
143	err = 0;
144	err |= __get_user(temp.uint, &chaninfo32->subdev);
145	err |= __put_user(temp.uint, &chaninfo->subdev);
146	err |= __get_user(temp.uptr, &chaninfo32->maxdata_list);
147	err |= __put_user(compat_ptr(temp.uptr), &chaninfo->maxdata_list);
148	err |= __get_user(temp.uptr, &chaninfo32->flaglist);
149	err |= __put_user(compat_ptr(temp.uptr), &chaninfo->flaglist);
150	err |= __get_user(temp.uptr, &chaninfo32->rangelist);
151	err |= __put_user(compat_ptr(temp.uptr), &chaninfo->rangelist);
152	if (err)
153		return -EFAULT;
154
155	return translated_ioctl(file, COMEDI_CHANINFO, (unsigned long)chaninfo);
156}
157
158/* Handle 32-bit COMEDI_RANGEINFO ioctl. */
159static int compat_rangeinfo(struct file *file, unsigned long arg)
160{
161	comedi_rangeinfo __user *rangeinfo;
162	struct comedi32_rangeinfo_struct __user *rangeinfo32;
163	int err;
164	union {
165		unsigned int uint;
166		compat_uptr_t uptr;
167	} temp;
168
169	rangeinfo32 = compat_ptr(arg);
170	rangeinfo = compat_alloc_user_space(sizeof(*rangeinfo));
171
172	/* Copy rangeinfo structure. */
173	if (!access_ok(VERIFY_READ, rangeinfo32, sizeof(*rangeinfo32))
174			|| !access_ok(VERIFY_WRITE, rangeinfo,
175				sizeof(*rangeinfo))) {
176		return -EFAULT;
177	}
178	err = 0;
179	err |= __get_user(temp.uint, &rangeinfo32->range_type);
180	err |= __put_user(temp.uint, &rangeinfo->range_type);
181	err |= __get_user(temp.uptr, &rangeinfo32->range_ptr);
182	err |= __put_user(compat_ptr(temp.uptr), &rangeinfo->range_ptr);
183	if (err)
184		return -EFAULT;
185
186	return translated_ioctl(file, COMEDI_RANGEINFO,
187			(unsigned long)rangeinfo);
188}
189
190/* Copy 32-bit cmd structure to native cmd structure. */
191static int get_compat_cmd(comedi_cmd __user *cmd,
192		struct comedi32_cmd_struct __user *cmd32)
193{
194	int err;
195	union {
196		unsigned int uint;
197		compat_uptr_t uptr;
198	} temp;
199
200	/* Copy cmd structure. */
201	if (!access_ok(VERIFY_READ, cmd32, sizeof(*cmd32))
202			|| !access_ok(VERIFY_WRITE, cmd, sizeof(*cmd))) {
203		return -EFAULT;
204	}
205	err = 0;
206	err |= __get_user(temp.uint, &cmd32->subdev);
207	err |= __put_user(temp.uint, &cmd->subdev);
208	err |= __get_user(temp.uint, &cmd32->flags);
209	err |= __put_user(temp.uint, &cmd->flags);
210	err |= __get_user(temp.uint, &cmd32->start_src);
211	err |= __put_user(temp.uint, &cmd->start_src);
212	err |= __get_user(temp.uint, &cmd32->start_arg);
213	err |= __put_user(temp.uint, &cmd->start_arg);
214	err |= __get_user(temp.uint, &cmd32->scan_begin_src);
215	err |= __put_user(temp.uint, &cmd->scan_begin_src);
216	err |= __get_user(temp.uint, &cmd32->scan_begin_arg);
217	err |= __put_user(temp.uint, &cmd->scan_begin_arg);
218	err |= __get_user(temp.uint, &cmd32->convert_src);
219	err |= __put_user(temp.uint, &cmd->convert_src);
220	err |= __get_user(temp.uint, &cmd32->convert_arg);
221	err |= __put_user(temp.uint, &cmd->convert_arg);
222	err |= __get_user(temp.uint, &cmd32->scan_end_src);
223	err |= __put_user(temp.uint, &cmd->scan_end_src);
224	err |= __get_user(temp.uint, &cmd32->scan_end_arg);
225	err |= __put_user(temp.uint, &cmd->scan_end_arg);
226	err |= __get_user(temp.uint, &cmd32->stop_src);
227	err |= __put_user(temp.uint, &cmd->stop_src);
228	err |= __get_user(temp.uint, &cmd32->stop_arg);
229	err |= __put_user(temp.uint, &cmd->stop_arg);
230	err |= __get_user(temp.uptr, &cmd32->chanlist);
231	err |= __put_user(compat_ptr(temp.uptr), &cmd->chanlist);
232	err |= __get_user(temp.uint, &cmd32->chanlist_len);
233	err |= __put_user(temp.uint, &cmd->chanlist_len);
234	err |= __get_user(temp.uptr, &cmd32->data);
235	err |= __put_user(compat_ptr(temp.uptr), &cmd->data);
236	err |= __get_user(temp.uint, &cmd32->data_len);
237	err |= __put_user(temp.uint, &cmd->data_len);
238	return err ? -EFAULT : 0;
239}
240
241/* Copy native cmd structure to 32-bit cmd structure. */
242static int put_compat_cmd(struct comedi32_cmd_struct __user *cmd32, comedi_cmd __user *cmd)
243{
244	int err;
245	unsigned int temp;
246
247	/* Copy back most of cmd structure. */
248	/* Assume the pointer values are already valid. */
249	/* (Could use ptr_to_compat() to set them, but that wasn't implemented
250	 * until kernel version 2.6.11.) */
251	if (!access_ok(VERIFY_READ, cmd, sizeof(*cmd))
252			|| !access_ok(VERIFY_WRITE, cmd32, sizeof(*cmd32))) {
253		return -EFAULT;
254	}
255	err = 0;
256	err |= __get_user(temp, &cmd->subdev);
257	err |= __put_user(temp, &cmd32->subdev);
258	err |= __get_user(temp, &cmd->flags);
259	err |= __put_user(temp, &cmd32->flags);
260	err |= __get_user(temp, &cmd->start_src);
261	err |= __put_user(temp, &cmd32->start_src);
262	err |= __get_user(temp, &cmd->start_arg);
263	err |= __put_user(temp, &cmd32->start_arg);
264	err |= __get_user(temp, &cmd->scan_begin_src);
265	err |= __put_user(temp, &cmd32->scan_begin_src);
266	err |= __get_user(temp, &cmd->scan_begin_arg);
267	err |= __put_user(temp, &cmd32->scan_begin_arg);
268	err |= __get_user(temp, &cmd->convert_src);
269	err |= __put_user(temp, &cmd32->convert_src);
270	err |= __get_user(temp, &cmd->convert_arg);
271	err |= __put_user(temp, &cmd32->convert_arg);
272	err |= __get_user(temp, &cmd->scan_end_src);
273	err |= __put_user(temp, &cmd32->scan_end_src);
274	err |= __get_user(temp, &cmd->scan_end_arg);
275	err |= __put_user(temp, &cmd32->scan_end_arg);
276	err |= __get_user(temp, &cmd->stop_src);
277	err |= __put_user(temp, &cmd32->stop_src);
278	err |= __get_user(temp, &cmd->stop_arg);
279	err |= __put_user(temp, &cmd32->stop_arg);
280	/* Assume chanlist pointer is unchanged. */
281	err |= __get_user(temp, &cmd->chanlist_len);
282	err |= __put_user(temp, &cmd32->chanlist_len);
283	/* Assume data pointer is unchanged. */
284	err |= __get_user(temp, &cmd->data_len);
285	err |= __put_user(temp, &cmd32->data_len);
286	return err ? -EFAULT : 0;
287}
288
289/* Handle 32-bit COMEDI_CMD ioctl. */
290static int compat_cmd(struct file *file, unsigned long arg)
291{
292	comedi_cmd __user *cmd;
293	struct comedi32_cmd_struct __user *cmd32;
294	int rc;
295
296	cmd32 = compat_ptr(arg);
297	cmd = compat_alloc_user_space(sizeof(*cmd));
298
299	rc = get_compat_cmd(cmd, cmd32);
300	if (rc)
301		return rc;
302
303	return translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd);
304}
305
306/* Handle 32-bit COMEDI_CMDTEST ioctl. */
307static int compat_cmdtest(struct file *file, unsigned long arg)
308{
309	comedi_cmd __user *cmd;
310	struct comedi32_cmd_struct __user *cmd32;
311	int rc, err;
312
313	cmd32 = compat_ptr(arg);
314	cmd = compat_alloc_user_space(sizeof(*cmd));
315
316	rc = get_compat_cmd(cmd, cmd32);
317	if (rc)
318		return rc;
319
320	rc = translated_ioctl(file, COMEDI_CMDTEST, (unsigned long)cmd);
321	if (rc < 0)
322		return rc;
323
324	err = put_compat_cmd(cmd32, cmd);
325	if (err)
326		rc = err;
327
328	return rc;
329}
330
331/* Copy 32-bit insn structure to native insn structure. */
332static int get_compat_insn(comedi_insn __user *insn,
333		struct comedi32_insn_struct __user *insn32)
334{
335	int err;
336	union {
337		unsigned int uint;
338		compat_uptr_t uptr;
339	} temp;
340
341	/* Copy insn structure.  Ignore the unused members. */
342	err = 0;
343	if (!access_ok(VERIFY_READ, insn32, sizeof(*insn32))
344			|| !access_ok(VERIFY_WRITE, insn, sizeof(*insn)))
345		return -EFAULT;
346
347	err |= __get_user(temp.uint, &insn32->insn);
348	err |= __put_user(temp.uint, &insn->insn);
349	err |= __get_user(temp.uint, &insn32->n);
350	err |= __put_user(temp.uint, &insn->n);
351	err |= __get_user(temp.uptr, &insn32->data);
352	err |= __put_user(compat_ptr(temp.uptr), &insn->data);
353	err |= __get_user(temp.uint, &insn32->subdev);
354	err |= __put_user(temp.uint, &insn->subdev);
355	err |= __get_user(temp.uint, &insn32->chanspec);
356	err |= __put_user(temp.uint, &insn->chanspec);
357	return err ? -EFAULT : 0;
358}
359
360/* Handle 32-bit COMEDI_INSNLIST ioctl. */
361static int compat_insnlist(struct file *file, unsigned long arg)
362{
363	struct combined_insnlist {
364		comedi_insnlist insnlist;
365		comedi_insn insn[1];
366	} __user *s;
367	struct comedi32_insnlist_struct __user *insnlist32;
368	struct comedi32_insn_struct __user *insn32;
369	compat_uptr_t uptr;
370	unsigned int n_insns, n;
371	int err, rc;
372
373	insnlist32 = compat_ptr(arg);
374
375	/* Get 32-bit insnlist structure.  */
376	if (!access_ok(VERIFY_READ, insnlist32, sizeof(*insnlist32))) {
377		return -EFAULT;
378	}
379	err = 0;
380	err |= __get_user(n_insns, &insnlist32->n_insns);
381	err |= __get_user(uptr, &insnlist32->insns);
382	insn32 = compat_ptr(uptr);
383	if (err)
384		return -EFAULT;
385
386	/* Allocate user memory to copy insnlist and insns into. */
387	s = compat_alloc_user_space(offsetof(struct combined_insnlist,
388				insn[n_insns]));
389
390	/* Set native insnlist structure. */
391	if (!access_ok(VERIFY_WRITE, &s->insnlist, sizeof(s->insnlist))) {
392		return -EFAULT;
393	}
394	err |= __put_user(n_insns, &s->insnlist.n_insns);
395	err |= __put_user(&s->insn[0], &s->insnlist.insns);
396	if (err)
397		return -EFAULT;
398
399	/* Copy insn structures. */
400	for (n = 0; n < n_insns; n++) {
401		rc = get_compat_insn(&s->insn[n], &insn32[n]);
402		if (rc)
403			return rc;
404	}
405
406	return translated_ioctl(file, COMEDI_INSNLIST,
407			(unsigned long)&s->insnlist);
408}
409
410/* Handle 32-bit COMEDI_INSN ioctl. */
411static int compat_insn(struct file *file, unsigned long arg)
412{
413	comedi_insn __user *insn;
414	struct comedi32_insn_struct __user *insn32;
415	int rc;
416
417	insn32 = compat_ptr(arg);
418	insn = compat_alloc_user_space(sizeof(*insn));
419
420	rc = get_compat_insn(insn, insn32);
421	if (rc)
422		return rc;
423
424	return translated_ioctl(file, COMEDI_INSN, (unsigned long)insn);
425}
426
427/* Process untranslated ioctl. */
428/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */
429static inline int raw_ioctl(struct file *file, unsigned int cmd,
430		unsigned long arg)
431{
432	int rc;
433
434	switch (cmd) {
435	case COMEDI_DEVCONFIG:
436	case COMEDI_DEVINFO:
437	case COMEDI_SUBDINFO:
438	case COMEDI_BUFCONFIG:
439	case COMEDI_BUFINFO:
440		/* Just need to translate the pointer argument. */
441		arg = (unsigned long)compat_ptr(arg);
442		rc = translated_ioctl(file, cmd, arg);
443		break;
444	case COMEDI_LOCK:
445	case COMEDI_UNLOCK:
446	case COMEDI_CANCEL:
447	case COMEDI_POLL:
448		/* No translation needed. */
449		rc = translated_ioctl(file, cmd, arg);
450		break;
451	case COMEDI32_CHANINFO:
452		rc = compat_chaninfo(file, arg);
453		break;
454	case COMEDI32_RANGEINFO:
455		rc = compat_rangeinfo(file, arg);
456		break;
457	case COMEDI32_CMD:
458		rc = compat_cmd(file, arg);
459		break;
460	case COMEDI32_CMDTEST:
461		rc = compat_cmdtest(file, arg);
462		break;
463	case COMEDI32_INSNLIST:
464		rc = compat_insnlist(file, arg);
465		break;
466	case COMEDI32_INSN:
467		rc = compat_insn(file, arg);
468		break;
469	default:
470		rc = -ENOIOCTLCMD;
471		break;
472	}
473	return rc;
474}
475
476#ifdef HAVE_COMPAT_IOCTL	/* defined in <linux/fs.h> 2.6.11 onwards */
477
478/* compat_ioctl file operation. */
479/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */
480long comedi_compat_ioctl(struct file *file, unsigned int cmd,
481		unsigned long arg)
482{
483	return raw_ioctl(file, cmd, arg);
484}
485
486#else /* HAVE_COMPAT_IOCTL */
487
488/*
489 * Brain-dead ioctl compatibility for 2.6.10 and earlier.
490 *
491 * It's brain-dead because cmd numbers need to be unique system-wide!
492 * The comedi driver could end up attempting to execute ioctls for non-Comedi
493 * devices because it registered the system-wide cmd code first.  Similarly,
494 * another driver could end up attempting to execute ioctls for a Comedi
495 * device because it registered the cmd code first.  Chaos ensues.
496 */
497
498/* Handler for all 32-bit ioctl codes registered by this driver. */
499static int mapped_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg,
500		struct file *file)
501{
502	int rc;
503
504	/* Make sure we are dealing with a Comedi device. */
505	if (imajor(file->f_dentry->d_inode) != COMEDI_MAJOR)
506		return -ENOTTY;
507
508	rc = raw_ioctl(file, cmd, arg);
509	/* Do not return -ENOIOCTLCMD. */
510	if (rc == -ENOIOCTLCMD)
511		rc = -ENOTTY;
512
513	return rc;
514}
515
516struct ioctl32_map {
517	unsigned int cmd;
518	int (*handler)(unsigned int, unsigned int, unsigned long,
519			struct file *);
520	int registered;
521};
522
523static struct ioctl32_map comedi_ioctl32_map[] = {
524	{ COMEDI_DEVCONFIG, mapped_ioctl, 0 },
525	{ COMEDI_DEVINFO, mapped_ioctl, 0 },
526	{ COMEDI_SUBDINFO, mapped_ioctl, 0 },
527	{ COMEDI_BUFCONFIG, mapped_ioctl, 0 },
528	{ COMEDI_BUFINFO, mapped_ioctl, 0 },
529	{ COMEDI_LOCK, mapped_ioctl, 0 },
530	{ COMEDI_UNLOCK, mapped_ioctl, 0 },
531	{ COMEDI_CANCEL, mapped_ioctl, 0 },
532	{ COMEDI_POLL, mapped_ioctl, 0 },
533	{ COMEDI32_CHANINFO, mapped_ioctl, 0 },
534	{ COMEDI32_RANGEINFO, mapped_ioctl, 0 },
535	{ COMEDI32_CMD, mapped_ioctl, 0 },
536	{ COMEDI32_CMDTEST, mapped_ioctl, 0 },
537	{ COMEDI32_INSNLIST, mapped_ioctl, 0 },
538	{ COMEDI32_INSN, mapped_ioctl, 0 },
539};
540
541#define NUM_IOCTL32_MAPS ARRAY_SIZE(comedi_ioctl32_map)
542
543/* Register system-wide 32-bit ioctl handlers. */
544void comedi_register_ioctl32(void)
545{
546	int n, rc;
547
548	for (n = 0; n < NUM_IOCTL32_MAPS; n++) {
549		rc = register_ioctl32_conversion(comedi_ioctl32_map[n].cmd,
550				comedi_ioctl32_map[n].handler);
551		if (rc) {
552			printk(KERN_WARNING
553					"comedi: failed to register 32-bit "
554					"compatible ioctl handler for 0x%X - "
555					"expect bad things to happen!\n",
556					comedi_ioctl32_map[n].cmd);
557		}
558		comedi_ioctl32_map[n].registered = !rc;
559	}
560}
561
562/* Unregister system-wide 32-bit ioctl translations. */
563void comedi_unregister_ioctl32(void)
564{
565	int n, rc;
566
567	for (n = 0; n < NUM_IOCTL32_MAPS; n++) {
568		if (comedi_ioctl32_map[n].registered) {
569			rc = unregister_ioctl32_conversion(
570					comedi_ioctl32_map[n].cmd,
571					comedi_ioctl32_map[n].handler);
572			if (rc) {
573				printk(KERN_ERR
574					"comedi: failed to unregister 32-bit "
575					"compatible ioctl handler for 0x%X - "
576					"expect kernel Oops!\n",
577					comedi_ioctl32_map[n].cmd);
578			} else {
579				comedi_ioctl32_map[n].registered = 0;
580			}
581		}
582	}
583}
584
585#endif	/* HAVE_COMPAT_IOCTL */
586
587#endif	/* CONFIG_COMPAT */
588