]>
Commit | Line | Data |
---|---|---|
0b28cb4b SP |
1 | /* |
2 | * ISHTP client driver for HID (ISH) | |
3 | * | |
4 | * Copyright (c) 2014-2016, 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/module.h> | |
17 | #include <linux/hid.h> | |
18 | #include <linux/sched.h> | |
19 | #include "ishtp/ishtp-dev.h" | |
20 | #include "ishtp/client.h" | |
21 | #include "ishtp-hid.h" | |
22 | ||
23 | /* Rx ring buffer pool size */ | |
24 | #define HID_CL_RX_RING_SIZE 32 | |
25 | #define HID_CL_TX_RING_SIZE 16 | |
26 | ||
27 | /** | |
28 | * report_bad_packets() - Report bad packets | |
29 | * @hid_ishtp_cl: Client instance to get stats | |
30 | * @recv_buf: Raw received host interface message | |
31 | * @cur_pos: Current position index in payload | |
32 | * @payload_len: Length of payload expected | |
33 | * | |
34 | * Dumps error in case bad packet is received | |
35 | */ | |
36 | static void report_bad_packet(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, | |
37 | size_t cur_pos, size_t payload_len) | |
38 | { | |
39 | struct hostif_msg *recv_msg = recv_buf; | |
40 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
41 | ||
42 | dev_err(&client_data->cl_device->dev, "[hid-ish]: BAD packet %02X\n" | |
43 | "total_bad=%u cur_pos=%u\n" | |
44 | "[%02X %02X %02X %02X]\n" | |
45 | "payload_len=%u\n" | |
46 | "multi_packet_cnt=%u\n" | |
47 | "is_response=%02X\n", | |
48 | recv_msg->hdr.command, client_data->bad_recv_cnt, | |
49 | (unsigned int)cur_pos, | |
50 | ((unsigned char *)recv_msg)[0], ((unsigned char *)recv_msg)[1], | |
51 | ((unsigned char *)recv_msg)[2], ((unsigned char *)recv_msg)[3], | |
52 | (unsigned int)payload_len, client_data->multi_packet_cnt, | |
53 | recv_msg->hdr.command & ~CMD_MASK); | |
54 | } | |
55 | ||
56 | /** | |
57 | * process_recv() - Received and parse incoming packet | |
58 | * @hid_ishtp_cl: Client instance to get stats | |
59 | * @recv_buf: Raw received host interface message | |
60 | * @data_len: length of the message | |
61 | * | |
62 | * Parse the incoming packet. If it is a response packet then it will update | |
63 | * per instance flags and wake up the caller waiting to for the response. | |
64 | */ | |
65 | static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, | |
66 | size_t data_len) | |
67 | { | |
68 | struct hostif_msg *recv_msg; | |
69 | unsigned char *payload; | |
70 | struct device_info *dev_info; | |
71 | int i, j; | |
72 | size_t payload_len, total_len, cur_pos; | |
73 | int report_type; | |
74 | struct report_list *reports_list; | |
75 | char *reports; | |
76 | size_t report_len; | |
77 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
78 | int curr_hid_dev = client_data->cur_hid_dev; | |
79 | ||
0b28cb4b SP |
80 | payload = recv_buf + sizeof(struct hostif_msg_hdr); |
81 | total_len = data_len; | |
82 | cur_pos = 0; | |
83 | ||
84 | do { | |
37ba3c35 HG |
85 | if (cur_pos + sizeof(struct hostif_msg) > total_len) { |
86 | dev_err(&client_data->cl_device->dev, | |
87 | "[hid-ish]: error, received %u which is less than data header %u\n", | |
88 | (unsigned int)data_len, | |
89 | (unsigned int)sizeof(struct hostif_msg_hdr)); | |
90 | ++client_data->bad_recv_cnt; | |
91 | ish_hw_reset(hid_ishtp_cl->dev); | |
92 | break; | |
93 | } | |
94 | ||
0b28cb4b SP |
95 | recv_msg = (struct hostif_msg *)(recv_buf + cur_pos); |
96 | payload_len = recv_msg->hdr.size; | |
97 | ||
98 | /* Sanity checks */ | |
99 | if (cur_pos + payload_len + sizeof(struct hostif_msg) > | |
100 | total_len) { | |
101 | ++client_data->bad_recv_cnt; | |
102 | report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos, | |
103 | payload_len); | |
104 | ish_hw_reset(hid_ishtp_cl->dev); | |
105 | break; | |
106 | } | |
107 | ||
108 | hid_ishtp_trace(client_data, "%s %d\n", | |
109 | __func__, recv_msg->hdr.command & CMD_MASK); | |
110 | ||
111 | switch (recv_msg->hdr.command & CMD_MASK) { | |
112 | case HOSTIF_DM_ENUM_DEVICES: | |
113 | if ((!(recv_msg->hdr.command & ~CMD_MASK) || | |
114 | client_data->init_done)) { | |
115 | ++client_data->bad_recv_cnt; | |
116 | report_bad_packet(hid_ishtp_cl, recv_msg, | |
117 | cur_pos, | |
118 | payload_len); | |
119 | ish_hw_reset(hid_ishtp_cl->dev); | |
120 | break; | |
121 | } | |
122 | client_data->hid_dev_count = (unsigned int)*payload; | |
123 | if (!client_data->hid_devices) | |
124 | client_data->hid_devices = devm_kzalloc( | |
125 | &client_data->cl_device->dev, | |
126 | client_data->hid_dev_count * | |
127 | sizeof(struct device_info), | |
128 | GFP_KERNEL); | |
129 | if (!client_data->hid_devices) { | |
130 | dev_err(&client_data->cl_device->dev, | |
131 | "Mem alloc failed for hid device info\n"); | |
132 | wake_up_interruptible(&client_data->init_wait); | |
133 | break; | |
134 | } | |
135 | for (i = 0; i < client_data->hid_dev_count; ++i) { | |
136 | if (1 + sizeof(struct device_info) * i >= | |
137 | payload_len) { | |
138 | dev_err(&client_data->cl_device->dev, | |
318fc2a8 | 139 | "[hid-ish]: [ENUM_DEVICES]: content size %zu is bigger than payload_len %zu\n", |
0b28cb4b | 140 | 1 + sizeof(struct device_info) |
318fc2a8 | 141 | * i, payload_len); |
0b28cb4b SP |
142 | } |
143 | ||
144 | if (1 + sizeof(struct device_info) * i >= | |
145 | data_len) | |
146 | break; | |
147 | ||
148 | dev_info = (struct device_info *)(payload + 1 + | |
149 | sizeof(struct device_info) * i); | |
150 | if (client_data->hid_devices) | |
151 | memcpy(client_data->hid_devices + i, | |
152 | dev_info, | |
153 | sizeof(struct device_info)); | |
154 | } | |
155 | ||
156 | client_data->enum_devices_done = true; | |
157 | wake_up_interruptible(&client_data->init_wait); | |
158 | ||
159 | break; | |
160 | ||
161 | case HOSTIF_GET_HID_DESCRIPTOR: | |
162 | if ((!(recv_msg->hdr.command & ~CMD_MASK) || | |
163 | client_data->init_done)) { | |
164 | ++client_data->bad_recv_cnt; | |
165 | report_bad_packet(hid_ishtp_cl, recv_msg, | |
166 | cur_pos, | |
167 | payload_len); | |
168 | ish_hw_reset(hid_ishtp_cl->dev); | |
169 | break; | |
170 | } | |
171 | if (!client_data->hid_descr[curr_hid_dev]) | |
172 | client_data->hid_descr[curr_hid_dev] = | |
173 | devm_kmalloc(&client_data->cl_device->dev, | |
174 | payload_len, GFP_KERNEL); | |
175 | if (client_data->hid_descr[curr_hid_dev]) { | |
176 | memcpy(client_data->hid_descr[curr_hid_dev], | |
177 | payload, payload_len); | |
178 | client_data->hid_descr_size[curr_hid_dev] = | |
179 | payload_len; | |
180 | client_data->hid_descr_done = true; | |
181 | } | |
182 | wake_up_interruptible(&client_data->init_wait); | |
183 | ||
184 | break; | |
185 | ||
186 | case HOSTIF_GET_REPORT_DESCRIPTOR: | |
187 | if ((!(recv_msg->hdr.command & ~CMD_MASK) || | |
188 | client_data->init_done)) { | |
189 | ++client_data->bad_recv_cnt; | |
190 | report_bad_packet(hid_ishtp_cl, recv_msg, | |
191 | cur_pos, | |
192 | payload_len); | |
193 | ish_hw_reset(hid_ishtp_cl->dev); | |
194 | break; | |
195 | } | |
196 | if (!client_data->report_descr[curr_hid_dev]) | |
197 | client_data->report_descr[curr_hid_dev] = | |
198 | devm_kmalloc(&client_data->cl_device->dev, | |
199 | payload_len, GFP_KERNEL); | |
200 | if (client_data->report_descr[curr_hid_dev]) { | |
201 | memcpy(client_data->report_descr[curr_hid_dev], | |
202 | payload, | |
203 | payload_len); | |
204 | client_data->report_descr_size[curr_hid_dev] = | |
205 | payload_len; | |
206 | client_data->report_descr_done = true; | |
207 | } | |
208 | wake_up_interruptible(&client_data->init_wait); | |
209 | ||
210 | break; | |
211 | ||
212 | case HOSTIF_GET_FEATURE_REPORT: | |
213 | report_type = HID_FEATURE_REPORT; | |
214 | goto do_get_report; | |
215 | ||
216 | case HOSTIF_GET_INPUT_REPORT: | |
217 | report_type = HID_INPUT_REPORT; | |
218 | do_get_report: | |
219 | /* Get index of device that matches this id */ | |
220 | for (i = 0; i < client_data->num_hid_devices; ++i) { | |
221 | if (recv_msg->hdr.device_id == | |
222 | client_data->hid_devices[i].dev_id) | |
223 | if (client_data->hid_sensor_hubs[i]) { | |
224 | hid_input_report( | |
225 | client_data->hid_sensor_hubs[ | |
226 | i], | |
227 | report_type, payload, | |
228 | payload_len, 0); | |
229 | ishtp_hid_wakeup( | |
230 | client_data->hid_sensor_hubs[ | |
231 | i]); | |
232 | break; | |
233 | } | |
234 | } | |
235 | break; | |
236 | ||
237 | case HOSTIF_SET_FEATURE_REPORT: | |
238 | /* Get index of device that matches this id */ | |
239 | for (i = 0; i < client_data->num_hid_devices; ++i) { | |
240 | if (recv_msg->hdr.device_id == | |
241 | client_data->hid_devices[i].dev_id) | |
242 | if (client_data->hid_sensor_hubs[i]) { | |
243 | ishtp_hid_wakeup( | |
244 | client_data->hid_sensor_hubs[ | |
245 | i]); | |
246 | break; | |
247 | } | |
248 | } | |
249 | break; | |
250 | ||
251 | case HOSTIF_PUBLISH_INPUT_REPORT: | |
252 | report_type = HID_INPUT_REPORT; | |
253 | for (i = 0; i < client_data->num_hid_devices; ++i) | |
254 | if (recv_msg->hdr.device_id == | |
255 | client_data->hid_devices[i].dev_id) | |
256 | if (client_data->hid_sensor_hubs[i]) | |
257 | hid_input_report( | |
258 | client_data->hid_sensor_hubs[ | |
259 | i], | |
260 | report_type, payload, | |
261 | payload_len, 0); | |
262 | break; | |
263 | ||
264 | case HOSTIF_PUBLISH_INPUT_REPORT_LIST: | |
265 | report_type = HID_INPUT_REPORT; | |
266 | reports_list = (struct report_list *)payload; | |
267 | reports = (char *)reports_list->reports; | |
268 | ||
269 | for (j = 0; j < reports_list->num_of_reports; j++) { | |
270 | recv_msg = (struct hostif_msg *)(reports + | |
271 | sizeof(uint16_t)); | |
272 | report_len = *(uint16_t *)reports; | |
273 | payload = reports + sizeof(uint16_t) + | |
274 | sizeof(struct hostif_msg_hdr); | |
275 | payload_len = report_len - | |
276 | sizeof(struct hostif_msg_hdr); | |
277 | ||
278 | for (i = 0; i < client_data->num_hid_devices; | |
279 | ++i) | |
280 | if (recv_msg->hdr.device_id == | |
281 | client_data->hid_devices[i].dev_id && | |
282 | client_data->hid_sensor_hubs[i]) { | |
283 | hid_input_report( | |
284 | client_data->hid_sensor_hubs[ | |
285 | i], | |
286 | report_type, | |
287 | payload, payload_len, | |
288 | 0); | |
289 | } | |
290 | ||
291 | reports += sizeof(uint16_t) + report_len; | |
292 | } | |
293 | break; | |
294 | default: | |
295 | ++client_data->bad_recv_cnt; | |
296 | report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos, | |
297 | payload_len); | |
298 | ish_hw_reset(hid_ishtp_cl->dev); | |
299 | break; | |
300 | ||
301 | } | |
302 | ||
303 | if (!cur_pos && cur_pos + payload_len + | |
304 | sizeof(struct hostif_msg) < total_len) | |
305 | ++client_data->multi_packet_cnt; | |
306 | ||
307 | cur_pos += payload_len + sizeof(struct hostif_msg); | |
308 | payload += payload_len + sizeof(struct hostif_msg); | |
309 | ||
310 | } while (cur_pos < total_len); | |
311 | } | |
312 | ||
313 | /** | |
314 | * ish_cl_event_cb() - bus driver callback for incoming message/packet | |
315 | * @device: Pointer to the the ishtp client device for which this message | |
316 | * is targeted | |
317 | * | |
318 | * Remove the packet from the list and process the message by calling | |
319 | * process_recv | |
320 | */ | |
321 | static void ish_cl_event_cb(struct ishtp_cl_device *device) | |
322 | { | |
323 | struct ishtp_cl *hid_ishtp_cl = device->driver_data; | |
324 | struct ishtp_cl_rb *rb_in_proc; | |
325 | size_t r_length; | |
326 | unsigned long flags; | |
327 | ||
328 | if (!hid_ishtp_cl) | |
329 | return; | |
330 | ||
331 | spin_lock_irqsave(&hid_ishtp_cl->in_process_spinlock, flags); | |
332 | while (!list_empty(&hid_ishtp_cl->in_process_list.list)) { | |
333 | rb_in_proc = list_entry( | |
334 | hid_ishtp_cl->in_process_list.list.next, | |
335 | struct ishtp_cl_rb, list); | |
336 | list_del_init(&rb_in_proc->list); | |
337 | spin_unlock_irqrestore(&hid_ishtp_cl->in_process_spinlock, | |
338 | flags); | |
339 | ||
340 | if (!rb_in_proc->buffer.data) | |
341 | return; | |
342 | ||
343 | r_length = rb_in_proc->buf_idx; | |
344 | ||
345 | /* decide what to do with received data */ | |
346 | process_recv(hid_ishtp_cl, rb_in_proc->buffer.data, r_length); | |
347 | ||
348 | ishtp_cl_io_rb_recycle(rb_in_proc); | |
349 | spin_lock_irqsave(&hid_ishtp_cl->in_process_spinlock, flags); | |
350 | } | |
351 | spin_unlock_irqrestore(&hid_ishtp_cl->in_process_spinlock, flags); | |
352 | } | |
353 | ||
354 | /** | |
355 | * hid_ishtp_set_feature() - send request to ISH FW to set a feature request | |
356 | * @hid: hid device instance for this request | |
357 | * @buf: feature buffer | |
358 | * @len: Length of feature buffer | |
359 | * @report_id: Report id for the feature set request | |
360 | * | |
361 | * This is called from hid core .request() callback. This function doesn't wait | |
362 | * for response. | |
363 | */ | |
364 | void hid_ishtp_set_feature(struct hid_device *hid, char *buf, unsigned int len, | |
365 | int report_id) | |
366 | { | |
367 | struct ishtp_hid_data *hid_data = hid->driver_data; | |
368 | struct ishtp_cl_data *client_data = hid_data->client_data; | |
369 | struct hostif_msg *msg = (struct hostif_msg *)buf; | |
370 | int rv; | |
371 | int i; | |
372 | ||
373 | hid_ishtp_trace(client_data, "%s hid %p\n", __func__, hid); | |
374 | ||
375 | rv = ishtp_hid_link_ready_wait(client_data); | |
376 | if (rv) { | |
377 | hid_ishtp_trace(client_data, "%s hid %p link not ready\n", | |
378 | __func__, hid); | |
379 | return; | |
380 | } | |
381 | ||
382 | memset(msg, 0, sizeof(struct hostif_msg)); | |
383 | msg->hdr.command = HOSTIF_SET_FEATURE_REPORT; | |
384 | for (i = 0; i < client_data->num_hid_devices; ++i) { | |
385 | if (hid == client_data->hid_sensor_hubs[i]) { | |
386 | msg->hdr.device_id = | |
387 | client_data->hid_devices[i].dev_id; | |
388 | break; | |
389 | } | |
390 | } | |
391 | ||
392 | if (i == client_data->num_hid_devices) | |
393 | return; | |
394 | ||
395 | rv = ishtp_cl_send(client_data->hid_ishtp_cl, buf, len); | |
396 | if (rv) | |
397 | hid_ishtp_trace(client_data, "%s hid %p send failed\n", | |
398 | __func__, hid); | |
399 | } | |
400 | ||
401 | /** | |
402 | * hid_ishtp_get_report() - request to get feature/input report | |
403 | * @hid: hid device instance for this request | |
404 | * @report_id: Report id for the get request | |
405 | * @report_type: Report type for the this request | |
406 | * | |
407 | * This is called from hid core .request() callback. This function will send | |
408 | * request to FW and return without waiting for response. | |
409 | */ | |
410 | void hid_ishtp_get_report(struct hid_device *hid, int report_id, | |
411 | int report_type) | |
412 | { | |
413 | struct ishtp_hid_data *hid_data = hid->driver_data; | |
414 | struct ishtp_cl_data *client_data = hid_data->client_data; | |
749ab300 | 415 | struct hostif_msg_to_sensor msg = {}; |
0b28cb4b SP |
416 | int rv; |
417 | int i; | |
418 | ||
419 | hid_ishtp_trace(client_data, "%s hid %p\n", __func__, hid); | |
420 | rv = ishtp_hid_link_ready_wait(client_data); | |
421 | if (rv) { | |
422 | hid_ishtp_trace(client_data, "%s hid %p link not ready\n", | |
423 | __func__, hid); | |
424 | return; | |
425 | } | |
426 | ||
749ab300 | 427 | msg.hdr.command = (report_type == HID_FEATURE_REPORT) ? |
0b28cb4b SP |
428 | HOSTIF_GET_FEATURE_REPORT : HOSTIF_GET_INPUT_REPORT; |
429 | for (i = 0; i < client_data->num_hid_devices; ++i) { | |
430 | if (hid == client_data->hid_sensor_hubs[i]) { | |
749ab300 | 431 | msg.hdr.device_id = |
0b28cb4b SP |
432 | client_data->hid_devices[i].dev_id; |
433 | break; | |
434 | } | |
435 | } | |
436 | ||
437 | if (i == client_data->num_hid_devices) | |
438 | return; | |
439 | ||
749ab300 HG |
440 | msg.report_id = report_id; |
441 | rv = ishtp_cl_send(client_data->hid_ishtp_cl, (uint8_t *)&msg, | |
442 | sizeof(msg)); | |
0b28cb4b SP |
443 | if (rv) |
444 | hid_ishtp_trace(client_data, "%s hid %p send failed\n", | |
445 | __func__, hid); | |
446 | } | |
447 | ||
448 | /** | |
449 | * ishtp_hid_link_ready_wait() - Wait for link ready | |
450 | * @client_data: client data instance | |
451 | * | |
452 | * If the transport link started suspend process, then wait, till either | |
453 | * resumed or timeout | |
454 | * | |
455 | * Return: 0 on success, non zero on error | |
456 | */ | |
457 | int ishtp_hid_link_ready_wait(struct ishtp_cl_data *client_data) | |
458 | { | |
459 | int rc; | |
460 | ||
461 | if (client_data->suspended) { | |
462 | hid_ishtp_trace(client_data, "wait for link ready\n"); | |
463 | rc = wait_event_interruptible_timeout( | |
464 | client_data->ishtp_resume_wait, | |
465 | !client_data->suspended, | |
466 | 5 * HZ); | |
467 | ||
468 | if (rc == 0) { | |
469 | hid_ishtp_trace(client_data, "link not ready\n"); | |
470 | return -EIO; | |
471 | } | |
472 | hid_ishtp_trace(client_data, "link ready\n"); | |
473 | } | |
474 | ||
475 | return 0; | |
476 | } | |
477 | ||
478 | /** | |
479 | * ishtp_enum_enum_devices() - Enumerate hid devices | |
480 | * @hid_ishtp_cl: client instance | |
481 | * | |
482 | * Helper function to send request to firmware to enumerate HID devices | |
483 | * | |
484 | * Return: 0 on success, non zero on error | |
485 | */ | |
486 | static int ishtp_enum_enum_devices(struct ishtp_cl *hid_ishtp_cl) | |
487 | { | |
488 | struct hostif_msg msg; | |
489 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
490 | int retry_count; | |
491 | int rv; | |
492 | ||
493 | /* Send HOSTIF_DM_ENUM_DEVICES */ | |
494 | memset(&msg, 0, sizeof(struct hostif_msg)); | |
495 | msg.hdr.command = HOSTIF_DM_ENUM_DEVICES; | |
496 | rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *)&msg, | |
497 | sizeof(struct hostif_msg)); | |
498 | if (rv) | |
499 | return rv; | |
500 | ||
501 | retry_count = 0; | |
502 | while (!client_data->enum_devices_done && | |
503 | retry_count < 10) { | |
504 | wait_event_interruptible_timeout(client_data->init_wait, | |
505 | client_data->enum_devices_done, | |
506 | 3 * HZ); | |
507 | ++retry_count; | |
508 | if (!client_data->enum_devices_done) | |
509 | /* Send HOSTIF_DM_ENUM_DEVICES */ | |
510 | rv = ishtp_cl_send(hid_ishtp_cl, | |
511 | (unsigned char *) &msg, | |
512 | sizeof(struct hostif_msg)); | |
513 | } | |
514 | if (!client_data->enum_devices_done) { | |
515 | dev_err(&client_data->cl_device->dev, | |
516 | "[hid-ish]: timed out waiting for enum_devices\n"); | |
517 | return -ETIMEDOUT; | |
518 | } | |
519 | if (!client_data->hid_devices) { | |
520 | dev_err(&client_data->cl_device->dev, | |
521 | "[hid-ish]: failed to allocate HID dev structures\n"); | |
522 | return -ENOMEM; | |
523 | } | |
524 | ||
525 | client_data->num_hid_devices = client_data->hid_dev_count; | |
526 | dev_info(&hid_ishtp_cl->device->dev, | |
527 | "[hid-ish]: enum_devices_done OK, num_hid_devices=%d\n", | |
528 | client_data->num_hid_devices); | |
529 | ||
530 | return 0; | |
531 | } | |
532 | ||
533 | /** | |
534 | * ishtp_get_hid_descriptor() - Get hid descriptor | |
535 | * @hid_ishtp_cl: client instance | |
536 | * @index: Index into the hid_descr array | |
537 | * | |
538 | * Helper function to send request to firmware get HID descriptor of a device | |
539 | * | |
540 | * Return: 0 on success, non zero on error | |
541 | */ | |
542 | static int ishtp_get_hid_descriptor(struct ishtp_cl *hid_ishtp_cl, int index) | |
543 | { | |
544 | struct hostif_msg msg; | |
545 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
546 | int rv; | |
547 | ||
548 | /* Get HID descriptor */ | |
549 | client_data->hid_descr_done = false; | |
550 | memset(&msg, 0, sizeof(struct hostif_msg)); | |
551 | msg.hdr.command = HOSTIF_GET_HID_DESCRIPTOR; | |
552 | msg.hdr.device_id = client_data->hid_devices[index].dev_id; | |
553 | rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg, | |
554 | sizeof(struct hostif_msg)); | |
555 | if (rv) | |
556 | return rv; | |
557 | ||
558 | if (!client_data->hid_descr_done) { | |
559 | wait_event_interruptible_timeout(client_data->init_wait, | |
560 | client_data->hid_descr_done, | |
561 | 3 * HZ); | |
562 | if (!client_data->hid_descr_done) { | |
563 | dev_err(&client_data->cl_device->dev, | |
564 | "[hid-ish]: timed out for hid_descr_done\n"); | |
565 | return -EIO; | |
566 | } | |
567 | ||
568 | if (!client_data->hid_descr[index]) { | |
569 | dev_err(&client_data->cl_device->dev, | |
570 | "[hid-ish]: allocation HID desc fail\n"); | |
571 | return -ENOMEM; | |
572 | } | |
573 | } | |
574 | ||
575 | return 0; | |
576 | } | |
577 | ||
578 | /** | |
579 | * ishtp_get_report_descriptor() - Get report descriptor | |
580 | * @hid_ishtp_cl: client instance | |
581 | * @index: Index into the hid_descr array | |
582 | * | |
583 | * Helper function to send request to firmware get HID report descriptor of | |
584 | * a device | |
585 | * | |
586 | * Return: 0 on success, non zero on error | |
587 | */ | |
588 | static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl, | |
589 | int index) | |
590 | { | |
591 | struct hostif_msg msg; | |
592 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
593 | int rv; | |
594 | ||
595 | /* Get report descriptor */ | |
596 | client_data->report_descr_done = false; | |
597 | memset(&msg, 0, sizeof(struct hostif_msg)); | |
598 | msg.hdr.command = HOSTIF_GET_REPORT_DESCRIPTOR; | |
599 | msg.hdr.device_id = client_data->hid_devices[index].dev_id; | |
600 | rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg, | |
601 | sizeof(struct hostif_msg)); | |
602 | if (rv) | |
603 | return rv; | |
604 | ||
605 | if (!client_data->report_descr_done) | |
606 | wait_event_interruptible_timeout(client_data->init_wait, | |
607 | client_data->report_descr_done, | |
608 | 3 * HZ); | |
609 | if (!client_data->report_descr_done) { | |
610 | dev_err(&client_data->cl_device->dev, | |
611 | "[hid-ish]: timed out for report descr\n"); | |
612 | return -EIO; | |
613 | } | |
614 | if (!client_data->report_descr[index]) { | |
615 | dev_err(&client_data->cl_device->dev, | |
616 | "[hid-ish]: failed to alloc report descr\n"); | |
617 | return -ENOMEM; | |
618 | } | |
619 | ||
620 | return 0; | |
621 | } | |
622 | ||
623 | /** | |
624 | * hid_ishtp_cl_init() - Init function for ISHTP client | |
625 | * @hid_ishtp_cl: ISHTP client instance | |
626 | * @reset: true if called for init after reset | |
627 | * | |
628 | * This function complete the initializtion of the client. The summary of | |
629 | * processing: | |
630 | * - Send request to enumerate the hid clients | |
631 | * Get the HID descriptor for each enumearated device | |
632 | * Get report description of each device | |
633 | * Register each device wik hid core by calling ishtp_hid_probe | |
634 | * | |
635 | * Return: 0 on success, non zero on error | |
636 | */ | |
637 | static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset) | |
638 | { | |
639 | struct ishtp_device *dev; | |
640 | unsigned long flags; | |
641 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
642 | int i; | |
643 | int rv; | |
644 | ||
645 | dev_dbg(&client_data->cl_device->dev, "%s\n", __func__); | |
646 | hid_ishtp_trace(client_data, "%s reset flag: %d\n", __func__, reset); | |
647 | ||
648 | rv = ishtp_cl_link(hid_ishtp_cl, ISHTP_HOST_CLIENT_ID_ANY); | |
649 | if (rv) { | |
650 | dev_err(&client_data->cl_device->dev, | |
651 | "ishtp_cl_link failed\n"); | |
652 | return -ENOMEM; | |
653 | } | |
654 | ||
655 | client_data->init_done = 0; | |
656 | ||
657 | dev = hid_ishtp_cl->dev; | |
658 | ||
659 | /* Connect to FW client */ | |
660 | hid_ishtp_cl->rx_ring_size = HID_CL_RX_RING_SIZE; | |
661 | hid_ishtp_cl->tx_ring_size = HID_CL_TX_RING_SIZE; | |
662 | ||
663 | spin_lock_irqsave(&dev->fw_clients_lock, flags); | |
664 | i = ishtp_fw_cl_by_uuid(dev, &hid_ishtp_guid); | |
665 | if (i < 0) { | |
666 | spin_unlock_irqrestore(&dev->fw_clients_lock, flags); | |
667 | dev_err(&client_data->cl_device->dev, | |
668 | "ish client uuid not found\n"); | |
669 | return i; | |
670 | } | |
671 | hid_ishtp_cl->fw_client_id = dev->fw_clients[i].client_id; | |
672 | spin_unlock_irqrestore(&dev->fw_clients_lock, flags); | |
673 | hid_ishtp_cl->state = ISHTP_CL_CONNECTING; | |
674 | ||
675 | rv = ishtp_cl_connect(hid_ishtp_cl); | |
676 | if (rv) { | |
677 | dev_err(&client_data->cl_device->dev, | |
678 | "client connect fail\n"); | |
679 | goto err_cl_unlink; | |
680 | } | |
681 | ||
682 | hid_ishtp_trace(client_data, "%s client connected\n", __func__); | |
683 | ||
684 | /* Register read callback */ | |
685 | ishtp_register_event_cb(hid_ishtp_cl->device, ish_cl_event_cb); | |
686 | ||
687 | rv = ishtp_enum_enum_devices(hid_ishtp_cl); | |
688 | if (rv) | |
689 | goto err_cl_disconnect; | |
690 | ||
691 | hid_ishtp_trace(client_data, "%s enumerated device count %d\n", | |
692 | __func__, client_data->num_hid_devices); | |
693 | ||
694 | for (i = 0; i < client_data->num_hid_devices; ++i) { | |
695 | client_data->cur_hid_dev = i; | |
696 | ||
697 | rv = ishtp_get_hid_descriptor(hid_ishtp_cl, i); | |
698 | if (rv) | |
699 | goto err_cl_disconnect; | |
700 | ||
701 | rv = ishtp_get_report_descriptor(hid_ishtp_cl, i); | |
702 | if (rv) | |
703 | goto err_cl_disconnect; | |
704 | ||
705 | if (!reset) { | |
706 | rv = ishtp_hid_probe(i, client_data); | |
707 | if (rv) { | |
708 | dev_err(&client_data->cl_device->dev, | |
709 | "[hid-ish]: HID probe for #%u failed: %d\n", | |
710 | i, rv); | |
711 | goto err_cl_disconnect; | |
712 | } | |
713 | } | |
714 | } /* for() on all hid devices */ | |
715 | ||
716 | client_data->init_done = 1; | |
717 | client_data->suspended = false; | |
718 | wake_up_interruptible(&client_data->ishtp_resume_wait); | |
719 | hid_ishtp_trace(client_data, "%s successful init\n", __func__); | |
720 | return 0; | |
721 | ||
722 | err_cl_disconnect: | |
723 | hid_ishtp_cl->state = ISHTP_CL_DISCONNECTING; | |
724 | ishtp_cl_disconnect(hid_ishtp_cl); | |
725 | err_cl_unlink: | |
726 | ishtp_cl_unlink(hid_ishtp_cl); | |
727 | return rv; | |
728 | } | |
729 | ||
730 | /** | |
731 | * hid_ishtp_cl_deinit() - Deinit function for ISHTP client | |
732 | * @hid_ishtp_cl: ISHTP client instance | |
733 | * | |
734 | * Unlink and free hid client | |
735 | */ | |
736 | static void hid_ishtp_cl_deinit(struct ishtp_cl *hid_ishtp_cl) | |
737 | { | |
738 | ishtp_cl_unlink(hid_ishtp_cl); | |
739 | ishtp_cl_flush_queues(hid_ishtp_cl); | |
740 | ||
741 | /* disband and free all Tx and Rx client-level rings */ | |
742 | ishtp_cl_free(hid_ishtp_cl); | |
743 | } | |
744 | ||
745 | static void hid_ishtp_cl_reset_handler(struct work_struct *work) | |
746 | { | |
747 | struct ishtp_cl_data *client_data; | |
748 | struct ishtp_cl *hid_ishtp_cl; | |
749 | struct ishtp_cl_device *cl_device; | |
750 | int retry; | |
751 | int rv; | |
752 | ||
753 | client_data = container_of(work, struct ishtp_cl_data, work); | |
754 | ||
755 | hid_ishtp_cl = client_data->hid_ishtp_cl; | |
756 | cl_device = client_data->cl_device; | |
757 | ||
758 | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | |
759 | hid_ishtp_cl); | |
760 | dev_dbg(&cl_device->dev, "%s\n", __func__); | |
761 | ||
762 | hid_ishtp_cl_deinit(hid_ishtp_cl); | |
763 | ||
764 | hid_ishtp_cl = ishtp_cl_allocate(cl_device->ishtp_dev); | |
765 | if (!hid_ishtp_cl) | |
766 | return; | |
767 | ||
768 | cl_device->driver_data = hid_ishtp_cl; | |
769 | hid_ishtp_cl->client_data = client_data; | |
770 | client_data->hid_ishtp_cl = hid_ishtp_cl; | |
771 | ||
772 | client_data->num_hid_devices = 0; | |
773 | ||
774 | for (retry = 0; retry < 3; ++retry) { | |
775 | rv = hid_ishtp_cl_init(hid_ishtp_cl, 1); | |
776 | if (!rv) | |
777 | break; | |
778 | dev_err(&client_data->cl_device->dev, "Retry reset init\n"); | |
779 | } | |
780 | if (rv) { | |
781 | dev_err(&client_data->cl_device->dev, "Reset Failed\n"); | |
782 | hid_ishtp_trace(client_data, "%s Failed hid_ishtp_cl %p\n", | |
783 | __func__, hid_ishtp_cl); | |
784 | } | |
785 | } | |
786 | ||
787 | /** | |
788 | * hid_ishtp_cl_probe() - ISHTP client driver probe | |
789 | * @cl_device: ISHTP client device instance | |
790 | * | |
791 | * This function gets called on device create on ISHTP bus | |
792 | * | |
793 | * Return: 0 on success, non zero on error | |
794 | */ | |
795 | static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device) | |
796 | { | |
797 | struct ishtp_cl *hid_ishtp_cl; | |
798 | struct ishtp_cl_data *client_data; | |
799 | int rv; | |
800 | ||
801 | if (!cl_device) | |
802 | return -ENODEV; | |
803 | ||
804 | if (uuid_le_cmp(hid_ishtp_guid, | |
805 | cl_device->fw_client->props.protocol_name) != 0) | |
806 | return -ENODEV; | |
807 | ||
808 | client_data = devm_kzalloc(&cl_device->dev, sizeof(*client_data), | |
809 | GFP_KERNEL); | |
810 | if (!client_data) | |
811 | return -ENOMEM; | |
812 | ||
813 | hid_ishtp_cl = ishtp_cl_allocate(cl_device->ishtp_dev); | |
814 | if (!hid_ishtp_cl) | |
815 | return -ENOMEM; | |
816 | ||
817 | cl_device->driver_data = hid_ishtp_cl; | |
818 | hid_ishtp_cl->client_data = client_data; | |
819 | client_data->hid_ishtp_cl = hid_ishtp_cl; | |
820 | client_data->cl_device = cl_device; | |
821 | ||
822 | init_waitqueue_head(&client_data->init_wait); | |
823 | init_waitqueue_head(&client_data->ishtp_resume_wait); | |
824 | ||
825 | INIT_WORK(&client_data->work, hid_ishtp_cl_reset_handler); | |
826 | ||
827 | rv = hid_ishtp_cl_init(hid_ishtp_cl, 0); | |
828 | if (rv) { | |
829 | ishtp_cl_free(hid_ishtp_cl); | |
830 | return rv; | |
831 | } | |
832 | ishtp_get_device(cl_device); | |
833 | ||
834 | return 0; | |
835 | } | |
836 | ||
837 | /** | |
838 | * hid_ishtp_cl_remove() - ISHTP client driver remove | |
839 | * @cl_device: ISHTP client device instance | |
840 | * | |
841 | * This function gets called on device remove on ISHTP bus | |
842 | * | |
843 | * Return: 0 | |
844 | */ | |
845 | static int hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device) | |
846 | { | |
847 | struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; | |
848 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
849 | ||
850 | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | |
851 | hid_ishtp_cl); | |
852 | ||
853 | dev_dbg(&cl_device->dev, "%s\n", __func__); | |
854 | hid_ishtp_cl->state = ISHTP_CL_DISCONNECTING; | |
855 | ishtp_cl_disconnect(hid_ishtp_cl); | |
856 | ishtp_put_device(cl_device); | |
857 | ishtp_hid_remove(client_data); | |
858 | hid_ishtp_cl_deinit(hid_ishtp_cl); | |
859 | ||
860 | hid_ishtp_cl = NULL; | |
861 | ||
862 | client_data->num_hid_devices = 0; | |
863 | ||
864 | return 0; | |
865 | } | |
866 | ||
867 | /** | |
868 | * hid_ishtp_cl_reset() - ISHTP client driver reset | |
869 | * @cl_device: ISHTP client device instance | |
870 | * | |
871 | * This function gets called on device reset on ISHTP bus | |
872 | * | |
873 | * Return: 0 | |
874 | */ | |
875 | static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device) | |
876 | { | |
877 | struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; | |
878 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
879 | ||
880 | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | |
881 | hid_ishtp_cl); | |
882 | ||
883 | schedule_work(&client_data->work); | |
884 | ||
885 | return 0; | |
886 | } | |
887 | ||
888 | #define to_ishtp_cl_device(d) container_of(d, struct ishtp_cl_device, dev) | |
889 | ||
890 | /** | |
891 | * hid_ishtp_cl_suspend() - ISHTP client driver suspend | |
892 | * @device: device instance | |
893 | * | |
894 | * This function gets called on system suspend | |
895 | * | |
896 | * Return: 0 | |
897 | */ | |
898 | static int hid_ishtp_cl_suspend(struct device *device) | |
899 | { | |
900 | struct ishtp_cl_device *cl_device = to_ishtp_cl_device(device); | |
901 | struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; | |
902 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
903 | ||
904 | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | |
905 | hid_ishtp_cl); | |
906 | client_data->suspended = true; | |
907 | ||
908 | return 0; | |
909 | } | |
910 | ||
911 | /** | |
912 | * hid_ishtp_cl_resume() - ISHTP client driver resume | |
913 | * @device: device instance | |
914 | * | |
915 | * This function gets called on system resume | |
916 | * | |
917 | * Return: 0 | |
918 | */ | |
919 | static int hid_ishtp_cl_resume(struct device *device) | |
920 | { | |
921 | struct ishtp_cl_device *cl_device = to_ishtp_cl_device(device); | |
922 | struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data; | |
923 | struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data; | |
924 | ||
925 | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | |
926 | hid_ishtp_cl); | |
927 | client_data->suspended = false; | |
928 | return 0; | |
929 | } | |
930 | ||
931 | static const struct dev_pm_ops hid_ishtp_pm_ops = { | |
932 | .suspend = hid_ishtp_cl_suspend, | |
933 | .resume = hid_ishtp_cl_resume, | |
934 | }; | |
935 | ||
936 | static struct ishtp_cl_driver hid_ishtp_cl_driver = { | |
937 | .name = "ish-hid", | |
938 | .probe = hid_ishtp_cl_probe, | |
939 | .remove = hid_ishtp_cl_remove, | |
940 | .reset = hid_ishtp_cl_reset, | |
941 | .driver.pm = &hid_ishtp_pm_ops, | |
942 | }; | |
943 | ||
944 | static int __init ish_hid_init(void) | |
945 | { | |
946 | int rv; | |
947 | ||
948 | /* Register ISHTP client device driver with ISHTP Bus */ | |
949 | rv = ishtp_cl_driver_register(&hid_ishtp_cl_driver); | |
950 | ||
951 | return rv; | |
952 | ||
953 | } | |
954 | ||
955 | static void __exit ish_hid_exit(void) | |
956 | { | |
957 | ishtp_cl_driver_unregister(&hid_ishtp_cl_driver); | |
958 | } | |
959 | ||
960 | late_initcall(ish_hid_init); | |
961 | module_exit(ish_hid_exit); | |
962 | ||
963 | MODULE_DESCRIPTION("ISH ISHTP HID client driver"); | |
964 | /* Primary author */ | |
965 | MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>"); | |
966 | /* | |
967 | * Several modification for multi instance support | |
968 | * suspend/resume and clean up | |
969 | */ | |
970 | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); | |
971 | ||
972 | MODULE_LICENSE("GPL"); | |
973 | MODULE_ALIAS("ishtp:*"); |