diff options
Diffstat (limited to 'drivers/misc/mei/wd.c')
-rw-r--r-- | drivers/misc/mei/wd.c | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c new file mode 100644 index 00000000000..6be5605707b --- /dev/null +++ b/drivers/misc/mei/wd.c | |||
@@ -0,0 +1,379 @@ | |||
1 | /* | ||
2 | * | ||
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | ||
4 | * Copyright (c) 2003-2012, Intel Corporation. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms and conditions of the GNU General Public License, | ||
8 | * version 2, as published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | * | ||
15 | */ | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/moduleparam.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/pci.h> | ||
21 | #include <linux/sched.h> | ||
22 | #include <linux/watchdog.h> | ||
23 | |||
24 | #include "mei_dev.h" | ||
25 | #include "hw.h" | ||
26 | #include "interface.h" | ||
27 | #include <linux/mei.h> | ||
28 | |||
29 | static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; | ||
30 | static const u8 mei_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 }; | ||
31 | |||
32 | const u8 mei_wd_state_independence_msg[3][4] = { | ||
33 | {0x05, 0x02, 0x51, 0x10}, | ||
34 | {0x05, 0x02, 0x52, 0x10}, | ||
35 | {0x07, 0x02, 0x01, 0x10} | ||
36 | }; | ||
37 | |||
38 | /* | ||
39 | * AMT Watchdog Device | ||
40 | */ | ||
41 | #define INTEL_AMT_WATCHDOG_ID "INTCAMT" | ||
42 | |||
43 | /* UUIDs for AMT F/W clients */ | ||
44 | const uuid_le mei_wd_guid = UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, 0x89, | ||
45 | 0x9D, 0xA9, 0x15, 0x14, 0xCB, | ||
46 | 0x32, 0xAB); | ||
47 | |||
48 | static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout) | ||
49 | { | ||
50 | dev_dbg(&dev->pdev->dev, "wd: set timeout=%d.\n", timeout); | ||
51 | memcpy(dev->wd_data, mei_start_wd_params, MEI_WD_PARAMS_SIZE); | ||
52 | memcpy(dev->wd_data + MEI_WD_PARAMS_SIZE, &timeout, sizeof(u16)); | ||
53 | } | ||
54 | |||
55 | /** | ||
56 | * host_init_wd - mei initialization wd. | ||
57 | * | ||
58 | * @dev: the device structure | ||
59 | * returns -ENENT if wd client cannot be found | ||
60 | * -EIO if write has failed | ||
61 | */ | ||
62 | int mei_wd_host_init(struct mei_device *dev) | ||
63 | { | ||
64 | mei_cl_init(&dev->wd_cl, dev); | ||
65 | |||
66 | /* look for WD client and connect to it */ | ||
67 | dev->wd_cl.state = MEI_FILE_DISCONNECTED; | ||
68 | dev->wd_timeout = AMT_WD_DEFAULT_TIMEOUT; | ||
69 | |||
70 | /* find ME WD client */ | ||
71 | mei_find_me_client_update_filext(dev, &dev->wd_cl, | ||
72 | &mei_wd_guid, MEI_WD_HOST_CLIENT_ID); | ||
73 | |||
74 | dev_dbg(&dev->pdev->dev, "wd: check client\n"); | ||
75 | if (MEI_FILE_CONNECTING != dev->wd_cl.state) { | ||
76 | dev_info(&dev->pdev->dev, "wd: failed to find the client\n"); | ||
77 | return -ENOENT; | ||
78 | } | ||
79 | |||
80 | if (mei_connect(dev, &dev->wd_cl)) { | ||
81 | dev_err(&dev->pdev->dev, "wd: failed to connect to the client\n"); | ||
82 | dev->wd_cl.state = MEI_FILE_DISCONNECTED; | ||
83 | dev->wd_cl.host_client_id = 0; | ||
84 | return -EIO; | ||
85 | } | ||
86 | dev->wd_cl.timer_count = CONNECT_TIMEOUT; | ||
87 | |||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * mei_wd_send - sends watch dog message to fw. | ||
93 | * | ||
94 | * @dev: the device structure | ||
95 | * | ||
96 | * returns 0 if success, | ||
97 | * -EIO when message send fails | ||
98 | * -EINVAL when invalid message is to be sent | ||
99 | */ | ||
100 | int mei_wd_send(struct mei_device *dev) | ||
101 | { | ||
102 | struct mei_msg_hdr *mei_hdr; | ||
103 | |||
104 | mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; | ||
105 | mei_hdr->host_addr = dev->wd_cl.host_client_id; | ||
106 | mei_hdr->me_addr = dev->wd_cl.me_client_id; | ||
107 | mei_hdr->msg_complete = 1; | ||
108 | mei_hdr->reserved = 0; | ||
109 | |||
110 | if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_PARAMS_SIZE)) | ||
111 | mei_hdr->length = MEI_START_WD_DATA_SIZE; | ||
112 | else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_PARAMS_SIZE)) | ||
113 | mei_hdr->length = MEI_WD_PARAMS_SIZE; | ||
114 | else | ||
115 | return -EINVAL; | ||
116 | |||
117 | return mei_write_message(dev, mei_hdr, dev->wd_data, mei_hdr->length); | ||
118 | } | ||
119 | |||
120 | /** | ||
121 | * mei_wd_stop - sends watchdog stop message to fw. | ||
122 | * | ||
123 | * @dev: the device structure | ||
124 | * @preserve: indicate if to keep the timeout value | ||
125 | * | ||
126 | * returns 0 if success, | ||
127 | * -EIO when message send fails | ||
128 | * -EINVAL when invalid message is to be sent | ||
129 | */ | ||
130 | int mei_wd_stop(struct mei_device *dev, bool preserve) | ||
131 | { | ||
132 | int ret; | ||
133 | u16 wd_timeout = dev->wd_timeout; | ||
134 | |||
135 | cancel_delayed_work(&dev->timer_work); | ||
136 | if (dev->wd_cl.state != MEI_FILE_CONNECTED || !dev->wd_timeout) | ||
137 | return 0; | ||
138 | |||
139 | dev->wd_timeout = 0; | ||
140 | dev->wd_due_counter = 0; | ||
141 | memcpy(dev->wd_data, mei_stop_wd_params, MEI_WD_PARAMS_SIZE); | ||
142 | dev->stop = true; | ||
143 | |||
144 | ret = mei_flow_ctrl_creds(dev, &dev->wd_cl); | ||
145 | if (ret < 0) | ||
146 | goto out; | ||
147 | |||
148 | if (ret && dev->mei_host_buffer_is_empty) { | ||
149 | ret = 0; | ||
150 | dev->mei_host_buffer_is_empty = false; | ||
151 | |||
152 | if (!mei_wd_send(dev)) { | ||
153 | ret = mei_flow_ctrl_reduce(dev, &dev->wd_cl); | ||
154 | if (ret) | ||
155 | goto out; | ||
156 | } else { | ||
157 | dev_err(&dev->pdev->dev, "wd: send stop failed\n"); | ||
158 | } | ||
159 | |||
160 | dev->wd_pending = false; | ||
161 | } else { | ||
162 | dev->wd_pending = true; | ||
163 | } | ||
164 | dev->wd_stopped = false; | ||
165 | mutex_unlock(&dev->device_lock); | ||
166 | |||
167 | ret = wait_event_interruptible_timeout(dev->wait_stop_wd, | ||
168 | dev->wd_stopped, 10 * HZ); | ||
169 | mutex_lock(&dev->device_lock); | ||
170 | if (dev->wd_stopped) { | ||
171 | dev_dbg(&dev->pdev->dev, "wd: stop completed ret=%d.\n", ret); | ||
172 | ret = 0; | ||
173 | } else { | ||
174 | if (!ret) | ||
175 | ret = -ETIMEDOUT; | ||
176 | dev_warn(&dev->pdev->dev, | ||
177 | "wd: stop failed to complete ret=%d.\n", ret); | ||
178 | } | ||
179 | |||
180 | if (preserve) | ||
181 | dev->wd_timeout = wd_timeout; | ||
182 | |||
183 | out: | ||
184 | return ret; | ||
185 | } | ||
186 | |||
187 | /* | ||
188 | * mei_wd_ops_start - wd start command from the watchdog core. | ||
189 | * | ||
190 | * @wd_dev - watchdog device struct | ||
191 | * | ||
192 | * returns 0 if success, negative errno code for failure | ||
193 | */ | ||
194 | static int mei_wd_ops_start(struct watchdog_device *wd_dev) | ||
195 | { | ||
196 | int err = -ENODEV; | ||
197 | struct mei_device *dev; | ||
198 | |||
199 | dev = pci_get_drvdata(mei_device); | ||
200 | if (!dev) | ||
201 | return -ENODEV; | ||
202 | |||
203 | mutex_lock(&dev->device_lock); | ||
204 | |||
205 | if (dev->mei_state != MEI_ENABLED) { | ||
206 | dev_dbg(&dev->pdev->dev, | ||
207 | "wd: mei_state != MEI_ENABLED mei_state = %d\n", | ||
208 | dev->mei_state); | ||
209 | goto end_unlock; | ||
210 | } | ||
211 | |||
212 | if (dev->wd_cl.state != MEI_FILE_CONNECTED) { | ||
213 | dev_dbg(&dev->pdev->dev, | ||
214 | "MEI Driver is not connected to Watchdog Client\n"); | ||
215 | goto end_unlock; | ||
216 | } | ||
217 | |||
218 | mei_wd_set_start_timeout(dev, dev->wd_timeout); | ||
219 | |||
220 | err = 0; | ||
221 | end_unlock: | ||
222 | mutex_unlock(&dev->device_lock); | ||
223 | return err; | ||
224 | } | ||
225 | |||
226 | /* | ||
227 | * mei_wd_ops_stop - wd stop command from the watchdog core. | ||
228 | * | ||
229 | * @wd_dev - watchdog device struct | ||
230 | * | ||
231 | * returns 0 if success, negative errno code for failure | ||
232 | */ | ||
233 | static int mei_wd_ops_stop(struct watchdog_device *wd_dev) | ||
234 | { | ||
235 | struct mei_device *dev; | ||
236 | dev = pci_get_drvdata(mei_device); | ||
237 | |||
238 | if (!dev) | ||
239 | return -ENODEV; | ||
240 | |||
241 | mutex_lock(&dev->device_lock); | ||
242 | mei_wd_stop(dev, false); | ||
243 | mutex_unlock(&dev->device_lock); | ||
244 | |||
245 | return 0; | ||
246 | } | ||
247 | |||
248 | /* | ||
249 | * mei_wd_ops_ping - wd ping command from the watchdog core. | ||
250 | * | ||
251 | * @wd_dev - watchdog device struct | ||
252 | * | ||
253 | * returns 0 if success, negative errno code for failure | ||
254 | */ | ||
255 | static int mei_wd_ops_ping(struct watchdog_device *wd_dev) | ||
256 | { | ||
257 | int ret = 0; | ||
258 | struct mei_device *dev; | ||
259 | dev = pci_get_drvdata(mei_device); | ||
260 | |||
261 | if (!dev) | ||
262 | return -ENODEV; | ||
263 | |||
264 | mutex_lock(&dev->device_lock); | ||
265 | |||
266 | if (dev->wd_cl.state != MEI_FILE_CONNECTED) { | ||
267 | dev_err(&dev->pdev->dev, "wd: not connected.\n"); | ||
268 | ret = -ENODEV; | ||
269 | goto end; | ||
270 | } | ||
271 | |||
272 | /* Check if we can send the ping to HW*/ | ||
273 | if (dev->mei_host_buffer_is_empty && | ||
274 | mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) { | ||
275 | |||
276 | dev->mei_host_buffer_is_empty = false; | ||
277 | dev_dbg(&dev->pdev->dev, "wd: sending ping\n"); | ||
278 | |||
279 | if (mei_wd_send(dev)) { | ||
280 | dev_err(&dev->pdev->dev, "wd: send failed.\n"); | ||
281 | ret = -EIO; | ||
282 | goto end; | ||
283 | } | ||
284 | |||
285 | if (mei_flow_ctrl_reduce(dev, &dev->wd_cl)) { | ||
286 | dev_err(&dev->pdev->dev, | ||
287 | "wd: mei_flow_ctrl_reduce() failed.\n"); | ||
288 | ret = -EIO; | ||
289 | goto end; | ||
290 | } | ||
291 | |||
292 | } else { | ||
293 | dev->wd_pending = true; | ||
294 | } | ||
295 | |||
296 | end: | ||
297 | mutex_unlock(&dev->device_lock); | ||
298 | return ret; | ||
299 | } | ||
300 | |||
301 | /* | ||
302 | * mei_wd_ops_set_timeout - wd set timeout command from the watchdog core. | ||
303 | * | ||
304 | * @wd_dev - watchdog device struct | ||
305 | * @timeout - timeout value to set | ||
306 | * | ||
307 | * returns 0 if success, negative errno code for failure | ||
308 | */ | ||
309 | static int mei_wd_ops_set_timeout(struct watchdog_device *wd_dev, unsigned int timeout) | ||
310 | { | ||
311 | struct mei_device *dev; | ||
312 | dev = pci_get_drvdata(mei_device); | ||
313 | |||
314 | if (!dev) | ||
315 | return -ENODEV; | ||
316 | |||
317 | /* Check Timeout value */ | ||
318 | if (timeout < AMT_WD_MIN_TIMEOUT || timeout > AMT_WD_MAX_TIMEOUT) | ||
319 | return -EINVAL; | ||
320 | |||
321 | mutex_lock(&dev->device_lock); | ||
322 | |||
323 | dev->wd_timeout = timeout; | ||
324 | wd_dev->timeout = timeout; | ||
325 | mei_wd_set_start_timeout(dev, dev->wd_timeout); | ||
326 | |||
327 | mutex_unlock(&dev->device_lock); | ||
328 | |||
329 | return 0; | ||
330 | } | ||
331 | |||
332 | /* | ||
333 | * Watchdog Device structs | ||
334 | */ | ||
335 | static const struct watchdog_ops wd_ops = { | ||
336 | .owner = THIS_MODULE, | ||
337 | .start = mei_wd_ops_start, | ||
338 | .stop = mei_wd_ops_stop, | ||
339 | .ping = mei_wd_ops_ping, | ||
340 | .set_timeout = mei_wd_ops_set_timeout, | ||
341 | }; | ||
342 | static const struct watchdog_info wd_info = { | ||
343 | .identity = INTEL_AMT_WATCHDOG_ID, | ||
344 | .options = WDIOF_KEEPALIVEPING, | ||
345 | }; | ||
346 | |||
347 | static struct watchdog_device amt_wd_dev = { | ||
348 | .info = &wd_info, | ||
349 | .ops = &wd_ops, | ||
350 | .timeout = AMT_WD_DEFAULT_TIMEOUT, | ||
351 | .min_timeout = AMT_WD_MIN_TIMEOUT, | ||
352 | .max_timeout = AMT_WD_MAX_TIMEOUT, | ||
353 | }; | ||
354 | |||
355 | |||
356 | void mei_watchdog_register(struct mei_device *dev) | ||
357 | { | ||
358 | dev_dbg(&dev->pdev->dev, "dev->wd_timeout =%d.\n", dev->wd_timeout); | ||
359 | |||
360 | dev->wd_due_counter = !!dev->wd_timeout; | ||
361 | |||
362 | if (watchdog_register_device(&amt_wd_dev)) { | ||
363 | dev_err(&dev->pdev->dev, | ||
364 | "wd: unable to register watchdog device.\n"); | ||
365 | dev->wd_interface_reg = false; | ||
366 | } else { | ||
367 | dev_dbg(&dev->pdev->dev, | ||
368 | "wd: successfully register watchdog interface.\n"); | ||
369 | dev->wd_interface_reg = true; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | void mei_watchdog_unregister(struct mei_device *dev) | ||
374 | { | ||
375 | if (dev->wd_interface_reg) | ||
376 | watchdog_unregister_device(&amt_wd_dev); | ||
377 | dev->wd_interface_reg = false; | ||
378 | } | ||
379 | |||