As I noted earlier this year, I’m currently fighting with the LucidSound LS30 headset and some specific oddities that it has. The whole process has lead me into a mountain of learning about the Linux kernel, it’s development and the deployment of software in a broader context. The personal problem of course being this will do little to help me professionally if at all—I can code, but I don’t want to make it my primary job. Even so, it has lead me to some rather humorous comments with in the Linux kernel source, such as this nugget:

linux-4.13.0/sound/usb/card.c:289

/*
 * Firmware writers cannot count to three.  So to find
 * the IAD on the NuForce UDH-100, also check the next
 * interface.
 */

Firstly let me take a brief moment to describe some of the details I’ve learned related to my specific device, then I’ll get into the core trick I finally figured out to making debugging a bit easier.

LS30 Oddities

The core problem I can discern in the LS30 lies in the specifics of how it reports it’s functional behavior, and the specifics of how the Linux USB Sound subsystem parses this description. At this point root causes are largely speculative, and until recently I had not nailed down a way to quickly rebuild modules to help decipher what the kernel was doing. Short of hopping to a Linux distribution distributed with a nearly vanilla kernel, I was somewhat bound by the documentation currently available.

My suspicion is the majority of USB sound card devices (e.g. adapters, DACs, and USB headsets) make use of a single interface descriptor containing a set of hardware control descriptors. The control descriptors are a series of simple structures that indicate which interfaces (in the software sense) on a USB device correspond to various physical functions. They provide, among other things, a category based qualitative description of the specific interface’s function (i.e. a headset, headphones, speakers, etc.), audio flow direction information (input/output), channel count, and some more technical information as well. Some descriptors also describe software interfaced controls such as volume and mute.

The LS30, being a “feature rich” device, contains a multitude of audio interfaces. These various interfaces are meant to allow a user to split game chat and game audio to the headset. This would allow them to balance game volume against the rabid yelling of the other players. The design idea not problematic on it’s own, but the specific implementation in the device description splits the interface descriptor into two sets (found below for the curious). This is where the Linux kernel’s current idiosyncrasies come into play:

linux-4.13.0/sound/usb/card.c:606

/*
 * For devices with more than one control interface, we assume the
 * first contains the audio controls. We might need a more specific
 * check here in the future.
 */

Well good sir, future is here. And it looks like we’re going to have to write it.

The problem observed is only some of the endpoints actually show up. As indicated by the comment above, the Linux kernel only investigates the grouping of interfaces described in the first interface descriptor. More over, it doesn’t even appear to validate that there are any control descriptors in the first descriptor. The groupings in the LS30 are organized such that the headset’s stereo audio output is in the second grouping of descriptors. This results in the kernel only passing the audio endpoints in the first set, rendering the headset’s functionality somewhat limited.

The fix is likely a one of validating that no deeper problem exists, then expanding the code to inspect and configure interface descriptors. A simple approach in description, likely somewhat painful for an engineer with no history of contributing to a project the scale of the Linux kernel. However, for simple validation I can likely assume index 3 (rather than zero) is the correct point to start parsing interfaces, and verify the kernel’s response.

Building in-tree modules

The key bit of work that will finally let me do my testing is deciphering the Debian/Ubuntu kernel build system, to let me quickly rebuild modules with the proper vermagic string into the kernel. In short, when building kernel modules, the stock build system inserts a signature into modules indicating which kernel version they are designed to operate against. This is fine in the context of preventing users from mixing and matching unrelated binaries1, but it creates a few headaches for those of us tied to the Ubuntu/Debian kernel numbering schemes. On my current Ubuntu 17.10 based system, the vermagic string should be 4.13.0-39-generic SMP mod_unload, but if I were to blindly pull the current kernel source, let the build system apply it’s various patches, a plain build would spit out 4.13.16 SMP mod_unload. A subtle difference, but enough to prevent insmod from letting me load a modified module without forcing an override.

A slow but valid approach (from the Debian documentation) would be to rebuild the entire kernel every time I made a small change to a source file, and potentially wait hours on end. Being who I am, I have little patience for something being slow for no good damn reason. At the same time, I’m also torn by neglecting the hidden magic that I might be missing by manually inserting changes into the source tree Makefile to produce the correct vermagic string.

As it turns out, the trick to produce fast builds with the correct string is relatively simple. The Debian build system will build into a sub-directory for each kernel image it generates. This is readily observable when watching where results are placed when running a full build. What is less obvious, is the build scripts modify the stock Makefile and insert it into these build directories, linking back to the top level source. Simply moving into this directory lets us reuse the traditional kernel build commands.

Reproducible Steps

