Machine: ThinkPad T480 (i7-8650U), currently running Libreboot/coreboot
1d8755f2425 (2026-09-28). Before flashing I took a full 16 MiB dump of the
Lenovo factory firmware (BIOS version N24RE03WL), so I could compare the vendor
ACPI tables against what coreboot emits.
== How the vendor tables were extracted (reproducible) ==
The ACPI tables are not stored in clear in the Lenovo image, so plain grep/iasl
on the dump finds nothing:
- UEFIExtract on the 16 MiB dump parses the firmware volumes but yields no
ACPI tables and no ACPI table GUIDs (EB9D2D30-... for DSDT, etc.).
- The DXE volume is stored as an LZMA-compressed blob. Decompressing it
(FORMAT_ALONE) yields ~14.9 MiB containing the real tables.
- Locating tables by signature + valid header length + valid checksum finds
1 DSDT (140807 bytes, OEM ID "INTEL", table ID "SKL") and 34 SSDTs.
- iasl -d on those gives readable ASL.
== What the vendor firmware actually contains ==
The DSDT only declares the processors and defers to an SSDT:
External (_PR_.PR00, DeviceObj)
External (_PR_.PR00.LPSS, PkgObj)
External (_PR_.PR00.TPSS, PkgObj)
If (CondRefOf (\_PR.PR00._PPC))
If ((CondRefOf (\_PR.PR00._PSS) && CondRefOf (\_PR.PR00._PPC)))
SSDT "Cpu0Ist" (OEM ID "PmRef") implements the interface on PR00:
Method (_PPC, 0) { Return (\_PR.CPPC) }
Name (_PCT, Package (0x02)
{
ResourceTemplate () { Register (FFixedHW, 0x00, 0x00, 0x0000000000000000, ,) },
ResourceTemplate () { Register (FFixedHW, 0x00, 0x00, 0x0000000000000000, ,) }
})
Method (_PSS, 0)
{
If ((\_SB.OSCP & 0x0400)) // OS supports CPPC
{
Return (TPSS)
}
Else
{
Return (LPSS)
}
}
SSDT "ApIst" covers the remaining CPUs by delegating to PR00:
Scope (\_PR.PR01)
{
Method (_PPC) { Return (\_PR.PR00._PPC ()) }
Method (_PCT) { Return (\_PR.PR00._PCT ()) }
Method (_PSS) { Return (\_PR.PR00._PSS ()) }
}
... repeated up to PR15.
== The important part ==
Every entry in both packages is the placeholder 0x80000000:
Name (LPSS, Package (0x10) // 16 states
{
Package (0x06) { 0x80000000, 0x80000000, 0x80000000,
0x80000000, 0x80000000, 0x80000000 },
...
})
Name (TPSS, Package (0x28) // 40 states
{
Package (0x06) { 0x80000000, 0x80000000, 0x80000000,
0x80000000, 0x80000000, 0x80000000 },
...
})
Unique values found in LPSS: 0x80000000. Unique values in TPSS: 0x80000000.
No real frequency (800/1900/4200 MHz) appears anywhere in the 34 SSDTs.
Likewise _PCT is a pair of FFixedHW registers with Address 0x0, Bit Width 0x00
and Bit Offset 0x00, i.e. also a placeholder.
This is consistent with the SSDT name ("Cpu0Ist" = CPU0 Initialization State
Table): the vendor firmware fills these tables in at runtime, presumably from
the CPU's own MSRs, and the ROM only carries the template.
== Why this matters for the fix ==
The reference .dsl files requested earlier in this ticket have been provided,
but they cannot be used to simply copy values into coreboot: there are no
values in them to copy. Implementing this in coreboot means generating
_PSS/_PCT/_PPC (and _PSD) at runtime, computing the state list from the CPU
(MSR_PLATFORM_INFO for the max non-turbo ratio, MSR_TURBO_RATIO_LIMIT for the
turbo bins), not backporting a static table.
Current state of coreboot, for reference (checked on 1d8755f2425): searching
src/mainboard/lenovo/sklkbl_thinkpad/ and src/soc/intel/sklake/ for _PCT, _PPC,
_PSS and Processor returns 0 matches. The only related symbol left in
src/arch/x86/acpi/statdef.asl is the unused #define NOTIFY_CPU_PPCCHG. So there
is no existing infrastructure to enable - it would be new code.
== Relation to Xen ==
Worth noting for anyone following this: Qubes' Xen already has HWP support
(QubesOS/qubes-vmm-xen#158, merged Feb 2023, which closed
QubesOS/qubes-issues#4604). But the HWP driver still needs the ACPI P-state
data to initialise its policy, so with no _PSS/_PPC/_PCT present, cpufreq=xen:hwp
has nothing to work with and the CPU stays pinned. Hence the firmware side is
the blocker, and it is the one that has not moved.