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 | */ |
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(size: sizeof(int) + bufsize + 1, GFP_KERNEL))) |
80 | return -ENOMEM; |
81 | |
82 | if (copy_from_user(to: &(*opp_p)->oprom_array, |
83 | from: &info->oprom_array, n: bufsize)) { |
84 | kfree(objp: *opp_p); |
85 | return -EFAULT; |
86 | } |
87 | return bufsize; |
88 | } |
89 | |
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(objp: *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(objp: *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 | */ |
123 | static int copyout(void __user *info, struct openpromio *opp, int len) |
124 | { |
125 | if (copy_to_user(to: info, from: opp, n: len)) |
126 | return -EFAULT; |
127 | return 0; |
128 | } |
129 | |
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(node: dp, name: op->oprom_array, lenp: &len)) || |
137 | len <= 0 || len > bufsize) |
138 | return copyout(info: argp, opp: op, len: 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(info: argp, opp: op, len: sizeof(int) + bufsize); |
145 | } |
146 | |
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(info: argp, opp: op, len: sizeof(int)); |
154 | if (op->oprom_array[0] == '\0') { |
155 | prop = dp->properties; |
156 | if (!prop) |
157 | return copyout(info: argp, opp: op, len: sizeof(int)); |
158 | len = strlen(prop->name); |
159 | } else { |
160 | prop = of_find_property(np: dp, name: op->oprom_array, NULL); |
161 | |
162 | if (!prop || |
163 | !prop->next || |
164 | (len = strlen(prop->next->name)) + 1 > bufsize) |
165 | return copyout(info: argp, opp: op, len: 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(info: argp, opp: op, len: sizeof(int) + bufsize); |
175 | } |
176 | |
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 | |
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(handle: 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(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(info: argp, opp: op, len: bufsize + sizeof(int)); |
230 | } |
231 | |
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(domain: 0, |
242 | bus: ((int *) op->oprom_array)[0], |
243 | devfn: ((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(info: argp, opp: op, len: bufsize + sizeof(int)); |
250 | |
251 | pci_dev_put(dev: pdev); |
252 | #endif |
253 | } |
254 | |
255 | return err; |
256 | } |
257 | |
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(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(info: argp, opp: op, len: bufsize + sizeof(int)); |
270 | } |
271 | |
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(info: argp, opp: op, len: bufsize + sizeof(int)); |
284 | } |
285 | |
286 | /* |
287 | * SunOS and Solaris /dev/openprom ioctl calls. |
288 | */ |
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(info: argp, opp_p: &opp); |
301 | else |
302 | bufsize = copyin(info: argp, opp_p: &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, op: opp, bufsize); |
313 | break; |
314 | |
315 | case OPROMNXTOPT: |
316 | case OPROMNXTPROP: |
317 | error = opromnxtprop(argp, dp, op: opp, bufsize); |
318 | break; |
319 | |
320 | case OPROMSETOPT: |
321 | case OPROMSETOPT2: |
322 | error = opromsetopt(dp, op: opp, bufsize); |
323 | break; |
324 | |
325 | case OPROMNEXT: |
326 | case OPROMCHILD: |
327 | case OPROMSETCUR: |
328 | error = opromnext(argp, cmd, dp, op: opp, bufsize, data); |
329 | break; |
330 | |
331 | case OPROMPCI2NODE: |
332 | error = oprompci2node(argp, dp, op: opp, bufsize, data); |
333 | break; |
334 | |
335 | case OPROMPATH2NODE: |
336 | error = oprompath2node(argp, dp, op: opp, bufsize, data); |
337 | break; |
338 | |
339 | case OPROMGETBOOTARGS: |
340 | error = opromgetbootargs(argp, op: 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(objp: opp); |
358 | mutex_unlock(lock: &openprom_mutex); |
359 | |
360 | return error; |
361 | } |
362 | |
363 | static struct device_node *get_node(phandle n, DATA *data) |
364 | { |
365 | struct device_node *dp = of_find_node_by_phandle(handle: 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. */ |
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(error: -EINVAL); |
378 | |
379 | return memdup_user_nul(user, len); |
380 | } |
381 | |
382 | /* |
383 | * NetBSD /dev/openprom ioctl calls. |
384 | */ |
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(to: &op, from: argp, n: sizeof(op))) |
394 | return -EFAULT; |
395 | |
396 | dp = get_node(n: op.op_nodeid, data); |
397 | |
398 | str = copyin_string(user: op.op_name, len: op.op_namelen); |
399 | if (IS_ERR(ptr: str)) |
400 | return PTR_ERR(ptr: str); |
401 | |
402 | pval = of_get_property(node: dp, name: str, lenp: &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(to: argp, from: &op, n: sizeof(op)) || |
409 | copy_to_user(to: op.op_buf, from: pval, n: len)) |
410 | err = -EFAULT; |
411 | } |
412 | kfree(objp: str); |
413 | |
414 | return err; |
415 | } |
416 | |
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(to: &op, from: argp, n: sizeof(op))) |
426 | return -EFAULT; |
427 | |
428 | dp = get_node(n: op.op_nodeid, data); |
429 | if (!dp) |
430 | return -EINVAL; |
431 | |
432 | str = copyin_string(user: op.op_name, len: op.op_namelen); |
433 | if (IS_ERR(ptr: str)) |
434 | return PTR_ERR(ptr: str); |
435 | |
436 | if (str[0] == '\0') { |
437 | prop = dp->properties; |
438 | } else { |
439 | prop = of_find_property(np: dp, name: str, NULL); |
440 | if (prop) |
441 | prop = prop->next; |
442 | } |
443 | kfree(objp: 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(to: argp, from: &op, n: sizeof(op))) |
454 | return -EFAULT; |
455 | |
456 | if (len && |
457 | copy_to_user(to: op.op_buf, from: prop->value, n: len)) |
458 | return -EFAULT; |
459 | |
460 | return 0; |
461 | } |
462 | |
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(to: &op, from: argp, n: sizeof(op))) |
471 | return -EFAULT; |
472 | |
473 | dp = get_node(n: op.op_nodeid, data); |
474 | if (!dp) |
475 | return -EINVAL; |
476 | |
477 | str = copyin_string(user: op.op_name, len: op.op_namelen); |
478 | if (IS_ERR(ptr: str)) |
479 | return PTR_ERR(ptr: str); |
480 | |
481 | tmp = copyin_string(user: op.op_buf, len: op.op_buflen); |
482 | if (IS_ERR(ptr: tmp)) { |
483 | kfree(objp: str); |
484 | return PTR_ERR(ptr: tmp); |
485 | } |
486 | |
487 | err = of_set_property(dp, str, tmp, op.op_buflen); |
488 | |
489 | kfree(objp: str); |
490 | kfree(objp: tmp); |
491 | |
492 | return err; |
493 | } |
494 | |
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(to: &nd, from: argp, n: sizeof(phandle))) |
503 | return -EFAULT; |
504 | |
505 | if (nd == 0) { |
506 | if (cmd != OPIOCGETNEXT) |
507 | return -EINVAL; |
508 | dp = of_find_node_by_path(path: "/" ); |
509 | } else { |
510 | dp = of_find_node_by_phandle(handle: 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(to: argp, from: &nd, n: sizeof(phandle))) |
522 | return -EFAULT; |
523 | |
524 | return 0; |
525 | } |
526 | |
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(to: argp, from: &options_node->phandle, n: 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(lock: &openprom_mutex); |
566 | |
567 | return err; |
568 | } |
569 | |
570 | |
571 | /* |
572 | * Handoff control to the correct ioctl handler. |
573 | */ |
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 | dp: 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 | dp: 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 | dp: 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 | |
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 | |
665 | static int openprom_open(struct inode * inode, struct file * file) |
666 | { |
667 | DATA *data; |
668 | |
669 | data = kmalloc(size: 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(path: "/" ); |
675 | data->lastnode = data->current_node; |
676 | file->private_data = (void *) data; |
677 | mutex_unlock(lock: &openprom_mutex); |
678 | |
679 | return 0; |
680 | } |
681 | |
682 | static int openprom_release(struct inode * inode, struct file * file) |
683 | { |
684 | kfree(objp: file->private_data); |
685 | return 0; |
686 | } |
687 | |
688 | static const struct file_operations openprom_fops = { |
689 | .owner = THIS_MODULE, |
690 | .llseek = no_llseek, |
691 | .unlocked_ioctl = openprom_ioctl, |
692 | .compat_ioctl = openprom_compat_ioctl, |
693 | .open = openprom_open, |
694 | .release = openprom_release, |
695 | }; |
696 | |
697 | static struct miscdevice openprom_dev = { |
698 | .minor = SUN_OPENPROM_MINOR, |
699 | .name = "openprom" , |
700 | .fops = &openprom_fops, |
701 | }; |
702 | |
703 | static int __init openprom_init(void) |
704 | { |
705 | int err; |
706 | |
707 | err = misc_register(misc: &openprom_dev); |
708 | if (err) |
709 | return err; |
710 | |
711 | options_node = of_get_child_by_name(node: of_find_node_by_path(path: "/" ), name: "options" ); |
712 | if (!options_node) { |
713 | misc_deregister(misc: &openprom_dev); |
714 | return -EIO; |
715 | } |
716 | |
717 | return 0; |
718 | } |
719 | |
720 | static void __exit openprom_cleanup(void) |
721 | { |
722 | misc_deregister(misc: &openprom_dev); |
723 | } |
724 | |
725 | module_init(openprom_init); |
726 | module_exit(openprom_cleanup); |
727 | |