watchdog: core: make sure the watchdog_worker is not deferred
commit 4cd13c21b2
("softirq: Let ksoftirqd do its job") has the
effect of deferring timer handling in case of high CPU load, hence
delaying the delayed work allthought the worker is running which
high realtime priority.
As hrtimers are not managed by softirqs, this patch replaces the
delayed work by a plain work and uses an hrtimer to schedule that work.
Signed-off-by: Christophe Leroy <christophe.leroy@c-s.fr>
Reviewed-by: Guenter Roeck <Linux@roeck-us.net>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
This commit is contained in:
parent
1d2e5eb521
commit
1ff688209e
1 changed files with 52 additions and 34 deletions
|
@ -36,10 +36,10 @@
|
||||||
#include <linux/errno.h> /* For the -ENODEV/... values */
|
#include <linux/errno.h> /* For the -ENODEV/... values */
|
||||||
#include <linux/fs.h> /* For file operations */
|
#include <linux/fs.h> /* For file operations */
|
||||||
#include <linux/init.h> /* For __init/__exit/... */
|
#include <linux/init.h> /* For __init/__exit/... */
|
||||||
#include <linux/jiffies.h> /* For timeout functions */
|
#include <linux/hrtimer.h> /* For hrtimers */
|
||||||
#include <linux/kernel.h> /* For printk/panic/... */
|
#include <linux/kernel.h> /* For printk/panic/... */
|
||||||
#include <linux/kref.h> /* For data references */
|
#include <linux/kref.h> /* For data references */
|
||||||
#include <linux/kthread.h> /* For kthread_delayed_work */
|
#include <linux/kthread.h> /* For kthread_work */
|
||||||
#include <linux/miscdevice.h> /* For handling misc devices */
|
#include <linux/miscdevice.h> /* For handling misc devices */
|
||||||
#include <linux/module.h> /* For module stuff/... */
|
#include <linux/module.h> /* For module stuff/... */
|
||||||
#include <linux/mutex.h> /* For mutexes */
|
#include <linux/mutex.h> /* For mutexes */
|
||||||
|
@ -67,9 +67,10 @@ struct watchdog_core_data {
|
||||||
struct cdev cdev;
|
struct cdev cdev;
|
||||||
struct watchdog_device *wdd;
|
struct watchdog_device *wdd;
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
unsigned long last_keepalive;
|
ktime_t last_keepalive;
|
||||||
unsigned long last_hw_keepalive;
|
ktime_t last_hw_keepalive;
|
||||||
struct kthread_delayed_work work;
|
struct hrtimer timer;
|
||||||
|
struct kthread_work work;
|
||||||
unsigned long status; /* Internal status bits */
|
unsigned long status; /* Internal status bits */
|
||||||
#define _WDOG_DEV_OPEN 0 /* Opened ? */
|
#define _WDOG_DEV_OPEN 0 /* Opened ? */
|
||||||
#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
|
#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
|
||||||
|
@ -109,18 +110,19 @@ static inline bool watchdog_need_worker(struct watchdog_device *wdd)
|
||||||
(t && !watchdog_active(wdd) && watchdog_hw_running(wdd));
|
(t && !watchdog_active(wdd) && watchdog_hw_running(wdd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static long watchdog_next_keepalive(struct watchdog_device *wdd)
|
static ktime_t watchdog_next_keepalive(struct watchdog_device *wdd)
|
||||||
{
|
{
|
||||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||||
unsigned int timeout_ms = wdd->timeout * 1000;
|
unsigned int timeout_ms = wdd->timeout * 1000;
|
||||||
unsigned long keepalive_interval;
|
ktime_t keepalive_interval;
|
||||||
unsigned long last_heartbeat;
|
ktime_t last_heartbeat, latest_heartbeat;
|
||||||
unsigned long virt_timeout;
|
ktime_t virt_timeout;
|
||||||
unsigned int hw_heartbeat_ms;
|
unsigned int hw_heartbeat_ms;
|
||||||
|
|
||||||
virt_timeout = wd_data->last_keepalive + msecs_to_jiffies(timeout_ms);
|
virt_timeout = ktime_add(wd_data->last_keepalive,
|
||||||
|
ms_to_ktime(timeout_ms));
|
||||||
hw_heartbeat_ms = min_not_zero(timeout_ms, wdd->max_hw_heartbeat_ms);
|
hw_heartbeat_ms = min_not_zero(timeout_ms, wdd->max_hw_heartbeat_ms);
|
||||||
keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2);
|
keepalive_interval = ms_to_ktime(hw_heartbeat_ms / 2);
|
||||||
|
|
||||||
if (!watchdog_active(wdd))
|
if (!watchdog_active(wdd))
|
||||||
return keepalive_interval;
|
return keepalive_interval;
|
||||||
|
@ -130,8 +132,11 @@ static long watchdog_next_keepalive(struct watchdog_device *wdd)
|
||||||
* after the most recent ping from userspace, the last
|
* after the most recent ping from userspace, the last
|
||||||
* worker ping has to come in hw_heartbeat_ms before this timeout.
|
* worker ping has to come in hw_heartbeat_ms before this timeout.
|
||||||
*/
|
*/
|
||||||
last_heartbeat = virt_timeout - msecs_to_jiffies(hw_heartbeat_ms);
|
last_heartbeat = ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms));
|
||||||
return min_t(long, last_heartbeat - jiffies, keepalive_interval);
|
latest_heartbeat = ktime_sub(last_heartbeat, ktime_get());
|
||||||
|
if (ktime_before(latest_heartbeat, keepalive_interval))
|
||||||
|
return latest_heartbeat;
|
||||||
|
return keepalive_interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void watchdog_update_worker(struct watchdog_device *wdd)
|
static inline void watchdog_update_worker(struct watchdog_device *wdd)
|
||||||
|
@ -139,30 +144,33 @@ static inline void watchdog_update_worker(struct watchdog_device *wdd)
|
||||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||||
|
|
||||||
if (watchdog_need_worker(wdd)) {
|
if (watchdog_need_worker(wdd)) {
|
||||||
long t = watchdog_next_keepalive(wdd);
|
ktime_t t = watchdog_next_keepalive(wdd);
|
||||||
|
|
||||||
if (t > 0)
|
if (t > 0)
|
||||||
kthread_mod_delayed_work(watchdog_kworker,
|
hrtimer_start(&wd_data->timer, t, HRTIMER_MODE_REL);
|
||||||
&wd_data->work, t);
|
|
||||||
} else {
|
} else {
|
||||||
kthread_cancel_delayed_work_sync(&wd_data->work);
|
hrtimer_cancel(&wd_data->timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __watchdog_ping(struct watchdog_device *wdd)
|
static int __watchdog_ping(struct watchdog_device *wdd)
|
||||||
{
|
{
|
||||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||||
unsigned long earliest_keepalive = wd_data->last_hw_keepalive +
|
ktime_t earliest_keepalive, now;
|
||||||
msecs_to_jiffies(wdd->min_hw_heartbeat_ms);
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (time_is_after_jiffies(earliest_keepalive)) {
|
earliest_keepalive = ktime_add(wd_data->last_hw_keepalive,
|
||||||
kthread_mod_delayed_work(watchdog_kworker, &wd_data->work,
|
ms_to_ktime(wdd->min_hw_heartbeat_ms));
|
||||||
earliest_keepalive - jiffies);
|
now = ktime_get();
|
||||||
|
|
||||||
|
if (ktime_after(earliest_keepalive, now)) {
|
||||||
|
hrtimer_start(&wd_data->timer,
|
||||||
|
ktime_sub(earliest_keepalive, now),
|
||||||
|
HRTIMER_MODE_REL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
wd_data->last_hw_keepalive = jiffies;
|
wd_data->last_hw_keepalive = now;
|
||||||
|
|
||||||
if (wdd->ops->ping)
|
if (wdd->ops->ping)
|
||||||
err = wdd->ops->ping(wdd); /* ping the watchdog */
|
err = wdd->ops->ping(wdd); /* ping the watchdog */
|
||||||
|
@ -195,7 +203,7 @@ static int watchdog_ping(struct watchdog_device *wdd)
|
||||||
|
|
||||||
set_bit(_WDOG_KEEPALIVE, &wd_data->status);
|
set_bit(_WDOG_KEEPALIVE, &wd_data->status);
|
||||||
|
|
||||||
wd_data->last_keepalive = jiffies;
|
wd_data->last_keepalive = ktime_get();
|
||||||
return __watchdog_ping(wdd);
|
return __watchdog_ping(wdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,9 +218,7 @@ static void watchdog_ping_work(struct kthread_work *work)
|
||||||
{
|
{
|
||||||
struct watchdog_core_data *wd_data;
|
struct watchdog_core_data *wd_data;
|
||||||
|
|
||||||
wd_data = container_of(container_of(work, struct kthread_delayed_work,
|
wd_data = container_of(work, struct watchdog_core_data, work);
|
||||||
work),
|
|
||||||
struct watchdog_core_data, work);
|
|
||||||
|
|
||||||
mutex_lock(&wd_data->lock);
|
mutex_lock(&wd_data->lock);
|
||||||
if (watchdog_worker_should_ping(wd_data))
|
if (watchdog_worker_should_ping(wd_data))
|
||||||
|
@ -220,6 +226,16 @@ static void watchdog_ping_work(struct kthread_work *work)
|
||||||
mutex_unlock(&wd_data->lock);
|
mutex_unlock(&wd_data->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer)
|
||||||
|
{
|
||||||
|
struct watchdog_core_data *wd_data;
|
||||||
|
|
||||||
|
wd_data = container_of(timer, struct watchdog_core_data, timer);
|
||||||
|
|
||||||
|
kthread_queue_work(watchdog_kworker, &wd_data->work);
|
||||||
|
return HRTIMER_NORESTART;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* watchdog_start: wrapper to start the watchdog.
|
* watchdog_start: wrapper to start the watchdog.
|
||||||
* @wdd: the watchdog device to start
|
* @wdd: the watchdog device to start
|
||||||
|
@ -234,7 +250,7 @@ static void watchdog_ping_work(struct kthread_work *work)
|
||||||
static int watchdog_start(struct watchdog_device *wdd)
|
static int watchdog_start(struct watchdog_device *wdd)
|
||||||
{
|
{
|
||||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||||
unsigned long started_at;
|
ktime_t started_at;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (watchdog_active(wdd))
|
if (watchdog_active(wdd))
|
||||||
|
@ -242,7 +258,7 @@ static int watchdog_start(struct watchdog_device *wdd)
|
||||||
|
|
||||||
set_bit(_WDOG_KEEPALIVE, &wd_data->status);
|
set_bit(_WDOG_KEEPALIVE, &wd_data->status);
|
||||||
|
|
||||||
started_at = jiffies;
|
started_at = ktime_get();
|
||||||
if (watchdog_hw_running(wdd) && wdd->ops->ping)
|
if (watchdog_hw_running(wdd) && wdd->ops->ping)
|
||||||
err = wdd->ops->ping(wdd);
|
err = wdd->ops->ping(wdd);
|
||||||
else
|
else
|
||||||
|
@ -928,7 +944,9 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||||
if (IS_ERR_OR_NULL(watchdog_kworker))
|
if (IS_ERR_OR_NULL(watchdog_kworker))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
kthread_init_delayed_work(&wd_data->work, watchdog_ping_work);
|
kthread_init_work(&wd_data->work, watchdog_ping_work);
|
||||||
|
hrtimer_init(&wd_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||||
|
wd_data->timer.function = watchdog_timer_expired;
|
||||||
|
|
||||||
if (wdd->id == 0) {
|
if (wdd->id == 0) {
|
||||||
old_wd_data = wd_data;
|
old_wd_data = wd_data;
|
||||||
|
@ -964,7 +982,7 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Record time of most recent heartbeat as 'just before now'. */
|
/* Record time of most recent heartbeat as 'just before now'. */
|
||||||
wd_data->last_hw_keepalive = jiffies - 1;
|
wd_data->last_hw_keepalive = ktime_sub(ktime_get(), 1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the watchdog is running, prevent its driver from being unloaded,
|
* If the watchdog is running, prevent its driver from being unloaded,
|
||||||
|
@ -974,8 +992,7 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||||
__module_get(wdd->ops->owner);
|
__module_get(wdd->ops->owner);
|
||||||
kref_get(&wd_data->kref);
|
kref_get(&wd_data->kref);
|
||||||
if (handle_boot_enabled)
|
if (handle_boot_enabled)
|
||||||
kthread_queue_delayed_work(watchdog_kworker,
|
hrtimer_start(&wd_data->timer, 0, HRTIMER_MODE_REL);
|
||||||
&wd_data->work, 0);
|
|
||||||
else
|
else
|
||||||
pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n",
|
pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n",
|
||||||
wdd->id);
|
wdd->id);
|
||||||
|
@ -1012,7 +1029,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
|
||||||
watchdog_stop(wdd);
|
watchdog_stop(wdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
kthread_cancel_delayed_work_sync(&wd_data->work);
|
hrtimer_cancel(&wd_data->timer);
|
||||||
|
kthread_cancel_work_sync(&wd_data->work);
|
||||||
|
|
||||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue