Skip to contentSkip to conversation

workqueue / drivers: Add device-managed allocate workqueue

PATCH Applied Krzysztof Kozlowski 6 participants 19 messages Mar 5 – Mar 20

Subsystems (10)
lore
Share & tools
lore
#0 lore raw

v2 cover workqueue / drivers: Add device-managed allocate workqueue

Merging / Dependency
====================
All further patches depend on the first one, thus this probably should go via one tree, e.g. power supply. The first patch might be needed for other trees as well, e.g. if more drivers are discovered, so the best if it is on dedicated branch in case it has to be shared.

Changes in v2:
==============
- See individual patches
- Link to v1: https://patch.msgid.link/[email protected]

Description
===========
Add a Resource-managed version of alloc_workqueue() to fix common problem of drivers mixing devm() calls with destroy_workqueue. Such naive and discouraged driver approach leads to difficult to debug bugs when the driver:

1. Allocates workqueue in standard way and destroys it in driver remove() callback,
2. Sets work struct with devm_work_autocancel(),
3. Registers interrupt handler with devm_request_threaded_irq().

Which leads to following unbind/removal path:

1. destroy_workqueue() via driver remove(),
Any interrupt coming now would still execute the interrupt handler, which queues work on destroyed workqueue.
2. devm_irq_release(),
3. devm_work_drop() -> cancel_work_sync() on destroyed workqueue.

devm_alloc_workqueue() has two benefits:
1. Solves above problem of mix-and-match devres and non-devres code in driver,
2. Simplify any sane drivers which were correctly using alloc_workqueue() + devm_add_action_or_reset().

Best regards,
Krzysztof

---
Krzysztof Kozlowski (10):
workqueue: devres: Add device-managed allocate workqueue
power: supply: cw2015: Free allocated workqueue
power: supply: max77705: Drop duplicated IRQ error message
power: supply: max77705: Free allocated workqueue and fix removal order
power: supply: mt6370: Simplify with devm_alloc_ordered_workqueue()
power: supply: ipaq_micro: Simplify with devm
mfd: ezx-pcap: Drop memory allocation error message
mfd: ezx-pcap: Return directly instead of empty gotos
mfd: ezx-pcap: Avoid rescheduling after destroying workqueue platform/chrome: cros_usbpd_logger: Simplify with devm

 Documentation/driver-api/driver-model/devres.rst |  4 ++
 drivers/mfd/ezx-pcap.c                           | 27 +++++--------
 drivers/platform/chrome/cros_usbpd_logger.c      | 18 ++++-----
 drivers/power/supply/cw2015_battery.c            |  3 +-
 drivers/power/supply/ipaq_micro_battery.c        | 50 ++++++++----------------
 drivers/power/supply/max77705_charger.c          | 36 ++++++-----------
 drivers/power/supply/mt6370-charger.c            | 13 +-----
 include/linux/workqueue.h                        | 22 +++++++++++
 kernel/workqueue.c                               | 28 +++++++++++++
 9 files changed, 100 insertions(+), 101 deletions(-)

---
base-commit: 9739e59909e70058fab7a131d7bee60d447ab732
change-id: 20260220-workqueue-devm-d9591e5e70eb

Best regards,

Signature · 1 line
Krzysztof Kozlowski <[email protected]>
#1 lore raw

PATCH v2 1/10 workqueue: devres: Add device-managed allocate workqueue

Add a Resource-managed version of alloc_workqueue() to fix common problem of drivers mixing devm() calls with destroy_workqueue. Such naive and discouraged driver approach leads to difficult to debug bugs when the driver:

1. Allocates workqueue in standard way and destroys it in driver remove() callback,
2. Sets work struct with devm_work_autocancel(),
3. Registers interrupt handler with devm_request_threaded_irq().

Which leads to following unbind/removal path:

1. destroy_workqueue() via driver remove(),
Any interrupt coming now would still execute the interrupt handler, which queues work on destroyed workqueue.
2. devm_irq_release(),
3. devm_work_drop() -> cancel_work_sync() on destroyed workqueue.

devm_alloc_workqueue() has two benefits:
1. Solves above problem of mix-and-match devres and non-devres code in driver,
2. Simplify any sane drivers which were correctly using alloc_workqueue() + devm_add_action_or_reset().

---

All further patches depend on this one.

Changes in v2:
1. Drop devm_create_workqueue(), devm_create_freezable_workqueue() and devm_create_singlethread_workqueue()

2. Simplify with devm_add_action_or_reset()

3. Do not export devm_destroy_workqueue()

4. I did not move the declarations to devm-helpers.h because consensus was not reached and I think it would not be accurate place. The main alloc_workqueue() is here, so should be the devm- interface.

