Linux顯示卡驅動,DRM Atomic Commit流程介紹

上一篇文章介紹了DRM顯示一幀影象的Atomic介面,從系統呼叫到DRM內部的實現流程。Atomic介面把使用者空間中的引數設定好後,分配一個drm_atomic_state物件,然後commit此state即完成影象的更新。這篇文章繼續介紹Commit一個state的大體流程。 Atomic介面在核心層面從drm_ioctl開始,然後呼叫drm_mode_atomic_ioctl分配state物件,設定相關屬性,最後呼叫drm_atomic_commit提交一個state。可以這麼說,一幀影象的重新整理,就是一個state commit的過程。state commit的過程如下圖所示:

Linux顯示卡驅動,DRM Atomic Commit流程介紹

1、atomic_commit介面

atomic_commit介面位於drm_mode_config成員drm_mode_config_funcs結構體中,是commit的頂層介面,每個驅動程式可以實現自己的commit行為,也可以呼叫核心統一實現的函式。大部分驅動都會定義自己的drm_mode_config_funcs物件,但把atomic_commit介面指定為核心的預設實現函式drm_atomic_helper_commit。drm_atomic_commit函式封裝了呼叫atomic_commit介面的邏輯,在呼叫之前先執行drm_atomic_check_only檢查state是否符合要求,如果state引數不正確將返回並報錯。

int drm_atomic_helper_commit(struct drm_device *dev, struct drm_atomic_state *state, bool nonblock){ int ret; if (state->async_update) { ret = drm_atomic_helper_prepare_planes(dev, state); if (ret) return ret; drm_atomic_helper_async_commit(dev, state); drm_atomic_helper_cleanup_planes(dev, state); return 0; } ret = drm_atomic_helper_setup_commit(state, nonblock); if (ret) return ret; INIT_WORK(&state->commit_work, commit_work); ret = drm_atomic_helper_prepare_planes(dev, state); if (ret) return ret; if (!nonblock) { ret = drm_atomic_helper_wait_for_fences(dev, state, true); if (ret) goto err; } /* * This is the point of no return - everything below never fails except * when the hw goes bonghits。 Which means we can commit the new state on * the software side now。 */ ret = drm_atomic_helper_swap_state(state, true); if (ret) goto err; /* * Everything below can be run asynchronously without the need to grab * any modeset locks at all under one condition: It must be guaranteed * that the asynchronous work has either been cancelled (if the driver * supports it, which at least requires that the framebuffers get * cleaned up with drm_atomic_helper_cleanup_planes()) or completed * before the new state gets committed on the software side with * drm_atomic_helper_swap_state()。 * * This scheme allows new atomic state updates to be prepared and * checked in parallel to the asynchronous completion of the previous * update。 Which is important since compositors need to figure out the * composition of the next frame right after having submitted the * current layout。 * * NOTE: Commit work has multiple phases, first hardware commit, then * cleanup。 We want them to overlap, hence need system_unbound_wq to * make sure work items don‘t artificially stall on each another。 */ drm_atomic_state_get(state); if (nonblock) queue_work(system_unbound_wq, &state->commit_work); else commit_tail(state); return 0;err: drm_atomic_helper_cleanup_planes(dev, state); return ret;}EXPORT_SYMBOL(drm_atomic_helper_commit);

drm_atomic_helper_commit是核心實現的標準commit流程,驅動程式只要在drm_mode_config_funcs的介面中改成自己的函式,並在函式中包含對此函式的呼叫即可實現自己的流程,例如,追蹤介面呼叫或除錯程式等。函式主要工作是setup_commit環境,初始化一個工作佇列,prepare_planes的內容,最後由工作函式(commit_work)提交到硬體中。 Commit需要非同步執行,以便能夠在不同的CRTC上並行地Commit Atomic,最簡單的方法就是把工作函式加入到在system_unbound_wq工作佇列上。驅動程式來不需要拆分Atomic並行地執行每個commit,這對於modesets來說是十分有幫助的,因為這些必須作為一個全域性操作來完成,而有時啟用或禁用CRTC可能需要很長時間。 如果一個drm_atomic_state對應多個CRTC的update,DRM會對CRTC進行排序。在更新plane狀態時,驅動程式不能在其atomic_check過程中獲取不相關的CRTC的drm_crtc_state,因為這樣會阻礙多個CRTC的並行提交。而且,我們應該儘可能避免新增額外的狀態結構,因為在排序和鎖定時對這個有要求。 state隨drm_atomic_helper_swap_state同步更新。意味著在所有modeset鎖的保護下,呼叫者永遠不會看到不一致的狀態。 接下來就會按步驟的執行 a) pre-plane commit b) plane commit c) post-plane commit 然後,在舊的幀緩衝區不再顯示後清理它。 至於後續的同步工作由drm_crtc_commit物件負責。

