On Thu, 2026-02-12 at 13:47 +0200, Ville Syrjälä wrote:
> On Wed, Feb 11, 2026 at 04:48:52PM -0800, Khaled Almahallawy wrote:
> > The driver automatically allocates a Y-plane (4A/5A) when userspace
> > configures an NV12 surface. The allocation loop doesn't check if a
> > candidate plane is already configured by userspace in the same
> > atomic
> > commit, causing conflict as observed in this i915_display_info:
> > 
> >   [PLANE:124:plane 4A]: type=OVL
> >       uapi: [FB:566] AB24 little-endian (0x34324241),0x0,1920x1280,
> > visible=visible
> >       planar: Linked to [PLANE:34:plane 1A] as a Y plane
> >       hw: [FB:564] NV12 little-endian (0x3231564e),0x0,1920x1080,
> > visible=yes
> > 
> > Plane 4A's uapi state shows userspace's AB24 framebuffer, but the
> > hw
> > state shows it was reprogrammed with the NV12 Y-plane.
> > 
> > Example triggered by experiment with IGT test to commit NV12 +
> > multiple
> > AB24 planes:
> > 
> >   === Testing with NV12 primary + 3 ABGR8888 overlays ===
> >     Plane 0 (Primary): NV12 1920x1080 at (0, 0)
> >     Plane 1 (Overlay 0): ABGR8888 1920x1280 (fullscreen) at (0, 0)
> >     Plane 2 (Overlay 1): ABGR8888 1920x1280 (fullscreen) at (0, 0)
> >     Plane 3 (Overlay 2): ABGR8888 1920x1280 (fullscreen) at (0, 0)
> >     TEST_ONLY passed, committing...
> >     Atomic commit SUCCEEDED
> > 
> > The bug triggers a kernel WARNING in unlink_nv12_plane():
> >   WARNING: drivers/gpu/drm/i915/display/intel_plane.c:1521
> >   drm_WARN_ON(plane_state->uapi.visible)
> > 
> 
> I think the actual bug is that we unlink the nv12 planes after
> plane_atomic_check(). unlink_nv12_plane() will then clobber
> some things in the crtc state that was set up by 
> plane_atomic_check().
> 
> So we perhaps want something like this:
> 
> diff --git a/drivers/gpu/drm/i915/display/intel_plane.c
> b/drivers/gpu/drm/i915/display/intel_plane.c
> index 3dc2ed52147f..98d0255b8b18 100644
> --- a/drivers/gpu/drm/i915/display/intel_plane.c
> +++ b/drivers/gpu/drm/i915/display/intel_plane.c
> @@ -441,6 +441,8 @@ void intel_plane_set_invisible(struct
> intel_crtc_state *crtc_state,
>  {
>       struct intel_plane *plane = to_intel_plane(plane_state-
> >uapi.plane);
>  
> +     unlink_nv12_plane(crtc_state, plane_state);
> +
>       crtc_state->active_planes &= ~BIT(plane->id);
>       crtc_state->scaled_planes &= ~BIT(plane->id);
>       crtc_state->nv12_planes &= ~BIT(plane->id);
> @@ -1513,6 +1515,9 @@ static void unlink_nv12_plane(struct
> intel_crtc_state *crtc_state,
>       struct intel_display *display =
> to_intel_display(plane_state);
>       struct intel_plane *plane = to_intel_plane(plane_state-
> >uapi.plane);
>  
> +     if (!plane_state->planar_linked_plane)
> +             return;
> +
>       plane_state->planar_linked_plane = NULL;
>  
>       if (!plane_state->is_y_plane)
> @@ -1550,8 +1555,7 @@ static int icl_check_nv12_planes(struct
> intel_atomic_state *state,
>               if (plane->pipe != crtc->pipe)
>                       continue;
>  
> -             if (plane_state->planar_linked_plane)
> -                     unlink_nv12_plane(crtc_state, plane_state);
> +             unlink_nv12_plane(crtc_state, plane_state);
>       }
>  
>       if (!crtc_state->nv12_planes)
> 
> With that we could perhaps even drop the second unlink_nv12_plane()
> call, but haven't really thought through the details...
> 
Hi Ville,

Thank you for this fix - I've tested it and confirmed it works
correctly. The Y-plane (5A) is now properly assigned without stealing
plane 4A that userspace had configured. Logs are below

For context, this issue came up while working with Android and
drm_hwcomposer, which tends to use MANY overlay layers simultaneously.
When combining NV12 video playback with multiple AB24 layers we started
to see issues.


Could you please suggest the next steps? If you plan to send this fix
here is

Tested-by: Khaled Almahallawy <[email protected]>

Or Would you be willing to share that Y-plane selection patch and we
can test it against the Android/drm_hwcomposer use case

Thanks again for the quick turnaround on this!

For reference, below are the logs:. 


igt run:

sudo ./kms_plane_multiple --run-subtest atomic-plane-stress-with-
formats
IGT-Version: 2.3-gd9d59d09a (x86_64) (Linux: 6.19.0-CI_DRM_17981-
g923df5da4a4a x86_64)
Using IGT_SRANDOM=1770899565 for randomisation
Opened device: /dev/dri/card1
Starting subtest: atomic-plane-stress-with-formats
Starting dynamic subtest: pipe-A-eDP-1
Testing pipe A with eDP-1
Found 6 total planes (4 overlays) on pipe A
Primary plane: NV12 1920x1080 at (0, 0)

=== Testing with NV12 primary + 1 ABGR8888 overlay ===
  Plane 0 (Primary): NV12 1920x1080 at (0, 0)
  Plane 1 (Overlay 0): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  ✓ TEST_ONLY passed, committing...
  ✓ Atomic commit SUCCEEDED

>>> Check i915_display_info now (cat
/sys/kernel/debug/dri/0/i915_display_info) <<<
>>> Press ENTER to continue...


=== Testing with NV12 primary + 2 ABGR8888 overlays ===
  Plane 0 (Primary): NV12 1920x1080 at (0, 0)
  Plane 1 (Overlay 0): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  Plane 2 (Overlay 1): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  ✓ TEST_ONLY passed, committing...
  ✓ Atomic commit SUCCEEDED

>>> Check i915_display_info now (cat
/sys/kernel/debug/dri/0/i915_display_info) <<<
>>> Press ENTER to continue...


=== Testing with NV12 primary + 3 ABGR8888 overlays ===
  Plane 0 (Primary): NV12 1920x1080 at (0, 0)
  Plane 1 (Overlay 0): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  Plane 2 (Overlay 1): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  Plane 3 (Overlay 2): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  ✓ TEST_ONLY passed, committing...
  ✓ Atomic commit SUCCEEDED

>>> Check i915_display_info now (cat
/sys/kernel/debug/dri/0/i915_display_info) <<<
>>> Press ENTER to continue...


=== Testing with NV12 primary + 4 ABGR8888 overlays ===
  Plane 0 (Primary): NV12 1920x1080 at (0, 0)
  Plane 1 (Overlay 0): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  Plane 2 (Overlay 1): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  Plane 3 (Overlay 2): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  Plane 4 (Overlay 3): ABGR8888 1920x1280 (fullscreen) at (0, 0)
  ✗ TEST_ONLY rejected (ret=-22)
  ✗ No working configuration found with 4 overlays

=== Results ===
Maximum configuration: NV12 primary + 3 ABGR8888 overlays
Dynamic subtest pipe-A-eDP-1: SUCCESS (299.910s)
Subtest atomic-plane-stress-with-formats: SUCCESS (299.910s)



display_info:


        [PLANE:124:plane 4A]: type=OVL
                uapi: [FB:566] AB24 little-endian
(0x34324241),0x0,1920x1280, visible=visible,
src=1920.000000x1280.000000+0.000000+0.000000, dst=1920x1280+0+0, rota
tion=0 (0x00000001)
                hw: [FB:566] AB24 little-endian
(0x34324241),0x0,1920x1280, visible=yes,
src=1920.000000x1280.000000+0.000000+0.000000, dst=1920x1280+0+0,
rotation=0
 (0x00000001)
        [PLANE:134:plane 5A]: type=OVL
                uapi: [FB:0] n/a,0x0,0x0,, visible=Y plane,
src=0.000000x0.000000+0.000000+0.000000, dst=0x0+0+0, rotation=0
(0x00000001)
                planar: Linked to [PLANE:34:plane 1A] as a Y plane
                hw: [FB:564] NV12 little-endian
(0x3231564e),0x0,1920x1080, visible=no,
src=1920.000000x1080.000000+0.000000+0.000000, dst=1920x1080+0+0,
rotation=0 


Kernel logs:

[156.742148] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A] ddb (177-1376) -> (   0-   0)
[156.742370] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A] level wm0, wm1, wm2, wm3, wm4, wm5, wm6, wm7, twm,
swm, stwm -> wm0, wm1, wm2, wm3, wm4, wm5, wm6, wm7, twm, swm, stwm
[156.742537] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A]   lines   3,   3,   3,   4,   5,   7,  10,  12,  
0,   0,   1 ->   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
[156.742701] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A]  blocks  48,  48,  48,  65,  81, 113, 161, 193,  
0,   0,  16 ->   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
[156.742871] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A] min_ddb  58,  58,  58,  76,  93, 129, 181, 215,  
0,   0,   0 ->   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
[156.743035] i915 0000:00:02.0: [drm:intel_bw_atomic_check [i915]]
[CRTC:150:pipe A] data rate 3328000 num active planes 3 -> 2
[156.743239] i915 0000:00:02.0: [drm:intel_bw_atomic_check [i915]] QGV
point 0: max bw 14993 required 1665
[156.913297] i915 0000:00:02.0: [drm:verify_connector_state [i915]]
[CONNECTOR:291:DP-1]
[156.913477] i915 0000:00:02.0: [drm:intel_atomic_commit_tail [i915]]
[CRTC:150:pipe A]

