|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * ISHTP client driver for HID (ISH) | 
|  | * | 
|  | * Copyright (c) 2014-2016, Intel Corporation. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/hid.h> | 
|  | #include <linux/intel-ish-client-if.h> | 
|  | #include <linux/sched.h> | 
|  | #include "ishtp-hid.h" | 
|  |  | 
|  | /* ISH Transport protocol (ISHTP in short) GUID */ | 
|  | static const guid_t hid_ishtp_guid = | 
|  | GUID_INIT(0x33AECD58, 0xB679, 0x4E54, | 
|  | 0x9B, 0xD9, 0xA0, 0x4D, 0x34, 0xF0, 0xC2, 0x26); | 
|  |  | 
|  | /* Rx ring buffer pool size */ | 
|  | #define HID_CL_RX_RING_SIZE	32 | 
|  | #define HID_CL_TX_RING_SIZE	16 | 
|  |  | 
|  | #define cl_data_to_dev(client_data) ishtp_device(client_data->cl_device) | 
|  |  | 
|  | /** | 
|  | * report_bad_packet() - Report bad packets | 
|  | * @hid_ishtp_cl:	Client instance to get stats | 
|  | * @recv_buf:		Raw received host interface message | 
|  | * @cur_pos:		Current position index in payload | 
|  | * @payload_len:	Length of payload expected | 
|  | * | 
|  | * Dumps error in case bad packet is received | 
|  | */ | 
|  | static void report_bad_packet(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, | 
|  | size_t cur_pos,  size_t payload_len) | 
|  | { | 
|  | struct hostif_msg *recv_msg = recv_buf; | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  |  | 
|  | dev_err(cl_data_to_dev(client_data), "[hid-ish]: BAD packet %02X\n" | 
|  | "total_bad=%u cur_pos=%u\n" | 
|  | "[%02X %02X %02X %02X]\n" | 
|  | "payload_len=%u\n" | 
|  | "multi_packet_cnt=%u\n" | 
|  | "is_response=%02X\n", | 
|  | recv_msg->hdr.command, client_data->bad_recv_cnt, | 
|  | (unsigned int)cur_pos, | 
|  | ((unsigned char *)recv_msg)[0], ((unsigned char *)recv_msg)[1], | 
|  | ((unsigned char *)recv_msg)[2], ((unsigned char *)recv_msg)[3], | 
|  | (unsigned int)payload_len, client_data->multi_packet_cnt, | 
|  | recv_msg->hdr.command & ~CMD_MASK); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * process_recv() - Received and parse incoming packet | 
|  | * @hid_ishtp_cl:	Client instance to get stats | 
|  | * @recv_buf:		Raw received host interface message | 
|  | * @data_len:		length of the message | 
|  | * | 
|  | * Parse the incoming packet. If it is a response packet then it will update | 
|  | * per instance flags and wake up the caller waiting to for the response. | 
|  | */ | 
|  | static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf, | 
|  | size_t data_len) | 
|  | { | 
|  | struct hostif_msg *recv_msg; | 
|  | unsigned char *payload; | 
|  | struct device_info *dev_info; | 
|  | int i, j; | 
|  | size_t	payload_len, total_len, cur_pos, raw_len; | 
|  | int report_type; | 
|  | struct report_list *reports_list; | 
|  | char *reports; | 
|  | size_t report_len; | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  | int curr_hid_dev = client_data->cur_hid_dev; | 
|  | struct ishtp_hid_data *hid_data = NULL; | 
|  | struct hid_device *hid = NULL; | 
|  |  | 
|  | payload = recv_buf + sizeof(struct hostif_msg_hdr); | 
|  | total_len = data_len; | 
|  | cur_pos = 0; | 
|  |  | 
|  | do { | 
|  | if (cur_pos + sizeof(struct hostif_msg) > total_len) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: error, received %u which is less than data header %u\n", | 
|  | (unsigned int)data_len, | 
|  | (unsigned int)sizeof(struct hostif_msg_hdr)); | 
|  | ++client_data->bad_recv_cnt; | 
|  | ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl)); | 
|  | break; | 
|  | } | 
|  |  | 
|  | recv_msg = (struct hostif_msg *)(recv_buf + cur_pos); | 
|  | payload_len = recv_msg->hdr.size; | 
|  |  | 
|  | /* Sanity checks */ | 
|  | if (cur_pos + payload_len + sizeof(struct hostif_msg) > | 
|  | total_len) { | 
|  | ++client_data->bad_recv_cnt; | 
|  | report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos, | 
|  | payload_len); | 
|  | ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl)); | 
|  | break; | 
|  | } | 
|  |  | 
|  | hid_ishtp_trace(client_data,  "%s %d\n", | 
|  | __func__, recv_msg->hdr.command & CMD_MASK); | 
|  |  | 
|  | switch (recv_msg->hdr.command & CMD_MASK) { | 
|  | case HOSTIF_DM_ENUM_DEVICES: | 
|  | if ((!(recv_msg->hdr.command & ~CMD_MASK) || | 
|  | client_data->init_done)) { | 
|  | ++client_data->bad_recv_cnt; | 
|  | report_bad_packet(hid_ishtp_cl, recv_msg, | 
|  | cur_pos, | 
|  | payload_len); | 
|  | ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl)); | 
|  | break; | 
|  | } | 
|  | client_data->hid_dev_count = (unsigned int)*payload; | 
|  | if (!client_data->hid_devices) | 
|  | client_data->hid_devices = devm_kcalloc( | 
|  | cl_data_to_dev(client_data), | 
|  | client_data->hid_dev_count, | 
|  | sizeof(struct device_info), | 
|  | GFP_KERNEL); | 
|  | if (!client_data->hid_devices) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "Mem alloc failed for hid device info\n"); | 
|  | wake_up_interruptible(&client_data->init_wait); | 
|  | break; | 
|  | } | 
|  | for (i = 0; i < client_data->hid_dev_count; ++i) { | 
|  | if (1 + sizeof(struct device_info) * i >= | 
|  | payload_len) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: [ENUM_DEVICES]: content size %zu is bigger than payload_len %zu\n", | 
|  | 1 + sizeof(struct device_info) | 
|  | * i, payload_len); | 
|  | } | 
|  |  | 
|  | if (1 + sizeof(struct device_info) * i >= | 
|  | data_len) | 
|  | break; | 
|  |  | 
|  | dev_info = (struct device_info *)(payload + 1 + | 
|  | sizeof(struct device_info) * i); | 
|  | if (client_data->hid_devices) | 
|  | memcpy(client_data->hid_devices + i, | 
|  | dev_info, | 
|  | sizeof(struct device_info)); | 
|  | } | 
|  |  | 
|  | client_data->enum_devices_done = true; | 
|  | wake_up_interruptible(&client_data->init_wait); | 
|  |  | 
|  | break; | 
|  |  | 
|  | case HOSTIF_GET_HID_DESCRIPTOR: | 
|  | if ((!(recv_msg->hdr.command & ~CMD_MASK) || | 
|  | client_data->init_done)) { | 
|  | ++client_data->bad_recv_cnt; | 
|  | report_bad_packet(hid_ishtp_cl, recv_msg, | 
|  | cur_pos, | 
|  | payload_len); | 
|  | ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl)); | 
|  | break; | 
|  | } | 
|  | if (!client_data->hid_descr[curr_hid_dev]) | 
|  | client_data->hid_descr[curr_hid_dev] = | 
|  | devm_kmalloc(cl_data_to_dev(client_data), | 
|  | payload_len, GFP_KERNEL); | 
|  | if (client_data->hid_descr[curr_hid_dev]) { | 
|  | memcpy(client_data->hid_descr[curr_hid_dev], | 
|  | payload, payload_len); | 
|  | client_data->hid_descr_size[curr_hid_dev] = | 
|  | payload_len; | 
|  | client_data->hid_descr_done = true; | 
|  | } | 
|  | wake_up_interruptible(&client_data->init_wait); | 
|  |  | 
|  | break; | 
|  |  | 
|  | case HOSTIF_GET_REPORT_DESCRIPTOR: | 
|  | if ((!(recv_msg->hdr.command & ~CMD_MASK) || | 
|  | client_data->init_done)) { | 
|  | ++client_data->bad_recv_cnt; | 
|  | report_bad_packet(hid_ishtp_cl, recv_msg, | 
|  | cur_pos, | 
|  | payload_len); | 
|  | ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl)); | 
|  | break; | 
|  | } | 
|  | if (!client_data->report_descr[curr_hid_dev]) | 
|  | client_data->report_descr[curr_hid_dev] = | 
|  | devm_kmalloc(cl_data_to_dev(client_data), | 
|  | payload_len, GFP_KERNEL); | 
|  | if (client_data->report_descr[curr_hid_dev])  { | 
|  | memcpy(client_data->report_descr[curr_hid_dev], | 
|  | payload, | 
|  | payload_len); | 
|  | client_data->report_descr_size[curr_hid_dev] = | 
|  | payload_len; | 
|  | client_data->report_descr_done = true; | 
|  | } | 
|  | wake_up_interruptible(&client_data->init_wait); | 
|  |  | 
|  | break; | 
|  |  | 
|  | case HOSTIF_GET_FEATURE_REPORT: | 
|  | report_type = HID_FEATURE_REPORT; | 
|  | goto	do_get_report; | 
|  |  | 
|  | case HOSTIF_GET_INPUT_REPORT: | 
|  | report_type = HID_INPUT_REPORT; | 
|  | do_get_report: | 
|  | /* Get index of device that matches this id */ | 
|  | for (i = 0; i < client_data->num_hid_devices; ++i) { | 
|  | if (recv_msg->hdr.device_id == | 
|  | client_data->hid_devices[i].dev_id) { | 
|  | hid = client_data->hid_sensor_hubs[i]; | 
|  | if (!hid) | 
|  | break; | 
|  |  | 
|  | hid_data = hid->driver_data; | 
|  | if (hid_data->raw_get_req) { | 
|  | raw_len = | 
|  | (hid_data->raw_buf_size < | 
|  | payload_len) ? | 
|  | hid_data->raw_buf_size : | 
|  | payload_len; | 
|  |  | 
|  | memcpy(hid_data->raw_buf, | 
|  | payload, raw_len); | 
|  | } else { | 
|  | hid_input_report | 
|  | (hid, report_type, | 
|  | payload, payload_len, | 
|  | 0); | 
|  | } | 
|  |  | 
|  | ishtp_hid_wakeup(hid); | 
|  | break; | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case HOSTIF_SET_FEATURE_REPORT: | 
|  | /* Get index of device that matches this id */ | 
|  | for (i = 0; i < client_data->num_hid_devices; ++i) { | 
|  | if (recv_msg->hdr.device_id == | 
|  | client_data->hid_devices[i].dev_id) | 
|  | if (client_data->hid_sensor_hubs[i]) { | 
|  | ishtp_hid_wakeup( | 
|  | client_data->hid_sensor_hubs[ | 
|  | i]); | 
|  | break; | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case HOSTIF_PUBLISH_INPUT_REPORT: | 
|  | report_type = HID_INPUT_REPORT; | 
|  | for (i = 0; i < client_data->num_hid_devices; ++i) | 
|  | if (recv_msg->hdr.device_id == | 
|  | client_data->hid_devices[i].dev_id) | 
|  | if (client_data->hid_sensor_hubs[i]) | 
|  | hid_input_report( | 
|  | client_data->hid_sensor_hubs[ | 
|  | i], | 
|  | report_type, payload, | 
|  | payload_len, 0); | 
|  | break; | 
|  |  | 
|  | case HOSTIF_PUBLISH_INPUT_REPORT_LIST: | 
|  | report_type = HID_INPUT_REPORT; | 
|  | reports_list = (struct report_list *)payload; | 
|  | reports = (char *)reports_list->reports; | 
|  |  | 
|  | for (j = 0; j < reports_list->num_of_reports; j++) { | 
|  | recv_msg = (struct hostif_msg *)(reports + | 
|  | sizeof(uint16_t)); | 
|  | report_len = *(uint16_t *)reports; | 
|  | payload = reports + sizeof(uint16_t) + | 
|  | sizeof(struct hostif_msg_hdr); | 
|  | payload_len = report_len - | 
|  | sizeof(struct hostif_msg_hdr); | 
|  |  | 
|  | for (i = 0; i < client_data->num_hid_devices; | 
|  | ++i) | 
|  | if (recv_msg->hdr.device_id == | 
|  | client_data->hid_devices[i].dev_id && | 
|  | client_data->hid_sensor_hubs[i]) { | 
|  | hid_input_report( | 
|  | client_data->hid_sensor_hubs[ | 
|  | i], | 
|  | report_type, | 
|  | payload, payload_len, | 
|  | 0); | 
|  | } | 
|  |  | 
|  | reports += sizeof(uint16_t) + report_len; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | ++client_data->bad_recv_cnt; | 
|  | report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos, | 
|  | payload_len); | 
|  | ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl)); | 
|  | break; | 
|  |  | 
|  | } | 
|  |  | 
|  | if (!cur_pos && cur_pos + payload_len + | 
|  | sizeof(struct hostif_msg) < total_len) | 
|  | ++client_data->multi_packet_cnt; | 
|  |  | 
|  | cur_pos += payload_len + sizeof(struct hostif_msg); | 
|  | payload += payload_len + sizeof(struct hostif_msg); | 
|  |  | 
|  | } while (cur_pos < total_len); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ish_cl_event_cb() - bus driver callback for incoming message/packet | 
|  | * @device:	Pointer to the the ishtp client device for which this message | 
|  | *		is targeted | 
|  | * | 
|  | * Remove the packet from the list and process the message by calling | 
|  | * process_recv | 
|  | */ | 
|  | static void ish_cl_event_cb(struct ishtp_cl_device *device) | 
|  | { | 
|  | struct ishtp_cl	*hid_ishtp_cl = ishtp_get_drvdata(device); | 
|  | struct ishtp_cl_rb *rb_in_proc; | 
|  | size_t r_length; | 
|  |  | 
|  | if (!hid_ishtp_cl) | 
|  | return; | 
|  |  | 
|  | while ((rb_in_proc = ishtp_cl_rx_get_rb(hid_ishtp_cl)) != NULL) { | 
|  | if (!rb_in_proc->buffer.data) | 
|  | return; | 
|  |  | 
|  | r_length = rb_in_proc->buf_idx; | 
|  |  | 
|  | /* decide what to do with received data */ | 
|  | process_recv(hid_ishtp_cl, rb_in_proc->buffer.data, r_length); | 
|  |  | 
|  | ishtp_cl_io_rb_recycle(rb_in_proc); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_set_feature() - send request to ISH FW to set a feature request | 
|  | * @hid:	hid device instance for this request | 
|  | * @buf:	feature buffer | 
|  | * @len:	Length of feature buffer | 
|  | * @report_id:	Report id for the feature set request | 
|  | * | 
|  | * This is called from hid core .request() callback. This function doesn't wait | 
|  | * for response. | 
|  | */ | 
|  | void hid_ishtp_set_feature(struct hid_device *hid, char *buf, unsigned int len, | 
|  | int report_id) | 
|  | { | 
|  | struct ishtp_hid_data *hid_data =  hid->driver_data; | 
|  | struct ishtp_cl_data *client_data = hid_data->client_data; | 
|  | struct hostif_msg *msg = (struct hostif_msg *)buf; | 
|  | int	rv; | 
|  | int	i; | 
|  |  | 
|  | hid_ishtp_trace(client_data,  "%s hid %p\n", __func__, hid); | 
|  |  | 
|  | rv = ishtp_hid_link_ready_wait(client_data); | 
|  | if (rv) { | 
|  | hid_ishtp_trace(client_data,  "%s hid %p link not ready\n", | 
|  | __func__, hid); | 
|  | return; | 
|  | } | 
|  |  | 
|  | memset(msg, 0, sizeof(struct hostif_msg)); | 
|  | msg->hdr.command = HOSTIF_SET_FEATURE_REPORT; | 
|  | for (i = 0; i < client_data->num_hid_devices; ++i) { | 
|  | if (hid == client_data->hid_sensor_hubs[i]) { | 
|  | msg->hdr.device_id = | 
|  | client_data->hid_devices[i].dev_id; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (i == client_data->num_hid_devices) | 
|  | return; | 
|  |  | 
|  | rv = ishtp_cl_send(client_data->hid_ishtp_cl, buf, len); | 
|  | if (rv) | 
|  | hid_ishtp_trace(client_data,  "%s hid %p send failed\n", | 
|  | __func__, hid); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_get_report() - request to get feature/input report | 
|  | * @hid:	hid device instance for this request | 
|  | * @report_id:	Report id for the get request | 
|  | * @report_type:	Report type for the this request | 
|  | * | 
|  | * This is called from hid core .request() callback. This function will send | 
|  | * request to FW and return without waiting for response. | 
|  | */ | 
|  | void hid_ishtp_get_report(struct hid_device *hid, int report_id, | 
|  | int report_type) | 
|  | { | 
|  | struct ishtp_hid_data *hid_data =  hid->driver_data; | 
|  | struct ishtp_cl_data *client_data = hid_data->client_data; | 
|  | struct hostif_msg_to_sensor msg = {}; | 
|  | int	rv; | 
|  | int	i; | 
|  |  | 
|  | hid_ishtp_trace(client_data,  "%s hid %p\n", __func__, hid); | 
|  | rv = ishtp_hid_link_ready_wait(client_data); | 
|  | if (rv) { | 
|  | hid_ishtp_trace(client_data,  "%s hid %p link not ready\n", | 
|  | __func__, hid); | 
|  | return; | 
|  | } | 
|  |  | 
|  | msg.hdr.command = (report_type == HID_FEATURE_REPORT) ? | 
|  | HOSTIF_GET_FEATURE_REPORT : HOSTIF_GET_INPUT_REPORT; | 
|  | for (i = 0; i < client_data->num_hid_devices; ++i) { | 
|  | if (hid == client_data->hid_sensor_hubs[i]) { | 
|  | msg.hdr.device_id = | 
|  | client_data->hid_devices[i].dev_id; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (i == client_data->num_hid_devices) | 
|  | return; | 
|  |  | 
|  | msg.report_id = report_id; | 
|  | rv = ishtp_cl_send(client_data->hid_ishtp_cl, (uint8_t *)&msg, | 
|  | sizeof(msg)); | 
|  | if (rv) | 
|  | hid_ishtp_trace(client_data,  "%s hid %p send failed\n", | 
|  | __func__, hid); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ishtp_hid_link_ready_wait() - Wait for link ready | 
|  | * @client_data:	client data instance | 
|  | * | 
|  | * If the transport link started suspend process, then wait, till either | 
|  | * resumed or timeout | 
|  | * | 
|  | * Return: 0 on success, non zero on error | 
|  | */ | 
|  | int ishtp_hid_link_ready_wait(struct ishtp_cl_data *client_data) | 
|  | { | 
|  | int rc; | 
|  |  | 
|  | if (client_data->suspended) { | 
|  | hid_ishtp_trace(client_data,  "wait for link ready\n"); | 
|  | rc = wait_event_interruptible_timeout( | 
|  | client_data->ishtp_resume_wait, | 
|  | !client_data->suspended, | 
|  | 5 * HZ); | 
|  |  | 
|  | if (rc == 0) { | 
|  | hid_ishtp_trace(client_data,  "link not ready\n"); | 
|  | return -EIO; | 
|  | } | 
|  | hid_ishtp_trace(client_data,  "link ready\n"); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ishtp_enum_enum_devices() - Enumerate hid devices | 
|  | * @hid_ishtp_cl:	client instance | 
|  | * | 
|  | * Helper function to send request to firmware to enumerate HID devices | 
|  | * | 
|  | * Return: 0 on success, non zero on error | 
|  | */ | 
|  | static int ishtp_enum_enum_devices(struct ishtp_cl *hid_ishtp_cl) | 
|  | { | 
|  | struct hostif_msg msg; | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  | int retry_count; | 
|  | int rv; | 
|  |  | 
|  | /* Send HOSTIF_DM_ENUM_DEVICES */ | 
|  | memset(&msg, 0, sizeof(struct hostif_msg)); | 
|  | msg.hdr.command = HOSTIF_DM_ENUM_DEVICES; | 
|  | rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *)&msg, | 
|  | sizeof(struct hostif_msg)); | 
|  | if (rv) | 
|  | return rv; | 
|  |  | 
|  | retry_count = 0; | 
|  | while (!client_data->enum_devices_done && | 
|  | retry_count < 10) { | 
|  | wait_event_interruptible_timeout(client_data->init_wait, | 
|  | client_data->enum_devices_done, | 
|  | 3 * HZ); | 
|  | ++retry_count; | 
|  | if (!client_data->enum_devices_done) | 
|  | /* Send HOSTIF_DM_ENUM_DEVICES */ | 
|  | rv = ishtp_cl_send(hid_ishtp_cl, | 
|  | (unsigned char *) &msg, | 
|  | sizeof(struct hostif_msg)); | 
|  | } | 
|  | if (!client_data->enum_devices_done) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: timed out waiting for enum_devices\n"); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | if (!client_data->hid_devices) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: failed to allocate HID dev structures\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | client_data->num_hid_devices = client_data->hid_dev_count; | 
|  | dev_info(ishtp_device(client_data->cl_device), | 
|  | "[hid-ish]: enum_devices_done OK, num_hid_devices=%d\n", | 
|  | client_data->num_hid_devices); | 
|  |  | 
|  | return	0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ishtp_get_hid_descriptor() - Get hid descriptor | 
|  | * @hid_ishtp_cl:	client instance | 
|  | * @index:		Index into the hid_descr array | 
|  | * | 
|  | * Helper function to send request to firmware get HID descriptor of a device | 
|  | * | 
|  | * Return: 0 on success, non zero on error | 
|  | */ | 
|  | static int ishtp_get_hid_descriptor(struct ishtp_cl *hid_ishtp_cl, int index) | 
|  | { | 
|  | struct hostif_msg msg; | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  | int rv; | 
|  |  | 
|  | /* Get HID descriptor */ | 
|  | client_data->hid_descr_done = false; | 
|  | memset(&msg, 0, sizeof(struct hostif_msg)); | 
|  | msg.hdr.command = HOSTIF_GET_HID_DESCRIPTOR; | 
|  | msg.hdr.device_id = client_data->hid_devices[index].dev_id; | 
|  | rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg, | 
|  | sizeof(struct hostif_msg)); | 
|  | if (rv) | 
|  | return rv; | 
|  |  | 
|  | if (!client_data->hid_descr_done) { | 
|  | wait_event_interruptible_timeout(client_data->init_wait, | 
|  | client_data->hid_descr_done, | 
|  | 3 * HZ); | 
|  | if (!client_data->hid_descr_done) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: timed out for hid_descr_done\n"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (!client_data->hid_descr[index]) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: allocation HID desc fail\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * ishtp_get_report_descriptor() - Get report descriptor | 
|  | * @hid_ishtp_cl:	client instance | 
|  | * @index:		Index into the hid_descr array | 
|  | * | 
|  | * Helper function to send request to firmware get HID report descriptor of | 
|  | * a device | 
|  | * | 
|  | * Return: 0 on success, non zero on error | 
|  | */ | 
|  | static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl, | 
|  | int index) | 
|  | { | 
|  | struct hostif_msg msg; | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  | int rv; | 
|  |  | 
|  | /* Get report descriptor */ | 
|  | client_data->report_descr_done = false; | 
|  | memset(&msg, 0, sizeof(struct hostif_msg)); | 
|  | msg.hdr.command = HOSTIF_GET_REPORT_DESCRIPTOR; | 
|  | msg.hdr.device_id = client_data->hid_devices[index].dev_id; | 
|  | rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg, | 
|  | sizeof(struct hostif_msg)); | 
|  | if (rv) | 
|  | return rv; | 
|  |  | 
|  | if (!client_data->report_descr_done) | 
|  | wait_event_interruptible_timeout(client_data->init_wait, | 
|  | client_data->report_descr_done, | 
|  | 3 * HZ); | 
|  | if (!client_data->report_descr_done) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: timed out for report descr\n"); | 
|  | return -EIO; | 
|  | } | 
|  | if (!client_data->report_descr[index]) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: failed to alloc report descr\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_cl_init() - Init function for ISHTP client | 
|  | * @hid_ishtp_cl:	ISHTP client instance | 
|  | * @reset:		true if called for init after reset | 
|  | * | 
|  | * This function complete the initializtion of the client. The summary of | 
|  | * processing: | 
|  | * - Send request to enumerate the hid clients | 
|  | *	Get the HID descriptor for each enumearated device | 
|  | *	Get report description of each device | 
|  | *	Register each device wik hid core by calling ishtp_hid_probe | 
|  | * | 
|  | * Return: 0 on success, non zero on error | 
|  | */ | 
|  | static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset) | 
|  | { | 
|  | struct ishtp_device *dev; | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  | struct ishtp_fw_client *fw_client; | 
|  | int i; | 
|  | int rv; | 
|  |  | 
|  | dev_dbg(cl_data_to_dev(client_data), "%s\n", __func__); | 
|  | hid_ishtp_trace(client_data,  "%s reset flag: %d\n", __func__, reset); | 
|  |  | 
|  | rv = ishtp_cl_link(hid_ishtp_cl); | 
|  | if (rv) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "ishtp_cl_link failed\n"); | 
|  | return	-ENOMEM; | 
|  | } | 
|  |  | 
|  | client_data->init_done = 0; | 
|  |  | 
|  | dev = ishtp_get_ishtp_device(hid_ishtp_cl); | 
|  |  | 
|  | /* Connect to FW client */ | 
|  | ishtp_set_tx_ring_size(hid_ishtp_cl, HID_CL_TX_RING_SIZE); | 
|  | ishtp_set_rx_ring_size(hid_ishtp_cl, HID_CL_RX_RING_SIZE); | 
|  |  | 
|  | fw_client = ishtp_fw_cl_get_client(dev, &hid_ishtp_guid); | 
|  | if (!fw_client) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "ish client uuid not found\n"); | 
|  | return -ENOENT; | 
|  | } | 
|  | ishtp_cl_set_fw_client_id(hid_ishtp_cl, | 
|  | ishtp_get_fw_client_id(fw_client)); | 
|  | ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_CONNECTING); | 
|  |  | 
|  | rv = ishtp_cl_connect(hid_ishtp_cl); | 
|  | if (rv) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "client connect fail\n"); | 
|  | goto err_cl_unlink; | 
|  | } | 
|  |  | 
|  | hid_ishtp_trace(client_data,  "%s client connected\n", __func__); | 
|  |  | 
|  | /* Register read callback */ | 
|  | ishtp_register_event_cb(client_data->cl_device, ish_cl_event_cb); | 
|  |  | 
|  | rv = ishtp_enum_enum_devices(hid_ishtp_cl); | 
|  | if (rv) | 
|  | goto err_cl_disconnect; | 
|  |  | 
|  | hid_ishtp_trace(client_data,  "%s enumerated device count %d\n", | 
|  | __func__, client_data->num_hid_devices); | 
|  |  | 
|  | for (i = 0; i < client_data->num_hid_devices; ++i) { | 
|  | client_data->cur_hid_dev = i; | 
|  |  | 
|  | rv = ishtp_get_hid_descriptor(hid_ishtp_cl, i); | 
|  | if (rv) | 
|  | goto err_cl_disconnect; | 
|  |  | 
|  | rv = ishtp_get_report_descriptor(hid_ishtp_cl, i); | 
|  | if (rv) | 
|  | goto err_cl_disconnect; | 
|  |  | 
|  | if (!reset) { | 
|  | rv = ishtp_hid_probe(i, client_data); | 
|  | if (rv) { | 
|  | dev_err(cl_data_to_dev(client_data), | 
|  | "[hid-ish]: HID probe for #%u failed: %d\n", | 
|  | i, rv); | 
|  | goto err_cl_disconnect; | 
|  | } | 
|  | } | 
|  | } /* for() on all hid devices */ | 
|  |  | 
|  | client_data->init_done = 1; | 
|  | client_data->suspended = false; | 
|  | wake_up_interruptible(&client_data->ishtp_resume_wait); | 
|  | hid_ishtp_trace(client_data,  "%s successful init\n", __func__); | 
|  | return 0; | 
|  |  | 
|  | err_cl_disconnect: | 
|  | ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_DISCONNECTING); | 
|  | ishtp_cl_disconnect(hid_ishtp_cl); | 
|  | err_cl_unlink: | 
|  | ishtp_cl_unlink(hid_ishtp_cl); | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_cl_deinit() - Deinit function for ISHTP client | 
|  | * @hid_ishtp_cl:	ISHTP client instance | 
|  | * | 
|  | * Unlink and free hid client | 
|  | */ | 
|  | static void hid_ishtp_cl_deinit(struct ishtp_cl *hid_ishtp_cl) | 
|  | { | 
|  | ishtp_cl_unlink(hid_ishtp_cl); | 
|  | ishtp_cl_flush_queues(hid_ishtp_cl); | 
|  |  | 
|  | /* disband and free all Tx and Rx client-level rings */ | 
|  | ishtp_cl_free(hid_ishtp_cl); | 
|  | } | 
|  |  | 
|  | static void hid_ishtp_cl_reset_handler(struct work_struct *work) | 
|  | { | 
|  | struct ishtp_cl_data *client_data; | 
|  | struct ishtp_cl *hid_ishtp_cl; | 
|  | struct ishtp_cl_device *cl_device; | 
|  | int retry; | 
|  | int rv; | 
|  |  | 
|  | client_data = container_of(work, struct ishtp_cl_data, work); | 
|  |  | 
|  | hid_ishtp_cl = client_data->hid_ishtp_cl; | 
|  | cl_device = client_data->cl_device; | 
|  |  | 
|  | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | 
|  | hid_ishtp_cl); | 
|  | dev_dbg(ishtp_device(client_data->cl_device), "%s\n", __func__); | 
|  |  | 
|  | hid_ishtp_cl_deinit(hid_ishtp_cl); | 
|  |  | 
|  | hid_ishtp_cl = ishtp_cl_allocate(cl_device); | 
|  | if (!hid_ishtp_cl) | 
|  | return; | 
|  |  | 
|  | ishtp_set_drvdata(cl_device, hid_ishtp_cl); | 
|  | ishtp_set_client_data(hid_ishtp_cl, client_data); | 
|  | client_data->hid_ishtp_cl = hid_ishtp_cl; | 
|  |  | 
|  | client_data->num_hid_devices = 0; | 
|  |  | 
|  | for (retry = 0; retry < 3; ++retry) { | 
|  | rv = hid_ishtp_cl_init(hid_ishtp_cl, 1); | 
|  | if (!rv) | 
|  | break; | 
|  | dev_err(cl_data_to_dev(client_data), "Retry reset init\n"); | 
|  | } | 
|  | if (rv) { | 
|  | dev_err(cl_data_to_dev(client_data), "Reset Failed\n"); | 
|  | hid_ishtp_trace(client_data, "%s Failed hid_ishtp_cl %p\n", | 
|  | __func__, hid_ishtp_cl); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void hid_ishtp_cl_resume_handler(struct work_struct *work) | 
|  | { | 
|  | struct ishtp_cl_data *client_data = container_of(work, struct ishtp_cl_data, resume_work); | 
|  | struct ishtp_cl *hid_ishtp_cl = client_data->hid_ishtp_cl; | 
|  |  | 
|  | if (ishtp_wait_resume(ishtp_get_ishtp_device(hid_ishtp_cl))) { | 
|  | client_data->suspended = false; | 
|  | wake_up_interruptible(&client_data->ishtp_resume_wait); | 
|  | } | 
|  | } | 
|  |  | 
|  | ishtp_print_log ishtp_hid_print_trace; | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_cl_probe() - ISHTP client driver probe | 
|  | * @cl_device:		ISHTP client device instance | 
|  | * | 
|  | * This function gets called on device create on ISHTP bus | 
|  | * | 
|  | * Return: 0 on success, non zero on error | 
|  | */ | 
|  | static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device) | 
|  | { | 
|  | struct ishtp_cl *hid_ishtp_cl; | 
|  | struct ishtp_cl_data *client_data; | 
|  | int rv; | 
|  |  | 
|  | if (!cl_device) | 
|  | return	-ENODEV; | 
|  |  | 
|  | client_data = devm_kzalloc(ishtp_device(cl_device), | 
|  | sizeof(*client_data), | 
|  | GFP_KERNEL); | 
|  | if (!client_data) | 
|  | return -ENOMEM; | 
|  |  | 
|  | hid_ishtp_cl = ishtp_cl_allocate(cl_device); | 
|  | if (!hid_ishtp_cl) | 
|  | return -ENOMEM; | 
|  |  | 
|  | ishtp_set_drvdata(cl_device, hid_ishtp_cl); | 
|  | ishtp_set_client_data(hid_ishtp_cl, client_data); | 
|  | client_data->hid_ishtp_cl = hid_ishtp_cl; | 
|  | client_data->cl_device = cl_device; | 
|  |  | 
|  | init_waitqueue_head(&client_data->init_wait); | 
|  | init_waitqueue_head(&client_data->ishtp_resume_wait); | 
|  |  | 
|  | INIT_WORK(&client_data->work, hid_ishtp_cl_reset_handler); | 
|  | INIT_WORK(&client_data->resume_work, hid_ishtp_cl_resume_handler); | 
|  |  | 
|  |  | 
|  | ishtp_hid_print_trace = ishtp_trace_callback(cl_device); | 
|  |  | 
|  | rv = hid_ishtp_cl_init(hid_ishtp_cl, 0); | 
|  | if (rv) { | 
|  | ishtp_cl_free(hid_ishtp_cl); | 
|  | return rv; | 
|  | } | 
|  | ishtp_get_device(cl_device); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_cl_remove() - ISHTP client driver remove | 
|  | * @cl_device:		ISHTP client device instance | 
|  | * | 
|  | * This function gets called on device remove on ISHTP bus | 
|  | * | 
|  | * Return: 0 | 
|  | */ | 
|  | static void hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device) | 
|  | { | 
|  | struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device); | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  |  | 
|  | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | 
|  | hid_ishtp_cl); | 
|  |  | 
|  | dev_dbg(ishtp_device(cl_device), "%s\n", __func__); | 
|  | ishtp_set_connection_state(hid_ishtp_cl, ISHTP_CL_DISCONNECTING); | 
|  | ishtp_cl_disconnect(hid_ishtp_cl); | 
|  | ishtp_put_device(cl_device); | 
|  | ishtp_hid_remove(client_data); | 
|  | hid_ishtp_cl_deinit(hid_ishtp_cl); | 
|  |  | 
|  | hid_ishtp_cl = NULL; | 
|  |  | 
|  | client_data->num_hid_devices = 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_cl_reset() - ISHTP client driver reset | 
|  | * @cl_device:		ISHTP client device instance | 
|  | * | 
|  | * This function gets called on device reset on ISHTP bus | 
|  | * | 
|  | * Return: 0 | 
|  | */ | 
|  | static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device) | 
|  | { | 
|  | struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device); | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  |  | 
|  | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | 
|  | hid_ishtp_cl); | 
|  |  | 
|  | schedule_work(&client_data->work); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_cl_suspend() - ISHTP client driver suspend | 
|  | * @device:	device instance | 
|  | * | 
|  | * This function gets called on system suspend | 
|  | * | 
|  | * Return: 0 | 
|  | */ | 
|  | static int hid_ishtp_cl_suspend(struct device *device) | 
|  | { | 
|  | struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device); | 
|  | struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device); | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  |  | 
|  | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | 
|  | hid_ishtp_cl); | 
|  | client_data->suspended = true; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * hid_ishtp_cl_resume() - ISHTP client driver resume | 
|  | * @device:	device instance | 
|  | * | 
|  | * This function gets called on system resume | 
|  | * | 
|  | * Return: 0 | 
|  | */ | 
|  | static int hid_ishtp_cl_resume(struct device *device) | 
|  | { | 
|  | struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device); | 
|  | struct ishtp_cl *hid_ishtp_cl = ishtp_get_drvdata(cl_device); | 
|  | struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl); | 
|  |  | 
|  | hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__, | 
|  | hid_ishtp_cl); | 
|  | schedule_work(&client_data->resume_work); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dev_pm_ops hid_ishtp_pm_ops = { | 
|  | .suspend = hid_ishtp_cl_suspend, | 
|  | .resume = hid_ishtp_cl_resume, | 
|  | }; | 
|  |  | 
|  | static struct ishtp_cl_driver	hid_ishtp_cl_driver = { | 
|  | .name = "ish-hid", | 
|  | .guid = &hid_ishtp_guid, | 
|  | .probe = hid_ishtp_cl_probe, | 
|  | .remove = hid_ishtp_cl_remove, | 
|  | .reset = hid_ishtp_cl_reset, | 
|  | .driver.pm = &hid_ishtp_pm_ops, | 
|  | }; | 
|  |  | 
|  | static int __init ish_hid_init(void) | 
|  | { | 
|  | int	rv; | 
|  |  | 
|  | /* Register ISHTP client device driver with ISHTP Bus */ | 
|  | rv = ishtp_cl_driver_register(&hid_ishtp_cl_driver, THIS_MODULE); | 
|  |  | 
|  | return rv; | 
|  |  | 
|  | } | 
|  |  | 
|  | static void __exit ish_hid_exit(void) | 
|  | { | 
|  | ishtp_cl_driver_unregister(&hid_ishtp_cl_driver); | 
|  | } | 
|  |  | 
|  | late_initcall(ish_hid_init); | 
|  | module_exit(ish_hid_exit); | 
|  |  | 
|  | MODULE_DESCRIPTION("ISH ISHTP HID client driver"); | 
|  | /* Primary author */ | 
|  | MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>"); | 
|  | /* | 
|  | * Several modification for multi instance support | 
|  | * suspend/resume and clean up | 
|  | */ | 
|  | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); | 
|  |  | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_ALIAS("ishtp:*"); |