依「你現在在幹嘛」排:§1 抄一行就走、§2 照著打、 §3 卡住時掃、§4–§6 想讀懂機制時才讀、§7–§8 查名詞與出處。
§1速查
三十秒內要拿到的東西。細節往下。
DP 只在左前那一孔
| sysfs | i2c | 實體位置 | DP |
|---|---|---|---|
port0 | 0-0038 | USB-C Left Back(靠螢幕轉軸) | ❌ |
port1 | 0-003f | USB-C Left Front(靠觸控板) | ✅ |
port2 | 0-003b | USB-C Right | ❌ |
port3 | 0-003a | (內部,非實體孔) | ❌ |
對照表可以自己讀出來,不用記:
for c in /proc/device-tree/soc/*/*/connector; do
echo "$(basename $(dirname $c)): $(tr -d '\0' < $c/label)"
done
插對之後 ls /sys/class/typec/port*-partner 會出現 port1-partner。
插錯孔完全沒有反應,也不會有任何錯誤訊息。
走的是 atcphy1。atcphy3 在 dts 裡註明
ATC3 is used for DisplayPort -> HDMI only,那是內建 HDMI 孔的專用路徑。
現在跑的是哪顆,以及怎麼確認
uname -r # 要是 7.1.13-fairydust+
strings /lib/modules/$(uname -r)/kernel/drivers/phy/apple/phy-apple-atc.ko \
| grep 'park PIPE handler' # 有這行 = #622 真的編進去了
/boot/dtb 搶回去(原因見 §2.4):
readlink /boot/dtb # 指到 dtb-…asahi… 就是被搶走了
sudo ln -sfn /boot/dtbs/<fairydust 版本> /boot/dtb
sudo update-m1n1
§2照著做
怎麼 build、怎麼裝、怎麼退回、更新之後要檢查什麼。全部是照著做就好的東西。
2.1 build 一顆 fairydust kernel
這顆 kernel 要滿足的五件事(為什麼要做這一步見 方向 §3):
- clone fairydust 分支
.config一定要從現在的kernel-16kconfig 起手。 Apple Silicon 上 16K pages 是必需的,這是最容易踩的坑- 開啟 DP alt mode 相關選項,建議編成 module(方便用
dmesg確認有沒有載到) - build kernel 加 DTB —— Asahi 靠 device tree,別只裝 vmlinuz
- 確認 GRUB 選單會顯示(Asahi 預設會跳過),否則你沒地方選回舊 kernel
它的 README 也獨立寫了同一件事:「Plug adapter into the FRONT-MOST USB-C port」 —— 跟 dts 裡的
USB-C Left Front 對得上。它預設會多打一個 patch(修內建 HDMI 在 suspend/resume 後不亮), 想要原汁原味的分支就
SKIP_PATCHES=1。
- 材料只有
.config來自本機。原始碼是git clone AsahiLinux/linux --branch fairydust --filter=blob:none(約 3GB);.config則是cp /boot/config-$(uname -r)—— 所以 16K page 那個坑它自動幫你避開了 - README 的第 9 步不會跑。
main()裡setup_display_script是註解掉的,註解寫「causes login loop on KDE/Wayland」。 對 Hyprland 使用者是好消息 —— 那段會寫一個用xrandr、 還硬寫死2560x1600的腳本(14" 是 3024x1964), 外加 XDG autostart 和一條RUN+=跑sleep 2的 udev rule。全部不會發生 - 它改 GRUB 的那行對你是空轉。它用
sed 's/GRUB_TIMEOUT_STYLE=.*/…/',只改既有的行;/etc/default/grub裡沒有這一行的話什麼都不會發生,要自己加
2.2 只改模組時的 40 秒迴圈
改動只在 *.ko(例如只動了 iomfb.c 或 atc.c)的話,
不需要重裝 kernel 本體,也就不會觸發那個把 /boot/dtb 指歪的 hook:
cd ~/linux-fairydust
make -j8 # 增量,約 40 秒
strings drivers/phy/apple/phy-apple-atc.ko | grep 'park PIPE handler' # 驗證
sudo make INSTALL_MOD_STRIP=1 modules_install
sudo dracut -f --kver 7.1.13-fairydust+
readlink -f /boot/dtb # 要是 /boot/dtbs/7.1.13-fairydust+
sudo reboot
strings 不要跳過。
2026-09-24 那次中途跑過 git apply -R,之後 build 出來的模組其實沒有 patch,
測了一輪結果當然跟沒改一樣。
光看 git diff 或檔案時間戳不夠 —— 要確認編出來的東西裡面有。
2.3 退回與移除
- 暫時退回:重開機,在 GRUB 選
7.1.13-402.asahi.fc44.aarch64+16k。 官方 kernel 完全沒被動過 - 退掉某個 patch:
cd ~/linux-fairydust && git apply -R ~/dev/asahi-fairydust-display/patches/<檔名>, 然後照上面那組重編重裝。退完記得也把patches/裡那個檔移走, 否則下次跑 build 腳本又會自動套回去 - 完全移除:
~/dev/asahi-fairydust-display/asahi-fairydust-uninstall.sh。 它會把 kernel、模組、DTB、/boot/dtb、m1n1 全部還原, 但要先重開機回官方 kernel 才跑得動
/boot/dtb + update-m1n1 ——
m1n1 之後每次開機都帶著 fairydust 的 device tree,包括你在 GRUB 選回官方 kernel 那次。
其餘(模組、DTB、vmlinuz、initramfs)都帶 -fairydust 後綴並存,不覆蓋任何東西。repo 裡的
asahi-fairydust-uninstall.sh 會把這些全部還原,
包含把 /boot/dtb 指回官方版本再跑一次 update-m1n1。
它會擋你在 fairydust kernel 裡執行,要先重開機回官方 kernel。
2.4 官方 kernel 更新之後要檢查
/boot/dtb 搶回去 —— 這是最容易中招的一點。Fedora 的
/usr/lib/kernel/install.d/10-devicetree.install 在每次 kernel
add 時會無條件做這件事:
rm -f /boot/dtb
ln -s dtb-$KERNEL_VERSION /boot/dtb
它掃的是 /boot/dtb-*(連字號),
而 make dtbs_install 把 fairydust 的 DTB 裝到
/boot/dtbs/<版本>(複數、斜線)——
兩者對不上,所以 fairydust 的 DTB 對那支 hook 是隱形的。
緊接著 15-update-m1n1.install 會跑 update-m1n1 把官方 DTB 刷進去。結果:
omarchy update 帶進一顆新官方 kernel 之後,
你的 fairydust kernel 會帶著官方 DTB 開機 ——
ENABLE_DCPEXT_TYPEC 那些節點不見了,外接螢幕安靜地不再亮。
症狀是「明明沒動什麼,某天就壞了」。檢查與修復的三行指令在 §1 速查, 值得做成
omarchy 的 post-update hook,跟你其他那幾個放在一起。
(這台跑的是 omadora —— omarchy 的 Fedora Asahi 版,指令名稱還是 omarchy。)
installonly_limit管不到它。 那個限制只作用在 RPM 標記為 installonly 的套件;腳本走的是make install, 裝出來的不是 RPM,dnf 不會把它擠掉。 (會被擠掉的是最舊那顆官方 kernel。)omarchy update不會幫你重 build fairydust,上游有新 commit 要自己重跑
2.5 m1n1 雙機 trace:怎麼架起來
這一節是遠期的(方向 §3 的階段 3)。現在不會用到,但它是操作,所以住在這裡。
這不是虛擬機。macOS 跑在 target 真正的 Apple Silicon CPU 上,原生速度、真的硬體; m1n1 待在 EL2,只把幾頁 MMIO 設成 trap,攔下來記錄再放行。macOS 本體不用改。 完整的角色分配見 圖 3。
在 target(Mac mini)的 1TR Recovery Terminal 裡(對測試 volume 做)
bputil -nkcas # 降安全模型
csrutil disable # 關 SIP
nvram boot-args=-v
kmutil configure-boot -c build/m1n1.bin --raw --entry-point 2048 \
--lowest-virtual-address 0 -v /Volumes/macOSTest
取得 macOS kernel
在 target 上開進測試 volume,從這裡挖 kernelcache,用 img4tool 解出 Mach-O:
/System/Volumes/Preboot/[UUID]/boot/[hash]/System/Library/Caches/com.apple.kernelcaches/
在 host(MacBook Pro)上跑
這兩支都是 proxyclient 工具,跑在 host 上,隔著 USB 線指揮 target。
export M1N1DEVICE=/dev/ttyACM0 # host 是 Linux;host 若是 macOS 則為 /dev/cu.usbmodemP_01
# 每次都先 chainload working tree 的 m1n1
python3 proxyclient/tools/chainload.py build/m1n1.macho
# 掛著 DCP tracer 開 macOS
python3 proxyclient/tools/run_guest.py \
-m proxyclient/hv/trace_dcp.py \
path/to/kernelcache.macho \
-- "debug=0x14e serial=3 apcie=0xfffffffe -enable-kprintf-spam wdt=-1 clpc=0"
兩個終端機視窗
| 裝置 | 用途 |
|---|---|
ttyACM0host 若是 macOS: cu.usbmodemP_01 |
proxy —— run_guest.py 走這條,trace 輸出印在這裡 |
ttyACM1host 若是 macOS: cu.usbmodemP_03 |
guest 虛擬 UART —— macOS 自己的 console |
(進 DFU 模式是另一回事:Apple 對每個機型指定了特定的孔,那要用時再查。 角色若對調成 MBP 當 target,就要記得 14" MBP 的 DP 只在左前那一孔, proxy 的線別插在那裡。)
§3卡住了怎麼查
每個工具只看得到一層。搞混層次就會把時間花在錯的地方 —— 這一節就是為了避免那件事。
各個工具在看哪一層
| 工具 | 它看得到什麼 | 看不到什麼 |
|---|---|---|
/sys/class/typec/port*-partner |
有沒有東西插進來、插在哪一個孔 | 那東西是不是螢幕 |
/sys/class/drm/*/status.../modes |
kernel 認不認得這個顯示接頭、有沒有拿到 EDID | 畫面有沒有真的出去 |
journalctl -k |
driver 和 DCP firmware 在講什麼(最底層的真話) | userspace 做了什麼 |
udevadm monitor --udev --subsystem-match=drm |
kernel 有沒有通知 userspace | userspace 有沒有理它 |
hyprland.log$XDG_RUNTIME_DIR/hypr/*/ |
合成器收到之後怎麼想、做了什麼、失敗在哪 | 硬體狀態 |
wlr-randr / hyprctl monitors |
合成器目前認為的輸出狀態 | 那個認知對不對 |
hyprland.log 是最容易被忘記、但最常給出答案的那一個。
2026-09-24 那次熱插拔,前面四個工具都顯示「一切正常」,
只有它講出真話:Cannot commit when a page-flip is awaiting。
一張決策表
你的 MBP 跑 Hyprland,這會製造一種很容易誤判的情況:kernel 其實已經把螢幕接起來了,但畫面就是沒出來。 這不是假設 —— PR #622 的作者就留了這個已知問題: 「重新接上之後,userspace 沒有對 BAD link status 做出反應,要手動 toggle output」。那是合成器層,不是 kernel。
每次測試都照這個順序查,對應圖 1 的層次:
# ① kernel 有沒有看到這個接頭?
ls /sys/class/drm/
cat /sys/class/drm/*/status # connected / disconnected
# ② 有沒有拿到 mode?
cat /sys/class/drm/card*/*/modes
wc -l < /sys/class/drm/card2-DP-1/modes
# ③ 合成器有沒有接手?
hyprctl monitors all
wlr-randr
# ④ 底層在講什麼
journalctl -k -b | grep -iE 'dcp|atcphy|tipd|typec|altmode|oob_hotplug|flip_done|valid_mode'
# ⑤ 合成器怎麼想的 —— 最容易被忘記,但 2026-09-24 那次是它給出答案
tail -f $XDG_RUNTIME_DIR/hypr/*/hyprland.log
# 拔插的當下開著這個看:kernel 到底有沒有通知 userspace
udevadm monitor --udev --subsystem-match=drm
# 是哪一台螢幕、插在哪個孔
ls -d /sys/class/typec/port*-partner
| ① connector | ② modes | ③ Hyprland | 結論 |
|---|---|---|---|
| disconnected | — | — | 卡在斷點 1(atcphy,訊號沒出去)—— 先確認你用的是 fairydust、插的是左前那一孔、而且是冷插 |
| connected | 空的 | — | 卡在斷點 2(DCP 沒給 mode) |
| connected | 有 mode | 沒看到 | Hyprland 沒接手,kernel 那邊其實通了 |
| connected | 有 mode | 看得到、還說 enabled | 但畫面還是黑的 —— 這一種 2026-09-24 實際撞到了,見附錄 §4 日誌。
去看 hyprland.log 找 Cannot commit when a page-flip is awaiting |
第三種是好消息 —— 代表你已經穿過兩個斷點了,剩下的是 userspace 設定。 第四種最難認,因為每一層都回報「我沒問題」。
hyprctl dispatch dpms off DP-1 在 Lua 設定下會被剖析器拒絕。
繞過去的辦法是直接用 wlr-randr(走 Wayland 協定,不經過 Hyprland 的設定層):
wlr-randr --output DP-1 --off && sleep 1 && wlr-randr --output DP-1 --on
(這次它成功逼出了 modeset,但因為上面那個 page flip 卡死,畫面還是沒回來。)