---
 Documentation/driver-api/driver-model/devres.rst |  4 ++++
 include/linux/workqueue.h                        | 22 +++++++++++++++++++
 kernel/workqueue.c                               | 28 ++++++++++++++++++++++++
 3 files changed, 54 insertions(+)
Documentation/driver-api/driver-model/devres.rst +4 -0
index 7d2b897d66fa..017fb155a5bc 100644
@@ -464,3 +464,7 @@ SPI
 
 WATCHDOG
   devm_watchdog_register_device()
+
+WORKQUEUE
+  devm_alloc_workqueue()
+  devm_alloc_ordered_workqueue()
include/linux/workqueue.h +22 -0
index fc5744402a66..66a94c171b0b 100644
@@ -512,6 +512,26 @@ __printf(1, 4) struct workqueue_struct *
 alloc_workqueue_noprof(const char *fmt, unsigned int flags, int max_active, ...);
 #define alloc_workqueue(...)	alloc_hooks(alloc_workqueue_noprof(__VA_ARGS__))
 
+/**
+ * devm_alloc_workqueue - Resource-managed allocate a workqueue
+ * @dev: Device to allocate workqueue for
+ * @fmt: printf format for the name of the workqueue
+ * @flags: WQ_* flags
+ * @max_active: max in-flight work items, 0 for default
+ * @...: args for @fmt
+ *
+ * Resource managed workqueue, see alloc_workqueue() for details.
+ *
+ * The workqueue will be automatically destroyed on driver detach.  Typically
+ * this should be used in drivers already relying on devm interafaces.
+ *
+ * RETURNS:
+ * Pointer to the allocated workqueue on success, %NULL on failure.
+ */
+__printf(2, 5) struct workqueue_struct *
+devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int flags,
+		     int max_active, ...);
+
 #ifdef CONFIG_LOCKDEP
 /**
  * alloc_workqueue_lockdep_map - allocate a workqueue with user-defined lockdep_map
@@ -568,6 +588,8 @@ alloc_workqueue_lockdep_map(const char *fmt, unsigned int flags, int max_active,
  */
 #define alloc_ordered_workqueue(fmt, flags, args...)			\
 	alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)
+#define devm_alloc_ordered_workqueue(dev, fmt, flags, args...)		\
+	devm_alloc_workqueue(dev, fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)
 
 #define create_workqueue(name)						\
 	alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM | WQ_PERCPU, 1, (name))
kernel/workqueue.c +28 -0
index a1bfabeaef41..5cc5e6a400c9 100644
@@ -41,6 +41,7 @@
 #include <linux/mempolicy.h>
 #include <linux/freezer.h>
 #include <linux/debug_locks.h>
+#include <linux/device/devres.h>
 #include <linux/lockdep.h>
 #include <linux/idr.h>
 #include <linux/jhash.h>
@@ -5909,6 +5910,33 @@ struct workqueue_struct *alloc_workqueue_noprof(const char *fmt,
 }
 EXPORT_SYMBOL_GPL(alloc_workqueue_noprof);
 
+static void devm_workqueue_release(void *res)
+{
+	destroy_workqueue(res);
+}
+
+__printf(2, 5) struct workqueue_struct *
+devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int flags,
+		     int max_active, ...)
+{
+	struct workqueue_struct *wq;
+	va_list args;
+	int ret;
+
+	va_start(args, max_active);
+	wq = alloc_workqueue(fmt, flags, max_active, args);
+	va_end(args);
+	if (!wq)
+		return NULL;
+
+	ret = devm_add_action_or_reset(dev, devm_workqueue_release, wq);
+	if (ret)
+		return NULL;
+
+	return wq;
+}
+EXPORT_SYMBOL_GPL(devm_alloc_workqueue);
+
 #ifdef CONFIG_LOCKDEP
 __printf(1, 5)
 struct workqueue_struct *
Signature · 1 line
2.51.0
maintainer Acked-by on v2 1/10
#2 lore raw

Re: [PATCH v2 01/10] workqueue: devres: Add device-managed allocate workqueue

Quoted 18 lines · Krzysztof Kozlowski

Add a Resource-managed version of alloc_workqueue() to fix common problem of drivers mixing devm() calls with destroy_workqueue. Such naive and discouraged driver approach leads to difficult to debug bugs when the driver:

1. Allocates workqueue in standard way and destroys it in driver remove() callback,
2. Sets work struct with devm_work_autocancel(),
3. Registers interrupt handler with devm_request_threaded_irq().

Which leads to following unbind/removal path:

1. destroy_workqueue() via driver remove(),
Any interrupt coming now would still execute the interrupt handler, which queues work on destroyed workqueue.
2. devm_irq_release(),
3. devm_work_drop() -> cancel_work_sync() on destroyed workqueue.

