aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomas Winkler <tomas.winkler@intel.com>2016-02-07 15:46:48 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2016-02-07 16:00:52 -0500
commit7a23f80eaae9c6b5175cd7a96634a91ed9928aff (patch)
treeba308c5af8f7d9536966c8738cb6a9ba4f89a3fb
parente97cdb303c04bfe60283094bdced68a0d363bd5d (diff)
watchdog: mei_wdt: register wd device only if required
For Intel Broadwell and newer platforms, the ME device can inform the host whether the watchdog functionality is activated or not. If the watchdog functionality is not activated then the watchdog interface can be not registered and eliminate unnecessary pings and hence lower the power consumption by avoiding waking up the device. The feature can be deactivated also without reboot in that case the watchdog device should be unregistered at runtime. The information regarding the deactivation is reported in the ping response command. In runtime case the unregistration has to be run from a worker so that the ping initiated by the watchdog core completes. Otherwise the flow will deadlock on watchdog core mutex which both ping and unregistration acquire. Reviewed-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com> Signed-off-by: Tomas Winkler <tomas.winkler@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/watchdog/mei_wdt.c200
1 files changed, 191 insertions, 9 deletions
diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c
index e7e3f144f2b0..9addf8902f74 100644
--- a/drivers/watchdog/mei_wdt.c
+++ b/drivers/watchdog/mei_wdt.c
@@ -16,6 +16,7 @@
16#include <linux/slab.h> 16#include <linux/slab.h>
17#include <linux/interrupt.h> 17#include <linux/interrupt.h>
18#include <linux/debugfs.h> 18#include <linux/debugfs.h>
19#include <linux/completion.h>
19#include <linux/watchdog.h> 20#include <linux/watchdog.h>
20 21
21#include <linux/uuid.h> 22#include <linux/uuid.h>
@@ -38,27 +39,35 @@
38 39
39/* Sub Commands */ 40/* Sub Commands */
40#define MEI_MC_START_WD_TIMER_REQ 0x13 41#define MEI_MC_START_WD_TIMER_REQ 0x13
42#define MEI_MC_START_WD_TIMER_RES 0x83
43#define MEI_WDT_STATUS_SUCCESS 0
44#define MEI_WDT_WDSTATE_NOT_REQUIRED 0x1
41#define MEI_MC_STOP_WD_TIMER_REQ 0x14 45#define MEI_MC_STOP_WD_TIMER_REQ 0x14
42 46
43/** 47/**
44 * enum mei_wdt_state - internal watchdog state 48 * enum mei_wdt_state - internal watchdog state
45 * 49 *
50 * @MEI_WDT_PROBE: wd in probing stage
46 * @MEI_WDT_IDLE: wd is idle and not opened 51 * @MEI_WDT_IDLE: wd is idle and not opened
47 * @MEI_WDT_START: wd was opened, start was called 52 * @MEI_WDT_START: wd was opened, start was called
48 * @MEI_WDT_RUNNING: wd is expecting keep alive pings 53 * @MEI_WDT_RUNNING: wd is expecting keep alive pings
49 * @MEI_WDT_STOPPING: wd is stopping and will move to IDLE 54 * @MEI_WDT_STOPPING: wd is stopping and will move to IDLE
55 * @MEI_WDT_NOT_REQUIRED: wd device is not required
50 */ 56 */
51enum mei_wdt_state { 57enum mei_wdt_state {
58 MEI_WDT_PROBE,
52 MEI_WDT_IDLE, 59 MEI_WDT_IDLE,
53 MEI_WDT_START, 60 MEI_WDT_START,
54 MEI_WDT_RUNNING, 61 MEI_WDT_RUNNING,
55 MEI_WDT_STOPPING, 62 MEI_WDT_STOPPING,
63 MEI_WDT_NOT_REQUIRED,
56}; 64};
57 65
58#if IS_ENABLED(CONFIG_DEBUG_FS)
59static const char *mei_wdt_state_str(enum mei_wdt_state state) 66static const char *mei_wdt_state_str(enum mei_wdt_state state)
60{ 67{
61 switch (state) { 68 switch (state) {
69 case MEI_WDT_PROBE:
70 return "PROBE";
62 case MEI_WDT_IDLE: 71 case MEI_WDT_IDLE:
63 return "IDLE"; 72 return "IDLE";
64 case MEI_WDT_START: 73 case MEI_WDT_START:
@@ -67,11 +76,12 @@ static const char *mei_wdt_state_str(enum mei_wdt_state state)
67 return "RUNNING"; 76 return "RUNNING";
68 case MEI_WDT_STOPPING: 77 case MEI_WDT_STOPPING:
69 return "STOPPING"; 78 return "STOPPING";
79 case MEI_WDT_NOT_REQUIRED:
80 return "NOT_REQUIRED";
70 default: 81 default:
71 return "unknown"; 82 return "unknown";
72 } 83 }
73} 84}
74#endif /* CONFIG_DEBUG_FS */
75 85
76/** 86/**
77 * struct mei_wdt - mei watchdog driver 87 * struct mei_wdt - mei watchdog driver
@@ -79,6 +89,10 @@ static const char *mei_wdt_state_str(enum mei_wdt_state state)
79 * 89 *
80 * @cldev: mei watchdog client device 90 * @cldev: mei watchdog client device
81 * @state: watchdog internal state 91 * @state: watchdog internal state
92 * @resp_required: ping required response
93 * @response: ping response completion
94 * @unregister: unregister worker
95 * @reg_lock: watchdog device registration lock
82 * @timeout: watchdog current timeout 96 * @timeout: watchdog current timeout
83 * 97 *
84 * @dbgfs_dir: debugfs dir entry 98 * @dbgfs_dir: debugfs dir entry
@@ -88,6 +102,10 @@ struct mei_wdt {
88 102
89 struct mei_cl_device *cldev; 103 struct mei_cl_device *cldev;
90 enum mei_wdt_state state; 104 enum mei_wdt_state state;
105 bool resp_required;
106 struct completion response;
107 struct work_struct unregister;
108 struct mutex reg_lock;
91 u16 timeout; 109 u16 timeout;
92 110
93#if IS_ENABLED(CONFIG_DEBUG_FS) 111#if IS_ENABLED(CONFIG_DEBUG_FS)
@@ -124,6 +142,19 @@ struct mei_wdt_start_request {
124} __packed; 142} __packed;
125 143
126/** 144/**
145 * struct mei_wdt_start_response watchdog start/ping response
146 *
147 * @hdr: Management Control Command Header
148 * @status: operation status
149 * @wdstate: watchdog status bit mask
150 */
151struct mei_wdt_start_response {
152 struct mei_mc_hdr hdr;
153 u8 status;
154 u8 wdstate;
155} __packed;
156
157/**
127 * struct mei_wdt_stop_request - watchdog stop 158 * struct mei_wdt_stop_request - watchdog stop
128 * 159 *
129 * @hdr: Management Control Command Header 160 * @hdr: Management Control Command Header
@@ -244,13 +275,18 @@ static int mei_wdt_ops_ping(struct watchdog_device *wdd)
244 if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING) 275 if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING)
245 return 0; 276 return 0;
246 277
278 if (wdt->resp_required)
279 init_completion(&wdt->response);
280
281 wdt->state = MEI_WDT_RUNNING;
247 ret = mei_wdt_ping(wdt); 282 ret = mei_wdt_ping(wdt);
248 if (ret) 283 if (ret)
249 return ret; 284 return ret;
250 285
251 wdt->state = MEI_WDT_RUNNING; 286 if (wdt->resp_required)
287 ret = wait_for_completion_killable(&wdt->response);
252 288
253 return 0; 289 return ret;
254} 290}
255 291
256/** 292/**
@@ -291,14 +327,34 @@ static struct watchdog_info wd_info = {
291}; 327};
292 328
293/** 329/**
330 * __mei_wdt_is_registered - check if wdt is registered
331 *
332 * @wdt: mei watchdog device
333 *
334 * Return: true if the wdt is registered with the watchdog subsystem
335 * Locking: should be called under wdt->reg_lock
336 */
337static inline bool __mei_wdt_is_registered(struct mei_wdt *wdt)
338{
339 return !!watchdog_get_drvdata(&wdt->wdd);
340}
341
342/**
294 * mei_wdt_unregister - unregister from the watchdog subsystem 343 * mei_wdt_unregister - unregister from the watchdog subsystem
295 * 344 *
296 * @wdt: mei watchdog device 345 * @wdt: mei watchdog device
297 */ 346 */
298static void mei_wdt_unregister(struct mei_wdt *wdt) 347static void mei_wdt_unregister(struct mei_wdt *wdt)
299{ 348{
300 watchdog_unregister_device(&wdt->wdd); 349 mutex_lock(&wdt->reg_lock);
301 watchdog_set_drvdata(&wdt->wdd, NULL); 350
351 if (__mei_wdt_is_registered(wdt)) {
352 watchdog_unregister_device(&wdt->wdd);
353 watchdog_set_drvdata(&wdt->wdd, NULL);
354 memset(&wdt->wdd, 0, sizeof(wdt->wdd));
355 }
356
357 mutex_unlock(&wdt->reg_lock);
302} 358}
303 359
304/** 360/**
@@ -318,6 +374,13 @@ static int mei_wdt_register(struct mei_wdt *wdt)
318 374
319 dev = &wdt->cldev->dev; 375 dev = &wdt->cldev->dev;
320 376
377 mutex_lock(&wdt->reg_lock);
378
379 if (__mei_wdt_is_registered(wdt)) {
380 ret = 0;
381 goto out;
382 }
383
321 wdt->wdd.info = &wd_info; 384 wdt->wdd.info = &wd_info;
322 wdt->wdd.ops = &wd_ops; 385 wdt->wdd.ops = &wd_ops;
323 wdt->wdd.parent = dev; 386 wdt->wdd.parent = dev;
@@ -332,9 +395,106 @@ static int mei_wdt_register(struct mei_wdt *wdt)
332 watchdog_set_drvdata(&wdt->wdd, NULL); 395 watchdog_set_drvdata(&wdt->wdd, NULL);
333 } 396 }
334 397
398 wdt->state = MEI_WDT_IDLE;
399
400out:
401 mutex_unlock(&wdt->reg_lock);
335 return ret; 402 return ret;
336} 403}
337 404
405static void mei_wdt_unregister_work(struct work_struct *work)
406{
407 struct mei_wdt *wdt = container_of(work, struct mei_wdt, unregister);
408
409 mei_wdt_unregister(wdt);
410}
411
412/**
413 * mei_wdt_event_rx - callback for data receive
414 *
415 * @cldev: bus device
416 */
417static void mei_wdt_event_rx(struct mei_cl_device *cldev)
418{
419 struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
420 struct mei_wdt_start_response res;
421 const size_t res_len = sizeof(res);
422 int ret;
423
424 ret = mei_cldev_recv(wdt->cldev, (u8 *)&res, res_len);
425 if (ret < 0) {
426 dev_err(&cldev->dev, "failure in recv %d\n", ret);
427 return;
428 }
429
430 /* Empty response can be sent on stop */
431 if (ret == 0)
432 return;
433
434 if (ret < sizeof(struct mei_mc_hdr)) {
435 dev_err(&cldev->dev, "recv small data %d\n", ret);
436 return;
437 }
438
439 if (res.hdr.command != MEI_MANAGEMENT_CONTROL ||
440 res.hdr.versionnumber != MEI_MC_VERSION_NUMBER) {
441 dev_err(&cldev->dev, "wrong command received\n");
442 return;
443 }
444
445 if (res.hdr.subcommand != MEI_MC_START_WD_TIMER_RES) {
446 dev_warn(&cldev->dev, "unsupported command %d :%s[%d]\n",
447 res.hdr.subcommand,
448 mei_wdt_state_str(wdt->state),
449 wdt->state);
450 return;
451 }
452
453 /* Run the unregistration in a worker as this can be
454 * run only after ping completion, otherwise the flow will
455 * deadlock on watchdog core mutex.
456 */
457 if (wdt->state == MEI_WDT_RUNNING) {
458 if (res.wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED) {
459 wdt->state = MEI_WDT_NOT_REQUIRED;
460 schedule_work(&wdt->unregister);
461 }
462 goto out;
463 }
464
465 if (wdt->state == MEI_WDT_PROBE) {
466 if (res.wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED) {
467 wdt->state = MEI_WDT_NOT_REQUIRED;
468 } else {
469 /* stop the watchdog and register watchdog device */
470 mei_wdt_stop(wdt);
471 mei_wdt_register(wdt);
472 }
473 return;
474 }
475
476 dev_warn(&cldev->dev, "not in correct state %s[%d]\n",
477 mei_wdt_state_str(wdt->state), wdt->state);
478
479out:
480 if (!completion_done(&wdt->response))
481 complete(&wdt->response);
482}
483
484/**
485 * mei_wdt_event - callback for event receive
486 *
487 * @cldev: bus device
488 * @events: event mask
489 * @context: callback context
490 */
491static void mei_wdt_event(struct mei_cl_device *cldev,
492 u32 events, void *context)
493{
494 if (events & BIT(MEI_CL_EVENT_RX))
495 mei_wdt_event_rx(cldev);
496}
497
338#if IS_ENABLED(CONFIG_DEBUG_FS) 498#if IS_ENABLED(CONFIG_DEBUG_FS)
339 499
340static ssize_t mei_dbgfs_read_state(struct file *file, char __user *ubuf, 500static ssize_t mei_dbgfs_read_state(struct file *file, char __user *ubuf,
@@ -403,8 +563,13 @@ static int mei_wdt_probe(struct mei_cl_device *cldev,
403 return -ENOMEM; 563 return -ENOMEM;
404 564
405 wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT; 565 wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT;
406 wdt->state = MEI_WDT_IDLE; 566 wdt->state = MEI_WDT_PROBE;
407 wdt->cldev = cldev; 567 wdt->cldev = cldev;
568 wdt->resp_required = mei_cldev_ver(cldev) > 0x1;
569 mutex_init(&wdt->reg_lock);
570 init_completion(&wdt->response);
571 INIT_WORK(&wdt->unregister, mei_wdt_unregister_work);
572
408 mei_cldev_set_drvdata(cldev, wdt); 573 mei_cldev_set_drvdata(cldev, wdt);
409 574
410 ret = mei_cldev_enable(cldev); 575 ret = mei_cldev_enable(cldev);
@@ -413,9 +578,20 @@ static int mei_wdt_probe(struct mei_cl_device *cldev,
413 goto err_out; 578 goto err_out;
414 } 579 }
415 580
581 ret = mei_cldev_register_event_cb(wdt->cldev, BIT(MEI_CL_EVENT_RX),
582 mei_wdt_event, NULL);
583 if (ret) {
584 dev_err(&cldev->dev, "Could not register event ret=%d\n", ret);
585 goto err_disable;
586 }
587
416 wd_info.firmware_version = mei_cldev_ver(cldev); 588 wd_info.firmware_version = mei_cldev_ver(cldev);
417 589
418 ret = mei_wdt_register(wdt); 590 if (wdt->resp_required)
591 ret = mei_wdt_ping(wdt);
592 else
593 ret = mei_wdt_register(wdt);
594
419 if (ret) 595 if (ret)
420 goto err_disable; 596 goto err_disable;
421 597
@@ -437,6 +613,12 @@ static int mei_wdt_remove(struct mei_cl_device *cldev)
437{ 613{
438 struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); 614 struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
439 615
616 /* Free the caller in case of fw initiated or unexpected reset */
617 if (!completion_done(&wdt->response))
618 complete(&wdt->response);
619
620 cancel_work_sync(&wdt->unregister);
621
440 mei_wdt_unregister(wdt); 622 mei_wdt_unregister(wdt);
441 623
442 mei_cldev_disable(cldev); 624 mei_cldev_disable(cldev);
@@ -452,7 +634,7 @@ static int mei_wdt_remove(struct mei_cl_device *cldev)
452 0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB) 634 0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB)
453 635
454static struct mei_cl_device_id mei_wdt_tbl[] = { 636static struct mei_cl_device_id mei_wdt_tbl[] = {
455 { .uuid = MEI_UUID_WD, .version = 0x1}, 637 { .uuid = MEI_UUID_WD, .version = MEI_CL_VERSION_ANY },
456 /* required last entry */ 638 /* required last entry */
457 { } 639 { }
458}; 640};