Skip to contentSkip to conversation

Driver for Logitech M560

PATCH Superseded Goffredo Baroncelli 4 participants 14 messages Sep 2, 2014 – Apr 27, 2015 1 message not in the archive

Subsystems (4)
lore
Share & tools
lore
#1 lore raw

PATCH 1/1 Driver for Logitech M560

In reply to a message not in the archive

Sorry,
I repost this email because I didn't see it in the mailing list NR
G.Baroncelli

-------- Forwarded Message --------
Subject: Driver for Logitech M560
Date: Mon, 01 Sep 2014 21:20:20 +0200
From: Goffredo Baroncelli <[email protected]>
Reply-To: [email protected]
To: Benjamin Tissoires <[email protected]>
CC: Nestor Lopez Casado <[email protected]>, open list:HID CORE LAYER <[email protected]>

Hi Benjamin,

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

The Logitech M560 is is a mouse designed for windows 8. Comparing to a standard one, some buttons (the middle one and the two ones placed on the side) are bounded to a key combination instead of a classic "mouse" button.

Think this mouse as a pair of mouse and keyboard. When the middle button is pressed the it sends a key (as keyboard) combination, the same for the other two side button. Instead the left/right/wheel work correctly.
To complicate further the things, the middle button send a key combination the odd press, and another one for the even press; in the latter case it sends also a left click. But the worst thing is that no event is generated when the middle button is released.

Moreover this device is a wireless mouse which uses the unifying receiver.

I discovered that it is possible to re-configure the mouse sending a command (see function m560_send_config_command()). After this command the mouse sends some sequence when the buttons are pressed and/or released (see comments for an explanation of the mouse protocol).

If the mouse is "silent" (no data is sent) for more than PACKET_TIMEOUT seconds (currently 10), when the next packet comes, the driver try to reconfigure it.
This because it is possible that the mouse is switched-off and lost its setting. Se we need to reconfigure.

Benjamin, I have to highlight three things:
when the dj driver detects a disconnection, it
sends a sequence like

0x01 0x00 0x00 0x00 0x00 0x00 0x00 ...
0x02 0x00 0x00 0x00 0x00 0x00 0x00 ...

because the mouse is both a keyboard (0x01) and a mouse (0x02). But this sequence is a valid one (when two buttons are released) due to the strangeness of the protocol.

Can I suggest to send in these case a sequence like

<device_type> 0xff 0xff 0xff 0xff 0xff 0xff....

? I suspect that this would be less common.

Another thing that seemed strange to me is that report with ID equal 0x20 and 0x21 are handled so the packet are forwarded to the driver from the 3rd byte onward. In the others cases (this mouse send also packet with ID=0x11) the report id forwarded as is (without by-passing the first two bytes). It is the expected behaviour, or the code was written on the basis the assumption that the devices send ID=0x20/0x21 and the other ID are sent only by the receiver ?

Finally I have to point out that to work, this driver has to be inserted BEFORE the hid_logitech_dj

In fact I had to add a file /etc/modprobe.d/hid-logitech_dj.conf as below

install hid_logitech_dj modprobe hid_logitech_m560; sleep 5s ; modprobe --ignore-install hid_logitech_dj

It is possible that the "sleep 5s" is not needed.

BR
G.Baroncelli

Signature · 9 lines
gpg @keyserver.linux.it: Goffredo Baroncelli (kreijackATinwind.it>
Key fingerprint BBF5 1610 0B64 DAC6 5F7D  17B2 0EDA 9B37 8B82 E0B5


---

This driver add support for the mouse Logitech m560

Signed-off-by: Goffredo Baroncelli <[email protected]>
Makefile +1 -0
index 3016586..86157c0 100644
@@ -1,6 +1,7 @@
 obj-m	+= hid-logitech-dj.o
 obj-m	+= hid-logitech-hidpp.o
 obj-m	+= hid-logitech-wtp.o
+obj-m	+= hid-logitech-m560.o
 
 KDIR := /lib/modules/$(shell uname -r)/build
 PWD := $(shell pwd)
hid-logitech-m560.c new file +378 -0

378 lines not on this page · raw message

#2 lore raw
In reply to a message not in the archive

Hi Goffredo,

Quoted 4 lines · Goffredo Baroncelli

Hi Benjamin,

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

Quoted 24 lines

The Logitech M560 is is a mouse designed for windows 8. Comparing to a standard one, some buttons (the middle one and the two ones placed on the side) are bounded to a key combination instead of a classic "mouse" button.

Think this mouse as a pair of mouse and keyboard. When the middle button is pressed the it sends a key (as keyboard) combination, the same for the other two side button. Instead the left/right/wheel work correctly.
To complicate further the things, the middle button send a key combination the odd press, and another one for the even press; in the latter case it sends also a left click. But the worst thing is that no event is generated when the middle button is released.

Moreover this device is a wireless mouse which uses the unifying receiver.

I discovered that it is possible to re-configure the mouse sending a command (see function m560_send_config_command()). After this command the mouse sends some sequence when the buttons are pressed and/or released (see comments for an explanation of the mouse protocol).

Quoted 5 lines

If the mouse is "silent" (no data is sent) for more than PACKET_TIMEOUT seconds (currently 10), when the next packet comes, the driver try to reconfigure it.
This because it is possible that the mouse is switched-off and lost its setting. Se we need to reconfigure.

This just looks weird. I am pretty sure we managed to properly tackle that for the touchpads, there should not be any difference with the mouse. Or maybe we did not?
Anyway, calling this every timeout is not a good solution IMO.

Quoted 13 lines

Benjamin, I have to highlight three things:
when the dj driver detects a disconnection, it
sends a sequence like

0x01 0x00 0x00 0x00 0x00 0x00 0x00 ...
0x02 0x00 0x00 0x00 0x00 0x00 0x00 ...

because the mouse is both a keyboard (0x01) and a mouse (0x02). But this sequence is a valid one (when two buttons are released) due to the strangeness of the protocol.

Quoted 5 lines

Can I suggest to send in these case a sequence like

<device_type> 0xff 0xff 0xff 0xff 0xff 0xff....

? I suspect that this would be less common.

Nope. This does not work. The idea behind sending the "null" report is that this report will reset the current state of the keyboards/mice by sending a "all buttons are now released" event. It is _not_ designed as a marker that the device has been disconnected.

If you need this info, then another mechanism has to be used.

Quoted 4 lines

Another thing that seemed strange to me is that report with ID equal 0x20 and 0x21 are handled so the packet are forwarded to the driver from the 3rd byte onward.

Yes. 0x20 and 0x21 are DJ reports, which encapsulate a mouse/keyboard report coming from the DJ device.
To be properly handled by the final driver, we have to remove the encapsulation fields so that the reports are just plain HID.

Quoted 6 lines

In the others cases (this mouse send also packet with ID=0x11) the report id forwarded as is (without by-passing the first two bytes). It is the expected behaviour, or the code was written on the basis the assumption that the devices send ID=0x20/0x21 and the other ID are sent only by the receiver ?

No. The 0x10/0x11 reports are from the HID++ Logitech-specific implementation, and must be forwarded as is. They have to be handled as such by the final driver, and so, they must keep their report ID.

Quoted 10 lines

Finally I have to point out that to work, this driver has to be inserted BEFORE the hid_logitech_dj

In fact I had to add a file /etc/modprobe.d/hid-logitech_dj.conf as below

install hid_logitech_dj modprobe hid_logitech_m560; sleep 5s ; modprobe --ignore-install hid_logitech_dj

It is possible that the "sleep 5s" is not needed.

Hmm. This might be because there are some conflicts between your current install and the one you just changed. I would not worry about that. When this work will land upstream, there will be not problems.

I still did not decided if I am going to take this right now into the github tree or not (and make a deeper review).

BTW, there is no need to send such patch to the LKML currently, you are working against my non upstream branch. What you can do is either setup a github clone with your patch/modifications if you want to share them, or I can also pull this in a separate branch.

Once I'll finish the changes I want to do to hid-logitech-dj and hid-logitech-hidpp, then we can think of merging that in my submission.
Sorry, I still need a little bit of time to finish up this work.

Cheers,
Benjamin

Quoted 410 lines

--
gpg @keyserver.linux.it: Goffredo Baroncelli (kreijackATinwind.it> Key fingerprint BBF5 1610 0B64 DAC6 5F7D 17B2 0EDA 9B37 8B82 E0B5

---

This driver add support for the mouse Logitech m560

Makefile +1 -0
index 3016586..86157c0 100644
@@ -1,6 +1,7 @@
 obj-m  += hid-logitech-dj.o
 obj-m  += hid-logitech-hidpp.o
 obj-m  += hid-logitech-wtp.o
+obj-m  += hid-logitech-m560.o
 
 KDIR := /lib/modules/$(shell uname -r)/build
 PWD := $(shell pwd)
hid-logitech-m560.c new file +378 -0
new file mode 100644
index 0000000..5758423
@@ -0,0 +1,378 @@
+/*
+ *  HID driver for Logitech m560 mouse
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+#include "hid-ids.h"
+#include "hid-logitech-dj.h"
+#include "hid-logitech-hidpp.h"
+
+#define DJ_DEVICE_ID_M560 0x402d
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate further the things, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command
+ *     10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never send a keyboard key event
+ * - for the three mouse button it sends:
+ *     middle button               press   11<xx>0a 3500af00...
+ *     side 1 button (forward)     press   11<xx>0a 3500b000...
+ *     side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *     middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+static u8 m560_config_command[] = {0x35, 0x00, 0xaf, 0x03};
+
+struct m560_private_data {
+       union {
+               struct dj_report        dj_report;
+               u8 data[sizeof(struct dj_report)];
+       };
+       bool do_config_command;
+       struct delayed_work  work;
+       struct dj_device *djdev;
+       unsigned long packet_last_time;
+};
+
+/* how the button are mapped in the report */
+#define MOUSE_BTN_LEFT         0
+#define MOUSE_BTN_RIGHT                1
+#define MOUSE_BTN_MIDDLE       2
+#define MOUSE_BTN_WHEEL_LEFT   3
+#define MOUSE_BTN_WHEEL_RIGHT  4
+#define MOUSE_BTN_FORWARD      5
+#define MOUSE_BTN_BACKWARD     6
+
+#define CONFIG_COMMAND_TIMEOUT (3*HZ)
+#define PACKET_TIMEOUT         (10*HZ)
+
+
+/*
+ * m560_send_config_command - send the config_command to the mouse
+ *
+ * @dev: hid device where the mouse belongs
+ *
+ * @return: >0 OK, <0 error
+ */
+static int m560_send_config_command(struct hid_device *hdev)
+{
+       struct dj_report *dj_report;
+       int retval;
+       struct dj_device *dj_device = hdev->driver_data;
+       struct hid_device *djrcv_hdev = dj_device->dj_receiver_dev->hdev;
+
+       dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL);
+       if (!dj_report)
+               return -ENOMEM;
+
+       dj_report->report_id = REPORT_ID_HIDPP_SHORT;
+       dj_report->device_index = dj_device->device_index;
+       dj_report->report_type = 0x0a;
+
+       memcpy(dj_report->report_params, m560_config_command,
+                                       sizeof(m560_config_command));
+       retval = hid_hw_raw_request(djrcv_hdev,
+               dj_report->report_id,
+               (void *)dj_report, HIDPP_REPORT_SHORT_LENGTH,
+               HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+
+       kfree(dj_report);
+       return retval;
+
+}
+
+/*
+ * delayedwork_callback - handle the sending of the config_command.
+ * It schedules another sending because sometime the mouse doesn't understand
+ * the first request and returns an error. So until an ack is received, this
+ * function continue to reschedule a sending each RESET_TIMEOUT seconds
+ *
+ * @work: work_struct struct
+ */
+static void delayedwork_callback(struct work_struct *work)
+{
+       struct m560_private_data *mydata =
+               container_of(work, struct m560_private_data, work.work);
+       struct hid_device *hdev = mydata->djdev->hdev;
+
+       if (!mydata->do_config_command)
+               return;
+
+       if (schedule_delayed_work(&mydata->work, CONFIG_COMMAND_TIMEOUT) == 0) {
+               dbg_hid(
+                 "%s: did not schedule the work item, was already queued\n",
+                 __func__);
+       }
+
+       m560_send_config_command(hdev);
+}
+
+/*
+ * start_config_command - start sending config_command
+ *
+ * @mydata: pointer to private driver data
+ */
+static inline void start_config_command(struct m560_private_data *mydata)
+{
+       mydata->do_config_command = true;
+       if (schedule_delayed_work(&mydata->work, HZ/2) == 0) {
+               struct hid_device *hdev = mydata->djdev->hdev;
+               hid_err(hdev,
+                  "%s: did not schedule the work item, was already queued\n",
+                  __func__);
+       }
+}
+
+/*
+ * stop_config_command - stop sending config_command
+ *
+ * @mydata: pointer to private driver data
+ */
+static inline void stop_config_command(struct m560_private_data *mydata)
+{
+       mydata->do_config_command = false;
+}
+
+/*
+ * m560_djdevice_probe - perform the probing of the device.
+ *
+ * @hdev: hid device
+ * @id: hid device id
+ */
+static int m560_djdevice_probe(struct hid_device *hdev,
+                        const struct hid_device_id *id)
+{
+
+       int ret;
+       struct m560_private_data *mydata;
+       struct dj_device *dj_device = hdev->driver_data;
+
+       if (strcmp(hdev->name, "M560"))
+               return -ENODEV;
+
+       mydata = kzalloc(sizeof(struct m560_private_data), GFP_KERNEL);
+       if (!mydata)
+               return -ENOMEM;
+
+       mydata->djdev = dj_device;
+
+       /* force it so input_mapping doesn't pass anything */
+       mydata->do_config_command = true;
+
+       ret = hid_parse(hdev);
+       if (!ret)
+               ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+
+       /* we can't set before */
+       hid_set_drvdata(hdev, mydata);
+
+       if (ret) {
+               kfree(mydata);
+               return ret;
+       }
+       INIT_DELAYED_WORK(&mydata->work, delayedwork_callback);
+
+       start_config_command(mydata);
+
+       return 0;
+}
+
+static inline int mouse_btn_byte(int bit)
+{
+       return (bit / 8) & 0x01;
+}
+
+static inline int mouse_btn_bit(int bit)
+{
+       return 1 << (bit & 0x07);
+}
+
+static int m560_dj_raw_event(struct hid_device *hdev,
+                       struct hid_report *report, u8 *data, int size)
+{
+       struct m560_private_data *mydata = hid_get_drvdata(hdev);
+       int i;
+
+       /* count the zeros; the for body is empty */
+       for (i = 1 ; i < size && data[i] == 0 ; i++) ;
+
+       /* check if the data is a mouse related report */
+       if (data[0] != REPORT_TYPE_MOUSE && data[2] != 0x0a)
+               return 1;
+
+       /*
+        * Check if the last packet was older than PACKET_TIMEOUT
+        * This may be a sign of a disconnection; in this case
+        * we resend the config_command
+        *
+        * NOTE: in case of a disconnection, the receiver driver
+        * send the sequence 0x01 0x00 ... 0x00 and
+        * 0x02 0x00 ... 0x00, but these are possibile true packet
+        * sent by the mouse (release of the left and the front button)
+        */
+       if (mydata->packet_last_time &&
+           (jiffies - mydata->packet_last_time) > PACKET_TIMEOUT) {
+               start_config_command(mydata);
+       }
+       mydata->packet_last_time = jiffies;
+
+       /* check if the report is the ack of the config_command */
+       if (data[0] == 0x11 && data[2] == 0x0a &&
+           size >= (3+sizeof(m560_config_command)) &&
+           !memcmp(data+3, m560_config_command,
+               sizeof(m560_config_command))) {
+
+                       stop_config_command(mydata);
+                       return true;
+       }
+
+       /*
+        * check if the report is a mouse button sequence
+        *
+        * mydata->data[0] = type (0x02)
+        * mydata->data[1..2] = buttons
+        * mydata->data[3..5] = xy
+        * mydata->data[6] = wheel
+        * mydata->data[7] = horizontal wheel
+        */
+       if (data[0] == 0x11 && data[2] == 0x0a && data[06] == 0x00) {
+               int btn, i, maxsize;
+
+               /* check if the event is a button */
+               btn = data[5];
+               if (btn != 0x00 && btn != 0xb0 && btn != 0xae && btn != 0xaf)
+                       return true;
+
+               if (btn == 0xaf)
+                       mydata->data[1+mouse_btn_byte(MOUSE_BTN_MIDDLE)] |=
+                               mouse_btn_bit(MOUSE_BTN_MIDDLE);
+               else if (btn == 0xb0)
+                       mydata->data[1+mouse_btn_byte(MOUSE_BTN_FORWARD)] |=
+                               mouse_btn_bit(MOUSE_BTN_FORWARD);
+               else if (btn == 0xae)
+                       mydata->data[1+mouse_btn_byte(MOUSE_BTN_BACKWARD)] |=
+                               mouse_btn_bit(MOUSE_BTN_BACKWARD);
+
+               else if (btn == 0x00) {
+                       mydata->data[1+mouse_btn_byte(MOUSE_BTN_MIDDLE)] &=
+                               ~mouse_btn_bit(MOUSE_BTN_MIDDLE);
+                       mydata->data[1+mouse_btn_byte(MOUSE_BTN_FORWARD)] &=
+                               ~mouse_btn_bit(MOUSE_BTN_FORWARD);
+                       mydata->data[1+mouse_btn_byte(MOUSE_BTN_BACKWARD)] &=
+                               ~mouse_btn_bit(MOUSE_BTN_BACKWARD);
+               }
+
+               /* replace the report with a standard one */
+               if (size > sizeof(mydata->data))
+                       maxsize = sizeof(mydata->data);
+               else
+                       maxsize = size;
+               for (i = 0 ; i < maxsize ; i++)
+                       data[i] = mydata->data[i];
+
+               return 1;
+       }
+
+       /* check if the report is a "standard" mouse report */
+       if (data[0] == REPORT_TYPE_MOUSE) {
+               int i;
+
+               /* horizontal wheel handling */
+               if (data[1+mouse_btn_byte(MOUSE_BTN_WHEEL_LEFT)] &
+                   mouse_btn_bit(MOUSE_BTN_WHEEL_LEFT))
+                       data[1+6] = -1;
+               if (data[1+mouse_btn_byte(MOUSE_BTN_WHEEL_RIGHT)] &
+                   mouse_btn_bit(MOUSE_BTN_WHEEL_RIGHT))
+                       data[1+6] =  1;
+
+               data[1+mouse_btn_byte(MOUSE_BTN_WHEEL_LEFT)] &=
+                   ~mouse_btn_bit(MOUSE_BTN_WHEEL_LEFT);
+               data[1+mouse_btn_byte(MOUSE_BTN_WHEEL_RIGHT)] &=
+                   ~mouse_btn_bit(MOUSE_BTN_WHEEL_RIGHT);
+
+               /* copy the button status */
+               for (i = 0 ; i < 3 ; i++)
+                       mydata->data[i] = data[i];
+       }
+
+       return 1;
+}
+
+/*
+ * This function performs the cleanup when the device is removed
+ */
+static void m560_djdevice_remove(struct hid_device *hdev)
+{
+       struct m560_private_data *mydata = hid_get_drvdata(hdev);
+
+       if (!mydata)
+               return;
+
+       cancel_delayed_work_sync(&mydata->work);
+       hid_hw_stop(hdev);
+       kfree(mydata);
+}
+
+/*
+ * This function avoids that any event different from the mouse ones
+ * goes to the upper level
+ */
+static int m560_djdevice_input_mapping(struct hid_device *hdev,
+               struct hid_input *hi, struct hid_field *field,
+               struct hid_usage *usage, unsigned long **bit, int *max)
+{
+       if (field->application != HID_GD_MOUSE)
+               return -1;
+       return 0;
+}
+
+static const struct hid_device_id m560_dj_device[] = {
+       { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE_GENERIC,
+               USB_VENDOR_ID_LOGITECH, DJ_DEVICE_ID_M560)},
+       {}
+};
+
+MODULE_DEVICE_TABLE(hid, m560_dj_device);
+
+struct hid_driver hid_logitech_dj_device_driver_m560 = {
+       .name = "m560",
+       .id_table = m560_dj_device,
+       .probe = m560_djdevice_probe,
+       .remove = m560_djdevice_remove,
+       .input_mapping = m560_djdevice_input_mapping,
+       .raw_event = m560_dj_raw_event,
+};
+
+module_hid_driver(hid_logitech_dj_device_driver_m560)
+
+MODULE_AUTHOR("Goffredo Baroncelli <[email protected]>");
+MODULE_DESCRIPTION("Logitech Wireless Mouse m560");
+MODULE_LICENSE("GPL");
#3 lore raw
Quoted 5 lines · Benjamin Tissoires

Hi Goffredo,

Quoted 2 lines · Goffredo Baroncelli

Hi Benjamin,

Quoted 7 lines
Quoted 2 lines

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

:-) For me this is not a problem. I solved my issue (I made a dkms package for this mouse), but I want to share my effort..

