From: Lee Jones <lee@kernel.org>
To: sashiko-reviews@lists.linux.dev
Cc: Fenglin Wu <fenglin.wu@oss.qualcomm.com>, linux-leds@vger.kernel.org
Subject: Re: [PATCH v5] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode
Date: Thu, 16 Jul 2026 14:07:21 +0100 [thread overview]
Message-ID: <20260716130721.GJ1260374@google.com> (raw)
In-Reply-To: <20260708051716.3C4241F000E9@smtp.kernel.org>
On Wed, 08 Jul 2026, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
>
> New issues:
> - [Medium] Mixing goto-based error handling with scope-based cleanup (__free and guard) in the same function.
Either provide a good explanation or fix please.
> Pre-existing issues:
> - [High] `lpg_lut_free()` fails to clear LUT slots for valid patterns of length 1, permanently leaking hardware LUT capacity.
> --
>
> --- Patch [1]: [PATCH v5] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode ---
> commit d02ed2e49b5e3fa47ddff854cf6f3e5653dde705
> Author: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
> Subject: leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode
>
> This commit scales the pattern data according to the sub-led intensity
> and programs the HW pattern separately for each LPG channel. This ensures
> the LED device respects intensity settings instead of always operating in
> a white-balanced mode.
>
> Link: https://patch.msgid.link/20260629-lpg-rgb-color-balance-fix-v4-1-4db8592fb3c5@oss.qualcomm.com
>
> > diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
> > index d7d6518de30f2..7ba4765e7cfc2 100644
> > --- a/drivers/leds/rgb/leds-qcom-lpg.c
> > +++ b/drivers/leds/rgb/leds-qcom-lpg.c
>
> [ ... ]
>
> > @@ -1087,43 +1098,64 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
> > * duration of all steps.
> > */
> > if (lpg->lut_base || lpg->lut_sdam) {
> > - lo_pause = pattern[0].delta_t;
> > - hi_pause = pattern[actual_len - 1].delta_t;
> > + prep->lo_pause = pattern[0].delta_t;
> > + prep->hi_pause = pattern[actual_len - 1].delta_t;
> > } else {
> > if (delta_t != pattern[0].delta_t || delta_t != pattern[actual_len - 1].delta_t)
> > - goto out_free_pattern;
> > + return -EINVAL;
> > + prep->lo_pause = 0;
> > + prep->hi_pause = 0;
> > }
> >
> > + prep->data = no_free_ptr(pattern);
> > + prep->len = actual_len;
> > + prep->delta_t = delta_t;
> > + prep->ping_pong = ping_pong;
>
> [Severity: High]
> This is a pre-existing issue, but does lpg_lut_free() fail to clear LUT
> slots for valid patterns of length 1?
>
> When a user writes a 4-element palindrome pattern (which translates to
> len == 2 internal steps), lpg_prepare_pattern() calculates:
>
> if (ping_pong)
> actual_len = (len + 1) / 2;
>
> making actual_len = 1.
>
> Later, when this pattern is cleared or an error path triggers, lpg_lut_free()
> checks:
>
> len = hi_idx - lo_idx + 1;
> if (len == 1)
> return;
>
> This early return seems to silently skip clearing the allocated bitmap slot,
> permanently leaking hardware LUT capacity over time.
>
> > + return 0;
> > +}
> > +
> > +static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
> > + u32 len, int repeat)
> > +{
> > + struct lpg_channel *chan;
> > + struct lpg *lpg = led->lpg;
> > + struct lpg_pattern pattern;
> > + unsigned int lo_idx;
> > + unsigned int hi_idx;
> > + unsigned int i;
> > + int ret;
> > +
> > + ret = lpg_prepare_pattern(lpg, led_pattern, len, repeat, &pattern);
> > + if (ret < 0)
> > + return ret;
> > +
> > + guard(mutex)(&lpg->lock);
> >
> > if (lpg->lut_base)
> > - ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx);
> > + ret = lpg_lut_store(lpg, pattern.data, pattern.len, &lo_idx, &hi_idx);
> > else
> > - ret = lpg_lut_store_sdam(lpg, pattern, actual_len, &lo_idx, &hi_idx);
> > + ret = lpg_lut_store_sdam(lpg, pattern.data, pattern.len, &lo_idx, &hi_idx);
> >
> > if (ret < 0)
> > - goto out_unlock;
> > + goto free_pattern;
> >
> > for (i = 0; i < led->num_channels; i++) {
> > chan = led->channels[i];
> >
> > - chan->ramp_tick_ms = delta_t;
> > - chan->ramp_ping_pong = ping_pong;
> > + chan->ramp_tick_ms = pattern.delta_t;
> > + chan->ramp_ping_pong = pattern.ping_pong;
> > chan->ramp_oneshot = repeat != -1;
> >
> > - chan->ramp_lo_pause_ms = lo_pause;
> > - chan->ramp_hi_pause_ms = hi_pause;
> > + chan->ramp_lo_pause_ms = pattern.lo_pause;
> > + chan->ramp_hi_pause_ms = pattern.hi_pause;
> >
> > chan->pattern_lo_idx = lo_idx;
> > chan->pattern_hi_idx = hi_idx;
> > }
> >
> > -out_unlock:
> > - mutex_unlock(&lpg->lock);
> > -out_free_pattern:
> > - kfree(pattern);
> > -
> > +free_pattern:
> > + kfree(pattern.data);
> > return ret;
> > }
>
> [Severity: Medium]
> Does this code mix goto-based error handling with scope-based cleanup?
>
> The cleanup subsystem guidelines suggest avoiding mixing goto labels
> (like free_pattern) and guard() declarations within the same function.
> Since pattern.data is manually managed here alongside the guarded mutex,
> this might make the code more prone to LIFO ordering mistakes or resource
> leaks during future refactoring.
>
> [ ... ]
>
> > @@ -1144,23 +1176,86 @@ static int lpg_pattern_single_set(struct led_classdev *cdev,
> > }
> >
> > static int lpg_pattern_mc_set(struct led_classdev *cdev,
> > - struct led_pattern *pattern, u32 len,
> > + struct led_pattern *led_pattern, u32 len,
> > int repeat)
> > {
> > struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
> > struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
> > + struct lpg *lpg = led->lpg;
> > + struct lpg_channel *chan;
> > + struct lpg_pattern pattern;
> > unsigned int triled_mask = 0;
> > - int ret, i;
> > + unsigned int lo_idx;
> > + unsigned int hi_idx;
> > + unsigned int scale;
> > + unsigned int i, j;
> > + int ret;
> > +
> > + ret = lpg_prepare_pattern(lpg, led_pattern, len, repeat, &pattern);
> > + if (ret < 0)
> > + return ret;
> > +
> > + /* Allocate buffer for the per-channel scaled pattern copy */
> > + struct led_pattern *scaled __free(kfree) =
> > + kmalloc_array(pattern.len, sizeof(*scaled), GFP_KERNEL);
> > + if (!scaled) {
> > + kfree(pattern.data);
> > + return -ENOMEM;
> > + }
> >
> > for (i = 0; i < led->num_channels; i++)
> > triled_mask |= led->channels[i]->triled_mask;
> > - triled_set(led->lpg, triled_mask, 0);
> > + triled_set(lpg, triled_mask, 0);
> >
> > - ret = lpg_pattern_set(led, pattern, len, repeat);
> > + led_mc_calc_color_components(mc, LED_FULL);
> > +
> > + /*
> > + * Each channel gets its own LUT block scaled by subled_info[i].brightness
> > + * so the pattern respects the configured colour balance.
> > + */
> > + guard(mutex)(&lpg->lock);
> > +
> > + for (i = 0; i < led->num_channels; i++) {
> > + chan = led->channels[i];
> > + scale = mc->subled_info[i].brightness;
> > +
> > + for (j = 0; j < pattern.len; j++) {
> > + scaled[j].brightness = DIV_ROUND_CLOSEST(
> > + (u32)pattern.data[j].brightness * scale, LED_FULL);
> > + scaled[j].delta_t = pattern.data[j].delta_t;
> > + }
> > +
> > + if (lpg->lut_base)
> > + ret = lpg_lut_store(lpg, scaled, pattern.len, &lo_idx, &hi_idx);
> > + else
> > + ret = lpg_lut_store_sdam(lpg, scaled, pattern.len, &lo_idx, &hi_idx);
> > +
> > + if (ret < 0) {
> > + /* Free LUT slots already allocated for previous channels */
> > + while (i-- > 0) {
> > + chan = led->channels[i];
> > + lpg_lut_free(lpg, chan->pattern_lo_idx, chan->pattern_hi_idx);
> > + chan->pattern_lo_idx = 0;
> > + chan->pattern_hi_idx = 0;
> > + }
> > +
> > + goto free_pattern;
> > + }
> > +
> > + chan->ramp_tick_ms = pattern.delta_t;
> > + chan->ramp_ping_pong = pattern.ping_pong;
> > + chan->ramp_oneshot = repeat != -1;
> > + chan->ramp_lo_pause_ms = pattern.lo_pause;
> > + chan->ramp_hi_pause_ms = pattern.hi_pause;
> > + chan->pattern_lo_idx = lo_idx;
> > + chan->pattern_hi_idx = hi_idx;
> > + }
> > +
> > +free_pattern:
> > + kfree(pattern.data);
> > if (ret < 0)
> > return ret;
> >
> > - led_mc_calc_color_components(mc, LED_FULL);
> > lpg_brightness_set(led, cdev, mc->subled_info);
> >
> > return 0;
>
> [Severity: Medium]
> Similarly here, does this function mix __free(), guard(), and a goto label?
>
> The __free(kfree) and guard(mutex) helpers are used alongside the
> free_pattern goto label to manually kfree pattern.data. The guidelines
> recommend converting all resources in a function to scope-based cleanup
> or none of them, to ensure consistent ownership semantics.
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260707-lpg-rgb-color-balance-fix-v5-1-99e2d73084fc@oss.qualcomm.com?part=1
--
Lee Jones
prev parent reply other threads:[~2026-07-16 13:07 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-08 5:07 [PATCH v5] leds: rgb: leds-qcom-lpg: Fix LED color balancing in HW pattern mode Fenglin Wu
2026-07-08 5:17 ` sashiko-bot
2026-07-16 13:07 ` Lee Jones [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260716130721.GJ1260374@google.com \
--to=lee@kernel.org \
--cc=fenglin.wu@oss.qualcomm.com \
--cc=linux-leds@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox