From: sashiko-bot@kernel.org
To: "Emfox Zhou" <emfox@debian.org>
Cc: linux-input@vger.kernel.org, dmitry.torokhov@gmail.com
Subject: Re: [PATCH v3 2/5] HID: intel-thc-hid: intel-ipts: receive reports
Date: Sat, 14 Nov 2026 19:10:28 +0000 [thread overview]
Message-ID: <sashiko-outbox-214954@kernel.org> (raw)
In-Reply-To: <20261114183624.2379573-3-emfox@debian.org>
Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [Medium] HID: intel-thc-hid: intel-ipts: false IRQ_HANDLED in
thc_ipts_hardirq()
- [Medium] HID: intel-thc-hid: intel-ipts: shared IRQ race unmasks
thc_ipts_thread()
- [Medium] HID: intel-thc-hid: intel-ipts: stopping race in
thc_ipts_port_stop()
- [Medium] HID: intel-thc-hid: intel-ipts: PRD descriptors not reset in
thc_ipts_recover()
- [Medium] HID: intel-ipts: thc_dma_write() still stalls when recovery
masks IRQ
--
commit b17ea629fa85b2d8eee1f00ee19bff9913e9e83b
Author: Emfox Zhou <emfox@debian.org>
HID: intel-thc-hid: intel-ipts: receive reports
This commit configures the RXDMA2 and TXDMA engines on the THC port,
registers a shared threaded interrupt handler to receive and log incoming
frames, and adds a recovery work item to reset RXDMA on transfer errors.
> diff --git a/drivers/hid/intel-thc-hid/intel-ipts/ipts.c b/drivers/hid/intel-thc-hid/intel-ipts/ipts.c
> index 264479630ff6e..ac0074a85bfa4 100644
> --- a/drivers/hid/intel-thc-hid/intel-ipts/ipts.c
> +++ b/drivers/hid/intel-thc-hid/intel-ipts/ipts.c
[ ... ]
> @@ -127,6 +139,209 @@ static int thc_ipts_touch_set(struct thc_ipts *idev, bool enable)
[ ... ]
> +irqreturn_t thc_ipts_hardirq(int irq, void *data)
> +{
> + struct thc_ipts *idev = data;
> +
> + /*
> + * The pin is shared and level triggered: mask first, or it stays
> + * asserted on the way out. The cause itself can only be read in the
> + * thread.
> + */
> + thc_interrupt_enable(idev->thc, false);
> +
> + /* A reset in flight owns the engine, and the interrupt is ours. */
> + if (READ_ONCE(idev->recovering))
> + return IRQ_HANDLED;
> +
> + /*
> + * Nothing of ours is pending in these two states and the port is
> + * masked: the interrupt belongs to another function on the line.
> + */
> + if (READ_ONCE(idev->stopping) || !READ_ONCE(idev->dma_ready))
> + return IRQ_NONE;
> +
> + return IRQ_WAKE_THREAD;
> +}
[Severity: Medium]
Should thc_ipts_hardirq() and thc_ipts_thread() return IRQ_NONE instead of
IRQ_HANDLED when idev->recovering is set?
idev->recovering is only set in thc_ipts_thread() after thc_ipts_hardirq()
has already masked the port via thc_interrupt_enable(idev->thc, false),
and the port remains masked until thc_ipts_recover() finishes. While
masked, this port cannot assert the shared INTx line, so any interrupt
arriving during recovery comes from another device on the shared pin.
Furthermore, if thc_rxdma_reset() fails in thc_ipts_recover() (or if
thc_ipts_port_stop() cancels recover_work), idev->recovering stays true
forever and causes thc_ipts_hardirq() to claim every subsequent interrupt
on the shared line, defeating spurious-interrupt detection.
[ ... ]
> +void thc_ipts_recover(struct work_struct *work)
> +{
> + struct thc_ipts *idev = container_of(work, struct thc_ipts,
> + recover_work);
> +
> + if (thc_rxdma_reset(idev->thc)) {
> + /* Nothing left: keep the port masked until it restarts. */
> + dev_err(idev->dev, "cannot reset the receive engine\n");
> + return;
> + }
[Severity: Medium]
Does thc_rxdma_reset() leave mutated PRD descriptors in the RXDMA2 ring
after an error?
When RXDMA2 writes a frame into a PRD table, the hardware updates each
consumed entry's len, sets end_of_prd on the final entry, and sets the
PRD table hw_status. During normal operation, thc_dma_read() calls
thc_copy_one_sgl_to_prd() to restore those fields before advancing the
write pointer. When thc_ipts_thread() sets need_recover on a transfer
error and skips thc_ipts_read_frames() (or when read_dma_buffer() fails
before thc_copy_one_sgl_to_prd() runs), thc_rxdma_reset() only calls
thc_dma_unconfigure() and thc_dma_configure(), which reset the hardware
ring pointers without calling thc_copy_sgls_to_prd(). Those unconsumed
PRD tables retain truncated entry lengths and stale end_of_prd bits, so
later larger frames can trigger PRD buffer overrun errors.
> +
> + WRITE_ONCE(idev->recovering, false);
> + if (!READ_ONCE(idev->stopping))
> + thc_interrupt_enable(idev->thc, true);
> +}
> +
> +irqreturn_t thc_ipts_thread(int irq, void *data)
> +{
[ ... ]
> + /*
> + * The work item resets the engine and unmasks the port again; a frame
> + * read while it rewrites the ring would corrupt the descriptors, so
> + * ->recovering keeps the handler off the line until it is done.
> + */
> + if (need_recover) {
> + WRITE_ONCE(idev->recovering, true);
> + schedule_work(&idev->recover_work);
> + return IRQ_HANDLED;
> + }
[Severity: Medium]
Can a concurrent thc_dma_write() still stall until its timeout when
recovery masks the port?
thc_rxdma_reset() is deferred to recover_work because thc_dma_write()
holds dev->thc_bus_lock while waiting on dev->write_complete_wait for
thc_interrupt_handler() to process THC_M_PRT_WRITE_INT_STS_THC_WRDMA_CMPL_STATUS.
However, when need_recover is true, thc_ipts_thread() sets idev->recovering
and returns with the port interrupt still masked, and both
thc_ipts_hardirq() and thc_ipts_thread() ignore further interrupts until
thc_ipts_recover() finishes. If an in-flight TxDMA transfer completes
after the polling loop in thc_ipts_thread() exits (or if a caller enters
thc_dma_write() and takes dev->thc_bus_lock before recover_work runs),
thc_ipts_recover() blocks on dev->thc_bus_lock while the TxDMA completion
interrupt stays masked, stalling thc_dma_write() for its full timeout.
> +
> + if (!READ_ONCE(idev->stopping))
> + thc_interrupt_enable(idev->thc, true);
> +
> + /* An interrupt with no cause of ours is not ours to claim. */
> + return ret;
> +}
[Severity: Medium]
Can a shared interrupt race with port unmasking and run thc_ipts_thread()
while the port interrupt is enabled?
thc_ipts_probe() requests the interrupt with IRQF_SHARED |
IRQF_COND_ONESHOT, which leaves IRQS_ONESHOT unset when no earlier handler
on the line requested IRQF_ONESHOT, so thc_ipts_hardirq() can run on
another CPU while thc_ipts_thread() is executing:
CPU0 CPU1
thc_ipts_thread()
for (pass = 0; ...)
thc_interrupt_handler()
<hardirq: shared INTx>
thc_ipts_hardirq()
thc_interrupt_enable(thc, false)
return IRQ_WAKE_THREAD
(sets IRQTF_RUNTHREAD)
thc_interrupt_enable(thc, true)
thc_ipts_thread() <-- runs with port unmasked
The same inverted state also occurs even with IRQS_ONESHOT if a shared
interrupt fires between WRITE_ONCE(idev->recovering, false) and
thc_interrupt_enable(idev->thc, true) in thc_ipts_recover(), or between
WRITE_ONCE(idev->dma_ready, true) and thc_interrupt_enable(idev->thc, true)
in thc_ipts_port_start(). If that second thc_ipts_thread() run hits an
error and takes the need_recover path, neither it nor thc_ipts_recover()
masks the port, so thc_rxdma_reset() runs with interrupts enabled on the
port.
[ ... ]
> @@ -220,6 +451,16 @@ void thc_ipts_port_stop(struct thc_ipts *idev)
> if (!idev->thc)
> return;
>
> + /*
> + * Mask the line, then wait for a handler that may be holding a
> + * frame for the HID device which is about to go away.
> + */
> + WRITE_ONCE(idev->stopping, true);
> + thc_interrupt_enable(idev->thc, false);
> + synchronize_irq(idev->irq);
> + cancel_work_sync(&idev->recover_work);
[Severity: Medium]
Can thc_ipts_thread() or thc_ipts_recover() re-enable interrupts during
thc_ipts_port_stop()?
CPU0 CPU1
thc_ipts_thread()
if (!READ_ONCE(idev->stopping))
thc_ipts_port_stop()
WRITE_ONCE(idev->stopping, true)
thc_interrupt_enable(thc, false)
thc_interrupt_enable(thc, true) <-- re-enables IRQ during stop
synchronize_irq(idev->irq)
cancel_work_sync(&idev->recover_work)
Because thc_ipts_port_stop() masks the port before synchronize_irq() and
cancel_work_sync(), an in-flight thread or recovery work item that has
already passed its !READ_ONCE(idev->stopping) check will unmask the port
again, leaving THC_M_PRT_INT_EN_GBL_INT_EN set while the controller and
DMA buffers are torn down. Calling thc_interrupt_enable(idev->thc, false)
after synchronize_irq() and cancel_work_sync() as well would close this
window.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261114183624.2379573-1-emfox@debian.org?part=2
next prev parent reply other threads:[~2026-11-14 19:10 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 18:36 [PATCH v3 0/5] HID: intel-thc-hid: intel-ipts: support THC IPTS ports Emfox Zhou
2026-11-14 18:36 ` [PATCH v3 1/5] HID: intel-thc-hid: intel-ipts: bring up the port Emfox Zhou
2026-11-14 18:55 ` sashiko-bot
2026-11-14 18:36 ` [PATCH v3 2/5] HID: intel-thc-hid: intel-ipts: receive reports Emfox Zhou
2026-11-14 19:10 ` sashiko-bot [this message]
2026-11-14 18:36 ` [PATCH v3 3/5] HID: intel-thc-hid: intel-ipts: register the HID device Emfox Zhou
2026-11-14 18:59 ` sashiko-bot
2026-11-14 18:36 ` [PATCH v3 4/5] HID: intel-thc-hid: intel-ipts: handle system sleep Emfox Zhou
2026-11-14 19:02 ` sashiko-bot
2026-11-14 18:36 ` [PATCH v3 5/5] HID: intel-thc-hid: intel-ipts: document the IPTS mode Emfox Zhou
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=sashiko-outbox-214954@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=dmitry.torokhov@gmail.com \
--cc=emfox@debian.org \
--cc=linux-input@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