方向

目標、學習階梯、遠期的 m1n1 雙機 trace。不常看,但走偏的時候回來對一下。

§1大方向

你手上是 MacBook Pro 14" M1 Pro(MacBookPro18,3,SoC 代號 t600x),跑 omadora(Fedora Asahi Remix + Hyprland,與 macOS 26 雙系統), 外接 Apple Studio Display。好消息是 Studio Display 走的是 USB-C DisplayPort Alt Mode,不需要 Thunderbolt tunneling —— 那條路才是真的無解。

這條路上原本疊了兩個斷點,但其中一個已經沒了。 #601 在 2026-09-07 以 completed 關閉 —— 回報者用的是跟你一模一樣的 MacBookPro18,3, 結論是那不是 bug,是 stable kernel 根本沒有 DP alt mode 這條路, 換到 fairydust kernel 之後螢幕就亮了。

所以整個專案的形狀比原本單純:先驗證這個結論在你的機器上也成立(一個下午的事), 剩下的就只有第二個斷點 —— Studio Display 專屬的 DCP 交握。

2026-09-23 修訂。本頁最初的版本把 #601 當成「t600x 專屬的 atcphy bug」, 整條學習路線都建立在「先解 Bug 1 才看得到 Bug 2」之上。查證 GitHub 現況後這個前提不成立了: #601 已結案、fairydust 的 t600x device tree 早就開好了、#579 也已經有四則討論 (含一位 Asahi 開發者提出、至今沒人做的具體實驗)。整份文件都依此改寫過。

圖裡可以切深/淺色、縮放;左上角「Guided Views」有四個視角(訊號主路徑、斷點 1、斷點 2、你能改的範圍),點了會把不相關的部分淡掉。

先分清楚:哪些東西在 Mac 裡,哪些在 Studio Display 裡

這件事最容易搞混,因為兩邊各有一顆 Apple 處理器:

  • DCP(顯示協處理器)在 Mac 裡 —— 負責產生畫面、跟螢幕交握
  • A13 Bionic 在 Studio Display 裡 —— 那是一整顆 iPhone 等級的 SoC,跑 Apple 自家韌體,管面板、USB hub、攝影機、喇叭

斷點 2 就是這兩顆之間的問題:Mac 的 DCP 回報「對面那顆還在睡」(INACTIVE_SINK_DETECTED), 而 Linux driver 不知道該怎麼回應。所以要「叫醒」的是螢幕裡那顆,不是 Mac 裡那顆。

圖 6 · Mac 與 Studio Display 各有什麼在新分頁開啟,可全螢幕操作 ↗

左邊虛線框內全在 Mac 裡,右邊虛線框內全在螢幕裡,中間那條就是唯一的實體連線。

順帶兩個容易忽略的事實:那條 USB-C 上同時跑影像、USB 資料和電力, 而且電力方向是反的 —— Studio Display 供電給 Mac,最高 96W。 #601 的失敗點正是在「USB3 + DP 混合模式」的切換上,跟線上同時有 USB 資料這件事直接相關。

兩個斷點,現在只剩一個

斷點 1 · Type-C PHY斷點 2 · DCP 交握
症狀atcphy_mux_set WARN
Failed to lock pipehandler
acking unhandled call 20
link 立刻被拆掉
追蹤#601 —— closed(2026-09-07)#579 —— open,4 則討論
現況✅ 不是 bug,是功能缺口。stable kernel 沒有 DP alt mode 的路,fairydust 有。 回報者在 MacBookPro18,3 上換到 fairydust 後 USB-C 螢幕直接點亮 ⬜ 沒人知道正確答案。DCP 送出 call 20,driver 盲目 ack,firmware 隨即放棄
屬於你的 kernel 版本(不是你的機器)你的 螢幕(Studio Display)
你要做的驗證:build fairydust,插一台普通 USB-C 螢幕。會亮就結束了錄下 macOS 對 call 20 的回應序列,寫成規格,照規格寫 C

