comedi_compat32.c revision 02c7b237f6a01f580319187d4bdbf712b27a5460
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/uaccess.h>
30
31#include "comedi_compat32.h"
32
33#ifdef CONFIG_COMPAT
34
35#define COMEDI32_CHANINFO _IOR(CIO, 3, struct comedi32_chaninfo_struct)
36#define COMEDI32_RANGEINFO _IOR(CIO, 8, struct comedi32_rangeinfo_struct)
37/* N.B. COMEDI32_CMD and COMEDI_CMD ought to use _IOWR, not _IOR.
38 * It's too late to change it now, but it only affects the command number. */
39#define COMEDI32_CMD _IOR(CIO, 9, struct comedi32_cmd_struct)
40/* N.B. COMEDI32_CMDTEST and COMEDI_CMDTEST ought to use _IOWR, not _IOR.
41 * It's too late to change it now, but it only affects the command number. */
42#define COMEDI32_CMDTEST _IOR(CIO, 10, struct comedi32_cmd_struct)
43#define COMEDI32_INSNLIST _IOR(CIO, 11, struct comedi32_insnlist_struct)
44#define COMEDI32_INSN _IOR(CIO, 12, struct comedi32_insn_struct)
45
46struct comedi32_chaninfo_struct {
47	unsigned int subdev;
48	compat_uptr_t maxdata_list;	/* 32-bit 'unsigned int *' */
49	compat_uptr_t flaglist;	/* 32-bit 'unsigned int *' */
50	compat_uptr_t rangelist;	/* 32-bit 'unsigned int *' */
51	unsigned int unused[4];
52};
53
54struct comedi32_rangeinfo_struct {
55	unsigned int range_type;
56	compat_uptr_t range_ptr;	/* 32-bit 'void *' */
57};
58
59struct comedi32_cmd_struct {
60	unsigned int subdev;
61	unsigned int flags;
62	unsigned int start_src;
63	unsigned int start_arg;
64	unsigned int scan_begin_src;
65	unsigned int scan_begin_arg;
66	unsigned int convert_src;
67	unsigned int convert_arg;
68	unsigned int scan_end_src;
69	unsigned int scan_end_arg;
70	unsigned int stop_src;
71	unsigned int stop_arg;
72	compat_uptr_t chanlist;	/* 32-bit 'unsigned int *' */
73	unsigned int chanlist_len;
74	compat_uptr_t data;	/* 32-bit 'short *' */
75	unsigned int data_len;
76};
77
78struct comedi32_insn_struct {
79	unsigned int insn;
80	unsigned int n;
81	compat_uptr_t data;	/* 32-bit 'unsigned int *' */
82	unsigned int subdev;
83	unsigned int chanspec;
84	unsigned int unused[3];
85};
86
87struct comedi32_insnlist_struct {
88	unsigned int n_insns;
89	compat_uptr_t insns;	/* 32-bit 'struct comedi_insn *' */
90};
91
92/* Handle translated ioctl. */
93static int translated_ioctl(struct file *file, unsigned int cmd,
94			    unsigned long arg)
95{
96	if (!file->f_op)
97		return -ENOTTY;
98
99	if (file->f_op->unlocked_ioctl)
100		return file->f_op->unlocked_ioctl(file, cmd, arg);
101
102	return -ENOTTY;
103}
104
105/* Handle 32-bit COMEDI_CHANINFO ioctl. */
106static int compat_chaninfo(struct file *file, unsigned long arg)
107{
108	struct comedi_chaninfo __user *chaninfo;
109	struct comedi32_chaninfo_struct __user *chaninfo32;
110	int err;
111	union {
112		unsigned int uint;
113		compat_uptr_t uptr;
114	} temp;
115
116	chaninfo32 = compat_ptr(arg);
117	chaninfo = compat_alloc_user_space(sizeof(*chaninfo));
118
119	/* Copy chaninfo structure.  Ignore unused members. */
120	if (!access_ok(VERIFY_READ, chaninfo32, sizeof(*chaninfo32))
121	    || !access_ok(VERIFY_WRITE, chaninfo, sizeof(*chaninfo))) {
122		return -EFAULT;
123	}
124	err = 0;
125	err |= __get_user(temp.uint, &chaninfo32->subdev);
126	err |= __put_user(temp.uint, &chaninfo->subdev);
127	err |= __get_user(temp.uptr, &chaninfo32->maxdata_list);
128	err |= __put_user(compat_ptr(temp.uptr), &chaninfo->maxdata_list);
129	err |= __get_user(temp.uptr, &chaninfo32->flaglist);
130	err |= __put_user(compat_ptr(temp.uptr), &chaninfo->flaglist);
131	err |= __get_user(temp.uptr, &chaninfo32->rangelist);
132	err |= __put_user(compat_ptr(temp.uptr), &chaninfo->rangelist);
133	if (err)
134		return -EFAULT;
135
136	return translated_ioctl(file, COMEDI_CHANINFO, (unsigned long)chaninfo);
137}
138
139/* Handle 32-bit COMEDI_RANGEINFO ioctl. */
140static int compat_rangeinfo(struct file *file, unsigned long arg)
141{
142	struct comedi_rangeinfo __user *rangeinfo;
143	struct comedi32_rangeinfo_struct __user *rangeinfo32;
144	int err;
145	union {
146		unsigned int uint;
147		compat_uptr_t uptr;
148	} temp;
149
150	rangeinfo32 = compat_ptr(arg);
151	rangeinfo = compat_alloc_user_space(sizeof(*rangeinfo));
152
153	/* Copy rangeinfo structure. */
154	if (!access_ok(VERIFY_READ, rangeinfo32, sizeof(*rangeinfo32))
155	    || !access_ok(VERIFY_WRITE, rangeinfo, sizeof(*rangeinfo))) {
156		return -EFAULT;
157	}
158	err = 0;
159	err |= __get_user(temp.uint, &rangeinfo32->range_type);
160	err |= __put_user(temp.uint, &rangeinfo->range_type);
161	err |= __get_user(temp.uptr, &rangeinfo32->range_ptr);
162	err |= __put_user(compat_ptr(temp.uptr), &rangeinfo->range_ptr);
163	if (err)
164		return -EFAULT;
165
166	return translated_ioctl(file, COMEDI_RANGEINFO,
167				(unsigned long)rangeinfo);
168}
169
170/* Copy 32-bit cmd structure to native cmd structure. */
171static int get_compat_cmd(struct comedi_cmd __user *cmd,
172			  struct comedi32_cmd_struct __user *cmd32)
173{
174	int err;
175	union {
176		unsigned int uint;
177		compat_uptr_t uptr;
178	} temp;
179
180	/* Copy cmd structure. */
181	if (!access_ok(VERIFY_READ, cmd32, sizeof(*cmd32))
182	    || !access_ok(VERIFY_WRITE, cmd, sizeof(*cmd))) {
183		return -EFAULT;
184	}
185	err = 0;
186	err |= __get_user(temp.uint, &cmd32->subdev);
187	err |= __put_user(temp.uint, &cmd->subdev);
188	err |= __get_user(temp.uint, &cmd32->flags);
189	err |= __put_user(temp.uint, &cmd->flags);
190	err |= __get_user(temp.uint, &cmd32->start_src);
191	err |= __put_user(temp.uint, &cmd->start_src);
192	err |= __get_user(temp.uint, &cmd32->start_arg);
193	err |= __put_user(temp.uint, &cmd->start_arg);
194	err |= __get_user(temp.uint, &cmd32->scan_begin_src);
195	err |= __put_user(temp.uint, &cmd->scan_begin_src);
196	err |= __get_user(temp.uint, &cmd32->scan_begin_arg);
197	err |= __put_user(temp.uint, &cmd->scan_begin_arg);
198	err |= __get_user(temp.uint, &cmd32->convert_src);
199	err |= __put_user(temp.uint, &cmd->convert_src);
200	err |= __get_user(temp.uint, &cmd32->convert_arg);
201	err |= __put_user(temp.uint, &cmd->convert_arg);
202	err |= __get_user(temp.uint, &cmd32->scan_end_src);
203	err |= __put_user(temp.uint, &cmd->scan_end_src);
204	err |= __get_user(temp.uint, &cmd32->scan_end_arg);
205	err |= __put_user(temp.uint, &cmd->scan_end_arg);
206	err |= __get_user(temp.uint, &cmd32->stop_src);
207	err |= __put_user(temp.uint, &cmd->stop_src);
208	err |= __get_user(temp.uint, &cmd32->stop_arg);
209	err |= __put_user(temp.uint, &cmd->stop_arg);
210	err |= __get_user(temp.uptr, &cmd32->chanlist);
211	err |= __put_user(compat_ptr(temp.uptr), &cmd->chanlist);
212	err |= __get_user(temp.uint, &cmd32->chanlist_len);
213	err |= __put_user(temp.uint, &cmd->chanlist_len);
214	err |= __get_user(temp.uptr, &cmd32->data);
215	err |= __put_user(compat_ptr(temp.uptr), &cmd->data);
216	err |= __get_user(temp.uint, &cmd32->data_len);
217	err |= __put_user(temp.uint, &cmd->data_len);
218	return err ? -EFAULT : 0;
219}
220
221/* Copy native cmd structure to 32-bit cmd structure. */
222static int put_compat_cmd(struct comedi32_cmd_struct __user *cmd32,
223			  struct comedi_cmd __user *cmd)
224{
225	int err;
226	unsigned int temp;
227
228	/* Copy back most of cmd structure. */
229	/* Assume the pointer values are already valid. */
230	/* (Could use ptr_to_compat() to set them, but that wasn't implemented
231	 * until kernel version 2.6.11.) */
232	if (!access_ok(VERIFY_READ, cmd, sizeof(*cmd))
233	    || !access_ok(VERIFY_WRITE, cmd32, sizeof(*cmd32))) {
234		return -EFAULT;
235	}
236	err = 0;
237	err |= __get_user(temp, &cmd->subdev);
238	err |= __put_user(temp, &cmd32->subdev);
239	err |= __get_user(temp, &cmd->flags);
240	err |= __put_user(temp, &cmd32->flags);
241	err |= __get_user(temp, &cmd->start_src);
242	err |= __put_user(temp, &cmd32->start_src);
243	err |= __get_user(temp, &cmd->start_arg);
244	err |= __put_user(temp, &cmd32->start_arg);
245	err |= __get_user(temp, &cmd->scan_begin_src);
246	err |= __put_user(temp, &cmd32->scan_begin_src);
247	err |= __get_user(temp, &cmd->scan_begin_arg);
248	err |= __put_user(temp, &cmd32->scan_begin_arg);
249	err |= __get_user(temp, &cmd->convert_src);
250	err |= __put_user(temp, &cmd32->convert_src);
251	err |= __get_user(temp, &cmd->convert_arg);
252	err |= __put_user(temp, &cmd32->convert_arg);
253	err |= __get_user(temp, &cmd->scan_end_src);
254	err |= __put_user(temp, &cmd32->scan_end_src);
255	err |= __get_user(temp, &cmd->scan_end_arg);
256	err |= __put_user(temp, &cmd32->scan_end_arg);
257	err |= __get_user(temp, &cmd->stop_src);
258	err |= __put_user(temp, &cmd32->stop_src);
259	err |= __get_user(temp, &cmd->stop_arg);
260	err |= __put_user(temp, &cmd32->stop_arg);
261	/* Assume chanlist pointer is unchanged. */
262	err |= __get_user(temp, &cmd->chanlist_len);
263	err |= __put_user(temp, &cmd32->chanlist_len);
264	/* Assume data pointer is unchanged. */
265	err |= __get_user(temp, &cmd->data_len);
266	err |= __put_user(temp, &cmd32->data_len);
267	return err ? -EFAULT : 0;
268}
269
270/* Handle 32-bit COMEDI_CMD ioctl. */
271static int compat_cmd(struct file *file, unsigned long arg)
272{
273	struct comedi_cmd __user *cmd;
274	struct comedi32_cmd_struct __user *cmd32;
275	int rc;
276
277	cmd32 = compat_ptr(arg);
278	cmd = compat_alloc_user_space(sizeof(*cmd));
279
280	rc = get_compat_cmd(cmd, cmd32);
281	if (rc)
282		return rc;
283
284	return translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd);
285}
286
287/* Handle 32-bit COMEDI_CMDTEST ioctl. */
288static int compat_cmdtest(struct file *file, unsigned long arg)
289{
290	struct comedi_cmd __user *cmd;
291	struct comedi32_cmd_struct __user *cmd32;
292	int rc, err;
293
294	cmd32 = compat_ptr(arg);
295	cmd = compat_alloc_user_space(sizeof(*cmd));
296
297	rc = get_compat_cmd(cmd, cmd32);
298	if (rc)
299		return rc;
300
301	rc = translated_ioctl(file, COMEDI_CMDTEST, (unsigned long)cmd);
302	if (rc < 0)
303		return rc;
304
305	err = put_compat_cmd(cmd32, cmd);
306	if (err)
307		rc = err;
308
309	return rc;
310}
311
312/* Copy 32-bit insn structure to native insn structure. */
313static int get_compat_insn(struct comedi_insn __user *insn,
314			   struct comedi32_insn_struct __user *insn32)
315{
316	int err;
317	union {
318		unsigned int uint;
319		compat_uptr_t uptr;
320	} temp;
321
322	/* Copy insn structure.  Ignore the unused members. */
323	err = 0;
324	if (!access_ok(VERIFY_READ, insn32, sizeof(*insn32))
325	    || !access_ok(VERIFY_WRITE, insn, sizeof(*insn)))
326		return -EFAULT;
327
328	err |= __get_user(temp.uint, &insn32->insn);
329	err |= __put_user(temp.uint, &insn->insn);
330	err |= __get_user(temp.uint, &insn32->n);
331	err |= __put_user(temp.uint, &insn->n);
332	err |= __get_user(temp.uptr, &insn32->data);
333	err |= __put_user(compat_ptr(temp.uptr), &insn->data);
334	err |= __get_user(temp.uint, &insn32->subdev);
335	err |= __put_user(temp.uint, &insn->subdev);
336	err |= __get_user(temp.uint, &insn32->chanspec);
337	err |= __put_user(temp.uint, &insn->chanspec);
338	return err ? -EFAULT : 0;
339}
340
341/* Handle 32-bit COMEDI_INSNLIST ioctl. */
342static int compat_insnlist(struct file *file, unsigned long arg)
343{
344	struct combined_insnlist {
345		struct comedi_insnlist insnlist;
346		struct comedi_insn insn[1];
347	} __user *s;
348	struct comedi32_insnlist_struct __user *insnlist32;
349	struct comedi32_insn_struct __user *insn32;
350	compat_uptr_t uptr;
351	unsigned int n_insns, n;
352	int err, rc;
353
354	insnlist32 = compat_ptr(arg);
355
356	/* Get 32-bit insnlist structure.  */
357	if (!access_ok(VERIFY_READ, insnlist32, sizeof(*insnlist32)))
358		return -EFAULT;
359
360	err = 0;
361	err |= __get_user(n_insns, &insnlist32->n_insns);
362	err |= __get_user(uptr, &insnlist32->insns);
363	insn32 = compat_ptr(uptr);
364	if (err)
365		return -EFAULT;
366
367	/* Allocate user memory to copy insnlist and insns into. */
368	s = compat_alloc_user_space(offsetof(struct combined_insnlist,
369					     insn[n_insns]));
370
371	/* Set native insnlist structure. */
372	if (!access_ok(VERIFY_WRITE, &s->insnlist, sizeof(s->insnlist)))
373		return -EFAULT;
374
375	err |= __put_user(n_insns, &s->insnlist.n_insns);
376	err |= __put_user(&s->insn[0], &s->insnlist.insns);
377	if (err)
378		return -EFAULT;
379
380	/* Copy insn structures. */
381	for (n = 0; n < n_insns; n++) {
382		rc = get_compat_insn(&s->insn[n], &insn32[n]);
383		if (rc)
384			return rc;
385	}
386
387	return translated_ioctl(file, COMEDI_INSNLIST,
388				(unsigned long)&s->insnlist);
389}
390
391/* Handle 32-bit COMEDI_INSN ioctl. */
392static int compat_insn(struct file *file, unsigned long arg)
393{
394	struct comedi_insn __user *insn;
395	struct comedi32_insn_struct __user *insn32;
396	int rc;
397
398	insn32 = compat_ptr(arg);
399	insn = compat_alloc_user_space(sizeof(*insn));
400
401	rc = get_compat_insn(insn, insn32);
402	if (rc)
403		return rc;
404
405	return translated_ioctl(file, COMEDI_INSN, (unsigned long)insn);
406}
407
408/* Process untranslated ioctl. */
409/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */
410static inline int raw_ioctl(struct file *file, unsigned int cmd,
411			    unsigned long arg)
412{
413	int rc;
414
415	switch (cmd) {
416	case COMEDI_DEVCONFIG:
417	case COMEDI_DEVINFO:
418	case COMEDI_SUBDINFO:
419	case COMEDI_BUFCONFIG:
420	case COMEDI_BUFINFO:
421		/* Just need to translate the pointer argument. */
422		arg = (unsigned long)compat_ptr(arg);
423		rc = translated_ioctl(file, cmd, arg);
424		break;
425	case COMEDI_LOCK:
426	case COMEDI_UNLOCK:
427	case COMEDI_CANCEL:
428	case COMEDI_POLL:
429		/* No translation needed. */
430		rc = translated_ioctl(file, cmd, arg);
431		break;
432	case COMEDI32_CHANINFO:
433		rc = compat_chaninfo(file, arg);
434		break;
435	case COMEDI32_RANGEINFO:
436		rc = compat_rangeinfo(file, arg);
437		break;
438	case COMEDI32_CMD:
439		rc = compat_cmd(file, arg);
440		break;
441	case COMEDI32_CMDTEST:
442		rc = compat_cmdtest(file, arg);
443		break;
444	case COMEDI32_INSNLIST:
445		rc = compat_insnlist(file, arg);
446		break;
447	case COMEDI32_INSN:
448		rc = compat_insn(file, arg);
449		break;
450	default:
451		rc = -ENOIOCTLCMD;
452		break;
453	}
454	return rc;
455}
456
457/* compat_ioctl file operation. */
458/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */
459long comedi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
460{
461	return raw_ioctl(file, cmd, arg);
462}
463
464#endif /* CONFIG_COMPAT */
465