v2.4.8 -> v2.4.8.1
[opensuse:kernel.git] / drivers / sbus / char / openprom.c
1 /*
2  * Linux/SPARC PROM Configuration Driver
3  * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
4  * Copyright (C) 1996 Eddie C. Dost  (ecd@skynet.be)
5  *
6  * This character device driver allows user programs to access the
7  * PROM device tree. It is compatible with the SunOS /dev/openprom
8  * driver and the NetBSD /dev/openprom driver. The SunOS eeprom
9  * utility works without any modifications.
10  *
11  * The driver uses a minor number under the misc device major. The
12  * file read/write mode determines the type of access to the PROM.
13  * Interrupts are disabled whenever the driver calls into the PROM for
14  * sanity's sake.
15  */
16
17 /* This program is free software; you can redistribute it and/or
18  * modify it under the terms of the GNU General Public License as
19  * published by the Free Software Foundation; either version 2 of the
20  * License, or (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful, but
23  * WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  * General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public License
28  * along with this program; if not, write to the Free Software
29  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
30  */
31
32 #define PROMLIB_INTERNAL
33
34 #include <linux/config.h>
35 #include <linux/module.h>
36 #include <linux/kernel.h>
37 #include <linux/sched.h>
38 #include <linux/errno.h>
39 #include <linux/slab.h>
40 #include <linux/string.h>
41 #include <linux/miscdevice.h>
42 #include <linux/init.h>
43 #include <asm/oplib.h>
44 #include <asm/system.h>
45 #include <asm/uaccess.h>
46 #include <asm/openpromio.h>
47 #ifdef CONFIG_PCI
48 #include <linux/pci.h>
49 #include <asm/pbm.h>
50 #endif
51
52 /* Private data kept by the driver for each descriptor. */
53 typedef struct openprom_private_data
54 {
55         int current_node;       /* Current node for SunOS ioctls. */
56         int lastnode;           /* Last valid node used by BSD ioctls. */
57 } DATA;
58
59 /* ID of the PROM node containing all of the EEPROM options. */
60 static int options_node = 0;
61
62 /*
63  * Copy an openpromio structure into kernel space from user space.
64  * This routine does error checking to make sure that all memory
65  * accesses are within bounds. A pointer to the allocated openpromio
66  * structure will be placed in "*opp_p". Return value is the length
67  * of the user supplied buffer.
68  */
69 static int copyin(struct openpromio *info, struct openpromio **opp_p)
70 {
71         int bufsize;
72
73         if (!info || !opp_p)
74                 return -EFAULT;
75
76         if (get_user(bufsize, &info->oprom_size))
77                 return -EFAULT;
78
79         if (bufsize == 0)
80                 return -EINVAL;
81
82         /* If the bufsize is too large, just limit it.
83          * Fix from Jason Rappleye.
84          */
85         if (bufsize > OPROMMAXPARAM)
86                 bufsize = OPROMMAXPARAM;
87
88         if (!(*opp_p = kmalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
89                 return -ENOMEM;
90         memset(*opp_p, 0, sizeof(int) + bufsize + 1);
91
92         if (copy_from_user(&(*opp_p)->oprom_array,
93                            &info->oprom_array, bufsize)) {
94                 kfree(*opp_p);
95                 return -EFAULT;
96         }
97         return bufsize;
98 }
99
100 static int getstrings(struct openpromio *info, struct openpromio **opp_p)
101 {
102         int n, bufsize;
103         char c;
104
105         if (!info || !opp_p)
106                 return -EFAULT;
107
108         if (!(*opp_p = kmalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
109                 return -ENOMEM;
110
111         memset(*opp_p, 0, sizeof(int) + OPROMMAXPARAM + 1);
112         (*opp_p)->oprom_size = 0;
113
114         n = bufsize = 0;
115         while ((n < 2) && (bufsize < OPROMMAXPARAM)) {
116                 if (get_user(c, &info->oprom_array[bufsize])) {
117                         kfree(*opp_p);
118                         return -EFAULT;
119                 }
120                 if (c == '\0')
121                         n++;
122                 (*opp_p)->oprom_array[bufsize++] = c;
123         }
124         if (!n) {
125                 kfree(*opp_p);
126                 return -EINVAL;
127         }
128         return bufsize;
129 }
130
131 /*
132  * Copy an openpromio structure in kernel space back to user space.
133  */
134 static int copyout(void *info, struct openpromio *opp, int len)
135 {
136         if (copy_to_user(info, opp, len))
137                 return -EFAULT;
138         return 0;
139 }
140
141 /*
142  *      SunOS and Solaris /dev/openprom ioctl calls.
143  */
144 static int openprom_sunos_ioctl(struct inode * inode, struct file * file,
145                                 unsigned int cmd, unsigned long arg, int node)
146 {
147         DATA *data = (DATA *) file->private_data;
148         char buffer[OPROMMAXPARAM+1], *buf;
149         struct openpromio *opp;
150         unsigned long flags;
151         int bufsize, len, error = 0;
152         extern char saved_command_line[];
153         static int cnt;
154
155         if (cmd == OPROMSETOPT)
156                 bufsize = getstrings((void *)arg, &opp);
157         else
158                 bufsize = copyin((void *)arg, &opp);
159
160         if (bufsize < 0)
161                 return bufsize;
162
163         switch (cmd) {
164         case OPROMGETOPT:
165         case OPROMGETPROP:
166                 save_and_cli(flags);
167                 len = prom_getproplen(node, opp->oprom_array);
168                 restore_flags(flags);
169
170                 if (len <= 0 || len > bufsize) {
171                         error = copyout((void *)arg, opp, sizeof(int));
172                         break;
173                 }
174
175                 save_and_cli(flags);
176                 len = prom_getproperty(node, opp->oprom_array, buffer, bufsize);
177                 restore_flags(flags);
178
179                 memcpy(opp->oprom_array, buffer, len);
180                 opp->oprom_array[len] = '\0';
181                 opp->oprom_size = len;
182
183                 error = copyout((void *)arg, opp, sizeof(int) + bufsize);
184                 break;
185
186         case OPROMNXTOPT:
187         case OPROMNXTPROP:
188                 save_and_cli(flags);
189                 buf = prom_nextprop(node, opp->oprom_array, buffer);
190                 restore_flags(flags);
191
192                 len = strlen(buf);
193                 if (len == 0 || len + 1 > bufsize) {
194                         error = copyout((void *)arg, opp, sizeof(int));
195                         break;
196                 }
197
198                 memcpy(opp->oprom_array, buf, len);
199                 opp->oprom_array[len] = '\0';
200                 opp->oprom_size = ++len;
201
202                 error = copyout((void *)arg, opp, sizeof(int) + bufsize);
203                 break;
204
205         case OPROMSETOPT:
206         case OPROMSETOPT2:
207                 buf = opp->oprom_array + strlen(opp->oprom_array) + 1;
208                 len = opp->oprom_array + bufsize - buf;
209
210                 save_and_cli(flags);
211                 error = prom_setprop(options_node, opp->oprom_array,
212                                      buf, len);
213                 restore_flags(flags);
214
215                 if (error < 0)
216                         error = -EINVAL;
217                 break;
218
219         case OPROMNEXT:
220         case OPROMCHILD:
221         case OPROMSETCUR:
222                 if (bufsize < sizeof(int)) {
223                         error = -EINVAL;
224                         break;
225                 }
226
227                 node = *((int *) opp->oprom_array);
228
229                 save_and_cli(flags);
230                 switch (cmd) {
231                 case OPROMNEXT: node = __prom_getsibling(node); break;
232                 case OPROMCHILD: node = __prom_getchild(node); break;
233                 case OPROMSETCUR: break;
234                 }
235                 restore_flags(flags);
236
237                 data->current_node = node;
238                 *((int *)opp->oprom_array) = node;
239                 opp->oprom_size = sizeof(int);
240
241                 error = copyout((void *)arg, opp, bufsize + sizeof(int));
242                 break;
243
244         case OPROMPCI2NODE:
245                 error = -EINVAL;
246
247                 if (bufsize >= 2*sizeof(int)) {
248 #ifdef CONFIG_PCI
249                         struct pci_dev *pdev;
250                         struct pcidev_cookie *pcp;
251                         pdev = pci_find_slot (((int *) opp->oprom_array)[0],
252                                               ((int *) opp->oprom_array)[1]);
253
254                         pcp = pdev->sysdata;
255                         if (pcp != NULL && pcp->prom_node != -1 && pcp->prom_node) {
256                                 node = pcp->prom_node;
257                                 data->current_node = node;
258                                 *((int *)opp->oprom_array) = node;
259                                 opp->oprom_size = sizeof(int);
260                                 error = copyout((void *)arg, opp, bufsize + sizeof(int));
261                         }
262 #endif
263                 }
264                 break;
265
266         case OPROMPATH2NODE:
267                 save_and_cli(flags);
268                 node = prom_finddevice(opp->oprom_array);
269                 restore_flags(flags);
270                 data->current_node = node;
271                 *((int *)opp->oprom_array) = node;
272                 opp->oprom_size = sizeof(int);
273
274                 error = copyout((void *)arg, opp, bufsize + sizeof(int));
275                 break;
276
277         case OPROMGETBOOTARGS:
278                 buf = saved_command_line;
279
280                 len = strlen(buf);
281
282                 if (len > bufsize) {
283                         error = -EINVAL;
284                         break;
285                 }
286
287                 strcpy(opp->oprom_array, buf);
288                 opp->oprom_size = len;
289
290                 error = copyout((void *)arg, opp, bufsize + sizeof(int));
291                 break;
292
293         case OPROMU2P:
294         case OPROMGETCONS:
295         case OPROMGETFBNAME:
296                 if (cnt++ < 10)
297                         printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n");
298                 error = -EINVAL;
299                 break;
300         default:
301                 if (cnt++ < 10)
302                         printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
303                 error = -EINVAL;
304                 break;
305         }
306
307         kfree(opp);
308         return error;
309 }
310
311
312 /* Return nonzero if a specific node is in the PROM device tree. */
313 static int intree(int root, int node)
314 {
315         for (; root != 0; root = prom_getsibling(root))
316                 if (root == node || intree(prom_getchild(root),node))
317                         return 1;
318         return 0;
319 }
320
321 /* Return nonzero if a specific node is "valid". */
322 static int goodnode(int n, DATA *data)
323 {
324         if (n == data->lastnode || n == prom_root_node || n == options_node)
325                 return 1;
326         if (n == 0 || n == -1 || !intree(prom_root_node,n))
327                 return 0;
328         data->lastnode = n;
329         return 1;
330 }
331
332 /* Copy in a whole string from userspace into kernelspace. */
333 static int copyin_string(char *user, size_t len, char **ptr)
334 {
335         char *tmp;
336
337         tmp = kmalloc(len + 1, GFP_KERNEL);
338         if (!tmp)
339                 return -ENOMEM;
340
341         if(copy_from_user(tmp, user, len)) {
342                 kfree(tmp);
343                 return -EFAULT;
344         }
345
346         tmp[len] = '\0';
347
348         *ptr = tmp;
349
350         return 0;
351 }
352
353 /*
354  *      NetBSD /dev/openprom ioctl calls.
355  */
356 static int openprom_bsd_ioctl(struct inode * inode, struct file * file,
357                               unsigned int cmd, unsigned long arg)
358 {
359         DATA *data = (DATA *) file->private_data;
360         struct opiocdesc op;
361         unsigned long flags;
362         int error, node, len;
363         char *str, *tmp;
364         char buffer[64];
365         static int cnt;
366
367         switch (cmd) {
368         case OPIOCGET:
369                 if (copy_from_user(&op, (void *)arg, sizeof(op)))
370                         return -EFAULT;
371
372                 if (!goodnode(op.op_nodeid,data))
373                         return -EINVAL;
374
375                 error = copyin_string(op.op_name, op.op_namelen, &str);
376                 if (error)
377                         return error;
378
379                 save_and_cli(flags);
380                 len = prom_getproplen(op.op_nodeid,str);
381                 restore_flags(flags);
382
383                 if (len > op.op_buflen) {
384                         kfree(str);
385                         return -ENOMEM;
386                 }
387
388                 op.op_buflen = len;
389
390                 if (len <= 0) {
391                         kfree(str);
392                         /* Verified by the above copy_from_user */
393                         if (__copy_to_user((void *)arg, &op,
394                                        sizeof(op)))
395                                 return -EFAULT;
396                         return 0;
397                 }
398
399                 tmp = kmalloc(len + 1, GFP_KERNEL);
400                 if (!tmp) {
401                         kfree(str);
402                         return -ENOMEM;
403                 }
404
405                 save_and_cli(flags);
406                 prom_getproperty(op.op_nodeid, str, tmp, len);
407                 restore_flags(flags);
408
409                 tmp[len] = '\0';
410
411                 error = __copy_to_user((void *)arg, &op, sizeof(op));
412                 if (!error)
413                         error = copy_to_user(op.op_buf, tmp, len);
414
415                 kfree(tmp);
416                 kfree(str);
417
418                 return error;
419
420         case OPIOCNEXTPROP:
421                 if (copy_from_user(&op, (void *)arg, sizeof(op)))
422                         return -EFAULT;
423
424                 if (!goodnode(op.op_nodeid,data))
425                         return -EINVAL;
426
427                 error = copyin_string(op.op_name, op.op_namelen, &str);
428                 if (error)
429                         return error;
430
431                 save_and_cli(flags);
432                 tmp = prom_nextprop(op.op_nodeid,str,buffer);
433                 restore_flags(flags);
434
435                 if (tmp) {
436                         len = strlen(tmp);
437                         if (len > op.op_buflen)
438                                 len = op.op_buflen;
439                         else
440                                 op.op_buflen = len;
441                 } else {
442                         len = op.op_buflen = 0;
443                 }
444
445                 error = verify_area(VERIFY_WRITE, (void *)arg, sizeof(op));
446                 if (error) {
447                         kfree(str);
448                         return error;
449                 }
450
451                 error = verify_area(VERIFY_WRITE, op.op_buf, len);
452                 if (error) {
453                         kfree(str);
454                         return error;
455                 }
456
457                 error = __copy_to_user((void *)arg, &op, sizeof(op));
458                 if (!error) error = __copy_to_user(op.op_buf, tmp, len);
459
460                 kfree(str);
461
462                 return error;
463
464         case OPIOCSET:
465                 if (copy_from_user(&op, (void *)arg, sizeof(op)))
466                         return -EFAULT;
467
468                 if (!goodnode(op.op_nodeid,data))
469                         return -EINVAL;
470
471                 error = copyin_string(op.op_name, op.op_namelen, &str);
472                 if (error)
473                         return error;
474
475                 error = copyin_string(op.op_buf, op.op_buflen, &tmp);
476                 if (error) {
477                         kfree(str);
478                         return error;
479                 }
480
481                 save_and_cli(flags);
482                 len = prom_setprop(op.op_nodeid,str,tmp,op.op_buflen+1);
483                 restore_flags(flags);
484
485                 if (len != op.op_buflen)
486                         return -EINVAL;
487
488                 kfree(str);
489                 kfree(tmp);
490
491                 return 0;
492
493         case OPIOCGETOPTNODE:
494                 if (copy_to_user((void *)arg, &options_node, sizeof(int)))
495                         return -EFAULT;
496                 return 0;
497
498         case OPIOCGETNEXT:
499         case OPIOCGETCHILD:
500                 if (copy_from_user(&node, (void *)arg, sizeof(int)))
501                         return -EFAULT;
502
503                 save_and_cli(flags);
504                 if (cmd == OPIOCGETNEXT)
505                         node = __prom_getsibling(node);
506                 else
507                         node = __prom_getchild(node);
508                 restore_flags(flags);
509
510                 if (__copy_to_user((void *)arg, &node, sizeof(int)))
511                         return -EFAULT;
512
513                 return 0;
514
515         default:
516                 if (cnt++ < 10)
517                         printk(KERN_INFO "openprom_bsd_ioctl: cmd 0x%X\n", cmd);
518                 return -EINVAL;
519
520         }
521 }
522
523
524 /*
525  *      Handoff control to the correct ioctl handler.
526  */
527 static int openprom_ioctl(struct inode * inode, struct file * file,
528                           unsigned int cmd, unsigned long arg)
529 {
530         DATA *data = (DATA *) file->private_data;
531         static int cnt;
532
533         switch (cmd) {
534         case OPROMGETOPT:
535         case OPROMNXTOPT:
536                 if ((file->f_mode & FMODE_READ) == 0)
537                         return -EPERM;
538                 return openprom_sunos_ioctl(inode, file, cmd, arg,
539                                             options_node);
540
541         case OPROMSETOPT:
542         case OPROMSETOPT2:
543                 if ((file->f_mode & FMODE_WRITE) == 0)
544                         return -EPERM;
545                 return openprom_sunos_ioctl(inode, file, cmd, arg,
546                                             options_node);
547
548         case OPROMNEXT:
549         case OPROMCHILD:
550         case OPROMGETPROP:
551         case OPROMNXTPROP:
552                 if ((file->f_mode & FMODE_READ) == 0)
553                         return -EPERM;
554                 return openprom_sunos_ioctl(inode, file, cmd, arg,
555                                             data->current_node);
556
557         case OPROMU2P:
558         case OPROMGETCONS:
559         case OPROMGETFBNAME:
560         case OPROMGETBOOTARGS:
561         case OPROMSETCUR:
562         case OPROMPCI2NODE:
563         case OPROMPATH2NODE:
564                 if ((file->f_mode & FMODE_READ) == 0)
565                         return -EPERM;
566                 return openprom_sunos_ioctl(inode, file, cmd, arg, 0);
567
568         case OPIOCGET:
569         case OPIOCNEXTPROP:
570         case OPIOCGETOPTNODE:
571         case OPIOCGETNEXT:
572         case OPIOCGETCHILD:
573                 if ((file->f_mode & FMODE_READ) == 0)
574                         return -EBADF;
575                 return openprom_bsd_ioctl(inode,file,cmd,arg);
576
577         case OPIOCSET:
578                 if ((file->f_mode & FMODE_WRITE) == 0)
579                         return -EBADF;
580                 return openprom_bsd_ioctl(inode,file,cmd,arg);
581
582         default:
583                 if (cnt++ < 10)
584                         printk("openprom_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
585                 return -EINVAL;
586         }
587 }
588
589 static int openprom_open(struct inode * inode, struct file * file)
590 {
591         DATA *data;
592
593         data = (DATA *) kmalloc(sizeof(DATA), GFP_KERNEL);
594         if (!data)
595                 return -ENOMEM;
596
597         data->current_node = prom_root_node;
598         data->lastnode = prom_root_node;
599         file->private_data = (void *)data;
600
601         return 0;
602 }
603
604 static int openprom_release(struct inode * inode, struct file * file)
605 {
606         kfree(file->private_data);
607         return 0;
608 }
609
610 static struct file_operations openprom_fops = {
611         owner:          THIS_MODULE,
612         llseek:         no_llseek,
613         ioctl:          openprom_ioctl,
614         open:           openprom_open,
615         release:        openprom_release,
616 };
617
618 static struct miscdevice openprom_dev = {
619         SUN_OPENPROM_MINOR, "openprom", &openprom_fops
620 };
621
622 EXPORT_NO_SYMBOLS;
623
624 static int __init openprom_init(void)
625 {
626         unsigned long flags;
627         int error;
628
629         error = misc_register(&openprom_dev);
630         if (error) {
631                 printk(KERN_ERR "openprom: unable to get misc minor\n");
632                 return error;
633         }
634
635         save_and_cli(flags);
636         options_node = prom_getchild(prom_root_node);
637         options_node = prom_searchsiblings(options_node,"options");
638         restore_flags(flags);
639
640         if (options_node == 0 || options_node == -1) {
641                 printk(KERN_ERR "openprom: unable to find options node\n");
642                 misc_deregister(&openprom_dev);
643                 return -EIO;
644         }
645
646         return 0;
647 }
648
649 static void __exit openprom_cleanup(void)
650 {
651         misc_deregister(&openprom_dev);
652 }
653
654 module_init(openprom_init);
655 module_exit(openprom_cleanup);