兩個依然是獨立的,而且分層測試依然是方法論 —— 只是第一層現在預期會直接通過。 普通 USB-C 螢幕會亮、Studio Display 不會亮,就是你要的那條分界線: 它同時確認了斷點 1 已解、斷點 2 是螢幕側的問題。

斷點 2 跟 SoC 無關。#579 的原始回報者用的是 M2 13" MacBook Pro(t8112), 後來 thejamescollins 在 M1 Max 16"(t6001) 上重現了完全一樣的序列。 兩顆不同世代的 SoC 撞同一面牆,所以問題在 Studio Display 那一側 —— 這也是為什麼 §5 讓 Mac mini 當 target 是合理的:t8103 沒有理由例外。

§2遠中近目標

近程 · 數天~兩週

打底

  • build 出一顆能開機的 fairydust kernel,舊 kernel 留著隨時退回
  • 插一台普通 USB-C 螢幕,確認 #601 的結論在你機器上也成立
  • 用 full-spec 非 Thunderbolt USB-C 線接 Studio Display,把結果貼到 #579(見入口的「下一步」)
  • m1n1 proxy 模式接通,能從 Python 戳暫存器
  • 讀完 Asahi 的 RE 政策原文

驗收:你有一個「改 → build → 開機 → 看 dmesg」的迭代迴圈, 而且知道自己的機器停在哪一層。

中程 · 數週

學會讀

  • 能在 phy/apple/atc.c 加 printk 並看懂執行路徑
  • 能架起 m1n1 hypervisor,錄下一份讀得懂的 trace
  • 若 fairydust 上還看得到 atcphy_mux_set 的 WARN,拿 PR #503 實測看看有沒有變化 (#622 是被 AI policy 關掉的,測可以,但它的程式碼不能當成你自己的成果送上游)
  • 回報結果 —— 這件事本身就是社群現在缺的

驗收:你看得懂 driver 在對硬體做什麼,也看得懂 trace 在講什麼。

遠程 · 無期限

解題

  • 從 macOS trace 反推 call 20 的正確回應序列
  • 把序列寫成一份別人看得懂的規格,貼進 #579
  • 照規格寫 C,成果送 PR(前提見 §7)

驗收:螢幕會亮。但真正的收穫是你不再怕任何一顆沒有文件的晶片。

§3完整步驟

六個階段。每一階段都有可驗收的產出,而且都不會把機器弄壞 —— 最差的情況是白花一個下午 build kernel。

上排是六個階段,下排是里程碑;上方三個色帶對應近/中/遠程。

階段 0 · build 一顆 fairydust kernel

環境:裝好的 Asahi Linux | 產出:迭代迴圈

USB-C 出圖的程式碼還沒進正式 kernel,在 fairydust 分支上。 不用重裝系統 —— kernel 只是一個 package,你多裝一顆並存,開機時選。

要滿足哪些條件、腳本做了什麼、更新後要檢查什麼 —— 全部在 實作 §2.1。

階段 0.5 · m1n1 proxy 的 Python REPL

環境:target 進 proxy 裸機模式 + host 跑 Python | 產出:對 MMIO 的直覺

