From 2f31c52529103d8f0e1485272064f982d14ce54a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 30 Sep 2014 13:18:27 -0400 Subject: HID: Introduce hidpp, a module to handle Logitech hid++ devices Logitech devices use a vendor protocol to communicate various information with the device. This protocol is called HID++, and an exerpt can be found here: https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=shar The main difficulty which is related to this protocol is that it is a synchronous protocol using the input reports. So when we want to get some information from the device, we need to wait for a matching input report. This driver introduce this capabilities to be able to support the multitouch mode of the Logitech Wireless Touchpad T651 (the bluetooth one). The multitouch data is available directly from the mouse input reports, and we just need to query the device on connect about its caracteristics. HID++ and the touchpad features has a specific reporting mode which uses pure HID++ reports, but Logitech told us not to use it for this specific device. During QA, they detected that some bluetooth input reports where lost, and so the only supported mode is the pointer mode. Signed-off-by: Benjamin Tissoires Tested-by: Andrew de los Reyes Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 842 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 842 insertions(+) create mode 100644 drivers/hid/hid-logitech-hidpp.c (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c new file mode 100644 index 000000000000..7dd9163f7e03 --- /dev/null +++ b/drivers/hid/hid-logitech-hidpp.c @@ -0,0 +1,842 @@ +/* + * HIDPP protocol for Logitech Unifying receivers + * + * Copyright (c) 2011 Logitech (c) + * Copyright (c) 2012-2013 Google (c) + * Copyright (c) 2013-2014 Red Hat Inc. + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hid-ids.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Benjamin Tissoires "); +MODULE_AUTHOR("Nestor Lopez Casado "); + +#define REPORT_ID_HIDPP_SHORT 0x10 +#define REPORT_ID_HIDPP_LONG 0x11 + +#define HIDPP_REPORT_SHORT_LENGTH 7 +#define HIDPP_REPORT_LONG_LENGTH 20 + +#define HIDPP_QUIRK_CLASS_WTP BIT(0) + +/* + * There are two hidpp protocols in use, the first version hidpp10 is known + * as register access protocol or RAP, the second version hidpp20 is known as + * feature access protocol or FAP + * + * Most older devices (including the Unifying usb receiver) use the RAP protocol + * where as most newer devices use the FAP protocol. Both protocols are + * compatible with the underlying transport, which could be usb, Unifiying, or + * bluetooth. The message lengths are defined by the hid vendor specific report + * descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and + * the HIDPP_LONG report type (total message length 20 bytes) + * + * The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG + * messages. The Unifying receiver itself responds to RAP messages (device index + * is 0xFF for the receiver), and all messages (short or long) with a device + * index between 1 and 6 are passed untouched to the corresponding paired + * Unifying device. + * + * The paired device can be RAP or FAP, it will receive the message untouched + * from the Unifiying receiver. + */ + +struct fap { + u8 feature_index; + u8 funcindex_clientid; + u8 params[HIDPP_REPORT_LONG_LENGTH - 4U]; +}; + +struct rap { + u8 sub_id; + u8 reg_address; + u8 params[HIDPP_REPORT_LONG_LENGTH - 4U]; +}; + +struct hidpp_report { + u8 report_id; + u8 device_index; + union { + struct fap fap; + struct rap rap; + u8 rawbytes[sizeof(struct fap)]; + }; +} __packed; + +struct hidpp_device { + struct hid_device *hid_dev; + struct mutex send_mutex; + void *send_receive_buf; + wait_queue_head_t wait; + bool answer_available; + u8 protocol_major; + u8 protocol_minor; + + void *private_data; + + unsigned long quirks; +}; + + +#define HIDPP_ERROR 0x8f +#define HIDPP_ERROR_SUCCESS 0x00 +#define HIDPP_ERROR_INVALID_SUBID 0x01 +#define HIDPP_ERROR_INVALID_ADRESS 0x02 +#define HIDPP_ERROR_INVALID_VALUE 0x03 +#define HIDPP_ERROR_CONNECT_FAIL 0x04 +#define HIDPP_ERROR_TOO_MANY_DEVICES 0x05 +#define HIDPP_ERROR_ALREADY_EXISTS 0x06 +#define HIDPP_ERROR_BUSY 0x07 +#define HIDPP_ERROR_UNKNOWN_DEVICE 0x08 +#define HIDPP_ERROR_RESOURCE_ERROR 0x09 +#define HIDPP_ERROR_REQUEST_UNAVAILABLE 0x0a +#define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b +#define HIDPP_ERROR_WRONG_PIN_CODE 0x0c + +static int __hidpp_send_report(struct hid_device *hdev, + struct hidpp_report *hidpp_report) +{ + int fields_count, ret; + + switch (hidpp_report->report_id) { + case REPORT_ID_HIDPP_SHORT: + fields_count = HIDPP_REPORT_SHORT_LENGTH; + break; + case REPORT_ID_HIDPP_LONG: + fields_count = HIDPP_REPORT_LONG_LENGTH; + break; + default: + return -ENODEV; + } + + /* + * set the device_index as the receiver, it will be overwritten by + * hid_hw_request if needed + */ + hidpp_report->device_index = 0xff; + + ret = hid_hw_raw_request(hdev, hidpp_report->report_id, + (u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT, + HID_REQ_SET_REPORT); + + return ret == fields_count ? 0 : -1; +} + +static int hidpp_send_message_sync(struct hidpp_device *hidpp, + struct hidpp_report *message, + struct hidpp_report *response) +{ + int ret; + + mutex_lock(&hidpp->send_mutex); + + hidpp->send_receive_buf = response; + hidpp->answer_available = false; + + /* + * So that we can later validate the answer when it arrives + * in hidpp_raw_event + */ + *response = *message; + + ret = __hidpp_send_report(hidpp->hid_dev, message); + + if (ret) { + dbg_hid("__hidpp_send_report returned err: %d\n", ret); + memset(response, 0, sizeof(struct hidpp_report)); + goto exit; + } + + if (!wait_event_timeout(hidpp->wait, hidpp->answer_available, + 5*HZ)) { + dbg_hid("%s:timeout waiting for response\n", __func__); + memset(response, 0, sizeof(struct hidpp_report)); + ret = -ETIMEDOUT; + } + + if (response->report_id == REPORT_ID_HIDPP_SHORT && + response->fap.feature_index == HIDPP_ERROR) { + ret = response->fap.params[1]; + dbg_hid("__hidpp_send_report got hidpp error %02X\n", ret); + goto exit; + } + +exit: + mutex_unlock(&hidpp->send_mutex); + return ret; + +} + +static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, + u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count, + struct hidpp_report *response) +{ + struct hidpp_report *message = kzalloc(sizeof(struct hidpp_report), + GFP_KERNEL); + int ret; + + if (param_count > sizeof(message->fap.params)) + return -EINVAL; + + message->report_id = REPORT_ID_HIDPP_LONG; + message->fap.feature_index = feat_index; + message->fap.funcindex_clientid = funcindex_clientid; + memcpy(&message->fap.params, params, param_count); + + ret = hidpp_send_message_sync(hidpp, message, response); + kfree(message); + return ret; +} + +static inline bool hidpp_match_answer(struct hidpp_report *question, + struct hidpp_report *answer) +{ + return (answer->fap.feature_index == question->fap.feature_index) && + (answer->fap.funcindex_clientid == question->fap.funcindex_clientid); +} + +static inline bool hidpp_match_error(struct hidpp_report *question, + struct hidpp_report *answer) +{ + return (answer->fap.feature_index == HIDPP_ERROR) && + (answer->fap.funcindex_clientid == question->fap.feature_index) && + (answer->fap.params[0] == question->fap.funcindex_clientid); +} + +/* -------------------------------------------------------------------------- */ +/* 0x0000: Root */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_ROOT 0x0000 +#define HIDPP_PAGE_ROOT_IDX 0x00 + +#define CMD_ROOT_GET_FEATURE 0x01 +#define CMD_ROOT_GET_PROTOCOL_VERSION 0x11 + +static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature, + u8 *feature_index, u8 *feature_type) +{ + struct hidpp_report response; + int ret; + u8 params[2] = { feature >> 8, feature & 0x00FF }; + + ret = hidpp_send_fap_command_sync(hidpp, + HIDPP_PAGE_ROOT_IDX, + CMD_ROOT_GET_FEATURE, + params, 2, &response); + if (ret) + return ret; + + *feature_index = response.fap.params[0]; + *feature_type = response.fap.params[1]; + + return ret; +} + +static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) +{ + struct hidpp_report response; + int ret; + + ret = hidpp_send_fap_command_sync(hidpp, + HIDPP_PAGE_ROOT_IDX, + CMD_ROOT_GET_PROTOCOL_VERSION, + NULL, 0, &response); + + if (ret == 1) { + hidpp->protocol_major = 1; + hidpp->protocol_minor = 0; + return 0; + } + + if (ret) + return -ret; + + hidpp->protocol_major = response.fap.params[0]; + hidpp->protocol_minor = response.fap.params[1]; + + return ret; +} + +static bool hidpp_is_connected(struct hidpp_device *hidpp) +{ + int ret; + + ret = hidpp_root_get_protocol_version(hidpp); + if (!ret) + hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + return ret == 0; +} + +/* -------------------------------------------------------------------------- */ +/* 0x0005: GetDeviceNameType */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_GET_DEVICE_NAME_TYPE 0x0005 + +#define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT 0x01 +#define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME 0x11 +#define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE 0x21 + +static int hidpp_devicenametype_get_count(struct hidpp_device *hidpp, + u8 feature_index, u8 *nameLength) +{ + struct hidpp_report response; + int ret; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_GET_DEVICE_NAME_TYPE_GET_COUNT, NULL, 0, &response); + + if (ret) + return -ret; + + *nameLength = response.fap.params[0]; + + return ret; +} + +static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp, + u8 feature_index, u8 char_index, char *device_name, int len_buf) +{ + struct hidpp_report response; + int ret, i; + int count; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME, &char_index, 1, + &response); + + if (ret) + return -ret; + + if (response.report_id == REPORT_ID_HIDPP_LONG) + count = HIDPP_REPORT_LONG_LENGTH - 4; + else + count = HIDPP_REPORT_SHORT_LENGTH - 4; + + if (len_buf < count) + count = len_buf; + + for (i = 0; i < count; i++) + device_name[i] = response.fap.params[i]; + + return count; +} + +static char *hidpp_get_device_name(struct hidpp_device *hidpp, u8 *name_length) +{ + u8 feature_type; + u8 feature_index; + u8 __name_length; + char *name; + unsigned index = 0; + int ret; + + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE, + &feature_index, &feature_type); + if (ret) + goto out_err; + + ret = hidpp_devicenametype_get_count(hidpp, feature_index, + &__name_length); + if (ret) + goto out_err; + + name = kzalloc(__name_length + 1, GFP_KERNEL); + if (!name) + goto out_err; + + *name_length = __name_length + 1; + while (index < __name_length) + index += hidpp_devicenametype_get_device_name(hidpp, + feature_index, index, name + index, + __name_length - index); + + return name; + +out_err: + *name_length = 0; + return NULL; +} + +/* -------------------------------------------------------------------------- */ +/* 0x6100: TouchPadRawXY */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_TOUCHPAD_RAW_XY 0x6100 + +#define CMD_TOUCHPAD_GET_RAW_INFO 0x01 + +#define TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT 0x01 +#define TOUCHPAD_RAW_XY_ORIGIN_UPPER_LEFT 0x03 + +struct hidpp_touchpad_raw_info { + u16 x_size; + u16 y_size; + u8 z_range; + u8 area_range; + u8 timestamp_unit; + u8 maxcontacts; + u8 origin; + u16 res; +}; + +struct hidpp_touchpad_raw_xy_finger { + u8 contact_type; + u8 contact_status; + u16 x; + u16 y; + u8 z; + u8 area; + u8 finger_id; +}; + +struct hidpp_touchpad_raw_xy { + u16 timestamp; + struct hidpp_touchpad_raw_xy_finger fingers[2]; + u8 spurious_flag; + u8 end_of_frame; + u8 finger_count; + u8 button; +}; + +static int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp, + u8 feature_index, struct hidpp_touchpad_raw_info *raw_info) +{ + struct hidpp_report response; + int ret; + u8 *params = (u8 *)response.fap.params; + + ret = hidpp_send_fap_command_sync(hidpp, feature_index, + CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response); + + if (ret) + return -ret; + + raw_info->x_size = get_unaligned_be16(¶ms[0]); + raw_info->y_size = get_unaligned_be16(¶ms[2]); + raw_info->z_range = params[4]; + raw_info->area_range = params[5]; + raw_info->maxcontacts = params[7]; + raw_info->origin = params[8]; + /* res is given in unit per inch */ + raw_info->res = get_unaligned_be16(¶ms[13]) * 2 / 51; + + return ret; +} + +/* ************************************************************************** */ +/* */ +/* Device Support */ +/* */ +/* ************************************************************************** */ + +/* -------------------------------------------------------------------------- */ +/* Touchpad HID++ devices */ +/* -------------------------------------------------------------------------- */ + +struct wtp_data { + struct input_dev *input; + u16 x_size, y_size; + u8 finger_count; + u8 mt_feature_index; + u8 button_feature_index; + u8 maxcontacts; + bool flip_y; + unsigned int resolution; +}; + +static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + return -1; +} + +static void wtp_input_configured(struct hid_device *hdev, + struct hid_input *hidinput) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct wtp_data *wd = hidpp->private_data; + struct input_dev *input_dev = hidinput->input; + + __set_bit(EV_ABS, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); + __clear_bit(EV_REL, input_dev->evbit); + __clear_bit(EV_LED, input_dev->evbit); + + input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, wd->x_size, 0, 0); + input_abs_set_res(input_dev, ABS_MT_POSITION_X, wd->resolution); + input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, wd->y_size, 0, 0); + input_abs_set_res(input_dev, ABS_MT_POSITION_Y, wd->resolution); + + /* Max pressure is not given by the devices, pick one */ + input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 50, 0, 0); + + input_set_capability(input_dev, EV_KEY, BTN_LEFT); + + __set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit); + + input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER | + INPUT_MT_DROP_UNUSED); + + wd->input = input_dev; +} + +static void wtp_touch_event(struct wtp_data *wd, + struct hidpp_touchpad_raw_xy_finger *touch_report) +{ + int slot; + + if (!touch_report->finger_id || touch_report->contact_type) + /* no actual data */ + return; + + slot = input_mt_get_slot_by_key(wd->input, touch_report->finger_id); + + input_mt_slot(wd->input, slot); + input_mt_report_slot_state(wd->input, MT_TOOL_FINGER, + touch_report->contact_status); + if (touch_report->contact_status) { + input_event(wd->input, EV_ABS, ABS_MT_POSITION_X, + touch_report->x); + input_event(wd->input, EV_ABS, ABS_MT_POSITION_Y, + wd->flip_y ? wd->y_size - touch_report->y : + touch_report->y); + input_event(wd->input, EV_ABS, ABS_MT_PRESSURE, + touch_report->area); + } +} + +static void wtp_send_raw_xy_event(struct hidpp_device *hidpp, + struct hidpp_touchpad_raw_xy *raw) +{ + struct wtp_data *wd = hidpp->private_data; + int i; + + for (i = 0; i < 2; i++) + wtp_touch_event(wd, &(raw->fingers[i])); + + if (raw->end_of_frame) + input_event(wd->input, EV_KEY, BTN_LEFT, raw->button); + + if (raw->end_of_frame || raw->finger_count <= 2) { + input_mt_sync_frame(wd->input); + input_sync(wd->input); + } +} + +static int wtp_mouse_raw_xy_event(struct hidpp_device *hidpp, u8 *data) +{ + struct wtp_data *wd = hidpp->private_data; + u8 c1_area = ((data[7] & 0xf) * (data[7] & 0xf) + + (data[7] >> 4) * (data[7] >> 4)) / 2; + u8 c2_area = ((data[13] & 0xf) * (data[13] & 0xf) + + (data[13] >> 4) * (data[13] >> 4)) / 2; + struct hidpp_touchpad_raw_xy raw = { + .timestamp = data[1], + .fingers = { + { + .contact_type = 0, + .contact_status = !!data[7], + .x = get_unaligned_le16(&data[3]), + .y = get_unaligned_le16(&data[5]), + .z = c1_area, + .area = c1_area, + .finger_id = data[2], + }, { + .contact_type = 0, + .contact_status = !!data[13], + .x = get_unaligned_le16(&data[9]), + .y = get_unaligned_le16(&data[11]), + .z = c2_area, + .area = c2_area, + .finger_id = data[8], + } + }, + .finger_count = wd->maxcontacts, + .spurious_flag = 0, + .end_of_frame = (data[0] >> 7) == 0, + .button = data[0] & 0x01, + }; + + wtp_send_raw_xy_event(hidpp, &raw); + + return 1; +} + +static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct wtp_data *wd = hidpp->private_data; + + if (!wd || !wd->input || (data[0] != 0x02) || size < 21) + return 1; + + return wtp_mouse_raw_xy_event(hidpp, &data[7]); +} + +static int wtp_get_config(struct hidpp_device *hidpp) +{ + struct wtp_data *wd = hidpp->private_data; + struct hidpp_touchpad_raw_info raw_info = {0}; + u8 feature_type; + int ret; + + ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY, + &wd->mt_feature_index, &feature_type); + if (ret) + /* means that the device is not powered up */ + return ret; + + ret = hidpp_touchpad_get_raw_info(hidpp, wd->mt_feature_index, + &raw_info); + if (ret) + return ret; + + wd->x_size = raw_info.x_size; + wd->y_size = raw_info.y_size; + wd->maxcontacts = raw_info.maxcontacts; + wd->flip_y = raw_info.origin == TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT; + wd->resolution = raw_info.res; + + return 0; +} + +static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct wtp_data *wd; + + wd = devm_kzalloc(&hdev->dev, sizeof(struct wtp_data), + GFP_KERNEL); + if (!wd) + return -ENOMEM; + + hidpp->private_data = wd; + + return 0; +}; + +/* -------------------------------------------------------------------------- */ +/* Generic HID++ devices */ +/* -------------------------------------------------------------------------- */ + +static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) + return wtp_input_mapping(hdev, hi, field, usage, bit, max); + + return 0; +} + +static void hidpp_input_configured(struct hid_device *hdev, + struct hid_input *hidinput) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) + wtp_input_configured(hdev, hidinput); +} + +static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, + int size) +{ + struct hidpp_report *question = hidpp->send_receive_buf; + struct hidpp_report *answer = hidpp->send_receive_buf; + struct hidpp_report *report = (struct hidpp_report *)data; + + /* + * If the mutex is locked then we have a pending answer from a + * previoulsly sent command + */ + if (unlikely(mutex_is_locked(&hidpp->send_mutex))) { + /* + * Check for a correct hidpp20 answer or the corresponding + * error + */ + if (hidpp_match_answer(question, report) || + hidpp_match_error(question, report)) { + *answer = *report; + hidpp->answer_available = true; + wake_up(&hidpp->wait); + /* + * This was an answer to a command that this driver sent + * We return 1 to hid-core to avoid forwarding the + * command upstream as it has been treated by the driver + */ + + return 1; + } + } + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) + return wtp_raw_event(hidpp->hid_dev, data, size); + + return 0; +} + +static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + switch (data[0]) { + case REPORT_ID_HIDPP_LONG: + if (size != HIDPP_REPORT_LONG_LENGTH) { + hid_err(hdev, "received hid++ report of bad size (%d)", + size); + return 1; + } + return hidpp_raw_hidpp_event(hidpp, data, size); + case REPORT_ID_HIDPP_SHORT: + if (size != HIDPP_REPORT_SHORT_LENGTH) { + hid_err(hdev, "received hid++ report of bad size (%d)", + size); + return 1; + } + return hidpp_raw_hidpp_event(hidpp, data, size); + } + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) + return wtp_raw_event(hdev, data, size); + + return 0; +} + +static void hidpp_overwrite_name(struct hid_device *hdev) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + char *name; + u8 name_length; + + name = hidpp_get_device_name(hidpp, &name_length); + + if (!name) + hid_err(hdev, "unable to retrieve the name of the device"); + else + snprintf(hdev->name, sizeof(hdev->name), "%s", name); + + kfree(name); +} + +static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct hidpp_device *hidpp; + int ret; + bool connected; + + hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device), + GFP_KERNEL); + if (!hidpp) + return -ENOMEM; + + hidpp->hid_dev = hdev; + hid_set_drvdata(hdev, hidpp); + + hidpp->quirks = id->driver_data; + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { + ret = wtp_allocate(hdev, id); + if (ret) + return ret; + } + + mutex_init(&hidpp->send_mutex); + init_waitqueue_head(&hidpp->wait); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "%s:parse failed\n", __func__); + goto hid_parse_fail; + } + + /* Allow incoming packets */ + hid_device_io_start(hdev); + + connected = hidpp_is_connected(hidpp); + if (!connected) { + hid_err(hdev, "Device not connected"); + goto hid_parse_fail; + } + + /* the device is connected, we can ask for its name */ + hid_info(hdev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + hidpp_overwrite_name(hdev); + + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { + ret = wtp_get_config(hidpp); + if (ret) + goto hid_parse_fail; + } + + /* Block incoming packets */ + hid_device_io_stop(hdev); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); + goto hid_hw_start_fail; + } + + return ret; + +hid_hw_start_fail: +hid_parse_fail: + mutex_destroy(&hidpp->send_mutex); + hid_set_drvdata(hdev, NULL); + return ret; +} + +static void hidpp_remove(struct hid_device *hdev) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + mutex_destroy(&hidpp->send_mutex); + hid_hw_stop(hdev); +} + +static const struct hid_device_id hidpp_devices[] = { + { /* wireless touchpad T651 */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_T651), + .driver_data = HIDPP_QUIRK_CLASS_WTP }, + {} +}; + +MODULE_DEVICE_TABLE(hid, hidpp_devices); + +static struct hid_driver hidpp_driver = { + .name = "logitech-hidpp-device", + .id_table = hidpp_devices, + .probe = hidpp_probe, + .remove = hidpp_remove, + .raw_event = hidpp_raw_event, + .input_configured = hidpp_input_configured, + .input_mapping = hidpp_input_mapping, +}; + +module_hid_driver(hidpp_driver); -- cgit v1.2.3-55-g7522 From ab94e562ed45c99914fe874b7feaf75b80ceea84 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 30 Sep 2014 13:18:28 -0400 Subject: HID: logitech: move dj devices to the HID++ module Devices connected through the Logitech Wireless Receiver are HID++ devices. We can handle them here to benefit from this new module and activate enhaced support of the various wireless touchpad or mice with touch sensors on them. Signed-off-by: Benjamin Tissoires Tested-by: Andrew de los Reyes Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + drivers/hid/hid-logitech-dj.c | 42 +--------------------------------------- drivers/hid/hid-logitech-hidpp.c | 21 ++++++++++++-------- 3 files changed, 15 insertions(+), 49 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 6f299cd54e6d..dd49a9b14b9c 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -371,6 +371,7 @@ config HID_LOGITECH_DJ tristate "Logitech Unifying receivers full support" depends on HIDRAW depends on HID_LOGITECH + select HID_LOGITECH_HIDPP ---help--- Say Y if you want support for Logitech Unifying receivers and devices. Unifying receivers are capable of pairing up to 6 Logitech compliant diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 6aea16dca3ff..45a7eacdfe98 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -977,48 +977,8 @@ static struct hid_driver logi_djreceiver_driver = { #endif }; +module_hid_driver(logi_djreceiver_driver); -static const struct hid_device_id logi_dj_devices[] = { - { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, - USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, - {} -}; - -static struct hid_driver logi_djdevice_driver = { - .name = "logitech-djdevice", - .id_table = logi_dj_devices, -}; - - -static int __init logi_dj_init(void) -{ - int retval; - - dbg_hid("Logitech-DJ:%s\n", __func__); - - retval = hid_register_driver(&logi_djreceiver_driver); - if (retval) - return retval; - - retval = hid_register_driver(&logi_djdevice_driver); - if (retval) - hid_unregister_driver(&logi_djreceiver_driver); - - return retval; - -} - -static void __exit logi_dj_exit(void) -{ - dbg_hid("Logitech-DJ:%s\n", __func__); - - hid_unregister_driver(&logi_djdevice_driver); - hid_unregister_driver(&logi_djreceiver_driver); - -} - -module_init(logi_dj_init); -module_exit(logi_dj_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Logitech"); MODULE_AUTHOR("Nestor Lopez Casado"); diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 7dd9163f7e03..48dec394dd38 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -777,15 +777,17 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_device_io_start(hdev); connected = hidpp_is_connected(hidpp); - if (!connected) { - hid_err(hdev, "Device not connected"); - goto hid_parse_fail; - } + if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) { + if (!connected) { + hid_err(hdev, "Device not connected"); + goto hid_parse_fail; + } - /* the device is connected, we can ask for its name */ - hid_info(hdev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); - hidpp_overwrite_name(hdev); + /* the device is connected, we can ask for its name */ + hid_info(hdev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + hidpp_overwrite_name(hdev); + } if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { ret = wtp_get_config(hidpp); @@ -824,6 +826,9 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_T651), .driver_data = HIDPP_QUIRK_CLASS_WTP }, + + { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, {} }; -- cgit v1.2.3-55-g7522 From 33797820af98cde5c7cee00d00f0d8e255ea199f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 30 Sep 2014 13:18:30 -0400 Subject: HID: logitech: allow the DJ device to request the unifying name The names of the DJ devices are stored in the receiver. These names can be retrieved through a HID++ command. However, the protocol says that you have to ask the receiver for that, not the device iteself. Introduce a special case in the DJ handling where a device can request its unifying name, and when such a name is given, forward it also to the corresponding device. On the HID++ side, the receiver talks only HID++ 1.0, so we need to implement this part of the protocol in the module. Signed-off-by: Benjamin Tissoires Tested-by: Andrew de los Reyes Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-dj.c | 24 ++++++++++-- drivers/hid/hid-logitech-hidpp.c | 80 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 7 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index feddacd87b8b..9bc39421627f 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -667,6 +667,9 @@ static void logi_dj_ll_close(struct hid_device *hid) dbg_hid("%s:%s\n", __func__, hid->phys); } +static u8 unifying_name_query[] = {0x10, 0xff, 0x83, 0xb5, 0x40, 0x00, 0x00}; +static u8 unifying_name_answer[] = {0x11, 0xff, 0x83, 0xb5}; + static int logi_dj_ll_raw_request(struct hid_device *hid, unsigned char reportnum, __u8 *buf, size_t count, unsigned char report_type, @@ -682,7 +685,13 @@ static int logi_dj_ll_raw_request(struct hid_device *hid, if (count < 2) return -EINVAL; - buf[1] = djdev->device_index; + /* special case where we should not overwrite + * the device_index */ + if (count == 7 && !memcmp(buf, unifying_name_query, + sizeof(unifying_name_query))) + buf[4] |= djdev->device_index - 1; + else + buf[1] = djdev->device_index; return hid_hw_raw_request(djrcv_dev->hdev, reportnum, buf, count, report_type, reqtype); } @@ -873,8 +882,17 @@ static int logi_dj_hidpp_event(struct hid_device *hdev, unsigned long flags; u8 device_index = dj_report->device_index; - if (device_index == HIDPP_RECEIVER_INDEX) - return false; + if (device_index == HIDPP_RECEIVER_INDEX) { + /* special case were the device wants to know its unifying + * name */ + if (size == HIDPP_REPORT_LONG_LENGTH && + !memcmp(data, unifying_name_answer, + sizeof(unifying_name_answer)) && + ((data[4] & 0xF0) == 0x40)) + device_index = (data[4] & 0x0F) + 1; + else + return false; + } /* * Data is from the HID++ collection, in this case, we forward the diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 48dec394dd38..e748e45b5b2f 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -205,6 +205,31 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, return ret; } +static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, + u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count, + struct hidpp_report *response) +{ + struct hidpp_report *message = kzalloc(sizeof(struct hidpp_report), + GFP_KERNEL); + int ret; + + if ((report_id != REPORT_ID_HIDPP_SHORT) && + (report_id != REPORT_ID_HIDPP_LONG)) + return -EINVAL; + + if (param_count > sizeof(message->rap.params)) + return -EINVAL; + + message->report_id = report_id; + message->rap.sub_id = sub_id; + message->rap.reg_address = reg_address; + memcpy(&message->rap.params, params, param_count); + + ret = hidpp_send_message_sync(hidpp_dev, message, response); + kfree(message); + return ret; +} + static inline bool hidpp_match_answer(struct hidpp_report *question, struct hidpp_report *answer) { @@ -220,6 +245,45 @@ static inline bool hidpp_match_error(struct hidpp_report *question, (answer->fap.params[0] == question->fap.funcindex_clientid); } +/* -------------------------------------------------------------------------- */ +/* HIDP++ 1.0 commands */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_SET_REGISTER 0x80 +#define HIDPP_GET_REGISTER 0x81 +#define HIDPP_SET_LONG_REGISTER 0x82 +#define HIDPP_GET_LONG_REGISTER 0x83 + +#define HIDPP_REG_PAIRING_INFORMATION 0xB5 +#define DEVICE_NAME 0x40 + +static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev) +{ + struct hidpp_report response; + int ret; + /* hid-logitech-dj is in charge of setting the right device index */ + u8 params[1] = { DEVICE_NAME }; + char *name; + int len; + + ret = hidpp_send_rap_command_sync(hidpp_dev, + REPORT_ID_HIDPP_SHORT, + HIDPP_GET_LONG_REGISTER, + HIDPP_REG_PAIRING_INFORMATION, + params, 1, &response); + if (ret) + return NULL; + + len = response.rap.params[1]; + + name = kzalloc(len + 1, GFP_KERNEL); + if (!name) + return NULL; + + memcpy(name, &response.rap.params[2], len); + return name; +} + /* -------------------------------------------------------------------------- */ /* 0x0000: Root */ /* -------------------------------------------------------------------------- */ @@ -726,13 +790,21 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, return 0; } -static void hidpp_overwrite_name(struct hid_device *hdev) +static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); char *name; u8 name_length; - name = hidpp_get_device_name(hidpp, &name_length); + if (use_unifying) + /* + * the device is connected through an Unifying receiver, and + * might not be already connected. + * Ask the receiver for its name. + */ + name = hidpp_get_unifying_name(hidpp); + else + name = hidpp_get_device_name(hidpp, &name_length); if (!name) hid_err(hdev, "unable to retrieve the name of the device"); @@ -783,12 +855,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) goto hid_parse_fail; } - /* the device is connected, we can ask for its name */ hid_info(hdev, "HID++ %u.%u device connected.\n", hidpp->protocol_major, hidpp->protocol_minor); - hidpp_overwrite_name(hdev); } + hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE); + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { ret = wtp_get_config(hidpp); if (ret) -- cgit v1.2.3-55-g7522 From c39e3d5fc9dd3e16c6f59dd94d827540040de66d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 30 Sep 2014 13:18:32 -0400 Subject: HID: logitech-hidpp: late bind the input device on wireless connection Now that the receiver forwards the connect/disconnect events, we can know when the device is available to communicate with us. When it is ready, we can for instance retrieve its full name, which guarantee that we always have the same name for the DJ device (the DJ name is somewhat shorter than the HID++ name). This mechanism is mandatory for the touchpads line, which has the min/max information stored in the device. This information can only be retrieved when the device is connected. So we can not populate the input device until we are sure that the device is connected. This patch creates a new input device for such devices. However, this input is not bound to hid directly, so the various drivers which wants to use it are required to process completely the incoming reports in .raw_event(). Note that the patch in itself just adds the bits for the next ones, and this feature is disabled by default. Signed-off-by: Benjamin Tissoires Tested-by: Andrew de los Reyes Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 155 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 8 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index e748e45b5b2f..9561a1f9b42b 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -36,6 +36,9 @@ MODULE_AUTHOR("Nestor Lopez Casado "); #define HIDPP_QUIRK_CLASS_WTP BIT(0) +/* bits 1..20 are reserved for classes */ +#define HIDPP_QUIRK_DELAYED_INIT BIT(21) + /* * There are two hidpp protocols in use, the first version hidpp10 is known * as register access protocol or RAP, the second version hidpp20 is known as @@ -91,6 +94,11 @@ struct hidpp_device { void *private_data; + struct work_struct work; + struct kfifo delayed_work_fifo; + atomic_t connected; + struct input_dev *delayed_input; + unsigned long quirks; }; @@ -110,6 +118,8 @@ struct hidpp_device { #define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b #define HIDPP_ERROR_WRONG_PIN_CODE 0x0c +static void hidpp_connect_event(struct hidpp_device *hidpp_dev); + static int __hidpp_send_report(struct hid_device *hdev, struct hidpp_report *hidpp_report) { @@ -230,6 +240,13 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, return ret; } +static void delayed_work_cb(struct work_struct *work) +{ + struct hidpp_device *hidpp = container_of(work, struct hidpp_device, + work); + hidpp_connect_event(hidpp); +} + static inline bool hidpp_match_answer(struct hidpp_report *question, struct hidpp_report *answer) { @@ -245,6 +262,12 @@ static inline bool hidpp_match_error(struct hidpp_report *question, (answer->fap.params[0] == question->fap.funcindex_clientid); } +static inline bool hidpp_report_is_connect_event(struct hidpp_report *report) +{ + return (report->report_id == REPORT_ID_HIDPP_SHORT) && + (report->rap.sub_id == 0x41); +} + /* -------------------------------------------------------------------------- */ /* HIDP++ 1.0 commands */ /* -------------------------------------------------------------------------- */ @@ -535,12 +558,10 @@ static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi, return -1; } -static void wtp_input_configured(struct hid_device *hdev, - struct hid_input *hidinput) +static void wtp_populate_input(struct hidpp_device *hidpp, + struct input_dev *input_dev, bool origin_is_hid_core) { - struct hidpp_device *hidpp = hid_get_drvdata(hdev); struct wtp_data *wd = hidpp->private_data; - struct input_dev *input_dev = hidinput->input; __set_bit(EV_ABS, input_dev->evbit); __set_bit(EV_KEY, input_dev->evbit); @@ -716,13 +737,20 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi, return 0; } +static void hidpp_populate_input(struct hidpp_device *hidpp, + struct input_dev *input, bool origin_is_hid_core) +{ + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) + wtp_populate_input(hidpp, input, origin_is_hid_core); +} + static void hidpp_input_configured(struct hid_device *hdev, struct hid_input *hidinput) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct input_dev *input = hidinput->input; - if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) - wtp_input_configured(hdev, hidinput); + hidpp_populate_input(hidpp, input, true); } static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, @@ -756,6 +784,15 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, } } + if (unlikely(hidpp_report_is_connect_event(report))) { + atomic_set(&hidpp->connected, + !(report->rap.params[0] & (1 << 6))); + if ((hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) && + (schedule_work(&hidpp->work) == 0)) + dbg_hid("%s: connect event already queued\n", __func__); + return 1; + } + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) return wtp_raw_event(hidpp->hid_dev, data, size); @@ -814,11 +851,99 @@ static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying) kfree(name); } +static int hidpp_input_open(struct input_dev *dev) +{ + struct hid_device *hid = input_get_drvdata(dev); + + return hid_hw_open(hid); +} + +static void hidpp_input_close(struct input_dev *dev) +{ + struct hid_device *hid = input_get_drvdata(dev); + + hid_hw_close(hid); +} + +static struct input_dev *hidpp_allocate_input(struct hid_device *hdev) +{ + struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev); + + if (!input_dev) + return NULL; + + input_set_drvdata(input_dev, hdev); + input_dev->open = hidpp_input_open; + input_dev->close = hidpp_input_close; + + input_dev->name = hdev->name; + input_dev->phys = hdev->phys; + input_dev->uniq = hdev->uniq; + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + input_dev->dev.parent = &hdev->dev; + + return input_dev; +} + +static void hidpp_connect_event(struct hidpp_device *hidpp) +{ + struct hid_device *hdev = hidpp->hid_dev; + int ret = 0; + bool connected = atomic_read(&hidpp->connected); + struct input_dev *input; + char *name, *devm_name; + u8 name_length; + + if (!connected || hidpp->delayed_input) + return; + + if (!hidpp->protocol_major) { + ret = !hidpp_is_connected(hidpp); + if (ret) { + hid_err(hdev, "Can not get the protocol version.\n"); + return; + } + } + + /* the device is already connected, we can ask for its name and + * protocol */ + hid_info(hdev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + + input = hidpp_allocate_input(hdev); + if (!input) { + hid_err(hdev, "cannot allocate new input device: %d\n", ret); + return; + } + + name = hidpp_get_device_name(hidpp, &name_length); + if (!name) { + hid_err(hdev, "unable to retrieve the name of the device"); + } else { + devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name); + if (devm_name) + input->name = devm_name; + kfree(name); + } + + hidpp_populate_input(hidpp, input, false); + + ret = input_register_device(input); + if (ret) + input_free_device(input); + + hidpp->delayed_input = input; +} + static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct hidpp_device *hidpp; int ret; bool connected; + unsigned int connect_mask = HID_CONNECT_DEFAULT; hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device), GFP_KERNEL); @@ -836,6 +961,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } + INIT_WORK(&hidpp->work, delayed_work_cb); mutex_init(&hidpp->send_mutex); init_waitqueue_head(&hidpp->wait); @@ -860,8 +986,9 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) } hidpp_overwrite_name(hdev, id->group == HID_GROUP_LOGITECH_DJ_DEVICE); + atomic_set(&hidpp->connected, connected); - if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { + if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) { ret = wtp_get_config(hidpp); if (ret) goto hid_parse_fail; @@ -870,16 +997,27 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* Block incoming packets */ hid_device_io_stop(hdev); - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) + connect_mask &= ~HID_CONNECT_HIDINPUT; + + ret = hid_hw_start(hdev, connect_mask); if (ret) { hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); goto hid_hw_start_fail; } + if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) { + /* Allow incoming packets */ + hid_device_io_start(hdev); + + hidpp_connect_event(hidpp); + } + return ret; hid_hw_start_fail: hid_parse_fail: + cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); hid_set_drvdata(hdev, NULL); return ret; @@ -889,6 +1027,7 @@ static void hidpp_remove(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); hid_hw_stop(hdev); } -- cgit v1.2.3-55-g7522 From 586bdc4e4f15fec37dc37167d6f314ea94aa14d1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 30 Sep 2014 13:18:33 -0400 Subject: HID: logitech-hidpp: Add Wireless Touchpad T650 support All the bits are now in place to add the support of the Touchpad T650. The creation/population of the input device is delayed until the device is ready. The T650 uses the special HID++ reporting protocol, so activate this on connect. Signed-off-by: Benjamin Tissoires Tested-by: Andrew de los Reyes Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 105 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 9561a1f9b42b..f9a4ec0ff76d 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -471,6 +471,9 @@ out_err: #define HIDPP_PAGE_TOUCHPAD_RAW_XY 0x6100 #define CMD_TOUCHPAD_GET_RAW_INFO 0x01 +#define CMD_TOUCHPAD_SET_RAW_REPORT_STATE 0x21 + +#define EVENT_TOUCHPAD_RAW_XY 0x00 #define TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT 0x01 #define TOUCHPAD_RAW_XY_ORIGIN_UPPER_LEFT 0x03 @@ -530,6 +533,59 @@ static int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp, return ret; } +static int hidpp_touchpad_set_raw_report_state(struct hidpp_device *hidpp_dev, + u8 feature_index, bool send_raw_reports, + bool sensor_enhanced_settings) +{ + struct hidpp_report response; + + /* + * Params: + * bit 0 - enable raw + * bit 1 - 16bit Z, no area + * bit 2 - enhanced sensitivity + * bit 3 - width, height (4 bits each) instead of area + * bit 4 - send raw + gestures (degrades smoothness) + * remaining bits - reserved + */ + u8 params = send_raw_reports | (sensor_enhanced_settings << 2); + + return hidpp_send_fap_command_sync(hidpp_dev, feature_index, + CMD_TOUCHPAD_SET_RAW_REPORT_STATE, ¶ms, 1, &response); +} + +static void hidpp_touchpad_touch_event(u8 *data, + struct hidpp_touchpad_raw_xy_finger *finger) +{ + u8 x_m = data[0] << 2; + u8 y_m = data[2] << 2; + + finger->x = x_m << 6 | data[1]; + finger->y = y_m << 6 | data[3]; + + finger->contact_type = data[0] >> 6; + finger->contact_status = data[2] >> 6; + + finger->z = data[4]; + finger->area = data[5]; + finger->finger_id = data[6] >> 4; +} + +static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev, + u8 *data, struct hidpp_touchpad_raw_xy *raw_xy) +{ + memset(raw_xy, 0, sizeof(struct hidpp_touchpad_raw_xy)); + raw_xy->end_of_frame = data[8] & 0x01; + raw_xy->spurious_flag = (data[8] >> 1) & 0x01; + raw_xy->finger_count = data[15] & 0x0f; + raw_xy->button = (data[8] >> 2) & 0x01; + + if (raw_xy->finger_count) { + hidpp_touchpad_touch_event(&data[2], &raw_xy->fingers[0]); + hidpp_touchpad_touch_event(&data[9], &raw_xy->fingers[1]); + } +} + /* ************************************************************************** */ /* */ /* Device Support */ @@ -672,11 +728,28 @@ static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); struct wtp_data *wd = hidpp->private_data; + struct hidpp_report *report = (struct hidpp_report *)data; + struct hidpp_touchpad_raw_xy raw; - if (!wd || !wd->input || (data[0] != 0x02) || size < 21) + if (!wd || !wd->input) return 1; - return wtp_mouse_raw_xy_event(hidpp, &data[7]); + switch (data[0]) { + case 0x02: + if (size < 21) + return 1; + return wtp_mouse_raw_xy_event(hidpp, &data[7]); + case REPORT_ID_HIDPP_LONG: + if ((report->fap.feature_index != wd->mt_feature_index) || + (report->fap.funcindex_clientid != EVENT_TOUCHPAD_RAW_XY)) + return 1; + hidpp_touchpad_raw_xy_event(hidpp, data + 4, &raw); + + wtp_send_raw_xy_event(hidpp, &raw); + return 0; + } + + return 0; } static int wtp_get_config(struct hidpp_device *hidpp) @@ -721,6 +794,27 @@ static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id) return 0; }; +static void wtp_connect(struct hid_device *hdev, bool connected) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + struct wtp_data *wd = hidpp->private_data; + int ret; + + if (!connected) + return; + + if (!wd->x_size) { + ret = wtp_get_config(hidpp); + if (ret) { + hid_err(hdev, "Can not get wtp config: %d\n", ret); + return; + } + } + + hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index, + true, true); +} + /* -------------------------------------------------------------------------- */ /* Generic HID++ devices */ /* -------------------------------------------------------------------------- */ @@ -897,6 +991,9 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) char *name, *devm_name; u8 name_length; + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) + wtp_connect(hdev, connected); + if (!connected || hidpp->delayed_input) return; @@ -1033,6 +1130,10 @@ static void hidpp_remove(struct hid_device *hdev) } static const struct hid_device_id hidpp_devices[] = { + { /* wireless touchpad T650 */ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, 0x4101), + .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT }, { /* wireless touchpad T651 */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_T651), -- cgit v1.2.3-55-g7522 From 57ac86cf52e903d9e3e0f12b34c814cce6b65550 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 30 Sep 2014 13:18:34 -0400 Subject: HID: logitech-hidpp: add support of the first Logitech Wireless Touchpad This touchpad differs from the T650 in several ways: - the resolution is not correctly returned by the device - it presents physical buttons, so the button flag in the raw touch report is not filled. Signed-off-by: Benjamin Tissoires Tested-by: Andrew de los Reyes Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index f9a4ec0ff76d..17e27e98e442 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -38,6 +38,7 @@ MODULE_AUTHOR("Nestor Lopez Casado "); /* bits 1..20 are reserved for classes */ #define HIDPP_QUIRK_DELAYED_INIT BIT(21) +#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) /* * There are two hidpp protocols in use, the first version hidpp10 is known @@ -596,6 +597,8 @@ static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev, /* Touchpad HID++ devices */ /* -------------------------------------------------------------------------- */ +#define WTP_MANUAL_RESOLUTION 39 + struct wtp_data { struct input_dev *input; u16 x_size, y_size; @@ -634,7 +637,10 @@ static void wtp_populate_input(struct hidpp_device *hidpp, input_set_capability(input_dev, EV_KEY, BTN_LEFT); - __set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit); + if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) + input_set_capability(input_dev, EV_KEY, BTN_RIGHT); + else + __set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit); input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED); @@ -676,7 +682,8 @@ static void wtp_send_raw_xy_event(struct hidpp_device *hidpp, for (i = 0; i < 2; i++) wtp_touch_event(wd, &(raw->fingers[i])); - if (raw->end_of_frame) + if (raw->end_of_frame && + !(hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS)) input_event(wd->input, EV_KEY, BTN_LEFT, raw->button); if (raw->end_of_frame || raw->finger_count <= 2) { @@ -736,9 +743,17 @@ static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size) switch (data[0]) { case 0x02: - if (size < 21) - return 1; - return wtp_mouse_raw_xy_event(hidpp, &data[7]); + if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) { + input_event(wd->input, EV_KEY, BTN_LEFT, + !!(data[1] & 0x01)); + input_event(wd->input, EV_KEY, BTN_RIGHT, + !!(data[1] & 0x02)); + input_sync(wd->input); + } else { + if (size < 21) + return 1; + return wtp_mouse_raw_xy_event(hidpp, &data[7]); + } case REPORT_ID_HIDPP_LONG: if ((report->fap.feature_index != wd->mt_feature_index) || (report->fap.funcindex_clientid != EVENT_TOUCHPAD_RAW_XY)) @@ -775,6 +790,8 @@ static int wtp_get_config(struct hidpp_device *hidpp) wd->maxcontacts = raw_info.maxcontacts; wd->flip_y = raw_info.origin == TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT; wd->resolution = raw_info.res; + if (!wd->resolution) + wd->resolution = WTP_MANUAL_RESOLUTION; return 0; } @@ -1130,6 +1147,11 @@ static void hidpp_remove(struct hid_device *hdev) } static const struct hid_device_id hidpp_devices[] = { + { /* wireless touchpad */ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, 0x4011), + .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT | + HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS }, { /* wireless touchpad T650 */ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, USB_VENDOR_ID_LOGITECH, 0x4101), -- cgit v1.2.3-55-g7522 From 3a61e97563d78a2ca10752902449570d8433ce76 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 30 Sep 2014 13:18:35 -0400 Subject: HID: logitech-hidpp: support combo keyboard touchpad TK820 The TK820 presents both a keyboard and a touchpad on the same physical (and logical device). Use the generic hid-input processing for the keyboard part. The keyboard input device is created when the receiver is plugged in, so no events are missed on connect. When the device actaully connects, we can set it to use the raw multitouch reporting to have a consistent user experience accross all Logitech touchpads. Signed-off-by: Benjamin Tissoires Tested-by: Andrew de los Reyes Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 17e27e98e442..361e97da1169 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -39,6 +39,7 @@ MODULE_AUTHOR("Nestor Lopez Casado "); /* bits 1..20 are reserved for classes */ #define HIDPP_QUIRK_DELAYED_INIT BIT(21) #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) +#define HIDPP_QUIRK_MULTI_INPUT BIT(23) /* * There are two hidpp protocols in use, the first version hidpp10 is known @@ -614,6 +615,12 @@ static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + if ((hidpp->quirks & HIDPP_QUIRK_MULTI_INPUT) && + (field->application == HID_GD_KEYBOARD)) + return 0; + return -1; } @@ -622,6 +629,10 @@ static void wtp_populate_input(struct hidpp_device *hidpp, { struct wtp_data *wd = hidpp->private_data; + if ((hidpp->quirks & HIDPP_QUIRK_MULTI_INPUT) && origin_is_hid_core) + /* this is the generic hid-input call */ + return; + __set_bit(EV_ABS, input_dev->evbit); __set_bit(EV_KEY, input_dev->evbit); __clear_bit(EV_REL, input_dev->evbit); @@ -1114,6 +1125,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) connect_mask &= ~HID_CONNECT_HIDINPUT; + /* Re-enable hidinput for multi-input devices */ + if (hidpp->quirks & HIDPP_QUIRK_MULTI_INPUT) + connect_mask |= HID_CONNECT_HIDINPUT; + ret = hid_hw_start(hdev, connect_mask); if (ret) { hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); @@ -1160,6 +1175,11 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_T651), .driver_data = HIDPP_QUIRK_CLASS_WTP }, + { /* Keyboard TK820 */ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, + USB_VENDOR_ID_LOGITECH, 0x4102), + .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_MULTI_INPUT | + HIDPP_QUIRK_CLASS_WTP }, { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, USB_VENDOR_ID_LOGITECH, HID_ANY_ID)}, -- cgit v1.2.3-55-g7522 From 3e7830ceb94cd06c05832a0d53cf324db3792418 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 31 Oct 2014 12:14:39 +0300 Subject: HID: logitech-hidpp: leaks and NULL dereferences Shift the allocation down a few lines to avoid a memory leak and also add a check for allocation failure. Fixes: 2f31c5252910 ('HID: Introduce hidpp, a module to handle Logitech hid++ devices') Signed-off-by: Dan Carpenter Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 361e97da1169..8d2d54b527b0 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -200,13 +200,15 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count, struct hidpp_report *response) { - struct hidpp_report *message = kzalloc(sizeof(struct hidpp_report), - GFP_KERNEL); + struct hidpp_report *message; int ret; if (param_count > sizeof(message->fap.params)) return -EINVAL; + message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); + if (!message) + return -ENOMEM; message->report_id = REPORT_ID_HIDPP_LONG; message->fap.feature_index = feat_index; message->fap.funcindex_clientid = funcindex_clientid; @@ -221,8 +223,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count, struct hidpp_report *response) { - struct hidpp_report *message = kzalloc(sizeof(struct hidpp_report), - GFP_KERNEL); + struct hidpp_report *message; int ret; if ((report_id != REPORT_ID_HIDPP_SHORT) && @@ -232,6 +233,9 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, if (param_count > sizeof(message->rap.params)) return -EINVAL; + message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); + if (!message) + return -ENOMEM; message->report_id = report_id; message->rap.sub_id = sub_id; message->rap.reg_address = reg_address; -- cgit v1.2.3-55-g7522 From 8c9952b26b2be25311706082598e5e176eb92748 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 3 Nov 2014 16:09:58 -0500 Subject: HID: logitech-hidpp: fix negated returns Reported by Dan Carpenter: drivers/hid/hid-logitech-hidpp.c:359 hidpp_root_get_protocol_version() warn: should this return really be negated? drivers/hid/hid-logitech-hidpp.c:398 hidpp_devicenametype_get_count() warn: should this return really be negated? drivers/hid/hid-logitech-hidpp.c:417 hidpp_devicenametype_get_device_name() warn: should this return really be negated? drivers/hid/hid-logitech-hidpp.c:524 hidpp_touchpad_get_raw_info() warn: should this return really be negated? The problem lies in hidpp_send_message_sync() which can return 2 types of errors depending of their sign. Adding a comment there to clarify what is happening. To solve that, print an error in case of a protocol problem, and raise -EPROTO instead. Reported-by: Dan Carpenter Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 8d2d54b527b0..61f9e75c45d2 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -151,6 +151,14 @@ static int __hidpp_send_report(struct hid_device *hdev, return ret == fields_count ? 0 : -1; } +/** + * hidpp_send_message_sync() returns 0 in case of success, and something else + * in case of a failure. + * - If ' something else' is positive, that means that an error has been raised + * by the protocol itself. + * - If ' something else' is negative, that means that we had a classic error + * (-ENOMEM, -EPIPE, etc...) + */ static int hidpp_send_message_sync(struct hidpp_device *hidpp, struct hidpp_report *message, struct hidpp_report *response) @@ -359,8 +367,13 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) return 0; } + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } if (ret) - return -ret; + return ret; hidpp->protocol_major = response.fap.params[0]; hidpp->protocol_minor = response.fap.params[1]; @@ -398,8 +411,13 @@ static int hidpp_devicenametype_get_count(struct hidpp_device *hidpp, ret = hidpp_send_fap_command_sync(hidpp, feature_index, CMD_GET_DEVICE_NAME_TYPE_GET_COUNT, NULL, 0, &response); + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } if (ret) - return -ret; + return ret; *nameLength = response.fap.params[0]; @@ -417,8 +435,13 @@ static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp, CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME, &char_index, 1, &response); + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } if (ret) - return -ret; + return ret; if (response.report_id == REPORT_ID_HIDPP_LONG) count = HIDPP_REPORT_LONG_LENGTH - 4; @@ -524,8 +547,13 @@ static int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp, ret = hidpp_send_fap_command_sync(hidpp, feature_index, CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response); + if (ret > 0) { + hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", + __func__, ret); + return -EPROTO; + } if (ret) - return -ret; + return ret; raw_info->x_size = get_unaligned_be16(¶ms[0]); raw_info->y_size = get_unaligned_be16(¶ms[2]); -- cgit v1.2.3-55-g7522 From 552f12eb68440a0328dc9929066f6d0321a75874 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 3 Nov 2014 16:09:59 -0500 Subject: HID: logitech-hidpp: 2 fixes in hidpp_root_get_protocol_version() - remove the constant '1' - when the device is not connected, the protocol error HIDPP_ERROR_RESOURCE_ERROR is raised. We should not warn the user about it because it is somewhat expected as an answer when we check if the device is connected. Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 61f9e75c45d2..1a6395d0c2bd 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -361,12 +361,16 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) CMD_ROOT_GET_PROTOCOL_VERSION, NULL, 0, &response); - if (ret == 1) { + if (ret == HIDPP_ERROR_INVALID_SUBID) { hidpp->protocol_major = 1; hidpp->protocol_minor = 0; return 0; } + /* the device might not be connected */ + if (ret == HIDPP_ERROR_RESOURCE_ERROR) + return -EIO; + if (ret > 0) { hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n", __func__, ret); -- cgit v1.2.3-55-g7522 From 02cc097e62d51bf6e5cb4209ca90d9bf68e358b5 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 11 Dec 2014 13:51:17 +0100 Subject: HID: logitech-hidpp: do not return the name length We do not make any use of the actual name length get through hidpp_get_device_name(). Original patch by Benjamin Tissoires, this patch also replaces a (now) unnecessary goto by return NULL. Signed-off-by: Peter Wu Signed-off-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 1a6395d0c2bd..5066df8afee5 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -461,7 +461,7 @@ static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp, return count; } -static char *hidpp_get_device_name(struct hidpp_device *hidpp, u8 *name_length) +static char *hidpp_get_device_name(struct hidpp_device *hidpp) { u8 feature_type; u8 feature_index; @@ -473,28 +473,23 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp, u8 *name_length) ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE, &feature_index, &feature_type); if (ret) - goto out_err; + return NULL; ret = hidpp_devicenametype_get_count(hidpp, feature_index, &__name_length); if (ret) - goto out_err; + return NULL; name = kzalloc(__name_length + 1, GFP_KERNEL); if (!name) - goto out_err; + return NULL; - *name_length = __name_length + 1; while (index < __name_length) index += hidpp_devicenametype_get_device_name(hidpp, feature_index, index, name + index, __name_length - index); return name; - -out_err: - *name_length = 0; - return NULL; } /* -------------------------------------------------------------------------- */ @@ -989,7 +984,6 @@ static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); char *name; - u8 name_length; if (use_unifying) /* @@ -999,7 +993,7 @@ static void hidpp_overwrite_name(struct hid_device *hdev, bool use_unifying) */ name = hidpp_get_unifying_name(hidpp); else - name = hidpp_get_device_name(hidpp, &name_length); + name = hidpp_get_device_name(hidpp); if (!name) hid_err(hdev, "unable to retrieve the name of the device"); @@ -1053,7 +1047,6 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) bool connected = atomic_read(&hidpp->connected); struct input_dev *input; char *name, *devm_name; - u8 name_length; if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) wtp_connect(hdev, connected); @@ -1080,7 +1073,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } - name = hidpp_get_device_name(hidpp, &name_length); + name = hidpp_get_device_name(hidpp); if (!name) { hid_err(hdev, "unable to retrieve the name of the device"); } else { -- cgit v1.2.3-55-g7522 From 1430ee73b566a30bc210501d1e0e99c519707f1e Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 11 Dec 2014 13:51:18 +0100 Subject: HID: logitech-hidpp: check name retrieval return code hidpp_devicenametype_get_device_name() may return a negative value on protocol errors (for example, when the device is powered off). Explicitly check this condition to avoid a long-running loop. (0 cannot be returned as __name_length - index > 0, but check for it anyway as it would otherwise result in an infinite loop.) Signed-off-by: Peter Wu Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 5066df8afee5..4d72c20fa233 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -484,10 +484,16 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp) if (!name) return NULL; - while (index < __name_length) - index += hidpp_devicenametype_get_device_name(hidpp, + while (index < __name_length) { + ret = hidpp_devicenametype_get_device_name(hidpp, feature_index, index, name + index, __name_length - index); + if (ret <= 0) { + kfree(name); + return NULL; + } + index += ret; + } return name; } -- cgit v1.2.3-55-g7522 From 3a034a7a7d61c30809255c1fc27d47d52c8c1bb6 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 11 Dec 2014 13:51:19 +0100 Subject: HID: logitech-hidpp: add boundary check for name retrieval The HID response has a limited size. Do not trust the value returned by hardware, check that it really fits in the message. Signed-off-by: Peter Wu Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 4d72c20fa233..4292cc33c119 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -313,6 +313,9 @@ static char *hidpp_get_unifying_name(struct hidpp_device *hidpp_dev) len = response.rap.params[1]; + if (2 + len > sizeof(response.rap.params)) + return NULL; + name = kzalloc(len + 1, GFP_KERNEL); if (!name) return NULL; -- cgit v1.2.3-55-g7522 From f486d9dbcc8bc2fc14aac0bf6e24637485107496 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 11 Dec 2014 13:51:20 +0100 Subject: HID: logitech-hidpp: disable io in probe error path Balance a hid_device_io_start() call with hid_device_io_stop() in the error path. This avoids processing of HID reports when the probe fails which possibly leads to invalid memory access in hid_device_probe() as report_enum->report_id_hash might already be freed via hid_close_report(). hid_set_drvdata() is called before wtp_allocate, be consistent and clear drvdata too on the error path of wtp_allocate. Signed-off-by: Peter Wu Reviewed-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 4292cc33c119..2f420c0b6609 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -1121,7 +1121,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { ret = wtp_allocate(hdev, id); if (ret) - return ret; + goto wtp_allocate_fail; } INIT_WORK(&hidpp->work, delayed_work_cb); @@ -1141,6 +1141,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) { if (!connected) { hid_err(hdev, "Device not connected"); + hid_device_io_stop(hdev); goto hid_parse_fail; } @@ -1186,6 +1187,7 @@ hid_hw_start_fail: hid_parse_fail: cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); +wtp_allocate_fail: hid_set_drvdata(hdev, NULL); return ret; } -- cgit v1.2.3-55-g7522