Quoted 31 lines
Quoted 25 lines

The Logitech M560 is is a mouse designed for windows 8. Comparing to a standard one, some buttons (the middle one and the two ones placed on the side) are bounded to a key combination instead of a classic "mouse" button.

Think this mouse as a pair of mouse and keyboard. When the middle button is pressed the it sends a key (as keyboard) combination, the same for the other two side button. Instead the left/right/wheel work correctly.
To complicate further the things, the middle button send a key combination the odd press, and another one for the even press; in the latter case it sends also a left click. But the worst thing is that no event is generated when the middle button is released.

Moreover this device is a wireless mouse which uses the unifying receiver.

I discovered that it is possible to re-configure the mouse sending a command (see function m560_send_config_command()). After this command the mouse sends some sequence when the buttons are pressed and/or released (see comments for an explanation of the mouse protocol).

Quoted 5 lines

If the mouse is "silent" (no data is sent) for more than PACKET_TIMEOUT seconds (currently 10), when the next packet comes, the driver try to reconfigure it.
This because it is possible that the mouse is switched-off and lost its setting. Se we need to reconfigure.

Quoted 4 lines

This just looks weird. I am pretty sure we managed to properly tackle that for the touchpads, there should not be any difference with the mouse. Or maybe we did not?
Anyway, calling this every timeout is not a good solution IMO.

I looked at the wtp source, and I was not able to find anything. Anyway I will remove the reset-by-timeout; eventually I split this in another patch for further development

Quoted 19 lines
Quoted 19 lines

Benjamin, I have to highlight three things:
when the dj driver detects a disconnection, it
sends a sequence like

0x01 0x00 0x00 0x00 0x00 0x00 0x00 ...
0x02 0x00 0x00 0x00 0x00 0x00 0x00 ...

because the mouse is both a keyboard (0x01) and a mouse (0x02). But this sequence is a valid one (when two buttons are released) due to the strangeness of the protocol.

Can I suggest to send in these case a sequence like

<device_type> 0xff 0xff 0xff 0xff 0xff 0xff....

Quoted 8 lines
Quoted 1 line

? I suspect that this would be less common.

Nope. This does not work. The idea behind sending the "null" report is that this report will reset the current state of the keyboards/mice by sending a "all buttons are now released" event. It is _not_ designed as a marker that the device has been disconnected.

If you need this info, then another mechanism has to be used.

Have you any suggestions ?

Quoted 18 lines
Quoted 5 lines

Another thing that seemed strange to me is that report with ID equal 0x20 and 0x21 are handled so the packet are forwarded to the driver from the 3rd byte onward.

Yes. 0x20 and 0x21 are DJ reports, which encapsulate a mouse/keyboard report coming from the DJ device.
To be properly handled by the final driver, we have to remove the encapsulation fields so that the reports are just plain HID.

Quoted 6 lines

In the others cases (this mouse send also packet with ID=0x11) the report id forwarded as is (without by-passing the first two bytes). It is the expected behaviour, or the code was written on the basis the assumption that the devices send ID=0x20/0x21 and the other ID are sent only by the receiver ?

Quoted 3 lines

No. The 0x10/0x11 reports are from the HID++ Logitech-specific implementation, and must be forwarded as is. They have to be handled as such by the final driver, and so, they must keep their report ID.

Ok, I don't have any problem about that. I want to be sure that it is intentionally and not an hole discovered due to the M560 mouse protocol.

Quoted 16 lines
Quoted 11 lines

Finally I have to point out that to work, this driver has to be inserted BEFORE the hid_logitech_dj

In fact I had to add a file /etc/modprobe.d/hid-logitech_dj.conf as below

install hid_logitech_dj modprobe hid_logitech_m560; sleep 5s ; modprobe --ignore-install hid_logitech_dj

It is possible that the "sleep 5s" is not needed.

Hmm. This might be because there are some conflicts between your current install and the one you just changed. I would not worry about that. When this work will land upstream, there will be not problems.

Quoted 7 lines

I still did not decided if I am going to take this right now into the github tree or not (and make a deeper review).

BTW, there is no need to send such patch to the LKML currently, you are working against my non upstream branch. What you can do is either setup a github clone with your patch/modifications if you want to share them, or I can also pull this in a separate branch.

Ok, I will prepare (I hope this week-end) a github repo for a pull. Let me to remove the reset-by-timeout code.

Quoted 5 lines

Once I'll finish the changes I want to do to hid-logitech-dj and hid-logitech-hidpp, then we can think of merging that in my submission.
Sorry, I still need a little bit of time to finish up this work.

Ok, thanks

Quoted 3 lines

Cheers,
Benjamin

[... cut ... cut ... ]

G.Baroncelli

Signature · 2 lines
gpg @keyserver.linux.it: Goffredo Baroncelli (kreijackATinwind.it>
Key fingerprint BBF5 1610 0B64 DAC6 5F7D  17B2 0EDA 9B37 8B82 E0B5
#4 lore raw
Quoted 6 lines · On Fri, 05 Sep 2014 19:47:44 +0200 Goffredo Baroncelli <[email protected]>…
Quoted 5 lines · Benjamin Tissoires

Hi Goffredo,

Quoted 2 lines · Goffredo Baroncelli

Hi Benjamin,

Quoted 8 lines
Quoted 7 lines
Quoted 2 lines

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

Hi Benjamin, any news about upstreaming this work?

Quoted 3 lines

:-) For me this is not a problem. I solved my issue (I made a dkms package for this mouse), but I want to share my effort..

Goffredo, is this the latest version of your dkms-enabled external module?
https://github.com/kreijack/hid-logitech-dj/tree/m560-dkms

Thanks,
Antonio

Signature · 6 lines
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
#5 lore raw
Quoted 20 lines · Antonio Ospite
Quoted 6 lines · On Fri, 05 Sep 2014 19:47:44 +0200 Goffredo Baroncelli <[email protected]>…
Quoted 5 lines · Benjamin Tissoires

Hi Goffredo,

Quoted 2 lines · Goffredo Baroncelli

Hi Benjamin,

Quoted 8 lines
Quoted 7 lines
Quoted 2 lines

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

