Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lenovo ThinkCentre M920q — Coreboot Flash Guide

Tested and verified guide for flashing coreboot on the Lenovo ThinkCentre M920q. Should also work on the M720q, M920x, and ThinkStation P330 Tiny (same board, untested).

Overview

FeatureValue
CPUIntel Core 8th/9th Gen (Coffee Lake / Coffee Lake Refresh)
ChipsetIntel Q370 (Cannon Lake PCH-H)
DRAM2× SO-DIMM DDR4-2400/2666, max 64 GB
ME versionv12
Super I/ONCT6686D-L (Nuvoton)
TPMInfineon SLB 9670VQ2.0 (TPM 2.0)
Boot GuardNot fused — direct external flash works
SoC coreboot directorysoc/intel/cannonlake

What Works

  • USB 3.0 / 2.0 (front and rear)
  • USB-C (charging and data)
  • Gigabit Ethernet
  • SATA and NVMe
  • HDMI
  • WiFi slot
  • TPM 2.0
  • Internal speaker
  • COM1 serial (via daughter board)
  • PCIe x8 riser slot (tested with BA7H70 Rev 1.2 riser + Intel X540-T2 10GbE)
  • S3 suspend/resume
  • PXE boot (via edk2 built-in UEFI network stack)

Known Issues

  • MRC cache corruption with HT-enabled CPUs — coreboot’s memory training cache appears to capture invalid state when used with hyperthreaded Coffee Lake CPUs (i7-8700, i7-8700T, i9-9900T). Once written, this poisoned cache prevents subsequent boots even with non-HT CPUs until the BIOS region is reflashed. Workaround: disable MRC cache entirely (see CPU Compatibility section). Root cause not yet identified.
  • Soft reboot from OS hangs — power cycle requiredreboot from Linux or BSD leaves the system in a black screen state. Caused by the ACPI reset path on these coreboot ports. Workaround: see Post-Install OS Configuration.
  • Warm reboot occasionally fails after disabling MRC cache — without cached training data, FSP-M trains memory from scratch on every boot, including warm reboots. Occasionally trips up. Power cycle always recovers. Acceptable tradeoff for the MRC workaround.
  • HT defaults to disabled at first boot — the M920q port exposes a hyper_threading CMOS option but cmos.default doesn’t set it, so fresh CMOS reads as 0 = Disable. Add hyper_threading=Enable to src/mainboard/lenovo/m720q_m920q/cmos.default before building, or toggle at runtime with nvramtool after first boot.
  • Front audio jacks do not work (upstream coreboot bug)
  • DIMM2 slot does not workcoreboot bug #592. Only DIMM1 is usable. Use a single larger SO-DIMM if more RAM is needed
  • ME Communication Controller (PCI 16.0) remains visible with HAP bit set — this is normal hardware enumeration, the ME firmware is disabled

CPU Compatibility

CPUCores / ThreadsTDPHTStatus
i5-8500T6C / 6T35WNo✅ Works out of the box (developer tested)
i3-8100T4C / 4T35WNo✅ Works out of the box
i7-8700T6C / 12T35WYes⚠️ Works with MRC cache disabled
i7-87006C / 12T65WYes⚠️ Untested with MRC cache disabled — likely works
i9-9900T8C / 16T35WYes❓ Untested — likely needs MRC cache disabled

For HT-enabled CPUs (any i7/i9 Coffee Lake), disable MRC cache by adding the following to your defconfig before building:

# CONFIG_CACHE_MRC_SETTINGS is not set

Then rebuild with make olddefconfig && make clean && make -j$(nproc).

Important warning about CPU swapping: if you boot the system with an HT-enabled CPU while MRC cache is enabled, the cache becomes poisoned and all subsequent boots will fail — including with previously working non-HT CPUs — until you reflash the BIOS region. Either disable MRC cache from the start, or always reflash before swapping CPUs.

The trade-off for disabling MRC cache: cold boot is ~1-2 seconds slower (full memory training every boot instead of using cached values), and warm reboots occasionally fail and require a power cycle to recover.

Board Variants — M720q / M920q / M920x / P330 Tiny

The M920q, M920x, M720q, and ThinkStation P330 Tiny all share the same base motherboard (EQ370 NM-B551 / IQ3X0IL) with different SMD component populations. The same coreboot ROM should work on all four variants. Only the M920q has been tested — the others should work in principle but are unconfirmed.

VariantCoreboot Tested
M920qYes
M720qNo (should work — same board)
M920xNo (should work — same board)
P330 TinyNo (should work — same board)

Flash Chips

The M920q has two SOIC-8 SPI flash chips, both 3.3V, soldered (not socketed):

