diff options
Diffstat (limited to 'drivers/s390/cio/device.c')
-rw-r--r-- | drivers/s390/cio/device.c | 1135 |
1 files changed, 1135 insertions, 0 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c new file mode 100644 index 000000000000..df0325505e4e --- /dev/null +++ b/drivers/s390/cio/device.c | |||
@@ -0,0 +1,1135 @@ | |||
1 | /* | ||
2 | * drivers/s390/cio/device.c | ||
3 | * bus driver for ccw devices | ||
4 | * $Revision: 1.131 $ | ||
5 | * | ||
6 | * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, | ||
7 | * IBM Corporation | ||
8 | * Author(s): Arnd Bergmann (arndb@de.ibm.com) | ||
9 | * Cornelia Huck (cohuck@de.ibm.com) | ||
10 | * Martin Schwidefsky (schwidefsky@de.ibm.com) | ||
11 | */ | ||
12 | #include <linux/config.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/spinlock.h> | ||
16 | #include <linux/errno.h> | ||
17 | #include <linux/err.h> | ||
18 | #include <linux/slab.h> | ||
19 | #include <linux/list.h> | ||
20 | #include <linux/device.h> | ||
21 | #include <linux/workqueue.h> | ||
22 | |||
23 | #include <asm/ccwdev.h> | ||
24 | #include <asm/cio.h> | ||
25 | |||
26 | #include "cio.h" | ||
27 | #include "css.h" | ||
28 | #include "device.h" | ||
29 | #include "ioasm.h" | ||
30 | |||
31 | /******************* bus type handling ***********************/ | ||
32 | |||
33 | /* The Linux driver model distinguishes between a bus type and | ||
34 | * the bus itself. Of course we only have one channel | ||
35 | * subsystem driver and one channel system per machine, but | ||
36 | * we still use the abstraction. T.R. says it's a good idea. */ | ||
37 | static int | ||
38 | ccw_bus_match (struct device * dev, struct device_driver * drv) | ||
39 | { | ||
40 | struct ccw_device *cdev = to_ccwdev(dev); | ||
41 | struct ccw_driver *cdrv = to_ccwdrv(drv); | ||
42 | const struct ccw_device_id *ids = cdrv->ids, *found; | ||
43 | |||
44 | if (!ids) | ||
45 | return 0; | ||
46 | |||
47 | found = ccw_device_id_match(ids, &cdev->id); | ||
48 | if (!found) | ||
49 | return 0; | ||
50 | |||
51 | cdev->id.driver_info = found->driver_info; | ||
52 | |||
53 | return 1; | ||
54 | } | ||
55 | |||
56 | /* | ||
57 | * Hotplugging interface for ccw devices. | ||
58 | * Heavily modeled on pci and usb hotplug. | ||
59 | */ | ||
60 | static int | ||
61 | ccw_hotplug (struct device *dev, char **envp, int num_envp, | ||
62 | char *buffer, int buffer_size) | ||
63 | { | ||
64 | struct ccw_device *cdev = to_ccwdev(dev); | ||
65 | int i = 0; | ||
66 | int length = 0; | ||
67 | |||
68 | if (!cdev) | ||
69 | return -ENODEV; | ||
70 | |||
71 | /* what we want to pass to /sbin/hotplug */ | ||
72 | |||
73 | envp[i++] = buffer; | ||
74 | length += scnprintf(buffer, buffer_size - length, "CU_TYPE=%04X", | ||
75 | cdev->id.cu_type); | ||
76 | if ((buffer_size - length <= 0) || (i >= num_envp)) | ||
77 | return -ENOMEM; | ||
78 | ++length; | ||
79 | buffer += length; | ||
80 | |||
81 | envp[i++] = buffer; | ||
82 | length += scnprintf(buffer, buffer_size - length, "CU_MODEL=%02X", | ||
83 | cdev->id.cu_model); | ||
84 | if ((buffer_size - length <= 0) || (i >= num_envp)) | ||
85 | return -ENOMEM; | ||
86 | ++length; | ||
87 | buffer += length; | ||
88 | |||
89 | /* The next two can be zero, that's ok for us */ | ||
90 | envp[i++] = buffer; | ||
91 | length += scnprintf(buffer, buffer_size - length, "DEV_TYPE=%04X", | ||
92 | cdev->id.dev_type); | ||
93 | if ((buffer_size - length <= 0) || (i >= num_envp)) | ||
94 | return -ENOMEM; | ||
95 | ++length; | ||
96 | buffer += length; | ||
97 | |||
98 | envp[i++] = buffer; | ||
99 | length += scnprintf(buffer, buffer_size - length, "DEV_MODEL=%02X", | ||
100 | cdev->id.dev_model); | ||
101 | if ((buffer_size - length <= 0) || (i >= num_envp)) | ||
102 | return -ENOMEM; | ||
103 | |||
104 | envp[i] = 0; | ||
105 | |||
106 | return 0; | ||
107 | } | ||
108 | |||
109 | struct bus_type ccw_bus_type = { | ||
110 | .name = "ccw", | ||
111 | .match = &ccw_bus_match, | ||
112 | .hotplug = &ccw_hotplug, | ||
113 | }; | ||
114 | |||
115 | static int io_subchannel_probe (struct device *); | ||
116 | static int io_subchannel_remove (struct device *); | ||
117 | void io_subchannel_irq (struct device *); | ||
118 | static int io_subchannel_notify(struct device *, int); | ||
119 | static void io_subchannel_verify(struct device *); | ||
120 | static void io_subchannel_ioterm(struct device *); | ||
121 | static void io_subchannel_shutdown(struct device *); | ||
122 | |||
123 | struct css_driver io_subchannel_driver = { | ||
124 | .subchannel_type = SUBCHANNEL_TYPE_IO, | ||
125 | .drv = { | ||
126 | .name = "io_subchannel", | ||
127 | .bus = &css_bus_type, | ||
128 | .probe = &io_subchannel_probe, | ||
129 | .remove = &io_subchannel_remove, | ||
130 | .shutdown = &io_subchannel_shutdown, | ||
131 | }, | ||
132 | .irq = io_subchannel_irq, | ||
133 | .notify = io_subchannel_notify, | ||
134 | .verify = io_subchannel_verify, | ||
135 | .termination = io_subchannel_ioterm, | ||
136 | }; | ||
137 | |||
138 | struct workqueue_struct *ccw_device_work; | ||
139 | struct workqueue_struct *ccw_device_notify_work; | ||
140 | static wait_queue_head_t ccw_device_init_wq; | ||
141 | static atomic_t ccw_device_init_count; | ||
142 | |||
143 | static int __init | ||
144 | init_ccw_bus_type (void) | ||
145 | { | ||
146 | int ret; | ||
147 | |||
148 | init_waitqueue_head(&ccw_device_init_wq); | ||
149 | atomic_set(&ccw_device_init_count, 0); | ||
150 | |||
151 | ccw_device_work = create_singlethread_workqueue("cio"); | ||
152 | if (!ccw_device_work) | ||
153 | return -ENOMEM; /* FIXME: better errno ? */ | ||
154 | ccw_device_notify_work = create_singlethread_workqueue("cio_notify"); | ||
155 | if (!ccw_device_notify_work) { | ||
156 | ret = -ENOMEM; /* FIXME: better errno ? */ | ||
157 | goto out_err; | ||
158 | } | ||
159 | slow_path_wq = create_singlethread_workqueue("kslowcrw"); | ||
160 | if (!slow_path_wq) { | ||
161 | ret = -ENOMEM; /* FIXME: better errno ? */ | ||
162 | goto out_err; | ||
163 | } | ||
164 | if ((ret = bus_register (&ccw_bus_type))) | ||
165 | goto out_err; | ||
166 | |||
167 | if ((ret = driver_register(&io_subchannel_driver.drv))) | ||
168 | goto out_err; | ||
169 | |||
170 | wait_event(ccw_device_init_wq, | ||
171 | atomic_read(&ccw_device_init_count) == 0); | ||
172 | flush_workqueue(ccw_device_work); | ||
173 | return 0; | ||
174 | out_err: | ||
175 | if (ccw_device_work) | ||
176 | destroy_workqueue(ccw_device_work); | ||
177 | if (ccw_device_notify_work) | ||
178 | destroy_workqueue(ccw_device_notify_work); | ||
179 | if (slow_path_wq) | ||
180 | destroy_workqueue(slow_path_wq); | ||
181 | return ret; | ||
182 | } | ||
183 | |||
184 | static void __exit | ||
185 | cleanup_ccw_bus_type (void) | ||
186 | { | ||
187 | driver_unregister(&io_subchannel_driver.drv); | ||
188 | bus_unregister(&ccw_bus_type); | ||
189 | destroy_workqueue(ccw_device_notify_work); | ||
190 | destroy_workqueue(ccw_device_work); | ||
191 | } | ||
192 | |||
193 | subsys_initcall(init_ccw_bus_type); | ||
194 | module_exit(cleanup_ccw_bus_type); | ||
195 | |||
196 | /************************ device handling **************************/ | ||
197 | |||
198 | /* | ||
199 | * A ccw_device has some interfaces in sysfs in addition to the | ||
200 | * standard ones. | ||
201 | * The following entries are designed to export the information which | ||
202 | * resided in 2.4 in /proc/subchannels. Subchannel and device number | ||
203 | * are obvious, so they don't have an entry :) | ||
204 | * TODO: Split chpids and pimpampom up? Where is "in use" in the tree? | ||
205 | */ | ||
206 | static ssize_t | ||
207 | chpids_show (struct device * dev, char * buf) | ||
208 | { | ||
209 | struct subchannel *sch = to_subchannel(dev); | ||
210 | struct ssd_info *ssd = &sch->ssd_info; | ||
211 | ssize_t ret = 0; | ||
212 | int chp; | ||
213 | |||
214 | for (chp = 0; chp < 8; chp++) | ||
215 | ret += sprintf (buf+ret, "%02x ", ssd->chpid[chp]); | ||
216 | |||
217 | ret += sprintf (buf+ret, "\n"); | ||
218 | return min((ssize_t)PAGE_SIZE, ret); | ||
219 | } | ||
220 | |||
221 | static ssize_t | ||
222 | pimpampom_show (struct device * dev, char * buf) | ||
223 | { | ||
224 | struct subchannel *sch = to_subchannel(dev); | ||
225 | struct pmcw *pmcw = &sch->schib.pmcw; | ||
226 | |||
227 | return sprintf (buf, "%02x %02x %02x\n", | ||
228 | pmcw->pim, pmcw->pam, pmcw->pom); | ||
229 | } | ||
230 | |||
231 | static ssize_t | ||
232 | devtype_show (struct device *dev, char *buf) | ||
233 | { | ||
234 | struct ccw_device *cdev = to_ccwdev(dev); | ||
235 | struct ccw_device_id *id = &(cdev->id); | ||
236 | |||
237 | if (id->dev_type != 0) | ||
238 | return sprintf(buf, "%04x/%02x\n", | ||
239 | id->dev_type, id->dev_model); | ||
240 | else | ||
241 | return sprintf(buf, "n/a\n"); | ||
242 | } | ||
243 | |||
244 | static ssize_t | ||
245 | cutype_show (struct device *dev, char *buf) | ||
246 | { | ||
247 | struct ccw_device *cdev = to_ccwdev(dev); | ||
248 | struct ccw_device_id *id = &(cdev->id); | ||
249 | |||
250 | return sprintf(buf, "%04x/%02x\n", | ||
251 | id->cu_type, id->cu_model); | ||
252 | } | ||
253 | |||
254 | static ssize_t | ||
255 | online_show (struct device *dev, char *buf) | ||
256 | { | ||
257 | struct ccw_device *cdev = to_ccwdev(dev); | ||
258 | |||
259 | return sprintf(buf, cdev->online ? "1\n" : "0\n"); | ||
260 | } | ||
261 | |||
262 | static void | ||
263 | ccw_device_remove_disconnected(struct ccw_device *cdev) | ||
264 | { | ||
265 | struct subchannel *sch; | ||
266 | /* | ||
267 | * Forced offline in disconnected state means | ||
268 | * 'throw away device'. | ||
269 | */ | ||
270 | sch = to_subchannel(cdev->dev.parent); | ||
271 | device_unregister(&sch->dev); | ||
272 | /* Reset intparm to zeroes. */ | ||
273 | sch->schib.pmcw.intparm = 0; | ||
274 | cio_modify(sch); | ||
275 | put_device(&sch->dev); | ||
276 | } | ||
277 | |||
278 | int | ||
279 | ccw_device_set_offline(struct ccw_device *cdev) | ||
280 | { | ||
281 | int ret; | ||
282 | |||
283 | if (!cdev) | ||
284 | return -ENODEV; | ||
285 | if (!cdev->online || !cdev->drv) | ||
286 | return -EINVAL; | ||
287 | |||
288 | if (cdev->drv->set_offline) { | ||
289 | ret = cdev->drv->set_offline(cdev); | ||
290 | if (ret != 0) | ||
291 | return ret; | ||
292 | } | ||
293 | cdev->online = 0; | ||
294 | spin_lock_irq(cdev->ccwlock); | ||
295 | ret = ccw_device_offline(cdev); | ||
296 | if (ret == -ENODEV) { | ||
297 | if (cdev->private->state != DEV_STATE_NOT_OPER) { | ||
298 | cdev->private->state = DEV_STATE_OFFLINE; | ||
299 | dev_fsm_event(cdev, DEV_EVENT_NOTOPER); | ||
300 | } | ||
301 | spin_unlock_irq(cdev->ccwlock); | ||
302 | return ret; | ||
303 | } | ||
304 | spin_unlock_irq(cdev->ccwlock); | ||
305 | if (ret == 0) | ||
306 | wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); | ||
307 | else { | ||
308 | pr_debug("ccw_device_offline returned %d, device %s\n", | ||
309 | ret, cdev->dev.bus_id); | ||
310 | cdev->online = 1; | ||
311 | } | ||
312 | return ret; | ||
313 | } | ||
314 | |||
315 | int | ||
316 | ccw_device_set_online(struct ccw_device *cdev) | ||
317 | { | ||
318 | int ret; | ||
319 | |||
320 | if (!cdev) | ||
321 | return -ENODEV; | ||
322 | if (cdev->online || !cdev->drv) | ||
323 | return -EINVAL; | ||
324 | |||
325 | spin_lock_irq(cdev->ccwlock); | ||
326 | ret = ccw_device_online(cdev); | ||
327 | spin_unlock_irq(cdev->ccwlock); | ||
328 | if (ret == 0) | ||
329 | wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); | ||
330 | else { | ||
331 | pr_debug("ccw_device_online returned %d, device %s\n", | ||
332 | ret, cdev->dev.bus_id); | ||
333 | return ret; | ||
334 | } | ||
335 | if (cdev->private->state != DEV_STATE_ONLINE) | ||
336 | return -ENODEV; | ||
337 | if (!cdev->drv->set_online || cdev->drv->set_online(cdev) == 0) { | ||
338 | cdev->online = 1; | ||
339 | return 0; | ||
340 | } | ||
341 | spin_lock_irq(cdev->ccwlock); | ||
342 | ret = ccw_device_offline(cdev); | ||
343 | spin_unlock_irq(cdev->ccwlock); | ||
344 | if (ret == 0) | ||
345 | wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); | ||
346 | else | ||
347 | pr_debug("ccw_device_offline returned %d, device %s\n", | ||
348 | ret, cdev->dev.bus_id); | ||
349 | return (ret = 0) ? -ENODEV : ret; | ||
350 | } | ||
351 | |||
352 | static ssize_t | ||
353 | online_store (struct device *dev, const char *buf, size_t count) | ||
354 | { | ||
355 | struct ccw_device *cdev = to_ccwdev(dev); | ||
356 | int i, force, ret; | ||
357 | char *tmp; | ||
358 | |||
359 | if (atomic_compare_and_swap(0, 1, &cdev->private->onoff)) | ||
360 | return -EAGAIN; | ||
361 | |||
362 | if (cdev->drv && !try_module_get(cdev->drv->owner)) { | ||
363 | atomic_set(&cdev->private->onoff, 0); | ||
364 | return -EINVAL; | ||
365 | } | ||
366 | if (!strncmp(buf, "force\n", count)) { | ||
367 | force = 1; | ||
368 | i = 1; | ||
369 | } else { | ||
370 | force = 0; | ||
371 | i = simple_strtoul(buf, &tmp, 16); | ||
372 | } | ||
373 | if (i == 1) { | ||
374 | /* Do device recognition, if needed. */ | ||
375 | if (cdev->id.cu_type == 0) { | ||
376 | ret = ccw_device_recognition(cdev); | ||
377 | if (ret) { | ||
378 | printk(KERN_WARNING"Couldn't start recognition " | ||
379 | "for device %s (ret=%d)\n", | ||
380 | cdev->dev.bus_id, ret); | ||
381 | goto out; | ||
382 | } | ||
383 | wait_event(cdev->private->wait_q, | ||
384 | cdev->private->flags.recog_done); | ||
385 | } | ||
386 | if (cdev->drv && cdev->drv->set_online) | ||
387 | ccw_device_set_online(cdev); | ||
388 | } else if (i == 0) { | ||
389 | if (cdev->private->state == DEV_STATE_DISCONNECTED) | ||
390 | ccw_device_remove_disconnected(cdev); | ||
391 | else if (cdev->drv && cdev->drv->set_offline) | ||
392 | ccw_device_set_offline(cdev); | ||
393 | } | ||
394 | if (force && cdev->private->state == DEV_STATE_BOXED) { | ||
395 | ret = ccw_device_stlck(cdev); | ||
396 | if (ret) { | ||
397 | printk(KERN_WARNING"ccw_device_stlck for device %s " | ||
398 | "returned %d!\n", cdev->dev.bus_id, ret); | ||
399 | goto out; | ||
400 | } | ||
401 | /* Do device recognition, if needed. */ | ||
402 | if (cdev->id.cu_type == 0) { | ||
403 | cdev->private->state = DEV_STATE_NOT_OPER; | ||
404 | ret = ccw_device_recognition(cdev); | ||
405 | if (ret) { | ||
406 | printk(KERN_WARNING"Couldn't start recognition " | ||
407 | "for device %s (ret=%d)\n", | ||
408 | cdev->dev.bus_id, ret); | ||
409 | goto out; | ||
410 | } | ||
411 | wait_event(cdev->private->wait_q, | ||
412 | cdev->private->flags.recog_done); | ||
413 | } | ||
414 | if (cdev->drv && cdev->drv->set_online) | ||
415 | ccw_device_set_online(cdev); | ||
416 | } | ||
417 | out: | ||
418 | if (cdev->drv) | ||
419 | module_put(cdev->drv->owner); | ||
420 | atomic_set(&cdev->private->onoff, 0); | ||
421 | return count; | ||
422 | } | ||
423 | |||
424 | static ssize_t | ||
425 | available_show (struct device *dev, char *buf) | ||
426 | { | ||
427 | struct ccw_device *cdev = to_ccwdev(dev); | ||
428 | struct subchannel *sch; | ||
429 | |||
430 | switch (cdev->private->state) { | ||
431 | case DEV_STATE_BOXED: | ||
432 | return sprintf(buf, "boxed\n"); | ||
433 | case DEV_STATE_DISCONNECTED: | ||
434 | case DEV_STATE_DISCONNECTED_SENSE_ID: | ||
435 | case DEV_STATE_NOT_OPER: | ||
436 | sch = to_subchannel(dev->parent); | ||
437 | if (!sch->lpm) | ||
438 | return sprintf(buf, "no path\n"); | ||
439 | else | ||
440 | return sprintf(buf, "no device\n"); | ||
441 | default: | ||
442 | /* All other states considered fine. */ | ||
443 | return sprintf(buf, "good\n"); | ||
444 | } | ||
445 | } | ||
446 | |||
447 | static DEVICE_ATTR(chpids, 0444, chpids_show, NULL); | ||
448 | static DEVICE_ATTR(pimpampom, 0444, pimpampom_show, NULL); | ||
449 | static DEVICE_ATTR(devtype, 0444, devtype_show, NULL); | ||
450 | static DEVICE_ATTR(cutype, 0444, cutype_show, NULL); | ||
451 | static DEVICE_ATTR(online, 0644, online_show, online_store); | ||
452 | extern struct device_attribute dev_attr_cmb_enable; | ||
453 | static DEVICE_ATTR(availability, 0444, available_show, NULL); | ||
454 | |||
455 | static struct attribute * subch_attrs[] = { | ||
456 | &dev_attr_chpids.attr, | ||
457 | &dev_attr_pimpampom.attr, | ||
458 | NULL, | ||
459 | }; | ||
460 | |||
461 | static struct attribute_group subch_attr_group = { | ||
462 | .attrs = subch_attrs, | ||
463 | }; | ||
464 | |||
465 | static inline int | ||
466 | subchannel_add_files (struct device *dev) | ||
467 | { | ||
468 | return sysfs_create_group(&dev->kobj, &subch_attr_group); | ||
469 | } | ||
470 | |||
471 | static struct attribute * ccwdev_attrs[] = { | ||
472 | &dev_attr_devtype.attr, | ||
473 | &dev_attr_cutype.attr, | ||
474 | &dev_attr_online.attr, | ||
475 | &dev_attr_cmb_enable.attr, | ||
476 | &dev_attr_availability.attr, | ||
477 | NULL, | ||
478 | }; | ||
479 | |||
480 | static struct attribute_group ccwdev_attr_group = { | ||
481 | .attrs = ccwdev_attrs, | ||
482 | }; | ||
483 | |||
484 | static inline int | ||
485 | device_add_files (struct device *dev) | ||
486 | { | ||
487 | return sysfs_create_group(&dev->kobj, &ccwdev_attr_group); | ||
488 | } | ||
489 | |||
490 | static inline void | ||
491 | device_remove_files(struct device *dev) | ||
492 | { | ||
493 | sysfs_remove_group(&dev->kobj, &ccwdev_attr_group); | ||
494 | } | ||
495 | |||
496 | /* this is a simple abstraction for device_register that sets the | ||
497 | * correct bus type and adds the bus specific files */ | ||
498 | int | ||
499 | ccw_device_register(struct ccw_device *cdev) | ||
500 | { | ||
501 | struct device *dev = &cdev->dev; | ||
502 | int ret; | ||
503 | |||
504 | dev->bus = &ccw_bus_type; | ||
505 | |||
506 | if ((ret = device_add(dev))) | ||
507 | return ret; | ||
508 | |||
509 | set_bit(1, &cdev->private->registered); | ||
510 | if ((ret = device_add_files(dev))) { | ||
511 | if (test_and_clear_bit(1, &cdev->private->registered)) | ||
512 | device_del(dev); | ||
513 | } | ||
514 | return ret; | ||
515 | } | ||
516 | |||
517 | static struct ccw_device * | ||
518 | get_disc_ccwdev_by_devno(unsigned int devno, struct ccw_device *sibling) | ||
519 | { | ||
520 | struct ccw_device *cdev; | ||
521 | struct list_head *entry; | ||
522 | struct device *dev; | ||
523 | |||
524 | if (!get_bus(&ccw_bus_type)) | ||
525 | return NULL; | ||
526 | down_read(&ccw_bus_type.subsys.rwsem); | ||
527 | cdev = NULL; | ||
528 | list_for_each(entry, &ccw_bus_type.devices.list) { | ||
529 | dev = get_device(container_of(entry, | ||
530 | struct device, bus_list)); | ||
531 | if (!dev) | ||
532 | continue; | ||
533 | cdev = to_ccwdev(dev); | ||
534 | if ((cdev->private->state == DEV_STATE_DISCONNECTED) && | ||
535 | (cdev->private->devno == devno) && | ||
536 | (cdev != sibling)) { | ||
537 | cdev->private->state = DEV_STATE_NOT_OPER; | ||
538 | break; | ||
539 | } | ||
540 | put_device(dev); | ||
541 | cdev = NULL; | ||
542 | } | ||
543 | up_read(&ccw_bus_type.subsys.rwsem); | ||
544 | put_bus(&ccw_bus_type); | ||
545 | |||
546 | return cdev; | ||
547 | } | ||
548 | |||
549 | static void | ||
550 | ccw_device_add_changed(void *data) | ||
551 | { | ||
552 | |||
553 | struct ccw_device *cdev; | ||
554 | |||
555 | cdev = (struct ccw_device *)data; | ||
556 | if (device_add(&cdev->dev)) { | ||
557 | put_device(&cdev->dev); | ||
558 | return; | ||
559 | } | ||
560 | set_bit(1, &cdev->private->registered); | ||
561 | if (device_add_files(&cdev->dev)) { | ||
562 | if (test_and_clear_bit(1, &cdev->private->registered)) | ||
563 | device_unregister(&cdev->dev); | ||
564 | } | ||
565 | } | ||
566 | |||
567 | extern int css_get_ssd_info(struct subchannel *sch); | ||
568 | |||
569 | void | ||
570 | ccw_device_do_unreg_rereg(void *data) | ||
571 | { | ||
572 | struct ccw_device *cdev; | ||
573 | struct subchannel *sch; | ||
574 | int need_rename; | ||
575 | |||
576 | cdev = (struct ccw_device *)data; | ||
577 | sch = to_subchannel(cdev->dev.parent); | ||
578 | if (cdev->private->devno != sch->schib.pmcw.dev) { | ||
579 | /* | ||
580 | * The device number has changed. This is usually only when | ||
581 | * a device has been detached under VM and then re-appeared | ||
582 | * on another subchannel because of a different attachment | ||
583 | * order than before. Ideally, we should should just switch | ||
584 | * subchannels, but unfortunately, this is not possible with | ||
585 | * the current implementation. | ||
586 | * Instead, we search for the old subchannel for this device | ||
587 | * number and deregister so there are no collisions with the | ||
588 | * newly registered ccw_device. | ||
589 | * FIXME: Find another solution so the block layer doesn't | ||
590 | * get possibly sick... | ||
591 | */ | ||
592 | struct ccw_device *other_cdev; | ||
593 | |||
594 | need_rename = 1; | ||
595 | other_cdev = get_disc_ccwdev_by_devno(sch->schib.pmcw.dev, | ||
596 | cdev); | ||
597 | if (other_cdev) { | ||
598 | struct subchannel *other_sch; | ||
599 | |||
600 | other_sch = to_subchannel(other_cdev->dev.parent); | ||
601 | if (get_device(&other_sch->dev)) { | ||
602 | stsch(other_sch->irq, &other_sch->schib); | ||
603 | if (other_sch->schib.pmcw.dnv) { | ||
604 | other_sch->schib.pmcw.intparm = 0; | ||
605 | cio_modify(other_sch); | ||
606 | } | ||
607 | device_unregister(&other_sch->dev); | ||
608 | } | ||
609 | } | ||
610 | /* Update ssd info here. */ | ||
611 | css_get_ssd_info(sch); | ||
612 | cdev->private->devno = sch->schib.pmcw.dev; | ||
613 | } else | ||
614 | need_rename = 0; | ||
615 | device_remove_files(&cdev->dev); | ||
616 | if (test_and_clear_bit(1, &cdev->private->registered)) | ||
617 | device_del(&cdev->dev); | ||
618 | if (need_rename) | ||
619 | snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", | ||
620 | sch->schib.pmcw.dev); | ||
621 | PREPARE_WORK(&cdev->private->kick_work, | ||
622 | ccw_device_add_changed, (void *)cdev); | ||
623 | queue_work(ccw_device_work, &cdev->private->kick_work); | ||
624 | } | ||
625 | |||
626 | static void | ||
627 | ccw_device_release(struct device *dev) | ||
628 | { | ||
629 | struct ccw_device *cdev; | ||
630 | |||
631 | cdev = to_ccwdev(dev); | ||
632 | kfree(cdev->private); | ||
633 | kfree(cdev); | ||
634 | } | ||
635 | |||
636 | /* | ||
637 | * Register recognized device. | ||
638 | */ | ||
639 | static void | ||
640 | io_subchannel_register(void *data) | ||
641 | { | ||
642 | struct ccw_device *cdev; | ||
643 | struct subchannel *sch; | ||
644 | int ret; | ||
645 | unsigned long flags; | ||
646 | |||
647 | cdev = (struct ccw_device *) data; | ||
648 | sch = to_subchannel(cdev->dev.parent); | ||
649 | |||
650 | if (!list_empty(&sch->dev.children)) { | ||
651 | bus_rescan_devices(&ccw_bus_type); | ||
652 | goto out; | ||
653 | } | ||
654 | /* make it known to the system */ | ||
655 | ret = ccw_device_register(cdev); | ||
656 | if (ret) { | ||
657 | printk (KERN_WARNING "%s: could not register %s\n", | ||
658 | __func__, cdev->dev.bus_id); | ||
659 | put_device(&cdev->dev); | ||
660 | spin_lock_irqsave(&sch->lock, flags); | ||
661 | sch->dev.driver_data = NULL; | ||
662 | spin_unlock_irqrestore(&sch->lock, flags); | ||
663 | kfree (cdev->private); | ||
664 | kfree (cdev); | ||
665 | put_device(&sch->dev); | ||
666 | if (atomic_dec_and_test(&ccw_device_init_count)) | ||
667 | wake_up(&ccw_device_init_wq); | ||
668 | return; | ||
669 | } | ||
670 | |||
671 | ret = subchannel_add_files(cdev->dev.parent); | ||
672 | if (ret) | ||
673 | printk(KERN_WARNING "%s: could not add attributes to %s\n", | ||
674 | __func__, sch->dev.bus_id); | ||
675 | put_device(&cdev->dev); | ||
676 | out: | ||
677 | cdev->private->flags.recog_done = 1; | ||
678 | put_device(&sch->dev); | ||
679 | wake_up(&cdev->private->wait_q); | ||
680 | if (atomic_dec_and_test(&ccw_device_init_count)) | ||
681 | wake_up(&ccw_device_init_wq); | ||
682 | } | ||
683 | |||
684 | void | ||
685 | ccw_device_call_sch_unregister(void *data) | ||
686 | { | ||
687 | struct ccw_device *cdev = data; | ||
688 | struct subchannel *sch; | ||
689 | |||
690 | sch = to_subchannel(cdev->dev.parent); | ||
691 | device_unregister(&sch->dev); | ||
692 | /* Reset intparm to zeroes. */ | ||
693 | sch->schib.pmcw.intparm = 0; | ||
694 | cio_modify(sch); | ||
695 | put_device(&cdev->dev); | ||
696 | put_device(&sch->dev); | ||
697 | } | ||
698 | |||
699 | /* | ||
700 | * subchannel recognition done. Called from the state machine. | ||
701 | */ | ||
702 | void | ||
703 | io_subchannel_recog_done(struct ccw_device *cdev) | ||
704 | { | ||
705 | struct subchannel *sch; | ||
706 | |||
707 | if (css_init_done == 0) { | ||
708 | cdev->private->flags.recog_done = 1; | ||
709 | return; | ||
710 | } | ||
711 | switch (cdev->private->state) { | ||
712 | case DEV_STATE_NOT_OPER: | ||
713 | cdev->private->flags.recog_done = 1; | ||
714 | /* Remove device found not operational. */ | ||
715 | if (!get_device(&cdev->dev)) | ||
716 | break; | ||
717 | sch = to_subchannel(cdev->dev.parent); | ||
718 | PREPARE_WORK(&cdev->private->kick_work, | ||
719 | ccw_device_call_sch_unregister, (void *) cdev); | ||
720 | queue_work(slow_path_wq, &cdev->private->kick_work); | ||
721 | if (atomic_dec_and_test(&ccw_device_init_count)) | ||
722 | wake_up(&ccw_device_init_wq); | ||
723 | break; | ||
724 | case DEV_STATE_BOXED: | ||
725 | /* Device did not respond in time. */ | ||
726 | case DEV_STATE_OFFLINE: | ||
727 | /* | ||
728 | * We can't register the device in interrupt context so | ||
729 | * we schedule a work item. | ||
730 | */ | ||
731 | if (!get_device(&cdev->dev)) | ||
732 | break; | ||
733 | PREPARE_WORK(&cdev->private->kick_work, | ||
734 | io_subchannel_register, (void *) cdev); | ||
735 | queue_work(slow_path_wq, &cdev->private->kick_work); | ||
736 | break; | ||
737 | } | ||
738 | } | ||
739 | |||
740 | static int | ||
741 | io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch) | ||
742 | { | ||
743 | int rc; | ||
744 | struct ccw_device_private *priv; | ||
745 | |||
746 | sch->dev.driver_data = cdev; | ||
747 | sch->driver = &io_subchannel_driver; | ||
748 | cdev->ccwlock = &sch->lock; | ||
749 | /* Init private data. */ | ||
750 | priv = cdev->private; | ||
751 | priv->devno = sch->schib.pmcw.dev; | ||
752 | priv->irq = sch->irq; | ||
753 | priv->state = DEV_STATE_NOT_OPER; | ||
754 | INIT_LIST_HEAD(&priv->cmb_list); | ||
755 | init_waitqueue_head(&priv->wait_q); | ||
756 | init_timer(&priv->timer); | ||
757 | |||
758 | /* Set an initial name for the device. */ | ||
759 | snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", | ||
760 | sch->schib.pmcw.dev); | ||
761 | |||
762 | /* Increase counter of devices currently in recognition. */ | ||
763 | atomic_inc(&ccw_device_init_count); | ||
764 | |||
765 | /* Start async. device sensing. */ | ||
766 | spin_lock_irq(&sch->lock); | ||
767 | rc = ccw_device_recognition(cdev); | ||
768 | spin_unlock_irq(&sch->lock); | ||
769 | if (rc) { | ||
770 | if (atomic_dec_and_test(&ccw_device_init_count)) | ||
771 | wake_up(&ccw_device_init_wq); | ||
772 | } | ||
773 | return rc; | ||
774 | } | ||
775 | |||
776 | static int | ||
777 | io_subchannel_probe (struct device *pdev) | ||
778 | { | ||
779 | struct subchannel *sch; | ||
780 | struct ccw_device *cdev; | ||
781 | int rc; | ||
782 | unsigned long flags; | ||
783 | |||
784 | sch = to_subchannel(pdev); | ||
785 | if (sch->dev.driver_data) { | ||
786 | /* | ||
787 | * This subchannel already has an associated ccw_device. | ||
788 | * Register it and exit. This happens for all early | ||
789 | * device, e.g. the console. | ||
790 | */ | ||
791 | cdev = sch->dev.driver_data; | ||
792 | device_initialize(&cdev->dev); | ||
793 | ccw_device_register(cdev); | ||
794 | subchannel_add_files(&sch->dev); | ||
795 | /* | ||
796 | * Check if the device is already online. If it is | ||
797 | * the reference count needs to be corrected | ||
798 | * (see ccw_device_online and css_init_done for the | ||
799 | * ugly details). | ||
800 | */ | ||
801 | if (cdev->private->state != DEV_STATE_NOT_OPER && | ||
802 | cdev->private->state != DEV_STATE_OFFLINE && | ||
803 | cdev->private->state != DEV_STATE_BOXED) | ||
804 | get_device(&cdev->dev); | ||
805 | return 0; | ||
806 | } | ||
807 | cdev = kmalloc (sizeof(*cdev), GFP_KERNEL); | ||
808 | if (!cdev) | ||
809 | return -ENOMEM; | ||
810 | memset(cdev, 0, sizeof(struct ccw_device)); | ||
811 | cdev->private = kmalloc(sizeof(struct ccw_device_private), | ||
812 | GFP_KERNEL | GFP_DMA); | ||
813 | if (!cdev->private) { | ||
814 | kfree(cdev); | ||
815 | return -ENOMEM; | ||
816 | } | ||
817 | memset(cdev->private, 0, sizeof(struct ccw_device_private)); | ||
818 | atomic_set(&cdev->private->onoff, 0); | ||
819 | cdev->dev = (struct device) { | ||
820 | .parent = pdev, | ||
821 | .release = ccw_device_release, | ||
822 | }; | ||
823 | INIT_LIST_HEAD(&cdev->private->kick_work.entry); | ||
824 | /* Do first half of device_register. */ | ||
825 | device_initialize(&cdev->dev); | ||
826 | |||
827 | if (!get_device(&sch->dev)) { | ||
828 | if (cdev->dev.release) | ||
829 | cdev->dev.release(&cdev->dev); | ||
830 | return -ENODEV; | ||
831 | } | ||
832 | |||
833 | rc = io_subchannel_recog(cdev, to_subchannel(pdev)); | ||
834 | if (rc) { | ||
835 | spin_lock_irqsave(&sch->lock, flags); | ||
836 | sch->dev.driver_data = NULL; | ||
837 | spin_unlock_irqrestore(&sch->lock, flags); | ||
838 | if (cdev->dev.release) | ||
839 | cdev->dev.release(&cdev->dev); | ||
840 | } | ||
841 | |||
842 | return rc; | ||
843 | } | ||
844 | |||
845 | static void | ||
846 | ccw_device_unregister(void *data) | ||
847 | { | ||
848 | struct ccw_device *cdev; | ||
849 | |||
850 | cdev = (struct ccw_device *)data; | ||
851 | if (test_and_clear_bit(1, &cdev->private->registered)) | ||
852 | device_unregister(&cdev->dev); | ||
853 | put_device(&cdev->dev); | ||
854 | } | ||
855 | |||
856 | static int | ||
857 | io_subchannel_remove (struct device *dev) | ||
858 | { | ||
859 | struct ccw_device *cdev; | ||
860 | unsigned long flags; | ||
861 | |||
862 | if (!dev->driver_data) | ||
863 | return 0; | ||
864 | cdev = dev->driver_data; | ||
865 | /* Set ccw device to not operational and drop reference. */ | ||
866 | spin_lock_irqsave(cdev->ccwlock, flags); | ||
867 | dev->driver_data = NULL; | ||
868 | cdev->private->state = DEV_STATE_NOT_OPER; | ||
869 | spin_unlock_irqrestore(cdev->ccwlock, flags); | ||
870 | /* | ||
871 | * Put unregistration on workqueue to avoid livelocks on the css bus | ||
872 | * semaphore. | ||
873 | */ | ||
874 | if (get_device(&cdev->dev)) { | ||
875 | PREPARE_WORK(&cdev->private->kick_work, | ||
876 | ccw_device_unregister, (void *) cdev); | ||
877 | queue_work(ccw_device_work, &cdev->private->kick_work); | ||
878 | } | ||
879 | return 0; | ||
880 | } | ||
881 | |||
882 | static int | ||
883 | io_subchannel_notify(struct device *dev, int event) | ||
884 | { | ||
885 | struct ccw_device *cdev; | ||
886 | |||
887 | cdev = dev->driver_data; | ||
888 | if (!cdev) | ||
889 | return 0; | ||
890 | if (!cdev->drv) | ||
891 | return 0; | ||
892 | if (!cdev->online) | ||
893 | return 0; | ||
894 | return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0; | ||
895 | } | ||
896 | |||
897 | static void | ||
898 | io_subchannel_verify(struct device *dev) | ||
899 | { | ||
900 | struct ccw_device *cdev; | ||
901 | |||
902 | cdev = dev->driver_data; | ||
903 | if (cdev) | ||
904 | dev_fsm_event(cdev, DEV_EVENT_VERIFY); | ||
905 | } | ||
906 | |||
907 | static void | ||
908 | io_subchannel_ioterm(struct device *dev) | ||
909 | { | ||
910 | struct ccw_device *cdev; | ||
911 | |||
912 | cdev = dev->driver_data; | ||
913 | if (!cdev) | ||
914 | return; | ||
915 | cdev->private->state = DEV_STATE_CLEAR_VERIFY; | ||
916 | if (cdev->handler) | ||
917 | cdev->handler(cdev, cdev->private->intparm, | ||
918 | ERR_PTR(-EIO)); | ||
919 | } | ||
920 | |||
921 | static void | ||
922 | io_subchannel_shutdown(struct device *dev) | ||
923 | { | ||
924 | struct subchannel *sch; | ||
925 | struct ccw_device *cdev; | ||
926 | int ret; | ||
927 | |||
928 | sch = to_subchannel(dev); | ||
929 | cdev = dev->driver_data; | ||
930 | |||
931 | if (cio_is_console(sch->irq)) | ||
932 | return; | ||
933 | if (!sch->schib.pmcw.ena) | ||
934 | /* Nothing to do. */ | ||
935 | return; | ||
936 | ret = cio_disable_subchannel(sch); | ||
937 | if (ret != -EBUSY) | ||
938 | /* Subchannel is disabled, we're done. */ | ||
939 | return; | ||
940 | cdev->private->state = DEV_STATE_QUIESCE; | ||
941 | if (cdev->handler) | ||
942 | cdev->handler(cdev, cdev->private->intparm, | ||
943 | ERR_PTR(-EIO)); | ||
944 | ret = ccw_device_cancel_halt_clear(cdev); | ||
945 | if (ret == -EBUSY) { | ||
946 | ccw_device_set_timeout(cdev, HZ/10); | ||
947 | wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); | ||
948 | } | ||
949 | cio_disable_subchannel(sch); | ||
950 | } | ||
951 | |||
952 | #ifdef CONFIG_CCW_CONSOLE | ||
953 | static struct ccw_device console_cdev; | ||
954 | static struct ccw_device_private console_private; | ||
955 | static int console_cdev_in_use; | ||
956 | |||
957 | static int | ||
958 | ccw_device_console_enable (struct ccw_device *cdev, struct subchannel *sch) | ||
959 | { | ||
960 | int rc; | ||
961 | |||
962 | /* Initialize the ccw_device structure. */ | ||
963 | cdev->dev = (struct device) { | ||
964 | .parent = &sch->dev, | ||
965 | }; | ||
966 | /* Initialize the subchannel structure */ | ||
967 | sch->dev.parent = &css_bus_device; | ||
968 | sch->dev.bus = &css_bus_type; | ||
969 | |||
970 | rc = io_subchannel_recog(cdev, sch); | ||
971 | if (rc) | ||
972 | return rc; | ||
973 | |||
974 | /* Now wait for the async. recognition to come to an end. */ | ||
975 | spin_lock_irq(cdev->ccwlock); | ||
976 | while (!dev_fsm_final_state(cdev)) | ||
977 | wait_cons_dev(); | ||
978 | rc = -EIO; | ||
979 | if (cdev->private->state != DEV_STATE_OFFLINE) | ||
980 | goto out_unlock; | ||
981 | ccw_device_online(cdev); | ||
982 | while (!dev_fsm_final_state(cdev)) | ||
983 | wait_cons_dev(); | ||
984 | if (cdev->private->state != DEV_STATE_ONLINE) | ||
985 | goto out_unlock; | ||
986 | rc = 0; | ||
987 | out_unlock: | ||
988 | spin_unlock_irq(cdev->ccwlock); | ||
989 | return 0; | ||
990 | } | ||
991 | |||
992 | struct ccw_device * | ||
993 | ccw_device_probe_console(void) | ||
994 | { | ||
995 | struct subchannel *sch; | ||
996 | int ret; | ||
997 | |||
998 | if (xchg(&console_cdev_in_use, 1) != 0) | ||
999 | return NULL; | ||
1000 | sch = cio_probe_console(); | ||
1001 | if (IS_ERR(sch)) { | ||
1002 | console_cdev_in_use = 0; | ||
1003 | return (void *) sch; | ||
1004 | } | ||
1005 | memset(&console_cdev, 0, sizeof(struct ccw_device)); | ||
1006 | memset(&console_private, 0, sizeof(struct ccw_device_private)); | ||
1007 | console_cdev.private = &console_private; | ||
1008 | ret = ccw_device_console_enable(&console_cdev, sch); | ||
1009 | if (ret) { | ||
1010 | cio_release_console(); | ||
1011 | console_cdev_in_use = 0; | ||
1012 | return ERR_PTR(ret); | ||
1013 | } | ||
1014 | console_cdev.online = 1; | ||
1015 | return &console_cdev; | ||
1016 | } | ||
1017 | #endif | ||
1018 | |||
1019 | /* | ||
1020 | * get ccw_device matching the busid, but only if owned by cdrv | ||
1021 | */ | ||
1022 | struct ccw_device * | ||
1023 | get_ccwdev_by_busid(struct ccw_driver *cdrv, const char *bus_id) | ||
1024 | { | ||
1025 | struct device *d, *dev; | ||
1026 | struct device_driver *drv; | ||
1027 | |||
1028 | drv = get_driver(&cdrv->driver); | ||
1029 | if (!drv) | ||
1030 | return 0; | ||
1031 | |||
1032 | down_read(&drv->bus->subsys.rwsem); | ||
1033 | |||
1034 | dev = NULL; | ||
1035 | list_for_each_entry(d, &drv->devices, driver_list) { | ||
1036 | dev = get_device(d); | ||
1037 | |||
1038 | if (dev && !strncmp(bus_id, dev->bus_id, BUS_ID_SIZE)) | ||
1039 | break; | ||
1040 | else if (dev) { | ||
1041 | put_device(dev); | ||
1042 | dev = NULL; | ||
1043 | } | ||
1044 | } | ||
1045 | up_read(&drv->bus->subsys.rwsem); | ||
1046 | put_driver(drv); | ||
1047 | |||
1048 | return dev ? to_ccwdev(dev) : 0; | ||
1049 | } | ||
1050 | |||
1051 | /************************** device driver handling ************************/ | ||
1052 | |||
1053 | /* This is the implementation of the ccw_driver class. The probe, remove | ||
1054 | * and release methods are initially very similar to the device_driver | ||
1055 | * implementations, with the difference that they have ccw_device | ||
1056 | * arguments. | ||
1057 | * | ||
1058 | * A ccw driver also contains the information that is needed for | ||
1059 | * device matching. | ||
1060 | */ | ||
1061 | static int | ||
1062 | ccw_device_probe (struct device *dev) | ||
1063 | { | ||
1064 | struct ccw_device *cdev = to_ccwdev(dev); | ||
1065 | struct ccw_driver *cdrv = to_ccwdrv(dev->driver); | ||
1066 | int ret; | ||
1067 | |||
1068 | cdev->drv = cdrv; /* to let the driver call _set_online */ | ||
1069 | |||
1070 | ret = cdrv->probe ? cdrv->probe(cdev) : -ENODEV; | ||
1071 | |||
1072 | if (ret) { | ||
1073 | cdev->drv = 0; | ||
1074 | return ret; | ||
1075 | } | ||
1076 | |||
1077 | return 0; | ||
1078 | } | ||
1079 | |||
1080 | static int | ||
1081 | ccw_device_remove (struct device *dev) | ||
1082 | { | ||
1083 | struct ccw_device *cdev = to_ccwdev(dev); | ||
1084 | struct ccw_driver *cdrv = cdev->drv; | ||
1085 | int ret; | ||
1086 | |||
1087 | pr_debug("removing device %s\n", cdev->dev.bus_id); | ||
1088 | if (cdrv->remove) | ||
1089 | cdrv->remove(cdev); | ||
1090 | if (cdev->online) { | ||
1091 | cdev->online = 0; | ||
1092 | spin_lock_irq(cdev->ccwlock); | ||
1093 | ret = ccw_device_offline(cdev); | ||
1094 | spin_unlock_irq(cdev->ccwlock); | ||
1095 | if (ret == 0) | ||
1096 | wait_event(cdev->private->wait_q, | ||
1097 | dev_fsm_final_state(cdev)); | ||
1098 | else | ||
1099 | //FIXME: we can't fail! | ||
1100 | pr_debug("ccw_device_offline returned %d, device %s\n", | ||
1101 | ret, cdev->dev.bus_id); | ||
1102 | } | ||
1103 | ccw_device_set_timeout(cdev, 0); | ||
1104 | cdev->drv = 0; | ||
1105 | return 0; | ||
1106 | } | ||
1107 | |||
1108 | int | ||
1109 | ccw_driver_register (struct ccw_driver *cdriver) | ||
1110 | { | ||
1111 | struct device_driver *drv = &cdriver->driver; | ||
1112 | |||
1113 | drv->bus = &ccw_bus_type; | ||
1114 | drv->name = cdriver->name; | ||
1115 | drv->probe = ccw_device_probe; | ||
1116 | drv->remove = ccw_device_remove; | ||
1117 | |||
1118 | return driver_register(drv); | ||
1119 | } | ||
1120 | |||
1121 | void | ||
1122 | ccw_driver_unregister (struct ccw_driver *cdriver) | ||
1123 | { | ||
1124 | driver_unregister(&cdriver->driver); | ||
1125 | } | ||
1126 | |||
1127 | MODULE_LICENSE("GPL"); | ||
1128 | EXPORT_SYMBOL(ccw_device_set_online); | ||
1129 | EXPORT_SYMBOL(ccw_device_set_offline); | ||
1130 | EXPORT_SYMBOL(ccw_driver_register); | ||
1131 | EXPORT_SYMBOL(ccw_driver_unregister); | ||
1132 | EXPORT_SYMBOL(get_ccwdev_by_busid); | ||
1133 | EXPORT_SYMBOL(ccw_bus_type); | ||
1134 | EXPORT_SYMBOL(ccw_device_work); | ||
1135 | EXPORT_SYMBOL(ccw_device_notify_work); | ||