Skip to content
1 change: 1 addition & 0 deletions drivers/misc/mei/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ config INTEL_MEI_GSC

config INTEL_MEI_VSC_HW
tristate "Intel visual sensing controller device transport driver"
depends on INTEL_MEI
depends on ACPI && SPI
depends on GPIOLIB || COMPILE_TEST
help
Expand Down
8 changes: 7 additions & 1 deletion drivers/misc/mei/platform-vsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ static int mei_vsc_hw_reset(struct mei_device *mei_dev, bool intr_enable)

vsc_tp_reset(hw->tp);

vsc_tp_intr_disable(hw->tp);
if (!intr_enable)
return 0;

return vsc_tp_init(hw->tp, mei_dev->dev);
}
Expand Down Expand Up @@ -379,6 +380,8 @@ static int mei_vsc_probe(struct platform_device *pdev)
err_cancel:
mei_cancel_work(mei_dev);

vsc_tp_register_event_cb(tp, NULL, NULL);

mei_disable_interrupts(mei_dev);

return ret;
Expand All @@ -387,11 +390,14 @@ static int mei_vsc_probe(struct platform_device *pdev)
static void mei_vsc_remove(struct platform_device *pdev)
{
struct mei_device *mei_dev = platform_get_drvdata(pdev);
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);

pm_runtime_disable(mei_dev->dev);

mei_stop(mei_dev);

vsc_tp_register_event_cb(hw->tp, NULL, NULL);

Comment on lines 397 to +400
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since VSC events are now delivered via vsc_tp's workqueue, mei_stop()/mei_reset() no longer implicitly synchronize with the event callback (it used to run in the IRQ thread). In mei_vsc_remove(), the callback is unset only after mei_stop(), leaving a window where a previously queued VSC event-work item can still run mei_vsc_event_cb() while teardown is in progress. Consider unsetting the callback before calling mei_stop() (and similarly in other teardown paths) so that any queued work will observe a NULL callback and exit quickly.

Copilot uses AI. Check for mistakes.
mei_disable_interrupts(mei_dev);

mei_deregister(mei_dev);
Expand Down
41 changes: 25 additions & 16 deletions drivers/misc/mei/vsc-tp.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
#include <linux/workqueue.h>

#include "vsc-tp.h"

Expand Down Expand Up @@ -72,12 +73,12 @@ struct vsc_tp {

atomic_t assert_cnt;
wait_queue_head_t xfer_wait;
struct work_struct event_work;

vsc_tp_event_cb_t event_notify;
void *event_notify_context;

/* used to protect command download */
struct mutex mutex;
struct mutex event_notify_mutex; /* protects event_notify + context */
struct mutex mutex; /* protects command download */
};

/* GPIO resources */
Expand All @@ -102,17 +103,19 @@ static irqreturn_t vsc_tp_isr(int irq, void *data)

wake_up(&tp->xfer_wait);

return IRQ_WAKE_THREAD;
schedule_work(&tp->event_work);

return IRQ_HANDLED;
}

static irqreturn_t vsc_tp_thread_isr(int irq, void *data)
static void vsc_tp_event_work(struct work_struct *work)
{
struct vsc_tp *tp = data;
struct vsc_tp *tp = container_of(work, struct vsc_tp, event_work);

guard(mutex)(&tp->event_notify_mutex);

if (tp->event_notify)
tp->event_notify(tp->event_notify_context);

return IRQ_HANDLED;
}

/* wakeup firmware and wait for response */
Expand Down Expand Up @@ -364,8 +367,6 @@ void vsc_tp_reset(struct vsc_tp *tp)
gpiod_set_value_cansleep(tp->wakeupfw, 1);

atomic_set(&tp->assert_cnt, 0);
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vsc_tp_reset() no longer re-enables the IRQ after the reset sequence (the enable_irq() call was dropped). Since the function still disables the IRQ earlier, this can leak IRQ disable-depth across reset/stop/resume flows and potentially leave the IRQ permanently disabled. Please ensure the IRQ enable/disable balance is preserved (e.g., manage IRQ state only via the MEI intr_enable/intr_disable ops, or otherwise restore the prior IRQ state after reset).

