Created
August 31, 2025 12:26
-
-
Save Kabouik/ae20204997d4946f5d1fad217cf4430c to your computer and use it in GitHub Desktop.
Omial's work for Linux touchscreen support on GPD MicroPC 2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# | |
# End-to-end helper to: | |
# - dump ACPI tables | |
# - extract & decompile DSDT | |
# - bump DefinitionBlock revision | |
# - insert Externals inside top-level braces | |
# - inject PTPL._OFF() workaround into TPL1._DSM in Scope(_SB.PC00.I2C0) | |
# - recompile AML (uppercase DSDT.aml) inside working dir | |
# - package minimal cpio at /boot/acpi_override.cpio | |
# - configure GRUB to always load it as an early initrd (persists across upgrades) | |
# | |
# Requires: acpidump, acpixtract, iasl, cpio, grub2-mkconfig | |
# | |
# Tested as working on Fedora Workstation 42 | |
# | |
# WARNING: Largely ChatGPT5 generated, expect idiocy | |
# | |
# Licence: Public domain, do as you like | |
# | |
import os | |
import re | |
import sys | |
import glob | |
import shutil | |
import subprocess | |
from pathlib import Path | |
from tempfile import TemporaryDirectory | |
# --------------------------- Helpers --------------------------- | |
def run(cmd, check=True, capture=False, cwd=None): | |
print("+", " ".join(cmd), f"(cwd={cwd})" if cwd else "") | |
if capture: | |
return subprocess.run(cmd, check=check, text=True, cwd=cwd, | |
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout | |
else: | |
subprocess.run(cmd, check=check, cwd=cwd) | |
def which(prog): | |
from shutil import which as _which | |
return _which(prog) | |
def require_tools(tools): | |
missing = [t for t in tools if not which(t)] | |
if missing: | |
print("\nERROR: Missing tools:", ", ".join(missing)) | |
print("Install them, e.g.:") | |
print(" sudo dnf install -y acpica-tools iasl cpio grub2-tools\n") | |
sys.exit(1) | |
def must_be_root(): | |
if os.geteuid() != 0: | |
print("Please run as root (sudo).") | |
sys.exit(1) | |
def kernel_config_has_table_upgrade(): | |
cfg = Path(f"/boot/config-{os.uname().release}") | |
if not cfg.exists(): | |
# try fallback | |
cfg = Path("/proc/config.gz") | |
if cfg.exists(): | |
out = run(["bash","-lc", | |
r"zgrep -E '^CONFIG_ACPI_TABLE_UPGRADE=y' /proc/config.gz || true"], | |
check=False, capture=True) | |
return "CONFIG_ACPI_TABLE_UPGRADE=y" in out | |
return False | |
txt = cfg.read_text(errors="ignore") | |
return "CONFIG_ACPI_TABLE_UPGRADE=y" in txt | |
def detect_grub_cfg_output_path(): | |
""" | |
Choose the correct grub.cfg file to write. | |
Fedora/RHEL: /boot/grub2/grub.cfg (even on UEFI) | |
Debian/Ubuntu: /boot/grub/grub.cfg | |
""" | |
for p in ("/boot/grub2/grub.cfg", "/boot/grub/grub.cfg"): | |
if Path(p).exists(): | |
return p | |
# Default sensibly for Fedora if nothing exists yet (grub2-mkconfig will create it) | |
return "/boot/grub2/grub.cfg" | |
def ensure_grub_early_initrd(entry="acpi_override.cpio"): | |
cfgfile = Path("/etc/default/grub") | |
cfgfile.parent.mkdir(parents=True, exist_ok=True) | |
cfg = cfgfile.read_text(errors="ignore") if cfgfile.exists() else "" | |
import re | |
line_re = re.compile(r'^\s*GRUB_EARLY_INITRD_LINUX_CUSTOM\s*=\s*"(.*)"\s*$', re.M) | |
m = line_re.search(cfg) | |
if m: | |
vals = m.group(1).split() | |
if entry not in vals: | |
vals.append(entry) | |
new = line_re.sub(f'GRUB_EARLY_INITRD_LINUX_CUSTOM="{" ".join(vals)}"', cfg) | |
cfgfile.write_text(new) | |
print(f"Updated {cfgfile} with GRUB_EARLY_INITRD_LINUX_CUSTOM.") | |
else: | |
print(f"{cfgfile}: GRUB_EARLY_INITRD_LINUX_CUSTOM already contains {entry}") | |
else: | |
with cfgfile.open("a") as f: | |
f.write(f'\nGRUB_EARLY_INITRD_LINUX_CUSTOM="{entry}"\n') | |
print(f"Added GRUB_EARLY_INITRD_LINUX_CUSTOM to {cfgfile}.") | |
outpath = detect_grub_cfg_output_path() | |
print(f"Regenerating GRUB config at {outpath} …") | |
run(["grub2-mkconfig", "-o", outpath]) | |
# --------------------------- DSDT patching --------------------------- | |
def bump_definitionblock_revision(text): | |
pat = re.compile( | |
r'(DefinitionBlock\s*\(\s*"[^"]*"\s*,\s*"DSDT"\s*,\s*\d+\s*,\s*"[^"]*"\s*,\s*"[^"]*"\s*,\s*)(0x[0-9A-Fa-f]+)(\s*\))' | |
) | |
def repl(m): | |
hexv = m.group(2) | |
val = int(hexv, 16) + 1 | |
bumped = f"0x{val:08X}" | |
print(f" - Bumped DefinitionBlock revision: {hexv} -> {bumped}") | |
return m.group(1) + bumped + m.group(3) | |
new_text, n = pat.subn(repl, text, count=1) | |
if n == 0: | |
print("WARNING: Could not find DefinitionBlock to bump revision.") | |
return text | |
return new_text | |
def insert_externals_inside_toplevel_brace(lines): | |
externals = [ | |
' External (_SB_.PC00.I2C0.PTPL, UnknownObj)\n', | |
' External (_SB_.PC00.I2C0.PTPL._OFF, MethodObj) // 0 Arguments\n', | |
'\n', | |
] | |
out = [] | |
inserted = False | |
seen_defblock = False | |
for ln in lines: | |
out.append(ln) | |
if not seen_defblock and "DefinitionBlock" in ln: | |
seen_defblock = True | |
elif seen_defblock and (not inserted) and "{" in ln: | |
out.extend(externals) | |
inserted = True | |
if inserted: | |
print(" - Inserted External() declarations inside top-level braces.") | |
else: | |
print("WARNING: Did not insert Externals (couldn't find top-level '{').") | |
return out | |
def inject_ptpl_off_in_tpl1_dsm(lines): | |
""" | |
Target: | |
Scope (_SB.PC00.I2C0) | |
Device (TPL1) | |
Method (_DSM, 4, ... ) | |
Insert after the opening '{' of that _DSM: | |
If (CondRefOf(\\_SB.PC00.I2C0.PTPL)) | |
{ | |
\\_SB.PC00.I2C0.PTPL._OFF() | |
Sleep (200) | |
} | |
""" | |
whole = "".join(lines) | |
if r"\_SB.PC00.I2C0.PTPL._OFF" in whole: | |
print(" - PTPL._OFF already present; skipping injection.") | |
return lines | |
scope_pat = re.compile(r'^\s*Scope\s*\(\s*_SB\.PC00\.I2C0\s*\)', re.M) | |
device_pat = re.compile(r'^\s*Device\s*\(\s*TPL1\s*\)', re.M) | |
method_pat = re.compile(r'^\s*Method\s*\(\s*_DSM\s*,\s*4\b', re.M) | |
# Pre-scan counts for debugging | |
scope_hits = list(scope_pat.finditer(whole)) | |
device_hits = list(device_pat.finditer(whole)) | |
method_hits = list(method_pat.finditer(whole)) | |
print(f" - Debug: Scope(_SB.PC00.I2C0) matches: {len(scope_hits)}") | |
print(f" - Debug: Device(TPL1) matches: {len(device_hits)}") | |
print(f" - Debug: Method(_DSM,4,...) matches: {len(method_hits)}") | |
if scope_hits: | |
print(f" first Scope hit at approx char {scope_hits[0].start()}") | |
if device_hits: | |
print(f" first Device hit at approx char {device_hits[0].start()}") | |
if method_hits: | |
print(f" first Method hit at approx char {method_hits[0].start()}") | |
out = [] | |
depth = 0 | |
in_target_scope = False | |
scope_depth = None | |
in_target_dev = False | |
dev_depth = None | |
seen_method_sig = False | |
inserted = False | |
def brace_delta(s: str) -> int: | |
return s.count("{") - s.count("}") | |
for idx, ln in enumerate(lines, start=1): | |
# Header matches before we change depth | |
if (not in_target_scope) and scope_pat.match(ln): | |
print(f" >> matched Scope(_SB.PC00.I2C0) at line {idx}") | |
elif in_target_scope and (not in_target_dev) and device_pat.match(ln): | |
print(f" >> matched Device(TPL1) at line {idx}") | |
elif in_target_scope and in_target_dev and (not seen_method_sig) and method_pat.match(ln): | |
seen_method_sig = True | |
print(f" >> matched Method(_DSM,4,...) at line {idx}") | |
out.append(ln) | |
# After method signature, on its '{' line inject | |
if in_target_scope and in_target_dev and seen_method_sig and (not inserted): | |
if "{" in ln: | |
indent = re.match(r'^(\s*)', ln).group(1) + " " | |
block = [ | |
f"{indent}// Workaround: force a cold power cycle so the I2C slave will ACK at probe\n", | |
f"{indent}If (CondRefOf(\\_SB.PC00.I2C0.PTPL))\n", | |
f"{indent}{{\n", | |
f"{indent} \\_SB.PC00.I2C0.PTPL._OFF()\n", | |
f"{indent} Sleep (200)\n", | |
f"{indent}}}\n", | |
] | |
out.extend(block) | |
inserted = True | |
print(f" >> injected PTPL._OFF() block after line {idx}") | |
d = brace_delta(ln) | |
depth += d | |
# entry/exit tracking for scope | |
if (not in_target_scope) and scope_pat.match(ln): | |
in_target_scope = True | |
scope_depth = depth+1 | |
elif in_target_scope and (depth < (scope_depth or 0)): | |
in_target_scope = False | |
scope_depth = None | |
in_target_dev = False | |
dev_depth = None | |
seen_method_sig = False | |
# entry/exit tracking for device | |
if in_target_scope and (not in_target_dev) and device_pat.match(ln): | |
in_target_dev = True | |
dev_depth = depth+1 | |
elif in_target_dev and (depth < (dev_depth or 0)): | |
in_target_dev = False | |
dev_depth = None | |
seen_method_sig = False | |
if inserted: | |
print(" - Inserted PTPL._OFF() block in TPL1._DSM.") | |
return out | |
# Hard error + debugging advice | |
print("\nERROR: Did not find the target Method(_DSM,4,...) inside Device(TPL1) within Scope(_SB.PC00.I2C0).") | |
print("Tips:") | |
print(" * Confirm the TPL1 device really lives under Scope(_SB.PC00.I2C0).") | |
print(" * Confirm the _DSM signature is exactly Method (_DSM, 4, ...).") | |
print(" * Search the generated dsdt.dsl for 'Scope (_SB.PC00.I2C0)' and 'Device (TPL1)'.") | |
print(" * Counts above show how many raw matches were seen in the file.") | |
sys.exit(3) | |
# --------------------------- Main flow --------------------------- | |
def main(): | |
must_be_root() | |
if not kernel_config_has_table_upgrade(): | |
print("\nERROR: Your kernel does not have CONFIG_ACPI_TABLE_UPGRADE=y.") | |
print("This method relies on initrd ACPI table override. Please enable it or use a kernel that has it.\n") | |
sys.exit(1) | |
require_tools(["acpidump", "acpixtract", "iasl", "cpio", "grub2-mkconfig"]) | |
with TemporaryDirectory(prefix="acpi_fix_") as wd_tmp: | |
wd = Path(wd_tmp) | |
print(f"Work dir: {wd}") | |
# 1) Dump & extract (outputs to wd via cwd) | |
acpi_out = wd/"acpi.out" | |
run(["acpidump", "-o", str(acpi_out)], cwd=wd) | |
run(["acpixtract", "-a", str(acpi_out)], cwd=wd) | |
# 2) Find DSDT.dat (case-insensitive) | |
dsdt_dat = None | |
for cand in ["DSDT.dat", "dsdt.dat"]: | |
p = wd/cand | |
if p.exists(): | |
dsdt_dat = p | |
break | |
if not dsdt_dat: | |
# very unlikely if cwd=wd, but try pulling from CWD as a fallback | |
for f in glob.glob("DSDT.dat") + glob.glob("dsdt.dat"): | |
src = Path(f).resolve() | |
dst = wd/src.name | |
shutil.copy2(src, dst) | |
dsdt_dat = dst | |
break | |
if not dsdt_dat or not dsdt_dat.exists(): | |
print("ERROR: Could not find DSDT.dat after acpixtract.") | |
sys.exit(1) | |
print(f"Using {dsdt_dat.name}") | |
# 3) Decompile -> dsdt.dsl *in wd*, hide warnings | |
# -p sets the output prefix (so output will be wd/dsdt.dsl) | |
run(["iasl", "-d", "-p", "dsdt", str(dsdt_dat.name)], cwd=wd) | |
dsl = wd/"dsdt.dsl" | |
if not dsl.exists(): | |
print("ERROR: dsdt.dsl not found after iasl -d.") | |
sys.exit(1) | |
# 4) Patch DSDT.dsl | |
original = dsl.read_text(errors="ignore") | |
patched = bump_definitionblock_revision(original) | |
lines = patched.splitlines(True) | |
lines = insert_externals_inside_toplevel_brace(lines) | |
lines = inject_ptpl_off_in_tpl1_dsm(lines) | |
backup = wd/"dsdt.dsl.bak" | |
backup.write_text(original) | |
dsl.write_text("".join(lines)) | |
print(f"Patched DSDT written: {dsl}") | |
# 5) Recompile AML into wd, uppercase DSDT.aml, hide warnings | |
# -p DSDT ensures output file is wd/DSDT.aml | |
run(["iasl", "-tc", "-ve", "-oa", "-p", "DSDT", "dsdt.dsl"], cwd=wd) | |
aml = wd/"DSDT.aml" | |
if not aml.exists(): | |
print("ERROR: DSDT.aml not produced by iasl.") | |
sys.exit(1) | |
# 6) Build minimal override cpio at /boot/acpi_override.cpio | |
stage = wd/"acpi_override"/"kernel"/"firmware"/"acpi" | |
stage.mkdir(parents=True, exist_ok=True) | |
shutil.copy2(aml, stage/"DSDT.aml") | |
target_cpio = Path("/boot/acpi_override.cpio") | |
print(f"Creating {target_cpio} …") | |
run(["bash","-lc", | |
f"cd '{(wd/'acpi_override').as_posix()}' && " | |
f"find kernel | cpio -H newc -o > '{target_cpio.as_posix()}'"]) | |
# 7) Configure GRUB to always load early initrd | |
ensure_grub_early_initrd("acpi_override.cpio") | |
print("\nDone.") | |
print("Next steps:") | |
print(" 1) Reboot") | |
print(" 2) See if touchscreen works?!") | |
print(" 3) Otherwise, verify that the kernel finds the replacement: sudo dmesg | grep -i 'DSDT ACPI table found in initrd'") | |
print(" 4) Also verify that the loaded table contains the patch:") | |
print(" sudo cat /sys/firmware/acpi/tables/DSDT > /tmp/DSDT.bin") | |
print(" iasl -d /tmp/DSDT.bin") | |
print(" grep -n 'PTPL\\._OFF' /tmp/DSDT.dsl") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment