Re: [PATCH v6 2/4] pwm: make it possible to apply pwm changes in atomic context

From: Thierry Reding
Date: Fri Dec 08 2023 - 11:20:02 EST


On Wed, Nov 29, 2023 at 09:13:35AM +0000, Sean Young wrote:
> Some pwm devices require sleeping, for example if the pwm device is
> connected over i2c. However, many pwm devices could be used from atomic
> context, e.g. memmory mapped pwm. This is useful for, for example, the
> pwm-ir-tx driver which requires precise timing. Sleeping causes havoc
> with the generated IR signal.
>
> Since not all pmw devices can support atomic context, we also add a
> pwm_is_atomic() function to check if it is supported.

s/i2c/I2C/ and s/pwm/PWM/ in the above. Also, s/memmory/memory/

>
> Signed-off-by: Sean Young <sean@xxxxxxxx>
> ---
> Documentation/driver-api/pwm.rst | 9 +++++
> drivers/pwm/core.c | 63 ++++++++++++++++++++++++++------
> drivers/pwm/pwm-renesas-tpu.c | 1 -
> include/linux/pwm.h | 29 ++++++++++++++-
> 4 files changed, 87 insertions(+), 15 deletions(-)
>
> diff --git a/Documentation/driver-api/pwm.rst b/Documentation/driver-api/pwm.rst
> index f1d8197c8c43..1d4536fdf47c 100644
> --- a/Documentation/driver-api/pwm.rst
> +++ b/Documentation/driver-api/pwm.rst
> @@ -43,6 +43,15 @@ After being requested, a PWM has to be configured using::
>
> int pwm_apply_might_sleep(struct pwm_device *pwm, struct pwm_state *state);
>
> +Some PWM devices can be used from atomic context. You can check if this is
> +supported with::
> +
> + bool pwm_is_atomic(struct pwm_device *pwm);

This is now going to look a bit odd. I think it'd be best to change this
to pwm_might_sleep() for consistency with the pwm_apply_might_sleep()
function. Fine to keep the actual member variable as atomic, though, so
we don't have to change the default for all drivers.

