Bringing Up Mainline Linux 7.1 on the Xiaomi Mi 8 (Snapdragon 845) Without UART
Over the past few days I brought up Mainline Linux 7.1.0-rc1 (linux-postmarketos-qcom-sdm845) and postmarketOS edge on a Xiaomi Mi 8 (dipper) (Qualcomm Snapdragon 845, 6 GB LPDDR4X RAM, 128 GB UFS 2.1).
Today, the phone boots into postmarketOS from internal UFS with all 8 Kryo 385 cores (qcom-cpufreq-hw + deep cluster-sleep-0 idle), a working 1080×2248@60Hz Samsung EA8074 AMOLED DRM/KMS display, Adreno 630 (freedreno) 3D acceleration, ST FTS5 10-finger multitouch, TI TAS2557 + WCD9340 audio (bottom speaker, earpiece, and both microphones), Bluetooth (WCN3990), and an OpenRC charge-hysteresis daemon keeping the battery between 60% and 80% on permanent USB power.
Getting there required solving three subtle hardware/firmware interactions without a hardware UART console—using only crash-time memory inspection, an initramfs kmsg logger that survives switch_root, and systematic Device Tree bisection.
1. The Starting Point & Debugging Without UART
When a mainline kernel panics or locks the bus before the USB controller (dwc3-qcom at 0xa600000) and AMOLED panel come up, a phone without UART gives you only two visible states:
- It stays frozen on the Xiaomi Mi boot splash screen, or
- It resets back into the bootloader (
fastboot) or Qualcomm CrashMode.
Even worse, on the Mi 8 (dipper), the stock Xiaomi bootloader zeros RAM on warm reboot, which wipes standard pstore / ramoops buffers at 0x9f000000 before a recovery kernel can read them.
To make early and mid-boot crashes observable without soldering to tiny internal pads, I built two mechanisms:
- Pre-USB Bisection via
initramfs: Fastboot-booting test Device Trees (fastboot boot test.img) while toggling a single node at a time (status = "disabled"vs"okay"). If USB CDC NCM (172.16.42.1) enumerates within 8 seconds, the kernel survived early init. - Post-
switch_rootPersistentkmsgRecorder (99-mi8-kmsglog.sh): Once UFS (ufshcd-qcom) came online, I needed to capture the exact final kernel lines before hard SoC bus hangs that occurred afterinitramfshanded control to OpenRC on the root filesystem.- An initramfs background process gets killed or loses its binary path during
switch_root. - To survive
switch_root, my hook formatted/dev/sda17(the 256 MB UFScachepartition), copied the staticbusyboxbinary onto/cache/bb, and launched:
- An initramfs background process gets killed or loses its binary path during
/cache/bb sh -c '
exec </dev/null >/dev/null 2>&1
/cache/bb cat /dev/kmsg > /cache/kmsg.log &
while true; do
/cache/bb date +%s > /cache/heartbeat
/cache/bb sync
/cache/bb sleep 1
done
' &
Because /cache stayed mounted with a 1-second sync loop, even a total SoC lockup left the final kernel trace intact on /dev/sda17 for inspection on the next boot.
2. Trap #1: TrustZone SPI GPIO Fault Before USB
The first attempt at booting an SDM845 Device Tree on dipper hung immediately on the Mi splash screen—before USB, before UFS, and before the framebuffer initialized.
On Qualcomm SDM845 devices, the TLMM pin controller (pinctrl-sdm845 at 0x03400000) exposes 150 GPIOs. However, Xiaomi's TrustZone (TZ) firmware exclusively locks the SPI controllers connected to the fingerprint sensor and embedded Secure Element (eSE) on GPIOs 0..3 and 81..84.
When Linux's pinctrl-msm driver registers the GPIO chip, it iterates over all unreserved pins to read their direction and configuration registers. Touching GPIO 0 or GPIO 81 triggers an immediate TrustZone XPU access violation, halting the CPU before the kernel can bring up any I/O.
Adding the reservation to &tlmm immediately got the kernel past early init and brought up USB NCM and all 6 UFS LUNs (sda–sdf):
&tlmm {
gpio-reserved-ranges = <0 4>, <81 4>;
};
3. Trap #2: The 16-Second cluster-sleep-0 Hang (Orphaned Bootloader MDSS Splash)
With USB and UFS working on a minimal headless Device Tree (&mdss disabled), I ran into the strangest bug of the entire port:
- If I disabled the CPU cluster idle state (
cluster_sleep_0, PSCI suspend parameter0x4100c244) so cores only entered per-corewfi(C1) andrail-power-collapse(C2), the phone stayed up indefinitely. - The moment I re-enabled
cluster_sleep_0, the phone hard-hung ~16 seconds into every single boot—even withclk_ignore_unusedandpd_ignore_unusedon the kernel command line.
Catching the Culprit in /cache/kmsg.log
Using the persistent /cache/kmsg.log recorder with initcall_debug and driver probe logging, I watched what happened right at the 16-second mark:
- From
t = 0 stot = 15 s, the kernel boots, mounts/dev/sda21, starts OpenRC, andmdev/udevloads kernel modules for peripheral devices (venus,camss, sound, sensors). - During this window, CPU clusters enter and exit
cluster_sleep_0thousands of times without issue. - At
t ≈ 16 s, the last modular consumer finishes probing. Immediately, the Qualcomm NoC interconnect driver (qnoc-sdm845on1500000.interconnectand17900000.interconnect) fires itssync_state()callback—assuming all consumers have now placed their runtime bandwidth votes—and releases the boot-time interconnect bandwidth floors. - Within milliseconds of
qnoc-sdm845dropping its boot votes and the CPU cluster enteringcluster_sleep_0(which switches RPMh to itsSLEEPTCS set), the entire SoC freezes hard.
Why Dropping Interconnect Boot Votes Froze a "Headless" Kernel
Why would dropping unused interconnect votes freeze the SoC if clk_ignore_unused and pd_ignore_unused were already set?
Because of the Xiaomi bootloader's continuous splash screen (cont_splash)!
When XBL/ABL turns on the AMOLED panel to show the "Mi" boot logo, it configures the MDSS Display Processing Unit (ae01000.display-controller) to continuously DMA-fetch the splash framebuffer from DDR (0x9d400000–0x9f800000) and stream it over DSI (ae94000.dsi).
Because my initial Device Tree had &mdss { status = "disabled"; }, no Linux driver ever took ownership of MDSS:
- MDSS stayed powered on and kept issuing DDR read bursts for the splash image.
- Because
&mdsswas disabled in DT, nobody voted on themdp0-mem(qcom,msm8998-bwmon/mem_noc) interconnect path in Linux. - As long as
qnoc-sdm845held its boot-time max bandwidth floor, MDSS's orphaned DMA reads succeeded. - The instant
qnoc-sdm845reachedsync_state()and released the boot floor—andcluster_sleep_0allowed the RPMh sleep set to engage—MDSS issued a DMA read across an unvoted memory interconnect path and deadlocked the SoC bus.
Proving and Fixing It Properly
First, I confirmed the hypothesis: simply keeping a single interconnect consumer unprobed (or adding the icc_bwmon node) prevented sync_state() from zeroing the path and stopped the hang.
Then I implemented the proper architectural fix: instead of leaving MDSS orphaned on the bootloader splash, I brought up the full DRM/KMS display pipeline (&mdss, &mdss_mdp, &mdss_dsi0):
- Generated a mainline DRM panel driver (
drivers/gpu/drm/panel/panel-samsung-ea8074.c) from the downstreamdsi_panel_ea8074_notch_fhd_cmd_display.dtsiinit/off MCS command sequences (0xF0password unlock, column/page address1080×2248, tear-on0x35, DCS0x51/0x531023-step brightness control). - Built
panel-samsung-ea8074(CONFIG_DRM_PANEL_SAMSUNG_EA8074=y) into7.1.0-rc1-sdm845and enabled&mdssinsdm845-xiaomi-dipper.dts.
With msm_dpu owning MDSS, the kernel holds proper mdp0-mem interconnect votes whenever the display is active and cleanly shuts down the DPU/DSI DMA engines when the display blanks (echo 1 > /sys/class/graphics/fb0/blank). Result:
- Native
1080×2248@60HzAMOLED output (card0-DSI-1) + Adreno 630 (freedreno) 3D acceleration. - All 3 idle states (
wfi,rail-power-collapse, andcluster-sleep-0) and full system suspend (s2idle) run rock-solid with zero interconnect hacks.
4. Trap #3: ST FTS5 Touch Regulator (vdd_l28) & WCD9340 Microphone MCLK
Two peripheral quirks rounded out the bring-up:
ST FTS5 Touchscreen (st,stmfts5 @ i2c14 0x49)
The Mi 8 uses an STMicroelectronics FingerTip S (FTS5, chip ID 0x54 0x36 rev 0x02) touchscreen controller powered by vddio (vreg_l14a_1p8) and a 3.0 V analog supply (avdd on vreg_l28a_3p0).
In my first attempt, defining vdd_l28 in the apps_rsc RPMh regulator block with fixed 3.3 V, regulator-boot-on, and REGULATOR_MODE_HPM crashed RPMh during early regulator registration before USB came up. Defining vdd_l28 with its hardware range (2856000–3312000 µV) and letting the stmfts driver enable it lazily at module probe time brought up all 10 multitouch slots cleanly (/dev/input/event3).
WCD9340 Audio Capture Returning Silence Unless Playback Was Active
Audio on the Mi 8 combines a TI TAS2557 smart speaker amplifier (i2c5 @ 0x4c + Primary MI2S) for the bottom speaker with a Qualcomm WCD9340 (pahu) SLIMbus codec for the top earpiece and microphones (AMIC1 bottom mic, AMIC3 top mic).
After extracting the device firmware (adsp.mbn squashed via pil-squash.py, tas2557_uCDSP.bin, and crbtfw21.tlv), speaker and earpiece playback worked immediately via Q6AFE/Q6ASM. However, recording from the microphones (arecord -D hw:0,1) returned pure digital zeros unless speaker playback was running at the exact same moment!
Tracing the ASoC DAPM graph in sound/soc/qcom/sdm845.c explained why:
- The machine driver wires the WCD9340
MCLKclock supply widget only toRX_BIAS(the playback path). - When starting a standalone capture stream (
MultiMedia2),ADC1/ADC3andMIC BIAS1/MIC BIAS4powered up, but nothing in the capture DAPM path pulled inMCLK!
Adding explicit ADC -> MCLK edges in the Device Tree's sound node solved it cleanly without touching kernel C code:
audio-routing =
"RX_BIAS", "MCLK",
"ADC1", "MCLK",
"ADC2", "MCLK",
"ADC3", "MCLK",
"AMIC1", "MIC BIAS4",
"AMIC2", "MIC BIAS2",
"AMIC3", "MIC BIAS1";
5. Summary of Results
| Feature | Status | Details |
|---|---|---|
| Kernel | 7.1.0-rc1-sdm845 |
Mainline sdm845 kernel + custom sdm845-xiaomi-dipper.dtb |
| Display & GPU | Working | Samsung EA8074 1080×2248 AMOLED (panel-samsung-ea8074) + Adreno 630 (freedreno) |
| Touchscreen | Working | ST FTS5 (st,stmfts5) 10-point capacitive touch + powerkey-display blank/unblank |
| Audio | Working | Bottom speaker (TAS2557), earpiece (WCD9340 EAR), dual mics (AMIC1/AMIC3) + ALSA UCM |
| Power & Idle | Working | cluster-sleep-0 + s2idle (~40 mA suspended, ~120 mA screen off) + 60–80% charge-limit |
The full Device Tree, kernel patch (panel-samsung-ea8074.c + stmfts compatible), ALSA UCM profiles, and helper tools are documented on the Xiaomi Mi 8 (dipper) Device Page and in the xiaomi-mi8-dipper-linux repository.