Skip to content

Instantly share code, notes, and snippets.

@joevt
Last active August 3, 2025 21:36
Show Gist options
  • Save joevt/4f6d4d97b560efab9603ac509bf00122 to your computer and use it in GitHub Desktop.
Save joevt/4f6d4d97b560efab9603ac509bf00122 to your computer and use it in GitHub Desktop.

Revisions

  1. joevt revised this gist Dec 5, 2024. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions ThunderboltUtil.sh
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    #! /bin/zsh
    # ThunderboltUtil.sh v1.6
    # by joevt Apr 2, 2024
    # ThunderboltUtil.sh v1.7
    # by joevt Dec 5, 2024

    #=========================================================================================
    #
    @@ -1101,8 +1101,8 @@ loadoneioregfile () {
    elsif ( $inhex ) { $inhex = 0; print "" . (lc ($thehex =~ s/ //gr)) . ">\n" }
    if ( /^([ |]*)\+\-o (.+) </ ) { $indent = (length $1) / 2; $name = $2; $thepath =~ s|^((/[^/]*){$indent}).*|$1/$name| }
    elsif ( /^[ |]*"(ThunderboltDROM|thunderbolt-drom)" = <(.*)>/i ) { print "'"${thesource}"'" . ":" . $thepath . "/" . $1 . " = <" . $2 . ">\n" }
    elsif ( /^[ |]*"(ThunderboltDROM|thunderbolt-drom)" = $/i ) { print "'"${thesource}"'" . ":" . $thepath . "/" . $1 . " = <"; $inhex=1; $thehex="" }
    elsif ( /^[ |]*"(ThunderboltDROM|thunderbolt-drom|DROM)" = <(.*)>/i ) { print "'"${thesource}"'" . ":" . $thepath . "/" . $1 . " = <" . $2 . ">\n" }
    elsif ( /^[ |]*"(ThunderboltDROM|thunderbolt-drom|DROM)" = $/i ) { print "'"${thesource}"'" . ":" . $thepath . "/" . $1 . " = <"; $inhex=1; $thehex="" }
    }
    ' < "${thefilename}"
    ); do
  2. joevt revised this gist Apr 4, 2024. 1 changed file with 248 additions and 79 deletions.
    327 changes: 248 additions & 79 deletions ThunderboltUtil.sh
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,6 @@
    # by joevt Jul 7, 2020
    #! /bin/zsh
    # ThunderboltUtil.sh v1.6
    # by joevt Apr 2, 2024

    #=========================================================================================
    #
    @@ -229,6 +231,34 @@ crc32c () {
    }


    CRCTABLE=00000000F26B8303E13B70F71350F3F4C79A971F35F1141C26A1E7E8D4CA64EB8AD958CF78B2DBCC6BE228389989AB3B4D43CFD0BF284CD3AC78BF275E133C24105EC76FE235446CF165B798030E349BD7C4507025AFD37336FF2087C494A3849A879FA068EC1CA37BBCEF5789D76C545D1D08BFAF768BBCBC2678484E4DFB4B20BD8EDED2D60DDDC186FE2933ED7D2AE72719C1154C9AC2061C6936F477EA35AA64D611580F55124B5FA6E6B93425E56DFE410E9F95C20D8CC531F97EAEB2FA30E349B1C288CAB2D1D8394623B3BA45F779DEAE05125DAD1642AE59E4292D5ABA3A117E4851927D5B016189A96AE28A7DA086618FCB05629C9BF6966EF07595417B1DBCB3109EBFA0406D4B522BEE4886E18AA3748A09A067DAFA5495B17957CBA2457339C9C6702A993584D8F2B6870C38D26CFE53516FED03A29B1F6821985125DAD3A34E59D0B01EAA244275292796BF4DCC64D4CECF77843D3B85EFBE38DBFC821C2997011F3AC7F2EBC8AC71E81C661503EE0D9600FD5D65F40F36E6F761C6936293AD106180FDE39572966096A65C047D5437877E4767748AB50CF789EB1FCBAD197448AE0A24BB5AF84F38592C855CB2DEEEDFB1CDBE2C453FD5AF467198540D83F3D70E90A324FA62C8A7F9B602C312446940115739B3E5A55230E6FB410CC2092A8FC11A7A7C35E811FF363CDB9BDDCEB018DEDDE0EB2A2F8B682982F63B78709DB87B63CD4B8F91A6C88C456CAC67B7072F64A457DC90563C5F93082F63B7FA44E0B4E91413401B7F9043CFB5F4A83DDE77AB2E8E845FDCE5075C92A8FC1760C37F1473938CE081F80FE355326B08A759E80BB4091BFF466298FC1871A4D8EA1A27DBF94AD42F0B21572CDFEB33C72D80B0C43ED04330CCBBC033A24BB5A6502036A54370C551B11B465265D122B997BAA1BA84EA524E7681D14D2892ED69DAF96E6AC9A99D9E3BC21E9DEF087A761D63F9750E330A81FC588982B21572C9407EF1CA532E023EA145813D758FE5D687E466D594B4952166DF162238CC2A06CAA7A905D9F75AF12B9CD9F2FF56BD190D3D3E1A1E6DCDEEEC064EEDC38D26C431E6A5C722B65633D0DDD5300417B1DBF67C32D8E52CC12C1747422F49547E0BBB3FFD08A86F0EFC5A048DFF8ECEE9147CA56A176FF599E39D9E1AE0D3D3E1AB21B862A832E8915CC083125F144976B4E622F5B7F572064307198540590AB964AB613A67B831C9934A5A4A909E902E7B6CFBAD787FAB5E8C8DC0DD8FE330A81A115B2B19020BD8EDF0605BEE24AA3F05D6C1BC06C5914FF237FACCF169E9F0D59B8273D688D280227AB90321AE7367CA5C18E4C94F48173DBD23943EF36E6F750105EC7612551F82E03E9C8134F4F86AC69F7B69D5CF889D27A40B9E79B737BA8BDCB4B9988C474D6AE7C44EBE2DA0A54C4623A65F16D052AD7D5351

    CRC32_8 () {
    local crc=$1
    local b=$2
    local ndx=$(( (crc ^ b) & 0xFF ))
    printf $(( (crc >> 8) ^ 0x${CRCTABLE:$ndx*8:8} ))
    }

    # same as crc32c but faster
    CRC32 () {
    local crc=$(( 0xFFFFFFFF ));
    for bp in $(xxd -p -c 1); do
    ((crc = $(CRC32_8 $crc 0x$bp) ))
    done
    printf "%08x" $((crc ^ 0xFFFFFFFF))
    }

    # same as CRC32 but faster
    CRC32b () {
    local crc=$(( 0xFFFFFFFF ));
    for bp in $(xxd -p -c 1); do
    (( crc = (crc >> 8) ^ 0x${CRCTABLE:$(( ((crc ^ 0x$bp) & 0xFF) * 8 )):8} ))
    done
    printf "%08x" $((crc ^ 0xFFFFFFFF))
    }


    replacebytes () {
    local bytepos=$(($1*2))
    local thebytes=$2
    @@ -287,7 +317,13 @@ processdrom () {
    theuid=$dotheuid
    fi

    theexpectedcrc8=$((0x$(xxd -p -r <<< "$theuid" | crc8uid)))
    if [[ $theuid == 0000000000000000 ]]; then
    isusb4=1
    theexpectedcrc8=0
    else
    isusb4=0
    theexpectedcrc8=$((0x$(xxd -p -r <<< "$theuid" | crc8uid)))
    fi
    (( theexpectedcrc8 != thecrc8 )) && {
    if (( dorepairchecksums )); then
    replacebytes 0 "$(printf "%02x" $theexpectedcrc8)"
    @@ -297,8 +333,8 @@ processdrom () {
    fi
    }

    (( dodump )) && {
    printf "0x01) UID: 0x%s" "$theuidnum"
    (( dodump && !isusb4 )) && {
    printf "0x01) UID: 0x%s // Vendor ID (USB-IF):0x%s Component ID:0x%s Router ID:0x%s" "$theuidnum" "${theuidnum:0:4}" "${theuidnum:4:11}" "${theuidnum:15:1}"
    if [[ $dosetuid = 1 && $theuidnum != "$douuidnum" ]]; then
    printf " (changed: 0x%s)" "$douuidnum"
    fi
    @@ -308,86 +344,213 @@ processdrom () {
    thecrc32=$((0x${thedrom:24:2}${thedrom:22:2}${thedrom:20:2}${thedrom:18:2}))
    thedatalen=$((0x${thedrom:30:2}${thedrom:28:2} & 0x3ff))

    thedeviceromrevision=$((0x${thedrom:26:2}))
    (( dodump )) && printf "0x0d) Device ROM Revision: %d\n" $thedeviceromrevision
    theversion=$((0x${thedrom:26:2}))
    (( dodump )) && {
    printf "0x0d) Version: %d" $theversion # Device ROM Revision
    if (( isusb4 )); then
    (( theversion == 3 )) && printf " // USB4" || printf " (expected 3)"
    else
    (( theversion == 1 )) && printf " // TBT3" || printf " (expected 1)"
    fi
    printf "\n"
    }

    thereserved=$(((0x${thedrom:30:2}${thedrom:28:2} & ~0x3ff) >> 10))
    (( dodump && thereserved )) && printf "0x0e) Reserved: %d (expected 0)\n" $thereserved

    theunknown=$(((0x${thedrom:30:2}${thedrom:28:2} & 0x3ff) >> 10))
    (( dodump && theunknown )) && printf "0x0e) Unknown: %d (expected 0)\n" $theunknown
    thevendorid=0
    themodelid=0
    themodelrev=0
    theeepromrev=0

    local startentryoffset=16
    if (( !isusb4 )); then

    thevendorid=$((0x${thedrom:34:2}${thedrom:32:2}))
    (( dodump )) && printf "0x10) Vendor ID: 0x%X\n" $thevendorid
    thevendorid=$((0x${thedrom:34:2}${thedrom:32:2}))
    (( dodump )) && printf "0x10) TBT3-Vendor ID: 0x%X\n" $thevendorid

    themodelid=$((0x${thedrom:38:2}${thedrom:36:2}))
    (( dodump )) && printf "0x12) Device ID: 0x%X\n" $themodelid
    themodelid=$((0x${thedrom:38:2}${thedrom:36:2}))
    (( dodump )) && printf "0x12) TBT3-Device ID: 0x%X\n" $themodelid

    themodelrev=$((0x${thedrom:40:2}))
    (( dodump )) && printf "0x14) Device Revision: 0x%X\n" $themodelrev
    themodelrev=$((0x${thedrom:40:2}))
    (( dodump )) && printf "0x14) TBT3-Model Revision: 0x%X\n" $themodelrev

    theeepromrev=$((0x${thedrom:42:2}))
    (( dodump )) && printf "0x15) EEPROM Revision: %d\n" $theeepromrev
    theeepromrev=$((0x${thedrom:42:2}))
    (( dodump )) && printf "0x15) TBT3-NVM Revision: %d\n" $theeepromrev

    startentryoffset=22
    fi

    while ((1)); do
    local portoffset=22
    local entryoffset=$startentryoffset
    ((thedataend = 13 + thedatalen))

    # Keep a list of ports and strings and generic data
    Ports=()

    while (( portoffset <= thedataend )); do
    [[ -z ${thedrom:$portoffset*2:2} ]] && break
    while (( entryoffset <= thedataend )); do
    [[ -z ${thedrom:$entryoffset*2:2} ]] && break

    local portlen=$((0x${thedrom:$portoffset*2:2}))
    local entrylen=$((0x${thedrom:$entryoffset*2:2}))

    if ((portlen < 2)); then
    (( dodump && (portoffset != thedataend) )) && echo "Unexpected error: port length is < 2: $portoffset + $portlen <= $thedataend"
    if ((entrylen < 2)); then
    (( dodump && (entryoffset != thedataend) )) && echo "Unexpected error: port length is < 2: $entryoffset + $entrylen <= $thedataend"
    break
    fi
    (( portoffset == thedataend )) && {
    (( entryoffset == thedataend )) && {
    (( dodump )) && echo " ============== (following bytes are unexpected)"
    ((thedataend += portlen))
    ((thedataend += entrylen))
    }

    local theporttype=$((0x${thedrom:$portoffset*2+2:2} >> 7)) # 0=generic,1=port
    local theportdisabled=$(((0x${thedrom:$portoffset*2+2:2} >> 6) & 1)) # 0=enabled,1=disabled
    local theportnumber=$((0x${thedrom:$portoffset*2+2:2} & 0x3f)) # 1..9,a..d
    local theportbytes=${thedrom:$portoffset*2+4:$portlen*2 - 4}
    local actualportlen=$((${#theportbytes} / 2 + 2))
    local theentrytype=$((0x${thedrom:$entryoffset*2+2:2} >> 7)) # 0=generic,1=Adapter Entry
    local theadapterdisabled=$(((0x${thedrom:$entryoffset*2+2:2} >> 6) & 1)) # 0=enabled,1=disabled
    local theadapternumber=$((0x${thedrom:$entryoffset*2+2:2} & 0x3f)) # 1..9,a..d
    local theadapterbytes=${thedrom:$entryoffset*2+4:$entrylen*2 - 4}
    local actualentrylen=$((${#theadapterbytes} / 2 + 2))

    if (( dodump )); then
    printf "0x%02x) %s %X: " $portoffset "$( ((theportdisabled)) && printf "-" || printf " ")" $theportnumber
    if (( theporttype || theportnumber < 1 || theportnumber > 2 )); then
    printf "%s" "$theportbytes"
    printf "0x%02x) %s %X: " $entryoffset "$( ((theadapterdisabled)) && printf "-" || printf " ")" $theadapternumber
    if (( theentrytype || theadapternumber < 1 || theadapternumber > 2 )); then
    printf "%s" "$theadapterbytes"
    else
    printf "\"%s\"" "$(perl -pE "s/(00)+$//" <<< "${theportbytes}" | xxd -p -r)"
    if [[ $(perl -pE "s/.*?((00)+)$/\1/" <<< "${theportbytes}") != "00" ]]; then
    printf " (expected single terminating null character: %s)" "$theportbytes"
    printf "\"%s\"" "$(perl -pE "s/(00)+$//" <<< "${theadapterbytes}" | xxd -p -r)"
    if [[ $(perl -pE "s/.*?((00)+)$/\1/" <<< "${theadapterbytes}") != "00" ]]; then
    printf " (expected single terminating null character: %s)" "$theadapterbytes"
    fi
    fi

    if (( !theporttype && theportdisabled )); then
    printf " (unexpectedly disabled)"
    if (( theentrytype )); then
    if (( theadapterdisabled && entrylen != 2 )); then
    printf " (unexpectedly disabled)"
    fi
    else
    if (( theadapterdisabled )); then
    printf " (unexpectedly disabled)"
    fi
    fi
    fi

    if ((theportnumber <= 0)); then
    if ((theadapternumber <= 0)); then
    (( dodump )) && printf " (expected port number > 0)"
    elif ((theportnumber == (theporttype ? doportnumber : dostringnumber))); then
    elif ((theadapternumber == (theentrytype ? doportnumber : dostringnumber))); then
    ((dodump)) && {
    ((dosetport || dosetstring)) && printf " (replaced)" || printf " (removed)"
    }
    else
    Ports+=("$(printf "%d %02x %02x %s %s" $((1 - theporttype)) $theportnumber $portoffset $theportdisabled "$theportbytes")")
    Ports+=("$(printf "%d %02x %02x %s %s" $((1 - theentrytype)) $theadapternumber $entryoffset $theadapterdisabled "$theadapterbytes")")
    fi

    if (( portoffset + portlen > thedataend )); then
    (( dodump )) && printf " (unexpected error: bytes exceeds expected end: 0x%02x + %d = 0x%02x > 0x%02x)" $portoffset $portlen $((portoffset + portlen)) "$thedataend"
    if (( entryoffset + entrylen > thedataend )); then
    (( dodump )) && printf " (unexpected error: bytes exceeds expected end: 0x%02x + %d = 0x%02x > 0x%02x)" $entryoffset $entrylen $((entryoffset + entrylen)) "$thedataend"
    fi
    if (( portlen != actualportlen )); then
    (( dodump )) && printf " (unexpected error: too few remaining bytes: %d > %d)" $portlen $actualportlen
    if (( entrylen != actualentrylen )); then
    (( dodump )) && printf " (unexpected error: too few remaining bytes: %d > %d)" $entrylen $actualentrylen
    fi
    (( dodump )) && echo

    ((portoffset += actualportlen))
    if (( dodump )); then

    case ${isusb4}_${theentrytype}_$(printf "%02X" $theadapternumber)_${theadapterbytes} in

    ?_1_??_??????)
    local thePreferredLaneAdapter=$(( 0x0${theadapterbytes:4:2} & 0x3f ))
    local thePreferenceValid=$(( (0x0${theadapterbytes:4:2} >> 6) & 1 ))
    local theReserved=$(( (0x0${theadapterbytes:4:2} >> 7) & 1 ))
    printf " // DP {"
    ((0x${theadapterbytes:0:4})) && printf " Unknown:0x%s," "${theadapterbytes:0:4}"
    printf " Preferred Lane Adapter:%d, Preference Valid:%d }" "$thePreferredLaneAdapter" "$thePreferenceValid"
    (( theReserved )) && printf ", Reserved:%d (expected 0)" $theReserved
    ;;
    0_1_??_????????????)
    printf " // TBT3-Lane Adapter { Lane:%d" "$(( (0x0${theadapterbytes:0:2} >> 4) & 1 ))"
    printf ", Dual-Lane Link Capable:"
    case $(( (0x0${theadapterbytes:0:2} >> 7) & 1 )) in
    0) printf "No" ;;
    1) printf "Yes" ;;
    esac
    printf ", 2nd Adapter Num:%d" "$(( (0x0${theadapterbytes:2:2} >> 0) & 0x3f ))"
    ((0x${theadapterbytes:4:8})) && printf ", Unknown:0x%s" "${theadapterbytes:4:8}"
    printf " }"
    ;;
    0_1_??_??????????????????)
    printf " // TBT3-PCIe Upstream Adapter { xx:%02x.%d" \
    $(( (0x0${theadapterbytes:0:2} & 0x18) | (0x0${theadapterbytes:0:2} >> 5) )) \
    $(( 0x0${theadapterbytes:0:2} & 7 ))
    ((0x${theadapterbytes:2:16})) && printf ", Unknown:0x%s" "${theadapterbytes:2:16}"
    printf " }"
    ;;
    0_1_??_??)
    printf " // TBT3-PCIe Downstream Adapter { xx:%02x.%d }" \
    $(( (0x0${theadapterbytes:0:2} & 0x18) | (0x0${theadapterbytes:0:2} >> 5) )) \
    $(( 0x0${theadapterbytes:0:2} & 7 ))
    ;;
    ?_0_01_*) printf " // ASCII Vendor Name" ;;
    ?_0_02_*) printf " // ASCII Model Name" ;;
    ?_0_08_*)
    local theTMUMode=$(( 0x0${theadapterbytes:0:2} & 3 ))
    local theTMURefresh=$(( (0x0${theadapterbytes:0:2} >> 2) & 3 ))
    local theReserved=$(( (0x0${theadapterbytes:0:2} >> 4) & 15 ))
    printf " // TMU Minimum Requested Mode { TMU Mode:"
    case $theTMUMode in
    0) printf "Off" ;;
    1) printf "Unidirectional" ;;
    2) printf "Bidirectional" ;;
    *) printf "Reserved" ;;
    esac
    printf ", TMU Refresh Rate:"
    case $theTMURefresh in
    1) printf "HiFi" ;;
    2) printf "LowRes" ;;
    *) printf "Reserved" ;;
    esac
    (( theReserved )) && printf ", Reserved:%d (expected 0)" $theReserved
    printf " }"
    ;;
    ?_0_09_*)
    local theBcdUSBSpec="$(( 0x0${theadapterbytes:2:1} * 10 + 0x0${theadapterbytes:3:1} )).${theadapterbytes:0:1}.${theadapterbytes:1:1}"
    local theIdVendor=$((0x${theadapterbytes:6:2}${theadapterbytes:4:2}))
    local theIdProduct=$((0x${theadapterbytes:10:2}${theadapterbytes:8:2}))
    local theBcdProductFWRevision="$(( 0x0${theadapterbytes:14:1} * 10 + 0x0${theadapterbytes:15:1} )).${theadapterbytes:12:1}.${theadapterbytes:13:1}"
    local theTID=$((0x${theadapterbytes:22:2}${theadapterbytes:20:2}${theadapterbytes:18:2}${theadapterbytes:16:2}))
    local theProductHWRevision=$((0x${theadapterbytes:24:2}))
    printf " // Product Descriptor { USB Spec:%s, Vendor ID:0x%04x, Product ID:0x%04x, Product FW Revision:%s, TID:0x%08x, Product HW Revision:%d }" \
    "$theBcdUSBSpec" "$theIdVendor" "$theIdProduct" "$theBcdProductFWRevision" "$theTID" "$theProductHWRevision"
    ;;
    ?_0_0[ACD]_*)
    local theLANGID=$((0x${theadapterbytes:2:2}${theadapterbytes:0:2}))
    # https://docs.microsoft.com/en-us/windows/win32/intl/language-identifier-constants-and-strings
    local theUTF16="$( xxd -p -r <<< ${theadapterbytes:4} | iconv -f UTF-16LE )"
    printf " // "
    case $(printf "0x%X" $theadapternumber) in
    0xA) printf "Serial Number" ;;
    0xC) printf "UTF16 Vendor Name" ;;
    0xD) printf "UTF16 Model Name" ;;
    esac
    printf " { LANGID:0x%04X, _:\"%s\" }" "$theLANGID" "$theUTF16"
    ;;
    ?_0_0B_*)
    printf " // USB Port Mapping {"
    for (( usb=0; usb < ${#theadapterbytes} / 6; usb++ )); do
    local theUSB=${theadapterbytes:$((usb*6)):6}
    (( usb )) && printf ","
    printf " { USB3 Port Number:%d, PD Port Number:%d, xHCI Index:%d, USB Type-C:%d, USB3 Adapter Number:%d, Tunnelling Support:%d }" \
    $(( (0x${theUSB:0:2} >> 0) & 0x0f )) \
    $(( (0x${theUSB:2:2} >> 0) & 0x1f )) \
    $(( (0x${theUSB:2:2} >> 5) & 3 )) \
    $(( (0x${theUSB:2:2} >> 7) & 1 )) \
    $(( (0x${theUSB:4:2} >> 0) & 0x3f )) \
    $(( (0x${theUSB:4:2} >> 7) & 1 ))
    done
    printf " }"
    ;;
    ?_0_3F_*) printf " // Reserved" ;;
    ?_0_3*) printf " // Vendor Specific Type" ;;
    ?_0_*) printf " // Reserved" ;;
    esac

    echo
    fi

    ((entryoffset += actualentrylen))
    done

    if (( doportnumber >= 0 || dostringnumber >= 0 )); then
    @@ -396,12 +559,12 @@ processdrom () {
    local allportbytes=""
    allportbytes=$(
    IFS=$'\n'
    for theportstring in $(sort <<< "${Ports[*]}"); do
    printf "%02x%02x%s" $(( (${#theportstring} - 10) / 2 + 2 )) $(( ((1 - ${theportstring:0:1}) << 7) | (${theportstring:8:1} << 6) | (0x${theportstring:2:2} & 0x3f) )) "${theportstring:10}"
    for theentrystring in $(sort <<< "${Ports[*]}"); do
    printf "%02x%02x%s" $(( (${#theentrystring} - 10) / 2 + 2 )) $(( ((1 - ${theentrystring:0:1}) << 7) | (${theentrystring:8:1} << 6) | (0x${theentrystring:2:2} & 0x3f) )) "${theentrystring:10}"
    done
    )
    replacebytes 22 "$allportbytes" ${#thedrom}
    ((thedatalen = ${#allportbytes}/2 + 22 - 13 ))
    replacebytes $startentryoffset "$allportbytes" ${#thedrom}
    ((thedatalen = ${#allportbytes}/2 + $startentryoffset - 13 ))
    replacebytes 14 "$(printf "%04x" "$thedatalen" | sed -E 's/(..)(..)/\2\1/')"

    doportnumber=-1
    @@ -412,7 +575,7 @@ processdrom () {
    fi
    done

    theexpectedcrc32=$((0x$(xxd -p -r <<< "${thedrom:26:$thedatalen*2}" | crc32c)))
    theexpectedcrc32=$((0x$(xxd -p -r <<< "${thedrom:26:$thedatalen*2}" | CRC32b)))
    (( theexpectedcrc32 != thecrc32 )) && {
    if (( dorepairchecksums )); then
    replacebytes 9 "$(printf "%08x" $theexpectedcrc32 | sed -E 's/(..)(..)(..)(..)/\4\3\2\1/')"
    @@ -423,20 +586,20 @@ processdrom () {
    }

    (( dodump )) && {
    printf "0x%02x) End" "$portoffset"
    if [[ -n $(tr -d '0' <<< "${thedrom:$portoffset*2}") ]]; then
    printf " (unexpected bytes: %s)" "${thedrom:$portoffset*2}"
    printf "0x%02x) End" "$entryoffset"
    if [[ -n $(tr -d '0' <<< "${thedrom:$entryoffset*2}") ]]; then
    printf " (unexpected bytes: %s)" "${thedrom:$entryoffset*2}"
    fi
    echo
    }

    if (( domake )); then

    IFS=$'\n'
    portoffset=22
    for theportstring in $(sort <<< "${Ports[*]}"); do
    portlen=$(( (${#theportstring} - 10) / 2 + 2 ))
    ((portoffset += portlen))
    entryoffset=$startentryoffset
    for theentrystring in $(sort <<< "${Ports[*]}"); do
    entrylen=$(( (${#theentrystring} - 10) / 2 + 2 ))
    ((entryoffset += entrylen))
    done

    printf '
    @@ -453,42 +616,42 @@ processdrom () {
    /* 0x14 */ 0x%s, // Device Revision: 0x%X
    /* 0x15 */ 0x%s, // EEPROM Revision: %d
    ' \
    $portoffset \
    $entryoffset \
    "${thedrom:0:2}" $thecrc8 \
    "${thedrom:2:2}" "${thedrom:4:2}" "${thedrom:6:2}" "${thedrom:8:2}" "${thedrom:10:2}" "${thedrom:12:2}" "${thedrom:14:2}" "${thedrom:16:2}" $((0x${thedrom:2:2})) "$theuidnum" \
    "${thedrom:18:2}" "${thedrom:20:2}" "${thedrom:22:2}" "${thedrom:24:2}" $thecrc32 \
    "${thedrom:26:2}" $thedeviceromrevision \
    "${thedrom:26:2}" $theversion \
    "${thedrom:28:2}" "${thedrom:30:2}" "$thedatalen" \
    "${thedrom:32:2}" "${thedrom:34:2}" $thevendorid \
    "${thedrom:36:2}" "${thedrom:38:2}" $themodelid \
    "${thedrom:40:2}" $themodelrev \
    "${thedrom:42:2}" $theeepromrev

    IFS=$'\n'
    portoffset=22
    for theportstring in $(sort <<< "${Ports[*]}"); do
    portlen=$(( (${#theportstring} - 10) / 2 + 2 ))
    theportnumber=$((0x${theportstring:2:2} & 0x3f))
    theportdisabled=${theportstring:8:1}
    theporttype=$((1 - ${theportstring:0:1}))
    printf " /* 0x%02X %s %X */ 0x%02x, 0x%02x, %s" $portoffset "$( ((theportdisabled)) && printf "-" || printf " " )" $theportnumber $portlen \
    $(( (theporttype << 7) | (theportdisabled << 6) | theportnumber )) \
    "$( perl -pE 's/(..)/0x\1, /g' <<< "${theportstring:10}" )"
    if (( theporttype == 0 )); then
    entryoffset=$startentryoffset
    for theentrystring in $(sort <<< "${Ports[*]}"); do
    entrylen=$(( (${#theentrystring} - 10) / 2 + 2 ))
    theadapternumber=$((0x${theentrystring:2:2} & 0x3f))
    theadapterdisabled=${theentrystring:8:1}
    theentrytype=$((1 - ${theentrystring:0:1}))
    printf " /* 0x%02X %s %X */ 0x%02x, 0x%02x, %s" $entryoffset "$( ((theadapterdisabled)) && printf "-" || printf " " )" $theadapternumber $entrylen \
    $(( (theentrytype << 7) | (theadapterdisabled << 6) | theadapternumber )) \
    "$( perl -pE 's/(..)/0x\1, /g' <<< "${theentrystring:10}" )"
    if (( theentrytype == 0 )); then
    local thestring=""
    thestring="$(perl -pE "s/(00)+$//" <<< "${theportstring:10}" | xxd -p -r)"
    if (( theportnumber == 1 )); then
    thestring="$(perl -pE "s/(00)+$//" <<< "${theentrystring:10}" | xxd -p -r)"
    if (( theadapternumber == 1 )); then
    printf '// Vendor Name: "%s"' "$thestring"
    elif (( theportnumber == 2 )); then
    elif (( theadapternumber == 2 )); then
    printf '// Device Name: "%s"' "$thestring"
    else
    printf '// "%s"' "${theportstring:10}"
    printf '// "%s"' "${theentrystring:10}"
    fi
    elif (( ${#theportstring} == 12 )); then
    printf '// PCIe xx:%02x.%x' $((0x${theportstring:10} >> 5)) $((0x${theportstring:10} & 0x1f)) #### fix this - function should only be 3 bits - therefore there are 2 unknown bits?
    elif (( ${#theentrystring} == 12 )); then
    printf '// PCIe xx:%02x.%x' $((0x${theentrystring:10} >> 5)) $((0x${theentrystring:10} & 0x1f)) #### fix this - function should only be 3 bits - therefore there are 2 unknown bits?
    fi
    printf "\n"
    ((portoffset += portlen))
    ((entryoffset += entrylen))
    done
    printf " },\n"
    fi
    @@ -639,7 +802,7 @@ loadstring () {
    ((numstrings++))
    sourcename="string:$numstrings"
    fi
    adddrom "$sourcename" "$1"
    adddrom "$sourcename" $(xxd -p -r <<< "$1" | xxd -p -c 99999)
    }


    @@ -932,12 +1095,18 @@ loadoneioregfile () {
    IFS=$'\n'
    for dromitem in $(
    perl -e '
    $inhex=0;
    $thepath=""; while (<>) {
    if ( $inhex && /^[ |]*[0-9A-F]+:((?: [0-9A-F]{2}){1,32})/ ) { $thehex .= $1 }
    elsif ( $inhex ) { $inhex = 0; print "" . (lc ($thehex =~ s/ //gr)) . ">\n" }
    if ( /^([ |]*)\+\-o (.+) </ ) { $indent = (length $1) / 2; $name = $2; $thepath =~ s|^((/[^/]*){$indent}).*|$1/$name| }
    if ( /^[ |]*"(ThunderboltDROM)" = <(.*)>/i ) { print "'"${thesource}"'" . ":" . $thepath . "/" . $1 . " = <" . $2 . ">\n" }
    elsif ( /^[ |]*"(ThunderboltDROM|thunderbolt-drom)" = <(.*)>/i ) { print "'"${thesource}"'" . ":" . $thepath . "/" . $1 . " = <" . $2 . ">\n" }
    elsif ( /^[ |]*"(ThunderboltDROM|thunderbolt-drom)" = $/i ) { print "'"${thesource}"'" . ":" . $thepath . "/" . $1 . " = <"; $inhex=1; $thehex="" }
    }
    ' < "${thefilename}"
    ); do
    #echo "«${dromitem}»"
    loadonedromitem "${dromitem}"
    done
    }
  3. joevt revised this gist Dec 31, 2020. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions ThunderboltUtil.sh
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,3 @@
    #! /bin/zsh
    # ThunderboltUtil.sh v1.5
    # by joevt Jul 7, 2020

    #=========================================================================================
  4. joevt revised this gist Jul 7, 2020. 1 changed file with 63 additions and 43 deletions.
    106 changes: 63 additions & 43 deletions ThunderboltUtil.sh
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    #! /bin/zsh
    # ThunderboltUtil.sh v1.4
    # by joevt Jul 5, 2020
    # ThunderboltUtil.sh v1.5
    # by joevt Jul 7, 2020

    #=========================================================================================
    #
    @@ -739,32 +739,32 @@ loadonedslfile () {
    local thesource="$2"
    [[ -z $thesource ]] && thesource="$thefilename"

    #
    # LeadNameChar := [A-Za-z_]
    # DigitChar := [0-9]
    # NameChar := [A-Za-z_0-9]
    # RootChar := \
    # ParentPrefixChar := ^
    # PathSeparatorChar := .
    #
    # // Names and paths
    #
    # NameSeg := [A-Za-z_][A-Za-z_0-9]{0,3}
    #
    # PrefixPath := \^*
    #
    # NamePathTail := ([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #
    # NamePath := (([A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*)|)
    #
    # NonEmptyNamePath := [A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #
    # NameString := ([\\]|\^+|)(([A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*)|)
    # | [A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #

    IFS=$'\n'
    for dromitem in $(
    #
    # LeadNameChar := [A-Za-z_]
    # DigitChar := [0-9]
    # NameChar := [A-Za-z_0-9]
    # RootChar := \
    # ParentPrefixChar := ^
    # PathSeparatorChar := .
    #
    # // Names and paths
    #
    # NameSeg := [A-Za-z_][A-Za-z_0-9]{0,3}
    #
    # PrefixPath := \^*
    #
    # NamePathTail := ([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #
    # NamePath := (([A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*)|)
    #
    # NonEmptyNamePath := [A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #
    # NameString := ([\\]|\^+|)(([A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*)|)
    # | [A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #
    perl -e '
    use strict;
    @@ -791,10 +791,20 @@ loadonedslfile () {
    my $dodump = 0;
    my $buffersize = 0;
    if ( ($line2 . $line1) =~ /"ThunderboltDROM",\s*Buffer\s*\(0x([0-9A-F]+)\)/ ) {
    # print "gotpattern «" . $line2 . "»«" . $line1 . "»\n";
    if ( $line2 =~ /"ThunderboltDROM",\s*$/) {
    $dodump = 1;
    $buffersize = hex($1);
    if ( $line1 =~ /^\s*Buffer\s*\(0x([0-9A-F]+)\)/ ) {
    $buffersize = hex($1);
    }
    elsif ( $line1 =~ /^\s*Buffer\s*\(One\)/ ) {
    $buffersize = 1;
    }
    elsif ( $line1 =~ /^\s*Buffer\s*\(Zero\)/ ) {
    $buffersize = 0;
    }
    else {
    $dodump = 0;
    }
    }
    if ( $dodump == 1 ) {
    @@ -827,31 +837,40 @@ loadonedslfile () {
    addline($_);
    if ( /(External|Device|Field|Method|Name|Scope)\s*\(\s*([\\]|\^+|)(([A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*)|)/ ) {
    my $command = $1;
    my $prefix = $2;
    my $prefix = $2; # \ or ^+ or empty
    my $nameparts = $3;
    $nameparts =~ s/([A-Za-z0-9])_+/\1/g; # remove trailing spaces
    #print "nameparts:" . $nameparts . "\n";
    if ( $prefix =~ /\\/ ) {
    # absolute path
    $nextacpipath = $prefix . $nameparts;
    }
    elsif ( $prefix =~ /\^+/ ) {
    # parent path - remove from the current path a number of parents equal to the number of ^ characters
    my $pattern = "(.*?)([\\\\.][^.]+){0," . length($prefix) . "}\$";
    # empty path means root "\"
    $nextacpipath = ($acpipath =~ s/$pattern/\1/r =~ s/^$/\\/r) . "$nameparts";
    }
    elsif ( ($command eq "Scope") && ($nameparts =~ /[^.]+/) ) {
    if ( $paths{$acpipath . "." . $nameparts} ) {
    $nextacpipath = (($acpipath . "." . $nameparts) =~ s/^\\\./\\/r =~ s/([A-Za-z0-9])_+/\1/gr);
    }
    elsif ( ($acpipath . ".") =~ /(.*[.\\]$nameparts)[.].*/ ) {
    $nextacpipath = (($acpipath . ".") =~ s/(.*[.\\]$nameparts)[.].*/\1/r)
    } else {
    $nextacpipath = (($acpipath . "." . $nameparts) =~ s/^\\\./\\/r =~ s/([A-Za-z0-9])_+/\1/gr);
    print STDERR "unknown path " . $nextacpipath . "\n";
    }
    }
    else {
    $nextacpipath = (($acpipath . "." . $nameparts) =~ s/^\\\./\\/r =~ s/([A-Za-z0-9])_+/\1/gr);
    # assume path is current path appended with name
    $nextacpipath = (($acpipath . "." . $nameparts) =~ s/^\\\./\\/r); # "\." is "\"
    if ( ($command eq "Scope") && ($nameparts =~ /[^.]+/) && !($paths{$nextacpipath}) ) {
    # For Scope, if path does not exist...
    if ( ($acpipath . ".") =~ /(.*[.\\]$nameparts)[.].*/ ) {
    # if current path includes names then set scope to parent that ends at name
    $nextacpipath = (($acpipath . ".") =~ s/(.*[.\\]$nameparts)[.].*/\1/r)
    } else {
    # unknown path - assume it exists
    print STDERR "unknown path " . $nextacpipath . "\n";
    #print STDERR "$_\n" for keys %paths;
    }
    }
    }
    $nextcodepath = (($codepath . "." . $prefix . $nameparts) =~ s/^\\\./\\/r =~ s/([A-Za-z0-9])_+/\1/gr =~ s/\\\\/\\/r);
    # code path is just current path appended with entire prefix/nameparts (no interpretation of prefix characters or scoping)
    $nextcodepath = (($codepath . "." . $prefix . $nameparts) =~ s/^\\\./\\/r =~ s/\\\\/\\/r); # "\." is "\" and "\\" is "\"
    if ( !$paths{$nextacpipath} ) {
    # keep a list of all paths
    $paths{$nextacpipath} = 1;
    }
    }
    @@ -870,6 +889,7 @@ loadonedslfile () {
    }
    group("", "\\", "\\");
    # print "$_\n" for keys %paths;
    ' < "$thefilename"
    ); do
    (( debug )) && echo ":" loadonedromitem "${dromitem}" 1>&2
  5. joevt revised this gist Jul 5, 2020. No changes.
  6. joevt revised this gist Jul 5, 2020. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions ThunderboltUtil.sh
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    #! /bin/zsh
    # ThunderboltUtil.sh v1.3
    # ThunderboltUtil.sh v1.4
    # by joevt Jul 5, 2020

    #=========================================================================================
    @@ -398,7 +398,7 @@ processdrom () {
    local allportbytes=""
    allportbytes=$(
    IFS=$'\n'
    for theportstring in $(sort <<< "${Ports[@]}"); do
    for theportstring in $(sort <<< "${Ports[*]}"); do
    printf "%02x%02x%s" $(( (${#theportstring} - 10) / 2 + 2 )) $(( ((1 - ${theportstring:0:1}) << 7) | (${theportstring:8:1} << 6) | (0x${theportstring:2:2} & 0x3f) )) "${theportstring:10}"
    done
    )
    @@ -436,7 +436,7 @@ processdrom () {

    IFS=$'\n'
    portoffset=22
    for theportstring in $(sort <<< "${Ports[@]}"); do
    for theportstring in $(sort <<< "${Ports[*]}"); do
    portlen=$(( (${#theportstring} - 10) / 2 + 2 ))
    ((portoffset += portlen))
    done
    @@ -468,7 +468,7 @@ processdrom () {

    IFS=$'\n'
    portoffset=22
    for theportstring in $(sort <<< "${Ports[@]}"); do
    for theportstring in $(sort <<< "${Ports[*]}"); do
    portlen=$(( (${#theportstring} - 10) / 2 + 2 ))
    theportnumber=$((0x${theportstring:2:2} & 0x3f))
    theportdisabled=${theportstring:8:1}
  7. joevt revised this gist Jul 5, 2020. 1 changed file with 6 additions and 3 deletions.
    9 changes: 6 additions & 3 deletions ThunderboltUtil.sh
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    #! /bin/zsh
    # ThunderboltUtil.sh v1.2
    # by joevt Jul 4, 2020
    # ThunderboltUtil.sh v1.3
    # by joevt Jul 5, 2020

    #=========================================================================================
    #
    @@ -683,7 +683,10 @@ loadfwfile () {
    $thedrom =~ s/(ff )*$//g;
    $thedrom =~ s/ //g;
    $version = "vers_unknown";
    if ($X < hex(4000)) {
    if ($X == hex(200)) {
    $version = "v" . substr($_, (10) * 3, 2);
    $partition = "linux";
    } elsif ($X < hex(4000)) {
    $partition = "missing header";
    } elsif ($X < hex(82000)) {
    $version = "v" . substr($_, (hex(4000) + 10) * 3, 2);
  8. joevt revised this gist Jul 4, 2020. 1 changed file with 5 additions and 3 deletions.
    8 changes: 5 additions & 3 deletions ThunderboltUtil.sh
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    #! /bin/zsh
    # ThunderboltUtil.sh v1.1
    # by joevt Jun 29, 2020
    # ThunderboltUtil.sh v1.2
    # by joevt Jul 4, 2020

    #=========================================================================================
    #
    @@ -250,7 +250,9 @@ processdrom () {

    while (( $# )); do
    local param="$1"; shift
    eval "local $param=1"
    if [[ $param != '-' ]]; then
    eval "local $param=1"
    fi
    case "$param" in
    dosetuid)
    local douuidnum=""
  9. joevt created this gist Jun 30, 2020.
    1,042 changes: 1,042 additions & 0 deletions ThunderboltUtil.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1042 @@
    #! /bin/zsh
    # ThunderboltUtil.sh v1.1
    # by joevt Jun 29, 2020

    #=========================================================================================
    #
    #
    # Thunderbolt DROM Notes:
    #
    #
    # https://lore.kernel.org/patchwork/patch/714766/
    #
    # Macs with Thunderbolt 1 do not have a unit-specific DROM: The DROM is
    # empty with uid 0x1000000000000. (Apple started factory-burning a unit-
    # specific DROM with Thunderbolt 2.)
    #
    # Instead, the NHI EFI driver supplies a DROM in a device property. Use
    # it if available. It's only available when booting with the efistub.
    # If it's not available, silently fall back to our hardcoded DROM.
    #
    # The size of the DROM is always 256 bytes. The number is hardcoded into
    # the NHI EFI driver. This commit can deal with an arbitrary size however,
    # just in case they ever change that.
    #
    # A modification is needed in the resume code where we currently read the
    # uid of all switches in the hierarchy to detect plug events that occurred
    # during sleep. On Thunderbolt 1 root switches this will now lead to a
    # mismatch between the uid of the empty DROM and the EFI DROM. Exempt the
    # root switch from this check: It's built in, so the uid should never
    # change. However we continue to *read* the uid of the root switch, this
    # seems like a good way to test its reachability after resume.
    #
    # Background information: The EFI firmware volume contains ROM files for
    # the NHI, GMUX and several other chips as well as key material. This
    # strategy allows Apple to deploy ROM or key updates by simply publishing
    # an EFI firmware update on their website. Drivers do not access those
    # files directly but rather through a file server via EFI protocol
    # AC5E4829-A8FD-440B-AF33-9FFE013B12D8. Files are identified by GUID, the
    # NHI DROM has 339370BD-CFC6-4454-8EF7-704653120818.
    #
    # The NHI EFI driver amends that file with a unit-specific uid. The uid
    # has 64 bit but its entropy is much lower: 24 bit represent the model,
    # 24 bit are taken from a serial number, 16 bit are fixed. The NHI EFI
    # driver obtains the serial number via the DataHub protocol, copies it
    # into the DROM, calculates the CRC and submits the result as a device
    # property.
    #
    #
    # https://github.com/torvalds/linux/blob/master/drivers/thunderbolt/eeprom.c
    #
    # #define TB_DROM_DATA_START 13
    # struct tb_drom_header {
    # /* BYTE 0 */
    # u8 uid_crc8; /* checksum for uid */
    # /* BYTES 1-8 */
    # u64 uid;
    # /* BYTES 9-12 */
    # u32 data_crc32; /* checksum for data_len bytes starting at byte 13 */
    # /* BYTE 13 */
    # u8 device_rom_revision; /* should be <= 1 */
    # u16 data_len:10;
    # u8 __unknown1:6;
    # /* BYTES 16-21 */
    # u16 vendor_id;
    # u16 model_id;
    # u8 model_rev;
    # u8 eeprom_rev;
    # } __packed;
    #
    # enum tb_drom_entry_type {
    # /* force unsigned to prevent "one-bit signed bitfield" warning */
    # TB_DROM_ENTRY_GENERIC = 0U,
    # TB_DROM_ENTRY_PORT,
    # };
    #
    # struct tb_drom_entry_header {
    # u8 len;
    # u8 index:6;
    # bool port_disabled:1; /* only valid if type is TB_DROM_ENTRY_PORT */
    # enum tb_drom_entry_type type:1;
    # } __packed;
    #
    # struct tb_drom_entry_generic {
    # struct tb_drom_entry_header header;
    # u8 data[];
    # } __packed;
    #
    # struct tb_drom_entry_port {
    # /* BYTES 0-1 */
    # struct tb_drom_entry_header header;
    # /* BYTE 2 */
    # u8 dual_link_port_rid:4; // "Dual-Link Port RID" 0 for the first Thunderbolt controller, 1 for the second Thunderbolt controller, 2 for ?
    # u8 link_nr:1; // "Lane" 0 for Lane 1, 1 for Lane 2
    # u8 unknown1:2; // 0
    # bool has_dual_link_port:1; // 1
    #
    # /* BYTE 3 */
    # u8 dual_link_port_nr:6; // "Dual-Link Port" 2,1,4,3 for ports 1,2,3,4
    # u8 unknown2:2; // 0
    #
    # /* BYTES 4 - 5 TODO decode */
    # u8 micro2:4; // 0 0,1,2 (0 for first controller, 1 for second controller)
    # u8 micro1:4; // 4 8
    # u8 micro3; // "Micro Address" 26,24 00,01
    # // or "HPM Address"?
    #
    # /* BYTES 6-7, TODO: verify (find hardware that has these set) */
    # u8 peer_port_rid:4; // 0
    # u8 unknown3:3; // 0
    # bool has_peer_port:1; // 0
    # u8 peer_port_nr:6; // 0
    # u8 unknown4:2; // 0
    # } __packed;
    #
    #
    #
    #
    #
    # Macmini8,1
    #
    # 0x00) CRC8: 0x..
    # 0x01) UID: 0x000115CE05397601 Least significant byte is Thunderbolt Bus number (0,1,...)
    # 0x09) CRC32c: 0x........
    # 0x0d) Device ROM Revision: 1
    # 0x0e) Length: 0x....
    # 0x10) Vendor ID: 0x1 two bytes;
    # 1 = Apple
    # 0x12) Device ID: 0xD two bytes;
    # 16=MacPro7,1
    # 13=iMac19,1 or Macmini8,1
    # 12=iMac18,3 or iMacPro1,1
    # 11=MacBookPro11,5 // Thunderbolt 2
    # 0x14) Device Revision: 0x1
    # 0x15) EEPROM Revision: 0
    # 0x16) 1: 8 1 0 2 8 1 00 0000 Thunderbolt Port
    # 0x1e) 2: 9 1 0 1 8 1 00 0000 Thunderbolt Port
    # 0x26) 3: 8 1 0 4 8 1 01 0000 Thunderbolt Port
    # 0x2e) 4: 9 1 0 3 8 1 01 0000 Thunderbolt Port
    # 0x36) 5: 0 9 0 1 0 0 DP or HDMI Adapter (DP In Adapter) Sink 0
    # 0x3b) 6: 0 9 0 1 0 0 DP or HDMI Adapter (DP In Adapter) Sink 1
    # 0x40) 7: Thunderbolt NHI Adapter
    # 0x42) 8: 2 0 PCIe Adapter (PCI Down Adapter) @1 (3 bits PCI Device, 5 bits for ???)
    # 0x45) 9: 8 0 PCIe Adapter (PCI Down Adapter) @4
    # 0x48) - A:
    # 0x4a) - B:
    # 0x4c) 1: "Apple Inc."
    # 0x59) 2: "Macintosh"
    # 0x65) End
    #
    #
    # MacBookPro11,5
    #
    # 0x01) UID: 0x0001001701C9F100
    # 0x0d) Device ROM Revision: 1
    # 0x10) Vendor ID: 0x1
    # 0x12) Device ID: 0xB
    # 0x14) Device Revision: 0x1
    # 0x15) EEPROM Revision: 1
    # 0x16) 1: 8 0 0 2 8 0 00 0000 Thunderbolt Port 1, Dual Link Port, Lane 0, Dual-Link Port 2, Micro Address 0
    # 0x1e) 2: 9 0 0 1 8 0 00 0000 Thunderbolt Port 2, Dual Link Port, Lane 1, Dual-Link Port 1, Micro Address 0
    # 0x26) 3: 8 0 0 4 8 0 01 0000 Thunderbolt Port 3, Dual Link Port, Lane 0, Dual-Link Port 4, Micro Address 1
    # 0x2e) 4: 9 0 0 3 8 0 01 0000 Thunderbolt Port 4, Dual Link Port, Lane 1, Dual-Link Port 3, Micro Address 1
    # 0x36) 5: 000000000000 Thunderbolt NHI Adapter
    # 0x3e) 6: 6 0 PCIe Adapter (PCI Down Adapter) @3 (3 bits PCI Device, 5 bits for ???)
    # 0x41) 7: 8 0 PCIe Adapter (PCI Down Adapter) @4
    # 0x44) 8: a 0 PCIe Adapter (PCI Down Adapter) @5
    # 0x47) 9: c 0 PCIe Adapter (PCI Down Adapter) @6
    # 0x4a) - A:
    # 0x4c) B: 500082 DP or HDMI Adapter (DP In Adapter) Sink 0, Port Affinity 1,2, Preferred Null Port 2
    # 0x51) C: 500084 DP or HDMI Adapter (DP In Adapter) Sink 1, Port Affinity 3,4, Preferred Null Port 4
    # 0x56) 1: "Apple Inc."
    # 0x63) 2: "Macintosh"
    # 0x6f) End
    #
    #=========================================================================================
    # Modify DROM


    iasl_location=/Applications/MaciASL.app/Contents/MacOS/iasl-stable


    getarrstart () {
    # bash arrays start at 0
    # zsh arrays start at 1 (applies only to [] syntax) but this can be changed with "setopt ksh_arrays"
    # zsh arrays start at 0 when using ${arr:x:x} syntax
    local arr=(1 0)
    arrstart=${arr[1]}
    }
    getarrstart


    crc () {
    local bits=$(($1))
    local inputreflected=$(($2))
    local outputreflected=$(($3))
    local polynomial=$(($4))
    local crc=$(($5))
    local finalxor=$(($6))
    for data in $(xxd -p -c 1 | {
    if (( inputreflected )); then
    rev | tr '0123456789abcdef' '084c2a6e195d3b7f'
    else
    cat
    fi
    }); do
    ((crc ^= (0x$data << (bits-8))))
    for ((i=0;i<8;i++)); do
    if ((crc >> (bits-1))); then
    ((crc = (crc << 1) ^ polynomial))
    else
    ((crc <<= 1))
    fi
    ((crc &= ((1 << $bits)-1)))
    done
    done
    printf "%0$(((bits+3) / 4))x" $((crc ^ finalxor)) | {
    if (( outputreflected )); then
    rev | tr '0123456789abcdef' '084c2a6e195d3b7f'
    else
    cat
    fi
    }
    }

    crc8uid () {
    crc 8 0 0 7 0 0xdb
    }

    crc32c () {
    crc 32 1 1 0x1edc6f41 0xffffffff 0xffffffff
    }


    replacebytes () {
    local bytepos=$(($1*2))
    local thebytes=$2
    local thelen=${#thebytes}
    [[ -n $3 ]] && thelen=$(($3*2))
    thedrom=${thedrom:0:$bytepos}${thebytes}${thedrom:$bytepos+thelen}
    }

    processdrom () {
    (( debug )) && echo ": processdrom " "$@" 1>&2

    local dosetuid=0
    local dosetport=0
    local dosetstring=0
    local doportnumber=-1
    local dostringnumber=-1

    while (( $# )); do
    local param="$1"; shift
    eval "local $param=1"
    case "$param" in
    dosetuid)
    local douuidnum=""
    douuidnum=$(perl -pe '$_="0000000000000000" . $_;s/[^A-Fa-f0-9]//g;s/.*(.{16})$/\1/' <<< "$1" | tr 'a-f' 'A-F') # make UID uppercase like Apple does
    shift
    local dotheuid=""
    dotheuid=$(tr 'A-F' 'a-f' <<< "${douuidnum:14:2}${douuidnum:12:2}${douuidnum:10:2}${douuidnum:8:2}${douuidnum:6:2}${douuidnum:4:2}${douuidnum:2:2}${douuidnum:0:2}")
    ;;
    dosetport)
    local doportnumber="$(($1))"; shift
    local doportcontents="$1"; shift
    local doportdisable="$1"
    [[ $doportdisable = "-" || $doportdisable = "1" ]] && doportdisable=1 || doportdisable=0
    ;;
    dodeleteport)
    local doportnumber="$(($1))"; shift
    ;;
    dosetstring)
    local dostringnumber="$(($1))"; shift
    local dostringcontents="$1"; shift
    ;;
    dodeletestring)
    local dostringnumber="$(($1))"; shift
    ;;
    esac
    done

    thecrc8=$((0x${thedrom:0:2}))
    theuid=${thedrom:2:16}
    theuidnum=$(tr 'a-f' 'A-F' <<< "${thedrom:16:2}${thedrom:14:2}${thedrom:12:2}${thedrom:10:2}${thedrom:8:2}${thedrom:6:2}${thedrom:4:2}${thedrom:2:2}") # make UID uppercase like Apple does

    if [[ $dosetuid = 1 && $theuidnum != "$douuidnum" ]]; then
    replacebytes 1 "$dotheuid"
    theuid=$dotheuid
    fi

    theexpectedcrc8=$((0x$(xxd -p -r <<< "$theuid" | crc8uid)))
    (( theexpectedcrc8 != thecrc8 )) && {
    if (( dorepairchecksums )); then
    replacebytes 0 "$(printf "%02x" $theexpectedcrc8)"
    (( dodump )) && printf "0x00) CRC8: 0x%02x (changed: 0x%02x)\n" $thecrc8 $theexpectedcrc8
    else
    (( dodump )) && printf "0x00) CRC8: 0x%02x (expected: 0x%02x)\n" $thecrc8 $theexpectedcrc8
    fi
    }

    (( dodump )) && {
    printf "0x01) UID: 0x%s" "$theuidnum"
    if [[ $dosetuid = 1 && $theuidnum != "$douuidnum" ]]; then
    printf " (changed: 0x%s)" "$douuidnum"
    fi
    echo
    }

    thecrc32=$((0x${thedrom:24:2}${thedrom:22:2}${thedrom:20:2}${thedrom:18:2}))
    thedatalen=$((0x${thedrom:30:2}${thedrom:28:2} & 0x3ff))

    thedeviceromrevision=$((0x${thedrom:26:2}))
    (( dodump )) && printf "0x0d) Device ROM Revision: %d\n" $thedeviceromrevision

    theunknown=$(((0x${thedrom:30:2}${thedrom:28:2} & 0x3ff) >> 10))
    (( dodump && theunknown )) && printf "0x0e) Unknown: %d (expected 0)\n" $theunknown

    thevendorid=$((0x${thedrom:34:2}${thedrom:32:2}))
    (( dodump )) && printf "0x10) Vendor ID: 0x%X\n" $thevendorid

    themodelid=$((0x${thedrom:38:2}${thedrom:36:2}))
    (( dodump )) && printf "0x12) Device ID: 0x%X\n" $themodelid

    themodelrev=$((0x${thedrom:40:2}))
    (( dodump )) && printf "0x14) Device Revision: 0x%X\n" $themodelrev

    theeepromrev=$((0x${thedrom:42:2}))
    (( dodump )) && printf "0x15) EEPROM Revision: %d\n" $theeepromrev

    while ((1)); do
    local portoffset=22
    ((thedataend = 13 + thedatalen))

    # Keep a list of ports and strings and generic data
    Ports=()

    while (( portoffset <= thedataend )); do
    [[ -z ${thedrom:$portoffset*2:2} ]] && break

    local portlen=$((0x${thedrom:$portoffset*2:2}))

    if ((portlen < 2)); then
    (( dodump && (portoffset != thedataend) )) && echo "Unexpected error: port length is < 2: $portoffset + $portlen <= $thedataend"
    break
    fi
    (( portoffset == thedataend )) && {
    (( dodump )) && echo " ============== (following bytes are unexpected)"
    ((thedataend += portlen))
    }

    local theporttype=$((0x${thedrom:$portoffset*2+2:2} >> 7)) # 0=generic,1=port
    local theportdisabled=$(((0x${thedrom:$portoffset*2+2:2} >> 6) & 1)) # 0=enabled,1=disabled
    local theportnumber=$((0x${thedrom:$portoffset*2+2:2} & 0x3f)) # 1..9,a..d
    local theportbytes=${thedrom:$portoffset*2+4:$portlen*2 - 4}
    local actualportlen=$((${#theportbytes} / 2 + 2))

    if (( dodump )); then
    printf "0x%02x) %s %X: " $portoffset "$( ((theportdisabled)) && printf "-" || printf " ")" $theportnumber
    if (( theporttype || theportnumber < 1 || theportnumber > 2 )); then
    printf "%s" "$theportbytes"
    else
    printf "\"%s\"" "$(perl -pE "s/(00)+$//" <<< "${theportbytes}" | xxd -p -r)"
    if [[ $(perl -pE "s/.*?((00)+)$/\1/" <<< "${theportbytes}") != "00" ]]; then
    printf " (expected single terminating null character: %s)" "$theportbytes"
    fi
    fi

    if (( !theporttype && theportdisabled )); then
    printf " (unexpectedly disabled)"
    fi
    fi

    if ((theportnumber <= 0)); then
    (( dodump )) && printf " (expected port number > 0)"
    elif ((theportnumber == (theporttype ? doportnumber : dostringnumber))); then
    ((dodump)) && {
    ((dosetport || dosetstring)) && printf " (replaced)" || printf " (removed)"
    }
    else
    Ports+=("$(printf "%d %02x %02x %s %s" $((1 - theporttype)) $theportnumber $portoffset $theportdisabled "$theportbytes")")
    fi

    if (( portoffset + portlen > thedataend )); then
    (( dodump )) && printf " (unexpected error: bytes exceeds expected end: 0x%02x + %d = 0x%02x > 0x%02x)" $portoffset $portlen $((portoffset + portlen)) "$thedataend"
    fi
    if (( portlen != actualportlen )); then
    (( dodump )) && printf " (unexpected error: too few remaining bytes: %d > %d)" $portlen $actualportlen
    fi
    (( dodump )) && echo

    ((portoffset += actualportlen))
    done

    if (( doportnumber >= 0 || dostringnumber >= 0 )); then
    (( dosetport )) && Ports+=( "$( printf "%d %02x %02x %s %s" 0 $doportnumber 0 "$doportdisable" "$doportcontents" )" )
    (( dosetstring )) && Ports+=( "$( printf "%d %02x %02x %s %s" 1 $dostringnumber 0 "0" "$(printf "%s\0" "$dostringcontents" | xxd -p -c 9999)" )" )
    local allportbytes=""
    allportbytes=$(
    IFS=$'\n'
    for theportstring in $(sort <<< "${Ports[@]}"); do
    printf "%02x%02x%s" $(( (${#theportstring} - 10) / 2 + 2 )) $(( ((1 - ${theportstring:0:1}) << 7) | (${theportstring:8:1} << 6) | (0x${theportstring:2:2} & 0x3f) )) "${theportstring:10}"
    done
    )
    replacebytes 22 "$allportbytes" ${#thedrom}
    ((thedatalen = ${#allportbytes}/2 + 22 - 13 ))
    replacebytes 14 "$(printf "%04x" "$thedatalen" | sed -E 's/(..)(..)/\2\1/')"

    doportnumber=-1
    dostringnumber=-1
    (( dodump )) && echo " after changes:"
    else
    break
    fi
    done

    theexpectedcrc32=$((0x$(xxd -p -r <<< "${thedrom:26:$thedatalen*2}" | crc32c)))
    (( theexpectedcrc32 != thecrc32 )) && {
    if (( dorepairchecksums )); then
    replacebytes 9 "$(printf "%08x" $theexpectedcrc32 | sed -E 's/(..)(..)(..)(..)/\4\3\2\1/')"
    (( dodump )) && printf "0x09) CRC32: 0x%08x (changed: 0x%08x)\n" $thecrc32 $theexpectedcrc32
    else
    (( dodump )) && printf "0x09) CRC32: 0x%08x (expected: 0x%08x)\n" $thecrc32 $theexpectedcrc32
    fi
    }

    (( dodump )) && {
    printf "0x%02x) End" "$portoffset"
    if [[ -n $(tr -d '0' <<< "${thedrom:$portoffset*2}") ]]; then
    printf " (unexpected bytes: %s)" "${thedrom:$portoffset*2}"
    fi
    echo
    }

    if (( domake )); then

    IFS=$'\n'
    portoffset=22
    for theportstring in $(sort <<< "${Ports[@]}"); do
    portlen=$(( (${#theportstring} - 10) / 2 + 2 ))
    ((portoffset += portlen))
    done

    printf '
    "ThunderboltDROM",
    Buffer (0x%X)
    {
    /* 0x00 */ 0x%s, // CRC8 checksum: 0x%02X
    /* 0x01 */ 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, // Thunderbolt Bus %d, UID: 0x%s
    /* 0x09 */ 0x%s, 0x%s, 0x%s, 0x%s, // CRC32c checksum: 0x%08X
    /* 0x0D */ 0x%s, // Device ROM Revision: %d
    /* 0x0E */ 0x%s, 0x%s, // Length: %d (starting from previous byte)
    /* 0x10 */ 0x%s, 0x%s, // Vendor ID: 0x%X
    /* 0x12 */ 0x%s, 0x%s, // Device ID: 0x%X
    /* 0x14 */ 0x%s, // Device Revision: 0x%X
    /* 0x15 */ 0x%s, // EEPROM Revision: %d
    ' \
    $portoffset \
    "${thedrom:0:2}" $thecrc8 \
    "${thedrom:2:2}" "${thedrom:4:2}" "${thedrom:6:2}" "${thedrom:8:2}" "${thedrom:10:2}" "${thedrom:12:2}" "${thedrom:14:2}" "${thedrom:16:2}" $((0x${thedrom:2:2})) "$theuidnum" \
    "${thedrom:18:2}" "${thedrom:20:2}" "${thedrom:22:2}" "${thedrom:24:2}" $thecrc32 \
    "${thedrom:26:2}" $thedeviceromrevision \
    "${thedrom:28:2}" "${thedrom:30:2}" "$thedatalen" \
    "${thedrom:32:2}" "${thedrom:34:2}" $thevendorid \
    "${thedrom:36:2}" "${thedrom:38:2}" $themodelid \
    "${thedrom:40:2}" $themodelrev \
    "${thedrom:42:2}" $theeepromrev

    IFS=$'\n'
    portoffset=22
    for theportstring in $(sort <<< "${Ports[@]}"); do
    portlen=$(( (${#theportstring} - 10) / 2 + 2 ))
    theportnumber=$((0x${theportstring:2:2} & 0x3f))
    theportdisabled=${theportstring:8:1}
    theporttype=$((1 - ${theportstring:0:1}))
    printf " /* 0x%02X %s %X */ 0x%02x, 0x%02x, %s" $portoffset "$( ((theportdisabled)) && printf "-" || printf " " )" $theportnumber $portlen \
    $(( (theporttype << 7) | (theportdisabled << 6) | theportnumber )) \
    "$( perl -pE 's/(..)/0x\1, /g' <<< "${theportstring:10}" )"
    if (( theporttype == 0 )); then
    local thestring=""
    thestring="$(perl -pE "s/(00)+$//" <<< "${theportstring:10}" | xxd -p -r)"
    if (( theportnumber == 1 )); then
    printf '// Vendor Name: "%s"' "$thestring"
    elif (( theportnumber == 2 )); then
    printf '// Device Name: "%s"' "$thestring"
    else
    printf '// "%s"' "${theportstring:10}"
    fi
    elif (( ${#theportstring} == 12 )); then
    printf '// PCIe xx:%02x.%x' $((0x${theportstring:10} >> 5)) $((0x${theportstring:10} & 0x1f)) #### fix this - function should only be 3 bits - therefore there are 2 unknown bits?
    fi
    printf "\n"
    ((portoffset += portlen))
    done
    printf " },\n"
    fi
    }


    repairchecksums () {
    processdrom dorepairchecksums
    }

    setuid () {
    processdrom dorepairchecksums dosetuid "$1"
    }

    setport () {
    processdrom dorepairchecksums dosetport "$@"
    }

    deleteport () {
    processdrom dorepairchecksums dodeleteport "$1"
    }

    setstring () {
    processdrom dorepairchecksums dosetstring "$@"
    }

    deletestring () {
    processdrom dorepairchecksums dodeletestring "$1"
    }


    #=========================================================================================
    # Files from DROM


    dumpdrom () {
    processdrom dodump
    }

    makedromdsl () {
    processdrom domake
    }


    makedromdslall () {
    local savedrom="$thedrom"
    local thefolderpath="$1"
    local i=""
    for ((i = 1 ; i <= ${#droms[@]} ; i++)); do
    usedromnum "$i"
    makedromdsl > "${thefolderpath:=.}/${thefilenamebase}_makedromdsl.txt"
    done
    usedromstring "$savedrom"
    }


    dumpdromall () {
    local i=""
    for ((i = 1 ; i <= ${#droms[@]} ; i++)); do
    echo "======================================="
    echo "$i)"
    usedromnum "$i"
    dumpdrom
    done
    }


    dumpdromalltofiles () {
    local savedrom="$thedrom"
    local thefolderpath="$1"
    local i=""
    for ((i = 1 ; i <= ${#droms[@]} ; i++)); do
    usedromnum "$i"
    dumpdrom > "${thefolderpath:=.}/${thefilenamebase}_dumpdrom.txt"
    done
    usedromstring "$savedrom"
    }


    #=========================================================================================
    # Use DROM


    cleardrominfo () {
    # clear anything here that is not cleared by processdrom
    :
    }


    usedromstring () {
    cleardrominfo
    (( debug )) && echo ": usedromstring $1" 1>&2
    thedrom="$1"
    processdrom
    }


    usedromnum () {
    cleardrominfo
    thedrom=${droms[arrstart - 1 + $1]}
    processdrom
    thefilenamebase="${thefilenamebase}_$i"
    }


    #=========================================================================================
    # DROM List


    cleardroms () {
    droms=()
    paths=()
    }
    cleardroms


    adddrom () {
    local savedrom="$thedrom"
    local thepath="$1"
    usedromstring "$2"
    (( ignoreuid )) && replacebytes 0 188877665544332211

    local isnew=1
    local i=""
    for ((i = 0 ; i < ${#droms[@]} ; i++)); do
    if [[ $thedrom = "${droms[i+arrstart]}" ]]; then
    if [[ ! "$(printf "_\n%s\n_" "${paths[i+arrstart]}")" =~ $(printf ".*\n%s\n.*" "${thepath}") ]]; then
    paths[i+arrstart]="$(printf "%s\n%s" "${paths[i+arrstart]}" "$thepath")"
    fi
    isnew=0
    break
    fi
    done

    if (( isnew )); then
    (( debug )) && echo ": isnew" 1>&2
    droms+=("$thedrom")
    paths+=("$thepath")
    fi
    usedromstring "$savedrom"
    }


    numstrings=0
    loadstring () {
    local sourcename="$2"
    if [[ -z $sourcename ]]; then
    ((numstrings++))
    sourcename="string:$numstrings"
    fi
    adddrom "$sourcename" "$1"
    }


    loadhexfile () {
    adddrom "$1" "$(xxd -r "$1" | xxd -p -c 99999)"
    }


    loadonedromitem () {
    local dromitem="$1"
    local thepath="${dromitem% = <*}"
    local thedrom="${dromitem##* = <}"
    local thedrom="${thedrom%>}"
    (( debug )) && echo ":" adddrom "${thepath}" "${thedrom}" 1>&2
    adddrom "${thepath}" "${thedrom}"
    }


    loadfwfile () {
    while (( $# )); do
    local thefilename="$1"
    shift
    IFS=$'\n'
    for dromitem in $(
    xxd -p -c 9999999999 "$thefilename" | perl -e '
    while (<>) {
    s/(..)/\1 /g;
    $start = 0;
    foreach my $offset (0, 4096 * 3) {
    $start = hex(substr($_, $offset, 11) =~ s/(..) (..) (..) (..)/$4$3$2$1/r);
    last if ($start != hex("ffffffff"));
    }
    # look for DROM
    while ( /(?{$X=pos()})44 52 4f 4d 20 20 20 20 ff ff ff ff ff ff ff ff ((.. ){1008})(?{$Y=pos()})/g ) {
    $X /= 3;
    $thedrom=$1;
    $thedrom =~ s/(ff )*$//g;
    $thedrom =~ s/ //g;
    $version = "vers_unknown";
    if ($X < hex(4000)) {
    $partition = "missing header";
    } elsif ($X < hex(82000)) {
    $version = "v" . substr($_, (hex(4000) + 10) * 3, 2);
    if ($start == hex(4000)) {
    $partition = "active";
    } elsif ($start == hex(82000)) {
    $partition = "inactive";
    } else {
    $partition = "unknown start";
    }
    } elsif ($X < hex(100000)) {
    $version = "v" . substr($_, (hex(82000) + 10) * 3, 2);
    if ($start == hex(4000)) {
    $partition = "inactive";
    } elsif ($start == hex(82000)) {
    $partition = "active";
    } else {
    $partition = "unknown start";
    }
    } else {
    $partition = "rom too large";
    }
    pos() = $Y;
    $nvmversion = "nvm_unknown";
    # get nvm version from after EE_PCIE
    if ( /(?{$Z=pos()})45 45 5f 50 43 49 45 20 (?:ff ){8}(?:.. )*?(..) (..) (..) (..) (?:.. ){4}(?:ff ){8}(?{$W=pos()})/g ) {
    pos() = $W;
    # need to find out what all the digits are for - I am just guessing here:
    $nvmversion="nvm_v" . (( $2 . $1 . "." . $3 . $4 ) =~ s/0*([^.]*.)\.0*(.+)/\1.\2/r );
    }
    else
    {
    pos() = $Y;
    }
    printf ("%s:%s:%s:%s:0x%x = <%s>\n", "'"${thefilename}"'", $partition, $version, $nvmversion, $X, $thedrom);
    }
    }
    '
    ); do
    (( debug )) && echo ":" loadonedromitem "${dromitem}" 1>&2
    loadonedromitem "${dromitem}"
    done
    done
    }


    loadonedslfile () {
    local thefilename="$1"
    local thesource="$2"
    [[ -z $thesource ]] && thesource="$thefilename"

    IFS=$'\n'
    for dromitem in $(
    #
    # LeadNameChar := [A-Za-z_]
    # DigitChar := [0-9]
    # NameChar := [A-Za-z_0-9]
    # RootChar := \
    # ParentPrefixChar := ^
    # PathSeparatorChar := .
    #
    # // Names and paths
    #
    # NameSeg := [A-Za-z_][A-Za-z_0-9]{0,3}
    #
    # PrefixPath := \^*
    #
    # NamePathTail := ([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #
    # NamePath := (([A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*)|)
    #
    # NonEmptyNamePath := [A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #
    # NameString := ([\\]|\^+|)(([A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*)|)
    # | [A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*
    #
    perl -e '
    use strict;
    my $line3 = "";
    my $line2 = "";
    my $line1 = "";
    my %paths = ( "\\" => 1 );
    sub addline {
    $line3 = $line2;
    $line2 = $line1;
    $line1 = $_[0];
    }
    sub group {
    my $indent = $_[0];
    my $codepath = $_[1];
    my $acpipath = $_[2];
    my $nextacpipath = $acpipath;
    my $nextcodepath = $codepath;
    my $dodump = 0;
    my $buffersize = 0;
    if ( ($line2 . $line1) =~ /"ThunderboltDROM",\s*Buffer\s*\(0x([0-9A-F]+)\)/ ) {
    # print "gotpattern «" . $line2 . "»«" . $line1 . "»\n";
    $dodump = 1;
    $buffersize = hex($1);
    }
    if ( $dodump == 1 ) {
    print "'"$thesource"':" . $acpipath . ":ThunderboltDROM = <";
    }
    while (<>) {
    s/\s*\/\/.*//; # single line // comment
    s/\s*\/\*.*?\*\/\s*//g; # single line /* */ comment
    s/\s*(.*?)\s*$/\1/g; # remove indents and trailing spaces
    if (/^\/\*.*/ .. /^.*?\*\/.*/) { } # multi line comment
    elsif ( /^$/ ) { } # skip blank lines
    elsif ( /^{$/ ) {
    group($indent . "\t", $nextcodepath, $nextacpipath);
    }
    elsif ( /^}/ ) {
    if ( $dodump == 1 ) {
    if ($buffersize < 0) {
    print STDERR "Buffer size is too small\n";
    }
    elsif ($buffersize > 0) {
    print "00"x$buffersize;
    }
    print ">\n";
    $dodump = 0;
    }
    last;
    }
    else {
    addline($_);
    if ( /(External|Device|Field|Method|Name|Scope)\s*\(\s*([\\]|\^+|)(([A-Za-z_][A-Za-z_0-9]{0,3}([.][A-Za-z_][A-Za-z_0-9]{0,3})*)|)/ ) {
    my $command = $1;
    my $prefix = $2;
    my $nameparts = $3;
    if ( $prefix =~ /\\/ ) {
    $nextacpipath = $prefix . $nameparts;
    }
    elsif ( $prefix =~ /\^+/ ) {
    my $pattern = "(.*?)([\\\\.][^.]+){0," . length($prefix) . "}\$";
    $nextacpipath = ($acpipath =~ s/$pattern/\1/r =~ s/^$/\\/r) . "$nameparts";
    }
    elsif ( ($command eq "Scope") && ($nameparts =~ /[^.]+/) ) {
    if ( $paths{$acpipath . "." . $nameparts} ) {
    $nextacpipath = (($acpipath . "." . $nameparts) =~ s/^\\\./\\/r =~ s/([A-Za-z0-9])_+/\1/gr);
    }
    elsif ( ($acpipath . ".") =~ /(.*[.\\]$nameparts)[.].*/ ) {
    $nextacpipath = (($acpipath . ".") =~ s/(.*[.\\]$nameparts)[.].*/\1/r)
    } else {
    $nextacpipath = (($acpipath . "." . $nameparts) =~ s/^\\\./\\/r =~ s/([A-Za-z0-9])_+/\1/gr);
    print STDERR "unknown path " . $nextacpipath . "\n";
    }
    }
    else {
    $nextacpipath = (($acpipath . "." . $nameparts) =~ s/^\\\./\\/r =~ s/([A-Za-z0-9])_+/\1/gr);
    }
    $nextcodepath = (($codepath . "." . $prefix . $nameparts) =~ s/^\\\./\\/r =~ s/([A-Za-z0-9])_+/\1/gr =~ s/\\\\/\\/r);
    if ( !$paths{$nextacpipath} ) {
    $paths{$nextacpipath} = 1;
    }
    }
    else {
    $nextacpipath = $acpipath;
    $nextcodepath = $codepath;
    if ( $dodump == 1 ) {
    my $outline = ($line1 =~ s/0x//gr =~ s/[, ]//gr);
    print $outline;
    $buffersize -= length($outline) / 2;
    }
    }
    }
    }
    }
    group("", "\\", "\\");
    ' < "$thefilename"
    ); do
    (( debug )) && echo ":" loadonedromitem "${dromitem}" 1>&2
    loadonedromitem "${dromitem}"
    done
    }


    loaddslfile () {
    while (( $# )); do
    local thefilename="$1"
    shift
    loadonedslfile "$thefilename"
    done
    }


    loadamlfile () {
    [[ -f $iasl_location ]] || {
    echo "# Update iasl_location" 1>&2
    exit 1
    }

    thedirname=$(mktemp -d /tmp/aml.XXXXXX) || exit 1
    while (( $# )); do
    local thefilename="$1"
    shift
    if "$iasl_location" -p "$thedirname/xxx" "$thefilename" > /dev/null 2> "$thedirname/out.txt"; then
    loadonedslfile "$thedirname/xxx.dsl" "$thefilename"
    else
    cat "$thedirname/out.txt" 1>&2
    fi
    done
    }


    loadoneioregfile () {
    local thefilename="$1"
    local thesource="$2"
    [[ -z $thesource ]] && thesource="$thefilename"

    IFS=$'\n'
    for dromitem in $(
    perl -e '
    $thepath=""; while (<>) {
    if ( /^([ |]*)\+\-o (.+) </ ) { $indent = (length $1) / 2; $name = $2; $thepath =~ s|^((/[^/]*){$indent}).*|$1/$name| }
    if ( /^[ |]*"(ThunderboltDROM)" = <(.*)>/i ) { print "'"${thesource}"'" . ":" . $thepath . "/" . $1 . " = <" . $2 . ">\n" }
    }
    ' < "${thefilename}"
    ); do
    loadonedromitem "${dromitem}"
    done
    }


    loadioregfile () {
    while (( $# )); do
    local thefilename="$1"
    shift
    loadoneioregfile "$thefilename"
    done
    }


    loadioreg () {
    local tmpfilename=""
    tmpfilename=$(mktemp /tmp/local_ioreg.XXXXXX) || exit 1
    ioreg -lw0 > "$tmpfilename"
    loadoneioregfile "$tmpfilename" "ioreg"
    }


    listdroms () {
    local savedrom="$thedrom"
    local i=""
    for ((i = 1 ; i <= ${#droms[@]} ; i++)); do
    usedromnum "$i"

    echo "$i)"
    echo "thedrom=$thedrom"
    echo "sources:"
    echo "${paths[i+arrstart-1]}"
    echo
    done
    usedromstring "$savedrom"
    }


    #=========================================================================================
    # Misc


    extractfwfromexe () {
    while (( $# )); do
    local thefilename="$1"
    shift
    perl -0777 -e '
    while (<>) {
    #printf "file:%s\n", $ARGV;
    @files = ( /\0([^\0]+.bin)(?=\0)/g );
    $filenum = 0;
    while ( /(?{$x=pos()})DROM(?{$y=pos()})/g ) {
    $sb = substr $_, $x - 0x4200 - 4, 4;
    $size = (ord(substr $sb, 3, 1) << 24) | (ord(substr $sb, 2, 1) << 16) | (ord(substr $sb, 1, 1) << 8) | ord (substr $sb, 0, 1);
    $filename = @files[$filenum];
    if ( $filename =~ /^$/ ) { $filename = "fw.bin"; }
    $dir = sprintf "%s/%02d", `dirname "$ARGV"` =~ s/\n$//r, $filenum;
    `mkdir -p "$dir"`;
    open(FH, ">", "$dir/$filename") or die $!;
    print FH substr $_, $x - 0x4200, $size;
    close(FH);
    pos() = $y;
    $filenum++;
    }
    }
    ' "$thefilename"
    done
    }


    #=========================================================================================
    # Help

    dromhelp () {
    echo $'
    Commands:
    Get DROM
    loadioreg
    loadstring hexstring [sourcename]
    loadfwfile filepath...
    loadamlfile filepath...
    loaddslfile filepath...
    loadioregfile filepath...
    loadhexfile filepath # for reverses xxd
    listdroms
    cleardroms
    Use DROM
    usedromnum numberfromlist
    usedromstring lowercasehexstring
    Modify DROM
    repairchecksums
    replacebytes bytepos lowercasehexstring [numbytestoreplace]
    setuid newuid
    setport 0xportnumber portcontents [-]
    deleteport 0xportnumber
    setstring 0xstringnumber stringvalue
    deletestring 0xstringnumber
    Files from DROM
    dumpdrom
    makedromdsl
    makedromdslall [folderpath]
    dumpdromall
    dumpdromalltofiles [folderpath]
    Misc
    extractfwfromexe exepath... # $(grep -l --include \'*.exe\' -R \'DROM\' . )
    Variables
    dodump # Set to 1 to dump the DROM while changes are made to the DROM.
    debug # Set to 1 to output debugging info (uses stderr)
    ignoreuid # Set to 1 to replace all uids with 0x1122334455667788.
    # DROMs with the same contents except UID will be considered identical.
    Help
    dromhelp
    '
    }

    #dromhelp


    #=========================================================================================