--- zzzz-none-000/linux-3.10.107/drivers/hid/hid-logitech-dj.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/hid/hid-logitech-dj.c 2021-02-04 17:41:59.000000000 +0000 @@ -26,9 +26,104 @@ #include #include #include +#include #include #include "hid-ids.h" -#include "hid-logitech-dj.h" + +#define DJ_MAX_PAIRED_DEVICES 6 +#define DJ_MAX_NUMBER_NOTIFICATIONS 8 +#define DJ_RECEIVER_INDEX 0 +#define DJ_DEVICE_INDEX_MIN 1 +#define DJ_DEVICE_INDEX_MAX 6 + +#define DJREPORT_SHORT_LENGTH 15 +#define DJREPORT_LONG_LENGTH 32 + +#define REPORT_ID_DJ_SHORT 0x20 +#define REPORT_ID_DJ_LONG 0x21 + +#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_RECEIVER_INDEX 0xff + +#define REPORT_TYPE_RFREPORT_FIRST 0x01 +#define REPORT_TYPE_RFREPORT_LAST 0x1F + +/* Command Switch to DJ mode */ +#define REPORT_TYPE_CMD_SWITCH 0x80 +#define CMD_SWITCH_PARAM_DEVBITFIELD 0x00 +#define CMD_SWITCH_PARAM_TIMEOUT_SECONDS 0x01 +#define TIMEOUT_NO_KEEPALIVE 0x00 + +/* Command to Get the list of Paired devices */ +#define REPORT_TYPE_CMD_GET_PAIRED_DEVICES 0x81 + +/* Device Paired Notification */ +#define REPORT_TYPE_NOTIF_DEVICE_PAIRED 0x41 +#define SPFUNCTION_MORE_NOTIF_EXPECTED 0x01 +#define SPFUNCTION_DEVICE_LIST_EMPTY 0x02 +#define DEVICE_PAIRED_PARAM_SPFUNCTION 0x00 +#define DEVICE_PAIRED_PARAM_EQUAD_ID_LSB 0x01 +#define DEVICE_PAIRED_PARAM_EQUAD_ID_MSB 0x02 +#define DEVICE_PAIRED_RF_REPORT_TYPE 0x03 + +/* Device Un-Paired Notification */ +#define REPORT_TYPE_NOTIF_DEVICE_UNPAIRED 0x40 + + +/* Connection Status Notification */ +#define REPORT_TYPE_NOTIF_CONNECTION_STATUS 0x42 +#define CONNECTION_STATUS_PARAM_STATUS 0x00 +#define STATUS_LINKLOSS 0x01 + +/* Error Notification */ +#define REPORT_TYPE_NOTIF_ERROR 0x7F +#define NOTIF_ERROR_PARAM_ETYPE 0x00 +#define ETYPE_KEEPALIVE_TIMEOUT 0x01 + +/* supported DJ HID && RF report types */ +#define REPORT_TYPE_KEYBOARD 0x01 +#define REPORT_TYPE_MOUSE 0x02 +#define REPORT_TYPE_CONSUMER_CONTROL 0x03 +#define REPORT_TYPE_SYSTEM_CONTROL 0x04 +#define REPORT_TYPE_MEDIA_CENTER 0x08 +#define REPORT_TYPE_LEDS 0x0E + +/* RF Report types bitfield */ +#define STD_KEYBOARD 0x00000002 +#define STD_MOUSE 0x00000004 +#define MULTIMEDIA 0x00000008 +#define POWER_KEYS 0x00000010 +#define MEDIA_CENTER 0x00000100 +#define KBD_LEDS 0x00004000 + +struct dj_report { + u8 report_id; + u8 device_index; + u8 report_type; + u8 report_params[DJREPORT_SHORT_LENGTH - 3]; +}; + +struct dj_receiver_dev { + struct hid_device *hdev; + struct dj_device *paired_dj_devices[DJ_MAX_PAIRED_DEVICES + + DJ_DEVICE_INDEX_MIN]; + struct work_struct work; + struct kfifo notif_fifo; + spinlock_t lock; + bool querying_devices; +}; + +struct dj_device { + struct hid_device *hdev; + struct dj_receiver_dev *dj_receiver_dev; + u32 reports_supported; + u8 device_index; +}; /* Keyboard descriptor (1) */ static const char kbd_descriptor[] = { @@ -44,14 +139,6 @@ 0x19, 0xE0, /* USAGE_MINIMUM (Left Control) */ 0x29, 0xE7, /* USAGE_MAXIMUM (Right GUI) */ 0x81, 0x02, /* INPUT (Data,Var,Abs) */ - 0x95, 0x05, /* REPORT COUNT (5) */ - 0x05, 0x08, /* USAGE PAGE (LED page) */ - 0x19, 0x01, /* USAGE MINIMUM (1) */ - 0x29, 0x05, /* USAGE MAXIMUM (5) */ - 0x91, 0x02, /* OUTPUT (Data, Variable, Absolute) */ - 0x95, 0x01, /* REPORT COUNT (1) */ - 0x75, 0x03, /* REPORT SIZE (3) */ - 0x91, 0x01, /* OUTPUT (Constant) */ 0x95, 0x06, /* REPORT_COUNT (6) */ 0x75, 0x08, /* REPORT_SIZE (8) */ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ @@ -60,6 +147,18 @@ 0x19, 0x00, /* USAGE_MINIMUM (no event) */ 0x2A, 0xFF, 0x00, /* USAGE_MAXIMUM (reserved) */ 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ + 0x85, 0x0e, /* REPORT_ID (14) */ + 0x05, 0x08, /* USAGE PAGE (LED page) */ + 0x95, 0x05, /* REPORT COUNT (5) */ + 0x75, 0x01, /* REPORT SIZE (1) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x19, 0x01, /* USAGE MINIMUM (1) */ + 0x29, 0x05, /* USAGE MAXIMUM (5) */ + 0x91, 0x02, /* OUTPUT (Data, Variable, Absolute) */ + 0x95, 0x01, /* REPORT COUNT (1) */ + 0x75, 0x03, /* REPORT SIZE (3) */ + 0x91, 0x01, /* OUTPUT (Constant) */ 0xC0 }; @@ -152,6 +251,57 @@ 0xc0, /* EndCollection */ }; /* */ +/* HIDPP descriptor */ +static const char hidpp_descriptor[] = { + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x01, /* Usage (Vendor Usage 1) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x10, /* Report ID (16) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x06, /* Report Count (6) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x09, 0x01, /* Usage (Vendor Usage 1) */ + 0x81, 0x00, /* Input (Data,Arr,Abs) */ + 0x09, 0x01, /* Usage (Vendor Usage 1) */ + 0x91, 0x00, /* Output (Data,Arr,Abs) */ + 0xc0, /* End Collection */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x02, /* Usage (Vendor Usage 2) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x11, /* Report ID (17) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x13, /* Report Count (19) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x09, 0x02, /* Usage (Vendor Usage 2) */ + 0x81, 0x00, /* Input (Data,Arr,Abs) */ + 0x09, 0x02, /* Usage (Vendor Usage 2) */ + 0x91, 0x00, /* Output (Data,Arr,Abs) */ + 0xc0, /* End Collection */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x04, /* Usage (Vendor Usage 0x04) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x85, 0x20, /* Report ID (32) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x0e, /* Report Count (14) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x09, 0x41, /* Usage (Vendor Usage 0x41) */ + 0x81, 0x00, /* Input (Data,Arr,Abs) */ + 0x09, 0x41, /* Usage (Vendor Usage 0x41) */ + 0x91, 0x00, /* Output (Data,Arr,Abs) */ + 0x85, 0x21, /* Report ID (33) */ + 0x95, 0x1f, /* Report Count (31) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x26, 0xff, 0x00, /* Logical Maximum (255) */ + 0x09, 0x42, /* Usage (Vendor Usage 0x42) */ + 0x81, 0x00, /* Input (Data,Arr,Abs) */ + 0x09, 0x42, /* Usage (Vendor Usage 0x42) */ + 0x91, 0x00, /* Output (Data,Arr,Abs) */ + 0xc0, /* End Collection */ +}; + /* Maximum size of all defined hid reports in bytes (including report id) */ #define MAX_REPORT_SIZE 8 @@ -161,7 +311,8 @@ sizeof(mse_descriptor) + \ sizeof(consumer_descriptor) + \ sizeof(syscontrol_descriptor) + \ - sizeof(media_descriptor)) + sizeof(media_descriptor) + \ + sizeof(hidpp_descriptor)) /* Number of possible hid report types that can be created by this driver. * @@ -189,9 +340,6 @@ static struct hid_ll_driver logi_dj_ll_driver; -static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf, - size_t count, - unsigned char report_type); static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev); static void logi_dj_recv_destroy_djhid_device(struct dj_receiver_dev *djrcv_dev, @@ -251,16 +399,19 @@ } dj_hiddev->ll_driver = &logi_dj_ll_driver; - dj_hiddev->hid_output_raw_report = logi_dj_output_hidraw_report; dj_hiddev->dev.parent = &djrcv_hdev->dev; dj_hiddev->bus = BUS_USB; dj_hiddev->vendor = le16_to_cpu(usbdev->descriptor.idVendor); - dj_hiddev->product = le16_to_cpu(usbdev->descriptor.idProduct); + dj_hiddev->product = + (dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_MSB] + << 8) | + dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_LSB]; snprintf(dj_hiddev->name, sizeof(dj_hiddev->name), - "Logitech Unifying Device. Wireless PID:%02x%02x", - dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_MSB], - dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_LSB]); + "Logitech Unifying Device. Wireless PID:%04x", + dj_hiddev->product); + + dj_hiddev->group = HID_GROUP_LOGITECH_DJ_DEVICE; usb_make_path(usbdev, dj_hiddev->phys, sizeof(dj_hiddev->phys)); snprintf(tmpstr, sizeof(tmpstr), ":%d", dj_report->device_index); @@ -385,18 +536,6 @@ djdev = djrcv_dev->paired_dj_devices[dj_report->device_index]; - if (!djdev) { - dbg_hid("djrcv_dev->paired_dj_devices[dj_report->device_index]" - " is NULL, index %d\n", dj_report->device_index); - kfifo_in(&djrcv_dev->notif_fifo, dj_report, sizeof(struct dj_report)); - - if (schedule_work(&djrcv_dev->work) == 0) { - dbg_hid("%s: did not schedule the work item, was already " - "queued\n", __func__); - } - return; - } - memset(reportbuffer, 0, sizeof(reportbuffer)); for (i = 0; i < NUMBER_OF_HID_REPORTS; i++) { @@ -421,18 +560,6 @@ dj_device = djrcv_dev->paired_dj_devices[dj_report->device_index]; - if (dj_device == NULL) { - dbg_hid("djrcv_dev->paired_dj_devices[dj_report->device_index]" - " is NULL, index %d\n", dj_report->device_index); - kfifo_in(&djrcv_dev->notif_fifo, dj_report, sizeof(struct dj_report)); - - if (schedule_work(&djrcv_dev->work) == 0) { - dbg_hid("%s: did not schedule the work item, was already " - "queued\n", __func__); - } - return; - } - if ((dj_report->report_type > ARRAY_SIZE(hid_reportid_size_map) - 1) || (hid_reportid_size_map[dj_report->report_type] == 0)) { dbg_hid("invalid report type:%x\n", dj_report->report_type); @@ -446,6 +573,13 @@ } } +static void logi_dj_recv_forward_hidpp(struct dj_device *dj_dev, u8 *data, + int size) +{ + /* We are called from atomic context (tasklet && djrcv->lock held) */ + if (hid_input_report(dj_dev->hdev, HID_INPUT_REPORT, data, size, 1)) + dbg_hid("hid_input_report error\n"); +} static int logi_dj_recv_send_report(struct dj_receiver_dev *djrcv_dev, struct dj_report *dj_report) @@ -496,7 +630,9 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, unsigned timeout) { + struct hid_device *hdev = djrcv_dev->hdev; struct dj_report *dj_report; + u8 *buf; int retval; dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL); @@ -508,6 +644,37 @@ dj_report->report_params[CMD_SWITCH_PARAM_DEVBITFIELD] = 0x3F; dj_report->report_params[CMD_SWITCH_PARAM_TIMEOUT_SECONDS] = (u8)timeout; retval = logi_dj_recv_send_report(djrcv_dev, dj_report); + + /* + * Ugly sleep to work around a USB 3.0 bug when the receiver is still + * processing the "switch-to-dj" command while we send an other command. + * 50 msec should gives enough time to the receiver to be ready. + */ + msleep(50); + + /* + * Magical bits to set up hidpp notifications when the dj devices + * are connected/disconnected. + * + * We can reuse dj_report because HIDPP_REPORT_SHORT_LENGTH is smaller + * than DJREPORT_SHORT_LENGTH. + */ + buf = (u8 *)dj_report; + + memset(buf, 0, HIDPP_REPORT_SHORT_LENGTH); + + buf[0] = REPORT_ID_HIDPP_SHORT; + buf[1] = 0xFF; + buf[2] = 0x80; + buf[3] = 0x00; + buf[4] = 0x00; + buf[5] = 0x09; + buf[6] = 0x00; + + hid_hw_raw_request(hdev, REPORT_ID_HIDPP_SHORT, buf, + HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT, + HID_REQ_SET_REPORT); + kfree(dj_report); return retval; } @@ -525,19 +692,59 @@ dbg_hid("%s:%s\n", __func__, hid->phys); } -static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf, - size_t count, - unsigned char report_type) +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, + int reqtype) { - /* Called by hid raw to send data */ - dbg_hid("%s\n", __func__); + struct dj_device *djdev = hid->driver_data; + struct dj_receiver_dev *djrcv_dev = djdev->dj_receiver_dev; + u8 *out_buf; + int ret; - return 0; + if ((buf[0] == REPORT_ID_HIDPP_SHORT) || + (buf[0] == REPORT_ID_HIDPP_LONG)) { + if (count < 2) + return -EINVAL; + + /* 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); + } + + if (buf[0] != REPORT_TYPE_LEDS) + return -EINVAL; + + out_buf = kzalloc(DJREPORT_SHORT_LENGTH, GFP_ATOMIC); + if (!out_buf) + return -ENOMEM; + + if (count > DJREPORT_SHORT_LENGTH - 2) + count = DJREPORT_SHORT_LENGTH - 2; + + out_buf[0] = REPORT_ID_DJ_SHORT; + out_buf[1] = djdev->device_index; + memcpy(out_buf + 2, buf, count); + + ret = hid_hw_raw_request(djrcv_dev->hdev, out_buf[0], out_buf, + DJREPORT_SHORT_LENGTH, report_type, reqtype); + + kfree(out_buf); + return ret; } -static void rdcat(char **rdesc, unsigned int *rsize, const char *data, unsigned int size) +static void rdcat(char *rdesc, unsigned int *rsize, const char *data, unsigned int size) { - memcpy(*rdesc + *rsize, data, size); + memcpy(rdesc + *rsize, data, size); *rsize += size; } @@ -560,31 +767,31 @@ if (djdev->reports_supported & STD_KEYBOARD) { dbg_hid("%s: sending a kbd descriptor, reports_supported: %x\n", __func__, djdev->reports_supported); - rdcat(&rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor)); + rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor)); } if (djdev->reports_supported & STD_MOUSE) { dbg_hid("%s: sending a mouse descriptor, reports_supported: " "%x\n", __func__, djdev->reports_supported); - rdcat(&rdesc, &rsize, mse_descriptor, sizeof(mse_descriptor)); + rdcat(rdesc, &rsize, mse_descriptor, sizeof(mse_descriptor)); } if (djdev->reports_supported & MULTIMEDIA) { dbg_hid("%s: sending a multimedia report descriptor: %x\n", __func__, djdev->reports_supported); - rdcat(&rdesc, &rsize, consumer_descriptor, sizeof(consumer_descriptor)); + rdcat(rdesc, &rsize, consumer_descriptor, sizeof(consumer_descriptor)); } if (djdev->reports_supported & POWER_KEYS) { dbg_hid("%s: sending a power keys report descriptor: %x\n", __func__, djdev->reports_supported); - rdcat(&rdesc, &rsize, syscontrol_descriptor, sizeof(syscontrol_descriptor)); + rdcat(rdesc, &rsize, syscontrol_descriptor, sizeof(syscontrol_descriptor)); } if (djdev->reports_supported & MEDIA_CENTER) { dbg_hid("%s: sending a media center report descriptor: %x\n", __func__, djdev->reports_supported); - rdcat(&rdesc, &rsize, media_descriptor, sizeof(media_descriptor)); + rdcat(rdesc, &rsize, media_descriptor, sizeof(media_descriptor)); } if (djdev->reports_supported & KBD_LEDS) { @@ -592,64 +799,14 @@ __func__, djdev->reports_supported); } + rdcat(rdesc, &rsize, hidpp_descriptor, sizeof(hidpp_descriptor)); + retval = hid_parse_report(hid, rdesc, rsize); kfree(rdesc); return retval; } -static int logi_dj_ll_input_event(struct input_dev *dev, unsigned int type, - unsigned int code, int value) -{ - /* Sent by the input layer to handle leds and Force Feedback */ - struct hid_device *dj_hiddev = input_get_drvdata(dev); - struct dj_device *dj_dev = dj_hiddev->driver_data; - - struct dj_receiver_dev *djrcv_dev = - dev_get_drvdata(dj_hiddev->dev.parent); - struct hid_device *dj_rcv_hiddev = djrcv_dev->hdev; - struct hid_report_enum *output_report_enum; - - struct hid_field *field; - struct hid_report *report; - unsigned char *data; - int offset; - - dbg_hid("%s: %s, type:%d | code:%d | value:%d\n", - __func__, dev->phys, type, code, value); - - if (type != EV_LED) - return -1; - - offset = hidinput_find_field(dj_hiddev, type, code, &field); - - if (offset == -1) { - dev_warn(&dev->dev, "event field not found\n"); - return -1; - } - hid_set_field(field, offset, value); - - data = hid_alloc_report_buf(field->report, GFP_KERNEL); - if (!data) { - dev_warn(&dev->dev, "failed to allocate report buf memory\n"); - return -1; - } - - hid_output_report(field->report, &data[0]); - - output_report_enum = &dj_rcv_hiddev->report_enum[HID_OUTPUT_REPORT]; - report = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT]; - hid_set_field(report->field[0], 0, dj_dev->device_index); - hid_set_field(report->field[0], 1, REPORT_TYPE_LEDS); - hid_set_field(report->field[0], 2, data[1]); - - hid_hw_request(dj_rcv_hiddev, report, HID_REQ_SET_REPORT); - - kfree(data); - - return 0; -} - static int logi_dj_ll_start(struct hid_device *hid) { dbg_hid("%s\n", __func__); @@ -668,11 +825,10 @@ .stop = logi_dj_ll_stop, .open = logi_dj_ll_open, .close = logi_dj_ll_close, - .hidinput_input_event = logi_dj_ll_input_event, + .raw_request = logi_dj_ll_raw_request, }; - -static int logi_dj_raw_event(struct hid_device *hdev, +static int logi_dj_dj_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { @@ -680,36 +836,24 @@ struct dj_report *dj_report = (struct dj_report *) data; unsigned long flags; - dbg_hid("%s, size:%d\n", __func__, size); - - /* Here we receive all data coming from iface 2, there are 4 cases: - * - * 1) Data should continue its normal processing i.e. data does not - * come from the DJ collection, in which case we do nothing and - * return 0, so hid-core can continue normal processing (will forward - * to associated hidraw device) + /* + * Here we receive all data coming from iface 2, there are 3 cases: * - * 2) Data is from DJ collection, and is intended for this driver i. e. - * data contains arrival, departure, etc notifications, in which case - * we queue them for delayed processing by the work queue. We return 1 - * to hid-core as no further processing is required from it. + * 1) Data is intended for this driver i. e. data contains arrival, + * departure, etc notifications, in which case we queue them for delayed + * processing by the work queue. We return 1 to hid-core as no further + * processing is required from it. * - * 3) Data is from DJ collection, and informs a connection change, - * if the change means rf link loss, then we must send a null report - * to the upper layer to discard potentially pressed keys that may be - * repeated forever by the input layer. Return 1 to hid-core as no - * further processing is required. + * 2) Data informs a connection change, if the change means rf link + * loss, then we must send a null report to the upper layer to discard + * potentially pressed keys that may be repeated forever by the input + * layer. Return 1 to hid-core as no further processing is required. * - * 4) Data is from DJ collection and is an actual input event from - * a paired DJ device in which case we forward it to the correct hid - * device (via hid_input_report() ) and return 1 so hid-core does not do - * anything else with it. + * 3) Data is an actual input event from a paired DJ device in which + * case we forward it to the correct hid device (via hid_input_report() + * ) and return 1 so hid-core does not anything else with it. */ - /* case 1) */ - if (data[0] != REPORT_ID_DJ_SHORT) - return false; - if ((dj_report->device_index < DJ_DEVICE_INDEX_MIN) || (dj_report->device_index > DJ_DEVICE_INDEX_MAX)) { /* @@ -724,8 +868,17 @@ } spin_lock_irqsave(&djrcv_dev->lock, flags); + + if (!djrcv_dev->paired_dj_devices[dj_report->device_index]) { + /* received an event for an unknown device, bail out */ + logi_dj_recv_queue_notification(djrcv_dev, dj_report); + goto out; + } + switch (dj_report->report_type) { case REPORT_TYPE_NOTIF_DEVICE_PAIRED: + /* pairing notifications are handled above the switch */ + break; case REPORT_TYPE_NOTIF_DEVICE_UNPAIRED: logi_dj_recv_queue_notification(djrcv_dev, dj_report); break; @@ -738,11 +891,101 @@ default: logi_dj_recv_forward_report(djrcv_dev, dj_report); } + +out: spin_unlock_irqrestore(&djrcv_dev->lock, flags); return true; } +static int logi_dj_hidpp_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, + int size) +{ + struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev); + struct dj_report *dj_report = (struct dj_report *) data; + unsigned long flags; + u8 device_index = dj_report->device_index; + + 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 + * data to the corresponding child dj device and return 0 to hid-core + * so he data also goes to the hidraw device of the receiver. This + * allows a user space application to implement the full HID++ routing + * via the receiver. + */ + + if ((device_index < DJ_DEVICE_INDEX_MIN) || + (device_index > DJ_DEVICE_INDEX_MAX)) { + /* + * Device index is wrong, bail out. + * This driver can ignore safely the receiver notifications, + * so ignore those reports too. + */ + dev_err(&hdev->dev, "%s: invalid device index:%d\n", + __func__, dj_report->device_index); + return false; + } + + spin_lock_irqsave(&djrcv_dev->lock, flags); + + if (!djrcv_dev->paired_dj_devices[device_index]) + /* received an event for an unknown device, bail out */ + goto out; + + logi_dj_recv_forward_hidpp(djrcv_dev->paired_dj_devices[device_index], + data, size); + +out: + spin_unlock_irqrestore(&djrcv_dev->lock, flags); + + return false; +} + +static int logi_dj_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, + int size) +{ + dbg_hid("%s, size:%d\n", __func__, size); + + switch (data[0]) { + case REPORT_ID_DJ_SHORT: + if (size != DJREPORT_SHORT_LENGTH) { + dev_err(&hdev->dev, "DJ report of bad size (%d)", size); + return false; + } + return logi_dj_dj_event(hdev, report, data, size); + case REPORT_ID_HIDPP_SHORT: + if (size != HIDPP_REPORT_SHORT_LENGTH) { + dev_err(&hdev->dev, + "Short HID++ report of bad size (%d)", size); + return false; + } + return logi_dj_hidpp_event(hdev, report, data, size); + case REPORT_ID_HIDPP_LONG: + if (size != HIDPP_REPORT_LONG_LENGTH) { + dev_err(&hdev->dev, + "Long HID++ report of bad size (%d)", size); + return false; + } + return logi_dj_hidpp_event(hdev, report, data, size); + } + + return false; +} + static int logi_dj_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -750,9 +993,6 @@ struct dj_receiver_dev *djrcv_dev; int retval; - if (is_dj_device((struct dj_device *)hdev->driver_data)) - return -ENODEV; - dbg_hid("%s called for ifnum %d\n", __func__, intf->cur_altsetting->desc.bInterfaceNumber); @@ -821,10 +1061,10 @@ } /* This is enabling the polling urb on the IN endpoint */ - retval = hdev->ll_driver->open(hdev); + retval = hid_hw_open(hdev); if (retval < 0) { - dev_err(&hdev->dev, "%s:hdev->ll_driver->open returned " - "error:%d\n", __func__, retval); + dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n", + __func__, retval); goto llopen_failed; } @@ -841,7 +1081,7 @@ return retval; logi_dj_recv_query_paired_devices_failed: - hdev->ll_driver->close(hdev); + hid_hw_close(hdev); llopen_failed: switch_to_dj_mode_fail: @@ -883,7 +1123,7 @@ cancel_work_sync(&djrcv_dev->work); - hdev->ll_driver->close(hdev); + hid_hw_close(hdev); hid_hw_stop(hdev); /* I suppose that at this point the only context that can access @@ -905,22 +1145,6 @@ hid_set_drvdata(hdev, NULL); } -static int logi_djdevice_probe(struct hid_device *hdev, - const struct hid_device_id *id) -{ - int ret; - struct dj_device *dj_dev = hdev->driver_data; - - if (!is_dj_device(dj_dev)) - return -ENODEV; - - ret = hid_parse(hdev); - if (!ret) - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - - return ret; -} - static const struct hid_device_id logi_dj_receivers[] = { {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER)}, @@ -942,51 +1166,8 @@ #endif }; +module_hid_driver(logi_djreceiver_driver); -static const struct hid_device_id logi_dj_devices[] = { - {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, - USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER)}, - {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, - USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2)}, - {} -}; - -static struct hid_driver logi_djdevice_driver = { - .name = "logitech-djdevice", - .id_table = logi_dj_devices, - .probe = logi_djdevice_probe, -}; - - -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");