TLDR: Bios unlocking and hacking to undervolt and unlock vGPU profiles
For anyone else hardware hacking on the Lenovo platform, I did a few things:
I have an m920q with an i9 9900T ES cpu, and 64GB of memory. I'm using this to host proxmox and a couple of windows and a debian VM. I wanted to have hardware accelerated environments and the Lenovo bios options leave a little to be desired.
Figured I would put some LLM tokens to good use and get it all working. Getting there wasn't easy. Lenovo locks down these boards heavily, and getting the Intel driver to cooperate took a lot of reverse engineering. Here is the step-by-step journey of how I did this.
Phase 1: Defeating the 35W Power Limit & Plundervolt
The i9-9900T ES is heavily throttled by Lenovo’s conservative 35W limits. Worse, modern BIOS updates include the "Plundervolt" microcode patches that completely lock out software undervolting.
- The Flash: I used a CH341A hardware flasher to dump the 8MB BIOS and 16MB BIOS chips downgraded the CPU microcode to 0x98 (pre-Plundervolt), and flashed it back.
Hardware Preparation
The Lenovo m920q uses a "split BIOS" design across two physical SPI flash chips on the motherboard (located near the PCI Slot and the WiFi slot):
- Chip 2 (8MB): W25Q64 (Intel ME, Microcode, and Descriptor) - the one nearest to the rear of the machine labeled bios 2.
The Patching Process (Microcode Downgrade)
The goal was to replace the modern, locked microcode (e.g., 0xCA or higher) with version 0x98, which allows for software voltage control (MSR 0x150).
Tool: UEFITool. (or MMTool).
Locate the Microcode:
Opened the bios_backup.bin in UEFITool and searched for the "Microcode" volumes.
Identify CPUID:
For the i9-9900T (Coffee Lake), the CPUID is typically 906ED or 906EA.
Replace:
Downloaded the specific 0x98 microcode binary (e.g., cpu906ed_plat22_ver00000098_2018-09-03_PRD.bin) and used "Replace Body" in UEFITool to swap out the new version for the old one.
Save:
Exported the file as modded_bios_8mb.bin and flashed back to the chip. I spent some time setting the ME/AMT settings back as this was erased during the process.
Flash:
flashrom -p ch341a_spi -c "W25Q64BV/W25Q64CV/W25Q64FV" -w modded_bios_8mb.bin
- The Power Mod: With the microcode downgraded, I booted into a standalone UEFI shell (via USB) and used setup_var.efi to disable the Overclocking Lock EFI variable (0x7BD to 0x00).
To apply the undervolt and power limits on Proxmox, we used the intel-undervolt utility. Here are the specific configuration values and commands used to lock in the 55W performance profile.
All settings are stored in /etc/intel-undervolt.conf on the proxmox host. Below is the exact block we used for i9-9900T:
# CPU Undervolting (Offsets in mV)
# Index 0: CPU Core | Index 1: GPU | Index 2: CPU Cache
undervolt 0 'CPU Core' -100
undervolt 1 'GPU' 0
undervolt 2 'CPU Cache' -100
# Power Limits (Watts/Time)
# PL1: Sustained Power (55W)
# PL2: Burst Power (92W)
power package 0 55 92
Once the configuration file is saved, you run these commands to apply the settings and ensure they survive a reboot.
Apply the settings immediately:
intel-undervolt apply
Check the current status (to verify the -100mV took effect):
intel-undervolt read
Enable the systemd service (so it applies automatically at boot):
systemctl enable --now intel-undervolt
To prove the 55W PL1 was working, we ran a stress test and monitored the package power:
# Run a 16-thread stress test
sysbench cpu --threads=16 --time=300 run
# In another terminal, watch the power draw
watch -n 1 "intel-undervolt read | grep 'Package'"
* Before: The CPU would hit 35W and immediately throttle the clocks down to ~1.8GHz.
* After: The CPU sustained 55W continuously, maintaining much higher boost clocks (up to its 3.8GHz cap) without thermal throttling.
The Result: I used intel-undervolt in Proxmox to push PL1 to 55W and applied a -100mV core/cache undervolt. Sysbench scores jumped to 14,662 pts, outperforming a 95W i7-8700K, while peaking at only 76°C.
Phase 2: The GVT-g VRAM Wall
With the CPU unleashed, I configured Intel GVT-g to split the UHD 630 iGPU among my VMs. But when I tried to start them, Proxmox threw the dreaded error:
pci device '0000:00:02.0' has no available instances of 'i915-GVTg_V5_4'
- The Problem: The Lenovo BIOS defaults to a measly 32MB of DVMT Pre-Allocated memory (Stolen Memory). To run a high-res V5_4 vGPU slice, you need at least 128MB.
- The Dead Ends: I tried using common Lenovo EFI offsets (like 0x18F and 0x8F5) to increase the VRAM. The system ignored them. I then tried the Proxmox kernel parameter ignore_resource_conflict=1 hoping the Linux kernel would just overcommit the memory. It failed; the hardware driver strictly enforces the limit.
- The Breakthrough (IFR Extraction): Lenovo shuffles hex addresses with every minor BIOS update. Guessing was useless. I took my BIOS dump, ran it through UEFIExtract to pull the Setup PE32 module, and pushed it through ifrextractor. This generated a text map of my exact BIOS. We found the real offset for DVMT Pre-Allocated was 0xA44. We set it to 0x08 (256MB).
Phase 3: The Aperture Trap
After unlocking 256MB of VRAM, I could successfully boot one VM. But starting a second one threw the "no available instances" error again. Why?
- The Investigation: Back to the IFR text dump. The Intel driver requires "Aperture Size" (the CPU-to-GPU memory window) to map the VMs. By default, Lenovo sets this to 256MB. Each VM needs a large chunk of that window
- The Fix: In the IFR dump, we found the hidden offsets for GTT Size (0x992) and Aperture Size (0x993). We pushed the GTT to 8MB (0x03) and commanded the Aperture to open to a massive 2048MB (0x0F)
- The Result: Rebooted, ran lspci -v, and saw Memory at ... [size=2G]. Suddenly, the sysfs tree showed 3 available instances!
Here are the UEFI patch offsets for my platform (you're might be different if you're on a different BIOS/CPU)
| Setting | Offset | Value | What it Does |
|---|
| Overclocking Lock | 0x7BD | 0x00 | Unlocks software undervolting |
| BIOS Lock | 0xB7B | 0x00 | Disables flash protection |
| DVMT Pre-Allocated | 0xA44 | 0x08 | Increases Stolen Memory to 256MB |
| GTT Size | 0x992 | 0x03 | Increases mapping table to 8MB |
| Aperture Size | 0x993 | 0x0F | Increases Aperture to 2048MB (2GB) |