/* 2003-Jul-06 Paul Sladen ** http://www.paul.sladen.org/thinkpad-r31/hibernation-header.html ** IBM ThinkPad R30/R31 hibernation utility. ** GPL v2. No Warranty. Blah blah. */ #define VERSION "v0.01" #include #include #include #include #include #include #include #include #include unsigned char const acr_default_header[] = { /* "ACER 0V SUSPEND.", "ACR_0V DAT" */ 'A', 'C', 'E', 'R', ' ', '0', 'V', ' ', 'S', 'U', 'S', 'P', 'E', 'N', 'D', '.', 0x00, 0x00, 0x00, 0x92, 0x42, 0x42, 0x42, 0x42, 0xaa, 0x42, 0x42, 0x42, 0x42, 'A', 'C', 'R', '_', '0', 'V', ' ', ' ', 'D', 'A', 'T', 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42 }; enum { CMOS_ADDRESS_PORT = 0x70, CMOS_DATA_PORT = 0x71, CMOS_LBA_BYTE0 = 0x54, CMOS_LBA_BYTE1 = 0x55, CMOS_LBA_BYTE2 = 0x56, CMOS_LBA_BYTE3 = 0x57, CMOS_AA_OFFSET = 0x58, CMOS_CHECKSUM = 0x59 }; enum { ACR_HEADER_LENGTH = 512, ACR_BANNER = 0, ACR_BANNER_LENGTH = 16, ACR_MYSTERY_BYTE0 = 0x13, ACR_MYSTERY_BYTE1 = 0x12, ACR_MYSTERY_BYTE2 = 0x11, ACR_MYSTERY_BYTE3 = 0x10, ACR_RAM_BYTE0 = 0x17, ACR_RAM_BYTE1 = 0x16, ACR_RAM_BYTE2 = 0x15, ACR_RAM_BYTE3 = 0x14, ACR_AA_OFFSET = 0x18, ACR_LBA_BYTE0 = 0x19, ACR_LBA_BYTE1 = 0x1a, ACR_LBA_BYTE2 = 0x1b, ACR_LBA_BYTE3 = 0x1c, ACR_FAT_NAME = 0x1d, ACR_FAT_NAME_LENGTH = 11, ACR_TIMESTAMP_BYTE0 = 0x32, ACR_TIMESTAMP_BYTE1 = 0x33, ACR_TIMESTAMP_BYTE2 = 0x34, ACR_TIMESTAMP_BYTE3 = 0x35, ACR_CHECKSUM = 0x36, ACR_VERSION_HI = 0x1fd, ACR_VERSION_LO = 0x1ff }; static unsigned char acr_checksum (unsigned int lba) { int checksum; checksum = 0; checksum -= (lba >> 0) & 0xff; checksum -= (lba >> 8) & 0xff; checksum -= (lba >> 16) & 0xff; checksum -= (lba >> 24) & 0xff; checksum -= 0xaa; checksum &= 0xff; return checksum; } static inline void do_cmos_write(int offset, unsigned char value) { outb(offset, CMOS_ADDRESS_PORT); outb(value, CMOS_DATA_PORT); } static inline unsigned int do_cmos_read(int offset) { outb(offset, CMOS_ADDRESS_PORT); return inb(CMOS_DATA_PORT); } static void do_cmos_lba_pointer(unsigned int lba) { do_cmos_write(CMOS_LBA_BYTE0, (lba >> 0) & 0xff); do_cmos_write(CMOS_LBA_BYTE1, (lba >> 8) & 0xff); do_cmos_write(CMOS_LBA_BYTE2, (lba >> 16) & 0xff); do_cmos_write(CMOS_LBA_BYTE3, (lba >> 24) & 0xff); if(lba) { do_cmos_write(CMOS_AA_OFFSET, 0xaa); do_cmos_write(CMOS_CHECKSUM, acr_checksum(lba)); } else { do_cmos_write(CMOS_AA_OFFSET, 0x00); do_cmos_write(CMOS_CHECKSUM, 0x00); } } static int fetch_cmos_lba_pointer(void) { unsigned int mathematical_checksum, detected_checksum; unsigned int lba; int magic_aa, good_checksum; if (-1 == ioperm(CMOS_ADDRESS_PORT, 2, 1)) { fprintf(stderr, "I need root permissions to read the CMOS!\n"); exit(EXIT_FAILURE); } if(magic_aa = (do_cmos_read(CMOS_AA_OFFSET) == 0xaa)) fprintf(stderr, "Magic 0xAA CMOS flag found\n"); else fprintf(stderr, "Magic 0xAA byte not found in CMOS RAM\n"); lba = 0; lba |= (do_cmos_read(CMOS_LBA_BYTE0) & 0xff) << 0; lba |= (do_cmos_read(CMOS_LBA_BYTE1) & 0xff) << 8; lba |= (do_cmos_read(CMOS_LBA_BYTE2) & 0xff) << 16; lba |= (do_cmos_read(CMOS_LBA_BYTE3) & 0xff) << 24; mathematical_checksum = acr_checksum(lba); detected_checksum = do_cmos_read(CMOS_CHECKSUM); if(good_checksum = (mathematical_checksum == detected_checksum)) fprintf(stderr, "CMOS LBA Checksum matches!\n"); else fprintf(stderr, "Mismatch: Calculated Checksum: %#02x, but read %#02x from CMOS!\n"); fprintf(stderr, "CMOS LBA sector offset: %#08x (about %'d MB)\n", lba, lba>>11); return (magic_aa && good_checksum) ? lba : 0; } /* This next function was ripped lock-stock-and-barrel from lphdisk-0.9.1 */ /* `lphdisk' is under the Artistic License. I can remove it if it causes problems. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* The following is a hack to take advantage of the ext2 "_llseek" system */ /* call to do seeks to "long long" offsets under linux (this is needed to */ /* seek to sectors beyond 4194303 (2GB)). This isn't directly supported by */ /* glibc, so we need to make our own interface function for it. We should */ /* be able to get the _NR__llseek define from linux/unistd.h. From this we */ /* can construct a wrapper to perform the right system call. */ #include /* for __NR__llseek */ typedef long long lloff_t; #ifdef __NR__llseek static _syscall5(int,_llseek, unsigned int,fd, unsigned long,offset_high, unsigned long,offset_low, lloff_t *,result, unsigned int,origin) lloff_t llseek (unsigned int fd, lloff_t offset, unsigned int origin) { lloff_t result; int retval; retval = _llseek (fd, ((unsigned long long) offset) >> 32, ((unsigned long long) offset) & 0xffffffff, &result, origin); return (retval == -1 ? (lloff_t) retval : result); } #else /* __NR__llseek */ /* Somehow, __NR__llseek wasn't in linux/unistd.h. This shouldn't ever */ /* happen, but better safe than sorry.. The best we can do is emulate it */ /* with lseek, and hope we don't get an offset that's too large (throw an */ /* error if we do) */ lloff_t llseek (unsigned int fd, lloff_t offset, unsigned int origin) { off_t offt_offset = (off_t) offset; if ((lloff_t)offt_offset != offset) { /* converting to off_t and back yields different result, indicating an */ /* overflow.. */ errno = EINVAL; return -1; } else { return lseek(fd, offt_offset, origin); } } #endif /* __NR__llseek */ int fetch_acr_header(unsigned char *header, const char *device, unsigned int lba) { int fd; struct stat properties; long long offset; if(-1 == (fd = open(device, O_RDONLY))) { fprintf(stderr, "Failed to open `%s' read only\n", device); exit(EXIT_FAILURE); } if(fstat(fd, &properties)) { fprintf(stderr, "Died on fstat()ing `%s'\n", device); exit(EXIT_FAILURE); } offset = (long long)lba * 512; if (-1 == llseek(fd, offset, SEEK_SET)) { fprintf(stderr, "Failed on llseek() reading `%s'\n", device); exit(EXIT_FAILURE); } memset(header, 0x00, 512); switch(read(fd, header, 512)) { case 512: fprintf(stderr, "Successfully read 512 bytes of hibernation header from `%s'\n", device); break; case -1: fprintf(stderr, "Failed trying to read(-1) the 512 byte header from `%s'\n", device); case 0: default: fprintf(stderr, "Failed to read() all 512 bytes from header on `%s'\n", device); exit(EXIT_FAILURE); break; } close(fd); return 0; } void parse_acr_header_from_lba(unsigned lba_offset) { unsigned char *header; unsigned int ram, lba, aa_field, checksum, version_hi, version_lo, mystery; time_t timestamp; char dos_fat_name[ACR_FAT_NAME_LENGTH+1]; char banner[ACR_BANNER_LENGTH+1]; if(!(header = malloc(512))) { fprintf (stderr, "couldn't malloc(512)\n"); return; } fetch_acr_header(header, "/dev/hda", lba_offset); if(memcmp(ACR_BANNER + acr_default_header, ACR_BANNER + header, ACR_BANNER_LENGTH)) fprintf(stderr, "The hibernation header banner (\"ACER 0V SUSPEND\") doesn't match\n"); else fprintf(stderr, "The hibernation header banner (\"ACER 0V SUSPEND\") matches, Yeah!\n"); mystery = 0; mystery |= (header[ACR_MYSTERY_BYTE0] & 0xff) << 0; mystery |= (header[ACR_MYSTERY_BYTE1] & 0xff) << 8; mystery |= (header[ACR_MYSTERY_BYTE2] & 0xff) << 16; mystery |= (header[ACR_MYSTERY_BYTE3] & 0xff) << 24; ram = 0; ram |= (header[ACR_RAM_BYTE0] & 0xff) << 0; ram |= (header[ACR_RAM_BYTE1] & 0xff) << 8; ram |= (header[ACR_RAM_BYTE2] & 0xff) << 16; ram |= (header[ACR_RAM_BYTE3] & 0xff) << 24; lba = 0; lba |= (header[ACR_LBA_BYTE0] & 0xff) << 0; lba |= (header[ACR_LBA_BYTE1] & 0xff) << 8; lba |= (header[ACR_LBA_BYTE2] & 0xff) << 16; lba |= (header[ACR_LBA_BYTE3] & 0xff) << 24; timestamp = 0; timestamp |= (header[ACR_TIMESTAMP_BYTE0] & 0xff) << 0; timestamp |= (header[ACR_TIMESTAMP_BYTE1] & 0xff) << 8; timestamp |= (header[ACR_TIMESTAMP_BYTE2] & 0xff) << 16; timestamp |= (header[ACR_TIMESTAMP_BYTE3] & 0xff) << 24; checksum = header[ACR_CHECKSUM]; aa_field = header[ACR_AA_OFFSET]; version_hi = header[ACR_VERSION_HI]; version_lo = header[ACR_VERSION_LO]; memcpy(dos_fat_name, ACR_FAT_NAME+header, ACR_FAT_NAME_LENGTH); dos_fat_name[ACR_FAT_NAME_LENGTH] = '\0'; memcpy(banner, ACR_BANNER+header, ACR_BANNER_LENGTH); banner[ACR_BANNER_LENGTH] = '\0'; printf("By following the CMOS, I found a hibernation header on disk with:\n" " Header Banner: \"%s\"\n" " RAM Size: %#010x (%d MB, %d Bytes)\n" " DOS FAT Name: \"%s\"\n" " LBA Sector: %#010x (approx. %dMB from start of disk)\n" " Timestamp: %#010x--%s" " 0xAA field: %#04x\n" " Checksum: %#04x [%s]\n" " Version: %d.%d\n" " Mystery Field: %#010x (%d)\n", banner, ram, ram>>20, ram, dos_fat_name, lba, lba>>11, timestamp, ctime(×tamp), aa_field, checksum, acr_checksum(lba)==checksum ? "Correct!" : "Wrong(!)", version_hi, version_lo, mystery, mystery ); free(header); } static unsigned char * do_acr_header (unsigned char *header, unsigned int ram, unsigned int lba, int timestamp) { memset(header, 0x00, 512); memcpy(header, acr_default_header, sizeof(acr_default_header)); header[ACR_RAM_BYTE0] = (ram >> 0) & 0xff; header[ACR_RAM_BYTE1] = (ram >> 8) & 0xff; header[ACR_RAM_BYTE2] = (ram >> 16) & 0xff; header[ACR_RAM_BYTE3] = (ram >> 24) & 0xff; header[ACR_LBA_BYTE0] = (lba >> 0) & 0xff; header[ACR_LBA_BYTE1] = (lba >> 8) & 0xff; header[ACR_LBA_BYTE2] = (lba >> 16) & 0xff; header[ACR_LBA_BYTE3] = (lba >> 24) & 0xff; header[ACR_TIMESTAMP_BYTE0] = (timestamp >> 0) & 0xff; header[ACR_TIMESTAMP_BYTE1] = (timestamp >> 8) & 0xff; header[ACR_TIMESTAMP_BYTE2] = (timestamp >> 16) & 0xff; header[ACR_TIMESTAMP_BYTE3] = (timestamp >> 24) & 0xff; header[ACR_CHECKSUM] = acr_checksum(lba); header[ACR_VERSION_HI] = 0x01; header[ACR_VERSION_LO] = 0x62; return header; } void dump_test_header() { unsigned char *header; if(!(header = malloc(512))) { fprintf (stderr, "couldn't malloc(512)\n"); return; } /* header, RAM, LBA offset, time_t */ /* do_acr_header(header, 384<<20, 0x035ab11c, 0x3eff6703); */ do_acr_header(header, 384<<20, 0x035ab11c, time(0)); fwrite(header, 1, 512, stdout); free(header); } void usage(char **argv) { printf("Usage: %s [options...]\n" "IBM ThinkPad R30/R31 hibernation utility "VERSION" (Like SleepMgr on Windows)\n" "See: http://www.paul.sladen.org/thinkpad-r31/hibernation-header.html\n" "\n" " -h Help\n" " -r Read the CMOS and display the current hibernation condition\n" " -d Dump a test-header to stdout\n" "\n", *argv); } int main(int argc, char **argv, char *env) { unsigned int lba; for(;;) { switch(getopt(argc, argv, "hrd")) { case 'r': fprintf(stderr, "%s: Reading CMOS state\n", *argv); lba = fetch_cmos_lba_pointer(); fprintf(stderr, "%s: Reading Hibernation File state\n\n", *argv); parse_acr_header_from_lba(lba); exit(EXIT_SUCCESS); break; case 'd': dump_test_header(); exit(EXIT_SUCCESS); break; case ':': case 'h': default: usage(argv); exit(EXIT_SUCCESS); break; } } exit(EXIT_SUCCESS); }