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

  1. This information is current as of firmware version 3.07 with EC version hx20_v0.0.1-369d3c3.
  2. This information comes with no warranty.
  3. 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:

  1. Write 0x0000 | 0x03 (offset 0, autoincrementing 32-bit I/O) to 0x802 (16-bit).
  2. Write data, 32 bits at a time, to 0x804 and 0x806.
    • For data longer than 32 bits, repeat writes to 0x804 and 0x806.
    • For data shorter than 32 bits, write it byte-by-byte into 0x804, 0x805, 0x806 and 0x807.
  3. Write 0xDA to 0x204.

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

  1. 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
  2. Clone the repository.
  3. make utils. This will produce an ectool binary at $/build/bds/util/ectool.
  4. .../ectool --interface=fwk version
  5. 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 or b1,w3 will write 0x01 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.

ectool ... led left blue
ectool ... led power green

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:

  1. with 0x00
  2. 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:

  1. Whether the chassis has been opened
  2. Magic number 0xEC
  3. How many times the chassis has been opened with power applied
  4. 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...
It works!

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 0x994) 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.


  1. Specifically, if EfiEdidDiscoveredProtocol doesn’t return any handles. ↩︎

  2. Data returned by the real-time clock is in binary-coded decimal (BCD) ↩︎

  3. The Microchip MEC172x data sheet calls this a REGION; section 16.10.4 pp. 280 ↩︎

  4. This offset isn’t referenced in the setup form, so it is not driven directly by user input on the Setup page. ↩︎