[v4] drm/ssd130x: Store the HW buffer in the driver-private CRTC state

Message ID 20230913052938.1114651-1-javierm@redhat.com
State New
Headers
Series [v4] drm/ssd130x: Store the HW buffer in the driver-private CRTC state |

Commit Message

Javier Martinez Canillas Sept. 13, 2023, 5:29 a.m. UTC
  The commit 45b58669e532 ("drm/ssd130x: Allocate buffer in the plane's
.atomic_check() callback") moved the allocation of the intermediate and
HW buffers from the encoder's .atomic_enable callback, to the plane's
.atomic_check callback.

This was suggested by Maxime Ripard, because drivers aren't allowed to
fail after the drm_atomic_helper_swap_state() function has been called.

And the encoder's .atomic_enable happens after the new atomic state has
been swapped, so allocations (that can fail) shouldn't be done there.

But the HW buffer isn't really tied to the plane's state. It has a fixed
size that only depends on the (also fixed) display resolution defined in
the Device Tree Blob.

That buffer can be considered part of the CRTC state, and for this reason
makes more sense to do its allocation in the CRTC .atomic_check callback.

The other allocated buffer (used to store a conversion from the emulated
XR24 format to the native R1 format) is part of the plane's state, since
it will be optional once the driver supports R1 and allows user-space to
set that pixel format.

So let's keep the allocation for it in the plane's .atomic_check callback,
this can't be moved to the CRTC's .atomic_check because changing a format
does not trigger a CRTC mode set.

Reported-by: Geert Uytterhoeven <geert@linux-m68k.org>
Closes: https://lore.kernel.org/dri-devel/CAMuHMdWv_QSatDgihr8=2SXHhvp=icNxumZcZOPwT9Q_QiogNQ@mail.gmail.com/
Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
---

Changes in v4:
- Fix a build warning reported by the robot (missing static in helper function).

Changes in v3:
- Call drm_atomic_get_crtc_state() in the plane's .atomic_check (Maxime Ripard).

Changes in v2:
- Drop RFC prefix.
- Fix typo in commit message (Thomas Zimmermann).
- Store the HW buffer in the driver's private CRTC state (Thomas Zimmermann).
- Just use kmalloc() kcalloc() when allocating buffers (Thomas Zimmermann).
- Keep the allocation of the intermediate buffer in the plane's .atomic_check

 drivers/gpu/drm/solomon/ssd130x.c | 153 +++++++++++++++++++++++-------
 1 file changed, 118 insertions(+), 35 deletions(-)
  

Comments

Maxime Ripard Sept. 14, 2023, 1:05 p.m. UTC | #1
Hi,

On Wed, Sep 13, 2023 at 07:29:25AM +0200, Javier Martinez Canillas wrote:
>  static const struct drm_crtc_helper_funcs ssd130x_crtc_helper_funcs = {
>  	.mode_valid = ssd130x_crtc_helper_mode_valid,
> -	.atomic_check = drm_crtc_helper_atomic_check,
> +	.atomic_check = ssd130x_crtc_helper_atomic_check,
>  };

Sorry I didn't catch that sooner, but there's no reason to call that
function a helper.

With that fixed (and feel free to fix while applying)

Acked-by: Maxime Ripard <mripard@kernel.org>

Maxime
  
Javier Martinez Canillas Sept. 14, 2023, 1:23 p.m. UTC | #2
Maxime Ripard <mripard@kernel.org> writes:

Hello Maxime,

> Hi,
>
> On Wed, Sep 13, 2023 at 07:29:25AM +0200, Javier Martinez Canillas wrote:
>>  static const struct drm_crtc_helper_funcs ssd130x_crtc_helper_funcs = {
>>  	.mode_valid = ssd130x_crtc_helper_mode_valid,
>> -	.atomic_check = drm_crtc_helper_atomic_check,
>> +	.atomic_check = ssd130x_crtc_helper_atomic_check,
>>  };
>
> Sorry I didn't catch that sooner, but there's no reason to call that
> function a helper.
>

Yeah, agreed that there's no reason but others drivers already add the
_helper prefix for struct drm_*_helper_funcs callbacks, and I did that
in this driver as well to follow (what appears to be?) a convention.

So I've to that now for the struct drm_crtc_helper_funcs handlers to be
consistent with the rest of the driver, e.g for plane:

static const struct drm_plane_helper_funcs ssd130x_primary_plane_helper_funcs = {
	DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
	.atomic_check = ssd130x_primary_plane_helper_atomic_check,
	.atomic_update = ssd130x_primary_plane_helper_atomic_update,
	.atomic_disable = ssd130x_primary_plane_helper_atomic_disable,
};

static const struct drm_plane_funcs ssd130x_primary_plane_funcs = {
	.update_plane = drm_atomic_helper_update_plane,
	.disable_plane = drm_atomic_helper_disable_plane,
	.reset = ssd130x_primary_plane_reset,
	.atomic_duplicate_state = ssd130x_primary_plane_duplicate_state,
	.atomic_destroy_state = ssd130x_primary_plane_destroy_state,
	.destroy = drm_plane_cleanup,
};

> With that fixed (and feel free to fix while applying)
>
> Acked-by: Maxime Ripard <mripard@kernel.org>
>
> Maxime
  
Maxime Ripard Sept. 14, 2023, 2:15 p.m. UTC | #3
On Thu, Sep 14, 2023 at 03:23:53PM +0200, Javier Martinez Canillas wrote:
> Maxime Ripard <mripard@kernel.org> writes:
> 
> Hello Maxime,
> 
> > Hi,
> >
> > On Wed, Sep 13, 2023 at 07:29:25AM +0200, Javier Martinez Canillas wrote:
> >>  static const struct drm_crtc_helper_funcs ssd130x_crtc_helper_funcs = {
> >>  	.mode_valid = ssd130x_crtc_helper_mode_valid,
> >> -	.atomic_check = drm_crtc_helper_atomic_check,
> >> +	.atomic_check = ssd130x_crtc_helper_atomic_check,
> >>  };
> >
> > Sorry I didn't catch that sooner, but there's no reason to call that
> > function a helper.
> >
> 
> Yeah, agreed that there's no reason but others drivers already add the
> _helper prefix for struct drm_*_helper_funcs callbacks, and I did that
> in this driver as well to follow (what appears to be?) a convention.

From a quick grep, it looks like it's the exception rather than the norm

> So I've to that now for the struct drm_crtc_helper_funcs handlers to be
> consistent with the rest of the driver, e.g for plane:
> 
> static const struct drm_plane_helper_funcs ssd130x_primary_plane_helper_funcs = {
> 	DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
> 	.atomic_check = ssd130x_primary_plane_helper_atomic_check,
> 	.atomic_update = ssd130x_primary_plane_helper_atomic_update,
> 	.atomic_disable = ssd130x_primary_plane_helper_atomic_disable,
> };
> 
> static const struct drm_plane_funcs ssd130x_primary_plane_funcs = {
> 	.update_plane = drm_atomic_helper_update_plane,
> 	.disable_plane = drm_atomic_helper_disable_plane,
> 	.reset = ssd130x_primary_plane_reset,
> 	.atomic_duplicate_state = ssd130x_primary_plane_duplicate_state,
> 	.atomic_destroy_state = ssd130x_primary_plane_destroy_state,
> 	.destroy = drm_plane_cleanup,
> };

Ack.

I still believe we should be removing the helper part, those are not
helpers. But it's not a big deal anyway.

Maxime
  
Javier Martinez Canillas Sept. 14, 2023, 4:38 p.m. UTC | #4
Maxime Ripard <mripard@kernel.org> writes:

> On Thu, Sep 14, 2023 at 03:23:53PM +0200, Javier Martinez Canillas wrote:
>> Maxime Ripard <mripard@kernel.org> writes:
>> 
>> Hello Maxime,
>> 
>> > Hi,
>> >
>> > On Wed, Sep 13, 2023 at 07:29:25AM +0200, Javier Martinez Canillas wrote:
>> >>  static const struct drm_crtc_helper_funcs ssd130x_crtc_helper_funcs = {
>> >>  	.mode_valid = ssd130x_crtc_helper_mode_valid,
>> >> -	.atomic_check = drm_crtc_helper_atomic_check,
>> >> +	.atomic_check = ssd130x_crtc_helper_atomic_check,
>> >>  };
>> >
>> > Sorry I didn't catch that sooner, but there's no reason to call that
>> > function a helper.
>> >
>> 
>> Yeah, agreed that there's no reason but others drivers already add the
>> _helper prefix for struct drm_*_helper_funcs callbacks, and I did that
>> in this driver as well to follow (what appears to be?) a convention.
>
> From a quick grep, it looks like it's the exception rather than the norm
>

Ah, I guess that was just unlucky when looking at others drivers as
reference when writing this one.

>> So I've to that now for the struct drm_crtc_helper_funcs handlers to be
>> consistent with the rest of the driver, e.g for plane:
>> 
>> static const struct drm_plane_helper_funcs ssd130x_primary_plane_helper_funcs = {
>> 	DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
>> 	.atomic_check = ssd130x_primary_plane_helper_atomic_check,
>> 	.atomic_update = ssd130x_primary_plane_helper_atomic_update,
>> 	.atomic_disable = ssd130x_primary_plane_helper_atomic_disable,
>> };
>> 
>> static const struct drm_plane_funcs ssd130x_primary_plane_funcs = {
>> 	.update_plane = drm_atomic_helper_update_plane,
>> 	.disable_plane = drm_atomic_helper_disable_plane,
>> 	.reset = ssd130x_primary_plane_reset,
>> 	.atomic_duplicate_state = ssd130x_primary_plane_duplicate_state,
>> 	.atomic_destroy_state = ssd130x_primary_plane_destroy_state,
>> 	.destroy = drm_plane_cleanup,
>> };
>
> Ack.
>
> I still believe we should be removing the helper part, those are not
> helpers. But it's not a big deal anyway.
>

Probably it comes down to semantics since one could argue that are helper
functions in the driver that are used as callbacks.

But yes, I agree that if is not the norm, it's better to get rid of those.
I'll post a follow-up patch.

> Maxime
  
Javier Martinez Canillas Sept. 14, 2023, 6:15 p.m. UTC | #5
Maxime Ripard <mripard@kernel.org> writes:

[...]

>
> Acked-by: Maxime Ripard <mripard@kernel.org>
>

Pushed to drm-misc (drm-misc-next). Thanks!
  

Patch

diff --git a/drivers/gpu/drm/solomon/ssd130x.c b/drivers/gpu/drm/solomon/ssd130x.c
index 3b4dde09538a..8ab02724f65f 100644
--- a/drivers/gpu/drm/solomon/ssd130x.c
+++ b/drivers/gpu/drm/solomon/ssd130x.c
@@ -141,14 +141,23 @@  const struct ssd130x_deviceinfo ssd130x_variants[] = {
 };
 EXPORT_SYMBOL_NS_GPL(ssd130x_variants, DRM_SSD130X);
 
+struct ssd130x_crtc_state {
+	struct drm_crtc_state base;
+	/* Buffer to store pixels in HW format and written to the panel */
+	u8 *data_array;
+};
+
 struct ssd130x_plane_state {
 	struct drm_shadow_plane_state base;
 	/* Intermediate buffer to convert pixels from XRGB8888 to HW format */
 	u8 *buffer;
-	/* Buffer to store pixels in HW format and written to the panel */
-	u8 *data_array;
 };
 
+static inline struct ssd130x_crtc_state *to_ssd130x_crtc_state(struct drm_crtc_state *state)
+{
+	return container_of(state, struct ssd130x_crtc_state, base);
+}
+
 static inline struct ssd130x_plane_state *to_ssd130x_plane_state(struct drm_plane_state *state)
 {
 	return container_of(state, struct ssd130x_plane_state, base.base);
@@ -448,13 +457,11 @@  static int ssd130x_init(struct ssd130x_device *ssd130x)
 }
 
 static int ssd130x_update_rect(struct ssd130x_device *ssd130x,
-			       struct ssd130x_plane_state *ssd130x_state,
-			       struct drm_rect *rect)
+			       struct drm_rect *rect, u8 *buf,
+			       u8 *data_array)
 {
 	unsigned int x = rect->x1;
 	unsigned int y = rect->y1;
-	u8 *buf = ssd130x_state->buffer;
-	u8 *data_array = ssd130x_state->data_array;
 	unsigned int width = drm_rect_width(rect);
 	unsigned int height = drm_rect_height(rect);
 	unsigned int line_length = DIV_ROUND_UP(width, 8);
@@ -550,12 +557,10 @@  static int ssd130x_update_rect(struct ssd130x_device *ssd130x,
 	return ret;
 }
 
-static void ssd130x_clear_screen(struct ssd130x_device *ssd130x,
-				 struct ssd130x_plane_state *ssd130x_state)
+static void ssd130x_clear_screen(struct ssd130x_device *ssd130x, u8 *data_array)
 {
 	unsigned int page_height = ssd130x->device_info->page_height;
 	unsigned int pages = DIV_ROUND_UP(ssd130x->height, page_height);
-	u8 *data_array = ssd130x_state->data_array;
 	unsigned int width = ssd130x->width;
 	int ret, i;
 
@@ -594,15 +599,13 @@  static void ssd130x_clear_screen(struct ssd130x_device *ssd130x,
 	}
 }
 
-static int ssd130x_fb_blit_rect(struct drm_plane_state *state,
+static int ssd130x_fb_blit_rect(struct drm_framebuffer *fb,
 				const struct iosys_map *vmap,
-				struct drm_rect *rect)
+				struct drm_rect *rect,
+				u8 *buf, u8 *data_array)
 {
-	struct drm_framebuffer *fb = state->fb;
 	struct ssd130x_device *ssd130x = drm_to_ssd130x(fb->dev);
 	unsigned int page_height = ssd130x->device_info->page_height;
-	struct ssd130x_plane_state *ssd130x_state = to_ssd130x_plane_state(state);
-	u8 *buf = ssd130x_state->buffer;
 	struct iosys_map dst;
 	unsigned int dst_pitch;
 	int ret = 0;
@@ -622,7 +625,7 @@  static int ssd130x_fb_blit_rect(struct drm_plane_state *state,
 
 	drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
 
-	ssd130x_update_rect(ssd130x, ssd130x_state, rect);
+	ssd130x_update_rect(ssd130x, rect, buf, data_array);
 
 	return ret;
 }
@@ -634,12 +637,19 @@  static int ssd130x_primary_plane_helper_atomic_check(struct drm_plane *plane,
 	struct ssd130x_device *ssd130x = drm_to_ssd130x(drm);
 	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
 	struct ssd130x_plane_state *ssd130x_state = to_ssd130x_plane_state(plane_state);
-	unsigned int page_height = ssd130x->device_info->page_height;
-	unsigned int pages = DIV_ROUND_UP(ssd130x->height, page_height);
+	struct drm_crtc *crtc = plane_state->crtc;
+	struct drm_crtc_state *crtc_state;
 	const struct drm_format_info *fi;
 	unsigned int pitch;
 	int ret;
 
+	if (!crtc)
+		return -EINVAL;
+
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+
 	ret = drm_plane_helper_atomic_check(plane, state);
 	if (ret)
 		return ret;
@@ -654,14 +664,6 @@  static int ssd130x_primary_plane_helper_atomic_check(struct drm_plane *plane,
 	if (!ssd130x_state->buffer)
 		return -ENOMEM;
 
-	ssd130x_state->data_array = kcalloc(ssd130x->width, pages, GFP_KERNEL);
-	if (!ssd130x_state->data_array) {
-		kfree(ssd130x_state->buffer);
-		/* Set to prevent a double free in .atomic_destroy_state() */
-		ssd130x_state->buffer = NULL;
-		return -ENOMEM;
-	}
-
 	return 0;
 }
 
@@ -671,6 +673,10 @@  static void ssd130x_primary_plane_helper_atomic_update(struct drm_plane *plane,
 	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
 	struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane);
 	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc);
+	struct ssd130x_crtc_state *ssd130x_crtc_state =  to_ssd130x_crtc_state(crtc_state);
+	struct ssd130x_plane_state *ssd130x_plane_state = to_ssd130x_plane_state(plane_state);
+	struct drm_framebuffer *fb = plane_state->fb;
 	struct drm_atomic_helper_damage_iter iter;
 	struct drm_device *drm = plane->dev;
 	struct drm_rect dst_clip;
@@ -687,7 +693,9 @@  static void ssd130x_primary_plane_helper_atomic_update(struct drm_plane *plane,
 		if (!drm_rect_intersect(&dst_clip, &damage))
 			continue;
 
-		ssd130x_fb_blit_rect(plane_state, &shadow_plane_state->data[0], &dst_clip);
+		ssd130x_fb_blit_rect(fb, &shadow_plane_state->data[0], &dst_clip,
+				     ssd130x_plane_state->buffer,
+				     ssd130x_crtc_state->data_array);
 	}
 
 	drm_dev_exit(idx);
@@ -698,13 +706,21 @@  static void ssd130x_primary_plane_helper_atomic_disable(struct drm_plane *plane,
 {
 	struct drm_device *drm = plane->dev;
 	struct ssd130x_device *ssd130x = drm_to_ssd130x(drm);
-	struct ssd130x_plane_state *ssd130x_state = to_ssd130x_plane_state(plane->state);
+	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
+	struct drm_crtc_state *crtc_state;
+	struct ssd130x_crtc_state *ssd130x_crtc_state;
 	int idx;
 
+	if (!plane_state->crtc)
+		return;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc);
+	ssd130x_crtc_state = to_ssd130x_crtc_state(crtc_state);
+
 	if (!drm_dev_enter(drm, &idx))
 		return;
 
-	ssd130x_clear_screen(ssd130x, ssd130x_state);
+	ssd130x_clear_screen(ssd130x, ssd130x_crtc_state->data_array);
 
 	drm_dev_exit(idx);
 }
@@ -737,9 +753,8 @@  static struct drm_plane_state *ssd130x_primary_plane_duplicate_state(struct drm_
 	if (!ssd130x_state)
 		return NULL;
 
-	/* The buffers are not duplicated and are allocated in .atomic_check */
+	/* The buffer is not duplicated and is allocated in .atomic_check */
 	ssd130x_state->buffer = NULL;
-	ssd130x_state->data_array = NULL;
 
 	new_shadow_plane_state = &ssd130x_state->base;
 
@@ -753,7 +768,6 @@  static void ssd130x_primary_plane_destroy_state(struct drm_plane *plane,
 {
 	struct ssd130x_plane_state *ssd130x_state = to_ssd130x_plane_state(state);
 
-	kfree(ssd130x_state->data_array);
 	kfree(ssd130x_state->buffer);
 
 	__drm_gem_destroy_shadow_plane_state(&ssd130x_state->base);
@@ -793,6 +807,75 @@  static enum drm_mode_status ssd130x_crtc_helper_mode_valid(struct drm_crtc *crtc
 	return MODE_OK;
 }
 
+static int ssd130x_crtc_helper_atomic_check(struct drm_crtc *crtc,
+					    struct drm_atomic_state *state)
+{
+	struct drm_device *drm = crtc->dev;
+	struct ssd130x_device *ssd130x = drm_to_ssd130x(drm);
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	struct ssd130x_crtc_state *ssd130x_state = to_ssd130x_crtc_state(crtc_state);
+	unsigned int page_height = ssd130x->device_info->page_height;
+	unsigned int pages = DIV_ROUND_UP(ssd130x->height, page_height);
+	int ret;
+
+	ret = drm_crtc_helper_atomic_check(crtc, state);
+	if (ret)
+		return ret;
+
+	ssd130x_state->data_array = kmalloc(ssd130x->width * pages, GFP_KERNEL);
+	if (!ssd130x_state->data_array)
+		return -ENOMEM;
+
+	return 0;
+}
+
+/* Called during init to allocate the CRTC's atomic state. */
+static void ssd130x_crtc_reset(struct drm_crtc *crtc)
+{
+	struct ssd130x_crtc_state *ssd130x_state;
+
+	WARN_ON(crtc->state);
+
+	ssd130x_state = kzalloc(sizeof(*ssd130x_state), GFP_KERNEL);
+	if (!ssd130x_state)
+		return;
+
+	__drm_atomic_helper_crtc_reset(crtc, &ssd130x_state->base);
+}
+
+static struct drm_crtc_state *ssd130x_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+	struct ssd130x_crtc_state *old_ssd130x_state;
+	struct ssd130x_crtc_state *ssd130x_state;
+
+	if (WARN_ON(!crtc->state))
+		return NULL;
+
+	old_ssd130x_state = to_ssd130x_crtc_state(crtc->state);
+	ssd130x_state = kmemdup(old_ssd130x_state, sizeof(*ssd130x_state), GFP_KERNEL);
+	if (!ssd130x_state)
+		return NULL;
+
+	/* The buffer is not duplicated and is allocated in .atomic_check */
+	ssd130x_state->data_array = NULL;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &ssd130x_state->base);
+
+	return &ssd130x_state->base;
+}
+
+static void ssd130x_crtc_destroy_state(struct drm_crtc *crtc,
+				       struct drm_crtc_state *state)
+{
+	struct ssd130x_crtc_state *ssd130x_state = to_ssd130x_crtc_state(state);
+
+	kfree(ssd130x_state->data_array);
+
+	__drm_atomic_helper_crtc_destroy_state(state);
+
+	kfree(ssd130x_state);
+}
+
 /*
  * The CRTC is always enabled. Screen updates are performed by
  * the primary plane's atomic_update function. Disabling clears
@@ -800,16 +883,16 @@  static enum drm_mode_status ssd130x_crtc_helper_mode_valid(struct drm_crtc *crtc
  */
 static const struct drm_crtc_helper_funcs ssd130x_crtc_helper_funcs = {
 	.mode_valid = ssd130x_crtc_helper_mode_valid,
-	.atomic_check = drm_crtc_helper_atomic_check,
+	.atomic_check = ssd130x_crtc_helper_atomic_check,
 };
 
 static const struct drm_crtc_funcs ssd130x_crtc_funcs = {
-	.reset = drm_atomic_helper_crtc_reset,
+	.reset = ssd130x_crtc_reset,
 	.destroy = drm_crtc_cleanup,
 	.set_config = drm_atomic_helper_set_config,
 	.page_flip = drm_atomic_helper_page_flip,
-	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
-	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.atomic_duplicate_state = ssd130x_crtc_duplicate_state,
+	.atomic_destroy_state = ssd130x_crtc_destroy_state,
 };
 
 static void ssd130x_encoder_helper_atomic_enable(struct drm_encoder *encoder,