Hi Benjamin, any news about upstreaming this work?

Quoted 7 lines
Quoted 3 lines

:-) For me this is not a problem. I solved my issue (I made a dkms package for this mouse), but I want to share my effort..

Goffredo, is this the latest version of your dkms-enabled external module?
https://github.com/kreijack/hid-logitech-dj/tree/m560-dkms

Hi Antonio,

I was never capable to make dkms working; if you have got it, please geve me a patch... Anyway the most update branch is m560-devel, but you have to insert by hand the modules..

BR
Goffredo

Quoted 4 lines

Thanks,
Antonio

Signature · 2 lines
gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it>
Key fingerprint BBF5 1610 0B64 DAC6 5F7D  17B2 0EDA 9B37 8B82 E0B5
#6 lore raw
In reply to Antonio Ospite

Hi Antonio,

Quoted 12 lines · Antonio Ospite
Quoted 9 lines · On Fri, 05 Sep 2014 19:47:44 +0200 Goffredo Baroncelli <[email protected]>…
Quoted 8 lines · Benjamin Tissoires

Hi Goffredo,

Quoted 4 lines · Goffredo Baroncelli

Hi Benjamin,

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Quoted 7 lines
Quoted 5 lines
Quoted 4 lines

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

Hi Benjamin, any news about upstreaming this work?

The hidpp / raw touchpad mode is already upstream since the v3.19 kernel. \o/

We just need to port Goffredo's changes now and push them too. There has been some differences since this branch was published. The main is that there is no more special subdrivers in several files, everything is handled in hidpp. I think the logic behind is somewhat the same so it should not be too much a problem to do the work.

Cheers,
Benjamin

Quoted 19 lines
Quoted 4 lines

:-) For me this is not a problem. I solved my issue (I made a dkms package for this mouse), but I want to share my effort..

Goffredo, is this the latest version of your dkms-enabled external module?
https://github.com/kreijack/hid-logitech-dj/tree/m560-dkms

Thanks,
Antonio

--
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

#7 lore raw

Hi,

after the Antonio's mail, I updated my work on the latest kernel (3.19.3); this work for me, but I am sure that I made some mistakes, so please consider this a beta. I added a new device class (M560), and I put some hooks to process the raw data.

I used the following "quirks":
- HIDPP_QUIRK_DELAYED_INIT
- HIDPP_QUIRK_MULTI_INPUT

these work (when the mouse is connected, the configuration message is sent), but I am not sure if this is correct.
Anyway in the past I noticed some false spurious reconnections, and re-sending the configuration time to time made the mouse not-responsive for few tens of second (it seems a low value, but I noticed it); so I am not sure if I will leave the "reconfiguration" on reconnection.

Comments are welcome.

BR
G.Baroncelli

drivers/hid/hid-logitech-hidpp.c +241 -0
index a93cefe..4d907d6 100644
@@ -35,6 +35,7 @@ MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>");
 #define HIDPP_REPORT_LONG_LENGTH		20
 
 #define HIDPP_QUIRK_CLASS_WTP			BIT(0)
+#define HIDPP_QUIRK_CLASS_M560			BIT(1)
 
 /* bits 1..20 are reserved for classes */
 #define HIDPP_QUIRK_DELAYED_INIT		BIT(21)
@@ -925,6 +926,225 @@ static void wtp_connect(struct hid_device *hdev, bool connected)
 }
 
 /* -------------------------------------------------------------------------- */
+/* Logitech M560 devices                                                     */
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate further the things, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command
+ *	10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never send a keyboard key event
+ * - for the three mouse button it sends:
+ *	middle button               press   11<xx>0a 3500af00...
+ *	side 1 button (forward)     press   11<xx>0a 3500b000...
+ *	side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *	middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+static u8 m560_config_command[] = {0x35, 0x00, 0xaf, 0x03};
+
+struct m560_private_data {
+	u8 prev_data[30]; // FIXME: select a right size
+	int btn_middle:1;
+	int btn_forward:1;
+	int btn_backward:1;
+};
+
+/* how the button are mapped in the report */
+#define MOUSE_BTN_LEFT		0
+#define MOUSE_BTN_RIGHT		1
+#define MOUSE_BTN_MIDDLE	2
+#define MOUSE_BTN_WHEEL_LEFT	3
+#define MOUSE_BTN_WHEEL_RIGHT	4
+#define MOUSE_BTN_FORWARD	5
+#define MOUSE_BTN_BACKWARD	6
+
+/*
+ * m560_priv_data - helper to convert from hidpp_device to m560_private_data
+ *
+ * @hdev: hid device
+ *
+ * @return: return m560_private_data if available, NULL otherwise
+ */
+static inline struct m560_private_data *m560_priv_data(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+	return hidpp_dev ? hidpp_dev->private_data : NULL;
+}
+
+/*
+ * m560_send_config_command - send the config_command to the mouse
+ *
+ * @dev: hid device where the mouse belongs
+ *
+ * @return: 0 OK
+ */
+static int m560_send_config_command(struct hid_device *hdev) {
+	struct hidpp_report response;
+	struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+	int ret;
+
+	ret = hidpp_send_rap_command_sync(
+		hidpp_dev,
+		REPORT_ID_HIDPP_SHORT,
+		0x0a,
+		m560_config_command[0],
+		m560_config_command+1,
+		sizeof(m560_config_command)-1,
+		&response
+	);
+
+	return ret;
+}
+
+static int m560_allocate(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *d;
+
+	d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
+			GFP_KERNEL);
+	if (!d)
+		return -ENOMEM;
+
+	hidpp->private_data = d;
+	//d->hidpp_dev = hidpp;
+	//d->hid_dev = hdev;
+
+	return 0;
+};
+
+static inline void set_btn_bit(u8 *data, int bit)
+{
+	int bytenr = bit / 8;
+	int bitmask = 1 << (bit & 0x07);
+
+	data[bytenr] |= bitmask;
+}
+
+static inline int get_btn_bit(u8 *data, int bit)
+{
+	int bytenr = bit / 8;
+	int bitmask = 1 << (bit & 0x07);
+
+	return !!(data[bytenr] & bitmask);
+}
+
+static inline void clear_btn_bit(u8 *data, int bit)
+{
+	int bytenr = bit / 8;
+	int bitmask = 1 << (bit & 0x07);
+
+	data[bytenr] &= ~bitmask;
+}
+
+static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct m560_private_data *mydata = m560_priv_data(hdev);
+
+	/* check if the data is a mouse related report */
+	if (data[0] != 0x02 && data[2] != 0x0a)
+		return 1;
+
+	/* check if the report is the ack of the config_command */
+	if (data[0] == 0x11 && data[2] == 0x0a &&
+	    size >= (3+sizeof(m560_config_command)) &&
+	    !memcmp(data+3, m560_config_command,
+		sizeof(m560_config_command))) {
+			return true;
+	}
+
+	if (data[0] == 0x11 && data[2] == 0x0a && data[06] == 0x00) {
+		/*
+		 * m560 mouse button report
+		 *
+		 * data[0] = 0x11
+		 * data[1] = deviceid
+		 * data[2] = 0x0a
+		 * data[5] = button (0xaf->middle, 0xb0->forward,
+		 * 		     0xaf ->backward, 0x00->release all)
+		 * data[6] = 0x00
+		 */
+
+		int btn, i, maxsize;
+
+		/* check if the event is a button */
+		btn = data[5];
+		if (btn != 0x00 && btn != 0xb0 && btn != 0xae && btn != 0xaf)
+			return true;
+
+		if (btn == 0xaf)
+			mydata->btn_middle = 1;
+		else if (btn == 0xb0)
+			mydata->btn_forward = 1;
+		else if (btn == 0xae)
+			mydata->btn_backward = 1;
+		else if (btn == 0x00) {
+			mydata->btn_backward = 0;
+			mydata->btn_forward = 0;
+			mydata->btn_middle = 0;
+		}
+
+		/* replace the report with the old one */
+		if (size > sizeof(mydata->prev_data))
+			maxsize = sizeof(mydata->prev_data);
+		else
+			maxsize = size;
+		for (i = 0 ; i < maxsize ; i++)
+			data[i] = mydata->prev_data[i];
+
+	} else if (data[0] == 0x02) {
+		/*
+		 * standard mouse report
+		 *
+		 * data[0] = type (0x02)
+		 * data[1..2] = buttons
+		 * data[3..5] = xy
+		 * data[6] = wheel
+		 * data[7] = horizontal wheel
+		 */
+
+		/* horizontal wheel handling */
+		if (get_btn_bit(data+1,MOUSE_BTN_WHEEL_LEFT))
+			data[1+6] = -1;
+		if (get_btn_bit(data+1,MOUSE_BTN_WHEEL_RIGHT))
+			data[1+6] =  1;
+
+		clear_btn_bit(data+1, MOUSE_BTN_WHEEL_LEFT);
+		clear_btn_bit(data+1, MOUSE_BTN_WHEEL_RIGHT);
+
+		/* copy the type and buttons status */
+		memcpy(mydata->prev_data, data, 3);
+	}
+
+	/* add the extra buttons */
+	if (mydata->btn_middle)
+		set_btn_bit(data+1, MOUSE_BTN_MIDDLE);
+	if (mydata->btn_forward)
+		set_btn_bit(data+1, MOUSE_BTN_FORWARD);
+	if (mydata->btn_backward)
+		set_btn_bit(data+1, MOUSE_BTN_BACKWARD);
+
+	return 1;
+}
+
+/* -------------------------------------------------------------------------- */
 /* Generic HID++ devices                                                      */
 /* -------------------------------------------------------------------------- */
 
@@ -936,6 +1156,9 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_input_mapping(hdev, hi, field, usage, bit, max);
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
+		field->application != HID_GD_MOUSE)
+		                return -1;
 
 	return 0;
 }
@@ -998,6 +1221,8 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_raw_event(hidpp->hid_dev, data, size);
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		return m560_raw_event(hidpp->hid_dev, data, size);
 
 	return 0;
 }
@@ -1026,6 +1251,8 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_raw_event(hdev, data, size);
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		return m560_raw_event(hidpp->hid_dev, data, size);
 
 	return 0;
 }
@@ -1100,6 +1327,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		wtp_connect(hdev, connected);
+	if ((hidpp->quirks & HIDPP_QUIRK_CLASS_M560) && connected)
+		m560_send_config_command(hdev);
 
 	if (!connected || hidpp->delayed_input)
 		return;
@@ -1164,6 +1393,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 		if (ret)
 			goto wtp_allocate_fail;
 	}
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+		ret = m560_allocate(hdev);
+		if (ret)
+			goto wtp_allocate_fail;
+	}
 
 	INIT_WORK(&hidpp->work, delayed_work_cb);
 	mutex_init(&hidpp->send_mutex);
@@ -1240,6 +1474,7 @@ static void hidpp_remove(struct hid_device *hdev)
 	cancel_work_sync(&hidpp->work);
 	mutex_destroy(&hidpp->send_mutex);
 	hid_hw_stop(hdev);
+
 }
 
 static const struct hid_device_id hidpp_devices[] = {
@@ -1261,6 +1496,12 @@ static const struct hid_device_id hidpp_devices[] = {
 		USB_VENDOR_ID_LOGITECH, 0x4102),
 	  .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_MULTI_INPUT |
 			 HIDPP_QUIRK_CLASS_WTP },
