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:

  1. It stays frozen on the Xiaomi Mi boot splash screen, or
  2. 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:

  1. 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.
  2. Post-switch_root Persistent kmsg Recorder (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 after initramfs handed 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 UFS cache partition), copied the static busybox binary onto /cache/bb, and launched:
/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 parameter 0x4100c244) so cores only entered per-core wfi (C1) and rail-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 with clk_ignore_unused and pd_ignore_unused on 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:

  1. From t = 0 s to t = 15 s, the kernel boots, mounts /dev/sda21, starts OpenRC, and mdev/udev loads kernel modules for peripheral devices (venus, camss, sound, sensors).
  2. During this window, CPU clusters enter and exit cluster_sleep_0 thousands of times without issue.
  3. At t ≈ 16 s, the last modular consumer finishes probing. Immediately, the Qualcomm NoC interconnect driver (qnoc-sdm845 on 1500000.interconnect and 17900000.interconnect) fires its sync_state() callback—assuming all consumers have now placed their runtime bandwidth votes—and releases the boot-time interconnect bandwidth floors.
  4. Within milliseconds of qnoc-sdm845 dropping its boot votes and the CPU cluster entering cluster_sleep_0 (which switches RPMh to its SLEEP TCS 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 &mdss was disabled in DT, nobody voted on the mdp0-mem (qcom,msm8998-bwmon / mem_noc) interconnect path in Linux.
  • As long as qnoc-sdm845 held its boot-time max bandwidth floor, MDSS's orphaned DMA reads succeeded.
  • The instant qnoc-sdm845 reached sync_state() and released the boot floor—and cluster_sleep_0 allowed 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 downstream dsi_panel_ea8074_notch_fhd_cmd_display.dtsi init/off MCS command sequences (0xF0 password unlock, column/page address 1080×2248, tear-on 0x35, DCS 0x51/0x53 1023-step brightness control).
  • Built panel-samsung-ea8074 (CONFIG_DRM_PANEL_SAMSUNG_EA8074=y) into 7.1.0-rc1-sdm845 and enabled &mdss in sdm845-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@60Hz AMOLED output (card0-DSI-1) + Adreno 630 (freedreno) 3D acceleration.
  • All 3 idle states (wfi, rail-power-collapse, and cluster-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 MCLK clock supply widget only to RX_BIAS (the playback path).
  • When starting a standalone capture stream (MultiMedia2), ADC1/ADC3 and MIC BIAS1/MIC BIAS4 powered up, but nothing in the capture DAPM path pulled in MCLK!

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.

Related Posts