ChipBoard LabelModelSizeflashprog -c stringPull-up Notes
BIOS1U31W25Q128JV16 MiBW25Q128.V/WP and /HOLD pulled high by board — no action needed
BIOS2U32W25Q64JV8 MiBW25Q64JV-.Q/WP (pin 3) and /HOLD (pin 7) must be held HIGH manually

Total ROM size: 24 MiB (16 + 8).


Prerequisites

For SPI programmer setup (Raspberry Pi or Pico), flashprog installation, GPIO wiring tables, and parts lists see Tools — SPI Programmers.

M920q-Specific Notes

In addition to the standard programmer setup in Tools, the M920q requires:

  • A 3.3V splitter wire for the BIOS2 chip — split a Dupont wire from any RPi 3.3V pin to both pin 3 (/WP) and pin 7 (/HOLD) on the Pomona clip. BIOS1 has on-board pull-ups for these pins; BIOS2 does not.
  • See Tools — /WP and /HOLD Troubleshooting for general guidance on pull-up issues.

Important: Disconnect the M920q power adapter before attaching the programmer. Do not supply mains power while flashing.


Step 1: Read Original Firmware

Triple-read each chip — see Tools — Triple-Read Methodology for the general procedure. All three SHA256 checksums must match before proceeding. If they don’t, reseat the clip and read again.

BIOS1 (16 MiB)

No extra pull-ups required.

sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q128.V" -r m920q_bios1_dump1.bin
sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q128.V" -r m920q_bios1_dump2.bin
sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q128.V" -r m920q_bios1_dump3.bin

sha256sum m920q_bios1_dump*.bin

BIOS2 (8 MiB)

Move clip to BIOS2. Hold /WP (pin 3) and /HOLD (pin 7) HIGH using the 3.3V splitter described in Prerequisites.

sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q64JV-.Q" -r m920q_bios2_dump1.bin
sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q64JV-.Q" -r m920q_bios2_dump2.bin
sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q64JV-.Q" -r m920q_bios2_dump3.bin

sha256sum m920q_bios2_dump*.bin

Troubleshooting: If BIOS2 is not detected, verify /WP and /HOLD are held HIGH. BIOS1 has on-board pull-ups for these pins; BIOS2 does not. You may also try lowering spispeed to 512 if detection is unreliable. See Tools — /WP and /HOLD Troubleshooting for more.

Combine and Back Up

cat m920q_bios1_dump1.bin m920q_bios2_dump1.bin > m920q_original_24mb.bin

ls -la m920q_original_24mb.bin
# Must be exactly 25165824 bytes (24 MiB)

Back up all dump files to a safe off-machine location. These are your recovery lifeline.


Step 2: Extract Regions and Set HAP Bit

Perform these steps on your build machine. For ifdtool build instructions see Tools — ifdtool.

Extract Regions

util/ifdtool/ifdtool -p cnl -x /path/to/m920q_original_24mb.bin

The -p cnl flag specifies Cannon Lake platform. This produces:

Output FileContents
flashregion_0_flashdescriptor.binIntel Flash Descriptor
flashregion_1_bios.binBIOS region (coreboot replaces this — not needed)
flashregion_2_intel_me.binIntel CSME firmware
flashregion_3_gbe.binGigabit Ethernet NVM (contains MAC address)

Set HAP Bit on Descriptor

The HAP (High Assurance Platform) bit tells the ME to enter a graceful disabled state after early initialisation.

Do NOT use me_cleaner on this platform. The M920q uses ME v12. Stripping ME modules with me_cleaner puts ME v12 into an error state and can prevent boot. The coreboot project recommends using the HAP bit rather than me_cleaner.

util/ifdtool/ifdtool -p cnl -M 1 flashregion_0_flashdescriptor.bin
mv flashregion_0_flashdescriptor.bin flashregion_0_flashdescriptor.bin.orig
mv flashregion_0_flashdescriptor.bin.new flashregion_0_flashdescriptor.bin

Place Blobs

mkdir -p 3rdparty/blobs/mainboard/lenovo/m720q_m920q

cp flashregion_0_flashdescriptor.bin \
   3rdparty/blobs/mainboard/lenovo/m720q_m920q/descriptor.bin

cp flashregion_2_intel_me.bin \
   3rdparty/blobs/mainboard/lenovo/m720q_m920q/me.bin

cp flashregion_3_gbe.bin \
   3rdparty/blobs/mainboard/lenovo/m720q_m920q/gbe.bin

The ME binary must be the original unmodified version extracted from your dump. The VBT (data.vbt) is already bundled in the coreboot source tree at src/mainboard/lenovo/m720q_m920q/data.vbt. The FSP binaries are fetched automatically via git submodules (3rdparty/fsp/CoffeeLakeFspBinPkg/).


Step 3: Configure and Build

For build dependencies and cross-compiler toolchain setup see Tools — Build Dependencies and Tools — Cross-Compiler.

Using the Provided defconfig