+	{ /* Mouse logitech M560 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x402d),
+	  .driver_data = HIDPP_QUIRK_CLASS_M560 | HIDPP_QUIRK_DELAYED_INIT |
+			 HIDPP_QUIRK_MULTI_INPUT
+	},
 
 	{ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
 		USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
Quoted 54 lines · Benjamin Tissoires

Hi Antonio,

Quoted 12 lines · Antonio Ospite
Quoted 9 lines · On Fri, 05 Sep 2014 19:47:44 +0200 Goffredo Baroncelli <[email protected]>…
Quoted 8 lines · Benjamin Tissoires

Hi Goffredo,

Quoted 4 lines · Goffredo Baroncelli

Hi Benjamin,

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Quoted 7 lines
Quoted 5 lines
Quoted 4 lines

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

Hi Benjamin, any news about upstreaming this work?

The hidpp / raw touchpad mode is already upstream since the v3.19 kernel. \o/

We just need to port Goffredo's changes now and push them too. There has been some differences since this branch was published. The main is that there is no more special subdrivers in several files, everything is handled in hidpp. I think the logic behind is somewhat the same so it should not be too much a problem to do the work.

Cheers,
Benjamin

Quoted 19 lines
Quoted 4 lines

:-) For me this is not a problem. I solved my issue (I made a dkms package for this mouse), but I want to share my effort..

Goffredo, is this the latest version of your dkms-enabled external module?
https://github.com/kreijack/hid-logitech-dj/tree/m560-dkms

Thanks,
Antonio

--
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

Signature · 2 lines
gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it>
Key fingerprint BBF5 1610 0B64 DAC6 5F7D  17B2 0EDA 9B37 8B82 E0B5
#13 lore raw
In reply to Antonio Ospite

Hi Antonio,

I update my repository; I rebased my work on the current linux v3.19.3 kernel. The new repository is https://github.com/kreijack/logitech-m560.git
(branch master).

If you want to make a dkms package for this driver I am interested to integrate

BR
G.Baroncelli

Quoted 31 lines · Antonio Ospite
Quoted 6 lines · On Fri, 05 Sep 2014 19:47:44 +0200 Goffredo Baroncelli <[email protected]>…
Quoted 5 lines · Benjamin Tissoires

Hi Goffredo,

Quoted 2 lines · Goffredo Baroncelli

Hi Benjamin,

Quoted 8 lines
Quoted 7 lines
Quoted 2 lines

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

Hi Benjamin, any news about upstreaming this work?

Quoted 3 lines

:-) For me this is not a problem. I solved my issue (I made a dkms package for this mouse), but I want to share my effort..

Goffredo, is this the latest version of your dkms-enabled external module?
https://github.com/kreijack/hid-logitech-dj/tree/m560-dkms

Thanks,
Antonio

Signature · 2 lines
gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it>
Key fingerprint BBF5 1610 0B64 DAC6 5F7D  17B2 0EDA 9B37 8B82 E0B5
#14 lore raw
Quoted 8 lines · On Sun, 12 Apr 2015 20:47:39 +0200 Goffredo Baroncelli <[email protected]>…

Hi Antonio,

I update my repository; I rebased my work on the current linux v3.19.3 kernel. The new repository is https://github.com/kreijack/logitech-m560.git
(branch master).

If you want to make a dkms package for this driver I am interested to integrate

Hi Goffredo, I should be able to do some tests later this week. I'll let you know.

Thanks,
Antonio

Signature · 6 lines
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
#8 lore raw
In reply to Goffredo Baroncelli
Quoted 3 lines · Goffredo Baroncelli

Hi,

after the Antonio's mail, I updated my work on the latest kernel (3.19.3); this work for me, but I am sure that I made some mistakes, so please consider this a beta.

Thanks for the patch. I have some comments, please see inline.

Quoted 5 lines

I added a new device class (M560), and I put some hooks to process the raw data.

I used the following "quirks":
- HIDPP_QUIRK_DELAYED_INIT
- HIDPP_QUIRK_MULTI_INPUT

I don't think you really need HIDPP_QUIRK_MULTI_INPUT. If the mouse has only one input node (or you want to send only on the input node), then you don't need to keep the keyboard node.

[10 min later]

OK, after reviewing the code, I see why you kept HIDPP_QUIRK_MULTI_INPUT. I am not entirely sure what is the best
solution: keeping it and rely on hid-input for the parsing/allocating of regular mouse events and having .raw_event() tampering with the raw report, or just trash hid-input and process entirely the incoming report...

Quoted 5 lines

these work (when the mouse is connected, the configuration message is sent), but I am not sure if this is correct.
Anyway in the past I noticed some false spurious reconnections, and re-sending the configuration time to time made the mouse not-responsive for few tens of second (it seems a low value, but I noticed it); so I am not sure if I will leave the "reconfiguration" on reconnection.

Comments are welcome.

Please for the next submission do a proper one with a commit message and a signed-of-by line. This way, we can integrate it when the code looks clearer or someone else can take over the job if you decide not to port it upstream.

Quoted 10 lines

BR
G.Baroncelli

drivers/hid/hid-logitech-hidpp.c +0 -0
index a93cefe..4d907d6 100644

drivers/hid/hid-logitech-hidpp.c MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>"); · line 40

Quoted 6 lines
drivers/hid/hid-logitech-hidpp.c +1 -0
@@ -35,6 +35,7 @@ MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>");
 #define HIDPP_REPORT_LONG_LENGTH               20
 
 #define HIDPP_QUIRK_CLASS_WTP                  BIT(0)
+#define HIDPP_QUIRK_CLASS_M560                 BIT(1)
 
 /* bits 1..20 are reserved for classes */

Technically speaking, this should be changed to "2..20" :)

Quoted 27 lines
drivers/hid/hid-logitech-hidpp.c +22 -0
 #define HIDPP_QUIRK_DELAYED_INIT               BIT(21)
@@ -925,6 +926,225 @@ static void wtp_connect(struct hid_device *hdev, bool connected)
 }
 
 /* -------------------------------------------------------------------------- */
+/* Logitech M560 devices                                                     */
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate further the things, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 960

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+ *     10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never send a keyboard key event
+ * - for the three mouse button it sends:
+ *     middle button               press   11<xx>0a 3500af00...
+ *     side 1 button (forward)     press   11<xx>0a 3500b000...
+ *     side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *     middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+static u8 m560_config_command[] = {0x35, 0x00, 0xaf, 0x03};

please mark this one as const.

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 963

Quoted 3 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+
+struct m560_private_data {
+       u8 prev_data[30]; // FIXME: select a right size

Don't insert FIXME, fix it before the integration :)

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 966

Quoted 3 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+       int btn_middle:1;
+       int btn_forward:1;
+       int btn_backward:1;

using a int per button seems a lot of lost space. Maybe you can use a bitmask or at least bool (which remaps to an unsigned char IIRC).

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 976

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+};
+
+/* how the button are mapped in the report */
+#define MOUSE_BTN_LEFT         0
+#define MOUSE_BTN_RIGHT                1
+#define MOUSE_BTN_MIDDLE       2
+#define MOUSE_BTN_WHEEL_LEFT   3
+#define MOUSE_BTN_WHEEL_RIGHT  4
+#define MOUSE_BTN_FORWARD      5
+#define MOUSE_BTN_BACKWARD     6

Please prefix those defines by M560.

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 989

Quoted 13 lines
drivers/hid/hid-logitech-hidpp.c +13 -0
+
+/*
+ * m560_priv_data - helper to convert from hidpp_device to m560_private_data
+ *
+ * @hdev: hid device
+ *
+ * @return: return m560_private_data if available, NULL otherwise
+ */
+static inline struct m560_private_data *m560_priv_data(struct hid_device *hdev)
+{
+       struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+       return hidpp_dev ? hidpp_dev->private_data : NULL;
+}

Are you sure we really need this check for M560 mice? if you are calling this in the M560 protocol handling only and made sure in the probe that private_data gets allocated, no need to double check things.

Quoted 7 lines
drivers/hid/hid-logitech-hidpp.c +7 -0
+
+/*
+ * m560_send_config_command - send the config_command to the mouse
+ *
+ * @dev: hid device where the mouse belongs
+ *
+ * @return: 0 OK

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1006

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+ */
+static int m560_send_config_command(struct hid_device *hdev) {
+       struct hidpp_report response;
+       struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+       int ret;
+
+       ret = hidpp_send_rap_command_sync(
+               hidpp_dev,
+               REPORT_ID_HIDPP_SHORT,
+               0x0a,

Please use a define for this sub id.
BTW, are you sure the mouse is HID++ 1.0? If it is 2.0, you have to use the fap protocol, not the rap.

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1009

Quoted 3 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+               m560_config_command[0],
+               m560_config_command+1,
+               sizeof(m560_config_command)-1,

I would personally define m560_config_command[0] in a separate define (M560_BUTTON_MODE_REGISTER for instance) and keep the parameters to the right size right now.

Quoted 9 lines
drivers/hid/hid-logitech-hidpp.c +9 -0
+               &response
+       );
+
+       return ret;
+}
+
+static int m560_allocate(struct hid_device *hdev)
+{
+       struct hidpp_device *hidpp = hid_get_drvdata(hdev);

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1028

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+       struct m560_private_data *d;
+
+       d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
+                       GFP_KERNEL);
+       if (!d)
+               return -ENOMEM;
+
+       hidpp->private_data = d;
+       //d->hidpp_dev = hidpp;
+       //d->hid_dev = hdev;

No leftover debug comments please.

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1038

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+
+       return 0;
+};
+
+static inline void set_btn_bit(u8 *data, int bit)
+{
+       int bytenr = bit / 8;
+       int bitmask = 1 << (bit & 0x07);
+
+       data[bytenr] |= bitmask;

You seem to be using {set|get|clear}_btn_bit only with MOUSE_BTN_* which max is 6. You don't really need this helpers and just use plain bit operations.
If you define MOUSE_BTN_* as BIT(0)..BIT(6), you don't need extra layer at all.

Quoted 14 lines
drivers/hid/hid-logitech-hidpp.c +14 -0
+}
+
+static inline int get_btn_bit(u8 *data, int bit)
+{
+       int bytenr = bit / 8;
+       int bitmask = 1 << (bit & 0x07);
+
+       return !!(data[bytenr] & bitmask);
+}
+
+static inline void clear_btn_bit(u8 *data, int bit)
+{
+       int bytenr = bit / 8;
+       int bitmask = 1 << (bit & 0x07);

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1062

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+
+       data[bytenr] &= ~bitmask;
+}
+
+static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+       struct m560_private_data *mydata = m560_priv_data(hdev);
+
+       /* check if the data is a mouse related report */
+       if (data[0] != 0x02 && data[2] != 0x0a)

Please no magical numbers. You should have these defined somewhere (OK, I did not define 0x02 myself, but you should!)

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1066

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
+               return 1;
+
+       /* check if the report is the ack of the config_command */
+       if (data[0] == 0x11 && data[2] == 0x0a &&

0x11 is REPORT_ID_HIDPP_LONG

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1070

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
+           size >= (3+sizeof(m560_config_command)) &&
+           !memcmp(data+3, m560_config_command,
+               sizeof(m560_config_command))) {
+                       return true;

this whole test is a lot messy. There should be a way to clean it a little (maybe just define what you expect, and use one memcmp).

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+       }
+
+       if (data[0] == 0x11 && data[2] == 0x0a && data[06] == 0x00) {
+               /*
+                * m560 mouse button report
+                *
+                * data[0] = 0x11
+                * data[1] = deviceid
+                * data[2] = 0x0a
+                * data[5] = button (0xaf->middle, 0xb0->forward,

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1090

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+                *                   0xaf ->backward, 0x00->release all)
+                * data[6] = 0x00
+                */
+
+               int btn, i, maxsize;
+
+               /* check if the event is a button */
+               btn = data[5];
+               if (btn != 0x00 && btn != 0xb0 && btn != 0xae && btn != 0xaf)
+                       return true;

I wouldn't be surprised if these buttons where just bitmasks. Do you still get the same values if you press 2/3 buttons at the same time?

0xa0 seems common to all these bytes (0b10100000). BIT(0) seems to be btn_middle
BIT(4) seems to be btn_forward

but btn_backward does not make any sense. Nestor, can you tell us what these magic packets are?

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1102

Quoted 12 lines
drivers/hid/hid-logitech-hidpp.c +12 -0
+
+               if (btn == 0xaf)
+                       mydata->btn_middle = 1;
+               else if (btn == 0xb0)
+                       mydata->btn_forward = 1;
+               else if (btn == 0xae)
+                       mydata->btn_backward = 1;
+               else if (btn == 0x00) {
+                       mydata->btn_backward = 0;
+                       mydata->btn_forward = 0;
+                       mydata->btn_middle = 0;
+               }

As mentioned above, this looks fragile if 2 buttons are pressed at the same time, the first one will be spuriously released.

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1110

Quoted 8 lines
drivers/hid/hid-logitech-hidpp.c +8 -0
+
+               /* replace the report with the old one */
+               if (size > sizeof(mydata->prev_data))
+                       maxsize = sizeof(mydata->prev_data);
+               else
+                       maxsize = size;
+               for (i = 0 ; i < maxsize ; i++)
+                       data[i] = mydata->prev_data[i];

I think here and later, you are making things too complicated. Can't you just emit the buttons when retrieving them with input_event(), and call input_sync() now and drop the whole rewriting of HID report which just add some overhead and complexity.

Quoted 7 lines
drivers/hid/hid-logitech-hidpp.c +7 -0
+
+       } else if (data[0] == 0x02) {
+               /*
+                * standard mouse report
+                *
+                * data[0] = type (0x02)
+                * data[1..2] = buttons

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1127

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+                * data[3..5] = xy
+                * data[6] = wheel
+                * data[7] = horizontal wheel
+                */
+
+               /* horizontal wheel handling */
+               if (get_btn_bit(data+1,MOUSE_BTN_WHEEL_LEFT))
+                       data[1+6] = -1;
+               if (get_btn_bit(data+1,MOUSE_BTN_WHEEL_RIGHT))
+                       data[1+6] =  1;

Do we really need to clear those buttons? Xorg should be able to remap them to BTN_HORIZ_WHEEL or so.

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1133

Quoted 6 lines
drivers/hid/hid-logitech-hidpp.c +6 -0
+
+               clear_btn_bit(data+1, MOUSE_BTN_WHEEL_LEFT);
+               clear_btn_bit(data+1, MOUSE_BTN_WHEEL_RIGHT);
+
+               /* copy the type and buttons status */
+               memcpy(mydata->prev_data, data, 3);

This can be dropped if you directly call input_event in the previous case.

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1142

Quoted 9 lines
drivers/hid/hid-logitech-hidpp.c +9 -0
+       }
+
+       /* add the extra buttons */
+       if (mydata->btn_middle)
+               set_btn_bit(data+1, MOUSE_BTN_MIDDLE);
+       if (mydata->btn_forward)
+               set_btn_bit(data+1, MOUSE_BTN_FORWARD);
+       if (mydata->btn_backward)
+               set_btn_bit(data+1, MOUSE_BTN_BACKWARD);

This is somewhat disturbing. I think it is fine, but an other solution would be to simply ignore the mapping of those buttons in input_mapping.

Quoted 46 lines
drivers/hid/hid-logitech-hidpp.c +14 -0
+
+       return 1;
+}
+
+/* -------------------------------------------------------------------------- */
 /* Generic HID++ devices                                                      */
 /* -------------------------------------------------------------------------- */
 
@@ -936,6 +1156,9 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_input_mapping(hdev, hi, field, usage, bit, max);
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
+               field->application != HID_GD_MOUSE)
+                               return -1;
 
        return 0;
 }
@@ -998,6 +1221,8 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_raw_event(hidpp->hid_dev, data, size);
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+               return m560_raw_event(hidpp->hid_dev, data, size);
 
        return 0;
 }
@@ -1026,6 +1251,8 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_raw_event(hdev, data, size);
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+               return m560_raw_event(hidpp->hid_dev, data, size);
 
        return 0;
 }
@@ -1100,6 +1327,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                wtp_connect(hdev, connected);
+       if ((hidpp->quirks & HIDPP_QUIRK_CLASS_M560) && connected)
+               m560_send_config_command(hdev);
 
        if (!connected || hidpp->delayed_input)
                return;

drivers/hid/hid-logitech-hidpp.c static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) · line 1399

Quoted 7 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
@@ -1164,6 +1393,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
                if (ret)
                        goto wtp_allocate_fail;
        }
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+               ret = m560_allocate(hdev);
+               if (ret)
+                       goto wtp_allocate_fail;

This looks weird. we should probably rename the label to "class_allocate_fail" or something similar.

drivers/hid/hid-logitech-hidpp.c static void hidpp_remove(struct hid_device *hdev) · line 1477

Quoted 9 lines
drivers/hid/hid-logitech-hidpp.c +2 -0
+       }
 
        INIT_WORK(&hidpp->work, delayed_work_cb);
        mutex_init(&hidpp->send_mutex);
@@ -1240,6 +1474,7 @@ static void hidpp_remove(struct hid_device *hdev)
        cancel_work_sync(&hidpp->work);
        mutex_destroy(&hidpp->send_mutex);
        hid_hw_stop(hdev);
+

Please no extra line out of context.

Quoted 8 lines
drivers/hid/hid-logitech-hidpp.c +0 -0
 }
 
 static const struct hid_device_id hidpp_devices[] = {
@@ -1261,6 +1496,12 @@ static const struct hid_device_id hidpp_devices[] = {
                USB_VENDOR_ID_LOGITECH, 0x4102),
          .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_MULTI_INPUT |

drivers/hid/hid-logitech-hidpp.c static const struct hid_device_id hidpp_devices[] = { · line 1507

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +6 -0
                         HIDPP_QUIRK_CLASS_WTP },
+       { /* Mouse logitech M560 */
+         HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+               USB_VENDOR_ID_LOGITECH, 0x402d),
+         .driver_data = HIDPP_QUIRK_CLASS_M560 | HIDPP_QUIRK_DELAYED_INIT |
+                        HIDPP_QUIRK_MULTI_INPUT
+       },
 
        { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
                USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},

One last comment. Please check the patch against ./script/checkpatch.pl. There are here and ther some whitespace problems that checkpatch will raise.

Thanks for the submission.

Cheers,
Benjamin

Quoted 61 lines · Benjamin Tissoires
Quoted 25 lines

Hi Antonio,

Quoted 12 lines · Antonio Ospite
Quoted 9 lines · On Fri, 05 Sep 2014 19:47:44 +0200 Goffredo Baroncelli <[email protected]>…
Quoted 8 lines · Benjamin Tissoires

Hi Goffredo,

Quoted 4 lines · Goffredo Baroncelli

Hi Benjamin,

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Quoted 7 lines
Quoted 5 lines
Quoted 4 lines

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

Hi Benjamin, any news about upstreaming this work?

The hidpp / raw touchpad mode is already upstream since the v3.19 kernel. \o/

Quoted 29 lines

We just need to port Goffredo's changes now and push them too. There has been some differences since this branch was published. The main is that there is no more special subdrivers in several files, everything is handled in hidpp. I think the logic behind is somewhat the same so it should not be too much a problem to do the work.

Cheers,
Benjamin

Quoted 19 lines
Quoted 4 lines

:-) For me this is not a problem. I solved my issue (I made a dkms package for this mouse), but I want to share my effort..

Goffredo, is this the latest version of your dkms-enabled external module?
https://github.com/kreijack/hid-logitech-dj/tree/m560-dkms

Thanks,
Antonio

--
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

--
gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it> Key fingerprint BBF5 1610 0B64 DAC6 5F7D 17B2 0EDA 9B37 8B82 E0B5

#9 lore raw
Quoted 52 lines · Benjamin Tissoires
Quoted 3 lines · Goffredo Baroncelli

Hi,

after the Antonio's mail, I updated my work on the latest kernel (3.19.3); this work for me, but I am sure that I made some mistakes, so please consider this a beta.

Thanks for the patch. I have some comments, please see inline.

Quoted 5 lines

I added a new device class (M560), and I put some hooks to process the raw data.

I used the following "quirks":
- HIDPP_QUIRK_DELAYED_INIT
- HIDPP_QUIRK_MULTI_INPUT

I don't think you really need HIDPP_QUIRK_MULTI_INPUT. If the mouse has only one input node (or you want to send only on the input node), then you don't need to keep the keyboard node.

[10 min later]

OK, after reviewing the code, I see why you kept HIDPP_QUIRK_MULTI_INPUT. I am not entirely sure what is the best
solution: keeping it and rely on hid-input for the parsing/allocating of regular mouse events and having .raw_event() tampering with the raw report, or just trash hid-input and process entirely the incoming report...

Quoted 5 lines

these work (when the mouse is connected, the configuration message is sent), but I am not sure if this is correct.
Anyway in the past I noticed some false spurious reconnections, and re-sending the configuration time to time made the mouse not-responsive for few tens of second (it seems a low value, but I noticed it); so I am not sure if I will leave the "reconfiguration" on reconnection.

Comments are welcome.

Please for the next submission do a proper one with a commit message and a signed-of-by line. This way, we can integrate it when the code looks clearer or someone else can take over the job if you decide not to port it upstream.

Quoted 12 lines

BR
G.Baroncelli

drivers/hid/hid-logitech-hidpp.c +0 -0
index a93cefe..4d907d6 100644
@@ -35,6 +35,7 @@ MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>");
 #define HIDPP_REPORT_LONG_LENGTH               20
 
Quoted 6 lines

drivers/hid/hid-logitech-hidpp.c MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>"); · line 40

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +1 -0
 #define HIDPP_QUIRK_CLASS_WTP                  BIT(0)
+#define HIDPP_QUIRK_CLASS_M560                 BIT(1)
 
 /* bits 1..20 are reserved for classes */

Technically speaking, this should be changed to "2..20" :)

ok

Quoted 41 lines
Quoted 28 lines
drivers/hid/hid-logitech-hidpp.c +22 -0
 #define HIDPP_QUIRK_DELAYED_INIT               BIT(21)
@@ -925,6 +926,225 @@ static void wtp_connect(struct hid_device *hdev, bool connected)
 }
 
 /* -------------------------------------------------------------------------- */
+/* Logitech M560 devices                                                     */
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate further the things, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 960

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+ *     10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never send a keyboard key event
+ * - for the three mouse button it sends:
+ *     middle button               press   11<xx>0a 3500af00...
+ *     side 1 button (forward)     press   11<xx>0a 3500b000...
+ *     side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *     middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+static u8 m560_config_command[] = {0x35, 0x00, 0xaf, 0x03};

please mark this one as const.

Quoted 5 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 963

Quoted 3 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+
+struct m560_private_data {
+       u8 prev_data[30]; // FIXME: select a right size

Don't insert FIXME, fix it before the integration :)

ok

Quoted 7 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 966

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+       int btn_middle:1;
+       int btn_forward:1;
+       int btn_backward:1;

using a int per button seems a lot of lost space. Maybe you can use a bitmask or at least bool (which remaps to an unsigned char IIRC).

ok

Quoted 13 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 976

Quoted 11 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+};
+
+/* how the button are mapped in the report */
+#define MOUSE_BTN_LEFT         0
+#define MOUSE_BTN_RIGHT                1
+#define MOUSE_BTN_MIDDLE       2
+#define MOUSE_BTN_WHEEL_LEFT   3
+#define MOUSE_BTN_WHEEL_RIGHT  4
+#define MOUSE_BTN_FORWARD      5
+#define MOUSE_BTN_BACKWARD     6

Please prefix those defines by M560.

ok

Quoted 10 lines
Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+
+/*
+ * m560_priv_data - helper to convert from hidpp_device to m560_private_data
+ *
+ * @hdev: hid device
+ *
+ * @return: return m560_private_data if available, NULL otherwise
+ */
+static inline struct m560_private_data *m560_priv_data(struct hid_device *hdev)
+{
Quoted 8 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 989

Quoted 3 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+       struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+       return hidpp_dev ? hidpp_dev->private_data : NULL;
+}

Are you sure we really need this check for M560 mice? if you are calling this in the M560 protocol handling only and made sure in the probe that private_data gets allocated, no need to double check things.

I will remove, because there was used only one time

Quoted 13 lines
Quoted 13 lines
drivers/hid/hid-logitech-hidpp.c +13 -0
+
+/*
+ * m560_send_config_command - send the config_command to the mouse
+ *
+ * @dev: hid device where the mouse belongs
+ *
+ * @return: 0 OK
+ */
+static int m560_send_config_command(struct hid_device *hdev) {
+       struct hidpp_report response;
+       struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+       int ret;
+
Quoted 8 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1006

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
+       ret = hidpp_send_rap_command_sync(
+               hidpp_dev,
+               REPORT_ID_HIDPP_SHORT,
+               0x0a,

Please use a define for this sub id.
BTW, are you sure the mouse is HID++ 1.0? If it is 2.0, you have to use the fap protocol, not the rap.

I don't know :-) ***************

Quoted 8 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1009

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+               m560_config_command[0],
+               m560_config_command+1,
+               sizeof(m560_config_command)-1,

I would personally define m560_config_command[0] in a separate define (M560_BUTTON_MODE_REGISTER for instance) and keep the parameters to the right size right now.

I rearranged a bit the code

Quoted 13 lines
Quoted 13 lines
drivers/hid/hid-logitech-hidpp.c +13 -0
+               &response
+       );
+
+       return ret;
+}
+
+static int m560_allocate(struct hid_device *hdev)
+{
+       struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+       struct m560_private_data *d;
+
+       d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
+                       GFP_KERNEL);
Quoted 8 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1028

Quoted 6 lines
drivers/hid/hid-logitech-hidpp.c +6 -0
+       if (!d)
+               return -ENOMEM;
+
+       hidpp->private_data = d;
+       //d->hidpp_dev = hidpp;
+       //d->hid_dev = hdev;

No leftover debug comments please.

ok

Quoted 7 lines
Quoted 7 lines
drivers/hid/hid-logitech-hidpp.c +7 -0
+
+       return 0;
+};
+
+static inline void set_btn_bit(u8 *data, int bit)
+{
+       int bytenr = bit / 8;
Quoted 8 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1038

Quoted 3 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+       int bitmask = 1 << (bit & 0x07);
+
+       data[bytenr] |= bitmask;

You seem to be using {set|get|clear}_btn_bit only with MOUSE_BTN_* which max is 6. You don't really need this helpers and just use plain bit operations.
If you define MOUSE_BTN_* as BIT(0)..BIT(6), you don't need extra layer at all.

ok

Quoted 29 lines
Quoted 15 lines
drivers/hid/hid-logitech-hidpp.c +14 -0
+}
+
+static inline int get_btn_bit(u8 *data, int bit)
+{
+       int bytenr = bit / 8;
+       int bitmask = 1 << (bit & 0x07);
+
+       return !!(data[bytenr] & bitmask);
+}
+
+static inline void clear_btn_bit(u8 *data, int bit)
+{
+       int bytenr = bit / 8;
+       int bitmask = 1 << (bit & 0x07);

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1062

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+
+       data[bytenr] &= ~bitmask;
+}
+
+static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+       struct m560_private_data *mydata = m560_priv_data(hdev);
+
+       /* check if the data is a mouse related report */
+       if (data[0] != 0x02 && data[2] != 0x0a)

Please no magical numbers. You should have these defined somewhere (OK, I did not define 0x02 myself, but you should!)

