1 From bdbe81445407644492b9ac69a24d35e3202d773b Mon Sep 17 00:00:00 2001
2 From: Krzysztof Kozlowski <k.kozlowski@samsung.com>
3 Date: Mon, 13 Oct 2014 15:34:30 +0200
4 Subject: power: charger-manager: Fix accessing invalidated power supply after fuel gauge unbind
6 From: Krzysztof Kozlowski <k.kozlowski@samsung.com>
8 commit bdbe81445407644492b9ac69a24d35e3202d773b upstream.
10 The charger manager obtained reference to fuel gauge power supply in probe
11 with power_supply_get_by_name() for later usage. However if fuel gauge
12 driver was removed and re-added then this reference would point to old
13 power supply (from driver which was removed).
15 This lead to accessing old (and probably invalid) memory which could be
17 $ echo "12-0036" > /sys/bus/i2c/drivers/max17042/unbind
18 $ echo "12-0036" > /sys/bus/i2c/drivers/max17042/bind
19 $ cat /sys/devices/virtual/power_supply/battery/capacity
20 [ 240.480084] INFO: task cat:1393 blocked for more than 120 seconds.
21 [ 240.484799] Not tainted 3.17.0-next-20141007-00028-ge60b6dd79570 #203
22 [ 240.491782] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
23 [ 240.499589] cat D c0469530 0 1393 1 0x00000000
24 [ 240.505947] [<c0469530>] (__schedule) from [<c0469d3c>] (schedule_preempt_disabled+0x14/0x20)
25 [ 240.514449] [<c0469d3c>] (schedule_preempt_disabled) from [<c046af08>] (mutex_lock_nested+0x1bc/0x458)
26 [ 240.523736] [<c046af08>] (mutex_lock_nested) from [<c0287a98>] (regmap_read+0x30/0x60)
27 [ 240.531647] [<c0287a98>] (regmap_read) from [<c032238c>] (max17042_get_property+0x2e8/0x350)
28 [ 240.540055] [<c032238c>] (max17042_get_property) from [<c03247d8>] (charger_get_property+0x264/0x348)
29 [ 240.549252] [<c03247d8>] (charger_get_property) from [<c0320764>] (power_supply_show_property+0x48/0x1e0)
30 [ 240.558808] [<c0320764>] (power_supply_show_property) from [<c027308c>] (dev_attr_show+0x1c/0x48)
31 [ 240.567664] [<c027308c>] (dev_attr_show) from [<c0141fb0>] (sysfs_kf_seq_show+0x84/0x104)
32 [ 240.575814] [<c0141fb0>] (sysfs_kf_seq_show) from [<c0140b18>] (kernfs_seq_show+0x24/0x28)
33 [ 240.584061] [<c0140b18>] (kernfs_seq_show) from [<c0104574>] (seq_read+0x1b0/0x484)
34 [ 240.591702] [<c0104574>] (seq_read) from [<c00e1e24>] (vfs_read+0x88/0x144)
35 [ 240.598640] [<c00e1e24>] (vfs_read) from [<c00e1f20>] (SyS_read+0x40/0x8c)
36 [ 240.605507] [<c00e1f20>] (SyS_read) from [<c000e760>] (ret_fast_syscall+0x0/0x48)
37 [ 240.612952] 4 locks held by cat/1393:
38 [ 240.616589] #0: (&p->lock){+.+.+.}, at: [<c01043f4>] seq_read+0x30/0x484
39 [ 240.623414] #1: (&of->mutex){+.+.+.}, at: [<c01417dc>] kernfs_seq_start+0x1c/0x8c
40 [ 240.631086] #2: (s_active#31){++++.+}, at: [<c01417e4>] kernfs_seq_start+0x24/0x8c
41 [ 240.638777] #3: (&map->mutex){+.+...}, at: [<c0287a98>] regmap_read+0x30/0x60
43 The charger-manager should get reference to fuel gauge power supply on
44 each use of get_property callback. The thermal zone 'tzd' field of
45 power supply should not be used because of the same reason.
47 Additionally this change solves also the issue with nested
48 thermal_zone_get_temp() calls and related false lockdep positive for
49 deadlock for thermal zone's mutex [1]. When fuel gauge is used as source of
50 temperature then the charger manager forwards its get_temp calls to fuel
51 gauge thermal zone. So actually different mutexes are used (one for
52 charger manager thermal zone and second for fuel gauge thermal zone) but
53 for lockdep this is one class of mutex.
55 The recursion is removed by retrieving temperature through power
56 supply's get_property().
58 In case external thermal zone is used ('cm-thermal-zone' property is
59 present in DTS) the recursion does not exist. Charger manager simply
60 exports POWER_SUPPLY_PROP_TEMP_AMBIENT property (instead of
61 POWER_SUPPLY_PROP_TEMP) thus no thermal zone is created for this power
64 [1] https://lkml.org/lkml/2014/10/6/309
66 Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
67 Fixes: 3bb3dbbd56ea ("power_supply: Add initial Charger-Manager driver")
68 Signed-off-by: Sebastian Reichel <sre@kernel.org>
69 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
72 drivers/power/charger-manager.c | 99 ++++++++++++++++++++++++----------
73 include/linux/power/charger-manager.h | 1
74 2 files changed, 71 insertions(+), 29 deletions(-)
76 --- a/drivers/power/charger-manager.c
77 +++ b/drivers/power/charger-manager.c
78 @@ -97,6 +97,7 @@ static struct charger_global_desc *g_des
79 static bool is_batt_present(struct charger_manager *cm)
81 union power_supply_propval val;
82 + struct power_supply *psy;
86 @@ -107,7 +108,11 @@ static bool is_batt_present(struct charg
90 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
91 + psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
95 + ret = psy->get_property(psy,
96 POWER_SUPPLY_PROP_PRESENT, &val);
97 if (ret == 0 && val.intval)
99 @@ -167,12 +172,14 @@ static bool is_ext_pwr_online(struct cha
100 static int get_batt_uV(struct charger_manager *cm, int *uV)
102 union power_supply_propval val;
103 + struct power_supply *fuel_gauge;
106 - if (!cm->fuel_gauge)
107 + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
111 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
112 + ret = fuel_gauge->get_property(fuel_gauge,
113 POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
116 @@ -248,6 +255,7 @@ static bool is_full_charged(struct charg
118 struct charger_desc *desc = cm->desc;
119 union power_supply_propval val;
120 + struct power_supply *fuel_gauge;
124 @@ -255,11 +263,15 @@ static bool is_full_charged(struct charg
125 if (!is_batt_present(cm))
128 - if (cm->fuel_gauge && desc->fullbatt_full_capacity > 0) {
129 + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
133 + if (desc->fullbatt_full_capacity > 0) {
136 /* Not full if capacity of fuel gauge isn't full */
137 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
138 + ret = fuel_gauge->get_property(fuel_gauge,
139 POWER_SUPPLY_PROP_CHARGE_FULL, &val);
140 if (!ret && val.intval > desc->fullbatt_full_capacity)
142 @@ -273,10 +285,10 @@ static bool is_full_charged(struct charg
145 /* Full, if the capacity is more than fullbatt_soc */
146 - if (cm->fuel_gauge && desc->fullbatt_soc > 0) {
147 + if (desc->fullbatt_soc > 0) {
150 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
151 + ret = fuel_gauge->get_property(fuel_gauge,
152 POWER_SUPPLY_PROP_CAPACITY, &val);
153 if (!ret && val.intval >= desc->fullbatt_soc)
155 @@ -551,6 +563,20 @@ static int check_charging_duration(struc
159 +static int cm_get_battery_temperature_by_psy(struct charger_manager *cm,
162 + struct power_supply *fuel_gauge;
164 + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
168 + return fuel_gauge->get_property(fuel_gauge,
169 + POWER_SUPPLY_PROP_TEMP,
170 + (union power_supply_propval *)temp);
173 static int cm_get_battery_temperature(struct charger_manager *cm,
176 @@ -560,15 +586,18 @@ static int cm_get_battery_temperature(st
179 #ifdef CONFIG_THERMAL
180 - ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp);
182 - /* Calibrate temperature unit */
185 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
186 - POWER_SUPPLY_PROP_TEMP,
187 - (union power_supply_propval *)temp);
188 + if (cm->tzd_batt) {
189 + ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp);
191 + /* Calibrate temperature unit */
196 + /* if-else continued from CONFIG_THERMAL */
197 + ret = cm_get_battery_temperature_by_psy(cm, temp);
203 @@ -827,6 +856,7 @@ static int charger_get_property(struct p
204 struct charger_manager *cm = container_of(psy,
205 struct charger_manager, charger_psy);
206 struct charger_desc *desc = cm->desc;
207 + struct power_supply *fuel_gauge;
211 @@ -857,14 +887,20 @@ static int charger_get_property(struct p
212 ret = get_batt_uV(cm, &val->intval);
214 case POWER_SUPPLY_PROP_CURRENT_NOW:
215 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
216 + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
221 + ret = fuel_gauge->get_property(fuel_gauge,
222 POWER_SUPPLY_PROP_CURRENT_NOW, val);
224 case POWER_SUPPLY_PROP_TEMP:
225 case POWER_SUPPLY_PROP_TEMP_AMBIENT:
226 return cm_get_battery_temperature(cm, &val->intval);
227 case POWER_SUPPLY_PROP_CAPACITY:
228 - if (!cm->fuel_gauge) {
229 + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
234 @@ -875,7 +911,7 @@ static int charger_get_property(struct p
238 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
239 + ret = fuel_gauge->get_property(fuel_gauge,
240 POWER_SUPPLY_PROP_CAPACITY, val);
243 @@ -924,7 +960,14 @@ static int charger_get_property(struct p
245 case POWER_SUPPLY_PROP_CHARGE_NOW:
246 if (is_charging(cm)) {
247 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
248 + fuel_gauge = power_supply_get_by_name(
249 + cm->desc->psy_fuel_gauge);
255 + ret = fuel_gauge->get_property(fuel_gauge,
256 POWER_SUPPLY_PROP_CHARGE_NOW,
259 @@ -1485,14 +1528,15 @@ err:
263 -static int cm_init_thermal_data(struct charger_manager *cm)
264 +static int cm_init_thermal_data(struct charger_manager *cm,
265 + struct power_supply *fuel_gauge)
267 struct charger_desc *desc = cm->desc;
268 union power_supply_propval val;
271 /* Verify whether fuel gauge provides battery temperature */
272 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
273 + ret = fuel_gauge->get_property(fuel_gauge,
274 POWER_SUPPLY_PROP_TEMP, &val);
277 @@ -1502,8 +1546,6 @@ static int cm_init_thermal_data(struct c
278 cm->desc->measure_battery_temp = true;
280 #ifdef CONFIG_THERMAL
281 - cm->tzd_batt = cm->fuel_gauge->tzd;
283 if (ret && desc->thermal_zone) {
285 thermal_zone_get_zone_by_name(desc->thermal_zone);
286 @@ -1666,6 +1708,7 @@ static int charger_manager_probe(struct
289 union power_supply_propval val;
290 + struct power_supply *fuel_gauge;
292 if (g_desc && !rtc_dev && g_desc->rtc_name) {
293 rtc_dev = rtc_class_open(g_desc->rtc_name);
294 @@ -1744,8 +1787,8 @@ static int charger_manager_probe(struct
298 - cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
299 - if (!cm->fuel_gauge) {
300 + fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
302 dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
303 desc->psy_fuel_gauge);
305 @@ -1788,13 +1831,13 @@ static int charger_manager_probe(struct
306 cm->charger_psy.num_properties = psy_default.num_properties;
308 /* Find which optional psy-properties are available */
309 - if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
310 + if (!fuel_gauge->get_property(fuel_gauge,
311 POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
312 cm->charger_psy.properties[cm->charger_psy.num_properties] =
313 POWER_SUPPLY_PROP_CHARGE_NOW;
314 cm->charger_psy.num_properties++;
316 - if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
317 + if (!fuel_gauge->get_property(fuel_gauge,
318 POWER_SUPPLY_PROP_CURRENT_NOW,
320 cm->charger_psy.properties[cm->charger_psy.num_properties] =
321 @@ -1802,7 +1845,7 @@ static int charger_manager_probe(struct
322 cm->charger_psy.num_properties++;
325 - ret = cm_init_thermal_data(cm);
326 + ret = cm_init_thermal_data(cm, fuel_gauge);
328 dev_err(&pdev->dev, "Failed to initialize thermal data\n");
329 cm->desc->measure_battery_temp = false;
330 --- a/include/linux/power/charger-manager.h
331 +++ b/include/linux/power/charger-manager.h
332 @@ -253,7 +253,6 @@ struct charger_manager {
334 struct charger_desc *desc;
336 - struct power_supply *fuel_gauge;
337 struct power_supply **charger_stat;
339 #ifdef CONFIG_THERMAL