Quoted 7 lines

devm_alloc_workqueue() has two benefits:
1. Solves above problem of mix-and-match devres and non-devres code in driver,
2. Simplify any sane drivers which were correctly using alloc_workqueue() + devm_add_action_or_reset().

Please let me know how you wanna route the patch.

Thanks.

Signature · 1 line
tejun
#3 lore raw

Re: [PATCH v2 01/10] workqueue: devres: Add device-managed allocate workqueue

Quoted 25 lines · Tejun Heo
Quoted 24 lines · Krzysztof Kozlowski

Add a Resource-managed version of alloc_workqueue() to fix common problem of drivers mixing devm() calls with destroy_workqueue. Such naive and discouraged driver approach leads to difficult to debug bugs when the driver:

1. Allocates workqueue in standard way and destroys it in driver remove() callback,
2. Sets work struct with devm_work_autocancel(),
3. Registers interrupt handler with devm_request_threaded_irq().

Which leads to following unbind/removal path:

1. destroy_workqueue() via driver remove(),
Any interrupt coming now would still execute the interrupt handler, which queues work on destroyed workqueue.
2. devm_irq_release(),
3. devm_work_drop() -> cancel_work_sync() on destroyed workqueue.

devm_alloc_workqueue() has two benefits:
1. Solves above problem of mix-and-match devres and non-devres code in driver,
2. Simplify any sane drivers which were correctly using alloc_workqueue() + devm_add_action_or_reset().

Quoted 5 lines
Quoted 1 line

Please let me know how you wanna route the patch.

Like I described in cover letter, so I propose this going via one tree, e.g. power supply. The first patch might be needed for other trees as well, e.g. if more drivers are discovered, so the best if it is on dedicated branch in case it has to be shared just in case.

Does this look reasonable @Tejun, @Sebastian?

Best regards,
Krzysztof

maintainer
#4 lore raw

Re: [PATCH v2 01/10] workqueue: devres: Add device-managed allocate workqueue

Hello,

Applied the first patch to a dedicated branch. Please pull from:

git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git for-7.1-devm-alloc-wq

Thanks.

--
tejun

#5 lore raw

PATCH v2 2/10 power: supply: cw2015: Free allocated workqueue

In reply to Krzysztof Kozlowski

Use devm interface so allocated workqueue will be freed during device removal and error paths, thus fixing a memory leak.

Change is not equivalent in the workqueue itself: use non-legacy API which does not set (__WQ_LEGACY | WQ_MEM_RECLAIM). The workqueue is used to read updated data from the battery, thus there is no point to run it for memory reclaim.

Fixes b4c7715c10c1 ("power: supply: add CellWise cw2015 fuel gauge driver")

---

Depends on devm_xxx() from earlier patches.

Changes in v2:
1. Use devm_alloc_ordered_workqueue(), mention this in commit msg

---
 drivers/power/supply/cw2015_battery.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/power/supply/cw2015_battery.c +2 -1
index a05dcc4a48f2..286524d2318c 100644
@@ -694,7 +694,8 @@ static int cw_bat_probe(struct i2c_client *client)
 			 "No monitored battery, some properties will be missing\n");
 	}
 
-	cw_bat->battery_workqueue = create_singlethread_workqueue("rk_battery");
+	cw_bat->battery_workqueue = devm_alloc_ordered_workqueue(&client->dev,
+								 "rk_battery", 0);
 	if (!cw_bat->battery_workqueue)
 		return -ENOMEM;
 
Signature · 1 line
2.51.0
#6 lore raw

PATCH v2 3/10 power: supply: max77705: Drop duplicated IRQ error message

In reply to Krzysztof Kozlowski

Core already prints error message on devm_request_threaded_irq() failure, so no need to do that second time.

---

Changes in v2:
1. New patch

---
 drivers/power/supply/max77705_charger.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)
drivers/power/supply/max77705_charger.c +2 -6
index 5dd02f658f5b..0dfe4ab10919 100644
@@ -666,19 +666,15 @@ static int max77705_charger_probe(struct i2c_client *i2c)
 					NULL, max77705_chgin_irq,
 					IRQF_TRIGGER_NONE,
 					"chgin-irq", chg);
-	if (ret) {
-		dev_err_probe(dev, ret, "Failed to Request chgin IRQ\n");
+	if (ret)
 		goto destroy_wq;
-	}
 
 	ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_AICL_I),
 					NULL, max77705_aicl_irq,
 					IRQF_TRIGGER_NONE,
 					"aicl-irq", chg);
-	if (ret) {
-		dev_err_probe(dev, ret, "Failed to Request aicl IRQ\n");
+	if (ret)
 		goto destroy_wq;
-	}
 
 	ret = max77705_charger_enable(chg);
 	if (ret) {
Signature · 1 line
2.51.0
#7 lore raw

PATCH v2 4/10 power: supply: max77705: Free allocated workqueue and fix removal order

In reply to Krzysztof Kozlowski

Use devm interface for allocating workqueue to fix two bugs at the same time:

1. Driver leaks the memory on remove(), because the workqueue is not destroyed.

2. Driver allocates workqueue and then registers interrupt handlers with devm interface. This means that probe error paths will not use a reversed order, but first destroy the workqueue and then, via devm release handlers, free the interrupt.

The interrupt handler schedules work on this exact workqueue, thus if interrupt is hit in this short time window - after destroying workqueue, but before devm() frees the interrupt - the schedulled work will lead to use of freed memory.

Change is not equivalent in the workqueue itself: use non-legacy API which does not set (__WQ_LEGACY | WQ_MEM_RECLAIM). The workqueue is used to update power supply (power_supply_changed()) status, thus there is no point to run it for memory reclaim. Note that dev_name() is not directly used in second argument to prevent possible unlikely parsing any "%" character in device name as format.

Fixes 11741b8e382d ("power: supply: max77705: Fix workqueue error handling in probe")
Fixes a6a494c8e3ce ("power: supply: max77705: Add charger driver for Maxim 77705")

---

Changes in v2:
1. Use devm_alloc_ordered_workqueue(), mention this in commit msg

---
 drivers/power/supply/max77705_charger.c | 28 +++++++++-------------------
 1 file changed, 9 insertions(+), 19 deletions(-)
drivers/power/supply/max77705_charger.c +9 -19
index 0dfe4ab10919..63b0b4f0cd21 100644
@@ -646,47 +646,37 @@ static int max77705_charger_probe(struct i2c_client *i2c)
 	if (ret)
 		return dev_err_probe(dev, ret, "failed to add irq chip\n");
 
-	chg->wqueue = create_singlethread_workqueue(dev_name(dev));
+	chg->wqueue = devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(dev));
 	if (!chg->wqueue)
 		return -ENOMEM;
 
 	ret = devm_work_autocancel(dev, &chg->chgin_work, max77705_chgin_isr_work);
-	if (ret) {
-		dev_err_probe(dev, ret, "failed to initialize interrupt work\n");
-		goto destroy_wq;
-	}
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to initialize interrupt work\n");
 
 	ret = max77705_charger_initialize(chg);
-	if (ret) {
-		dev_err_probe(dev, ret, "failed to initialize charger IC\n");
-		goto destroy_wq;
-	}
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to initialize charger IC\n");
 
 	ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_CHGIN_I),
 					NULL, max77705_chgin_irq,
 					IRQF_TRIGGER_NONE,
 					"chgin-irq", chg);
 	if (ret)
-		goto destroy_wq;
+		return ret;
 
 	ret = devm_request_threaded_irq(dev, regmap_irq_get_virq(irq_data, MAX77705_AICL_I),
 					NULL, max77705_aicl_irq,
 					IRQF_TRIGGER_NONE,
 					"aicl-irq", chg);
 	if (ret)
-		goto destroy_wq;
+		return ret;
 
 	ret = max77705_charger_enable(chg);
-	if (ret) {
-		dev_err_probe(dev, ret, "failed to enable charge\n");
-		goto destroy_wq;
-	}
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable charge\n");
 
 	return devm_add_action_or_reset(dev, max77705_charger_disable, chg);
-
-destroy_wq:
-	destroy_workqueue(chg->wqueue);
-	return ret;
 }
 
 static const struct of_device_id max77705_charger_of_match[] = {
Signature · 1 line
2.51.0
#8 lore raw

PATCH v2 5/10 power: supply: mt6370: Simplify with devm_alloc_ordered_workqueue()

In reply to Krzysztof Kozlowski

Simplify the driver probe function by using devm_alloc_ordered_workqueue() which handles the cleanup already.

Change is not equivalent in the workqueue itself: use non-legacy API which does not set (__WQ_LEGACY | WQ_MEM_RECLAIM). The workqueue is used to update power supply data (power_supply_changed()) status, thus there is no point to run it for memory reclaim. Note that dev_name() is not directly used in second argument to prevent possible unlikely parsing any "%" character in device name as format.

---

Changes in v2:
1. Use devm_alloc_ordered_workqueue(), mention this in commit msg

---
 drivers/power/supply/mt6370-charger.c | 13 +------------
 1 file changed, 1 insertion(+), 12 deletions(-)
drivers/power/supply/mt6370-charger.c +1 -12
index e6db961d5818..916556baa854 100644
@@ -761,13 +761,6 @@ static int mt6370_chg_init_psy(struct mt6370_priv *priv)
 	return PTR_ERR_OR_ZERO(priv->psy);
 }
 
