Exploring the Embedded Controller

Hey all! :wave:t2:

For the past week, I’ve been exploring the embedded controller on our awesome laptops, and I’d like to share some of my findings.

This is going to be somewhat structured and somewhat stream-of-consciousness, so bear with me. Most of the same findings are written up on my website at The Framework Laptop's Embedded Controller (EC) :: HowettNET.


@nrp mentioned here that the EC firmware is based on chromium-ec, and it seemed like it would be a fun challenge to reverse engineer how the battery charge limit feature in 3.07 worked. Given that the EC may eventually be open source, this could serve as a living document for its capabilities.


  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. If you break your computer, I’m sorry, but that might be a really interesting finding.
  3. It’s my dearest hope to not tie the Framework team’s hands – if any of the information presented here limits what they can do in future versions for fear of breaking compatibility with a few community hacks, this project will have been a failure.


The EC uses a customized version of the CrOS EC v3 LPC protocol, which Chromium’s ectool does not support out of the box. I’m hosting a fork at https://github.com/DHowett/fw-ectool that does. It only works on Linux.


  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. Fun!

This tool offers an extended raw command, allowing you to send anything 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.


  • 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.


This thing is really cool! It supports a lot of the normal CrOS EC commands for reporting status and reconfiguring the system at runtime. Of particular note is ledyou can configure the two side LEDs and the power button.

Most of the features in the Advanced page of UEFI setup are synced to the EC on startup using a number of OEM commands, with values and structures documented here. The settings are applied on every boot, so if you change something like the charge limit it will be reset on restart. This is both good and bad. :slight_smile:

Charge Limit

The charge limit is pushed to the EC using command 3E03. You can set a new charge limit at runtime with…

ectool fwchargelimit 80

Older versions of the tool did not support the fwchargelimit command, and you had to send the charge limit host command manually:

ectool --interface=fwk raw 0x3E03 b2,wXXXX

where XXXX is the charge limit from 0-100 in hex (00 .. 64).

EDIT 2022-01-10: I’ve added a more ergonomic command for this!

Key Mapping

The “Swap Ctrl/Fn” setting relies on command 3E0C (dubbed “Keyboard Mapping”). It looks like it takes a set of matrix positions and scancodes. As such, it’s possible to remap any key to emit any¹ scancode. To help with remapping, I’ve reversed the key matrix layout².

I thought it would be very cool to write an EFI driver that remapped Caps Lock to Escape on every boot. However, because the firmware doesn’t support ESP driver loading, it doesn’t work unless you have a compatible bootloader. The source lives here (edk2 required to build.)

Feel free to pile on with questions, info, or findings of your own!

¹ Almost any. I haven’t been able to set any extended scancodes that require escapes in the PC/AT scancode set.
² The EC can simulate a key press at a matrix position, so I just ran kbpress 128 times and asked it to press every matrix position and observed what it emitted using xev and evtest. :sweat_smile: This was before I determined that the key mapping host command can report the matrix mapping! I went back and double-checked afterwards.


I have this working on my batch 5 i5 DIY under arch Linux, with secure boot off of course. Successfully changed charge limit up and down, so my “charge to 100% from userland control” and “auto default to 80% charge” goal is now complete! Merry Christmas to us!

Thanks @DHowett , @Kieran_Levin , @nrp and the rest of the framework team for making this possible!


Wrote a quick little bash script to accept a decimal charge target argument, check it for validity between 20 and 100 percent, change it to hex, and call ectool. If no argument is present, default to 80 percent target.

Be sure to set your path to ectool correctly if you are going to use this!


case $1 in
    ''|*[!0-9]*) chg_dec=80 ;;
    *) chg_dec=$1 ;;

  if [ $chg_dec -gt 99 ]; then
  elif [ $chg_dec -lt 20 ]; then

chg_hex=$( printf '%x\n' $chg_dec )

sudo /SET/PATH/TO/ectool --interface=fwk raw 0x3E03 b2,w$chg_hex


It looks like the EFI Driver plan won’t work unaided; the firmware doesn’t appear to implement an optional part of the UEFI specification, where it can choose to respect DriverOrder and Driver####. It would be pretty cool if it would :grin:


Really cool , although some of us aren’t really that knowledged in programming, could you make a gui for those inexperienced users?

