]>
Commit | Line | Data |
---|---|---|
016e29c0 DM |
1 | From 222818c3d84c1f3190767f5f09f2b9b9a0e0ca7f Mon Sep 17 00:00:00 2001 |
2 | From: Tomas Winkler <tomas.winkler@intel.com> | |
3 | Date: Fri, 8 Jan 2016 00:49:22 +0200 | |
4 | Subject: watchdog: mei_wdt: implement MEI iAMT watchdog driver | |
5 | ||
6 | Create a driver with the generic watchdog interface | |
7 | for the MEI iAMT watchdog device. | |
8 | ||
9 | Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com> | |
10 | Signed-off-by: Tomas Winkler <tomas.winkler@intel.com> | |
11 | Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> | |
12 | ||
13 | PVE NOTES: the old code always enables this watchdog, but we do not | |
14 | want to use this watchdog because it does not reboot the host, and min | |
15 | timeout is 120 seconds. With this patch, we can simply blacklist mei_wdt. | |
16 | ||
17 | --- | |
18 | Documentation/misc-devices/mei/mei.txt | 12 +- | |
19 | MAINTAINERS | 1 + | |
20 | drivers/watchdog/Kconfig | 15 ++ | |
21 | drivers/watchdog/Makefile | 1 + | |
22 | drivers/watchdog/mei_wdt.c | 404 +++++++++++++++++++++++++++++++++ | |
23 | 5 files changed, 427 insertions(+), 6 deletions(-) | |
24 | create mode 100644 drivers/watchdog/mei_wdt.c | |
25 | ||
26 | diff --git a/Documentation/misc-devices/mei/mei.txt b/Documentation/misc-devices/mei/mei.txt | |
27 | index 91c1fa3..2b80a0c 100644 | |
28 | --- a/Documentation/misc-devices/mei/mei.txt | |
29 | +++ b/Documentation/misc-devices/mei/mei.txt | |
30 | @@ -231,15 +231,15 @@ IT knows when a platform crashes even when there is a hard failure on the host. | |
31 | The Intel AMT Watchdog is composed of two parts: | |
32 | 1) Firmware feature - receives the heartbeats | |
33 | and sends an event when the heartbeats stop. | |
34 | - 2) Intel MEI driver - connects to the watchdog feature, configures the | |
35 | - watchdog and sends the heartbeats. | |
36 | + 2) Intel MEI iAMT watchdog driver - connects to the watchdog feature, | |
37 | + configures the watchdog and sends the heartbeats. | |
38 | ||
39 | -The Intel MEI driver uses the kernel watchdog API to configure the Intel AMT | |
40 | -Watchdog and to send heartbeats to it. The default timeout of the | |
41 | +The Intel iAMT watchdog MEI driver uses the kernel watchdog API to configure | |
42 | +the Intel AMT Watchdog and to send heartbeats to it. The default timeout of the | |
43 | watchdog is 120 seconds. | |
44 | ||
45 | -If the Intel AMT Watchdog feature does not exist (i.e. the connection failed), | |
46 | -the Intel MEI driver will disable the sending of heartbeats. | |
47 | +If the Intel AMT is not enabled in the firmware then the watchdog client won't enumerate | |
48 | +on the me client bus and watchdog devices won't be exposed. | |
49 | ||
50 | ||
51 | Supported Chipsets | |
52 | diff --git a/MAINTAINERS b/MAINTAINERS | |
53 | index 30aca4a..d63b3c7 100644 | |
54 | --- a/MAINTAINERS | |
55 | +++ b/MAINTAINERS | |
56 | @@ -5751,6 +5751,7 @@ S: Supported | |
57 | F: include/uapi/linux/mei.h | |
58 | F: include/linux/mei_cl_bus.h | |
59 | F: drivers/misc/mei/* | |
60 | +F: drivers/watchdog/mei_wdt.c | |
61 | F: Documentation/misc-devices/mei/* | |
62 | ||
63 | INTEL MIC DRIVERS (mic) | |
64 | diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig | |
65 | index 4f0e7be..57f8721 100644 | |
66 | --- a/drivers/watchdog/Kconfig | |
67 | +++ b/drivers/watchdog/Kconfig | |
68 | @@ -1212,6 +1212,21 @@ config SBC_EPX_C3_WATCHDOG | |
69 | To compile this driver as a module, choose M here: the | |
70 | module will be called sbc_epx_c3. | |
71 | ||
72 | +config INTEL_MEI_WDT | |
73 | + tristate "Intel MEI iAMT Watchdog" | |
74 | + depends on INTEL_MEI && X86 | |
75 | + select WATCHDOG_CORE | |
76 | + ---help--- | |
77 | + A device driver for the Intel MEI iAMT watchdog. | |
78 | + | |
79 | + The Intel AMT Watchdog is an OS Health (Hang/Crash) watchdog. | |
80 | + Whenever the OS hangs or crashes, iAMT will send an event | |
81 | + to any subscriber to this event. The watchdog doesn't reset the | |
82 | + the platform. | |
83 | + | |
84 | + To compile this driver as a module, choose M here: | |
85 | + the module will be called mei_wdt. | |
86 | + | |
87 | # M32R Architecture | |
88 | ||
89 | # M68K Architecture | |
90 | diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile | |
91 | index f566753..efc4f78 100644 | |
92 | --- a/drivers/watchdog/Makefile | |
93 | +++ b/drivers/watchdog/Makefile | |
94 | @@ -126,6 +126,7 @@ obj-$(CONFIG_MACHZ_WDT) += machzwd.o | |
95 | obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o | |
96 | obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o | |
97 | obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o | |
98 | +obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o | |
99 | ||
100 | # M32R Architecture | |
101 | ||
102 | diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c | |
103 | new file mode 100644 | |
104 | index 0000000..32e3e1d | |
105 | --- /dev/null | |
106 | +++ b/drivers/watchdog/mei_wdt.c | |
107 | @@ -0,0 +1,404 @@ | |
108 | +/* | |
109 | + * Intel Management Engine Interface (Intel MEI) Linux driver | |
110 | + * Copyright (c) 2015, Intel Corporation. | |
111 | + * | |
112 | + * This program is free software; you can redistribute it and/or modify it | |
113 | + * under the terms and conditions of the GNU General Public License, | |
114 | + * version 2, as published by the Free Software Foundation. | |
115 | + * | |
116 | + * This program is distributed in the hope it will be useful, but WITHOUT | |
117 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
118 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
119 | + * more details. | |
120 | + */ | |
121 | + | |
122 | +#include <linux/module.h> | |
123 | +#include <linux/slab.h> | |
124 | +#include <linux/interrupt.h> | |
125 | +#include <linux/watchdog.h> | |
126 | + | |
127 | +#include <linux/uuid.h> | |
128 | +#include <linux/mei_cl_bus.h> | |
129 | + | |
130 | +/* | |
131 | + * iAMT Watchdog Device | |
132 | + */ | |
133 | +#define INTEL_AMT_WATCHDOG_ID "iamt_wdt" | |
134 | + | |
135 | +#define MEI_WDT_DEFAULT_TIMEOUT 120 /* seconds */ | |
136 | +#define MEI_WDT_MIN_TIMEOUT 120 /* seconds */ | |
137 | +#define MEI_WDT_MAX_TIMEOUT 65535 /* seconds */ | |
138 | + | |
139 | +/* Commands */ | |
140 | +#define MEI_MANAGEMENT_CONTROL 0x02 | |
141 | + | |
142 | +/* MEI Management Control version number */ | |
143 | +#define MEI_MC_VERSION_NUMBER 0x10 | |
144 | + | |
145 | +/* Sub Commands */ | |
146 | +#define MEI_MC_START_WD_TIMER_REQ 0x13 | |
147 | +#define MEI_MC_STOP_WD_TIMER_REQ 0x14 | |
148 | + | |
149 | +/** | |
150 | + * enum mei_wdt_state - internal watchdog state | |
151 | + * | |
152 | + * @MEI_WDT_IDLE: wd is idle and not opened | |
153 | + * @MEI_WDT_START: wd was opened, start was called | |
154 | + * @MEI_WDT_RUNNING: wd is expecting keep alive pings | |
155 | + * @MEI_WDT_STOPPING: wd is stopping and will move to IDLE | |
156 | + */ | |
157 | +enum mei_wdt_state { | |
158 | + MEI_WDT_IDLE, | |
159 | + MEI_WDT_START, | |
160 | + MEI_WDT_RUNNING, | |
161 | + MEI_WDT_STOPPING, | |
162 | +}; | |
163 | + | |
164 | +/** | |
165 | + * struct mei_wdt - mei watchdog driver | |
166 | + * @wdd: watchdog device | |
167 | + * | |
168 | + * @cldev: mei watchdog client device | |
169 | + * @state: watchdog internal state | |
170 | + * @timeout: watchdog current timeout | |
171 | + */ | |
172 | +struct mei_wdt { | |
173 | + struct watchdog_device wdd; | |
174 | + | |
175 | + struct mei_cl_device *cldev; | |
176 | + enum mei_wdt_state state; | |
177 | + u16 timeout; | |
178 | +}; | |
179 | + | |
180 | +/* | |
181 | + * struct mei_mc_hdr - Management Control Command Header | |
182 | + * | |
183 | + * @command: Management Control (0x2) | |
184 | + * @bytecount: Number of bytes in the message beyond this byte | |
185 | + * @subcommand: Management Control Subcommand | |
186 | + * @versionnumber: Management Control Version (0x10) | |
187 | + */ | |
188 | +struct mei_mc_hdr { | |
189 | + u8 command; | |
190 | + u8 bytecount; | |
191 | + u8 subcommand; | |
192 | + u8 versionnumber; | |
193 | +}; | |
194 | + | |
195 | +/** | |
196 | + * struct mei_wdt_start_request watchdog start/ping | |
197 | + * | |
198 | + * @hdr: Management Control Command Header | |
199 | + * @timeout: timeout value | |
200 | + * @reserved: reserved (legacy) | |
201 | + */ | |
202 | +struct mei_wdt_start_request { | |
203 | + struct mei_mc_hdr hdr; | |
204 | + u16 timeout; | |
205 | + u8 reserved[17]; | |
206 | +} __packed; | |
207 | + | |
208 | +/** | |
209 | + * struct mei_wdt_stop_request - watchdog stop | |
210 | + * | |
211 | + * @hdr: Management Control Command Header | |
212 | + */ | |
213 | +struct mei_wdt_stop_request { | |
214 | + struct mei_mc_hdr hdr; | |
215 | +} __packed; | |
216 | + | |
217 | +/** | |
218 | + * mei_wdt_ping - send wd start/ping command | |
219 | + * | |
220 | + * @wdt: mei watchdog device | |
221 | + * | |
222 | + * Return: 0 on success, | |
223 | + * negative errno code on failure | |
224 | + */ | |
225 | +static int mei_wdt_ping(struct mei_wdt *wdt) | |
226 | +{ | |
227 | + struct mei_wdt_start_request req; | |
228 | + const size_t req_len = sizeof(req); | |
229 | + int ret; | |
230 | + | |
231 | + memset(&req, 0, req_len); | |
232 | + req.hdr.command = MEI_MANAGEMENT_CONTROL; | |
233 | + req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand); | |
234 | + req.hdr.subcommand = MEI_MC_START_WD_TIMER_REQ; | |
235 | + req.hdr.versionnumber = MEI_MC_VERSION_NUMBER; | |
236 | + req.timeout = wdt->timeout; | |
237 | + | |
238 | + ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len); | |
239 | + if (ret < 0) | |
240 | + return ret; | |
241 | + | |
242 | + return 0; | |
243 | +} | |
244 | + | |
245 | +/** | |
246 | + * mei_wdt_stop - send wd stop command | |
247 | + * | |
248 | + * @wdt: mei watchdog device | |
249 | + * | |
250 | + * Return: 0 on success, | |
251 | + * negative errno code on failure | |
252 | + */ | |
253 | +static int mei_wdt_stop(struct mei_wdt *wdt) | |
254 | +{ | |
255 | + struct mei_wdt_stop_request req; | |
256 | + const size_t req_len = sizeof(req); | |
257 | + int ret; | |
258 | + | |
259 | + memset(&req, 0, req_len); | |
260 | + req.hdr.command = MEI_MANAGEMENT_CONTROL; | |
261 | + req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand); | |
262 | + req.hdr.subcommand = MEI_MC_STOP_WD_TIMER_REQ; | |
263 | + req.hdr.versionnumber = MEI_MC_VERSION_NUMBER; | |
264 | + | |
265 | + ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len); | |
266 | + if (ret < 0) | |
267 | + return ret; | |
268 | + | |
269 | + return 0; | |
270 | +} | |
271 | + | |
272 | +/** | |
273 | + * mei_wdt_ops_start - wd start command from the watchdog core. | |
274 | + * | |
275 | + * @wdd: watchdog device | |
276 | + * | |
277 | + * Return: 0 on success or -ENODEV; | |
278 | + */ | |
279 | +static int mei_wdt_ops_start(struct watchdog_device *wdd) | |
280 | +{ | |
281 | + struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | |
282 | + | |
283 | + wdt->state = MEI_WDT_START; | |
284 | + wdd->timeout = wdt->timeout; | |
285 | + return 0; | |
286 | +} | |
287 | + | |
288 | +/** | |
289 | + * mei_wdt_ops_stop - wd stop command from the watchdog core. | |
290 | + * | |
291 | + * @wdd: watchdog device | |
292 | + * | |
293 | + * Return: 0 if success, negative errno code for failure | |
294 | + */ | |
295 | +static int mei_wdt_ops_stop(struct watchdog_device *wdd) | |
296 | +{ | |
297 | + struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | |
298 | + int ret; | |
299 | + | |
300 | + if (wdt->state != MEI_WDT_RUNNING) | |
301 | + return 0; | |
302 | + | |
303 | + wdt->state = MEI_WDT_STOPPING; | |
304 | + | |
305 | + ret = mei_wdt_stop(wdt); | |
306 | + if (ret) | |
307 | + return ret; | |
308 | + | |
309 | + wdt->state = MEI_WDT_IDLE; | |
310 | + | |
311 | + return 0; | |
312 | +} | |
313 | + | |
314 | +/** | |
315 | + * mei_wdt_ops_ping - wd ping command from the watchdog core. | |
316 | + * | |
317 | + * @wdd: watchdog device | |
318 | + * | |
319 | + * Return: 0 if success, negative errno code on failure | |
320 | + */ | |
321 | +static int mei_wdt_ops_ping(struct watchdog_device *wdd) | |
322 | +{ | |
323 | + struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | |
324 | + int ret; | |
325 | + | |
326 | + if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING) | |
327 | + return 0; | |
328 | + | |
329 | + ret = mei_wdt_ping(wdt); | |
330 | + if (ret) | |
331 | + return ret; | |
332 | + | |
333 | + wdt->state = MEI_WDT_RUNNING; | |
334 | + | |
335 | + return 0; | |
336 | +} | |
337 | + | |
338 | +/** | |
339 | + * mei_wdt_ops_set_timeout - wd set timeout command from the watchdog core. | |
340 | + * | |
341 | + * @wdd: watchdog device | |
342 | + * @timeout: timeout value to set | |
343 | + * | |
344 | + * Return: 0 if success, negative errno code for failure | |
345 | + */ | |
346 | +static int mei_wdt_ops_set_timeout(struct watchdog_device *wdd, | |
347 | + unsigned int timeout) | |
348 | +{ | |
349 | + | |
350 | + struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | |
351 | + | |
352 | + /* valid value is already checked by the caller */ | |
353 | + wdt->timeout = timeout; | |
354 | + wdd->timeout = timeout; | |
355 | + | |
356 | + return 0; | |
357 | +} | |
358 | + | |
359 | +static const struct watchdog_ops wd_ops = { | |
360 | + .owner = THIS_MODULE, | |
361 | + .start = mei_wdt_ops_start, | |
362 | + .stop = mei_wdt_ops_stop, | |
363 | + .ping = mei_wdt_ops_ping, | |
364 | + .set_timeout = mei_wdt_ops_set_timeout, | |
365 | +}; | |
366 | + | |
367 | +/* not const as the firmware_version field need to be retrieved */ | |
368 | +static struct watchdog_info wd_info = { | |
369 | + .identity = INTEL_AMT_WATCHDOG_ID, | |
370 | + .options = WDIOF_KEEPALIVEPING | | |
371 | + WDIOF_SETTIMEOUT | | |
372 | + WDIOF_ALARMONLY, | |
373 | +}; | |
374 | + | |
375 | +/** | |
376 | + * mei_wdt_unregister - unregister from the watchdog subsystem | |
377 | + * | |
378 | + * @wdt: mei watchdog device | |
379 | + */ | |
380 | +static void mei_wdt_unregister(struct mei_wdt *wdt) | |
381 | +{ | |
382 | + watchdog_unregister_device(&wdt->wdd); | |
383 | + watchdog_set_drvdata(&wdt->wdd, NULL); | |
384 | +} | |
385 | + | |
386 | +/** | |
387 | + * mei_wdt_register - register with the watchdog subsystem | |
388 | + * | |
389 | + * @wdt: mei watchdog device | |
390 | + * | |
391 | + * Return: 0 if success, negative errno code for failure | |
392 | + */ | |
393 | +static int mei_wdt_register(struct mei_wdt *wdt) | |
394 | +{ | |
395 | + struct device *dev; | |
396 | + int ret; | |
397 | + | |
398 | + if (!wdt || !wdt->cldev) | |
399 | + return -EINVAL; | |
400 | + | |
401 | + dev = &wdt->cldev->dev; | |
402 | + | |
403 | + wdt->wdd.info = &wd_info; | |
404 | + wdt->wdd.ops = &wd_ops; | |
405 | + wdt->wdd.parent = dev; | |
406 | + wdt->wdd.timeout = MEI_WDT_DEFAULT_TIMEOUT; | |
407 | + wdt->wdd.min_timeout = MEI_WDT_MIN_TIMEOUT; | |
408 | + wdt->wdd.max_timeout = MEI_WDT_MAX_TIMEOUT; | |
409 | + | |
410 | + watchdog_set_drvdata(&wdt->wdd, wdt); | |
411 | + ret = watchdog_register_device(&wdt->wdd); | |
412 | + if (ret) { | |
413 | + dev_err(dev, "unable to register watchdog device = %d.\n", ret); | |
414 | + watchdog_set_drvdata(&wdt->wdd, NULL); | |
415 | + } | |
416 | + | |
417 | + return ret; | |
418 | +} | |
419 | + | |
420 | +static int mei_wdt_probe(struct mei_cl_device *cldev, | |
421 | + const struct mei_cl_device_id *id) | |
422 | +{ | |
423 | + struct mei_wdt *wdt; | |
424 | + int ret; | |
425 | + | |
426 | + wdt = kzalloc(sizeof(struct mei_wdt), GFP_KERNEL); | |
427 | + if (!wdt) | |
428 | + return -ENOMEM; | |
429 | + | |
430 | + wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT; | |
431 | + wdt->state = MEI_WDT_IDLE; | |
432 | + wdt->cldev = cldev; | |
433 | + mei_cldev_set_drvdata(cldev, wdt); | |
434 | + | |
435 | + ret = mei_cldev_enable(cldev); | |
436 | + if (ret < 0) { | |
437 | + dev_err(&cldev->dev, "Could not enable cl device\n"); | |
438 | + goto err_out; | |
439 | + } | |
440 | + | |
441 | + wd_info.firmware_version = mei_cldev_ver(cldev); | |
442 | + | |
443 | + ret = mei_wdt_register(wdt); | |
444 | + if (ret) | |
445 | + goto err_disable; | |
446 | + | |
447 | + return 0; | |
448 | + | |
449 | +err_disable: | |
450 | + mei_cldev_disable(cldev); | |
451 | + | |
452 | +err_out: | |
453 | + kfree(wdt); | |
454 | + | |
455 | + return ret; | |
456 | +} | |
457 | + | |
458 | +static int mei_wdt_remove(struct mei_cl_device *cldev) | |
459 | +{ | |
460 | + struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); | |
461 | + | |
462 | + mei_wdt_unregister(wdt); | |
463 | + | |
464 | + mei_cldev_disable(cldev); | |
465 | + | |
466 | + kfree(wdt); | |
467 | + | |
468 | + return 0; | |
469 | +} | |
470 | + | |
471 | +#define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \ | |
472 | + 0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB) | |
473 | + | |
474 | +static struct mei_cl_device_id mei_wdt_tbl[] = { | |
475 | + { .uuid = MEI_UUID_WD, .version = 0x1}, | |
476 | + /* required last entry */ | |
477 | + { } | |
478 | +}; | |
479 | +MODULE_DEVICE_TABLE(mei, mei_wdt_tbl); | |
480 | + | |
481 | +static struct mei_cl_driver mei_wdt_driver = { | |
482 | + .id_table = mei_wdt_tbl, | |
483 | + .name = KBUILD_MODNAME, | |
484 | + | |
485 | + .probe = mei_wdt_probe, | |
486 | + .remove = mei_wdt_remove, | |
487 | +}; | |
488 | + | |
489 | +static int __init mei_wdt_init(void) | |
490 | +{ | |
491 | + int ret; | |
492 | + | |
493 | + ret = mei_cldev_driver_register(&mei_wdt_driver); | |
494 | + if (ret) { | |
495 | + pr_err(KBUILD_MODNAME ": module registration failed\n"); | |
496 | + return ret; | |
497 | + } | |
498 | + return 0; | |
499 | +} | |
500 | + | |
501 | +static void __exit mei_wdt_exit(void) | |
502 | +{ | |
503 | + mei_cldev_driver_unregister(&mei_wdt_driver); | |
504 | +} | |
505 | + | |
506 | +module_init(mei_wdt_init); | |
507 | +module_exit(mei_wdt_exit); | |
508 | + | |
509 | +MODULE_AUTHOR("Intel Corporation"); | |
510 | +MODULE_LICENSE("GPL"); | |
511 | +MODULE_DESCRIPTION("Device driver for Intel MEI iAMT watchdog"); | |
512 | -- | |
513 | cgit v0.12 | |
514 |