Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * drivers/s390/cio/css.c | |
3 | * driver for channel subsystem | |
1da177e4 LT |
4 | * |
5 | * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, | |
6 | * IBM Corporation | |
7 | * Author(s): Arnd Bergmann (arndb@de.ibm.com) | |
4ce3b30c | 8 | * Cornelia Huck (cornelia.huck@de.ibm.com) |
1da177e4 LT |
9 | */ |
10 | #include <linux/module.h> | |
11 | #include <linux/init.h> | |
12 | #include <linux/device.h> | |
13 | #include <linux/slab.h> | |
14 | #include <linux/errno.h> | |
15 | #include <linux/list.h> | |
16 | ||
17 | #include "css.h" | |
18 | #include "cio.h" | |
19 | #include "cio_debug.h" | |
20 | #include "ioasm.h" | |
21 | #include "chsc.h" | |
22 | ||
1da177e4 LT |
23 | int need_rescan = 0; |
24 | int css_init_done = 0; | |
fb6958a5 | 25 | static int max_ssid = 0; |
1da177e4 | 26 | |
a28c6944 | 27 | struct channel_subsystem *css[__MAX_CSSID + 1]; |
1da177e4 | 28 | |
a28c6944 | 29 | int css_characteristics_avail = 0; |
1da177e4 | 30 | |
f97a56fb CH |
31 | inline int |
32 | for_each_subchannel(int(*fn)(struct subchannel_id, void *), void *data) | |
33 | { | |
34 | struct subchannel_id schid; | |
35 | int ret; | |
36 | ||
37 | init_subchannel_id(&schid); | |
38 | ret = -ENODEV; | |
39 | do { | |
fb6958a5 CH |
40 | do { |
41 | ret = fn(schid, data); | |
42 | if (ret) | |
43 | break; | |
44 | } while (schid.sch_no++ < __MAX_SUBCHANNEL); | |
45 | schid.sch_no = 0; | |
46 | } while (schid.ssid++ < max_ssid); | |
f97a56fb CH |
47 | return ret; |
48 | } | |
49 | ||
1da177e4 | 50 | static struct subchannel * |
a8237fc4 | 51 | css_alloc_subchannel(struct subchannel_id schid) |
1da177e4 LT |
52 | { |
53 | struct subchannel *sch; | |
54 | int ret; | |
55 | ||
56 | sch = kmalloc (sizeof (*sch), GFP_KERNEL | GFP_DMA); | |
57 | if (sch == NULL) | |
58 | return ERR_PTR(-ENOMEM); | |
a8237fc4 | 59 | ret = cio_validate_subchannel (sch, schid); |
1da177e4 LT |
60 | if (ret < 0) { |
61 | kfree(sch); | |
62 | return ERR_PTR(ret); | |
63 | } | |
1da177e4 LT |
64 | |
65 | if (sch->st != SUBCHANNEL_TYPE_IO) { | |
66 | /* For now we ignore all non-io subchannels. */ | |
67 | kfree(sch); | |
68 | return ERR_PTR(-EINVAL); | |
69 | } | |
70 | ||
71 | /* | |
72 | * Set intparm to subchannel address. | |
73 | * This is fine even on 64bit since the subchannel is always located | |
74 | * under 2G. | |
75 | */ | |
76 | sch->schib.pmcw.intparm = (__u32)(unsigned long)sch; | |
77 | ret = cio_modify(sch); | |
78 | if (ret) { | |
79 | kfree(sch); | |
80 | return ERR_PTR(ret); | |
81 | } | |
82 | return sch; | |
83 | } | |
84 | ||
85 | static void | |
86 | css_free_subchannel(struct subchannel *sch) | |
87 | { | |
88 | if (sch) { | |
89 | /* Reset intparm to zeroes. */ | |
90 | sch->schib.pmcw.intparm = 0; | |
91 | cio_modify(sch); | |
92 | kfree(sch); | |
93 | } | |
94 | ||
95 | } | |
96 | ||
97 | static void | |
98 | css_subchannel_release(struct device *dev) | |
99 | { | |
100 | struct subchannel *sch; | |
101 | ||
102 | sch = to_subchannel(dev); | |
a8237fc4 | 103 | if (!cio_is_console(sch->schid)) |
1da177e4 LT |
104 | kfree(sch); |
105 | } | |
106 | ||
107 | extern int css_get_ssd_info(struct subchannel *sch); | |
108 | ||
109 | static int | |
110 | css_register_subchannel(struct subchannel *sch) | |
111 | { | |
112 | int ret; | |
113 | ||
114 | /* Initialize the subchannel structure */ | |
a28c6944 | 115 | sch->dev.parent = &css[0]->device; |
1da177e4 LT |
116 | sch->dev.bus = &css_bus_type; |
117 | sch->dev.release = &css_subchannel_release; | |
118 | ||
119 | /* make it known to the system */ | |
120 | ret = device_register(&sch->dev); | |
121 | if (ret) | |
122 | printk (KERN_WARNING "%s: could not register %s\n", | |
123 | __func__, sch->dev.bus_id); | |
124 | else | |
125 | css_get_ssd_info(sch); | |
126 | return ret; | |
127 | } | |
128 | ||
129 | int | |
a8237fc4 | 130 | css_probe_device(struct subchannel_id schid) |
1da177e4 LT |
131 | { |
132 | int ret; | |
133 | struct subchannel *sch; | |
134 | ||
a8237fc4 | 135 | sch = css_alloc_subchannel(schid); |
1da177e4 LT |
136 | if (IS_ERR(sch)) |
137 | return PTR_ERR(sch); | |
138 | ret = css_register_subchannel(sch); | |
139 | if (ret) | |
140 | css_free_subchannel(sch); | |
141 | return ret; | |
142 | } | |
143 | ||
b0744bd2 CH |
144 | static int |
145 | check_subchannel(struct device * dev, void * data) | |
146 | { | |
147 | struct subchannel *sch; | |
a8237fc4 | 148 | struct subchannel_id *schid = data; |
b0744bd2 CH |
149 | |
150 | sch = to_subchannel(dev); | |
a8237fc4 | 151 | return schid_equal(&sch->schid, schid); |
b0744bd2 CH |
152 | } |
153 | ||
1da177e4 | 154 | struct subchannel * |
a8237fc4 | 155 | get_subchannel_by_schid(struct subchannel_id schid) |
1da177e4 | 156 | { |
1da177e4 LT |
157 | struct device *dev; |
158 | ||
b0744bd2 | 159 | dev = bus_find_device(&css_bus_type, NULL, |
a8237fc4 | 160 | (void *)&schid, check_subchannel); |
1da177e4 | 161 | |
b0744bd2 | 162 | return dev ? to_subchannel(dev) : NULL; |
1da177e4 LT |
163 | } |
164 | ||
b0744bd2 | 165 | |
1da177e4 | 166 | static inline int |
a8237fc4 | 167 | css_get_subchannel_status(struct subchannel *sch, struct subchannel_id schid) |
1da177e4 LT |
168 | { |
169 | struct schib schib; | |
170 | int cc; | |
171 | ||
172 | cc = stsch(schid, &schib); | |
173 | if (cc) | |
174 | return CIO_GONE; | |
175 | if (!schib.pmcw.dnv) | |
176 | return CIO_GONE; | |
177 | if (sch && sch->schib.pmcw.dnv && | |
178 | (schib.pmcw.dev != sch->schib.pmcw.dev)) | |
179 | return CIO_REVALIDATE; | |
180 | if (sch && !sch->lpm) | |
181 | return CIO_NO_PATH; | |
182 | return CIO_OPER; | |
183 | } | |
184 | ||
185 | static int | |
a8237fc4 | 186 | css_evaluate_subchannel(struct subchannel_id schid, int slow) |
1da177e4 LT |
187 | { |
188 | int event, ret, disc; | |
189 | struct subchannel *sch; | |
190 | unsigned long flags; | |
191 | ||
a8237fc4 | 192 | sch = get_subchannel_by_schid(schid); |
1da177e4 LT |
193 | disc = sch ? device_is_disconnected(sch) : 0; |
194 | if (disc && slow) { | |
195 | if (sch) | |
196 | put_device(&sch->dev); | |
197 | return 0; /* Already processed. */ | |
198 | } | |
199 | /* | |
200 | * We've got a machine check, so running I/O won't get an interrupt. | |
201 | * Kill any pending timers. | |
202 | */ | |
203 | if (sch) | |
204 | device_kill_pending_timer(sch); | |
205 | if (!disc && !slow) { | |
206 | if (sch) | |
207 | put_device(&sch->dev); | |
208 | return -EAGAIN; /* Will be done on the slow path. */ | |
209 | } | |
a8237fc4 | 210 | event = css_get_subchannel_status(sch, schid); |
fb6958a5 CH |
211 | CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n", |
212 | schid.ssid, schid.sch_no, event, | |
a8237fc4 | 213 | sch?(disc?"disconnected":"normal"):"unknown", |
1da177e4 LT |
214 | slow?"slow":"fast"); |
215 | switch (event) { | |
216 | case CIO_NO_PATH: | |
217 | case CIO_GONE: | |
218 | if (!sch) { | |
219 | /* Never used this subchannel. Ignore. */ | |
220 | ret = 0; | |
221 | break; | |
222 | } | |
223 | if (disc && (event == CIO_NO_PATH)) { | |
224 | /* | |
225 | * Uargh, hack again. Because we don't get a machine | |
226 | * check on configure on, our path bookkeeping can | |
227 | * be out of date here (it's fine while we only do | |
228 | * logical varying or get chsc machine checks). We | |
229 | * need to force reprobing or we might miss devices | |
230 | * coming operational again. It won't do harm in real | |
231 | * no path situations. | |
232 | */ | |
233 | spin_lock_irqsave(&sch->lock, flags); | |
234 | device_trigger_reprobe(sch); | |
235 | spin_unlock_irqrestore(&sch->lock, flags); | |
236 | ret = 0; | |
237 | break; | |
238 | } | |
239 | if (sch->driver && sch->driver->notify && | |
240 | sch->driver->notify(&sch->dev, event)) { | |
241 | cio_disable_subchannel(sch); | |
242 | device_set_disconnected(sch); | |
243 | ret = 0; | |
244 | break; | |
245 | } | |
246 | /* | |
247 | * Unregister subchannel. | |
248 | * The device will be killed automatically. | |
249 | */ | |
250 | cio_disable_subchannel(sch); | |
251 | device_unregister(&sch->dev); | |
252 | /* Reset intparm to zeroes. */ | |
253 | sch->schib.pmcw.intparm = 0; | |
254 | cio_modify(sch); | |
255 | put_device(&sch->dev); | |
256 | ret = 0; | |
257 | break; | |
258 | case CIO_REVALIDATE: | |
259 | /* | |
260 | * Revalidation machine check. Sick. | |
261 | * We don't notify the driver since we have to throw the device | |
262 | * away in any case. | |
263 | */ | |
264 | if (!disc) { | |
265 | device_unregister(&sch->dev); | |
266 | /* Reset intparm to zeroes. */ | |
267 | sch->schib.pmcw.intparm = 0; | |
268 | cio_modify(sch); | |
269 | put_device(&sch->dev); | |
a8237fc4 | 270 | ret = css_probe_device(schid); |
1da177e4 LT |
271 | } else { |
272 | /* | |
273 | * We can't immediately deregister the disconnected | |
274 | * device since it might block. | |
275 | */ | |
276 | spin_lock_irqsave(&sch->lock, flags); | |
277 | device_trigger_reprobe(sch); | |
278 | spin_unlock_irqrestore(&sch->lock, flags); | |
279 | ret = 0; | |
280 | } | |
281 | break; | |
282 | case CIO_OPER: | |
283 | if (disc) { | |
284 | spin_lock_irqsave(&sch->lock, flags); | |
285 | /* Get device operational again. */ | |
286 | device_trigger_reprobe(sch); | |
287 | spin_unlock_irqrestore(&sch->lock, flags); | |
288 | } | |
a8237fc4 | 289 | ret = sch ? 0 : css_probe_device(schid); |
1da177e4 LT |
290 | break; |
291 | default: | |
292 | BUG(); | |
293 | ret = 0; | |
294 | } | |
295 | return ret; | |
296 | } | |
297 | ||
f97a56fb CH |
298 | static int |
299 | css_rescan_devices(struct subchannel_id schid, void *data) | |
1da177e4 | 300 | { |
f97a56fb | 301 | return css_evaluate_subchannel(schid, 1); |
1da177e4 LT |
302 | } |
303 | ||
304 | struct slow_subchannel { | |
305 | struct list_head slow_list; | |
a8237fc4 | 306 | struct subchannel_id schid; |
1da177e4 LT |
307 | }; |
308 | ||
309 | static LIST_HEAD(slow_subchannels_head); | |
310 | static DEFINE_SPINLOCK(slow_subchannel_lock); | |
311 | ||
312 | static void | |
313 | css_trigger_slow_path(void) | |
314 | { | |
315 | CIO_TRACE_EVENT(4, "slowpath"); | |
316 | ||
317 | if (need_rescan) { | |
318 | need_rescan = 0; | |
f97a56fb | 319 | for_each_subchannel(css_rescan_devices, NULL); |
1da177e4 LT |
320 | return; |
321 | } | |
322 | ||
323 | spin_lock_irq(&slow_subchannel_lock); | |
324 | while (!list_empty(&slow_subchannels_head)) { | |
325 | struct slow_subchannel *slow_sch = | |
326 | list_entry(slow_subchannels_head.next, | |
327 | struct slow_subchannel, slow_list); | |
328 | ||
329 | list_del_init(slow_subchannels_head.next); | |
330 | spin_unlock_irq(&slow_subchannel_lock); | |
331 | css_evaluate_subchannel(slow_sch->schid, 1); | |
332 | spin_lock_irq(&slow_subchannel_lock); | |
333 | kfree(slow_sch); | |
334 | } | |
335 | spin_unlock_irq(&slow_subchannel_lock); | |
336 | } | |
337 | ||
338 | typedef void (*workfunc)(void *); | |
339 | DECLARE_WORK(slow_path_work, (workfunc)css_trigger_slow_path, NULL); | |
340 | struct workqueue_struct *slow_path_wq; | |
341 | ||
342 | /* | |
343 | * Rescan for new devices. FIXME: This is slow. | |
344 | * This function is called when we have lost CRWs due to overflows and we have | |
345 | * to do subchannel housekeeping. | |
346 | */ | |
347 | void | |
348 | css_reiterate_subchannels(void) | |
349 | { | |
350 | css_clear_subchannel_slow_list(); | |
351 | need_rescan = 1; | |
352 | } | |
353 | ||
354 | /* | |
355 | * Called from the machine check handler for subchannel report words. | |
356 | */ | |
357 | int | |
fb6958a5 | 358 | css_process_crw(int rsid1, int rsid2) |
1da177e4 LT |
359 | { |
360 | int ret; | |
a8237fc4 | 361 | struct subchannel_id mchk_schid; |
1da177e4 | 362 | |
fb6958a5 CH |
363 | CIO_CRW_EVENT(2, "source is subchannel %04X, subsystem id %x\n", |
364 | rsid1, rsid2); | |
1da177e4 LT |
365 | |
366 | if (need_rescan) | |
367 | /* We need to iterate all subchannels anyway. */ | |
368 | return -EAGAIN; | |
a8237fc4 CH |
369 | |
370 | init_subchannel_id(&mchk_schid); | |
fb6958a5 CH |
371 | mchk_schid.sch_no = rsid1; |
372 | if (rsid2 != 0) | |
373 | mchk_schid.ssid = (rsid2 >> 8) & 3; | |
374 | ||
1da177e4 LT |
375 | /* |
376 | * Since we are always presented with IPI in the CRW, we have to | |
377 | * use stsch() to find out if the subchannel in question has come | |
378 | * or gone. | |
379 | */ | |
a8237fc4 | 380 | ret = css_evaluate_subchannel(mchk_schid, 0); |
1da177e4 | 381 | if (ret == -EAGAIN) { |
a8237fc4 | 382 | if (css_enqueue_subchannel_slow(mchk_schid)) { |
1da177e4 LT |
383 | css_clear_subchannel_slow_list(); |
384 | need_rescan = 1; | |
385 | } | |
386 | } | |
387 | return ret; | |
388 | } | |
389 | ||
f97a56fb CH |
390 | static int __init |
391 | __init_channel_subsystem(struct subchannel_id schid, void *data) | |
392 | { | |
393 | struct subchannel *sch; | |
394 | int ret; | |
395 | ||
396 | if (cio_is_console(schid)) | |
397 | sch = cio_get_console_subchannel(); | |
398 | else { | |
399 | sch = css_alloc_subchannel(schid); | |
400 | if (IS_ERR(sch)) | |
401 | ret = PTR_ERR(sch); | |
402 | else | |
403 | ret = 0; | |
404 | switch (ret) { | |
405 | case 0: | |
406 | break; | |
407 | case -ENOMEM: | |
408 | panic("Out of memory in init_channel_subsystem\n"); | |
409 | /* -ENXIO: no more subchannels. */ | |
410 | case -ENXIO: | |
411 | return ret; | |
412 | default: | |
413 | return 0; | |
414 | } | |
415 | } | |
416 | /* | |
417 | * We register ALL valid subchannels in ioinfo, even those | |
418 | * that have been present before init_channel_subsystem. | |
419 | * These subchannels can't have been registered yet (kmalloc | |
420 | * not working) so we do it now. This is true e.g. for the | |
421 | * console subchannel. | |
422 | */ | |
423 | css_register_subchannel(sch); | |
424 | return 0; | |
425 | } | |
426 | ||
1da177e4 | 427 | static void __init |
a28c6944 | 428 | css_generate_pgid(struct channel_subsystem *css, u32 tod_high) |
1da177e4 | 429 | { |
a28c6944 CH |
430 | if (css_characteristics_avail && css_general_characteristics.mcss) { |
431 | css->global_pgid.pgid_high.ext_cssid.version = 0x80; | |
432 | css->global_pgid.pgid_high.ext_cssid.cssid = css->cssid; | |
433 | } else { | |
1da177e4 | 434 | #ifdef CONFIG_SMP |
a28c6944 | 435 | css->global_pgid.pgid_high.cpu_addr = hard_smp_processor_id(); |
1da177e4 | 436 | #else |
a28c6944 | 437 | css->global_pgid.pgid_high.cpu_addr = 0; |
1da177e4 LT |
438 | #endif |
439 | } | |
a28c6944 CH |
440 | css->global_pgid.cpu_id = ((cpuid_t *) __LC_CPUID)->ident; |
441 | css->global_pgid.cpu_model = ((cpuid_t *) __LC_CPUID)->machine; | |
442 | css->global_pgid.tod_high = tod_high; | |
443 | ||
444 | } | |
445 | ||
3b793060 CH |
446 | static void |
447 | channel_subsystem_release(struct device *dev) | |
448 | { | |
449 | struct channel_subsystem *css; | |
450 | ||
451 | css = to_css(dev); | |
452 | kfree(css); | |
453 | } | |
454 | ||
a28c6944 CH |
455 | static inline void __init |
456 | setup_css(int nr) | |
457 | { | |
458 | u32 tod_high; | |
459 | ||
460 | memset(css[nr], 0, sizeof(struct channel_subsystem)); | |
461 | css[nr]->valid = 1; | |
462 | css[nr]->cssid = nr; | |
463 | sprintf(css[nr]->device.bus_id, "css%x", nr); | |
3b793060 | 464 | css[nr]->device.release = channel_subsystem_release; |
a28c6944 CH |
465 | tod_high = (u32) (get_clock() >> 32); |
466 | css_generate_pgid(css[nr], tod_high); | |
1da177e4 LT |
467 | } |
468 | ||
469 | /* | |
470 | * Now that the driver core is running, we can setup our channel subsystem. | |
471 | * The struct subchannel's are created during probing (except for the | |
472 | * static console subchannel). | |
473 | */ | |
474 | static int __init | |
475 | init_channel_subsystem (void) | |
476 | { | |
a28c6944 | 477 | int ret, i; |
1da177e4 LT |
478 | |
479 | if (chsc_determine_css_characteristics() == 0) | |
480 | css_characteristics_avail = 1; | |
481 | ||
1da177e4 LT |
482 | if ((ret = bus_register(&css_bus_type))) |
483 | goto out; | |
1da177e4 | 484 | |
fb6958a5 CH |
485 | /* Try to enable MSS. */ |
486 | ret = chsc_enable_facility(CHSC_SDA_OC_MSS); | |
487 | switch (ret) { | |
488 | case 0: /* Success. */ | |
489 | max_ssid = __MAX_SSID; | |
490 | break; | |
491 | case -ENOMEM: | |
492 | goto out_bus; | |
493 | default: | |
494 | max_ssid = 0; | |
495 | } | |
a28c6944 CH |
496 | /* Setup css structure. */ |
497 | for (i = 0; i <= __MAX_CSSID; i++) { | |
498 | css[i] = kmalloc(sizeof(struct channel_subsystem), GFP_KERNEL); | |
499 | if (!css[i]) { | |
500 | ret = -ENOMEM; | |
fb6958a5 | 501 | goto out_unregister; |
a28c6944 CH |
502 | } |
503 | setup_css(i); | |
504 | ret = device_register(&css[i]->device); | |
505 | if (ret) | |
506 | goto out_free; | |
507 | } | |
1da177e4 LT |
508 | css_init_done = 1; |
509 | ||
510 | ctl_set_bit(6, 28); | |
511 | ||
f97a56fb | 512 | for_each_subchannel(__init_channel_subsystem, NULL); |
1da177e4 | 513 | return 0; |
a28c6944 CH |
514 | out_free: |
515 | kfree(css[i]); | |
fb6958a5 | 516 | out_unregister: |
a28c6944 CH |
517 | while (i > 0) { |
518 | i--; | |
519 | device_unregister(&css[i]->device); | |
520 | } | |
fb6958a5 | 521 | out_bus: |
1da177e4 LT |
522 | bus_unregister(&css_bus_type); |
523 | out: | |
524 | return ret; | |
525 | } | |
526 | ||
527 | /* | |
528 | * find a driver for a subchannel. They identify by the subchannel | |
529 | * type with the exception that the console subchannel driver has its own | |
530 | * subchannel type although the device is an i/o subchannel | |
531 | */ | |
532 | static int | |
533 | css_bus_match (struct device *dev, struct device_driver *drv) | |
534 | { | |
535 | struct subchannel *sch = container_of (dev, struct subchannel, dev); | |
536 | struct css_driver *driver = container_of (drv, struct css_driver, drv); | |
537 | ||
538 | if (sch->st == driver->subchannel_type) | |
539 | return 1; | |
540 | ||
541 | return 0; | |
542 | } | |
543 | ||
8bbace7e CH |
544 | static int |
545 | css_probe (struct device *dev) | |
546 | { | |
547 | struct subchannel *sch; | |
548 | ||
549 | sch = to_subchannel(dev); | |
550 | sch->driver = container_of (dev->driver, struct css_driver, drv); | |
551 | return (sch->driver->probe ? sch->driver->probe(sch) : 0); | |
552 | } | |
553 | ||
554 | static int | |
555 | css_remove (struct device *dev) | |
556 | { | |
557 | struct subchannel *sch; | |
558 | ||
559 | sch = to_subchannel(dev); | |
560 | return (sch->driver->remove ? sch->driver->remove(sch) : 0); | |
561 | } | |
562 | ||
563 | static void | |
564 | css_shutdown (struct device *dev) | |
565 | { | |
566 | struct subchannel *sch; | |
567 | ||
568 | sch = to_subchannel(dev); | |
569 | if (sch->driver->shutdown) | |
570 | sch->driver->shutdown(sch); | |
571 | } | |
572 | ||
1da177e4 | 573 | struct bus_type css_bus_type = { |
8bbace7e CH |
574 | .name = "css", |
575 | .match = css_bus_match, | |
576 | .probe = css_probe, | |
577 | .remove = css_remove, | |
578 | .shutdown = css_shutdown, | |
1da177e4 LT |
579 | }; |
580 | ||
581 | subsys_initcall(init_channel_subsystem); | |
582 | ||
1da177e4 | 583 | int |
a8237fc4 | 584 | css_enqueue_subchannel_slow(struct subchannel_id schid) |
1da177e4 LT |
585 | { |
586 | struct slow_subchannel *new_slow_sch; | |
587 | unsigned long flags; | |
588 | ||
589 | new_slow_sch = kmalloc(sizeof(struct slow_subchannel), GFP_ATOMIC); | |
590 | if (!new_slow_sch) | |
591 | return -ENOMEM; | |
592 | memset(new_slow_sch, 0, sizeof(struct slow_subchannel)); | |
593 | new_slow_sch->schid = schid; | |
594 | spin_lock_irqsave(&slow_subchannel_lock, flags); | |
595 | list_add_tail(&new_slow_sch->slow_list, &slow_subchannels_head); | |
596 | spin_unlock_irqrestore(&slow_subchannel_lock, flags); | |
597 | return 0; | |
598 | } | |
599 | ||
600 | void | |
601 | css_clear_subchannel_slow_list(void) | |
602 | { | |
603 | unsigned long flags; | |
604 | ||
605 | spin_lock_irqsave(&slow_subchannel_lock, flags); | |
606 | while (!list_empty(&slow_subchannels_head)) { | |
607 | struct slow_subchannel *slow_sch = | |
608 | list_entry(slow_subchannels_head.next, | |
609 | struct slow_subchannel, slow_list); | |
610 | ||
611 | list_del_init(slow_subchannels_head.next); | |
612 | kfree(slow_sch); | |
613 | } | |
614 | spin_unlock_irqrestore(&slow_subchannel_lock, flags); | |
615 | } | |
616 | ||
617 | ||
618 | ||
619 | int | |
620 | css_slow_subchannels_exist(void) | |
621 | { | |
622 | return (!list_empty(&slow_subchannels_head)); | |
623 | } | |
624 | ||
625 | MODULE_LICENSE("GPL"); | |
626 | EXPORT_SYMBOL(css_bus_type); | |
1da177e4 | 627 | EXPORT_SYMBOL_GPL(css_characteristics_avail); |