diff mbox series

[v2,4/8] drm/mipi-dsi: Introduce mipi_dsi_*_write_seq_multi()

Message ID 20240426165839.v2.4.Ie94246c30fe95101e0e26dd5f96e976dbeb8f242@changeid (mailing list archive)
State New, archived
Headers show
Series drm/mipi-dsi: Reduce bloat and add funcs for cleaner init seqs | expand

Commit Message

Doug Anderson April 26, 2024, 11:58 p.m. UTC
The current mipi_dsi_*_write_seq() macros are non-intutitive because
they contain a hidden "return" statement that will return out of the
_caller_ of the macro. Let's mark them as deprecated and instead
introduce some new macros that are more intuitive.

These new macros are less optimal when an error occurs but should
behave more optimally when there is no error. Specifically these new
macros cause smaller code to get generated and the code size savings
(less to fetch from RAM, less cache space used, less RAM used) are
important. Since the error case isn't something we need to optimize
for and these new macros are easier to understand and more flexible,
they should be used.

After converting to use these new functions, one example shows some
nice savings while also being easier to understand.

$ scripts/bloat-o-meter \
  ...after/panel-novatek-nt36672e.ko \
  ...ctx/panel-novatek-nt36672e.ko
add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-988 (-988)
Function                                     old     new   delta
nt36672e_1080x2408_60hz_init                6236    5248    -988
Total: Before=10651, After=9663, chg -9.28%

Signed-off-by: Douglas Anderson <dianders@chromium.org>
---
Right now this patch introduces two new functions in
drm_mipi_dsi.c. Alternatively we could have changed the prototype of
the "chatty" functions and made the deprecated macros adapt to the new
prototype. While this sounds nice, it bloated callers of the
deprecated functioin a bit because it caused the compiler to emit less
optimal code. It doesn't seem terrible to add two more functions, so I
went that way.

Changes in v2:
- New

 drivers/gpu/drm/drm_mipi_dsi.c | 56 +++++++++++++++++++++++++++++++++
 include/drm/drm_mipi_dsi.h     | 57 ++++++++++++++++++++++++++++++++++
 2 files changed, 113 insertions(+)

Comments

Sam Ravnborg April 27, 2024, 6:32 a.m. UTC | #1
Hi Douglas.

On Fri, Apr 26, 2024 at 04:58:37PM -0700, Douglas Anderson wrote:
> The current mipi_dsi_*_write_seq() macros are non-intutitive because
> they contain a hidden "return" statement that will return out of the
> _caller_ of the macro. Let's mark them as deprecated and instead
> introduce some new macros that are more intuitive.
> 
> These new macros are less optimal when an error occurs but should
> behave more optimally when there is no error. Specifically these new
> macros cause smaller code to get generated and the code size savings
> (less to fetch from RAM, less cache space used, less RAM used) are
> important. Since the error case isn't something we need to optimize
> for and these new macros are easier to understand and more flexible,
> they should be used.

The whole ignore-and-return-early-if-an-error-occured concept is nice
and make this easy to understand and use. I have a few nits in the
following, but the overall concept is nice.

	Sam

> 
> After converting to use these new functions, one example shows some
> nice savings while also being easier to understand.
> 
> $ scripts/bloat-o-meter \
>   ...after/panel-novatek-nt36672e.ko \
>   ...ctx/panel-novatek-nt36672e.ko
> add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-988 (-988)
> Function                                     old     new   delta
> nt36672e_1080x2408_60hz_init                6236    5248    -988
> Total: Before=10651, After=9663, chg -9.28%
> 
> Signed-off-by: Douglas Anderson <dianders@chromium.org>
> ---
> Right now this patch introduces two new functions in
> drm_mipi_dsi.c. Alternatively we could have changed the prototype of
> the "chatty" functions and made the deprecated macros adapt to the new
> prototype. While this sounds nice, it bloated callers of the
> deprecated functioin a bit because it caused the compiler to emit less
> optimal code. It doesn't seem terrible to add two more functions, so I
> went that way.
The concern here is when it will be cleaned up. As a minimum please
consider adding an item to todo.rst that details what should be done
to get rid of the now obsolete chatty functions so someone can pick it
up.


