ARCHIVE: The Framework Laptop’s Embedded Controller (EC), Reverse Engineered
Framework Computer open-sourced their embedded controller firmware!
This is an archived copy of the document I wrote based on reverse engineering the firmware before it was released as open source.
You can find the latest copy here.
NOTE: This information was gathered from Framework UEFI 3.07. No warranty is expressed or implied. Use at your own risk.
Nirav Patel mentioned in “Free the EC!” and “Coreboot Only” that the EC firmware in the Framework Laptop was based on the CrOS EC.
User-toggleable features such as the battery charge limit and the power button brightness are copied from NVRAM to the
EC by OEMBIOSSyncToECDxe
on every boot. Changes made by interacting directly with the EC will not be retained across
boots.
Disclaimer⌗
- This information is current as of firmware version 3.07 with EC version
hx20_v0.0.1-369d3c3
. - This information comes with no warranty.
- This page is informative, not normative, and makes no guarantee that the EC will not change in an incompatible way. It is also my hope that this information does not restrict what Framework can do in the future.
Protocol⌗
The embedded controller uses the protocol documented in the Microchip MEC172x data sheet to exchange v3 commands as described in the EC-3PO docs.
To send a command:
- Write
0x0000 | 0x03
(offset 0, autoincrementing 32-bit I/O) to0x802
(16-bit). - Write data, 32 bits at a time, to
0x804
and0x806
.- For data longer than 32 bits, repeat writes to
0x804
and0x806
. - For data shorter than 32 bits, write it byte-by-byte into
0x804
,0x805
,0x806
and0x807
.
- For data longer than 32 bits, repeat writes to
- Write
0xDA
to0x204
.
Tools⌗
I have published a fork of the Chromium project’s ectool ( DHowett/fw-ectool ), which implements the above protocol. It only works on Linux.
Instructions⌗
- If you’re using a kernel that supports lockdown and secure boot is enabled, make sure you turn it off. This application uses raw port I/O and requires a higher I/O privilege level, which is locked down when secure boot is enabled
- Clone the repository.
make utils
. This will produce anectool
binary at$/build/bds/util/ectool
..../ectool --interface=fwk version
- Go wild!
Raw Communication⌗
fw-ectool offers a new command, raw
, which allows you to send undocumented or unsupported commands to the EC. It will handle encoding the packet as a v3 exchange.
Syntax: raw 0xCOMMAND payload
payload
takes the form of a long string of type codes (b
, w
, d
, q
) and values in hex.
Examples:
b1
will write one byte,0x01
.w3
will write one word in little-endian,0x03 0x00
.b1w3
orb1,w3
will write0x01 0x03 0x00
.
For a more realistic example of its use, see the Keyboard Mapping section.
Sample⌗
$ ectool --interface=fwk version
RO version: hx20_v0.0.1-369d3c3
RW version:
Firmware copy: RO
Build info: hx20_v0.0.1-369d3c3 2021-12-13 21:47:58 runner@fv-az209-518
Tool version: v2.0.11601-7e9447fef 2021-12-22 11:25:36 dustin@rigel
CrOS EC Commands⌗
led⌗
The led
command supports the power
, left
and right
LEDs.
The power
LED does not support blue
.
Fan Duty⌗
The fanduty
(set fan duty percentage) and autofanctrl
(restore fan control to automatic) commands both appear to
work and control the main system fan.
Thanks to D.H for the report.
OEM Commands⌗
NOTE: Assume all structures are packed as with
__attribute__((packed))
or__pragma(pack(1))
3E01 - Unlock Flash I/O⌗
During startup, OEMBIOSSyncToECDxe
calls this function twice:
- with
0x00
- with
0x03
It does this around a region of code that writes the first-boot date to flash.
Testing reveals that flash reading is disabled (all reads return 0
) unless you call 3E01(0x00)
beforehand.
3E02 - Disable Fn/Power, Zap VBAT RAM⌗
Source: Reversed EC firmware
Invoking command 3E02
with the byte 0x01
remaps the Fn key to emit scancode E016
and the fingerprint
power key to emit scancode E025
. The onboard power switch is not impacted.
Calling it again with byte 0x00
restores the original functions of both the Fn key and the power key.
Calling it with byte 0x5A
(ASCII Z
) both disables the Fn key as in 0x01
and zaps a region of the EC’s
non-volatile memory that stores, among other things, the battery charge limit.
3E03 - Set Battery Charge Limit⌗
#define FW_EC_CMD_CHARGE_LIMIT 0x3E03
#define FW_EC_CHARGE_LIMIT_CLEAR BIT(0)
#define FW_EC_CHARGE_LIMIT_SET BIT(1)
#define FW_EC_CHARGE_LIMIT_QUERY BIT(3)
struct fw_ec_params_charge_limit {
uint8_t flags;
uint16_t limit; // 20 - 100
} __ec_align1;
struct fw_ec_response_charge_limit {
uint16_t limit;
} __ec_align1;
This function can set, clear or query the charge limit.
Example: Query⌗
$ ectool --interface=fwk raw 0x3e03 b8
3e03(...1 bytes...)
08 |. |
Read 2 bytes
50 00 |P. |
Example: Set⌗
$ ectool --interface=fwk raw 0x3e03 b2,w50
3e03(...3 bytes...)
02 50 00 |.P. |
Read 0 bytes
3E04⌗
Source: Reversed EC firmware
#define FW_EC_GET_FAN_RPM 0x3E04
// No parameters
struct fw_ec_response_get_fan_rpm {
uint32_t rpm;
} __ec_align1;
Returns the current RPM of the first fan, calculated from the count in MCHP_TACH_0_STATUS
.
3E05⌗
Unknown. Appears to have something to do with configuring power state transitions.
3E06⌗
Unknown. Appears to have something to do with the “ME Lock”.
3E07⌗
Unknown. Sets up “S5 Power Up” (perhaps this is a wake from sleep feature?)
3E08 - Enable/Disable PS/2 Mouse Emulation⌗
#define FW_EC_CMD_SET_PS2_EMULATION 0x3E08
struct fw_ec_params_set_ps2_emulation {
uint8_t enabled; // 0x00 = disabled, 0x01 = enabled
} __ec_align1;
This corresponds to the “Enable PS/2 Mouse Emulation” setting in Setup.
3E09 - Historical Chassis Intrusion Data⌗
Source: Reversed EC firmware
Query⌗
When called with word 0x0000
, reports the contents of four bytes of VBAT RAM. The reported bytes appear
to be:
- Whether the chassis has been opened
- Magic number
0xEC
- How many times the chassis has been opened with power applied
- How many times the chassis has been opened with power removed
Unlike 3E0F - Chassis Status, this host command reports all historical information on chassis intrusion.
Speculation: The UEFI might read this data and measure it into a TPM PCR; if so, any TPM data bound to that PCR would become unavailable if the chassis were opened.
Reset⌗
When called with word 0x00CE
, this host command clears all chassis intrusion data (flag and counts.)
Deassert⌗
When called with any other value, this host command clears only the chassis intrusion flag.
3E0A⌗
Unknown. Interacts with the CYP5525 retimer and control registers.
3E0B - BIOS Complete⌗
OEMBIOSSyncToECDxe
calls this function with 0x04
if there is no display connected1.
Further investigation reveals that this function signals “BIOS Complete”.
3E0C - Keyboard Mapping⌗
#define FW_EC_CMD_SET_KEY_MAPPING 0x3E0C
struct scancode_matrix_pair {
uint8_t row;
uint8_t column;
uint16_t scancode;
} __ec_align1;
enum fw_ec_key_mapping_op {
FW_EC_KEY_MAPPING_GET,
FW_EC_KEY_MAPPING_SET,
};
struct fw_ec_params_set_key_mapping {
uint32_t count;
uint32_t op;
struct scancode_matrix_pair pairs[]; // up to 0x20
} __ec_align1;
OEMBIOSSyncToECDxe
calls this function at boot to sync up the Swap Ctrl/Fn setting.
On : 0x00000002 0x00000001 0x00140202 0x00FF0C01 (bytes)
: {2, 1, {{2, 2, 0x0014}, {1, 12, 0x00FF}}} (structure)
Off: 0x00000002 0x00000001 0x00FF0202 0x00140C01 (bytes)
: {2, 1, {{2, 2, 0x00FF}, {1, 12, 0x0014}}} (structure)
Example⌗
Remapping 1, 12 (Left Ctrl) to SCANCODE_CAPSLOCK
(0x058
):
$ ectool --interface=fwk raw 0x3E0C d1,d1,b1,bc,w58
Writing 3e0c [01 00 00 00 01 00 00 00 01 0C 58 00]
...reply snipped...
Remapping Caps Lock to Esc :
# Remap 4, 4 (Caps) to 0x0076 (ESC as defined in PC/AT)
$ ectool --interface=fwk raw 0x3E0C d1,d1,b4,b4,76
This function can also query the key matrix layout.
See the Framework Key Matrix for all known key positions.
3E0D - Set vPro Remote Wake⌗
#define FW_EC_CMD_SET_VPRO_REMOTE_WAKE 0x3E0D
struct fw_ec_params_set_vpro_remote_wake {
uint8_t enabled; // 0x00 = disabled, 0x01 = enabled
} __ec_align1;
3E0E - Set Power Button Brightness⌗
#define FW_EC_CMD_SET_POWER_BUTTON_BRIGHTNESS 0x3E0E
enum fw_ec_power_button_brightness {
FW_EC_POWER_BUTTON_BRIGHTNESS_HIGH,
FW_EC_POWER_BUTTON_BRIGHTNESS_MEDIUM,
FW_EC_POWER_BUTTON_BRIGHTNESS_LOW
};
enum fw_ec_power_button_brightess_op {
FW_EC_POWER_BUTTON_BRIGHTNESS_SET,
FW_EC_POWER_BUTTON_BRIGHTNESS_GET,
};
struct fw_ec_params_set_power_button_brightness {
uint8_t brightness; // from above constants
uint8_t op;
} __ec_align1;
3E0F - Chassis Status⌗
Source: Reversed EC firmware
#define FW_EC_GET_CHASSIS_STATUS 0x3E0F
// No parameters
struct fw_ec_response_get_chassis_status {
uint8_t status;
} __ec_align1;
The response to this host command is a byte signalling the immediate status of the chassis intrusion detection switch.
Unlike 3E09 - Historical Chassis Intrusion Data, this host command only reports the current state of the chassis.
Flash Regions⌗
3C040 - 3C04F - First Boot Date⌗
On boot, OEMBIOSSyncToECDxe
reads 16 bytes from 3C040
. If they’re uninitialized (0xFF
), it populates them with the
current day, month and year according to the RTC2. This only happens one time, so this flash region contains
the date on which the laptop was first booted.
Observed Contents⌗
0003C040: 0610 2100 0000 0000 0000 0000 0000 0000 ..!.............
^ ^ ^
| | `-year (2 digits, BCD)
| `-month (BCD)
`-day (BCD)
Thanks to lbkNhubert and Jake_Bailey for confirming reports.
Other Exchanges⌗
8048⌗
During startup, OEMBIOSSyncToECDxe
writes 0x8048
to port 0x802
and reads back one byte. The high bit in the
address selects the second data bank3, leaving us with an offset of 0x48
.
It mixes the response with a single byte from the UEFI Setup data (offset 0x99
4) and writes the resulting
data back to bank 2 + 0x48
.
uint8_t setup = /* ... read from Setup NVar */;
uint8_t val;
ec_transact(EC_TX_READ, 0x8048, &val, 1);
val = val & 0xfe | setup[0x99];
ec_transact(EC_TX_WRITE, 0x8048, &val, 1);
On my machine, this results in the following port writes:
0x802 <- 0x8048
0x804 -> v (= 0)
0x802 <- 0x8048
0x804 <- 0x29 (v & 0xfe | 0x29)
Platform Information⌗
The EC appears to be a Microchip MEC-family chip with 8Mb flash.
VBAT RAM⌗
This chip offers a small amount of battery-backed RAM that is used to store various platform info and user configuration details across power state transitions.
Offset | Used for… |
---|---|
0x14 | Charge limit |
0x15 | |
0x16 | |
0x17 | |
0x18 | Keyboard backlight |
0x19 | Chassis open count (while power off; VCI_IN2 signals) |
0x1A | Magic number 0xEC |
0x1B | Chassis open count (while power on) |
0x1C | Allow vPro Remote Wake flag |
0x1D | Chassis open flag |
0x1E | Power button brightness |
Undocumented Features⌗
The EC exposes a number of features that you do not need I/O port access or custom commands to access.
The Keyboard⌗
The following undocumented Fn chords are recognized.
- Fn+B - Ctrl+Break
- Fn+P - Pause
- Fn+K - Scroll Lock
Power Button⌗
Strings in the EC firmware suggest that holding the power button for 10 seconds will force a “battery discharge,” and holding it for 20 seconds will force an EC chip reset.
-
Specifically, if
EfiEdidDiscoveredProtocol
doesn’t return any handles. ↩︎ -
Data returned by the real-time clock is in binary-coded decimal (BCD) ↩︎
-
The Microchip MEC172x data sheet calls this a REGION; section 16.10.4 pp. 280 ↩︎
-
This offset isn’t referenced in the setup form, so it is not driven directly by user input on the Setup page. ↩︎