setup_commit

建立drm_crtc_commit物件,程式碼如下:

int drm_atomic_helper_setup_commit(struct drm_atomic_state *state, bool nonblock){ struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; struct drm_connector *conn; struct drm_connector_state *old_conn_state, *new_conn_state; struct drm_plane *plane; struct drm_plane_state *old_plane_state, *new_plane_state; struct drm_crtc_commit *commit; int i, ret; for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { commit = kzalloc(sizeof(*commit), GFP_KERNEL); if (!commit) return -ENOMEM; init_commit(commit, crtc); new_crtc_state->commit = commit; ret = stall_checks(crtc, nonblock); if (ret) return ret; /* Drivers only send out events when at least either current or * new CRTC state is active。 Complete right away if everything * stays off。 */ if (!old_crtc_state->active && !new_crtc_state->active) { complete_all(&commit->flip_done); continue; } /* Legacy cursor updates are fully unsynced。 */ if (state->legacy_cursor_update) { complete_all(&commit->flip_done); continue; } if (!new_crtc_state->event) { commit->event = kzalloc(sizeof(*commit->event), GFP_KERNEL); if (!commit->event) return -ENOMEM; new_crtc_state->event = commit->event; } new_crtc_state->event->base。completion = &commit->flip_done; new_crtc_state->event->base。completion_release = release_crtc_commit; drm_crtc_commit_get(commit); commit->abort_completion = true; state->crtcs[i]。commit = commit; drm_crtc_commit_get(commit); } for_each_oldnew_connector_in_state(state, conn, old_conn_state, new_conn_state, i) { /* Userspace is not allowed to get ahead of the previous * commit with nonblocking ones。 */ if (nonblock && old_conn_state->commit && !try_wait_for_completion(&old_conn_state->commit->flip_done)) return -EBUSY; /* Always track connectors explicitly for e。g。 link retraining。 */ commit = crtc_or_fake_commit(state, new_conn_state->crtc ?: old_conn_state->crtc); if (!commit) return -ENOMEM; new_conn_state->commit = drm_crtc_commit_get(commit); } for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) { /* Userspace is not allowed to get ahead of the previous * commit with nonblocking ones。 */ if (nonblock && old_plane_state->commit && !try_wait_for_completion(&old_plane_state->commit->flip_done)) return -EBUSY; /* Always track planes explicitly for async pageflip support。 */ commit = crtc_or_fake_commit(state, new_plane_state->crtc ?: old_plane_state->crtc); if (!commit) return -ENOMEM; new_plane_state->commit = drm_crtc_commit_get(commit); } return 0;}EXPORT_SYMBOL(drm_atomic_helper_setup_commit);

drm_atomic_helper_setup_commit函式分配drm_crtc_commit物件並初始化它。drm_crtc_commit物件控制整個commit過程的過程,可以這麼說,整個commit其實就是圍繞drm_crtc_commit的幾個事件來處理的。state在實際提交給硬體之前,必須呼叫drm_atomic_helper_wait_for_dependencies()等待上一次的完成,對於非阻塞提交,還必須將此呼叫置於非同步工作執行緒中。 硬體提交完成後必須使用drm_atomic_helper_commit_hw_done()發出訊號。在此步驟之後,不允許驅動程式讀取或更改任何永久性軟體或硬體modeset狀態。唯一的例外是透過除drm_modeset_lock鎖以外的其他方式進行狀態保護的物件,只能檢查帶有指向舊狀態結構指標的獨立state,例如使用drm_atomic_helper_cleanup_planes()清理舊緩衝區。 最後,在清理之前,驅動程式必須呼叫drm_atomic_helper_commit_cleanup_done()。 預設情況下,不需要顯式地清理drm_atomic_helper_setup_commit函式分配的資源drm_atomic_state_default_clear()會自動地完成它。

prepare_planes

在commit之前,需要準備plane的各種資源