[156.913628] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:94:plane 3A] ddb (1376-2575) -> (   0-   0)
[156.913823] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:94:plane 3A] level wm0, wm1, wm2, wm3, wm4, wm5, wm6, wm7, twm,
swm, stwm -> wm0, wm1, wm2, wm3, wm4, wm5, wm6, wm7, twm, swm, stwm
[156.913993] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:94:plane 3A]   lines   3,   3,   3,   4,   5,   7,  10,  12,  
0,   0,   1 ->   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
[156.914149] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:94:plane 3A]  blocks  48,  48,  48,  65,  81, 113, 161, 193,  
0,   0,  16 ->   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
[156.914306] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:94:plane 3A] min_ddb  58,  58,  58,  76,  93, 129, 181, 215,  
0,   0,   0 ->   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
[156.914480] i915 0000:00:02.0: [drm:intel_bw_atomic_check [i915]]
[CRTC:150:pipe A] data rate 3328000 num active planes 2 -> 2
[156.914669] i915 0000:00:02.0: [drm:intel_bw_atomic_check [i915]] QGV
point 0: max bw 14993 required 1665
[157.080238] i915 0000:00:02.0: [drm:verify_connector_state [i915]]
[CONNECTOR:291:DP-1]
[157.080413] i915 0000:00:02.0: [drm:intel_atomic_commit_tail [i915]]
[CRTC:150:pipe A]

