Hibernate (Suspend to disk) for the IBM ThinkPad R30/R31 MkII

On the 2003 April 1st (no joke!) I got an IBM ThinkPad R31 and I've yet to find a better system for running Debian GNU/Linux on. Everything works including the LinModem (binary only...) and hardware APM hibernation (this document); in addition the keyboard is better than most and there are 3 Mouse buttons. The bummer is that you have to install Windows 98 to run the Acer/IBM SleepMgr.exe program. It claims to run under some form of DOS aswell, but I've failed. Oh, sorry one thing doesn't work—The S-Video TV Out!

Creating it with Windows

Aftering installing Windows 98 over the remains of the IBM Rescue partition, I initially did some work on reverse-engerineering the system during June 2003. Then, at the start of May 2004 I decided I wanted to repartition my laptop disk and so I needed to write a program that could produce the magic file that I depend on for hibernation—my laptop has uptimes of over 30-days of being powered on between reboots (about 3-4 months in real-time).

The SleepMgr (Sleep Manager) program creates a magic file in the root of one of your FAT16 or FAT32 partitions name `acr_0v.dat' which stands for ``Acer Zero-Volt Suspend'' (ie, it draws no voltage when powered off). Some other BIOS hibernate systems depend on it being a FAT file-system because they intelligently read the filesystem. With the Acer/IBM filesystem this does not appear to be the case.

When creating the hibernation file SleepMgr does lots of work to ensure the file is contiguous. There is a reason for this; a value is stored in the CMOS which is 28-bit pointer to the start of the file on the physical disk. This is the number of sectors from the start of the hard-disk which means that copying or duplicating the `acr_0v.dat' file does not work—it is the contents on the physical disk that matter.

Programs

There are copies of SleepMgr.exe at the location below (there are about 3 versions):

There is code that I have written for GNU/Linux called `acrdisk', which can setup up the NVRAM and create the necesary partition headers (md5sum the same as SleepMgr!) But it does not configure the BIOS yet, I'm currently stuck with that!

CMOS RAM:

Key: Offset Generate-able Copy-able Dunno Diff'ed Hex Description Ascii

The CMOS (Non-Volatile RAM) is exposed under Linux via `/dev/nvram' driver. Dumping the CMOS via I/O commands yields 128-bytes of data but the `nvram' device is only 114-bytes long, this is because the first 14-bytes map to the Real-Time Clock (RTC) and are hidden by the driver. The offsets in the dump below are from reading out the offsets before I realised that `/dev/nvram' accomplished the same thing alot more easily (and with locking and automatic checksumming!).

An advantage of writing and reading from `/dev/nvram' is that I can replace it with a normal file for testing purposes—this has proved very useful. After various bits of ignoring how other people believed the system worked, I dug the following reference in the CMOS (subtract 14 for the offset in `/dev/nvram'):

-00000050  xx xx xx xx b3 4b 50 00  00 08 xx xx xx xx xx xx  |................|
+00000050  7b 77 37 00 1c b1 5a 03  aa 2c 80 08 01 42 20 31  |................|
                       ^^^^^^^^^^^  -- ^^                                      
                       le: LBA Off     Checksum? [matches file]                

So the `/dev/nvram' offsets that matter are:

Checksum

I finally understand the checksum and why it is so. The full 128-byte of the CMOS is checksummed by adding up each byte and the result must equal zero. The checksum at 0x4a is exactly the same and has the cunning effect that across the 6 bytes used for hibernate the net-difference is still zero meaning that the global CMOS checksum need not be updated!

On-Disk Header:

So, following that pointer in the CMOS we can find the start of the hibernation block; to make sure that we've found it, the first 512-bytes are a 1-sector header block. This can be used to double check that the BIOS has found the right place and it is correctly setup. In addition is contains information on the amount of space allocated.

 00000000  41 43 45 52 20 30 56 20  53 55 53 50 45 4e 44 2e  |ACER 0V SUSPEND.|
           ------------------------------------------------                    
           A  C  E  R     0  V       S  U  S  P  E  N  D  .                    

-00000010  00 00 00 92 10 00 00 00  aa 63 ce 8a 00 41 43 52  |........?c?..ACR|
+00000010  00 00 00 92 18 00 00 00  aa 1c b1 5a 03 41 43 52  |........?.?Z.ACR|
           -- ^^^^^^^^^^^ --------  ^^ ^^^^^^^^^^^ --------                    
           \0  RAM bytes          Flag LBA offset   A  C  R                    


 00000020  5f 30 56 20 20 44 41 54  ff ff 00 00 ff ff 00 00  |_0V  DAT??..??..|
           -----------------------  ----------- -----------                    
           _  0  V        D  A  T                                              

-00000030  00 00 ed f1 d4 3e 9b 00  00 00 00 00 00 00 00 00  |..???>..........|
+00000030  00 00 03 67 ff 3e 2c 00  00 00 00 00 00 00 00 00  |...g?>,.........|
                 ^^^^^^^^^^^ ^^                                                
                 Timestamp   Checksum [Same as BIOS?.  512-sum(0x32..0x35)]    


 00000xxx  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
           -----------------------  -----------------------                    
           [rest of header is padded with nulls]                               


 000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 01 00 62  |...............b|
           -----------------------  ----------- ----- -----                    
                                                Version Num?                   

Explanation of header fields

The next header...


After the on-disk header, there is initially a piece of firmware dumped...

00000000  eb 36             jmp short 0x38
00000002        00 00 38 00 00 01  86 ad 94 06 00 41 43 52  |.6..8........ACR|
                      ^^^^^           ^^^^^^^^^^^ ^^^^^^^^
	              Header?         Length      A  C  R

00000010  30 30 31 30 30 ad 94 06  00 00 38 00 05 01 02 00  |00100.....8.....|
          ^^^^^^^^^^^^^^ ^^^^^^^^^^^^    ^^^^^ ^^
	  0  0  1  0  0  Length          Start? Digits?

00000020  00 03 00 04 e8 5d 00 cf  00 03 00 04 e0 0d ae 0b  |.....]..........|

00000030  00 00 00 00 00 00 00 00                           |........`....t =|

