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