diff options
| author | Hendrik Brueckner <brueckner@linux.vnet.ibm.com> | 2009-06-16 04:30:45 -0400 |
|---|---|---|
| committer | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2009-06-16 04:31:19 -0400 |
| commit | 0259162ecd083f1ce0f6022e669f393330b06f4d (patch) | |
| tree | 0e2efc9d8919e29ba64e355ba78ff14ec9969e76 | |
| parent | c23cad923bfebd295ec49dc9265569993903488d (diff) | |
[S390] pm: hvc_iucv power management callbacks
The patch adds supporting for suspending and resuming IUCV HVC terminal
devices from disk. The obligatory Linux device driver interfaces has
been added by registering a device driver on the IUCV bus.
For each IUCV HVC terminal device the driver creates a respective device
on the IUCV bus.
To support suspend and resume, the PM freeze callback severs any established
IUCV communication path and triggers a HVC tty hang-up when the system image
is restored.
IUCV communication path are no longer valid when the z/VM guest is halted.
The device driver initialization has been updated to register devices and
the a new routine has been extracted to facilitate the hang-up of IUCV HVC
terminal devices.
Signed-off-by: Hendrik Brueckner <brueckner@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
| -rw-r--r-- | drivers/char/hvc_iucv.c | 204 |
1 files changed, 161 insertions, 43 deletions
diff --git a/drivers/char/hvc_iucv.c b/drivers/char/hvc_iucv.c index 54481a887769..86105efb4eb6 100644 --- a/drivers/char/hvc_iucv.c +++ b/drivers/char/hvc_iucv.c | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | * This HVC device driver provides terminal access using | 4 | * This HVC device driver provides terminal access using |
| 5 | * z/VM IUCV communication paths. | 5 | * z/VM IUCV communication paths. |
| 6 | * | 6 | * |
| 7 | * Copyright IBM Corp. 2008 | 7 | * Copyright IBM Corp. 2008, 2009 |
| 8 | * | 8 | * |
| 9 | * Author(s): Hendrik Brueckner <brueckner@linux.vnet.ibm.com> | 9 | * Author(s): Hendrik Brueckner <brueckner@linux.vnet.ibm.com> |
| 10 | */ | 10 | */ |
| @@ -15,6 +15,7 @@ | |||
| 15 | #include <asm/ebcdic.h> | 15 | #include <asm/ebcdic.h> |
| 16 | #include <linux/ctype.h> | 16 | #include <linux/ctype.h> |
| 17 | #include <linux/delay.h> | 17 | #include <linux/delay.h> |
| 18 | #include <linux/device.h> | ||
| 18 | #include <linux/init.h> | 19 | #include <linux/init.h> |
| 19 | #include <linux/mempool.h> | 20 | #include <linux/mempool.h> |
| 20 | #include <linux/moduleparam.h> | 21 | #include <linux/moduleparam.h> |
| @@ -74,6 +75,7 @@ struct hvc_iucv_private { | |||
| 74 | wait_queue_head_t sndbuf_waitq; /* wait for send completion */ | 75 | wait_queue_head_t sndbuf_waitq; /* wait for send completion */ |
| 75 | struct list_head tty_outqueue; /* outgoing IUCV messages */ | 76 | struct list_head tty_outqueue; /* outgoing IUCV messages */ |
| 76 | struct list_head tty_inqueue; /* incoming IUCV messages */ | 77 | struct list_head tty_inqueue; /* incoming IUCV messages */ |
| 78 | struct device *dev; /* device structure */ | ||
| 77 | }; | 79 | }; |
| 78 | 80 | ||
| 79 | struct iucv_tty_buffer { | 81 | struct iucv_tty_buffer { |
| @@ -542,7 +544,68 @@ static void flush_sndbuf_sync(struct hvc_iucv_private *priv) | |||
| 542 | 544 | ||
| 543 | if (sync_wait) | 545 | if (sync_wait) |
| 544 | wait_event_timeout(priv->sndbuf_waitq, | 546 | wait_event_timeout(priv->sndbuf_waitq, |
| 545 | tty_outqueue_empty(priv), HZ); | 547 | tty_outqueue_empty(priv), HZ/10); |
| 548 | } | ||
| 549 | |||
| 550 | /** | ||
| 551 | * hvc_iucv_hangup() - Sever IUCV path and schedule hvc tty hang up | ||
| 552 | * @priv: Pointer to hvc_iucv_private structure | ||
| 553 | * | ||
| 554 | * This routine severs an existing IUCV communication path and hangs | ||
| 555 | * up the underlying HVC terminal device. | ||
| 556 | * The hang-up occurs only if an IUCV communication path is established; | ||
| 557 | * otherwise there is no need to hang up the terminal device. | ||
| 558 | * | ||
| 559 | * The IUCV HVC hang-up is separated into two steps: | ||
| 560 | * 1. After the IUCV path has been severed, the iucv_state is set to | ||
| 561 | * IUCV_SEVERED. | ||
| 562 | * 2. Later, when the HVC thread calls hvc_iucv_get_chars(), the | ||
| 563 | * IUCV_SEVERED state causes the tty hang-up in the HVC layer. | ||
| 564 | * | ||
| 565 | * If the tty has not yet been opened, clean up the hvc_iucv_private | ||
| 566 | * structure to allow re-connects. | ||
| 567 | * If the tty has been opened, let get_chars() return -EPIPE to signal | ||
| 568 | * the HVC layer to hang up the tty and, if so, wake up the HVC thread | ||
| 569 | * to call get_chars()... | ||
| 570 | * | ||
| 571 | * Special notes on hanging up a HVC terminal instantiated as console: | ||
| 572 | * Hang-up: 1. do_tty_hangup() replaces file ops (= hung_up_tty_fops) | ||
| 573 | * 2. do_tty_hangup() calls tty->ops->close() for console_filp | ||
| 574 | * => no hangup notifier is called by HVC (default) | ||
| 575 | * 2. hvc_close() returns because of tty_hung_up_p(filp) | ||
| 576 | * => no delete notifier is called! | ||
| 577 | * Finally, the back-end is not being notified, thus, the tty session is | ||
| 578 | * kept active (TTY_OPEN) to be ready for re-connects. | ||
| 579 | * | ||
| 580 | * Locking: spin_lock(&priv->lock) w/o disabling bh | ||
| 581 | */ | ||
| 582 | static void hvc_iucv_hangup(struct hvc_iucv_private *priv) | ||
| 583 | { | ||
| 584 | struct iucv_path *path; | ||
| 585 | |||
| 586 | path = NULL; | ||
| 587 | spin_lock(&priv->lock); | ||
| 588 | if (priv->iucv_state == IUCV_CONNECTED) { | ||
| 589 | path = priv->path; | ||
| 590 | priv->path = NULL; | ||
| 591 | priv->iucv_state = IUCV_SEVERED; | ||
| 592 | if (priv->tty_state == TTY_CLOSED) | ||
| 593 | hvc_iucv_cleanup(priv); | ||
| 594 | else | ||
| 595 | /* console is special (see above) */ | ||
| 596 | if (priv->is_console) { | ||
| 597 | hvc_iucv_cleanup(priv); | ||
| 598 | priv->tty_state = TTY_OPENED; | ||
| 599 | } else | ||
| 600 | hvc_kick(); | ||
| 601 | } | ||
| 602 | spin_unlock(&priv->lock); | ||
| 603 | |||
| 604 | /* finally sever path (outside of priv->lock due to lock ordering) */ | ||
| 605 | if (path) { | ||
| 606 | iucv_path_sever(path, NULL); | ||
| 607 | iucv_path_free(path); | ||
| 608 | } | ||
| 546 | } | 609 | } |
| 547 | 610 | ||
| 548 | /** | 611 | /** |
| @@ -735,11 +798,8 @@ out_path_handled: | |||
| 735 | * @ipuser: User specified data for this path | 798 | * @ipuser: User specified data for this path |
| 736 | * (AF_IUCV: port/service name and originator port) | 799 | * (AF_IUCV: port/service name and originator port) |
| 737 | * | 800 | * |
| 738 | * The function also severs the path (as required by the IUCV protocol) and | 801 | * This function calls the hvc_iucv_hangup() function for the |
| 739 | * sets the iucv state to IUCV_SEVERED for the associated struct | 802 | * respective IUCV HVC terminal. |
| 740 | * hvc_iucv_private instance. Later, the IUCV_SEVERED state triggers a tty | ||
| 741 | * hangup (hvc_iucv_get_chars() / hvc_iucv_write()). | ||
| 742 | * If tty portion of the HVC is closed, clean up the outqueue. | ||
| 743 | * | 803 | * |
| 744 | * Locking: struct hvc_iucv_private->lock | 804 | * Locking: struct hvc_iucv_private->lock |
| 745 | */ | 805 | */ |
| @@ -747,33 +807,7 @@ static void hvc_iucv_path_severed(struct iucv_path *path, u8 ipuser[16]) | |||
| 747 | { | 807 | { |
| 748 | struct hvc_iucv_private *priv = path->private; | 808 | struct hvc_iucv_private *priv = path->private; |
| 749 | 809 | ||
| 750 | spin_lock(&priv->lock); | 810 | hvc_iucv_hangup(priv); |
| 751 | priv->iucv_state = IUCV_SEVERED; | ||
| 752 | |||
| 753 | /* If the tty has not yet been opened, clean up the hvc_iucv_private | ||
| 754 | * structure to allow re-connects. | ||
| 755 | * This is also done for our console device because console hangups | ||
| 756 | * are handled specially and no notifier is called by HVC. | ||
| 757 | * The tty session is active (TTY_OPEN) and ready for re-connects... | ||
| 758 | * | ||
| 759 | * If it has been opened, let get_chars() return -EPIPE to signal the | ||
| 760 | * HVC layer to hang up the tty. | ||
| 761 | * If so, we need to wake up the HVC thread to call get_chars()... | ||
| 762 | */ | ||
| 763 | priv->path = NULL; | ||
| 764 | if (priv->tty_state == TTY_CLOSED) | ||
| 765 | hvc_iucv_cleanup(priv); | ||
| 766 | else | ||
| 767 | if (priv->is_console) { | ||
| 768 | hvc_iucv_cleanup(priv); | ||
| 769 | priv->tty_state = TTY_OPENED; | ||
| 770 | } else | ||
| 771 | hvc_kick(); | ||
| 772 | spin_unlock(&priv->lock); | ||
| 773 | |||
| 774 | /* finally sever path (outside of priv->lock due to lock ordering) */ | ||
| 775 | iucv_path_sever(path, ipuser); | ||
| 776 | iucv_path_free(path); | ||
| 777 | } | 811 | } |
| 778 | 812 | ||
| 779 | /** | 813 | /** |
| @@ -853,6 +887,37 @@ static void hvc_iucv_msg_complete(struct iucv_path *path, | |||
| 853 | destroy_tty_buffer_list(&list_remove); | 887 | destroy_tty_buffer_list(&list_remove); |
| 854 | } | 888 | } |
| 855 | 889 | ||
| 890 | /** | ||
| 891 | * hvc_iucv_pm_freeze() - Freeze PM callback | ||
| 892 | * @dev: IUVC HVC terminal device | ||
| 893 | * | ||
| 894 | * Sever an established IUCV communication path and | ||
| 895 | * trigger a hang-up of the underlying HVC terminal. | ||
| 896 | */ | ||
| 897 | static int hvc_iucv_pm_freeze(struct device *dev) | ||
| 898 | { | ||
| 899 | struct hvc_iucv_private *priv = dev_get_drvdata(dev); | ||
| 900 | |||
| 901 | local_bh_disable(); | ||
| 902 | hvc_iucv_hangup(priv); | ||
| 903 | local_bh_enable(); | ||
| 904 | |||
| 905 | return 0; | ||
| 906 | } | ||
| 907 | |||
| 908 | /** | ||
| 909 | * hvc_iucv_pm_restore_thaw() - Thaw and restore PM callback | ||
| 910 | * @dev: IUVC HVC terminal device | ||
| 911 | * | ||
| 912 | * Wake up the HVC thread to trigger hang-up and respective | ||
| 913 | * HVC back-end notifier invocations. | ||
| 914 | */ | ||
| 915 | static int hvc_iucv_pm_restore_thaw(struct device *dev) | ||
| 916 | { | ||
| 917 | hvc_kick(); | ||
| 918 | return 0; | ||
| 919 | } | ||
| 920 | |||
| 856 | 921 | ||
| 857 | /* HVC operations */ | 922 | /* HVC operations */ |
| 858 | static struct hv_ops hvc_iucv_ops = { | 923 | static struct hv_ops hvc_iucv_ops = { |
| @@ -863,6 +928,20 @@ static struct hv_ops hvc_iucv_ops = { | |||
| 863 | .notifier_hangup = hvc_iucv_notifier_hangup, | 928 | .notifier_hangup = hvc_iucv_notifier_hangup, |
| 864 | }; | 929 | }; |
| 865 | 930 | ||
| 931 | /* Suspend / resume device operations */ | ||
| 932 | static struct dev_pm_ops hvc_iucv_pm_ops = { | ||
| 933 | .freeze = hvc_iucv_pm_freeze, | ||
| 934 | .thaw = hvc_iucv_pm_restore_thaw, | ||
| 935 | .restore = hvc_iucv_pm_restore_thaw, | ||
| 936 | }; | ||
| 937 | |||
| 938 | /* IUCV HVC device driver */ | ||
| 939 | static struct device_driver hvc_iucv_driver = { | ||
| 940 | .name = KMSG_COMPONENT, | ||
| 941 | .bus = &iucv_bus, | ||
| 942 | .pm = &hvc_iucv_pm_ops, | ||
| 943 | }; | ||
| 944 | |||
| 866 | /** | 945 | /** |
| 867 | * hvc_iucv_alloc() - Allocates a new struct hvc_iucv_private instance | 946 | * hvc_iucv_alloc() - Allocates a new struct hvc_iucv_private instance |
| 868 | * @id: hvc_iucv_table index | 947 | * @id: hvc_iucv_table index |
| @@ -897,14 +976,12 @@ static int __init hvc_iucv_alloc(int id, unsigned int is_console) | |||
| 897 | /* set console flag */ | 976 | /* set console flag */ |
| 898 | priv->is_console = is_console; | 977 | priv->is_console = is_console; |
| 899 | 978 | ||
| 900 | /* finally allocate hvc */ | 979 | /* allocate hvc device */ |
| 901 | priv->hvc = hvc_alloc(HVC_IUCV_MAGIC + id, /* PAGE_SIZE */ | 980 | priv->hvc = hvc_alloc(HVC_IUCV_MAGIC + id, /* PAGE_SIZE */ |
| 902 | HVC_IUCV_MAGIC + id, &hvc_iucv_ops, 256); | 981 | HVC_IUCV_MAGIC + id, &hvc_iucv_ops, 256); |
| 903 | if (IS_ERR(priv->hvc)) { | 982 | if (IS_ERR(priv->hvc)) { |
| 904 | rc = PTR_ERR(priv->hvc); | 983 | rc = PTR_ERR(priv->hvc); |
| 905 | free_page((unsigned long) priv->sndbuf); | 984 | goto out_error_hvc; |
| 906 | kfree(priv); | ||
| 907 | return rc; | ||
| 908 | } | 985 | } |
| 909 | 986 | ||
| 910 | /* notify HVC thread instead of using polling */ | 987 | /* notify HVC thread instead of using polling */ |
| @@ -915,8 +992,45 @@ static int __init hvc_iucv_alloc(int id, unsigned int is_console) | |||
| 915 | memcpy(priv->srv_name, name, 8); | 992 | memcpy(priv->srv_name, name, 8); |
| 916 | ASCEBC(priv->srv_name, 8); | 993 | ASCEBC(priv->srv_name, 8); |
| 917 | 994 | ||
| 995 | /* create and setup device */ | ||
| 996 | priv->dev = kzalloc(sizeof(*priv->dev), GFP_KERNEL); | ||
| 997 | if (!priv->dev) { | ||
| 998 | rc = -ENOMEM; | ||
| 999 | goto out_error_dev; | ||
| 1000 | } | ||
| 1001 | dev_set_name(priv->dev, "hvc_iucv%d", id); | ||
| 1002 | dev_set_drvdata(priv->dev, priv); | ||
| 1003 | priv->dev->bus = &iucv_bus; | ||
| 1004 | priv->dev->parent = iucv_root; | ||
| 1005 | priv->dev->driver = &hvc_iucv_driver; | ||
| 1006 | priv->dev->release = (void (*)(struct device *)) kfree; | ||
| 1007 | rc = device_register(priv->dev); | ||
| 1008 | if (rc) { | ||
| 1009 | kfree(priv->dev); | ||
| 1010 | goto out_error_dev; | ||
| 1011 | } | ||
| 1012 | |||
| 918 | hvc_iucv_table[id] = priv; | 1013 | hvc_iucv_table[id] = priv; |
| 919 | return 0; | 1014 | return 0; |
| 1015 | |||
| 1016 | out_error_dev: | ||
| 1017 | hvc_remove(priv->hvc); | ||
| 1018 | out_error_hvc: | ||
| 1019 | free_page((unsigned long) priv->sndbuf); | ||
| 1020 | kfree(priv); | ||
| 1021 | |||
| 1022 | return rc; | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | /** | ||
| 1026 | * hvc_iucv_destroy() - Destroy and free hvc_iucv_private instances | ||
| 1027 | */ | ||
| 1028 | static void __init hvc_iucv_destroy(struct hvc_iucv_private *priv) | ||
| 1029 | { | ||
| 1030 | hvc_remove(priv->hvc); | ||
| 1031 | device_unregister(priv->dev); | ||
| 1032 | free_page((unsigned long) priv->sndbuf); | ||
| 1033 | kfree(priv); | ||
| 920 | } | 1034 | } |
| 921 | 1035 | ||
| 922 | /** | 1036 | /** |
| @@ -1109,6 +1223,11 @@ static int __init hvc_iucv_init(void) | |||
| 1109 | goto out_error; | 1223 | goto out_error; |
| 1110 | } | 1224 | } |
| 1111 | 1225 | ||
| 1226 | /* register IUCV HVC device driver */ | ||
| 1227 | rc = driver_register(&hvc_iucv_driver); | ||
| 1228 | if (rc) | ||
| 1229 | goto out_error; | ||
| 1230 | |||
| 1112 | /* parse hvc_iucv_allow string and create z/VM user ID filter list */ | 1231 | /* parse hvc_iucv_allow string and create z/VM user ID filter list */ |
| 1113 | if (hvc_iucv_filter_string) { | 1232 | if (hvc_iucv_filter_string) { |
| 1114 | rc = hvc_iucv_setup_filter(hvc_iucv_filter_string); | 1233 | rc = hvc_iucv_setup_filter(hvc_iucv_filter_string); |
| @@ -1183,15 +1302,14 @@ out_error_iucv: | |||
| 1183 | iucv_unregister(&hvc_iucv_handler, 0); | 1302 | iucv_unregister(&hvc_iucv_handler, 0); |
| 1184 | out_error_hvc: | 1303 | out_error_hvc: |
| 1185 | for (i = 0; i < hvc_iucv_devices; i++) | 1304 | for (i = 0; i < hvc_iucv_devices; i++) |
| 1186 | if (hvc_iucv_table[i]) { | 1305 | if (hvc_iucv_table[i]) |
| 1187 | if (hvc_iucv_table[i]->hvc) | 1306 | hvc_iucv_destroy(hvc_iucv_table[i]); |
| 1188 | hvc_remove(hvc_iucv_table[i]->hvc); | ||
| 1189 | kfree(hvc_iucv_table[i]); | ||
| 1190 | } | ||
| 1191 | out_error_memory: | 1307 | out_error_memory: |
| 1192 | mempool_destroy(hvc_iucv_mempool); | 1308 | mempool_destroy(hvc_iucv_mempool); |
| 1193 | kmem_cache_destroy(hvc_iucv_buffer_cache); | 1309 | kmem_cache_destroy(hvc_iucv_buffer_cache); |
| 1194 | out_error: | 1310 | out_error: |
| 1311 | if (hvc_iucv_filter) | ||
| 1312 | kfree(hvc_iucv_filter); | ||
| 1195 | hvc_iucv_devices = 0; /* ensure that we do not provide any device */ | 1313 | hvc_iucv_devices = 0; /* ensure that we do not provide any device */ |
| 1196 | return rc; | 1314 | return rc; |
| 1197 | } | 1315 | } |
