Steps to modify the T60 fan rpm levels used in the acpi thermal management
----------------------------------------------------------------------------------------------
I came into a T60 recently (widescreen model with ATI Mobility Radeon X1400). Like many other Thinkpads (including the T22, T41, R51, and G40 with which I have extensive experience), there appears to be a bug in the way the thermal management implements hysteresis. On power-on, the fans run reasonably (at least for the first 0-40 minutes or so), but as time goes on, especially after several hours, the fans are on more or less incessantly (with temps fine, and even with the pc idling). If the fans are reasonably quiet, while somewhat annoying, its ok. With the T60, however, the fan noise is insanely loud. The lowest rpm is around 3000 which is WAY to high, period. The T60, in my judgment, is a bit of a dog of a pc, a far cry from pre-T5X thinkpads; the keyboard is cheap/flimsy, the thinklite shines in your face (not on the keyboard), the pc beep is way to loud (clearly a hw and/or ec sw bug with the hd audio), etc.
I actually started to design a simple circuit (using ff's and a pll) to increase the frequency of the tach signal to spoof the embedded controller (ec) into thinking the fan was running faster than it really was. Such a circuit is pretty easy to prototype, but to make it robust, flexible, and small enough to fit inside the laptop is difficult to do at home; hence, this is really a dead end.. much better to try and solve this issue in software. So, on the hunch that the ec uses a table of 'target' rpms to drive fan speed management, I decided to look at the ec in earnest. It turns out that the offending table exists, and is easily modified.
Of course, this is a DANGEROUS (and possibly unwise) thing to do; if you get a checksum wrong, the pc may not boot, and changing rpm values may have unintended consequences which render the pc unusable. Nevertheless, I decided to do it, and all appears to be well. In addition to the rpm table mod, I also use a kernel module to set up trip points to handle fan control (not optimum, and with obvious pitfalls, eg, a kernel oops => no fan control..), but, for me, acceptable (been doing this since the T22). My T60 temps are now steady at 47-50 C at idle/editing, 49-52 C under moderate activity (eg, web browsing), 55-59 C on Hulu, and 55-69 C under heavy loads (eg, a 5-6 hr gcc build). I consider these temps entirely satisfactory, in-line with (or better than) other thinkpads I've used, and indescribably more pleasant than having the pc constantly roaring at 3000+ rpm, at 45-47 C, while you edit a text file in vi, under kde.
Anyone considering a firmware mod such as follows should take the time to fully understand what is being done, and fully appreciate the consequences if something goes amiss.. I recommend this procedure to no one, will not post code, and bear no responsibility in any way for anything which occurs as a result of anyone carrying out this procedure..
So, here are the steps I followed (under linux):
(1) Extract the ec code, and for convenience, rename the ec:
$ 7z x -o7iuj17uc.iso-files 7iuj17uc.iso
$ cd 7iuj17uc.iso-files
$ cp {\$,}01AC000.FL2
(2) Orientation:
$ hd 01AC000.FL2 | more .. etc
Here, alias hd='hexdump -e '\''"%06.6_ax " 16/1 "%02X "'\'' -e '\''" " 16/1 "%_p" "\n"'\'''
A quick look at the hex dump reveals the following:
000000 - 002000 (0-8k ) largely empty header terminated w/long 00-block
002000 - 010000 (8-64k) ff-block (part of header perhaps)
010000 - 02ffff (128k) probably data+code (ff-block at 027038)
030000 - eof loadable modules and related (module signatures like: 00 31 31 00 42 1B 05 .. are seen, ie, 27-byte LZ5-compressed modules)
(3) Pull out code+data portion of ec for dis-assembly:
$ dd if=01AC000.FL2 of=01AC000.FL2.bin ibs=1 skip=$((0x010000)) count=$((0x17030))
(4) Build binutils-2.16.1.tar.bz2 with configure option --target=h8300-coff and dis-asemble:
$ h8300-coff-objdump -Dmh8300s -bbinary 01AC000.FL2.tmp2 > 01AC000.FL2.binutils.s
I think binutils-2.16.1 is the last verison with h8s support.
After having examined the Renesas doc at
http://am.renesas.com/products/mpumcu/h ... tation.jsp (REJ09B0331-0500 Sep.14.06 Hardware Reference and REJ09B0139-0400 Feb.24.06 Software Manual), we find the following from the disassembled code:
010000 - 0101bf
interrupt vector table; 010000 is the h8s program zero-base address H'00000000; 0x000013f0 is the Power-on/Reset vector => Power-on/Reset first instruction is at 0113f0
011300
code validation (zero-sum LRC); this code is straightforward; from it we find that blocks [010000, 010400) and [011000, 030000) are validated; block [010400, 011000) is very likely checksummed as well (it sums to zero), but is not validated here (a code bug !?)
and from the validation code at 011300:
0103fe - 0103ff probably block [010000, 010400) checksum
010400 - 010401 probably block [010400, 011000) checksum (but not validated at 011300 as noted above)
02fffe - 02ffff probably block [011000, 030000) checksum
(5) In the hope/belief that somewhere in the code there lies an rpm table, and, given a knowledge of what these values are, eg, by collecting some data like:
level reported rpms (acpi 0x84-0x85)
----- ------------------------------
128: 3294 3315 3298 3352
1: 2807 2851 2864 2847 2976 2936 2914 2932 3009 3005
2: 2819 2817 2786 2971
3: 3437 3438
4: 3627 3570 3435
5: 3494 3500
6: 3893 3886 3932
7: 3971
its easy enough to write some C to romp through the ec file looking for clusters of appropriate values. For example, read the the file word-by-word, looking for three contiguous words each of which is in, eg, the ranges 2700-3100, or 3300-3600, or 3700-4200. There are many ways to do this sort of thing, but amazingly, the foregoing worked like a charm (only a small number of hits, all but several being easy to rule out).
Thus, the table of fan rpms used for fan control is found to be at 0110b4 in the ec file. Each entry is 10 bytes:
$ hd -s 0x0110b4 01AC000.FL2|head -8
0110b4 04 20 0A BE 0B 54 0D DE 7F FF 04 20 0A 8C 0B 22 . .¾.T.Þ.ÿ. ..."
0110c4 0D 16 7F FF 04 20 0B EA 0D AC 0E D8 7F FF 04 20 ...ÿ. .ê.¬.Ø.ÿ.
0110d4 0B EA 0C E4 0E D8 7F FF 04 20 0B 86 0D 7A 0E D8 .ê.ä.Ø.ÿ. ...z.Ø
0110e4 7F FF 04 20 0B 86 0C E4 0E D8 7F FF 04 20 0B B8 .ÿ. ...ä.Ø.ÿ. .¸
0110f4 0D DE 0F 0A 7F FF 04 20 0B B8 0D DE 0F 0A 7F FF .Þ...ÿ. .¸.Þ...ÿ
011104 04 20 0B B8 0D AC 11 30 7F FF 04 20 0B B8 0D AC . .¸.¬.0.ÿ. .¸.¬
011114 11 30 7F FF 04 20 0B B8 0D 7A 0E D8 7F FF 04 20 .0.ÿ. .¸.z.Ø.ÿ.
011124 0B B8 0D 7A 0E D8 7F FF 08 00 22 2A 50 39 55 3C .¸.z.Ø.ÿ.."*P9U<
and we have:
fan level: ? 1,2 3-5 6-7 ?
0110b4: 0x0420 0x0ABE 0x0B54 0x0DDE 0x7FFF ~ 1056 2750 2900 3550 32767
0110be: 0x0420 0x0A8C 0x0B22 0x0D16 0x7FFF ~ 1056 2700 2850 3350 32767
0110c8: 0x0420 0x0BEA 0x0DAC 0x0ED8 0x7FFF ~ 1056 3050 3500 3800 32767
0110d2: 0x0420 0x0BEA 0x0CE4 0x0ED8 0x7FFF ~ 1056 3050 3300 3800 32767
0110dc: 0x0420 0x0B86 0x0D7A 0x0ED8 0x7FFF ~ 1056 2950 3450 3800 32767
0110e6: 0x0420 0x0B86 0x0CE4 0x0ED8 0x7FFF ~ 1056 2950 3300 3800 32767
0110f0: 0x0420 0x0BB8 0x0DDE 0x0F0A 0x7FFF ~ 1056 3000 3550 3850 32767
0110fa: 0x0420 0x0BB8 0x0DDE 0x0F0A 0x7FFF ~ 1056 3000 3550 3850 32767
011104: 0x0420 0x0BB8 0x0DAC 0x1130 0x7FFF ~ 1056 3000 3500 4400 32767
01110e: 0x0420 0x0BB8 0x0DAC 0x1130 0x7FFF ~ 1056 3000 3500 4400 32767
011118: 0x0420 0x0BB8 0x0D7A 0x0ED8 0x7FFF ~ 1056 3000 3450 3800 32767
011122: 0x0420 0x0BB8 0x0D7A 0x0ED8 0x7FFF ~ 1056 3000 3450 3800 32767
Code examination suggests that the '1056' and '32767' values are not rpms (the may be used as lower and upper rpm bounds), but this is unimportant here; the level 1-7 values are the ones to be modified.
(6) Modify the level 1-2, 3-5, and 6-7 as desired, eg,
change: ------ -1000 -700 -200
0x0110b4 1056 2750 2900 3550 32767 <-- original values
------------ ------ 1750 2200 3350 <-- modified values
0x0110be 1056 2700 2850 3350 32767
------------ ------ 1700 2150 3150
0x0110d2 1056 3050 3300 3800 32767
------------ ------ 2050 2600 3600
0x0110c8 1056 3050 3500 3800 32767
------------ ------ 2050 2800 3600
0x0110e6 1056 2950 3300 3800 32767
------------ ------ 1950 2600 3600
0x0110dc 1056 2950 3450 3800 32767
------------ ------ 1950 2750 3600
0x0110f0 1056 3000 3550 3850 32767
------------ ------ 2000 2850 3650
0x0110fa 1056 3000 3550 3850 32767
------------ ------ 2000 2850 3650
0x011104 1056 3000 3500 4400 32767
------------ ------ 2000 2800 4200
0x01110e 1056 3000 3500 4400 32767
------------ ------ 2000 2800 4200
0x011118 1056 3000 3450 3800 32767
------------ ------ 2000 2750 3600
0x011122 1056 3000 3450 3800 32767
------------ ------ 2000 2750 3600
Perhaps, all of these needn't be modified.. perhaps only subsets are used for particular T60s.. but I suspect (based on accumulating a large table of actual rpm readings over a 2 hour period) they all play a role in the underlying thermal/fan control management algorithm (and figuring this out, if even possible, would take a whole lot more work); so modify all of them for the first cut. Before I settled on modifying the rpms in this table, I spent many hours over several weeks tracing the ec code by first locating routines referencing the table base address, then routines calling these routines, etc.
By the way, ~ 2000 rpm is inaudible, and ~ 2800 is reasonably quiet (the hd is louder).
(7) Update the checksum word for the memory block containing this table (ie, block [011000, 030000).
This is the tricky (and dangerous step). Tracing the ec code from the Power-on/Reset vector onward, one quickly finds where the code is validated. It turns out that the ec uses simple zero-sum LRC validations. The code is at file offset 0x11300 and is quite straightforward. There appear to be at least three distinct checksummed memory blocks (this is guesswork, based on simply word-summing suspected blocks of memory; if the sum is zero, it is very likely that it is checksummed), but at 0x11300 only two of them are actually validated (a possible bug in the code/or perhaps the third is validated elsewhere). For zero-sum LRCs, the sum of all words in the memory block will be zero (the checksum word being included in the sum). In principle, the checksum word could be any word in the block, or could even be obfuscated (herein lies the danger: one could inadvertently modify important data or code), but it is often at the end, sometimes at the start, or in the header (if present). For block [011000, 030000) it appears to be at 0x02fffe.
02fff0 FF FF FF FF FF FF FF FF FF FF FF FF FF FF 06 2A ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.*
Re-sum the words in block [011000, 030000) of the modified ec, write the sum to 0x02fffe, and the re-sum the result to verify correctness.
(8) Take a couple of days to mull things over, and then, re-flash, reboot, and pray..
By default bios/ec updates will not re-flash the same version (only version upgrades and downgrades (with a warning) are allowed). EC versions change much less frequently than bios versions, and even if you do a bios upgrade, if the ec version hasn't changed it won't be re-flashed.
Its easy enough to make a custom bootable iso/cd with a modified lcreflsh.bat to allow re-flashing the current ec, if, eg, changing 'flash2.exe /u' to 'flash2.exe /u /s' works.. but I'm not sure this will work (haven't tried it).
An annoying, but easy way to do the re-flash is to use windows:
(a) get the .exe version of the bios update and run it to extract the files (by default to, eg,
C:/DRIVERS/7iuj17us); do not flash yet-- you only want to extract the files at this point
(b) copy in the modified ec over the original one in C:/DRIVERS/7iuj17us
(c) open a dosbox, cd to C:/DRIVERS/7iuj17us, and issue: WINUPTP.EXE /s
The /s option turns off bios and ec version checking, so both will be re-flashed. WINUPTP.EXE /s
will run silently (unless there's a verbose option /v ?), so as long as it terminates within several
minutes without any printed errors, the flash succeeded w/o issue.
(d) reboot after WINUPTP.EXE /s terminates
(9) Sit back and enjoy the singular quietude, for the first time with this pc.