Quoted 6 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1066

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
+               return 1;
+
+       /* check if the report is the ack of the config_command */
+       if (data[0] == 0x11 && data[2] == 0x0a &&

0x11 is REPORT_ID_HIDPP_LONG

ok

Quoted 8 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1070

Quoted 5 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
+           size >= (3+sizeof(m560_config_command)) &&
+           !memcmp(data+3, m560_config_command,
+               sizeof(m560_config_command))) {
+                       return true;

this whole test is a lot messy. There should be a way to clean it a little (maybe just define what you expect, and use one memcmp).

I removed this check, because the configure command is send in another way

Quoted 44 lines
Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+       }
+
+       if (data[0] == 0x11 && data[2] == 0x0a && data[06] == 0x00) {
+               /*
+                * m560 mouse button report
+                *
+                * data[0] = 0x11
+                * data[1] = deviceid
+                * data[2] = 0x0a
+                * data[5] = button (0xaf->middle, 0xb0->forward,

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1090

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+                *                   0xaf ->backward, 0x00->release all)
+                * data[6] = 0x00
+                */
+
+               int btn, i, maxsize;
+
+               /* check if the event is a button */
+               btn = data[5];
+               if (btn != 0x00 && btn != 0xb0 && btn != 0xae && btn != 0xaf)
+                       return true;

I wouldn't be surprised if these buttons where just bitmasks. Do you still get the same values if you press 2/3 buttons at the same time?

0xa0 seems common to all these bytes (0b10100000). BIT(0) seems to be btn_middle
BIT(4) seems to be btn_forward

but btn_backward does not make any sense. Nestor, can you tell us what these magic packets are?

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1102

Quoted 12 lines
drivers/hid/hid-logitech-hidpp.c +12 -0
+
+               if (btn == 0xaf)
+                       mydata->btn_middle = 1;
+               else if (btn == 0xb0)
+                       mydata->btn_forward = 1;
+               else if (btn == 0xae)
+                       mydata->btn_backward = 1;
+               else if (btn == 0x00) {
+                       mydata->btn_backward = 0;
+                       mydata->btn_forward = 0;
+                       mydata->btn_middle = 0;
+               }
Quoted 2 lines

As mentioned above, this looks fragile if 2 buttons are pressed at the same time, the first one will be spuriously released.

Unfortunately, when the two button are released the same bytes sequence is emitted :-( There is no way to distinguish the three release events

Quoted 10 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1110

Quoted 9 lines
drivers/hid/hid-logitech-hidpp.c +8 -0
+
+               /* replace the report with the old one */
+               if (size > sizeof(mydata->prev_data))
+                       maxsize = sizeof(mydata->prev_data);
+               else
+                       maxsize = size;
+               for (i = 0 ; i < maxsize ; i++)
+                       data[i] = mydata->prev_data[i];
Quoted 4 lines

I think here and later, you are making things too complicated. Can't you just emit the buttons when retrieving them with input_event(), and call input_sync() now and drop the whole rewriting of HID report which just add some overhead and complexity.

I need more input, because I am not familiar with input_event()/input_sync(): I tried to replace the changing of the report with something like:

	if (btn == 0xaf) {
		input_event(mydata->input, EV_KEY, BTN_MIDDLE, 1);
		input_sync(mydata->input);
		return 0;
	}

But the result was that I never got a middle-button event... I missing something.

Quoted 13 lines
Quoted 13 lines
drivers/hid/hid-logitech-hidpp.c +12 -0
+
+       } else if (data[0] == 0x02) {
+               /*
+                * standard mouse report
+                *
+                * data[0] = type (0x02)
+                * data[1..2] = buttons
+                * data[3..5] = xy
+                * data[6] = wheel
+                * data[7] = horizontal wheel
+                */
+
Quoted 8 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1127

Quoted 5 lines
drivers/hid/hid-logitech-hidpp.c +5 -0
+               /* horizontal wheel handling */
+               if (get_btn_bit(data+1,MOUSE_BTN_WHEEL_LEFT))
+                       data[1+6] = -1;
+               if (get_btn_bit(data+1,MOUSE_BTN_WHEEL_RIGHT))
+                       data[1+6] =  1;

Do we really need to clear those buttons? Xorg should be able to remap them to BTN_HORIZ_WHEEL or so.

Maybe, but so it works out of the box without xmodmap ...

Quoted 67 lines

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1133

Quoted 7 lines
drivers/hid/hid-logitech-hidpp.c +6 -0
+
+               clear_btn_bit(data+1, MOUSE_BTN_WHEEL_LEFT);
+               clear_btn_bit(data+1, MOUSE_BTN_WHEEL_RIGHT);
+
+               /* copy the type and buttons status */
+               memcpy(mydata->prev_data, data, 3);

This can be dropped if you directly call input_event in the previous case.

drivers/hid/hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1142

Quoted 9 lines
drivers/hid/hid-logitech-hidpp.c +9 -0
+       }
+
+       /* add the extra buttons */
+       if (mydata->btn_middle)
+               set_btn_bit(data+1, MOUSE_BTN_MIDDLE);
+       if (mydata->btn_forward)
+               set_btn_bit(data+1, MOUSE_BTN_FORWARD);
+       if (mydata->btn_backward)
+               set_btn_bit(data+1, MOUSE_BTN_BACKWARD);

This is somewhat disturbing. I think it is fine, but an other solution would be to simply ignore the mapping of those buttons in input_mapping.

Quoted 43 lines
drivers/hid/hid-logitech-hidpp.c +14 -0
+
+       return 1;
+}
+
+/* -------------------------------------------------------------------------- */
 /* Generic HID++ devices                                                      */
 /* -------------------------------------------------------------------------- */
 
@@ -936,6 +1156,9 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_input_mapping(hdev, hi, field, usage, bit, max);
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
+               field->application != HID_GD_MOUSE)
+                               return -1;
 
        return 0;
 }
@@ -998,6 +1221,8 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_raw_event(hidpp->hid_dev, data, size);
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+               return m560_raw_event(hidpp->hid_dev, data, size);
 
        return 0;
 }
@@ -1026,6 +1251,8 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_raw_event(hdev, data, size);
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+               return m560_raw_event(hidpp->hid_dev, data, size);
 
        return 0;
 }
@@ -1100,6 +1327,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                wtp_connect(hdev, connected);
+       if ((hidpp->quirks & HIDPP_QUIRK_CLASS_M560) && connected)
+               m560_send_config_command(hdev);
 
Quoted 13 lines

drivers/hid/hid-logitech-hidpp.c static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) · line 1399

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
        if (!connected || hidpp->delayed_input)
                return;
@@ -1164,6 +1393,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
                if (ret)
                        goto wtp_allocate_fail;
        }
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+               ret = m560_allocate(hdev);
+               if (ret)
+                       goto wtp_allocate_fail;

This looks weird. we should probably rename the label to "class_allocate_fail" or something similar.

ok

Quoted 12 lines

drivers/hid/hid-logitech-hidpp.c static void hidpp_remove(struct hid_device *hdev) · line 1477

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +2 -0
+       }
 
        INIT_WORK(&hidpp->work, delayed_work_cb);
        mutex_init(&hidpp->send_mutex);
@@ -1240,6 +1474,7 @@ static void hidpp_remove(struct hid_device *hdev)
        cancel_work_sync(&hidpp->work);
        mutex_destroy(&hidpp->send_mutex);
        hid_hw_stop(hdev);
+

Please no extra line out of context.

ok

Quoted 14 lines
Quoted 14 lines
drivers/hid/hid-logitech-hidpp.c +6 -0
 }
 
 static const struct hid_device_id hidpp_devices[] = {
@@ -1261,6 +1496,12 @@ static const struct hid_device_id hidpp_devices[] = {
                USB_VENDOR_ID_LOGITECH, 0x4102),
          .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_MULTI_INPUT |
                         HIDPP_QUIRK_CLASS_WTP },
+       { /* Mouse logitech M560 */
+         HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+               USB_VENDOR_ID_LOGITECH, 0x402d),
+         .driver_data = HIDPP_QUIRK_CLASS_M560 | HIDPP_QUIRK_DELAYED_INIT |
+                        HIDPP_QUIRK_MULTI_INPUT
+       },
 
Quoted 8 lines

drivers/hid/hid-logitech-hidpp.c static const struct hid_device_id hidpp_devices[] = { · line 1507

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +0 -0
        { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
                USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},

One last comment. Please check the patch against ./script/checkpatch.pl. There are here and ther some whitespace problems that checkpatch will raise.

Ok

Quoted 68 lines

Thanks for the submission.

Cheers,
Benjamin

Quoted 61 lines · Benjamin Tissoires
Quoted 25 lines

Hi Antonio,

Quoted 12 lines · Antonio Ospite
Quoted 9 lines · On Fri, 05 Sep 2014 19:47:44 +0200 Goffredo Baroncelli <[email protected]>…
Quoted 8 lines · Benjamin Tissoires

Hi Goffredo,

Quoted 4 lines · Goffredo Baroncelli

Hi Benjamin,

following the Nestor suggestion, I rewrote the driver for the mouse Logitech M560 on the basis of your work (branch "for-whot").

Quoted 7 lines
Quoted 5 lines
Quoted 4 lines

Just for the record. This branch is located here:
https://github.com/bentiss/hid-logitech-dj/tree/for-whot and I *really* need to finish this work so that everything can go upstream :(

Hi Benjamin, any news about upstreaming this work?

The hidpp / raw touchpad mode is already upstream since the v3.19 kernel. \o/

Quoted 29 lines

We just need to port Goffredo's changes now and push them too. There has been some differences since this branch was published. The main is that there is no more special subdrivers in several files, everything is handled in hidpp. I think the logic behind is somewhat the same so it should not be too much a problem to do the work.

Cheers,
Benjamin

Quoted 19 lines
Quoted 4 lines

:-) For me this is not a problem. I solved my issue (I made a dkms package for this mouse), but I want to share my effort..

Goffredo, is this the latest version of your dkms-enabled external module?
https://github.com/kreijack/hid-logitech-dj/tree/m560-dkms

Thanks,
Antonio

--
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

--
gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it> Key fingerprint BBF5 1610 0B64 DAC6 5F7D 17B2 0EDA 9B37 8B82 E0B5

Signature · 2 lines
gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it>
Key fingerprint BBF5 1610 0B64 DAC6 5F7D  17B2 0EDA 9B37 8B82 E0B5
#10 lore raw

PATCH v2 1/1 Driver for Logitech M560

In reply to Benjamin Tissoires

Hi Benjamin,

I accepted near all your suggestions; the one which I leaved out is the one related to using input_event()/input_sync() instead of rewriting the hid report. I am not against to it, but... I don't know enough the hid stack, so I need some more suggestions from your side.

In the meantime, I updated the patch on the basis of your suggestions.

BR
G.Baroncelli

Signature · 35 lines
gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it>
Key fingerprint BBF5 1610 0B64 DAC6 5F7D  17B2 0EDA 9B37 8B82 E0B5

----

The Logitech M560 is is a mouse designed for windows 8. 
Comparing to a standard one, some buttons (the middle one and the 
two ones placed on the side) are bounded to a key combination 
instead of a classic "mouse" button.

Think this mouse as a pair of mouse and keyboard. When the middle
button is pressed the it sends a key (as keyboard) 
combination, the same for the other two side button.
Instead the left/right/wheel work correctly.
To complicate further the things, the middle button send a
key combination the odd press, and another one for the even press;
in the latter case it sends also a left click. But the worst thing 
is that no event is generated when the middle button is released.

Moreover this device is a wireless mouse which uses the unifying 
receiver.

I discovered that it is possible to re-configure the mouse
sending a command (see function m560_send_config_command()).
After this command the mouse sends some sequence when the
buttons are pressed and/or released (see comments for
an explanation of the mouse protocol).

This patch update the file driver/hid/hid-logitech-hidpp.c (v3.19.3)

Signed-off-by: Goffredo Baroncelli <[email protected]>

Changelog:
- v1 first issue
- v2 accepted Benjamin Tissoires suggestions
hid-logitech-hidpp.c +195 -4
index a93cefe..805ecb6 100644
@@ -35,8 +35,9 @@ MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>");
 #define HIDPP_REPORT_LONG_LENGTH		20
 
 #define HIDPP_QUIRK_CLASS_WTP			BIT(0)
+#define HIDPP_QUIRK_CLASS_M560			BIT(1)
 
-/* bits 1..20 are reserved for classes */
+/* bits 2..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)
@@ -925,6 +926,177 @@ static void wtp_connect(struct hid_device *hdev, bool connected)
 }
 
 /* -------------------------------------------------------------------------- */
+/* Logitech M560 devices                                                     */
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate further the things, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command
+ *	10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never send a keyboard key event
+ * - for the three mouse button it sends:
+ *	middle button               press   11<xx>0a 3500af00...
+ *	side 1 button (forward)     press   11<xx>0a 3500b000...
+ *	side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *	middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+
+static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03};
+
+struct m560_private_data {
+	u8 prev_data[10];
+	u8 button_pressed;
+};
+
+/* how the button are mapped in the report */
+#define M560_MOUSE_BTN_LEFT		0x01
+#define M560_MOUSE_BTN_RIGHT		0x02
+#define M560_MOUSE_BTN_MIDDLE		0x04
+#define M560_MOUSE_BTN_WHEEL_LEFT	0x08
+#define M560_MOUSE_BTN_WHEEL_RIGHT	0x10
+#define M560_MOUSE_BTN_FORWARD		0x20
+#define M560_MOUSE_BTN_BACKWARD		0x40
+#define M560_SUB_ID			0x0a
+#define M560_BUTTON_MODE_REGISTER	0x35
+
+/*
+ * m560_send_config_command - send the config_command to the mouse
+ *
+ * @dev: hid device where the mouse belongs
+ *
+ * @return: 0 OK
+ */
+static int m560_send_config_command(struct hid_device *hdev)
+{
+	struct hidpp_report response;
+	struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+	int ret;
+
+	ret = hidpp_send_rap_command_sync(
+		hidpp_dev,
+		REPORT_ID_HIDPP_SHORT,
+		M560_SUB_ID,
+		M560_BUTTON_MODE_REGISTER,
+		(u8 *)m560_config_parameter,
+		sizeof(m560_config_parameter),
+		&response
+	);
+
+	return ret;
+}
+
+static int m560_allocate(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *d;
+
+	d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
+			GFP_KERNEL);
+	if (!d)
+		return -ENOMEM;
+
+	hidpp->private_data = d;
+
+	return 0;
+};
+
+static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *mydata = hidpp->private_data;
+	u8 *btn_ptr = data+1;
+	u8 *wheel_ptr = data+1+6;
+
+	/* check if the data is a mouse related report */
+	if (data[0] != 0x02 && data[2] != M560_SUB_ID)
+		return 1;
+
+	if (data[0] == REPORT_ID_HIDPP_LONG &&
+	    data[2] == M560_SUB_ID && data[06] == 0x00) {
+		/*
+		 * m560 mouse button report
+		 *
+		 * data[0] = 0x11
+		 * data[1] = deviceid
+		 * data[2] = 0x0a
+		 * data[5] = button (0xaf->middle, 0xb0->forward,
+		 *                   0xaf ->backward, 0x00->release all)
+		 * data[6] = 0x00
+		 */
+
+		int btn, i, maxsize;
+
+		/* check if the event is a button */
+		btn = data[5];
+		if (btn != 0x00 && btn != 0xb0 && btn != 0xae && btn != 0xaf)
+			return 1;
+
+		if (btn == 0xaf)
+			mydata->button_pressed |= M560_MOUSE_BTN_MIDDLE;
+		else if (btn == 0xb0)
+			mydata->button_pressed |= M560_MOUSE_BTN_FORWARD;
+		else if (btn == 0xae)
+			mydata->button_pressed |= M560_MOUSE_BTN_BACKWARD;
+		else if (btn == 0x00)
+			mydata->button_pressed &= ~(M560_MOUSE_BTN_BACKWARD|
+				M560_MOUSE_BTN_MIDDLE|M560_MOUSE_BTN_FORWARD);
+
+		/* replace the report with the old one */
+		if (size > sizeof(mydata->prev_data))
+			maxsize = sizeof(mydata->prev_data);
+		else
+			maxsize = size;
+		for (i = 0 ; i < maxsize ; i++)
+			data[i] = mydata->prev_data[i];
+
+	} else if (data[0] == 0x02) {
+		/*
+		 * standard mouse report
+		 *
+		 * data[0] = type (0x02)
+		 * data[1..2] = buttons
+		 * data[3..5] = xy
+		 * data[6] = wheel
+		 * data[7] = horizontal wheel
+		 */
+
+		/* horizontal wheel handling */
+		if (*btn_ptr & M560_MOUSE_BTN_WHEEL_LEFT)
+			*wheel_ptr = -1;
+		if (*btn_ptr & M560_MOUSE_BTN_WHEEL_RIGHT)
+			*wheel_ptr =  1;
+
+		*btn_ptr &= ~(M560_MOUSE_BTN_WHEEL_LEFT|
+				M560_MOUSE_BTN_WHEEL_RIGHT);
+
+		/* copy the type and buttons status */
+		memcpy(mydata->prev_data, data, 3);
+	}
+
+	/* add the extra buttons */
+	*btn_ptr |= mydata->button_pressed;
+
+	return 1;
+}
+
+/* -------------------------------------------------------------------------- */
 /* Generic HID++ devices                                                      */
 /* -------------------------------------------------------------------------- */
 
