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