diff options
-rw-r--r-- | Documentation/watchdog/watchdog-kernel-api.txt | 22 | ||||
-rw-r--r-- | drivers/watchdog/watchdog_dev.c | 52 | ||||
-rw-r--r-- | include/linux/watchdog.h | 10 |
3 files changed, 64 insertions, 20 deletions
diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 15a02595ade1..954134a5c4a4 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt | |||
@@ -137,10 +137,10 @@ are: | |||
137 | * stop: with this routine the watchdog timer device is being stopped. | 137 | * stop: with this routine the watchdog timer device is being stopped. |
138 | The routine needs a pointer to the watchdog timer device structure as a | 138 | The routine needs a pointer to the watchdog timer device structure as a |
139 | parameter. It returns zero on success or a negative errno code for failure. | 139 | parameter. It returns zero on success or a negative errno code for failure. |
140 | Some watchdog timer hardware can only be started and not be stopped. The | 140 | Some watchdog timer hardware can only be started and not be stopped. |
141 | driver supporting this hardware needs to make sure that a start and stop | 141 | If a watchdog can not be stopped, the watchdog driver must set the |
142 | routine is being provided. This can be done by using a timer in the driver | 142 | WDOG_HW_RUNNING flag in its stop function to inform the watchdog core that |
143 | that regularly sends a keepalive ping to the watchdog timer hardware. | 143 | the watchdog is still running. |
144 | 144 | ||
145 | Not all watchdog timer hardware supports the same functionality. That's why | 145 | Not all watchdog timer hardware supports the same functionality. That's why |
146 | all other routines/operations are optional. They only need to be provided if | 146 | all other routines/operations are optional. They only need to be provided if |
@@ -189,11 +189,19 @@ The 'ref' and 'unref' operations are no longer used and deprecated. | |||
189 | The status bits should (preferably) be set with the set_bit and clear_bit alike | 189 | The status bits should (preferably) be set with the set_bit and clear_bit alike |
190 | bit-operations. The status bits that are defined are: | 190 | bit-operations. The status bits that are defined are: |
191 | * WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device | 191 | * WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device |
192 | is active or not. When the watchdog is active after booting, then you should | 192 | is active or not from user perspective. User space is expected to send |
193 | set this status bit (Note: when you register the watchdog timer device with | 193 | heartbeat requests to the driver while this flag is set. |
194 | this bit set, then opening /dev/watchdog will skip the start operation) | ||
195 | * WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog. | 194 | * WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog. |
196 | If this bit is set then the watchdog timer will not be able to stop. | 195 | If this bit is set then the watchdog timer will not be able to stop. |
196 | * WDOG_HW_RUNNING: Set by the watchdog driver if the hardware watchdog is | ||
197 | running. The bit must be set if the watchdog timer hardware can not be | ||
198 | stopped. The bit may also be set if the watchdog timer is running after | ||
199 | booting, before the watchdog device is opened. If set, the watchdog | ||
200 | infrastructure will send keepalives to the watchdog hardware while | ||
201 | WDOG_ACTIVE is not set. | ||
202 | Note: when you register the watchdog timer device with this bit set, | ||
203 | then opening /dev/watchdog will skip the start operation but send a keepalive | ||
204 | request instead. | ||
197 | 205 | ||
198 | To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog | 206 | To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog |
199 | timer device) you can either: | 207 | timer device) you can either: |
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index e668a9e8b648..5d3a9fa4856e 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c | |||
@@ -92,7 +92,8 @@ static inline bool watchdog_need_worker(struct watchdog_device *wdd) | |||
92 | * requests. | 92 | * requests. |
93 | * - Userspace requests a longer timeout than the hardware can handle. | 93 | * - Userspace requests a longer timeout than the hardware can handle. |
94 | */ | 94 | */ |
95 | return watchdog_active(wdd) && hm && t > hm; | 95 | return hm && ((watchdog_active(wdd) && t > hm) || |
96 | (t && !watchdog_active(wdd) && watchdog_hw_running(wdd))); | ||
96 | } | 97 | } |
97 | 98 | ||
98 | static long watchdog_next_keepalive(struct watchdog_device *wdd) | 99 | static long watchdog_next_keepalive(struct watchdog_device *wdd) |
@@ -108,6 +109,9 @@ static long watchdog_next_keepalive(struct watchdog_device *wdd) | |||
108 | hw_heartbeat_ms = min(timeout_ms, wdd->max_hw_heartbeat_ms); | 109 | hw_heartbeat_ms = min(timeout_ms, wdd->max_hw_heartbeat_ms); |
109 | keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2); | 110 | keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2); |
110 | 111 | ||
112 | if (!watchdog_active(wdd)) | ||
113 | return keepalive_interval; | ||
114 | |||
111 | /* | 115 | /* |
112 | * To ensure that the watchdog times out wdd->timeout seconds | 116 | * To ensure that the watchdog times out wdd->timeout seconds |
113 | * after the most recent ping from userspace, the last | 117 | * after the most recent ping from userspace, the last |
@@ -161,7 +165,7 @@ static int watchdog_ping(struct watchdog_device *wdd) | |||
161 | { | 165 | { |
162 | struct watchdog_core_data *wd_data = wdd->wd_data; | 166 | struct watchdog_core_data *wd_data = wdd->wd_data; |
163 | 167 | ||
164 | if (!watchdog_active(wdd)) | 168 | if (!watchdog_active(wdd) && !watchdog_hw_running(wdd)) |
165 | return 0; | 169 | return 0; |
166 | 170 | ||
167 | wd_data->last_keepalive = jiffies; | 171 | wd_data->last_keepalive = jiffies; |
@@ -178,7 +182,7 @@ static void watchdog_ping_work(struct work_struct *work) | |||
178 | 182 | ||
179 | mutex_lock(&wd_data->lock); | 183 | mutex_lock(&wd_data->lock); |
180 | wdd = wd_data->wdd; | 184 | wdd = wd_data->wdd; |
181 | if (wdd && watchdog_active(wdd)) | 185 | if (wdd && (watchdog_active(wdd) || watchdog_hw_running(wdd))) |
182 | __watchdog_ping(wdd); | 186 | __watchdog_ping(wdd); |
183 | mutex_unlock(&wd_data->lock); | 187 | mutex_unlock(&wd_data->lock); |
184 | } | 188 | } |
@@ -204,7 +208,10 @@ static int watchdog_start(struct watchdog_device *wdd) | |||
204 | return 0; | 208 | return 0; |
205 | 209 | ||
206 | started_at = jiffies; | 210 | started_at = jiffies; |
207 | err = wdd->ops->start(wdd); | 211 | if (watchdog_hw_running(wdd) && wdd->ops->ping) |
212 | err = wdd->ops->ping(wdd); | ||
213 | else | ||
214 | err = wdd->ops->start(wdd); | ||
208 | if (err == 0) { | 215 | if (err == 0) { |
209 | set_bit(WDOG_ACTIVE, &wdd->status); | 216 | set_bit(WDOG_ACTIVE, &wdd->status); |
210 | wd_data->last_keepalive = started_at; | 217 | wd_data->last_keepalive = started_at; |
@@ -228,8 +235,7 @@ static int watchdog_start(struct watchdog_device *wdd) | |||
228 | 235 | ||
229 | static int watchdog_stop(struct watchdog_device *wdd) | 236 | static int watchdog_stop(struct watchdog_device *wdd) |
230 | { | 237 | { |
231 | struct watchdog_core_data *wd_data = wdd->wd_data; | 238 | int err = 0; |
232 | int err; | ||
233 | 239 | ||
234 | if (!watchdog_active(wdd)) | 240 | if (!watchdog_active(wdd)) |
235 | return 0; | 241 | return 0; |
@@ -243,7 +249,7 @@ static int watchdog_stop(struct watchdog_device *wdd) | |||
243 | err = wdd->ops->stop(wdd); | 249 | err = wdd->ops->stop(wdd); |
244 | if (err == 0) { | 250 | if (err == 0) { |
245 | clear_bit(WDOG_ACTIVE, &wdd->status); | 251 | clear_bit(WDOG_ACTIVE, &wdd->status); |
246 | cancel_delayed_work(&wd_data->work); | 252 | watchdog_update_worker(wdd); |
247 | } | 253 | } |
248 | 254 | ||
249 | return err; | 255 | return err; |
@@ -641,7 +647,7 @@ static int watchdog_open(struct inode *inode, struct file *file) | |||
641 | * If the /dev/watchdog device is open, we don't want the module | 647 | * If the /dev/watchdog device is open, we don't want the module |
642 | * to be unloaded. | 648 | * to be unloaded. |
643 | */ | 649 | */ |
644 | if (!try_module_get(wdd->ops->owner)) { | 650 | if (!watchdog_hw_running(wdd) && !try_module_get(wdd->ops->owner)) { |
645 | err = -EBUSY; | 651 | err = -EBUSY; |
646 | goto out_clear; | 652 | goto out_clear; |
647 | } | 653 | } |
@@ -652,7 +658,8 @@ static int watchdog_open(struct inode *inode, struct file *file) | |||
652 | 658 | ||
653 | file->private_data = wd_data; | 659 | file->private_data = wd_data; |
654 | 660 | ||
655 | kref_get(&wd_data->kref); | 661 | if (!watchdog_hw_running(wdd)) |
662 | kref_get(&wd_data->kref); | ||
656 | 663 | ||
657 | /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ | 664 | /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ |
658 | return nonseekable_open(inode, file); | 665 | return nonseekable_open(inode, file); |
@@ -713,15 +720,22 @@ static int watchdog_release(struct inode *inode, struct file *file) | |||
713 | } | 720 | } |
714 | 721 | ||
715 | cancel_delayed_work_sync(&wd_data->work); | 722 | cancel_delayed_work_sync(&wd_data->work); |
723 | watchdog_update_worker(wdd); | ||
716 | 724 | ||
717 | /* make sure that /dev/watchdog can be re-opened */ | 725 | /* make sure that /dev/watchdog can be re-opened */ |
718 | clear_bit(_WDOG_DEV_OPEN, &wd_data->status); | 726 | clear_bit(_WDOG_DEV_OPEN, &wd_data->status); |
719 | 727 | ||
720 | done: | 728 | done: |
721 | mutex_unlock(&wd_data->lock); | 729 | mutex_unlock(&wd_data->lock); |
722 | /* Allow the owner module to be unloaded again */ | 730 | /* |
723 | module_put(wd_data->cdev.owner); | 731 | * Allow the owner module to be unloaded again unless the watchdog |
724 | kref_put(&wd_data->kref, watchdog_core_data_release); | 732 | * is still running. If the watchdog is still running, it can not |
733 | * be stopped, and its driver must not be unloaded. | ||
734 | */ | ||
735 | if (!watchdog_hw_running(wdd)) { | ||
736 | module_put(wdd->ops->owner); | ||
737 | kref_put(&wd_data->kref, watchdog_core_data_release); | ||
738 | } | ||
725 | return 0; | 739 | return 0; |
726 | } | 740 | } |
727 | 741 | ||
@@ -798,8 +812,20 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) | |||
798 | old_wd_data = NULL; | 812 | old_wd_data = NULL; |
799 | kref_put(&wd_data->kref, watchdog_core_data_release); | 813 | kref_put(&wd_data->kref, watchdog_core_data_release); |
800 | } | 814 | } |
815 | return err; | ||
801 | } | 816 | } |
802 | return err; | 817 | |
818 | /* | ||
819 | * If the watchdog is running, prevent its driver from being unloaded, | ||
820 | * and schedule an immediate ping. | ||
821 | */ | ||
822 | if (watchdog_hw_running(wdd)) { | ||
823 | __module_get(wdd->ops->owner); | ||
824 | kref_get(&wd_data->kref); | ||
825 | queue_delayed_work(watchdog_wq, &wd_data->work, 0); | ||
826 | } | ||
827 | |||
828 | return 0; | ||
803 | } | 829 | } |
804 | 830 | ||
805 | /* | 831 | /* |
diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 8e82daecb7d3..e2f45549b243 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h | |||
@@ -105,6 +105,7 @@ struct watchdog_device { | |||
105 | #define WDOG_ACTIVE 0 /* Is the watchdog running/active */ | 105 | #define WDOG_ACTIVE 0 /* Is the watchdog running/active */ |
106 | #define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */ | 106 | #define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */ |
107 | #define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */ | 107 | #define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */ |
108 | #define WDOG_HW_RUNNING 3 /* True if HW watchdog running */ | ||
108 | struct list_head deferred; | 109 | struct list_head deferred; |
109 | }; | 110 | }; |
110 | 111 | ||
@@ -117,6 +118,15 @@ static inline bool watchdog_active(struct watchdog_device *wdd) | |||
117 | return test_bit(WDOG_ACTIVE, &wdd->status); | 118 | return test_bit(WDOG_ACTIVE, &wdd->status); |
118 | } | 119 | } |
119 | 120 | ||
121 | /* | ||
122 | * Use the following function to check whether or not the hardware watchdog | ||
123 | * is running | ||
124 | */ | ||
125 | static inline bool watchdog_hw_running(struct watchdog_device *wdd) | ||
126 | { | ||
127 | return test_bit(WDOG_HW_RUNNING, &wdd->status); | ||
128 | } | ||
129 | |||
120 | /* Use the following function to set the nowayout feature */ | 130 | /* Use the following function to set the nowayout feature */ |
121 | static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout) | 131 | static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout) |
122 | { | 132 | { |