Skip to content

Instantly share code, notes, and snippets.

@joevt
Last active April 7, 2024 09:31
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.
A set of functions to examine and modify Thunderbolt DROMs
#! /bin/zsh
# ThunderboltUtil.sh v1.6
# by joevt Apr 2, 2024
#=========================================================================================
#
#
# 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
}
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
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
if [[ $param != '-' ]]; then
eval "local $param=1"
fi
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
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)"
(( 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 && !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
echo
}
thecrc32=$((0x${thedrom:24:2}${thedrom:22:2}${thedrom:20:2}${thedrom:18:2}))
thedatalen=$((0x${thedrom:30:2}${thedrom:28:2} & 0x3ff))
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
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) TBT3-Vendor ID: 0x%X\n" $thevendorid
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) TBT3-Model Revision: 0x%X\n" $themodelrev
theeepromrev=$((0x${thedrom:42:2}))
(( dodump )) && printf "0x15) TBT3-NVM Revision: %d\n" $theeepromrev
startentryoffset=22
fi
while ((1)); do
local entryoffset=$startentryoffset
((thedataend = 13 + thedatalen))
# Keep a list of ports and strings and generic data
Ports=()
while (( entryoffset <= thedataend )); do
[[ -z ${thedrom:$entryoffset*2:2} ]] && break
local entrylen=$((0x${thedrom:$entryoffset*2:2}))
if ((entrylen < 2)); then
(( dodump && (entryoffset != thedataend) )) && echo "Unexpected error: port length is < 2: $entryoffset + $entrylen <= $thedataend"
break
fi
(( entryoffset == thedataend )) && {
(( dodump )) && echo " ============== (following bytes are unexpected)"
((thedataend += entrylen))
}
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: " $entryoffset "$( ((theadapterdisabled)) && printf "-" || printf " ")" $theadapternumber
if (( theentrytype || theadapternumber < 1 || theadapternumber > 2 )); then
printf "%s" "$theadapterbytes"
else
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 (( theentrytype )); then
if (( theadapterdisabled && entrylen != 2 )); then
printf " (unexpectedly disabled)"
fi
else
if (( theadapterdisabled )); then
printf " (unexpectedly disabled)"
fi
fi
fi
if ((theadapternumber <= 0)); then
(( dodump )) && printf " (expected port number > 0)"
elif ((theadapternumber == (theentrytype ? doportnumber : dostringnumber))); then
((dodump)) && {
((dosetport || dosetstring)) && printf " (replaced)" || printf " (removed)"
}
else
Ports+=("$(printf "%d %02x %02x %s %s" $((1 - theentrytype)) $theadapternumber $entryoffset $theadapterdisabled "$theadapterbytes")")
fi
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 (( entrylen != actualentrylen )); then
(( dodump )) && printf " (unexpected error: too few remaining bytes: %d > %d)" $entrylen $actualentrylen
fi
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
(( 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 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 $startentryoffset "$allportbytes" ${#thedrom}
((thedatalen = ${#allportbytes}/2 + $startentryoffset - 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}" | CRC32b)))
(( 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" "$entryoffset"
if [[ -n $(tr -d '0' <<< "${thedrom:$entryoffset*2}") ]]; then
printf " (unexpected bytes: %s)" "${thedrom:$entryoffset*2}"
fi
echo
}
if (( domake )); then
IFS=$'\n'
entryoffset=$startentryoffset
for theentrystring in $(sort <<< "${Ports[*]}"); do
entrylen=$(( (${#theentrystring} - 10) / 2 + 2 ))
((entryoffset += entrylen))
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
' \
$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}" $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'
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)+$//" <<< "${theentrystring:10}" | xxd -p -r)"
if (( theadapternumber == 1 )); then
printf '// Vendor Name: "%s"' "$thestring"
elif (( theadapternumber == 2 )); then
printf '// Device Name: "%s"' "$thestring"
else
printf '// "%s"' "${theentrystring:10}"
fi
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"
((entryoffset += entrylen))
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" $(xxd -p -r <<< "$1" | xxd -p -c 99999)
}
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(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);
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"
#
# 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 $(
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 =~ /"ThunderboltDROM",\s*$/) {
$dodump = 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 ) {
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; # \ 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";
}
else {
# 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;
}
}
}
# 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;
}
}
else {
$nextacpipath = $acpipath;
$nextcodepath = $codepath;
if ( $dodump == 1 ) {
my $outline = ($line1 =~ s/0x//gr =~ s/[, ]//gr);
print $outline;
$buffersize -= length($outline) / 2;
}
}
}
}
}
group("", "\\", "\\");
# print "$_\n" for keys %paths;
' < "$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 '
$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| }
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
}
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
#=========================================================================================
@profzei
Copy link

profzei commented Jan 4, 2022

Hi,
I'm trying using your code for getting Thunderbolt DROM from JHL6240 for Huawei Matebook X pro 2018
Link TBT

but when I run on Terminal the following commands:

source ThunderboltUtils.sh
debug=1
dodump=1
loadfwfile "[0]"
listdroms

(where [0] and CERTIFICATE are the content of inner .exe file and CERTIFICATE contains references to Symantec...)
this is what I get...

: usedromstring 
: processdrom 
0x00) CRC8: 0x00 (expected: 0xdb)
0x01) UID: 0x
0x0d) Device ROM Revision: 0
0x10) Vendor ID: 0x0
0x12) Device ID: 0x0
0x14) Device Revision: 0x0
0x15) EEPROM Revision: 0
0x16) End

i.e. an error from CRC8?

@joevt
Copy link
Author

joevt commented Jan 5, 2022

@profzei [0] is definitely not a Thunderbolt firmware file. Isn't this MateBook_X_Pro_TBT_17.3.73.6.exe file just an installer for the Intel Thunderbolt drivers?

All those zeros (especially the empty UID) means nothing was read from the file.

Maybe try using Linux to extract the firmware. Then you can dump the contents of the DROM from that.
Also, you can use linux to read from the registers of the drom to see what adapters are actually used
https://www.tonymacx86.com/threads/z490-z590-will-z590-ever-have-macos-support.308084/post-2235429

@joevt
Copy link
Author

joevt commented Apr 4, 2024

April 2, 2024

  • Added some Thunderbolt 4 / USB4 support.
  • Try to interpret more info for each port.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment