|
@@ -16,6 +16,7 @@
|
|
#include <linux/slab.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/debugfs.h>
|
|
|
|
+#include <linux/completion.h>
|
|
#include <linux/watchdog.h>
|
|
#include <linux/watchdog.h>
|
|
|
|
|
|
#include <linux/uuid.h>
|
|
#include <linux/uuid.h>
|
|
@@ -38,27 +39,35 @@
|
|
|
|
|
|
/* Sub Commands */
|
|
/* Sub Commands */
|
|
#define MEI_MC_START_WD_TIMER_REQ 0x13
|
|
#define MEI_MC_START_WD_TIMER_REQ 0x13
|
|
|
|
+#define MEI_MC_START_WD_TIMER_RES 0x83
|
|
|
|
+#define MEI_WDT_STATUS_SUCCESS 0
|
|
|
|
+#define MEI_WDT_WDSTATE_NOT_REQUIRED 0x1
|
|
#define MEI_MC_STOP_WD_TIMER_REQ 0x14
|
|
#define MEI_MC_STOP_WD_TIMER_REQ 0x14
|
|
|
|
|
|
/**
|
|
/**
|
|
* enum mei_wdt_state - internal watchdog state
|
|
* enum mei_wdt_state - internal watchdog state
|
|
*
|
|
*
|
|
|
|
+ * @MEI_WDT_PROBE: wd in probing stage
|
|
* @MEI_WDT_IDLE: wd is idle and not opened
|
|
* @MEI_WDT_IDLE: wd is idle and not opened
|
|
* @MEI_WDT_START: wd was opened, start was called
|
|
* @MEI_WDT_START: wd was opened, start was called
|
|
* @MEI_WDT_RUNNING: wd is expecting keep alive pings
|
|
* @MEI_WDT_RUNNING: wd is expecting keep alive pings
|
|
* @MEI_WDT_STOPPING: wd is stopping and will move to IDLE
|
|
* @MEI_WDT_STOPPING: wd is stopping and will move to IDLE
|
|
|
|
+ * @MEI_WDT_NOT_REQUIRED: wd device is not required
|
|
*/
|
|
*/
|
|
enum mei_wdt_state {
|
|
enum mei_wdt_state {
|
|
|
|
+ MEI_WDT_PROBE,
|
|
MEI_WDT_IDLE,
|
|
MEI_WDT_IDLE,
|
|
MEI_WDT_START,
|
|
MEI_WDT_START,
|
|
MEI_WDT_RUNNING,
|
|
MEI_WDT_RUNNING,
|
|
MEI_WDT_STOPPING,
|
|
MEI_WDT_STOPPING,
|
|
|
|
+ MEI_WDT_NOT_REQUIRED,
|
|
};
|
|
};
|
|
|
|
|
|
-#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
|
|
static const char *mei_wdt_state_str(enum mei_wdt_state state)
|
|
static const char *mei_wdt_state_str(enum mei_wdt_state state)
|
|
{
|
|
{
|
|
switch (state) {
|
|
switch (state) {
|
|
|
|
+ case MEI_WDT_PROBE:
|
|
|
|
+ return "PROBE";
|
|
case MEI_WDT_IDLE:
|
|
case MEI_WDT_IDLE:
|
|
return "IDLE";
|
|
return "IDLE";
|
|
case MEI_WDT_START:
|
|
case MEI_WDT_START:
|
|
@@ -67,11 +76,12 @@ static const char *mei_wdt_state_str(enum mei_wdt_state state)
|
|
return "RUNNING";
|
|
return "RUNNING";
|
|
case MEI_WDT_STOPPING:
|
|
case MEI_WDT_STOPPING:
|
|
return "STOPPING";
|
|
return "STOPPING";
|
|
|
|
+ case MEI_WDT_NOT_REQUIRED:
|
|
|
|
+ return "NOT_REQUIRED";
|
|
default:
|
|
default:
|
|
return "unknown";
|
|
return "unknown";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-#endif /* CONFIG_DEBUG_FS */
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
* struct mei_wdt - mei watchdog driver
|
|
* struct mei_wdt - mei watchdog driver
|
|
@@ -79,6 +89,10 @@ static const char *mei_wdt_state_str(enum mei_wdt_state state)
|
|
*
|
|
*
|
|
* @cldev: mei watchdog client device
|
|
* @cldev: mei watchdog client device
|
|
* @state: watchdog internal state
|
|
* @state: watchdog internal state
|
|
|
|
+ * @resp_required: ping required response
|
|
|
|
+ * @response: ping response completion
|
|
|
|
+ * @unregister: unregister worker
|
|
|
|
+ * @reg_lock: watchdog device registration lock
|
|
* @timeout: watchdog current timeout
|
|
* @timeout: watchdog current timeout
|
|
*
|
|
*
|
|
* @dbgfs_dir: debugfs dir entry
|
|
* @dbgfs_dir: debugfs dir entry
|
|
@@ -88,6 +102,10 @@ struct mei_wdt {
|
|
|
|
|
|
struct mei_cl_device *cldev;
|
|
struct mei_cl_device *cldev;
|
|
enum mei_wdt_state state;
|
|
enum mei_wdt_state state;
|
|
|
|
+ bool resp_required;
|
|
|
|
+ struct completion response;
|
|
|
|
+ struct work_struct unregister;
|
|
|
|
+ struct mutex reg_lock;
|
|
u16 timeout;
|
|
u16 timeout;
|
|
|
|
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
@@ -123,6 +141,19 @@ struct mei_wdt_start_request {
|
|
u8 reserved[17];
|
|
u8 reserved[17];
|
|
} __packed;
|
|
} __packed;
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * struct mei_wdt_start_response watchdog start/ping response
|
|
|
|
+ *
|
|
|
|
+ * @hdr: Management Control Command Header
|
|
|
|
+ * @status: operation status
|
|
|
|
+ * @wdstate: watchdog status bit mask
|
|
|
|
+ */
|
|
|
|
+struct mei_wdt_start_response {
|
|
|
|
+ struct mei_mc_hdr hdr;
|
|
|
|
+ u8 status;
|
|
|
|
+ u8 wdstate;
|
|
|
|
+} __packed;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* struct mei_wdt_stop_request - watchdog stop
|
|
* struct mei_wdt_stop_request - watchdog stop
|
|
*
|
|
*
|
|
@@ -244,13 +275,18 @@ static int mei_wdt_ops_ping(struct watchdog_device *wdd)
|
|
if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING)
|
|
if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
|
|
+ if (wdt->resp_required)
|
|
|
|
+ init_completion(&wdt->response);
|
|
|
|
+
|
|
|
|
+ wdt->state = MEI_WDT_RUNNING;
|
|
ret = mei_wdt_ping(wdt);
|
|
ret = mei_wdt_ping(wdt);
|
|
if (ret)
|
|
if (ret)
|
|
return ret;
|
|
return ret;
|
|
|
|
|
|
- wdt->state = MEI_WDT_RUNNING;
|
|
|
|
|
|
+ if (wdt->resp_required)
|
|
|
|
+ ret = wait_for_completion_killable(&wdt->response);
|
|
|
|
|
|
- return 0;
|
|
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -290,6 +326,19 @@ static struct watchdog_info wd_info = {
|
|
WDIOF_ALARMONLY,
|
|
WDIOF_ALARMONLY,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * __mei_wdt_is_registered - check if wdt is registered
|
|
|
|
+ *
|
|
|
|
+ * @wdt: mei watchdog device
|
|
|
|
+ *
|
|
|
|
+ * Return: true if the wdt is registered with the watchdog subsystem
|
|
|
|
+ * Locking: should be called under wdt->reg_lock
|
|
|
|
+ */
|
|
|
|
+static inline bool __mei_wdt_is_registered(struct mei_wdt *wdt)
|
|
|
|
+{
|
|
|
|
+ return !!watchdog_get_drvdata(&wdt->wdd);
|
|
|
|
+}
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* mei_wdt_unregister - unregister from the watchdog subsystem
|
|
* mei_wdt_unregister - unregister from the watchdog subsystem
|
|
*
|
|
*
|
|
@@ -297,8 +346,15 @@ static struct watchdog_info wd_info = {
|
|
*/
|
|
*/
|
|
static void mei_wdt_unregister(struct mei_wdt *wdt)
|
|
static void mei_wdt_unregister(struct mei_wdt *wdt)
|
|
{
|
|
{
|
|
- watchdog_unregister_device(&wdt->wdd);
|
|
|
|
- watchdog_set_drvdata(&wdt->wdd, NULL);
|
|
|
|
|
|
+ mutex_lock(&wdt->reg_lock);
|
|
|
|
+
|
|
|
|
+ if (__mei_wdt_is_registered(wdt)) {
|
|
|
|
+ watchdog_unregister_device(&wdt->wdd);
|
|
|
|
+ watchdog_set_drvdata(&wdt->wdd, NULL);
|
|
|
|
+ memset(&wdt->wdd, 0, sizeof(wdt->wdd));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mutex_unlock(&wdt->reg_lock);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -318,6 +374,13 @@ static int mei_wdt_register(struct mei_wdt *wdt)
|
|
|
|
|
|
dev = &wdt->cldev->dev;
|
|
dev = &wdt->cldev->dev;
|
|
|
|
|
|
|
|
+ mutex_lock(&wdt->reg_lock);
|
|
|
|
+
|
|
|
|
+ if (__mei_wdt_is_registered(wdt)) {
|
|
|
|
+ ret = 0;
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
wdt->wdd.info = &wd_info;
|
|
wdt->wdd.info = &wd_info;
|
|
wdt->wdd.ops = &wd_ops;
|
|
wdt->wdd.ops = &wd_ops;
|
|
wdt->wdd.parent = dev;
|
|
wdt->wdd.parent = dev;
|
|
@@ -332,9 +395,106 @@ static int mei_wdt_register(struct mei_wdt *wdt)
|
|
watchdog_set_drvdata(&wdt->wdd, NULL);
|
|
watchdog_set_drvdata(&wdt->wdd, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ wdt->state = MEI_WDT_IDLE;
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ mutex_unlock(&wdt->reg_lock);
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void mei_wdt_unregister_work(struct work_struct *work)
|
|
|
|
+{
|
|
|
|
+ struct mei_wdt *wdt = container_of(work, struct mei_wdt, unregister);
|
|
|
|
+
|
|
|
|
+ mei_wdt_unregister(wdt);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * mei_wdt_event_rx - callback for data receive
|
|
|
|
+ *
|
|
|
|
+ * @cldev: bus device
|
|
|
|
+ */
|
|
|
|
+static void mei_wdt_event_rx(struct mei_cl_device *cldev)
|
|
|
|
+{
|
|
|
|
+ struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
|
|
|
|
+ struct mei_wdt_start_response res;
|
|
|
|
+ const size_t res_len = sizeof(res);
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ ret = mei_cldev_recv(wdt->cldev, (u8 *)&res, res_len);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ dev_err(&cldev->dev, "failure in recv %d\n", ret);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Empty response can be sent on stop */
|
|
|
|
+ if (ret == 0)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ if (ret < sizeof(struct mei_mc_hdr)) {
|
|
|
|
+ dev_err(&cldev->dev, "recv small data %d\n", ret);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (res.hdr.command != MEI_MANAGEMENT_CONTROL ||
|
|
|
|
+ res.hdr.versionnumber != MEI_MC_VERSION_NUMBER) {
|
|
|
|
+ dev_err(&cldev->dev, "wrong command received\n");
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (res.hdr.subcommand != MEI_MC_START_WD_TIMER_RES) {
|
|
|
|
+ dev_warn(&cldev->dev, "unsupported command %d :%s[%d]\n",
|
|
|
|
+ res.hdr.subcommand,
|
|
|
|
+ mei_wdt_state_str(wdt->state),
|
|
|
|
+ wdt->state);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Run the unregistration in a worker as this can be
|
|
|
|
+ * run only after ping completion, otherwise the flow will
|
|
|
|
+ * deadlock on watchdog core mutex.
|
|
|
|
+ */
|
|
|
|
+ if (wdt->state == MEI_WDT_RUNNING) {
|
|
|
|
+ if (res.wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED) {
|
|
|
|
+ wdt->state = MEI_WDT_NOT_REQUIRED;
|
|
|
|
+ schedule_work(&wdt->unregister);
|
|
|
|
+ }
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (wdt->state == MEI_WDT_PROBE) {
|
|
|
|
+ if (res.wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED) {
|
|
|
|
+ wdt->state = MEI_WDT_NOT_REQUIRED;
|
|
|
|
+ } else {
|
|
|
|
+ /* stop the watchdog and register watchdog device */
|
|
|
|
+ mei_wdt_stop(wdt);
|
|
|
|
+ mei_wdt_register(wdt);
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dev_warn(&cldev->dev, "not in correct state %s[%d]\n",
|
|
|
|
+ mei_wdt_state_str(wdt->state), wdt->state);
|
|
|
|
+
|
|
|
|
+out:
|
|
|
|
+ if (!completion_done(&wdt->response))
|
|
|
|
+ complete(&wdt->response);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * mei_wdt_event - callback for event receive
|
|
|
|
+ *
|
|
|
|
+ * @cldev: bus device
|
|
|
|
+ * @events: event mask
|
|
|
|
+ * @context: callback context
|
|
|
|
+ */
|
|
|
|
+static void mei_wdt_event(struct mei_cl_device *cldev,
|
|
|
|
+ u32 events, void *context)
|
|
|
|
+{
|
|
|
|
+ if (events & BIT(MEI_CL_EVENT_RX))
|
|
|
|
+ mei_wdt_event_rx(cldev);
|
|
|
|
+}
|
|
|
|
+
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
|
|
|
|
static ssize_t mei_dbgfs_read_state(struct file *file, char __user *ubuf,
|
|
static 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,
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT;
|
|
wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT;
|
|
- wdt->state = MEI_WDT_IDLE;
|
|
|
|
|
|
+ wdt->state = MEI_WDT_PROBE;
|
|
wdt->cldev = cldev;
|
|
wdt->cldev = cldev;
|
|
|
|
+ wdt->resp_required = mei_cldev_ver(cldev) > 0x1;
|
|
|
|
+ mutex_init(&wdt->reg_lock);
|
|
|
|
+ init_completion(&wdt->response);
|
|
|
|
+ INIT_WORK(&wdt->unregister, mei_wdt_unregister_work);
|
|
|
|
+
|
|
mei_cldev_set_drvdata(cldev, wdt);
|
|
mei_cldev_set_drvdata(cldev, wdt);
|
|
|
|
|
|
ret = mei_cldev_enable(cldev);
|
|
ret = mei_cldev_enable(cldev);
|
|
@@ -413,9 +578,20 @@ static int mei_wdt_probe(struct mei_cl_device *cldev,
|
|
goto err_out;
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ ret = mei_cldev_register_event_cb(wdt->cldev, BIT(MEI_CL_EVENT_RX),
|
|
|
|
+ mei_wdt_event, NULL);
|
|
|
|
+ if (ret) {
|
|
|
|
+ dev_err(&cldev->dev, "Could not register event ret=%d\n", ret);
|
|
|
|
+ goto err_disable;
|
|
|
|
+ }
|
|
|
|
+
|
|
wd_info.firmware_version = mei_cldev_ver(cldev);
|
|
wd_info.firmware_version = mei_cldev_ver(cldev);
|
|
|
|
|
|
- ret = mei_wdt_register(wdt);
|
|
|
|
|
|
+ if (wdt->resp_required)
|
|
|
|
+ ret = mei_wdt_ping(wdt);
|
|
|
|
+ else
|
|
|
|
+ ret = mei_wdt_register(wdt);
|
|
|
|
+
|
|
if (ret)
|
|
if (ret)
|
|
goto err_disable;
|
|
goto err_disable;
|
|
|
|
|
|
@@ -437,6 +613,12 @@ static int mei_wdt_remove(struct mei_cl_device *cldev)
|
|
{
|
|
{
|
|
struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
|
|
struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
|
|
|
|
|
|
|
|
+ /* Free the caller in case of fw initiated or unexpected reset */
|
|
|
|
+ if (!completion_done(&wdt->response))
|
|
|
|
+ complete(&wdt->response);
|
|
|
|
+
|
|
|
|
+ cancel_work_sync(&wdt->unregister);
|
|
|
|
+
|
|
mei_wdt_unregister(wdt);
|
|
mei_wdt_unregister(wdt);
|
|
|
|
|
|
mei_cldev_disable(cldev);
|
|
mei_cldev_disable(cldev);
|
|
@@ -452,7 +634,7 @@ static int mei_wdt_remove(struct mei_cl_device *cldev)
|
|
0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB)
|
|
0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB)
|
|
|
|
|
|
static struct mei_cl_device_id mei_wdt_tbl[] = {
|
|
static struct mei_cl_device_id mei_wdt_tbl[] = {
|
|
- { .uuid = MEI_UUID_WD, .version = 0x1},
|
|
|
|
|
|
+ { .uuid = MEI_UUID_WD, .version = MEI_CL_VERSION_ANY },
|
|
/* required last entry */
|
|
/* required last entry */
|
|
{ }
|
|
{ }
|
|
};
|
|
};
|