PWM regression causing failures with the pwm-atmel driver

From: Peter Rosin
Date: Mon May 22 2023 - 11:20:13 EST


Hi!

I have a device with a "sound card" that has an amplifier that needs
an extra boost when high amplification is requested. This extra
boost is controlled with a pwm-regulator.

As of commit c73a3107624d ("pwm: Handle .get_state() failures") this
device no longer works. I have tracked the problem to an unfortunate
interaction between the underlying PWM driver and the PWM core.

The driver is drivers/pwm/pwm-atmel.c which has difficulties getting
the period and/or duty_cycle from the HW when the PWM is not enabled.
Because of this, I think, the driver does not fill in .period and
.duty_cycle at all in atmel_pwm_get_state() unless the PWM is enabled.

However, the PWM core is not expecting these fields to be left as-is,
at least not in pwm_adjust_config(), and its local state variable on
the stack ends up with whatever crap was on the stack on entry for
these fields. That fails spectacularly when the function continues to
do math on these uninitialized values.

In particular, I find this in the kernel log when a bad kernel runs:
pwm-regulator: probe of reg-ana failed with error -22

Before commit c73a3107624d this was a silent failure, and the situation
"repaired itself" when the PWM was later reprogrammed, at least for my
case. After that commit, the failure is fatal and the "sound card"
fails to come up at all.


I see a couple of adjustments that could be made.

1. Zero out some fields in the driver:

@@ -390,4 +390,6 @@ static int atmel_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
state->enabled = true;
} else {
+ state->period = 0;
+ state->duty_cycle = 0;
state->enabled = false;
}


2. Don't try to "adjust" a disabled PWM:

@@ -656,7 +656,7 @@ int pwm_adjust_config(struct pwm_device *pwm)
* In either case, we setup the new period and polarity, and assign a
* duty cycle of 0.
*/
- if (!state.period) {
+ if (!state.enabled || !state.period) {
state.duty_cycle = 0;
state.period = pargs.period;
state.polarity = pargs.polarity;


3. Zero out the state before calling pwm_get_state:

@@ -115,7 +115,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
}

if (pwm->chip->ops->get_state) {
- struct pwm_state state;
+ struct pwm_state state = { .enabled = true };

err = pwm->chip->ops->get_state(pwm->chip, pwm, &state);
trace_pwm_get(pwm, &state, err);
@@ -448,7 +448,7 @@ static void pwm_apply_state_debug(struct pwm_device *pwm,
{
struct pwm_state *last = &pwm->last;
struct pwm_chip *chip = pwm->chip;
- struct pwm_state s1, s2;
+ struct pwm_state s1 = { .enabled = false }, s2;
int err;

if (!IS_ENABLED(CONFIG_PWM_DEBUG))
@@ -634,7 +634,7 @@ EXPORT_SYMBOL_GPL(pwm_capture);
*/
int pwm_adjust_config(struct pwm_device *pwm)
{
- struct pwm_state state;
+ struct pwm_state state = { .enabled = false };
struct pwm_args pargs;

pwm_get_args(pwm, &pargs);
@@ -1119,7 +1119,7 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)

for (i = 0; i < chip->npwm; i++) {
struct pwm_device *pwm = &chip->pwms[i];
- struct pwm_state state;
+ struct pwm_state state = { .enabled = false };

pwm_get_state(pwm, &state);



I have verified that any of the above approaches resolve the
regression but I don't know which approach is best? Maybe more
than one?

However:

Approach 1. will maybe clobber the saved pwm->state such that
it no longer works to get the period/duty_cycle if/when the
PWM is disabled? Maybe only for some corner case? But that might
be a significant corner case?

Approach 2. will maybe mess up some unrelated functionality?

Approach 3. is ugly, intrusive and is in all likelihood
incomplete. It also needs a rebase from the culprit commit.

Cheers,
Peter

#regzbot introduced c73a3107624d