00000038  60                pusha
00000039  1e                push ds
0000003a  06                push es
0000003b  0b c0             or ax,ax
0000003d  74 20             jz 0x5f
0000003f  3d 01 00          cmp ax,0x1
00000042  74 20             jz 0x64
00000044  3d 02 00          cmp ax,0x2
00000047  74 20             jz 0x69
00000049  3d 03 00          cmp ax,0x3
0000004c  74 20             jz 0x6e
0000004e  3d 04 00          cmp ax,0x4
00000051  74 20             jz 0x73
00000053  3d 80 00          cmp ax,0x80
00000056  74 20             jz 0x78
00000058  3d 81 00          cmp ax,0x81
0000005b  74 20             jz 0x7d
0000005d  eb 21             jmp short 0x80
0000005f  e8 23 00          call 0x85
00000062  eb 1c             jmp short 0x80
00000064  e8 e1 01          call 0x248
00000067  eb 17             jmp short 0x80
00000069  e8 4e 02          call 0x2ba
0000006c  eb 12             jmp short 0x80
0000006e  e8 73 02          call 0x2e4
00000071  eb 0d             jmp short 0x80
00000073  e8 98 05          call 0x60e
00000076  eb 08             jmp short 0x80
00000078  e8 59 07          call 0x7d4
0000007b  eb 03             jmp short 0x80
0000007d  e8 86 07          call 0x806
00000080  07                pop es
00000081  1f                pop ds
00000082  61                popa
00000083  cb                retf

Trying things out




import time
time.localtime(0x3eff6703)
time.localtime(0x3ed4faed)
(2003, 6, 29, 23, 24, 3, 6, 180, 1)
(2003, 5, 28, 19, 7, 41, 2, 148, 1)

0x3e 0011 1110

0xf  1111
0xe  1110
0xd  1101
0xc  1100
0xb  1011
0xa  1010
0x9  1001
0x8  1000
0x7  0111
0x6  0110
0x5  0101
0x4  0100
0x3  0011
0x2  0010
0x1  0001
0x0  0000

print hex((0x0 - 0x63 - 0xce - 0x8a - 0x00 - 0xaa) & 0xff)

0x

print 0x00edf1d4 15593940
print 0xd4f1ed00 -722342656
print 0x000367ff 223231
print 0xff670300 -10026240

>>> hex(0xaa-0x92)
'0x18'

    print hex(512-0xb3+0x4b+0x50+0xaa+0x08)

>>> hex(0x1c+0xb1+0x5a+0x03+0xaa+0x2c)
'0x200'
>>> hex(512-0x1c-0xb1-0x5a-0x03-0xaa)
'0x2c'
>>> 

0x367ff3e-0x35ab11c

>>> 0x367ff3e-0x35ab11c
871970
>>> (0x367ff3e-0x35ab11c)*512
446448640



  

From some Googling, it looks like this hibernation setup may well be the same as the IBM ThinkPad 310/310E/315/315E/i1400/i1100. These are presumably other models that have come via the same Acer/Twainese OEM supply chain in the past.

>>> hex(0x1c+0xb1+0x5a+0x03) '0x12a' >>> hex(0x1c+0xb1+0x5a+0x03-0x2c) '0xfe' >>> hex(0x1c+0xb1+0x5a+0x03-0x2c^0xff) '0x1' >>> iSeries (IBM) Thinkpad 1161 IBM THINKPAD i 1450 TP 310E/ED,315E/ED ThinkPad 310E ``Upon restoring system from 0V suspend, you need to wait a while (about 10 seconds)''