drm_atomic_commit()runs the check phase (driveratomic_checkcallbacks) and then the commit tail, which vmaps the framebuffer, calls the driver'satomic_update()and unmaps it again.swap_state()runs before the commit tail, soplane->stateinsideatomic_update()is already the new state.
- Check phase:
drm_atomic_helper_check()→drm_atomic_helper_check_planes()→drm_atomic_helper_check_plane_damage()(blob →plane_state->damage), then the driver's plane and CRTCatomic_checkcallbacks. - Commit phase:
drm_atomic_helper_setup_commit()+prepare_planes()runs.begin_fb_access = drm_gem_begin_shadow_fb_access(vmap), thendrm_atomic_helper_swap_state(), thendrm_atomic_helper_commit_tail(). drm_atomic_helper_commit_planes()callsdrm_tutorial_plane_helper_atomic_update(); afterwardsend_fb_accessvunmaps the framebuffer.- Because
swap_state()already ran,plane->stateis the new state; the old state comes fromdrm_atomic_get_old_plane_state(state, plane)- exactly what the driver does before merging damage. - The driver's
atomic_update()walksfb->obj[0]→to_drm_gem_dma_obj(obj)→dma_obj->vaddr, logs geometry and the top-left 4x4 pixels, then merges and logs the damage rectangle.
drm_atomic_commit(state) [drm_atomic.c]
├─ drm_atomic_check_only()
│ └─ mode_config.funcs->atomic_check = drm_atomic_helper_check
│ ├─ drm_atomic_helper_check_modeset()
│ └─ drm_atomic_helper_check_planes()
│ ├─ drm_atomic_helper_check_plane_damage()
│ │ → fb_damage_clips blob → plane_state->damage
│ ├─ plane->helper_private->atomic_check
│ │ = drm_tutorial_plane_helper_atomic_check
│ │ └─ drm_atomic_helper_check_plane_state(
│ │ ..., DRM_PLANE_NO_SCALING, DRM_PLANE_NO_SCALING,
│ │ false, false)
│ └─ crtc->helper_private->atomic_check
│ = drm_tutorial_crtc_helper_atomic_check
│ ├─ drm_atomic_helper_check_crtc_primary_plane()
│ └─ drm_atomic_add_affected_planes()
└─ mode_config.funcs->atomic_commit = drm_atomic_helper_commit
├─ drm_atomic_helper_setup_commit() + prepare_planes()
│ └─ plane->helper_private->begin_fb_access
│ = drm_gem_begin_shadow_fb_access()
│ └─ drm_gem_fb_vmap() // map the fb's BOs into kernel VA
├─ drm_atomic_helper_swap_state() // old/new states swapped;
│ // plane->state now points at the NEW state
└─ commit_tail() (blocking path)
└─ mode_config.helper_private->atomic_commit_tail
= drm_atomic_helper_commit_tail()
├─ drm_atomic_helper_commit_modeset_disables()
│ → crtc atomic_disable (logs only)
├─ drm_atomic_helper_commit_planes(dev, state, 0)
│ └─ for each plane in the state:
│ plane->helper_private->atomic_update
│ = drm_tutorial_plane_helper_atomic_update()
│ └─ then end_fb_access loop:
│ drm_gem_end_shadow_fb_access() → drm_gem_fb_vunmap()
├─ drm_atomic_helper_commit_modeset_enables()
│ → crtc atomic_enable (logs only)
├─ drm_atomic_helper_fake_vblank()
├─ drm_atomic_helper_commit_hw_done()
├─ drm_atomic_helper_wait_for_vblanks()
└─ drm_atomic_helper_cleanup_planes()
→ plane cleanup_fb
(on error: drm_atomic_helper_unprepare_planes())
drm_atomic_helper_commit_planes() runs atomic_update for every plane in the
state whose new state has a CRTC (or that is being disabled). The
begin_fb_access / end_fb_access helpers bracket the update:
drm_gem_begin_shadow_fb_access() vmaps the framebuffer objects into kernel
address space, drm_gem_end_shadow_fb_access() unmaps them again.
This is the driver's "hardware programming" step - for a real device this is where you would program scanout registers. The tutorial version inspects the state instead:
if (!fb) return;- the plane may be disabled, in which case there is no framebuffer.drm_atomic_get_old_plane_state(state, plane)- remember the previous state; it is needed for damage merging.drm_dev_enter()- guard against the device being unplugged concurrently (drm_tutorial_remove()callsdrm_dev_unplug()).- Walk from the framebuffer to the backing memory:
fb->obj[0]→to_drm_gem_dma_obj(obj)→dma_obj->vaddr. This is the kernel virtual address of the GEM DMA buffer - the same mapping the fbdev client vmap'ed, so the pixels written through/dev/fb0are visible here. - Print the framebuffer geometry (
width,height,pitches[0],vaddr) and dump the top-left 4x4 RGB565 pixels, indexing withy * (fb->pitches[0] / 2) + x(pitch is in bytes, RGB565 is 2 bytes). drm_atomic_helper_damage_merged(old_plane_state, plane_state, &rect)- merge the damage rectangles of the old and new plane state into one rectangle, then logx1,y1,x2,y2. Merging both states matters because the fbdev client may accumulate several dirtyfb clips before the worker runs; a region can be damaged in both states and would otherwise be reported twice or missed.drm_dev_exit()- balance the earlier enter.
- The helper function names above are specific to the WSL2 kernel documented in the root README and were not re-verified against another kernel version.
- fbdev-write-path.md - what calls
drm_atomic_commit()here - kms-objects.md - the callbacks referenced above
- driver-faq.md - why the state looks the way it does