HOWTO Athlon 64 X2 Cool'n'Quiet

How to get Cool'n'Quiet work on a Linux system with a dual core CPU on a mainboard that lacks ACPI _PSS support

ATTENTION: In this HOWTO I explain what I did to get Cool'n'Quiet work on my dual core system. Although it works very well for me, I cannot assure that it will work for you too nor am I responsible for any damage that contingently could occur to your system.
I have recently upgraded my system from a single core Athlon 64 to a dual core Athlon 64 X2 3800. Everything went fine so far, except one important thing: Cool'n'Quiet did no longer work. When trying to load the powernow-k8 kernel module, it complained about the PSB BIOS structure not being compatible with multi processor systems:
powernow-k8: Found 2 AMD Athlon 64 / Opteron processors (version 1.60.0)
powernow-k8: MP systems not supported by PSB BIOS structure
powernow-k8: MP systems not supported by PSB BIOS structure
There are two ways to control the frequency and voltage of the CPU. Either through the PSB structure or via the ACPI _PSS tables. Unfortunately the BIOS of my mainboard (Soltek SL-B9D-FGR) does not include the ACPI _PSS tables and the PSB table only works on single CPU machines, while a system with a dual core CPU is technically a multi processor system. That is why powernow-k8 failed to work. When searching the web for this special problem, the recommended solution was to upgrade the system BIOS. Unfortunately there is no BIOS update available for my mainboard and probably never will be.

The only way to control cpu frequency and voltage on SMP machines is through the ACPI _PSS tables, so I had to find a way to add those missing tables to my BIOS. The _PSS is part of the so called DSDT which is an acronym for Differentiated System Description Table. It includes information about the system configuration and is loaded by the operating system at boot time. After some investigation I found out that Linux offers an option to load an alternate DSDT instead of the one from the BIOS.

Modifying the DSDT

With
# cat /proc/acpi/dsdt > wejp-dsdt.aml
the DSDT can be retrievied from the BIOS and
$ iasl -d wejp-dsdt.aml
disassembles it to a readable format (wejp-dsdt.dsl).

When opening the disassembled DSDT, I found out that the CPU sections were empty:
Processor (\_PR.CPU0, 0x00, 0x00000000, 0x00) {}
Processor (\_PR.CPU1, 0x01, 0x00000000, 0x00) {}
I compared it to a DSDT from another Socket 939 Athlon 64 mainboard that was known to have a working _PSS table. Actually, the CPU sections of that DSDT included the _PSS table that was missing in my BIOS. Copying the whole part inside the CPU sections and modifying it to match my system configuration did the trick. Lots of DSDTs from various mainboards are available here: http://acpi.sourceforge.net/dsdt/view.php. Probably any Socket 939 mainboard with a working _PSS table should be okay.

