]>
Commit | Line | Data |
---|---|---|
b833b481 SR |
1 | /* |
2 | * Legacy iSeries specific vio initialisation | |
3 | * that needs to be built in (not a module). | |
4 | * | |
bfce5c3c | 5 | * © Copyright 2007 IBM Corporation |
b833b481 SR |
6 | * Author: Stephen Rothwell |
7 | * Some parts collected from various other files | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU General Public License as | |
11 | * published by the Free Software Foundation; either version 2 of the | |
12 | * License, or (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, but | |
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program; if not, write to the Free Software Foundation, | |
21 | * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
22 | */ | |
23 | #include <linux/of.h> | |
24 | #include <linux/init.h> | |
5a0e3ad6 | 25 | #include <linux/slab.h> |
b833b481 SR |
26 | #include <linux/completion.h> |
27 | #include <linux/proc_fs.h> | |
4b16f8e2 | 28 | #include <linux/export.h> |
b833b481 SR |
29 | |
30 | #include <asm/firmware.h> | |
8251b4c4 | 31 | #include <asm/vio.h> |
b833b481 SR |
32 | #include <asm/iseries/vio.h> |
33 | #include <asm/iseries/iommu.h> | |
34 | #include <asm/iseries/hv_types.h> | |
35 | #include <asm/iseries/hv_lp_event.h> | |
36 | ||
37 | #define FIRST_VTY 0 | |
38 | #define NUM_VTYS 1 | |
39 | #define FIRST_VSCSI (FIRST_VTY + NUM_VTYS) | |
40 | #define NUM_VSCSIS 1 | |
41 | #define FIRST_VLAN (FIRST_VSCSI + NUM_VSCSIS) | |
42 | #define NUM_VLANS HVMAXARCHITECTEDVIRTUALLANS | |
43 | #define FIRST_VIODASD (FIRST_VLAN + NUM_VLANS) | |
44 | #define NUM_VIODASDS HVMAXARCHITECTEDVIRTUALDISKS | |
45 | #define FIRST_VIOCD (FIRST_VIODASD + NUM_VIODASDS) | |
46 | #define NUM_VIOCDS HVMAXARCHITECTEDVIRTUALCDROMS | |
47 | #define FIRST_VIOTAPE (FIRST_VIOCD + NUM_VIOCDS) | |
48 | #define NUM_VIOTAPES HVMAXARCHITECTEDVIRTUALTAPES | |
49 | ||
7465ce0d SR |
50 | struct vio_waitevent { |
51 | struct completion com; | |
52 | int rc; | |
53 | u16 sub_result; | |
54 | }; | |
55 | ||
56 | struct vio_resource { | |
57 | char rsrcname[10]; | |
58 | char type[4]; | |
59 | char model[3]; | |
60 | }; | |
61 | ||
8251b4c4 | 62 | static struct property *new_property(const char *name, int length, |
b833b481 SR |
63 | const void *value) |
64 | { | |
65 | struct property *np = kzalloc(sizeof(*np) + strlen(name) + 1 + length, | |
66 | GFP_KERNEL); | |
67 | ||
68 | if (!np) | |
69 | return NULL; | |
70 | np->name = (char *)(np + 1); | |
71 | np->value = np->name + strlen(name) + 1; | |
72 | strcpy(np->name, name); | |
73 | memcpy(np->value, value, length); | |
74 | np->length = length; | |
75 | return np; | |
76 | } | |
77 | ||
16e543ff | 78 | static void free_property(struct property *np) |
b833b481 SR |
79 | { |
80 | kfree(np); | |
81 | } | |
82 | ||
8251b4c4 | 83 | static struct device_node *new_node(const char *path, |
b833b481 SR |
84 | struct device_node *parent) |
85 | { | |
86 | struct device_node *np = kzalloc(sizeof(*np), GFP_KERNEL); | |
87 | ||
88 | if (!np) | |
89 | return NULL; | |
307a54cc | 90 | np->full_name = kstrdup(path, GFP_KERNEL); |
b833b481 SR |
91 | if (!np->full_name) { |
92 | kfree(np); | |
93 | return NULL; | |
94 | } | |
b833b481 SR |
95 | of_node_set_flag(np, OF_DYNAMIC); |
96 | kref_init(&np->kref); | |
97 | np->parent = of_node_get(parent); | |
98 | return np; | |
99 | } | |
100 | ||
8251b4c4 | 101 | static void free_node(struct device_node *np) |
b833b481 SR |
102 | { |
103 | struct property *next; | |
104 | struct property *prop; | |
105 | ||
106 | next = np->properties; | |
107 | while (next) { | |
108 | prop = next; | |
109 | next = prop->next; | |
110 | free_property(prop); | |
111 | } | |
112 | of_node_put(np->parent); | |
113 | kfree(np->full_name); | |
114 | kfree(np); | |
115 | } | |
116 | ||
8251b4c4 | 117 | static int add_string_property(struct device_node *np, const char *name, |
b833b481 SR |
118 | const char *value) |
119 | { | |
120 | struct property *nprop = new_property(name, strlen(value) + 1, value); | |
121 | ||
122 | if (!nprop) | |
123 | return 0; | |
124 | prom_add_property(np, nprop); | |
125 | return 1; | |
126 | } | |
127 | ||
8251b4c4 | 128 | static int add_raw_property(struct device_node *np, const char *name, |
b833b481 SR |
129 | int length, const void *value) |
130 | { | |
131 | struct property *nprop = new_property(name, length, value); | |
132 | ||
133 | if (!nprop) | |
134 | return 0; | |
135 | prom_add_property(np, nprop); | |
136 | return 1; | |
137 | } | |
138 | ||
8251b4c4 SR |
139 | static struct device_node *do_device_node(struct device_node *parent, |
140 | const char *name, u32 reg, u32 unit, const char *type, | |
141 | const char *compat, struct vio_resource *res) | |
142 | { | |
143 | struct device_node *np; | |
144 | char path[32]; | |
145 | ||
146 | snprintf(path, sizeof(path), "/vdevice/%s@%08x", name, reg); | |
147 | np = new_node(path, parent); | |
148 | if (!np) | |
149 | return NULL; | |
150 | if (!add_string_property(np, "name", name) || | |
151 | !add_string_property(np, "device_type", type) || | |
152 | !add_string_property(np, "compatible", compat) || | |
153 | !add_raw_property(np, "reg", sizeof(reg), ®) || | |
154 | !add_raw_property(np, "linux,unit_address", | |
155 | sizeof(unit), &unit)) { | |
156 | goto node_free; | |
157 | } | |
158 | if (res) { | |
159 | if (!add_raw_property(np, "linux,vio_rsrcname", | |
160 | sizeof(res->rsrcname), res->rsrcname) || | |
161 | !add_raw_property(np, "linux,vio_type", | |
162 | sizeof(res->type), res->type) || | |
163 | !add_raw_property(np, "linux,vio_model", | |
164 | sizeof(res->model), res->model)) | |
165 | goto node_free; | |
166 | } | |
167 | np->name = of_get_property(np, "name", NULL); | |
168 | np->type = of_get_property(np, "device_type", NULL); | |
169 | of_attach_node(np); | |
170 | #ifdef CONFIG_PROC_DEVICETREE | |
171 | if (parent->pde) { | |
172 | struct proc_dir_entry *ent; | |
173 | ||
174 | ent = proc_mkdir(strrchr(np->full_name, '/') + 1, parent->pde); | |
175 | if (ent) | |
176 | proc_device_tree_add_node(np, ent); | |
177 | } | |
178 | #endif | |
179 | return np; | |
180 | ||
181 | node_free: | |
182 | free_node(np); | |
183 | return NULL; | |
184 | } | |
185 | ||
186 | /* | |
187 | * This is here so that we can dynamically add viodasd | |
188 | * devices without exposing all the above infrastructure. | |
189 | */ | |
190 | struct vio_dev *vio_create_viodasd(u32 unit) | |
191 | { | |
192 | struct device_node *vio_root; | |
193 | struct device_node *np; | |
194 | struct vio_dev *vdev = NULL; | |
195 | ||
196 | vio_root = of_find_node_by_path("/vdevice"); | |
197 | if (!vio_root) | |
198 | return NULL; | |
199 | np = do_device_node(vio_root, "viodasd", FIRST_VIODASD + unit, unit, | |
200 | "block", "IBM,iSeries-viodasd", NULL); | |
201 | of_node_put(vio_root); | |
202 | if (np) { | |
203 | vdev = vio_register_device_node(np); | |
204 | if (!vdev) | |
205 | free_node(np); | |
206 | } | |
207 | return vdev; | |
208 | } | |
209 | EXPORT_SYMBOL_GPL(vio_create_viodasd); | |
210 | ||
211 | static void __init handle_block_event(struct HvLpEvent *event) | |
212 | { | |
213 | struct vioblocklpevent *bevent = (struct vioblocklpevent *)event; | |
214 | struct vio_waitevent *pwe; | |
215 | ||
216 | if (event == NULL) | |
217 | /* Notification that a partition went away! */ | |
218 | return; | |
219 | /* First, we should NEVER get an int here...only acks */ | |
220 | if (hvlpevent_is_int(event)) { | |
221 | printk(KERN_WARNING "handle_viod_request: " | |
222 | "Yikes! got an int in viodasd event handler!\n"); | |
223 | if (hvlpevent_need_ack(event)) { | |
224 | event->xRc = HvLpEvent_Rc_InvalidSubtype; | |
225 | HvCallEvent_ackLpEvent(event); | |
226 | } | |
227 | return; | |
228 | } | |
229 | ||
230 | switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) { | |
231 | case vioblockopen: | |
232 | /* | |
233 | * Handle a response to an open request. We get all the | |
234 | * disk information in the response, so update it. The | |
235 | * correlation token contains a pointer to a waitevent | |
236 | * structure that has a completion in it. update the | |
237 | * return code in the waitevent structure and post the | |
238 | * completion to wake up the guy who sent the request | |
239 | */ | |
240 | pwe = (struct vio_waitevent *)event->xCorrelationToken; | |
241 | pwe->rc = event->xRc; | |
242 | pwe->sub_result = bevent->sub_result; | |
243 | complete(&pwe->com); | |
244 | break; | |
245 | case vioblockclose: | |
246 | break; | |
247 | default: | |
248 | printk(KERN_WARNING "handle_viod_request: unexpected subtype!"); | |
249 | if (hvlpevent_need_ack(event)) { | |
250 | event->xRc = HvLpEvent_Rc_InvalidSubtype; | |
251 | HvCallEvent_ackLpEvent(event); | |
252 | } | |
253 | } | |
254 | } | |
255 | ||
256 | static void __init probe_disk(struct device_node *vio_root, u32 unit) | |
257 | { | |
258 | HvLpEvent_Rc hvrc; | |
259 | struct vio_waitevent we; | |
260 | u16 flags = 0; | |
261 | ||
262 | retry: | |
263 | init_completion(&we.com); | |
264 | ||
265 | /* Send the open event to OS/400 */ | |
266 | hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, | |
267 | HvLpEvent_Type_VirtualIo, | |
268 | viomajorsubtype_blockio | vioblockopen, | |
269 | HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, | |
270 | viopath_sourceinst(viopath_hostLp), | |
271 | viopath_targetinst(viopath_hostLp), | |
272 | (u64)(unsigned long)&we, VIOVERSION << 16, | |
273 | ((u64)unit << 48) | ((u64)flags<< 32), | |
274 | 0, 0, 0); | |
275 | if (hvrc != 0) { | |
276 | printk(KERN_WARNING "probe_disk: bad rc on HV open %d\n", | |
277 | (int)hvrc); | |
278 | return; | |
279 | } | |
280 | ||
281 | wait_for_completion(&we.com); | |
282 | ||
283 | if (we.rc != 0) { | |
284 | if (flags != 0) | |
285 | return; | |
286 | /* try again with read only flag set */ | |
287 | flags = vioblockflags_ro; | |
288 | goto retry; | |
289 | } | |
290 | ||
291 | /* Send the close event to OS/400. We DON'T expect a response */ | |
292 | hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, | |
293 | HvLpEvent_Type_VirtualIo, | |
294 | viomajorsubtype_blockio | vioblockclose, | |
295 | HvLpEvent_AckInd_NoAck, HvLpEvent_AckType_ImmediateAck, | |
296 | viopath_sourceinst(viopath_hostLp), | |
297 | viopath_targetinst(viopath_hostLp), | |
298 | 0, VIOVERSION << 16, | |
299 | ((u64)unit << 48) | ((u64)flags << 32), | |
300 | 0, 0, 0); | |
301 | if (hvrc != 0) { | |
302 | printk(KERN_WARNING "probe_disk: " | |
303 | "bad rc sending event to OS/400 %d\n", (int)hvrc); | |
304 | return; | |
305 | } | |
306 | ||
307 | do_device_node(vio_root, "viodasd", FIRST_VIODASD + unit, unit, | |
308 | "block", "IBM,iSeries-viodasd", NULL); | |
309 | } | |
310 | ||
311 | static void __init get_viodasd_info(struct device_node *vio_root) | |
312 | { | |
313 | int rc; | |
314 | u32 unit; | |
315 | ||
316 | rc = viopath_open(viopath_hostLp, viomajorsubtype_blockio, 2); | |
317 | if (rc) { | |
318 | printk(KERN_WARNING "get_viodasd_info: " | |
319 | "error opening path to host partition %d\n", | |
320 | viopath_hostLp); | |
321 | return; | |
322 | } | |
323 | ||
324 | /* Initialize our request handler */ | |
325 | vio_setHandler(viomajorsubtype_blockio, handle_block_event); | |
326 | ||
327 | for (unit = 0; unit < HVMAXARCHITECTEDVIRTUALDISKS; unit++) | |
328 | probe_disk(vio_root, unit); | |
329 | ||
330 | vio_clearHandler(viomajorsubtype_blockio); | |
331 | viopath_close(viopath_hostLp, viomajorsubtype_blockio, 2); | |
332 | } | |
333 | ||
b833b481 SR |
334 | static void __init handle_cd_event(struct HvLpEvent *event) |
335 | { | |
336 | struct viocdlpevent *bevent; | |
7465ce0d | 337 | struct vio_waitevent *pwe; |
b833b481 SR |
338 | |
339 | if (!event) | |
340 | /* Notification that a partition went away! */ | |
341 | return; | |
342 | ||
343 | /* First, we should NEVER get an int here...only acks */ | |
344 | if (hvlpevent_is_int(event)) { | |
345 | printk(KERN_WARNING "handle_cd_event: got an unexpected int\n"); | |
346 | if (hvlpevent_need_ack(event)) { | |
347 | event->xRc = HvLpEvent_Rc_InvalidSubtype; | |
348 | HvCallEvent_ackLpEvent(event); | |
349 | } | |
350 | return; | |
351 | } | |
352 | ||
353 | bevent = (struct viocdlpevent *)event; | |
354 | ||
355 | switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) { | |
356 | case viocdgetinfo: | |
7465ce0d | 357 | pwe = (struct vio_waitevent *)event->xCorrelationToken; |
b833b481 SR |
358 | pwe->rc = event->xRc; |
359 | pwe->sub_result = bevent->sub_result; | |
360 | complete(&pwe->com); | |
361 | break; | |
362 | ||
363 | default: | |
364 | printk(KERN_WARNING "handle_cd_event: " | |
365 | "message with unexpected subtype %0x04X!\n", | |
366 | event->xSubtype & VIOMINOR_SUBTYPE_MASK); | |
367 | if (hvlpevent_need_ack(event)) { | |
368 | event->xRc = HvLpEvent_Rc_InvalidSubtype; | |
369 | HvCallEvent_ackLpEvent(event); | |
370 | } | |
371 | } | |
372 | } | |
373 | ||
374 | static void __init get_viocd_info(struct device_node *vio_root) | |
375 | { | |
376 | HvLpEvent_Rc hvrc; | |
377 | u32 unit; | |
7465ce0d SR |
378 | struct vio_waitevent we; |
379 | struct vio_resource *unitinfo; | |
b833b481 SR |
380 | dma_addr_t unitinfo_dmaaddr; |
381 | int ret; | |
382 | ||
383 | ret = viopath_open(viopath_hostLp, viomajorsubtype_cdio, 2); | |
384 | if (ret) { | |
385 | printk(KERN_WARNING | |
386 | "get_viocd_info: error opening path to host partition %d\n", | |
387 | viopath_hostLp); | |
388 | return; | |
389 | } | |
390 | ||
391 | /* Initialize our request handler */ | |
392 | vio_setHandler(viomajorsubtype_cdio, handle_cd_event); | |
393 | ||
394 | unitinfo = iseries_hv_alloc( | |
395 | sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS, | |
396 | &unitinfo_dmaaddr, GFP_ATOMIC); | |
397 | if (!unitinfo) { | |
398 | printk(KERN_WARNING | |
399 | "get_viocd_info: error allocating unitinfo\n"); | |
400 | goto clear_handler; | |
401 | } | |
402 | ||
403 | memset(unitinfo, 0, sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS); | |
404 | ||
405 | init_completion(&we.com); | |
406 | ||
407 | hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, | |
408 | HvLpEvent_Type_VirtualIo, | |
409 | viomajorsubtype_cdio | viocdgetinfo, | |
410 | HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, | |
411 | viopath_sourceinst(viopath_hostLp), | |
412 | viopath_targetinst(viopath_hostLp), | |
413 | (u64)&we, VIOVERSION << 16, unitinfo_dmaaddr, 0, | |
414 | sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS, 0); | |
415 | if (hvrc != HvLpEvent_Rc_Good) { | |
416 | printk(KERN_WARNING | |
417 | "get_viocd_info: cdrom error sending event. rc %d\n", | |
418 | (int)hvrc); | |
419 | goto hv_free; | |
420 | } | |
421 | ||
422 | wait_for_completion(&we.com); | |
423 | ||
424 | if (we.rc) { | |
425 | printk(KERN_WARNING "get_viocd_info: bad rc %d:0x%04X\n", | |
426 | we.rc, we.sub_result); | |
427 | goto hv_free; | |
428 | } | |
429 | ||
430 | for (unit = 0; (unit < HVMAXARCHITECTEDVIRTUALCDROMS) && | |
431 | unitinfo[unit].rsrcname[0]; unit++) { | |
8251b4c4 SR |
432 | if (!do_device_node(vio_root, "viocd", FIRST_VIOCD + unit, unit, |
433 | "block", "IBM,iSeries-viocd", &unitinfo[unit])) | |
434 | break; | |
b833b481 SR |
435 | } |
436 | ||
437 | hv_free: | |
438 | iseries_hv_free(sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS, | |
439 | unitinfo, unitinfo_dmaaddr); | |
440 | clear_handler: | |
441 | vio_clearHandler(viomajorsubtype_cdio); | |
442 | viopath_close(viopath_hostLp, viomajorsubtype_cdio, 2); | |
443 | } | |
444 | ||
7465ce0d SR |
445 | /* Handle interrupt events for tape */ |
446 | static void __init handle_tape_event(struct HvLpEvent *event) | |
447 | { | |
448 | struct vio_waitevent *we; | |
449 | struct viotapelpevent *tevent = (struct viotapelpevent *)event; | |
450 | ||
451 | if (event == NULL) | |
452 | /* Notification that a partition went away! */ | |
453 | return; | |
454 | ||
455 | we = (struct vio_waitevent *)event->xCorrelationToken; | |
456 | switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) { | |
457 | case viotapegetinfo: | |
458 | we->rc = tevent->sub_type_result; | |
459 | complete(&we->com); | |
460 | break; | |
461 | default: | |
462 | printk(KERN_WARNING "handle_tape_event: weird ack\n"); | |
463 | } | |
464 | } | |
465 | ||
466 | static void __init get_viotape_info(struct device_node *vio_root) | |
467 | { | |
468 | HvLpEvent_Rc hvrc; | |
469 | u32 unit; | |
470 | struct vio_resource *unitinfo; | |
471 | dma_addr_t unitinfo_dmaaddr; | |
472 | size_t len = sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALTAPES; | |
473 | struct vio_waitevent we; | |
474 | int ret; | |
475 | ||
b0ff153c ME |
476 | init_completion(&we.com); |
477 | ||
7465ce0d SR |
478 | ret = viopath_open(viopath_hostLp, viomajorsubtype_tape, 2); |
479 | if (ret) { | |
480 | printk(KERN_WARNING "get_viotape_info: " | |
481 | "error on viopath_open to hostlp %d\n", ret); | |
482 | return; | |
483 | } | |
484 | ||
485 | vio_setHandler(viomajorsubtype_tape, handle_tape_event); | |
486 | ||
487 | unitinfo = iseries_hv_alloc(len, &unitinfo_dmaaddr, GFP_ATOMIC); | |
488 | if (!unitinfo) | |
489 | goto clear_handler; | |
490 | ||
491 | memset(unitinfo, 0, len); | |
492 | ||
493 | hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, | |
494 | HvLpEvent_Type_VirtualIo, | |
495 | viomajorsubtype_tape | viotapegetinfo, | |
496 | HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, | |
497 | viopath_sourceinst(viopath_hostLp), | |
498 | viopath_targetinst(viopath_hostLp), | |
499 | (u64)(unsigned long)&we, VIOVERSION << 16, | |
500 | unitinfo_dmaaddr, len, 0, 0); | |
501 | if (hvrc != HvLpEvent_Rc_Good) { | |
502 | printk(KERN_WARNING "get_viotape_info: hv error on op %d\n", | |
503 | (int)hvrc); | |
504 | goto hv_free; | |
505 | } | |
506 | ||
507 | wait_for_completion(&we.com); | |
508 | ||
509 | for (unit = 0; (unit < HVMAXARCHITECTEDVIRTUALTAPES) && | |
510 | unitinfo[unit].rsrcname[0]; unit++) { | |
8251b4c4 SR |
511 | if (!do_device_node(vio_root, "viotape", FIRST_VIOTAPE + unit, |
512 | unit, "byte", "IBM,iSeries-viotape", | |
513 | &unitinfo[unit])) | |
514 | break; | |
7465ce0d SR |
515 | } |
516 | ||
517 | hv_free: | |
518 | iseries_hv_free(len, unitinfo, unitinfo_dmaaddr); | |
519 | clear_handler: | |
520 | vio_clearHandler(viomajorsubtype_tape); | |
521 | viopath_close(viopath_hostLp, viomajorsubtype_tape, 2); | |
522 | } | |
523 | ||
b833b481 SR |
524 | static int __init iseries_vio_init(void) |
525 | { | |
526 | struct device_node *vio_root; | |
7992344f | 527 | int ret = -ENODEV; |
b833b481 SR |
528 | |
529 | if (!firmware_has_feature(FW_FEATURE_ISERIES)) | |
7992344f | 530 | goto out; |
b833b481 SR |
531 | |
532 | iommu_vio_init(); | |
533 | ||
534 | vio_root = of_find_node_by_path("/vdevice"); | |
535 | if (!vio_root) | |
7992344f | 536 | goto out; |
b833b481 SR |
537 | |
538 | if (viopath_hostLp == HvLpIndexInvalid) { | |
539 | vio_set_hostlp(); | |
540 | /* If we don't have a host, bail out */ | |
541 | if (viopath_hostLp == HvLpIndexInvalid) | |
542 | goto put_node; | |
543 | } | |
544 | ||
8251b4c4 | 545 | get_viodasd_info(vio_root); |
b833b481 | 546 | get_viocd_info(vio_root); |
7465ce0d | 547 | get_viotape_info(vio_root); |
b833b481 | 548 | |
7992344f | 549 | ret = 0; |
b833b481 SR |
550 | |
551 | put_node: | |
552 | of_node_put(vio_root); | |
7992344f SR |
553 | out: |
554 | return ret; | |
b833b481 SR |
555 | } |
556 | arch_initcall(iseries_vio_init); |