m1n1 proxy mode 是一個跑在裸機上的 Python REPL: target(Mac mini)什麼作業系統都沒開,host(MBP)用 Python 隔著 USB 線直接讀寫它的暫存器。 m1n1/hw/*.py 裡有一堆現成的暫存器對照表類別。

export M1N1DEVICE=/dev/ttyACM0   # host 是 Linux(你的 MBP)
                                 # host 若是 macOS 則為 /dev/cu.usbmodemP_01

p.read32(addr)
p.write32(addr, val)

讀 device tree 找到一個 block、從 Python 戳它的暫存器、看硬體有什麼反應。 沒有別的東西能這麼快建立「軟體到底對硬體做了什麼」的直覺。

這裡開始有兩套環境,從第一天就要分清楚:
① proxy 裸機模式(mini 當 target,這時候它上面沒有任何作業系統在跑)
② 裝好的 Asahi(MBP,寫 driver、測 driver、順便當 host 看 trace)

階段 1 · 讀 driver

環境:裝好的 Asahi | 產出:讀得懂程式碼

在現有 driver 加 printk,看它實際跑到哪一行。目標檔案兩個:

  • drivers/phy/apple/atc.c —— Type-C PHY,直接讀寫實體暫存器,中間沒有魔法。 對「想搞懂硬體到 kernel 那段空白」的人來說,這是教科書等級的清楚
  • drivers/gpu/drm/apple/ —— DCP 的 mailbox RPC,抽象好幾層,晚一點再啃

階段 2 · 先 trace 簡單的裝置

環境:m1n1 hypervisor | 產出:讀得懂 tracer 輸出

架起 hypervisor(做法見 圖 3 與 §5),但第一次別碰 DCP。 先找一個簡單的裝置錄一份 trace,把「這串輸出在講什麼」搞清楚。

階段 3 · 轉接頭雙邊 trace ← 整條路的脊椎

環境:m1n1 hypervisor | 產出:一份「正確長相」的 DCP 對照組

插一個便宜的 USB-C → HDMI 轉接頭,在 macOS 和 Linux 兩邊都錄 trace,然後 diff。

關鍵在於:這是一個你知道正確答案的案例。macOS 那邊會動、Linux 這邊不會, 差異就是答案 —— 你不會卡在「我看不懂這段是對是錯」。

要用轉接頭,不要用內建 HDMI 孔。 內建 HDMI 走獨立的 DP→HDMI 轉換晶片(在 t600x 的 dts 裡是 atcphy3 專用), 它的 DCP 對話形狀跟 Type-C 路徑不一樣。 轉接頭走的才是同一條 atcphy → dcpext → dptx,只是少了那顆 A13。
這一步有人做過了,而且成功。thejamescollins 在 M1 Max 16"(apple,t6001)上 插 Apple USB-C Digital AV Multiport Adapter 轉 HDMI, 在 Hyprland 下跑到 4096x2160@60(#579 的 2026-09-04 留言)。

值得注意的是他的 dmesg:頭兩次還是走了 oob_hotplug status:0x2 → acking unhandled call 20 → dcp_dptx_disconnect, 第二次之後才出現 atcphy_mux_set 的 WARN,然後自己恢復並走進 oob_hotplug status:0x1 → dcp_dptx_connect。 也就是那個 WARN 不必然致命 —— 這正是你要拿來當對照組的「正確長相」。

階段 4 · Studio Display

環境:m1n1 hypervisor + driver | 產出:斷點 2 的答案

掛上 trace_dcp.py、開 macOS、插上 Studio Display,在 proxy 終端找 call 20(DPTX_APCALL_INACTIVE_SINK_DETECTED), 看 macOS 回了什麼、接下來的幾十筆 message 是什麼順序。

第一次的目標不是解決問題,是確認「call 20 有被錄到」。 錄得到,剩下就是讀懂序列;錄不到(例如 macOS 在 hypervisor 下走了別條路),那是另一個要先解的問題。

在這之前有一個更便宜的實驗,而且是 Asahi 開發者親自點名的。 chadmed 在 #579(2026-08-29)要求用「非 Thunderbolt 的 full-spec USB-C 線」重測 —— 不要 Thunderbolt 線,也不要充電線。回報者當時手上沒有那種線,到今天還沒有人做。

你有 Studio Display、有機器,缺的只是一條線。這件事不用 build kernel、不用 m1n1、不用裝第二份 macOS, 卻可能直接改變 #579 的診斷方向 —— 因為 james 用充電線(USB 2 控制組)時, 螢幕的 USB hub 反而正常列舉了,線材差異在這個 bug 裡顯然還沒被排除。

一個未知數:「叫醒 A13」未必全在 DCP 這條路上,也可能有一部分走 USB-PD(tipd), 也就是修改點可能橫跨兩個 driver。這件事 trace 出來的第一天就會知道。

§4兩個階段:Python 找答案,C 寫進 kernel

整個專案用兩種語言,節奏差了三個數量級。搞混這件事會浪費非常多時間。

注意兩條虛線回頭箭頭:那是兩個迴圈,上面那條幾秒一圈,下面那條幾十分鐘一圈。

階段 A · 一次幾秒

研究(Python,在 host 上)

改 tracer → 重跑 → 再看。目的是找出答案。

trace_dcp.py 是量測儀器,不是產品 —— 它永遠不會進 kernel,也不會出現在你的 PR 裡。

階段 B · 一次幾十分鐘

實作(C,在 kernel 裡)

改 C → build → 重開機 → 看 dmesg。目的是讓 Linux 真的會動。

把 call 20 從現在的「盲目 ack」換成照序列做事的 handler, 位置在 drivers/gpu/drm/apple/。

中間那個交接點才是重點

階段 A 的產出不是程式碼,是一份規格,大概長這樣:

DCP 送 call 20 之後,macOS 回了 ⟨某個值⟩,接著依序做了 A、B、C,然後 link training 才開始。

這份文字有三個用途:你自己照它寫 C、貼到 #579 讓別人能接手、 以及它本身就是 clean room 的標準結構(觀察 → 寫規格 → 照規格實作,你只是一個人扮兩個角色)。

先在 Python 階段把事情弄清楚,再動 C。 帶著一半的猜測去改 kernel,你會把大量時間花在等 build 上。

斷點 1 連階段 B 都不用。 它現在只是「換一顆 kernel」—— 沒有要改的 C,沒有要 cherry-pick 的 patch。 所以整份計畫裡唯一真正要寫程式的,只有斷點 2,而它必須先經過階段 A。

這也讓原本「先拿一個答案已知的題目練手」的設計少了一題。替代的練手題是階段 3: 轉接頭在 macOS 和 Linux 兩邊都會動(james 已經證明了), 所以那是一個你知道正確答案、可以用來校準自己閱讀 trace 能力的案例。

§5角色、機器與版本

左邊黃框是 host(MacBook Pro),右邊紅框是 target(Mac mini)。Studio Display 接在 target 那一側。

兩個角色,不要搞混

target(被觀測)host(旁觀者)
跑什麼m1n1(EL2)+ macOS guest(EL1)Python proxyclient
螢幕插哪插在這台不插
trace硬體在這裡產生、m1n1 在這裡攔截在這裡解碼、印出來
需要什麼一份 macOS 14.8.3、m1n1一條 USB 線、python

必須是同一台的原因很直接:你要觀測的是「target 的 DCP 在有螢幕插進來時做了什麼」。 螢幕插到 host 上,你什麼都錄不到。host 純粹是個終端機 —— 因為 target 那時候的畫面正被 guest 佔著。

你這兩台怎麼分配

Mac mini(target)  ← Studio Display 插這裡
      │ USB
      ↓
MacBook Pro(host) ← 用內建螢幕看 trace
  • MBP 當 host:它有內建螢幕,不用再找一台顯示器給 host 看 trace; 而且它是你裝好 Asahi 的那台,Python 環境現成的
  • mini 當 target:斷點 2 是螢幕那一側的問題 —— #579 在 t8112(M2)上報,thejamescollins 在 t6001(M1 Max)上重現, 兩顆不同世代的 SoC 撞同一面牆,t8103 沒有理由例外。 插上 Studio Display 就是一個乾淨的測試台
  • 降安全模型、關 SIP、裝第二份 macOS 都發生在 mini 上,日常機完全不用動

如果之後有需要把 target 換成 MBP(例如要比對 t600x 自己的 DCP 韌體行為),角色就對調: Studio Display 插到 MBP 的左前那一孔(見 §3 階段 0), mini 得自己找個畫面(HDMI 接台電視就行)。同一時間一台機器只能扮一個角色。

捕獲在 target,解讀在 host

m1n1 的 hypervisor 是 Python 驅動的:target 上攔到事件後透過 USB 回報給 host, 由 host 上的 Python 決定怎麼解讀、要不要放行。所以 -m proxyclient/hv/trace_dcp.py 掛的那個模組,是在 host 上執行的。

這表示你之後要加解碼邏輯是在 host 上寫 Python,不是在 target 上改 C —— 也就是 §4 說的那個快迴圈。

版本規則:真正擋人的是「出廠版本」

一個常見的誤解(我也搞錯過):以為不能裝比目前韌體舊的 macOS。實際規則是——

任何 Mac 都不能開機於比它「出廠時那一版」更舊的 macOS。 而在 Recovery 裡對一個獨立的 volume group 安裝舊版時,韌體不會被改動。
機器出廠版本裝 14.8.3
MacBook Pro 14" M1 Pro(2021)macOS 12 Monterey✅ 可以
Mac mini M1(2020)macOS 11 Big Sur✅ 可以

所以你現在跑 macOS 26 不影響這件事,兩台都在範圍內。 兩個要注意的:

  • 第二份 macOS 需要完整的 volume group(System + Data + 配對的 Recovery,獨立 container), 不是在現有 container 裡多切一個 volume
  • 選 14.8.3 不要選 13.5。文件兩個都列,但新版 macOS 裝的安全性變更「可能影響使用明顯更舊版本的能力」。 14.8.3 離現在近、風險小;真出問題 13.5 還在後面當備案
韌體版本會影響你看到的症狀。
  • 你的 DCP 韌體來自 macOS 26;#579 的回報者跑的是 7.1.6.fairydust 時期的組合
  • DCP 的協議會隨韌體版本改變(driver 裡有針對不同版本的 struct layout)
  • 你的 Studio Display 韌體也跟別人未必同版

#579 是 2026-08-24 開的,james 的測試一路做到 2026-09-04 —— 資料其實很新,不是陳年舊案。但版本組合仍然人人不同,所以比對時要一併記下版本, 別只貼 dmesg。

實際怎麼架(1TR 裡要打什麼、怎麼挖 kernelcache、host 那兩支 proxyclient 指令、 兩個終端機視窗各是誰)都是操作,放在 實作 §2.5。

§6規矩

Asahi 有三份政策要遵守:Code of Conduct、Copyright and Reverse Engineering Policy、 Generative AI Policy。第二份才是真正規範你日常工作的,動手前務必自己讀原文。

本頁的政策判讀是依據公開文件的推論,只有 Asahi 自己能認定 —— 動手前先去問一次。

工作守則

觀察硬體行為 → 自己理解 → 自己寫 → 送 PR

這條路徑同時滿足法律安全與政策合規,而且也是最快的。原因:

  • 載入 Apple firmware 原封不動跑 ✅ —— 你買 Mac 時就取得授權,沒有複製、沒有散布。 Asahi 從不散布 firmware,是從你自己機器的 macOS 抓的
  • 觀察行為、自己寫實作 ✅ —— 觀測到的是事實,事實不受著作權保護
  • 反編譯 Apple driver 來改 ❌ —— 衍生作品;而且 Linux 是 GPL,你無權重新授權
LLM 的界線(含我)。 Asahi 的 Generative AI Policy 是禁令,不是揭露制度 —— 說明手法只影響罰則輕重,不影響允不允許。而且政策裡點名 「用 LLM 解讀 m1n1 hypervisor 的 trace」會收到警告。 PR #622 就是因此被關掉的,不是技術問題。

這是單向門:一旦透過這個管道學到那段機制,沒辦法事後洗掉再乾淨地貢獻同一份成果。 所以守得住的規則是 —— 別把 trace 內容貼進 LLM,讓那份資料根本沒進到模型的 context。 通用機制(DP link training、mailbox RPC 概念、工具怎麼用)可以問;「我錄到這段是什麼意思」不要問。

政策管的是對 Asahi 的貢獻,不管你在自己機器上跑什麼。 只想修好自己的螢幕、不打算上游,這個限制完全不存在。