-static void mt6370_chg_destroy_wq(void *data)
-{
-	struct workqueue_struct *wq = data;
-
-	destroy_workqueue(wq);
-}
-
 static irqreturn_t mt6370_attach_i_handler(int irq, void *data)
 {
 	struct mt6370_priv *priv = data;
@@ -893,14 +886,10 @@ static int mt6370_chg_probe(struct platform_device *pdev)
 
 	priv->attach = MT6370_ATTACH_STAT_DETACH;
 
-	priv->wq = create_singlethread_workqueue(dev_name(priv->dev));
+	priv->wq = devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(priv->dev));
 	if (!priv->wq)
 		return -ENOMEM;
 
-	ret = devm_add_action_or_reset(dev, mt6370_chg_destroy_wq, priv->wq);
-	if (ret)
-		return ret;
-
 	ret = devm_work_autocancel(dev, &priv->bc12_work, mt6370_chg_bc12_work_func);
 	if (ret)
 		return dev_err_probe(dev, ret, "Failed to init bc12 work\n");
Signature · 1 line
2.51.0
#9 lore raw

PATCH v2 6/10 power: supply: ipaq_micro: Simplify with devm

In reply to Krzysztof Kozlowski

Simplify the driver by using devm interfaces, which allow to drop probe() error paths and the remove() callback.

---
 drivers/power/supply/ipaq_micro_battery.c | 50 ++++++++++---------------------
 1 file changed, 16 insertions(+), 34 deletions(-)
drivers/power/supply/ipaq_micro_battery.c +16 -34
index ff8573a5ca6d..5e3fe3852d0e 100644
@@ -7,6 +7,7 @@
  * Author : Linus Walleij <[email protected]>
  */
 
+#include <linux/devm-helpers.h>
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/platform_device.h>
@@ -232,49 +233,31 @@ static int micro_batt_probe(struct platform_device *pdev)
 		return -ENOMEM;
 
 	mb->micro = dev_get_drvdata(pdev->dev.parent);
-	mb->wq = alloc_workqueue("ipaq-battery-wq",
-				 WQ_MEM_RECLAIM | WQ_PERCPU, 0);
+	mb->wq = devm_alloc_workqueue(&pdev->dev, "ipaq-battery-wq",
+				      WQ_MEM_RECLAIM | WQ_PERCPU, 0);
 	if (!mb->wq)
 		return -ENOMEM;
 
-	INIT_DELAYED_WORK(&mb->update, micro_battery_work);
+	ret = devm_delayed_work_autocancel(&pdev->dev, &mb->update, micro_battery_work);
+	if (ret)
+		return ret;
+
 	platform_set_drvdata(pdev, mb);
 	queue_delayed_work(mb->wq, &mb->update, 1);
 
-	micro_batt_power = power_supply_register(&pdev->dev,
-						 &micro_batt_power_desc, NULL);
-	if (IS_ERR(micro_batt_power)) {
-		ret = PTR_ERR(micro_batt_power);
-		goto batt_err;
-	}
+	micro_batt_power = devm_power_supply_register(&pdev->dev,
+						      &micro_batt_power_desc,
+						      NULL);
+	if (IS_ERR(micro_batt_power))
+		return PTR_ERR(micro_batt_power);
 
-	micro_ac_power = power_supply_register(&pdev->dev,
-					       &micro_ac_power_desc, NULL);
-	if (IS_ERR(micro_ac_power)) {
-		ret = PTR_ERR(micro_ac_power);
-		goto ac_err;
-	}
+	micro_ac_power = devm_power_supply_register(&pdev->dev,
+						    &micro_ac_power_desc, NULL);
+	if (IS_ERR(micro_ac_power))
+		return PTR_ERR(micro_ac_power);
 
 	dev_info(&pdev->dev, "iPAQ micro battery driver\n");
 	return 0;
-
-ac_err:
-	power_supply_unregister(micro_batt_power);
-batt_err:
-	cancel_delayed_work_sync(&mb->update);
-	destroy_workqueue(mb->wq);
-	return ret;
-}
-
-static void micro_batt_remove(struct platform_device *pdev)
-
-{
-	struct micro_battery *mb = platform_get_drvdata(pdev);
-
-	power_supply_unregister(micro_ac_power);
-	power_supply_unregister(micro_batt_power);
-	cancel_delayed_work_sync(&mb->update);
-	destroy_workqueue(mb->wq);
 }
 
 static int __maybe_unused micro_batt_suspend(struct device *dev)
