]>
Commit | Line | Data |
---|---|---|
222818c3 TW |
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> | |
c9cf20ee | 18 | #include <linux/debugfs.h> |
222818c3 TW |
19 | #include <linux/watchdog.h> |
20 | ||
21 | #include <linux/uuid.h> | |
22 | #include <linux/mei_cl_bus.h> | |
23 | ||
24 | /* | |
25 | * iAMT Watchdog Device | |
26 | */ | |
27 | #define INTEL_AMT_WATCHDOG_ID "iamt_wdt" | |
28 | ||
29 | #define MEI_WDT_DEFAULT_TIMEOUT 120 /* seconds */ | |
30 | #define MEI_WDT_MIN_TIMEOUT 120 /* seconds */ | |
31 | #define MEI_WDT_MAX_TIMEOUT 65535 /* seconds */ | |
32 | ||
33 | /* Commands */ | |
34 | #define MEI_MANAGEMENT_CONTROL 0x02 | |
35 | ||
36 | /* MEI Management Control version number */ | |
37 | #define MEI_MC_VERSION_NUMBER 0x10 | |
38 | ||
39 | /* Sub Commands */ | |
40 | #define MEI_MC_START_WD_TIMER_REQ 0x13 | |
41 | #define MEI_MC_STOP_WD_TIMER_REQ 0x14 | |
42 | ||
43 | /** | |
44 | * enum mei_wdt_state - internal watchdog state | |
45 | * | |
46 | * @MEI_WDT_IDLE: wd is idle and not opened | |
47 | * @MEI_WDT_START: wd was opened, start was called | |
48 | * @MEI_WDT_RUNNING: wd is expecting keep alive pings | |
49 | * @MEI_WDT_STOPPING: wd is stopping and will move to IDLE | |
50 | */ | |
51 | enum mei_wdt_state { | |
52 | MEI_WDT_IDLE, | |
53 | MEI_WDT_START, | |
54 | MEI_WDT_RUNNING, | |
55 | MEI_WDT_STOPPING, | |
56 | }; | |
57 | ||
c9cf20ee TW |
58 | #if IS_ENABLED(CONFIG_DEBUG_FS) |
59 | static const char *mei_wdt_state_str(enum mei_wdt_state state) | |
60 | { | |
61 | switch (state) { | |
62 | case MEI_WDT_IDLE: | |
63 | return "IDLE"; | |
64 | case MEI_WDT_START: | |
65 | return "START"; | |
66 | case MEI_WDT_RUNNING: | |
67 | return "RUNNING"; | |
68 | case MEI_WDT_STOPPING: | |
69 | return "STOPPING"; | |
70 | default: | |
71 | return "unknown"; | |
72 | } | |
73 | } | |
74 | #endif /* CONFIG_DEBUG_FS */ | |
75 | ||
222818c3 TW |
76 | /** |
77 | * struct mei_wdt - mei watchdog driver | |
78 | * @wdd: watchdog device | |
79 | * | |
80 | * @cldev: mei watchdog client device | |
81 | * @state: watchdog internal state | |
82 | * @timeout: watchdog current timeout | |
c9cf20ee TW |
83 | * |
84 | * @dbgfs_dir: debugfs dir entry | |
222818c3 TW |
85 | */ |
86 | struct mei_wdt { | |
87 | struct watchdog_device wdd; | |
88 | ||
89 | struct mei_cl_device *cldev; | |
90 | enum mei_wdt_state state; | |
91 | u16 timeout; | |
c9cf20ee TW |
92 | |
93 | #if IS_ENABLED(CONFIG_DEBUG_FS) | |
94 | struct dentry *dbgfs_dir; | |
95 | #endif /* CONFIG_DEBUG_FS */ | |
222818c3 TW |
96 | }; |
97 | ||
98 | /* | |
99 | * struct mei_mc_hdr - Management Control Command Header | |
100 | * | |
101 | * @command: Management Control (0x2) | |
102 | * @bytecount: Number of bytes in the message beyond this byte | |
103 | * @subcommand: Management Control Subcommand | |
104 | * @versionnumber: Management Control Version (0x10) | |
105 | */ | |
106 | struct mei_mc_hdr { | |
107 | u8 command; | |
108 | u8 bytecount; | |
109 | u8 subcommand; | |
110 | u8 versionnumber; | |
111 | }; | |
112 | ||
113 | /** | |
114 | * struct mei_wdt_start_request watchdog start/ping | |
115 | * | |
116 | * @hdr: Management Control Command Header | |
117 | * @timeout: timeout value | |
118 | * @reserved: reserved (legacy) | |
119 | */ | |
120 | struct mei_wdt_start_request { | |
121 | struct mei_mc_hdr hdr; | |
122 | u16 timeout; | |
123 | u8 reserved[17]; | |
124 | } __packed; | |
125 | ||
126 | /** | |
127 | * struct mei_wdt_stop_request - watchdog stop | |
128 | * | |
129 | * @hdr: Management Control Command Header | |
130 | */ | |
131 | struct mei_wdt_stop_request { | |
132 | struct mei_mc_hdr hdr; | |
133 | } __packed; | |
134 | ||
135 | /** | |
136 | * mei_wdt_ping - send wd start/ping command | |
137 | * | |
138 | * @wdt: mei watchdog device | |
139 | * | |
140 | * Return: 0 on success, | |
141 | * negative errno code on failure | |
142 | */ | |
143 | static int mei_wdt_ping(struct mei_wdt *wdt) | |
144 | { | |
145 | struct mei_wdt_start_request req; | |
146 | const size_t req_len = sizeof(req); | |
147 | int ret; | |
148 | ||
149 | memset(&req, 0, req_len); | |
150 | req.hdr.command = MEI_MANAGEMENT_CONTROL; | |
151 | req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand); | |
152 | req.hdr.subcommand = MEI_MC_START_WD_TIMER_REQ; | |
153 | req.hdr.versionnumber = MEI_MC_VERSION_NUMBER; | |
154 | req.timeout = wdt->timeout; | |
155 | ||
156 | ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len); | |
157 | if (ret < 0) | |
158 | return ret; | |
159 | ||
160 | return 0; | |
161 | } | |
162 | ||
163 | /** | |
164 | * mei_wdt_stop - send wd stop command | |
165 | * | |
166 | * @wdt: mei watchdog device | |
167 | * | |
168 | * Return: 0 on success, | |
169 | * negative errno code on failure | |
170 | */ | |
171 | static int mei_wdt_stop(struct mei_wdt *wdt) | |
172 | { | |
173 | struct mei_wdt_stop_request req; | |
174 | const size_t req_len = sizeof(req); | |
175 | int ret; | |
176 | ||
177 | memset(&req, 0, req_len); | |
178 | req.hdr.command = MEI_MANAGEMENT_CONTROL; | |
179 | req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand); | |
180 | req.hdr.subcommand = MEI_MC_STOP_WD_TIMER_REQ; | |
181 | req.hdr.versionnumber = MEI_MC_VERSION_NUMBER; | |
182 | ||
183 | ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len); | |
184 | if (ret < 0) | |
185 | return ret; | |
186 | ||
187 | return 0; | |
188 | } | |
189 | ||
190 | /** | |
191 | * mei_wdt_ops_start - wd start command from the watchdog core. | |
192 | * | |
193 | * @wdd: watchdog device | |
194 | * | |
195 | * Return: 0 on success or -ENODEV; | |
196 | */ | |
197 | static int mei_wdt_ops_start(struct watchdog_device *wdd) | |
198 | { | |
199 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | |
200 | ||
201 | wdt->state = MEI_WDT_START; | |
202 | wdd->timeout = wdt->timeout; | |
203 | return 0; | |
204 | } | |
205 | ||
206 | /** | |
207 | * mei_wdt_ops_stop - wd stop command from the watchdog core. | |
208 | * | |
209 | * @wdd: watchdog device | |
210 | * | |
211 | * Return: 0 if success, negative errno code for failure | |
212 | */ | |
213 | static int mei_wdt_ops_stop(struct watchdog_device *wdd) | |
214 | { | |
215 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | |
216 | int ret; | |
217 | ||
218 | if (wdt->state != MEI_WDT_RUNNING) | |
219 | return 0; | |
220 | ||
221 | wdt->state = MEI_WDT_STOPPING; | |
222 | ||
223 | ret = mei_wdt_stop(wdt); | |
224 | if (ret) | |
225 | return ret; | |
226 | ||
227 | wdt->state = MEI_WDT_IDLE; | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
232 | /** | |
233 | * mei_wdt_ops_ping - wd ping command from the watchdog core. | |
234 | * | |
235 | * @wdd: watchdog device | |
236 | * | |
237 | * Return: 0 if success, negative errno code on failure | |
238 | */ | |
239 | static int mei_wdt_ops_ping(struct watchdog_device *wdd) | |
240 | { | |
241 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | |
242 | int ret; | |
243 | ||
244 | if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING) | |
245 | return 0; | |
246 | ||
247 | ret = mei_wdt_ping(wdt); | |
248 | if (ret) | |
249 | return ret; | |
250 | ||
251 | wdt->state = MEI_WDT_RUNNING; | |
252 | ||
253 | return 0; | |
254 | } | |
255 | ||
256 | /** | |
257 | * mei_wdt_ops_set_timeout - wd set timeout command from the watchdog core. | |
258 | * | |
259 | * @wdd: watchdog device | |
260 | * @timeout: timeout value to set | |
261 | * | |
262 | * Return: 0 if success, negative errno code for failure | |
263 | */ | |
264 | static int mei_wdt_ops_set_timeout(struct watchdog_device *wdd, | |
265 | unsigned int timeout) | |
266 | { | |
267 | ||
268 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); | |
269 | ||
270 | /* valid value is already checked by the caller */ | |
271 | wdt->timeout = timeout; | |
272 | wdd->timeout = timeout; | |
273 | ||
274 | return 0; | |
275 | } | |
276 | ||
277 | static const struct watchdog_ops wd_ops = { | |
278 | .owner = THIS_MODULE, | |
279 | .start = mei_wdt_ops_start, | |
280 | .stop = mei_wdt_ops_stop, | |
281 | .ping = mei_wdt_ops_ping, | |
282 | .set_timeout = mei_wdt_ops_set_timeout, | |
283 | }; | |
284 | ||
285 | /* not const as the firmware_version field need to be retrieved */ | |
286 | static struct watchdog_info wd_info = { | |
287 | .identity = INTEL_AMT_WATCHDOG_ID, | |
288 | .options = WDIOF_KEEPALIVEPING | | |
289 | WDIOF_SETTIMEOUT | | |
290 | WDIOF_ALARMONLY, | |
291 | }; | |
292 | ||
293 | /** | |
294 | * mei_wdt_unregister - unregister from the watchdog subsystem | |
295 | * | |
296 | * @wdt: mei watchdog device | |
297 | */ | |
298 | static void mei_wdt_unregister(struct mei_wdt *wdt) | |
299 | { | |
300 | watchdog_unregister_device(&wdt->wdd); | |
301 | watchdog_set_drvdata(&wdt->wdd, NULL); | |
302 | } | |
303 | ||
304 | /** | |
305 | * mei_wdt_register - register with the watchdog subsystem | |
306 | * | |
307 | * @wdt: mei watchdog device | |
308 | * | |
309 | * Return: 0 if success, negative errno code for failure | |
310 | */ | |
311 | static int mei_wdt_register(struct mei_wdt *wdt) | |
312 | { | |
313 | struct device *dev; | |
314 | int ret; | |
315 | ||
316 | if (!wdt || !wdt->cldev) | |
317 | return -EINVAL; | |
318 | ||
319 | dev = &wdt->cldev->dev; | |
320 | ||
321 | wdt->wdd.info = &wd_info; | |
322 | wdt->wdd.ops = &wd_ops; | |
323 | wdt->wdd.parent = dev; | |
324 | wdt->wdd.timeout = MEI_WDT_DEFAULT_TIMEOUT; | |
325 | wdt->wdd.min_timeout = MEI_WDT_MIN_TIMEOUT; | |
326 | wdt->wdd.max_timeout = MEI_WDT_MAX_TIMEOUT; | |
327 | ||
328 | watchdog_set_drvdata(&wdt->wdd, wdt); | |
329 | ret = watchdog_register_device(&wdt->wdd); | |
330 | if (ret) { | |
331 | dev_err(dev, "unable to register watchdog device = %d.\n", ret); | |
332 | watchdog_set_drvdata(&wdt->wdd, NULL); | |
333 | } | |
334 | ||
335 | return ret; | |
336 | } | |
337 | ||
c9cf20ee TW |
338 | #if IS_ENABLED(CONFIG_DEBUG_FS) |
339 | ||
340 | static ssize_t mei_dbgfs_read_state(struct file *file, char __user *ubuf, | |
341 | size_t cnt, loff_t *ppos) | |
342 | { | |
343 | struct mei_wdt *wdt = file->private_data; | |
344 | const size_t bufsz = 32; | |
345 | char buf[bufsz]; | |
346 | ssize_t pos; | |
347 | ||
348 | pos = scnprintf(buf, bufsz, "state: %s\n", | |
349 | mei_wdt_state_str(wdt->state)); | |
350 | ||
351 | return simple_read_from_buffer(ubuf, cnt, ppos, buf, pos); | |
352 | } | |
353 | ||
354 | static const struct file_operations dbgfs_fops_state = { | |
355 | .open = simple_open, | |
356 | .read = mei_dbgfs_read_state, | |
357 | .llseek = generic_file_llseek, | |
358 | }; | |
359 | ||
360 | static void dbgfs_unregister(struct mei_wdt *wdt) | |
361 | { | |
362 | debugfs_remove_recursive(wdt->dbgfs_dir); | |
363 | wdt->dbgfs_dir = NULL; | |
364 | } | |
365 | ||
366 | static int dbgfs_register(struct mei_wdt *wdt) | |
367 | { | |
368 | struct dentry *dir, *f; | |
369 | ||
370 | dir = debugfs_create_dir(KBUILD_MODNAME, NULL); | |
371 | if (!dir) | |
372 | return -ENOMEM; | |
373 | ||
374 | wdt->dbgfs_dir = dir; | |
375 | f = debugfs_create_file("state", S_IRUSR, dir, wdt, &dbgfs_fops_state); | |
376 | if (!f) | |
377 | goto err; | |
378 | ||
379 | return 0; | |
380 | err: | |
381 | dbgfs_unregister(wdt); | |
382 | return -ENODEV; | |
383 | } | |
384 | ||
385 | #else | |
386 | ||
387 | static inline void dbgfs_unregister(struct mei_wdt *wdt) {} | |
388 | ||
389 | static inline int dbgfs_register(struct mei_wdt *wdt) | |
390 | { | |
391 | return 0; | |
392 | } | |
393 | #endif /* CONFIG_DEBUG_FS */ | |
394 | ||
222818c3 TW |
395 | static int mei_wdt_probe(struct mei_cl_device *cldev, |
396 | const struct mei_cl_device_id *id) | |
397 | { | |
398 | struct mei_wdt *wdt; | |
399 | int ret; | |
400 | ||
401 | wdt = kzalloc(sizeof(struct mei_wdt), GFP_KERNEL); | |
402 | if (!wdt) | |
403 | return -ENOMEM; | |
404 | ||
405 | wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT; | |
406 | wdt->state = MEI_WDT_IDLE; | |
407 | wdt->cldev = cldev; | |
408 | mei_cldev_set_drvdata(cldev, wdt); | |
409 | ||
410 | ret = mei_cldev_enable(cldev); | |
411 | if (ret < 0) { | |
412 | dev_err(&cldev->dev, "Could not enable cl device\n"); | |
413 | goto err_out; | |
414 | } | |
415 | ||
416 | wd_info.firmware_version = mei_cldev_ver(cldev); | |
417 | ||
418 | ret = mei_wdt_register(wdt); | |
419 | if (ret) | |
420 | goto err_disable; | |
421 | ||
c9cf20ee TW |
422 | if (dbgfs_register(wdt)) |
423 | dev_warn(&cldev->dev, "cannot register debugfs\n"); | |
424 | ||
222818c3 TW |
425 | return 0; |
426 | ||
427 | err_disable: | |
428 | mei_cldev_disable(cldev); | |
429 | ||
430 | err_out: | |
431 | kfree(wdt); | |
432 | ||
433 | return ret; | |
434 | } | |
435 | ||
436 | static int mei_wdt_remove(struct mei_cl_device *cldev) | |
437 | { | |
438 | struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); | |
439 | ||
440 | mei_wdt_unregister(wdt); | |
441 | ||
442 | mei_cldev_disable(cldev); | |
443 | ||
c9cf20ee TW |
444 | dbgfs_unregister(wdt); |
445 | ||
222818c3 TW |
446 | kfree(wdt); |
447 | ||
448 | return 0; | |
449 | } | |
450 | ||
451 | #define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \ | |
452 | 0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB) | |
453 | ||
454 | static struct mei_cl_device_id mei_wdt_tbl[] = { | |
455 | { .uuid = MEI_UUID_WD, .version = 0x1}, | |
456 | /* required last entry */ | |
457 | { } | |
458 | }; | |
459 | MODULE_DEVICE_TABLE(mei, mei_wdt_tbl); | |
460 | ||
461 | static struct mei_cl_driver mei_wdt_driver = { | |
462 | .id_table = mei_wdt_tbl, | |
463 | .name = KBUILD_MODNAME, | |
464 | ||
465 | .probe = mei_wdt_probe, | |
466 | .remove = mei_wdt_remove, | |
467 | }; | |
468 | ||
469 | static int __init mei_wdt_init(void) | |
470 | { | |
471 | int ret; | |
472 | ||
473 | ret = mei_cldev_driver_register(&mei_wdt_driver); | |
474 | if (ret) { | |
475 | pr_err(KBUILD_MODNAME ": module registration failed\n"); | |
476 | return ret; | |
477 | } | |
478 | return 0; | |
479 | } | |
480 | ||
481 | static void __exit mei_wdt_exit(void) | |
482 | { | |
483 | mei_cldev_driver_unregister(&mei_wdt_driver); | |
484 | } | |
485 | ||
486 | module_init(mei_wdt_init); | |
487 | module_exit(mei_wdt_exit); | |
488 | ||
489 | MODULE_AUTHOR("Intel Corporation"); | |
490 | MODULE_LICENSE("GPL"); | |
491 | MODULE_DESCRIPTION("Device driver for Intel MEI iAMT watchdog"); |