First fetch the source of the project, and insert your existing system’s configuration into the build tree.2

$ apt source linux
$ apt build-dep linux
$ cd linux-*
$ cp /boot/config-$(uname -r) ./.config

Next, we call the Debian workhorse scripts that do the preliminary configuration and kick of a build of the kernel image. In the example I’m assuming the kernel you want to build is the *-generic variant rather than something like the *-lowlatency variant.

$ fakeroot make -f debian/rules clean build-generic

From here we can actually kill the full build process. The Debian rules scripts will start to build everything, but early in the build process it finishes configuring a dependent build environment we can tinker with. I’ve attempted to nail down the specifics of the dependency flow, but in the package maintainers desire for robustness and flexibility we have lost obvious coherency3.

From here we can hop on over to the dependant target, and build just the modules we want to work with.

$ cd debian/build/build-generic
$ make M=sound/usb

Check the specific module and…

$ modinfo ./sound/usb/snd-usb-audio.ko | grep magic
vermagic:       4.13.0-39-generic SMP mod_unload

Payday! Now, any changes to source files are properly recognized, and calls to make will recompile only the subset of object files required to generate an appropriately tagged kernel module.

In retrospect I’m quite bothered that it took me so long to decipher what turned out to be somewhat simple procedure, but to date I haven’t found anyone with a similar problem related to either the build process or similar device problems, so I’m similarly unsurprised wandering in the dark was fruitless for so long.

Enjoy.


Appendix

Below is an trimmed and annotated tshark dump of a USB transaction against the LucidSound LS30 headset in it’s final state as referenced above. All items described in the interface descriptor indexed 3.0 are unavailable to the system as of writing.

Frame 6: 473 bytes on wire (3784 bits), 473 bytes captured (3784 bits)
USB URB
CONFIGURATION DESCRIPTOR
👉INTERFACE DESCRIPTOR (0.0): class Audio👈
    Class-specific Audio Ctl. Intf. Desc.: Header Descriptor
    Class-specific Audio Ctl. Intf. Desc.: Input terminal descriptor
    Class-specific Audio Ctl. Intf. Desc.: Feature unit descriptor
    Class-specific Audio Ctl. Intf. Desc.: Output terminal descriptor
    Class-specific Audio Ctl. Intf. Desc.: Input terminal descriptor
    Class-specific Audio Ctl. Intf. Desc.: Feature unit descriptor
    Class-specific Audio Ctl. Intf. Desc.: Output terminal descriptor
INTERFACE DESCRIPTOR (1.0): class Audio
INTERFACE DESCRIPTOR (1.1): class Audio
    Class-specific Audio Strm. Intf. Desc.: General AS Descriptor
    Class-specific Audio Strm. Intf. Desc.: Format type descriptor
ENDPOINT DESCRIPTOR
    Class-specific Audio Strm. Endpt. Desc.
INTERFACE DESCRIPTOR (2.0): class Audio
INTERFACE DESCRIPTOR (2.1): class Audio
    Class-specific Audio Strm. Intf. Desc.: General AS Descriptor
    Class-specific Audio Strm. Intf. Desc.: Format type descriptor
ENDPOINT DESCRIPTOR
    Class-specific Audio Strm. Endpt. Desc.
👉INTERFACE DESCRIPTOR (3.0): class Audio👈
    Class-specific Audio Ctl. Intf. Desc.: Header Descriptor
    Class-specific Audio Ctl. Intf. Desc.: Input terminal descriptor
    Class-specific Audio Ctl. Intf. Desc.: Feature unit descriptor
    Class-specific Audio Ctl. Intf. Desc.: Output terminal descriptor
INTERFACE DESCRIPTOR (4.0): class Audio
INTERFACE DESCRIPTOR (4.1): class Audio
    Class-specific Audio Strm. Intf. Desc.: General AS Descriptor
    Class-specific Audio Strm. Intf. Desc.: Format type descriptor
ENDPOINT DESCRIPTOR
    Class-specific Audio Strm. Endpt. Desc.
INTERFACE DESCRIPTOR (4.2): class Audio
    Class-specific Audio Strm. Intf. Desc.: General AS Descriptor
    Class-specific Audio Strm. Intf. Desc.: Format type descriptor
ENDPOINT DESCRIPTOR
    Class-specific Audio Strm. Endpt. Desc.
INTERFACE DESCRIPTOR (5.0): class HID
HID DESCRIPTOR
ENDPOINT DESCRIPTOR
  1. i.e. doing really stupid things 

  2. Listing edited 2018-06-02. I forgot to include the command to install build dependencies. 

  3. If you, dear reader, know out how to fix this without killing or finishing the full build, let me know!