Forge bootable, hands-off Ubuntu installers for T2 MacBooks — Wi-Fi, Touch Bar and i9 unthrottling included.
One script takes the excellent t2linux patched Ubuntu ISO and turns it into an installer that does almost everything by itself. You answer exactly two things — who are you and which disk — and boot into an Ubuntu where the internal keyboard, trackpad, Wi-Fi, Bluetooth, audio and Touch Bar all work.
./build-t2-iso.sh
# write the resulting ISO to a USB stick, boot the Mac holding Option/Alt| Step | How |
|---|---|
| Wi-Fi/Bluetooth firmware | captured automatically from the macOS install on the internal disk during installation (get-apple-firmware get_from_macos, using the apfs driver the t2linux ISO already ships. No prior macOS-side step needed.) |
| Kernel parameters | intel_iommu=on iommu=pt pm_async=off |
| T2 VHCI at boot | oneshot unit: modprobe t2bce_vhci || modprobe apple_bce — tries the new MFD stack, falls back to the legacy driver, never loads both; apple-bce modalias autoload is blacklisted per upstream's recommendation — see the kernel gotcha |
| Touch Bar (Esc / F-keys) | tiny-dfr .deb bundled in the ISO, installed offline |
| SSH | openssh-server installed offline from the ISO pool |
| i9 anti-throttling | PL1 power cap service, installed only when the target is a MacBookPro15,1 (see measurements) |
| Identity & partitioning | interactive on purpose — subiquity asks for user/password/hostname, and you pick the target disk (the one irreversible step) |
The whole install is offline: no network needed until you log in, and by then Wi-Fi already works.
A post-install log with per-step ok/fail lands in
/var/log/t2-autoinstall.log on the installed system.
./build-t2-iso.sh [options]
--base-iso PATH already-downloaded t2linux ISO (otherwise fetched from
GitHub and sha256-verified)
--firmware PATH firmware.tar for the target Mac. Only needed if the Mac
no longer has macOS on the internal disk — otherwise the
installer captures the firmware by itself.
--disk-serial SER udev ID_SERIAL of the target disk, as a partitioning
safety net: if the storage step ever ran non-interactively,
subiquity could only touch this disk, and aborts if it is
absent. NOTE: use the serial Linux sees (udevadm info),
NOT the one macOS shows — they differ on USB enclosures.
--release TAG t2linux/T2-Ubuntu release [v7.0.9-1]
--flavor F flavor-version [ubuntu-26.04]
--out PATH output ISORequirements: bash, curl, xorriso, shasum/sha256sum. Runs on macOS
or Linux.
On the target Mac (once): boot into Recovery (Cmd+R) → Startup Security Utility → Secure Boot to No Security, allow external boot.
The internal keyboard/trackpad go through the T2's Buffer Copy Engine, whose
VHCI module does not autoload — and its name changes between kernels
(verified with modinfo on both):
| Kernel | VHCI module | the other name |
|---|---|---|
stock Ubuntu + DKMS (7.0.0-x-generic, what the ISO installs) |
apple_bce |
t2bce_* does not exist |
t2linux patched (7.1.5-1-t2-*, what apt full-upgrade brings in) |
t2bce_vhci (in-tree) |
apple_bce does not exist |
If your config names only one of them, a kernel upgrade silently boots you
into a machine with no internal keyboard or trackpad. And naming both in
modules-load.d has the opposite hazard: should the two generations ever
coexist for one kernel (e.g. DKMS leftovers of apple-bce built against the t2
kernel), both would get loaded and race for the same PCI device.
So the installer does neither. It ships a oneshot unit that runs
modprobe t2bce_vhci || modprobe apple_bce — new stack first, legacy driver
as fallback, never both — plus a blacklist apple_bce entry, which blocks
modalias autoload only (the unit's explicit modprobe still works on kernels
without t2bce). That blacklist is exactly what upstream
(deqrocks/t2bce) recommends.
For the record, loading t2bce_vhci does not bring it up "alone": modprobe
resolves t2bce_core + t2bce_dma through module dependencies, and
t2bce_audio binds by its own PCI alias (106b:1803), so the whole MFD stack
comes up (verified in the boot journal: dma → core → vhci in dependency
order, audio bound). An explicit load is required in the first place because
t2bce_vhci ships no modalias — and, curiously, t2bce_core does carry a
PCI alias (106b:1801) yet udev coldplug did not autoload it on this
machine; without an explicit entry the boot comes up with no input devices.
For a disk in a USB enclosure, macOS shows the enclosure's serial while udev's
ID_SERIAL (what subiquity's storage match: uses) is the inner disk's. A
match: serial: taken from macOS will never match — the install aborts (which
is at least fail-safe). Take the serial from
udevadm info --query=property --name=/dev/sdX | grep ID_SERIAL on Linux.
The 2018 15" MacBook Pro's i9-8950HK is famous for throttling. Under Linux the firmware defaults make it worse than macOS: PL1 comes up as 100 W with a ~28 s averaging window — effectively unlimited — so the only governor left is temperature, and the SMC's BD PROCHOT (asserted by the VRM, not the die).
Measured on a MacBookPro15,1 (i9-8950HK, 32 GB), Ubuntu 26.04, t2linux
kernel 7.1.5-1-t2-resolute. Method: stress-ng bogo-ops/s (delivered work,
not MHz), real power via RAPL energy_uj deltas, throttle attribution via the
perf-limit-reasons MSR (0x64F), 3 reps × 60 s per config, cooldown to 62 °C
between runs, thermald stopped during the sweep. Sweep script and raw CSVs
in bench/.
Measured transient at firmware defaults (0.5 s RAPL energy sampling, cold
start at 53 °C, 12-thread matrixprod):
0.5 s 88.8 W 100 C <- ~89 W burst, Tjmax in under a second
1-9 s 80->57 W 100 C <- ~9 s pinned at Tjmax, thermal-throttling
9.5 s+ 11<->73 W 65<->100 <- the BD PROCHOT sawtooth begins
Note what this means: the initial burst is ~89 W (not 100), and the package goes from 53 °C to Tjmax in under a second — so PL1's ~28 s firmware window never gets a say in any of it; the thermal limit acts first. (An earlier revision of this text claimed the window "lets the CPU pull 100 W for several seconds"; review feedback correctly called that unsupported, and the measurement above replaces it.) After ~9 s of riding Tjmax, BD PROCHOT pins all cores at 799 MHz while the die cools to ~73 °C — the assertion comes from the VRM, not the CPU sensor. The result oscillates between 3500 and 800 MHz.
| Config | bogo-ops/s | vs base | pkg W | °C | PROCHOT time |
|---|---|---|---|---|---|
| firmware defaults (PL1 100 W) | 5903 ±94 | — | 47.0 | 95 | 19 % |
| BD PROCHOT disabled only | 6745 ±115 | +14.3 % | 50.3 | 100 | 0 % |
| PL1 = 55 W | 6279 ±41 | +6.4 % | 49.0 | 96 | 13 % |
| PL1 = 45 W | 6485 ±13 | +9.8 % | 45.4 | 93 | 0 % |
| PL1 = 40 W | 6087 ±100 | +3.1 % | 40.3 | 93 | 0 % |
| PL1 = 35 W | 5865 ±44 | −0.6 % | 35.3 | 91 | 0 % |
| PL1 45 W + fans max | 6499 | +10.1 % | 45.4 | 92 | 0 % |
| PL1 45 W + BD PROCHOT off | 6518 | +10.4 % | 45.2 | 97 | 0 % |
With int64: +7.4 %. With all (mixed): +8.4 %.
Fan-curve check (added after review): all runs above use the stock T2/SMC fan curve. To test whether a custom/aggressive fan curve changes the baseline pathology, the firmware-defaults case was re-run with both fans pinned at max RPM before the load starts (stronger than any curve response): 5870/6073/6129 bogo-ops/s (~6024 ±134, ~+2 % vs auto, within noise) with PROCHOT still firing 16-38 % of the time. Even maximal airflow does not prevent the VRM-side PROCHOT sawtooth, and the initial excursion (Tjmax in under a second from a 53 °C start) is faster than any fan can spin up. Fan control changes the noise, not the pattern.
The dGPU (AMD Baffin, 40 W) is a separate package but shares heatpipes and
fans with the CPU — GPU load alone drags the idle CPU package to 80–85 °C.
Loaded with glmark2-drm at forced-high DPM plus stress-ng:
| Config | bogo-ops/s | CPU | GPU | PROCHOT |
|---|---|---|---|---|
| GPU only | — | 11.8 W / 80–85 °C | 25 W / 87 °C | 0 % |
| CPU only, PL1 45 W | 5941–6457 | 40–45 W / 94–98 °C | idle | 0–7 % |
| CPU+GPU, PL1 45 W | 6102–6110 | 41 W / 99 °C | 24 W / 87 °C | 0 % |
| CPU+GPU, no cap | 5558–5566 | 39 W / 95 °C | 24 W / 87 °C | 20–25 % |
- PL1 = 45 W is the sweet spot on this model: +7–10 % sustained across load types, PROCHOT eliminated, cooler, and far more predictable (σ 13–29 vs 94–115 bogo-ops/s).
- The baseline only draws ~47 W — the firmware's 100 W PL1 never was the active limit. The gain comes from changing the throttling regime (eliminating PROCHOT collapses), not from "limiting power".
- Disabling BD PROCHOT alone (the approach linked from the t2linux wiki via
turnoff-BD-PROCHOT) is the fastest (+14.3 %) but runs pinned at 100 °C, draws more, and removes the VRM's only protection. - Combining the cap with BD PROCHOT off adds nothing over the cap alone — at 45 W PROCHOT no longer fires, so the bit is redundant. Keeping the protection costs ~0.6 % (within noise).
- Forcing fans to max adds ~8 °C of headroom and no performance. The
applesmcfan interface works, by the way — the attributes live on the ACPI device node (APP0001:00/fan*_{input,output,manual}), not underhwmon/. - Under combined CPU+GPU load the cap matters more: without it PROCHOT returns (20–25 %) and delivered CPU work drops ~9 % below the capped case.
Caveats: one machine, one CPU (MacBookPro15,1 with the i9-8950HK). The
DMI gate also matches the i7-8750H/i7-8850H variants of the same chassis —
all three options are 45 W TDP parts, so the cap equals design TDP for each,
but it was measured only on the i9. The sweep was run with thermald
stopped to isolate variables; the shipped configuration (cap + thermald
running, ordered so thermald adopts the cap as its baseline) was spot-checked
under load and across reboots, but has not had the full 39-run sweep.
Single-thread/interactive loads draw ~32 W package on this chip — under the
45 W cap — so bursty desktop use never engages it, and PL2 (125 W) is left
untouched for sub-second transients. Replications on other T2 models
welcome — the sweep is one command: sudo python3 bench/bench-throttling.py.
build-t2-iso.sh the ISO builder
autoinstall/late.sh post-install, runs from late-commands
powercap/ PL1 cap: defaults, script, systemd unit
t2-postinstall.sh standalone post-install (plan B / existing installs)
bench/ measurement scripts + raw CSV data
- t2linux — the kernel patches, ISOs, wiki and drivers that make any of this possible.
- AdityaGarg8/t2-ubuntu-repo
— the apt repo shipping
tiny-dfr,apple-firmware-scriptand the T2 audio config. - The firmware extraction script is by Aditya Garg, Orlando Chamberlain and Sharpened Blade, based on work by the Asahi Linux contributors.
Apple firmware is proprietary: it is extracted from your Mac's macOS at install time and is never distributed by this repo or its ISOs.