mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-16 05:31:37 -04:00
Dismantle class device last in probe error flow to avoid accessing
freed memory like:
[ 87.926774] WARNING: CPU: 9 PID: 518 at kernel/workqueue.c:4234
__flush_work+0x340/0x390
...
[ 87.926912] Workqueue: async async_run_entry_fn
[ 87.926918] RIP: e030:__flush_work+0x340/0x390
[ 87.926923] Code: 26 9d 05 00 65 48 8b 15 26 3c ca 02 48 85 db 48 8b
04 24 48 89 54 24 58 0f 85 de fe ff ff e9 f6 fd ff ff 0f 0b e9 77 ff ff
ff <0f> 0b e9 70 ff ff ff 0f 0b e9 19 ff ff ff e8 7d 8b 0e 01 48 89 de
[ 87.926931] RSP: e02b:ffffc900412ebc00 EFLAGS: 00010246
[ 87.926936] RAX: 0000000000000000 RBX: ffff888103e55090 RCX: 0000000000000000
[ 87.926941] RDX: 000fffffffe00000 RSI: 0000000000000001 RDI: ffffc900412ebc60
[ 87.926945] RBP: ffff888103e55090 R08: ffffffffc1266ec8 R09: ffff8881109076e8
[ 87.926949] R10: 0000000080040003 R11: 0000000000000000 R12: ffff888103e54000
[ 87.926953] R13: ffffc900412ebc18 R14: 0000000000000001 R15: 0000000000000000
[ 87.926962] FS: 0000000000000000(0000) GS:ffff888233238000(0000) knlGS:0000000000000000
[ 87.926967] CS: e030 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 87.926971] CR2: 00007e7923b32708 CR3: 00000001088df000 CR4: 0000000000050660
[ 87.926977] Call Trace:
[ 87.926981] <TASK>
[ 87.926987] ? __call_rcu_common.constprop.0+0x11e/0x310
[ 87.926993] cancel_work_sync+0x5e/0x80
[ 87.926999] mei_cancel_work+0x19/0x40 [mei]
[ 87.927051] mei_me_probe+0x273/0x2b0 [mei_me]
[ 87.927060] local_pci_probe+0x45/0x90
[ 87.927066] pci_call_probe+0x5b/0x180
[ 87.927070] pci_device_probe+0x95/0x140
[ 87.927074] ? driver_sysfs_add+0x57/0xc0
[ 87.927079] really_probe+0xde/0x340
[ 87.927083] ? pm_runtime_barrier+0x54/0x90
[ 87.927087] __driver_probe_device+0x78/0x110
[ 87.927092] driver_probe_device+0x1f/0xa0
[ 87.927095] __driver_attach_async_helper+0x5e/0xe0
[ 87.927100] async_run_entry_fn+0x34/0x130
[ 87.927104] process_one_work+0x18d/0x340
[ 87.927108] worker_thread+0x256/0x3a0
[ 87.927111] ? __pfx_worker_thread+0x10/0x10
[ 87.927115] kthread+0xfc/0x240
[ 87.927120] ? __pfx_kthread+0x10/0x10
[ 87.927124] ? __pfx_kthread+0x10/0x10
[ 87.927127] ret_from_fork+0xf5/0x110
[ 87.927132] ? __pfx_kthread+0x10/0x10
[ 87.927136] ret_from_fork_asm+0x1a/0x30
[ 87.927141] </TASK>
Tested-by: Guenter Roeck <groeck@google.com>
Reported-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
Closes: https://lore.kernel.org/lkml/aQbYAXPADqfiXUYO@mail-itl/
Reported-by: Guenter Roeck <linux@roeck-us.net>
Closes: https://lore.kernel.org/lkml/8deef7c4-ac75-4db8-91b7-02cf0e39e371@roeck-us.net/
Fixes: 7704e6be4e ("mei: hook mei_device on class device")
Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com>
Tested-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
Link: https://patch.msgid.link/20251102180836.1203314-1-alexander.usyskin@intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
460 lines
10 KiB
C
460 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2023, Intel Corporation.
|
|
* Intel Visual Sensing Controller Interface Linux driver
|
|
*/
|
|
|
|
#include <linux/align.h>
|
|
#include <linux/cache.h>
|
|
#include <linux/cleanup.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mei.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/overflow.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/timekeeping.h>
|
|
#include <linux/types.h>
|
|
|
|
#include <asm-generic/bug.h>
|
|
#include <linux/unaligned.h>
|
|
|
|
#include "mei_dev.h"
|
|
#include "vsc-tp.h"
|
|
|
|
#define MEI_VSC_DRV_NAME "intel_vsc"
|
|
|
|
#define MEI_VSC_MAX_MSG_SIZE 512
|
|
|
|
#define MEI_VSC_POLL_DELAY_US (100 * USEC_PER_MSEC)
|
|
#define MEI_VSC_POLL_TIMEOUT_US (400 * USEC_PER_MSEC)
|
|
|
|
#define mei_dev_to_vsc_hw(dev) ((struct mei_vsc_hw *)((dev)->hw))
|
|
|
|
struct mei_vsc_host_timestamp {
|
|
u64 realtime;
|
|
u64 boottime;
|
|
};
|
|
|
|
struct mei_vsc_hw {
|
|
struct vsc_tp *tp;
|
|
|
|
bool fw_ready;
|
|
bool host_ready;
|
|
|
|
atomic_t write_lock_cnt;
|
|
|
|
u32 rx_len;
|
|
u32 rx_hdr;
|
|
|
|
/* buffer for tx */
|
|
char tx_buf[MEI_VSC_MAX_MSG_SIZE + sizeof(struct mei_msg_hdr)] ____cacheline_aligned;
|
|
/* buffer for rx */
|
|
char rx_buf[MEI_VSC_MAX_MSG_SIZE + sizeof(struct mei_msg_hdr)] ____cacheline_aligned;
|
|
};
|
|
|
|
static int mei_vsc_read_helper(struct mei_vsc_hw *hw, u8 *buf,
|
|
u32 max_len)
|
|
{
|
|
struct mei_vsc_host_timestamp ts = {
|
|
.realtime = ktime_to_ns(ktime_get_real()),
|
|
.boottime = ktime_to_ns(ktime_get_boottime()),
|
|
};
|
|
|
|
return vsc_tp_xfer(hw->tp, VSC_TP_CMD_READ, &ts, sizeof(ts),
|
|
buf, max_len);
|
|
}
|
|
|
|
static int mei_vsc_write_helper(struct mei_vsc_hw *hw, u8 *buf, u32 len)
|
|
{
|
|
u8 status;
|
|
|
|
return vsc_tp_xfer(hw->tp, VSC_TP_CMD_WRITE, buf, len, &status,
|
|
sizeof(status));
|
|
}
|
|
|
|
static int mei_vsc_fw_status(struct mei_device *mei_dev,
|
|
struct mei_fw_status *fw_status)
|
|
{
|
|
if (!fw_status)
|
|
return -EINVAL;
|
|
|
|
fw_status->count = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline enum mei_pg_state mei_vsc_pg_state(struct mei_device *mei_dev)
|
|
{
|
|
return MEI_PG_OFF;
|
|
}
|
|
|
|
static void mei_vsc_intr_enable(struct mei_device *mei_dev)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
|
|
vsc_tp_intr_enable(hw->tp);
|
|
}
|
|
|
|
static void mei_vsc_intr_disable(struct mei_device *mei_dev)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
|
|
vsc_tp_intr_disable(hw->tp);
|
|
}
|
|
|
|
/* mei framework requires this ops */
|
|
static void mei_vsc_intr_clear(struct mei_device *mei_dev)
|
|
{
|
|
}
|
|
|
|
/* wait for pending irq handler */
|
|
static void mei_vsc_synchronize_irq(struct mei_device *mei_dev)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
|
|
vsc_tp_intr_synchronize(hw->tp);
|
|
}
|
|
|
|
static int mei_vsc_hw_config(struct mei_device *mei_dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static bool mei_vsc_host_is_ready(struct mei_device *mei_dev)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
|
|
return hw->host_ready;
|
|
}
|
|
|
|
static bool mei_vsc_hw_is_ready(struct mei_device *mei_dev)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
|
|
return hw->fw_ready;
|
|
}
|
|
|
|
static int mei_vsc_hw_start(struct mei_device *mei_dev)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
int ret, rlen;
|
|
u8 buf;
|
|
|
|
hw->host_ready = true;
|
|
|
|
vsc_tp_intr_enable(hw->tp);
|
|
|
|
ret = read_poll_timeout(mei_vsc_read_helper, rlen,
|
|
rlen >= 0, MEI_VSC_POLL_DELAY_US,
|
|
MEI_VSC_POLL_TIMEOUT_US, true,
|
|
hw, &buf, sizeof(buf));
|
|
if (ret) {
|
|
dev_err(&mei_dev->dev, "wait fw ready failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
hw->fw_ready = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool mei_vsc_hbuf_is_ready(struct mei_device *mei_dev)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
|
|
return atomic_read(&hw->write_lock_cnt) == 0;
|
|
}
|
|
|
|
static int mei_vsc_hbuf_empty_slots(struct mei_device *mei_dev)
|
|
{
|
|
return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE;
|
|
}
|
|
|
|
static u32 mei_vsc_hbuf_depth(const struct mei_device *mei_dev)
|
|
{
|
|
return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE;
|
|
}
|
|
|
|
static int mei_vsc_write(struct mei_device *mei_dev,
|
|
const void *hdr, size_t hdr_len,
|
|
const void *data, size_t data_len)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
char *buf = hw->tx_buf;
|
|
int ret;
|
|
|
|
if (WARN_ON(!hdr || !IS_ALIGNED(hdr_len, 4)))
|
|
return -EINVAL;
|
|
|
|
if (!data || data_len > MEI_VSC_MAX_MSG_SIZE)
|
|
return -EINVAL;
|
|
|
|
atomic_inc(&hw->write_lock_cnt);
|
|
|
|
memcpy(buf, hdr, hdr_len);
|
|
memcpy(buf + hdr_len, data, data_len);
|
|
|
|
ret = mei_vsc_write_helper(hw, buf, hdr_len + data_len);
|
|
|
|
atomic_dec_if_positive(&hw->write_lock_cnt);
|
|
|
|
return ret < 0 ? ret : 0;
|
|
}
|
|
|
|
static inline u32 mei_vsc_read(const struct mei_device *mei_dev)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
int ret;
|
|
|
|
ret = mei_vsc_read_helper(hw, hw->rx_buf, sizeof(hw->rx_buf));
|
|
if (ret < 0 || ret < sizeof(u32))
|
|
return 0;
|
|
hw->rx_len = ret;
|
|
|
|
hw->rx_hdr = get_unaligned_le32(hw->rx_buf);
|
|
|
|
return hw->rx_hdr;
|
|
}
|
|
|
|
static int mei_vsc_count_full_read_slots(struct mei_device *mei_dev)
|
|
{
|
|
return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE;
|
|
}
|
|
|
|
static int mei_vsc_read_slots(struct mei_device *mei_dev, unsigned char *buf,
|
|
unsigned long len)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
struct mei_msg_hdr *hdr;
|
|
|
|
hdr = (struct mei_msg_hdr *)&hw->rx_hdr;
|
|
if (len != hdr->length || hdr->length + sizeof(*hdr) != hw->rx_len)
|
|
return -EINVAL;
|
|
|
|
memcpy(buf, hw->rx_buf + sizeof(*hdr), len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool mei_vsc_pg_in_transition(struct mei_device *mei_dev)
|
|
{
|
|
return mei_dev->pg_event >= MEI_PG_EVENT_WAIT &&
|
|
mei_dev->pg_event <= MEI_PG_EVENT_INTR_WAIT;
|
|
}
|
|
|
|
static bool mei_vsc_pg_is_enabled(struct mei_device *mei_dev)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static int mei_vsc_hw_reset(struct mei_device *mei_dev, bool intr_enable)
|
|
{
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
|
|
vsc_tp_reset(hw->tp);
|
|
|
|
if (!intr_enable)
|
|
return 0;
|
|
|
|
return vsc_tp_init(hw->tp, mei_dev->parent);
|
|
}
|
|
|
|
static const struct mei_hw_ops mei_vsc_hw_ops = {
|
|
.fw_status = mei_vsc_fw_status,
|
|
.pg_state = mei_vsc_pg_state,
|
|
|
|
.host_is_ready = mei_vsc_host_is_ready,
|
|
.hw_is_ready = mei_vsc_hw_is_ready,
|
|
.hw_reset = mei_vsc_hw_reset,
|
|
.hw_config = mei_vsc_hw_config,
|
|
.hw_start = mei_vsc_hw_start,
|
|
|
|
.pg_in_transition = mei_vsc_pg_in_transition,
|
|
.pg_is_enabled = mei_vsc_pg_is_enabled,
|
|
|
|
.intr_clear = mei_vsc_intr_clear,
|
|
.intr_enable = mei_vsc_intr_enable,
|
|
.intr_disable = mei_vsc_intr_disable,
|
|
.synchronize_irq = mei_vsc_synchronize_irq,
|
|
|
|
.hbuf_free_slots = mei_vsc_hbuf_empty_slots,
|
|
.hbuf_is_ready = mei_vsc_hbuf_is_ready,
|
|
.hbuf_depth = mei_vsc_hbuf_depth,
|
|
.write = mei_vsc_write,
|
|
|
|
.rdbuf_full_slots = mei_vsc_count_full_read_slots,
|
|
.read_hdr = mei_vsc_read,
|
|
.read = mei_vsc_read_slots,
|
|
};
|
|
|
|
static void mei_vsc_event_cb(void *context)
|
|
{
|
|
struct mei_device *mei_dev = context;
|
|
struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev);
|
|
struct list_head cmpl_list;
|
|
s32 slots;
|
|
int ret;
|
|
|
|
if (mei_dev->dev_state == MEI_DEV_RESETTING ||
|
|
mei_dev->dev_state == MEI_DEV_INITIALIZING)
|
|
return;
|
|
|
|
INIT_LIST_HEAD(&cmpl_list);
|
|
|
|
guard(mutex)(&mei_dev->device_lock);
|
|
|
|
while (vsc_tp_need_read(hw->tp)) {
|
|
/* check slots available for reading */
|
|
slots = mei_count_full_read_slots(mei_dev);
|
|
|
|
ret = mei_irq_read_handler(mei_dev, &cmpl_list, &slots);
|
|
if (ret) {
|
|
if (ret != -ENODATA) {
|
|
if (mei_dev->dev_state != MEI_DEV_RESETTING &&
|
|
mei_dev->dev_state != MEI_DEV_POWER_DOWN)
|
|
schedule_work(&mei_dev->reset_work);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev);
|
|
ret = mei_irq_write_handler(mei_dev, &cmpl_list);
|
|
if (ret)
|
|
dev_err(&mei_dev->dev, "dispatch write request failed: %d\n", ret);
|
|
|
|
mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev);
|
|
mei_irq_compl_handler(mei_dev, &cmpl_list);
|
|
}
|
|
|
|
static int mei_vsc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct mei_device *mei_dev;
|
|
struct mei_vsc_hw *hw;
|
|
struct vsc_tp *tp;
|
|
int ret;
|
|
|
|
tp = *(struct vsc_tp **)dev_get_platdata(dev);
|
|
if (!tp)
|
|
return dev_err_probe(dev, -ENODEV, "no platform data\n");
|
|
|
|
mei_dev = kzalloc(size_add(sizeof(*mei_dev), sizeof(*hw)), GFP_KERNEL);
|
|
if (!mei_dev)
|
|
return -ENOMEM;
|
|
|
|
mei_device_init(mei_dev, dev, false, &mei_vsc_hw_ops);
|
|
|
|
mei_dev->fw_f_fw_ver_supported = 0;
|
|
mei_dev->kind = "ivsc";
|
|
|
|
hw = mei_dev_to_vsc_hw(mei_dev);
|
|
atomic_set(&hw->write_lock_cnt, 0);
|
|
hw->tp = tp;
|
|
|
|
platform_set_drvdata(pdev, mei_dev);
|
|
|
|
vsc_tp_register_event_cb(tp, mei_vsc_event_cb, mei_dev);
|
|
|
|
ret = mei_register(mei_dev, dev);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = mei_start(mei_dev);
|
|
if (ret) {
|
|
dev_err_probe(dev, ret, "init hw failed\n");
|
|
goto err;
|
|
}
|
|
|
|
pm_runtime_enable(mei_dev->parent);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
mei_cancel_work(mei_dev);
|
|
|
|
vsc_tp_register_event_cb(tp, NULL, NULL);
|
|
|
|
mei_disable_interrupts(mei_dev);
|
|
|
|
mei_deregister(mei_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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->parent);
|
|
|
|
mei_stop(mei_dev);
|
|
|
|
vsc_tp_register_event_cb(hw->tp, NULL, NULL);
|
|
|
|
mei_disable_interrupts(mei_dev);
|
|
|
|
mei_deregister(mei_dev);
|
|
}
|
|
|
|
static int mei_vsc_suspend(struct device *dev)
|
|
{
|
|
struct mei_device *mei_dev;
|
|
int ret = 0;
|
|
|
|
mei_dev = dev_get_drvdata(dev);
|
|
if (!mei_dev)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&mei_dev->device_lock);
|
|
|
|
if (!mei_write_is_idle(mei_dev))
|
|
ret = -EAGAIN;
|
|
|
|
mutex_unlock(&mei_dev->device_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mei_vsc_resume(struct device *dev)
|
|
{
|
|
struct mei_device *mei_dev;
|
|
|
|
mei_dev = dev_get_drvdata(dev);
|
|
if (!mei_dev)
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static DEFINE_SIMPLE_DEV_PM_OPS(mei_vsc_pm_ops, mei_vsc_suspend, mei_vsc_resume);
|
|
|
|
static const struct platform_device_id mei_vsc_id_table[] = {
|
|
{ MEI_VSC_DRV_NAME },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, mei_vsc_id_table);
|
|
|
|
static struct platform_driver mei_vsc_drv = {
|
|
.probe = mei_vsc_probe,
|
|
.remove = mei_vsc_remove,
|
|
.id_table = mei_vsc_id_table,
|
|
.driver = {
|
|
.name = MEI_VSC_DRV_NAME,
|
|
.pm = &mei_vsc_pm_ops,
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
},
|
|
};
|
|
module_platform_driver(mei_vsc_drv);
|
|
|
|
MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>");
|
|
MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
|
|
MODULE_DESCRIPTION("Intel Visual Sensing Controller Interface");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_IMPORT_NS("VSC_TP");
|