> +
> +If true, the PWM can be configured from atomic context with::
> +
> + int pwm_apply_atomic(struct pwm_device *pwm, struct pwm_state *state);
> +
> This API controls both the PWM period/duty_cycle config and the
> enable/disable state.
>
> diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
> index fc92ba622e56..63174e207400 100644
> --- a/drivers/pwm/core.c
> +++ b/drivers/pwm/core.c
> @@ -463,24 +463,15 @@ static void pwm_apply_debug(struct pwm_device *pwm,
> }
>
> /**
> - * pwm_apply_might_sleep() - atomically apply a new state to a PWM device
> + * pwm_apply_unchecked() - atomically apply a new state to a PWM device
> * @pwm: PWM device
> * @state: new state to apply
> */
> -int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
> +static int pwm_apply_unchecked(struct pwm_device *pwm, const struct pwm_state *state)
> {
> struct pwm_chip *chip;
> int err;
>
> - /*
> - * Some lowlevel driver's implementations of .apply() make use of
> - * mutexes, also with some drivers only returning when the new
> - * configuration is active calling pwm_apply_might_sleep() from atomic context
> - * is a bad idea. So make it explicit that calling this function might
> - * sleep.
> - */
> - might_sleep();
> -
> if (!pwm || !state || !state->period ||
> state->duty_cycle > state->period)
> return -EINVAL;
> @@ -501,16 +492,64 @@ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
>
> pwm->state = *state;
>
> + return 0;
> +}
> +
> +/**
> + * pwm_apply_might_sleep() - atomically apply a new state to a PWM device
> + * Cannot be used in atomic context.
> + * @pwm: PWM device
> + * @state: new state to apply
> + */
> +int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
> +{
> + int err;
> +
> + /*
> + * Some lowlevel driver's implementations of .apply() make use of
> + * mutexes, also with some drivers only returning when the new
> + * configuration is active calling pwm_apply_might_sleep() from atomic context
> + * is a bad idea. So make it explicit that calling this function might
> + * sleep.
> + */
> + might_sleep();
> +
> + if (IS_ENABLED(CONFIG_PWM_DEBUG) && pwm->chip->atomic) {
> + /*
> + * Catch any sleeping drivers when atomic is set.

I think this is slightly confusing. What we're really trying to catch
here is drivers that are marked as "atomic" but which still end up
sleeping during ->apply(), right? So maybe something like this would be
a bit more explicit:

/*
* Catch any drivers that have been marked as atomic but
* that will sleep anyway.
*/

> +/**
> + * pwm_apply_atomic() - apply a new state to a PWM device from atomic context
> + * Not all pwm devices support this function, check with pwm_is_atomic().

s/pwm/PWM/ here...

> + * @pwm: PWM device
> + * @state: new state to apply
> + */
> +int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
> +{
> + WARN_ONCE(!pwm->chip->atomic,
> + "sleeping pwm driver used in atomic context");

... and in the warning message as well.

> +
> + return pwm_apply_unchecked(pwm, state);
> +}
> +EXPORT_SYMBOL_GPL(pwm_apply_atomic);
> +
> /**
> * pwm_capture() - capture and report a PWM signal
> * @pwm: PWM device
> diff --git a/drivers/pwm/pwm-renesas-tpu.c b/drivers/pwm/pwm-renesas-tpu.c
> index 4239f2c3e8b2..47ea92cd8c67 100644
> --- a/drivers/pwm/pwm-renesas-tpu.c
> +++ b/drivers/pwm/pwm-renesas-tpu.c
> @@ -11,7 +11,6 @@
> #include <linux/init.h>
> #include <linux/ioport.h>
> #include <linux/module.h>
> -#include <linux/mutex.h>
> #include <linux/of.h>
> #include <linux/platform_device.h>
> #include <linux/pm_runtime.h>
> diff --git a/include/linux/pwm.h b/include/linux/pwm.h
> index 5c5c456948a4..f1fa1243e95a 100644
> --- a/include/linux/pwm.h
> +++ b/include/linux/pwm.h
> @@ -286,6 +286,7 @@ struct pwm_ops {
> * @npwm: number of PWMs controlled by this chip
> * @of_xlate: request a PWM device given a device tree PWM specifier
> * @of_pwm_n_cells: number of cells expected in the device tree PWM specifier
> + * @atomic: can the driver call pwm_apply_atomic in atomic context

Maybe: "can the driver's ->apply() be called in atomic context"?

> * @list: list node for internal use
> * @pwms: array of PWM devices allocated by the framework
> */
> @@ -299,6 +300,7 @@ struct pwm_chip {
> struct pwm_device * (*of_xlate)(struct pwm_chip *chip,
> const struct of_phandle_args *args);
> unsigned int of_pwm_n_cells;
> + bool atomic;
>
> /* only used internally by the PWM framework */
> struct list_head list;
> @@ -308,6 +310,7 @@ struct pwm_chip {
> #if IS_ENABLED(CONFIG_PWM)
> /* PWM user APIs */
> int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state);
> +int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state);
> int pwm_adjust_config(struct pwm_device *pwm);
>
> /**
> @@ -378,6 +381,17 @@ static inline void pwm_disable(struct pwm_device *pwm)
> pwm_apply_might_sleep(pwm, &state);
> }
>
> +/**
> + * pwm_is_atomic() - is pwm_apply_atomic() supported?
> + * @pwm: PWM device
> + *
> + * Returns: true pwm_apply_atomic() can be called from atomic context.
> + */
> +static inline bool pwm_is_atomic(struct pwm_device *pwm)

As I mentioned above, I think it'd be more consistent to call this
pwm_might_sleep().

> +{
> + return pwm->chip->atomic;
> +}
> +
> /* PWM provider APIs */
> int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
> unsigned long timeout);
> @@ -406,16 +420,27 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev,
> struct fwnode_handle *fwnode,
> const char *con_id);
> #else
> +static inline bool pwm_is_atomic(struct pwm_device *pwm)
> +{
> + return false;
> +}
> +
> static inline int pwm_apply_might_sleep(struct pwm_device *pwm,
> const struct pwm_state *state)
> {
> might_sleep();
> - return -ENOTSUPP;
> + return -EOPNOTSUPP;
> +}
> +
> +static inline int pwm_apply_atomic(struct pwm_device *pwm,
> + const struct pwm_state *state)
> +{
> + return -EOPNOTSUPP;
> }
>
> static inline int pwm_adjust_config(struct pwm_device *pwm)
> {
> - return -ENOTSUPP;
> + return -EOPNOTSUPP;
> }

What's wrong with ENOTSUPP?

Thierry

Attachment: signature.asc
Description: PGP signature