A few months ago, while looking into some display issues with my HP Envy 15 laptop, I somehow become aware that the subwoofer was not active when I ran Linux. The sound worked perfectly well otherwise, but there was no bass and it was not nearly as loud as I remembered from Windows. I initially fiddled with the “model” parameter for the driver, as the Internet will always suggest, without any understanding of what it was doing. None of the configurations worked right for my laptop, so last week I decided to dive in and really understand what was wrong.
The task led me on an adventure to places far more obscure than I might have ever expected to go, and proved over and over again that I am really, really dumb. Nonetheless, I succeeded in fixing my audio problems well-enough that I also understand why they are not fully fixed, and why that doesn’t matter.
Intel High Definition Audio
Modern laptops and sound cards implement a standard known as Intel High Definition Audio. This is really quite an elegant solution, as it is quite capable of supporting almost any sound configuration you could imagine, and means that we only need one driver to support any compliant sound card. That’s not how it really works, but that’s the idea, and it still provides a lot of commonality despite differences and bugs between vendors.
The standard is governed by a publicly available specification: Intel High Definition Audio Specification. When I first went through this spec, I learned almost nothing from it. It’s extremely generic and I am just slow to absorb new information. It’s main use at the level of debugging I want to do is as a reference guide for HDA Verbs.
HP Envy 15 Beats Audio
The HP Envy 15 comes with Beats-branded audio. This was not a selling point for me, but the general large improvement in audio over a typical laptop (on my previous laptop, I had trouble hearing it even at maximum volume) was. HP doesn’t put a lot of detail in their description of this feature, nor provide that many options in the Beats Audio control panel for Windows. So, I had little idea what it really was, other than the bass disappeared when I turned off the checkbox in Windows.
It turns out Beats Audio is essentially more speakers and a subwoofer, along with possible some hardware-assisted or software-only audio processing. I suspect the technology is purely branded, rather than developed in any way by Beats.
The HDA implementation is powered by an IDT 92HD91B1 SoC. Note: IDT purchased audio assets from Sigmatel, so you may see this name in places like the Linux Kernel, which were written prior to this acquisition.
On Linux, only the front speakers outputted any sound, so it was quite adequate sounding for a laptop, but lacked the fullness and loudness of the audio I got when running Windows.
So, the task was fairly straightforward enable the subwoofer and rear speakers. This was slightly complicated by my lack of awareness that these speakers all existed. In fact, I’m still not sure if these are all the speakers on the Envy 15. The Internet has told me it has 6.1 speakers, and one experiment I ran led me to believe I was hearing sound from two speakers on the bottom of the laptop. Empirical evidence has told me, however, that these are the only speakers.
Intel High Definition Audio Programming Interface
HDA provides a standard bus protocol that implement commands called verbs. One or more verb types may be transmitted to each node, represented by a node identifier (NID). Set verbs take a parameter. So, the protocol is dead-simple. Here’s an example:
0x13 0x71F 0x92
This command is decoded as:
NID = 0x13
Verb = 0x71F (Set Configuration Default bits [31:24])
Payload = 0x92 (Port connectivity = Fixed function; Location = Front)
This command programs the pin with NID=0x13 to be a Front speaker. The operating system uses this configuration to properly configure your audio devices. Verbs and their payload and response format can be found in the Intel HDA specification.
The trickier part is determining what NID has what purpose.
It did not occur to me to search for my chip on the web until I was about a week into this project. I’m used to public specs rarely being available for consumer product devices, so I just assumed it would not. To my surprise, when I finally did search, I found a data sheet over 300 pages long! Hopefully the IDT Confidential marking is an oversight, rather than the availability of the datasheet.
The key resource in this document was the block diagram of my chip.
This widget diagram, along with the block diagram, spells out exactly how the HDA interface relates to the chip.
HDA defines a variety of widget types identified by a NID. For debugging purposes, the most important widgets are Pin Widgets. These correspond to audio sinks and sources such as speakers, headphone jacks, line-out, line-in and microphone jacks.
Widgets are interconnected to ultimately stream audio in and out of the chip. As an example, Port D (NID 0xD) on my chip happens to be connected to the front Speakers on my laptop. The only certain way to determine this is from the OEM or experimentation. While not all Pins are suitable for output, the ones that are can be freely connected by the OEM. The chip has a default configured programmed in it, but it certainly will not match the OEM configuration. Later, I’ll discuss how Linux determines this information automatically.
Port D is a Pin Widget that outputs analog audio data to two outputs: left and right. Pin Widgets can support up to two speakers. For surround sound, multiple Pin Widgets are required.
Port D has a Connection List, hard-coded by IDT. This indicates the possible sources of the analog audio data to the pin. The block diagram shows three possible inputs for Port D: DAC0, DAC1 and MixerOutVol. Following these nets, you can see that these correspond to NIDs 0x13, 0x14 and 0x1c respectively. Since there are multiple sources, it is possible to select a connection with the Connection Select Control verb. Both DAC0 and DAC1 are suitable sources for digital audio data from the OS. Having two sources simply means that we can stream multiple audio sources simultaneously (though only one may be connected to this output at a time).
Note that DAC0 and DAC1 are Audio Output Converter widgets. As such, they contain two particularly important features: volume and mute. These are controlled per channel using the Amplifier Gain/Mute verbs. MixerOutVol is more complicated, though a look at the block diagram makes it fairly clear what it does. It can sum the output from up to six widgets. So, as an example, if you were streaming two different audio files on DAC0 and DAC1, if you connect Port D to MixerOutVol, you would hear both streams simultaneously from the front speakers.
There are various other specialized widgets for which I won’t go into detail but will point out for further study:
- Digital PC_BEEP – used for the equivalent of the PC Speaker
- Mono Mux -> Mono Mix -> Mono – single channel output suitable for something monaural Any guesses on which NID the subwoofer is connected to?
- SPDIF – Digital output. Not covered in this article, but they are part of HDA too.
ACPI BIOS Configuration
How can an OS figure out the pin configuration? It’s not programmed into an EEPROM somewhere by the OEM. The chip defaults to what IDT programs in it, which will not correspond to the actual connections.
ACPI BIOSes can provide HDA pin configuration. This corresponds to the Configuration Default register data for each Pin Widget in the system.
This is why my Envy works perfectly when I run Linux. I get two channels of sound from the front speakers because that’s exactly what the HP BIOS told Linux it had.
Much later, I confirmed that Windows fares no better with its generic HDA driver. I get the same output and same configuration.
In Linux kernel parlance, this is called a buggy BIOS. It is lying about the actual hardware configuration, and thus makes the user think something is broken. Of course, you can see what HP might be thinking. Beats is a value-add and presumably requires royalties. So, on Windows you only get the full sound output when you install the IDT driver (distributed by HP). It must reconfigure the pins when you click “Beats Audio”. Linux users are just out of luck.
Linux HDA Drivers
The Linux HDA driver is quite robust. It knows what to do with pin configurations, but it is also filled with workaround after workaround for “quirks” in certain hardware. It turned out my hardware had no quirk defined and so speaker configuration was coming straight from ACPI.
Trying some other configurations, such as “hp-zephyr” (what is an HP Zephyr? I don’t think it exists, but perhaps it’s an internal code-name at HP?) enabled the subwoofer. However, it also disabled the front speakers.
This model override can be done with modprobe, and Ubuntu has a place already set up in /etc/modprobe.d/alsa-base.conf
Adding an option to snd-hda-intel model=<modelname> will force the driver to use a quirks mode instead of ACPI configuration.
I really wanted to debug the configuration directly to figure out this problem. I came upon the tool HDA-Analyzer, which is a fantastic tool, but I do not recommend it as the first tool you use. This tool completely skewed my limited understanding of HDA, and I simply deemed it impossible to do things like change the location of a Pin Widget because this isn’t possible in the tool.
Instead, I highly recommend the more obscure tool from the Linux Sound maintainer called hda-verb. From the discussion above, you can guess that it will let you send verbs directly to your chip (or rather codec, as a chip may contain more than one codec). As long as you’ve got a linux kernel headers installed, it should just build with make.
Now, you can execute commands directly using either the raw values or friendly named provided by hda-verb -l. So, for the example I provided above:
# hda-verb 0x13 0x71F 0x92
For verbs that do not take a payload, simply provide 0, as it will still require a third field.
You can look at the ACPI pin configuration via sysfs:
$ cat /sys/class/sound/hwC0D0/init_pin_config
Note that the exact device name may vary if you have more than one HDA device in your system. In my case hwC0D0 is present along with HwC0D3 for Intel Cougarpoint HDMI.
My pin configuration was as follows:
The extra speakers are connected to Pin Widget NID=0x0F and Pin Widget NID=0x10. So, an easy way to start is to simply copy the pin config from NID=0x0D, which was already working properly.
Reviewing the datasheet, you can see what this data means. 0x92XXX decodes to:
Port Connectivity = Fixed function (0x1)
Location = Internal (0x1), Front (0x2)
Device = Speaker (0x1)
Connection Type = Other analog (0x7)
Color = Unknown (0x0)
Misc = No Jack detect override (0x0)
Association = 0x1
Sequence = 0x0
To be thorough, you can change the location information so that hopefully Linux will ultimately determine correctly which speakers are which. To set NID 0xF, you use the Set Configuration Default commands. It is actually four commands – one for each byte of data.
# hda-verb 0x0f 0x71c 0x10
# hda-verb 0x0f 0x71d 0x0
# hda-verb 0x0f 0x71e 0x17
# hda-verb 0x0f 0x71f 0x91
In order for this to take effect, we need to reload the drivers, as well as the audio system (pulseaudio).
If you google it, you’ll find that you can kill pulseaudio simply by running pulseaudio -k. This does kill the process, but it will instantly restart, at least on Ubuntu 13.04. I struggled to figure out how this was happening, but while I still don’t know, I did figure out how to stop it. Pulseaudio provides a config file option to disable this. In /etc/pulse/client.conf you can set autospawn = no. Now just kill the process or run pulseaudio -k.
Next, you need to reload the drivers. This will seem impossible if you try to do it manually. All the drivers depend on one another and it seems there is no order you can unload them in, and if there were, it would take forever. Instead, ALSA provides a script to do this:
# alsa force-reload
You need to look carefully at the output of this command. It will list drivers not unloaded in parentheses on the first line, and then list the drivers that were reloaded. It is easy to skip over in the long list of modules if you’re not careful.
If pulseaudio is still running, this will fail. If you have anything accessing sound devices like HDA-Analyzer it will fail. Close whatever you can. In my experience, this commands always fails the first time no matter what. However, if I have followed the proper procedure and closed audio apps and pulseaudio, it generally works on the second attempt. If you can’t get it to work, you’ll have to reboot. This won’t help you in this experiment since these hda-verb commands above will not persist across a reboot.
If all went well, the new speakers are now enabled and you will hear audio from them. At this point, this is all you can really do. To debug further, you’ll need to use some additional tools. You can use some hda-verb queries to turn on and off the speakers, but this is tedious. Now it is a good time to try out HDA-Analyzer.
HDA-analyzer provides a GUI interface with a tree of all the widgets in your codecs. You can browse to widget 0x0F which you will see is labeled as an AUDIO_OUT, as you would expect. There is a checkbox “OUT” on the page which can be toggled. This will instantly turn on and off these speakers. If you notice a difference, you know that audio is actually being streamed out this widget. You can also play with a limited number of other items such as the connection list selection.
Digging Deeping into Linux
The procedure above is a good way to go about debugging problems. However, to make it permanent you really need to fix the driver. I’ll go into this process in the next article in this series.