Implementing the Azio Driver
At this point I was ready implementing the Azio driver, so I copied usbkbd.c
to a file called aziokbd.c
and began making edits there. I left the driver in drivers/hid/usbhid
to make compiling easier. Obviously I changed the script from last time to work with a module named aziokbd
instead of usbkbd
.
This is where developing in a virtual machine had a hidden benefit. I could easily toggle the USB passthru from the VirtualBox menu and thereby simulate plugging and unplugging the keyboard from the guest machine. Unfortunately this hid a problem from me that I will get to later.
To implement the driver, I just had to change the lines in usb_kbd_irq
to report the correct keycodes for the bytes coming in from the hardware with input_report_key
. I already had the pattern worked out from reverse engineering the protocol with wireshark and usbmon.
The Azio keyboard breaks the keys up into three chunks. When the first byte in the array is 01, it is a volume control. When it is 04, that is a “regular” key like a-z, 0-9, etc… Finally, a 05 indicates the function keys and numpad. In the driver I simply broke these three cases out into their own respective if/else if
branches. Since the volume only has two controls (up and down) I did not do anything fancy and just implemented them naively. For the other two cases I used the bitmasking trick and went through the remaining 7 bytes in the array.
The real trick was setting up the keycodes in the usb_kbd_keycode
array such that with a little math I could easily correspond an incoming bit with the outgoing keycode. I did that by arranging the keycodes into 8 rows of 8. The first 64 elements were for byte arrays starting with 04, the second 64 were for byte arrays starting with 05 and the rest remains unused (other than the two volume keys).
With the keycodes structured this way I could index into the array by taking the position of each bit and multiplying it by it by the position of the byte I was inspecting. For 05 keys, I just had to offset the indexing by 64 to move to the next 8x8 block in the array.
Using the Driver
Once that was complete I was able to compile and begin using my driver. As a matter of fact, I was already using the driver by this point. About the last half of the driver development was done with the Azio keyboard and my driver. Whenever I encountered a key that was not yet implemented I used the secondary keyboard. That would cause enough pain to implement the key. The implementation outlined above was the result of some refactoring and not the original algorithm. The only thing left was to get the LEDs for the lock keys working.
This was a pretty exhilarating milestone in my little project. At this point I ditched the VM and moved development to my workstation proper. This was when I discovered a second thing that was not working right. This is the issue I was referring to earlier, that the VM hid from me. The generic usbhid driver was always grabbing the keyboard first and the azio driver was not loading. Even after running modprobe aziokbd
, my driver was not getting access to the physical device.
ZOMG! Quirks are Quirky
This turned into a massive time sink. It is one that I am not sure I have escaped even to this day. If you search online for blacklisting a USB device you will find a lot of other people searching online for how to blacklist a USB device. Nobody really seems to know. In fact there appear to be two ways of doing depending on if the driver is compiled into the kernel or as a module. What you will find is that there is this thing called USB quirks. What you will not find is a consistent, well documented, and clear way to apply a “quirk”.
Unfortunately, even though I have got this working, it still feel as though I do not have it nailed down. Blacklisting works passing a option to the usbhid driver called “quirks”. The first part of the option’s value is the 16-bit USB vendor id, the second part is the 16-bit product id and third part is the “the u32 quirks value”. You can read the sum total of the documentation on this, that exists in the entire world, on lines 178-188 of hid-quirks.c. What are the valid u32 quirks values and what do the values mean? Apparently nobody knows. If you know where they are documented, please email me. I would very much like to know. There are just faint whispers on the wind that this is how you do it and some people have had success.
The USB vendor and product ids are easily obtained by running lsusb -v
and finding your device (assuming it is plugged in). Many places on the web will tell you that the magic number is 0x0004
. I am here to emphatically tell you that 0x0004
DOES NOT WORK… EXCEPT WHEN IT DOES!. Honestly, at this point I do not know what to tell anyone.
In sum, the command looks like this:
quirks=0x0c45:0x7603:[MAGIC_NUMBER]
You can pass it to the driver on the commandline by placing it after the driver name when calling modprobe, like so:
sudo modprobe usbhid quirks=0x0c45:0x7603:[MAGIC_NUMBER]
Since the usbhid driver will already be loaded, the full command is:
sudo rmmod usbhid && sudo modprobe usbhid quirks=0x0c45:0x7603:[MAGIC_NUMBER]
This is a good way to test it out and make sure that you have the quirk right, but eventually you will want this thing to just work at boot up. To do that, you put the quirks into file in /etc/modprobe.d
. I created the file usbhid.conf
with the following contents:
options usbhid quirks=0x0c45:0x7603:[MAGIC_NUMBER]
Here is the weird and confusing part. On my VM I had success with the magic number of 0x0007
. To this day I can go back through my bash command history and see where I issued it many times. Furthermore, if I look at my /etc/modprobe.d/usbhid.conf
it has the following line:
options usbhid quirks=0x0c45:0x7603:0x0007
It is working on my VM as I write this. I can passthru the L70 keyboard and reboot the VM and it works.
Transitioning to the Workstation
For some reason when I switched to my development workstation the quirk was not working. At that point I just sort of gave up. I would just load the driver with the commandline (except it was slightly more complicated because I had to also unload and load my mouse driver) and then sleep my machine.
Eventually that became a hassle and I got tired of having two keyboards attached to the computer and I sat down one night with the goal of solving it once and for all. I spent another several hours searching and loading and tweaking before I was ready to give up. I thought why does this work on the VM and not my desktop? Although I swear I copied the original file from the VM, I thought it time to compare the two. Sure enough I noticed the magic number was different. So my workstation’s /etc/modprobe.d/usbhid.conf
looked like this:
options usbhid quirks=0x0c45:0x7603:0x0004
I never did notice that I was using 0x0007
on the commandline but the file was using 0x0004
. When I changed the four to seven it suddenly started working.
I know you are thinking at this point you are kind of an idiot, but to this day I am sure that I would have started from the same working point on the VM and that I only began researching a second time when it did not work. However, I cannot rule out the notion that I put that stupid 4
in there to begin with and that was the problem the whole time.
Damn you Quirks!
Now here is where it gets interesting. The other day I upgraded to kernel 3.13.0-15 and my keyboard stopped working. Although I had much better things to do that night I spent the evening trying to figure out why, hours went by and I felt like it was groundhog’s day. But this time was a little different. Nothing would let me load that driver. I never figured it out and finally went to bed.
The next day I saw there were updates and one of them was a new kernel, 3.13.0-16. I installed, rebuilt the driver and loaded it, but the keyboard was still not working. Looking at the dmesg trace I could see that the usbhid driver was grabbing it before the azio driver was loaded. This was not supposed to be happening with the quirk in the config file. Since I only rebooted about 700 times in the last two days I figured What the heck? I will change that seven to a four. It’s about the only thing I haven’t tried. You already know it worked, right? So here I am, typing this blog post with a usbhid.conf
that looks like this:
swoogan@workstation:~$ cat /etc/modprobe.d/usbhid.conf
#options usbhid quirks=0x0c45:0x7603:0x0007
options usbhid quirks=0x0c45:0x7603:0x0004
Let’s just say I am waiting for the day where I will be switching those two around. I still find it hard to believe that the command that did not work now works and that I have two different quirks on the two machines. It is worth noting that the VM uses a much older kernel.
Lighting up the LEDs
Figuring out the LEDs was a little tricky. Again I did not know where in the driver that I should be looking at. There are a couple of places where the constants LED_NUML
, LED_CAPSL
, and LED_SCROLLL
are used so I littered the area with printk statements. After more and more printk statements and toggling the lock keys a few dozen times, I narrowed it down to the line kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
. It seemed that desc.bInterfaceNumber
was not holding the value that should be passed in. After little more tinkering, I got the LEDs to work by simply hardcoding 0
instead. The final line is
kbd->cr->wIndex = cpu_to_le16(0);
I will be honest and say that I do not know why that works or if it really does work in all cases. But it seems to work.
Building the Driver “Out of Tree”
To build a Linux driver outside of the kernel source tree you just need an appropriate makefile. I just created a folder in my standard development area on my machine for the Azio driver. I then moved my aziokbd.c file into it and created a Makefile
. To be honest, I shamelessly copied someone else’s makefile. I do not even remember where I got it from.
The only thing I did was changed whatever was in obj-m
to be aziokbd.o
and added an install target:
install:
cp aziokbd.ko /lib/modules/$(shell uname -r)/kernel/drivers/input/keyboard
echo 'aziokbd' >> /etc/modules
depmod
Final Steps
Now we get to today. I am using my Azio L70 keyboard daily and quiet enjoying the fact that I wrote the driver for it. However, there are two tasks I still have to work on:
- Fix the Meta key. It was working but has recently stopped functioning.
- DKMS
DKMS is dynamic kernel module support, which is a way for source code modules to be built dynamically when a new kernel is installed. If your module is not in the kernel, it is not included with system updates. If you have built it from source, it only gets built for a specific version of the kernel. This means that without DKMS you have to rebuild it every time you do a kernel upgrade. In my case it is particularly cumbersome because my keyboard is blacklisted from the generic usbhid, so after a kernel update it stops working.
If you are interested, check out the driver project page.
You can clone the repostoriy with:
hg clone https://bitbucket.org/Swoogan/aziokbd