Thread (8 messages) 8 messages, 3 authors, 3d ago

Re: [PATCH v3 3/3] input: misc: Add Qualcomm SPMI PMIC haptics driver

From: sashiko-bot@kernel.org
Date: 2026-07-14 05:34:31
Also in: linux-devicetree

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] AB-BA deadlock in the error path of haptics_start_fifo() between `h->fifo_lock` and `disable_irq()`.
- [High] Failure to write to the FIFO in the IRQ handler leaves the interrupt condition asserted, leading to an interrupt storm.
- [Medium] Stop requests for inactive effects incorrectly terminate the currently playing effect.
- [Low] Mixing `goto`-based error handling with `guard()` scope-based cleanup in `haptics_start_fifo()`.
--
--- Patch [3]: [PATCH v3 3/3] input: misc: Add Qualcomm SPMI PMIC haptics driver ---
commit 516c3c8b367d0b2e322ea97c78aee0907ed541f0
Author: Fenglin Wu [off-list ref]

    input: misc: Add Qualcomm SPMI PMIC haptics driver
    
    Add an initial driver for the Qualcomm PMIH0108 PMIC haptics module,
    named as HAP530_HV. This module supports several play modes, including
    DIRECT_PLAY, FIFO, PAT_MEM, and SWR, each with distinct data sourcing
    and hardware data handling logic.
quoted hunk ↗ jump to hunk
diff --git a/drivers/input/misc/qcom-spmi-haptics.c b/drivers/input/misc/qcom-spmi-haptics.c
new file mode 100644
index 0000000000000..ffbef7453459a
--- /dev/null
+++ b/drivers/input/misc/qcom-spmi-haptics.c
@@ -0,0 +1,1160 @@
[ ... ]
+static int haptics_start_fifo(struct qcom_haptics *h, int effect_id)
+{
+	struct qcom_haptics_effect *eff = &h->effects[effect_id];
+	u32 vmax = eff->vmax_mv ? eff->vmax_mv : h->vmax_mv;
+	u32 init_len;
+	bool data_done;
+	int ret;
+
+	if (!eff->fifo_data || !eff->data_len)
+		return -EINVAL;
+
+	ret = haptics_clear_faults(h);
+	if (ret)
+		return ret;
+
+	/* Disable auto-resonance for FIFO mode */
+	ret = cfg_update_bits(h, HAP_CFG_AUTORES_CFG_REG, AUTORES_EN_BIT, 0);
+	if (ret)
+		return ret;
+
+	ret = haptics_set_vmax(h, haptics_gain_scaled_vmax(h, vmax));
+	if (ret)
+		return ret;
+
+	ret = ptn_update_bits(h, HAP_PTN_FIFO_PLAY_RATE_REG,
+			      FIFO_PLAY_RATE_MASK,
+			      FIELD_PREP(FIFO_PLAY_RATE_MASK, eff->play_rate));
+	if (ret)
+		return ret;
+
+	/* Flush FIFO before loading new data */
+	ret = ptn_write(h, HAP_PTN_MEM_OP_ACCESS_REG, MEM_FLUSH_RELOAD_BIT);
+	if (ret)
+		return ret;
+	ret = ptn_write(h, HAP_PTN_MEM_OP_ACCESS_REG, 0);
+	if (ret)
+		return ret;
+
+	guard(mutex)(&h->fifo_lock);
+
+	/* Write the initial chunk and initialise streaming state */
+	init_len = min_t(u32, eff->data_len, FIFO_INIT_FILL);
+	ret = haptics_write_fifo_chunk(h, eff->fifo_data, init_len);
[Severity: Low]
Is it intended to mix goto-based error handling with scope-based guard()
cleanup here? The subsystem cleanup guidelines specify that functions should
use either goto-based cleanups or scope-based cleanups, but never both in
the same function.
+	if (ret)
+		return ret;
+
+	h->fifo_data    = eff->fifo_data;
+	h->data_len     = eff->data_len;
+	h->data_written = init_len;
+
+	/*
+	 * Set empty threshold.  When threshold > 0 the hardware fires the
+	 * FIFO-empty interrupt when occupancy drops below the threshold,
+	 * allowing the driver to refill.  A threshold of 0 disables the IRQ.
+	 */
+	data_done = (h->data_written >= h->data_len);
+	if (!data_done) {
+		ret = ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG,
+				FIFO_EMPTY_THRESH / FIFO_THRESH_LSB);
+		if (ret)
+			goto free_fifo;
+
+		haptics_fifo_irq_enable(h, true);
+	}
+
+	ret = cfg_write(h, HAP_CFG_SPMI_PLAY_REG,
+			 PLAY_EN_BIT | FIELD_PREP(PAT_SRC_MASK, PAT_SRC_FIFO));
+	if (ret) {
+		dev_err(h->dev, "trigger FIFO play failed, ret=%d\n", ret);
+		goto fail;
+	}
+
+	return 0;
+
+fail:
+	ptn_write(h, HAP_PTN_FIFO_EMPTY_CFG_REG, 0);
+	haptics_fifo_irq_enable(h, false);
+free_fifo:
+	h->fifo_data = NULL;
+	return ret;
+}
[Severity: High]
Can this sequence cause a deadlock? In haptics_start_fifo(), h->fifo_lock is
still held via the guard() scope when jumping to the fail label.

The code then calls haptics_fifo_irq_enable() which disables the IRQ
synchronously using disable_irq().

If the threaded IRQ handler haptics_fifo_empty_irq() is concurrently running,
it will block waiting for h->fifo_lock, while disable_irq() blocks waiting
for the handler to complete.

[ ... ]
+static irqreturn_t haptics_fifo_empty_irq(int irq, void *dev_id)
+{
+	struct qcom_haptics *h = dev_id;
+	u32 sts, to_write;
+	int ret;
+
+	ret = regmap_read(h->regmap,
+			  h->cfg_base + HAP_CFG_INT_RT_STS_REG, &sts);
+	if (ret || !(sts & FIFO_EMPTY_BIT))
+		return IRQ_HANDLED;
+
+	guard(mutex)(&h->fifo_lock);
+
+	if (!h->fifo_data)
+		return IRQ_HANDLED;
+
+	/* Refill: write the next chunk */
+	to_write = min_t(u32, h->data_len - h->data_written,
+			 h->fifo_len - FIFO_EMPTY_THRESH);
+	ret = haptics_write_fifo_chunk(h, &h->fifo_data[h->data_written], to_write);
+	if (ret) {
+		dev_err(h->dev, "refill FIFO samples failed, ret=%d\n", ret);
+		return IRQ_HANDLED;
+	}
[Severity: High]
If haptics_write_fifo_chunk() fails here, could this cause an interrupt
storm?

By returning IRQ_HANDLED without writing samples to the FIFO or disabling
the empty threshold via ptn_write(), the hardware FIFO occupancy remains
below the threshold.

Once the IRQ is unmasked upon return, the hardware will immediately trigger
the interrupt again.

[ ... ]
+static void haptics_play_work(struct work_struct *work)
+{
+	struct qcom_haptics *h = container_of(to_delayed_work(work),
+					      struct qcom_haptics, play_work);
+	struct haptics_play_req *req = haptics_dequeue_play_req(h);
+	bool rearmed = false;
+	u32 length_us;
+	int ret;
+
+	guard(mutex)(&h->play_lock);
+
+	if (req && req->play) {
+		ret = haptics_start_locked(h, req->effect_id);
+		if (ret) {
+			dev_err(h->dev, "play haptics failed, ret=%d\n", ret);
+		} else {
+			/*
+			 * Re-arm the work to stop the play or advance
+			 * to next play after the play duration
+			 */
+			length_us = h->effects[req->effect_id].length_us;
+			if (length_us) {
+				schedule_delayed_work(&h->play_work,
+						      usecs_to_jiffies(length_us));
+				rearmed = true;
+			}
+		}
+	} else {
+		haptics_stop_locked(h, false);
+	}
[Severity: Medium]
Will this unconditionally stop the active effect even if the stop request
was for a different effect?

Since the ff-core tracks multiple effects, an application might send a stop
command for an inactive effect. The else block here does not verify if
req->effect_id matches h->active_effect_id before terminating the hardware
playback.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260713-qcom-spmi-haptics-v3-0-c931bb7cb94f@oss.qualcomm.com?part=3
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help