@@ -303,7 +286,6 @@ static struct platform_driver micro_batt_device_driver = {
 		.pm	= &micro_batt_dev_pm_ops,
 	},
 	.probe		= micro_batt_probe,
-	.remove		= micro_batt_remove,
 };
 module_platform_driver(micro_batt_device_driver);
 
Signature · 1 line
2.51.0
#10 lore raw

PATCH v2 7/10 mfd: ezx-pcap: Drop memory allocation error message

In reply to Krzysztof Kozlowski

Drivers should not print error messages on memory allocation failures, because core already does it.

---

Further patch depends on this one, thus it should not be picked separately.

---
 drivers/mfd/ezx-pcap.c | 1 -
 1 file changed, 1 deletion(-)
drivers/mfd/ezx-pcap.c +0 -1
index 24ca140d6a48..cd0520a08224 100644
@@ -416,7 +416,6 @@ static int ezx_pcap_probe(struct spi_device *spi)
 	pcap->workqueue = create_singlethread_workqueue("pcapd");
 	if (!pcap->workqueue) {
 		ret = -ENOMEM;
-		dev_err(&spi->dev, "can't create pcap thread\n");
 		goto ret;
 	}
 
Signature · 1 line
2.51.0
#11 lore raw

PATCH v2 8/10 mfd: ezx-pcap: Return directly instead of empty gotos

In reply to Krzysztof Kozlowski

Code is easier to read if empty error paths simply return, instead of jumping to empty label doing only "return ret".

---
 drivers/mfd/ezx-pcap.c | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)
drivers/mfd/ezx-pcap.c +7 -11
index cd0520a08224..8e51c113a320 100644
@@ -384,17 +384,15 @@ static int ezx_pcap_probe(struct spi_device *spi)
 	struct pcap_platform_data *pdata = dev_get_platdata(&spi->dev);
 	struct pcap_chip *pcap;
 	int i, adc_irq;
-	int ret = -ENODEV;
+	int ret;
 
 	/* platform data is required */
 	if (!pdata)
-		goto ret;
+		return -ENODEV;
 
 	pcap = devm_kzalloc(&spi->dev, sizeof(*pcap), GFP_KERNEL);
-	if (!pcap) {
-		ret = -ENOMEM;
-		goto ret;
-	}
+	if (!pcap)
+		return -ENOMEM;
 
 	spin_lock_init(&pcap->io_lock);
 	spin_lock_init(&pcap->adc_lock);
@@ -407,17 +405,15 @@ static int ezx_pcap_probe(struct spi_device *spi)
 	spi->mode = SPI_MODE_0 | (pdata->config & PCAP_CS_AH ? SPI_CS_HIGH : 0);
 	ret = spi_setup(spi);
 	if (ret)
-		goto ret;
+		return ret;
 
 	pcap->spi = spi;
 
 	/* setup irq */
 	pcap->irq_base = pdata->irq_base;
 	pcap->workqueue = create_singlethread_workqueue("pcapd");
-	if (!pcap->workqueue) {
-		ret = -ENOMEM;
-		goto ret;
-	}
+	if (!pcap->workqueue)
+		return -ENOMEM;
 
 	/* redirect interrupts to AP, except adcdone2 */
 	if (!(pdata->config & PCAP_SECOND_PORT))
Signature · 1 line
2.51.0
#12 lore raw

PATCH v2 9/10 mfd: ezx-pcap: Avoid rescheduling after destroying workqueue

In reply to Krzysztof Kozlowski

Driver allocates workqueue and then registers additional interrupt handler with devm interface. This means that device removal will not use a reversed order, but first destroy workqueue and then, via devm release handlers, free the interrupt.

The interrupt handler registered with devm does not directly use/schedule work items on the workqueue and the remove() function correctly removes other IRQs handlers, however the code mixing devm and non-devm interfaces is difficult to analyze and read.

Make the code flow much more obvious by using devm interface for allocating the workqueue, so it will be freed with the rest of devm resources.

Change is not equivalent in the workqueue itself: use non-legacy API which does not set (__WQ_LEGACY | WQ_MEM_RECLAIM). The workqueue is used to update device registers, thus there is no point to run it for memory reclaim.

---

Depends on devm_xxx() from earlier patches.

Changes in v2:
1. Use devm_alloc_ordered_workqueue(), mention this in commit msg

---
 drivers/mfd/ezx-pcap.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)
drivers/mfd/ezx-pcap.c +2 -6
index 8e51c113a320..9a685ff8cd15 100644
@@ -375,8 +375,6 @@ static void ezx_pcap_remove(struct spi_device *spi)
 	/* cleanup irqchip */
 	for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++)
 		irq_set_chip_and_handler(i, NULL, NULL);
