watchdog: mei_wdt: implement MEI iAMT watchdog driver
authorTomas Winkler <tomas.winkler@intel.com>
Thu, 7 Jan 2016 22:49:22 +0000 (00:49 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 7 Feb 2016 06:11:06 +0000 (22:11 -0800)
Create a driver with the generic watchdog interface
for the MEI iAMT watchdog device.

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>
Documentation/misc-devices/mei/mei.txt
MAINTAINERS
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/mei_wdt.c [new file with mode: 0644]

index 91c1fa34f48b4a5a662bb14b2d422437795fd474..2b80a0cd621f09554fb74f2dc100787f76a6176b 100644 (file)
@@ -231,15 +231,15 @@ IT knows when a platform crashes even when there is a hard failure on the host.
 The Intel AMT Watchdog is composed of two parts:
        1) Firmware feature - receives the heartbeats
           and sends an event when the heartbeats stop.
-       2) Intel MEI driver - connects to the watchdog feature, configures the
-          watchdog and sends the heartbeats.
+       2) Intel MEI iAMT watchdog driver - connects to the watchdog feature,
+          configures the watchdog and sends the heartbeats.
 
-The Intel MEI driver uses the kernel watchdog API to configure the Intel AMT
-Watchdog and to send heartbeats to it. The default timeout of the
+The Intel iAMT watchdog MEI driver uses the kernel watchdog API to configure
+the Intel AMT Watchdog and to send heartbeats to it. The default timeout of the
 watchdog is 120 seconds.
 
-If the Intel AMT Watchdog feature does not exist (i.e. the connection failed),
-the Intel MEI driver will disable the sending of heartbeats.
+If the Intel AMT is not enabled in the firmware then the watchdog client won't enumerate
+on the me client bus and watchdog devices won't be exposed.
 
 
 Supported Chipsets