> 
> Changes in v2:
> - New
> 
>  drivers/gpu/drm/drm_mipi_dsi.c | 56 +++++++++++++++++++++++++++++++++
>  include/drm/drm_mipi_dsi.h     | 57 ++++++++++++++++++++++++++++++++++
>  2 files changed, 113 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
> index 1e062eda3b88..b7c75a4396e6 100644
> --- a/drivers/gpu/drm/drm_mipi_dsi.c
> +++ b/drivers/gpu/drm/drm_mipi_dsi.c
> @@ -792,6 +792,34 @@ int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
>  }
>  EXPORT_SYMBOL(mipi_dsi_generic_write_chatty);
>  
> +/**
> + * mipi_dsi_generic_write_multi() - mipi_dsi_generic_write_chatty() w/ accum_err
> + * @ctx: Context for multiple DSI transactions
> + * @payload: buffer containing the payload
> + * @size: size of payload buffer
> + *
> + * Like mipi_dsi_generic_write_chatty() but deals with errors in a way that
> + * makes it convenient to make several calls in a row.
> + */
> +void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
> +				  const void *payload, size_t size)
> +{
> +	struct mipi_dsi_device *dsi = ctx->dsi;
> +	struct device *dev = &dsi->dev;
> +	ssize_t ret;
> +
> +	if (ctx->accum_err)
> +		return;
> +
> +	ret = mipi_dsi_generic_write(dsi, payload, size);
> +	if (ret < 0) {
> +		ctx->accum_err = ret;
> +		dev_err_ratelimited(dev, "sending generic data %*ph failed: %d\n",
> +				    (int)size, payload, ctx->accum_err);
> +	}
I see no point in using ratelimited and then change it in the next
patch.

> +}
> +EXPORT_SYMBOL(mipi_dsi_generic_write_multi);
> +
>  /**
>   * mipi_dsi_generic_read() - receive data using a generic read packet
>   * @dsi: DSI peripheral device
> @@ -908,6 +936,34 @@ int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi,
>  }
>  EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_chatty);
>  
> +/**
> + * mipi_dsi_dcs_write_buffer_multi - mipi_dsi_dcs_write_buffer_chatty() w/ accum_err
> + * @ctx: Context for multiple DSI transactions
> + * @data: buffer containing data to be transmitted
> + * @len: size of transmission buffer
> + *
> + * Like mipi_dsi_dcs_write_buffer_chatty() but deals with errors in a way that
> + * makes it convenient to make several calls in a row.
> + */
> +void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
> +				     const void *data, size_t len)
> +{
> +	struct mipi_dsi_device *dsi = ctx->dsi;
> +	struct device *dev = &dsi->dev;
> +	ssize_t ret;
> +
> +	if (ctx->accum_err)
> +		return;
> +
> +	ret = mipi_dsi_dcs_write_buffer(dsi, data, len);
> +	if (ret < 0) {
> +		ctx->accum_err = ret;
> +		dev_err_ratelimited(dev, "sending dcs data %*ph failed: %d\n",
> +				    (int)len, data, ctx->accum_err);
> +	}
> +}
> +EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_multi);
> +
>  /**
>   * mipi_dsi_dcs_write() - send DCS write command
>   * @dsi: DSI peripheral device
> diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
> index 6d68d9927f46..379594649a42 100644
> --- a/include/drm/drm_mipi_dsi.h
> +++ b/include/drm/drm_mipi_dsi.h
> @@ -197,6 +197,22 @@ struct mipi_dsi_device {
>  	struct drm_dsc_config *dsc;
>  };
>  
> +/**
> + * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
> + * @dsi: Pointer to the MIPI DSI device
> + * @accum_err: Storage for the accumulated error over the multiple calls. Init
> + *	to 0. If a function encounters an error then the error code will be
> + *	stored here. If you call a function and this points to a non-zero
> + *	value then the function will be a noop. This allows calling a function
> + *	many times in a row and just checking the error at the end to see if
> + *	any of them failed.
> + */
> +
> +struct mipi_dsi_multi_context {
> +	struct mipi_dsi_device *dsi;
> +	int accum_err;
> +};
Inline comments is - I think - preferred.



