Open q60 opened 3 years ago
I'm unsure if the Embedded controller chip in chips.txt is really correct. The L420 / L520 had a ITE8502 EC, but not the L430/L530. I have a L530 here, which should be the same hardware, and it actually contains a Nuvoton NPCE885G EC (also according to the schematics: http://laptop-schematics.com/view/8924/ ). This would be a controller with CR16CPlus CPU (CompactRISC), which also matches the detected architecture of the EC code in the $01D4000.FL1 BIOS flash file. Firmware starts at offset 0x4081D0 in BIOS image file starting with "IN", shortly followed by the Copyright string "Copyright 1996-1999, all rights reserved Insyde Software Corp.".
There seems to be a thread a Thinkpad forum about the X210 EC, which seems to be the same model: https://forum.thinkpads.com/viewtopic.php?p=837219#p837219 There are 2 repositories with tools for EC patching and Checksum calculation for X210:
https://github.com/harrykipper/x210 https://github.com/jwise/x2100-ec
Maybe these informations are helpful for someone knowledgeable enough to patch the firmware. Some docs about battery authentication: https://www.ti.com/lit/pdf/slua346
Some more information: The BIOS Check for Battery is in Section_PE32_image_CC71B046-CF07-4DAE-AEAD-7046845BCD8A_LenovoVideoInitDxe.efi_body.bin
Maybe useful for reversing to know command?
if (EcIoDxe->Command(EcIoDxe, 0x88) & 1)
{
v3 = EcIoDxe->Command(EcIoDxe, 0xC2);
if ( v3 < 0 && !(v3 & 0x20) )
{
((void (__fastcall *)(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *))EfiHandle->ConOut->ClearScreen)(EfiHandle->ConOut);
sub_2850(15i64);
sub_2864(0i64, 0i64);
v5 = L"This system does not support batteries that are not genuine Lenovo-made or \n"
"\rauthorized. The system will continue to boot, but may not charge unauthorized \n"
"\rbatteries. \n"
"\r\n"
"ATTENTION: Lenovo has no responsibility for the performance or safety of \n"
"\runauthorized batteries, and provides no warranties for failures or damage \n"
"\rarising out of their use. \n"
"\r\n"
"Press the ESC key to continue.";
if ( v3 & 0x50 )
v5 = L"The battery installed is not supported by this system and will not charge.\n"
"\rPlease replace the battery with the correct Lenovo battery for this system. \n"
"\rPress the ESC key to continue.";
sub_287C(v5);
For better understanding of Command sequence, here is what EcIoDxe (Section_PE32_image_114CA60C-D965-4C13-BEF7-C4062248E1FA_EcIoDxe.efi_body.bin
) does:
#define EC_OEM_DATA 0x68
#define EC_OEM_SC 0x6c
/* EC_SC input */
#define EC_SMI_EVT (1 << 6) // 1: SMI event pending
#define EC_SCI_EVT (1 << 5) // 1: SCI event pending
#define EC_BURST (1 << 4) // controller is in burst mode
#define EC_CMD (1 << 3) // 1: byte in data register is command
// 0: byte in data register is data
#define EC_IBF (1 << 1) // 1: input buffer full (data ready for ec)
#define EC_OBF (1 << 0) // 1: output buffer full (data ready for host)
/* EC_SC output */
#define RD_EC 0x80 // Read Embedded Controller
#define WR_EC 0x81 // Write Embedded Controller
#define BE_EC 0x82 // Burst Enable Embedded Controller
#define BD_EC 0x83 // Burst Disable Embedded Controller
#define QR_EC 0x84 // Query Embedded Controller
EFI_STATUS __fastcall FlushInput(BYTE ecsc, BYTE ecdata)
{
BYTE buf;
EFI_STATUS ret;
while (1)
{
ret = CpuIo->Io->Read(NULL, EfiCpuIoWidthUint8, ecsc, 1, &buf);
if (!(buf & EC_OBF)) break;
CpuIo->Io->Read(NULL, EfiCpuIoWidthUint8, ecdata, 1, &buf);
}
return ret;
}
EFI_STATUS __fastcall WaitReady(BYTE ecsc)
{
int i;
BYTE buf;
EFI_STATUS ret;
for(i = 33; i; i--)
{
ret = CpuIo->Io->Read(NULL, EfiCpuIoWidthUint8, ecsc, 1, &buf);
if (!(buf & EC_OBF)) break;
gBootSvc->Stall(30);
}
return ret;
}
EFI_STATUS __fastcall WaitForAnswer(BYTE ecsc)
{
int i;
BYTE buf;
EFI_STATUS ret;
for(i = 33; i; i--)
{
ret = CpuIo->Io->Read(NULL, EfiCpuIoWidthUint8, ecsc, 1, &buf);
if (!(buf & EC_IBF)) break;
gBootSvc->Stall(30);
}
return ret;
}
BYTE __fastcall EcOemRead(BYTE ecsc, BYTE ecdata, BYTE cmd)
{
BYTE buf;
FlushInput(ecsc, ecdata);
WaitReady(ecsc);
buf = RD_EC;
CpuIo->Io->Write(NULL, EfiCpuIoWidthUint8, ecsc, 1, &buf);
WaitForAnswer(ecsc);
CpuIo->Io->Write(NULL, EfiCpuIoWidthUint8, ecdata, 1, &cmd);
WaitForAnswer(ecsc);
CpuIo->Io->Read(NULL, EfiCpuIoWidthUint8, ecdata, 1, &buf);
return buf;
}
BYTE __fastcall Command(void *this, BYTE cmd)
{
return EcOemRead(EC_OEM_SC, EC_OEM_DATA, cmd);
}
As I'm not experienced with Firmware Reverse engineering, Ghidra and
embedded stuff, I was unable to find the battery functions
in the L530 firmware.
I tried to find some clues in the firmware in order to find out
where these battery authentication functions are, but they seem to
work differently compared to X210 firmware.
In X210 firmware, there are command packets for battery communication
(struct BATT_REQ_QUERY
, see bat_state_Queries
and bat_prop_queries
)
that get sent via SMBUS. But in L530 firmware, I unfortunately
cannot find such a mechanism.
There also isn't much documentation on how the keyboard scancodes
should map (to fix INS/DEL/POS1 etc. keys with 7-row keyboard).
I found some tables referenced in scancode_do_trans
, but I don't
know how to map the values.
So as I don't have the expertise, I'm dumping my ghidra DB in the
hope that someone can pick it up and fix keyboard and battery.
Interestingly, there are 2 firmware images in the BIOS image:
1 starts at 0 (containing String G3HT40WW(1.14)
) and one starting
at 0x8000 (NB: Load address 20100 !). I guess the second one is
the real one, even though, they seem somewhat similar.
Would be interesting what the first one is...?
In Ghidra project, there are various parts. The Project is derived from https://github.com/jwise/x2100-ec/blob/master/notes/x2100-ec.gar by @jwise and I added the 2 firmware images from the dump:
File | Description |
---|---|
ec.bin | This is the second Firmware from L530. This should be worked on. |
fw-top.bin | This is the first Firmware from L530. Don't know much about it. |
x2100-ec.bin-patched | This is the x2100 firmware similar to L530, which I was comparing with |
x2100-ec.bin | x2100 Working base, can be ignored |
newec-gpe-patch.bin | New x2100 embedded controller FW? Not similar to L530 FW |
Hopefully, someone can find out the inner workings. Project: x2100-ec_2021_12_20.zip Firmware: fw.zip
Probably worth finding the EC command entry point and figuring out where it handles 0x88 and 0xC2. If you can replicate those results using something like https://github.com/exander77/x2100-ec-sys then you can pretty easily figure out what those entry points read, and then patch out whatever checks generate them (and whatever else refers to that memory...).
Updated Ghidra project, see bat_authenticated
function
My guess is that battery authentication routine is right before main():
**************************************************************
* FUNCTION *
**************************************************************
long __ptrcall2 bat_authenticated(uint * param_1, uint *
long R1R0:4 <RETURN>
uint * R3R2:4 param_1
uint * R5R4:4 param_2
undefined4 Stack[0x0]:4 param_1_00 XREF[1]: 00031960(*)
bat_authenticated XREF[1]: FUN_0003161a:0003176e(c)
00031950 e7 01 push $0x7, R7, ra
00031952 bf 60 ea ff ADDD $0xffea,SP
00031956 00 05 28 00 MOVD $0x28,R1R0
0003195a f0 61 ADDD SP,R1R0
0003195c 27 55 MOVD param_1,R8R7
...
So maybe just patching it to:
00031950 e7 01 push $0x7, R7, ra
00031952 10 5a MOVW $0x1,R0
00031954 e7 03 popret $0x7, R7, ra
works. Any volunteers? ;-)
So I had a look at the Keyboard handling routines and found out where they keyboard layout table lies and how to edit it. The results are plenty tables that might become handy for you to patch your own keyboard layout.
In Firmware G3HT40WW(1.14) there are 4 layout tables located at offset 0x39916
. They each have the following format (I don't count the prefix length byte in offset so that you have a "clean" keyboard layout offset matrix for the following tables):
Offset | Length | Description |
---|---|---|
Prefix | 0x01 | length of table at offset 0xa2 in bytes |
0x00 | 0x90 | Mapping table for the keys 0-144 |
0x90 | 0x12 | bitfield, specify whether to map a key via following Fn Mapping-table or not |
0xa2 | Fn-Modifiers enabled in mask at offset 0x90, Array of 2 bytes per entry: [Keypress, Fn-Keypress] |
Table 0 Unknown, maybe key table used on BIOS start screen..? Table 1 is the default table and the one we are interested in. It starts at offset 366BC Table 2 Unknown, can be selected with command 91h, C2h Table 3 Unknown
Here is the default layout:
Prefix: 54
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 0E 05 0A 2E 36 55 16 4E 18 00 A2 A1 00 00 00 8C
00000010 16 1E 26 25 3D 3E 46 45 1A 1E 1C A0 A3 00 00 00
00000020 15 1D 24 2D 3C 43 44 28 6A 00 04 00 00 00 00 00
00000030 0D 58 0C 2C 35 5B 14 54 66 82 02 00 00 00 88 00
00000040 1C 24 23 2B 3B 26 4B 4C 5D 00 00 9D 00 00 00 00
00000050 76 61 0E 34 33 12 64 52 10 00 08 00 A7 8A 00 00
00000060 1A 22 21 2A 3A 41 49 5D 5A 00 06 A4 00 00 89 8D
00000070 67 8E 00 22 31 51 13 4A 20 A9 A8 A5 A6 8B 00 00
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000090 44 01 00 07 80 04 44 04 22 04 24 05 00 04 08 01
000000A0 00 00 C1 C1 10 B0 C3 C3 30 B1 C5 C5 30 B2 C7 C7
000000B0 10 B3 C9 C9 30 B4 06 CB 10 C2 04 CD 10 C3 0C CF
000000C0 10 C4 03 D1 10 C5 0B D3 10 C6 80 D5 10 C7 0A D7
000000D0 30 BB 01 D9 30 BC 09 DB 30 B9 78 DD 10 B8 07 DF
000000E0 30 BA 29 E1 10 B6 32 E3 10 D0 1B E5 10 D1 42 E7
000000F0 10 D2 4D E9 10 D3 00
Per default, a key in the table (assignment HW-scancode of 7-row Keyboard to keycode location in table will be shown in the following table) is roughly assigned to the specified scancode of Set 2. The EC contains the standard translation table to Set 1 and the CPU-Translation table. For details on the tables, see here. Thsi is pretty much standard stuff, EC will take care of Make and Break codes accordingly. For codes >=80, there is a mapping table at offset 35f7c which contains of 2 bytes per entry. First byte is scancode and second byte is index in function pointer table at 34e6c which specifies on how to translate the table entry into a keycode. For you to create your keyboard layout, here is a list of some important "special" keycodes that are provided by this table:
Int. Code | Set 2 Scancode | Description | Keycode |
---|---|---|---|
80 | 83 | F7 | 80 |
81 | E0 5A | Keypad Enter | 81 |
82 | E0 1F | Left GUI | 82 |
83 | E0 27 | Right GUI | 83 |
84 | E0 2F | App | 84 |
85 | E0 37 | Keyboard Power | 85 |
86 | E0 3F | System Sleep | 86 |
87 | E0 5E | System Wake | 87 |
91 | E1 14 77 E1 F0 14 F0 77 | Pause | 9E |
92 | E0 7E E0 F0 7E | Ctrl+Pause | 9E |
93 | 84 | Alt+SysRq | 9D |
94 | E0 7C | KP-* | 9C |
95 | KP-* | 9C | |
96 | E0 4A | KP-/ | 9F |
97 | KP-/ | 9F | |
98 | E0 70 | Insert | A0 |
99 | Insert | A0 | |
9A | Insert | A0 | |
9B | E0 71 | Delete | A1 |
9C | Delete | A1 | |
9D | Delete | A1 | |
9E | E0 6C | Home | A2 |
9F | Home | A2 | |
A0 | Home | A2 | |
A1 | E0 69 | End | A3 |
A2 | End | A3 | |
A3 | End | A3 | |
A4 | E0 7D | PgUp | A4 |
A5 | PgUp | A4 | |
A6 | PgUp | A4 | |
A7 | E0 7A | PgDn | A5 |
A8 | PgDn | A5 | |
A9 | PgDn | A5 | |
AA | E0 6B | Left | A6 |
AB | Left | A6 | |
AC | Left | A6 | |
AD | E0 75 | Up | A7 |
AE | Up | A7 | |
AF | Up | A7 | |
B0 | E0 72 | Down | A8 |
B1 | Down | A8 | |
B2 | Down | A8 | |
B3 | E0 74 | Right | A9 |
B4 | Right | A9 | |
B5 | Right | A9 | |
B6 | E0 41 | Reply | |
B7 | E0 49 | ? | |
B8 | E0 3B | Stop | |
B9 | E0 34 | Play/Pause | |
BA | E0 23 | Mute | |
BB | E0 32 | Volume up | |
BC | E0 21 | Volume down | |
BD | E0 48 | ||
BE | E0 10 | Scan prev. Track | |
BF | E0 3A | WWW Home | |
C0 | E0 38 | WWW Back | |
C1 | E0 30 | WWW Forward | |
C2 | E0 28 | WWW Stop | |
C3 | E0 20 | WWW Refresh | |
C4 | E0 18 | WWW Favorites | |
C5 | E0 2B | Calculator | |
C6 | E0 40 | My Computer | |
C7 | E0 50 | Media select |
Please note that the Int. Code is used only internally. Codes >9B are sent through another mapping table at offset 34e88. So as a user, the Keycode column is significant, because it incorporates said mapping table. The codes are only valid up to A9, because at AA, there starts another table for the NumLock assignments which can be found at offset 35f4a:
Keycode | No-NL-Key | No-NL Scancode | NL-Key | NL Scancode |
---|---|---|---|---|
AA | 7 | 3D | KP-7 / Home | 6C |
AB | 8 | 3E | KP-8 / Up | 75 |
AC | 9 | 46 | KP-9 / PgUp | 7D |
AD | U | 3C | KP-4 / Left | 6B |
AE | I | 43 | KP-5 | 73 |
AF | O | 44 | KP-6 / Right | 74 |
B0 | J | 3B | KP-1 / End | 69 |
B1 | K | 42 | KP-2 / Down | 72 |
B2 | L | 4B | KP-3 / PgDn | 7A |
B3 | M | 3A | KP-0 / Ins | 70 |
B4 | .> | 49 | KP-. / Del | 71 |
B5 | /? | 4A | KP-/ | 9F |
B6 | ;: | 4C | KP-+ | 79 |
B7 | 0 ) | 45 | KP-* | 7C |
B8 | P | 4D | KP-- | 7B |
B9 | ENTER | 5A | KP Enter | 81 |
BA | - _ | 4E | KP-- | 7B |
BB | [{ | 54 | KP Enter | 81 |
BC | /? | 4A | KP-+ | 79 |
BD | ;: | 4C | KP-- | 7B |
BE | 0 ) | 45 | KP-/ | 9F |
BF | P | 4D | KP-* | 7C |
Now in case that there is a "special" command needed that cannot be covered by a scancode above or if there is a FN-Key combination, the key cannot be identified by 1 byte in the first table. In this case, a bit is set in the long bitfield at offset 0x90 in the key table. Each bit corresponds to a key, so bit 1 is the first key from the table, bit 2 the second, etc.
So the bit is determined by: scancode_table[0x90+(offset>>3)]
, i.e. to set a bit in this table:
scancode_table[0x90+(offset>>3)]|=(1<<(offset&7))
If this bit is set, the number in the first table isn't the scancode but an index into the table at offset A2.
As written, a table entry in the table at A2 consists of 2 bytes:
1) Scancode for normal keypress
2) Scancode for FN + key combination
Now there is a special handling in this table for keycodes >C0. If a code is >C0, the specified entry minus C0 is an index into the same table at A2 which then specifies a "command" code, as I call it. Command codes are identified by the first entry & 0x1F being 0x10 and the second being the command. The "commands" are the following (table not complete, if you feel like it, please complete empty/unknown entries):
Break Command codes: Command | Description | Set 2 Scancode |
---|---|---|
B0 | Mute | E0 F0 23 |
B1 | Vol Down | E0 F0 21 |
B2 | Vol Up | E0 F0 32 |
B3 | Mic Shut | |
B4 | ThinkVantage | |
B7 | Stop | E0 F0 3B |
B8 | Play/Pause | E0 F0 34 |
B9 | Scan prev. track | E0 F0 15 |
BA | Scan next track | E0 F0 4D |
BD | Num Lock | F0 77 |
D1 | SysReq | F0 84 F0 11 |
D2 | ScrLk | F0 7E |
Make Command codes: Command | Description | Set 2 Scancode |
---|---|---|
B0 | Mute | E0 23 |
B1 | Vol Down | E0 21 |
B2 | Vol Up | E0 32 |
B3 | Mic Shut | |
B4 | Thinkvantage | |
B5 | ? | |
B7 | Stop | E0 3B |
B8 | Play/Pause | E0 34 |
B9 | Scan prev. track | E0 15 |
BA | Scan next track | E0 4D |
BB | Brightness down | |
BC | Brightness up | |
BD | Num Lock | 77 |
C2 | ? | |
C3 | Screen Lock | |
C4 | Sleep | |
C5 | WiFi on/off | |
C6 | Lenovo Settings | |
C7 | Switch video mode | |
C8 | ? | |
CC | ? | |
D0 | CtrlBrk | 14 E0 7E E0 F0 7E F0 14 |
D1 | SysReq | 11 84 |
D2 | ScrLk | 7E |
D3 | Pause | E1 14 77 E1 F0 14 F0 77 |
So analyzing the current layout and checking a 7-row keyboard gives us the following layout:
Key | Offset | Keycode | Hw-Code | Scancode Set2 Make | Fn Mapping |
---|---|---|---|---|---|
` ~ | 0 | 0E | 00 10 | 0e | |
1 ! | 10 | 16 | 00 11 | 16 | |
2 @ | 11 | 1E | 01 11 | 1e | |
3 # | 12 | 26 | 02 11 | 26 | |
4 $ | 13 | 25 | 03 11 | 25 | |
5 % | 3 | 2E | 03 10 | 2e | |
6 ^ | 4 | 36 | 04 10 | 36 | |
7 & | 14 | 3D | 04 11 | 3d | |
8 * |
15 | 3E | 05 11 | 3e | |
9 ( | 16 | 46 | 06 11 | 46 | |
0 ) | 17 | 45 | 07 11 | 45 | |
- _ |
7 | 4E | 07 10 | 4e | |
= + | 5 | 55 | 05 10 | 55 | |
Backspace | 38 | 66 | 08 13 | 66 | |
Tab | 30 | 0D | 00 13 | 0d | |
Q | 20 | 15 | 00 12 | 15 | |
W | 21 | 1D | 01 12 | 1d | |
E | 22 | 24 | 02 12 | 24 | |
R | 23 | 2D | 03 12 | 2d | |
T | 33 | 2C | 03 13 | 2c | |
Y | 34 | 35 | 04 13 | 35 | |
U | 24 | 3C | 04 12 | 3c | |
I | 25 | 43 | 05 12 | 43 | |
O | 26 | 44 | 06 12 | 44 | |
P | 27 | 28 | 07 12 | 4d | Y |
[ { | 37 | 54 | 07 13 | 54 | |
] } | 35 | 5B | 05 13 | 5b | |
\ | | 51 | 61 | 01 15 | 5d | |
CapsLock | 31 | 58 | 01 13 | 58 | |
A | 40 | 1C | 00 14 | 1c | |
S | 41 | 24 | 01 14 | 1b | Y |
D | 42 | 23 | 02 14 | 23 | |
F | 43 | 2B | 03 14 | 2b | |
G | 53 | 34 | 03 15 | 34 | |
H | 54 | 33 | 04 15 | 33 | |
J | 44 | 3B | 04 14 | 3b | |
K | 45 | 26 | 05 14 | 42 | Y |
L | 46 | 4B | 06 14 | 4b | |
; : | 47 | 4C | 07 14 | 4c | |
' " | 57 | 52 | 07 15 | 52 | |
non-US-1 | 67 | 5D | 07 16 | 0 | |
Enter | 68 | 5A | 08 16 | 5a | |
LShift | 3E | 88 | 0E 13 | 12 | |
Z | 60 | 1A | 00 16 | 1a | |
X | 61 | 22 | 01 16 | 22 | |
C | 62 | 21 | 02 16 | 21 | |
V | 63 | 2A | 03 16 | 2a | |
B | 73 | 22 | 03 17 | 32 | Y |
N | 74 | 31 | 04 17 | 31 | |
M | 64 | 3A | 04 16 | 3a | |
, < | 65 | 41 | 05 16 | 41 | |
. > | 66 | 49 | 06 16 | 49 | |
/ ? | 77 | 4A | 07 17 | 4a | |
RShift | 6E | 89 | 0E 16 | 59 | |
LCtrl | 0F | 8C | 0F 10 | 14 | |
LAlt | 5D | 8A | 0D 15 | 11 | |
space | 78 | 20 | 08 17 | 29 | Y |
RAlt | 7D | 8B | 0D 17 | e0-11 | |
RCtrl | 6F | 8D | 0F 16 | e0-14 | |
Insert | 09 | A0 | 09 10 | E0 70 | |
Delete | 0A | A1 | 0A 10 | E0 71 | |
Home | 0C | A2 | 0C 10 | E0 6C | |
End | 1C | A3 | 0C 11 | E9 69 | |
PgUp | 0B | A4 | 0B 10 | E0 7D | |
PgDn | 1B | A5 | 0B 11 | E0 7A | |
Left | 7C | A6 | 0C 17 | e0-6b | |
Up | 5C | A7 | 0C 15 | e0-75 | |
Down | 7A | A8 | 0A 17 | e0-72 | |
Right | 79 | A9 | 09 17 | e0-74 | |
Esc | 50 | 76 | 00 15 | 76 | |
F1 | 01 | 05 | 01 10 | 05 | |
F2 | 02 | 0A | 02 10 | 06 | Y |
F3 | 32 | 0C | 02 13 | 04 | Y |
F4 | 52 | 0E | 02 15 | 0c | Y |
F5 | 58 | 10 | 08 15 | 03 | Y |
F6 | 55 | 12 | 05 15 | 0b | Y |
F7 | 36 | 14 | 06 13 | 83 | Y |
F8 | 06 | 16 | 06 10 | 0a | Y |
F9 | 08 | 18 | 08 10 | 01 | Y |
F10 | 18 | 1A | 08 11 | 09 | Y |
F11 | 1A | 1C | 0A 11 | 78 | Y |
F12 | 19 | 1E | 09 11 | 07 | Y |
WWW-Back | 6B | 0B 16 | E0 38 | ||
WWw-Fwd | 7B | 0B 17 | E0 30 | ||
PrtScr | 1D | 0D 11 | E0 7C | ||
ScrLock | 2D | 0D 12 | 7E | ||
Pause | 6C | 0C 16 | E1 14 77 E1 F0 14 F0 77 | ||
Vol Up | 2A | 04 | 0A 12 | E0 32 | Y |
Vol Down | 3A | 02 | 0A 13 | E0 21 | Y |
Mute | 4A | 00 | 0A 14 | E0 23 | Y |
ThinkVantage | 5A | 08 | 0A 15 | Y | |
Mic Shut | 6A | 06 | 0A 16 | Y | |
Left GUI(WIN) | 39 | 82 | 09 13 | E0 1F | |
Right GUI (Menu) | 4B | 0B 14 | E0 27 |
You may have noticed that offset is just Hw-Code byte2 << 4 | byte1
If you want to watch for HW key codes i.e. to implement another keyboard, use watch -n 2 with: dd if=/sys/kernel/debug/ec/ec0/ram bs=1 count=8 skip=68382 2>/dev/null | hexdump -C
Looking at the FN-Table that gets referenced by the keys in the table above when "Fn Mapping" is Y:
Mapping | Normal | Descr | FN | Descr |
---|---|---|---|---|
0 | C1 | -> Mapping 1 | C1 | -> Mapping 01 |
1 | 10 | Command | B0 | Mute |
2 | C3 | -> Mapping 3 | C3 | -> Mapping 03 |
3 | 30 | Command | B1 | Volume Down |
4 | C5 | -> Mapping 5 | C5 | -> Mapping 05 |
5 | 30 | Command | B2 | Volume Up |
6 | C7 | -> Mapping 7 | C7 | -> Mapping 07 |
7 | 10 | Command | B3 | Mic Shut |
8 | C9 | -> Mapping 9 | C9 | -> Mapping 09 |
9 | 30 | Command | B4 | ThinkVantage |
0A | 06 | F2 | CB | -> Mapping 0B |
0B | 10 | Command | C2 | |
0C | 04 | F3 | CD | -> Mapping 0D |
0D | 10 | Command | C3 | Screen Lock |
0E | 0C | F4 | CF | -> Mapping 0F |
0F | 10 | Command | C4 | Sleep |
10 | 03 | F5 | D1 | -> Mapping 11 |
11 | 10 | Command | C5 | WIFI on/off |
12 | 0B | F6 | D3 | -> Mapping 13 |
13 | 10 | Command | C6 | Lenovo settings |
14 | 80 | F7 via transtbl. | D5 | -> Mapping 15 |
15 | 10 | Command | C7 | Switch video mode |
16 | 0A | F8 | D7 | -> Mapping 17 |
17 | 30 | Command | BB | Brightness down |
18 | 01 | F9 | D9 | -> Mapping 19 |
19 | 30 | Command | BC | Brightness up |
1A | 09 | F10 | DB | -> Mapping 1B |
1B | 30 | Command | B9 | Scan prev. Track |
1C | 78 | F11 | DD | -> Mapping 1D |
1D | 10 | Command | B8 | Play/Pause |
1E | 07 | F12 | DF | -> Mapping 1F |
1F | 30 | Command | BA | Scan next track |
20 | 29 | Space | E1 | -> Mapping 21 |
21 | 10 | Command | B6 | Enable keyboard backlight? |
22 | 32 | B | E3 | -> Mapping 23 |
23 | 10 | Command | D0 | Ctrl+Break |
24 | 1B | S | E5 | -> Mapping 25 |
25 | 10 | Command | D1 | SysReq |
26 | 42 | K | E7 | -> Mapping 27 |
27 | 10 | Command | D2 | ScrollLock |
28 | 4D | P | E9 | -> Mapping 29 |
29 | 10 | Command | D3 | Pause |
Now that we know how the table is composed, we can re-assign the keys to the classic 7-row keyboard layout by moving some function keys around and fixing some wrong table entries (i.e. Pos1, Home, Del, PgUp, ...) also enabling NumLock. However, there is a problem with the WW-Forward and WWW-Back keys, because their entry in the internal Mapping table cannot be reached due to the key ranges assigned mentioned above. However, we see that Pause and Ctrl+Break are already mapped via the "Special" Tables for Fn, so the key codes 91 and 92 aren't really used. Therefore, we update the table at offset 35f7c at the entry for 91 to use WWW-Fwd and WWW-Back instead: These 2 entries are at offset 35f9e. Each entry consists of a Scancode and an Index to a function pointer. 01 is index for the function that just generates the E0 Make-code. So we change the table as follows:
Int. Code | Old Scanc. | Old FuncID | New Scanc. | New FuncID |
---|---|---|---|---|
91 | 00 | 04 | 38 | 01 |
92 | 01 | 04 | 30 | 01 |
After creating these 2 Keycodes, we can update the Keyboard layout table:
Key | Offset | Keycode | Fn Mapping | New Keycode | New Fn Mapping |
---|---|---|---|---|---|
WWW-Back | 6B | 00 | N | 91 | |
WWw-Fwd | 7B | 00 | N | 92 | |
PrtScr | 1D | 00 | N | 24 | Y |
ScrLock | 2D | 00 | N | 0C | Y |
Pause | 6C | 00 | N | 22 | Y |
Right GUI (Menu) | 4B | 9D | N | 84 | |
7 & | 14 | 3D | N | AA | |
8 * | 15 | 3E | N | AB | |
9 ( | 16 | 46 | N | AC | |
0 ) | 17 | 45 | N | BE | |
U | 24 | 3C | N | AD | |
I | 25 | 43 | N | AE | |
O | 26 | 44 | N | AF | |
P | 27 | 28 | Y | BF | N |
S | 41 | 24 | Y | 1B | N |
J | 44 | 3B | N | B0 | |
K | 45 | 26 | Y | B1 | N |
L | 46 | 4B | N | B2 | |
; : | 47 | 4C | N | BD | |
B | 73 | 22 | Y | 32 | N |
M | 64 | 3A | N | B3 | |
. > | 66 | 49 | N | B4 | |
/ ? | 77 | 4A | N | BC | |
space | 78 | 20 | Y | 29 | N |
Insert | 09 | 00 | N | A0 | |
Delete | 0A | A2 | N | A1 | |
Home | 0C | 00 | N | 18 | Y |
End | 1C | A3 | N | 16 | Y |
PgUp | 0B | A1 | N | 20 | Y |
PgDn | 1B | A0 | N | A5 | |
Left | 7C | A6 | N | 1A | Y |
Up | 5C | A7 | N | 26 | Y |
Down | 7A | A8 | N | 1C | Y |
Right | 79 | A9 | N | 1E | Y |
F3 | 32 | 0C | Y | 04 | N |
F8 | 06 | 16 | Y | 0A | N |
F9 | 08 | 18 | Y | 01 | N |
F10 | 18 | 1A | Y | 09 | N |
F11 | 1A | 1C | Y | 78 | N |
F12 | 19 | 1E | Y | 07 | N |
And the FN-Table:
Mapping | Normal | FN | New Key | New FN |
---|---|---|---|---|
0B | 10 | C2 | C3 | |
0C | 04 | CD | E7 | |
0D | 10 | C3 | BD | |
16 | 0A | D7 | A3 | |
18 | 01 | D9 | A2 | |
1A | 09 | DB | A6 | |
1C | 78 | DD | A8 | |
1E | 07 | DF | A9 | |
20 | 29 | E1 | A4 | |
22 | 32 | E3 | E9 | |
24 | 1B | E5 | 9D | |
26 | 42 | E7 | A7 | E8 |
28 | 4D | E9 | 10 | B7 |
This finally leads us to the new keyboard layout
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 0E 05 0A 2E 36 55 0A 4E 01 A0 A1 20 18 00 00 8C
00000010 16 1E 26 25 AA AB AC BE 09 07 78 A5 16 24 00 00
00000020 15 1D 24 2D AD AE AF BF 6A 00 04 00 00 0C 00 00
00000030 0D 58 04 2C 35 5B 14 54 66 82 02 00 00 00 88 00
00000040 1C 1B 23 2B B0 B1 B2 BD 5D 00 00 84 00 00 00 00
00000050 76 61 0E 34 33 12 64 52 10 00 08 00 26 8A 00 00
00000060 1A 22 21 2A B3 41 B4 5D 5A 00 06 91 22 00 89 8D
00000070 67 8E 00 32 31 51 13 BC 29 1E 1C 92 1A 8B 00 00
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000090 04 18 00 30 00 24 40 04 00 04 24 15 00 14 00 16
000000A0 00 00 C1 C1 10 B0 C3 C3 30 B1 C5 C5 30 B2 C7 C7
000000B0 10 B3 C9 C9 30 B4 06 CB 10 C3 E7 CD 10 BD 0C CF
000000C0 10 C4 03 D1 10 C5 0B D3 10 C6 80 D5 10 C7 A3 D7
000000D0 30 BB A2 D9 30 BC A6 DB 30 B9 A8 DD 10 B8 A9 DF
000000E0 30 BA A4 E1 10 B6 E9 E3 10 D0 9D E5 10 D1 A7 E8
000000F0 10 D2 10 B7 10 D3 00
You can try it out by compiling https://github.com/exander77/x2100-ec-sys and loading it with write support:
modprobe x2100_ec_sys write_support=1
Then verify that you have the correct firmware and the original table in place:
dd if=/sys/kernel/debug/ec/ec0/ram bs=1 count=247 skip=$[0x366BD] 2>/dev/null | hexdump -C
If it is the correct table from the top of this document, then you can patch the attached scancode.map in its place:
dd if=scancode.map of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$[0x366BD]
and assign the WWW-Keys accordingly:
echo -ne "\x38\x01\x30\x01" | dd of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$[0x35f9e]
Keyboard map should be in place until you remove battery and power, to make it permanent, you need to reflash bios with patched EC firmware.
Have fun.
Oh wow, wait, does the EC hotpatch stuff work out of the box without having to first patch the EC firmware? That's crazy, if so. And a serious security problem.
Anyway, this would be good news for you, since it means you can try hotpatching what you suspect to be the battery authentication routine without worrying about bricking your machine.
Yes, hotpatching works out of the box :-) Anyway, I do have a genuine battery, just wrong "model", so It's not "just" the auth check to patch, I shall have a look at the battery state machine later.
Well, someone particularly daring could probably use this to write software for the EC that overwrites the flash descriptor, and therefore would allow you to me_cleaner
the machine...
When updating EC firmware, is this just a matter of Checksumming new firmware with checksum.c and placing it in the previously dumped BIOS firmware image with dd and flashing it back with flashrom or are there any other checksums to consider?
I would like to ask if it is possible that someone with an programmer that is capable to do recovery can test if updating the EC Firmware would cause problems possibly with some checksums, or if it is enough to just apply the patched EC firmware to the BIOS image and reflash via flashrom. This would be very helpful. In order to carry out the test, I'd request you to do a harmless patch of the EC keyboard map, which doesn't have an effect on the proper operation of the keyboard, but shows if changing EC firmware would cause some CRC error to go off.
The EC firmware is divided into 2 parts. The first part is the loader that loads attached real firmware image to address 20100. As only the loader-Part of the firmware is checksummed, it is not necessary needed to recalculate the checksum of the loader part.
So for testing, the following steps should be carried out.
1) Flash most recent stock firmware with EC Firmware version Firmware G3HT40WW(1.14) 2) Dump BIOS with:
flashrom -p internal -r current-bios.bin --ifd -i bios
3) Check BIOS image so verify that we are patching the correct region
dd if=current-bios.bin bs=1 count=16 skip=$[0x41E5BD] 2>/dev/null | hexdump -C
This should result in the first row of the keyboard map:
00000000 0e 05 0a 2e 36 55 16 4e 18 00 a2 a1 00 00 00 8c |....6U.N........|
4) Patch an empty key mapping at offset C for test purposes:
cp current-bios.bin new-bios.bin
echo -ne "\x18" | dd conv=notrunc of=new-bios.bin bs=1 seek=$[0x41E5C9]
5) Flash back BIOS to machine
flashrom -p internal -w new-bios.bin --ifd -i bios
6) Shutdown machine, remove power connection and battery for at least 30 seconds so that embedded controller is forced to reinitialize
7) Power up machine, install and load https://github.com/exander77/x2100-ec-sys
modprobe x2100_ec_sys
8) Check memory region if the byte at offset C has changed:
dd if=/sys/kernel/debug/ec/ec0/ram bs=1 count=16 skip=$[0x366BD] 2>/dev/null | hexdump -C
Output should now read:
00000000 0e 05 0a 2e 36 55 16 4e 18 00 a2 a1 18 00 00 8c |....6U.N........|
I would be very grateful if someone can check this so that I can permanently install the 7-row keyboard firmware fix then. Thank you very much in advance!
I can test this! Can you give me a test patch that swaps some keys around (e.g. Z and X) for a visible effect rather than having to check the memory?
write software for the EC that overwrites the flash descriptor, and therefore would allow you to me_cleaner the machine
No need for that when 1vyrain exists. (Note: DO NOT use disable bits in me_cleaner like -S
, that fails to boot, only do the cleaning)
@unrelentingtech Thank you for your help! Sure, we can change something significant too, so to swap Z and X:
1) Flash most recent stock firmware with EC Firmware version Firmware G3HT40WW(1.14)
2) Create file named layout
with the following contents:
00000000:00000fff fd
00400000:007fffff bios
00400000:0041ffff ec
00001000:003fffff me
Then read ec firmware with:
flashrom -p internal -l layout -r current-bios.bin -i ec
3) Check BIOS image to verify that we are patching the correct region
dd if=current-bios.bin bs=1 count=16 skip=$[0x41E61D] 2>/dev/null | hexdump -C
This should result in the 6th row of the keyboard map:
1a 22 21 2a 3a 41 49 5d 5a 00 06 a4 00 00 89 8d |."!*:AI]Z.......|
4) Swap Z and X at offset 60:
cp current-bios.bin new-bios.bin
echo -ne "\x22\x1a" | dd conv=notrunc of=new-bios.bin bs=1 seek=$[0x41E61D]
5) Flash back BIOS to machine
flashrom -p internal -l layout -w new-bios.bin -i ec
In case it doesn't load the new EC fw, maybe we need to change FW version in header, but I will give instructions for this just in case that it doesn't work that way.
Yes, it worked! \o/
BTW, I don't think there's even a way for it to "not load" anything. Looks like this EC does not have its own flash, it's all always loaded from the main SPI flash: first, flashing the stock 2.04 bios also reverts the EC version to 1.13 (as shown by the setup screen), and also there is no "flashing the EC" step after any update (just flashed from stock to 2.76 from the official bootable ISO, that seems equivalent to what my manual updates did).
Thank you very much for testing, this is indeed good news!
As far as I understand, you only did ifdtool splitting because of your personal preference (i.e. to have clean offsets for patching the firmware), so the method oulined by me would still work, I guess? Basically I'd do it that way to not have a dependency on ifdtool (the same could also be accomplished by just using plain dd on the flashrom file, I guess).
The keyboard patch documentation may also be usable for non-7-row keyboard users, i.e. to enable numlock functionality on their keyboards.
Battery authentication is another interesting topic with this firmware, as OP asked for it, but there are so many weird state machines in there all sending undocumented commands to the Battery controller, I fear I'm unable to understand all this stuff, I guess someone more experienced needs to have a look at it. ACPI tables also don't really enlighten me regarding all the memory locations on the communication interface to the EC.
Well, at least we have a keyboard fix, will try it out myself soon.
you only did ifdtool splitting because of your personal preference
Yes, mostly because I do the modifications on my desktop, while flashrom is on a raspberry pi.
Battery authentication is another interesting topic with this firmware
Indeed. Is there any info from other machines with a similar EC that have already been patched?
Now took a closer look
echo -ne "\x0c\x60\x02" | dd of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$[0x1004a]
Should temporarily enable the battery. Will try to make a patch so that it permanently accepts any battery tomorrow (will need to modify some code of the state machine)
The xx30 Series of Thinkpads unfortunately incorporate some kind of battery check that makes it impossible to use an aftermarket and/or xx20 series battery, even though i.e. T420 battery will perfectly fit into L530 and also works. The reason is some kind of challenge/response battery athentication mechanism in the embedded controller. This was discovered by Dr. Matthew Chapman for his X230T Thinkpad and very well documented in his blog. Unfortunately, the Thinkpad L430 and L530 series use a different embededd controller (Nuvoton NPCE885G), so work had to be done to also patch this type of controller.
The authentication routine for battery authentication as pretty similar to
the one that is described by ZMatt. Mainly just the order of steps is
different.
In Firmware G3HT40WW(1.14) the state machine routine is at 288ac
.
The state of the state machine including various flags is a 3 byte array at
1004a
. The first 2 bytes can be seen as a little endian word, the 3rd
as a byte. However, to avoid confusion, I will address each byte seperately
in my description, so "byte 1" is at 1004a
, "byte 2" is at 1004b
, etc.
The lower 5 bits of the first word are indicating the state of the
autentication state machine which I partly will describe here, the other bits
are various flags.
State | Description |
---|---|
1 | Init |
2 | Wait for challenge to be constructed. The state machine for challenge construction and validation is at 3161a . Start write command 0x27 (with 17-byte challenge) |
3 | check success, retry if necessary |
4 | start read command 0x28 |
5 | check success, retry if necessary |
6 | start write command 0x3c (with 4-byte challenge) |
7 | check success, retry if necessary |
8 | start read command 0x3c |
9 | check success, retry if necessary. Challenge is validated at state machine in 3161a . If it succeeds, bit 0x40 in byte 2 of our 3 byte array at 1004a is set. |
12 | validate battery response |
There are more states, but they are not interesting to us for our purpose. The 4byte challenge, that gets written in Step 6, seems to be partly composed of current system date in bytes 3 and 4. The system date can be set by BIOS with the following 91h commands:
Command | Description |
---|---|
5Dh xx | Set current day |
5Eh xx | Set current month |
5Fh xx | Set current year |
If there is a failure in communicating with the Battery via SMbus,
there is a error recovery handler at 282c0
which resets the
state machine back to state 1 and saves the prvious state in
1004a
byte 2.
If communication fails 5 times in a row, there is a failure handler
at 286d4
that clears some bits of 1004a
byte 2 (described later):
The state changes are: States | Description |
---|---|
2, 4 | Clear bit 6 in byte 2 |
6, 8 | Clear bit 5, 7 in byte 2 |
19,21,23 | Reset state machine to 0 |
In States 2-8, it sets bit 4 in 1004a
byte 3 to indicate failure.
1004a Byte 2 flags (Bit numbers are zero-based, little endian):
Bit |
Description |
---|---|
7 | ? |
6 | Battery authenticated |
5 | Allow charging of battery? |
0-4 | Previous state machine state on retry |
1004a Byte 3 flags:
Bit |
Description |
---|---|
5-7 | Ununsed |
4 | Battery auth comm. failed, handler at 286d4 got executed |
3 | State 4 success, 17byte challenge read, check authentication |
2 | State 2 success |
1 | Current date has been set by BIOS with call to 91h 5Fh |
0 | State 12 check performed |
Now let's have a look at the state transition in each 3 described bytes when inserting a battery that doesn't support authentication.
State | Byte 1 | Byte 2 | Byte 3 |
---|---|---|---|
Battery in | 00 | 22 | 13 |
Battery removed | 00 | 02 | 02 |
Battery inserted | 01 | 06 | 02 |
02 | 26 | 02 | |
01 | 22 | 02 | |
02 | 22 | 02 | |
01 | 22 | 02 | |
00 | 22 | 13 |
So battery gets plugged in, state machine starts at 1, progresses to State 2, cryptographic challenge got prepared, 0x027 command is sent and then fails. It retries 5 times and then it finally sets error flag in byte 3 and resets state machine back to 0.
Judging from the states above, we want to
So we can simply write these bytes to the state machine field and have a temporary patch for an inserted battery. But as swapping the battery etc. would reset back to the not-authenticated state, having a permanent patch would be more desirable. In order to do this, we have to skip from state 2 to state 12 and ensure that Bits 5 and 6 in byte 2 get set. Byte 3 doesn't interest us, as we can just skip over the check in state 12. So the following patch should be enough:
Address | Old instruction | Old instr. bytes | New instr. | New instr. bytes | State | Comment |
---|---|---|---|---|---|---|
28a72 | BReq 0002915e | 00 18 ec 06 | BR *0x00028ac6 | e0 18 54 00 | 2 | Do net send battery auth challenge |
28ade | ORW 0x03, R0 | 30 26 | ORW 0x0C, R0 | c0 26 | 2 | Skip directly to state 12 |
28c68 | BRfs 0002915 | 80 18 f6 04 | BR 00028cc2 | e0 18 5a 00 | 12 | Always execute step (ignore byte 3 bit 0) and do not go back to state 6 |
28cea | TBITB $0x06,*0x1(R1R0) | 60 7b 01 00 | SBITB $0x06,*0x1(R1R0) | 60 73 01 00 | 12 | Set battery authenticated bit |
BRfc 00028d26 | 9c 11 | SBITB $0x05,*0x1(R1R0) | 50 73 01 00 | 12 | Set battery charging enable bit | |
BR *0x28d1e | e0 18 2c 00 | 12 | Now go on to code where both bits were enabled |
The addresses are based on RAM offset of firmware. If you want to calculate the address in firmware image, use the following calculation:
Op | Offset | Description |
---|---|---|
400000 | Offset of EC firmware in BIOS image | |
+ | 8000 | Offset of code that gets loaded at memory location 20100 |
- | 20100 | Memory location of code |
So the offset to add to the addresses above is 3e7f00
when patching firmware images.
You MUST ensure that you have the correct EC firmware version in use!! Firmware version must be: G3HT40WW(1.14) If unsure, you can check i.e. in BIOS.
The easiest method is to just modify the state of the state machine at runtime.
Advantage: Minimal invasive, just change EC memory
Disadvantage: It only lasts as long as the battery is inserted in the machine and there is enough power so the EC doesn't shut down
How to patch: 1) Compile x2100-ec-sys kernel module 2) If already installed, remove kernel module and reload it with write support:
rmmod x2100-ec-sys
modprobe x2100_ec_sys write_support=1
3) Ensure that battery is already inserted.
4) echo -ne "\x0c\x60\x02" | dd of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$[0x1004a]
Now battery should become ready and if not full start to charge.
Advantage: No permanent changes to EC firmware, if it causes bad side effects, you just need to remove battery and power to reset EC back to stock firmware. Unlike memory hotpatch, also survives battery pack change.
Disadvantage: It only lasts as long as there is enough power so the EC doesn't shut down
How to patch: 1) Compile x2100-ec-sys kernel module 2) If already installed, remove kernel module and reload it with write support:
rmmod x2100-ec-sys
modprobe x2100_ec_sys write_support=1
3) Patch the code from down to up so that it doesn't accidentally get jumped to while you are still patching, if it is still active during patching:
echo -ne "\x60\x73\x01\x00\x50\x73\x01\x00\xe0\x18\x2c\x00" | dd of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$((0x28cea))
echo -ne "\xe0\x18\x5a\x00" | dd of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$((0x28c68))
echo -ne "\xc0" | dd of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$((0x28ade))
echo -ne "\xe0\x18\x54\x00" | dd of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$((0x28a72))
4) If a battery is already inserted, kick off that state machine to rerun:
echo -ne "\x02" | dd of=/sys/kernel/debug/ec/ec0/ram bs=1 seek=$((0x1004a))
Now battery should become ready and if not full start to charge. Swapping the battery also works.
Advantage: Survives even power-off and battery removal, so loads again on EC-reinitialization
Disadvantage: You have to re-flash your EC firmware. If something goes wrong, you may end up with a bricked machine. Do this at your own risk. If something goes wrong, do not complain!
How to patch:
1) Flash most recent stock firmware with EC Firmware version G3HT40WW(1.14) so that you are at the current Firmware level and verify that stock FW works and is OK.
2) Create file named layout
with the following contents:
00000000:00000fff fd
00400000:007fffff bios
00400000:0041ffff ec
00001000:003fffff me
Then read ec firmware with:
flashrom -p internal -l layout -r current-bios.bin -i ec
3) Check BIOS image to verify that we are patching the correct region
dd if=current-bios.bin bs=1 count=16 skip=$((0x3e7f00+0x28cea)) 2>/dev/null | hexdump -C
This should result in the following original code:
60 7b 01 00 9c 11 72 5d 22 5f 20 55 20 4c 20 61 |
{....r]"_ U L a|
`
4) Apply patches to image
echo -ne "\x60\x73\x01\x00\x50\x73\x01\x00\xe0\x18\x2c\x00" | dd conv=notrunc of=current-bios.bin bs=1 seek=$((0x3e7f00+0x28cea))
echo -ne "\xe0\x18\x5a\x00" | dd conv=notrunc of=current-bios.bin bs=1 seek=$((0x3e7f00+0x28c68))
echo -ne "\xc0" | dd conv=notrunc of=current-bios.bin bs=1 seek=$((0x3e7f00+0x28ade))
echo -ne "\xe0\x18\x54\x00" | dd conv=notrunc of=current-bios.bin bs=1 seek=$((0x3e7f00+0x28a72))
5) Flash back BIOS to machine
flashrom -p internal -l layout -w new-bios.bin -i ec
6) Power down machine, force reload of EC firmware by removing battery and AC power for 30 seconds
This has not been tested yet, but should work.
Good luck!
You need to recompute the EC checksum before you flash it. Otherwise the machine won't boot.
Dynamically patching possibly-live code can be tricky. In my x2100 EC patch set I have some tools to build patches and to build a "safe" patch loader that operates atomically. If I remember in the morning I'll try to write more about it...
You need to recompute the EC checksum before you flash it. Otherwise the machine won't boot.
No, I already explained that:
The EC firmware is divided into 2 parts. The first part is the loader that loads attached real firmware image to address 20100. As only the loader-Part of the firmware is checksummed, it is not necessary to recalculate the checksum of the loader part. It will remain the same, when just changing the data that gets loaded. That was the whole point of the test to check whether there is some other hidden checksum in it, but as @unrelentingtech was able to patch it without issues, there doesn't seem to be a second check.
Dynamically patching possibly-live code can be tricky. In my x2100 EC patch set I have some tools to build patches and to build a "safe" patch loader that operates atomically. If I remember in the morning I'll try to write more about it...
I just wrote my own using just bash, dd and hexdump that verifies old contents and allows hot-patching and image patching of files and sequences, I will upload it to a seperate repo soon so that you can also check, if I missed something.
@unrelentingtech
How did you flash the firmware on the L530?
I tried flashrom -p internal -l layout -w new-bios.bin -i ec
and this resulted in:
Found Winbond flash chip "W25Q64.V" (8192 kB, SPI), mapped at physical address 0x00000000ff800000.
Enabling flash write... SPI Configuration is locked down.
Reading old flash chip contents... Transaction error!
FAILED
Maybe this is due to a different flash chip than yours, could it be...?
@unrelentingtech How did you flash the firmware on the L530?
With a raspberry pi and a chip clip.
You can only do internal with the 1vyrain "jailbreak" (see that thread where I was summoned from :D) on early enough firmware (2.54 or lower)
As 1vyrain FAQ states:
The mod is fully compatible with EC modifications, but it must be flashed before you use 1vyrain to update your BIOS.
I guess I have to find out how thinkpad-ec modifies the .FL1 files and then patch these to flash with Lenovo FW upgrade program instead of flashrom? It looks to me as the modifications just need to be carried out in the .FL1 file without any additional steps, as EC Firmware is not encrypted on L430 models. I thought that there are some UEFI checksums or stuff like that to consider, but I only find call of mec-tools which are for the encrypted firmware and L430 firmware is not encrypted and doesn't even need to be checksummed, as checksum of loader remains the same. So could it be that it's just patching .FL1 file (FW starts at offset 0x4001d0 in .FL1 file)? Would you be able to try it out?
The "flashed before" thing is mostly meant for those other models that have internal flash on the EC, or whatever they have. In our case this only means that flashing the pre-baked firmware image that comes on the 1vyrain ISO (for now only on a test version for L430!) will overwrite the EC region.
But I'm not saying to use that image! You can just run the jailbreak and then use flashrom from the command line with your own stuff.
Maybe we can eventually get them to include the EC modifications in their image...
@unrelentingtech Shit, I modified .FL1 file with patched EC Firmware and did:
dosflash /sd /sn /ipf ec /file G3ET94WW\$01D4000.FL1
to update EC firmware. This command is based on what the official Windows update program does:
winflash32 /silent /vcpu /sn /sd /v /exit /ipf bios ec /shutdown /file
So I was under the assumption that it can detect EC firmware location within FL1 file, but seems that it didn't. Any hints on how to recover? Seems I need to find someone with a chip clip and RaspPi...? I guess, that it's basically https://github.com/bibanon/Coreboot-ThinkPads/wiki/Hardware-Flashing-with-Raspberry-Pi with a different wiring for the respective BIOS chip Maybe you can guide me through the process of writing back a working BIOS+EC FW so that I don't make another mistake, as you seem to have a working setup for that. First I'd like to know where the chip is located on the board. I guess it's a WinBond W25Q64 and it looks like that
The chip is.. not in the most convenient location. I've had to modify the bottom case for easy access:
Also my Pi reboots every time I connect the chip clip, but otherwise it's just standard external flashing.
upd: also the pinout seems standardized for all SOIC-8 flash chips
Thanks for the fast reply! Seems that it will take 1-2 weeks until I can visit someone with a chip clip to try it out.
However I'm very curious what went wrong with flashing. My assumption is that the dosflash utility flashed the start of the FL1 BIOS image to the EC area (as i.e. on other models, it flashes FL2 files which directly only contain EC ROM), as only Power button lights up as soon as I plug in power cord. But on the other hand, why does original flash utility call contain "bios" AND "ec" region as parameters, if it doesn't know the layout of the flash file. I also found code to validate EC images in the flasher that seems to only run for certain machine types (those with seperate FL2, supposedly), and which would warn if image if bogus.
If I understand currectly, the EC always loads its code from BIOS chip, so it's not possible that I bricked the EC, right?
For reproducability, here is what I did:
I put this .img onto a stick (dd if=patched.l430.img of=/dev/sdX...
) and then modified autoexec.bat to also contain the /sn parameter (als FL1 states these informations are not used anymore and original Flasher update routine also uses this parameter):
patched.l430.zip
It is worth to mention that in BIOS USB Menu UEFI booting from removable media has to be turned on, even though Legacy Boot has to be used to boot the stick, otherwise, it won't boot.
If you dare, you can try it out and analyse what went wrong, but I don't know if there is a risk associated with it, even though you have an external flasher.
On the L430 there is no EC flash, no "EC region" on the IFD level (EC FW is embedded in the bios
) – so no flash tools should ever "know" about "it". Of course these vendor tools get reused between various models, so I'm not surprised that there is functionality that has undefined behavior on some models.
It has probably done something really stupid, I am curious now :) so I will run it soon and dump the result.
@q60 In the meantime, until we analyzed the reason for BIOS Update failure, you can use the hot-patching feature to solve your battery problem. This should be pretty risk-free. I made a repository with a bootable ISO to apply the patches in memory:
https://github.com/leecher1337/thinkpad-Lx30-ec/releases/tag/20211227
So here's what that did (on top of a clean 2.76 FW):
…yeah, look at these changes below 0x3fffff — it has corrupted the ME region!!
This is why I avoid vendor tools as much as possible.
Wow, thank you very much for trying! That is really weird, I suspected that EC code got trashed, but in fact, EC patch looks fine to me. The BIOS version that you were flashing from my Image was 2.54 (G3ET94WW) because it was the BIOS that was in my target machine. I should have told you that, because in theory, maybe the EC program argument was ignored and it flahed the old Firmware as a whole and what we are seeing here are the diffs between the versions. I just didn't expect a change in another area than EC code. But when comparing the FL1 files of both versions (2.76 and 2.54), I saw more diff than in your dump. But these FL1 files are only UEFI-capsules, so I guess I cannot do a straight compare and find the byte patterns in question. The possibility that comes to my mind is, that - at least in theory - it could still be the EC patch, because when you tested the modification of the keyboard table, this change only modified a data table in the file, whereas the battery patch also modifies code and maybe I missed some CRC routine or something that checks code integrity (but not data)?
So in order to check if it's the flash utility to blame or if it's the patched EC code, on restoring Firmware, you could leave out the EC area from flashing and check if machine then comes up again or if it remains broken (until EC area gets restored), which would mean back to reverse engineering for me and find the integrity check routine.
update: I got told that ME area can change on every boot too, and if you look at the end of the diff, these are mostly text strings.
update 2: I also found out that the IN header has a field for size of code and the keyboard table is after code. Size of keyboard map data is hardcoded with 0x3ad bytes.
There is no way it flashed the whole firmware — the whole "update" happened way too quickly, the log output said "Total blocks of the image = 32".
I'll do more experiments tomorrow, but to determine if it's the battery patch, can you provide it in the same dd
-command format as the key test patch? (or as an UEFIPatch)
You can apply it on any file with any offset you like conveniently with this script: https://github.com/leecher1337/thinkpad-Lx30-ec/tree/main/fwpat
It basically does all the dd-ing, you just need to call the "applypat" function with the desired offset and filename, see i.e.:
patches are the patches to apply, i.e.: "bat kb"
would be keyboard and battery, but "bat" would be enough for our test case.
Is this OK for you?
Yes. Initially when I viewed the battery file over there I saw a /sys/kernel/debug
path and got scared away :D but yes, the scripts actually make sense.
In case the patch is responsible for EC malfunction, there still could be 2 possibilities:
1) Some hidden function for code integrity check that I cannot find 2) Code patches cause a hang of the EC when called during initialization but work when patching after EC has initialized (as I verified patches do work by writing to EC RAM).
So in order to find this out, here is an additional test case by doing a harmless patch to the code to check if there is some kind of code integrity check that causes the malfunction:
Only change 10 50
at offset 31780
to 00 50
. This is code that never gets reached with the wrong battery and with a valid battery, it would mark it non-genuine, so just inverses the meaning of batt challenge-response check.
If this little change of a compare instruction still causes the EC to malfunction, I guess it must be some kind of hidden code integrity check.
Today's discoveries:
00 50
does not boot (might be the wrong 10 50
haha, you didn't specify the base for the offset and whether this is a hex or dec number, so 0x3e7f00 + 0x31780
was the only interpretation that did lead me to a 10 50
)bash patch.sh $((0x3e7f00)) l430.254.rom
works finebash patch.sh $((0x8000-0x20100)) l430.276.bios
+ FW patches + packed with ifdtool works fineTo confirm, here's what the changes were
Bonus content:
P.S. the EC datasheet link in your readme seems to return a 0-byte file. and honestly I could not find a datasheet for this anywhere on the internet. If you have it, please upload it somewhere!
Thank you for the extensive Tests! As I bricked my ThinkPad, I was unable to validate my 2 Byte Patch, offset calculation looks correct.
Anyway, as patching the EC directly worked (glad to hear that, because I would have been unable to identify CRC Check function), I conclude that your first assumption about the FW flash tool messing up the ME region ist correct then? So options are: 1) Trying to flash patched FL1 with default Tool options to Check If it also corrupts ME 2) Unlock Chip with 1vyrain and Patch EC Region directly 3) Trying to find the bug in the flash tool that causes corruption (looks like a ton of fun ;-) )
Would you agreed with that? And would you bei able to test 1) ? Or did I misinterpret the results?
Yes, I'll test that. Actually I'm not sure why you've added that option at all? What is that option even for?
You mean the option to specify EC Region? Well, I just took the autoexec template from this project and patched FL1 and it generally makes sense to only patch EC region so that the patch is universal regardless of the installed BIOS version, as long as EC FW version matches. And it is faster than flashing whole BIOS. It is really surprising that flasher uses the correct region from FL1, but still corrupts other areas.
Regarding datasheet: I can download the PDF without issues.
The ec
arg was already there, but you told me to add the /sn
flag. What does /sn
do?
I can download the PDF without issues
Right, worked fine from a different IP address. Seems like it's just a schematic though, no documentation, but still interesting.
You can get all parameters with dosflash /help
/sn
removes check for PartNumber.
If you try without it, you get an error, that PartNumber does not match. However, if you look at .FL1:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
006C61D0 43 4D 44 42 0C 00 00 00 23 00 00 00 42 00 17 42 CMDB....#...B..B
006C61E0 09 05 02 11 42 1E 05 02 29 42 36 05 02 42 42 4F ....B...)B6..BBO
006C61F0 05 02 57 42 69 6F 73 49 6E 66 6F 00 42 6F 61 72 ..WBiosInfo.Boar
006C6200 64 49 64 00 4E 6F 4C 6F 6E 67 65 72 55 73 65 64 dId.NoLongerUsed
006C6210 00 50 61 72 74 4E 75 6D 62 65 72 00 4E 6F 4C 6F .PartNumber.NoLo
006C6220 6E 67 65 72 55 73 65 64 00 52 65 6C 65 61 73 65 ngerUsed.Release
006C6230 44 61 74 65 00 4E 6F 4C 6F 6E 67 65 72 55 73 65 Date.NoLongerUse
006C6240 64 00 56 65 72 73 69 6F 6E 00 4E 6F 4C 6F 6E 67 d.Version.NoLong
006C6250 65 72 55 73 65 64 00 FF FF FF FF FF FF FF FF FF erUsed.ÿÿÿÿÿÿÿÿÿ
And official call of WinFlash32 also contains this parameter which indicates that it must be ignored.
To test DOS flash the 'standard' way, just call command
on prompt, it will start a menu that lets you update the FW.
You can also try to use the parameters for DosFlash, that I Specified above for winflash32, but there are actually 4 possible WinFlash32 calls in the .exe, so safest bet is to use menu driven command
call first. (command.com on DOS disk actually isn't the normal command.com interpreter, but the Lenovo menu for flashing)
Anyway, here are 4 possible calls to DosFlash/Winflash32 extracted from WINUPTP.EXE:
/bbl /silent /vcpu /sn /sd /v /exit /ipf bios /shutdown /file /bbl /silent /vcpu /sn /sd /v /exit /ipf bios ec /shutdown /file /bbl /silent /vcpu /sn /sd /v /exit /ipf bios /file /bbl /silent /vcpu /sn /sd /v /exit /ipf bios ec /file
In UEFI updater, the commands are similar and seem to depend on some kind of Machine Type or somehting like that (Phoenix BIOS proprietary 5380h subfunction, Parameters reordered for better comparability):
1603 /bbl /p /vcpu /silent /sn /sd /v /exit /ipf bios ec /vbl /file 1603(2) /bbl /p /vcpu /silent /sn /sd /v /exit /ipf bios /vbl /file 1608 /bbl /p /vcpu /silent /sn /sd /ss /v /exit /ipf bios ec /vbl /file 1610, 1613 /bbl /p /vcpu /silent /sn /sd /exit /ipf bios ec /vbl /file default /bbl /p /silent /sn /sd /ss /v /exit /ipf bios ec /vbl /file
From these, the last one looks best for me, but I wouldn't flash boot block (/bbl) and not update microcode (/vcpu).
Parameters: Param | Description | |
---|---|---|
/BBL | Program the boot block. By default, bootblock is not updated. | |
/SILENT | Silent operation (no beeps) | |
/VCPU | Update variable size CPU microcode. The ROM, BIOS image, and MCUfile (if defined) will be compared. The most recent MCU will be flashed to ROM. | |
/SN | Skip BIOS part number check. | |
/SD | Skip BIOS date check. | |
/V | Verify each block after programming it. | |
/EXIT | Exit without rebooting. | |
/IPF | Flash specific region ([region name] | all) |
/VBL | Show warning for Microsoft Bitlocker | |
/P | Production mode (minimize messages and delays). | |
/FILE | Filename of file to flash |
When looking at the diff, it looks like the EVSA (EFI Variable Storage Area) got updated, which would make some sense as I remember that when flashing the EC firmware, the DOSFLASH tool only copied the patch to some location and then machine rebooted to some kind of UEFI shell that did the actual flashing. So maybe, it just copies the flash updater there causing the differences in this area. But this still doesn't explain why the ME/BIOS is in a bricked state after flashing, as overwriting only EC area doesn't cause the same problem according to your research. hmmm
@unrelentingtech I bought a cheap CH341A programmer and fixed it for 3.3V on data lines (seems the schematic has a design flaw and normally uses 5V instead of 3.3V on the data lines!) so that I can fix my broken machine. Do I have to remove the CMOS battery before attaching the chip clip with the programmer to the flash chip?
Do I have to remove the CMOS battery before attaching the chip clip with the programmer to the flash chip?
I didn't remove anything, often not even the actual laptop battery lol
Hm, crap, the CH431A works fine when attaching a Flash chip directly into the socket (did this to verify that it is working correctly after 3.3V fix), but when attaching the chip clip to the BIOS Chip on the L530, just the Orange RUN-Led lights up (no documentation can be found for this board to find out what this means) and the programmer isn't detected by the host system. I double-checked for the correct wiring, but cannot find a wiring error. Pin1 is connected through properly. Weird. So I guess my only chance is using the RaspPi then, hopefully I haven't ruined anything yet. Arrrgh
the programmer isn't detected by the host system
hm. Probably the same issue that causes the Pi to reset.
As EC is also connected to SPI flash, maybe CH431A doesn't supply enough power to run both. I also heard that EC jams the SPI flash if it can't find the firmware which makes flashing even more complicated without desoldering and resoldering the chip.
As it seems that it's not easy to fix my machine and carry out some more tests, if would still be interesting if flashing whole BIOS image including patched EC firmware with dosflash also causes these strange corruption problems.
Btw.: I guess the symtpoms that you were experiencing were the same than mine after dosing dosflash with just ec area: Machine just doesn't power on on pressing the power button and when plugging in PSU, Power led lights up but machine does nothing.
I also noticed that you flash chip has a different description on its top than mine, so probably your machine is using another chip, not the WinBond W25Q64.V?
Flashrom does identify it as a W25Qsomething, so probably not different.
Machine just doesn't power on on pressing the power button
Yes, that's what happens with any kind of non-working firmware (e.g. me_cleaner disable bit too)
Are you powering the Flash Chip (Pin 8 - VCC) via 3.3V Pin 1 of the RaspPi or are you using system battery/AC adapter to power the flash chip and just use the following pinout:
Pin Chip | Name | Pin RaspPi |
---|---|---|
1 | CS | 24 |
2 | MISO | 21 |
3 | not used | not used |
4 | GND | 25 |
5 | MOSI | 19 |
6 | CLK | 23 |
7 | not used | not used |
8 | 3.3V |
I do connect 3v3 to the Pi. I'm pretty sure the chip won't get any power from the system until the system is on (which requires the EC… so it must be able to turn enough power on without the firmware to be able to even read the firmware…?).
Just tested with a multimeter, no voltage on any pins when the laptop is off.
Hm, weird.. After having no success with the CH431A, I tested chip clip with Raspberry Pi on another broken mainboard with the same flash chip in order to be on the safe side in case I brick something due to bad wiring or something. Triple-checked wiring (also checked clip cabling with pin headers), then put 3.3V on Pin 8, but nothing: Chip not detected. I'm running out of ideas now. Could be that the CH431A bricked both flash chips, but I doubt that when attaching a Flash Chip to the socket of the CH431A programmer works fine without damaging the chip.
I used flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=32768
to detect chip
any way this works w/ my machine? i am interested in using
42T4235
battery.