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