> +
>  #define MIPI_DSI_MODULE_PREFIX "mipi-dsi:"
>  
>  #define to_mipi_dsi_device(__dev)	container_of_const(__dev, struct mipi_dsi_device, dev)
> @@ -258,6 +274,8 @@ ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
>  			       size_t size);
>  int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
>  				  const void *payload, size_t size);
> +void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
> +				  const void *payload, size_t size);
>  ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
>  			      size_t num_params, void *data, size_t size);
>  
> @@ -283,6 +301,8 @@ ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi,
>  				  const void *data, size_t len);
>  int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi,
>  				     const void *data, size_t len);
> +void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
> +				     const void *data, size_t len);
>  ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd,
>  			   const void *data, size_t len);
>  ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data,
> @@ -319,6 +339,9 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi,
>   * This macro will print errors for you and will RETURN FROM THE CALLING
>   * FUNCTION (yes this is non-intuitive) upon error.
>   *
> + * Because of the non-intuitive return behavior, THIS MACRO IS DEPRECATED.
> + * Please replace calls of it with mipi_dsi_generic_write_seq_multi().
> + *
>   * @dsi: DSI peripheral device
>   * @seq: buffer containing the payload
>   */
> @@ -331,12 +354,30 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi,
>  			return ret;                                            \
>  	} while (0)
>  
> +/**
> + * mipi_dsi_generic_write_seq_multi - transmit data using a generic write packet
> + *
> + * This macro will print errors for you and error handling is optimized for
> + * callers that call this multiple times in a row.
> + *
> + * @ctx: Context for multiple DSI transactions
> + * @seq: buffer containing the payload
> + */
> +#define mipi_dsi_generic_write_seq_multi(ctx, seq...)                \
> +	do {                                                         \
> +		static const u8 d[] = { seq };                       \
> +		mipi_dsi_generic_write_multi(ctx, d, ARRAY_SIZE(d)); \
> +	} while (0)
> +
>  /**
>   * mipi_dsi_dcs_write_seq - transmit a DCS command with payload
>   *
>   * This macro will print errors for you and will RETURN FROM THE CALLING
>   * FUNCTION (yes this is non-intuitive) upon error.
>   *
> + * Because of the non-intuitive return behavior, THIS MACRO IS DEPRECATED.
> + * Please replace calls of it with mipi_dsi_dcs_write_seq_multi().
> + *
>   * @dsi: DSI peripheral device
>   * @cmd: Command
>   * @seq: buffer containing data to be transmitted
> @@ -350,6 +391,22 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi,
>  			return ret;                                            \
>  	} while (0)
>  
> +/**
> + * mipi_dsi_dcs_write_seq_multi - transmit a DCS command with payload
> + *
> + * This macro will print errors for you and error handling is optimized for
> + * callers that call this multiple times in a row.
> + *
> + * @ctx: Context for multiple DSI transactions
> + * @cmd: Command
> + * @seq: buffer containing data to be transmitted
> + */
> +#define mipi_dsi_dcs_write_seq_multi(ctx, cmd, seq...)                  \
> +	do {                                                            \
> +		static const u8 d[] = { cmd, seq };                     \
> +		mipi_dsi_dcs_write_buffer_multi(ctx, d, ARRAY_SIZE(d)); \
> +	} while (0)
> +
>  /**
>   * struct mipi_dsi_driver - DSI driver
>   * @driver: device driver model driver
> -- 
> 2.44.0.769.g3c40516874-goog
Neil Armstrong April 29, 2024, 9:38 a.m. UTC | #2
Hello Mister Anderson,

On 27/04/2024 01:58, Douglas Anderson wrote:
> The current mipi_dsi_*_write_seq() macros are non-intutitive because
> they contain a hidden "return" statement that will return out of the
> _caller_ of the macro. Let's mark them as deprecated and instead
> introduce some new macros that are more intuitive.
> 
> These new macros are less optimal when an error occurs but should
> behave more optimally when there is no error. Specifically these new
> macros cause smaller code to get generated and the code size savings
> (less to fetch from RAM, less cache space used, less RAM used) are
> important. Since the error case isn't something we need to optimize
> for and these new macros are easier to understand and more flexible,
> they should be used.
> 
> After converting to use these new functions, one example shows some
> nice savings while also being easier to understand.
> 
> $ scripts/bloat-o-meter \
>    ...after/panel-novatek-nt36672e.ko \
>    ...ctx/panel-novatek-nt36672e.ko
> add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-988 (-988)
> Function                                     old     new   delta
> nt36672e_1080x2408_60hz_init                6236    5248    -988
> Total: Before=10651, After=9663, chg -9.28%
> 
> Signed-off-by: Douglas Anderson <dianders@chromium.org>
> ---
> Right now this patch introduces two new functions in
> drm_mipi_dsi.c. Alternatively we could have changed the prototype of
> the "chatty" functions and made the deprecated macros adapt to the new
> prototype. While this sounds nice, it bloated callers of the
> deprecated functioin a bit because it caused the compiler to emit less
> optimal code. It doesn't seem terrible to add two more functions, so I
> went that way.
> 
> Changes in v2:
> - New
> 
>   drivers/gpu/drm/drm_mipi_dsi.c | 56 +++++++++++++++++++++++++++++++++
>   include/drm/drm_mipi_dsi.h     | 57 ++++++++++++++++++++++++++++++++++
>   2 files changed, 113 insertions(+)
> 
> diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
> index 1e062eda3b88..b7c75a4396e6 100644
> --- a/drivers/gpu/drm/drm_mipi_dsi.c
> +++ b/drivers/gpu/drm/drm_mipi_dsi.c
> @@ -792,6 +792,34 @@ int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
>   }
>   EXPORT_SYMBOL(mipi_dsi_generic_write_chatty);
>   

<snip>

>   };
>   
> +/**
> + * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
> + * @dsi: Pointer to the MIPI DSI device
> + * @accum_err: Storage for the accumulated error over the multiple calls. Init
> + *	to 0. If a function encounters an error then the error code will be
> + *	stored here. If you call a function and this points to a non-zero
> + *	value then the function will be a noop. This allows calling a function
> + *	many times in a row and just checking the error at the end to see if
> + *	any of them failed.
> + */
> +
> +struct mipi_dsi_multi_context {
> +	struct mipi_dsi_device *dsi;
> +	int accum_err;
> +};

I like the design, but having a context struct seems over-engineered while we could pass
a single int over without encapsulating it with mipi_dsi_multi_context.

void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
				     const void *data, size_t len);
vs
void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_device *dsi, int *accum_err,
				     const void *data, size_t len);

