diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/hid/intel-ish-hid/ishtp-hid-client.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'drivers/hid/intel-ish-hid/ishtp-hid-client.c')
-rw-r--r-- | drivers/hid/intel-ish-hid/ishtp-hid-client.c | 986 |
1 files changed, 986 insertions, 0 deletions
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c new file mode 100644 index 000000000..e3d70c546 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c @@ -0,0 +1,986 @@ +// 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; + 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 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_id_table[0].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", + .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"); |