Save the following as defconfig in your coreboot root directory. It configures:

  • edk2 payload (MrChromebox fork) with PXE, UEFI Shell, TPM 2.0, Secure Boot
  • SMMStore v2 for persistent UEFI NVRAM
  • libgfxinit for native graphics initialisation
  • Serial console on COM1 at 115200 baud
#
# Lenovo ThinkCentre M920q coreboot defconfig
#
# Payload: edk2 (MrChromebox) — PXE, UEFI Shell, TPM 2.0, Secure Boot, NVRAM
# ME: HAP bit set in descriptor (no me_cleaner)
#
# CPU compatibility:
#   Works out of the box: i5-8500T, i3-8100T (no HT)
#   Works with MRC cache disabled: i7-8700T (HT)
#   Untested but likely works with MRC cache disabled: i7-8700, i9-9900T
#
# For HT-enabled CPUs (any i7/i9 Coffee Lake), uncomment this line:
#   # CONFIG_CACHE_MRC_SETTINGS is not set
# Tradeoff: ~1-2s slower cold boot, occasional warm reboot fails
# WARNING: Booting an HT CPU without this poisons MRC cache for ALL CPUs
#
# Known issues:
#   - Soft reboot from OS hangs — requires reboot=pci (Linux) or
#     hw.acpi.handle_reboot=0 (FreeBSD/OPNsense) post-install
#   - DIMM2 slot broken (bug #592) — use DIMM1 only
#   - Front audio jacks don't work
#   - BIOS2 chip needs /WP + /HOLD held HIGH when flashing externally
#
CONFIG_VENDOR_LENOVO=y
CONFIG_BOARD_LENOVO_M920Q=y
CONFIG_IFD_BIN_PATH="3rdparty/blobs/mainboard/lenovo/m720q_m920q/descriptor.bin"
CONFIG_ME_BIN_PATH="3rdparty/blobs/mainboard/lenovo/m720q_m920q/me.bin"
CONFIG_GBE_BIN_PATH="3rdparty/blobs/mainboard/lenovo/m720q_m920q/gbe.bin"
# CONFIG_USE_ME_CLEANER is not set
# Uncomment for HT-enabled CPUs (i7-8700, i7-8700T, i9-9900T, etc.):
# # CONFIG_CACHE_MRC_SETTINGS is not set
CONFIG_PAYLOAD_EDK2=y
CONFIG_EDK2_REPO_MRCHROMEBOX=y
CONFIG_EDK2_UEFIPAYLOAD=y
CONFIG_SMMSTORE=y
CONFIG_SMMSTORE_V2=y
# iPXE network boot — adds "Network Boot" to edk2 boot menu
CONFIG_EDK2_ENABLE_IPXE=y
CONFIG_EDK2_IPXE_OPTION_NAME="Network Boot"
# CONFIG_RUN_FSP_GOP is not set
CONFIG_CONSOLE_SERIAL=y
CONFIG_TTYS0_BAUD=115200
CONFIG_DEFAULT_CONSOLE_LOGLEVEL=7

Then build:

make distclean
cp defconfig .config
make olddefconfig
make -j$(nproc)

Verify Output

ls -la build/coreboot.rom
# Must be 25165824 bytes (24 MiB)

./build/cbfstool build/coreboot.rom print
# Should list: bootblock, romstage, ramstage, dsdt.aml, payload (edk2),
# cpu_microcode_blob.bin, vbt.bin, fspm.bin, fsps.bin, etc.

Step 4: Split and Flash

Split the ROM

The 24 MiB ROM must be split to match the two physical flash chips:

dd if=build/coreboot.rom of=build/coreboot_bios1.rom bs=1M count=16
dd if=build/coreboot.rom of=build/coreboot_bios2.rom bs=8M skip=2

# Sanity check — recombine and compare
cat build/coreboot_bios1.rom build/coreboot_bios2.rom > build/coreboot_recombined.rom
sha256sum build/coreboot.rom build/coreboot_recombined.rom
# Must match

Flash BIOS1 (16 MiB)

No extra pull-ups needed.

sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q128.V" -w coreboot_bios1.rom

# Verify
sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q128.V" -r verify_bios1.bin
sha256sum coreboot_bios1.rom verify_bios1.bin

Flash BIOS2 (8 MiB)

Move clip to BIOS2. Hold /WP (pin 3) and /HOLD (pin 7) HIGH (same 3.3V splitter as reading).

sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q64JV-.Q" -w coreboot_bios2.rom

# Verify
sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q64JV-.Q" -r verify_bios2.bin
sha256sum coreboot_bios2.rom verify_bios2.bin

