]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/blob
0f9b4bf7691e70e10a68c16b2a5d8cce7bf353b3
[thirdparty/kernel/stable-queue.git] /
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
5
6 From: Krzysztof Kozlowski <k.kozlowski@samsung.com>
7
8 commit bdbe81445407644492b9ac69a24d35e3202d773b upstream.
9
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).
14
15 This lead to accessing old (and probably invalid) memory which could be
16 observed with:
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
42
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.
46
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.
54
55 The recursion is removed by retrieving temperature through power
56 supply's get_property().
57
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
62 supply.
63
64 [1] https://lkml.org/lkml/2014/10/6/309
65
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>
70
71 ---
72 drivers/power/charger-manager.c | 99 ++++++++++++++++++++++++----------
73 include/linux/power/charger-manager.h | 1
74 2 files changed, 71 insertions(+), 29 deletions(-)
75
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)
80 {
81 union power_supply_propval val;
82 + struct power_supply *psy;
83 bool present = false;
84 int i, ret;
85
86 @@ -107,7 +108,11 @@ static bool is_batt_present(struct charg
87 case CM_NO_BATTERY:
88 break;
89 case CM_FUEL_GAUGE:
90 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
91 + psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
92 + if (!psy)
93 + break;
94 +
95 + ret = psy->get_property(psy,
96 POWER_SUPPLY_PROP_PRESENT, &val);
97 if (ret == 0 && val.intval)
98 present = true;
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)
101 {
102 union power_supply_propval val;
103 + struct power_supply *fuel_gauge;
104 int ret;
105
106 - if (!cm->fuel_gauge)
107 + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
108 + if (!fuel_gauge)
109 return -ENODEV;
110
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);
114 if (ret)
115 return ret;
116 @@ -248,6 +255,7 @@ static bool is_full_charged(struct charg
117 {
118 struct charger_desc *desc = cm->desc;
119 union power_supply_propval val;
120 + struct power_supply *fuel_gauge;
121 int ret = 0;
122 int uV;
123
124 @@ -255,11 +263,15 @@ static bool is_full_charged(struct charg
125 if (!is_batt_present(cm))
126 return false;
127
128 - if (cm->fuel_gauge && desc->fullbatt_full_capacity > 0) {
129 + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
130 + if (!fuel_gauge)
131 + return false;
132 +
133 + if (desc->fullbatt_full_capacity > 0) {
134 val.intval = 0;
135
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)
141 return true;
142 @@ -273,10 +285,10 @@ static bool is_full_charged(struct charg
143 }
144
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) {
148 val.intval = 0;
149
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)
154 return true;
155 @@ -551,6 +563,20 @@ static int check_charging_duration(struc
156 return ret;
157 }
158
159 +static int cm_get_battery_temperature_by_psy(struct charger_manager *cm,
160 + int *temp)
161 +{
162 + struct power_supply *fuel_gauge;
163 +
164 + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
165 + if (!fuel_gauge)
166 + return -ENODEV;
167 +
168 + return fuel_gauge->get_property(fuel_gauge,
169 + POWER_SUPPLY_PROP_TEMP,
170 + (union power_supply_propval *)temp);
171 +}
172 +
173 static int cm_get_battery_temperature(struct charger_manager *cm,
174 int *temp)
175 {
176 @@ -560,15 +586,18 @@ static int cm_get_battery_temperature(st
177 return -ENODEV;
178
179 #ifdef CONFIG_THERMAL
180 - ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp);
181 - if (!ret)
182 - /* Calibrate temperature unit */
183 - *temp /= 100;
184 -#else
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);
190 + if (!ret)
191 + /* Calibrate temperature unit */
192 + *temp /= 100;
193 + } else
194 #endif
195 + {
196 + /* if-else continued from CONFIG_THERMAL */
197 + ret = cm_get_battery_temperature_by_psy(cm, temp);
198 + }
199 +
200 return ret;
201 }
202
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;
208 int ret = 0;
209 int uV;
210
211 @@ -857,14 +887,20 @@ static int charger_get_property(struct p
212 ret = get_batt_uV(cm, &val->intval);
213 break;
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);
217 + if (!fuel_gauge) {
218 + ret = -ENODEV;
219 + break;
220 + }
221 + ret = fuel_gauge->get_property(fuel_gauge,
222 POWER_SUPPLY_PROP_CURRENT_NOW, val);
223 break;
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);
230 + if (!fuel_gauge) {
231 ret = -ENODEV;
232 break;
233 }
234 @@ -875,7 +911,7 @@ static int charger_get_property(struct p
235 break;
236 }
237
238 - ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
239 + ret = fuel_gauge->get_property(fuel_gauge,
240 POWER_SUPPLY_PROP_CAPACITY, val);
241 if (ret)
242 break;
243 @@ -924,7 +960,14 @@ static int charger_get_property(struct p
244 break;
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);
250 + if (!fuel_gauge) {
251 + ret = -ENODEV;
252 + break;
253 + }
254 +
255 + ret = fuel_gauge->get_property(fuel_gauge,
256 POWER_SUPPLY_PROP_CHARGE_NOW,
257 val);
258 if (ret) {
259 @@ -1485,14 +1528,15 @@ err:
260 return ret;
261 }
262
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)
266 {
267 struct charger_desc *desc = cm->desc;
268 union power_supply_propval val;
269 int ret;
270
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);
275
276 if (!ret) {
277 @@ -1502,8 +1546,6 @@ static int cm_init_thermal_data(struct c
278 cm->desc->measure_battery_temp = true;
279 }
280 #ifdef CONFIG_THERMAL
281 - cm->tzd_batt = cm->fuel_gauge->tzd;
282 -
283 if (ret && desc->thermal_zone) {
284 cm->tzd_batt =
285 thermal_zone_get_zone_by_name(desc->thermal_zone);
286 @@ -1666,6 +1708,7 @@ static int charger_manager_probe(struct
287 int ret = 0, i = 0;
288 int j = 0;
289 union power_supply_propval val;
290 + struct power_supply *fuel_gauge;
291
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
295 }
296 }
297
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);
301 + if (!fuel_gauge) {
302 dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
303 desc->psy_fuel_gauge);
304 return -ENODEV;
305 @@ -1788,13 +1831,13 @@ static int charger_manager_probe(struct
306 cm->charger_psy.num_properties = psy_default.num_properties;
307
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++;
315 }
316 - if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
317 + if (!fuel_gauge->get_property(fuel_gauge,
318 POWER_SUPPLY_PROP_CURRENT_NOW,
319 &val)) {
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++;
323 }
324
325 - ret = cm_init_thermal_data(cm);
326 + ret = cm_init_thermal_data(cm, fuel_gauge);
327 if (ret) {
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 {
333 struct device *dev;
334 struct charger_desc *desc;
335
336 - struct power_supply *fuel_gauge;
337 struct power_supply **charger_stat;
338
339 #ifdef CONFIG_THERMAL