xref: /linux/drivers/sbus/char/openprom.c (revision 3fd6c59042dbba50391e30862beac979491145fe)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Linux/SPARC PROM Configuration Driver
4  * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
5  * Copyright (C) 1996 Eddie C. Dost  (ecd@skynet.be)
6  *
7  * This character device driver allows user programs to access the
8  * PROM device tree. It is compatible with the SunOS /dev/openprom
9  * driver and the NetBSD /dev/openprom driver. The SunOS eeprom
10  * utility works without any modifications.
11  *
12  * The driver uses a minor number under the misc device major. The
13  * file read/write mode determines the type of access to the PROM.
14  * Interrupts are disabled whenever the driver calls into the PROM for
15  * sanity's sake.
16  */
17 
18 
19 #include <linux/module.h>
20 #include <linux/kernel.h>
21 #include <linux/errno.h>
22 #include <linux/slab.h>
23 #include <linux/mutex.h>
24 #include <linux/string.h>
25 #include <linux/miscdevice.h>
26 #include <linux/init.h>
27 #include <linux/fs.h>
28 #include <asm/oplib.h>
29 #include <asm/prom.h>
30 #include <linux/uaccess.h>
31 #include <asm/openpromio.h>
32 #ifdef CONFIG_PCI
33 #include <linux/pci.h>
34 #endif
35 
36 MODULE_AUTHOR("Thomas K. Dyas <tdyas@noc.rutgers.edu> and Eddie C. Dost <ecd@skynet.be>");
37 MODULE_DESCRIPTION("OPENPROM Configuration Driver");
38 MODULE_LICENSE("GPL");
39 MODULE_VERSION("1.0");
40 MODULE_ALIAS_MISCDEV(SUN_OPENPROM_MINOR);
41 
42 /* Private data kept by the driver for each descriptor. */
43 typedef struct openprom_private_data
44 {
45 	struct device_node *current_node; /* Current node for SunOS ioctls. */
46 	struct device_node *lastnode; /* Last valid node used by BSD ioctls. */
47 } DATA;
48 
49 /* ID of the PROM node containing all of the EEPROM options. */
50 static DEFINE_MUTEX(openprom_mutex);
51 static struct device_node *options_node;
52 
53 /*
54  * Copy an openpromio structure into kernel space from user space.
55  * This routine does error checking to make sure that all memory
56  * accesses are within bounds. A pointer to the allocated openpromio
57  * structure will be placed in "*opp_p". Return value is the length
58  * of the user supplied buffer.
59  */
copyin(struct openpromio __user * info,struct openpromio ** opp_p)60 static int copyin(struct openpromio __user *info, struct openpromio **opp_p)
61 {
62 	unsigned int bufsize;
63 
64 	if (!info || !opp_p)
65 		return -EFAULT;
66 
67 	if (get_user(bufsize, &info->oprom_size))
68 		return -EFAULT;
69 
70 	if (bufsize == 0)
71 		return -EINVAL;
72 
73 	/* If the bufsize is too large, just limit it.
74 	 * Fix from Jason Rappleye.
75 	 */
76 	if (bufsize > OPROMMAXPARAM)
77 		bufsize = OPROMMAXPARAM;
78 
79 	if (!(*opp_p = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
80 		return -ENOMEM;
81 
82 	if (copy_from_user(&(*opp_p)->oprom_array,
83 			   &info->oprom_array, bufsize)) {
84 		kfree(*opp_p);
85 		return -EFAULT;
86 	}
87 	return bufsize;
88 }
89 
getstrings(struct openpromio __user * info,struct openpromio ** opp_p)90 static int getstrings(struct openpromio __user *info, struct openpromio **opp_p)
91 {
92 	int n, bufsize;
93 	char c;
94 
95 	if (!info || !opp_p)
96 		return -EFAULT;
97 
98 	if (!(*opp_p = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
99 		return -ENOMEM;
100 
101 	(*opp_p)->oprom_size = 0;
102 
103 	n = bufsize = 0;
104 	while ((n < 2) && (bufsize < OPROMMAXPARAM)) {
105 		if (get_user(c, &info->oprom_array[bufsize])) {
106 			kfree(*opp_p);
107 			return -EFAULT;
108 		}
109 		if (c == '\0')
110 			n++;
111 		(*opp_p)->oprom_array[bufsize++] = c;
112 	}
113 	if (!n) {
114 		kfree(*opp_p);
115 		return -EINVAL;
116 	}
117 	return bufsize;
118 }
119 
120 /*
121  * Copy an openpromio structure in kernel space back to user space.
122  */
copyout(void __user * info,struct openpromio * opp,int len)123 static int copyout(void __user *info, struct openpromio *opp, int len)
124 {
125 	if (copy_to_user(info, opp, len))
126 		return -EFAULT;
127 	return 0;
128 }
129 
opromgetprop(void __user * argp,struct device_node * dp,struct openpromio * op,int bufsize)130 static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
131 {
132 	const void *pval;
133 	int len;
134 
135 	if (!dp ||
136 	    !(pval = of_get_property(dp, op->oprom_array, &len)) ||
137 	    len <= 0 || len > bufsize)
138 		return copyout(argp, op, sizeof(int));
139 
140 	memcpy(op->oprom_array, pval, len);
141 	op->oprom_array[len] = '\0';
142 	op->oprom_size = len;
143 
144 	return copyout(argp, op, sizeof(int) + bufsize);
145 }
146 
opromnxtprop(void __user * argp,struct device_node * dp,struct openpromio * op,int bufsize)147 static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
148 {
149 	struct property *prop;
150 	int len;
151 
152 	if (!dp)
153 		return copyout(argp, op, sizeof(int));
154 	if (op->oprom_array[0] == '\0') {
155 		prop = dp->properties;
156 		if (!prop)
157 			return copyout(argp, op, sizeof(int));
158 		len = strlen(prop->name);
159 	} else {
160 		prop = of_find_property(dp, op->oprom_array, NULL);
161 
162 		if (!prop ||
163 		    !prop->next ||
164 		    (len = strlen(prop->next->name)) + 1 > bufsize)
165 			return copyout(argp, op, sizeof(int));
166 
167 		prop = prop->next;
168 	}
169 
170 	memcpy(op->oprom_array, prop->name, len);
171 	op->oprom_array[len] = '\0';
172 	op->oprom_size = ++len;
173 
174 	return copyout(argp, op, sizeof(int) + bufsize);
175 }
176 
opromsetopt(struct device_node * dp,struct openpromio * op,int bufsize)177 static int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize)
178 {
179 	char *buf = op->oprom_array + strlen(op->oprom_array) + 1;
180 	int len = op->oprom_array + bufsize - buf;
181 
182 	return of_set_property(options_node, op->oprom_array, buf, len);
183 }
184 
opromnext(void __user * argp,unsigned int cmd,struct device_node * dp,struct openpromio * op,int bufsize,DATA * data)185 static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
186 {
187 	phandle ph;
188 
189 	BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
190 
191 	if (bufsize < sizeof(phandle))
192 		return -EINVAL;
193 
194 	ph = *((int *) op->oprom_array);
195 	if (ph) {
196 		dp = of_find_node_by_phandle(ph);
197 		if (!dp)
198 			return -EINVAL;
199 
200 		switch (cmd) {
201 		case OPROMNEXT:
202 			dp = dp->sibling;
203 			break;
204 
205 		case OPROMCHILD:
206 			dp = dp->child;
207 			break;
208 
209 		case OPROMSETCUR:
210 		default:
211 			break;
212 		}
213 	} else {
214 		/* Sibling of node zero is the root node.  */
215 		if (cmd != OPROMNEXT)
216 			return -EINVAL;
217 
218 		dp = of_find_node_by_path("/");
219 	}
220 
221 	ph = 0;
222 	if (dp)
223 		ph = dp->phandle;
224 
225 	data->current_node = dp;
226 	*((int *) op->oprom_array) = ph;
227 	op->oprom_size = sizeof(phandle);
228 
229 	return copyout(argp, op, bufsize + sizeof(int));
230 }
231 
oprompci2node(void __user * argp,struct device_node * dp,struct openpromio * op,int bufsize,DATA * data)232 static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
233 {
234 	int err = -EINVAL;
235 
236 	if (bufsize >= 2*sizeof(int)) {
237 #ifdef CONFIG_PCI
238 		struct pci_dev *pdev;
239 		struct device_node *dp;
240 
241 		pdev = pci_get_domain_bus_and_slot(0,
242 						((int *) op->oprom_array)[0],
243 						((int *) op->oprom_array)[1]);
244 
245 		dp = pci_device_to_OF_node(pdev);
246 		data->current_node = dp;
247 		*((int *)op->oprom_array) = dp->phandle;
248 		op->oprom_size = sizeof(int);
249 		err = copyout(argp, op, bufsize + sizeof(int));
250 
251 		pci_dev_put(pdev);
252 #endif
253 	}
254 
255 	return err;
256 }
257 
oprompath2node(void __user * argp,struct device_node * dp,struct openpromio * op,int bufsize,DATA * data)258 static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
259 {
260 	phandle ph = 0;
261 
262 	dp = of_find_node_by_path(op->oprom_array);
263 	if (dp)
264 		ph = dp->phandle;
265 	data->current_node = dp;
266 	*((int *)op->oprom_array) = ph;
267 	op->oprom_size = sizeof(int);
268 
269 	return copyout(argp, op, bufsize + sizeof(int));
270 }
271 
opromgetbootargs(void __user * argp,struct openpromio * op,int bufsize)272 static int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize)
273 {
274 	char *buf = saved_command_line;
275 	int len = strlen(buf);
276 
277 	if (len > bufsize)
278 		return -EINVAL;
279 
280 	strcpy(op->oprom_array, buf);
281 	op->oprom_size = len;
282 
283 	return copyout(argp, op, bufsize + sizeof(int));
284 }
285 
286 /*
287  *	SunOS and Solaris /dev/openprom ioctl calls.
288  */
openprom_sunos_ioctl(struct file * file,unsigned int cmd,unsigned long arg,struct device_node * dp)289 static long openprom_sunos_ioctl(struct file * file,
290 				 unsigned int cmd, unsigned long arg,
291 				 struct device_node *dp)
292 {
293 	DATA *data = file->private_data;
294 	struct openpromio *opp = NULL;
295 	int bufsize, error = 0;
296 	static int cnt;
297 	void __user *argp = (void __user *)arg;
298 
299 	if (cmd == OPROMSETOPT)
300 		bufsize = getstrings(argp, &opp);
301 	else
302 		bufsize = copyin(argp, &opp);
303 
304 	if (bufsize < 0)
305 		return bufsize;
306 
307 	mutex_lock(&openprom_mutex);
308 
309 	switch (cmd) {
310 	case OPROMGETOPT:
311 	case OPROMGETPROP:
312 		error = opromgetprop(argp, dp, opp, bufsize);
313 		break;
314 
315 	case OPROMNXTOPT:
316 	case OPROMNXTPROP:
317 		error = opromnxtprop(argp, dp, opp, bufsize);
318 		break;
319 
320 	case OPROMSETOPT:
321 	case OPROMSETOPT2:
322 		error = opromsetopt(dp, opp, bufsize);
323 		break;
324 
325 	case OPROMNEXT:
326 	case OPROMCHILD:
327 	case OPROMSETCUR:
328 		error = opromnext(argp, cmd, dp, opp, bufsize, data);
329 		break;
330 
331 	case OPROMPCI2NODE:
332 		error = oprompci2node(argp, dp, opp, bufsize, data);
333 		break;
334 
335 	case OPROMPATH2NODE:
336 		error = oprompath2node(argp, dp, opp, bufsize, data);
337 		break;
338 
339 	case OPROMGETBOOTARGS:
340 		error = opromgetbootargs(argp, opp, bufsize);
341 		break;
342 
343 	case OPROMU2P:
344 	case OPROMGETCONS:
345 	case OPROMGETFBNAME:
346 		if (cnt++ < 10)
347 			printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n");
348 		error = -EINVAL;
349 		break;
350 	default:
351 		if (cnt++ < 10)
352 			printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
353 		error = -EINVAL;
354 		break;
355 	}
356 
357 	kfree(opp);
358 	mutex_unlock(&openprom_mutex);
359 
360 	return error;
361 }
362 
get_node(phandle n,DATA * data)363 static struct device_node *get_node(phandle n, DATA *data)
364 {
365 	struct device_node *dp = of_find_node_by_phandle(n);
366 
367 	if (dp)
368 		data->lastnode = dp;
369 
370 	return dp;
371 }
372 
373 /* Copy in a whole string from userspace into kernelspace. */
copyin_string(char __user * user,size_t len)374 static char * copyin_string(char __user *user, size_t len)
375 {
376 	if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0)
377 		return ERR_PTR(-EINVAL);
378 
379 	return memdup_user_nul(user, len);
380 }
381 
382 /*
383  *	NetBSD /dev/openprom ioctl calls.
384  */
opiocget(void __user * argp,DATA * data)385 static int opiocget(void __user *argp, DATA *data)
386 {
387 	struct opiocdesc op;
388 	struct device_node *dp;
389 	char *str;
390 	const void *pval;
391 	int err, len;
392 
393 	if (copy_from_user(&op, argp, sizeof(op)))
394 		return -EFAULT;
395 
396 	dp = get_node(op.op_nodeid, data);
397 
398 	str = copyin_string(op.op_name, op.op_namelen);
399 	if (IS_ERR(str))
400 		return PTR_ERR(str);
401 
402 	pval = of_get_property(dp, str, &len);
403 	err = 0;
404 	if (!pval || len > op.op_buflen) {
405 		err = -EINVAL;
406 	} else {
407 		op.op_buflen = len;
408 		if (copy_to_user(argp, &op, sizeof(op)) ||
409 		    copy_to_user(op.op_buf, pval, len))
410 			err = -EFAULT;
411 	}
412 	kfree(str);
413 
414 	return err;
415 }
416 
opiocnextprop(void __user * argp,DATA * data)417 static int opiocnextprop(void __user *argp, DATA *data)
418 {
419 	struct opiocdesc op;
420 	struct device_node *dp;
421 	struct property *prop;
422 	char *str;
423 	int len;
424 
425 	if (copy_from_user(&op, argp, sizeof(op)))
426 		return -EFAULT;
427 
428 	dp = get_node(op.op_nodeid, data);
429 	if (!dp)
430 		return -EINVAL;
431 
432 	str = copyin_string(op.op_name, op.op_namelen);
433 	if (IS_ERR(str))
434 		return PTR_ERR(str);
435 
436 	if (str[0] == '\0') {
437 		prop = dp->properties;
438 	} else {
439 		prop = of_find_property(dp, str, NULL);
440 		if (prop)
441 			prop = prop->next;
442 	}
443 	kfree(str);
444 
445 	if (!prop)
446 		len = 0;
447 	else
448 		len = prop->length;
449 
450 	if (len > op.op_buflen)
451 		len = op.op_buflen;
452 
453 	if (copy_to_user(argp, &op, sizeof(op)))
454 		return -EFAULT;
455 
456 	if (len &&
457 	    copy_to_user(op.op_buf, prop->value, len))
458 		return -EFAULT;
459 
460 	return 0;
461 }
462 
opiocset(void __user * argp,DATA * data)463 static int opiocset(void __user *argp, DATA *data)
464 {
465 	struct opiocdesc op;
466 	struct device_node *dp;
467 	char *str, *tmp;
468 	int err;
469 
470 	if (copy_from_user(&op, argp, sizeof(op)))
471 		return -EFAULT;
472 
473 	dp = get_node(op.op_nodeid, data);
474 	if (!dp)
475 		return -EINVAL;
476 
477 	str = copyin_string(op.op_name, op.op_namelen);
478 	if (IS_ERR(str))
479 		return PTR_ERR(str);
480 
481 	tmp = copyin_string(op.op_buf, op.op_buflen);
482 	if (IS_ERR(tmp)) {
483 		kfree(str);
484 		return PTR_ERR(tmp);
485 	}
486 
487 	err = of_set_property(dp, str, tmp, op.op_buflen);
488 
489 	kfree(str);
490 	kfree(tmp);
491 
492 	return err;
493 }
494 
opiocgetnext(unsigned int cmd,void __user * argp)495 static int opiocgetnext(unsigned int cmd, void __user *argp)
496 {
497 	struct device_node *dp;
498 	phandle nd;
499 
500 	BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
501 
502 	if (copy_from_user(&nd, argp, sizeof(phandle)))
503 		return -EFAULT;
504 
505 	if (nd == 0) {
506 		if (cmd != OPIOCGETNEXT)
507 			return -EINVAL;
508 		dp = of_find_node_by_path("/");
509 	} else {
510 		dp = of_find_node_by_phandle(nd);
511 		nd = 0;
512 		if (dp) {
513 			if (cmd == OPIOCGETNEXT)
514 				dp = dp->sibling;
515 			else
516 				dp = dp->child;
517 		}
518 	}
519 	if (dp)
520 		nd = dp->phandle;
521 	if (copy_to_user(argp, &nd, sizeof(phandle)))
522 		return -EFAULT;
523 
524 	return 0;
525 }
526 
openprom_bsd_ioctl(struct file * file,unsigned int cmd,unsigned long arg)527 static int openprom_bsd_ioctl(struct file * file,
528 			      unsigned int cmd, unsigned long arg)
529 {
530 	DATA *data = file->private_data;
531 	void __user *argp = (void __user *)arg;
532 	int err;
533 
534 	mutex_lock(&openprom_mutex);
535 	switch (cmd) {
536 	case OPIOCGET:
537 		err = opiocget(argp, data);
538 		break;
539 
540 	case OPIOCNEXTPROP:
541 		err = opiocnextprop(argp, data);
542 		break;
543 
544 	case OPIOCSET:
545 		err = opiocset(argp, data);
546 		break;
547 
548 	case OPIOCGETOPTNODE:
549 		BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
550 
551 		err = 0;
552 		if (copy_to_user(argp, &options_node->phandle, sizeof(phandle)))
553 			err = -EFAULT;
554 		break;
555 
556 	case OPIOCGETNEXT:
557 	case OPIOCGETCHILD:
558 		err = opiocgetnext(cmd, argp);
559 		break;
560 
561 	default:
562 		err = -EINVAL;
563 		break;
564 	}
565 	mutex_unlock(&openprom_mutex);
566 
567 	return err;
568 }
569 
570 
571 /*
572  *	Handoff control to the correct ioctl handler.
573  */
openprom_ioctl(struct file * file,unsigned int cmd,unsigned long arg)574 static long openprom_ioctl(struct file * file,
575 			   unsigned int cmd, unsigned long arg)
576 {
577 	DATA *data = file->private_data;
578 
579 	switch (cmd) {
580 	case OPROMGETOPT:
581 	case OPROMNXTOPT:
582 		if ((file->f_mode & FMODE_READ) == 0)
583 			return -EPERM;
584 		return openprom_sunos_ioctl(file, cmd, arg,
585 					    options_node);
586 
587 	case OPROMSETOPT:
588 	case OPROMSETOPT2:
589 		if ((file->f_mode & FMODE_WRITE) == 0)
590 			return -EPERM;
591 		return openprom_sunos_ioctl(file, cmd, arg,
592 					    options_node);
593 
594 	case OPROMNEXT:
595 	case OPROMCHILD:
596 	case OPROMGETPROP:
597 	case OPROMNXTPROP:
598 		if ((file->f_mode & FMODE_READ) == 0)
599 			return -EPERM;
600 		return openprom_sunos_ioctl(file, cmd, arg,
601 					    data->current_node);
602 
603 	case OPROMU2P:
604 	case OPROMGETCONS:
605 	case OPROMGETFBNAME:
606 	case OPROMGETBOOTARGS:
607 	case OPROMSETCUR:
608 	case OPROMPCI2NODE:
609 	case OPROMPATH2NODE:
610 		if ((file->f_mode & FMODE_READ) == 0)
611 			return -EPERM;
612 		return openprom_sunos_ioctl(file, cmd, arg, NULL);
613 
614 	case OPIOCGET:
615 	case OPIOCNEXTPROP:
616 	case OPIOCGETOPTNODE:
617 	case OPIOCGETNEXT:
618 	case OPIOCGETCHILD:
619 		if ((file->f_mode & FMODE_READ) == 0)
620 			return -EBADF;
621 		return openprom_bsd_ioctl(file,cmd,arg);
622 
623 	case OPIOCSET:
624 		if ((file->f_mode & FMODE_WRITE) == 0)
625 			return -EBADF;
626 		return openprom_bsd_ioctl(file,cmd,arg);
627 
628 	default:
629 		return -EINVAL;
630 	};
631 }
632 
openprom_compat_ioctl(struct file * file,unsigned int cmd,unsigned long arg)633 static long openprom_compat_ioctl(struct file *file, unsigned int cmd,
634 		unsigned long arg)
635 {
636 	long rval = -ENOTTY;
637 
638 	/*
639 	 * SunOS/Solaris only, the NetBSD one's have embedded pointers in
640 	 * the arg which we'd need to clean up...
641 	 */
642 	switch (cmd) {
643 	case OPROMGETOPT:
644 	case OPROMSETOPT:
645 	case OPROMNXTOPT:
646 	case OPROMSETOPT2:
647 	case OPROMNEXT:
648 	case OPROMCHILD:
649 	case OPROMGETPROP:
650 	case OPROMNXTPROP:
651 	case OPROMU2P:
652 	case OPROMGETCONS:
653 	case OPROMGETFBNAME:
654 	case OPROMGETBOOTARGS:
655 	case OPROMSETCUR:
656 	case OPROMPCI2NODE:
657 	case OPROMPATH2NODE:
658 		rval = openprom_ioctl(file, cmd, arg);
659 		break;
660 	}
661 
662 	return rval;
663 }
664 
openprom_open(struct inode * inode,struct file * file)665 static int openprom_open(struct inode * inode, struct file * file)
666 {
667 	DATA *data;
668 
669 	data = kmalloc(sizeof(DATA), GFP_KERNEL);
670 	if (!data)
671 		return -ENOMEM;
672 
673 	mutex_lock(&openprom_mutex);
674 	data->current_node = of_find_node_by_path("/");
675 	data->lastnode = data->current_node;
676 	file->private_data = (void *) data;
677 	mutex_unlock(&openprom_mutex);
678 
679 	return 0;
680 }
681 
openprom_release(struct inode * inode,struct file * file)682 static int openprom_release(struct inode * inode, struct file * file)
683 {
684 	kfree(file->private_data);
685 	return 0;
686 }
687 
688 static const struct file_operations openprom_fops = {
689 	.owner =	THIS_MODULE,
690 	.unlocked_ioctl = openprom_ioctl,
691 	.compat_ioctl =	openprom_compat_ioctl,
692 	.open =		openprom_open,
693 	.release =	openprom_release,
694 };
695 
696 static struct miscdevice openprom_dev = {
697 	.minor		= SUN_OPENPROM_MINOR,
698 	.name		= "openprom",
699 	.fops		= &openprom_fops,
700 };
701 
openprom_init(void)702 static int __init openprom_init(void)
703 {
704 	int err;
705 
706 	err = misc_register(&openprom_dev);
707 	if (err)
708 		return err;
709 
710 	options_node = of_get_child_by_name(of_find_node_by_path("/"), "options");
711 	if (!options_node) {
712 		misc_deregister(&openprom_dev);
713 		return -EIO;
714 	}
715 
716 	return 0;
717 }
718 
openprom_cleanup(void)719 static void __exit openprom_cleanup(void)
720 {
721 	misc_deregister(&openprom_dev);
722 }
723 
724 module_init(openprom_init);
725 module_exit(openprom_cleanup);
726