#!/usr/bin/env python3
"""DF-5145 trigger generator: SSDT with an IPI0001 ACPI device in SSIF mode.

Hand-assembled AML (no iasl needed):

    DefinitionBlock ("ssdt.aml", "SSDT", 2, "BOCHS ", "IPMISSIF", 0x1)
    {
        Scope (\\_SB)
        {
            Device (IPMI)
            {
                Name (_HID, EisaId ("IPI0001"))   // 0x26090001
                Name (_UID, 0x00)
                Name (_IFT, 0x04)                 // SSIF_MODE (ipmivars.h:132)
                Name (_SRV, 0x0200)               // IPMI 2.0 (read at ipmi_acpi.c:89)
                Name (_ADR, 0x20)                 // SMBus slave address (ipmi_acpi.c:105)
                Name (_STA, 0x0F)                 // present + functional
                Name (_CRS, ResourceTemplate ()   // IO (Decode16, 0x0CA2, 0x0CA3, 1, 2)
                {
                    IO (Decode16, 0x0CA2, 0x0CA3, 0x01, 0x02)
                })
            }
        }
    }

The _CRS resource is never touched by the buggy path: ipmi_acpi_attach()
returns at ipmi_acpi.c:109 ("SSIF interface not supported on ACPI") before
the resource-detection code at ipmi_acpi.c:114.  _CRS exists only so the
DragonFly ACPI bus enumerates the device as a newbus child.

Usage:  python3 ssdt_gen.py ssdt-ipmi-ssif.aml
Boot the QEMU guest with:  -acpitable file=ssdt-ipmi-ssif.aml
"""
import struct, sys

def nameseg(s):
    assert len(s) == 4
    return s.encode('ascii')

def name_dword(nm, val):
    return b'\x08' + nameseg(nm) + b'\x0c' + struct.pack('<I', val)

def name_byte(nm, val):
    return b'\x08' + nameseg(nm) + b'\x0a' + bytes([val])

def name_word(nm, val):
    return b'\x08' + nameseg(nm) + b'\x0b' + struct.pack('<H', val)

def block(op, content):
    """op + PkgLength + content; PkgLength counts op + pkglen bytes + content.

    ACPICA decode (psargs.c: AcpiPsGetNextPackageLength): with N extra bytes,
    value = (byteN << 4N) | ... | (byte0 & 0x0F); with 0 extra bytes, value =
    byte0 & 0x3F.  Verified against QEMU's own generated DSDT (5b 82 4f 04).
    """
    total = len(op) + 1 + len(content)      # try 1-byte pkglen first
    if total <= 0x3F:
        return op + bytes([total]) + content
    total = len(op) + 2 + len(content)      # 2-byte pkglen
    assert total < (1 << 12)
    return op + bytes([0x40 | (total & 0x0F), total >> 4]) + content

eisaid = (9 << 26) | (16 << 21) | (9 << 16) | 0x1       # "IPI0001" composed
# ACPICA stores EISAIDs byte-swapped: Name(_HID, 0x01000926) == EisaId("IPI0001")
# (verified byte-for-byte against QEMU's own isa-ipmi-kcs SSDT: 0c 26 09 00 01)
eisaid = struct.unpack('<I', struct.pack('>I', eisaid))[0]
assert eisaid == 0x01000926

# Buffer (10) { IO (Decode16, 0x0CA2, 0x0CA3, 1, 2) }  -- small IO descriptor + EndTag
# NOTE: the small I/O port descriptor's _LEN is a SINGLE byte (ACPI spec
# "I/O Port Descriptor": 47, decode(1), min(2), max(2), aln(1), len(1))
wio = bytes([0x47, 0x01]) + struct.pack('<HH', 0x0CA2, 0x0CA3) + \
      bytes([0x01, 0x02])
crs_bytes = wio + bytes([0x79, 0x00])
# Buffer (N) { IO(...) }  -- BufferOp PkgLength counts lead + 0a + size-byte + data
name_crs = b'\x08' + nameseg('_CRS') + b'\x11' + \
           bytes([3 + len(crs_bytes)]) + b'\x0a' + bytes([len(crs_bytes)]) + crs_bytes

body = (name_dword('_HID', eisaid) + name_byte('_UID', 0) +
        name_byte('_IFT', 4) +                  # SSIF_MODE
        name_word('_SRV', 0x0200) +             # WordConst, as QEMU emits it
        name_dword('_ADR', 0x20) +
        name_byte('_STA', 0x0F) +
        name_crs)

device = block(b'\x5b\x82', nameseg('IPMI') + body)
aml = block(b'\x10', b'\x5c\x5f\x53\x42\x5f' + device)   # Scope(\_SB)

hdr = (b'SSDT' + struct.pack('<I', 36 + len(aml)) + bytes([2, 0]) +
       b'BOCHS ' + b'IPMISSIF' + struct.pack('<I', 1) +
       b'INTL' + struct.pack('<I', 0x20180608))
assert len(hdr) == 36

table = bytearray(hdr + aml)
table[9] = (-sum(table)) & 0xFF
assert sum(table) & 0xFF == 0

out = sys.argv[1] if len(sys.argv) > 1 else 'ssdt-ipmi-ssif.aml'
open(out, 'wb').write(bytes(table))
print("wrote %s (%d bytes), checksum ok" % (out, len(table)))