[156.916097] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A] ddb (   0-   0) -> ( 177-1376)
[156.916277] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A] level wm0, wm1, wm2, wm3, wm4, wm5, wm6, wm7, twm,
swm, stwm -> wm0, wm1, wm2, wm3, wm4, wm5, wm6, wm7, twm, swm, stwm
[156.916231] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:34:plane 1A] ddb ( 177- 354) -> (   0- 177)
[156.916233] i915 0000:00:02.0: [drm:icl_check_nv12_planes [i915]]
Linking NV12 planes: UV plane [PLANE:34:plane 1A] using Y plane
[PLANE:134:plane 5A]
[156.916266] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:94:plane 3A] ddb (   0-   0) -> (1376-2575)
[156.916398] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:34:plane 1A] ddb (   0- 177) -> (   0- 177)
[156.916517] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A] ddb ( 177-1376) -> ( 177-1376)
[156.916681] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:94:plane 3A] ddb (1376-2575) -> (1376-2575)
[156.916802] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:124:plane 4A] ddb (   0-   0) -> (2575-3774)
[156.916922] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:134:plane 5A] ddb (   0-   0) -> (3774-4051)
[156.917056] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:134:plane 5A] level wm0, wm1, wm2, wm3, wm4, wm5, wm6, wm7, twm,
swm, stwm -> wm0, wm1, wm2, wm3, wm4, wm5, wm6, wm7, twm, swm, stwm
[156.917223] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:134:plane 5A]   lines   0,   0,   0,   0,   0,   0,   0,   0,  
0,   0,   0 ->   3,   3,   3,   5,   6,   8,  12,  14,   0,   0,   1
[156.917388] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:134:plane 5A]  blocks   0,   0,   0,   0,   0,   0,   0,   0,  
0,   0,   0 ->  46,  46,  46,  77,  93, 125, 185, 217,   0,   0,  16
[156.917552] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:134:plane 5A] min_ddb   0,   0,   0,   0,   0,   0,   0,   0,  
0,   0,   0 ->  55,  55,  55,  89, 107, 143, 207, 241,   0,   0,   0
[156.919192] i915 0000:00:02.0: [drm:intel_bw_atomic_check [i915]]
[CRTC:150:pipe A] data rate 2471100 num active planes 5
[156.919385] i915 0000:00:02.0: [drm:intel_bw_atomic_check [i915]] QGV
point 0: max bw 14993 required 1236

