Re: [PATCH v2 3/5] power: supply: core: battery-info: fully switch to fwnode

From: Hans de Goede
Date: Sun Jun 08 2025 - 16:16:29 EST


Hi,

On 8-Jun-25 9:57 PM, Hans de Goede wrote:
> Hi Sebastian,
> On 30-Apr-25 12:54 AM, Sebastian Reichel wrote:
>> Also use fwnode based parsing for "ocv-capacity-celsius" and
>> "resistance-temp-table", so that any DT specific bits are
>> removed from the power-supply core.
>>
>> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx>
>> Signed-off-by: Sebastian Reichel <sebastian.reichel@xxxxxxxxxxxxx>
>
> I have been testing this converting the ug3105 driver to
> use power_supply_batinfo_ocv2cap(), replacing the hardcoded
> ocv -> capacity table in that driver.
>
> While testing I hit a bug and while looking closer at this
> patch it needs more work on top of fixing that bug.
>
> See comments inline, also I've attached 3 fixup patches
> which can be squashed into this patch to address the remarks.
>
>> ---
>> drivers/power/supply/power_supply_core.c | 109 ++++++++++++++++++-------------
>> 1 file changed, 63 insertions(+), 46 deletions(-)
>>
>> diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
>> index 89947f1fe610d8a75756e1e4e5339b06349f9ab8..a8d1fe66e2486a833ccaa3ed77b861c6e52c5760 100644
>> --- a/drivers/power/supply/power_supply_core.c
>> +++ b/drivers/power/supply/power_supply_core.c
>> @@ -585,32 +585,19 @@ int power_supply_get_battery_info(struct power_supply *psy,
>> {
>> struct power_supply_resistance_temp_table *resist_table;
>> struct power_supply_battery_info *info;
>> - struct device_node *battery_np = NULL;
>> - struct fwnode_reference_args args;
>> - struct fwnode_handle *fwnode = NULL;
>> + struct fwnode_handle *srcnode, *fwnode;
>> const char *value;
>> - int err, len, index;
>> - const __be32 *list;
>> + int err, len, index, proplen;
>> + u32 *propdata;
>
> propname which is also a local-variable for a temporary
> malloc-ed buffer uses __free(kfree) instead of explicit
> kfree() calls IMHO it would be good to do that here too.
>
> This requires declaring it inside the
> "for (index = 0; index < len; index++)" loop like how
> this is done for propname, so that it gets freed on
> every loop iteration.
>
>
>> u32 min_max[2];
>>
>> - if (psy->dev.of_node) {
>> - battery_np = of_parse_phandle(psy->dev.of_node, "monitored-battery", 0);
>> - if (!battery_np)
>> - return -ENODEV;
>> + srcnode = dev_fwnode(&psy->dev);
>> + if (!srcnode && psy->dev.parent)
>> + srcnode = dev_fwnode(psy->dev.parent);
>>
>> - fwnode = fwnode_handle_get(of_fwnode_handle(battery_np));
>> - } else if (psy->dev.parent) {
>> - err = fwnode_property_get_reference_args(
>> - dev_fwnode(psy->dev.parent),
>> - "monitored-battery", NULL, 0, 0, &args);
>> - if (err)
>> - return err;
>> -
>> - fwnode = args.fwnode;
>> - }
>> -
>> - if (!fwnode)
>> - return -ENOENT;
>> + fwnode = fwnode_find_reference(srcnode, "monitored-battery", 0);
>> + if (IS_ERR(fwnode))
>> + return PTR_ERR(fwnode);
>>
>> err = fwnode_property_read_string(fwnode, "compatible", &value);
>> if (err)
>> @@ -740,15 +727,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
>> info->temp_max = min_max[1];
>> }
>>
>> - /*
>> - * The below code uses raw of-data parsing to parse
>> - * /schemas/types.yaml#/definitions/uint32-matrix
>> - * data, so for now this is only support with of.
>> - */
>> - if (!battery_np)
>> - goto out_ret_pointer;
>> -
>> - len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
>> + len = fwnode_property_count_u32(fwnode, "ocv-capacity-celsius");
>> if (len < 0 && len != -EINVAL) {
>> err = len;
>> goto out_put_node;
>> @@ -757,13 +736,13 @@ int power_supply_get_battery_info(struct power_supply *psy,
>> err = -EINVAL;
>> goto out_put_node;
>> } else if (len > 0) {
>> - of_property_read_u32_array(battery_np, "ocv-capacity-celsius",
>> + fwnode_property_read_u32_array(fwnode, "ocv-capacity-celsius",
>> info->ocv_temp, len);
>> }
>>
>> for (index = 0; index < len; index++) {
>> struct power_supply_battery_ocv_table *table;
>> - int i, tab_len, size;
>> + int i, tab_len;
>>
>> char *propname __free(kfree) = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d",
>> index);
>> @@ -772,60 +751,98 @@ int power_supply_get_battery_info(struct power_supply *psy,
>> err = -ENOMEM;
>> goto out_put_node;
>> }
>> - list = of_get_property(battery_np, propname, &size);
>> - if (!list || !size) {
>> + proplen = fwnode_property_count_u32(fwnode, propname);
>> + if (proplen < 0 || proplen % 2 != 0) {
>> dev_err(&psy->dev, "failed to get %s\n", propname);
>> power_supply_put_battery_info(psy, info);
>> err = -EINVAL;
>> goto out_put_node;
>> }
>> + propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
>
> As mentioned above I suggest to use the following here instead:
>
> u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
>
>
>> + if (!propdata) {
>> + kfree(propname);
>
> propname must NOT be free-ed here since it is marked __free(kfree),
> freeing it here will cause a double-free bug.
>
>> + power_supply_put_battery_info(psy, info);
>> + err = -EINVAL;
>> + goto out_put_node;
>> + }
>> + err = fwnode_property_read_u32_array(fwnode, propname, propdata, proplen);
>> + if (err < 0) {
>> + dev_err(&psy->dev, "failed to get %s\n", propname);
>> + kfree(propname);
>
> same as above.
>
>> + kfree(propdata);
>
> with the suggested "u32 *propdata __free(kfree)" this can and must be dropped.
>
>> + power_supply_put_battery_info(psy, info);
>> + goto out_put_node;
>> + }
>>
>> - tab_len = size / (2 * sizeof(__be32));
>> + tab_len = proplen / 2;
>> info->ocv_table_size[index] = tab_len;
>>
>> info->ocv_table[index] = table =
>> devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
>> if (!info->ocv_table[index]) {
>> + kfree(propdata);
>
> with the suggested "u32 *propdata __free(kfree)" this can and must be dropped.
>
>> power_supply_put_battery_info(psy, info);
>> err = -ENOMEM;
>> goto out_put_node;
>> }
>>
>> for (i = 0; i < tab_len; i++) {
>> - table[i].ocv = be32_to_cpu(*list);
>> - list++;
>> - table[i].capacity = be32_to_cpu(*list);
>> - list++;
>> + table[i].ocv = propdata[i*2];
>> + table[i].capacity = propdata[i*2+1];
>> }
>> +
>> + kfree(propdata);
>
> with the suggested "u32 *propdata __free(kfree)" this can and must be dropped.
>
>> }
>>
>> - list = of_get_property(battery_np, "resistance-temp-table", &len);
>> - if (!list || !len)
>> + proplen = fwnode_property_count_u32(fwnode, "resistance-temp-table");
>
> This will return -EINVAL when the property does not exist, making
> power_supply_get_battery_info() always fail when there is no
> "resistance-temp-table" in the battery fwnode. See the attached fixup
> patch for a suggested fix.
>
>> + if (proplen < 0 || proplen % 2 != 0) {
>> + power_supply_put_battery_info(psy, info);
>> + err = -ENOMEM;
>
> -ENOMEM is the wrong error code here.
>
>> goto out_ret_pointer;
>
> This should be "goto out_put_node" since this is an error path.
>
>> + } else if (proplen == 0) {
>> + goto out_ret_pointer;
>> + }
>>
>> - info->resist_table_size = len / (2 * sizeof(__be32));
>> + propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
>
> As mentioned above I suggest to use the following here instead:
>
> u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);

Ok, so this does not work, because we jump over this causing
propdata to not get initialized, but the __free(kfree) still
triggers.

Lesson learned: do not use modern C style mixing of declarations
and statements together with __free().

Attached are updated fixup patches which do not make things crash.

Regards,

Hans




>
>> + if (!propdata) {
>> + power_supply_put_battery_info(psy, info);
>> + err = -ENOMEM;
>> + goto out_ret_pointer;
>
> This should be "goto out_put_node" since this is an error path.
>
>> + }
>> +
>> + err = fwnode_property_read_u32_array(fwnode, "resistance-temp-table",
>> + propdata, proplen);
>> + if (err < 0) {
>> + kfree(propdata);
>
> with the suggested "u32 *propdata __free(kfree)" this can and must be dropped.
>
>> + power_supply_put_battery_info(psy, info);
>> + goto out_put_node;
>> + }
>> +
>> + info->resist_table_size = proplen / 2;
>> info->resist_table = resist_table = devm_kcalloc(&psy->dev,
>> info->resist_table_size,
>> sizeof(*resist_table),
>> GFP_KERNEL);
>> if (!info->resist_table) {
>> + kfree(propdata);
>
> with the suggested "u32 *propdata __free(kfree)" this can and must be dropped.
>
>> power_supply_put_battery_info(psy, info);
>> err = -ENOMEM;
>> goto out_put_node;
>> }
>>
>> for (index = 0; index < info->resist_table_size; index++) {
>> - resist_table[index].temp = be32_to_cpu(*list++);
>> - resist_table[index].resistance = be32_to_cpu(*list++);
>> + resist_table[index].temp = propdata[index*2];
>> + resist_table[index].resistance = propdata[index*2+1];
>> }
>>
>> + kfree(propdata);
>
> with the suggested "u32 *propdata __free(kfree)" this can and must be dropped.
>
>> +
>> out_ret_pointer:
>> /* Finally return the whole thing */
>> *info_out = info;
>>
>> out_put_node:
>> fwnode_handle_put(fwnode);
>> - of_node_put(battery_np);
>> return err;
>> }
>> EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
>>
>
> Regards,
>
> Hans
>
>
From 68d2414d201083904dad83af4ebb2c35fc850b79 Mon Sep 17 00:00:00 2001
From: Hans de Goede <hansg@xxxxxxxxxx>
Date: Sun, 8 Jun 2025 21:18:11 +0200
Subject: [PATCH 04/10] fixup! power: supply: core: battery-info: fully switch
to fwnode

After ("power: supply: core: battery-info: fully switch to fwnode")
power_supply_get_battery_info() will always fail for battery fwnodes
which do not define a "resistance-temp-table".

Fix this by cleanly exiting on both 0 and EINVAL returns from
fwnode_property_count_u32(fwnode, "resistance-temp-table") which
indicates that the property is empty or not there.

While at it also fix:

1. The weird -ENOMEM return for other errors. For other errors
propagate the existing error or -EINVAL for an odd proplen.
2. Wrongly using "goto out_ret_pointer" on errors, out_ret_pointer
should only be used on success, error paths should use
out_put_node;

Signed-off-by: Hans de Goede <hansg@xxxxxxxxxx>
---
drivers/power/supply/power_supply_core.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index a8d1fe66e248..9bbc3be2e483 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -795,19 +795,20 @@ int power_supply_get_battery_info(struct power_supply *psy,
}

proplen = fwnode_property_count_u32(fwnode, "resistance-temp-table");
- if (proplen < 0 || proplen % 2 != 0) {
+ if (proplen == 0 || proplen == -EINVAL) {
+ err = 0;
+ goto out_ret_pointer;
+ } else if (proplen < 0 || proplen % 2 != 0) {
power_supply_put_battery_info(psy, info);
- err = -ENOMEM;
- goto out_ret_pointer;
- } else if (proplen == 0) {
- goto out_ret_pointer;
+ err = (proplen < 0) ? proplen : -EINVAL;
+ goto out_put_node;
}

propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
if (!propdata) {
power_supply_put_battery_info(psy, info);
err = -ENOMEM;
- goto out_ret_pointer;
+ goto out_put_node;
}

err = fwnode_property_read_u32_array(fwnode, "resistance-temp-table",
--
2.49.0

From 1e2d54b1c946075590d011da17d96796ede58894 Mon Sep 17 00:00:00 2001
From: Hans de Goede <hansg@xxxxxxxxxx>
Date: Sun, 8 Jun 2025 21:37:59 +0200
Subject: [PATCH 05/10] fixup! power: supply: core: battery-info: fully switch
to fwnode

propname must NOT be manually free-ed here since it is marked
__free(kfree), freeing it will cause a double-free bug.

Also move propdata to be a __free(kfree) value for consistency
and also for cleaner code.

Signed-off-by: Hans de Goede <hansg@xxxxxxxxxx>
---
drivers/power/supply/power_supply_core.c | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)

diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 9bbc3be2e483..f2c79f15838d 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -588,7 +588,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
struct fwnode_handle *srcnode, *fwnode;
const char *value;
int err, len, index, proplen;
- u32 *propdata;
+ u32 *propdata __free(kfree) = NULL;
u32 min_max[2];

srcnode = dev_fwnode(&psy->dev);
@@ -758,9 +758,9 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = -EINVAL;
goto out_put_node;
}
- propdata = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
+
+ u32 *propdata __free(kfree) = kcalloc(proplen, sizeof(*propdata), GFP_KERNEL);
if (!propdata) {
- kfree(propname);
power_supply_put_battery_info(psy, info);
err = -EINVAL;
goto out_put_node;
@@ -768,8 +768,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = fwnode_property_read_u32_array(fwnode, propname, propdata, proplen);
if (err < 0) {
dev_err(&psy->dev, "failed to get %s\n", propname);
- kfree(propname);
- kfree(propdata);
power_supply_put_battery_info(psy, info);
goto out_put_node;
}
@@ -780,7 +778,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
info->ocv_table[index] = table =
devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
if (!info->ocv_table[index]) {
- kfree(propdata);
power_supply_put_battery_info(psy, info);
err = -ENOMEM;
goto out_put_node;
@@ -790,8 +787,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
table[i].ocv = propdata[i*2];
table[i].capacity = propdata[i*2+1];
}
-
- kfree(propdata);
}

proplen = fwnode_property_count_u32(fwnode, "resistance-temp-table");
@@ -814,7 +809,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
err = fwnode_property_read_u32_array(fwnode, "resistance-temp-table",
propdata, proplen);
if (err < 0) {
- kfree(propdata);
power_supply_put_battery_info(psy, info);
goto out_put_node;
}
@@ -825,7 +819,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
sizeof(*resist_table),
GFP_KERNEL);
if (!info->resist_table) {
- kfree(propdata);
power_supply_put_battery_info(psy, info);
err = -ENOMEM;
goto out_put_node;
@@ -836,8 +829,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
resist_table[index].resistance = propdata[index*2+1];
}

- kfree(propdata);
-
out_ret_pointer:
/* Finally return the whole thing */
*info_out = info;
--
2.49.0