From 5fd00ce71a1bfd5cd36f9d42ab657a8e7d6c54f2 Mon Sep 17 00:00:00 2001 From: Iru Cai Date: Sun, 26 Mar 2017 12:05:32 +0800 Subject: util: Add tools for dumping and inserting KBC1126 firmware images. Change-Id: Ic521b177b9602ff042312cccaaa89371db7c5855 Signed-off-by: Iru Cai Reviewed-on: https://review.coreboot.org/19071 Tested-by: build bot (Jenkins) Reviewed-by: Arthur Heymans --- util/kbc1126/README.md | 59 +++++++++++++++++++ util/kbc1126/kbc1126_ec_dump.c | 124 +++++++++++++++++++++++++++++++++++++++ util/kbc1126/kbc1126_ec_insert.c | 108 ++++++++++++++++++++++++++++++++++ util/kbc1126/makefile | 12 ++++ 4 files changed, 303 insertions(+) create mode 100644 util/kbc1126/README.md create mode 100644 util/kbc1126/kbc1126_ec_dump.c create mode 100644 util/kbc1126/kbc1126_ec_insert.c create mode 100644 util/kbc1126/makefile (limited to 'util/kbc1126') diff --git a/util/kbc1126/README.md b/util/kbc1126/README.md new file mode 100644 index 0000000000..8f39069793 --- /dev/null +++ b/util/kbc1126/README.md @@ -0,0 +1,59 @@ +KBC1126 firmware tools +====================== + +Many HP laptops use 8051-based SMSC KBC1098/KBC1126 as embedded +controller. Two blobs can be found in the HP firmware images. The +`kbc1126_ec_dump` and `kbc1126_ec_insert` tools are used to dump the +two blobs from the factory firmware and insert them to the firmware +image. + + +Firmware format +--------------- + +We can easily find the BIOS region of the HP laptop firmware from the +HP firmware update tool, which can be downloaded from the HP +website. Now I take HP Elitebook 8470p as an example. This laptop has +a 16MB flash chip, the last 5MB of which is the BIOS region. + +I use [radare2](https://radare.org) to analyze the firmware. Open the +firmware image, and we can see 8 bytes at `$s-0x100` (`$s` means the +image size). + + [0x00000000]> x @ $s-0x100 + - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF + 0x00ffff00 fff7 0008 f700 08ff 0000 0000 0000 0000 ................ + +X86 machines map the firmware at the end of the memory address +space. These 8 bytes tell the address of the two blobs, which we call +FW1 (uses bytes 0-3) and FW2 (uses bytes 4-7). + +Let's look at FW1. The first two bytes mean the address of FW1 is +0xfff700 (these two bytes use big endian), i.e. `$s-0x900`. Byte 2 and +3 are just complements of byte 1 and 2 (in this case, +0x0008=0xffff-0xfff7). + + [0x00000000]> x @ $s-0x900 + - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF + 0x00fff700 fc07 c13e 02ff 1000 0000 0000 0000 0000 ...>............ + +Both FW1 and FW2 use the same format: the first two bytes is payload +length, then a two-byte checksum, then the payload. The payload length +and checksum are both in little endian. The checksum is +[SYSV checksum](https://en.wikipedia.org/wiki/SYSV_checksum). + + +How to use the tools +-------------------- + +`kbc1126_ec_dump` is used to dump FW1 and FW2. Run `kbc1126_ec_dump +bios.rom`, then bios.rom.fw1 and bios.rom.fw2 are generated in the +working directory. + +`kbc1126_ec_insert` will overwrite a firmware image by inserting FW1 +and FW2 in it. Please run it for its usage. You need to specify the +offsets for FW1 and FW2. Using negative offset is recommended, which +means the distance to the end of the image. For example, if we want to +insert FW1 and FW2 at `$s-0x900` and `$s-0x90000` as the hp/8470p +factory firmware to coreboot.rom, you can run `kbc1126_ec_insert +coreboot.rom bios.rom.fw1 bios.rom.fw2 -0x900 -0x90000`. diff --git a/util/kbc1126/kbc1126_ec_dump.c b/util/kbc1126/kbc1126_ec_dump.c new file mode 100644 index 0000000000..7470012799 --- /dev/null +++ b/util/kbc1126/kbc1126_ec_dump.c @@ -0,0 +1,124 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2017 Iru Cai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +static void usage(const char *s) +{ + printf("%s \n", s); + exit(1); +} + +static void FseekEnd(FILE *fp, long o) +{ + if (fseek(fp, o, SEEK_END) != 0) { + puts("fseek() error!\n"); + exit(1); + } +} + +void dump_fw(FILE *dst, FILE *src, long offset) +{ + static unsigned char buf[65536]; + + if (offset > 0) + offset -= 0x1000000; + + printf("Dumping firmware at -0x%lx...", -offset); + + FseekEnd(src, offset); + unsigned short len; + unsigned short cksum; + unsigned short _cksum = 0; + fread(&len, 2, 1, src); + fread(&cksum, 2, 1, src); + fread(buf, len, 1, src); + + for (size_t i = 0; i < len; i++) { + _cksum += buf[i]; + } + if (_cksum == cksum) { + puts("checksum ok"); + } else { + puts("checksum fail"); + exit(1); + } + + fwrite(&len, 2, 1, dst); + fwrite(&cksum, 2, 1, dst); + fwrite(buf, len, 1, dst); +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) + usage(argv[0]); + + FILE *fp = fopen(argv[1], "rb+"); + + if (fp == NULL) { + puts("Error opening file!"); + exit(1); + } + + char *basename = strrchr(argv[1], '/'); + if (basename == NULL) + basename = argv[1]; + else + basename = basename + 1; + + int len = strlen(basename); + char fn1[len + 5], fn2[len + 5]; + strcpy(fn1, basename); + strcpy(fn2, basename); + strcat(fn1, ".fw1"); + strcat(fn2, ".fw2"); + + FILE *fw1 = fopen(fn1, "wb+"); + FILE *fw2 = fopen(fn2, "wb+"); + + long romsz; + FseekEnd(fp, -1); + romsz = ftell(fp) + 1; + printf("size of %s: 0x%lx\n", argv[1], romsz); + + if (romsz & 0xff) { + puts("The ROM size must be multiple of 0x100"); + exit(1); + } + + /* read offset of fw1 and fw2 */ + char offs[8]; + FseekEnd(fp, -0x100); + fread(offs, 8, 1, fp); + + assert(offs[0] + offs[2] == '\xff'); + assert(offs[1] + offs[3] == '\xff'); + assert(offs[4] + offs[6] == '\xff'); + assert(offs[5] + offs[7] == '\xff'); + long offw1 = (offs[0] << 16) | (offs[1] << 8); + long offw2 = (offs[4] << 16) | (offs[5] << 8); + + dump_fw(fw1, fp, offw1); + dump_fw(fw2, fp, offw2); + + fclose(fp); + fclose(fw1); + fclose(fw2); + return 0; +} diff --git a/util/kbc1126/kbc1126_ec_insert.c b/util/kbc1126/kbc1126_ec_insert.c new file mode 100644 index 0000000000..6c19c0e97f --- /dev/null +++ b/util/kbc1126/kbc1126_ec_insert.c @@ -0,0 +1,108 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2017 Iru Cai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +static void usage(const char *s) +{ + printf("%s \n", s); + exit(1); +} + +static void FseekEnd(FILE *fp, long o) +{ + if (fseek(fp, o, SEEK_END) != 0) { + puts("fseek() error!\n"); + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + if (argc < 6) + usage(argv[0]); + + FILE *fp = fopen(argv[1], "rb+"); + FILE *fw1 = fopen(argv[2], "rb"); + FILE *fw2 = fopen(argv[3], "rb"); + long offset1 = strtol(argv[4], NULL, 0); + long offset2 = strtol(argv[5], NULL, 0); + + if (fp == NULL || fw1 == NULL || fw2 == NULL) { + puts("Error opening file!"); + exit(1); + } + + if ((offset1 & 0xff) || (offset2 & 0xff)) { + puts("The offsets must be aligned to 0x100"); + exit(1); + } + + long romsz; + FseekEnd(fp, -1); + romsz = ftell(fp) + 1; + printf("size of %s: 0x%lx\n", argv[1], romsz); + + if (romsz & 0xff) { + puts("The ROM size must be multiple of 0x100"); + exit(1); + } + + if (offset1 > 0) + offset1 = offset1 - romsz; + + if (offset2 > 0) + offset2 = offset2 - romsz; + + /* write two offsets to $s-0x100 */ + char offs[8]; + long os; + os = 0x1000000 + offset1; + offs[0] = os >> 16; + offs[1] = os >> 8; + offs[2] = 0xff - offs[0]; + offs[3] = 0xff - offs[1]; + os = 0x1000000 + offset2; + offs[4] = os >> 16; + offs[5] = os >> 8; + offs[6] = 0xff - offs[4]; + offs[7] = 0xff - offs[5]; + for (size_t i = 0; i < 8; i++) { + printf("%02hhx ", offs[i]); + } + puts(""); + FseekEnd(fp, -0x100); + printf("writing to 0x%lx\n", ftell(fp)); + fwrite(offs, 1, 8, fp); + + /* write fw1 and fw2 */ + char c; + FseekEnd(fp, offset1); + printf("writing to 0x%lx\n", ftell(fp)); + while (fread(&c, 1, 1, fw1) == 1) { + fwrite(&c, 1, 1, fp); + } + FseekEnd(fp, offset2); + printf("writing to 0x%lx\n", ftell(fp)); + while (fread(&c, 1, 1, fw2) == 1) { + fwrite(&c, 1, 1, fp); + } + + fclose(fp); + fclose(fw1); + fclose(fw2); + return 0; +} diff --git a/util/kbc1126/makefile b/util/kbc1126/makefile new file mode 100644 index 0000000000..48268747d6 --- /dev/null +++ b/util/kbc1126/makefile @@ -0,0 +1,12 @@ +obj = kbc1126_ec_dump kbc1126_ec_insert +HOSTCC := $(if $(shell type gcc 2>/dev/null),gcc,cc) + +all: $(obj) + +%: %.c + $(HOSTCC) -Wall -o $@ $< + +clean: + rm -f kbc1126_ec_dump kbc1126_ec_insert + +.PHONY: all clean -- cgit v1.2.3