-
-	destroy_workqueue(pcap->workqueue);
 }
 
 static int ezx_pcap_probe(struct spi_device *spi)
@@ -411,7 +409,7 @@ static int ezx_pcap_probe(struct spi_device *spi)
 
 	/* setup irq */
 	pcap->irq_base = pdata->irq_base;
-	pcap->workqueue = create_singlethread_workqueue("pcapd");
+	pcap->workqueue = devm_alloc_ordered_workqueue(&spi->dev, "pcapd", 0);
 	if (!pcap->workqueue)
 		return -ENOMEM;
 
@@ -463,9 +461,7 @@ static int ezx_pcap_probe(struct spi_device *spi)
 free_irqchip:
 	for (i = pcap->irq_base; i < (pcap->irq_base + PCAP_NIRQS); i++)
 		irq_set_chip_and_handler(i, NULL, NULL);
-/* destroy_workqueue: */
-	destroy_workqueue(pcap->workqueue);
-ret:
+
 	return ret;
 }
 
Signature · 1 line
2.51.0
#13 lore raw

PATCH v2 10/10 platform/chrome: cros_usbpd_logger: Simplify with devm

In reply to Krzysztof Kozlowski

Simplify the driver by using devm interfaces, which allow to drop probe() error paths and the remove() callback.

Change is not equivalent in the workqueue itself: use non-legacy API which does not set (__WQ_LEGACY | WQ_MEM_RECLAIM). The workqueue is used to update logs, thus there is no point to run it for memory reclaim.

---

Changes in v2:
1. Use devm_alloc_ordered_workqueue(), mention this in commit msg

---
 drivers/platform/chrome/cros_usbpd_logger.c | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)
drivers/platform/chrome/cros_usbpd_logger.c +7 -11
index 7ce75e2e039e..d343e1ab6f08 100644
@@ -5,6 +5,7 @@
  * Copyright 2018 Google LLC.
  */
 
+#include <linux/devm-helpers.h>
 #include <linux/ktime.h>
 #include <linux/math64.h>
 #include <linux/mod_devicetable.h>
@@ -199,6 +200,7 @@ static int cros_usbpd_logger_probe(struct platform_device *pd)
 	struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
 	struct device *dev = &pd->dev;
 	struct logger_data *logger;
+	int ret;
 
 	logger = devm_kzalloc(dev, sizeof(*logger), GFP_KERNEL);
 	if (!logger)
@@ -210,25 +212,20 @@ static int cros_usbpd_logger_probe(struct platform_device *pd)
 	platform_set_drvdata(pd, logger);
 
 	/* Retrieve PD event logs periodically */
-	INIT_DELAYED_WORK(&logger->log_work, cros_usbpd_log_check);
-	logger->log_workqueue =	create_singlethread_workqueue("cros_usbpd_log");
+	logger->log_workqueue =	devm_alloc_ordered_workqueue(dev, "cros_usbpd_log", 0);
 	if (!logger->log_workqueue)
 		return -ENOMEM;
 
+	ret = devm_delayed_work_autocancel(dev, &logger->log_work, cros_usbpd_log_check);
+	if (ret)
+		return ret;
+
 	queue_delayed_work(logger->log_workqueue, &logger->log_work,
 			   CROS_USBPD_LOG_UPDATE_DELAY);
 
 	return 0;
 }
 
-static void cros_usbpd_logger_remove(struct platform_device *pd)
-{
-	struct logger_data *logger = platform_get_drvdata(pd);
-
-	cancel_delayed_work_sync(&logger->log_work);
-	destroy_workqueue(logger->log_workqueue);
-}
-
 static int __maybe_unused cros_usbpd_logger_resume(struct device *dev)
 {
 	struct logger_data *logger = dev_get_drvdata(dev);
@@ -263,7 +260,6 @@ static struct platform_driver cros_usbpd_logger_driver = {
 		.pm = &cros_usbpd_logger_pm_ops,
 	},
 	.probe = cros_usbpd_logger_probe,
-	.remove = cros_usbpd_logger_remove,
 	.id_table = cros_usbpd_logger_id,
 };
 
Signature · 1 line
2.51.0
maintainer Acked-by on v2 10/10
#14 lore raw

Re: [PATCH v2 10/10] platform/chrome: cros_usbpd_logger: Simplify with devm

Quoted 9 lines · Krzysztof Kozlowski

Simplify the driver by using devm interfaces, which allow to drop probe() error paths and the remove() callback.

Change is not equivalent in the workqueue itself: use non-legacy API which does not set (__WQ_LEGACY | WQ_MEM_RECLAIM). The workqueue is used to update logs, thus there is no point to run it for memory reclaim.

