~/linux-fairydust 查出來的,不是從文件推測的。
行號對應的是 fairydust 分支目前這一份 checkout,上游一改就會偏,但函式名和關係不會。
§1這條路上有哪些角色
先建立一件事:「CPU」不是一顆。M1 Pro 這顆 SoC 裡,跑 Linux 的那組核心叫 AP(Application Processor),旁邊還有十幾顆各自跑 Apple 韌體的協處理器。 顯示這條路上,AP 其實什麼都不直接做 —— 它在指揮別人。
| 角色 | 它是什麼 | 誰在控制它 | 跑誰的程式 |
|---|---|---|---|
| AP | 跑 Linux 的主核心 | 你 | Linux kernel |
| CD321x i2c 0-003f |
USB-C / USB-PD 控制器。協商供電與模式、偵測插拔與方向 | 自己(有自己的韌體),用 IRQ 通知 AP | Apple 韌體 |
ATC PHYatcphy1 |
Type-C 的實體層。同一組高速 lane 要當 USB3 還是 DP,由它切 | AP 直接讀寫暫存器 | Linux driver |
DP crossbaratcphy1_xbar |
顯示端的交換器:哪個 DCP 的輸出接到哪個 PHY | AP 直接讀寫 | Linux driver |
| DPTX PHY | DisplayPort 發送端:lane 數、link rate 在這裡設 | AP 直接讀寫,但是 DCP 叫它設的 | Linux driver |
DCPdcpext1 |
顯示協處理器。真正在操作顯示管線的是它 | 自己。AP 只能隔著 mailbox 送訊息 | Apple 韌體 |
| A13 | 在 Studio Display 裡面的一整顆 SoC,管面板、hub、攝影機 | 自己,透過 DP AUX 通道對話 | Apple 韌體 |
- DCP 不是你寫的程式。它跑 Apple 的閉源韌體,AP 對它只能送訊息、等回覆。 它「吞掉一張 page flip 卻不回報」這種事,你無法從內部修 —— 只能在 AP 這側防守。
- DPTX PHY 是 AP 在寫,但不是 AP 在決定。 DCP 透過訊息告訴 AP「lane 數改成 4、link rate 改成 HBR3」,AP 照做去寫暫存器。 這是一個反向的控制流:協處理器指揮作業系統,也就是名詞表裡的「AP call」。
§2device tree 把它們接起來
上面那些 driver 彼此完全不知道對方存在。
把它們綁在一起的是 device tree —— ARM 的硬體不會自我介紹,所以必須有人寫下「誰接誰」。
這台機器的接線在 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi:
// 第 12 行:這一整條路要不要存在,由這個開關決定
#define ENABLE_DCPEXT_TYPEC
typec1: connector { // 第 188 行 —— 左前那個 USB-C 孔
displayport = <&dcpext1>; // 第 196 行 —— 這個孔的 DP 輸出來自 dcpext1
ports {
port@1 {
typec1_connector_ss: endpoint {
remote-endpoint = <&atcphy1_typec_lanes>; // 第 211 行
}; // 高速 lane 接到 atcphy1
};
};
};
&dcpext1 { // 第 278 行
phys = <&atcphy1 PHY_TYPE_DP>; // 第 284 行 —— DCP 要用的 PHY 是 atcphy1 的 DP 模式
mux-controls = <&atcphy1_xbar 0>; // 第 286 行 —— 它要佔用 crossbar 的第 0 路
mux-control-names = "dp-xbar"; // 第 287 行 —— driver 用這個名字去拿
};
typec1 這個 connector 寫了 displayport = <&dcpext1>。
其他三個孔沒有這一行,所以對 Linux 而言它們根本沒有顯示能力 ——
插上去不是「壞掉」,是這條路在資料上就不存在。
正式 kernel 連 #define ENABLE_DCPEXT_TYPEC 都沒開,所以連 DP-1 都不會出現。
§3driver 地圖
哪個檔案管哪一塊硬體,以及它對外掛出什麼 —— 最後一欄是重點, 那決定了別人能怎麼呼叫它。
| 檔案 | 管哪塊硬體 | 它對外註冊/掛出什麼 |
|---|---|---|
drivers/usb/typec/tipd/core.c |
CD321x(USB-PD) | Type-C port;並持有別人的 handle:mux、role_sw、connector_fwnode。
這是整條路的發動機 —— 插拔事件從這裡開始 |
drivers/phy/apple/atc.c |
ATC PHY | typec_mux_register()(第 2374 行)、
typec_switch_register()(第 2256 行)、
PHY provider(第 2149 行)。三組 phy_ops:
USB2(1984)/USB3(2032)/DP(2109) |
drivers/mux/apple-display-crossbar.c |
DP crossbar | devm_mux_chip_register()(第 401 行)—— 掛成一個標準 mux 控制器 |
drivers/phy/apple/dptx.c |
DPTX PHY | phy_ops:set_mode(526)、validate(544)、
configure(570)。實作是 dptx_phy_activate()(125)、
set_link_rate()(324)、set_active_lane_count()(81) |
drivers/gpu/drm/apple/apple_drv.c |
DRM 這一側的門面 | DRM connector helper,其中
.oob_hotplug_event = apple_connector_oob_hotplug(第 250 行)
—— Type-C 世界進入顯示世界的唯一入口 |
drivers/gpu/drm/apple/dcp.c |
DCP 本體 | devm_apple_rtkit_init(dev, dcp, "mbox", …)(第 1076 行)建立 mailbox 連線;
對外開 dcp_dptx_connect_oob() / disconnect_oob()(433/439) |
drivers/gpu/drm/apple/dptxep.ciomfb.c、systemep.c、av.c、audio.c |
DCP 上的各個服務 | 不是硬體 driver,是跑在 mailbox 之上的協定端點(endpoint)。 DCP 韌體裡有好幾個服務,一個檔案對一個 |
drivers/gpu/drm/apple/afk.c |
— | 上面那些 endpoint 共用的傳輸層(環形緩衝、訊息切分) |
§4它們之間怎麼交互
沒有任何一個 driver 直接呼叫另一個 driver 的函式。
全部走核心框架:一邊 register,另一邊靠 device tree 的接線去 get,
然後呼叫框架的通用 API,框架再轉給對方的 callback。
一共用到五組,把它們認完,這條路上就沒有黑箱了。
① Type-C mux framework —— 切實體 lane
tipd/core.c typec_mux_set(cd321x->mux, &cd321x->state) // 559, 599, 614, 628, 635
↓(框架查出 DT 接線指向 atcphy1)
phy/apple/atc.c atcphy_mux_set() // 由 typec_mux_register(2374) 掛上
CD321x 的 driver 從頭到尾沒有 #include 過 atc.c 的任何東西。
它只知道「我有一個 mux」,那個 mux 是誰,是 device tree 第 211 行那條
remote-endpoint 決定的。
② DRM out-of-band hotplug —— 從 Type-C 跨進顯示
tipd/core.c drm_connector_oob_hotplug_event(cd321x->connector_fwnode, status) // 694, 754
↓(框架用 fwnode 找到對應的 drm_connector)
apple_drv.c apple_connector_oob_hotplug() // 87,由 .oob_hotplug_event(250) 掛上
↓
dcp.c dcp_dptx_connect_oob() / dcp_dptx_disconnect_oob() // 433 / 439
「out-of-band」的意思是:這個 hotplug 訊號不是從顯示線本身收到的, 是 USB-PD 那一側告訴顯示子系統的。一般 DP 螢幕靠 HPD pin,這裡靠 CD321x 轉述 —— 所以 Type-C 的 driver 才會需要認識一個 DRM connector。
③ mux framework —— 佔用 crossbar
dcp.c devm_mux_control_get(dev, "dp-xbar") // 1262(名字來自 DT 第 287 行)
dcp.c mux_control_select(dcp->xbar, mux_index) // 1267
↓
mux/apple-display-crossbar.c // devm_mux_chip_register(401)
④ PHY framework —— 但方向是反的
dptxep.c phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index) // 484
dptxep.c phy_configure(dptx->atcphy, &dptx->phy_ops) // 322, 429
↓
phy/apple/dptx.c dptx_phy_set_mode(526) / dptx_phy_configure(570)
→ dptx_phy_activate(125) / set_link_rate(324) / set_active_lane_count(81)
dptxep.c 是
DCP 的訊息處理函式 —— 也就是說:DCP 韌體送一則訊息過來說「把 lane 設成這樣」,
AP 收到之後才去呼叫 PHY framework。
控制流是從協處理器往作業系統走的,跟你平常寫 driver 的方向相反。
這就是「AP call」這個名詞的實際長相。
⑤ RTKit / AFK / EPIC —— 跟 DCP 講話的三層
| 層 | 負責 | 檔案 |
|---|---|---|
| RTKit | mailbox 上的最底層協定:開機、喚醒、共享記憶體、送/收一則訊息 | 核心共用;DCP 這邊在 dcp.c:1076 初始化 |
| AFK | 在 RTKit 之上鋪環形緩衝,讓多個服務共用同一條 mailbox | afk.c |
| EPIC / endpoint | 一個服務一個端點,各自有自己的訊息格式 | dptxep.c(DP 連線)、iomfb.c(送畫面)、
systemep.c、av.c、audio.c |
所以 dmesg 裡那句 new service DCPDPService on channel 17/19 是這一層在講話:
DCP 韌體宣告它開了一個服務,AP 這邊把 dptxep.c 掛上去。
服務沒開起來,dcp_dptx_connect() 第一件事就是拒絕
(dptx service for port %d not enabled,dcp.c:372)。
§5為什麼要這麼多層
第一次看會覺得繞,但每一層都在解決一個具體問題:
| 層 | 它存在的理由 |
|---|---|
| Type-C framework | 同一條線要輪流當 USB3、DP、Thunderbolt。誰決定(PD 控制器)和 誰執行(PHY)是兩顆不同的硬體,必須解耦 |
| PHY framework | 同一顆 ATC PHY 有三種身分(USB2/USB3/DP),三組 phy_ops 各自獨立,
呼叫者不必知道自己拿到的是哪一種 |
| mux framework | crossbar 是共用資源 —— 多個 DCP 搶同幾條輸出。
用 select/deselect 才有互斥 |
| DRM oob hotplug | 顯示子系統原本假設 hotplug 從顯示線來。Type-C 打破了這個假設, 所以要開一個正式的「從旁邊通知」入口 |
| RTKit / AFK / EPIC | DCP 是獨立的電腦。跟它溝通本質上是行程間通訊, 不是讀寫暫存器 —— 所以需要傳輸層、多工、協定三層 |