int drm_atomic_helper_prepare_planes(struct drm_device *dev, struct drm_atomic_state *state){ struct drm_connector *connector; struct drm_connector_state *new_conn_state; struct drm_plane *plane; struct drm_plane_state *new_plane_state; int ret, i, j; for_each_new_connector_in_state(state, connector, new_conn_state, i) { if (!new_conn_state->writeback_job) continue; ret = drm_writeback_prepare_job(new_conn_state->writeback_job); if (ret < 0) return ret; } for_each_new_plane_in_state(state, plane, new_plane_state, i) { const struct drm_plane_helper_funcs *funcs; funcs = plane->helper_private; if (funcs->prepare_fb) { ret = funcs->prepare_fb(plane, new_plane_state); if (ret) goto fail; } } return 0;fail: for_each_new_plane_in_state(state, plane, new_plane_state, j) { const struct drm_plane_helper_funcs *funcs; if (j >= i) continue; funcs = plane->helper_private; if (funcs->cleanup_fb) funcs->cleanup_fb(plane, new_plane_state); } return ret;}EXPORT_SYMBOL(drm_atomic_helper_prepare_planes);

drm_atomic_helper_prepare_planes函式透過呼叫drm_plane_helper_funcs。prepare_fb()。為plane準備新的狀態,特別是幀緩衝區。如果遇到任何失敗,此函式將在任何已成功準備的幀緩衝區上呼叫drm_plane_helper_funcs。cleanup_fb()。 prepare_fb函式是幾乎每個驅動都會實現的介面,為的是準備好一個可用幀緩衝區以便輸出,例如透過固定它的後備儲存器,或將它重新定位到一個連續的VRAM塊中。 此函式並不能中斷未完成的渲染,即使非同步提交能夠將任何錯誤返回給使用者空間,也不應該這樣做,更好的方法是填寫傳入的drm_plane_state。fence成員,由它來完成工作。如果驅動程式不支援fence,那麼應該透過plane結構中的私有成員實現等效功能。如果是始終固定緩衝區的驅動程式應該使用drm_gem_fb_prepare_fb()來代替此功能。prepare_fb返回成功後,系統將會呼叫cleanup_fb()清理fb。

2、commit_work

commit_work函式是工作佇列執行例程,負責把state提交到硬體的全部工作,函式進一步呼叫commit_tail,但最終在drm_atomic_helper_commit_tail得以實現具體功能。

int drm_atomic_helper_prepare_planes(struct drm_device *dev, struct drm_atomic_state *state){ struct drm_connector *connector; struct drm_connector_state *new_conn_state; struct drm_plane *plane; struct drm_plane_state *new_plane_state; int ret, i, j; for_each_new_connector_in_state(state, connector, new_conn_state, i) { if (!new_conn_state->writeback_job) continue; ret = drm_writeback_prepare_job(new_conn_state->writeback_job); if (ret < 0) return ret; } for_each_new_plane_in_state(state, plane, new_plane_state, i) { const struct drm_plane_helper_funcs *funcs; funcs = plane->helper_private; if (funcs->prepare_fb) { ret = funcs->prepare_fb(plane, new_plane_state); if (ret) goto fail; } } return 0;fail: for_each_new_plane_in_state(state, plane, new_plane_state, j) { const struct drm_plane_helper_funcs *funcs; if (j >= i) continue; funcs = plane->helper_private; if (funcs->cleanup_fb) funcs->cleanup_fb(plane, new_plane_state); } return ret;}EXPORT_SYMBOL(drm_atomic_helper_prepare_planes);

drm_atomic_helper_commit_tail函式按標準流程執行commit的流程,以相容各種顯示卡,每種顯示卡的驅動都可以在標準流程實現相應的介面以處理自己的業務

modeset_disables

drm_atomic_helper_commit_modeset_disables函式關閉所有需要關閉的輸出,並用新模式準備它們(如果需要)。為了與舊的CRTC 幫助函式相容,應該在commit plane之前呼叫它,這是預設的。不同的驅動程式可以將modeset分組,並在最後進行commit plane。這對於電源管理的驅動程式很有用,因為只有在實際啟用了CRTC時才會發生plane更新。

commit_planes

具體的commit行為,下一節講到。

modeset_enables

drm_atomic_helper_commit_modeset_enables函式啟用所有具有更新時必須關閉的新配置的輸出。有disable就有enable,此為配套函式,不同的驅動可以選擇實現不同的功能。

fake_vblank

drm_atomic_helper_fake_vblank函式用於遍歷所有CRTC併為drm_crtc_state。 no_vblank等於true和drm_crtc_state。event != NULL的Commit製造一個VBLANK事件。主要用於寫回聯結器以一次性模式工作。此操作僅對已入佇列的作業有效,對任何沒有接觸聯結器的管線無效,會導致在呼叫drm_atomic_helper_wait_for_vblank()或drm_atomic_helper_wait_for_flip_done()時超時。

hw_done

