diff options
author | Tomas Winkler <tomas.winkler@intel.com> | 2016-01-07 17:49:22 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-02-07 01:11:06 -0500 |
commit | 222818c3d84c1f3190767f5f09f2b9b9a0e0ca7f (patch) | |
tree | 71812f9021fe350bbec52494a07306410a96e8a2 | |
parent | fdd9b8655933c3eb3154fe1ed351c17b654258bd (diff) |
watchdog: mei_wdt: implement MEI iAMT watchdog driver
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>
-rw-r--r-- | Documentation/misc-devices/mei/mei.txt | 12 | ||||
-rw-r--r-- | MAINTAINERS | 1 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 15 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/mei_wdt.c | 404 |
5 files changed, 427 insertions, 6 deletions
diff --git a/Documentation/misc-devices/mei/mei.txt b/Documentation/misc-devices/mei/mei.txt index 91c1fa34f48b..2b80a0cd621f 100644 --- a/Documentation/misc-devices/mei/mei.txt +++ b/Documentation/misc-devices/mei/mei.txt | |||
@@ -231,15 +231,15 @@ IT knows when a platform crashes even when there is a hard failure on the host. | |||
231 | The Intel AMT Watchdog is composed of two parts: | 231 | The Intel AMT Watchdog is composed of two parts: |
232 | 1) Firmware feature - receives the heartbeats | 232 | 1) Firmware feature - receives the heartbeats |
233 | and sends an event when the heartbeats stop. | 233 | and sends an event when the heartbeats stop. |
234 | 2) Intel MEI driver - connects to the watchdog feature, configures the | 234 | 2) Intel MEI iAMT watchdog driver - connects to the watchdog feature, |
235 | watchdog and sends the heartbeats. | 235 | configures the watchdog and sends the heartbeats. |
236 | 236 | ||
237 | The Intel MEI driver uses the kernel watchdog API to configure the Intel AMT | 237 | The Intel iAMT watchdog MEI driver uses the kernel watchdog API to configure |
238 | Watchdog and to send heartbeats to it. The default timeout of the | 238 | the Intel AMT Watchdog and to send heartbeats to it. The default timeout of the |
239 | watchdog is 120 seconds. | 239 | watchdog is 120 seconds. |
240 | 240 | ||
241 | If the Intel AMT Watchdog feature does not exist (i.e. the connection failed), | 241 | If the Intel AMT is not enabled in the firmware then the watchdog client won't enumerate |
242 | the Intel MEI driver will disable the sending of heartbeats. | 242 | on the me client bus and watchdog devices won't be exposed. |
243 | 243 | ||
244 | 244 | ||
245 | Supported Chipsets | 245 | Supported Chipsets |
diff --git a/MAINTAINERS b/MAINTAINERS index 30aca4aa5467..d63b3c773c7d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -5751,6 +5751,7 @@ S: Supported | |||
5751 | F: include/uapi/linux/mei.h | 5751 | F: include/uapi/linux/mei.h |
5752 | F: include/linux/mei_cl_bus.h | 5752 | F: include/linux/mei_cl_bus.h |
5753 | F: drivers/misc/mei/* | 5753 | F: drivers/misc/mei/* |
5754 | F: drivers/watchdog/mei_wdt.c | ||
5754 | F: Documentation/misc-devices/mei/* | 5755 | F: Documentation/misc-devices/mei/* |
5755 | 5756 | ||
5756 | INTEL MIC DRIVERS (mic) | 5757 | INTEL MIC DRIVERS (mic) |
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 4f0e7be0da34..57f872122b16 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -1212,6 +1212,21 @@ config SBC_EPX_C3_WATCHDOG | |||
1212 | To compile this driver as a module, choose M here: the | 1212 | To compile this driver as a module, choose M here: the |
1213 | module will be called sbc_epx_c3. | 1213 | module will be called sbc_epx_c3. |
1214 | 1214 | ||
1215 | config INTEL_MEI_WDT | ||
1216 | tristate "Intel MEI iAMT Watchdog" | ||
1217 | depends on INTEL_MEI && X86 | ||
1218 | select WATCHDOG_CORE | ||
1219 | ---help--- | ||
1220 | A device driver for the Intel MEI iAMT watchdog. | ||
1221 | |||
1222 | The Intel AMT Watchdog is an OS Health (Hang/Crash) watchdog. | ||
1223 | Whenever the OS hangs or crashes, iAMT will send an event | ||
1224 | to any subscriber to this event. The watchdog doesn't reset the | ||
1225 | the platform. | ||
1226 | |||
1227 | To compile this driver as a module, choose M here: | ||
1228 | the module will be called mei_wdt. | ||
1229 | |||
1215 | # M32R Architecture | 1230 | # M32R Architecture |
1216 | 1231 | ||
1217 | # M68K Architecture | 1232 | # M68K Architecture |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index f566753256ab..efc4f788e0f2 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -126,6 +126,7 @@ obj-$(CONFIG_MACHZ_WDT) += machzwd.o | |||
126 | obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o | 126 | obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o |
127 | obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o | 127 | obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o |
128 | obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o | 128 | obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o |
129 | obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o | ||
129 | 130 | ||
130 | # M32R Architecture | 131 | # M32R Architecture |
131 | 132 | ||
diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c new file mode 100644 index 000000000000..32e3e1d55ef3 --- /dev/null +++ b/drivers/watchdog/mei_wdt.c | |||
@@ -0,0 +1,404 @@ | |||
1 | /* | ||
2 | * Intel Management Engine Interface (Intel MEI) Linux driver | ||
3 | * Copyright (c) 2015, Intel Corporation. | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms and conditions of the GNU General Public License, | ||
7 | * version 2, as published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | */ | ||
14 | |||
15 | #include <linux/module.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/watchdog.h> | ||
19 | |||
20 | #include <linux/uuid.h> | ||
21 | #include <linux/mei_cl_bus.h> | ||
22 | |||
23 | /* | ||
24 | * iAMT Watchdog Device | ||
25 | */ | ||
26 | #define INTEL_AMT_WATCHDOG_ID "iamt_wdt" | ||
27 | |||
28 | #define MEI_WDT_DEFAULT_TIMEOUT 120 /* seconds */ | ||
29 | #define MEI_WDT_MIN_TIMEOUT 120 /* seconds */ | ||
30 | #define MEI_WDT_MAX_TIMEOUT 65535 /* seconds */ | ||
31 | |||
32 | /* Commands */ | ||
33 | #define MEI_MANAGEMENT_CONTROL 0x02 | ||
34 | |||
35 | /* MEI Management Control version number */ | ||
36 | #define MEI_MC_VERSION_NUMBER 0x10 | ||
37 | |||
38 | /* Sub Commands */ | ||
39 | #define MEI_MC_START_WD_TIMER_REQ 0x13 | ||
40 | #define MEI_MC_STOP_WD_TIMER_REQ 0x14 | ||
41 | |||
42 | /** | ||
43 | * enum mei_wdt_state - internal watchdog state | ||
44 | * | ||
45 | * @MEI_WDT_IDLE: wd is idle and not opened | ||
46 | * @MEI_WDT_START: wd was opened, start was called | ||
47 | * @MEI_WDT_RUNNING: wd is expecting keep alive pings | ||
48 | * @MEI_WDT_STOPPING: wd is stopping and will move to IDLE | ||
49 | */ | ||
50 | enum mei_wdt_state { | ||
51 | MEI_WDT_IDLE, | ||
52 | MEI_WDT_START, | ||
53 | MEI_WDT_RUNNING, | ||
54 | MEI_WDT_STOPPING, | ||
55 | }; | ||
56 | |||
57 | /** | ||
58 | * struct mei_wdt - mei watchdog driver | ||
59 | * @wdd: watchdog device | ||
60 | * | ||
61 | * @cldev: mei watchdog client device | ||
62 | * @state: watchdog internal state | ||
63 | * @timeout: watchdog current timeout | ||
64 | */ | ||
65 | struct mei_wdt { | ||
66 | struct watchdog_device wdd; | ||
67 | |||
68 | struct mei_cl_device *cldev; | ||
69 | enum mei_wdt_state state; | ||
70 | u16 timeout; | ||
71 | }; | ||
72 | |||
73 | /* | ||
74 | * struct mei_mc_hdr - Management Control Command Header | ||
75 | * | ||
76 | * @command: Management Control (0x2) | ||
77 | * @bytecount: Number of bytes in the message beyond this byte | ||
78 | * @subcommand: Management Control Subcommand | ||
79 | * @versionnumber: Management Control Version (0x10) | ||
80 | */ | ||
81 | struct mei_mc_hdr { | ||
82 | u8 command; | ||
83 | u8 bytecount; | ||
84 | u8 subcommand; | ||
85 | u8 versionnumber; | ||
86 | }; | ||
87 | |||
88 | /** | ||
89 | * struct mei_wdt_start_request watchdog start/ping | ||
90 | * | ||
91 | * @hdr: Management Control Command Header | ||
92 | * @timeout: timeout value | ||
93 | * @reserved: reserved (legacy) | ||
94 | */ | ||
95 | struct mei_wdt_start_request { | ||
96 | struct mei_mc_hdr hdr; | ||
97 | u16 timeout; | ||
98 | u8 reserved[17]; | ||
99 | } __packed; | ||
100 | |||
101 | /** | ||
102 | * struct mei_wdt_stop_request - watchdog stop | ||
103 | * | ||
104 | * @hdr: Management Control Command Header | ||
105 | */ | ||
106 | struct mei_wdt_stop_request { | ||
107 | struct mei_mc_hdr hdr; | ||
108 | } __packed; | ||
109 | |||
110 | /** | ||
111 | * mei_wdt_ping - send wd start/ping command | ||
112 | * | ||
113 | * @wdt: mei watchdog device | ||
114 | * | ||
115 | * Return: 0 on success, | ||
116 | * negative errno code on failure | ||
117 | */ | ||
118 | static int mei_wdt_ping(struct mei_wdt *wdt) | ||
119 | { | ||
120 | struct mei_wdt_start_request req; | ||
121 | const size_t req_len = sizeof(req); | ||
122 | int ret; | ||
123 | |||
124 | memset(&req, 0, req_len); | ||
125 | req.hdr.command = MEI_MANAGEMENT_CONTROL; | ||
126 | req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand); | ||
127 | req.hdr.subcommand = MEI_MC_START_WD_TIMER_REQ; | ||
128 | req.hdr.versionnumber = MEI_MC_VERSION_NUMBER; | ||
129 | req.timeout = wdt->timeout; | ||
130 | |||
131 | ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len); | ||
132 | if (ret < 0) | ||
133 | return ret; | ||
134 | |||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * mei_wdt_stop - send wd stop command | ||
140 | * | ||
141 | * @wdt: mei watchdog device | ||
142 | * | ||
143 | * Return: 0 on success, | ||
144 | * negative errno code on failure | ||
145 | */ | ||
146 | static int mei_wdt_stop(struct mei_wdt *wdt) | ||
147 | { | ||
148 | struct mei_wdt_stop_request req; | ||
149 | const size_t req_len = sizeof(req); | ||
150 | int ret; | ||
151 | |||
152 | memset(&req, 0, req_len); | ||
153 | req.hdr.command = MEI_MANAGEMENT_CONTROL; | ||
154 | req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand); | ||
155 | req.hdr.subcommand = MEI_MC_STOP_WD_TIMER_REQ; | ||
156 | req.hdr.versionnumber = MEI_MC_VERSION_NUMBER; | ||
157 | |||
158 | ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len); | ||
159 | if (ret < 0) | ||
160 | return ret; | ||
161 | |||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | /** | ||
166 | * mei_wdt_ops_start - wd start command from the watchdog core. | ||
167 | * | ||
168 | * @wdd: watchdog device | ||
169 | * | ||
170 | * Return: 0 on success or -ENODEV; | ||
171 | */ | ||
172 | static int mei_wdt_ops_start(struct watchdog_device *wdd) | ||
173 | { | ||
174 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | ||
175 | |||
176 | wdt->state = MEI_WDT_START; | ||
177 | wdd->timeout = wdt->timeout; | ||
178 | return 0; | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * mei_wdt_ops_stop - wd stop command from the watchdog core. | ||
183 | * | ||
184 | * @wdd: watchdog device | ||
185 | * | ||
186 | * Return: 0 if success, negative errno code for failure | ||
187 | */ | ||
188 | static int mei_wdt_ops_stop(struct watchdog_device *wdd) | ||
189 | { | ||
190 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | ||
191 | int ret; | ||
192 | |||
193 | if (wdt->state != MEI_WDT_RUNNING) | ||
194 | return 0; | ||
195 | |||
196 | wdt->state = MEI_WDT_STOPPING; | ||
197 | |||
198 | ret = mei_wdt_stop(wdt); | ||
199 | if (ret) | ||
200 | return ret; | ||
201 | |||
202 | wdt->state = MEI_WDT_IDLE; | ||
203 | |||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * mei_wdt_ops_ping - wd ping command from the watchdog core. | ||
209 | * | ||
210 | * @wdd: watchdog device | ||
211 | * | ||
212 | * Return: 0 if success, negative errno code on failure | ||
213 | */ | ||
214 | static int mei_wdt_ops_ping(struct watchdog_device *wdd) | ||
215 | { | ||
216 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | ||
217 | int ret; | ||
218 | |||
219 | if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING) | ||
220 | return 0; | ||
221 | |||
222 | ret = mei_wdt_ping(wdt); | ||
223 | if (ret) | ||
224 | return ret; | ||
225 | |||
226 | wdt->state = MEI_WDT_RUNNING; | ||
227 | |||
228 | return 0; | ||
229 | } | ||
230 | |||
231 | /** | ||
232 | * mei_wdt_ops_set_timeout - wd set timeout command from the watchdog core. | ||
233 | * | ||
234 | * @wdd: watchdog device | ||
235 | * @timeout: timeout value to set | ||
236 | * | ||
237 | * Return: 0 if success, negative errno code for failure | ||
238 | */ | ||
239 | static int mei_wdt_ops_set_timeout(struct watchdog_device *wdd, | ||
240 | unsigned int timeout) | ||
241 | { | ||
242 | |||
243 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | ||
244 | |||
245 | /* valid value is already checked by the caller */ | ||
246 | wdt->timeout = timeout; | ||
247 | wdd->timeout = timeout; | ||
248 | |||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | static const struct watchdog_ops wd_ops = { | ||
253 | .owner = THIS_MODULE, | ||
254 | .start = mei_wdt_ops_start, | ||
255 | .stop = mei_wdt_ops_stop, | ||
256 | .ping = mei_wdt_ops_ping, | ||
257 | .set_timeout = mei_wdt_ops_set_timeout, | ||
258 | }; | ||
259 | |||
260 | /* not const as the firmware_version field need to be retrieved */ | ||
261 | static struct watchdog_info wd_info = { | ||
262 | .identity = INTEL_AMT_WATCHDOG_ID, | ||
263 | .options = WDIOF_KEEPALIVEPING | | ||
264 | WDIOF_SETTIMEOUT | | ||
265 | WDIOF_ALARMONLY, | ||
266 | }; | ||
267 | |||
268 | /** | ||
269 | * mei_wdt_unregister - unregister from the watchdog subsystem | ||
270 | * | ||
271 | * @wdt: mei watchdog device | ||
272 | */ | ||
273 | static void mei_wdt_unregister(struct mei_wdt *wdt) | ||
274 | { | ||
275 | watchdog_unregister_device(&wdt->wdd); | ||
276 | watchdog_set_drvdata(&wdt->wdd, NULL); | ||
277 | } | ||
278 | |||
279 | /** | ||
280 | * mei_wdt_register - register with the watchdog subsystem | ||
281 | * | ||
282 | * @wdt: mei watchdog device | ||
283 | * | ||
284 | * Return: 0 if success, negative errno code for failure | ||
285 | */ | ||
286 | static int mei_wdt_register(struct mei_wdt *wdt) | ||
287 | { | ||
288 | struct device *dev; | ||
289 | int ret; | ||
290 | |||
291 | if (!wdt || !wdt->cldev) | ||
292 | return -EINVAL; | ||
293 | |||
294 | dev = &wdt->cldev->dev; | ||
295 | |||
296 | wdt->wdd.info = &wd_info; | ||
297 | wdt->wdd.ops = &wd_ops; | ||
298 | wdt->wdd.parent = dev; | ||
299 | wdt->wdd.timeout = MEI_WDT_DEFAULT_TIMEOUT; | ||
300 | wdt->wdd.min_timeout = MEI_WDT_MIN_TIMEOUT; | ||
301 | wdt->wdd.max_timeout = MEI_WDT_MAX_TIMEOUT; | ||
302 | |||
303 | watchdog_set_drvdata(&wdt->wdd, wdt); | ||
304 | ret = watchdog_register_device(&wdt->wdd); | ||
305 | if (ret) { | ||
306 | dev_err(dev, "unable to register watchdog device = %d.\n", ret); | ||
307 | watchdog_set_drvdata(&wdt->wdd, NULL); | ||
308 | } | ||
309 | |||
310 | return ret; | ||
311 | } | ||
312 | |||
313 | static int mei_wdt_probe(struct mei_cl_device *cldev, | ||
314 | const struct mei_cl_device_id *id) | ||
315 | { | ||
316 | struct mei_wdt *wdt; | ||
317 | int ret; | ||
318 | |||
319 | wdt = kzalloc(sizeof(struct mei_wdt), GFP_KERNEL); | ||
320 | if (!wdt) | ||
321 | return -ENOMEM; | ||
322 | |||
323 | wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT; | ||
324 | wdt->state = MEI_WDT_IDLE; | ||
325 | wdt->cldev = cldev; | ||
326 | mei_cldev_set_drvdata(cldev, wdt); | ||
327 | |||
328 | ret = mei_cldev_enable(cldev); | ||
329 | if (ret < 0) { | ||
330 | dev_err(&cldev->dev, "Could not enable cl device\n"); | ||
331 | goto err_out; | ||
332 | } | ||
333 | |||
334 | wd_info.firmware_version = mei_cldev_ver(cldev); | ||
335 | |||
336 | ret = mei_wdt_register(wdt); | ||
337 | if (ret) | ||
338 | goto err_disable; | ||
339 | |||
340 | return 0; | ||
341 | |||
342 | err_disable: | ||
343 | mei_cldev_disable(cldev); | ||
344 | |||
345 | err_out: | ||
346 | kfree(wdt); | ||
347 | |||
348 | return ret; | ||
349 | } | ||
350 | |||
351 | static int mei_wdt_remove(struct mei_cl_device *cldev) | ||
352 | { | ||
353 | struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); | ||
354 | |||
355 | mei_wdt_unregister(wdt); | ||
356 | |||
357 | mei_cldev_disable(cldev); | ||
358 | |||
359 | kfree(wdt); | ||
360 | |||
361 | return 0; | ||
362 | } | ||
363 | |||
364 | #define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \ | ||
365 | 0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB) | ||
366 | |||
367 | static struct mei_cl_device_id mei_wdt_tbl[] = { | ||
368 | { .uuid = MEI_UUID_WD, .version = 0x1}, | ||
369 | /* required last entry */ | ||
370 | { } | ||
371 | }; | ||
372 | MODULE_DEVICE_TABLE(mei, mei_wdt_tbl); | ||
373 | |||
374 | static struct mei_cl_driver mei_wdt_driver = { | ||
375 | .id_table = mei_wdt_tbl, | ||
376 | .name = KBUILD_MODNAME, | ||
377 | |||
378 | .probe = mei_wdt_probe, | ||
379 | .remove = mei_wdt_remove, | ||
380 | }; | ||
381 | |||
382 | static int __init mei_wdt_init(void) | ||
383 | { | ||
384 | int ret; | ||
385 | |||
386 | ret = mei_cldev_driver_register(&mei_wdt_driver); | ||
387 | if (ret) { | ||
388 | pr_err(KBUILD_MODNAME ": module registration failed\n"); | ||
389 | return ret; | ||
390 | } | ||
391 | return 0; | ||
392 | } | ||
393 | |||
394 | static void __exit mei_wdt_exit(void) | ||
395 | { | ||
396 | mei_cldev_driver_unregister(&mei_wdt_driver); | ||
397 | } | ||
398 | |||
399 | module_init(mei_wdt_init); | ||
400 | module_exit(mei_wdt_exit); | ||
401 | |||
402 | MODULE_AUTHOR("Intel Corporation"); | ||
403 | MODULE_LICENSE("GPL"); | ||
404 | MODULE_DESCRIPTION("Device driver for Intel MEI iAMT watchdog"); | ||