Reviewed-by on the v2 series
#15 lore raw
In reply to Krzysztof Kozlowski
Quoted 32 lines · Krzysztof Kozlowski

Merging / Dependency
====================
All further patches depend on the first one, thus this probably should go via one tree, e.g. power supply. The first patch might be needed for other trees as well, e.g. if more drivers are discovered, so the best if it is on dedicated branch in case it has to be shared.

Changes in v2:
==============
- See individual patches
- Link to v1: https://patch.msgid.link/[email protected]

Description
===========
Add a Resource-managed version of alloc_workqueue() to fix common problem of drivers mixing devm() calls with destroy_workqueue. Such naive and discouraged driver approach leads to difficult to debug bugs when the driver:

1. Allocates workqueue in standard way and destroys it in driver remove() callback,
2. Sets work struct with devm_work_autocancel(),
3. Registers interrupt handler with devm_request_threaded_irq().

Which leads to following unbind/removal path:

1. destroy_workqueue() via driver remove(),
Any interrupt coming now would still execute the interrupt handler, which queues work on destroyed workqueue.
2. devm_irq_release(),
3. devm_work_drop() -> cancel_work_sync() on destroyed workqueue.

Quoted 5 lines

devm_alloc_workqueue() has two benefits:
1. Solves above problem of mix-and-match devres and non-devres code in driver,
2. Simplify any sane drivers which were correctly using alloc_workqueue() + devm_add_action_or_reset().

Thanks, this version LGTM, FWIW,

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#16 lore raw

Re: (subset) [PATCH v2 00/10] workqueue / drivers: Add device-managed allocate workqueue

In reply to Krzysztof Kozlowski
Quoted 8 lines · Krzysztof Kozlowski

Merging / Dependency
====================
All further patches depend on the first one, thus this probably should go via one tree, e.g. power supply. The first patch might be needed for other trees as well, e.g. if more drivers are discovered, so the best if it is on dedicated branch in case it has to be shared.

[...]

Applied, thanks!

[02/10] power: supply: cw2015: Free allocated workqueue

        commit: db254b0b232358ab1aeadebe8d147c99a3569559
[03/10] power: supply: max77705: Drop duplicated IRQ error message
        commit: 2064c64ceb1996ee02a6bbb1de05fd6e8028e3e4
[04/10] power: supply: max77705: Free allocated workqueue and fix removal order
        commit: 1e668baadefb16e81269dbfebf3ffc2672e3a3bb
[05/10] power: supply: mt6370: Simplify with devm_alloc_ordered_workqueue()
        commit: f23afa01040a41882a048e4957a7acac1426da6f
[06/10] power: supply: ipaq_micro: Simplify with devm
        commit: 2cfc7cac68e19c4acb236b8db6065bbaff5deee8

Best regards,

Signature · 1 line
Sebastian Reichel <[email protected]>
maintainer Applied
#17 lore raw

Re: (subset) [PATCH v2 00/10] workqueue / drivers: Add device-managed allocate workqueue

In reply to Krzysztof Kozlowski
Quoted 8 lines · Krzysztof Kozlowski

Merging / Dependency
====================
All further patches depend on the first one, thus this probably should go via one tree, e.g. power supply. The first patch might be needed for other trees as well, e.g. if more drivers are discovered, so the best if it is on dedicated branch in case it has to be shared.

[...]

Applied, thanks!

[07/10] mfd: ezx-pcap: Drop memory allocation error message

        commit: 33e0316783a205625b7c55a78041ddc0d5dce7c7
[08/10] mfd: ezx-pcap: Return directly instead of empty gotos
        commit: 444e11d9d9e56c994da8a253cdf7f33ac2eeb15b
[09/10] mfd: ezx-pcap: Avoid rescheduling after destroying workqueue
        commit: 356ee03f6ae7d04f90d8e2104660193c4f3a071c

--
Lee Jones [李琼斯]

maintainer
#18 lore raw

Re: (subset) [PATCH v2 00/10] workqueue / drivers: Add device-managed allocate workqueue

In reply to Krzysztof Kozlowski
Quoted 8 lines · Krzysztof Kozlowski

Merging / Dependency
====================
All further patches depend on the first one, thus this probably should go via one tree, e.g. power supply. The first patch might be needed for other trees as well, e.g. if more drivers are discovered, so the best if it is on dedicated branch in case it has to be shared.

[...]

Applied to

https://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux.git for-next

[10/10] platform/chrome: cros_usbpd_logger: Simplify with devm

        commit: 168e4b208ca8c2e04de20cc6cb7e2fb035dc1ec8

Thanks!