Skip to content

Instantly share code, notes, and snippets.

@al3xtjames
Last active April 20, 2024 02:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save al3xtjames/c27676d7434f4bc21fc91768c62a9740 to your computer and use it in GitHub Desktop.
Save al3xtjames/c27676d7434f4bc21fc91768c62a9740 to your computer and use it in GitHub Desktop.
Thunderbolt NVM firmware
{
description = "Minimal NixOS installation media";
inputs.nixos.url = "nixpkgs/nixos-23.11";
outputs = { self, nixos }: {
nixosConfigurations = {
exampleIso = nixos.lib.nixosSystem {
system = "x86_64-linux";
modules = [
"${nixos}/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix"
({
boot.kernelPatches = [{
patch = ./maple-ridge-nvm-upgrade.patch;
}];
})
];
};
};
};
}
diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
index d8b9c734a..fac16905a 100644
--- a/drivers/thunderbolt/icm.c
+++ b/drivers/thunderbolt/icm.c
@@ -2532,6 +2532,7 @@ struct tb *icm_probe(struct tb_nhi *nhi)
case PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_2C_NHI:
case PCI_DEVICE_ID_INTEL_MAPLE_RIDGE_4C_NHI:
+ icm->can_upgrade_nvm = true;
icm->is_supported = icm_tgl_is_supported;
icm->get_mode = icm_ar_get_mode;
icm->driver_ready = icm_tr_driver_ready;
#!/usr/bin/env python3
import argparse
import struct
from pathlib import Path
# https://github.com/torvalds/linux/blob/13a2e429f644691fca70049ea1c75f135957c788/drivers/thunderbolt/nvm.c#L19
INTEL_NVM_DEVID = 0x05
INTEL_NVM_VERSION = 0x08
INTEL_NVM_CSS = 0x10
INTEL_NVM_FLASH_SIZE = 0x45
# https://github.com/torvalds/linux/blob/13a2e429f644691fca70049ea1c75f135957c788/drivers/thunderbolt/nvm.c#L56
def strip_nvm(nvm: bytes) -> bytes:
nvm_offset = struct.unpack_from("<I", nvm)[0]
device_id = struct.unpack_from("<H", nvm, nvm_offset + INTEL_NVM_DEVID)[0]
version = struct.unpack_from("<I", nvm, nvm_offset + INTEL_NVM_VERSION)[0]
major_version = (version >> 16) & 0xFF
minor_version = (version >> 8) & 0xFF
nvm_size = struct.unpack_from("<I", nvm, nvm_offset + INTEL_NVM_FLASH_SIZE)[0]
nvm_size = (1024 * 1024 << (nvm_size & 7)) // 8
nvm_size = (nvm_size - 16 * 1024) // 2
print(f"NVM offset: 0x{nvm_offset:X}")
print(f"Device ID: 0x{device_id:X}")
print(f"NVM version: {major_version:X}.{minor_version:X}")
print(f"NVM size: 0x{nvm_size:X}")
return nvm[0 : nvm_offset + nvm_size]
def main() -> None:
parser = argparse.ArgumentParser(prog="nvm-strip")
parser.add_argument("input", type=Path)
parser.add_argument("output", type=Path)
args = parser.parse_args()
with open(args.input, "rb") as fp:
nvm = fp.read()
stripped_nvm = strip_nvm(nvm)
with open(args.output, "wb") as fp:
fp.write(stripped_nvm)
main()

Intel Thunderbolt NVM Firmware

Flash layout

Header

  • u32 (little endian) at 0x0 contains pointer to low region (FARB0)
  • u32 at 0x1000 contains pointer to high region (FARB1)
  • 0x0 or 0xFFFFFFFF indicates that the region is inactive

Digital section (starting at FARB0)

Update process

Linux

  • Linux exposes a sysfs interface to perform NVM updates
  • For some reason update support was never enabled on Maple Ridge
  • Can be used to downgrade NVM firmware
    • 38 → 31 worked for me on JHL8540 (on ProArt Z790)

UEFI (ASUS)

Other references

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