index 30aca4aa5467b53d6a171a27b57a4357fa09127b..d63b3c773c7da42dcc4d1a16ac483873299eb879 100644 (file)
@@ -5751,6 +5751,7 @@ S:        Supported
 F:     include/uapi/linux/mei.h
 F:     include/linux/mei_cl_bus.h
 F:     drivers/misc/mei/*
+F:     drivers/watchdog/mei_wdt.c
 F:     Documentation/misc-devices/mei/*
 
 INTEL MIC DRIVERS (mic)
index 4f0e7be0da346c90d8559d9653bd41927f722b22..57f872122b16bee37aec41d27ffbd688b968877d 100644 (file)
@@ -1212,6 +1212,21 @@ config SBC_EPX_C3_WATCHDOG
          To compile this driver as a module, choose M here: the
          module will be called sbc_epx_c3.
 
+config INTEL_MEI_WDT
+       tristate "Intel MEI iAMT Watchdog"
+       depends on INTEL_MEI && X86
+       select WATCHDOG_CORE
+       ---help---
+         A device driver for the Intel MEI iAMT watchdog.
+
+         The Intel AMT Watchdog is an OS Health (Hang/Crash) watchdog.
+         Whenever the OS hangs or crashes, iAMT will send an event
+         to any subscriber to this event. The watchdog doesn't reset the
+         the platform.
+
+         To compile this driver as a module, choose M here:
+         the module will be called mei_wdt.
+
 # M32R Architecture
 
 # M68K Architecture
index f566753256abbbb74535337d3f9a9cc0442fd78e..efc4f788e0f29cb1e5c69f488a0717728e16438f 100644 (file)
@@ -126,6 +126,7 @@ obj-$(CONFIG_MACHZ_WDT) += machzwd.o
 obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o
 obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o
 obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o
+obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o
 
 # M32R Architecture
 
diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c
new file mode 100644 (file)
index 0000000..32e3e1d
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+ * Intel Management Engine Interface (Intel MEI) Linux driver
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/watchdog.h>
+
+#include <linux/uuid.h>
+#include <linux/mei_cl_bus.h>
+
+/*
+ * iAMT Watchdog Device
+ */
+#define INTEL_AMT_WATCHDOG_ID "iamt_wdt"
+
+#define MEI_WDT_DEFAULT_TIMEOUT   120  /* seconds */
+#define MEI_WDT_MIN_TIMEOUT       120  /* seconds */
+#define MEI_WDT_MAX_TIMEOUT     65535  /* seconds */
+
+/* Commands */
+#define MEI_MANAGEMENT_CONTROL 0x02
+
+/* MEI Management Control version number */
+#define MEI_MC_VERSION_NUMBER  0x10
+
+/* Sub Commands */
+#define MEI_MC_START_WD_TIMER_REQ  0x13
+#define MEI_MC_STOP_WD_TIMER_REQ   0x14
+
+/**
+ * enum mei_wdt_state - internal watchdog state
+ *
+ * @MEI_WDT_IDLE: wd is idle and not opened
+ * @MEI_WDT_START: wd was opened, start was called
+ * @MEI_WDT_RUNNING: wd is expecting keep alive pings
+ * @MEI_WDT_STOPPING: wd is stopping and will move to IDLE
+ */
+enum mei_wdt_state {
+       MEI_WDT_IDLE,
+       MEI_WDT_START,
+       MEI_WDT_RUNNING,
+       MEI_WDT_STOPPING,
+};
+
+/**
+ * struct mei_wdt - mei watchdog driver
+ * @wdd: watchdog device
+ *
+ * @cldev: mei watchdog client device
+ * @state: watchdog internal state
+ * @timeout: watchdog current timeout
+ */
+struct mei_wdt {
+       struct watchdog_device wdd;
+
+       struct mei_cl_device *cldev;
+       enum mei_wdt_state state;
+       u16 timeout;
+};
+
+/*
+ * struct mei_mc_hdr - Management Control Command Header
+ *
+ * @command: Management Control (0x2)
+ * @bytecount: Number of bytes in the message beyond this byte
+ * @subcommand: Management Control Subcommand
+ * @versionnumber: Management Control Version (0x10)
+ */
+struct mei_mc_hdr {
+       u8 command;
+       u8 bytecount;
+       u8 subcommand;
+       u8 versionnumber;
+};
+
+/**
+ * struct mei_wdt_start_request watchdog start/ping
+ *
+ * @hdr: Management Control Command Header
+ * @timeout: timeout value
+ * @reserved: reserved (legacy)
+ */
+struct mei_wdt_start_request {
+       struct mei_mc_hdr hdr;
+       u16 timeout;
+       u8 reserved[17];
+} __packed;
+
+/**
+ * struct mei_wdt_stop_request - watchdog stop
+ *
+ * @hdr: Management Control Command Header
+ */
+struct mei_wdt_stop_request {
+       struct mei_mc_hdr hdr;
+} __packed;
+
+/**
+ * mei_wdt_ping - send wd start/ping command
+ *
+ * @wdt: mei watchdog device
+ *
+ * Return: 0 on success,
+ *         negative errno code on failure
+ */
+static int mei_wdt_ping(struct mei_wdt *wdt)
+{
+       struct mei_wdt_start_request req;
+       const size_t req_len = sizeof(req);
+       int ret;
+
+       memset(&req, 0, req_len);
+       req.hdr.command = MEI_MANAGEMENT_CONTROL;
+       req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand);
+       req.hdr.subcommand = MEI_MC_START_WD_TIMER_REQ;
+       req.hdr.versionnumber = MEI_MC_VERSION_NUMBER;
+       req.timeout = wdt->timeout;
+
+       ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+/**
+ * mei_wdt_stop - send wd stop command
+ *
+ * @wdt: mei watchdog device
+ *
+ * Return: 0 on success,
+ *         negative errno code on failure
+ */
+static int mei_wdt_stop(struct mei_wdt *wdt)
+{
+       struct mei_wdt_stop_request req;
+       const size_t req_len = sizeof(req);
+       int ret;
+
+       memset(&req, 0, req_len);
+       req.hdr.command = MEI_MANAGEMENT_CONTROL;
+       req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand);
+       req.hdr.subcommand = MEI_MC_STOP_WD_TIMER_REQ;
+       req.hdr.versionnumber = MEI_MC_VERSION_NUMBER;
+
+       ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+/**
+ * mei_wdt_ops_start - wd start command from the watchdog core.
+ *
+ * @wdd: watchdog device
+ *
+ * Return: 0 on success or -ENODEV;
+ */
+static int mei_wdt_ops_start(struct watchdog_device *wdd)
+{
+       struct mei_wdt *wdt = watchdog_get_drvdata(wdd);
+
+       wdt->state = MEI_WDT_START;
+       wdd->timeout = wdt->timeout;
+       return 0;
+}
+
+/**
+ * mei_wdt_ops_stop - wd stop command from the watchdog core.
+ *
+ * @wdd: watchdog device
+ *
+ * Return: 0 if success, negative errno code for failure
+ */
+static int mei_wdt_ops_stop(struct watchdog_device *wdd)
+{
+       struct mei_wdt *wdt = watchdog_get_drvdata(wdd);
+       int ret;
+
+       if (wdt->state != MEI_WDT_RUNNING)
+               return 0;
+
+       wdt->state = MEI_WDT_STOPPING;
+
+       ret = mei_wdt_stop(wdt);
+       if (ret)
+               return ret;
+
+       wdt->state = MEI_WDT_IDLE;
+
+       return 0;
+}
+
+/**
+ * mei_wdt_ops_ping - wd ping command from the watchdog core.
+ *
+ * @wdd: watchdog device
+ *
+ * Return: 0 if success, negative errno code on failure
+ */
+static int mei_wdt_ops_ping(struct watchdog_device *wdd)
+{
+       struct mei_wdt *wdt = watchdog_get_drvdata(wdd);
+       int ret;
+
+       if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING)
+               return 0;
+
+       ret = mei_wdt_ping(wdt);
+       if (ret)
+               return ret;
+
+       wdt->state = MEI_WDT_RUNNING;
+
+       return 0;
+}
+
+/**
+ * mei_wdt_ops_set_timeout - wd set timeout command from the watchdog core.
+ *
+ * @wdd: watchdog device
+ * @timeout: timeout value to set
+ *
+ * Return: 0 if success, negative errno code for failure
+ */
+static int mei_wdt_ops_set_timeout(struct watchdog_device *wdd,
+                                  unsigned int timeout)
+{
+
+       struct mei_wdt *wdt = watchdog_get_drvdata(wdd);
+
+       /* valid value is already checked by the caller */
+       wdt->timeout = timeout;
+       wdd->timeout = timeout;
+
+       return 0;
+}
+
+static const struct watchdog_ops wd_ops = {
+       .owner       = THIS_MODULE,
+       .start       = mei_wdt_ops_start,
+       .stop        = mei_wdt_ops_stop,
+       .ping        = mei_wdt_ops_ping,
+       .set_timeout = mei_wdt_ops_set_timeout,
+};
+
+/* not const as the firmware_version field need to be retrieved */
+static struct watchdog_info wd_info = {
+       .identity = INTEL_AMT_WATCHDOG_ID,
+       .options  = WDIOF_KEEPALIVEPING |
+                   WDIOF_SETTIMEOUT |
+                   WDIOF_ALARMONLY,
+};
+
+/**
+ * mei_wdt_unregister - unregister from the watchdog subsystem
+ *
+ * @wdt: mei watchdog device
+ */
+static void mei_wdt_unregister(struct mei_wdt *wdt)
+{
+       watchdog_unregister_device(&wdt->wdd);
+       watchdog_set_drvdata(&wdt->wdd, NULL);
+}
+
+/**
+ * mei_wdt_register - register with the watchdog subsystem
+ *
+ * @wdt: mei watchdog device
+ *
+ * Return: 0 if success, negative errno code for failure
+ */
+static int mei_wdt_register(struct mei_wdt *wdt)
+{
+       struct device *dev;
+       int ret;
+
+       if (!wdt || !wdt->cldev)
+               return -EINVAL;
+
+       dev = &wdt->cldev->dev;
+
+       wdt->wdd.info = &wd_info;
+       wdt->wdd.ops = &wd_ops;
+       wdt->wdd.parent = dev;
+       wdt->wdd.timeout = MEI_WDT_DEFAULT_TIMEOUT;
+       wdt->wdd.min_timeout = MEI_WDT_MIN_TIMEOUT;
+       wdt->wdd.max_timeout = MEI_WDT_MAX_TIMEOUT;
+
+       watchdog_set_drvdata(&wdt->wdd, wdt);
+       ret = watchdog_register_device(&wdt->wdd);
+       if (ret) {
+               dev_err(dev, "unable to register watchdog device = %d.\n", ret);
+               watchdog_set_drvdata(&wdt->wdd, NULL);
+       }
+
+       return ret;
+}
+
+static int mei_wdt_probe(struct mei_cl_device *cldev,
+                        const struct mei_cl_device_id *id)
+{
+       struct mei_wdt *wdt;
+       int ret;
+
+       wdt = kzalloc(sizeof(struct mei_wdt), GFP_KERNEL);
+       if (!wdt)
+               return -ENOMEM;
+
+       wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT;
+       wdt->state = MEI_WDT_IDLE;
+       wdt->cldev = cldev;
+       mei_cldev_set_drvdata(cldev, wdt);
+
+       ret = mei_cldev_enable(cldev);
+       if (ret < 0) {
+               dev_err(&cldev->dev, "Could not enable cl device\n");
+               goto err_out;
+       }
+
+       wd_info.firmware_version = mei_cldev_ver(cldev);
+
+       ret = mei_wdt_register(wdt);
+       if (ret)
+               goto err_disable;
+
+       return 0;
+
+err_disable:
+       mei_cldev_disable(cldev);
+
+err_out:
+       kfree(wdt);
+
+       return ret;
+}
+
+static int mei_wdt_remove(struct mei_cl_device *cldev)
+{
+       struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
+
+       mei_wdt_unregister(wdt);
+
+       mei_cldev_disable(cldev);
+
+       kfree(wdt);
+
+       return 0;
+}
+
+#define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \
+                           0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB)
+
+static struct mei_cl_device_id mei_wdt_tbl[] = {
+       { .uuid = MEI_UUID_WD, .version = 0x1},
+       /* required last entry */
+       { }
+};
+MODULE_DEVICE_TABLE(mei, mei_wdt_tbl);
+
+static struct mei_cl_driver mei_wdt_driver = {
+       .id_table = mei_wdt_tbl,
+       .name = KBUILD_MODNAME,
+
+       .probe = mei_wdt_probe,
+       .remove = mei_wdt_remove,
+};
+
+static int __init mei_wdt_init(void)
+{
+       int ret;
+
+       ret = mei_cldev_driver_register(&mei_wdt_driver);
+       if (ret) {
+               pr_err(KBUILD_MODNAME ": module registration failed\n");
+               return ret;
+       }
+       return 0;
+}
+
+static void __exit mei_wdt_exit(void)
+{
+       mei_cldev_driver_unregister(&mei_wdt_driver);
+}
+
+module_init(mei_wdt_init);
+module_exit(mei_wdt_exit);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Device driver for Intel MEI iAMT watchdog");