Suggested change
atomic_set(&tp->assert_cnt, 0);
atomic_set(&tp->assert_cnt, 0);
enable_irq(tp->spi->irq);

Copilot uses AI. Check for mistakes.

enable_irq(tp->spi->irq);
}
EXPORT_SYMBOL_NS_GPL(vsc_tp_reset, VSC_TP);

Expand Down Expand Up @@ -397,6 +398,8 @@ EXPORT_SYMBOL_NS_GPL(vsc_tp_need_read, VSC_TP);
int vsc_tp_register_event_cb(struct vsc_tp *tp, vsc_tp_event_cb_t event_cb,
void *context)
{
guard(mutex)(&tp->event_notify_mutex);

tp->event_notify = event_cb;
tp->event_notify_context = context;

Expand Down Expand Up @@ -504,7 +507,7 @@ static int vsc_tp_probe(struct spi_device *spi)
if (ret)
return ret;

tp->wakeuphost = devm_gpiod_get(dev, "wakeuphost", GPIOD_IN);
tp->wakeuphost = devm_gpiod_get(dev, "wakeuphostint", GPIOD_IN);
if (IS_ERR(tp->wakeuphost))
return PTR_ERR(tp->wakeuphost);

Expand All @@ -521,13 +524,15 @@ static int vsc_tp_probe(struct spi_device *spi)
tp->spi = spi;

irq_set_status_flags(spi->irq, IRQ_DISABLE_UNLAZY);
ret = request_threaded_irq(spi->irq, vsc_tp_isr, vsc_tp_thread_isr,
ret = request_threaded_irq(spi->irq, NULL, vsc_tp_isr,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
dev_name(dev), tp);
Comment on lines 526 to 529
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request_threaded_irq() is switched to run vsc_tp_isr() as the threaded handler, but vsc_tp_request_irq() (used by suspend/resume) still references the removed vsc_tp_thread_isr, which will cause a build failure. Please update vsc_tp_request_irq() to match the new IRQ registration pattern (primary handler NULL, threaded handler vsc_tp_isr()) or reintroduce the missing handler as appropriate.

Copilot uses AI. Check for mistakes.
if (ret)
return ret;

mutex_init(&tp->mutex);
mutex_init(&tp->event_notify_mutex);
INIT_WORK(&tp->event_work, vsc_tp_event_work);
Comment on lines 533 to +535
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In vsc_tp_probe(), the IRQ is requested before event_notify_mutex is initialized and before INIT_WORK(&tp->event_work, ...). Since the IRQ handler schedules tp->event_work, an interrupt firing immediately after request_threaded_irq() can queue/execute an uninitialized work item and use an uninitialized mutex. Initialize the mutex and work_struct before requesting the IRQ, or request the IRQ with IRQF_NO_AUTOEN and explicitly enable it after initialization.

Copilot uses AI. Check for mistakes.

/* only one child acpi device */
ret = acpi_dev_for_each_child(ACPI_COMPANION(dev),
Expand All @@ -550,10 +555,12 @@ static int vsc_tp_probe(struct spi_device *spi)
return 0;

err_destroy_lock:
mutex_destroy(&tp->mutex);

free_irq(spi->irq, tp);

cancel_work_sync(&tp->event_work);
mutex_destroy(&tp->event_notify_mutex);
mutex_destroy(&tp->mutex);

return ret;
}

Expand All @@ -563,9 +570,11 @@ static void vsc_tp_remove(struct spi_device *spi)

platform_device_unregister(tp->pdev);

mutex_destroy(&tp->mutex);

free_irq(spi->irq, tp);

cancel_work_sync(&tp->event_work);
mutex_destroy(&tp->event_notify_mutex);
mutex_destroy(&tp->mutex);
}

static const struct acpi_device_id vsc_tp_acpi_ids[] = {
Expand Down
Loading