@@ -936,6 +1108,9 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_input_mapping(hdev, hi, field, usage, bit, max);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
+		 field->application != HID_GD_MOUSE)
+			return -1;
 
 	return 0;
 }
@@ -998,6 +1173,8 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_raw_event(hidpp->hid_dev, data, size);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		return m560_raw_event(hidpp->hid_dev, data, size);
 
 	return 0;
 }
@@ -1026,7 +1203,8 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_raw_event(hdev, data, size);
-
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		return m560_raw_event(hidpp->hid_dev, data, size);
 	return 0;
 }
 
@@ -1100,6 +1278,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		wtp_connect(hdev, connected);
+	if ((hidpp->quirks & HIDPP_QUIRK_CLASS_M560) && connected)
+		m560_send_config_command(hdev);
 
 	if (!connected || hidpp->delayed_input)
 		return;
@@ -1162,7 +1342,12 @@ 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)
-			goto wtp_allocate_fail;
+			goto allocate_fail;
+	}
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+		ret = m560_allocate(hdev);
+		if (ret)
+			goto allocate_fail;
 	}
 
 	INIT_WORK(&hidpp->work, delayed_work_cb);
@@ -1228,7 +1413,7 @@ hid_hw_start_fail:
 hid_parse_fail:
 	cancel_work_sync(&hidpp->work);
 	mutex_destroy(&hidpp->send_mutex);
-wtp_allocate_fail:
+allocate_fail:
 	hid_set_drvdata(hdev, NULL);
 	return ret;
 }
@@ -1261,6 +1446,12 @@ static const struct hid_device_id hidpp_devices[] = {
 		USB_VENDOR_ID_LOGITECH, 0x4102),
 	  .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_MULTI_INPUT |
 			 HIDPP_QUIRK_CLASS_WTP },
+	{ /* Mouse logitech M560 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x402d),
+	  .driver_data = HIDPP_QUIRK_CLASS_M560 | HIDPP_QUIRK_DELAYED_INIT |
+			 HIDPP_QUIRK_MULTI_INPUT
+	},
 
 	{ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
 		USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
#11 lore raw
Quoted 10 lines · On Mon, 13 Apr 2015 20:24:05 +0200 Goffredo Baroncelli <[email protected]>…

Hi Benjamin,

I accepted near all your suggestions; the one which I leaved out is the one related to using input_event()/input_sync() instead of rewriting the hid report. I am not against to it, but... I don't know enough the hid stack, so I need some more suggestions from your side.

In the meantime, I updated the patch on the basis of your suggestions.

I am adding just some syntactic remarks below, I haven't tried or fully understood the code yet; just things to take in mind for when you are going to submit the next iteration of the patch.

In the short commit message (Subject) use the subsystem/driver prefix (use git log to figure it out), in this case it's something like:

HID: logitech-hidpp: add support for Logitech M560

The introduction/annotation goes after the '---' marker; it is the commit message which goes at the beginning of the email body so that "git am" can pick it up and ignore the annotations.
Check out Documentations/SubmittingPatches.

If possible use "git format-patch" and "git send-email" to submit kernel patches.

Quoted 34 lines

BR
G.Baroncelli

Signature · 29 lines
gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it>
Key fingerprint BBF5 1610 0B64 DAC6 5F7D  17B2 0EDA 9B37 8B82 E0B5

----

The Logitech M560 is is a mouse designed for windows 8. 
Comparing to a standard one, some buttons (the middle one and the 
two ones placed on the side) are bounded to a key combination 
instead of a classic "mouse" button.

Think this mouse as a pair of mouse and keyboard. When the middle
button is pressed the it sends a key (as keyboard) 
combination, the same for the other two side button.
Instead the left/right/wheel work correctly.
To complicate further the things, the middle button send a
key combination the odd press, and another one for the even press;
in the latter case it sends also a left click. But the worst thing 
is that no event is generated when the middle button is released.

Moreover this device is a wireless mouse which uses the unifying 
receiver.

I discovered that it is possible to re-configure the mouse
sending a command (see function m560_send_config_command()).
After this command the mouse sends some sequence when the
buttons are pressed and/or released (see comments for
an explanation of the mouse protocol).

This patch update the file driver/hid/hid-logitech-hidpp.c (v3.19.3)

Comments like this are OK for the annotation but they are not that useful in the commit message.

Quoted 7 lines

Changelog:
- v1 first issue
- v2 accepted Benjamin Tissoires suggestions

Patch history goes in the annotation section.

hid-logitech-hidpp.c

Quoted 1 line
hid-logitech-hidpp.c +0 -0

Submit patches against the full kernel tree.

Quoted 80 lines

index a93cefe..805ecb6 100644

hid-logitech-hidpp.c +64 -1
@@ -35,8 +35,9 @@ MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>");
 #define HIDPP_REPORT_LONG_LENGTH		20
 
 #define HIDPP_QUIRK_CLASS_WTP			BIT(0)
+#define HIDPP_QUIRK_CLASS_M560			BIT(1)
 
-/* bits 1..20 are reserved for classes */
+/* bits 2..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)
@@ -925,6 +926,177 @@ static void wtp_connect(struct hid_device *hdev, bool connected)
 }
 
 /* -------------------------------------------------------------------------- */
+/* Logitech M560 devices                                                     */
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate further the things, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command
+ *	10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never send a keyboard key event
+ * - for the three mouse button it sends:
+ *	middle button               press   11<xx>0a 3500af00...
+ *	side 1 button (forward)     press   11<xx>0a 3500b000...
+ *	side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *	middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+
+static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03};
+
+struct m560_private_data {
+	u8 prev_data[10];
+	u8 button_pressed;
+};
+
+/* how the button are mapped in the report */
+#define M560_MOUSE_BTN_LEFT		0x01
+#define M560_MOUSE_BTN_RIGHT		0x02
+#define M560_MOUSE_BTN_MIDDLE		0x04
+#define M560_MOUSE_BTN_WHEEL_LEFT	0x08
+#define M560_MOUSE_BTN_WHEEL_RIGHT	0x10
+#define M560_MOUSE_BTN_FORWARD		0x20
+#define M560_MOUSE_BTN_BACKWARD		0x40
+#define M560_SUB_ID			0x0a
+#define M560_BUTTON_MODE_REGISTER	0x35
+
+/*
+ * m560_send_config_command - send the config_command to the mouse
+ *
+ * @dev: hid device where the mouse belongs
+ *
+ * @return: 0 OK
+ */
+static int m560_send_config_command(struct hid_device *hdev)
+{
+	struct hidpp_report response;
+	struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+	int ret;

hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1000

Quoted 10 lines
hid-logitech-hidpp.c +10 -0
+
+	ret = hidpp_send_rap_command_sync(
+		hidpp_dev,
+		REPORT_ID_HIDPP_SHORT,
+		M560_SUB_ID,
+		M560_BUTTON_MODE_REGISTER,
+		(u8 *)m560_config_parameter,
+		sizeof(m560_config_parameter),
+		&response
+	);

kernel style is to wrap when needed, not earlier, but it's not a big deal.

Quoted 15 lines
hid-logitech-hidpp.c +15 -0
+
+	return ret;
+}
+
+static int m560_allocate(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *d;
+
+	d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
+			GFP_KERNEL);
+	if (!d)
+		return -ENOMEM;
+
+	hidpp->private_data = d;

hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1025

Quoted 10 lines
hid-logitech-hidpp.c +10 -0
+
+	return 0;
+};
+
+static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *mydata = hidpp->private_data;
+	u8 *btn_ptr = data+1;
+	u8 *wheel_ptr = data+1+6;

Put spaces around operators.

hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1032

Quoted 7 lines
hid-logitech-hidpp.c +7 -0
+
+	/* check if the data is a mouse related report */
+	if (data[0] != 0x02 && data[2] != M560_SUB_ID)
+		return 1;
+
+	if (data[0] == REPORT_ID_HIDPP_LONG &&
+	    data[2] == M560_SUB_ID && data[06] == 0x00) {
                                           ^^

Index in in octal (06) :)

Quoted 23 lines
hid-logitech-hidpp.c +23 -0
+		/*
+		 * m560 mouse button report
+		 *
+		 * data[0] = 0x11
+		 * data[1] = deviceid
+		 * data[2] = 0x0a
+		 * data[5] = button (0xaf->middle, 0xb0->forward,
+		 *                   0xaf ->backward, 0x00->release all)
+		 * data[6] = 0x00
+		 */
+
+		int btn, i, maxsize;
+
+		/* check if the event is a button */
+		btn = data[5];
+		if (btn != 0x00 && btn != 0xb0 && btn != 0xae && btn != 0xaf)
+			return 1;
+
+		if (btn == 0xaf)
+			mydata->button_pressed |= M560_MOUSE_BTN_MIDDLE;
+		else if (btn == 0xb0)
+			mydata->button_pressed |= M560_MOUSE_BTN_FORWARD;
+		else if (btn == 0xae)

hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1065

Quoted 10 lines
hid-logitech-hidpp.c +10 -0
+			mydata->button_pressed |= M560_MOUSE_BTN_BACKWARD;
+		else if (btn == 0x00)
+			mydata->button_pressed &= ~(M560_MOUSE_BTN_BACKWARD|
+				M560_MOUSE_BTN_MIDDLE|M560_MOUSE_BTN_FORWARD);
+
+		/* replace the report with the old one */
+		if (size > sizeof(mydata->prev_data))
+			maxsize = sizeof(mydata->prev_data);
+		else
+			maxsize = size;

a blank line before the 'for' makes it stand out.

Quoted 9 lines
hid-logitech-hidpp.c +9 -0
+		for (i = 0 ; i < maxsize ; i++)
+			data[i] = mydata->prev_data[i];
+
+	} else if (data[0] == 0x02) {
+		/*
+		 * standard mouse report
+		 *
+		 * data[0] = type (0x02)
+		 * data[1..2] = buttons

hid-logitech-hidpp.c static void wtp_connect(struct hid_device *hdev, bool connected) · line 1084

Quoted 10 lines
hid-logitech-hidpp.c +10 -0
+		 * data[3..5] = xy
+		 * data[6] = wheel
+		 * data[7] = horizontal wheel
+		 */
+
+		/* horizontal wheel handling */
+		if (*btn_ptr & M560_MOUSE_BTN_WHEEL_LEFT)
+			*wheel_ptr = -1;
+		if (*btn_ptr & M560_MOUSE_BTN_WHEEL_RIGHT)
+			*wheel_ptr =  1;

"else if" here? I am not sure.

Quoted 19 lines
hid-logitech-hidpp.c +15 -0
+
+		*btn_ptr &= ~(M560_MOUSE_BTN_WHEEL_LEFT|
+				M560_MOUSE_BTN_WHEEL_RIGHT);
+
+		/* copy the type and buttons status */
+		memcpy(mydata->prev_data, data, 3);
+	}
+
+	/* add the extra buttons */
+	*btn_ptr |= mydata->button_pressed;
+
+	return 1;
+}
+
+/* -------------------------------------------------------------------------- */
 /* Generic HID++ devices                                                      */
 /* -------------------------------------------------------------------------- */
 

hid-logitech-hidpp.c static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi, · line 1113

Quoted 6 lines
hid-logitech-hidpp.c +3 -0
@@ -936,6 +1108,9 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_input_mapping(hdev, hi, field, usage, bit, max);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
+		 field->application != HID_GD_MOUSE)
+			return -1;

you could add a m560_input_mapping() here and do the application check there, for symmetry with the current code.

Quoted 13 lines
hid-logitech-hidpp.c +2 -0
 	return 0;
 }
