1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver |
4 | * Copyright (c) 2015, Intel Corporation. |
5 | */ |
6 | |
7 | #include <linux/module.h> |
8 | #include <linux/slab.h> |
9 | #include <linux/interrupt.h> |
10 | #include <linux/debugfs.h> |
11 | #include <linux/completion.h> |
12 | #include <linux/watchdog.h> |
13 | |
14 | #include <linux/uuid.h> |
15 | #include <linux/mei_cl_bus.h> |
16 | |
17 | /* |
18 | * iAMT Watchdog Device |
19 | */ |
20 | #define INTEL_AMT_WATCHDOG_ID "iamt_wdt" |
21 | |
22 | #define MEI_WDT_DEFAULT_TIMEOUT 120 /* seconds */ |
23 | #define MEI_WDT_MIN_TIMEOUT 120 /* seconds */ |
24 | #define MEI_WDT_MAX_TIMEOUT 65535 /* seconds */ |
25 | |
26 | /* Commands */ |
27 | #define MEI_MANAGEMENT_CONTROL 0x02 |
28 | |
29 | /* MEI Management Control version number */ |
30 | #define MEI_MC_VERSION_NUMBER 0x10 |
31 | |
32 | /* Sub Commands */ |
33 | #define MEI_MC_START_WD_TIMER_REQ 0x13 |
34 | #define MEI_MC_START_WD_TIMER_RES 0x83 |
35 | #define MEI_WDT_STATUS_SUCCESS 0 |
36 | #define MEI_WDT_WDSTATE_NOT_REQUIRED 0x1 |
37 | #define MEI_MC_STOP_WD_TIMER_REQ 0x14 |
38 | |
39 | /** |
40 | * enum mei_wdt_state - internal watchdog state |
41 | * |
42 | * @MEI_WDT_PROBE: wd in probing stage |
43 | * @MEI_WDT_IDLE: wd is idle and not opened |
44 | * @MEI_WDT_START: wd was opened, start was called |
45 | * @MEI_WDT_RUNNING: wd is expecting keep alive pings |
46 | * @MEI_WDT_STOPPING: wd is stopping and will move to IDLE |
47 | * @MEI_WDT_NOT_REQUIRED: wd device is not required |
48 | */ |
49 | enum mei_wdt_state { |
50 | MEI_WDT_PROBE, |
51 | MEI_WDT_IDLE, |
52 | MEI_WDT_START, |
53 | MEI_WDT_RUNNING, |
54 | MEI_WDT_STOPPING, |
55 | MEI_WDT_NOT_REQUIRED, |
56 | }; |
57 | |
58 | static const char *mei_wdt_state_str(enum mei_wdt_state state) |
59 | { |
60 | switch (state) { |
61 | case MEI_WDT_PROBE: |
62 | return "PROBE" ; |
63 | case MEI_WDT_IDLE: |
64 | return "IDLE" ; |
65 | case MEI_WDT_START: |
66 | return "START" ; |
67 | case MEI_WDT_RUNNING: |
68 | return "RUNNING" ; |
69 | case MEI_WDT_STOPPING: |
70 | return "STOPPING" ; |
71 | case MEI_WDT_NOT_REQUIRED: |
72 | return "NOT_REQUIRED" ; |
73 | default: |
74 | return "unknown" ; |
75 | } |
76 | } |
77 | |
78 | /** |
79 | * struct mei_wdt - mei watchdog driver |
80 | * @wdd: watchdog device |
81 | * |
82 | * @cldev: mei watchdog client device |
83 | * @state: watchdog internal state |
84 | * @resp_required: ping required response |
85 | * @response: ping response completion |
86 | * @unregister: unregister worker |
87 | * @reg_lock: watchdog device registration lock |
88 | * @timeout: watchdog current timeout |
89 | * |
90 | * @dbgfs_dir: debugfs dir entry |
91 | */ |
92 | struct mei_wdt { |
93 | struct watchdog_device wdd; |
94 | |
95 | struct mei_cl_device *cldev; |
96 | enum mei_wdt_state state; |
97 | bool resp_required; |
98 | struct completion response; |
99 | struct work_struct unregister; |
100 | struct mutex reg_lock; |
101 | u16 timeout; |
102 | |
103 | #if IS_ENABLED(CONFIG_DEBUG_FS) |
104 | struct dentry *dbgfs_dir; |
105 | #endif /* CONFIG_DEBUG_FS */ |
106 | }; |
107 | |
108 | /** |
109 | * struct mei_mc_hdr - Management Control Command Header |
110 | * |
111 | * @command: Management Control (0x2) |
112 | * @bytecount: Number of bytes in the message beyond this byte |
113 | * @subcommand: Management Control Subcommand |
114 | * @versionnumber: Management Control Version (0x10) |
115 | */ |
116 | struct mei_mc_hdr { |
117 | u8 command; |
118 | u8 bytecount; |
119 | u8 subcommand; |
120 | u8 versionnumber; |
121 | }; |
122 | |
123 | /** |
124 | * struct mei_wdt_start_request - watchdog start/ping |
125 | * |
126 | * @hdr: Management Control Command Header |
127 | * @timeout: timeout value |
128 | * @reserved: reserved (legacy) |
129 | */ |
130 | struct mei_wdt_start_request { |
131 | struct mei_mc_hdr hdr; |
132 | u16 timeout; |
133 | u8 reserved[17]; |
134 | } __packed; |
135 | |
136 | /** |
137 | * struct mei_wdt_start_response - watchdog start/ping response |
138 | * |
139 | * @hdr: Management Control Command Header |
140 | * @status: operation status |
141 | * @wdstate: watchdog status bit mask |
142 | */ |
143 | struct mei_wdt_start_response { |
144 | struct mei_mc_hdr hdr; |
145 | u8 status; |
146 | u8 wdstate; |
147 | } __packed; |
148 | |
149 | /** |
150 | * struct mei_wdt_stop_request - watchdog stop |
151 | * |
152 | * @hdr: Management Control Command Header |
153 | */ |
154 | struct mei_wdt_stop_request { |
155 | struct mei_mc_hdr hdr; |
156 | } __packed; |
157 | |
158 | /** |
159 | * mei_wdt_ping - send wd start/ping command |
160 | * |
161 | * @wdt: mei watchdog device |
162 | * |
163 | * Return: 0 on success, |
164 | * negative errno code on failure |
165 | */ |
166 | static int mei_wdt_ping(struct mei_wdt *wdt) |
167 | { |
168 | struct mei_wdt_start_request req; |
169 | const size_t req_len = sizeof(req); |
170 | int ret; |
171 | |
172 | memset(&req, 0, req_len); |
173 | req.hdr.command = MEI_MANAGEMENT_CONTROL; |
174 | req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand); |
175 | req.hdr.subcommand = MEI_MC_START_WD_TIMER_REQ; |
176 | req.hdr.versionnumber = MEI_MC_VERSION_NUMBER; |
177 | req.timeout = wdt->timeout; |
178 | |
179 | ret = mei_cldev_send(cldev: wdt->cldev, buf: (u8 *)&req, length: req_len); |
180 | if (ret < 0) |
181 | return ret; |
182 | |
183 | return 0; |
184 | } |
185 | |
186 | /** |
187 | * mei_wdt_stop - send wd stop command |
188 | * |
189 | * @wdt: mei watchdog device |
190 | * |
191 | * Return: 0 on success, |
192 | * negative errno code on failure |
193 | */ |
194 | static int mei_wdt_stop(struct mei_wdt *wdt) |
195 | { |
196 | struct mei_wdt_stop_request req; |
197 | const size_t req_len = sizeof(req); |
198 | int ret; |
199 | |
200 | memset(&req, 0, req_len); |
201 | req.hdr.command = MEI_MANAGEMENT_CONTROL; |
202 | req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand); |
203 | req.hdr.subcommand = MEI_MC_STOP_WD_TIMER_REQ; |
204 | req.hdr.versionnumber = MEI_MC_VERSION_NUMBER; |
205 | |
206 | ret = mei_cldev_send(cldev: wdt->cldev, buf: (u8 *)&req, length: req_len); |
207 | if (ret < 0) |
208 | return ret; |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | /** |
214 | * mei_wdt_ops_start - wd start command from the watchdog core. |
215 | * |
216 | * @wdd: watchdog device |
217 | * |
218 | * Return: 0 on success or -ENODEV; |
219 | */ |
220 | static int mei_wdt_ops_start(struct watchdog_device *wdd) |
221 | { |
222 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); |
223 | |
224 | wdt->state = MEI_WDT_START; |
225 | wdd->timeout = wdt->timeout; |
226 | return 0; |
227 | } |
228 | |
229 | /** |
230 | * mei_wdt_ops_stop - wd stop command from the watchdog core. |
231 | * |
232 | * @wdd: watchdog device |
233 | * |
234 | * Return: 0 if success, negative errno code for failure |
235 | */ |
236 | static int mei_wdt_ops_stop(struct watchdog_device *wdd) |
237 | { |
238 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); |
239 | int ret; |
240 | |
241 | if (wdt->state != MEI_WDT_RUNNING) |
242 | return 0; |
243 | |
244 | wdt->state = MEI_WDT_STOPPING; |
245 | |
246 | ret = mei_wdt_stop(wdt); |
247 | if (ret) |
248 | return ret; |
249 | |
250 | wdt->state = MEI_WDT_IDLE; |
251 | |
252 | return 0; |
253 | } |
254 | |
255 | /** |
256 | * mei_wdt_ops_ping - wd ping command from the watchdog core. |
257 | * |
258 | * @wdd: watchdog device |
259 | * |
260 | * Return: 0 if success, negative errno code on failure |
261 | */ |
262 | static int mei_wdt_ops_ping(struct watchdog_device *wdd) |
263 | { |
264 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); |
265 | int ret; |
266 | |
267 | if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING) |
268 | return 0; |
269 | |
270 | if (wdt->resp_required) |
271 | init_completion(x: &wdt->response); |
272 | |
273 | wdt->state = MEI_WDT_RUNNING; |
274 | ret = mei_wdt_ping(wdt); |
275 | if (ret) |
276 | return ret; |
277 | |
278 | if (wdt->resp_required) |
279 | ret = wait_for_completion_killable(x: &wdt->response); |
280 | |
281 | return ret; |
282 | } |
283 | |
284 | /** |
285 | * mei_wdt_ops_set_timeout - wd set timeout command from the watchdog core. |
286 | * |
287 | * @wdd: watchdog device |
288 | * @timeout: timeout value to set |
289 | * |
290 | * Return: 0 if success, negative errno code for failure |
291 | */ |
292 | static int mei_wdt_ops_set_timeout(struct watchdog_device *wdd, |
293 | unsigned int timeout) |
294 | { |
295 | |
296 | struct mei_wdt *wdt = watchdog_get_drvdata(wdd); |
297 | |
298 | /* valid value is already checked by the caller */ |
299 | wdt->timeout = timeout; |
300 | wdd->timeout = timeout; |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static const struct watchdog_ops wd_ops = { |
306 | .owner = THIS_MODULE, |
307 | .start = mei_wdt_ops_start, |
308 | .stop = mei_wdt_ops_stop, |
309 | .ping = mei_wdt_ops_ping, |
310 | .set_timeout = mei_wdt_ops_set_timeout, |
311 | }; |
312 | |
313 | /* not const as the firmware_version field need to be retrieved */ |
314 | static struct watchdog_info wd_info = { |
315 | .identity = INTEL_AMT_WATCHDOG_ID, |
316 | .options = WDIOF_KEEPALIVEPING | |
317 | WDIOF_SETTIMEOUT | |
318 | WDIOF_ALARMONLY, |
319 | }; |
320 | |
321 | /** |
322 | * __mei_wdt_is_registered - check if wdt is registered |
323 | * |
324 | * @wdt: mei watchdog device |
325 | * |
326 | * Return: true if the wdt is registered with the watchdog subsystem |
327 | * Locking: should be called under wdt->reg_lock |
328 | */ |
329 | static inline bool __mei_wdt_is_registered(struct mei_wdt *wdt) |
330 | { |
331 | return !!watchdog_get_drvdata(wdd: &wdt->wdd); |
332 | } |
333 | |
334 | /** |
335 | * mei_wdt_unregister - unregister from the watchdog subsystem |
336 | * |
337 | * @wdt: mei watchdog device |
338 | */ |
339 | static void mei_wdt_unregister(struct mei_wdt *wdt) |
340 | { |
341 | mutex_lock(&wdt->reg_lock); |
342 | |
343 | if (__mei_wdt_is_registered(wdt)) { |
344 | watchdog_unregister_device(&wdt->wdd); |
345 | watchdog_set_drvdata(wdd: &wdt->wdd, NULL); |
346 | memset(&wdt->wdd, 0, sizeof(wdt->wdd)); |
347 | } |
348 | |
349 | mutex_unlock(lock: &wdt->reg_lock); |
350 | } |
351 | |
352 | /** |
353 | * mei_wdt_register - register with the watchdog subsystem |
354 | * |
355 | * @wdt: mei watchdog device |
356 | * |
357 | * Return: 0 if success, negative errno code for failure |
358 | */ |
359 | static int mei_wdt_register(struct mei_wdt *wdt) |
360 | { |
361 | struct device *dev; |
362 | int ret; |
363 | |
364 | if (!wdt || !wdt->cldev) |
365 | return -EINVAL; |
366 | |
367 | dev = &wdt->cldev->dev; |
368 | |
369 | mutex_lock(&wdt->reg_lock); |
370 | |
371 | if (__mei_wdt_is_registered(wdt)) { |
372 | ret = 0; |
373 | goto out; |
374 | } |
375 | |
376 | wdt->wdd.info = &wd_info; |
377 | wdt->wdd.ops = &wd_ops; |
378 | wdt->wdd.parent = dev; |
379 | wdt->wdd.timeout = MEI_WDT_DEFAULT_TIMEOUT; |
380 | wdt->wdd.min_timeout = MEI_WDT_MIN_TIMEOUT; |
381 | wdt->wdd.max_timeout = MEI_WDT_MAX_TIMEOUT; |
382 | |
383 | watchdog_set_drvdata(wdd: &wdt->wdd, data: wdt); |
384 | watchdog_stop_on_reboot(wdd: &wdt->wdd); |
385 | watchdog_stop_on_unregister(wdd: &wdt->wdd); |
386 | |
387 | ret = watchdog_register_device(&wdt->wdd); |
388 | if (ret) |
389 | watchdog_set_drvdata(wdd: &wdt->wdd, NULL); |
390 | |
391 | wdt->state = MEI_WDT_IDLE; |
392 | |
393 | out: |
394 | mutex_unlock(lock: &wdt->reg_lock); |
395 | return ret; |
396 | } |
397 | |
398 | static void mei_wdt_unregister_work(struct work_struct *work) |
399 | { |
400 | struct mei_wdt *wdt = container_of(work, struct mei_wdt, unregister); |
401 | |
402 | mei_wdt_unregister(wdt); |
403 | } |
404 | |
405 | /** |
406 | * mei_wdt_rx - callback for data receive |
407 | * |
408 | * @cldev: bus device |
409 | */ |
410 | static void mei_wdt_rx(struct mei_cl_device *cldev) |
411 | { |
412 | struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); |
413 | struct mei_wdt_start_response res; |
414 | const size_t res_len = sizeof(res); |
415 | int ret; |
416 | |
417 | ret = mei_cldev_recv(cldev: wdt->cldev, buf: (u8 *)&res, length: res_len); |
418 | if (ret < 0) { |
419 | dev_err(&cldev->dev, "failure in recv %d\n" , ret); |
420 | return; |
421 | } |
422 | |
423 | /* Empty response can be sent on stop */ |
424 | if (ret == 0) |
425 | return; |
426 | |
427 | if (ret < sizeof(struct mei_mc_hdr)) { |
428 | dev_err(&cldev->dev, "recv small data %d\n" , ret); |
429 | return; |
430 | } |
431 | |
432 | if (res.hdr.command != MEI_MANAGEMENT_CONTROL || |
433 | res.hdr.versionnumber != MEI_MC_VERSION_NUMBER) { |
434 | dev_err(&cldev->dev, "wrong command received\n" ); |
435 | return; |
436 | } |
437 | |
438 | if (res.hdr.subcommand != MEI_MC_START_WD_TIMER_RES) { |
439 | dev_warn(&cldev->dev, "unsupported command %d :%s[%d]\n" , |
440 | res.hdr.subcommand, |
441 | mei_wdt_state_str(wdt->state), |
442 | wdt->state); |
443 | return; |
444 | } |
445 | |
446 | /* Run the unregistration in a worker as this can be |
447 | * run only after ping completion, otherwise the flow will |
448 | * deadlock on watchdog core mutex. |
449 | */ |
450 | if (wdt->state == MEI_WDT_RUNNING) { |
451 | if (res.wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED) { |
452 | wdt->state = MEI_WDT_NOT_REQUIRED; |
453 | schedule_work(work: &wdt->unregister); |
454 | } |
455 | goto out; |
456 | } |
457 | |
458 | if (wdt->state == MEI_WDT_PROBE) { |
459 | if (res.wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED) { |
460 | wdt->state = MEI_WDT_NOT_REQUIRED; |
461 | } else { |
462 | /* stop the watchdog and register watchdog device */ |
463 | mei_wdt_stop(wdt); |
464 | mei_wdt_register(wdt); |
465 | } |
466 | return; |
467 | } |
468 | |
469 | dev_warn(&cldev->dev, "not in correct state %s[%d]\n" , |
470 | mei_wdt_state_str(wdt->state), wdt->state); |
471 | |
472 | out: |
473 | if (!completion_done(x: &wdt->response)) |
474 | complete(&wdt->response); |
475 | } |
476 | |
477 | /** |
478 | * mei_wdt_notif - callback for event notification |
479 | * |
480 | * @cldev: bus device |
481 | */ |
482 | static void mei_wdt_notif(struct mei_cl_device *cldev) |
483 | { |
484 | struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); |
485 | |
486 | if (wdt->state != MEI_WDT_NOT_REQUIRED) |
487 | return; |
488 | |
489 | mei_wdt_register(wdt); |
490 | } |
491 | |
492 | #if IS_ENABLED(CONFIG_DEBUG_FS) |
493 | |
494 | static ssize_t mei_dbgfs_read_activation(struct file *file, char __user *ubuf, |
495 | size_t cnt, loff_t *ppos) |
496 | { |
497 | struct mei_wdt *wdt = file->private_data; |
498 | const size_t bufsz = 32; |
499 | char buf[32]; |
500 | ssize_t pos; |
501 | |
502 | mutex_lock(&wdt->reg_lock); |
503 | pos = scnprintf(buf, size: bufsz, fmt: "%s\n" , |
504 | __mei_wdt_is_registered(wdt) ? "activated" : "deactivated" ); |
505 | mutex_unlock(lock: &wdt->reg_lock); |
506 | |
507 | return simple_read_from_buffer(to: ubuf, count: cnt, ppos, from: buf, available: pos); |
508 | } |
509 | |
510 | static const struct file_operations dbgfs_fops_activation = { |
511 | .open = simple_open, |
512 | .read = mei_dbgfs_read_activation, |
513 | .llseek = generic_file_llseek, |
514 | }; |
515 | |
516 | static ssize_t mei_dbgfs_read_state(struct file *file, char __user *ubuf, |
517 | size_t cnt, loff_t *ppos) |
518 | { |
519 | struct mei_wdt *wdt = file->private_data; |
520 | char buf[32]; |
521 | ssize_t pos; |
522 | |
523 | pos = scnprintf(buf, size: sizeof(buf), fmt: "state: %s\n" , |
524 | mei_wdt_state_str(state: wdt->state)); |
525 | |
526 | return simple_read_from_buffer(to: ubuf, count: cnt, ppos, from: buf, available: pos); |
527 | } |
528 | |
529 | static const struct file_operations dbgfs_fops_state = { |
530 | .open = simple_open, |
531 | .read = mei_dbgfs_read_state, |
532 | .llseek = generic_file_llseek, |
533 | }; |
534 | |
535 | static void dbgfs_unregister(struct mei_wdt *wdt) |
536 | { |
537 | debugfs_remove_recursive(dentry: wdt->dbgfs_dir); |
538 | wdt->dbgfs_dir = NULL; |
539 | } |
540 | |
541 | static void dbgfs_register(struct mei_wdt *wdt) |
542 | { |
543 | struct dentry *dir; |
544 | |
545 | dir = debugfs_create_dir(KBUILD_MODNAME, NULL); |
546 | wdt->dbgfs_dir = dir; |
547 | |
548 | debugfs_create_file(name: "state" , S_IRUSR, parent: dir, data: wdt, fops: &dbgfs_fops_state); |
549 | |
550 | debugfs_create_file(name: "activation" , S_IRUSR, parent: dir, data: wdt, |
551 | fops: &dbgfs_fops_activation); |
552 | } |
553 | |
554 | #else |
555 | |
556 | static inline void dbgfs_unregister(struct mei_wdt *wdt) {} |
557 | static inline void dbgfs_register(struct mei_wdt *wdt) {} |
558 | #endif /* CONFIG_DEBUG_FS */ |
559 | |
560 | static int mei_wdt_probe(struct mei_cl_device *cldev, |
561 | const struct mei_cl_device_id *id) |
562 | { |
563 | struct mei_wdt *wdt; |
564 | int ret; |
565 | |
566 | wdt = kzalloc(size: sizeof(struct mei_wdt), GFP_KERNEL); |
567 | if (!wdt) |
568 | return -ENOMEM; |
569 | |
570 | wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT; |
571 | wdt->state = MEI_WDT_PROBE; |
572 | wdt->cldev = cldev; |
573 | wdt->resp_required = mei_cldev_ver(cldev) > 0x1; |
574 | mutex_init(&wdt->reg_lock); |
575 | init_completion(x: &wdt->response); |
576 | INIT_WORK(&wdt->unregister, mei_wdt_unregister_work); |
577 | |
578 | mei_cldev_set_drvdata(cldev, data: wdt); |
579 | |
580 | ret = mei_cldev_enable(cldev); |
581 | if (ret < 0) { |
582 | dev_err(&cldev->dev, "Could not enable cl device\n" ); |
583 | goto err_out; |
584 | } |
585 | |
586 | ret = mei_cldev_register_rx_cb(cldev: wdt->cldev, rx_cb: mei_wdt_rx); |
587 | if (ret) { |
588 | dev_err(&cldev->dev, "Could not reg rx event ret=%d\n" , ret); |
589 | goto err_disable; |
590 | } |
591 | |
592 | ret = mei_cldev_register_notif_cb(cldev: wdt->cldev, notif_cb: mei_wdt_notif); |
593 | /* on legacy devices notification is not supported |
594 | */ |
595 | if (ret && ret != -EOPNOTSUPP) { |
596 | dev_err(&cldev->dev, "Could not reg notif event ret=%d\n" , ret); |
597 | goto err_disable; |
598 | } |
599 | |
600 | wd_info.firmware_version = mei_cldev_ver(cldev); |
601 | |
602 | if (wdt->resp_required) |
603 | ret = mei_wdt_ping(wdt); |
604 | else |
605 | ret = mei_wdt_register(wdt); |
606 | |
607 | if (ret) |
608 | goto err_disable; |
609 | |
610 | dbgfs_register(wdt); |
611 | |
612 | return 0; |
613 | |
614 | err_disable: |
615 | mei_cldev_disable(cldev); |
616 | |
617 | err_out: |
618 | kfree(objp: wdt); |
619 | |
620 | return ret; |
621 | } |
622 | |
623 | static void mei_wdt_remove(struct mei_cl_device *cldev) |
624 | { |
625 | struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev); |
626 | |
627 | /* Free the caller in case of fw initiated or unexpected reset */ |
628 | if (!completion_done(x: &wdt->response)) |
629 | complete(&wdt->response); |
630 | |
631 | cancel_work_sync(work: &wdt->unregister); |
632 | |
633 | mei_wdt_unregister(wdt); |
634 | |
635 | mei_cldev_disable(cldev); |
636 | |
637 | dbgfs_unregister(wdt); |
638 | |
639 | kfree(objp: wdt); |
640 | } |
641 | |
642 | #define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \ |
643 | 0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB) |
644 | |
645 | static const struct mei_cl_device_id mei_wdt_tbl[] = { |
646 | { .uuid = MEI_UUID_WD, .version = MEI_CL_VERSION_ANY }, |
647 | /* required last entry */ |
648 | { } |
649 | }; |
650 | MODULE_DEVICE_TABLE(mei, mei_wdt_tbl); |
651 | |
652 | static struct mei_cl_driver mei_wdt_driver = { |
653 | .id_table = mei_wdt_tbl, |
654 | .name = KBUILD_MODNAME, |
655 | |
656 | .probe = mei_wdt_probe, |
657 | .remove = mei_wdt_remove, |
658 | }; |
659 | |
660 | module_mei_cl_driver(mei_wdt_driver); |
661 | |
662 | MODULE_AUTHOR("Intel Corporation" ); |
663 | MODULE_LICENSE("GPL v2" ); |
664 | MODULE_DESCRIPTION("Device driver for Intel MEI iAMT watchdog" ); |
665 | |