Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * IBM PowerPC Virtual I/O Infrastructure Support. | |
3 | * | |
19dbd0f6 | 4 | * Copyright (c) 2003-2005 IBM Corp. |
1da177e4 LT |
5 | * Dave Engebretsen engebret@us.ibm.com |
6 | * Santiago Leon santil@us.ibm.com | |
7 | * Hollis Blanchard <hollisb@us.ibm.com> | |
19dbd0f6 | 8 | * Stephen Rothwell |
1da177e4 LT |
9 | * |
10 | * This program is free software; you can redistribute it and/or | |
11 | * modify it under the terms of the GNU General Public License | |
12 | * as published by the Free Software Foundation; either version | |
13 | * 2 of the License, or (at your option) any later version. | |
14 | */ | |
15 | ||
16 | #include <linux/init.h> | |
17 | #include <linux/console.h> | |
1da177e4 | 18 | #include <linux/module.h> |
1da177e4 LT |
19 | #include <linux/mm.h> |
20 | #include <linux/dma-mapping.h> | |
1da177e4 LT |
21 | #include <asm/iommu.h> |
22 | #include <asm/dma.h> | |
1da177e4 | 23 | #include <asm/vio.h> |
143dcec2 | 24 | #include <asm/prom.h> |
e10fa773 | 25 | #include <asm/firmware.h> |
1da177e4 | 26 | |
3e494c80 | 27 | struct vio_dev vio_bus_device = { /* fake "parent" device */ |
ac5b33c9 SR |
28 | .name = vio_bus_device.dev.bus_id, |
29 | .type = "", | |
ac5b33c9 SR |
30 | .dev.bus_id = "vio", |
31 | .dev.bus = &vio_bus_type, | |
1da177e4 | 32 | }; |
ac5b33c9 | 33 | |
71d276d7 | 34 | static struct vio_bus_ops vio_bus_ops; |
1da177e4 | 35 | |
e10fa773 SR |
36 | /** |
37 | * vio_match_device: - Tell if a VIO device has a matching | |
38 | * VIO device id structure. | |
39 | * @ids: array of VIO device id structures to search in | |
40 | * @dev: the VIO device structure to match against | |
41 | * | |
42 | * Used by a driver to check whether a VIO device present in the | |
43 | * system is in its list of supported devices. Returns the matching | |
44 | * vio_device_id structure or NULL if there is no match. | |
45 | */ | |
46 | static const struct vio_device_id *vio_match_device( | |
47 | const struct vio_device_id *ids, const struct vio_dev *dev) | |
48 | { | |
49 | while (ids->type[0] != '\0') { | |
50 | if (vio_bus_ops.match(ids, dev)) | |
51 | return ids; | |
52 | ids++; | |
53 | } | |
54 | return NULL; | |
55 | } | |
56 | ||
5c0b4b87 SR |
57 | /* |
58 | * Convert from struct device to struct vio_dev and pass to driver. | |
1da177e4 | 59 | * dev->driver has already been set by generic code because vio_bus_match |
5c0b4b87 SR |
60 | * succeeded. |
61 | */ | |
1da177e4 LT |
62 | static int vio_bus_probe(struct device *dev) |
63 | { | |
64 | struct vio_dev *viodev = to_vio_dev(dev); | |
65 | struct vio_driver *viodrv = to_vio_driver(dev->driver); | |
66 | const struct vio_device_id *id; | |
67 | int error = -ENODEV; | |
68 | ||
1da177e4 LT |
69 | if (!viodrv->probe) |
70 | return error; | |
71 | ||
72 | id = vio_match_device(viodrv->id_table, viodev); | |
5c0b4b87 | 73 | if (id) |
1da177e4 | 74 | error = viodrv->probe(viodev, id); |
1da177e4 LT |
75 | |
76 | return error; | |
77 | } | |
78 | ||
79 | /* convert from struct device to struct vio_dev and pass to driver. */ | |
80 | static int vio_bus_remove(struct device *dev) | |
81 | { | |
82 | struct vio_dev *viodev = to_vio_dev(dev); | |
83 | struct vio_driver *viodrv = to_vio_driver(dev->driver); | |
84 | ||
5c0b4b87 | 85 | if (viodrv->remove) |
1da177e4 | 86 | return viodrv->remove(viodev); |
1da177e4 LT |
87 | |
88 | /* driver can't remove */ | |
89 | return 1; | |
90 | } | |
91 | ||
34060104 SR |
92 | /* convert from struct device to struct vio_dev and pass to driver. */ |
93 | static void vio_bus_shutdown(struct device *dev) | |
94 | { | |
95 | struct vio_dev *viodev = to_vio_dev(dev); | |
96 | struct vio_driver *viodrv = to_vio_driver(dev->driver); | |
97 | ||
2f53a80f | 98 | if (dev->driver && viodrv->shutdown) |
34060104 SR |
99 | viodrv->shutdown(viodev); |
100 | } | |
101 | ||
1da177e4 LT |
102 | /** |
103 | * vio_register_driver: - Register a new vio driver | |
104 | * @drv: The vio_driver structure to be registered. | |
105 | */ | |
106 | int vio_register_driver(struct vio_driver *viodrv) | |
107 | { | |
108 | printk(KERN_DEBUG "%s: driver %s registering\n", __FUNCTION__, | |
6fdf5392 | 109 | viodrv->driver.name); |
1da177e4 LT |
110 | |
111 | /* fill in 'struct driver' fields */ | |
1da177e4 | 112 | viodrv->driver.bus = &vio_bus_type; |
1da177e4 LT |
113 | |
114 | return driver_register(&viodrv->driver); | |
115 | } | |
116 | EXPORT_SYMBOL(vio_register_driver); | |
117 | ||
118 | /** | |
119 | * vio_unregister_driver - Remove registration of vio driver. | |
120 | * @driver: The vio_driver struct to be removed form registration | |
121 | */ | |
122 | void vio_unregister_driver(struct vio_driver *viodrv) | |
123 | { | |
124 | driver_unregister(&viodrv->driver); | |
125 | } | |
126 | EXPORT_SYMBOL(vio_unregister_driver); | |
127 | ||
128 | /** | |
e10fa773 SR |
129 | * vio_register_device_node: - Register a new vio device. |
130 | * @of_node: The OF node for this device. | |
1da177e4 | 131 | * |
e10fa773 SR |
132 | * Creates and initializes a vio_dev structure from the data in |
133 | * of_node (dev.platform_data) and adds it to the list of virtual devices. | |
134 | * Returns a pointer to the created vio_dev or NULL if node has | |
135 | * NULL device_type or compatible fields. | |
1da177e4 | 136 | */ |
e10fa773 | 137 | struct vio_dev * __devinit vio_register_device_node(struct device_node *of_node) |
1da177e4 | 138 | { |
e10fa773 SR |
139 | struct vio_dev *viodev; |
140 | unsigned int *unit_address; | |
141 | unsigned int *irq_p; | |
142 | ||
143 | /* we need the 'device_type' property, in order to match with drivers */ | |
144 | if (of_node->type == NULL) { | |
145 | printk(KERN_WARNING "%s: node %s missing 'device_type'\n", | |
146 | __FUNCTION__, | |
147 | of_node->name ? of_node->name : "<unknown>"); | |
148 | return NULL; | |
1da177e4 | 149 | } |
e10fa773 SR |
150 | |
151 | unit_address = (unsigned int *)get_property(of_node, "reg", NULL); | |
152 | if (unit_address == NULL) { | |
153 | printk(KERN_WARNING "%s: node %s missing 'reg'\n", | |
154 | __FUNCTION__, | |
155 | of_node->name ? of_node->name : "<unknown>"); | |
156 | return NULL; | |
157 | } | |
158 | ||
159 | /* allocate a vio_dev for this node */ | |
160 | viodev = kzalloc(sizeof(struct vio_dev), GFP_KERNEL); | |
161 | if (viodev == NULL) | |
162 | return NULL; | |
163 | ||
164 | viodev->dev.platform_data = of_node_get(of_node); | |
165 | ||
166 | viodev->irq = NO_IRQ; | |
167 | irq_p = (unsigned int *)get_property(of_node, "interrupts", NULL); | |
168 | if (irq_p) { | |
169 | int virq = virt_irq_create_mapping(*irq_p); | |
170 | if (virq == NO_IRQ) { | |
171 | printk(KERN_ERR "Unable to allocate interrupt " | |
172 | "number for %s\n", of_node->full_name); | |
173 | } else | |
174 | viodev->irq = irq_offset_up(virq); | |
175 | } | |
176 | ||
177 | snprintf(viodev->dev.bus_id, BUS_ID_SIZE, "%x", *unit_address); | |
178 | viodev->name = of_node->name; | |
179 | viodev->type = of_node->type; | |
180 | viodev->unit_address = *unit_address; | |
181 | if (firmware_has_feature(FW_FEATURE_ISERIES)) { | |
182 | unit_address = (unsigned int *)get_property(of_node, | |
183 | "linux,unit_address", NULL); | |
184 | if (unit_address != NULL) | |
185 | viodev->unit_address = *unit_address; | |
186 | } | |
187 | viodev->iommu_table = vio_bus_ops.build_iommu_table(viodev); | |
188 | ||
189 | /* register with generic device framework */ | |
190 | if (vio_register_device(viodev) == NULL) { | |
191 | /* XXX free TCE table */ | |
192 | kfree(viodev); | |
193 | return NULL; | |
194 | } | |
195 | ||
196 | return viodev; | |
1da177e4 | 197 | } |
e10fa773 | 198 | EXPORT_SYMBOL(vio_register_device_node); |
1da177e4 | 199 | |
1da177e4 LT |
200 | /** |
201 | * vio_bus_init: - Initialize the virtual IO bus | |
202 | */ | |
71d276d7 | 203 | int __init vio_bus_init(struct vio_bus_ops *ops) |
1da177e4 LT |
204 | { |
205 | int err; | |
e10fa773 | 206 | struct device_node *node_vroot; |
1da177e4 | 207 | |
71d276d7 | 208 | vio_bus_ops = *ops; |
6312236f | 209 | |
1da177e4 LT |
210 | err = bus_register(&vio_bus_type); |
211 | if (err) { | |
212 | printk(KERN_ERR "failed to register VIO bus\n"); | |
213 | return err; | |
214 | } | |
215 | ||
5c0b4b87 SR |
216 | /* |
217 | * The fake parent of all vio devices, just to give us | |
3e494c80 SR |
218 | * a nice directory |
219 | */ | |
ac5b33c9 | 220 | err = device_register(&vio_bus_device.dev); |
1da177e4 | 221 | if (err) { |
3e494c80 SR |
222 | printk(KERN_WARNING "%s: device_register returned %i\n", |
223 | __FUNCTION__, err); | |
1da177e4 LT |
224 | return err; |
225 | } | |
226 | ||
e10fa773 SR |
227 | node_vroot = find_devices("vdevice"); |
228 | if (node_vroot) { | |
229 | struct device_node *of_node; | |
230 | ||
231 | /* | |
232 | * Create struct vio_devices for each virtual device in | |
233 | * the device tree. Drivers will associate with them later. | |
234 | */ | |
235 | for (of_node = node_vroot->child; of_node != NULL; | |
236 | of_node = of_node->sibling) { | |
237 | printk(KERN_DEBUG "%s: processing %p\n", | |
238 | __FUNCTION__, of_node); | |
239 | vio_register_device_node(of_node); | |
240 | } | |
241 | } | |
242 | ||
3e494c80 SR |
243 | return 0; |
244 | } | |
245 | ||
1da177e4 LT |
246 | /* vio_dev refcount hit 0 */ |
247 | static void __devinit vio_dev_release(struct device *dev) | |
248 | { | |
e10fa773 SR |
249 | if (dev->platform_data) { |
250 | /* XXX free TCE table */ | |
251 | of_node_put(dev->platform_data); | |
252 | } | |
1da177e4 LT |
253 | kfree(to_vio_dev(dev)); |
254 | } | |
255 | ||
e10fa773 | 256 | static ssize_t name_show(struct device *dev, |
5c0b4b87 | 257 | struct device_attribute *attr, char *buf) |
1da177e4 LT |
258 | { |
259 | return sprintf(buf, "%s\n", to_vio_dev(dev)->name); | |
260 | } | |
e10fa773 SR |
261 | |
262 | static ssize_t devspec_show(struct device *dev, | |
263 | struct device_attribute *attr, char *buf) | |
264 | { | |
265 | struct device_node *of_node = dev->platform_data; | |
266 | ||
267 | return sprintf(buf, "%s\n", of_node ? of_node->full_name : "none"); | |
268 | } | |
269 | ||
270 | static struct device_attribute vio_dev_attrs[] = { | |
271 | __ATTR_RO(name), | |
272 | __ATTR_RO(devspec), | |
273 | __ATTR_NULL | |
274 | }; | |
1da177e4 | 275 | |
b877b90f | 276 | struct vio_dev * __devinit vio_register_device(struct vio_dev *viodev) |
1da177e4 | 277 | { |
1da177e4 | 278 | /* init generic 'struct device' fields: */ |
ac5b33c9 | 279 | viodev->dev.parent = &vio_bus_device.dev; |
1da177e4 LT |
280 | viodev->dev.bus = &vio_bus_type; |
281 | viodev->dev.release = vio_dev_release; | |
282 | ||
283 | /* register with generic device framework */ | |
284 | if (device_register(&viodev->dev)) { | |
285 | printk(KERN_ERR "%s: failed to register device %s\n", | |
286 | __FUNCTION__, viodev->dev.bus_id); | |
287 | return NULL; | |
288 | } | |
1da177e4 LT |
289 | |
290 | return viodev; | |
291 | } | |
292 | ||
1da177e4 LT |
293 | void __devinit vio_unregister_device(struct vio_dev *viodev) |
294 | { | |
1da177e4 LT |
295 | device_unregister(&viodev->dev); |
296 | } | |
297 | EXPORT_SYMBOL(vio_unregister_device); | |
298 | ||
1da177e4 LT |
299 | static dma_addr_t vio_map_single(struct device *dev, void *vaddr, |
300 | size_t size, enum dma_data_direction direction) | |
301 | { | |
302 | return iommu_map_single(to_vio_dev(dev)->iommu_table, vaddr, size, | |
7daa411b | 303 | ~0ul, direction); |
1da177e4 LT |
304 | } |
305 | ||
306 | static void vio_unmap_single(struct device *dev, dma_addr_t dma_handle, | |
307 | size_t size, enum dma_data_direction direction) | |
308 | { | |
309 | iommu_unmap_single(to_vio_dev(dev)->iommu_table, dma_handle, size, | |
310 | direction); | |
311 | } | |
312 | ||
313 | static int vio_map_sg(struct device *dev, struct scatterlist *sglist, | |
314 | int nelems, enum dma_data_direction direction) | |
315 | { | |
316 | return iommu_map_sg(dev, to_vio_dev(dev)->iommu_table, sglist, | |
7daa411b | 317 | nelems, ~0ul, direction); |
1da177e4 LT |
318 | } |
319 | ||
320 | static void vio_unmap_sg(struct device *dev, struct scatterlist *sglist, | |
321 | int nelems, enum dma_data_direction direction) | |
322 | { | |
323 | iommu_unmap_sg(to_vio_dev(dev)->iommu_table, sglist, nelems, direction); | |
324 | } | |
325 | ||
326 | static void *vio_alloc_coherent(struct device *dev, size_t size, | |
dd0fc66f | 327 | dma_addr_t *dma_handle, gfp_t flag) |
1da177e4 LT |
328 | { |
329 | return iommu_alloc_coherent(to_vio_dev(dev)->iommu_table, size, | |
7daa411b | 330 | dma_handle, ~0ul, flag); |
1da177e4 LT |
331 | } |
332 | ||
333 | static void vio_free_coherent(struct device *dev, size_t size, | |
334 | void *vaddr, dma_addr_t dma_handle) | |
335 | { | |
336 | iommu_free_coherent(to_vio_dev(dev)->iommu_table, size, vaddr, | |
337 | dma_handle); | |
338 | } | |
339 | ||
340 | static int vio_dma_supported(struct device *dev, u64 mask) | |
341 | { | |
342 | return 1; | |
343 | } | |
344 | ||
345 | struct dma_mapping_ops vio_dma_ops = { | |
346 | .alloc_coherent = vio_alloc_coherent, | |
347 | .free_coherent = vio_free_coherent, | |
348 | .map_single = vio_map_single, | |
349 | .unmap_single = vio_unmap_single, | |
350 | .map_sg = vio_map_sg, | |
351 | .unmap_sg = vio_unmap_sg, | |
352 | .dma_supported = vio_dma_supported, | |
353 | }; | |
354 | ||
355 | static int vio_bus_match(struct device *dev, struct device_driver *drv) | |
356 | { | |
357 | const struct vio_dev *vio_dev = to_vio_dev(dev); | |
358 | struct vio_driver *vio_drv = to_vio_driver(drv); | |
359 | const struct vio_device_id *ids = vio_drv->id_table; | |
1da177e4 | 360 | |
5c0b4b87 | 361 | return (ids != NULL) && (vio_match_device(ids, vio_dev) != NULL); |
1da177e4 LT |
362 | } |
363 | ||
143dcec2 OH |
364 | static int vio_hotplug(struct device *dev, char **envp, int num_envp, |
365 | char *buffer, int buffer_size) | |
366 | { | |
367 | const struct vio_dev *vio_dev = to_vio_dev(dev); | |
e10fa773 | 368 | struct device_node *dn = dev->platform_data; |
143dcec2 OH |
369 | char *cp; |
370 | int length; | |
371 | ||
372 | if (!num_envp) | |
373 | return -ENOMEM; | |
374 | ||
e10fa773 | 375 | if (!dn) |
143dcec2 | 376 | return -ENODEV; |
e10fa773 | 377 | cp = (char *)get_property(dn, "compatible", &length); |
143dcec2 OH |
378 | if (!cp) |
379 | return -ENODEV; | |
380 | ||
381 | envp[0] = buffer; | |
382 | length = scnprintf(buffer, buffer_size, "MODALIAS=vio:T%sS%s", | |
383 | vio_dev->type, cp); | |
e10fa773 | 384 | if ((buffer_size - length) <= 0) |
143dcec2 OH |
385 | return -ENOMEM; |
386 | envp[1] = NULL; | |
387 | return 0; | |
388 | } | |
389 | ||
1da177e4 LT |
390 | struct bus_type vio_bus_type = { |
391 | .name = "vio", | |
e10fa773 | 392 | .dev_attrs = vio_dev_attrs, |
312c004d | 393 | .uevent = vio_hotplug, |
1da177e4 | 394 | .match = vio_bus_match, |
2f53a80f RK |
395 | .probe = vio_bus_probe, |
396 | .remove = vio_bus_remove, | |
397 | .shutdown = vio_bus_shutdown, | |
1da177e4 | 398 | }; |
e10fa773 SR |
399 | |
400 | /** | |
401 | * vio_get_attribute: - get attribute for virtual device | |
402 | * @vdev: The vio device to get property. | |
403 | * @which: The property/attribute to be extracted. | |
404 | * @length: Pointer to length of returned data size (unused if NULL). | |
405 | * | |
406 | * Calls prom.c's get_property() to return the value of the | |
407 | * attribute specified by @which | |
408 | */ | |
409 | const void *vio_get_attribute(struct vio_dev *vdev, char *which, int *length) | |
410 | { | |
411 | return get_property(vdev->dev.platform_data, which, length); | |
412 | } | |
413 | EXPORT_SYMBOL(vio_get_attribute); |