Hey you! Yes you! What does your computer do most of the time? If it's
like most computers, it just sits idle. Sure, some of us may have reasons
to leave our computers on all day, such as a web server, or a
mythtv box, but even then they sit idle.
Fortunately, CPU manufacturers have noticed this fact, and developed
technologies that allow processors to slow down when not being fully utilized.
On Intel chips this is called SpeedStep, and on AMD chips it's called Cool'n'Quiet.
Here's the dirty little secret you may have not known. Altough the chip you
bought may support this wonderful technology, your motherboard may not be able
to enable it. I previously had an AMD Athlon 64 3500+ in an MSI K8N Neo4
Platinum motherboard. It was really a nice combination, worked pretty zippy.
Then, when Conroe dropped at the end of July, I decided to take advantage of
the price drops and install a nice new AMD Athlon 64x2 4400+. After all, it
was just a matter of dropping the chip into the board. When I booted up
though, I was greated by something I didn't expect. The new system was drawing
65 watts more power than the old system was. Ouch. A look at the
dmesg logs showed exactly what the problem was.
Aug 2 13:06:39 spongebob kernel: [4294696.356000] powernow-k8: Found 1 AMD Athlon 64 / Opteron processors (version 1.50.4)
Aug 2 13:06:39 spongebob kernel: [4294696.360000] powernow-k8: BIOS error - no PSB or ACPI _PSS objects
That's odd, that can't be normal. I looked back further in my logs and
found this result from my Athlon 64 3500+:
Aug 1 07:10:00 spongebob kernel: [4294691.444000] powernow-k8: Found 1 AMD Athlon 64 / Opteron processors (version 1.50.4)
Aug 1 07:10:00 spongebob kernel: [4294691.444000] powernow-k8: 0 : fid 0xc (2000 MHz), vid 0x6 (1400 mV)
Aug 1 07:10:00 spongebob kernel: [4294691.444000] powernow-k8: 1 : fid 0xa (1800 MHz), vid 0x8 (1350 mV)
Aug 1 07:10:00 spongebob kernel: [4294691.444000] powernow-k8: 2 : fid 0x2 (1000 MHz), vid 0x12 (1100 mV)
Time for some fun hacking away at stuff. Apparently, this means that my
motherboard wasn't providing the correct data in the BIOS for the operating
system to know how to sleep the processor. In other words, while my processor
could be put to sleep, the motherboard didn't know how to. The specific item
that Linux was looking for is called the Differentiated System Description Table,
or DSDT for short. Lucky for us, there is a feature of Linux that lets you
override the motherboard DSDT with one you specify yourself. Wonderfuly, just
hack the DSDT and you've got powersaving! Wait, you didn't say we're going to
hack the firmware of a critical system did you? Of course I did! It's not that
hard, so let's jump right in!
First things you'll need the Intel ASL compiler/decompiler to build get your
DSDT to a readable format. Under ubuntu this is just a matter of running
sudo apt-get install iasl. Now we'll dump the DSDT to
a file and then decode it to make it human readable. Linux is nice in that it
makes the DSDT availble at /proc/acpi/dsdt
cat /proc/acpi/dsdt > custom.aml
iasl -d custom.aml
This should have created a file called custom.dsl.
Open it up with your favorite text editor and have a look around. If you're
like me and lacking support for power saving, see a block for your processors
that looks a little like this at the top:
Scope (\_PR)
{
Processor (\_PR.CPU0, 0x00, 0x00000000, 0x00) {}
Processor (\_PR.CPU1, 0x01, 0x00000000, 0x00) {}
}
The \_PR scope defines ACPI information for your
processors. As you can see here, it's completely empty. There are a couple of
ways to populate it, you can either trust the snippet of code that I'm going to
paste below, or you can try to find your own information from the ACPI4Linux DSDT
Repository. You'll need to find a DSDT for a motherboard that is
reasonably close to yours, thus if you've got a socket 939 motherboard, like I
do, you'll need to find a DSDT for a different socket 939 motherboard.
Unfortunately, their site is very difficult to navigate, so if you trust me,
you can paste the information here.
Scope (\_PR)
{
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 (0x04) // 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)
}
}
Whew, that's a lot of data. Don't ask me what it all means either, because I'm
not an ACPI expert. I had to dig around to find a DSDT that worked for me.
You'll notice that I have 4 different processor power states, which correspond
to the states for the AMD Athlon 64x2 4400+. If your processor doesn't have
four states, such as with an Athlon 64 3500+, then you'll want to comment out
the fastest state and change the line that says number of
p-states to 3 instead of 4. Anyway, your mileage may very.
You'll also notice that the first value for each p-state corresponds to the
speed in MHz. I'll leave it as an exercise to the reader what sort of damage
you can do with that.
Great, now you've got your spiffy new dsl file -- time to compile it and have
the kernel load it. Running the command iasl -tc
custom.dsl will produce a file called
custom.hex, this is your new compiled DSDT table for
inclusion in the kernel. If you're like me you might have had errors at this
point. If you're smart, you can fix them, if you're lazy you can ignore them.
I chose the latter.
Because you're running Ubuntu (right?), the kernel source tree already has
support for Cool'n'Quiet by default. But it doesn't enable drivers that
require additional firmware. Unselect Device Drivers-> Generic
Driver Options-> Select only drivers that don't need compile-time external
firmware. Then go to Power management options->
ACPI Support -> Include Custom DSDT and give it the path of your
custom.hex file you just created. Finally, rebuild your kernel with
make-kpkg clean && make --append-to-version=-customdsdt
kernel_image modules_image kernel_headers initrd. This will dump
a few new .deb files in /usr/src for you. Install them with dpkg
-i and reboot!
After the reboot your system will take lots of juice initially, that's because
a system is usually pegged during the boot sequence, but afterwards it should
drop precipitously. You can verify it's working by looking at the contents of
your dmesg output and see the following:
[17179571.504000] powernow-k8: Found 2 AMD Athlon 64 / Opteron processors (version 1.50.4)
[17179571.508000] powernow-k8: 0 : fid 0xe (2200 MHz), vid 0x6 (1400 mV)
[17179571.508000] powernow-k8: 1 : fid 0xc (2000 MHz), vid 0x8 (1350 mV)
[17179571.508000] powernow-k8: 2 : fid 0xa (1800 MHz), vid 0xa (1300 mV)
[17179571.508000] powernow-k8: 3 : fid 0x2 (1000 MHz), vid 0x12 (1100 mV)
If you see something like this, congratulations, it's working quite well. It's
not perfect, I still get a few little error, but at least Cool'n'Quiet works
now. My system idle power dropped from 195W to 134W. Going off the rough
estimate the 1W of power costs about $1/yr, that's a saving of $61! Nice. Of
course, it took me about four hours to figure this out, which means it may have
not been worth it, espeically if I apply a discount rate to my future savings,
but I digress. If you use external proprietary drivers, it might be worth your
time to install module-assistant so those get compiled too. Otherwise you'll
boot your nice new kernel without support for 3d acceleration and all that
jazz.
After all of this, I found a nice package that allows for the placement of the
DSDT in the initrd. Using this
method, you don't need to recompile your kernel every time you change your DSDT
-- instead you just rebuild your initrd. Very nice for situations where the
drivers aren't always built as part of the kernel build process, such as IVTV.
Happy Hacking!