drm_atomic_helper_commit_hw_done函式用於發出硬體提交步驟完成的訊號。在此步驟之後,不允許驅動程式讀取或更改任何永久性軟體或硬體模式集狀態。呼叫此功能後,驅動程式應嘗試推遲任何開銷或延遲較大的清理工作。

wait_vblank

drm_atomic_helper_wait_for_vblank函式在commit後,應該等待所有受影響的CRTC上的VBLANK。但它只會等待framebuffers已實際更改的CRTC,用以最佳化cursor和plane的更新。

3、commit planes

drm_atomic_helper_commit_planes函式是commit的核心函式,每一個CRTC的更新,都可以看作它內部每一個plane更新疊加後的成果。

void drm_atomic_helper_commit_planes(struct drm_device *dev, struct drm_atomic_state *old_state, uint32_t flags){ struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; struct drm_plane *plane; struct drm_plane_state *old_plane_state, *new_plane_state; int i; bool active_only = flags & DRM_PLANE_COMMIT_ACTIVE_ONLY; bool no_disable = flags & DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET; for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { const struct drm_crtc_helper_funcs *funcs; funcs = crtc->helper_private; if (!funcs || !funcs->atomic_begin) continue; if (active_only && !new_crtc_state->active) continue; funcs->atomic_begin(crtc, old_crtc_state); } for_each_oldnew_plane_in_state(old_state, plane, old_plane_state, new_plane_state, i) { const struct drm_plane_helper_funcs *funcs; bool disabling; funcs = plane->helper_private; if (!funcs) continue; disabling = drm_atomic_plane_disabling(old_plane_state, new_plane_state); if (active_only) { /* * Skip planes related to inactive CRTCs。 If the plane * is enabled use the state of the current CRTC。 If the * plane is being disabled use the state of the old * CRTC to avoid skipping planes being disabled on an * active CRTC。 */ if (!disabling && !plane_crtc_active(new_plane_state)) continue; if (disabling && !plane_crtc_active(old_plane_state)) continue; } /* * Special-case disabling the plane if drivers support it。 */ if (disabling && funcs->atomic_disable) { struct drm_crtc_state *crtc_state; crtc_state = old_plane_state->crtc->state; if (drm_atomic_crtc_needs_modeset(crtc_state) && no_disable) continue; funcs->atomic_disable(plane, old_plane_state); } else if (new_plane_state->crtc || disabling) { funcs->atomic_update(plane, old_plane_state); } } for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) { const struct drm_crtc_helper_funcs *funcs; funcs = crtc->helper_private; if (!funcs || !funcs->atomic_flush) continue; if (active_only && !new_crtc_state->active) continue; funcs->atomic_flush(crtc, old_crtc_state); }}EXPORT_SYMBOL(drm_atomic_helper_commit_planes);

drm_atomic_helper_commit_planes函式提交新的plane state,前提條件是atomic state必須儲存有相關的物件指標,因為這一步不許失敗。old_state引數表示哪些平面和CRTC需要更新。請注意,此功能一步完成所有CRTC的所有plane的更新。如果硬體不支援這種方法,可以檢視drm_atomic_helper_commit_planes_on_crtc函式。當CRTC被disable時,應用程式可以更新plane的引數。DRM/KMS核心將引數儲存在plane state中,當CRTC再次enable時,驅動就可以使用它們。因此,大多數驅動不需要通知plane為disabled CRTC進行更新。 驅動應該在flags中設定ACTIVE_ONLY標誌,以免收到與disabled CRTC的相關的更新通知。這避免了當驅動程式或硬體不能或不需要處理disabled CRTC上的更新時,需要新增手動忽略的程式碼。 如果相關顯示控制器要求在禁用CRTC時禁用CRTC的plane,則驅動可以在flags中設定NO_DISABLE_AFTER_MODESET標誌,如果舊的plane state的CRTC需要modesetting操作,此標誌將跳過對plane的drm_plane_helper_funcs。atomic_disable的呼叫。當然,驅動程式需要在CRTC disable回撥中disable plane,因為這個只能自己去完成。 為了與遺留程式碼保持最大的相容,drm_atomic_helper_commit預設並沒有設定ACTIVE_ONLY標誌,這點需要注意。

開始,函式遍歷state中的每一個CRTC,執行與它相關的atomic_begin介面,各驅動可以實現這些介面,處理自己需要處理的邏輯。 接著,遍歷state中的每一個plane,呼叫與plane相關的atomic_update介面,這個介面幾乎每個驅動都會實現,這就是把資料提交的硬體的具體執行的地方。 最後,又遍歷state中的每一個CRTC,執行與它相關的atomic_flush介面,確保資料提交到了硬體上。