Repository navigation
drm/apple: M3 Pro GPU memory leak fix: release replaced framebuffers on 14.7 firmware - #325
Conversation
Every commit that replaces the primary plane's framebuffer takes a reference on the old one (apple_plane_prepare_fb()), and commit_tail parks it on the DCP's swapped_out_fbs list until the display no longer scans it out. The template IOMFB path releases those entries when the firmware completes the swap. The native 14.7 path never does, so on the M3 every replaced framebuffer stays referenced until the driver goes away, and with it the imported GEM object, its dma-buf attachment, its display IOMMU mapping, and the buffer itself once its owner frees it. Release the retained framebuffers once a 14.7 swap has completed, and after the panel is blanked. The session already holds its own reference on the framebuffer it shows (active_fb), and a failed swap still keeps everything pinned, as before. The 14.7 backlight KUnit fixture now sets up the retained framebuffer list as probe does: dcp_poweroff() reaches the release through dcp_v14_blank(). On a J514S (M3 Pro, 3024x1964) at the KWin login screen, 600 pointer moves (about 3500 swaps) on unfixed integration/aurora took the display driver from 6 to 69 framebuffers and from 194 to 3706 references: one reference per swap. Each leaked framebuffer held its own 22.8 MiB display mapping (16 KiB more IOMMU page tables each), and KWin's three swapchain buffers ended up with 135 attachments. The boot splash's 22.7 MiB framebuffer stayed allocated after plymouthd had exited. With this change the same run kept 3 framebuffers and 5 references, and the splash framebuffer was freed once KWin took over. Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Assisted-by: Claude:claude-opus-5-5
250d112 to
41a2b44
Compare
chriskearney
left a comment
There was a problem hiding this comment.
Reviewed and boot-tested 41a2b44 on an M3 Pro J516S/T6030. Approving the bounded framebuffer-lifetime fix based on source review and the hardware comparison below. This is not a claim that the full OMT suite passed.
The 14.7 link waits for both the RPC reply and matching swap-complete event before returning success. active_fb keeps its own reference; failed/refused presents keep the retained pins; blank drains only after a successful black swap. I found no source blocker. Exact-head CI passed, including KUnit (1742 passed, 3 skipped); I did not run KUnit locally. Direct retained-list success/failure/failed-blank regression tests would be a useful follow-up.
Compared against 14-1 built from the PR base 4fdd0fc, using the installed configuration as build input and verifying the candidate's loaded appledrm build ID against the built artifact:
| Measurement | Base | Candidate |
|---|---|---|
| Internal swaps under load | 3535 | 3584 |
| Framebuffer-reference increase after internal load | +3580 | 0 |
| Framebuffers, start → after window churn | 6 → 30 | 7 → 7 |
| Reference total, start → after churn | 125 → 7141 | 11 → 11 |
| SecPageTables change after churn | +384 kB | +32 kB |
The protocol included internal redraw load, external presentation, internal DPMS off/on (inactive CRTC and active_fb=0 verified), and five window/fullscreen cycles. No DART faults, flip failures, refcount/UAF reports or kernel WARN/BUG appeared during this protocol.
OMT caveat: the candidate job remains tests_failed: H.264 and HEVC on-screen checks saw five correct bars but cyan #1a2338. Retained before/after desktop captures show persistent crash notifications covering the cyan sampling region. Independently reconstructing OMT's sample grid from those captures predicts both the baseline's passing median and the candidate's panel-color median. Notification occlusion is therefore a strong inference, not proof: the actual video-check screenshots were deleted. The full suite did not pass; lab-kernel package/boot checks also fail, and gpu.driver fails in both runs. An mpv userspace crash/notification also occurs on the base and remains a separate issue. These results have not been rescored or rerun.
Scope: M3 Pro internal panel plus presentation to one Sony 4K120 display through a TS4. The external route changed between runs (DCPEXT1 → DCPEXT0); this is a matched internal-panel comparison, not matched external qualification. Three startup HPD timeouts on inactive DCPEXT1 in the candidate were unchanged after the initial measurement; the same warning sequence exists on the base. No M3 Air/Max, suspend, hot-plug or long-soak coverage.
Problem
When a commit replaces the primary plane's framebuffer,
apple_plane_prepare_fb()takes a reference on the old one andapple_atomic_commit_tail()parks it on the DCP'sswapped_out_fbslist, so it stays alive while the display may still scan it out. The template IOMFB path (iomfb_template.c) releases those entries when the firmware completes the swap. The native 14.7 path (iomfb_v14_7.c) never does: nothing on it callsdcp_release_retained_framebuffers()ordcp_release_all_retained_framebuffers().So on the M3 every replaced framebuffer stays referenced until the driver goes away, and with it the imported GEM object, its dma-buf attachment, its display IOMMU mapping, and the buffer itself once its owner frees it. That covers every board on the 14.7 path: T6030 (M3 Pro), J516C/J514C (M3 Max), J613/J615 (M3 Air), and external displays on that firmware. I tested on the M3 Pro only.
Measured on a J514S (M3 Pro, 3024x1964) at the KWin login screen, on unfixed
integration/aurora(4fdd0fc), with 600 pointer moves (each one a compositor redraw and a primary-plane flip):SecPageTables).plymouthd, 22.7 MiB) stayed allocated after plymouthd had exited. The leaked reference was its last one.Fix
iomfb_v14_7_flush(): release all retained framebuffers oncedcp_v14_present()reports a completed swap.dcp_v14_blank(): release them once the blank has completed.The 14.7 session already holds its own reference on the framebuffer it shows (
active_fb). When a swap fails, or an external display refuses one, nothing is released. Anything that may still be scanned out stays pinned, as before.The 14.7 backlight KUnit fixture now sets up the retained-framebuffer list as probe does, because
dcp_poweroff()reaches the release throughdcp_v14_blank().Testing
J514S (M3 Pro), 14.7 DCP firmware, kernel 7.2.11-11-dsc3-ARCH. I built appledrm from
4fdd0fcwith and without this commit, booted each build in turn (identified by build-id), and ran the same measurement:4fdd0fcSecPageTablescheckpatch.pl --strictis clean, andcheck-commits.pyis clean (sign-off present). AW=1build ofiomfb_v14_7.chas no warnings, before or after.v14_bl_dpms_dispatchreachesdcp_v14_blank()throughdcp_poweroff(), and the 14.7 backlight fixture built its DCP without the retained-framebuffer list that probe initialises, so the new release oopsed on it. The fixture now initialises the list the way probe (anddcp_dropped_swap_test.c) does.iomfb_v14_7.ccompiles with the 14.7 KUnit cases enabled, and CI is running KUnit again.🤖 Generated with Claude Code