-
-
Save joevt/4f6d4d97b560efab9603ac509bf00122 to your computer and use it in GitHub Desktop.
#! /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 | |
#========================================================================================= |
joevt
commented
Jun 30, 2020
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?
@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
April 2, 2024
- Added some Thunderbolt 4 / USB4 support.
- Try to interpret more info for each port.