Commit | Line | Data |
---|---|---|
3ceb3e66 OW |
1 | /* |
2 | * | |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | |
733ba91c | 4 | * Copyright (c) 2003-2012, Intel Corporation. |
3ceb3e66 OW |
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> | |
3ceb3e66 | 20 | #include <linux/sched.h> |
9ce178e5 | 21 | #include <linux/watchdog.h> |
3ceb3e66 | 22 | |
47a73801 TW |
23 | #include <linux/mei.h> |
24 | ||
3ceb3e66 | 25 | #include "mei_dev.h" |
0edb23fc | 26 | #include "hbm.h" |
90e0b5f1 | 27 | #include "client.h" |
3ceb3e66 | 28 | |
3ceb3e66 OW |
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 | ||
70cd5337 TW |
32 | /* |
33 | * AMT Watchdog Device | |
34 | */ | |
35 | #define INTEL_AMT_WATCHDOG_ID "INTCAMT" | |
36 | ||
3ceb3e66 OW |
37 | /* UUIDs for AMT F/W clients */ |
38 | const uuid_le mei_wd_guid = UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, 0x89, | |
39 | 0x9D, 0xA9, 0x15, 0x14, 0xCB, | |
40 | 0x32, 0xAB); | |
41 | ||
5192dda1 | 42 | static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout) |
3ceb3e66 | 43 | { |
2bf94cab | 44 | dev_dbg(dev->dev, "wd: set timeout=%d.\n", timeout); |
248ffdf7 TW |
45 | memcpy(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE); |
46 | memcpy(dev->wd_data + MEI_WD_HDR_SIZE, &timeout, sizeof(u16)); | |
3ceb3e66 OW |
47 | } |
48 | ||
49 | /** | |
7cb1ba9b | 50 | * mei_wd_host_init - connect to the watchdog client |
3ceb3e66 OW |
51 | * |
52 | * @dev: the device structure | |
d49ed64a | 53 | * @me_cl: me client |
393b148f | 54 | * |
a8605ea2 | 55 | * Return: -ENOTTY if wd client cannot be found |
9487eb0a | 56 | * -EIO if write has failed |
7cb1ba9b | 57 | * 0 on success |
3ceb3e66 | 58 | */ |
d49ed64a | 59 | int mei_wd_host_init(struct mei_device *dev, struct mei_me_client *me_cl) |
3ceb3e66 | 60 | { |
781d0d89 | 61 | struct mei_cl *cl = &dev->wd_cl; |
781d0d89 TW |
62 | int ret; |
63 | ||
64 | mei_cl_init(cl, dev); | |
3ceb3e66 | 65 | |
248ffdf7 | 66 | dev->wd_timeout = MEI_WD_DEFAULT_TIMEOUT; |
c216fdeb | 67 | dev->wd_state = MEI_WD_IDLE; |
3ceb3e66 | 68 | |
781d0d89 | 69 | ret = mei_cl_link(cl, MEI_WD_HOST_CLIENT_ID); |
781d0d89 | 70 | if (ret < 0) { |
2bf94cab | 71 | dev_info(dev->dev, "wd: failed link client\n"); |
50f67a06 | 72 | return ret; |
781d0d89 TW |
73 | } |
74 | ||
d49ed64a | 75 | ret = mei_cl_connect(cl, me_cl, NULL); |
64092858 | 76 | if (ret) { |
2bf94cab | 77 | dev_err(dev->dev, "wd: failed to connect = %d\n", ret); |
64092858 TW |
78 | mei_cl_unlink(cl); |
79 | return ret; | |
3ceb3e66 | 80 | } |
617aa396 | 81 | |
64092858 TW |
82 | ret = mei_watchdog_register(dev); |
83 | if (ret) { | |
84 | mei_cl_disconnect(cl); | |
85 | mei_cl_unlink(cl); | |
86 | } | |
87 | return ret; | |
3ceb3e66 OW |
88 | } |
89 | ||
90 | /** | |
91 | * mei_wd_send - sends watch dog message to fw. | |
92 | * | |
93 | * @dev: the device structure | |
94 | * | |
a8605ea2 | 95 | * Return: 0 if success, |
3ceb3e66 OW |
96 | * -EIO when message send fails |
97 | * -EINVAL when invalid message is to be sent | |
b6d81fd6 | 98 | * -ENODEV on flow control failure |
3ceb3e66 OW |
99 | */ |
100 | int mei_wd_send(struct mei_device *dev) | |
101 | { | |
b6d81fd6 | 102 | struct mei_cl *cl = &dev->wd_cl; |
e46f1874 | 103 | struct mei_msg_hdr hdr; |
b6d81fd6 | 104 | int ret; |
3ceb3e66 | 105 | |
b6d81fd6 | 106 | hdr.host_addr = cl->host_client_id; |
d49ed64a | 107 | hdr.me_addr = mei_cl_me_id(cl); |
e46f1874 TW |
108 | hdr.msg_complete = 1; |
109 | hdr.reserved = 0; | |
479327fc | 110 | hdr.internal = 0; |
3ceb3e66 | 111 | |
248ffdf7 | 112 | if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE)) |
e46f1874 | 113 | hdr.length = MEI_WD_START_MSG_SIZE; |
248ffdf7 | 114 | else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_HDR_SIZE)) |
e46f1874 | 115 | hdr.length = MEI_WD_STOP_MSG_SIZE; |
b6d81fd6 | 116 | else { |
2bf94cab | 117 | dev_err(dev->dev, "wd: invalid message is to be sent, aborting\n"); |
3ceb3e66 | 118 | return -EINVAL; |
b6d81fd6 TW |
119 | } |
120 | ||
121 | ret = mei_write_message(dev, &hdr, dev->wd_data); | |
122 | if (ret) { | |
2bf94cab | 123 | dev_err(dev->dev, "wd: write message failed\n"); |
b6d81fd6 TW |
124 | return ret; |
125 | } | |
3ceb3e66 | 126 | |
b6d81fd6 TW |
127 | ret = mei_cl_flow_ctrl_reduce(cl); |
128 | if (ret) { | |
2bf94cab | 129 | dev_err(dev->dev, "wd: flow_ctrl_reduce failed.\n"); |
b6d81fd6 TW |
130 | return ret; |
131 | } | |
132 | ||
133 | return 0; | |
3ceb3e66 OW |
134 | } |
135 | ||
d8deca31 OW |
136 | /** |
137 | * mei_wd_stop - sends watchdog stop message to fw. | |
138 | * | |
139 | * @dev: the device structure | |
d8deca31 | 140 | * |
a8605ea2 | 141 | * Return: 0 if success |
5877255d TW |
142 | * on error: |
143 | * -EIO when message send fails | |
d8deca31 | 144 | * -EINVAL when invalid message is to be sent |
5877255d | 145 | * -ETIME on message timeout |
d8deca31 | 146 | */ |
c216fdeb | 147 | int mei_wd_stop(struct mei_device *dev) |
3ceb3e66 | 148 | { |
f3de9b63 | 149 | struct mei_cl *cl = &dev->wd_cl; |
3ceb3e66 | 150 | int ret; |
3ceb3e66 | 151 | |
f3de9b63 | 152 | if (!mei_cl_is_connected(cl) || |
c216fdeb | 153 | dev->wd_state != MEI_WD_RUNNING) |
3ceb3e66 OW |
154 | return 0; |
155 | ||
248ffdf7 | 156 | memcpy(dev->wd_data, mei_stop_wd_params, MEI_WD_STOP_MSG_SIZE); |
c216fdeb TW |
157 | |
158 | dev->wd_state = MEI_WD_STOPPING; | |
3ceb3e66 | 159 | |
f3de9b63 | 160 | ret = mei_cl_flow_ctrl_creds(cl); |
3ceb3e66 | 161 | if (ret < 0) |
5877255d | 162 | goto err; |
3ceb3e66 | 163 | |
6aae48ff | 164 | if (ret && mei_hbuf_acquire(dev)) { |
b6d81fd6 TW |
165 | ret = mei_wd_send(dev); |
166 | if (ret) | |
5877255d | 167 | goto err; |
eb9af0ac | 168 | dev->wd_pending = false; |
3ceb3e66 | 169 | } else { |
eb9af0ac | 170 | dev->wd_pending = true; |
3ceb3e66 | 171 | } |
c216fdeb | 172 | |
3ceb3e66 OW |
173 | mutex_unlock(&dev->device_lock); |
174 | ||
5877255d TW |
175 | ret = wait_event_timeout(dev->wait_stop_wd, |
176 | dev->wd_state == MEI_WD_IDLE, | |
177 | msecs_to_jiffies(MEI_WD_STOP_TIMEOUT)); | |
3ceb3e66 | 178 | mutex_lock(&dev->device_lock); |
5877255d TW |
179 | if (dev->wd_state != MEI_WD_IDLE) { |
180 | /* timeout */ | |
181 | ret = -ETIME; | |
2bf94cab | 182 | dev_warn(dev->dev, "wd: stop failed to complete ret=%d\n", ret); |
5877255d | 183 | goto err; |
a534bb6e | 184 | } |
2bf94cab | 185 | dev_dbg(dev->dev, "wd: stop completed after %u msec\n", |
5877255d TW |
186 | MEI_WD_STOP_TIMEOUT - jiffies_to_msecs(ret)); |
187 | return 0; | |
188 | err: | |
3ceb3e66 OW |
189 | return ret; |
190 | } | |
191 | ||
3908be6f | 192 | /** |
4a3cafd5 OW |
193 | * mei_wd_ops_start - wd start command from the watchdog core. |
194 | * | |
3908be6f | 195 | * @wd_dev: watchdog device struct |
4a3cafd5 | 196 | * |
a8605ea2 | 197 | * Return: 0 if success, negative errno code for failure |
4a3cafd5 OW |
198 | */ |
199 | static int mei_wd_ops_start(struct watchdog_device *wd_dev) | |
200 | { | |
4a3cafd5 | 201 | struct mei_device *dev; |
f3de9b63 TW |
202 | struct mei_cl *cl; |
203 | int err = -ENODEV; | |
4a3cafd5 | 204 | |
09649a85 | 205 | dev = watchdog_get_drvdata(wd_dev); |
4a3cafd5 OW |
206 | if (!dev) |
207 | return -ENODEV; | |
208 | ||
f3de9b63 TW |
209 | cl = &dev->wd_cl; |
210 | ||
4a3cafd5 OW |
211 | mutex_lock(&dev->device_lock); |
212 | ||
b210d750 | 213 | if (dev->dev_state != MEI_DEV_ENABLED) { |
2bf94cab | 214 | dev_dbg(dev->dev, "wd: dev_state != MEI_DEV_ENABLED dev_state = %s\n", |
b210d750 | 215 | mei_dev_state_str(dev->dev_state)); |
4a3cafd5 OW |
216 | goto end_unlock; |
217 | } | |
218 | ||
f3de9b63 TW |
219 | if (!mei_cl_is_connected(cl)) { |
220 | cl_dbg(dev, cl, "MEI Driver is not connected to Watchdog Client\n"); | |
4a3cafd5 OW |
221 | goto end_unlock; |
222 | } | |
223 | ||
55d33856 | 224 | mei_wd_set_start_timeout(dev, dev->wd_timeout); |
4a3cafd5 OW |
225 | |
226 | err = 0; | |
227 | end_unlock: | |
228 | mutex_unlock(&dev->device_lock); | |
229 | return err; | |
230 | } | |
231 | ||
3908be6f | 232 | /** |
4a3cafd5 OW |
233 | * mei_wd_ops_stop - wd stop command from the watchdog core. |
234 | * | |
3908be6f | 235 | * @wd_dev: watchdog device struct |
4a3cafd5 | 236 | * |
a8605ea2 | 237 | * Return: 0 if success, negative errno code for failure |
4a3cafd5 OW |
238 | */ |
239 | static int mei_wd_ops_stop(struct watchdog_device *wd_dev) | |
240 | { | |
241 | struct mei_device *dev; | |
4a3cafd5 | 242 | |
09649a85 | 243 | dev = watchdog_get_drvdata(wd_dev); |
4a3cafd5 OW |
244 | if (!dev) |
245 | return -ENODEV; | |
246 | ||
247 | mutex_lock(&dev->device_lock); | |
c216fdeb | 248 | mei_wd_stop(dev); |
4a3cafd5 OW |
249 | mutex_unlock(&dev->device_lock); |
250 | ||
251 | return 0; | |
252 | } | |
253 | ||
3908be6f | 254 | /** |
8c4a59a7 OW |
255 | * mei_wd_ops_ping - wd ping command from the watchdog core. |
256 | * | |
3908be6f | 257 | * @wd_dev: watchdog device struct |
8c4a59a7 | 258 | * |
a8605ea2 | 259 | * Return: 0 if success, negative errno code for failure |
8c4a59a7 OW |
260 | */ |
261 | static int mei_wd_ops_ping(struct watchdog_device *wd_dev) | |
262 | { | |
8c4a59a7 | 263 | struct mei_device *dev; |
3d32cf02 | 264 | struct mei_cl *cl; |
6aae48ff | 265 | int ret; |
8c4a59a7 | 266 | |
09649a85 | 267 | dev = watchdog_get_drvdata(wd_dev); |
8c4a59a7 OW |
268 | if (!dev) |
269 | return -ENODEV; | |
270 | ||
3d32cf02 TW |
271 | cl = &dev->wd_cl; |
272 | ||
8c4a59a7 OW |
273 | mutex_lock(&dev->device_lock); |
274 | ||
f3de9b63 TW |
275 | if (!mei_cl_is_connected(cl)) { |
276 | cl_err(dev, cl, "wd: not connected.\n"); | |
8c4a59a7 OW |
277 | ret = -ENODEV; |
278 | goto end; | |
279 | } | |
280 | ||
c216fdeb TW |
281 | dev->wd_state = MEI_WD_RUNNING; |
282 | ||
3d32cf02 | 283 | ret = mei_cl_flow_ctrl_creds(cl); |
6aae48ff TW |
284 | if (ret < 0) |
285 | goto end; | |
3d32cf02 | 286 | |
8c4a59a7 | 287 | /* Check if we can send the ping to HW*/ |
6aae48ff | 288 | if (ret && mei_hbuf_acquire(dev)) { |
2bf94cab | 289 | dev_dbg(dev->dev, "wd: sending ping\n"); |
8c4a59a7 | 290 | |
b6d81fd6 TW |
291 | ret = mei_wd_send(dev); |
292 | if (ret) | |
8c4a59a7 | 293 | goto end; |
b6d81fd6 | 294 | dev->wd_pending = false; |
8c4a59a7 OW |
295 | } else { |
296 | dev->wd_pending = true; | |
297 | } | |
298 | ||
299 | end: | |
300 | mutex_unlock(&dev->device_lock); | |
301 | return ret; | |
302 | } | |
303 | ||
3908be6f | 304 | /** |
55d33856 OW |
305 | * mei_wd_ops_set_timeout - wd set timeout command from the watchdog core. |
306 | * | |
3908be6f AU |
307 | * @wd_dev: watchdog device struct |
308 | * @timeout: timeout value to set | |
55d33856 | 309 | * |
a8605ea2 | 310 | * Return: 0 if success, negative errno code for failure |
55d33856 | 311 | */ |
5f1e54e8 TW |
312 | static int mei_wd_ops_set_timeout(struct watchdog_device *wd_dev, |
313 | unsigned int timeout) | |
55d33856 OW |
314 | { |
315 | struct mei_device *dev; | |
55d33856 | 316 | |
09649a85 | 317 | dev = watchdog_get_drvdata(wd_dev); |
55d33856 OW |
318 | if (!dev) |
319 | return -ENODEV; | |
320 | ||
321 | /* Check Timeout value */ | |
248ffdf7 | 322 | if (timeout < MEI_WD_MIN_TIMEOUT || timeout > MEI_WD_MAX_TIMEOUT) |
55d33856 OW |
323 | return -EINVAL; |
324 | ||
325 | mutex_lock(&dev->device_lock); | |
326 | ||
327 | dev->wd_timeout = timeout; | |
0197c1c4 | 328 | wd_dev->timeout = timeout; |
55d33856 OW |
329 | mei_wd_set_start_timeout(dev, dev->wd_timeout); |
330 | ||
331 | mutex_unlock(&dev->device_lock); | |
332 | ||
333 | return 0; | |
334 | } | |
335 | ||
4a3cafd5 OW |
336 | /* |
337 | * Watchdog Device structs | |
338 | */ | |
575c1e43 | 339 | static const struct watchdog_ops wd_ops = { |
4a3cafd5 OW |
340 | .owner = THIS_MODULE, |
341 | .start = mei_wd_ops_start, | |
342 | .stop = mei_wd_ops_stop, | |
8c4a59a7 | 343 | .ping = mei_wd_ops_ping, |
55d33856 | 344 | .set_timeout = mei_wd_ops_set_timeout, |
4a3cafd5 | 345 | }; |
575c1e43 | 346 | static const struct watchdog_info wd_info = { |
4a3cafd5 | 347 | .identity = INTEL_AMT_WATCHDOG_ID, |
c8df7292 TW |
348 | .options = WDIOF_KEEPALIVEPING | |
349 | WDIOF_SETTIMEOUT | | |
350 | WDIOF_ALARMONLY, | |
4a3cafd5 OW |
351 | }; |
352 | ||
68d923d5 | 353 | static struct watchdog_device amt_wd_dev = { |
4a3cafd5 OW |
354 | .info = &wd_info, |
355 | .ops = &wd_ops, | |
248ffdf7 TW |
356 | .timeout = MEI_WD_DEFAULT_TIMEOUT, |
357 | .min_timeout = MEI_WD_MIN_TIMEOUT, | |
358 | .max_timeout = MEI_WD_MAX_TIMEOUT, | |
4a3cafd5 OW |
359 | }; |
360 | ||
361 | ||
64092858 | 362 | int mei_watchdog_register(struct mei_device *dev) |
70cd5337 | 363 | { |
64092858 TW |
364 | |
365 | int ret; | |
366 | ||
6551881c | 367 | amt_wd_dev.parent = dev->dev; |
64092858 TW |
368 | /* unlock to perserve correct locking order */ |
369 | mutex_unlock(&dev->device_lock); | |
370 | ret = watchdog_register_device(&amt_wd_dev); | |
371 | mutex_lock(&dev->device_lock); | |
372 | if (ret) { | |
2bf94cab | 373 | dev_err(dev->dev, "wd: unable to register watchdog device = %d.\n", |
64092858 TW |
374 | ret); |
375 | return ret; | |
70cd5337 | 376 | } |
09649a85 | 377 | |
2bf94cab | 378 | dev_dbg(dev->dev, "wd: successfully register watchdog interface.\n"); |
09649a85 | 379 | watchdog_set_drvdata(&amt_wd_dev, dev); |
64092858 | 380 | return 0; |
70cd5337 TW |
381 | } |
382 | ||
383 | void mei_watchdog_unregister(struct mei_device *dev) | |
384 | { | |
d6921700 | 385 | if (watchdog_get_drvdata(&amt_wd_dev) == NULL) |
09649a85 TW |
386 | return; |
387 | ||
388 | watchdog_set_drvdata(&amt_wd_dev, NULL); | |
389 | watchdog_unregister_device(&amt_wd_dev); | |
70cd5337 TW |
390 | } |
391 |