summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Reichel <sebastian.reichel@collabora.com>2026-10-18 00:35:12 +0200
committerSebastian Reichel <sebastian.reichel@collabora.com>2026-10-18 00:35:12 +0200
commit6a559552107ab6b470af0cb5e6aab3aad17c19cc (patch)
tree860bbe16a37b30a0a7bc0f2de488bd763331dcf6
parent9c034b714ff97bbb0b9b9946857f7244db11eea0 (diff)
parent5b4bd13e0552926648a46c2c9a12a24dfaee3715 (diff)
Merge power-supply fixes branch
Merge power-supply fixes branch to extend the epc_sem unregister fix contained in it from external_power_changed to also cover get/set property. Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
-rw-r--r--drivers/power/supply/ds2780_battery.c2
-rw-r--r--drivers/power/supply/generic-adc-battery.c9
-rw-r--r--drivers/power/supply/max17042_battery.c2
-rw-r--r--drivers/power/supply/power_supply_core.c26
-rw-r--r--include/linux/power_supply.h16
5 files changed, 47 insertions, 8 deletions
diff --git a/drivers/power/supply/ds2780_battery.c b/drivers/power/supply/ds2780_battery.c
index 5b57bbba79d4..ef2446386c2a 100644
--- a/drivers/power/supply/ds2780_battery.c
+++ b/drivers/power/supply/ds2780_battery.c
@@ -304,7 +304,7 @@ static int ds2780_get_capacity(struct ds2780_device_info *dev_info,
return ret;
*capacity = raw;
- return raw;
+ return 0;
}
static int ds2780_get_status(struct ds2780_device_info *dev_info, int *status)
diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c
index c8ad3f11e808..7a1f8188bd27 100644
--- a/drivers/power/supply/generic-adc-battery.c
+++ b/drivers/power/supply/generic-adc-battery.c
@@ -90,12 +90,17 @@ static int gab_read_channel(struct gab *adc_bat, enum gab_chan_type channel,
int ret;
ret = iio_read_channel_processed(adc_bat->channel[channel], result);
- if (ret < 0)
+ if (ret < 0) {
dev_err(&adc_bat->psy->dev, "read channel error: %d\n", ret);
+ return ret;
+ }
+
+ if (channel == GAB_TEMP)
+ *result /= 100; /* Convert from milli- to deci-degree */
else
*result *= 1000;
- return ret;
+ return 0;
}
static int gab_get_property(struct power_supply *psy,
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index da4f89bb40ff..8138b52e05e1 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -1235,7 +1235,7 @@ static int max17042_parse_dt(struct max17042_chip *chip)
if (of_property_read_u32(np, "maxim,over-volt", &prop))
chip->vmax = INT_MAX;
else
- chip->vmin = prop;
+ chip->vmax = prop;
return 0;
}
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index d682989b18cb..c3f3c70b9449 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -1530,6 +1530,19 @@ void power_supply_external_power_changed(struct power_supply *psy)
!psy->desc->external_power_changed)
return;
+ /*
+ * Keep power_supply_unregister() from returning, and thus from letting
+ * the driver's data be freed, while the callback is running. The
+ * ->removing check has to happen under the lock: on its own, just like
+ * the use_cnt check above, it only tells us the supply was still there
+ * when we looked, not that it still is by the time the callback
+ * dereferences its driver data.
+ */
+ guard(rwsem_read)(&psy->epc_sem);
+
+ if (psy->removing)
+ return;
+
psy->desc->external_power_changed(psy);
}
EXPORT_SYMBOL_GPL(power_supply_external_power_changed);
@@ -1784,6 +1797,7 @@ __power_supply_register(struct device *parent,
}
spin_lock_init(&psy->changed_lock);
+ init_rwsem(&psy->epc_sem);
init_rwsem(&psy->extensions_sem);
INIT_LIST_HEAD(&psy->extensions);
@@ -1924,7 +1938,17 @@ EXPORT_SYMBOL_GPL(devm_power_supply_register);
void power_supply_unregister(struct power_supply *psy)
{
WARN_ON(atomic_dec_return(&psy->use_cnt));
- psy->removing = true;
+
+ /*
+ * Publish ->removing so that no new ->external_power_changed() call can
+ * start, and wait via semaphore for one that is already running: it may
+ * be a supplier's changed_work, which cancel_work_sync() below does not
+ * cover, and it may still dereference driver data that the caller is
+ * about to free.
+ */
+ scoped_guard(rwsem_write, &psy->epc_sem)
+ psy->removing = true;
+
cancel_work_sync(&psy->changed_work);
cancel_delayed_work_sync(&psy->deferred_register_work);
sysfs_remove_link(&psy->dev.kobj, "powers");
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index f6cbeffda653..cce61137aef4 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -363,6 +363,16 @@ struct power_supply {
bool removing;
atomic_t use_cnt;
struct power_supply_battery_info *battery_info;
+ /*
+ * Held for read while ->external_power_changed() runs, and for write by
+ * power_supply_unregister() when it publishes @removing, so that it
+ * waits for an in-flight callback to finish. Without this a driver's
+ * data, typically devm-allocated on its own device, can be freed while
+ * the callback is still using it.
+ * Must not be shared with extensions_sem: callbacks may read their own
+ * properties, which takes that one for read.
+ */
+ struct rw_semaphore epc_sem;
struct rw_semaphore extensions_sem; /* protects "extensions" */
struct list_head extensions;
#ifdef CONFIG_THERMAL
@@ -426,9 +436,9 @@ struct power_supply_vbat_ri_table {
* @charge_voltage_max_uv: maintenance charging voltage that is usually a bit
* lower than the constant_charge_voltage_max_uv. We can apply this settings
* charge_current_max_ua until we get back up to this voltage.
- * @safety_timer_minutes: maintenance charging safety timer, with an expiry
- * time in minutes. We will only use maintenance charging in this setting
- * for a certain amount of time, then we will first move to the next
+ * @charge_safety_timer_minutes: maintenance charging safety timer, with an
+ * expiry time in minutes. We will only use maintenance charging in this
+ * setting for a certain amount of time, then we will first move to the next
* maintenance charge current and voltage pair in respective array and wait
* for the next safety timer timeout, or, if we reached the last maintencance
* charging setting, disable charging until we reach