#!/usr/bin/env python3
"""
make_ssdt.py -- emit a minimal SSDT for the apple_gmux planted-ACPI rig.

Hand-encodes AML (no iasl needed):

  DefinitionBlock ("", "SSDT", 2, "AUDIT", "GMUXRIG", 0x1) {
      Scope (\_SB) {
          Device (<NAME>) {
              Name (_HID, "APP000B")          /* gmux HID, apple-gmux.c:708 */
              Name (_STA, 0x0F)               /* present + functional */
              Name (_CRS, ResourceTemplate () {
                  IO (Decode16, <BASE>, <BASE>, 0x01, 0x04)   /* 4-byte I/O window */
              })
          }
          ...
      }
  }

The 4-byte _CRS is the rig's point: apple_gmux attach never validates the
allocated I/O resource length (GMUX_MIN_IO_LEN at apple-gmux.c:77 is dead
code), so the production driver reads/writes gmux register offsets
(0x04..0x06, 0xcc..0xce, 0xd0, 0xd4) far past the 4-byte allocation.

Usage: make_ssdt.py <out.aml> NAME0=BASE0 [NAME1=BASE1 ...]
Bases are hex without 0x, e.g.: make_ssdt.py ssdt_b.aml AAA0=8e AAA2=4cc
"""
import sys


def pkglen(n):
    """ACPI PkgLength (no EISAID tricks, just the standard encoding)."""
    if n < 63:
        return bytes([n])
    assert n < 4096, "rig does not need bigger lengths"
    return bytes([0x40 | (n & 0x0F), (n >> 4) & 0xFF])


def nameop_path(path):
    """NameOp + 4-char name (e.g. _HID -> 08 5F 48 49 44)."""
    assert len(path) == 4
    return b"\x08" + path.encode()


def device(name, base, length=4):
    assert len(name) == 4
    body = b""
    # Name (_HID, "APP000B")  -- StringPrefix 0x0D + 7 chars + NUL
    body += nameop_path("_HID") + b"\x0D" + b"APP000B\x00"
    # Name (_STA, 0x0F)       -- BytePrefix 0x0A
    body += nameop_path("_STA") + b"\x0A\x0F"
    # Name (_CRS, Buffer { IO(Decode16, base, base, 1, 4), EndTag })
    # Small I/O descriptor 0x47: flags(1) min(2) max(2) aln(1) len(1)
    # -> 0x47 + 7 data bytes (small-descriptor length field is 3 bits, max 7).
    # Matches QEMU DSDT convention, e.g. COM1: 47 01 f8 03 f8 03 00 08.
    io_desc = bytes([0x47, 0x01,
                     base & 0xFF, (base >> 8) & 0xFF,
                     base & 0xFF, (base >> 8) & 0xFF,
                     0x01, length])
    # EndTag: 0x79 0x00 (checksum byte 0, matching iasl-emitted templates;
    # a computed nonzero byte makes ACPICA reject the table at load time)
    buf_bytes = io_desc + bytes([0x79, 0x00])
    buf_size_field = b"\x0A" + bytes([len(buf_bytes)])
    inner = buf_size_field + buf_bytes
    body += nameop_path("_CRS") + b"\x11" + pkglen(len(inner)) + inner
    # DeviceOp 0x5B 0x82 <PkgLen> <Name4> <body>
    total = 4 + len(body)
    return b"\x5B\x82" + pkglen(total) + name.encode() + body


def main():
    out = sys.argv[1]
    devices = []
    for spec in sys.argv[2:]:
        nm, rest = spec.split("=")
        if ":" in rest:
            base, ln = rest.split(":")
            devices.append(device(nm, int(base, 16), int(ln, 16)))
        else:
            devices.append(device(nm, int(base, 16)))
    scope_body = b"\x5C\x5F\x53\x42\x5F" + b"".join(devices)  # \_SB
    scope = b"\x10" + pkglen(len(scope_body)) + scope_body

    header = bytearray(36)
    header[0:4] = b"SSDT"
    header[6] = 2                      # revision
    header[10:16] = b"AUDIT "          # OEMID (6)
    header[16:24] = b"GMUXRIG1"        # OEM table id (8)
    header[24:28] = (1).to_bytes(4, "little")
    header[28:32] = b"INTL"
    header[32:36] = (1).to_bytes(4, "little")
    table = bytes(header) + scope
    table = table[:4] + len(table).to_bytes(4, "little") + table[8:]
    checksum = (0x100 - (sum(table) & 0xFF)) & 0xFF
    table = table[:9] + bytes([checksum]) + table[10:]
    with open(out, "wb") as f:
        f.write(table)
    print("wrote %s (%d bytes, csum 0x%02x)" % (out, len(table), checksum))


if __name__ == "__main__":
    main()
