On Broken ACPI DSDTs

A couple months ago, I replaced the AMD Sempron 3100+ (1.8GHz) in my HTPC with a newer AMD Athlon64 3200+ (2.2GHz). It'd been having some trouble multitasking: specifically recording and watching HD content at the same time could often cause stuttering, especially if the commercial flagger was running at the same time. It also had issues with some higher-bitrate 1080p content. So, I got the replacement CPU.

I swapped CPUs (scaring myself to death when I realised I'd almost broken the socket when the CPU got stuck to the heatsink, and I yanked the CPU out of the socket without lifting the ZIF lever), and noticed an odd error on boot:

powernow-k8: Found 1 AMD Athlon(tm) 64 Processor 3200+ processors (1 cpu cores) (version 2.00.00)
ACPI Exception (processor_perflib-0234): AE_NOT_FOUND, Evaluating _PSS [20070126]
powernow-k8: BIOS error - no PSB or ACPI _PSS objects
ACPI Exception (processor_perflib-0234): AE_NOT_FOUND, Evaluating _PSS [20070126]

Basically, that means the powernow-k8 driver was reporting that it wasn't provided a _PSS or PSB block in the ACPI DSDT, so it didn't know about any CPU P-states, and thus CPU frequency scaling wouldn't work. Since I do sometimes leave the machine on idle, this was a bit annoying, since it'd be burning some power that it wouldn't otherwise need to burn.

So, I googled a bit (oh, sorry, "performed some Google searches"), but didn't find anything particularly promising. I checked the BIOS settings; yes, the "AMD Cool'n'Quiet" feature is enabled. I tried disabling it, rebooting, and then re-enabling, and rebooting, but no luck.

Fast-forward a couple months. A few days ago, I ran across something that reminded me of the problem. Some stuff about many motherboard manufacturers/BIOS writers providing crappy DSDTs in their ACPI implementation (the DSDT is what tells an ACPI-compliant OS what ACPI features are supported, and how to used them). So, I found a nice resource that told me how to fetch the DSDT from my BIOS, and decompile it for inspection.

According to the decompiled source, the DSDT was compiled with Microsoft's compiler, which, I understand, is kinda crappy in that it lets a lot of errors past. When I tried to recompile my DSDT with Intel's compiler, it threw 6 errors and around 20 warnings. I poked around at the errors, and, with some helpful pointers, I managed to fix the errors, or just comment out the portions causing fatal errors that I was pretty sure don't do anything useful anyway.

The errors, however, didn't seem to have anything to do with the missing P-state descriptions, though I did notice that the DSDT source was missing either a _PSS section or PSB section, as the kernel complained about.

So I googled some more, and came across this guy, who seemed to have a similar problem, but with his Athlon64 X2. He basically said he noticed that his processor description sections were empty, and found a DSDT on the Linux ACPI website for a similar motherboard that happened to have the proper _PSS entries, and copied them over, tweaking them a bit. Fortunately he also posted the sections he added.

Now, the X2 isn't really the same chip as mine, but it was a place to start. I googled a bit more, and found another guy with a similar problem, and this guy even has the exact same CPU (down to the generation) as I do! Unfortunately, though his post seems to indicate he resolved the problem, he didn't post a how-to, but did mention he got some information from some documentation from AMD.

So, off to AMD's website. Cool, they provide quite a bit of technical documentation for their CPUs. I grabbed the BIOS and Kernel Developer's guide to my CPU, and started looking for stuff about "_PSS". Fortunately, it wasn't hard to find (the doc is over 450 pages long). The structures and values I found from the X2 guy's website are documented, and it gave some good defaults for some of the timing-related pieces.

Unfortunately, AMD's specs don't cover my particular spin of the CPU, so I couldn't figure out what P-states my CPU supports, or what voltages they should run at, or what the power dissipation is for each state.

So, I had to fudge some values and make some stuff up. Wikipedia says my CPU runs at 59 W. Fair enough. But that's for the default 2.2GHz frequency and 1.4V voltage. But what about lower frequencies? I decided to arbitrarily pick 1.6GHz at 1.3V and 800MHz at 1.2V for my other 2 P-states (presumably I could pick more, at 200MHz increments, but I wanted to keep it simple). Running a CPU at a lower lower voltage when underclocking is generally a reasonable thing to do; though dropping the voltage too low is probably a bad idea. But what about power dissipation? According to the specs, only the max/default P-state has to have its power dissipation value specified exactly. All slower P-states can be estimated. Good news. I just simply took the 59 W value and scaled it down based on both the frequency ratio and voltage ratio. Is that exact? No, certainly not. But it's probably not totally off the mark.

One value eluded me. One of the values is a control register that needs to get set. The control register has 7 different sub-values. AMD's documentation gives good defaults for six of them. The last one, PLL_LOCK_TIME, seems pretty important: it's the time it takes for the processor PLLs to re-lock after a state change. A PLL is essentially a clock generator. You have a phase detector and some kind of oscillator (where you can change the freqency), and then a feedback path that has some kind of divider circuit. Anyhow, I lucked out on this one: the X2 guy provided values for these, and since his values for all the other six seemed to match mine, I crossed my fingers and hoped his PLL_LOCK_TIME is the same as mine.

So, whew. I added the missing _PSS section to my DSDT, with the three P-states, and recompiled it, fortunately without errors. (I decided not to bother with the existing warnings because I'm lazy.) Now, how do we use it? According to the original Gentoo forum page I was looking at, you can either patch the kernel to use a file that you have to put in the kernel source tree in a particular location, or you can patch the kernel to read the DSDT out of an initrd. Boo, I don't wanna patch my kernel. So I googled a littler more, and it appears that recent kernels have a better option: you can enable a kernel config parameter, and it'll let you specify a path to a header file (helpfully generated by the DSDT compiler) to use. So, 'make menuconfig', and I dig around for the option. Not there. Oh, I need to disable the "Select only drivers that don't need compile-time external firmware" option. Ok... still not there. I search for config options with "DSDT" in them, and find that the one I want requires that CONFIG_X86_VOYAGER isn't set (which it isn't), CONFIG_ACPI is set (which it is), and CONFIG_STANDALONE isn't set (which it isn't, now that I disabled it). But still, no option. So, let's try another tack. I quit menuconfig, and open .config in a text editor. I remove the one DSDT-related line, save it, and run 'make oldconfig'. Now, it prompts me for the DSDT-override option, and, when I select it, I get prompted for a file location. Nice. And lame. But mostly nice. So, I recompile (first intentionally negelcting to copy the header file to the location I specified, just to make sure it would error out, confirming that the option was enabled), install the kernel image, and reboot. The box comes up, I ssh in, and check dmesg:

powernow-k8: Found 1 AMD Athlon(tm) 64 Processor 3200+ processors (1 cpu cores) (version 2.00.00)
powernow-k8:    0 : fid 0xe (2200 MHz), vid 0x6
powernow-k8:    1 : fid 0x8 (1600 MHz), vid 0xa
powernow-k8:    2 : fid 0x0 (800 MHz), vid 0xe

Oh, yes. Yes, that's nice. I cat /proc/cpuinfo, and see that my CPU is now running at 800MHz (I have the machine set up to use the 'ondemand' cpufreq governor, which sets the frequency to the lowest needed given the system load). I dig around in /sys/devices/system/cpu/cpu0/cpufreq/, setting different governors and speed values, and checking that /proc/cpuinfo reports the new speed. Success!

Presumably, I could add more P-states, but I think 1600MHz and 800MHz will do. For those of you with similar problems, my motherboard is a DFI K8M800-MLVF. Feel free to use my hacked DSDT if you have the same motherboard, or as a reference point if you have another Socket 754 system or similar CPU. Of course, I offer this with no warranty and accept no liability, and if your system explodes, it's your responsibility and all that.

See also: my AMD _PSS notes, and my AMD _PSS value table.