is the same, and it avoids having to declare a mipi_dsi_multi_context and set ctx->dsi,
and I'll find it much easier to migrate, just add a &ret and make sure ret is initialized to 0.


<snip>
Doug Anderson April 29, 2024, 2:13 p.m. UTC | #3
Hi,

On Fri, Apr 26, 2024 at 11:33 PM Sam Ravnborg <sam@ravnborg.org> wrote:
>
> > ---
> > Right now this patch introduces two new functions in
> > drm_mipi_dsi.c. Alternatively we could have changed the prototype of
> > the "chatty" functions and made the deprecated macros adapt to the new
> > prototype. While this sounds nice, it bloated callers of the
> > deprecated functioin a bit because it caused the compiler to emit less
> > optimal code. It doesn't seem terrible to add two more functions, so I
> > went that way.
> The concern here is when it will be cleaned up. As a minimum please
> consider adding an item to todo.rst that details what should be done
> to get rid of the now obsolete chatty functions so someone can pick it
> up.

Sure, I can add something to do TODO. Do folks agree that's the
preferred way to do things? A few thoughts I had:

1. In theory it could be useful to keep _both_ the "chatty" and
"multi" variants of the functions. In at least a few places the
"multi" variant was awkward. The resulting auo_kd101n80_45na_init()
[1] looked awkward. If I was writing just this function I would have
chosen an API more like the "chatty" one, but since I was trying to
make all the init functions similar I kept them all using the "multi"
API. Does it make sense to keep both long term?

[1] https://lore.kernel.org/all/20240426165839.v2.7.Ib5030ab5cd41b4e08b1958bd7e51571725723008@changeid/

2. Going completely the opposite direction, we could also not bother
saving as much space today and _force_ everyone to move to the new
"multi" API to get the full space savings.


So I guess three options: a) leave my patches the way they are and add
a TODO, b) Keep the "chatty" variants long term and remove my
"after-the-cut", or c) Don't get as much space savings today but don't
introduce a new exported function. Opinions?


> > @@ -792,6 +792,34 @@ int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
> >  }
> >  EXPORT_SYMBOL(mipi_dsi_generic_write_chatty);
> >
> > +/**
> > + * mipi_dsi_generic_write_multi() - mipi_dsi_generic_write_chatty() w/ accum_err
> > + * @ctx: Context for multiple DSI transactions
> > + * @payload: buffer containing the payload
> > + * @size: size of payload buffer
> > + *
> > + * Like mipi_dsi_generic_write_chatty() but deals with errors in a way that
> > + * makes it convenient to make several calls in a row.
> > + */
> > +void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
> > +                               const void *payload, size_t size)
> > +{
> > +     struct mipi_dsi_device *dsi = ctx->dsi;
> > +     struct device *dev = &dsi->dev;
> > +     ssize_t ret;
> > +
> > +     if (ctx->accum_err)
> > +             return;
> > +
> > +     ret = mipi_dsi_generic_write(dsi, payload, size);
> > +     if (ret < 0) {
> > +             ctx->accum_err = ret;
> > +             dev_err_ratelimited(dev, "sending generic data %*ph failed: %d\n",
> > +                                 (int)size, payload, ctx->accum_err);
> > +     }
> I see no point in using ratelimited and then change it in the next
> patch.

Sure, I'll re-order patches.


> > @@ -197,6 +197,22 @@ struct mipi_dsi_device {
> >       struct drm_dsc_config *dsc;
> >  };
> >
> > +/**
> > + * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
> > + * @dsi: Pointer to the MIPI DSI device
> > + * @accum_err: Storage for the accumulated error over the multiple calls. Init
> > + *   to 0. If a function encounters an error then the error code will be
> > + *   stored here. If you call a function and this points to a non-zero
> > + *   value then the function will be a noop. This allows calling a function
> > + *   many times in a row and just checking the error at the end to see if
> > + *   any of them failed.
> > + */
> > +
> > +struct mipi_dsi_multi_context {
> > +     struct mipi_dsi_device *dsi;
> > +     int accum_err;
> > +};
> Inline comments is - I think - preferred.

Sure, I'll update in the next version.
Doug Anderson April 29, 2024, 2:26 p.m. UTC | #4
Hi,

On Mon, Apr 29, 2024 at 2:38 AM Neil Armstrong
<neil.armstrong@linaro.org> wrote:
>
> > +/**
> > + * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
> > + * @dsi: Pointer to the MIPI DSI device
> > + * @accum_err: Storage for the accumulated error over the multiple calls. Init
> > + *   to 0. If a function encounters an error then the error code will be
> > + *   stored here. If you call a function and this points to a non-zero
> > + *   value then the function will be a noop. This allows calling a function
> > + *   many times in a row and just checking the error at the end to see if
> > + *   any of them failed.
> > + */
> > +
> > +struct mipi_dsi_multi_context {
> > +     struct mipi_dsi_device *dsi;
> > +     int accum_err;
> > +};
>
> I like the design, but having a context struct seems over-engineered while we could pass
> a single int over without encapsulating it with mipi_dsi_multi_context.
>
> void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
>                                      const void *data, size_t len);
> vs
> void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_device *dsi, int *accum_err,
>                                      const void *data, size_t len);
>
> is the same, and it avoids having to declare a mipi_dsi_multi_context and set ctx->dsi,
> and I'll find it much easier to migrate, just add a &ret and make sure ret is initialized to 0.

Yeah, I had the same reaction when Jani suggested the context style
[1] and I actually coded it up exactly as you suggest above. I then
changed my mind and went with the context. My motivation was that when
I tested it I found that using the context produced smaller code.
Specifically, from the description of this patch we see we end up
with:

Total: Before=10651, After=9663, chg -9.28%

...when I didn't have the context and I had the accum_err then instead
of getting ~9% smaller I believe it actually got ~0.5% bigger. This
makes some sense as the caller has to pass 4 parameters to each call
instead of 3.

It's not a giant size difference but it was at least some motivation
that helped push me in this direction. I'd also say that when I looked
at the code in the end the style grew on me. It's really not too
terrible to have one line in your functions that looks like:

struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi };

...and if that becomes the canonical way to do it then it's really
hard to accidentally forget to initialize the error value. With the
other API it's _very_ easy to forget to initialize the error value and
the compiler won't yell at you. It also makes it very obvious to the
caller that this function is doing something a little different than
most Linux APIs with that error return.

So I guess I'd say that I ended up being pretty happy with the
"context" even if it does feel a little over engineered and I'd argue
to keep it that way. That being said, if you feel strongly about it
then we can perhaps get others to chime in to see which style they
prefer? Let me know what you think.


[1] https://lore.kernel.org/r/8734r85tcf.fsf@intel.com
Jani Nikula April 29, 2024, 3:39 p.m. UTC | #5
On Mon, 29 Apr 2024, Doug Anderson <dianders@chromium.org> wrote:
> Hi,
>
> On Mon, Apr 29, 2024 at 2:38 AM Neil Armstrong
> <neil.armstrong@linaro.org> wrote:
>>
>> > +/**
>> > + * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
>> > + * @dsi: Pointer to the MIPI DSI device
>> > + * @accum_err: Storage for the accumulated error over the multiple calls. Init
>> > + *   to 0. If a function encounters an error then the error code will be
>> > + *   stored here. If you call a function and this points to a non-zero
>> > + *   value then the function will be a noop. This allows calling a function
>> > + *   many times in a row and just checking the error at the end to see if
>> > + *   any of them failed.
>> > + */
>> > +
>> > +struct mipi_dsi_multi_context {
>> > +     struct mipi_dsi_device *dsi;
>> > +     int accum_err;
>> > +};
>>
>> I like the design, but having a context struct seems over-engineered while we could pass
>> a single int over without encapsulating it with mipi_dsi_multi_context.
>>
>> void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
>>                                      const void *data, size_t len);
>> vs
>> void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_device *dsi, int *accum_err,
>>                                      const void *data, size_t len);
>>
>> is the same, and it avoids having to declare a mipi_dsi_multi_context and set ctx->dsi,
>> and I'll find it much easier to migrate, just add a &ret and make sure ret is initialized to 0.
>
> Yeah, I had the same reaction when Jani suggested the context style
> [1] and I actually coded it up exactly as you suggest above. I then
> changed my mind and went with the context. My motivation was that when
> I tested it I found that using the context produced smaller code.
> Specifically, from the description of this patch we see we end up
> with:
>
> Total: Before=10651, After=9663, chg -9.28%
>
> ...when I didn't have the context and I had the accum_err then instead
> of getting ~9% smaller I believe it actually got ~0.5% bigger. This
> makes some sense as the caller has to pass 4 parameters to each call
> instead of 3.
>
> It's not a giant size difference but it was at least some motivation
> that helped push me in this direction. I'd also say that when I looked
> at the code in the end the style grew on me. It's really not too
> terrible to have one line in your functions that looks like:
>
> struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi };
>
> ...and if that becomes the canonical way to do it then it's really
> hard to accidentally forget to initialize the error value. With the
> other API it's _very_ easy to forget to initialize the error value and
> the compiler won't yell at you. It also makes it very obvious to the
> caller that this function is doing something a little different than
> most Linux APIs with that error return.
>
> So I guess I'd say that I ended up being pretty happy with the
> "context" even if it does feel a little over engineered and I'd argue
> to keep it that way. That being said, if you feel strongly about it
> then we can perhaps get others to chime in to see which style they
> prefer? Let me know what you think.
>
>
> [1] https://lore.kernel.org/r/8734r85tcf.fsf@intel.com

FWIW, I don't feel strongly about this, and I could be persuaded either
way, but I've got this gut feeling that an extensible context parameter
might be benefitial future proofing in this case.

BR,
Jani.
Doug Anderson May 1, 2024, 3:49 p.m. UTC | #6
Hi,

On Mon, Apr 29, 2024 at 8:39 AM Jani Nikula <jani.nikula@linux.intel.com> wrote:
>
> On Mon, 29 Apr 2024, Doug Anderson <dianders@chromium.org> wrote:
> > Hi,
> >
> > On Mon, Apr 29, 2024 at 2:38 AM Neil Armstrong
> > <neil.armstrong@linaro.org> wrote:
> >>
> >> > +/**
> >> > + * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
> >> > + * @dsi: Pointer to the MIPI DSI device
> >> > + * @accum_err: Storage for the accumulated error over the multiple calls. Init
> >> > + *   to 0. If a function encounters an error then the error code will be
> >> > + *   stored here. If you call a function and this points to a non-zero
> >> > + *   value then the function will be a noop. This allows calling a function
> >> > + *   many times in a row and just checking the error at the end to see if
> >> > + *   any of them failed.
> >> > + */
> >> > +
> >> > +struct mipi_dsi_multi_context {
> >> > +     struct mipi_dsi_device *dsi;
> >> > +     int accum_err;
> >> > +};
> >>
> >> I like the design, but having a context struct seems over-engineered while we could pass
> >> a single int over without encapsulating it with mipi_dsi_multi_context.
> >>
> >> void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
> >>                                      const void *data, size_t len);
> >> vs
> >> void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_device *dsi, int *accum_err,
> >>                                      const void *data, size_t len);
> >>
> >> is the same, and it avoids having to declare a mipi_dsi_multi_context and set ctx->dsi,
> >> and I'll find it much easier to migrate, just add a &ret and make sure ret is initialized to 0.
> >
> > Yeah, I had the same reaction when Jani suggested the context style
> > [1] and I actually coded it up exactly as you suggest above. I then
> > changed my mind and went with the context. My motivation was that when
> > I tested it I found that using the context produced smaller code.
> > Specifically, from the description of this patch we see we end up
> > with:
> >
> > Total: Before=10651, After=9663, chg -9.28%
> >
> > ...when I didn't have the context and I had the accum_err then instead
> > of getting ~9% smaller I believe it actually got ~0.5% bigger. This
> > makes some sense as the caller has to pass 4 parameters to each call
> > instead of 3.
> >
> > It's not a giant size difference but it was at least some motivation
> > that helped push me in this direction. I'd also say that when I looked
> > at the code in the end the style grew on me. It's really not too
> > terrible to have one line in your functions that looks like:
> >
> > struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi };
> >
> > ...and if that becomes the canonical way to do it then it's really
> > hard to accidentally forget to initialize the error value. With the
> > other API it's _very_ easy to forget to initialize the error value and
> > the compiler won't yell at you. It also makes it very obvious to the
> > caller that this function is doing something a little different than
> > most Linux APIs with that error return.
> >
> > So I guess I'd say that I ended up being pretty happy with the
> > "context" even if it does feel a little over engineered and I'd argue
> > to keep it that way. That being said, if you feel strongly about it
> > then we can perhaps get others to chime in to see which style they
> > prefer? Let me know what you think.
> >
> >
> > [1] https://lore.kernel.org/r/8734r85tcf.fsf@intel.com
>
> FWIW, I don't feel strongly about this, and I could be persuaded either
> way, but I've got this gut feeling that an extensible context parameter
> might be benefitial future proofing in this case.

I've gone ahead and sent out a v3 where I still leave it as taking
`struct mipi_dsi_multi_context`. Neil: if you feel strongly about it,
I have no problem sending out a v4. I still think that the size
savings of using the context are a good "tiebreaking" factor in
choosing between the two styles... ;-)

-Doug
Dmitry Baryshkov May 1, 2024, 3:57 p.m. UTC | #7
On Wed, 1 May 2024 at 18:49, Doug Anderson <dianders@chromium.org> wrote:
>
> Hi,
>
> On Mon, Apr 29, 2024 at 8:39 AM Jani Nikula <jani.nikula@linux.intel.com> wrote:
> >
> > On Mon, 29 Apr 2024, Doug Anderson <dianders@chromium.org> wrote:
> > > Hi,
> > >
> > > On Mon, Apr 29, 2024 at 2:38 AM Neil Armstrong
> > > <neil.armstrong@linaro.org> wrote:
> > >>
> > >> > +/**
> > >> > + * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
> > >> > + * @dsi: Pointer to the MIPI DSI device
> > >> > + * @accum_err: Storage for the accumulated error over the multiple calls. Init
> > >> > + *   to 0. If a function encounters an error then the error code will be
> > >> > + *   stored here. If you call a function and this points to a non-zero
> > >> > + *   value then the function will be a noop. This allows calling a function
> > >> > + *   many times in a row and just checking the error at the end to see if
> > >> > + *   any of them failed.
> > >> > + */
> > >> > +
> > >> > +struct mipi_dsi_multi_context {
> > >> > +     struct mipi_dsi_device *dsi;
> > >> > +     int accum_err;
> > >> > +};
> > >>
> > >> I like the design, but having a context struct seems over-engineered while we could pass
> > >> a single int over without encapsulating it with mipi_dsi_multi_context.
> > >>
> > >> void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
> > >>                                      const void *data, size_t len);
> > >> vs
> > >> void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_device *dsi, int *accum_err,
> > >>                                      const void *data, size_t len);
> > >>
> > >> is the same, and it avoids having to declare a mipi_dsi_multi_context and set ctx->dsi,
> > >> and I'll find it much easier to migrate, just add a &ret and make sure ret is initialized to 0.
> > >
> > > Yeah, I had the same reaction when Jani suggested the context style
> > > [1] and I actually coded it up exactly as you suggest above. I then
> > > changed my mind and went with the context. My motivation was that when
> > > I tested it I found that using the context produced smaller code.
> > > Specifically, from the description of this patch we see we end up
> > > with:
> > >
> > > Total: Before=10651, After=9663, chg -9.28%
> > >
> > > ...when I didn't have the context and I had the accum_err then instead
> > > of getting ~9% smaller I believe it actually got ~0.5% bigger. This
> > > makes some sense as the caller has to pass 4 parameters to each call
> > > instead of 3.
> > >
> > > It's not a giant size difference but it was at least some motivation
> > > that helped push me in this direction. I'd also say that when I looked
> > > at the code in the end the style grew on me. It's really not too
> > > terrible to have one line in your functions that looks like:
> > >
> > > struct mipi_dsi_multi_context ctx = { .dsi = boe->dsi };
> > >
> > > ...and if that becomes the canonical way to do it then it's really
> > > hard to accidentally forget to initialize the error value. With the
> > > other API it's _very_ easy to forget to initialize the error value and
> > > the compiler won't yell at you. It also makes it very obvious to the
> > > caller that this function is doing something a little different than
> > > most Linux APIs with that error return.
> > >
> > > So I guess I'd say that I ended up being pretty happy with the
> > > "context" even if it does feel a little over engineered and I'd argue
> > > to keep it that way. That being said, if you feel strongly about it
> > > then we can perhaps get others to chime in to see which style they
> > > prefer? Let me know what you think.
> > >
> > >
> > > [1] https://lore.kernel.org/r/8734r85tcf.fsf@intel.com
> >
> > FWIW, I don't feel strongly about this, and I could be persuaded either
> > way, but I've got this gut feeling that an extensible context parameter
> > might be benefitial future proofing in this case.
>
> I've gone ahead and sent out a v3 where I still leave it as taking
> `struct mipi_dsi_multi_context`. Neil: if you feel strongly about it,
> I have no problem sending out a v4. I still think that the size
> savings of using the context are a good "tiebreaking" factor in
> choosing between the two styles... ;-)

I like the idea of context. If later on we need to add additional data
(like DSC PPS or drm_mode), we can simply extend the context
structure.

>
> -Doug
Linus Walleij May 6, 2024, 6:21 a.m. UTC | #8
On Mon, Apr 29, 2024 at 4:26 PM Doug Anderson <dianders@chromium.org> wrote:

> So I guess I'd say that I ended up being pretty happy with the
> "context" even if it does feel a little over engineered and I'd argue
> to keep it that way. That being said, if you feel strongly about it
> then we can perhaps get others to chime in to see which style they
> prefer? Let me know what you think.

I'm in favor of this design.
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c
index 1e062eda3b88..b7c75a4396e6 100644
--- a/drivers/gpu/drm/drm_mipi_dsi.c
+++ b/drivers/gpu/drm/drm_mipi_dsi.c
@@ -792,6 +792,34 @@  int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
 }
 EXPORT_SYMBOL(mipi_dsi_generic_write_chatty);
 
+/**
+ * mipi_dsi_generic_write_multi() - mipi_dsi_generic_write_chatty() w/ accum_err
+ * @ctx: Context for multiple DSI transactions
+ * @payload: buffer containing the payload
+ * @size: size of payload buffer
+ *
+ * Like mipi_dsi_generic_write_chatty() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
+				  const void *payload, size_t size)
+{
+	struct mipi_dsi_device *dsi = ctx->dsi;
+	struct device *dev = &dsi->dev;
+	ssize_t ret;
+
+	if (ctx->accum_err)
+		return;
+
+	ret = mipi_dsi_generic_write(dsi, payload, size);
+	if (ret < 0) {
+		ctx->accum_err = ret;
+		dev_err_ratelimited(dev, "sending generic data %*ph failed: %d\n",
+				    (int)size, payload, ctx->accum_err);
+	}
+}
+EXPORT_SYMBOL(mipi_dsi_generic_write_multi);
+
 /**
  * mipi_dsi_generic_read() - receive data using a generic read packet
  * @dsi: DSI peripheral device
@@ -908,6 +936,34 @@  int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi,
 }
 EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_chatty);
 
+/**
+ * mipi_dsi_dcs_write_buffer_multi - mipi_dsi_dcs_write_buffer_chatty() w/ accum_err
+ * @ctx: Context for multiple DSI transactions
+ * @data: buffer containing data to be transmitted
+ * @len: size of transmission buffer
+ *
+ * Like mipi_dsi_dcs_write_buffer_chatty() but deals with errors in a way that
+ * makes it convenient to make several calls in a row.
+ */
+void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
+				     const void *data, size_t len)
+{
+	struct mipi_dsi_device *dsi = ctx->dsi;
+	struct device *dev = &dsi->dev;
+	ssize_t ret;
+
+	if (ctx->accum_err)
+		return;
+
+	ret = mipi_dsi_dcs_write_buffer(dsi, data, len);
+	if (ret < 0) {
+		ctx->accum_err = ret;
+		dev_err_ratelimited(dev, "sending dcs data %*ph failed: %d\n",
+				    (int)len, data, ctx->accum_err);
+	}
+}
+EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_multi);
+
 /**
  * mipi_dsi_dcs_write() - send DCS write command
  * @dsi: DSI peripheral device
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
index 6d68d9927f46..379594649a42 100644
--- a/include/drm/drm_mipi_dsi.h
+++ b/include/drm/drm_mipi_dsi.h
@@ -197,6 +197,22 @@  struct mipi_dsi_device {
 	struct drm_dsc_config *dsc;
 };
 
+/**
+ * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row
+ * @dsi: Pointer to the MIPI DSI device
+ * @accum_err: Storage for the accumulated error over the multiple calls. Init
+ *	to 0. If a function encounters an error then the error code will be
+ *	stored here. If you call a function and this points to a non-zero
+ *	value then the function will be a noop. This allows calling a function
+ *	many times in a row and just checking the error at the end to see if
+ *	any of them failed.
+ */
+
+struct mipi_dsi_multi_context {
+	struct mipi_dsi_device *dsi;
+	int accum_err;
+};
+
 #define MIPI_DSI_MODULE_PREFIX "mipi-dsi:"
 
 #define to_mipi_dsi_device(__dev)	container_of_const(__dev, struct mipi_dsi_device, dev)
@@ -258,6 +274,8 @@  ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
 			       size_t size);
 int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi,
 				  const void *payload, size_t size);
+void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx,
+				  const void *payload, size_t size);
 ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
 			      size_t num_params, void *data, size_t size);
 
@@ -283,6 +301,8 @@  ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi,
 				  const void *data, size_t len);
 int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi,
 				     const void *data, size_t len);
+void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx,
+				     const void *data, size_t len);
 ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd,
 			   const void *data, size_t len);
 ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data,
@@ -319,6 +339,9 @@  int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi,
  * This macro will print errors for you and will RETURN FROM THE CALLING
  * FUNCTION (yes this is non-intuitive) upon error.
  *
+ * Because of the non-intuitive return behavior, THIS MACRO IS DEPRECATED.
+ * Please replace calls of it with mipi_dsi_generic_write_seq_multi().
+ *
  * @dsi: DSI peripheral device
  * @seq: buffer containing the payload
  */
@@ -331,12 +354,30 @@  int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi,
 			return ret;                                            \
 	} while (0)
 
+/**
+ * mipi_dsi_generic_write_seq_multi - transmit data using a generic write packet
+ *
+ * This macro will print errors for you and error handling is optimized for
+ * callers that call this multiple times in a row.
+ *
+ * @ctx: Context for multiple DSI transactions
+ * @seq: buffer containing the payload
+ */
+#define mipi_dsi_generic_write_seq_multi(ctx, seq...)                \
+	do {                                                         \
+		static const u8 d[] = { seq };                       \
+		mipi_dsi_generic_write_multi(ctx, d, ARRAY_SIZE(d)); \
+	} while (0)
+
 /**
  * mipi_dsi_dcs_write_seq - transmit a DCS command with payload
  *
  * This macro will print errors for you and will RETURN FROM THE CALLING
  * FUNCTION (yes this is non-intuitive) upon error.
  *
+ * Because of the non-intuitive return behavior, THIS MACRO IS DEPRECATED.
+ * Please replace calls of it with mipi_dsi_dcs_write_seq_multi().
+ *
  * @dsi: DSI peripheral device
  * @cmd: Command
  * @seq: buffer containing data to be transmitted
@@ -350,6 +391,22 @@  int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi,
 			return ret;                                            \
 	} while (0)
 
+/**
+ * mipi_dsi_dcs_write_seq_multi - transmit a DCS command with payload
+ *
+ * This macro will print errors for you and error handling is optimized for
+ * callers that call this multiple times in a row.
+ *
+ * @ctx: Context for multiple DSI transactions
+ * @cmd: Command
+ * @seq: buffer containing data to be transmitted
+ */
+#define mipi_dsi_dcs_write_seq_multi(ctx, cmd, seq...)                  \
+	do {                                                            \
+		static const u8 d[] = { cmd, seq };                     \
+		mipi_dsi_dcs_write_buffer_multi(ctx, d, ARRAY_SIZE(d)); \
+	} while (0)
+
 /**
  * struct mipi_dsi_driver - DSI driver
  * @driver: device driver model driver