mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 03:48:59 +00:00
63cafaf47a
One-element arrays as fake flex arrays are deprecated[1] as the kernel has switched to C99 flexible-array members instead. This case, however, has more complexity because it is a flexible array of flexible arrays and this patch needs to be ready to enable the new compiler flag -Wflex-array-member-not-at-end (coming in GCC-14) globally. So, define a new struct type for the single reports: struct report { uint16_t size; struct hostif_msg_hdr msg; } __packed; but without the payload (flex array) in it. And add this payload to the "hostif_msg" structure. This way, in the "report_list" structure we can declare a flex array of single reports which now do not contain another flex array. struct report_list { [...] struct report reports[]; } __packed; Therefore, the "struct hostif_msg" is now made up of a header and a payload. And the "struct report" uses only the "hostif_msg" header. The perfect solution would be for the "report" structure to use the whole "hostif_msg" structure but this is not possible due to nested flexible arrays. Anyway, the end result is equivalent since this patch does attempt to change the behaviour of the code. Now as well, we have more clarity after the cast from the raw bytes to the new structures. Refactor the code accordingly to use the new structures. Also, use "container_of()" whenever we need to retrieve a pointer to the flexible structure, through which we can access the flexible array if needed. Link: https://www.kernel.org/doc/html/next/process/deprecated.html#zero-length-and-one-element-arrays [1] Closes: https://github.com/KSPP/linux/issues/333 Signed-off-by: Erick Archer <erick.archer@outlook.com> Link: https://lore.kernel.org/r/AS8PR02MB723760CB93942370E92F00638BF72@AS8PR02MB7237.eurprd02.prod.outlook.com [kees: tweaked commit log and dropped struct_size() uses] Signed-off-by: Kees Cook <kees@kernel.org> Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> Signed-off-by: Jiri Kosina <jkosina@suse.com>
949 lines
26 KiB
C
949 lines
26 KiB
C
// 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 struct ishtp_device_id hid_ishtp_id_table[] = {
|
|
{ .guid = GUID_INIT(0x33AECD58, 0xB679, 0x4E54,
|
|
0x9B, 0xD9, 0xA0, 0x4D, 0x34, 0xF0, 0xC2, 0x26), },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(ishtp, hid_ishtp_id_table);
|
|
|
|
/* 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, msg_len;
|
|
int report_type;
|
|
struct report_list *reports_list;
|
|
struct report *report;
|
|
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;
|
|
report = reports_list->reports;
|
|
|
|
for (j = 0; j < reports_list->num_of_reports; j++) {
|
|
recv_msg = container_of(&report->msg,
|
|
struct hostif_msg, hdr);
|
|
report_len = report->size;
|
|
payload = recv_msg->payload;
|
|
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);
|
|
}
|
|
|
|
report += sizeof(*report) + payload_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;
|
|
|
|
}
|
|
|
|
msg_len = payload_len + sizeof(struct hostif_msg);
|
|
if (!cur_pos && cur_pos + msg_len < total_len)
|
|
++client_data->multi_packet_cnt;
|
|
|
|
cur_pos += msg_len;
|
|
payload += msg_len;
|
|
|
|
} while (cur_pos < total_len);
|
|
}
|
|
|
|
/**
|
|
* ish_cl_event_cb() - bus driver callback for incoming message/packet
|
|
* @device: Pointer to 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, bool reset)
|
|
{
|
|
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
|
|
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);
|
|
|
|
client_data->init_done = 0;
|
|
|
|
rv = ishtp_cl_establish_connection(hid_ishtp_cl,
|
|
&hid_ishtp_id_table[0].guid,
|
|
HID_CL_TX_RING_SIZE,
|
|
HID_CL_RX_RING_SIZE,
|
|
reset);
|
|
if (rv) {
|
|
dev_err(cl_data_to_dev(client_data),
|
|
"client connect fail\n");
|
|
goto err_cl_disconnect;
|
|
}
|
|
|
|
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_cl_destroy_connection(hid_ishtp_cl, reset);
|
|
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_destroy_connection(hid_ishtp_cl, false);
|
|
|
|
/* 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;
|
|
int retry;
|
|
int rv;
|
|
|
|
client_data = container_of(work, struct ishtp_cl_data, work);
|
|
|
|
hid_ishtp_cl = client_data->hid_ishtp_cl;
|
|
|
|
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__);
|
|
|
|
ishtp_cl_destroy_connection(hid_ishtp_cl, true);
|
|
|
|
client_data->num_hid_devices = 0;
|
|
|
|
for (retry = 0; retry < 3; ++retry) {
|
|
rv = hid_ishtp_cl_init(hid_ishtp_cl, true);
|
|
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, false);
|
|
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__);
|
|
hid_ishtp_cl_deinit(hid_ishtp_cl);
|
|
ishtp_put_device(cl_device);
|
|
ishtp_hid_remove(client_data);
|
|
|
|
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",
|
|
.id = hid_ishtp_id_table,
|
|
.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");
|