2026-03-26 — Initial Release
UnderVolter is now available as a native UEFI application for Intel Core processors spanning 2nd through 14th generation (Sandy Bridge to Raptor Lake). Supports voltage offset programming, power limit configuration, turbo ratio adjustments, and V/F curve overrides. Runs directly from UEFI firmware — no operating system required. Configuration via
UnderVolter.iniwith per-architecture profiles and safe default values.
UnderVolter — Native UEFI Undervolting Utility

📚 Table of Contents
- Overview
- Architecture
- Supported Processors
- Voltage Domains
- Power Limits
- Safety Features
- Configuration File
- BIOS Unlocking
- Deployment Methods
- OpenCore Integration
- EFI Boot Method
- Windows Boot Manager
- Build System
- QEMU Testing
- Source Code Structure
- Troubleshooting
Overview
UnderVolter is a native UEFI application that programs Intel CPU power management parameters directly from firmware, before any operating system loads. This approach provides several advantages over OS-based tools:
| Advantage | Description |
|---|---|
| No OS Dependencies | Runs on bare metal — no kernel drivers, no kernel extensions, no ring-0 components |
| Universal Compatibility | Works with Windows, Linux, macOS (Hackintosh), or headless server configurations |
| Persistent Settings | Applied at every boot — no need for startup scripts or scheduled tasks |
| Full MSR Access | Unrestricted access to Model-Specific Registers without kernel interference |
| Safe Defaults | Conservative voltage offsets with 20% safety margin based on community data |
| Emergency Exit | 2-second ESC key window to skip programming if system becomes unstable |
What Can UnderVolter Do?
- Voltage Offset Programming — Reduce CPU voltage per domain (P-Cores, E-Cores, Ring, Uncore, GT)
- Power Limit Configuration — Set PL1 (long-term), PL2 (short-term), PL3, PL4, and PP0 limits
- Turbo Ratio Control — Force maximum turbo ratios for P-Cores and E-Cores
- V/F Curve Overrides — Program custom voltage/frequency points per domain
- ICC Max Configuration — Set maximum current limits per voltage domain
- cTDP Control — Configure Configurable TDP levels and locking mechanisms
- Multi-Package Support — Handles multi-socket systems with per-package configuration
What UnderVolter Does NOT Do
- ❌ No dynamic adjustment — Settings applied once at boot (use ThrottleStop/Intel XTU for runtime tuning)
- ❌ No AMD support — Targets Intel Core processors only (AMD requires different MSR interfaces)
- ❌ No GUI interface — Text-based console UI with keyboard input (ESC to abort, F10 to override warnings)
- ❌ No automatic tuning — Does not test stability or find optimal values (user must configure via INI file)
Architecture
Execution Flow
- Entry Point — UEFI firmware loads
UnderVolter.efias application or driver - Configuration Load — Parses
UnderVolter.inifrom same directory or known fallback paths - CPU Detection — Reads CPUID to determine architecture, family, model, stepping
- Profile Selection — Matches detected CPU to INI profile (e.g.,
Profile.CoffeeLake) - MP Initialization — Locates all logical processors via EFI MP Services Protocol
- Package Discovery — Enumerates physical packages and cores per package
- VR Topology Probe — Discovers voltage regulator addresses and types via OC Mailbox
- Voltage Programming — Applies offset voltages per domain via FIVR interface
- Power Limit Programming — Configures PL1/PL2/PL3/PL4/PP0 via MSR and MMIO
- Lock Application — Locks configuration registers to prevent OS modification
- Status Display — Shows applied settings and waits for user acknowledgment
Memory Model
UnderVolter operates entirely within UEFI runtime services memory:
| Component | Size | Lifetime |
|---|---|---|
| Code Section | ~45 KB | Loaded by firmware |
| INI Parser | ~8 KB | Stack-allocated |
| CPU Data Tables | ~12 KB | Static read-only data |
| MP Dispatcher | ~5 KB | Heap-allocated |
| Console Buffer | ~16 KB | Framebuffer-backed |
Total runtime memory footprint: < 100 KB
Supported Processors
UnderVolter includes pre-configured profiles for the following Intel microarchitectures:
| Architecture | Generation | CPUID | Example Models | Safe Offset (P-Core) |
|---|---|---|---|---|
| Arrow Lake | Core Ultra 200S/HX (15th) | 6,197,* / 6,198,* |
Core Ultra 9 285K | 0 mV (new arch) |
| Meteor Lake | Core Ultra 100/200 H/U (14th) | 6,170,* |
Core Ultra 7 155H | 0 mV (new arch) |
| Raptor Lake | 13th/14th Gen | 6,183,* / 6,186,* / 6,191,* |
i9-14900K, i7-13700K | -80 mV |
| Alder Lake | 12th Gen | 6,151,* / 6,154,* |
i9-12900K, i5-12600K | -80 mV |
| Rocket Lake | 11th Gen Desktop | 6,167,* |
i9-11900K, i7-11700K | -80 mV |
| Tiger Lake | 11th Gen Mobile | 6,140,* / 6,141,* |
i7-1185G7, i7-11800H | -80 mV |
| Comet Lake | 10th Gen | 6,165,* / 6,166,* |
i9-10900K, i7-10700K | -40 mV (conservative) |
| Coffee Lake | 8th/9th Gen | 6,158,* / 6,159,* |
i9-9900K, i7-9750H | -180 mV (user tested) |
| Kaby Lake | 7th Gen | 6,142,* / 6,158,* |
i7-7700K, i5-7600K | -80 mV |
| Cascade Lake | Skylake-X Refresh (server/HEDT) | 6,85,* (step 6-7) |
i9-10980XE, Xeon W-2295 | -80 mV |
| Skylake | 6th Gen | 6,78,* / 6,94,* / 6,85,* (step 4) |
i7-6700K, i7-6820HQ | -80 mV |
| Broadwell | 5th Gen | 6,61,* / 6,71,* / 6,86,* |
i7-5775C, i5-5675C | -80 mV |
| Haswell | 4th Gen | 6,60,* / 6,69,* |
i7-4790K, i5-4690K | -65 mV |
| Ivy Bridge | 3rd Gen | 6,58,* / 6,62,* |
i7-3770K, i5-3570K | -65 mV |
| Sandy Bridge | 2nd Gen | 6,42,* / 6,45,* |
i7-2600K, i5-2500K | -65 mV |
Notes on Specific Architectures
Arrow Lake & Meteor Lake (Core Ultra)
- Disaggregated tile architecture — compute tile, SoC tile, IO tile are separate dies
- MSR 0x150 / OC Mailbox voltage offset interface still present on ARL-S desktop
- Voltage domains map to compute tile only (P-Core, E-Core, Ring); GT and Uncore have separate VRs outside traditional FIVR scope
- Default profiles set to 0 mV until community stability data matures
Lunar Lake (Core Ultra 200V, 15th Gen laptop)
- Uses embedded power delivery (ePD) — traditional MSR 0x150 voltage offset does not apply
- No profile in UnderVolter — this architecture is not supported
Tiger Lake (11th Gen Mobile)
- All-P-Core design (Willow Cove) — no E-Core domain;
OffsetVolts_ECOREhas no effect GTSLICEandGTUNSLICEdomains respond to offsets similarly to Skylake generation6,140,*= TGL-U (15 W);6,141,*= TGL-H (35–45 W)
Comet Lake (10th Gen)
- Higher failure rate at aggressive undervolts compared to other generations
- Community reports: -50 mV shows ~5% failure rate; -120 mV is risky
- Conservative defaults: -40 mV P-Core, -30 mV Ring
Coffee Lake (8th/9th Gen)
- User-tested values included: -180 mV P-Core, -100 mV Ring (i7-9750H)
- Excellent undervolting headroom on most samples; well-documented in overclocking communities
Hybrid Architecture (Alder Lake and newer)
- Separate P-Core and E-Core voltage domains
- Ring domain often shares voltage plane with E-Cores on desktop variants
- GT Slice/Uncore domains may respond independently
Voltage Domains
UnderVolter programs the following voltage domains via FIVR (Fully Integrated Voltage Regulator):
| Domain | INI Name | Description | Typical Safe Offset |
|---|---|---|---|
| IACORE | OffsetVolts_IACORE |
P-Cores (Performance cores) | -80 mV |
| ECORE | OffsetVolts_ECORE |
E-Cores (Efficiency cores, hybrid CPUs only) | -80 mV |
| RING | OffsetVolts_RING |
Ring bus, LLC, memory controller | -60 mV |
| UNCORE | OffsetVolts_UNCORE |
System Agent, iGPU (non-GT) | -50 mV |
| GTSLICE | OffsetVolts_GTSLICE |
Graphics execution units | -40 mV |
| GTUNSLICE | OffsetVolts_GTUNSLICE |
Graphics unsliced resources | -40 mV |
Voltage Offset Encoding
UnderVolter uses Intel's native MSR 0x150 voltage offset encoding:
| Range | Resolution | Step Size |
|---|---|---|
| -250 mV to +250 mV | ~0.5 mV | 1/256 of range |
The encoding table (OffsetVolts_S11[256]) maps linear mV values to the nonlinear MSR format required by Intel's voltage regulator.
ICC Max Configuration
Maximum current limits can be set per domain:
IccMax_IACORE = 65535 ; 65535 = unlimited (MAX)
IccMax_RING = 65535
IccMax_ECORE = 65535
IccMax_UNCORE = 65535
IccMax_GTSLICE = 0 ; 0 = use default
IccMax_GTUNSLICE = 0
Values are in 1/4 Ampere units (e.g., 620 = 155 A).
Power Limits
UnderVolter supports comprehensive power limit programming across multiple interfaces:
MSR Power Limits (Per-Package)
| Limit | INI Parameter | Description | Typical Value |
|---|---|---|---|
| PL1 | MsrPkgPL1_Power |
Long-term power limit (TDP) | 4294967295 (unlimited) |
| PL2 | MsrPkgPL2_Power |
Short-term power limit (28-56 sec) | 4294967295 (unlimited) |
| PL2 Time | MsrPkgPL_Time |
PL2 time window | 4294967295 (max) |
| PL3 | MsrPkgPL3_Power |
Peak power limit (ms range) | 4294967295 |
| PL4 | MsrPkgPL4_Current |
Current limit (A × 256) | 4294967295 |
| PP0 | MsrPkgPP0_Power |
Core power limit | 4294967295 |
MMIO Power Limits (Platform-Wide)
| Limit | INI Parameter | Description |
|---|---|---|
| MMIO PL1 | MmioPkgPL1_Power |
Package power limit via MMIO |
| MMIO PL2 | MmioPkgPL2_Power |
Package PL2 via MMIO |
| PSys PL1 | PlatformPL1_Power |
Total package + platform power |
| PSys PL2 | PlatformPL2_Power |
Platform short-term limit |
Power Limit Locking
UnderVolter can lock power limit registers to prevent OS modification:
LockMsrPkgPL12 = 1 ; Lock MSR PL1/PL2
LockMsrPkgPL3 = 1 ; Lock PL3
LockMsrPkgPL4 = 1 ; Lock PL4
LockMsrPP0 = 1 ; Lock PP0
LockPlatformPL = 1 ; Lock PSys limits
LockMmioPkgPL12 = 1 ; Lock MMIO PL1/PL2
cTDP Control
Configurable TDP levels allow dynamic TDP selection:
MaxCTDPLevel = 0 ; Maximum cTDP level (0 = nominal)
TdpControlLock = 1 ; Lock cTDP selection
Safety Features
UnderVolter implements multiple layers of safety mechanisms. The full startup sequence is:
CPU detected?
- No → Unknown CPU warning (F10 / 30 s) → abort or continue
- Yes → ESC window (2 s) → abort or apply settings
1. Unknown CPU Warning (F10 / 30 seconds)
If the detected CPU is not in the database, UnderVolter shows this message and starts a 30-second countdown:
WARNING: Detected CPU (model: X, family: Y, stepping: Z) is not known!
It is likely that proceeding further with hardware programming will result
in unpredictable behavior or with the system hang/reboot.
If you are a BIOS engineer or otherwise familiar with the detected CPU params
please edit CpuData.c and extend it with the detected CPU model/family/stepping
and its capabilities. Further changes to the UnderVolter code might be necessary.
Press F10 key within the next 30 seconds to IGNORE this warning.
Otherwise, UnderVolter will exit with no changes to the system.
The countdown is implemented as a UEFI timer event (TimerRelative, 300 000 000 × 100 ns = 30 s). UnderVolter blocks, waiting for either a keypress or the timer to fire.
| Action | Result |
|---|---|
| F10 pressed within 30 s | Prints Overriding unknown CPU detection... → continues to ESC window and hardware programming |
| Any other key / timeout | Prints Programming aborted. → exits immediately with EFI_ABORTED, no hardware changes |
2. Emergency Exit Window (ESC / 2 seconds)
Shown before any hardware programming begins. Duration: exactly 2 seconds (10 iterations × 20 substeps × 10 ms stall). A green progress bar fills across the screen.
Voltage offset is the preferred method. Make sure your PC is stable.
Press ESC within 2 seconds to skip voltage correction.
[████████████████████░░░░░░░░░░░░░░░░░░░░] 50%
| Action | Result |
|---|---|
| ESC pressed | Prints Aborting. → exits immediately with EFI_SUCCESS — voltage programming, power limits, turbo ratios are all skipped |
| No key / timeout | Continues to full hardware programming |
What ESC does NOT skip: CPU detection, platform discovery, self-test (if configured), and the exit delay countdown still run normally. ESC only prevents ApplyPolicy() from executing.
3. Missing INI — All-Disabled Safe Defaults
If UnderVolter.ini is not found on any volume:
WARNING: UnderVolter.ini not found! Using safe fallback defaults.
Safe defaults disable every programming operation:
| Setting | Safe default |
|---|---|
| Voltage offsets | 0 mV (no change) |
| Power limits | not programmed |
| Turbo ratios | not programmed |
| IccMax | not programmed |
| V/F curves | not programmed |
| Power tweaks (EETurbo, RaceToHalt) | disabled |
UnderVolter continues running, shows the console output, and exits normally — the system is completely untouched.
4. Conservative Profile Defaults
All built-in profiles include a 20% safety margin relative to community-reported stable values:
| Architecture | Community Stable | Safe Default (20% margin) |
|---|---|---|
| Raptor Lake | -100 to -150 mV | -80 mV |
| Alder Lake | -100 to -150 mV | -80 mV |
| Comet Lake | -50 mV (5% failure) | -40 mV |
| Coffee Lake | -150 to -200 mV | -180 mV (user tested) |
5. Self-Test Mode
Optional power management self-test (runs after voltage programming):
SelfTestMaxRuns = 0 ; 0 = disabled, >0 = run N iterations
6. Watchdog Timer Control
UEFI watchdog timer can be disabled to prevent a firmware timeout reset during long operations:
DisableFirwmareWDT = 0 ; 0 = keep enabled, 1 = disable
7. Safe ASM Interrupt Handler
Installs a custom UEFI interrupt handler before MSR programming to catch CPU exceptions and prevent a triple fault (system hang) if an MSR write fails:
EnableSaferAsm = 1 ; 1 = install safe handler (default), 0 = disable
Configuration File
UnderVolter uses UnderVolter.ini for all configuration settings. The file is parsed at startup and applies settings based on detected CPU architecture.
INI File Search Order
On startup, UnderVolter.efi searches for its configuration file across all mounted volumes using the following strategy. The same inner search is applied to each volume:
Inner search — per volume (in order):
- Dynamic path — derived at runtime from
LoadedImage->FilePath: extracts the directory containingUnderVolter.efiitself and appendsUnderVolter.ini. IfFilePathisNULLor path parsing fails, this step is skipped. - Fixed fallback paths — tried in this exact order if the dynamic path fails or is unavailable:
| # | Path | Deployment scenario |
|---|---|---|
| 1 | \EFI\OC\Drivers\UnderVolter.ini |
OpenCore — Drivers |
| 2 | \EFI\OC\Tools\UnderVolter.ini |
OpenCore — Tools |
| 3 | \EFI\OC\UnderVolter.ini |
OpenCore — root |
| 4 | \EFI\Microsoft\Boot\UnderVolter.ini |
Windows Boot Manager |
| 5 | \EFI\Boot\UnderVolter.ini |
Generic EFI / USB boot |
| 6 | \EFI\UnderVolter.ini |
Generic EFI root |
| 7 | \UnderVolter.ini |
ESP root |
Volume search order (outer loop):
LoadedImage->DeviceHandledirectly — the partition from whichUnderVolter.efiwas loaded. Applies the inner search above. Works in QEMU and on standard UEFI firmware.- All
SimpleFileSystemhandles — if Step 1 fails (common when OpenCore remaps the device handle to a controller/disk handle rather than a partition handle), UnderVolter enumerates every mounted FAT volume and applies the inner search to each in turn, stopping at the first match.
If no INI file is found on any volume:
UnderVolter prints a warning to the console:
WARNING: UnderVolter.ini not found! Using safe fallback defaults.
Then continues with all-disabled safe defaults: no voltage programming, no power limit changes, no turbo modifications — the system remains completely untouched. UnderVolter still runs to completion and shows the exit delay screen.
Global Settings
[Global]
; Program power tweaks (EETurbo, RaceToHalt, cTDP)
ProgramPowerTweaks = 1
; Enable Energy Efficient Turbo (reduces frequency at light loads for efficiency)
EnableEETurbo = 1
; Enable Race to Halt (boost clock aggressively, complete work fast, drop to idle)
EnableRaceToHalt = 1
; Maximum configurable TDP level (0 = nominal TDP)
MaxCTDPLevel = 0
; Lock cTDP register after programming (prevents OS from changing it)
TdpControlLock = 1
; Seconds to display the results table before exiting.
; Set to 0 to exit immediately (useful in automated boot chains).
; When INI is not found, this is forced to 0.
DelaySeconds = 3
; Quiet mode: 0 = show full UI and progress bars; 1 = silent (no output).
; In quiet mode: startup animation is skipped, no console output is produced,
; warnings (unknown CPU, missing INI) are suppressed, and the exit delay
; uses a plain stall instead of an interactive countdown.
; Use QuietMode = 1 in production boot chains where output is not visible.
QuietMode = 0
DelaySeconds — how it works:
In normal mode (QuietMode = 0), UnderVolter displays a countdown after the results table:
Time to exit: 3 s... (Press ANY KEY to exit)
The countdown polls the keyboard every 10 ms — press any key to exit immediately without waiting. Set DelaySeconds = 0 to skip the delay entirely (boot chain use case).
In quiet mode (QuietMode = 1), the delay becomes a plain gBS->Stall() with no console output and no key polling.
Profile Example: Coffee Lake
[Profile.CoffeeLake]
Architecture = "CoffeeLake"
; Forced turbo ratios (0 = use default)
ForcedRatioForPCoreCounts = 0
ForcedRatioForECoreCounts = 0
; ICC Max (in 1/4 A units, 65535 = MAX)
IccMax_IACORE = 65535
IccMax_RING = 65535
IccMax_UNCORE = 65535
IccMax_ECORE = 65535
IccMax_GTSLICE = 0
IccMax_GTUNSLICE = 0
; Voltage offsets (mV) — user tested values
OffsetVolts_IACORE = -180
OffsetVolts_RING = -100
OffsetVolts_UNCORE = 0
OffsetVolts_GTSLICE = -40
OffsetVolts_GTUNSLICE = -40
; Power Limits (MSR)
ProgramPL12_MSR = 1
EnableMsrPkgPL1 = 1
EnableMsrPkgPL2 = 1
MsrPkgPL1_Power = 4294967295 ; Unlimited
MsrPkgPL2_Power = 4294967295 ; Unlimited
MsrPkgPL_Time = 4294967295 ; Maximum time window
ClampMsrPkgPL12 = 0
LockMsrPkgPL12 = 1
; Power Limits (MMIO)
ProgramPL12_MMIO = 0
LockMmioPkgPL12 = 0
; Platform (PSys) Power Limits
ProgramPL12_PSys = 0
LockPlatformPL = 0
; PL3, PL4, PP0
ProgramPL3 = 0
ProgramPL4 = 0
ProgramPP0 = 0
V/F Point Programming (Advanced)
UnderVolter supports custom voltage/frequency point programming:
[Profile.RaptorLake]
; Enable V/F point programming for IACORE
Program_VF_Points_IACORE = 3
; V/F Point 0: Frequency (MHz) : Offset (mV)
VF_Point_0_IACORE = 800:-50
VF_Point_1_IACORE = 3200:-80
VF_Point_2_IACORE = 5200:-100
BIOS Unlocking
Many OEM laptops and some desktop systems ship with Overclocking Lock and CFG Lock enabled in BIOS, which blocks MSR 0x150 voltage offset writes. UnderVolter will silently fail to apply voltage settings on such machines unless these locks are disabled first.
Warning: Editing BIOS variables incorrectly can render a system unbootable. If something goes wrong, remove both the main battery and the CMOS coin cell (CR2032) to reset NVRAM — sometimes waiting up to an hour is required. Proceed at your own risk.
Tools Required
| Tool | Purpose | Source |
|---|---|---|
| FPTW64.exe | Dump BIOS firmware image | Intel ME System Tools (via win-raid.com) |
| UEFITool | Extract Setup section from firmware | github.com/LongSoft/UEFITool |
| IFR Extractor (LongSoft) | Decode BIOS options to human-readable text with VarStore/VarOffset | bundled with UEFITool |
Modified GRUB (grubx64.efi) |
Execute setup_var commands from EFI shell |
community builds |
Step 1 — Dump BIOS Firmware
Run from an elevated command prompt:
FPTW64.exe -d bios_dump.rom -bios
This saves a raw BIOS image (ROM file) to the current directory.
Step 2 — Extract the Setup Section
- Open
bios_dump.romin UEFITool - Press Ctrl+F, select Text search, enter:
Overclocking Lock - One result should appear inside a Setup section
- Right-click the parent file containing that match → Extract as is
- Save the
.ffsfile — you will need it in the next step
Step 3 — Decode BIOS Variables
- Open IFR Extractor, load the
.ffsfile extracted above, click Extract - Save the resulting
.txtfile - Open it and search (
Ctrl+F) forOverclocking Lock— note the VarOffset and VarStore name/ID - Also search for
CFG Lock— note its VarOffset and VarStore
Example from a Dell Vostro 7500:
Overclocking Lock → VarStore: CpuSetup (0x3), VarOffset: 0xDA, default: 0x1
CFG Lock → VarStore: CpuSetup (0x3), VarOffset: 0x3E, default: 0x1
The VarStore name tells you which setup_var_N command to use (e.g. CpuSetup = VarStoreId 0x3 → setup_var_3).
Step 4 — Disable the Locks via EFI Shell
- Copy your motherboard's
grubx64.efito the EFI partition and create a boot entry for it (or boot it from a USB drive) - At the GRUB prompt, use
setup_var_3(replace3with the actual VarStore ID for your machine):
setup_var_3 0xDA 0x00 # Disable Overclocking Lock
setup_var_3 0x3E 0x00 # Disable CFG Lock
Replace 0xDA / 0x3E with the VarOffset values from your own IFR Extractor output — they differ between models and CPU generations.
Real-world examples:
| Machine | Command |
|---|---|
| Dell XPS 15 7590 | setup_var_3 0x789 0x00 (OC Lock), setup_var_3 0x6ED 0x00 (CFG Lock) |
| Dell Vostro 7500 | setup_var_3 0xDA 0x00 (OC Lock), setup_var_3 0x3E 0x00 (CFG Lock) |
- Reboot. UnderVolter will now be able to write voltage offsets successfully.
Deployment Methods
UnderVolter can be deployed in several ways, with or without Loader.efi:
| Method | Executable | Persistence |
|---|---|---|
| OpenCore Driver | UnderVolter.efi directly |
Every boot |
| OpenCore Tool | UnderVolter.efi directly |
On-demand (from boot picker) |
| EFI Shell / startup.nsh | UnderVolter.efi directly |
On-demand or scripted |
Replace BOOTX64.EFI |
Loader.efi as BOOTX64.EFI |
Every boot (USB or EFI partition) |
| BIOS boot entry | Loader.efi (independent entry) |
Every boot, survives OS reinstall |
| Windows Boot Manager | UnderVolter.efi via BCD |
Every boot |
Optional: Loader.efi
Loader.efi is an optional helper that finds and launches UnderVolter.efi automatically — without requiring a fixed path — and then chainloads the normal UEFI boot sequence. It is designed to run transparently before the OS, adding no visible delay when everything succeeds.
UnderVolter.efi works completely independently of Loader: it can be loaded directly by OpenCore, EFI Shell, or the BIOS boot manager without Loader involved at all. Loader is only needed when you want automatic discovery combined with seamless chainloading.
How Loader Finds UnderVolter.efi (4-stage search)
| Stage | Method | Detail |
|---|---|---|
| 1 | Same directory as Loader.efi |
Derived from Loader's own LoadedImage->FilePath; direct file open, no recursion |
| 2 | \EFI\OC\Drivers\ on same device |
OpenCore Drivers directory; direct file open |
| 3 | Recursive search on same device | Depth-first, up to 4 directory levels from root; stops on first match |
| 4 | All EFI System Partitions | Enumerates all SimpleFileSystem handles; on each ESP repeats Stages 2+3 |
Loader identifies an ESP by checking for the presence of the \EFI directory. Only volumes that have \EFI are searched in Stage 4.
What Loader Does After UnderVolter Finishes
Once UnderVolter.efi completes (regardless of whether it succeeded or was skipped with ESC), Loader chainloads the normal boot sequence:
- Checks
BootNextUEFI variable — if set, boots that entry and clearsBootNext - Falls back to
BootOrder— iterates boot entries in order, skipping inactive ones - Skips its own entry (based on
BootCurrent) to prevent infinite loops - Self-loop prevention: Loader recognises itself by checking if a boot entry path ends in
Loader.efiorBOOTX64.EFIon the same partition — those entries are never started
If no boot entry succeeds, Loader calls gBS->Exit() with an error status.
If UnderVolter.efi Is Not Found
Loader skips the UnderVolter step entirely and proceeds directly to chainloading BootNext/BootOrder — the system boots normally with no voltage changes applied.
Typical Use Cases for Loader.efi
- Replace
BOOTX64.EFI— copyLoader.efias\EFI\BOOT\BOOTX64.EFI; it findsUnderVolter.efiin the same directory, runs it, then boots the OS - Standalone BIOS boot entry — add
Loader.efias a UEFI NVRAM boot entry independent of the OS boot manager; survives Windows reinstalls and updates; BIOS runs Loader first in boot order, Loader runs UnderVolter, then chainloads Windows
OpenCore Integration
Installation as UEFI Driver
Add UnderVolter to your OpenCore EFI partition:
EFI/
└── OC/
├── Drivers/
│ └── Undervolter.efi
├── Tools/
│ └── Undervolter.efi (optional, for manual execution)
├── UnderVolter.ini (configuration file)
└── config.plist
config.plist Entry
Add to UEFI → Drivers array in config.plist:
<key>Drivers</key>
<array>
<dict>
<key>Arguments</key>
<string></string>
<key>Comment</key>
<string>UnderVolter — CPU undervolting utility</string>
<key>Enabled</key>
<true/>
<key>LoadEarly</key>
<false/>
<key>Path</key>
<string>Undervolter.efi</string>
</dict>
<!-- Other drivers... -->
</array>
LoadEarly vs LoadLate
| Mode | Behavior | Use Case |
|---|---|---|
LoadEarly = false |
Loaded after OpenCore initialization | Standard usage |
LoadEarly = true |
Loaded before OpenCore drivers | Advanced: apply before other drivers |
Recommended: LoadEarly = false (default)
Optional: Add to UEFI Tools
For manual execution from OpenCore boot picker:
<key>Tools</key>
<array>
<dict>
<key>Arguments</key>
<string></string>
<key>Comment</key>
<string>UnderVolter — Manual execution</string>
<key>Enabled</key>
<true/>
<key>LoadEarly</key>
<false/>
<key>Path</key>
<string>Undervolter.efi</string>
</dict>
</array>
Access via OpenCore Boot Picker → Tools → Undervolter
config.plist Example Section
<key>UEFI</key>
<dict>
<key>Drivers</key>
<array>
<dict>
<key>Arguments</key>
<string></string>
<key>Comment</key>
<string></string>
<key>Enabled</key>
<true/>
<key>LoadEarly</key>
<false/>
<key>Path</key>
<string>Undervolter.efi</string>
</dict>
<dict>
<key>Arguments</key>
<string></string>
<key>Comment</key>
<string></string>
<key>Enabled</key>
<true/>
<key>LoadEarly</key>
<false/>
<key>Path</key>
<string>OpenRuntime.efi</string>
</dict>
</array>
<!-- Rest of UEFI configuration -->
</dict>
OC Configurator Users
If using OC Configurator or similar GUI tools:
- Open
config.plistin OC Configurator - Navigate to UEFI → Drivers
- Click + to add new driver
- Set Path to
Undervolter.efi - Set Enabled to ✓
- Set Comment to
UnderVolter — CPU undervolting - Save and reboot
EFI Boot Method
Boot from USB Drive
Create a bootable UEFI USB drive:
USB Drive (FAT32):
└── EFI/
└── BOOT/
├── BOOTX64.EFI (copy of UnderVolter.efi)
└── UnderVolter.ini (configuration file)
Steps:
- Format USB drive as FAT32
- Create directory structure:
EFI\BOOT\ - Copy
UnderVolter.efitoEFI\BOOT\BOOTX64.EFI - Copy
UnderVolter.initoEFI\BOOT\UnderVolter.ini - Boot from USB (press F12/F8/Del during POST)
- UnderVolter executes automatically
EFI Shell Execution
If your motherboard has built-in EFI Shell:
FS0:\> cd \EFI\Boot
FS0:\EFI\Boot> UnderVolter.efi
Or with full path:
FS0:\> \EFI\Boot\UnderVolter.efi
startup.nsh Script
Create startup.nsh in EFI partition root:
@echo -off
fs0:\EFI\Boot\UnderVolter.efi
echo.
echo === Completed. Press Enter to exit ===
pause
Windows Boot Manager
Method 1: Copy to Boot Directory
C:\EFI\Microsoft\Boot\
├── bootmgfw.efi
├── UnderVolter.efi
└── UnderVolter.ini
Add to BCD store:
bcdedit /copy {bootmgr} /d "UnderVolter"
bcdedit /set {GUID} path \EFI\Microsoft\Boot\UnderVolter.efi
bcdedit /set {GUID} device partition=C:
bcdedit /displayorder {GUID} /addlast
bcdedit /timeout 5
Replace {GUID} with the identifier returned by bcdedit /copy.
Method 2: Dual-Boot Entry
Create separate EFI partition for UnderVolter:
diskpart
create partition efi size=100
format quick fs=fat32 label="UV"
assign letter=V
exit
xcopy /Y UnderVolter.efi V:\EFI\Boot\BOOTX64.EFI
xcopy /Y UnderVolter.ini V:\EFI\Boot\
bcdedit /copy {bootmgr} /d "UnderVolter"
bcdedit /set {GUID} path \EFI\Boot\BOOTX64.EFI
bcdedit /set {GUID} device partition=V:
Build System
Requirements
- Visual Studio 2026 or later with C++ desktop development workload
- Windows SDK (included with VS)
- PowerShell 5.1 or later (for build scripts)
Build Process
# Navigate to project root
cd C:\Projekty\UnderVolter
# Run build script
.\build.ps1
Build Steps:
- Locate MSBuild — Uses
vswhere.exeto find Visual Studio installation - Clean Directories — Removes
bin\and.vs\cache directories - Build Release x64 — Compiles
UnderVolter.slnin Release/x64 configuration - Copy Output — Copies
UnderVolter.efi,Loader.efi, andUnderVolter.initobin\ - Set Timestamp — Sets file dates to
2030-01-01 00:00:00for reproducible builds - Clean Build Output — Removes intermediate files from
x64\Release\
Output Files
After successful build:
bin/
├── UnderVolter.efi (main application)
├── Loader.efi (optional loader for advanced configurations)
└── UnderVolter.ini (configuration template)
Static Build Signature
All files are timestamped with 2030-01-01 00:00:00 for:
- Reproducible builds
- Deterministic versioning
- Easy identification of build date
QEMU Testing
test-qemu.zip contains a ready-to-use QEMU/OVMF testing environment — extract and run run-qemu.ps1 to test UnderVolter without real hardware.
Requirements
| Component | Version / Notes |
|---|---|
| QEMU for Windows | ≥ 9.0 recommended; tested with 10.2.90 (v11.0.0-rc0); must include GTK display and bochs-display device support — use the official qemu.org Windows build |
| OVMF firmware | edk2-x86_64-code.fd — bundled with QEMU at C:\Program Files\qemu\share\ |
| OVMF variables | ovmf-vars.fd — included in test-qemu.zip; stores UEFI NVRAM state between runs |
| Working directory | C:\vm\qemu — the script copies binaries here and uses this directory as the virtual FAT disk |
Quick Start
# 1. Extract test-qemu.zip to C:\vm\qemu
# 2. Build UnderVolter (or use pre-built binaries from UnderVolter.zip)
# 3. Run:
.\run-qemu.ps1
The script automatically:
- Copies the latest
UnderVolter.efiandUnderVolter.inifrombin\intoC:\vm\qemu\if they have changed - Creates
startup.nshif missing — EFI Shell auto-runsUnderVolter.efion boot - Mounts
C:\vm\qemu\as a virtual FAT drive (fat:rw:via VirtIO) — the EFI Shell sees it asFS0: - Uses
bochs-displayat 1920×1080 with OVMF resolution hints viafw_cfg(higher quality than standard VGA) - Logs QEMU errors to
C:\vm\qemu\qemu-error.log
Simulated CPU
The test environment emulates an Intel i7-9750H (Coffee Lake) — the same CPUID that is matched by UnderVolter's CoffeeLake profile:
-cpu "qemu64,family=6,model=158,stepping=10"
To test a different architecture, change model and stepping to match any entry from the Supported Processors table.
Virtual Disk Layout
C:\vm\qemu\ acts as the root of the virtual FAT volume (FS0: in EFI Shell):
C:\vm\qemu\ ← FS0:\ in EFI Shell
├── UnderVolter.efi ← main application (auto-copied from bin\)
├── Loader.efi ← optional loader
├── UnderVolter.ini ← configuration (auto-copied from bin\)
├── startup.nsh ← auto-run script (created by run-qemu.ps1)
├── ovmf-vars.fd ← UEFI NVRAM variables (persistent across runs)
└── qemu-error.log ← QEMU stderr output
startup.nsh (Auto-Run Script)
@echo -off
fs0:\undervolter.efi
echo .
echo === Done. Press Enter to close QEMU ===
pause
Source Code Structure
UnderVolter/
├── src/
│ ├── UnderVolter.c # Main entry point (UefiMain)
│ ├── UnderVolter.ini # Configuration template
│ ├── Configuration.c # INI parser, policy application
│ ├── Platform.c # Package discovery, MP dispatch
│ ├── CpuInfo.c # CPUID detection, architecture identification
│ ├── CpuData.c # CPU database (supported models)
│ ├── CpuDataVR.c # VR topology data per architecture
│ ├── CpuMailboxes.c # OC Mailbox interface
│ ├── VoltTables.c # Voltage encoding tables
│ ├── PowerLimits.c # PL1/PL2/PL3/PL4 programming
│ ├── TurboRatioLimits.c # Turbo ratio configuration
│ ├── VFTuning.c # V/F curve programming
│ ├── SafetyPrompts.c # Warning dialogs, emergency exit
│ ├── ConsoleUi.c # Text console UI
│ ├── UiConsole.c # Low-level console output
│ ├── PrintStats.c # Settings display after programming
│ ├── InterruptHook.c # Safe ASM interrupt handler
│ ├── MpDispatcher.c # Multi-core dispatch
│ ├── LowLevel.c # MSR/MMIO access helpers
│ ├── DelayX86.c # Timing routines
│ ├── FixedPoint.c # Fixed-point math utilities
│ ├── SelfTest.c # Power management self-test
│ ├── OcMailbox.c # OverClock Mailbox interface
│ ├── SerialNumber.h # CPU serial number handling
│ ├── TimeWindows.c # Timing for Windows (if needed)
│ └── ASMx64/ # x64 assembly routines
├── bin/ # Build output
├── build.ps1 # Build script
├── merge.ps1 # Source merger for LLM analysis
├── run-qemu.ps1 # QEMU test script
└── UnderVolter.sln # Visual Studio solution
Key Modules
| Module | Responsibility |
|---|---|
UnderVolter.c |
Entry point, initialization sequence, main flow |
Configuration.c |
INI parsing, profile matching, policy application |
Platform.c |
MP services, package/core discovery, VR topology |
CpuInfo.c |
CPUID leaf parsing, architecture detection |
VoltTables.c |
Voltage offset encoding (256-entry lookup table) |
PowerLimits.c |
MSR/MMIO power limit programming |
SafetyPrompts.c |
Unknown CPU warning, emergency exit (ESC key) |
ConsoleUi.c |
Text UI, progress bars, status messages |
InterruptHook.c |
Safe exception handler (prevents triple faults) |
Troubleshooting
System Hangs After Programming
Symptom: System freezes or reboots after UnderVolter applies settings.
Solution:
- Reboot and press ESC within 2 seconds to skip voltage programming
- Reduce voltage offset in
UnderVolter.ini(e.g., from -100 mV to -80 mV) - Test stability with stress testing tools (Prime95, AIDA64, Cinebench)
- Gradually increase offset until stable maximum is found
"Unknown CPU" Warning
Symptom: UnderVolter shows warning about unknown CPU model/family/stepping.
Solution:
- Press F10 to override and continue (if you're confident CPU is compatible)
- Edit
CpuData.cto add your CPU's CPUID signature - Add corresponding profile in
UnderVolter.ini - Rebuild from source
INI File Not Found
Symptom: UnderVolter uses safe defaults instead of custom settings.
Solution:
- Ensure
UnderVolter.iniis in same directory asUnderVolter.efi - Check fallback paths:
\EFI\OC\Drivers\,\EFI\Boot\, etc. - Verify file encoding: UTF-8 without BOM
- Check for syntax errors in INI file (missing brackets, invalid keys)
QEMU Testing Fails
Symptom: QEMU exits immediately or shows errors.
Solution:
- Verify OVMF firmware paths in
run-qemu.ps1 - Ensure
efi-diskdirectory containsUnderVolter.efiandUnderVolter.ini - Check QEMU error log:
C:\vm\qemu\qemu-error.log - Use correct CPUID for your target architecture (e.g.,
family=6,model=158,stepping=10for Coffee Lake)
OpenCore Boot Loop
Symptom: System enters boot loop after adding UnderVolter to OpenCore.
Solution:
- Boot from USB with OpenCore
- Hold Space during boot to access verbose mode
- Locate error message related to
Undervolter.efi - Temporarily disable driver in
config.plist(Enabled = false) - Check
UnderVolter.inifor syntax errors - Try loading as Tool instead of Driver
Voltage Not Applied
Symptom: UnderVolter runs and exits normally but CPU voltage is unchanged.
Possible causes and solutions:
1. INI file not found — look for this line in console output:
WARNING: UnderVolter.ini not found! Using safe fallback defaults.
If present: all programming is disabled by default. Place UnderVolter.ini in the same directory as UnderVolter.efi. See INI File Search Order for all searched paths.
2. No matching profile for detected CPU — look for:
WARNING: No profile found for CoffeeLake. Searching for default.
If present: UnderVolter fell back to [Profile.Generic]. Add a [Profile.X] section matching your CPU's architecture name to UnderVolter.ini.
3. ESC was pressed during the 2-second countdown — voltage programming was deliberately skipped. Reboot and let the countdown finish.
4. BIOS locks active (most common on laptops) — MSR 0x150 writes are silently rejected by firmware:
- Check for
Overclocking LockandCFG Lockin BIOS settings - If hidden, use the BIOS Unlocking procedure to disable them via
setup_var - Applies to Skylake and newer; Sandy Bridge through Broadwell use a different voltage interface
5. OEM BIOS override — some firmware ignores MSR 0x150 entirely. Try updating BIOS to a newer version or check if an unlocked/modded BIOS is available for your model.
Download Links
| File | Size | Description |
|---|---|---|
| UnderVolter.zip | ~60 KB | Pre-built binaries + INI template |
| UnderVolter_source_code.zip | ~200 KB | Complete source code + build scripts |
| test-qemu.zip | ~5 KB | QEMU test configuration + startup.nsh |
Video Demonstration
Watch UnderVolter in action:
Video shows:
- QEMU/OVMF boot sequence
- CPU detection and profile loading
- Emergency exit demonstration (ESC key)
- Voltage programming progress
- Final settings display
License
Apache License 2.0
Full text available in project repository (LICENSE file).
Disclaimer
WARNING: This code is a proof of concept for educational purposes. It can modify internal computer configuration parameters and cause malfunctions or even permanent damage. It has been tested on a limited range of target CPUs and has minimal built-in failsafe mechanisms, thus making it unsuitable for recommended use by users not skilled in the art. Use it at your own risk.
All trademarks, logos, and brand names are the property of their respective owners. Intel, Core, and related marks are trademarks of Intel Corporation.
Contributing
Adding Support for New CPUs
-
Identify CPUID Signature
- Use CPU-Z, HWiNFO, or run
cpuidcommand in EFI Shell - Note: Family, Model, Stepping
- Use CPU-Z, HWiNFO, or run
-
Edit
CpuData.c{ .Family = 0x06, .Model = 0x97, .Stepping = 0x02, .uArch = "RaptorLake", .vtdt = &RaptorLakeVRData, } -
Add Profile to
UnderVolter.ini[Profile.RaptorLake] Architecture = "RaptorLake" OffsetVolts_IACORE = -80 OffsetVolts_RING = -60 ; ... rest of settings -
Test and Report
- Test on your hardware
- Report stable values to project repository
- Include: CPU model, BIOS version, stable offsets, stress test results
Reporting Issues
When reporting bugs or instability:
- CPU Model: i7-9750H, i9-12900K, etc.
- BIOS Version: F12, 1.2.3, etc.
- UnderVolter Version: Git commit hash or release tag
- Settings: Full
UnderVolter.inicontent - Symptoms: Hang, reboot, BSOD, no change, etc.
- Stress Test Results: Prime95, AIDA64, Cinebench scores
Community Resources
- Overclock.net — Active undervolting community with per-CPU stability data
- Reddit r/overclocking — User-submitted voltage/frequency curves
- TechPowerUp — CPU database with specifications and reviews
- Intel ARK — Official Intel processor specifications
Last updated: 2026-03-26