Hello, I could maybe make a gui, what operating system do you want it for?

I’m on Windows but you could also make one for Linux.

Would python work on both Linux and Winblows?

I don’t think the posted tool works on Windows out of the box, as it was designed for Chromebooks.

I’ve pushed an update to fw-ectool that adds a convenience command for setting the charge limit.

I’ve also learned that the charge limit and key mapping can be queried!

Charge Limit

# Query
$ ectool --interface=fwk fwchargelimit

# Set
$ ectool --interface=fwk fwchargelimit 65

Key Mapping

Question: What scancode is emitted by key matrix position r4, c8?

ectool --interface=fwk raw 0x3E0C d1,d0,b4,b8,w0
3e0c(...12 bytes...)
01 00 00 00 00 00 00 00 04 08 00 00             |............    |
                         ^  ^
                         |  |
                         |  |
Read 136 bytes           |  |
01 00 00 00 00 00 00 00 04 08 09 00 00 00 00 00 |................|
                         ^  ^ ^^^^^
                         |  |     `-Scancode
                         |  `-Column

(the above exchange has been annotated; the tool doesn’t do cool stuff like that :slightly_frowning_face:)

Looks like it’s 0x0009, or F10!



It appears this command is functional:

sudo ectool --interface=fwk fanduty XX


sudo ectool --interface=fwk autofanctrl 

does restore automatic fan control. I am considering writing a script and a companion service with rules for pushing the fan to high run for certain applications (X-Plane, Handbrake, etc) that seem to get just a little more fps work done with the fan pre-spun up to 100% vs in auto…


if you use something like pyinstaller, you can easily generate executables for the platform you are using it on. One option would be to use Wine for the windows executable and your Linux OS for the linux one.

1 Like

One interesting question, and this may be one for @Kieran_Levin , is what happens if the charge limit is set at zero?

It rejects it; the actual lower limit is 20.

Thx. Now have a charge limit set to ~60-80.
Charge limit set to 80 whenever charge drops below 61.
Charge limit set to 60 whenever charge goes above 79.

This mimicks my thinkpad’s settings and hopefully reduces charge cycles used. I’m not sure if maintaining a constant 60% charge uses more or less cycles than maintaining a charge between 60-80 while always plugged in. But I assume it doesn’t hurt too much if its worse since the battery is unused and will take days to go from 80% to 60% anyways.

Edit: I take that back. Its discharging at a rate that makes me think its only using battery power, though the led is flashing the same as it was when set to a static 60%. I guess if the charge limit is 60% and battery is 80%, it uses the battery without charging or supplementing power from ac until the limit is reached?

From my mid-grade understanding of battery chemistry and longevity issues, I would choose to keep a single constant moderate charge level, versus constantly cycling up and down 20%.


This is awesome work!

Excited to see if there’s any possibility for more advanced keyboard remapping. Would love to use the fn key as a modifier.

Unfortunately, it looks like the Fn key mappings are compiled in (today) and can’t be changed. You can change what the key labelled Fn does by mapping it to a different scancode and assigning different behaviors to it in the OS, but you won’t be able to treat it like a “layer” in the classical keyboarding sense.


@DHowett - thank you for building this, I’m enjoying poking around at things a bit. I noted on your page that the power led does not support blue (fyi for those reading, if you enter an incorrect color, in my case orange, you are presented with valid values - red green blue yellow white amber). When testing the different values, I found that when I entered blue as the color, the led appeared to turn off. While the ability at a glance to see if the power is on is lost, those who find the power led too bright may find this to be a reasonable temporary workaround to turn it off when so desired.


I’ve pushed a minor update to the page documenting host command 3E02, some Fn key combinations, VBAT RAM, and that fanduty/autofanctrl works (thanks @D.H!)

3E02 (more info)

This command seems to disable the FN/Power keys. When you call it with 0x01, it remaps them to send scancodes e016 and e025 (respectively). When you call it with 0x5A it resets some local configuration in the EC (charge limit, keyboard backlight, etc.)

Keys (more info)

Fn+B is Ctrl+Break, Fn+P is Pause, and Fn+K is Scroll Lock.

Using blue to turn off the power button LED is pretty clever, actually!