comedi_compat32.c revision 8086fff871940e6a348a733a303f39c086e4b3c5
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, 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 */
598