diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index a45ac7fa417b9aa97e673603bd166dda77c5379d..eef80ededdc6a616b6996301e7e058edc8be0a04 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -5,6 +5,7 @@ * Copyright (c) 2014-2016, Intel Corporation. */ +#include #include #include #include @@ -594,7 +595,6 @@ static void recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val) case MNG_RESET_NOTIFY: if (!ishtp_dev) { ishtp_dev = dev; - INIT_WORK(&fw_reset_work, fw_reset_work_fn); } schedule_work(&fw_reset_work); break; @@ -885,6 +885,7 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) { struct ishtp_device *dev; int i; + int ret; dev = devm_kzalloc(&pdev->dev, sizeof(struct ishtp_device) + sizeof(struct ish_hw), @@ -892,6 +893,7 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) if (!dev) return NULL; + dev->devc = &pdev->dev; ishtp_device_init(dev); init_waitqueue_head(&dev->wait_hw_ready); @@ -920,8 +922,13 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) list_add_tail(&tx_buf->link, &dev->wr_free_list); } + ret = devm_work_autocancel(&pdev->dev, &fw_reset_work, fw_reset_work_fn); + if (ret) { + dev_err(dev->devc, "Failed to initialise FW reset work\n"); + return NULL; + } + dev->ops = &ish_hw_ops; - dev->devc = &pdev->dev; dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr); return dev; } diff --git a/include/linux/devm-helpers.h b/include/linux/devm-helpers.h new file mode 100644 index 0000000000000000000000000000000000000000..50557f04ce80299218695c8bcf522add155d0e32 --- /dev/null +++ b/include/linux/devm-helpers.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __LINUX_DEVM_HELPERS_H +#define __LINUX_DEVM_HELPERS_H + +/* + * Functions which do automatically cancel operations or release resources upon + * driver detach. + * + * These should be helpful to avoid mixing the manual and devm-based resource + * management which can be source of annoying, rarely occurring, + * hard-to-reproduce bugs. + * + * Please take into account that devm based cancellation may be performed some + * time after the remove() is ran. + * + * Thus mixing devm and manual resource management can easily cause problems + * when unwinding operations with dependencies. IRQ scheduling a work in a queue + * is typical example where IRQs are often devm-managed and WQs are manually + * cleaned at remove(). If IRQs are not manually freed at remove() (and this is + * often the case when we use devm for IRQs) we have a period of time after + * remove() - and before devm managed IRQs are freed - where new IRQ may fire + * and schedule a work item which won't be cancelled because remove() was + * already ran. + */ + +#include +#include + +static inline void devm_delayed_work_drop(void *res) +{ + cancel_delayed_work_sync(res); +} + +/** + * devm_delayed_work_autocancel - Resource-managed work allocation + * @dev: Device which lifetime work is bound to + * @pdata: work to be cancelled when driver is detached + * + * Initialize work which is automatically cancelled when driver is detached. + * A few drivers need delayed work which must be cancelled before driver + * is detached to avoid accessing removed resources. + * devm_delayed_work_autocancel() can be used to omit the explicit + * cancelleation when driver is detached. + */ +static inline int devm_delayed_work_autocancel(struct device *dev, + struct delayed_work *w, + work_func_t worker) +{ + INIT_DELAYED_WORK(w, worker); + return devm_add_action(dev, devm_delayed_work_drop, w); +} + +static inline void devm_work_drop(void *res) +{ + cancel_work_sync(res); +} + +/** + * devm_work_autocancel - Resource-managed work allocation + * @dev: Device which lifetime work is bound to + * @w: Work to be added (and automatically cancelled) + * @worker: Worker function + * + * Initialize work which is automatically cancelled when driver is detached. + * A few drivers need to queue work which must be cancelled before driver + * is detached to avoid accessing removed resources. + * devm_work_autocancel() can be used to omit the explicit + * cancelleation when driver is detached. + */ +static inline int devm_work_autocancel(struct device *dev, + struct work_struct *w, + work_func_t worker) +{ + INIT_WORK(w, worker); + return devm_add_action(dev, devm_work_drop, w); +} + +#endif