§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 交握。
先分清楚:哪些東西在 Mac 裡,哪些在 Studio Display 裡
這件事最容易搞混,因為兩邊各有一顆 Apple 處理器:
- DCP(顯示協處理器)在 Mac 裡 —— 負責產生畫面、跟螢幕交握
- A13 Bionic 在 Studio Display 裡 —— 那是一整顆 iPhone 等級的 SoC,跑 Apple 自家韌體,管面板、USB hub、攝影機、喇叭
斷點 2 就是這兩顆之間的問題:Mac 的 DCP 回報「對面那顆還在睡」(INACTIVE_SINK_DETECTED),
而 Linux driver 不知道該怎麼回應。所以要「叫醒」的是螢幕裡那顆,不是 Mac 裡那顆。
順帶兩個容易忽略的事實:那條 USB-C 上同時跑影像、USB 資料和電力, 而且電力方向是反的 —— Studio Display 供電給 Mac,最高 96W。 #601 的失敗點正是在「USB3 + DP 混合模式」的切換上,跟線上同時有 USB 資料這件事直接相關。
兩個斷點,現在只剩一個
| 斷點 1 · Type-C PHY | 斷點 2 · DCP 交握 | |
|---|---|---|
| 症狀 | atcphy_mux_set WARNFailed to lock pipehandler | acking unhandled call 20link 立刻被拆掉 |
| 追蹤 | #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遠中近目標
打底
- 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 這邊不會, 差異就是答案 —— 你不會卡在「我看不懂這段是對是錯」。
atcphy3 專用),
它的 DCP 對話形狀跟 Type-C 路徑不一樣。
轉接頭走的才是同一條 atcphy → dcpext → dptx,只是少了那顆 A13。
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 下走了別條路),那是另一個要先解的問題。
你有 Studio Display、有機器,缺的只是一條線。這件事不用 build kernel、不用 m1n1、不用裝第二份 macOS, 卻可能直接改變 #579 的診斷方向 —— 因為 james 用充電線(USB 2 控制組)時, 螢幕的 USB hub 反而正常列舉了,線材差異在這個 bug 裡顯然還沒被排除。
一個未知數:「叫醒 A13」未必全在 DCP 這條路上,也可能有一部分走 USB-PD(tipd),
也就是修改點可能橫跨兩個 driver。這件事 trace 出來的第一天就會知道。
§4兩個階段:Python 找答案,C 寫進 kernel
整個專案用兩種語言,節奏差了三個數量級。搞混這件事會浪費非常多時間。
研究(Python,在 host 上)
改 tracer → 重跑 → 再看。目的是找出答案。
trace_dcp.py 是量測儀器,不是產品 ——
它永遠不會進 kernel,也不會出現在你的 PR 裡。
實作(C,在 kernel 裡)
改 C → build → 重開機 → 看 dmesg。目的是讓 Linux 真的會動。
把 call 20 從現在的「盲目 ack」換成照序列做事的 handler,
位置在 drivers/gpu/drm/apple/。
中間那個交接點才是重點
階段 A 的產出不是程式碼,是一份規格,大概長這樣:
這份文字有三個用途:你自己照它寫 C、貼到 #579 讓別人能接手、 以及它本身就是 clean room 的標準結構(觀察 → 寫規格 → 照規格實作,你只是一個人扮兩個角色)。
先在 Python 階段把事情弄清楚,再動 C。 帶著一半的猜測去改 kernel,你會把大量時間花在等 build 上。
這也讓原本「先拿一個答案已知的題目練手」的設計少了一題。替代的練手題是階段 3: 轉接頭在 macOS 和 Linux 兩邊都會動(james 已經證明了), 所以那是一個你知道正確答案、可以用來校準自己閱讀 trace 能力的案例。
§5角色、機器與版本
兩個角色,不要搞混
| 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。實際規則是——
| 機器 | 出廠版本 | 裝 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,你無權重新授權
這是單向門:一旦透過這個管道學到那段機制,沒辦法事後洗掉再乾淨地貢獻同一份成果。 所以守得住的規則是 —— 別把 trace 內容貼進 LLM,讓那份資料根本沒進到模型的 context。 通用機制(DP link training、mailbox RPC 概念、工具怎麼用)可以問;「我錄到這段是什麼意思」不要問。
政策管的是對 Asahi 的貢獻,不管你在自己機器上跑什麼。 只想修好自己的螢幕、不打算上游,這個限制完全不存在。