diff options
author | Peter Oberparleiter <peter.oberparleiter@de.ibm.com> | 2007-04-27 10:01:31 -0400 |
---|---|---|
committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2007-04-27 10:01:39 -0400 |
commit | e5854a5839fa426a7873f038080f63587de5f1f1 (patch) | |
tree | c0a3705df925e652ff0694a47e7acb98e5f7d7bd /drivers/s390/cio/chp.c | |
parent | f5ba6c863617c15d22cce5f8666ff4c832773025 (diff) |
[S390] cio: Channel-path configure function.
Add a new attribute to the channel-path sysfs directory through which
channel-path configure operations can be triggered. Also listen for
hardware events requesting channel-path configure operations and
process them accordingly.
Signed-off-by: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Diffstat (limited to 'drivers/s390/cio/chp.c')
-rw-r--r-- | drivers/s390/cio/chp.c | 250 |
1 files changed, 249 insertions, 1 deletions
diff --git a/drivers/s390/cio/chp.c b/drivers/s390/cio/chp.c index 8a5839147f8a..0e92c8c89860 100644 --- a/drivers/s390/cio/chp.c +++ b/drivers/s390/cio/chp.c | |||
@@ -10,9 +10,14 @@ | |||
10 | #include <linux/bug.h> | 10 | #include <linux/bug.h> |
11 | #include <linux/workqueue.h> | 11 | #include <linux/workqueue.h> |
12 | #include <linux/spinlock.h> | 12 | #include <linux/spinlock.h> |
13 | #include <linux/init.h> | ||
14 | #include <linux/jiffies.h> | ||
15 | #include <linux/wait.h> | ||
16 | #include <linux/mutex.h> | ||
13 | #include <asm/errno.h> | 17 | #include <asm/errno.h> |
18 | #include <asm/chpid.h> | ||
19 | #include <asm/sclp.h> | ||
14 | 20 | ||
15 | #include "chpid.h" | ||
16 | #include "cio.h" | 21 | #include "cio.h" |
17 | #include "css.h" | 22 | #include "css.h" |
18 | #include "ioasm.h" | 23 | #include "ioasm.h" |
@@ -20,6 +25,32 @@ | |||
20 | #include "chp.h" | 25 | #include "chp.h" |
21 | 26 | ||
22 | #define to_channelpath(device) container_of(device, struct channel_path, dev) | 27 | #define to_channelpath(device) container_of(device, struct channel_path, dev) |
28 | #define CHP_INFO_UPDATE_INTERVAL 1*HZ | ||
29 | |||
30 | enum cfg_task_t { | ||
31 | cfg_none, | ||
32 | cfg_configure, | ||
33 | cfg_deconfigure | ||
34 | }; | ||
35 | |||
36 | /* Map for pending configure tasks. */ | ||
37 | static enum cfg_task_t chp_cfg_task[__MAX_CSSID + 1][__MAX_CHPID + 1]; | ||
38 | static DEFINE_MUTEX(cfg_lock); | ||
39 | static int cfg_busy; | ||
40 | |||
41 | /* Map for channel-path status. */ | ||
42 | static struct sclp_chp_info chp_info; | ||
43 | static DEFINE_MUTEX(info_lock); | ||
44 | |||
45 | /* Time after which channel-path status may be outdated. */ | ||
46 | static unsigned long chp_info_expires; | ||
47 | |||
48 | /* Workqueue to perform pending configure tasks. */ | ||
49 | static struct workqueue_struct *chp_wq; | ||
50 | static struct work_struct cfg_work; | ||
51 | |||
52 | /* Wait queue for configure completion events. */ | ||
53 | static wait_queue_head_t cfg_wait_queue; | ||
23 | 54 | ||
24 | /* Return channel_path struct for given chpid. */ | 55 | /* Return channel_path struct for given chpid. */ |
25 | static inline struct channel_path *chpid_to_chp(struct chp_id chpid) | 56 | static inline struct channel_path *chpid_to_chp(struct chp_id chpid) |
@@ -251,6 +282,43 @@ static ssize_t chp_status_write(struct device *dev, | |||
251 | 282 | ||
252 | static DEVICE_ATTR(status, 0644, chp_status_show, chp_status_write); | 283 | static DEVICE_ATTR(status, 0644, chp_status_show, chp_status_write); |
253 | 284 | ||
285 | static ssize_t chp_configure_show(struct device *dev, | ||
286 | struct device_attribute *attr, char *buf) | ||
287 | { | ||
288 | struct channel_path *cp; | ||
289 | int status; | ||
290 | |||
291 | cp = container_of(dev, struct channel_path, dev); | ||
292 | status = chp_info_get_status(cp->chpid); | ||
293 | if (status < 0) | ||
294 | return status; | ||
295 | |||
296 | return snprintf(buf, PAGE_SIZE, "%d\n", status); | ||
297 | } | ||
298 | |||
299 | static int cfg_wait_idle(void); | ||
300 | |||
301 | static ssize_t chp_configure_write(struct device *dev, | ||
302 | struct device_attribute *attr, | ||
303 | const char *buf, size_t count) | ||
304 | { | ||
305 | struct channel_path *cp; | ||
306 | int val; | ||
307 | char delim; | ||
308 | |||
309 | if (sscanf(buf, "%d %c", &val, &delim) != 1) | ||
310 | return -EINVAL; | ||
311 | if (val != 0 && val != 1) | ||
312 | return -EINVAL; | ||
313 | cp = container_of(dev, struct channel_path, dev); | ||
314 | chp_cfg_schedule(cp->chpid, val); | ||
315 | cfg_wait_idle(); | ||
316 | |||
317 | return count; | ||
318 | } | ||
319 | |||
320 | static DEVICE_ATTR(configure, 0644, chp_configure_show, chp_configure_write); | ||
321 | |||
254 | static ssize_t chp_type_show(struct device *dev, struct device_attribute *attr, | 322 | static ssize_t chp_type_show(struct device *dev, struct device_attribute *attr, |
255 | char *buf) | 323 | char *buf) |
256 | { | 324 | { |
@@ -293,6 +361,7 @@ static DEVICE_ATTR(shared, 0444, chp_shared_show, NULL); | |||
293 | 361 | ||
294 | static struct attribute * chp_attrs[] = { | 362 | static struct attribute * chp_attrs[] = { |
295 | &dev_attr_status.attr, | 363 | &dev_attr_status.attr, |
364 | &dev_attr_configure.attr, | ||
296 | &dev_attr_type.attr, | 365 | &dev_attr_type.attr, |
297 | &dev_attr_cmg.attr, | 366 | &dev_attr_cmg.attr, |
298 | &dev_attr_shared.attr, | 367 | &dev_attr_shared.attr, |
@@ -323,6 +392,8 @@ int chp_new(struct chp_id chpid) | |||
323 | struct channel_path *chp; | 392 | struct channel_path *chp; |
324 | int ret; | 393 | int ret; |
325 | 394 | ||
395 | if (chp_is_registered(chpid)) | ||
396 | return 0; | ||
326 | chp = kzalloc(sizeof(struct channel_path), GFP_KERNEL); | 397 | chp = kzalloc(sizeof(struct channel_path), GFP_KERNEL); |
327 | if (!chp) | 398 | if (!chp) |
328 | return -ENOMEM; | 399 | return -ENOMEM; |
@@ -435,3 +506,180 @@ int chp_process_crw(int id, int status) | |||
435 | return 0; | 506 | return 0; |
436 | } | 507 | } |
437 | } | 508 | } |
509 | |||
510 | static inline int info_bit_num(struct chp_id id) | ||
511 | { | ||
512 | return id.id + id.cssid * (__MAX_CHPID + 1); | ||
513 | } | ||
514 | |||
515 | /* Force chp_info refresh on next call to info_validate(). */ | ||
516 | static void info_expire(void) | ||
517 | { | ||
518 | mutex_lock(&info_lock); | ||
519 | chp_info_expires = jiffies - 1; | ||
520 | mutex_unlock(&info_lock); | ||
521 | } | ||
522 | |||
523 | /* Ensure that chp_info is up-to-date. */ | ||
524 | static int info_update(void) | ||
525 | { | ||
526 | int rc; | ||
527 | |||
528 | mutex_lock(&info_lock); | ||
529 | rc = 0; | ||
530 | if (time_after(jiffies, chp_info_expires)) { | ||
531 | /* Data is too old, update. */ | ||
532 | rc = sclp_chp_read_info(&chp_info); | ||
533 | chp_info_expires = jiffies + CHP_INFO_UPDATE_INTERVAL ; | ||
534 | } | ||
535 | mutex_unlock(&info_lock); | ||
536 | |||
537 | return rc; | ||
538 | } | ||
539 | |||
540 | /** | ||
541 | * chp_info_get_status - retrieve configure status of a channel-path | ||
542 | * @chpid: channel-path ID | ||
543 | * | ||
544 | * On success, return 0 for standby, 1 for configured, 2 for reserved, | ||
545 | * 3 for not recognized. Return negative error code on error. | ||
546 | */ | ||
547 | int chp_info_get_status(struct chp_id chpid) | ||
548 | { | ||
549 | int rc; | ||
550 | int bit; | ||
551 | |||
552 | rc = info_update(); | ||
553 | if (rc) | ||
554 | return rc; | ||
555 | |||
556 | bit = info_bit_num(chpid); | ||
557 | mutex_lock(&info_lock); | ||
558 | if (!chp_test_bit(chp_info.recognized, bit)) | ||
559 | rc = CHP_STATUS_NOT_RECOGNIZED; | ||
560 | else if (chp_test_bit(chp_info.configured, bit)) | ||
561 | rc = CHP_STATUS_CONFIGURED; | ||
562 | else if (chp_test_bit(chp_info.standby, bit)) | ||
563 | rc = CHP_STATUS_STANDBY; | ||
564 | else | ||
565 | rc = CHP_STATUS_RESERVED; | ||
566 | mutex_unlock(&info_lock); | ||
567 | |||
568 | return rc; | ||
569 | } | ||
570 | |||
571 | /* Return configure task for chpid. */ | ||
572 | static enum cfg_task_t cfg_get_task(struct chp_id chpid) | ||
573 | { | ||
574 | return chp_cfg_task[chpid.cssid][chpid.id]; | ||
575 | } | ||
576 | |||
577 | /* Set configure task for chpid. */ | ||
578 | static void cfg_set_task(struct chp_id chpid, enum cfg_task_t cfg) | ||
579 | { | ||
580 | chp_cfg_task[chpid.cssid][chpid.id] = cfg; | ||
581 | } | ||
582 | |||
583 | /* Perform one configure/deconfigure request. Reschedule work function until | ||
584 | * last request. */ | ||
585 | static void cfg_func(struct work_struct *work) | ||
586 | { | ||
587 | struct chp_id chpid; | ||
588 | enum cfg_task_t t; | ||
589 | |||
590 | mutex_lock(&cfg_lock); | ||
591 | t = cfg_none; | ||
592 | chp_id_for_each(&chpid) { | ||
593 | t = cfg_get_task(chpid); | ||
594 | if (t != cfg_none) { | ||
595 | cfg_set_task(chpid, cfg_none); | ||
596 | break; | ||
597 | } | ||
598 | } | ||
599 | mutex_unlock(&cfg_lock); | ||
600 | |||
601 | switch (t) { | ||
602 | case cfg_configure: | ||
603 | sclp_chp_configure(chpid); | ||
604 | info_expire(); | ||
605 | chsc_chp_online(chpid); | ||
606 | break; | ||
607 | case cfg_deconfigure: | ||
608 | sclp_chp_deconfigure(chpid); | ||
609 | info_expire(); | ||
610 | chsc_chp_offline(chpid); | ||
611 | break; | ||
612 | case cfg_none: | ||
613 | /* Get updated information after last change. */ | ||
614 | info_update(); | ||
615 | mutex_lock(&cfg_lock); | ||
616 | cfg_busy = 0; | ||
617 | mutex_unlock(&cfg_lock); | ||
618 | wake_up_interruptible(&cfg_wait_queue); | ||
619 | return; | ||
620 | } | ||
621 | queue_work(chp_wq, &cfg_work); | ||
622 | } | ||
623 | |||
624 | /** | ||
625 | * chp_cfg_schedule - schedule chpid configuration request | ||
626 | * @chpid - channel-path ID | ||
627 | * @configure - Non-zero for configure, zero for deconfigure | ||
628 | * | ||
629 | * Schedule a channel-path configuration/deconfiguration request. | ||
630 | */ | ||
631 | void chp_cfg_schedule(struct chp_id chpid, int configure) | ||
632 | { | ||
633 | CIO_MSG_EVENT(2, "chp_cfg_sched%x.%02x=%d\n", chpid.cssid, chpid.id, | ||
634 | configure); | ||
635 | mutex_lock(&cfg_lock); | ||
636 | cfg_set_task(chpid, configure ? cfg_configure : cfg_deconfigure); | ||
637 | cfg_busy = 1; | ||
638 | mutex_unlock(&cfg_lock); | ||
639 | queue_work(chp_wq, &cfg_work); | ||
640 | } | ||
641 | |||
642 | /** | ||
643 | * chp_cfg_cancel_deconfigure - cancel chpid deconfiguration request | ||
644 | * @chpid - channel-path ID | ||
645 | * | ||
646 | * Cancel an active channel-path deconfiguration request if it has not yet | ||
647 | * been performed. | ||
648 | */ | ||
649 | void chp_cfg_cancel_deconfigure(struct chp_id chpid) | ||
650 | { | ||
651 | CIO_MSG_EVENT(2, "chp_cfg_cancel:%x.%02x\n", chpid.cssid, chpid.id); | ||
652 | mutex_lock(&cfg_lock); | ||
653 | if (cfg_get_task(chpid) == cfg_deconfigure) | ||
654 | cfg_set_task(chpid, cfg_none); | ||
655 | mutex_unlock(&cfg_lock); | ||
656 | } | ||
657 | |||
658 | static int cfg_wait_idle(void) | ||
659 | { | ||
660 | if (wait_event_interruptible(cfg_wait_queue, !cfg_busy)) | ||
661 | return -ERESTARTSYS; | ||
662 | return 0; | ||
663 | } | ||
664 | |||
665 | static int __init chp_init(void) | ||
666 | { | ||
667 | struct chp_id chpid; | ||
668 | |||
669 | chp_wq = create_singlethread_workqueue("cio_chp"); | ||
670 | if (!chp_wq) | ||
671 | return -ENOMEM; | ||
672 | INIT_WORK(&cfg_work, cfg_func); | ||
673 | init_waitqueue_head(&cfg_wait_queue); | ||
674 | if (info_update()) | ||
675 | return 0; | ||
676 | /* Register available channel-paths. */ | ||
677 | chp_id_for_each(&chpid) { | ||
678 | if (chp_info_get_status(chpid) != CHP_STATUS_NOT_RECOGNIZED) | ||
679 | chp_new(chpid); | ||
680 | } | ||
681 | |||
682 | return 0; | ||
683 | } | ||
684 | |||
685 | subsys_initcall(chp_init); | ||