Notes on thermal throttling, undervolting, and TDP regulation

or... how to break a laptop with Go without really trying.

Table of Contents

Background

It turns out that I know next to nothing about how modern consumer hardware works at a low level. I bought a schnazzy new ThinkPad X1C6 and while trying to iron out some brand-new-laptop Linux issues, I spent some time looking at its thermal management. It runs a little hotter than my last one, so I thought I’d have some fun and work it out.

It also turns out that although the temperature throttling and TDP tuning are well-documented, but in spite of Intel releasing their XTU tool, they don’t actually seem to document the process for altering the cpu voltage at all. There are a few smart people out there who’ve reverse engineered the MSR activity from XTU, so that’s pretty much all there is to work from.

It’s been a while since I did any bit twiddling and I’m trying to spin up on kubernetes development, so it seemed like as good any to dust off my Go chops and play around.

Model-Specific Registers (MSRs)

To get anything done with the CPUs, we need to use the MSR devices (/dev/cpu/$CPUNUM/msr)

For the MSRs that are documented, this seems to be the best place.

Throttle Temperature Control (msr 0x1a2)

Bits Field
63:28 Reserved
27:24 TCC Activation Offset (read/write)
23:16 Temperature Target (read only)
15:0 Reserved

PKG RAPL Power Limit Control (msr 0x610)

This one is split into two parts. 0x610 for the power limit control, and 0x606 for the unit definition for the settings in 0x610.

0x610

Bits Field
63:24 Reserved
23:17 Time window (s)
16 Package Clamping Limitation
15 Enable Power Limit
14:0 Package Power Limit

0x606

Bits Field
63:20 Reserved
19:16 Time Unit (Time related information (in seconds) is in unit of 1S/2^TU; where TU is an unsigned integer represented by bits 19:16. Default value is 1010b, indicating power unit is in 0.977 millisecond.)
15:13 Reserved
12:8 Energy Status Units (Energy related information (in Joules) is in unit of 1Joule/ (2^ESU); where ESU is an unsigned integer represented by bits 12:8. Default value is 01110b, indicating energy unit is in 61 microJoules)
7:4 Reserved
3:0 Power Units (Power related information (in Watts) is in unit of 1W/2^PU; where PU is an unsigned integer represented by bits 3:0. Default value is 1000b, indicating power unit is in 3.9 milliWatts increment)

Voltage Offset (msr 0x150)

Since this isn’t documented, this is where it gets a little tricky. From the available information out there by people smarter than me who’ve looked into this, here’s what I’ve been able to put together. This is probably 8 different kinds of wrong.

Example value:

63    56 55    48 47    40 39    32 31    24 23    16 15     8 7      0
10000000 00000000 00000010 00010001 11101100 11000000 00000000 00000000
   8   0    0   0    0   2    1   1    e   c    c   0    0   0    0   0
Bits Field
63 Unknown, but seems like it must be set to 1
43:40 Voltage Plane
36 Unknown, but seems like it must be set to 1
32 Write bit (set to 1 if writing value)
31:21 Voltage offset value (signed)

Next steps:

I’m writing some code to use the above.

See also: