diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/hid/hid-sensor-hub.c | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 of the __hrtimer_run_queues()
function:
<__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1)
<__hrtimer_run_queues+254>: mov %r14,%rdi
<__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12>
<__hrtimer_run_queues+263>: mov %eax,%r12d
<__hrtimer_run_queues+266>: xchg %ax,%ax
<__hrtimer_run_queues+268>: mov %r13,%rdi
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
RIP: 0010:__hrtimer_run_queues+0x106/0x230
Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is
at the 6th byte of the above CS CALL instruction. Since the CS CALL
instruction occupies 6 bytes, the exception occurred in the middle of that
call instruction.
The root cause is that when using eBPF tools to probe in the middle of a
function, a kprobe with INT3 is used as the underlying implementation.
During single-step emulation of the original CALL instruction,
int3_emulate_call() assumes that the probed CALL instruction is 5 bytes
long. However, the actual CS-prefixed CALL instruction occupies 6 bytes,
so it constructs an incorrect exception return address. When the CPU
returns from the kprobe handler, the next instruction to be executed is at
the address of the last byte of that CS CALL instruction. Coincidentally,
starting from that address, the CPU fetches and decodes a completely
different instruction, which ultimately triggers a kernel crash.
Fix the issue by using the actual instruction length obtained from
the instruction decoder when constructing the exception return
address, rather than relying on the hardcoded CALL_INSN_SIZE macro.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/hid/hid-sensor-hub.c')
| -rw-r--r-- | drivers/hid/hid-sensor-hub.c | 854 |
1 files changed, 854 insertions, 0 deletions
diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c new file mode 100644 index 000000000..6470a290e --- /dev/null +++ b/drivers/hid/hid-sensor-hub.c @@ -0,0 +1,854 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + */ + +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/mfd/core.h> +#include <linux/list.h> +#include <linux/hid-sensor-ids.h> +#include <linux/hid-sensor-hub.h> +#include "hid-ids.h" + +#define HID_SENSOR_HUB_ENUM_QUIRK 0x01 + +/** + * struct sensor_hub_data - Hold a instance data for a HID hub device + * @mutex: Mutex to serialize synchronous request. + * @lock: Spin lock to protect pending request structure. + * @dyn_callback_list: Holds callback function + * @dyn_callback_lock: spin lock to protect callback list + * @hid_sensor_hub_client_devs: Stores all MFD cells for a hub instance. + * @hid_sensor_client_cnt: Number of MFD cells, (no of sensors attached). + * @ref_cnt: Number of MFD clients have opened this device + */ +struct sensor_hub_data { + struct mutex mutex; + spinlock_t lock; + struct list_head dyn_callback_list; + spinlock_t dyn_callback_lock; + struct mfd_cell *hid_sensor_hub_client_devs; + int hid_sensor_client_cnt; + int ref_cnt; +}; + +/** + * struct hid_sensor_hub_callbacks_list - Stores callback list + * @list: list head. + * @usage_id: usage id for a physical device. + * @hsdev: Stored hid instance for current hub device. + * @usage_callback: Stores registered callback functions. + * @priv: Private data for a physical device. + */ +struct hid_sensor_hub_callbacks_list { + struct list_head list; + u32 usage_id; + struct hid_sensor_hub_device *hsdev; + struct hid_sensor_hub_callbacks *usage_callback; + void *priv; +}; + +static struct hid_report *sensor_hub_report(int id, struct hid_device *hdev, + int dir) +{ + struct hid_report *report; + + list_for_each_entry(report, &hdev->report_enum[dir].report_list, list) { + if (report->id == id) + return report; + } + hid_warn(hdev, "No report with id 0x%x found\n", id); + + return NULL; +} + +static int sensor_hub_get_physical_device_count(struct hid_device *hdev) +{ + int i; + int count = 0; + + for (i = 0; i < hdev->maxcollection; ++i) { + struct hid_collection *collection = &hdev->collection[i]; + if (collection->type == HID_COLLECTION_PHYSICAL || + collection->type == HID_COLLECTION_APPLICATION) + ++count; + } + + return count; +} + +static void sensor_hub_fill_attr_info( + struct hid_sensor_hub_attribute_info *info, + s32 index, s32 report_id, struct hid_field *field) +{ + info->index = index; + info->report_id = report_id; + info->units = field->unit; + info->unit_expo = field->unit_exponent; + info->size = (field->report_size * field->report_count)/8; + info->logical_minimum = field->logical_minimum; + info->logical_maximum = field->logical_maximum; +} + +static struct hid_sensor_hub_callbacks *sensor_hub_get_callback( + struct hid_device *hdev, + u32 usage_id, + int collection_index, + struct hid_sensor_hub_device **hsdev, + void **priv) +{ + struct hid_sensor_hub_callbacks_list *callback; + struct sensor_hub_data *pdata = hid_get_drvdata(hdev); + unsigned long flags; + + spin_lock_irqsave(&pdata->dyn_callback_lock, flags); + list_for_each_entry(callback, &pdata->dyn_callback_list, list) + if ((callback->usage_id == usage_id || + callback->usage_id == HID_USAGE_SENSOR_COLLECTION) && + (collection_index >= + callback->hsdev->start_collection_index) && + (collection_index < + callback->hsdev->end_collection_index)) { + *priv = callback->priv; + *hsdev = callback->hsdev; + spin_unlock_irqrestore(&pdata->dyn_callback_lock, + flags); + return callback->usage_callback; + } + spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); + + return NULL; +} + +int sensor_hub_register_callback(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct hid_sensor_hub_callbacks *usage_callback) +{ + struct hid_sensor_hub_callbacks_list *callback; + struct sensor_hub_data *pdata = hid_get_drvdata(hsdev->hdev); + unsigned long flags; + + spin_lock_irqsave(&pdata->dyn_callback_lock, flags); + list_for_each_entry(callback, &pdata->dyn_callback_list, list) + if (callback->usage_id == usage_id && + callback->hsdev == hsdev) { + spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); + return -EINVAL; + } + callback = kzalloc_obj(*callback, GFP_ATOMIC); + if (!callback) { + spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); + return -ENOMEM; + } + callback->hsdev = hsdev; + callback->usage_callback = usage_callback; + callback->usage_id = usage_id; + callback->priv = NULL; + /* + * If there is a handler registered for the collection type, then + * it will handle all reports for sensors in this collection. If + * there is also an individual sensor handler registration, then + * we want to make sure that the reports are directed to collection + * handler, as this may be a fusion sensor. So add collection handlers + * to the beginning of the list, so that they are matched first. + */ + if (usage_id == HID_USAGE_SENSOR_COLLECTION) + list_add(&callback->list, &pdata->dyn_callback_list); + else + list_add_tail(&callback->list, &pdata->dyn_callback_list); + spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(sensor_hub_register_callback); + +int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev, + u32 usage_id) +{ + struct hid_sensor_hub_callbacks_list *callback; + struct sensor_hub_data *pdata = hid_get_drvdata(hsdev->hdev); + unsigned long flags; + + spin_lock_irqsave(&pdata->dyn_callback_lock, flags); + list_for_each_entry(callback, &pdata->dyn_callback_list, list) + if (callback->usage_id == usage_id && + callback->hsdev == hsdev) { + list_del(&callback->list); + kfree(callback); + break; + } + spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(sensor_hub_remove_callback); + +int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, + u32 field_index, int buffer_size, void *buffer) +{ + struct hid_report *report; + struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + __s32 *buf32 = buffer; + int i = 0; + int remaining_bytes; + __s32 value; + int ret = 0; + + mutex_lock(&data->mutex); + report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT); + if (!report || (field_index >= report->maxfield)) { + ret = -EINVAL; + goto done_proc; + } + + remaining_bytes = buffer_size % sizeof(__s32); + buffer_size = buffer_size / sizeof(__s32); + if (buffer_size) { + for (i = 0; i < buffer_size; ++i) { + ret = hid_set_field(report->field[field_index], i, + (__force __s32)cpu_to_le32(*buf32)); + if (ret) + goto done_proc; + + ++buf32; + } + } + if (remaining_bytes) { + value = 0; + memcpy(&value, (u8 *)buf32, remaining_bytes); + ret = hid_set_field(report->field[field_index], i, + (__force __s32)cpu_to_le32(value)); + if (ret) + goto done_proc; + } + hid_hw_request(hsdev->hdev, report, HID_REQ_SET_REPORT); + hid_hw_wait(hsdev->hdev); + +done_proc: + mutex_unlock(&data->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(sensor_hub_set_feature); + +int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id, + u32 field_index, int buffer_size, void *buffer) +{ + struct hid_report *report; + struct hid_field *field; + struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + size_t field_size; + size_t report_size; + size_t copied = 0; + size_t to_copy; + int ret = 0; + unsigned int i; + + if (!buffer || buffer_size <= 0) + return -EINVAL; + + memset(buffer, 0, buffer_size); + + mutex_lock(&data->mutex); + report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT); + if (!report || (field_index >= report->maxfield) || + report->field[field_index]->report_count < 1) { + ret = -EINVAL; + goto done_proc; + } + hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); + hid_hw_wait(hsdev->hdev); + + field = report->field[field_index]; + + /* calculate number of bytes required to read this field */ + field_size = DIV_ROUND_UP(field->report_size, 8); + /* HID core stores each parsed report value in a __s32 slot. */ + if (!field_size || field_size > sizeof(field->value[0])) { + ret = -EINVAL; + goto done_proc; + } + if (field->report_count > SIZE_MAX / field_size) { + ret = -EINVAL; + goto done_proc; + } + + report_size = field_size * field->report_count; + report_size = min_t(size_t, report_size, buffer_size); + + for (i = 0; i < field->report_count && copied < report_size; ++i) { + to_copy = min(field_size, report_size - copied); + memcpy(&((u8 *)buffer)[copied], &field->value[i], to_copy); + copied += to_copy; + } + ret = copied; + +done_proc: + mutex_unlock(&data->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(sensor_hub_get_feature); + +int sensor_hub_input_attr_read_values(struct hid_sensor_hub_device *hsdev, + u32 usage_id, u32 attr_usage_id, + u32 report_id, + enum sensor_hub_read_flags flag, + u32 buffer_size, u8 *buffer) +{ + struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + struct hid_report *report; + unsigned long flags; + long cycles; + int ret; + + report = sensor_hub_report(report_id, hsdev->hdev, HID_INPUT_REPORT); + if (!report) + return -EINVAL; + + mutex_lock(hsdev->mutex_ptr); + if (flag == SENSOR_HUB_SYNC) { + memset(&hsdev->pending, 0, sizeof(hsdev->pending)); + init_completion(&hsdev->pending.ready); + hsdev->pending.usage_id = usage_id; + hsdev->pending.attr_usage_id = attr_usage_id; + hsdev->pending.max_raw_size = buffer_size; + hsdev->pending.raw_data = buffer; + + spin_lock_irqsave(&data->lock, flags); + hsdev->pending.status = true; + spin_unlock_irqrestore(&data->lock, flags); + } + mutex_lock(&data->mutex); + hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); + mutex_unlock(&data->mutex); + ret = 0; + if (flag == SENSOR_HUB_SYNC) { + cycles = wait_for_completion_interruptible_timeout(&hsdev->pending.ready, + HZ * 5); + if (cycles == 0) + ret = -ETIMEDOUT; + else if (cycles < 0) + ret = cycles; + + hsdev->pending.status = false; + } + mutex_unlock(hsdev->mutex_ptr); + + return ret; +} +EXPORT_SYMBOL_GPL(sensor_hub_input_attr_read_values); + +int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + u32 attr_usage_id, u32 report_id, + enum sensor_hub_read_flags flag, + bool is_signed) +{ + struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + unsigned long flags; + struct hid_report *report; + int ret_val = 0; + + report = sensor_hub_report(report_id, hsdev->hdev, + HID_INPUT_REPORT); + if (!report) + return -EINVAL; + + mutex_lock(hsdev->mutex_ptr); + if (flag == SENSOR_HUB_SYNC) { + memset(&hsdev->pending, 0, sizeof(hsdev->pending)); + init_completion(&hsdev->pending.ready); + hsdev->pending.usage_id = usage_id; + hsdev->pending.attr_usage_id = attr_usage_id; + hsdev->pending.raw_size = 0; + + spin_lock_irqsave(&data->lock, flags); + hsdev->pending.status = true; + spin_unlock_irqrestore(&data->lock, flags); + } + mutex_lock(&data->mutex); + hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT); + mutex_unlock(&data->mutex); + if (flag == SENSOR_HUB_SYNC) { + wait_for_completion_interruptible_timeout( + &hsdev->pending.ready, HZ*5); + switch (hsdev->pending.raw_size) { + case 1: + if (is_signed) + ret_val = *(s8 *)hsdev->pending.raw_data; + else + ret_val = *(u8 *)hsdev->pending.raw_data; + break; + case 2: + if (is_signed) + ret_val = *(s16 *)hsdev->pending.raw_data; + else + ret_val = *(u16 *)hsdev->pending.raw_data; + break; + case 4: + ret_val = *(u32 *)hsdev->pending.raw_data; + break; + default: + ret_val = 0; + } + kfree(hsdev->pending.raw_data); + hsdev->pending.status = false; + } + mutex_unlock(hsdev->mutex_ptr); + + return ret_val; +} +EXPORT_SYMBOL_GPL(sensor_hub_input_attr_get_raw_value); + +int hid_sensor_get_usage_index(struct hid_sensor_hub_device *hsdev, + u32 report_id, int field_index, u32 usage_id) +{ + struct hid_report *report; + struct hid_field *field; + int i; + + report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT); + if (!report || (field_index >= report->maxfield)) + goto done_proc; + + field = report->field[field_index]; + for (i = 0; i < field->maxusage; ++i) { + if (field->usage[i].hid == usage_id) + return field->usage[i].usage_index; + } + +done_proc: + return -EINVAL; +} +EXPORT_SYMBOL_GPL(hid_sensor_get_usage_index); + +int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev, + u8 type, + u32 usage_id, + u32 attr_usage_id, + struct hid_sensor_hub_attribute_info *info) +{ + int ret = -1; + int i; + struct hid_report *report; + struct hid_field *field; + struct hid_report_enum *report_enum; + struct hid_device *hdev = hsdev->hdev; + + /* Initialize with defaults */ + info->usage_id = usage_id; + info->attrib_id = attr_usage_id; + info->report_id = -1; + info->index = -1; + info->units = -1; + info->unit_expo = -1; + + report_enum = &hdev->report_enum[type]; + list_for_each_entry(report, &report_enum->report_list, list) { + for (i = 0; i < report->maxfield; ++i) { + field = report->field[i]; + if (field->maxusage) { + if ((field->physical == usage_id || + field->application == usage_id) && + (field->logical == attr_usage_id || + field->usage[0].hid == + attr_usage_id) && + (field->usage[0].collection_index >= + hsdev->start_collection_index) && + (field->usage[0].collection_index < + hsdev->end_collection_index)) { + + sensor_hub_fill_attr_info(info, i, + report->id, + field); + ret = 0; + break; + } + } + } + + } + + return ret; +} +EXPORT_SYMBOL_GPL(sensor_hub_input_get_attribute_info); + +static int sensor_hub_suspend(struct hid_device *hdev, pm_message_t message) +{ + struct sensor_hub_data *pdata = hid_get_drvdata(hdev); + struct hid_sensor_hub_callbacks_list *callback; + unsigned long flags; + + hid_dbg(hdev, " sensor_hub_suspend\n"); + spin_lock_irqsave(&pdata->dyn_callback_lock, flags); + list_for_each_entry(callback, &pdata->dyn_callback_list, list) { + if (callback->usage_callback->suspend) + callback->usage_callback->suspend( + callback->hsdev, callback->priv); + } + spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); + + return 0; +} + +static int sensor_hub_resume(struct hid_device *hdev) +{ + struct sensor_hub_data *pdata = hid_get_drvdata(hdev); + struct hid_sensor_hub_callbacks_list *callback; + unsigned long flags; + + hid_dbg(hdev, " sensor_hub_resume\n"); + spin_lock_irqsave(&pdata->dyn_callback_lock, flags); + list_for_each_entry(callback, &pdata->dyn_callback_list, list) { + if (callback->usage_callback->resume) + callback->usage_callback->resume( + callback->hsdev, callback->priv); + } + spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags); + + return 0; +} + +static int sensor_hub_reset_resume(struct hid_device *hdev) +{ + return 0; +} + +/* + * Handle raw report as sent by device + */ +static int sensor_hub_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *raw_data, int size) +{ + int i; + u8 *ptr; + int sz; + struct sensor_hub_data *pdata = hid_get_drvdata(hdev); + unsigned long flags; + struct hid_sensor_hub_callbacks *callback = NULL; + struct hid_collection *collection = NULL; + void *priv = NULL; + struct hid_sensor_hub_device *hsdev = NULL; + u32 copy_size; + u32 avail; + + hid_dbg(hdev, "sensor_hub_raw_event report id:0x%x size:%d type:%d\n", + report->id, size, report->type); + hid_dbg(hdev, "maxfield:%d\n", report->maxfield); + if (report->type != HID_INPUT_REPORT) + return 1; + + ptr = raw_data; + if (report->id) + ptr++; /* Skip report id */ + + spin_lock_irqsave(&pdata->lock, flags); + + for (i = 0; i < report->maxfield; ++i) { + hid_dbg(hdev, "%d collection_index:%x hid:%x sz:%x\n", + i, report->field[i]->usage->collection_index, + report->field[i]->usage->hid, + (report->field[i]->report_size * + report->field[i]->report_count)/8); + sz = (report->field[i]->report_size * + report->field[i]->report_count)/8; + collection = &hdev->collection[ + report->field[i]->usage->collection_index]; + hid_dbg(hdev, "collection->usage %x\n", + collection->usage); + + callback = sensor_hub_get_callback(hdev, + report->field[i]->physical ? report->field[i]->physical : + report->field[i]->application, + report->field[i]->usage[0].collection_index, + &hsdev, &priv); + if (!callback) { + ptr += sz; + continue; + } + if (hsdev->pending.status && (hsdev->pending.attr_usage_id == + report->field[i]->usage->hid || + hsdev->pending.attr_usage_id == + report->field[i]->logical)) { + hid_dbg(hdev, "data was pending ...\n"); + if (hsdev->pending.max_raw_size) { + if (hsdev->pending.index < hsdev->pending.max_raw_size) { + avail = hsdev->pending.max_raw_size - hsdev->pending.index; + copy_size = clamp(sz, 0U, avail); + + memcpy(hsdev->pending.raw_data + hsdev->pending.index, + ptr, copy_size); + hsdev->pending.index += copy_size; + if (hsdev->pending.index >= hsdev->pending.max_raw_size) { + hsdev->pending.raw_size = hsdev->pending.index; + complete(&hsdev->pending.ready); + } + } + } else { + hsdev->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC); + if (hsdev->pending.raw_data) + hsdev->pending.raw_size = sz; + else + hsdev->pending.raw_size = 0; + complete(&hsdev->pending.ready); + } + } + if (callback->capture_sample) { + if (report->field[i]->logical) + callback->capture_sample(hsdev, + report->field[i]->logical, sz, ptr, + callback->pdev); + else + callback->capture_sample(hsdev, + report->field[i]->usage->hid, sz, ptr, + callback->pdev); + } + ptr += sz; + } + if (callback && collection && callback->send_event) + callback->send_event(hsdev, collection->usage, + callback->pdev); + spin_unlock_irqrestore(&pdata->lock, flags); + + return 1; +} + +int sensor_hub_device_open(struct hid_sensor_hub_device *hsdev) +{ + int ret = 0; + struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + + mutex_lock(&data->mutex); + if (!data->ref_cnt) { + ret = hid_hw_open(hsdev->hdev); + if (ret) { + hid_err(hsdev->hdev, "failed to open hid device\n"); + mutex_unlock(&data->mutex); + return ret; + } + } + data->ref_cnt++; + mutex_unlock(&data->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(sensor_hub_device_open); + +void sensor_hub_device_close(struct hid_sensor_hub_device *hsdev) +{ + struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev); + + mutex_lock(&data->mutex); + data->ref_cnt--; + if (!data->ref_cnt) + hid_hw_close(hsdev->hdev); + mutex_unlock(&data->mutex); +} +EXPORT_SYMBOL_GPL(sensor_hub_device_close); + +static const __u8 *sensor_hub_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + /* + * Checks if the report descriptor of Thinkpad Helix 2 has a logical + * minimum for magnetic flux axis greater than the maximum. + */ + if (hdev->product == USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA && + *rsize == 2558 && rdesc[913] == 0x17 && rdesc[914] == 0x40 && + rdesc[915] == 0x81 && rdesc[916] == 0x08 && + rdesc[917] == 0x00 && rdesc[918] == 0x27 && + rdesc[921] == 0x07 && rdesc[922] == 0x00) { + /* Sets negative logical minimum for mag x, y and z */ + rdesc[914] = rdesc[935] = rdesc[956] = 0xc0; + rdesc[915] = rdesc[936] = rdesc[957] = 0x7e; + rdesc[916] = rdesc[937] = rdesc[958] = 0xf7; + rdesc[917] = rdesc[938] = rdesc[959] = 0xff; + } + + return rdesc; +} + +static int sensor_hub_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + struct sensor_hub_data *sd; + int i; + char *name; + int dev_cnt; + struct hid_sensor_hub_device *hsdev; + struct hid_sensor_hub_device *last_hsdev = NULL; + struct hid_sensor_hub_device *collection_hsdev = NULL; + + sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); + if (!sd) { + hid_err(hdev, "cannot allocate Sensor data\n"); + return -ENOMEM; + } + + hid_set_drvdata(hdev, sd); + + spin_lock_init(&sd->lock); + spin_lock_init(&sd->dyn_callback_lock); + mutex_init(&sd->mutex); + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + INIT_LIST_HEAD(&hdev->inputs); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | HID_CONNECT_DRIVER); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + INIT_LIST_HEAD(&sd->dyn_callback_list); + sd->hid_sensor_client_cnt = 0; + + dev_cnt = sensor_hub_get_physical_device_count(hdev); + if (dev_cnt > HID_MAX_PHY_DEVICES) { + hid_err(hdev, "Invalid Physical device count\n"); + ret = -EINVAL; + goto err_stop_hw; + } + sd->hid_sensor_hub_client_devs = devm_kcalloc(&hdev->dev, + dev_cnt, + sizeof(struct mfd_cell), + GFP_KERNEL); + if (sd->hid_sensor_hub_client_devs == NULL) { + hid_err(hdev, "Failed to allocate memory for mfd cells\n"); + ret = -ENOMEM; + goto err_stop_hw; + } + + for (i = 0; i < hdev->maxcollection; ++i) { + struct hid_collection *collection = &hdev->collection[i]; + + if (collection->type == HID_COLLECTION_PHYSICAL || + collection->type == HID_COLLECTION_APPLICATION) { + + hsdev = devm_kzalloc(&hdev->dev, sizeof(*hsdev), + GFP_KERNEL); + if (!hsdev) { + hid_err(hdev, "cannot allocate hid_sensor_hub_device\n"); + ret = -ENOMEM; + goto err_stop_hw; + } + hsdev->hdev = hdev; + hsdev->vendor_id = hdev->vendor; + hsdev->product_id = hdev->product; + hsdev->usage = collection->usage; + hsdev->mutex_ptr = devm_kzalloc(&hdev->dev, + sizeof(struct mutex), + GFP_KERNEL); + if (!hsdev->mutex_ptr) { + ret = -ENOMEM; + goto err_stop_hw; + } + mutex_init(hsdev->mutex_ptr); + hsdev->start_collection_index = i; + if (last_hsdev) + last_hsdev->end_collection_index = i; + last_hsdev = hsdev; + name = devm_kasprintf(&hdev->dev, GFP_KERNEL, + "HID-SENSOR-%x", + collection->usage); + if (name == NULL) { + hid_err(hdev, "Failed MFD device name\n"); + ret = -ENOMEM; + goto err_stop_hw; + } + sd->hid_sensor_hub_client_devs[ + sd->hid_sensor_client_cnt].name = name; + sd->hid_sensor_hub_client_devs[ + sd->hid_sensor_client_cnt].platform_data = + hsdev; + sd->hid_sensor_hub_client_devs[ + sd->hid_sensor_client_cnt].pdata_size = + sizeof(*hsdev); + hid_dbg(hdev, "Adding %s:%d\n", name, + hsdev->start_collection_index); + sd->hid_sensor_client_cnt++; + if (collection_hsdev) + collection_hsdev->end_collection_index = i; + if (collection->type == HID_COLLECTION_APPLICATION && + collection->usage == HID_USAGE_SENSOR_COLLECTION) + collection_hsdev = hsdev; + } + } + if (last_hsdev) + last_hsdev->end_collection_index = i; + if (collection_hsdev) + collection_hsdev->end_collection_index = i; + + ret = mfd_add_hotplug_devices(&hdev->dev, + sd->hid_sensor_hub_client_devs, + sd->hid_sensor_client_cnt); + if (ret < 0) + goto err_stop_hw; + + return ret; + +err_stop_hw: + hid_hw_stop(hdev); + + return ret; +} + +static int sensor_hub_finalize_pending_fn(struct device *dev, void *data) +{ + struct hid_sensor_hub_device *hsdev = dev->platform_data; + + if (hsdev->pending.status) + complete(&hsdev->pending.ready); + + return 0; +} + +static void sensor_hub_remove(struct hid_device *hdev) +{ + struct sensor_hub_data *data = hid_get_drvdata(hdev); + unsigned long flags; + + hid_dbg(hdev, " hardware removed\n"); + hid_hw_close(hdev); + hid_hw_stop(hdev); + + spin_lock_irqsave(&data->lock, flags); + device_for_each_child(&hdev->dev, NULL, + sensor_hub_finalize_pending_fn); + spin_unlock_irqrestore(&data->lock, flags); + + mfd_remove_devices(&hdev->dev); + mutex_destroy(&data->mutex); +} + +static const struct hid_device_id sensor_hub_devices[] = { + { HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, HID_ANY_ID, + HID_ANY_ID) }, + { } +}; +MODULE_DEVICE_TABLE(hid, sensor_hub_devices); + +static struct hid_driver sensor_hub_driver = { + .name = "hid-sensor-hub", + .id_table = sensor_hub_devices, + .probe = sensor_hub_probe, + .remove = sensor_hub_remove, + .raw_event = sensor_hub_raw_event, + .report_fixup = sensor_hub_report_fixup, + .suspend = pm_ptr(sensor_hub_suspend), + .resume = pm_ptr(sensor_hub_resume), + .reset_resume = pm_ptr(sensor_hub_reset_resume), +}; +module_hid_driver(sensor_hub_driver); + +MODULE_DESCRIPTION("HID Sensor Hub driver"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_LICENSE("GPL"); |
