]>
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; | |
e843e280 GS |
412 | /* -EIO: this subchannel set not supported. */ |
413 | case -EIO: | |
414 | return ret; | |
f97a56fb CH |
415 | default: |
416 | return 0; | |
417 | } | |
418 | } | |
419 | /* | |
420 | * We register ALL valid subchannels in ioinfo, even those | |
421 | * that have been present before init_channel_subsystem. | |
422 | * These subchannels can't have been registered yet (kmalloc | |
423 | * not working) so we do it now. This is true e.g. for the | |
424 | * console subchannel. | |
425 | */ | |
426 | css_register_subchannel(sch); | |
427 | return 0; | |
428 | } | |
429 | ||
1da177e4 | 430 | static void __init |
a28c6944 | 431 | css_generate_pgid(struct channel_subsystem *css, u32 tod_high) |
1da177e4 | 432 | { |
a28c6944 CH |
433 | if (css_characteristics_avail && css_general_characteristics.mcss) { |
434 | css->global_pgid.pgid_high.ext_cssid.version = 0x80; | |
435 | css->global_pgid.pgid_high.ext_cssid.cssid = css->cssid; | |
436 | } else { | |
1da177e4 | 437 | #ifdef CONFIG_SMP |
a28c6944 | 438 | css->global_pgid.pgid_high.cpu_addr = hard_smp_processor_id(); |
1da177e4 | 439 | #else |
a28c6944 | 440 | css->global_pgid.pgid_high.cpu_addr = 0; |
1da177e4 LT |
441 | #endif |
442 | } | |
a28c6944 CH |
443 | css->global_pgid.cpu_id = ((cpuid_t *) __LC_CPUID)->ident; |
444 | css->global_pgid.cpu_model = ((cpuid_t *) __LC_CPUID)->machine; | |
445 | css->global_pgid.tod_high = tod_high; | |
446 | ||
447 | } | |
448 | ||
3b793060 CH |
449 | static void |
450 | channel_subsystem_release(struct device *dev) | |
451 | { | |
452 | struct channel_subsystem *css; | |
453 | ||
454 | css = to_css(dev); | |
455 | kfree(css); | |
456 | } | |
457 | ||
a28c6944 CH |
458 | static inline void __init |
459 | setup_css(int nr) | |
460 | { | |
461 | u32 tod_high; | |
462 | ||
463 | memset(css[nr], 0, sizeof(struct channel_subsystem)); | |
464 | css[nr]->valid = 1; | |
465 | css[nr]->cssid = nr; | |
466 | sprintf(css[nr]->device.bus_id, "css%x", nr); | |
3b793060 | 467 | css[nr]->device.release = channel_subsystem_release; |
a28c6944 CH |
468 | tod_high = (u32) (get_clock() >> 32); |
469 | css_generate_pgid(css[nr], tod_high); | |
1da177e4 LT |
470 | } |
471 | ||
472 | /* | |
473 | * Now that the driver core is running, we can setup our channel subsystem. | |
474 | * The struct subchannel's are created during probing (except for the | |
475 | * static console subchannel). | |
476 | */ | |
477 | static int __init | |
478 | init_channel_subsystem (void) | |
479 | { | |
a28c6944 | 480 | int ret, i; |
1da177e4 LT |
481 | |
482 | if (chsc_determine_css_characteristics() == 0) | |
483 | css_characteristics_avail = 1; | |
484 | ||
1da177e4 LT |
485 | if ((ret = bus_register(&css_bus_type))) |
486 | goto out; | |
1da177e4 | 487 | |
fb6958a5 CH |
488 | /* Try to enable MSS. */ |
489 | ret = chsc_enable_facility(CHSC_SDA_OC_MSS); | |
490 | switch (ret) { | |
491 | case 0: /* Success. */ | |
492 | max_ssid = __MAX_SSID; | |
493 | break; | |
494 | case -ENOMEM: | |
495 | goto out_bus; | |
496 | default: | |
497 | max_ssid = 0; | |
498 | } | |
a28c6944 CH |
499 | /* Setup css structure. */ |
500 | for (i = 0; i <= __MAX_CSSID; i++) { | |
501 | css[i] = kmalloc(sizeof(struct channel_subsystem), GFP_KERNEL); | |
502 | if (!css[i]) { | |
503 | ret = -ENOMEM; | |
fb6958a5 | 504 | goto out_unregister; |
a28c6944 CH |
505 | } |
506 | setup_css(i); | |
507 | ret = device_register(&css[i]->device); | |
508 | if (ret) | |
509 | goto out_free; | |
510 | } | |
1da177e4 LT |
511 | css_init_done = 1; |
512 | ||
513 | ctl_set_bit(6, 28); | |
514 | ||
f97a56fb | 515 | for_each_subchannel(__init_channel_subsystem, NULL); |
1da177e4 | 516 | return 0; |
a28c6944 CH |
517 | out_free: |
518 | kfree(css[i]); | |
fb6958a5 | 519 | out_unregister: |
a28c6944 CH |
520 | while (i > 0) { |
521 | i--; | |
522 | device_unregister(&css[i]->device); | |
523 | } | |
fb6958a5 | 524 | out_bus: |
1da177e4 LT |
525 | bus_unregister(&css_bus_type); |
526 | out: | |
527 | return ret; | |
528 | } | |
529 | ||
530 | /* | |
531 | * find a driver for a subchannel. They identify by the subchannel | |
532 | * type with the exception that the console subchannel driver has its own | |
533 | * subchannel type although the device is an i/o subchannel | |
534 | */ | |
535 | static int | |
536 | css_bus_match (struct device *dev, struct device_driver *drv) | |
537 | { | |
538 | struct subchannel *sch = container_of (dev, struct subchannel, dev); | |
539 | struct css_driver *driver = container_of (drv, struct css_driver, drv); | |
540 | ||
541 | if (sch->st == driver->subchannel_type) | |
542 | return 1; | |
543 | ||
544 | return 0; | |
545 | } | |
546 | ||
8bbace7e CH |
547 | static int |
548 | css_probe (struct device *dev) | |
549 | { | |
550 | struct subchannel *sch; | |
551 | ||
552 | sch = to_subchannel(dev); | |
553 | sch->driver = container_of (dev->driver, struct css_driver, drv); | |
554 | return (sch->driver->probe ? sch->driver->probe(sch) : 0); | |
555 | } | |
556 | ||
557 | static int | |
558 | css_remove (struct device *dev) | |
559 | { | |
560 | struct subchannel *sch; | |
561 | ||
562 | sch = to_subchannel(dev); | |
563 | return (sch->driver->remove ? sch->driver->remove(sch) : 0); | |
564 | } | |
565 | ||
566 | static void | |
567 | css_shutdown (struct device *dev) | |
568 | { | |
569 | struct subchannel *sch; | |
570 | ||
571 | sch = to_subchannel(dev); | |
572 | if (sch->driver->shutdown) | |
573 | sch->driver->shutdown(sch); | |
574 | } | |
575 | ||
1da177e4 | 576 | struct bus_type css_bus_type = { |
8bbace7e CH |
577 | .name = "css", |
578 | .match = css_bus_match, | |
579 | .probe = css_probe, | |
580 | .remove = css_remove, | |
581 | .shutdown = css_shutdown, | |
1da177e4 LT |
582 | }; |
583 | ||
584 | subsys_initcall(init_channel_subsystem); | |
585 | ||
1da177e4 | 586 | int |
a8237fc4 | 587 | css_enqueue_subchannel_slow(struct subchannel_id schid) |
1da177e4 LT |
588 | { |
589 | struct slow_subchannel *new_slow_sch; | |
590 | unsigned long flags; | |
591 | ||
592 | new_slow_sch = kmalloc(sizeof(struct slow_subchannel), GFP_ATOMIC); | |
593 | if (!new_slow_sch) | |
594 | return -ENOMEM; | |
595 | memset(new_slow_sch, 0, sizeof(struct slow_subchannel)); | |
596 | new_slow_sch->schid = schid; | |
597 | spin_lock_irqsave(&slow_subchannel_lock, flags); | |
598 | list_add_tail(&new_slow_sch->slow_list, &slow_subchannels_head); | |
599 | spin_unlock_irqrestore(&slow_subchannel_lock, flags); | |
600 | return 0; | |
601 | } | |
602 | ||
603 | void | |
604 | css_clear_subchannel_slow_list(void) | |
605 | { | |
606 | unsigned long flags; | |
607 | ||
608 | spin_lock_irqsave(&slow_subchannel_lock, flags); | |
609 | while (!list_empty(&slow_subchannels_head)) { | |
610 | struct slow_subchannel *slow_sch = | |
611 | list_entry(slow_subchannels_head.next, | |
612 | struct slow_subchannel, slow_list); | |
613 | ||
614 | list_del_init(slow_subchannels_head.next); | |
615 | kfree(slow_sch); | |
616 | } | |
617 | spin_unlock_irqrestore(&slow_subchannel_lock, flags); | |
618 | } | |
619 | ||
620 | ||
621 | ||
622 | int | |
623 | css_slow_subchannels_exist(void) | |
624 | { | |
625 | return (!list_empty(&slow_subchannels_head)); | |
626 | } | |
627 | ||
628 | MODULE_LICENSE("GPL"); | |
629 | EXPORT_SYMBOL(css_bus_type); | |
1da177e4 | 630 | EXPORT_SYMBOL_GPL(css_characteristics_avail); |