[157.078870] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:34:plane 1A] ddb (   0- 177) -> (   0- 177)
[157.079049] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:64:plane 2A] ddb ( 177-1376) -> ( 177-1376)
[157.079224] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:94:plane 3A] ddb (1376-2575) -> (1376-2575)
[157.079390] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:124:plane 4A] ddb (2575-3774) -> (2575-3774)
[157.079554] i915 0000:00:02.0: [drm:skl_compute_wm [i915]]
[PLANE:134:plane 5A] ddb (3774-4051) -> (3774-4051)
[157.080052] i915 0000:00:02.0: [drm:intel_bw_atomic_check [i915]]
[CRTC:150:pipe A] data rate 2471100 num active planes 5
[157.080238] i915 0000:00:02.0: [drm:intel_bw_atomic_check [i915]] QGV
point 0: max bw 14993 required 1236

Thank You
Khaled


> > Fix by checking uapi.fb before allocating a Y-plane. If set,
> > userspace
> > configured this plane, so skip to the next candidate. This enables
> > graceful fallback (4A busy -> try 5A) rather than the current
> > behavior that steals planes from userspace.
> 
> I do have a patch in some branch that changes the Y plane
> selection to use 'enabled_planes' instead of 'active_planes'
> which is equivalent to this. It is perhaps the slightly more
> logical approach but it could result some specific usage
> scenarios losing NV12 scanout capability. IIRC I also had
> some unsolved issue with that approach, which is why I never
> even sent out the patch.
> 
> > IGT test and kernel fix generated with assistance from Claude
> > Sonnet 4.5
> > through an iterative process. The following is a summary of the
> > prompts
> > used:
> > 
> > IGT test generation prompt:
> > Need an IGT test to:
> > 1. Reproduce the NV12 + multiple AB24 plane allocation conflict
> > 2. Work across different GPU vendors (not Intel-specific)
> > 3. Discover hardware limits through iteration (not hardcoded)
> > 4. Test atomic commit behavior with mixed formats
> > 5. Validate driver properly rejects invalid configurations
> > 6. Help debug plane allocation issues (interactive inspection)
> > 
> > Kernel fix debug process:
> > 1. Explained NV12 UV->Y plane linking mechanism (link_nv12_planes)
> > 2. Traced Y-plane selection algorithm and hardware constraints
> > 3. Analyzed i915_display_info output showing uapi vs hw state
> > mismatch
> > 4. Triggered kernel WARNING in unlink_nv12_plane() confirming the
> > bug
> > 5. Traced kernel logs through atomic commit sequence
> > 6. Identified root cause: Y-plane allocation checks uapi.crtc, but
> > that's
> >    set later during plane validation. uapi.fb is set earlier during
> > state
> >    setup, making it the correct indicator of userspace
> > configuration
> > 7. Evaluated uapi.fb vs uapi.visible for detection timing
> > 8. Initially suggested rejecting commit with -EINVAL, but decided
> > graceful
> >    fallback with continue is better - allows trying alternate Y-
> > planes
> >    (4A busy -> 5A) instead of failing entire atomic commit
> > 9. Validated fix prevents plane stealing while allowing alternate
> > Y-plane
> > 
> > Cc: Uma Shankar <[email protected]>
> > Cc: Jani Nikula <[email protected]>
> > Cc: Ville Syrjala <[email protected]>
> > Signed-off-by: Khaled Almahallawy <[email protected]>
> > ---
> >  drivers/gpu/drm/i915/display/intel_plane.c | 4 ++++
> >  1 file changed, 4 insertions(+)
> > 
> > diff --git a/drivers/gpu/drm/i915/display/intel_plane.c
> > b/drivers/gpu/drm/i915/display/intel_plane.c
> > index 3dc2ed52147f..57d1a9cd226e 100644
> > --- a/drivers/gpu/drm/i915/display/intel_plane.c
> > +++ b/drivers/gpu/drm/i915/display/intel_plane.c
> > @@ -1578,6 +1578,10 @@ static int icl_check_nv12_planes(struct
> > intel_atomic_state *state,
> >                     if (IS_ERR(y_plane_state))
> >                             return PTR_ERR(y_plane_state);
> >  
> > +                   /* Reject if this Y-plane is being
> > configured by userspace */
> > +                   if (y_plane_state->uapi.fb)
> > +                           continue;
> > +
> >                     break;
> >             }
> >  
> > -- 
> > 2.43.0
> 

Reply via email to