]>
Commit | Line | Data |
---|---|---|
ab841160 OW |
1 | /* |
2 | * | |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | |
733ba91c | 4 | * Copyright (c) 2003-2012, Intel Corporation. |
ab841160 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 | ||
2f3d2b49 TW |
17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
18 | ||
ab841160 OW |
19 | #include <linux/module.h> |
20 | #include <linux/moduleparam.h> | |
21 | #include <linux/kernel.h> | |
22 | #include <linux/device.h> | |
23 | #include <linux/fs.h> | |
24 | #include <linux/errno.h> | |
25 | #include <linux/types.h> | |
26 | #include <linux/fcntl.h> | |
27 | #include <linux/aio.h> | |
28 | #include <linux/pci.h> | |
29 | #include <linux/poll.h> | |
30 | #include <linux/init.h> | |
31 | #include <linux/ioctl.h> | |
32 | #include <linux/cdev.h> | |
ab841160 OW |
33 | #include <linux/sched.h> |
34 | #include <linux/uuid.h> | |
35 | #include <linux/compat.h> | |
36 | #include <linux/jiffies.h> | |
37 | #include <linux/interrupt.h> | |
5b881e3c | 38 | #include <linux/miscdevice.h> |
ab841160 | 39 | |
4f3afe1d | 40 | #include <linux/mei.h> |
47a73801 TW |
41 | |
42 | #include "mei_dev.h" | |
9dc64d6a | 43 | #include "hw-me.h" |
90e0b5f1 | 44 | #include "client.h" |
ab841160 | 45 | |
daed6b5e TW |
46 | /* AMT device is a singleton on the platform */ |
47 | static struct pci_dev *mei_pdev; | |
ab841160 | 48 | |
ab841160 OW |
49 | /* mei_pci_tbl - PCI Device ID Table */ |
50 | static DEFINE_PCI_DEVICE_TABLE(mei_pci_tbl) = { | |
51 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, | |
52 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, | |
53 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, | |
54 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, | |
55 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, | |
56 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, | |
57 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, | |
58 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, | |
59 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, | |
60 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, | |
61 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, | |
62 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, | |
63 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, | |
64 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, | |
65 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, | |
66 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, | |
67 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, | |
68 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, | |
69 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, | |
70 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, | |
71 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, | |
72 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, | |
73 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, | |
74 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, | |
75 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, | |
76 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, | |
77 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, | |
78 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, | |
79 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, | |
80 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, | |
81 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, | |
9af51423 TW |
82 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT)}, |
83 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_LP)}, | |
ab841160 OW |
84 | |
85 | /* required last entry */ | |
86 | {0, } | |
87 | }; | |
88 | ||
89 | MODULE_DEVICE_TABLE(pci, mei_pci_tbl); | |
90 | ||
91 | static DEFINE_MUTEX(mei_mutex); | |
92 | ||
ab841160 | 93 | |
ab841160 OW |
94 | /** |
95 | * mei_open - the open function | |
96 | * | |
97 | * @inode: pointer to inode structure | |
98 | * @file: pointer to file structure | |
99 | * | |
100 | * returns 0 on success, <0 on error | |
101 | */ | |
102 | static int mei_open(struct inode *inode, struct file *file) | |
103 | { | |
104 | struct mei_cl *cl; | |
ab841160 | 105 | struct mei_device *dev; |
6f37aca8 TW |
106 | unsigned long cl_id; |
107 | int err; | |
ab841160 OW |
108 | |
109 | err = -ENODEV; | |
daed6b5e | 110 | if (!mei_pdev) |
ab841160 OW |
111 | goto out; |
112 | ||
daed6b5e | 113 | dev = pci_get_drvdata(mei_pdev); |
5b881e3c | 114 | if (!dev) |
ab841160 OW |
115 | goto out; |
116 | ||
117 | mutex_lock(&dev->device_lock); | |
118 | err = -ENOMEM; | |
c95efb74 | 119 | cl = mei_cl_allocate(dev); |
ab841160 | 120 | if (!cl) |
303dfbf5 | 121 | goto out_unlock; |
ab841160 OW |
122 | |
123 | err = -ENODEV; | |
b210d750 TW |
124 | if (dev->dev_state != MEI_DEV_ENABLED) { |
125 | dev_dbg(&dev->pdev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", | |
126 | mei_dev_state_str(dev->dev_state)); | |
ab841160 OW |
127 | goto out_unlock; |
128 | } | |
129 | err = -EMFILE; | |
1b812947 TW |
130 | if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { |
131 | dev_err(&dev->pdev->dev, "open_handle_count exceded %d", | |
132 | MEI_MAX_OPEN_HANDLE_COUNT); | |
ab841160 | 133 | goto out_unlock; |
1b812947 | 134 | } |
ab841160 | 135 | |
6f37aca8 | 136 | cl_id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); |
1b812947 TW |
137 | if (cl_id >= MEI_CLIENTS_MAX) { |
138 | dev_err(&dev->pdev->dev, "client_id exceded %d", | |
139 | MEI_CLIENTS_MAX) ; | |
ab841160 | 140 | goto out_unlock; |
1b812947 | 141 | } |
ab841160 | 142 | |
6f37aca8 TW |
143 | cl->host_client_id = cl_id; |
144 | ||
ab841160 OW |
145 | dev_dbg(&dev->pdev->dev, "client_id = %d\n", cl->host_client_id); |
146 | ||
147 | dev->open_handle_count++; | |
6f37aca8 | 148 | |
ab841160 OW |
149 | list_add_tail(&cl->link, &dev->file_list); |
150 | ||
151 | set_bit(cl->host_client_id, dev->host_clients_map); | |
152 | cl->state = MEI_FILE_INITIALIZING; | |
153 | cl->sm_state = 0; | |
154 | ||
155 | file->private_data = cl; | |
156 | mutex_unlock(&dev->device_lock); | |
157 | ||
5b881e3c | 158 | return nonseekable_open(inode, file); |
ab841160 OW |
159 | |
160 | out_unlock: | |
161 | mutex_unlock(&dev->device_lock); | |
162 | kfree(cl); | |
163 | out: | |
164 | return err; | |
165 | } | |
166 | ||
167 | /** | |
168 | * mei_release - the release function | |
169 | * | |
170 | * @inode: pointer to inode structure | |
171 | * @file: pointer to file structure | |
172 | * | |
173 | * returns 0 on success, <0 on error | |
174 | */ | |
175 | static int mei_release(struct inode *inode, struct file *file) | |
176 | { | |
177 | struct mei_cl *cl = file->private_data; | |
178 | struct mei_cl_cb *cb; | |
179 | struct mei_device *dev; | |
180 | int rets = 0; | |
181 | ||
182 | if (WARN_ON(!cl || !cl->dev)) | |
183 | return -ENODEV; | |
184 | ||
185 | dev = cl->dev; | |
186 | ||
187 | mutex_lock(&dev->device_lock); | |
a562d5c2 TW |
188 | if (cl == &dev->iamthif_cl) { |
189 | rets = mei_amthif_release(dev, file); | |
190 | goto out; | |
191 | } | |
192 | if (cl->state == MEI_FILE_CONNECTED) { | |
193 | cl->state = MEI_FILE_DISCONNECTING; | |
194 | dev_dbg(&dev->pdev->dev, | |
195 | "disconnecting client host client = %d, " | |
196 | "ME client = %d\n", | |
ab841160 OW |
197 | cl->host_client_id, |
198 | cl->me_client_id); | |
90e0b5f1 | 199 | rets = mei_cl_disconnect(cl); |
a562d5c2 TW |
200 | } |
201 | mei_cl_flush_queues(cl); | |
202 | dev_dbg(&dev->pdev->dev, "remove client host client = %d, ME client = %d\n", | |
203 | cl->host_client_id, | |
204 | cl->me_client_id); | |
205 | ||
206 | if (dev->open_handle_count > 0) { | |
207 | clear_bit(cl->host_client_id, dev->host_clients_map); | |
208 | dev->open_handle_count--; | |
209 | } | |
90e0b5f1 | 210 | mei_cl_unlink(cl); |
a562d5c2 TW |
211 | |
212 | /* free read cb */ | |
213 | cb = NULL; | |
214 | if (cl->read_cb) { | |
90e0b5f1 | 215 | cb = mei_cl_find_read_cb(cl); |
a562d5c2 TW |
216 | /* Remove entry from read list */ |
217 | if (cb) | |
218 | list_del(&cb->list); | |
219 | ||
220 | cb = cl->read_cb; | |
221 | cl->read_cb = NULL; | |
222 | } | |
ab841160 | 223 | |
a562d5c2 | 224 | file->private_data = NULL; |
ab841160 | 225 | |
a562d5c2 TW |
226 | if (cb) { |
227 | mei_io_cb_free(cb); | |
ab841160 | 228 | cb = NULL; |
ab841160 | 229 | } |
a562d5c2 TW |
230 | |
231 | kfree(cl); | |
232 | out: | |
ab841160 OW |
233 | mutex_unlock(&dev->device_lock); |
234 | return rets; | |
235 | } | |
236 | ||
237 | ||
238 | /** | |
239 | * mei_read - the read function. | |
240 | * | |
241 | * @file: pointer to file structure | |
242 | * @ubuf: pointer to user buffer | |
243 | * @length: buffer length | |
244 | * @offset: data offset in buffer | |
245 | * | |
246 | * returns >=0 data length on success , <0 on error | |
247 | */ | |
248 | static ssize_t mei_read(struct file *file, char __user *ubuf, | |
441ab50f | 249 | size_t length, loff_t *offset) |
ab841160 OW |
250 | { |
251 | struct mei_cl *cl = file->private_data; | |
252 | struct mei_cl_cb *cb_pos = NULL; | |
253 | struct mei_cl_cb *cb = NULL; | |
254 | struct mei_device *dev; | |
255 | int i; | |
256 | int rets; | |
257 | int err; | |
258 | ||
259 | ||
260 | if (WARN_ON(!cl || !cl->dev)) | |
261 | return -ENODEV; | |
262 | ||
263 | dev = cl->dev; | |
264 | ||
265 | mutex_lock(&dev->device_lock); | |
b210d750 | 266 | if (dev->dev_state != MEI_DEV_ENABLED) { |
ab841160 OW |
267 | rets = -ENODEV; |
268 | goto out; | |
269 | } | |
270 | ||
271 | if ((cl->sm_state & MEI_WD_STATE_INDEPENDENCE_MSG_SENT) == 0) { | |
272 | /* Do not allow to read watchdog client */ | |
07b509b7 | 273 | i = mei_me_cl_by_uuid(dev, &mei_wd_guid); |
ab841160 OW |
274 | if (i >= 0) { |
275 | struct mei_me_client *me_client = &dev->me_clients[i]; | |
ab841160 OW |
276 | if (cl->me_client_id == me_client->client_id) { |
277 | rets = -EBADF; | |
278 | goto out; | |
279 | } | |
280 | } | |
281 | } else { | |
282 | cl->sm_state &= ~MEI_WD_STATE_INDEPENDENCE_MSG_SENT; | |
283 | } | |
284 | ||
285 | if (cl == &dev->iamthif_cl) { | |
19838fb8 | 286 | rets = mei_amthif_read(dev, file, ubuf, length, offset); |
ab841160 OW |
287 | goto out; |
288 | } | |
289 | ||
ebb108ef | 290 | if (cl->read_cb && cl->read_cb->buf_idx > *offset) { |
ab841160 OW |
291 | cb = cl->read_cb; |
292 | goto copy_buffer; | |
ebb108ef TW |
293 | } else if (cl->read_cb && cl->read_cb->buf_idx > 0 && |
294 | cl->read_cb->buf_idx <= *offset) { | |
ab841160 OW |
295 | cb = cl->read_cb; |
296 | rets = 0; | |
297 | goto free; | |
ebb108ef | 298 | } else if ((!cl->read_cb || !cl->read_cb->buf_idx) && *offset > 0) { |
5f9092f3 | 299 | /*Offset needs to be cleaned for contiguous reads*/ |
ab841160 OW |
300 | *offset = 0; |
301 | rets = 0; | |
302 | goto out; | |
303 | } | |
304 | ||
90e0b5f1 | 305 | err = mei_cl_read_start(cl); |
ab841160 OW |
306 | if (err && err != -EBUSY) { |
307 | dev_dbg(&dev->pdev->dev, | |
308 | "mei start read failure with status = %d\n", err); | |
309 | rets = err; | |
310 | goto out; | |
311 | } | |
312 | ||
313 | if (MEI_READ_COMPLETE != cl->reading_state && | |
314 | !waitqueue_active(&cl->rx_wait)) { | |
315 | if (file->f_flags & O_NONBLOCK) { | |
316 | rets = -EAGAIN; | |
317 | goto out; | |
318 | } | |
319 | ||
320 | mutex_unlock(&dev->device_lock); | |
321 | ||
322 | if (wait_event_interruptible(cl->rx_wait, | |
323 | (MEI_READ_COMPLETE == cl->reading_state || | |
324 | MEI_FILE_INITIALIZING == cl->state || | |
325 | MEI_FILE_DISCONNECTED == cl->state || | |
326 | MEI_FILE_DISCONNECTING == cl->state))) { | |
327 | if (signal_pending(current)) | |
328 | return -EINTR; | |
329 | return -ERESTARTSYS; | |
330 | } | |
331 | ||
332 | mutex_lock(&dev->device_lock); | |
333 | if (MEI_FILE_INITIALIZING == cl->state || | |
334 | MEI_FILE_DISCONNECTED == cl->state || | |
335 | MEI_FILE_DISCONNECTING == cl->state) { | |
336 | rets = -EBUSY; | |
337 | goto out; | |
338 | } | |
339 | } | |
340 | ||
341 | cb = cl->read_cb; | |
342 | ||
343 | if (!cb) { | |
344 | rets = -ENODEV; | |
345 | goto out; | |
346 | } | |
347 | if (cl->reading_state != MEI_READ_COMPLETE) { | |
348 | rets = 0; | |
349 | goto out; | |
350 | } | |
351 | /* now copy the data to user space */ | |
352 | copy_buffer: | |
353 | dev_dbg(&dev->pdev->dev, "cb->response_buffer size - %d\n", | |
354 | cb->response_buffer.size); | |
ebb108ef TW |
355 | dev_dbg(&dev->pdev->dev, "cb->buf_idx - %lu\n", cb->buf_idx); |
356 | if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) { | |
ab841160 OW |
357 | rets = -EMSGSIZE; |
358 | goto free; | |
359 | } | |
360 | ||
ebb108ef TW |
361 | /* length is being truncated to PAGE_SIZE, |
362 | * however buf_idx may point beyond that */ | |
363 | length = min_t(size_t, length, cb->buf_idx - *offset); | |
ab841160 | 364 | |
441ab50f | 365 | if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) { |
ab841160 OW |
366 | rets = -EFAULT; |
367 | goto free; | |
368 | } | |
369 | ||
370 | rets = length; | |
371 | *offset += length; | |
ebb108ef | 372 | if ((unsigned long)*offset < cb->buf_idx) |
ab841160 OW |
373 | goto out; |
374 | ||
375 | free: | |
90e0b5f1 | 376 | cb_pos = mei_cl_find_read_cb(cl); |
ab841160 OW |
377 | /* Remove entry from read list */ |
378 | if (cb_pos) | |
fb601adb | 379 | list_del(&cb_pos->list); |
601a1efa | 380 | mei_io_cb_free(cb); |
ab841160 OW |
381 | cl->reading_state = MEI_IDLE; |
382 | cl->read_cb = NULL; | |
ab841160 OW |
383 | out: |
384 | dev_dbg(&dev->pdev->dev, "end mei read rets= %d\n", rets); | |
385 | mutex_unlock(&dev->device_lock); | |
386 | return rets; | |
387 | } | |
ab841160 OW |
388 | /** |
389 | * mei_write - the write function. | |
390 | * | |
391 | * @file: pointer to file structure | |
392 | * @ubuf: pointer to user buffer | |
393 | * @length: buffer length | |
394 | * @offset: data offset in buffer | |
395 | * | |
396 | * returns >=0 data length on success , <0 on error | |
397 | */ | |
398 | static ssize_t mei_write(struct file *file, const char __user *ubuf, | |
441ab50f | 399 | size_t length, loff_t *offset) |
ab841160 OW |
400 | { |
401 | struct mei_cl *cl = file->private_data; | |
402 | struct mei_cl_cb *write_cb = NULL; | |
403 | struct mei_msg_hdr mei_hdr; | |
404 | struct mei_device *dev; | |
405 | unsigned long timeout = 0; | |
406 | int rets; | |
407 | int i; | |
408 | ||
409 | if (WARN_ON(!cl || !cl->dev)) | |
410 | return -ENODEV; | |
411 | ||
412 | dev = cl->dev; | |
413 | ||
414 | mutex_lock(&dev->device_lock); | |
415 | ||
b210d750 | 416 | if (dev->dev_state != MEI_DEV_ENABLED) { |
75f0ee15 | 417 | rets = -ENODEV; |
b0d0cf77 | 418 | goto err; |
ab841160 OW |
419 | } |
420 | ||
75f0ee15 TW |
421 | i = mei_me_cl_by_id(dev, cl->me_client_id); |
422 | if (i < 0) { | |
423 | rets = -ENODEV; | |
b0d0cf77 | 424 | goto err; |
75f0ee15 TW |
425 | } |
426 | if (length > dev->me_clients[i].props.max_msg_length || length <= 0) { | |
427 | rets = -EMSGSIZE; | |
b0d0cf77 | 428 | goto err; |
75f0ee15 TW |
429 | } |
430 | ||
431 | if (cl->state != MEI_FILE_CONNECTED) { | |
432 | rets = -ENODEV; | |
433 | dev_err(&dev->pdev->dev, "host client = %d, is not connected to ME client = %d", | |
434 | cl->host_client_id, cl->me_client_id); | |
b0d0cf77 | 435 | goto err; |
75f0ee15 | 436 | } |
ab841160 | 437 | if (cl == &dev->iamthif_cl) { |
19838fb8 | 438 | write_cb = mei_amthif_find_read_list_entry(dev, file); |
ab841160 OW |
439 | |
440 | if (write_cb) { | |
441 | timeout = write_cb->read_time + | |
3870c320 | 442 | mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); |
ab841160 OW |
443 | |
444 | if (time_after(jiffies, timeout) || | |
75f0ee15 TW |
445 | cl->reading_state == MEI_READ_COMPLETE) { |
446 | *offset = 0; | |
fb601adb | 447 | list_del(&write_cb->list); |
601a1efa | 448 | mei_io_cb_free(write_cb); |
75f0ee15 | 449 | write_cb = NULL; |
ab841160 OW |
450 | } |
451 | } | |
452 | } | |
453 | ||
454 | /* free entry used in read */ | |
455 | if (cl->reading_state == MEI_READ_COMPLETE) { | |
456 | *offset = 0; | |
90e0b5f1 | 457 | write_cb = mei_cl_find_read_cb(cl); |
ab841160 | 458 | if (write_cb) { |
fb601adb | 459 | list_del(&write_cb->list); |
601a1efa | 460 | mei_io_cb_free(write_cb); |
ab841160 OW |
461 | write_cb = NULL; |
462 | cl->reading_state = MEI_IDLE; | |
463 | cl->read_cb = NULL; | |
ab841160 | 464 | } |
d91aaed3 | 465 | } else if (cl->reading_state == MEI_IDLE) |
ab841160 OW |
466 | *offset = 0; |
467 | ||
468 | ||
33d28c92 | 469 | write_cb = mei_io_cb_init(cl, file); |
ab841160 | 470 | if (!write_cb) { |
33d28c92 TW |
471 | dev_err(&dev->pdev->dev, "write cb allocation failed\n"); |
472 | rets = -ENOMEM; | |
b0d0cf77 | 473 | goto err; |
ab841160 | 474 | } |
33d28c92 TW |
475 | rets = mei_io_cb_alloc_req_buf(write_cb, length); |
476 | if (rets) | |
b0d0cf77 | 477 | goto err; |
ab841160 | 478 | |
33d28c92 | 479 | dev_dbg(&dev->pdev->dev, "cb request size = %zd\n", length); |
ab841160 | 480 | |
75f0ee15 TW |
481 | rets = copy_from_user(write_cb->request_buffer.data, ubuf, length); |
482 | if (rets) | |
b0d0cf77 | 483 | goto err; |
ab841160 OW |
484 | |
485 | cl->sm_state = 0; | |
486 | if (length == 4 && | |
487 | ((memcmp(mei_wd_state_independence_msg[0], | |
488 | write_cb->request_buffer.data, 4) == 0) || | |
489 | (memcmp(mei_wd_state_independence_msg[1], | |
490 | write_cb->request_buffer.data, 4) == 0) || | |
491 | (memcmp(mei_wd_state_independence_msg[2], | |
492 | write_cb->request_buffer.data, 4) == 0))) | |
493 | cl->sm_state |= MEI_WD_STATE_INDEPENDENCE_MSG_SENT; | |
494 | ||
ab841160 | 495 | if (cl == &dev->iamthif_cl) { |
ab5c4a56 | 496 | rets = mei_amthif_write(dev, write_cb); |
ab841160 | 497 | |
ab5c4a56 TW |
498 | if (rets) { |
499 | dev_err(&dev->pdev->dev, | |
500 | "amthi write failed with status = %d\n", rets); | |
501 | goto err; | |
ab841160 OW |
502 | } |
503 | mutex_unlock(&dev->device_lock); | |
75f0ee15 | 504 | return length; |
ab841160 OW |
505 | } |
506 | ||
4b8960b4 | 507 | write_cb->fop_type = MEI_FOP_WRITE; |
ab841160 OW |
508 | |
509 | dev_dbg(&dev->pdev->dev, "host client = %d, ME client = %d\n", | |
510 | cl->host_client_id, cl->me_client_id); | |
90e0b5f1 | 511 | rets = mei_cl_flow_ctrl_creds(cl); |
ab841160 | 512 | if (rets < 0) |
b0d0cf77 | 513 | goto err; |
ab841160 | 514 | |
b0d0cf77 TW |
515 | if (rets == 0 || dev->mei_host_buffer_is_empty == false) { |
516 | write_cb->buf_idx = 0; | |
517 | mei_hdr.msg_complete = 0; | |
ab841160 | 518 | cl->writing_state = MEI_WRITING; |
b0d0cf77 TW |
519 | goto out; |
520 | } | |
ab841160 | 521 | |
b0d0cf77 TW |
522 | dev->mei_host_buffer_is_empty = false; |
523 | if (length > mei_hbuf_max_data(dev)) { | |
524 | mei_hdr.length = mei_hbuf_max_data(dev); | |
525 | mei_hdr.msg_complete = 0; | |
ab841160 | 526 | } else { |
b0d0cf77 TW |
527 | mei_hdr.length = length; |
528 | mei_hdr.msg_complete = 1; | |
529 | } | |
530 | mei_hdr.host_addr = cl->host_client_id; | |
531 | mei_hdr.me_addr = cl->me_client_id; | |
532 | mei_hdr.reserved = 0; | |
15d4acc5 TW |
533 | |
534 | dev_dbg(&dev->pdev->dev, "write " MEI_HDR_FMT "\n", | |
535 | MEI_HDR_PRM(&mei_hdr)); | |
438763f3 | 536 | if (mei_write_message(dev, &mei_hdr, write_cb->request_buffer.data)) { |
b0d0cf77 TW |
537 | rets = -ENODEV; |
538 | goto err; | |
539 | } | |
540 | cl->writing_state = MEI_WRITING; | |
541 | write_cb->buf_idx = mei_hdr.length; | |
ab841160 | 542 | |
b0d0cf77 TW |
543 | out: |
544 | if (mei_hdr.msg_complete) { | |
90e0b5f1 | 545 | if (mei_cl_flow_ctrl_reduce(cl)) { |
b0d0cf77 TW |
546 | rets = -ENODEV; |
547 | goto err; | |
548 | } | |
549 | list_add_tail(&write_cb->list, &dev->write_waiting_list.list); | |
550 | } else { | |
fb601adb | 551 | list_add_tail(&write_cb->list, &dev->write_list.list); |
ab841160 | 552 | } |
b0d0cf77 | 553 | |
ab841160 OW |
554 | mutex_unlock(&dev->device_lock); |
555 | return length; | |
556 | ||
b0d0cf77 | 557 | err: |
ab841160 | 558 | mutex_unlock(&dev->device_lock); |
601a1efa | 559 | mei_io_cb_free(write_cb); |
ab841160 OW |
560 | return rets; |
561 | } | |
562 | ||
9f81abda TW |
563 | /** |
564 | * mei_ioctl_connect_client - the connect to fw client IOCTL function | |
565 | * | |
566 | * @dev: the device structure | |
567 | * @data: IOCTL connect data, input and output parameters | |
568 | * @file: private data of the file object | |
569 | * | |
570 | * Locking: called under "dev->device_lock" lock | |
571 | * | |
572 | * returns 0 on success, <0 on failure. | |
573 | */ | |
574 | static int mei_ioctl_connect_client(struct file *file, | |
575 | struct mei_connect_client_data *data) | |
576 | { | |
577 | struct mei_device *dev; | |
578 | struct mei_client *client; | |
579 | struct mei_cl *cl; | |
580 | int i; | |
581 | int rets; | |
582 | ||
583 | cl = file->private_data; | |
584 | if (WARN_ON(!cl || !cl->dev)) | |
585 | return -ENODEV; | |
586 | ||
587 | dev = cl->dev; | |
588 | ||
589 | if (dev->dev_state != MEI_DEV_ENABLED) { | |
590 | rets = -ENODEV; | |
591 | goto end; | |
592 | } | |
593 | ||
594 | if (cl->state != MEI_FILE_INITIALIZING && | |
595 | cl->state != MEI_FILE_DISCONNECTED) { | |
596 | rets = -EBUSY; | |
597 | goto end; | |
598 | } | |
599 | ||
600 | /* find ME client we're trying to connect to */ | |
601 | i = mei_me_cl_by_uuid(dev, &data->in_client_uuid); | |
602 | if (i >= 0 && !dev->me_clients[i].props.fixed_address) { | |
603 | cl->me_client_id = dev->me_clients[i].client_id; | |
604 | cl->state = MEI_FILE_CONNECTING; | |
605 | } | |
606 | ||
607 | dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", | |
608 | cl->me_client_id); | |
609 | dev_dbg(&dev->pdev->dev, "FW Client - Protocol Version = %d\n", | |
610 | dev->me_clients[i].props.protocol_version); | |
611 | dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n", | |
612 | dev->me_clients[i].props.max_msg_length); | |
613 | ||
614 | /* if we're connecting to amthi client then we will use the | |
615 | * existing connection | |
616 | */ | |
617 | if (uuid_le_cmp(data->in_client_uuid, mei_amthi_guid) == 0) { | |
618 | dev_dbg(&dev->pdev->dev, "FW Client is amthi\n"); | |
619 | if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { | |
620 | rets = -ENODEV; | |
621 | goto end; | |
622 | } | |
623 | clear_bit(cl->host_client_id, dev->host_clients_map); | |
624 | mei_cl_unlink(cl); | |
625 | ||
626 | kfree(cl); | |
627 | cl = NULL; | |
628 | file->private_data = &dev->iamthif_cl; | |
629 | ||
630 | client = &data->out_client_properties; | |
631 | client->max_msg_length = | |
632 | dev->me_clients[i].props.max_msg_length; | |
633 | client->protocol_version = | |
634 | dev->me_clients[i].props.protocol_version; | |
635 | rets = dev->iamthif_cl.status; | |
636 | ||
637 | goto end; | |
638 | } | |
639 | ||
640 | if (cl->state != MEI_FILE_CONNECTING) { | |
641 | rets = -ENODEV; | |
642 | goto end; | |
643 | } | |
644 | ||
645 | ||
646 | /* prepare the output buffer */ | |
647 | client = &data->out_client_properties; | |
648 | client->max_msg_length = dev->me_clients[i].props.max_msg_length; | |
649 | client->protocol_version = dev->me_clients[i].props.protocol_version; | |
650 | dev_dbg(&dev->pdev->dev, "Can connect?\n"); | |
651 | ||
652 | ||
653 | rets = mei_cl_connect(cl, file); | |
654 | ||
655 | end: | |
656 | dev_dbg(&dev->pdev->dev, "free connect cb memory."); | |
657 | return rets; | |
658 | } | |
659 | ||
ab841160 OW |
660 | |
661 | /** | |
662 | * mei_ioctl - the IOCTL function | |
663 | * | |
664 | * @file: pointer to file structure | |
665 | * @cmd: ioctl command | |
666 | * @data: pointer to mei message structure | |
667 | * | |
668 | * returns 0 on success , <0 on error | |
669 | */ | |
670 | static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) | |
671 | { | |
672 | struct mei_device *dev; | |
673 | struct mei_cl *cl = file->private_data; | |
674 | struct mei_connect_client_data *connect_data = NULL; | |
675 | int rets; | |
676 | ||
677 | if (cmd != IOCTL_MEI_CONNECT_CLIENT) | |
678 | return -EINVAL; | |
679 | ||
680 | if (WARN_ON(!cl || !cl->dev)) | |
681 | return -ENODEV; | |
682 | ||
683 | dev = cl->dev; | |
684 | ||
685 | dev_dbg(&dev->pdev->dev, "IOCTL cmd = 0x%x", cmd); | |
686 | ||
687 | mutex_lock(&dev->device_lock); | |
b210d750 | 688 | if (dev->dev_state != MEI_DEV_ENABLED) { |
ab841160 OW |
689 | rets = -ENODEV; |
690 | goto out; | |
691 | } | |
692 | ||
693 | dev_dbg(&dev->pdev->dev, ": IOCTL_MEI_CONNECT_CLIENT.\n"); | |
694 | ||
695 | connect_data = kzalloc(sizeof(struct mei_connect_client_data), | |
696 | GFP_KERNEL); | |
697 | if (!connect_data) { | |
698 | rets = -ENOMEM; | |
699 | goto out; | |
700 | } | |
701 | dev_dbg(&dev->pdev->dev, "copy connect data from user\n"); | |
702 | if (copy_from_user(connect_data, (char __user *)data, | |
703 | sizeof(struct mei_connect_client_data))) { | |
704 | dev_dbg(&dev->pdev->dev, "failed to copy data from userland\n"); | |
705 | rets = -EFAULT; | |
706 | goto out; | |
707 | } | |
9f81abda | 708 | |
ab841160 OW |
709 | rets = mei_ioctl_connect_client(file, connect_data); |
710 | ||
711 | /* if all is ok, copying the data back to user. */ | |
712 | if (rets) | |
713 | goto out; | |
714 | ||
715 | dev_dbg(&dev->pdev->dev, "copy connect data to user\n"); | |
716 | if (copy_to_user((char __user *)data, connect_data, | |
717 | sizeof(struct mei_connect_client_data))) { | |
718 | dev_dbg(&dev->pdev->dev, "failed to copy data to userland\n"); | |
719 | rets = -EFAULT; | |
720 | goto out; | |
721 | } | |
722 | ||
723 | out: | |
724 | kfree(connect_data); | |
725 | mutex_unlock(&dev->device_lock); | |
726 | return rets; | |
727 | } | |
728 | ||
729 | /** | |
730 | * mei_compat_ioctl - the compat IOCTL function | |
731 | * | |
732 | * @file: pointer to file structure | |
733 | * @cmd: ioctl command | |
734 | * @data: pointer to mei message structure | |
735 | * | |
736 | * returns 0 on success , <0 on error | |
737 | */ | |
738 | #ifdef CONFIG_COMPAT | |
739 | static long mei_compat_ioctl(struct file *file, | |
441ab50f | 740 | unsigned int cmd, unsigned long data) |
ab841160 OW |
741 | { |
742 | return mei_ioctl(file, cmd, (unsigned long)compat_ptr(data)); | |
743 | } | |
744 | #endif | |
745 | ||
746 | ||
747 | /** | |
748 | * mei_poll - the poll function | |
749 | * | |
750 | * @file: pointer to file structure | |
751 | * @wait: pointer to poll_table structure | |
752 | * | |
753 | * returns poll mask | |
754 | */ | |
755 | static unsigned int mei_poll(struct file *file, poll_table *wait) | |
756 | { | |
757 | struct mei_cl *cl = file->private_data; | |
758 | struct mei_device *dev; | |
759 | unsigned int mask = 0; | |
760 | ||
761 | if (WARN_ON(!cl || !cl->dev)) | |
762 | return mask; | |
763 | ||
764 | dev = cl->dev; | |
765 | ||
766 | mutex_lock(&dev->device_lock); | |
767 | ||
b210d750 | 768 | if (dev->dev_state != MEI_DEV_ENABLED) |
ab841160 OW |
769 | goto out; |
770 | ||
771 | ||
772 | if (cl == &dev->iamthif_cl) { | |
744f0f2f | 773 | mask = mei_amthif_poll(dev, file, wait); |
ab841160 OW |
774 | goto out; |
775 | } | |
776 | ||
777 | mutex_unlock(&dev->device_lock); | |
778 | poll_wait(file, &cl->tx_wait, wait); | |
779 | mutex_lock(&dev->device_lock); | |
780 | if (MEI_WRITE_COMPLETE == cl->writing_state) | |
781 | mask |= (POLLIN | POLLRDNORM); | |
782 | ||
783 | out: | |
784 | mutex_unlock(&dev->device_lock); | |
785 | return mask; | |
786 | } | |
787 | ||
5b881e3c OW |
788 | /* |
789 | * file operations structure will be used for mei char device. | |
790 | */ | |
791 | static const struct file_operations mei_fops = { | |
792 | .owner = THIS_MODULE, | |
793 | .read = mei_read, | |
794 | .unlocked_ioctl = mei_ioctl, | |
795 | #ifdef CONFIG_COMPAT | |
796 | .compat_ioctl = mei_compat_ioctl, | |
797 | #endif | |
798 | .open = mei_open, | |
799 | .release = mei_release, | |
800 | .write = mei_write, | |
801 | .poll = mei_poll, | |
802 | .llseek = no_llseek | |
803 | }; | |
804 | ||
805 | ||
806 | /* | |
807 | * Misc Device Struct | |
808 | */ | |
809 | static struct miscdevice mei_misc_device = { | |
c38ea24e | 810 | .name = "mei", |
5b881e3c OW |
811 | .fops = &mei_fops, |
812 | .minor = MISC_DYNAMIC_MINOR, | |
813 | }; | |
814 | ||
9a123f19 TW |
815 | /** |
816 | * mei_quirk_probe - probe for devices that doesn't valid ME interface | |
817 | * @pdev: PCI device structure | |
818 | * @ent: entry into pci_device_table | |
819 | * | |
820 | * returns true if ME Interface is valid, false otherwise | |
821 | */ | |
80c8ae28 | 822 | static bool mei_quirk_probe(struct pci_dev *pdev, |
9a123f19 TW |
823 | const struct pci_device_id *ent) |
824 | { | |
825 | u32 reg; | |
826 | if (ent->device == MEI_DEV_ID_PBG_1) { | |
827 | pci_read_config_dword(pdev, 0x48, ®); | |
828 | /* make sure that bit 9 is up and bit 10 is down */ | |
829 | if ((reg & 0x600) == 0x200) { | |
830 | dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); | |
831 | return false; | |
832 | } | |
833 | } | |
834 | return true; | |
835 | } | |
5b881e3c OW |
836 | /** |
837 | * mei_probe - Device Initialization Routine | |
838 | * | |
839 | * @pdev: PCI device structure | |
840 | * @ent: entry in kcs_pci_tbl | |
841 | * | |
842 | * returns 0 on success, <0 on failure. | |
843 | */ | |
80c8ae28 | 844 | static int mei_probe(struct pci_dev *pdev, |
5b881e3c OW |
845 | const struct pci_device_id *ent) |
846 | { | |
847 | struct mei_device *dev; | |
848 | int err; | |
849 | ||
850 | mutex_lock(&mei_mutex); | |
9a123f19 TW |
851 | |
852 | if (!mei_quirk_probe(pdev, ent)) { | |
853 | err = -ENODEV; | |
854 | goto end; | |
855 | } | |
856 | ||
daed6b5e | 857 | if (mei_pdev) { |
5b881e3c OW |
858 | err = -EEXIST; |
859 | goto end; | |
860 | } | |
861 | /* enable pci dev */ | |
862 | err = pci_enable_device(pdev); | |
863 | if (err) { | |
32c826b6 | 864 | dev_err(&pdev->dev, "failed to enable pci device.\n"); |
5b881e3c OW |
865 | goto end; |
866 | } | |
867 | /* set PCI host mastering */ | |
868 | pci_set_master(pdev); | |
869 | /* pci request regions for mei driver */ | |
068c0ae9 | 870 | err = pci_request_regions(pdev, KBUILD_MODNAME); |
5b881e3c | 871 | if (err) { |
32c826b6 | 872 | dev_err(&pdev->dev, "failed to get pci regions.\n"); |
5b881e3c OW |
873 | goto disable_device; |
874 | } | |
875 | /* allocates and initializes the mei dev structure */ | |
876 | dev = mei_device_init(pdev); | |
877 | if (!dev) { | |
878 | err = -ENOMEM; | |
879 | goto release_regions; | |
880 | } | |
881 | /* mapping IO device memory */ | |
882 | dev->mem_addr = pci_iomap(pdev, 0, 0); | |
883 | if (!dev->mem_addr) { | |
32c826b6 | 884 | dev_err(&pdev->dev, "mapping I/O device memory failure.\n"); |
5b881e3c OW |
885 | err = -ENOMEM; |
886 | goto free_device; | |
887 | } | |
888 | pci_enable_msi(pdev); | |
889 | ||
890 | /* request and enable interrupt */ | |
891 | if (pci_dev_msi_enabled(pdev)) | |
892 | err = request_threaded_irq(pdev->irq, | |
893 | NULL, | |
894 | mei_interrupt_thread_handler, | |
068c0ae9 | 895 | IRQF_ONESHOT, KBUILD_MODNAME, dev); |
5b881e3c OW |
896 | else |
897 | err = request_threaded_irq(pdev->irq, | |
898 | mei_interrupt_quick_handler, | |
899 | mei_interrupt_thread_handler, | |
068c0ae9 | 900 | IRQF_SHARED, KBUILD_MODNAME, dev); |
5b881e3c OW |
901 | |
902 | if (err) { | |
32c826b6 | 903 | dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n", |
5b881e3c | 904 | pdev->irq); |
169dc388 | 905 | goto disable_msi; |
5b881e3c OW |
906 | } |
907 | INIT_DELAYED_WORK(&dev->timer_work, mei_timer); | |
c1174c0e SO |
908 | INIT_WORK(&dev->init_work, mei_host_client_init); |
909 | ||
5b881e3c | 910 | if (mei_hw_init(dev)) { |
32c826b6 | 911 | dev_err(&pdev->dev, "init hw failure.\n"); |
5b881e3c OW |
912 | err = -ENODEV; |
913 | goto release_irq; | |
914 | } | |
915 | ||
916 | err = misc_register(&mei_misc_device); | |
917 | if (err) | |
918 | goto release_irq; | |
919 | ||
daed6b5e | 920 | mei_pdev = pdev; |
5b881e3c OW |
921 | pci_set_drvdata(pdev, dev); |
922 | ||
923 | ||
924 | schedule_delayed_work(&dev->timer_work, HZ); | |
925 | ||
926 | mutex_unlock(&mei_mutex); | |
927 | ||
2f3d2b49 | 928 | pr_debug("initialization successful.\n"); |
5b881e3c OW |
929 | |
930 | return 0; | |
931 | ||
932 | release_irq: | |
933 | /* disable interrupts */ | |
934 | dev->host_hw_state = mei_hcsr_read(dev); | |
935 | mei_disable_interrupts(dev); | |
936 | flush_scheduled_work(); | |
937 | free_irq(pdev->irq, dev); | |
169dc388 | 938 | disable_msi: |
5b881e3c | 939 | pci_disable_msi(pdev); |
5b881e3c OW |
940 | pci_iounmap(pdev, dev->mem_addr); |
941 | free_device: | |
942 | kfree(dev); | |
943 | release_regions: | |
944 | pci_release_regions(pdev); | |
945 | disable_device: | |
946 | pci_disable_device(pdev); | |
947 | end: | |
948 | mutex_unlock(&mei_mutex); | |
32c826b6 | 949 | dev_err(&pdev->dev, "initialization failed.\n"); |
5b881e3c OW |
950 | return err; |
951 | } | |
952 | ||
953 | /** | |
954 | * mei_remove - Device Removal Routine | |
955 | * | |
956 | * @pdev: PCI device structure | |
957 | * | |
958 | * mei_remove is called by the PCI subsystem to alert the driver | |
959 | * that it should release a PCI device. | |
960 | */ | |
486a5c28 | 961 | static void mei_remove(struct pci_dev *pdev) |
5b881e3c OW |
962 | { |
963 | struct mei_device *dev; | |
964 | ||
daed6b5e | 965 | if (mei_pdev != pdev) |
5b881e3c OW |
966 | return; |
967 | ||
968 | dev = pci_get_drvdata(pdev); | |
969 | if (!dev) | |
970 | return; | |
971 | ||
972 | mutex_lock(&dev->device_lock); | |
973 | ||
c216fdeb TW |
974 | cancel_delayed_work(&dev->timer_work); |
975 | ||
976 | mei_wd_stop(dev); | |
5b881e3c | 977 | |
daed6b5e | 978 | mei_pdev = NULL; |
5b881e3c OW |
979 | |
980 | if (dev->iamthif_cl.state == MEI_FILE_CONNECTED) { | |
981 | dev->iamthif_cl.state = MEI_FILE_DISCONNECTING; | |
90e0b5f1 | 982 | mei_cl_disconnect(&dev->iamthif_cl); |
5b881e3c OW |
983 | } |
984 | if (dev->wd_cl.state == MEI_FILE_CONNECTED) { | |
985 | dev->wd_cl.state = MEI_FILE_DISCONNECTING; | |
90e0b5f1 | 986 | mei_cl_disconnect(&dev->wd_cl); |
5b881e3c OW |
987 | } |
988 | ||
989 | /* Unregistering watchdog device */ | |
70cd5337 | 990 | mei_watchdog_unregister(dev); |
5b881e3c OW |
991 | |
992 | /* remove entry if already in list */ | |
993 | dev_dbg(&pdev->dev, "list del iamthif and wd file list.\n"); | |
90e0b5f1 TW |
994 | mei_cl_unlink(&dev->wd_cl); |
995 | mei_cl_unlink(&dev->iamthif_cl); | |
5b881e3c OW |
996 | |
997 | dev->iamthif_current_cb = NULL; | |
998 | dev->me_clients_num = 0; | |
999 | ||
1000 | mutex_unlock(&dev->device_lock); | |
1001 | ||
1002 | flush_scheduled_work(); | |
1003 | ||
1004 | /* disable interrupts */ | |
1005 | mei_disable_interrupts(dev); | |
1006 | ||
1007 | free_irq(pdev->irq, dev); | |
1008 | pci_disable_msi(pdev); | |
1009 | pci_set_drvdata(pdev, NULL); | |
1010 | ||
1011 | if (dev->mem_addr) | |
1012 | pci_iounmap(pdev, dev->mem_addr); | |
1013 | ||
1014 | kfree(dev); | |
1015 | ||
1016 | pci_release_regions(pdev); | |
1017 | pci_disable_device(pdev); | |
a44cab4a TW |
1018 | |
1019 | misc_deregister(&mei_misc_device); | |
5b881e3c | 1020 | } |
ab841160 OW |
1021 | #ifdef CONFIG_PM |
1022 | static int mei_pci_suspend(struct device *device) | |
1023 | { | |
1024 | struct pci_dev *pdev = to_pci_dev(device); | |
1025 | struct mei_device *dev = pci_get_drvdata(pdev); | |
1026 | int err; | |
1027 | ||
1028 | if (!dev) | |
1029 | return -ENODEV; | |
1030 | mutex_lock(&dev->device_lock); | |
c216fdeb TW |
1031 | |
1032 | cancel_delayed_work(&dev->timer_work); | |
1033 | ||
ab841160 | 1034 | /* Stop watchdog if exists */ |
c216fdeb | 1035 | err = mei_wd_stop(dev); |
ab841160 | 1036 | /* Set new mei state */ |
b210d750 TW |
1037 | if (dev->dev_state == MEI_DEV_ENABLED || |
1038 | dev->dev_state == MEI_DEV_RECOVERING_FROM_RESET) { | |
1039 | dev->dev_state = MEI_DEV_POWER_DOWN; | |
ab841160 OW |
1040 | mei_reset(dev, 0); |
1041 | } | |
1042 | mutex_unlock(&dev->device_lock); | |
1043 | ||
1044 | free_irq(pdev->irq, dev); | |
4f61a7ad | 1045 | pci_disable_msi(pdev); |
ab841160 OW |
1046 | |
1047 | return err; | |
1048 | } | |
1049 | ||
1050 | static int mei_pci_resume(struct device *device) | |
1051 | { | |
1052 | struct pci_dev *pdev = to_pci_dev(device); | |
1053 | struct mei_device *dev; | |
1054 | int err; | |
1055 | ||
1056 | dev = pci_get_drvdata(pdev); | |
1057 | if (!dev) | |
1058 | return -ENODEV; | |
1059 | ||
4f61a7ad TW |
1060 | pci_enable_msi(pdev); |
1061 | ||
1062 | /* request and enable interrupt */ | |
1063 | if (pci_dev_msi_enabled(pdev)) | |
1064 | err = request_threaded_irq(pdev->irq, | |
1065 | NULL, | |
1066 | mei_interrupt_thread_handler, | |
068c0ae9 | 1067 | IRQF_ONESHOT, KBUILD_MODNAME, dev); |
4f61a7ad TW |
1068 | else |
1069 | err = request_threaded_irq(pdev->irq, | |
ab841160 OW |
1070 | mei_interrupt_quick_handler, |
1071 | mei_interrupt_thread_handler, | |
068c0ae9 | 1072 | IRQF_SHARED, KBUILD_MODNAME, dev); |
4f61a7ad | 1073 | |
ab841160 | 1074 | if (err) { |
32c826b6 TW |
1075 | dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n", |
1076 | pdev->irq); | |
ab841160 OW |
1077 | return err; |
1078 | } | |
1079 | ||
1080 | mutex_lock(&dev->device_lock); | |
b210d750 | 1081 | dev->dev_state = MEI_DEV_POWER_UP; |
ab841160 OW |
1082 | mei_reset(dev, 1); |
1083 | mutex_unlock(&dev->device_lock); | |
1084 | ||
6d70e935 OW |
1085 | /* Start timer if stopped in suspend */ |
1086 | schedule_delayed_work(&dev->timer_work, HZ); | |
1087 | ||
ab841160 OW |
1088 | return err; |
1089 | } | |
1090 | static SIMPLE_DEV_PM_OPS(mei_pm_ops, mei_pci_suspend, mei_pci_resume); | |
1091 | #define MEI_PM_OPS (&mei_pm_ops) | |
1092 | #else | |
2d990362 | 1093 | #define MEI_PM_OPS NULL |
ab841160 OW |
1094 | #endif /* CONFIG_PM */ |
1095 | /* | |
1096 | * PCI driver structure | |
1097 | */ | |
1098 | static struct pci_driver mei_driver = { | |
068c0ae9 | 1099 | .name = KBUILD_MODNAME, |
ab841160 OW |
1100 | .id_table = mei_pci_tbl, |
1101 | .probe = mei_probe, | |
9306a8b0 BP |
1102 | .remove = mei_remove, |
1103 | .shutdown = mei_remove, | |
ab841160 OW |
1104 | .driver.pm = MEI_PM_OPS, |
1105 | }; | |
1106 | ||
6078188e | 1107 | module_pci_driver(mei_driver); |
ab841160 OW |
1108 | |
1109 | MODULE_AUTHOR("Intel Corporation"); | |
1110 | MODULE_DESCRIPTION("Intel(R) Management Engine Interface"); | |
1111 | MODULE_LICENSE("GPL v2"); |