Logitech’s Unifying technology is awesome. Plug the USB receiver in the computer, power on your keyboard or mouse and it is ready for use! Well, the devices themselves do work, but software to attach new devices to a single receiver (or detach bound devices) is only available for Mac OS X and Windows. This article shows a way to pair and unpair devices in Linux and describe the tools that have been used to accomplish that goal. If you are not interested in the technical details, but just want to know how to use the pairing tool, see ltunify: Logitech Unifying tool for Linux.
For Linux, Logitech engineer Benjamin Tissoires has published an unofficial utility that is able to pair new devices to a receiver. It does so by writing a magic byte sequence to the USB receiver. While the tool seems to work, I did not like writing random values to a device without knowing what the side-effects are. Besides, I also wanted to be able to bind the wireless device back to the original receiver. For these reasons, I started exploring possibilities for reverse engineering the protocol used to (un)pair devices.
At first, I looked at the kernel source code, drivers/hid/hid-logitech-dj.c and its related header file drivers/hid/hid-logitech-dj.h. This gave me an idea of how the USB data packet had to be interpreted. While I was actively reverse engineering the protocol, I spotted the Logitech protocol specification by coincidence. These documents can be found in my files directory. Especially the Logitech HID++ 1.0 specification for Unifying Receivers document was very insightful.
My notes and the source code of the resulting pairing program and reverse engineer utilities can be found on https://git.lekensteyn.nl/ltunify/.
Inspired by Avery’s post about reverse engineering the Logitech Unifying USB protocol, I used a virtual machine running Windows XP on a Linux host. To inspect the USB traffic, the usbmon kernel module was used on Linux. Instead of VirtualBox, I chose for QEMU because it is easier to setup for running over a LAN network. The remote desktop PC had plenty of RAM to store the virtual machine disk image, copies of that disk image and any iso files that were needed for the installation of the Windows XP guest system and drivers. See Wikibooks QEMU/Windows XP article for instructions on creating and installing a Windows XP virtual machine with QEMU.
Since my VM was running on a remote LAN machine, I had to use VNC for viewing the virtual machine interface. A virtual network adapter is also added to the VM for downloading Logitech programs. Before you can use the virtual network adapter, you need to install Windows VirtIO Drivers (this is optional as networking is not really necessary for tapping USB).
After creating the virtual machine, unload some kernel modules on the host OS to avoid interference with the Windows guest. Then determine the USB device that you want to pass to the Windows VM and grant yourself read/write permissions to this USB device.
remote$ sudo modprobe -vr usbhid hid-logitech-dj rmmod /lib/modules/3.5.0-25-generic/kernel/drivers/hid/hid-logitech-dj.ko rmmod /lib/modules/3.5.0-25-generic/kernel/drivers/hid/usbhid/usbhid.ko remote$ lsusb ... Bus 005 Device 004: ID 046d:c52b Logitech, Inc. Unifying Receiver ... remote$ sudo chgrp $USER /dev/bus/usb/005/004 remote$ qemu-system-i386 -enable-kvm -m 1G -vga std -monitor stdio -vnc :0 -S \ -device usb-host,hostbus=5,vendorid=0x046d,productid=0xc52b \ -netdev user,id=qnet0 -device virtio-net,netdev=qnet0 \ -hda winxp.img QEMU 1.4.0 monitor - type 'help' for more information (qemu)
The qemu-system-i386
command starts a 32-bit VM. Since the
-S
option was passed, it will not immediately boot the guest OS.
This allows you to attach a VNC client and watch the boot process. On your local
machine, connect to the VNC server (192.168.4.2
in this example):
$ gvncviewer 192.168.4.2:0 Connected to serverContinue the virtual machine by issuing the
c
command in the remote
qemu shell and your virtual machine should be booted up in a few seconds. You
are now ready to monitor USB traffic.
Linux makes it easy to monitor USB traffic with the usbmon kernel module. Avery
showed how to utilise /sys/kernel/debug/usb/usbmon/5u
to watch
the USB communication. In order to aid me in analysis of its output, I wrote a
small AWK script (usbmon.awk) that
labels bytes and colors the messages based on their direction (received or
sent). An example session is shown below where 5 is the Bus ID from
lsusb
.
$ sudo modprobe usbmon $ sudo mount -t debugfs none /sys/kernel/debug $ sudo cat /sys/kernel/debug/usb/usbmon/5u | usbmon.awk (lots of long lines here with a lot of noise)
The above method provides me the needed information, but it is too verbose and outputs lots of useless messages. It turns out that the usbmon module also provides a more flexible device interface that makes it easier to process USB messages. Therefore, I wrote a second tool that uses this interface to produce more relevant messages, read-dev-usbmon.c. Another advantage of this tool is that root privileges become unnecessary after setting the appropriate file permissions.
$ sudo modprobe usbmon $ sudo chmod g+r /dev/usbmon5 $ sudo chgrp $USER /dev/usbmon5 $ make read-dev-usbmon $ ./read-dev-usbmon /dev/usbmon5
In order to understand the contents of USB messages, I will first provide an overview of the protocol before investigating how the Logitech Unifying software works.
The messages that are passed in the USB protocol are pretty simple. Some messages are so-called notifications. To enable these notifications, a special device register must be written. Other messages written to the USB receiver will yield a message that describe the result of the requested action. Basically there is a three bytes header followed by a payload that has to interpreted according to the message type:
0x10
identifies a
short message with a payload length of 4 bytes. A long message is identified by
0x11
and has a payload length of 17.
0xFF
means that
the message is related to the receiver, values 0x01
to
0x06
identify paired devices one to six.
Messages can be sent from software to the receiver, or received from the receiver to the software. The meaning of these messages do not have to be the same. The four most important message types are listed below. These messages can query or change a device register.
0x80
- Short message to set the contents of a register (three
bytes available for the value to set).
0x81
- Short message to query the contents of a register (three
bytes available for parameters).
0x82
- Long message to set the contents of a register (16 bytes
available for the value).
0x83
- Short message to query the contents of a register that
yields a long message as result (three bytes available for parameters).
Sending a message to the receiver always generates another message. If the
command failed (for example, if the message type or register was invalid), an
error message will be delivered containing the message type, the register
address and an error code followed by 0x00
.
The exact parameters vary for device registers, refer to the registers.txt file in the git repo for an overview of these registers. As an example, consider the Enable HID++ Notifications register. Reading this short register will show all enabled notifications, writing this register will control which notifications will be received.
The short message, directed at the receiver should query a
register with few parameters, namely the Enable
HID++ Notifications register. No parameters are necessary (00 00
00
) and the message looks like:
10
FF
81
00
00 00 00
After writing this message to the USB receiver, you will receive a short message from the USB
receiver describing the response to the
query for the Enable HID++ Notifications
register. The message could look like:
10
FF
81
00
00 01 00
Note the 01 value above, it means that the software will receive
notifications whenever a device arrives or disappears. (See Section
4.1 of the Logitech HID++ 1.0 specification (pdf) for other possible values.)
Another example, the magic sequence provided by Benjamin Tissoires's pairing
program:
10
FF
80
B2
01 00 00
It is a short message that sets the value of the USB
receiver register Device Connection and
Disconnection (Pairing) to 01 00 00
. These parameters open
the receiver for new devices (01
) for a default period of 30
seconds (the last 00
). See Section
4.3 of the Logitech HID++ 1.0 specification for a detailed description of
this message type.
Now that you know the basics of the Logitech HID++ protocol and with the Big Brother environment being ready, we can proceed with looking at the behavior of Logitech Unifying software in the virtual machine using the read-dev-usbmon tool that was mentioned in a previous section about usbmon.
When Logitech’s software is started, it immediately exchanges some messages with the receiver. These messages first ask for receiver details (serial, firmware versions, etc.) and then allow software to receive device notifications from the receiver. Next, similar details are retrieved for all paired devices. (See registers.txt for detailed steps and an annotated dump.)
Pairing a device is easy and has been explained before. How does Logitech do it? Let’s see what the read-dev-usbmon tool shows when no devices are paired and the Next button is clicked in the Logitech® Unifying Software:
Software asks receiver to pair a new device within a timeout of 0x3C
(60) seconds.
Send report_id=10 short device=FF RECV type=80 SET_REG
reg=B2 DEVICE_PAIRING params=01 53 3C
(notification that lock is now open:)
Recv report_id=10 short device=FF RECV type=4A NOTIF_RECV_LOCK_CHANGED
params=01 00 00 00
(feedback that the register was succesfully set:)
Recv report_id=10 short device=FF RECV type=80 SET_REG
reg=B2 DEVICE_PAIRING params=00 00 00
Keyboard is switched on, A Unifying keyboard device with product ID 2010 is deteced.
Recv report_id=10 short device=01 DEV1 type=41 NOTIF_DEVICE_PAIRED
params=04 61 10 20
Software requests name of device:
Send report_id=10 short device=FF RECV type=83 GET_LONG_REG
reg=B5 PAIRING_INFO params=40 00 00
(oh, receiver also wants to tell us that receiver is closed for new pairings:)
Recv report_id=10 short device=FF RECV type=4A NOTIF_RECV_LOCK_CHANGED
params=00 00 00 00
Device responds with name of length 4, namely the UTF-8 string K800:
Recv report_id=11 long device=FF RECV type=83 GET_LONG_REG
reg=B5 PAIRING_INFO params=40 04 4B 38 30 30 00 00 00 00 00 00 00 00 00 00
Software asks for “extended pairing info”:
Send report_id=10 short device=FF RECV type=83 GET_LONG_REG
reg=B5 PAIRING_INFO params=30 00 00
Device responds with serial number and the location of the power switch:
Recv report_id=11 long device=FF RECV type=83 GET_LONG_REG
reg=B5 PAIRING_INFO params=30 FB 84 1B 86 1A 40 00 00 07 00 00 00 00 00 00
Receiver notifies that pairing has completed, a link had link has been established:
Recv report_id=10 short device=01 DEV1 type=41 NOTIF_DEVICE_PAIRED
params=04 A1 10 20
(more messages that read device firmware/bootloader versions have been omitted)
The key action is making the receiver accept new pairings (just like Benjamins pairing tool). The other commands are used to provide feedback, whether a device was succesfully paired or not. This was not too difficult to guess. The next, more interesting action is unpairing a device. This can be done by pressing the Advanced button from the home screen. After pressing that button, it can be observed that the software polls repeatedly for status updates (that will be ignored in the following annotation). The below messages are communicated when pressing the Unpair button:
Disconnect device number 1 (the one in device=…): Send report_id=10 short device=FF RECV type=80 SET_REG reg=B2 DEVICE_PAIRING params=03 01 00 Device 1 is now disconnected Recv report_id=10 short device=01 DEV1 type=40 NOTIF_DEVICE_UNPAIRED params=02 00 00 00 (register successfully set:) Recv report_id=10 short device=FF RECV type=80 SET_REG reg=B2 DEVICE_PAIRING params=00 00 00
Benjamin was right that unpairing could not be discovered as easy as pairing, but nevertheless it is not impossible, especially if a specification is available.
Logitech has more software for keyboards and mice, namely SetPoint. By tapping the USB traffic, it is possible to discover additional functionalities. For example, flipping the functionality of the F button (like F9 / Play) generated just four messages which can easily be guessed:
(Check the “Swap F key functions” checkbox in SetPoint settings) Software asks for current status of F key functions Send report_id=10 short device=01 DEV1 type=81 GET_REG reg=09 FN_KEY_SWAP? params=00 00 00 (device responds: F key is not swapped) Recv report_id=10 short device=01 DEV1 type=81 GET_REG reg=09 FN_KEY_SWAP? params=00 00 00 Software asks to swap F key functions Send report_id=10 short device=01 DEV1 type=80 SET_REG reg=09 FN_KEY_SWAP? params=00 01 00 (device responds, register is successfully changed) Recv report_id=10 short device=01 DEV1 type=80 SET_REG reg=09 FN_KEY_SWAP? params=00 00 00
ltunify is a program resulting from the gathered knowledge on the Logitech HID++ protocol. It allows you to pair additional devices like keyboards and mice to your Unifying receiver, unpair existing devices and list information about connected devices. This section will show you how to install the ltunify program.
Besides a C compiler and a way to fetch sources (wget+tar or git), you will need Linux 3.2 or newer with the hid-logitech-dj module. On Debian and Ubuntu distributions, the required packages can be installed using:
sudo apt-get install git gccFetch the sources and install
ltunify
to $HOME/bin/
using the next commands:
git clone https://git.lekensteyn.nl/ltunify.git cd ltunify make install-home
The following steps will assume that $HOME/bin
is available in your
path. If not, run:
export PATH="$HOME/bin:$PATH"If everything went well, you should be able to run
ltunify --help
to show the available options:
Usage: ltunify [options] cmd [cmd options] Logitech Unifying tool version dev Copyright (C) 2013 Peter Wu <lekensteyn@gmail.com> Generic options: -d, --device path Bypass detection, specify custom hidraw device. -D Print debugging information -h, --help Show this help message Commands: list - show all paired devices pair [timeout] - Try to pair within "timeout" seconds (1 to 255, default 0 which is an alias for 30s) unpair idx - Unpair device info idx - Show more detailed information for a device receiver-info - Show information about the receiver In the above lines, "idx" refers to the device number shown in the first column of the list command (between 1 and 6). Alternatively, you can use the following names (case-insensitive): Keyboard Mouse Numpad Presenter Trackball TouchpadThe below session shows you how to use a device index to unpair a mouse.
$ sudo ltunify list Devices count: 1 Connected devices: idx=1 Mouse M525 $ sudo ltunify unpair 1 Device 0x01 Mouse successfully unpaired $ sudo ltunify list Devices count: 0 Connected devices: $ sudo ltunify pair Please turn your wireless device off and on to start pairing. Found new device, id=0x01 Mouse $ sudo ltunify list Devices count: 1 Connected devices: idx=1 Mouse M525It is also possible to select a device by device type (case-insensitive). When multiple devices of the same type are available, the first one will be selected.
$ sudo ltunify unpair mouse Device 0x01 Mouse successfully unpaired
ltunify
does not have to run as root, but it is easier and faster
to say “run this program as root” instead of “change permissions of this file
and then run the program”. “this file” refers to some
/dev/hidrawX
where X is some number. An example
where I allow myself to do anything with the receiver:
$ ltunify list /dev/hidraw0: Permission denied Logitech Unifying Receiver device is not accessible. Try running this program as root or enable read/write permissions for /dev/hidraw0 $ ls -l /dev/hidraw0 crw------- 1 root root 251, 0 Apr 24 11:14 /dev/hidraw0 $ sudo chmod g+rw /dev/hidraw0 && sudo chgrp $USER /dev/hidraw0 $ ls -l /dev/hidraw0 crw-rw---- 1 root peter 251, 0 Apr 24 11:14 /dev/hidraw0 $ ltunify list Devices count: 1 Connected devices: idx=1 Mouse M525
To make this permanent, one could create a udev rule such as 42-logitech-unify-permissions.rules.
Logitech has created drivers that provides flawless Linux support for their products, but full pairing support was missing. Thanks to tools like usbmon and QEMU, I was able to analyse the protocol and relate it with the given HID++ 1.0 specification. That allowed me to create the ltunify program for Linux that provides similar functionality as the official Unifying Software.
Future work on this project could move functionality into a library, that could be used to create a graphical user interface for (un)pairing devices. Perhaps the hid-logitech-dj kernel module can also be improved to provide an API for toggling notifications or unpairing devices. More features can also be added to ltunify, for example swapping the F function keys of a keyboard, but the main purpose of this tool is to enable users to pair and unpair devices.
Other improvements could be implementing version 2.0 of the HID++ protocol, whatever benefits it may give. Since I do not have a device using the HID++ 2.0 protocol, I did not bother implementing it. If you are looking for a 2.0 example, UPower has an implementation of HID++ 2.0 for checking battery levels, contributed by Julien Danjou. (Added on 26 April 2013:) there is another tool that I was unaware of although it existed over a half year ago. The graphical Python program Solaar supports HID++ 2.0 and adds some extra functionality like solar charge status for keyboard and swapping FN keys. Definitely worth checking out!
What can other companies learn from this? USB-based protocols are very easy to capture and analyse. Given enough time, there will ultimately be a program that can use this protocol. Provide sufficient specifications to the open-source community, this makes us happy and save time!
The following third-party software have been used for this project:
Related links: