aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKrzysztof Kozlowski <k.kozlowski@samsung.com>2014-10-13 09:34:30 -0400
committerSebastian Reichel <sre@kernel.org>2014-10-27 22:30:20 -0400
commitbdbe81445407644492b9ac69a24d35e3202d773b (patch)
tree3bd57525f83c469d8543e304925c6b056f24375f
parentba9c91825d4a3bb49532d4a59c72e98b529b7eff (diff)
power: charger-manager: Fix accessing invalidated power supply after fuel gauge unbind
The charger manager obtained reference to fuel gauge power supply in probe with power_supply_get_by_name() for later usage. However if fuel gauge driver was removed and re-added then this reference would point to old power supply (from driver which was removed). This lead to accessing old (and probably invalid) memory which could be observed with: $ echo "12-0036" > /sys/bus/i2c/drivers/max17042/unbind $ echo "12-0036" > /sys/bus/i2c/drivers/max17042/bind $ cat /sys/devices/virtual/power_supply/battery/capacity [ 240.480084] INFO: task cat:1393 blocked for more than 120 seconds. [ 240.484799] Not tainted 3.17.0-next-20141007-00028-ge60b6dd79570 #203 [ 240.491782] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. [ 240.499589] cat D c0469530 0 1393 1 0x00000000 [ 240.505947] [<c0469530>] (__schedule) from [<c0469d3c>] (schedule_preempt_disabled+0x14/0x20) [ 240.514449] [<c0469d3c>] (schedule_preempt_disabled) from [<c046af08>] (mutex_lock_nested+0x1bc/0x458) [ 240.523736] [<c046af08>] (mutex_lock_nested) from [<c0287a98>] (regmap_read+0x30/0x60) [ 240.531647] [<c0287a98>] (regmap_read) from [<c032238c>] (max17042_get_property+0x2e8/0x350) [ 240.540055] [<c032238c>] (max17042_get_property) from [<c03247d8>] (charger_get_property+0x264/0x348) [ 240.549252] [<c03247d8>] (charger_get_property) from [<c0320764>] (power_supply_show_property+0x48/0x1e0) [ 240.558808] [<c0320764>] (power_supply_show_property) from [<c027308c>] (dev_attr_show+0x1c/0x48) [ 240.567664] [<c027308c>] (dev_attr_show) from [<c0141fb0>] (sysfs_kf_seq_show+0x84/0x104) [ 240.575814] [<c0141fb0>] (sysfs_kf_seq_show) from [<c0140b18>] (kernfs_seq_show+0x24/0x28) [ 240.584061] [<c0140b18>] (kernfs_seq_show) from [<c0104574>] (seq_read+0x1b0/0x484) [ 240.591702] [<c0104574>] (seq_read) from [<c00e1e24>] (vfs_read+0x88/0x144) [ 240.598640] [<c00e1e24>] (vfs_read) from [<c00e1f20>] (SyS_read+0x40/0x8c) [ 240.605507] [<c00e1f20>] (SyS_read) from [<c000e760>] (ret_fast_syscall+0x0/0x48) [ 240.612952] 4 locks held by cat/1393: [ 240.616589] #0: (&p->lock){+.+.+.}, at: [<c01043f4>] seq_read+0x30/0x484 [ 240.623414] #1: (&of->mutex){+.+.+.}, at: [<c01417dc>] kernfs_seq_start+0x1c/0x8c [ 240.631086] #2: (s_active#31){++++.+}, at: [<c01417e4>] kernfs_seq_start+0x24/0x8c [ 240.638777] #3: (&map->mutex){+.+...}, at: [<c0287a98>] regmap_read+0x30/0x60 The charger-manager should get reference to fuel gauge power supply on each use of get_property callback. The thermal zone 'tzd' field of power supply should not be used because of the same reason. Additionally this change solves also the issue with nested thermal_zone_get_temp() calls and related false lockdep positive for deadlock for thermal zone's mutex [1]. When fuel gauge is used as source of temperature then the charger manager forwards its get_temp calls to fuel gauge thermal zone. So actually different mutexes are used (one for charger manager thermal zone and second for fuel gauge thermal zone) but for lockdep this is one class of mutex. The recursion is removed by retrieving temperature through power supply's get_property(). In case external thermal zone is used ('cm-thermal-zone' property is present in DTS) the recursion does not exist. Charger manager simply exports POWER_SUPPLY_PROP_TEMP_AMBIENT property (instead of POWER_SUPPLY_PROP_TEMP) thus no thermal zone is created for this power supply. [1] https://lkml.org/lkml/2014/10/6/309 Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com> Cc: <stable@vger.kernel.org> Fixes: 3bb3dbbd56ea ("power_supply: Add initial Charger-Manager driver") Signed-off-by: Sebastian Reichel <sre@kernel.org>
-rw-r--r--drivers/power/charger-manager.c99
-rw-r--r--include/linux/power/charger-manager.h1
2 files changed, 71 insertions, 29 deletions
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c
index 7a1177ea238d..7ae8cb70204a 100644
--- a/drivers/power/charger-manager.c
+++ b/drivers/power/charger-manager.c
@@ -97,6 +97,7 @@ static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
97static bool is_batt_present(struct charger_manager *cm) 97static bool is_batt_present(struct charger_manager *cm)
98{ 98{
99 union power_supply_propval val; 99 union power_supply_propval val;
100 struct power_supply *psy;
100 bool present = false; 101 bool present = false;
101 int i, ret; 102 int i, ret;
102 103
@@ -107,7 +108,11 @@ static bool is_batt_present(struct charger_manager *cm)
107 case CM_NO_BATTERY: 108 case CM_NO_BATTERY:
108 break; 109 break;
109 case CM_FUEL_GAUGE: 110 case CM_FUEL_GAUGE:
110 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 111 psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
112 if (!psy)
113 break;
114
115 ret = psy->get_property(psy,
111 POWER_SUPPLY_PROP_PRESENT, &val); 116 POWER_SUPPLY_PROP_PRESENT, &val);
112 if (ret == 0 && val.intval) 117 if (ret == 0 && val.intval)
113 present = true; 118 present = true;
@@ -167,12 +172,14 @@ static bool is_ext_pwr_online(struct charger_manager *cm)
167static int get_batt_uV(struct charger_manager *cm, int *uV) 172static int get_batt_uV(struct charger_manager *cm, int *uV)
168{ 173{
169 union power_supply_propval val; 174 union power_supply_propval val;
175 struct power_supply *fuel_gauge;
170 int ret; 176 int ret;
171 177
172 if (!cm->fuel_gauge) 178 fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
179 if (!fuel_gauge)
173 return -ENODEV; 180 return -ENODEV;
174 181
175 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 182 ret = fuel_gauge->get_property(fuel_gauge,
176 POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); 183 POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
177 if (ret) 184 if (ret)
178 return ret; 185 return ret;
@@ -248,6 +255,7 @@ static bool is_full_charged(struct charger_manager *cm)
248{ 255{
249 struct charger_desc *desc = cm->desc; 256 struct charger_desc *desc = cm->desc;
250 union power_supply_propval val; 257 union power_supply_propval val;
258 struct power_supply *fuel_gauge;
251 int ret = 0; 259 int ret = 0;
252 int uV; 260 int uV;
253 261
@@ -255,11 +263,15 @@ static bool is_full_charged(struct charger_manager *cm)
255 if (!is_batt_present(cm)) 263 if (!is_batt_present(cm))
256 return false; 264 return false;
257 265
258 if (cm->fuel_gauge && desc->fullbatt_full_capacity > 0) { 266 fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
267 if (!fuel_gauge)
268 return false;
269
270 if (desc->fullbatt_full_capacity > 0) {
259 val.intval = 0; 271 val.intval = 0;
260 272
261 /* Not full if capacity of fuel gauge isn't full */ 273 /* Not full if capacity of fuel gauge isn't full */
262 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 274 ret = fuel_gauge->get_property(fuel_gauge,
263 POWER_SUPPLY_PROP_CHARGE_FULL, &val); 275 POWER_SUPPLY_PROP_CHARGE_FULL, &val);
264 if (!ret && val.intval > desc->fullbatt_full_capacity) 276 if (!ret && val.intval > desc->fullbatt_full_capacity)
265 return true; 277 return true;
@@ -273,10 +285,10 @@ static bool is_full_charged(struct charger_manager *cm)
273 } 285 }
274 286
275 /* Full, if the capacity is more than fullbatt_soc */ 287 /* Full, if the capacity is more than fullbatt_soc */
276 if (cm->fuel_gauge && desc->fullbatt_soc > 0) { 288 if (desc->fullbatt_soc > 0) {
277 val.intval = 0; 289 val.intval = 0;
278 290
279 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 291 ret = fuel_gauge->get_property(fuel_gauge,
280 POWER_SUPPLY_PROP_CAPACITY, &val); 292 POWER_SUPPLY_PROP_CAPACITY, &val);
281 if (!ret && val.intval >= desc->fullbatt_soc) 293 if (!ret && val.intval >= desc->fullbatt_soc)
282 return true; 294 return true;
@@ -551,6 +563,20 @@ static int check_charging_duration(struct charger_manager *cm)
551 return ret; 563 return ret;
552} 564}
553 565
566static int cm_get_battery_temperature_by_psy(struct charger_manager *cm,
567 int *temp)
568{
569 struct power_supply *fuel_gauge;
570
571 fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
572 if (!fuel_gauge)
573 return -ENODEV;
574
575 return fuel_gauge->get_property(fuel_gauge,
576 POWER_SUPPLY_PROP_TEMP,
577 (union power_supply_propval *)temp);
578}
579
554static int cm_get_battery_temperature(struct charger_manager *cm, 580static int cm_get_battery_temperature(struct charger_manager *cm,
555 int *temp) 581 int *temp)
556{ 582{
@@ -560,15 +586,18 @@ static int cm_get_battery_temperature(struct charger_manager *cm,
560 return -ENODEV; 586 return -ENODEV;
561 587
562#ifdef CONFIG_THERMAL 588#ifdef CONFIG_THERMAL
563 ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp); 589 if (cm->tzd_batt) {
564 if (!ret) 590 ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp);
565 /* Calibrate temperature unit */ 591 if (!ret)
566 *temp /= 100; 592 /* Calibrate temperature unit */
567#else 593 *temp /= 100;
568 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 594 } else
569 POWER_SUPPLY_PROP_TEMP,
570 (union power_supply_propval *)temp);
571#endif 595#endif
596 {
597 /* if-else continued from CONFIG_THERMAL */
598 ret = cm_get_battery_temperature_by_psy(cm, temp);
599 }
600
572 return ret; 601 return ret;
573} 602}
574 603
@@ -827,6 +856,7 @@ static int charger_get_property(struct power_supply *psy,
827 struct charger_manager *cm = container_of(psy, 856 struct charger_manager *cm = container_of(psy,
828 struct charger_manager, charger_psy); 857 struct charger_manager, charger_psy);
829 struct charger_desc *desc = cm->desc; 858 struct charger_desc *desc = cm->desc;
859 struct power_supply *fuel_gauge;
830 int ret = 0; 860 int ret = 0;
831 int uV; 861 int uV;
832 862
@@ -857,14 +887,20 @@ static int charger_get_property(struct power_supply *psy,
857 ret = get_batt_uV(cm, &val->intval); 887 ret = get_batt_uV(cm, &val->intval);
858 break; 888 break;
859 case POWER_SUPPLY_PROP_CURRENT_NOW: 889 case POWER_SUPPLY_PROP_CURRENT_NOW:
860 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 890 fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
891 if (!fuel_gauge) {
892 ret = -ENODEV;
893 break;
894 }
895 ret = fuel_gauge->get_property(fuel_gauge,
861 POWER_SUPPLY_PROP_CURRENT_NOW, val); 896 POWER_SUPPLY_PROP_CURRENT_NOW, val);
862 break; 897 break;
863 case POWER_SUPPLY_PROP_TEMP: 898 case POWER_SUPPLY_PROP_TEMP:
864 case POWER_SUPPLY_PROP_TEMP_AMBIENT: 899 case POWER_SUPPLY_PROP_TEMP_AMBIENT:
865 return cm_get_battery_temperature(cm, &val->intval); 900 return cm_get_battery_temperature(cm, &val->intval);
866 case POWER_SUPPLY_PROP_CAPACITY: 901 case POWER_SUPPLY_PROP_CAPACITY:
867 if (!cm->fuel_gauge) { 902 fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
903 if (!fuel_gauge) {
868 ret = -ENODEV; 904 ret = -ENODEV;
869 break; 905 break;
870 } 906 }
@@ -875,7 +911,7 @@ static int charger_get_property(struct power_supply *psy,
875 break; 911 break;
876 } 912 }
877 913
878 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 914 ret = fuel_gauge->get_property(fuel_gauge,
879 POWER_SUPPLY_PROP_CAPACITY, val); 915 POWER_SUPPLY_PROP_CAPACITY, val);
880 if (ret) 916 if (ret)
881 break; 917 break;
@@ -924,7 +960,14 @@ static int charger_get_property(struct power_supply *psy,
924 break; 960 break;
925 case POWER_SUPPLY_PROP_CHARGE_NOW: 961 case POWER_SUPPLY_PROP_CHARGE_NOW:
926 if (is_charging(cm)) { 962 if (is_charging(cm)) {
927 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 963 fuel_gauge = power_supply_get_by_name(
964 cm->desc->psy_fuel_gauge);
965 if (!fuel_gauge) {
966 ret = -ENODEV;
967 break;
968 }
969
970 ret = fuel_gauge->get_property(fuel_gauge,
928 POWER_SUPPLY_PROP_CHARGE_NOW, 971 POWER_SUPPLY_PROP_CHARGE_NOW,
929 val); 972 val);
930 if (ret) { 973 if (ret) {
@@ -1486,14 +1529,15 @@ err:
1486 return ret; 1529 return ret;
1487} 1530}
1488 1531
1489static int cm_init_thermal_data(struct charger_manager *cm) 1532static int cm_init_thermal_data(struct charger_manager *cm,
1533 struct power_supply *fuel_gauge)
1490{ 1534{
1491 struct charger_desc *desc = cm->desc; 1535 struct charger_desc *desc = cm->desc;
1492 union power_supply_propval val; 1536 union power_supply_propval val;
1493 int ret; 1537 int ret;
1494 1538
1495 /* Verify whether fuel gauge provides battery temperature */ 1539 /* Verify whether fuel gauge provides battery temperature */
1496 ret = cm->fuel_gauge->get_property(cm->fuel_gauge, 1540 ret = fuel_gauge->get_property(fuel_gauge,
1497 POWER_SUPPLY_PROP_TEMP, &val); 1541 POWER_SUPPLY_PROP_TEMP, &val);
1498 1542
1499 if (!ret) { 1543 if (!ret) {
@@ -1503,8 +1547,6 @@ static int cm_init_thermal_data(struct charger_manager *cm)
1503 cm->desc->measure_battery_temp = true; 1547 cm->desc->measure_battery_temp = true;
1504 } 1548 }
1505#ifdef CONFIG_THERMAL 1549#ifdef CONFIG_THERMAL
1506 cm->tzd_batt = cm->fuel_gauge->tzd;
1507
1508 if (ret && desc->thermal_zone) { 1550 if (ret && desc->thermal_zone) {
1509 cm->tzd_batt = 1551 cm->tzd_batt =
1510 thermal_zone_get_zone_by_name(desc->thermal_zone); 1552 thermal_zone_get_zone_by_name(desc->thermal_zone);
@@ -1667,6 +1709,7 @@ static int charger_manager_probe(struct platform_device *pdev)
1667 int ret = 0, i = 0; 1709 int ret = 0, i = 0;
1668 int j = 0; 1710 int j = 0;
1669 union power_supply_propval val; 1711 union power_supply_propval val;
1712 struct power_supply *fuel_gauge;
1670 1713
1671 if (g_desc && !rtc_dev && g_desc->rtc_name) { 1714 if (g_desc && !rtc_dev && g_desc->rtc_name) {
1672 rtc_dev = rtc_class_open(g_desc->rtc_name); 1715 rtc_dev = rtc_class_open(g_desc->rtc_name);
@@ -1745,8 +1788,8 @@ static int charger_manager_probe(struct platform_device *pdev)
1745 } 1788 }
1746 } 1789 }
1747 1790
1748 cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge); 1791 fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
1749 if (!cm->fuel_gauge) { 1792 if (!fuel_gauge) {
1750 dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", 1793 dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
1751 desc->psy_fuel_gauge); 1794 desc->psy_fuel_gauge);
1752 return -ENODEV; 1795 return -ENODEV;
@@ -1789,13 +1832,13 @@ static int charger_manager_probe(struct platform_device *pdev)
1789 cm->charger_psy.num_properties = psy_default.num_properties; 1832 cm->charger_psy.num_properties = psy_default.num_properties;
1790 1833
1791 /* Find which optional psy-properties are available */ 1834 /* Find which optional psy-properties are available */
1792 if (!cm->fuel_gauge->get_property(cm->fuel_gauge, 1835 if (!fuel_gauge->get_property(fuel_gauge,
1793 POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { 1836 POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
1794 cm->charger_psy.properties[cm->charger_psy.num_properties] = 1837 cm->charger_psy.properties[cm->charger_psy.num_properties] =
1795 POWER_SUPPLY_PROP_CHARGE_NOW; 1838 POWER_SUPPLY_PROP_CHARGE_NOW;
1796 cm->charger_psy.num_properties++; 1839 cm->charger_psy.num_properties++;
1797 } 1840 }
1798 if (!cm->fuel_gauge->get_property(cm->fuel_gauge, 1841 if (!fuel_gauge->get_property(fuel_gauge,
1799 POWER_SUPPLY_PROP_CURRENT_NOW, 1842 POWER_SUPPLY_PROP_CURRENT_NOW,
1800 &val)) { 1843 &val)) {
1801 cm->charger_psy.properties[cm->charger_psy.num_properties] = 1844 cm->charger_psy.properties[cm->charger_psy.num_properties] =
@@ -1803,7 +1846,7 @@ static int charger_manager_probe(struct platform_device *pdev)
1803 cm->charger_psy.num_properties++; 1846 cm->charger_psy.num_properties++;
1804 } 1847 }
1805 1848
1806 ret = cm_init_thermal_data(cm); 1849 ret = cm_init_thermal_data(cm, fuel_gauge);
1807 if (ret) { 1850 if (ret) {
1808 dev_err(&pdev->dev, "Failed to initialize thermal data\n"); 1851 dev_err(&pdev->dev, "Failed to initialize thermal data\n");
1809 cm->desc->measure_battery_temp = false; 1852 cm->desc->measure_battery_temp = false;
diff --git a/include/linux/power/charger-manager.h b/include/linux/power/charger-manager.h
index 07e7945a1ff2..5d90d329e0ba 100644
--- a/include/linux/power/charger-manager.h
+++ b/include/linux/power/charger-manager.h
@@ -253,7 +253,6 @@ struct charger_manager {
253 struct device *dev; 253 struct device *dev;
254 struct charger_desc *desc; 254 struct charger_desc *desc;
255 255
256 struct power_supply *fuel_gauge;
257 struct power_supply **charger_stat; 256 struct power_supply **charger_stat;
258 257
259#ifdef CONFIG_THERMAL 258#ifdef CONFIG_THERMAL