main
John Lauer README: component table per the wiki format, open-source section, no duplicate hero; generators at gen/ 39a59ef 7d ago
#!/usr/bin/env python3
"""FRU EEPROM image for the riser (OCP NIC 3.0 v1.6.0 section 5.10, IPMI Platform Management FRU Information
Storage Definition v1.0): common header, board info area, product info area, multirecord area with the OCP
Base OEM record 0xC0 (Table 66). Writes design/fru.bin (8 KiB, 0xFF padded) and design/fru.txt."""
import os, struct

ROOT = os.path.join(os.path.dirname(__file__), "..")


def tl(s):
    b = s.encode("ascii")
    assert len(b) < 64
    return bytes([0xC0 | len(b)]) + b


def area(body):
    body = bytes(body) + b"\xC1"
    n = len(body) + 1
    pad = (-n) % 8
    body += b"\x00" * pad
    body = bytes([body[0], (len(body) + 1) // 8]) + body[2:] if False else body
    return body


def finish(fixed_head, fields):
    raw = bytearray(fixed_head) + b"".join(fields) + b"\xC1"
    while (len(raw) + 1) % 8:
        raw += b"\x00"
    raw[1] = (len(raw) + 1) // 8          # area length in multiples of 8
    raw += bytes([(-sum(raw)) & 0xFF])
    return bytes(raw)


def build():
    MFG = "Adom (open hardware)"
    NAME = "OCP3 SFF to 4x OCuLink x4"
    PN = "OCP3-OCULINK4-A"
    SER = "0000000001"
    # board info: version 1, length (patched), language 0 (English), mfg time = minutes since 1996-01-01
    import datetime
    mins = int((datetime.datetime(2026, 10, 12) - datetime.datetime(1996, 1, 1)).total_seconds() // 60)
    board = finish(bytes([1, 0, 0]) + struct.pack("<I", mins)[:3],
                   [tl(MFG), tl(NAME), tl(SER), tl(PN), tl("fru-v1")])
    product = finish(bytes([1, 0, 0]),
                     [tl(MFG), tl(NAME), tl(PN), tl("A"), tl(SER), tl(""), tl("fru-v1")])
    # OCP Base OEM record 0xC0, OEM record version 0x06 (spec 1.6.0)
    rec = bytes([
        0x7F, 0xA6, 0x00,   # OCP IANA 42623, LSB first
        0x06,               # record version: 1.6.0
        0x01, 0x01,         # max power main / aux: under 1 W (logic + two LEDs), rounded up
        0x01, 0x01,         # hot / cold aisle cooling tier with passive cables: tier 1
        0x00,               # passive cooling (no fan)
        0x00, 0x00,         # hot aisle airflow with active cables: passive cables only
        0x00, 0x00,         # cold aisle airflow with active cables: passive cables only
        0x00, 0x00,         # UART config 1/2 (SFF: 0x00)
        0x00,               # no USB on the card edge
        0x00,               # manageability: none
        0x04,               # FRU write protection: mechanical jumper (JP1)
        0x00,               # programming mode not supported
        0x00, 0x00,         # active-cable tiers: passive cables only
        0x00, 0x00,         # transceiver reference power / temperature: passive cable
        0x00,               # thermal tier with fan fail: same (no fan)
        0x00,               # aux power supported ports: none (passive riser)
        0x00, 0x00,         # hot aisle standby airflow, passive cables
        0x00, 0x00,         # cold aisle standby airflow, passive cables
        0x00,               # maximum supported transceiver power
        0xFF,               # reserved
        0x00,               # number of physical (SMBus) controllers N = 0: last byte
    ])
    assert len(rec) == 32
    hdr = bytes([0xC0, 0x82, len(rec), (-sum(rec)) & 0xFF])     # type 0xC0, format 2 | end of list
    hdr += bytes([(-sum(hdr)) & 0xFF])
    multi = hdr + rec
    # common header: offsets in 8-byte units
    off_board = 1
    off_product = off_board + len(board) // 8
    off_multi = off_product + len(product) // 8
    common = bytes([1, 0, 0, off_board, off_product, off_multi, 0])
    common += bytes([(-sum(common)) & 0xFF])
    img = common + board + product + multi
    img += b"\xFF" * (8192 - len(img))
    return img, dict(board=board, product=product, multi=multi, common=common)


if __name__ == "__main__":
    img, parts = build()
    open(os.path.join(ROOT, "design", "fru.bin"), "wb").write(img)
    used = sum(len(v) for v in parts.values())
    with open(os.path.join(ROOT, "design", "fru.txt"), "w") as fh:
        fh.write("FRU EEPROM image for the OCP NIC 3.0 SFF to 4x OCuLink riser (BR24H64, 8 KiB, 2-byte addressing)\n")
        fh.write(f"used {used} bytes; common header {parts['common'].hex(' ')}\n")
        fh.write(f"board area {len(parts['board'])} B, product area {len(parts['product'])} B, multirecord {len(parts['multi'])} B\n")
        fh.write("OCP Base OEM record 0xC0: " + parts["multi"][5:].hex(" ") + "\n")
        fh.write("Program (JP1 bridged, card in the slot, Linux host): i2ctransfer with 2-byte addressing at 0x50 + SLOT_ID,\n"
                 "or ipmitool fru write <id> fru.bin from the BMC. Remove JP1 afterwards to write-protect.\n")
    print(f"fru.bin {len(img)} bytes, {used} used")
    # self-check: every area checksum sums to zero
    for k, v in parts.items():
        if k == "multi":   # multirecord: 5-byte header sums to zero; data sums to zero with the header's record checksum
            ok = sum(v[:5]) % 256 == 0 and (sum(v[5:]) + v[3]) % 256 == 0
        else:
            ok = sum(v) % 256 == 0
        print(k, "checksum ok" if ok else "BAD", len(v))