diff options
Diffstat (limited to 'drivers/s390/cio/device.c')
-rw-r--r-- | drivers/s390/cio/device.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 35441fa16be1..3c57c1a18bb8 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c | |||
@@ -138,6 +138,19 @@ static struct css_device_id io_subchannel_ids[] = { | |||
138 | }; | 138 | }; |
139 | MODULE_DEVICE_TABLE(css, io_subchannel_ids); | 139 | MODULE_DEVICE_TABLE(css, io_subchannel_ids); |
140 | 140 | ||
141 | static int io_subchannel_prepare(struct subchannel *sch) | ||
142 | { | ||
143 | struct ccw_device *cdev; | ||
144 | /* | ||
145 | * Don't allow suspend while a ccw device registration | ||
146 | * is still outstanding. | ||
147 | */ | ||
148 | cdev = sch_get_cdev(sch); | ||
149 | if (cdev && !device_is_registered(&cdev->dev)) | ||
150 | return -EAGAIN; | ||
151 | return 0; | ||
152 | } | ||
153 | |||
141 | static struct css_driver io_subchannel_driver = { | 154 | static struct css_driver io_subchannel_driver = { |
142 | .owner = THIS_MODULE, | 155 | .owner = THIS_MODULE, |
143 | .subchannel_type = io_subchannel_ids, | 156 | .subchannel_type = io_subchannel_ids, |
@@ -148,6 +161,7 @@ static struct css_driver io_subchannel_driver = { | |||
148 | .probe = io_subchannel_probe, | 161 | .probe = io_subchannel_probe, |
149 | .remove = io_subchannel_remove, | 162 | .remove = io_subchannel_remove, |
150 | .shutdown = io_subchannel_shutdown, | 163 | .shutdown = io_subchannel_shutdown, |
164 | .prepare = io_subchannel_prepare, | ||
151 | }; | 165 | }; |
152 | 166 | ||
153 | struct workqueue_struct *ccw_device_work; | 167 | struct workqueue_struct *ccw_device_work; |
@@ -1775,6 +1789,15 @@ ccw_device_probe_console(void) | |||
1775 | return &console_cdev; | 1789 | return &console_cdev; |
1776 | } | 1790 | } |
1777 | 1791 | ||
1792 | static int ccw_device_pm_restore(struct device *dev); | ||
1793 | |||
1794 | int ccw_device_force_console(void) | ||
1795 | { | ||
1796 | if (!console_cdev_in_use) | ||
1797 | return -ENODEV; | ||
1798 | return ccw_device_pm_restore(&console_cdev.dev); | ||
1799 | } | ||
1800 | EXPORT_SYMBOL_GPL(ccw_device_force_console); | ||
1778 | 1801 | ||
1779 | const char *cio_get_console_cdev_name(struct subchannel *sch) | 1802 | const char *cio_get_console_cdev_name(struct subchannel *sch) |
1780 | { | 1803 | { |
@@ -1895,6 +1918,242 @@ static void ccw_device_shutdown(struct device *dev) | |||
1895 | disable_cmf(cdev); | 1918 | disable_cmf(cdev); |
1896 | } | 1919 | } |
1897 | 1920 | ||
1921 | static int ccw_device_pm_prepare(struct device *dev) | ||
1922 | { | ||
1923 | struct ccw_device *cdev = to_ccwdev(dev); | ||
1924 | |||
1925 | if (work_pending(&cdev->private->kick_work)) | ||
1926 | return -EAGAIN; | ||
1927 | /* Fail while device is being set online/offline. */ | ||
1928 | if (atomic_read(&cdev->private->onoff)) | ||
1929 | return -EAGAIN; | ||
1930 | |||
1931 | if (cdev->online && cdev->drv && cdev->drv->prepare) | ||
1932 | return cdev->drv->prepare(cdev); | ||
1933 | |||
1934 | return 0; | ||
1935 | } | ||
1936 | |||
1937 | static void ccw_device_pm_complete(struct device *dev) | ||
1938 | { | ||
1939 | struct ccw_device *cdev = to_ccwdev(dev); | ||
1940 | |||
1941 | if (cdev->online && cdev->drv && cdev->drv->complete) | ||
1942 | cdev->drv->complete(cdev); | ||
1943 | } | ||
1944 | |||
1945 | static int ccw_device_pm_freeze(struct device *dev) | ||
1946 | { | ||
1947 | struct ccw_device *cdev = to_ccwdev(dev); | ||
1948 | struct subchannel *sch = to_subchannel(cdev->dev.parent); | ||
1949 | int ret, cm_enabled; | ||
1950 | |||
1951 | /* Fail suspend while device is in transistional state. */ | ||
1952 | if (!dev_fsm_final_state(cdev)) | ||
1953 | return -EAGAIN; | ||
1954 | if (!cdev->online) | ||
1955 | return 0; | ||
1956 | if (cdev->drv && cdev->drv->freeze) { | ||
1957 | ret = cdev->drv->freeze(cdev); | ||
1958 | if (ret) | ||
1959 | return ret; | ||
1960 | } | ||
1961 | |||
1962 | spin_lock_irq(sch->lock); | ||
1963 | cm_enabled = cdev->private->cmb != NULL; | ||
1964 | spin_unlock_irq(sch->lock); | ||
1965 | if (cm_enabled) { | ||
1966 | /* Don't have the css write on memory. */ | ||
1967 | ret = ccw_set_cmf(cdev, 0); | ||
1968 | if (ret) | ||
1969 | return ret; | ||
1970 | } | ||
1971 | /* From here on, disallow device driver I/O. */ | ||
1972 | spin_lock_irq(sch->lock); | ||
1973 | ret = cio_disable_subchannel(sch); | ||
1974 | spin_unlock_irq(sch->lock); | ||
1975 | |||
1976 | return ret; | ||
1977 | } | ||
1978 | |||
1979 | static int ccw_device_pm_thaw(struct device *dev) | ||
1980 | { | ||
1981 | struct ccw_device *cdev = to_ccwdev(dev); | ||
1982 | struct subchannel *sch = to_subchannel(cdev->dev.parent); | ||
1983 | int ret, cm_enabled; | ||
1984 | |||
1985 | if (!cdev->online) | ||
1986 | return 0; | ||
1987 | |||
1988 | spin_lock_irq(sch->lock); | ||
1989 | /* Allow device driver I/O again. */ | ||
1990 | ret = cio_enable_subchannel(sch, (u32)(addr_t)sch); | ||
1991 | cm_enabled = cdev->private->cmb != NULL; | ||
1992 | spin_unlock_irq(sch->lock); | ||
1993 | if (ret) | ||
1994 | return ret; | ||
1995 | |||
1996 | if (cm_enabled) { | ||
1997 | ret = ccw_set_cmf(cdev, 1); | ||
1998 | if (ret) | ||
1999 | return ret; | ||
2000 | } | ||
2001 | |||
2002 | if (cdev->drv && cdev->drv->thaw) | ||
2003 | ret = cdev->drv->thaw(cdev); | ||
2004 | |||
2005 | return ret; | ||
2006 | } | ||
2007 | |||
2008 | static void __ccw_device_pm_restore(struct ccw_device *cdev) | ||
2009 | { | ||
2010 | struct subchannel *sch = to_subchannel(cdev->dev.parent); | ||
2011 | int ret; | ||
2012 | |||
2013 | if (cio_is_console(sch->schid)) | ||
2014 | goto out; | ||
2015 | /* | ||
2016 | * While we were sleeping, devices may have gone or become | ||
2017 | * available again. Kick re-detection. | ||
2018 | */ | ||
2019 | spin_lock_irq(sch->lock); | ||
2020 | cdev->private->flags.resuming = 1; | ||
2021 | ret = ccw_device_recognition(cdev); | ||
2022 | spin_unlock_irq(sch->lock); | ||
2023 | if (ret) { | ||
2024 | CIO_MSG_EVENT(0, "Couldn't start recognition for device " | ||
2025 | "%s (ret=%d)\n", dev_name(&cdev->dev), ret); | ||
2026 | spin_lock_irq(sch->lock); | ||
2027 | cdev->private->state = DEV_STATE_DISCONNECTED; | ||
2028 | spin_unlock_irq(sch->lock); | ||
2029 | /* notify driver after the resume cb */ | ||
2030 | goto out; | ||
2031 | } | ||
2032 | wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev) || | ||
2033 | cdev->private->state == DEV_STATE_DISCONNECTED); | ||
2034 | |||
2035 | out: | ||
2036 | cdev->private->flags.resuming = 0; | ||
2037 | } | ||
2038 | |||
2039 | static int resume_handle_boxed(struct ccw_device *cdev) | ||
2040 | { | ||
2041 | cdev->private->state = DEV_STATE_BOXED; | ||
2042 | if (ccw_device_notify(cdev, CIO_BOXED)) | ||
2043 | return 0; | ||
2044 | ccw_device_schedule_sch_unregister(cdev); | ||
2045 | return -ENODEV; | ||
2046 | } | ||
2047 | |||
2048 | static int resume_handle_disc(struct ccw_device *cdev) | ||
2049 | { | ||
2050 | cdev->private->state = DEV_STATE_DISCONNECTED; | ||
2051 | if (ccw_device_notify(cdev, CIO_GONE)) | ||
2052 | return 0; | ||
2053 | ccw_device_schedule_sch_unregister(cdev); | ||
2054 | return -ENODEV; | ||
2055 | } | ||
2056 | |||
2057 | static int ccw_device_pm_restore(struct device *dev) | ||
2058 | { | ||
2059 | struct ccw_device *cdev = to_ccwdev(dev); | ||
2060 | struct subchannel *sch = to_subchannel(cdev->dev.parent); | ||
2061 | int ret = 0, cm_enabled; | ||
2062 | |||
2063 | __ccw_device_pm_restore(cdev); | ||
2064 | spin_lock_irq(sch->lock); | ||
2065 | if (cio_is_console(sch->schid)) { | ||
2066 | cio_enable_subchannel(sch, (u32)(addr_t)sch); | ||
2067 | spin_unlock_irq(sch->lock); | ||
2068 | goto out_restore; | ||
2069 | } | ||
2070 | cdev->private->flags.donotify = 0; | ||
2071 | /* check recognition results */ | ||
2072 | switch (cdev->private->state) { | ||
2073 | case DEV_STATE_OFFLINE: | ||
2074 | break; | ||
2075 | case DEV_STATE_BOXED: | ||
2076 | ret = resume_handle_boxed(cdev); | ||
2077 | spin_unlock_irq(sch->lock); | ||
2078 | if (ret) | ||
2079 | goto out; | ||
2080 | goto out_restore; | ||
2081 | case DEV_STATE_DISCONNECTED: | ||
2082 | goto out_disc_unlock; | ||
2083 | default: | ||
2084 | goto out_unreg_unlock; | ||
2085 | } | ||
2086 | /* check if the device id has changed */ | ||
2087 | if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) { | ||
2088 | CIO_MSG_EVENT(0, "resume: sch %s: failed (devno changed from " | ||
2089 | "%04x to %04x)\n", dev_name(&sch->dev), | ||
2090 | cdev->private->dev_id.devno, | ||
2091 | sch->schib.pmcw.dev); | ||
2092 | goto out_unreg_unlock; | ||
2093 | } | ||
2094 | /* check if the device type has changed */ | ||
2095 | if (!ccw_device_test_sense_data(cdev)) { | ||
2096 | ccw_device_update_sense_data(cdev); | ||
2097 | PREPARE_WORK(&cdev->private->kick_work, | ||
2098 | ccw_device_do_unbind_bind); | ||
2099 | queue_work(ccw_device_work, &cdev->private->kick_work); | ||
2100 | ret = -ENODEV; | ||
2101 | goto out_unlock; | ||
2102 | } | ||
2103 | if (!cdev->online) { | ||
2104 | ret = 0; | ||
2105 | goto out_unlock; | ||
2106 | } | ||
2107 | ret = ccw_device_online(cdev); | ||
2108 | if (ret) | ||
2109 | goto out_disc_unlock; | ||
2110 | |||
2111 | cm_enabled = cdev->private->cmb != NULL; | ||
2112 | spin_unlock_irq(sch->lock); | ||
2113 | |||
2114 | wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev)); | ||
2115 | if (cdev->private->state != DEV_STATE_ONLINE) { | ||
2116 | spin_lock_irq(sch->lock); | ||
2117 | goto out_disc_unlock; | ||
2118 | } | ||
2119 | if (cm_enabled) { | ||
2120 | ret = ccw_set_cmf(cdev, 1); | ||
2121 | if (ret) { | ||
2122 | CIO_MSG_EVENT(2, "resume: cdev %s: cmf failed " | ||
2123 | "(rc=%d)\n", dev_name(&cdev->dev), ret); | ||
2124 | ret = 0; | ||
2125 | } | ||
2126 | } | ||
2127 | |||
2128 | out_restore: | ||
2129 | if (cdev->online && cdev->drv && cdev->drv->restore) | ||
2130 | ret = cdev->drv->restore(cdev); | ||
2131 | out: | ||
2132 | return ret; | ||
2133 | |||
2134 | out_disc_unlock: | ||
2135 | ret = resume_handle_disc(cdev); | ||
2136 | spin_unlock_irq(sch->lock); | ||
2137 | if (ret) | ||
2138 | return ret; | ||
2139 | goto out_restore; | ||
2140 | |||
2141 | out_unreg_unlock: | ||
2142 | ccw_device_schedule_sch_unregister(cdev); | ||
2143 | ret = -ENODEV; | ||
2144 | out_unlock: | ||
2145 | spin_unlock_irq(sch->lock); | ||
2146 | return ret; | ||
2147 | } | ||
2148 | |||
2149 | static struct dev_pm_ops ccw_pm_ops = { | ||
2150 | .prepare = ccw_device_pm_prepare, | ||
2151 | .complete = ccw_device_pm_complete, | ||
2152 | .freeze = ccw_device_pm_freeze, | ||
2153 | .thaw = ccw_device_pm_thaw, | ||
2154 | .restore = ccw_device_pm_restore, | ||
2155 | }; | ||
2156 | |||
1898 | struct bus_type ccw_bus_type = { | 2157 | struct bus_type ccw_bus_type = { |
1899 | .name = "ccw", | 2158 | .name = "ccw", |
1900 | .match = ccw_bus_match, | 2159 | .match = ccw_bus_match, |
@@ -1902,6 +2161,7 @@ struct bus_type ccw_bus_type = { | |||
1902 | .probe = ccw_device_probe, | 2161 | .probe = ccw_device_probe, |
1903 | .remove = ccw_device_remove, | 2162 | .remove = ccw_device_remove, |
1904 | .shutdown = ccw_device_shutdown, | 2163 | .shutdown = ccw_device_shutdown, |
2164 | .pm = &ccw_pm_ops, | ||
1905 | }; | 2165 | }; |
1906 | 2166 | ||
1907 | /** | 2167 | /** |