@@ -998,6 +1173,8 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_raw_event(hidpp->hid_dev, data, size);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		return m560_raw_event(hidpp->hid_dev, data, size);
 
 	return 0;
 }

hid-logitech-hidpp.c static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, · line 1207

Quoted 6 lines
hid-logitech-hidpp.c +2 -1
@@ -1026,7 +1203,8 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		return wtp_raw_event(hdev, data, size);
-
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		return m560_raw_event(hidpp->hid_dev, data, size);

A blank line before the return wouldn't hurt.

hid-logitech-hidpp.c static void hidpp_connect_event(struct hidpp_device *hidpp) · line 1282

Quoted 9 lines
hid-logitech-hidpp.c +2 -0
 	return 0;
 }
 
@@ -1100,6 +1278,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
 
 	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
 		wtp_connect(hdev, connected);
+	if ((hidpp->quirks & HIDPP_QUIRK_CLASS_M560) && connected)
+		m560_send_config_command(hdev);

Maybe use "else if" here to highlight that these quirks are mutually exclusive.

hid-logitech-hidpp.c static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) · line 1347

Quoted 10 lines
hid-logitech-hidpp.c +3 -1
 	if (!connected || hidpp->delayed_input)
 		return;
@@ -1162,7 +1342,12 @@ 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)
-			goto wtp_allocate_fail;
+			goto allocate_fail;
+	}
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {

"else if" here too? More as a semantic hint than anything else, this also gives less ambiguity to the 'allocate_fail' label.

Quoted 16 lines
hid-logitech-hidpp.c +4 -1
+		ret = m560_allocate(hdev);
+		if (ret)
+			goto allocate_fail;
 	}
 
 	INIT_WORK(&hidpp->work, delayed_work_cb);
@@ -1228,7 +1413,7 @@ hid_hw_start_fail:
 hid_parse_fail:
 	cancel_work_sync(&hidpp->work);
 	mutex_destroy(&hidpp->send_mutex);
-wtp_allocate_fail:
+allocate_fail:
 	hid_set_drvdata(hdev, NULL);
 	return ret;
 }

hid-logitech-hidpp.c static const struct hid_device_id hidpp_devices[] = { · line 1453

Quoted 8 lines
hid-logitech-hidpp.c +5 -0
@@ -1261,6 +1446,12 @@ static const struct hid_device_id hidpp_devices[] = {
 		USB_VENDOR_ID_LOGITECH, 0x4102),
 	  .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_MULTI_INPUT |
 			 HIDPP_QUIRK_CLASS_WTP },
+	{ /* Mouse logitech M560 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x402d),
+	  .driver_data = HIDPP_QUIRK_CLASS_M560 | HIDPP_QUIRK_DELAYED_INIT |
+			 HIDPP_QUIRK_MULTI_INPUT

Order quirks in a way similar to the other devices, for consistency, i.e. HIDPP_QUIRK_CLASS_M560 may go last.

hid-logitech-hidpp.c static const struct hid_device_id hidpp_devices[] = {

Quoted 6 lines
hid-logitech-hidpp.c +1 -1
+	},
 
 	{ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
 		USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
 
--

I'll let you know when we (me or Dario, in CC) actually test the code.

Thanks,
Antonio

Signature · 6 lines
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
Tested-by on v2 1/1
#12 lore raw
In reply to Goffredo Baroncelli

Hi Goffredo,

I've tested your patch for Logitech M560 mouse on kernel v3.19.3 with xev and evtest commands, it works well.
In this mail I report the output of evtest command.

Bye,
Dario

Quoted 26 lines

----

The Logitech M560 is is a mouse designed for windows 8. Comparing to a standard one, some buttons (the middle one and the two ones placed on the side) are bounded to a key combination instead of a classic "mouse" button.

Think this mouse as a pair of mouse and keyboard. When the middle button is pressed the it sends a key (as keyboard) combination, the same for the other two side button. Instead the left/right/wheel work correctly.
To complicate further the things, the middle button send a key combination the odd press, and another one for the even press; in the latter case it sends also a left click. But the worst thing is that no event is generated when the middle button is released.

Moreover this device is a wireless mouse which uses the unifying receiver.

I discovered that it is possible to re-configure the mouse sending a command (see function m560_send_config_command()). After this command the mouse sends some sequence when the buttons are pressed and/or released (see comments for an explanation of the mouse protocol).

Quoted 3 lines

This patch update the file driver/hid/hid-logitech-hidpp.c (v3.19.3)

$ sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:    Power Button
/dev/input/event1:    Power Button
/dev/input/event2:    USB USB Keyboard
/dev/input/event3:    USB USB Keyboard
/dev/input/event4:    Logitech USB Receiver
/dev/input/event5:    Logitech USB Receiver
/dev/input/event6:    Logitech M560
/dev/input/event7:    Logitech Wireless Mouse M560
Select the device event number [0-7]: 6
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x46d product 0x402d version 0x111
Input device name: "Logitech M560"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 272 (BTN_LEFT)
    Event code 273 (BTN_RIGHT)
    Event code 274 (BTN_MIDDLE)
    Event code 275 (BTN_SIDE)
    Event code 276 (BTN_EXTRA)
    Event code 277 (BTN_FORWARD)
    Event code 278 (BTN_BACK)
    Event code 279 (BTN_TASK)
    Event code 280 (?)
    Event code 281 (?)
    Event code 282 (?)
    Event code 283 (?)
    Event code 284 (?)
    Event code 285 (?)
    Event code 286 (?)
    Event code 287 (?)
  Event type 2 (EV_REL)
    Event code 0 (REL_X)
    Event code 1 (REL_Y)
    Event code 6 (REL_HWHEEL)
    Event code 8 (REL_WHEEL)
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
Quoted 273 lines

Changelog:
- v1 first issue
- v2 accepted Benjamin Tissoires suggestions

hid-logitech-hidpp.c +195 -4
index a93cefe..805ecb6 100644
@@ -35,8 +35,9 @@ MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>");
 #define HIDPP_REPORT_LONG_LENGTH               20
 
 #define HIDPP_QUIRK_CLASS_WTP                  BIT(0)
+#define HIDPP_QUIRK_CLASS_M560                 BIT(1)
 
-/* bits 1..20 are reserved for classes */
+/* bits 2..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)
@@ -925,6 +926,177 @@ static void wtp_connect(struct hid_device *hdev, bool connected)
 }
 
 /* -------------------------------------------------------------------------- */
+/* Logitech M560 devices                                                     */
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate further the things, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command
+ *     10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never send a keyboard key event
+ * - for the three mouse button it sends:
+ *     middle button               press   11<xx>0a 3500af00...
+ *     side 1 button (forward)     press   11<xx>0a 3500b000...
+ *     side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *     middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+
+static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03};
+
+struct m560_private_data {
+       u8 prev_data[10];
+       u8 button_pressed;
+};
+
+/* how the button are mapped in the report */
+#define M560_MOUSE_BTN_LEFT            0x01
+#define M560_MOUSE_BTN_RIGHT           0x02
+#define M560_MOUSE_BTN_MIDDLE          0x04
+#define M560_MOUSE_BTN_WHEEL_LEFT      0x08
+#define M560_MOUSE_BTN_WHEEL_RIGHT     0x10
+#define M560_MOUSE_BTN_FORWARD         0x20
+#define M560_MOUSE_BTN_BACKWARD                0x40
+#define M560_SUB_ID                    0x0a
+#define M560_BUTTON_MODE_REGISTER      0x35
+
+/*
+ * m560_send_config_command - send the config_command to the mouse
+ *
+ * @dev: hid device where the mouse belongs
+ *
+ * @return: 0 OK
+ */
+static int m560_send_config_command(struct hid_device *hdev)
+{
+       struct hidpp_report response;
+       struct hidpp_device *hidpp_dev = hid_get_drvdata(hdev);
+       int ret;
+
+       ret = hidpp_send_rap_command_sync(
+               hidpp_dev,
+               REPORT_ID_HIDPP_SHORT,
+               M560_SUB_ID,
+               M560_BUTTON_MODE_REGISTER,
+               (u8 *)m560_config_parameter,
+               sizeof(m560_config_parameter),
+               &response
+       );
+
+       return ret;
+}
+
+static int m560_allocate(struct hid_device *hdev)
+{
+       struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+       struct m560_private_data *d;
+
+       d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
+                       GFP_KERNEL);
+       if (!d)
+               return -ENOMEM;
+
+       hidpp->private_data = d;
+
+       return 0;
+};
+
+static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+       struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+       struct m560_private_data *mydata = hidpp->private_data;
+       u8 *btn_ptr = data+1;
+       u8 *wheel_ptr = data+1+6;
+
+       /* check if the data is a mouse related report */
+       if (data[0] != 0x02 && data[2] != M560_SUB_ID)
+               return 1;
+
+       if (data[0] == REPORT_ID_HIDPP_LONG &&
+           data[2] == M560_SUB_ID && data[06] == 0x00) {
+               /*
+                * m560 mouse button report
+                *
+                * data[0] = 0x11
+                * data[1] = deviceid
+                * data[2] = 0x0a
+                * data[5] = button (0xaf->middle, 0xb0->forward,
+                *                   0xaf ->backward, 0x00->release all)
+                * data[6] = 0x00
+                */
+
+               int btn, i, maxsize;
+
+               /* check if the event is a button */
+               btn = data[5];
+               if (btn != 0x00 && btn != 0xb0 && btn != 0xae && btn != 0xaf)
+                       return 1;
+
+               if (btn == 0xaf)
+                       mydata->button_pressed |= M560_MOUSE_BTN_MIDDLE;
+               else if (btn == 0xb0)
+                       mydata->button_pressed |= M560_MOUSE_BTN_FORWARD;
+               else if (btn == 0xae)
+                       mydata->button_pressed |= M560_MOUSE_BTN_BACKWARD;
+               else if (btn == 0x00)
+                       mydata->button_pressed &= ~(M560_MOUSE_BTN_BACKWARD|
+                               M560_MOUSE_BTN_MIDDLE|M560_MOUSE_BTN_FORWARD);
+
+               /* replace the report with the old one */
+               if (size > sizeof(mydata->prev_data))
+                       maxsize = sizeof(mydata->prev_data);
+               else
+                       maxsize = size;
+               for (i = 0 ; i < maxsize ; i++)
+                       data[i] = mydata->prev_data[i];
+
+       } else if (data[0] == 0x02) {
+               /*
+                * standard mouse report
+                *
+                * data[0] = type (0x02)
+                * data[1..2] = buttons
+                * data[3..5] = xy
+                * data[6] = wheel
+                * data[7] = horizontal wheel
+                */
+
+               /* horizontal wheel handling */
+               if (*btn_ptr & M560_MOUSE_BTN_WHEEL_LEFT)
+                       *wheel_ptr = -1;
+               if (*btn_ptr & M560_MOUSE_BTN_WHEEL_RIGHT)
+                       *wheel_ptr =  1;
+
+               *btn_ptr &= ~(M560_MOUSE_BTN_WHEEL_LEFT|
+                               M560_MOUSE_BTN_WHEEL_RIGHT);
+
+               /* copy the type and buttons status */
+               memcpy(mydata->prev_data, data, 3);
+       }
+
+       /* add the extra buttons */
+       *btn_ptr |= mydata->button_pressed;
+
+       return 1;
+}
+
+/* -------------------------------------------------------------------------- */
 /* Generic HID++ devices                                                      */
 /* -------------------------------------------------------------------------- */
 
@@ -936,6 +1108,9 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_input_mapping(hdev, hi, field, usage, bit, max);
+       else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
+                field->application != HID_GD_MOUSE)
+                       return -1;
 
        return 0;
 }
@@ -998,6 +1173,8 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_raw_event(hidpp->hid_dev, data, size);
+       else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+               return m560_raw_event(hidpp->hid_dev, data, size);
 
        return 0;
 }
@@ -1026,7 +1203,8 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                return wtp_raw_event(hdev, data, size);
-
+       else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+               return m560_raw_event(hidpp->hid_dev, data, size);
        return 0;
 }
 
@@ -1100,6 +1278,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
 
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
                wtp_connect(hdev, connected);
+       if ((hidpp->quirks & HIDPP_QUIRK_CLASS_M560) && connected)
+               m560_send_config_command(hdev);
 
        if (!connected || hidpp->delayed_input)
                return;
@@ -1162,7 +1342,12 @@ 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)
-                       goto wtp_allocate_fail;
+                       goto allocate_fail;
+       }
+       if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+               ret = m560_allocate(hdev);
+               if (ret)
+                       goto allocate_fail;
        }
 
        INIT_WORK(&hidpp->work, delayed_work_cb);
@@ -1228,7 +1413,7 @@ hid_hw_start_fail:
 hid_parse_fail:
        cancel_work_sync(&hidpp->work);
        mutex_destroy(&hidpp->send_mutex);
-wtp_allocate_fail:
+allocate_fail:
        hid_set_drvdata(hdev, NULL);
        return ret;
 }
@@ -1261,6 +1446,12 @@ static const struct hid_device_id hidpp_devices[] = {
                USB_VENDOR_ID_LOGITECH, 0x4102),
          .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_MULTI_INPUT |
                         HIDPP_QUIRK_CLASS_WTP },
+       { /* Mouse logitech M560 */
+         HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+               USB_VENDOR_ID_LOGITECH, 0x402d),
+         .driver_data = HIDPP_QUIRK_CLASS_M560 | HIDPP_QUIRK_DELAYED_INIT |
+                        HIDPP_QUIRK_MULTI_INPUT
+       },
 
        { HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
                USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},