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 | /** |