Step 5: First Boot

  1. Install RAM in DIMM1 only — DIMM2 does not work under coreboot (bug #592)
  2. Connect a display via HDMI (try DisplayPort if no output on HDMI)
  3. Power on and wait up to 2 minutes — the first boot performs memory training with no display output. Subsequent boots are fast (MRC cache)
  4. The edk2 splash screen (coreboot hare) should appear. Press Esc to enter the boot menu

If It Doesn’t Boot

  • Verify RAM is in DIMM1 only
  • Try both display outputs (HDMI and DisplayPort)
  • Wait the full 2 minutes — memory training can be slow
  • If still no output, flash your original backup dumps to restore stock firmware (see Recovery below) and review your build configuration

Step 6: Post-Boot Verification

ME Status

sudo dmesg | grep -i mei

With HAP bit set, you should see only mei_hdcp binding for HDCP content protection via the i915 graphics driver:

mei_hdcp 0000:00:16.0-b638ab7e-94e2-4ea2-a552-d1c54b627f04: bound 0000:00:02.0 (ops i915 hdcp_ops [i915])

There should be no mei_me driver initialising and no AMT-related messages. The HECI controller at 00:16.0 will still appear in lspci as Communication controller: Intel Corporation Cannon Lake PCH HECI Controller — this is normal hardware enumeration. HAP prevents the ME firmware from actively running while the PCH hardware remains visible on the PCI bus.


Post-Install OS Configuration

Soft Reboot Fix (Required)

After installing an OS, you’ll likely find that reboot from the command line leaves the system in a black-screen hang state requiring a power cycle. This affects both Linux and BSD distributions and is caused by the ACPI reset path on these coreboot ports failing to properly trigger a chipset reset. The fix is to bypass the ACPI reset path entirely and use a direct CF9 chipset reset, which is functionally equivalent to a hardware reset.

Behaviour under Windows is unknown — neither tested nor reported. BSD-family systems (FreeBSD, OPNsense, pfSense) are assumed to be affected and have been confirmed to need the same workaround.

Linux (Debian, Ubuntu, Arch, Fedora, etc.)

Edit /etc/default/grub and add reboot=pci to the kernel command line:

GRUB_CMDLINE_LINUX_DEFAULT="quiet reboot=pci"

Then update GRUB:

sudo update-grub                              # Debian/Ubuntu
sudo grub-mkconfig -o /boot/grub/grub.cfg     # Arch/Fedora

For systemd-boot or other bootloaders, add reboot=pci to the kernel command line in the relevant boot entry config.

FreeBSD / OPNsense / pfSense

Add a loader tunable:

echo 'hw.acpi.handle_reboot="0"' >> /boot/loader.conf.local

On OPNsense this can also be set via the GUI: System → Settings → Tunables → Add → hw.acpi.handle_reboot = 0.

This setting takes effect on the next reboot, so the first reboot after applying it will still need a power cycle. Subsequent reboots should work cleanly.


Recovery

If the machine doesn’t boot, flash your original backup dumps externally:

# BIOS1 (no pull-ups needed)
sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q128.V" -w m920q_bios1_dump1.bin

# BIOS2 (hold /WP and /HOLD HIGH)
sudo flashprog -p linux_spi:dev=/dev/spidev0.0,spispeed=1000 \
  -c "W25Q64JV-.Q" -w m920q_bios2_dump1.bin

Internal Flashing (Subsequent Updates)

See Tools — Internal Re-Flashing. For the M920q, flash the BIOS region only:

sudo flashrom -p internal -N -w coreboot.rom --ifd -i bios

This writes only the BIOS region, leaving the descriptor, ME, and GbE regions untouched. No need to split the ROM for internal flashing.


Quick Reference

ItemDetails
ROM size24 MiB (two chips: 16 + 8)
BIOS1 splitdd if=coreboot.rom of=bios1.rom bs=1M count=16
BIOS2 splitdd if=coreboot.rom of=bios2.rom bs=8M skip=2
BIOS2 quirk/WP + /HOLD must be held HIGH externally
HAP bitifdtool -p cnl -M 1 descriptor.bin
DIMMSlot 1 only (bug #592)
Works out of the boxi5-8500T, i3-8100T (no HT)
Works with MRC cache disabledi7-8700T (HT)
MRC cache workaround# CONFIG_CACHE_MRC_SETTINGS is not set in defconfig
CPU swap warningBooting an HT CPU with MRC cache enabled poisons the cache for ALL CPUs — must reflash
Soft reboot fix (Linux)reboot=pci kernel parameter
Soft reboot fix (FreeBSD/OPNsense)hw.acpi.handle_reboot="0" loader tunable
iPXE / PXE bootCONFIG_EDK2_ENABLE_IPXE=y in defconfig
Boot GuardNot fused — no deguard needed
ME disableHAP bit only — do NOT use me_cleaner (ME v12)
Same board familyM720q, M920q (tested), M920x, P330 Tiny — all EQ370 NM-B551

References

License

This guide is released under CC BY-SA 4.0.