util: Add tools for dumping and inserting KBC1126 firmware images.
Change-Id: Ic521b177b9602ff042312cccaaa89371db7c5855 Signed-off-by: Iru Cai <mytbk920423@gmail.com> Reviewed-on: https://review.coreboot.org/19071 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
This commit is contained in:
parent
5f9fe7232a
commit
5fd00ce71a
|
@ -115,6 +115,8 @@ util/superiotool/superiotool
|
||||||
util/vgabios/testbios
|
util/vgabios/testbios
|
||||||
util/viatool/viatool
|
util/viatool/viatool
|
||||||
util/autoport/autoport
|
util/autoport/autoport
|
||||||
|
util/kbc1126/kbc1126_ec_dump
|
||||||
|
util/kbc1126/kbc1126_ec_insert
|
||||||
|
|
||||||
documentation/*.aux
|
documentation/*.aux
|
||||||
documentation/*.idx
|
documentation/*.idx
|
||||||
|
|
|
@ -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`.
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the coreboot project.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 Iru Cai <mytbk920423@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static void usage(const char *s)
|
||||||
|
{
|
||||||
|
printf("%s <rom file>\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;
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the coreboot project.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 Iru Cai <mytbk920423@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static void usage(const char *s)
|
||||||
|
{
|
||||||
|
printf("%s <rom file> <fw1> <fw2> <fw1 offset> <fw2 offset>\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;
|
||||||
|
}
|
|
@ -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
|
Loading…
Reference in New Issue