]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
e53e97ce DM |
2 | /* vio.c: Virtual I/O channel devices probing infrastructure. |
3 | * | |
4 | * Copyright (c) 2003-2005 IBM Corp. | |
5 | * Dave Engebretsen engebret@us.ibm.com | |
6 | * Santiago Leon santil@us.ibm.com | |
7 | * Hollis Blanchard <hollisb@us.ibm.com> | |
8 | * Stephen Rothwell | |
9 | * | |
10 | * Adapted to sparc64 by David S. Miller davem@davemloft.net | |
11 | */ | |
12 | ||
13 | #include <linux/kernel.h> | |
5a0e3ad6 | 14 | #include <linux/slab.h> |
e53e97ce | 15 | #include <linux/irq.h> |
7b64db60 | 16 | #include <linux/export.h> |
e53e97ce DM |
17 | #include <linux/init.h> |
18 | ||
19 | #include <asm/mdesc.h> | |
20 | #include <asm/vio.h> | |
21 | ||
e53e97ce DM |
22 | static const struct vio_device_id *vio_match_device( |
23 | const struct vio_device_id *matches, | |
24 | const struct vio_dev *dev) | |
25 | { | |
26 | const char *type, *compat; | |
27 | int len; | |
28 | ||
29 | type = dev->type; | |
30 | compat = dev->compat; | |
31 | len = dev->compat_len; | |
32 | ||
33 | while (matches->type[0] || matches->compat[0]) { | |
34 | int match = 1; | |
83292e0a DM |
35 | if (matches->type[0]) |
36 | match &= !strcmp(matches->type, type); | |
37 | ||
e53e97ce | 38 | if (matches->compat[0]) { |
83292e0a | 39 | match &= len && |
46bcea77 | 40 | of_find_in_proplist(compat, matches->compat, len); |
e53e97ce DM |
41 | } |
42 | if (match) | |
43 | return matches; | |
44 | matches++; | |
45 | } | |
46 | return NULL; | |
47 | } | |
48 | ||
5bde2c9b JPAG |
49 | static int vio_hotplug(struct device *dev, struct kobj_uevent_env *env) |
50 | { | |
51 | const struct vio_dev *vio_dev = to_vio_dev(dev); | |
52 | ||
53 | add_uevent_var(env, "MODALIAS=vio:T%sS%s", vio_dev->type, vio_dev->compat); | |
54 | return 0; | |
55 | } | |
56 | ||
e53e97ce DM |
57 | static int vio_bus_match(struct device *dev, struct device_driver *drv) |
58 | { | |
59 | struct vio_dev *vio_dev = to_vio_dev(dev); | |
60 | struct vio_driver *vio_drv = to_vio_driver(drv); | |
61 | const struct vio_device_id *matches = vio_drv->id_table; | |
62 | ||
63 | if (!matches) | |
64 | return 0; | |
65 | ||
66 | return vio_match_device(matches, vio_dev) != NULL; | |
67 | } | |
68 | ||
69 | static int vio_device_probe(struct device *dev) | |
70 | { | |
71 | struct vio_dev *vdev = to_vio_dev(dev); | |
72 | struct vio_driver *drv = to_vio_driver(dev->driver); | |
73 | const struct vio_device_id *id; | |
e53e97ce | 74 | |
aa512d5e JR |
75 | if (!drv->probe) |
76 | return -ENODEV; | |
77 | ||
78 | id = vio_match_device(drv->id_table, vdev); | |
79 | if (!id) | |
80 | return -ENODEV; | |
81 | ||
82 | /* alloc irqs (unless the driver specified not to) */ | |
83 | if (!drv->no_irq) { | |
84 | if (vdev->tx_irq == 0 && vdev->tx_ino != ~0UL) | |
85 | vdev->tx_irq = sun4v_build_virq(vdev->cdev_handle, | |
86 | vdev->tx_ino); | |
87 | ||
88 | if (vdev->rx_irq == 0 && vdev->rx_ino != ~0UL) | |
89 | vdev->rx_irq = sun4v_build_virq(vdev->cdev_handle, | |
90 | vdev->rx_ino); | |
e53e97ce DM |
91 | } |
92 | ||
aa512d5e | 93 | return drv->probe(vdev, id); |
e53e97ce DM |
94 | } |
95 | ||
96 | static int vio_device_remove(struct device *dev) | |
97 | { | |
98 | struct vio_dev *vdev = to_vio_dev(dev); | |
99 | struct vio_driver *drv = to_vio_driver(dev->driver); | |
100 | ||
aa512d5e JR |
101 | if (drv->remove) { |
102 | /* | |
103 | * Ideally, we would remove/deallocate tx/rx virqs | |
104 | * here - however, there are currently no support | |
105 | * routines to do so at the moment. TBD | |
106 | */ | |
107 | ||
e53e97ce | 108 | return drv->remove(vdev); |
aa512d5e | 109 | } |
e53e97ce DM |
110 | |
111 | return 1; | |
112 | } | |
113 | ||
114 | static ssize_t devspec_show(struct device *dev, | |
115 | struct device_attribute *attr, char *buf) | |
116 | { | |
117 | struct vio_dev *vdev = to_vio_dev(dev); | |
118 | const char *str = "none"; | |
119 | ||
48db7b7c | 120 | if (!strcmp(vdev->type, "vnet-port")) |
2c4f4ecb | 121 | str = "vnet"; |
48db7b7c | 122 | else if (!strcmp(vdev->type, "vdc-port")) |
2c4f4ecb | 123 | str = "vdisk"; |
e53e97ce DM |
124 | |
125 | return sprintf(buf, "%s\n", str); | |
126 | } | |
33acc6db | 127 | static DEVICE_ATTR_RO(devspec); |
e53e97ce | 128 | |
2c4f4ecb DM |
129 | static ssize_t type_show(struct device *dev, |
130 | struct device_attribute *attr, char *buf) | |
131 | { | |
132 | struct vio_dev *vdev = to_vio_dev(dev); | |
133 | return sprintf(buf, "%s\n", vdev->type); | |
134 | } | |
33acc6db | 135 | static DEVICE_ATTR_RO(type); |
2c4f4ecb | 136 | |
36128d20 JPAG |
137 | static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, |
138 | char *buf) | |
139 | { | |
140 | const struct vio_dev *vdev = to_vio_dev(dev); | |
141 | ||
142 | return sprintf(buf, "vio:T%sS%s\n", vdev->type, vdev->compat); | |
143 | } | |
33acc6db | 144 | static DEVICE_ATTR_RO(modalias); |
36128d20 | 145 | |
33acc6db GKH |
146 | static struct attribute *vio_dev_attrs[] = { |
147 | &dev_attr_devspec.attr, | |
148 | &dev_attr_type.attr, | |
149 | &dev_attr_modalias.attr, | |
150 | NULL, | |
151 | }; | |
152 | ATTRIBUTE_GROUPS(vio_dev); | |
e53e97ce DM |
153 | |
154 | static struct bus_type vio_bus_type = { | |
155 | .name = "vio", | |
33acc6db | 156 | .dev_groups = vio_dev_groups, |
5bde2c9b | 157 | .uevent = vio_hotplug, |
e53e97ce DM |
158 | .match = vio_bus_match, |
159 | .probe = vio_device_probe, | |
160 | .remove = vio_device_remove, | |
161 | }; | |
162 | ||
cb52d897 BH |
163 | int __vio_register_driver(struct vio_driver *viodrv, struct module *owner, |
164 | const char *mod_name) | |
e53e97ce DM |
165 | { |
166 | viodrv->driver.bus = &vio_bus_type; | |
cb52d897 BH |
167 | viodrv->driver.name = viodrv->name; |
168 | viodrv->driver.owner = owner; | |
169 | viodrv->driver.mod_name = mod_name; | |
e53e97ce DM |
170 | |
171 | return driver_register(&viodrv->driver); | |
172 | } | |
cb52d897 | 173 | EXPORT_SYMBOL(__vio_register_driver); |
e53e97ce DM |
174 | |
175 | void vio_unregister_driver(struct vio_driver *viodrv) | |
176 | { | |
177 | driver_unregister(&viodrv->driver); | |
178 | } | |
179 | EXPORT_SYMBOL(vio_unregister_driver); | |
180 | ||
a1f35ba3 | 181 | static void vio_dev_release(struct device *dev) |
e53e97ce DM |
182 | { |
183 | kfree(to_vio_dev(dev)); | |
184 | } | |
185 | ||
186 | static ssize_t | |
187 | show_pciobppath_attr(struct device *dev, struct device_attribute *attr, | |
188 | char *buf) | |
189 | { | |
190 | struct vio_dev *vdev; | |
191 | struct device_node *dp; | |
192 | ||
193 | vdev = to_vio_dev(dev); | |
194 | dp = vdev->dp; | |
195 | ||
196 | return snprintf (buf, PAGE_SIZE, "%s\n", dp->full_name); | |
197 | } | |
198 | ||
199 | static DEVICE_ATTR(obppath, S_IRUSR | S_IRGRP | S_IROTH, | |
200 | show_pciobppath_attr, NULL); | |
201 | ||
7694b024 | 202 | static struct device_node *cdev_node; |
e53e97ce DM |
203 | |
204 | static struct vio_dev *root_vdev; | |
205 | static u64 cdev_cfg_handle; | |
206 | ||
e2169a32 JR |
207 | static const u64 *vio_cfg_handle(struct mdesc_handle *hp, u64 node) |
208 | { | |
209 | const u64 *cfg_handle = NULL; | |
210 | u64 a; | |
211 | ||
212 | mdesc_for_each_arc(a, hp, node, MDESC_ARC_TYPE_BACK) { | |
213 | u64 target; | |
214 | ||
215 | target = mdesc_arc_target(hp, a); | |
216 | cfg_handle = mdesc_get_property(hp, target, | |
217 | "cfg-handle", NULL); | |
218 | if (cfg_handle) | |
219 | break; | |
220 | } | |
221 | ||
222 | return cfg_handle; | |
223 | } | |
224 | ||
f4d29ca7 JR |
225 | /** |
226 | * vio_vdev_node() - Find VDEV node in MD | |
227 | * @hp: Handle to the MD | |
228 | * @vdev: Pointer to VDEV | |
229 | * | |
230 | * Find the node in the current MD which matches the given vio_dev. This | |
231 | * must be done dynamically since the node value can change if the MD | |
232 | * is updated. | |
233 | * | |
234 | * NOTE: the MD must be locked, using mdesc_grab(), when calling this routine | |
235 | * | |
236 | * Return: The VDEV node in MDESC | |
237 | */ | |
238 | u64 vio_vdev_node(struct mdesc_handle *hp, struct vio_dev *vdev) | |
239 | { | |
240 | u64 node; | |
241 | ||
242 | if (vdev == NULL) | |
243 | return MDESC_NODE_NULL; | |
244 | ||
245 | node = mdesc_get_node(hp, (const char *)vdev->node_name, | |
246 | &vdev->md_node_info); | |
247 | ||
248 | return node; | |
249 | } | |
5d171050 | 250 | EXPORT_SYMBOL(vio_vdev_node); |
f4d29ca7 | 251 | |
43fdf274 DM |
252 | static void vio_fill_channel_info(struct mdesc_handle *hp, u64 mp, |
253 | struct vio_dev *vdev) | |
254 | { | |
255 | u64 a; | |
256 | ||
aa512d5e JR |
257 | vdev->tx_ino = ~0UL; |
258 | vdev->rx_ino = ~0UL; | |
259 | vdev->channel_id = ~0UL; | |
43fdf274 DM |
260 | mdesc_for_each_arc(a, hp, mp, MDESC_ARC_TYPE_FWD) { |
261 | const u64 *chan_id; | |
262 | const u64 *irq; | |
263 | u64 target; | |
264 | ||
265 | target = mdesc_arc_target(hp, a); | |
266 | ||
267 | irq = mdesc_get_property(hp, target, "tx-ino", NULL); | |
268 | if (irq) | |
aa512d5e | 269 | vdev->tx_ino = *irq; |
43fdf274 DM |
270 | |
271 | irq = mdesc_get_property(hp, target, "rx-ino", NULL); | |
aa512d5e | 272 | if (irq) |
ca605b7d | 273 | vdev->rx_ino = *irq; |
43fdf274 DM |
274 | |
275 | chan_id = mdesc_get_property(hp, target, "id", NULL); | |
276 | if (chan_id) | |
277 | vdev->channel_id = *chan_id; | |
278 | } | |
aa512d5e JR |
279 | |
280 | vdev->cdev_handle = cdev_cfg_handle; | |
43fdf274 DM |
281 | } |
282 | ||
ca605b7d SV |
283 | int vio_set_intr(unsigned long dev_ino, int state) |
284 | { | |
285 | int err; | |
286 | ||
287 | err = sun4v_vintr_set_valid(cdev_cfg_handle, dev_ino, state); | |
288 | return err; | |
289 | } | |
290 | EXPORT_SYMBOL(vio_set_intr); | |
291 | ||
43fdf274 | 292 | static struct vio_dev *vio_create_one(struct mdesc_handle *hp, u64 mp, |
06f3c3ac | 293 | const char *node_name, |
e53e97ce DM |
294 | struct device *parent) |
295 | { | |
0542eb7d | 296 | const char *type, *compat; |
e53e97ce DM |
297 | struct device_node *dp; |
298 | struct vio_dev *vdev; | |
83292e0a | 299 | int err, tlen, clen; |
58fb6666 | 300 | const u64 *id, *cfg_handle; |
e53e97ce | 301 | |
83292e0a | 302 | type = mdesc_get_property(hp, mp, "device-type", &tlen); |
2c4f4ecb | 303 | if (!type) { |
83292e0a DM |
304 | type = mdesc_get_property(hp, mp, "name", &tlen); |
305 | if (!type) { | |
43fdf274 | 306 | type = mdesc_node_name(hp, mp); |
83292e0a DM |
307 | tlen = strlen(type) + 1; |
308 | } | |
309 | } | |
06f3c3ac | 310 | if (tlen > VIO_MAX_TYPE_LEN || strlen(type) >= VIO_MAX_TYPE_LEN) { |
83292e0a DM |
311 | printk(KERN_ERR "VIO: Type string [%s] is too long.\n", |
312 | type); | |
313 | return NULL; | |
2c4f4ecb | 314 | } |
83292e0a | 315 | |
58fb6666 | 316 | id = mdesc_get_property(hp, mp, "id", NULL); |
91ba3c21 | 317 | |
e2169a32 | 318 | cfg_handle = vio_cfg_handle(hp, mp); |
6160f635 | 319 | |
43fdf274 | 320 | compat = mdesc_get_property(hp, mp, "device-type", &clen); |
83292e0a DM |
321 | if (!compat) { |
322 | clen = 0; | |
323 | } else if (clen > VIO_MAX_COMPAT_LEN) { | |
324 | printk(KERN_ERR "VIO: Compat len %d for [%s] is too long.\n", | |
325 | clen, type); | |
326 | return NULL; | |
327 | } | |
e53e97ce DM |
328 | |
329 | vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); | |
330 | if (!vdev) { | |
331 | printk(KERN_ERR "VIO: Could not allocate vio_dev\n"); | |
332 | return NULL; | |
333 | } | |
334 | ||
335 | vdev->mp = mp; | |
83292e0a DM |
336 | memcpy(vdev->type, type, tlen); |
337 | if (compat) | |
338 | memcpy(vdev->compat, compat, clen); | |
339 | else | |
340 | memset(vdev->compat, 0, sizeof(vdev->compat)); | |
e53e97ce DM |
341 | vdev->compat_len = clen; |
342 | ||
15c35e4e | 343 | vdev->port_id = ~0UL; |
aa512d5e JR |
344 | vdev->tx_irq = 0; |
345 | vdev->rx_irq = 0; | |
e53e97ce | 346 | |
43fdf274 | 347 | vio_fill_channel_info(hp, mp, vdev); |
e53e97ce | 348 | |
91ba3c21 | 349 | if (!id) { |
0542eb7d | 350 | dev_set_name(&vdev->dev, "%s", type); |
91ba3c21 | 351 | vdev->dev_no = ~(u64)0; |
58fb6666 | 352 | } else if (!cfg_handle) { |
0542eb7d | 353 | dev_set_name(&vdev->dev, "%s-%llu", type, *id); |
91ba3c21 | 354 | vdev->dev_no = *id; |
58fb6666 | 355 | } else { |
0542eb7d | 356 | dev_set_name(&vdev->dev, "%s-%llu-%llu", type, |
2222c313 | 357 | *cfg_handle, *id); |
58fb6666 | 358 | vdev->dev_no = *cfg_handle; |
15c35e4e | 359 | vdev->port_id = *id; |
91ba3c21 | 360 | } |
6160f635 | 361 | |
e53e97ce DM |
362 | vdev->dev.parent = parent; |
363 | vdev->dev.bus = &vio_bus_type; | |
364 | vdev->dev.release = vio_dev_release; | |
365 | ||
366 | if (parent == NULL) { | |
367 | dp = cdev_node; | |
368 | } else if (to_vio_dev(parent) == root_vdev) { | |
369 | dp = of_get_next_child(cdev_node, NULL); | |
370 | while (dp) { | |
371 | if (!strcmp(dp->type, type)) | |
372 | break; | |
373 | ||
374 | dp = of_get_next_child(cdev_node, dp); | |
375 | } | |
376 | } else { | |
377 | dp = to_vio_dev(parent)->dp; | |
378 | } | |
379 | vdev->dp = dp; | |
380 | ||
f4d29ca7 JR |
381 | /* |
382 | * node_name is NULL for the parent/channel-devices node and | |
383 | * the parent doesn't require the MD node info. | |
384 | */ | |
385 | if (node_name != NULL) { | |
06f3c3ac JR |
386 | (void) snprintf(vdev->node_name, VIO_MAX_NAME_LEN, "%s", |
387 | node_name); | |
388 | ||
f4d29ca7 JR |
389 | err = mdesc_get_node_info(hp, mp, node_name, |
390 | &vdev->md_node_info); | |
391 | if (err) { | |
392 | pr_err("VIO: Could not get MD node info %s, err=%d\n", | |
393 | dev_name(&vdev->dev), err); | |
394 | kfree(vdev); | |
395 | return NULL; | |
396 | } | |
397 | } | |
398 | ||
aa512d5e JR |
399 | pr_info("VIO: Adding device %s (tx_ino = %llx, rx_ino = %llx)\n", |
400 | dev_name(&vdev->dev), vdev->tx_ino, vdev->rx_ino); | |
6160f635 | 401 | |
e53e97ce DM |
402 | err = device_register(&vdev->dev); |
403 | if (err) { | |
404 | printk(KERN_ERR "VIO: Could not register device %s, err=%d\n", | |
2222c313 | 405 | dev_name(&vdev->dev), err); |
e53e97ce DM |
406 | kfree(vdev); |
407 | return NULL; | |
408 | } | |
409 | if (vdev->dp) | |
410 | err = sysfs_create_file(&vdev->dev.kobj, | |
411 | &dev_attr_obppath.attr); | |
412 | ||
413 | return vdev; | |
414 | } | |
415 | ||
06f3c3ac JR |
416 | static void vio_add(struct mdesc_handle *hp, u64 node, |
417 | const char *node_name) | |
e53e97ce | 418 | { |
06f3c3ac | 419 | (void) vio_create_one(hp, node, node_name, &root_vdev->dev); |
e53e97ce DM |
420 | } |
421 | ||
f4d29ca7 JR |
422 | struct vio_remove_node_data { |
423 | struct mdesc_handle *hp; | |
424 | u64 node; | |
c982aa9c JC |
425 | }; |
426 | ||
6160f635 | 427 | static int vio_md_node_match(struct device *dev, void *arg) |
e53e97ce | 428 | { |
6160f635 | 429 | struct vio_dev *vdev = to_vio_dev(dev); |
f4d29ca7 JR |
430 | struct vio_remove_node_data *node_data; |
431 | u64 node; | |
2c4f4ecb | 432 | |
f4d29ca7 | 433 | node_data = (struct vio_remove_node_data *)arg; |
e53e97ce | 434 | |
f4d29ca7 JR |
435 | node = vio_vdev_node(node_data->hp, vdev); |
436 | ||
437 | if (node == node_data->node) | |
438 | return 1; | |
439 | else | |
440 | return 0; | |
6160f635 | 441 | } |
2c4f4ecb | 442 | |
06f3c3ac | 443 | static void vio_remove(struct mdesc_handle *hp, u64 node, const char *node_name) |
6160f635 | 444 | { |
f4d29ca7 | 445 | struct vio_remove_node_data node_data; |
6160f635 | 446 | struct device *dev; |
2c4f4ecb | 447 | |
f4d29ca7 JR |
448 | node_data.hp = hp; |
449 | node_data.node = node; | |
c982aa9c | 450 | |
f4d29ca7 | 451 | dev = device_find_child(&root_vdev->dev, (void *)&node_data, |
6160f635 DM |
452 | vio_md_node_match); |
453 | if (dev) { | |
2222c313 | 454 | printk(KERN_INFO "VIO: Removing device %s\n", dev_name(dev)); |
6160f635 DM |
455 | |
456 | device_unregister(dev); | |
75e44803 | 457 | put_device(dev); |
c982aa9c | 458 | } else { |
f4d29ca7 | 459 | pr_err("VIO: %s node not found in MDESC\n", node_name); |
2c4f4ecb | 460 | } |
e53e97ce DM |
461 | } |
462 | ||
6160f635 DM |
463 | static struct mdesc_notifier_client vio_device_notifier = { |
464 | .add = vio_add, | |
465 | .remove = vio_remove, | |
466 | .node_name = "virtual-device-port", | |
467 | }; | |
468 | ||
07607c54 DM |
469 | /* We are only interested in domain service ports under the |
470 | * "domain-services" node. On control nodes there is another port | |
471 | * under "openboot" that we should not mess with as aparently that is | |
472 | * reserved exclusively for OBP use. | |
473 | */ | |
06f3c3ac JR |
474 | static void vio_add_ds(struct mdesc_handle *hp, u64 node, |
475 | const char *node_name) | |
07607c54 DM |
476 | { |
477 | int found; | |
478 | u64 a; | |
479 | ||
480 | found = 0; | |
481 | mdesc_for_each_arc(a, hp, node, MDESC_ARC_TYPE_BACK) { | |
482 | u64 target = mdesc_arc_target(hp, a); | |
483 | const char *name = mdesc_node_name(hp, target); | |
484 | ||
485 | if (!strcmp(name, "domain-services")) { | |
486 | found = 1; | |
487 | break; | |
488 | } | |
489 | } | |
490 | ||
491 | if (found) | |
06f3c3ac | 492 | (void) vio_create_one(hp, node, node_name, &root_vdev->dev); |
07607c54 DM |
493 | } |
494 | ||
6160f635 | 495 | static struct mdesc_notifier_client vio_ds_notifier = { |
07607c54 | 496 | .add = vio_add_ds, |
6160f635 DM |
497 | .remove = vio_remove, |
498 | .node_name = "domain-services-port", | |
499 | }; | |
500 | ||
7694b024 DM |
501 | static const char *channel_devices_node = "channel-devices"; |
502 | static const char *channel_devices_compat = "SUNW,sun4v-channel-devices"; | |
503 | static const char *cfg_handle_prop = "cfg-handle"; | |
e53e97ce DM |
504 | |
505 | static int __init vio_init(void) | |
506 | { | |
43fdf274 | 507 | struct mdesc_handle *hp; |
e53e97ce DM |
508 | const char *compat; |
509 | const u64 *cfg_handle; | |
510 | int err, len; | |
43fdf274 DM |
511 | u64 root; |
512 | ||
f8be339c DM |
513 | err = bus_register(&vio_bus_type); |
514 | if (err) { | |
515 | printk(KERN_ERR "VIO: Could not register bus type err=%d\n", | |
516 | err); | |
517 | return err; | |
518 | } | |
519 | ||
43fdf274 DM |
520 | hp = mdesc_grab(); |
521 | if (!hp) | |
522 | return 0; | |
e53e97ce | 523 | |
43fdf274 DM |
524 | root = mdesc_node_by_name(hp, MDESC_NODE_NULL, channel_devices_node); |
525 | if (root == MDESC_NODE_NULL) { | |
e53e97ce | 526 | printk(KERN_INFO "VIO: No channel-devices MDESC node.\n"); |
43fdf274 | 527 | mdesc_release(hp); |
e53e97ce DM |
528 | return 0; |
529 | } | |
530 | ||
531 | cdev_node = of_find_node_by_name(NULL, "channel-devices"); | |
43fdf274 | 532 | err = -ENODEV; |
e53e97ce DM |
533 | if (!cdev_node) { |
534 | printk(KERN_INFO "VIO: No channel-devices OBP node.\n"); | |
43fdf274 | 535 | goto out_release; |
e53e97ce DM |
536 | } |
537 | ||
43fdf274 | 538 | compat = mdesc_get_property(hp, root, "compatible", &len); |
e53e97ce DM |
539 | if (!compat) { |
540 | printk(KERN_ERR "VIO: Channel devices lacks compatible " | |
541 | "property\n"); | |
43fdf274 | 542 | goto out_release; |
e53e97ce | 543 | } |
46bcea77 | 544 | if (!of_find_in_proplist(compat, channel_devices_compat, len)) { |
e53e97ce DM |
545 | printk(KERN_ERR "VIO: Channel devices node lacks (%s) " |
546 | "compat entry.\n", channel_devices_compat); | |
43fdf274 | 547 | goto out_release; |
e53e97ce DM |
548 | } |
549 | ||
43fdf274 | 550 | cfg_handle = mdesc_get_property(hp, root, cfg_handle_prop, NULL); |
e53e97ce DM |
551 | if (!cfg_handle) { |
552 | printk(KERN_ERR "VIO: Channel devices lacks %s property\n", | |
553 | cfg_handle_prop); | |
43fdf274 | 554 | goto out_release; |
e53e97ce DM |
555 | } |
556 | ||
557 | cdev_cfg_handle = *cfg_handle; | |
558 | ||
06f3c3ac | 559 | root_vdev = vio_create_one(hp, root, NULL, NULL); |
6160f635 DM |
560 | err = -ENODEV; |
561 | if (!root_vdev) { | |
02b7d834 | 562 | printk(KERN_ERR "VIO: Could not create root device.\n"); |
6160f635 DM |
563 | goto out_release; |
564 | } | |
565 | ||
920c3ed7 DM |
566 | mdesc_register_notifier(&vio_device_notifier); |
567 | mdesc_register_notifier(&vio_ds_notifier); | |
568 | ||
43fdf274 | 569 | mdesc_release(hp); |
e53e97ce | 570 | |
6160f635 | 571 | return err; |
43fdf274 DM |
572 | |
573 | out_release: | |
574 | mdesc_release(hp); | |
575 | return err; | |
e53e97ce DM |
576 | } |
577 | ||
578 | postcore_initcall(vio_init); |