These are my modified CPU sections:
Processor (\_PR.CPU0, 0x00, 0x00000000, 0x00)
{
    Name (_PCT, Package (0x02)
    {
        ResourceTemplate ()
        {
            Register (FFixedHW,
                0x00,               // Register Bit Width
                0x00,               // Register Bit Offset
                0x0000000000000000, // Register Address
                )
        }, 

ResourceTemplate () { Register (FFixedHW, 0x00, // Register Bit Width 0x00, // Register Bit Offset 0x0000000000000000, // Register Address ) } }) Name (_PSS, Package (0x03) // number of p-states { /* Package (0x06) { 0x0898, // 2200 MHz 0x000105B8, 0x64, 0x07, 0xE020298E, 0x018E }, */

Package (0x06) { 0x07D0, // 2000 MHz 0xFCBC, 0x64, 0x07, 0xE0202A0C, 0x020C },

Package (0x06) { 0x0708, // 1800 MHz 0xD610, 0x64, 0x07, 0xE0202A8A, 0x028A },

Package (0x06) { 0x03E8, // 1000 MHz 0x6B6C, 0x64, 0x07, 0xE0202C82, 0x0482 } }) Name (PSXG, Buffer (0x18) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) Name (PSXF, Buffer (0x18) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) Name (PSXE, Buffer (0x18) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) Name (PSXD, Buffer (0x18) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) Name (PSXC, Buffer (0x18) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) Name (PSXB, Buffer (0x18) { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }) Method (_PPC, 0, NotSerialized) { Return (Zero) }

Name (PSSC, 0x0A) }

Processor (\_PR.CPU1, 0x01, 0x00000000, 0x00) { Name (APCT, Package (0x02) { ResourceTemplate () { Register (FFixedHW, 0x00, // Register Bit Width 0x00, // Register Bit Offset 0x0000000000000000, // Register Address ) },

ResourceTemplate () { Register (FFixedHW, 0x00, // Register Bit Width 0x00, // Register Bit Offset 0x0000000000000000, // Register Address ) } }) Name (APSS, Package (0x0A) { Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 },

Package (0x06) { 0x09999999, 0x00099999, 0x00999999, 0x00999999, 0x99999999, 0x99999999 } }) Method (APPC, 0, NotSerialized) { Return (Zero) }

Name (PSSC, 0x0A) }
I have added some comments, so you can see at first sight which block describes which frequency.

Note: The actual content of the _PSS table depends on the CPU used in the system. As you can see, the table I have used for my system was from a system with a CPU with 2200 MHz clock speed, while my CPU has 2000 MHz. That is why I needed to remove the 2200 MHz entry and decrease the number of p-state blocks from 0x04 to 0x03. Everything else was the same for my system (including CPU voltages). More details about these entries can be found in the ACPI specification.

To compile the DSDT into the format required for inclusion in the Linux kernel iasl can be used:
$ iasl -tc wejp-dsdt.dsl
A new file called wejp-dsdt.hex will be created. Unfortunately, many DSDTs contain errors. These errors need to be removed first, to be able to compile the disassembled DSDT with iasl. There are tutorials (e.g. http://www.cpqlinux.com/acpi-howto.html) available that explain how to remove common errors in DSDTs.

Preparing the kernel

To be able to use ACPI for controlling CPU frequency and voltage you need to select the following options:
Power management options  --->
  CPU Frequency scaling  --->
    [*] CPU Frequency scaling
    <*> 'conservative' cpufreq governor
    <*> AMD Opteron/Athlon64 PowerNow!
      [*] ACPI Processor P-States driver
Do not build them as modules.

To add the new DSDT to the kernel it is necessary to disable the following option in the kernel configuration:
Device Drivers  --->
  Generic Driver Options  --->
    [ ] Select only drivers that don't need compile-time external firmware
Now, the new DSDT can be added to the kernel using this option:
Power management options  --->
  ACPI (Advanced Configuration and Power Interface) Support  --->
    [*]   Include Custom DSDT
      (/home/wejp/wejp-dsdt.hex) Custom DSDT Table file to include
Please note that I have used Linux 2.6.16. For other kernel versions these options might be called differently or are not available at all.

Conclusion

After dissassembling the DSDT, adding the missing _PSS tables and compiling the modified DSDT into the Linux kernel, powernow-k8 actually works again:
powernow-k8: Found 2 AMD Athlon 64 / Opteron processors (version 1.60.0)
powernow-k8:    1 : fid 0xc (2000 MHz), vid 0x8 (1350 mV)
powernow-k8:    2 : fid 0xa (1800 MHz), vid 0xa (1300 mV)
powernow-k8:    3 : fid 0x2 (1000 MHz), vid 0x12 (1100 mV)

Supplement: Power saving through CPU undervolting

The DSDT can be modified in such a way that the CPU uses a lower voltage than its default voltage for each p-state. That results in a lower power consumption. Unfortunately this seems to work only for voltages down to 1100 mV (voltage of the lowest p-state). That means for the lowest p-state no further power saving is possible. When I first tried to modify my DSDT, I have tried to undervolt my CPU as well, but the only P-State I tried was the 1000 MHz state where the voltage is already 1100 mV. All tries in reducing the voltage below that level failed, so I gave up that idea until sombody mentioned to me that it is actually possible to lower the voltages for the higher p-states. That means the power consumption can be reduced for all p-states but the lowest.

P-State Calculator

To simplify the calculations of the p-state blocks I have written a little calculator which does all the calculations for you. It is embedded in this website, so you can interactively calculate the control and status values for various voltage/frequency combinations. The following p-state block will be changed when you select new values in the calculator and press the button.

FrequencyMHz
Core voltagemV


.
.

Package (0x06) { 0x07D0, 0xFCBC, 0x64, 0x07, 0xE0202A0C, // 2000 MHz, 1350 mV 0x020C },

. .
The lowest voltage possible depends on the quality of the CPU. To get a stable system with the lowest voltage possible, you need to lower the voltage step by step until the system begins to freeze under heavy load. The last value where the system did not freeze should be the minimum. 1250 mV at 2000 MHz works very reliable for me.

Please note that you can only modify existing p-states. You cannot add additional p-states or change the frequency of the p-states.
 
Andy (web) says:
2010-11-12 14:32:09
Great article. I'm working on doing the same thing to an Asus K8N-DRE mainboard with dual Opteron 290's. I'm tired of my system constantly running at full speed!

Question: Looking at your custom DSDT code, why are the P-states for CPU0 different than that of CPU1? CPU1's P-States show values like "0x0099999"... is that some kind of default value when the bios doesn't have P-States for the installed cpu?
wejp (web) says:
2010-11-12 14:57:20
Andy: Thanks.
I've just modified an existing DSDT code, so the differences were in there before my modification.
I'm not sure why the BIOS manufacturer chose to use different p-state blocks for each CPU. I guess the reason is that the second one will be ignored anyway, since the two cores on this kind of CPU cannot be set to different cpu speeds and voltages. Both cores always share the same clock frequency and voltage settings. The is true for at least this generation of CPUs. Some newer CPUs support individual settings for each core.
Andre (web) says:
2012-02-08 20:18:58
Hi Andy any progress on your opterons i am trying the same for my two opteron 285's
Andrew Elian (web) says:
2012-03-26 17:40:20
I happen to be running older hardware (DFI RX200 and an Opteron 175) and found your post to be very useful. Thanks for posting it and leaving it up.
andre (web) says:
2020-02-17 23:27:45
you give me once the possibility to scale my IBM opteron workstation THX again !

Leave a comment

Name
E-Mail
Website
Homepage
Comment