Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * Sysfs interface for the universal power supply monitor class
4 : : *
5 : : * Copyright © 2007 David Woodhouse <dwmw2@infradead.org>
6 : : * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
7 : : * Copyright © 2004 Szabolcs Gyurko
8 : : * Copyright © 2003 Ian Molton <spyro@f2s.com>
9 : : *
10 : : * Modified: 2004, Oct Szabolcs Gyurko
11 : : */
12 : :
13 : : #include <linux/ctype.h>
14 : : #include <linux/device.h>
15 : : #include <linux/power_supply.h>
16 : : #include <linux/slab.h>
17 : : #include <linux/stat.h>
18 : :
19 : : #include "power_supply.h"
20 : :
21 : : /*
22 : : * This is because the name "current" breaks the device attr macro.
23 : : * The "current" word resolves to "(get_current())" so instead of
24 : : * "current" "(get_current())" appears in the sysfs.
25 : : *
26 : : * The source of this definition is the device.h which calls __ATTR
27 : : * macro in sysfs.h which calls the __stringify macro.
28 : : *
29 : : * Only modification that the name is not tried to be resolved
30 : : * (as a macro let's say).
31 : : */
32 : :
33 : : #define POWER_SUPPLY_ATTR(_name) \
34 : : { \
35 : : .attr = { .name = #_name }, \
36 : : .show = power_supply_show_property, \
37 : : .store = power_supply_store_property, \
38 : : }
39 : :
40 : : static struct device_attribute power_supply_attrs[];
41 : :
42 : : static const char * const power_supply_type_text[] = {
43 : : "Unknown", "Battery", "UPS", "Mains", "USB",
44 : : "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
45 : : "USB_PD", "USB_PD_DRP", "BrickID"
46 : : };
47 : :
48 : : static const char * const power_supply_usb_type_text[] = {
49 : : "Unknown", "SDP", "DCP", "CDP", "ACA", "C",
50 : : "PD", "PD_DRP", "PD_PPS", "BrickID"
51 : : };
52 : :
53 : : static const char * const power_supply_status_text[] = {
54 : : "Unknown", "Charging", "Discharging", "Not charging", "Full"
55 : : };
56 : :
57 : : static const char * const power_supply_charge_type_text[] = {
58 : : "Unknown", "N/A", "Trickle", "Fast", "Standard", "Adaptive", "Custom"
59 : : };
60 : :
61 : : static const char * const power_supply_health_text[] = {
62 : : "Unknown", "Good", "Overheat", "Dead", "Over voltage",
63 : : "Unspecified failure", "Cold", "Watchdog timer expire",
64 : : "Safety timer expire", "Over current"
65 : : };
66 : :
67 : : static const char * const power_supply_technology_text[] = {
68 : : "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
69 : : "LiMn"
70 : : };
71 : :
72 : : static const char * const power_supply_capacity_level_text[] = {
73 : : "Unknown", "Critical", "Low", "Normal", "High", "Full"
74 : : };
75 : :
76 : : static const char * const power_supply_scope_text[] = {
77 : : "Unknown", "System", "Device"
78 : : };
79 : :
80 : : static ssize_t power_supply_show_usb_type(struct device *dev,
81 : : enum power_supply_usb_type *usb_types,
82 : : ssize_t num_usb_types,
83 : : union power_supply_propval *value,
84 : : char *buf)
85 : : {
86 : : enum power_supply_usb_type usb_type;
87 : : ssize_t count = 0;
88 : : bool match = false;
89 : : int i;
90 : :
91 : : for (i = 0; i < num_usb_types; ++i) {
92 : : usb_type = usb_types[i];
93 : :
94 : : if (value->intval == usb_type) {
95 : : count += sprintf(buf + count, "[%s] ",
96 : : power_supply_usb_type_text[usb_type]);
97 : : match = true;
98 : : } else {
99 : : count += sprintf(buf + count, "%s ",
100 : : power_supply_usb_type_text[usb_type]);
101 : : }
102 : : }
103 : :
104 : : if (!match) {
105 : : dev_warn(dev, "driver reporting unsupported connected type\n");
106 : : return -EINVAL;
107 : : }
108 : :
109 : : if (count)
110 : : buf[count - 1] = '\n';
111 : :
112 : : return count;
113 : : }
114 : :
115 : 0 : static ssize_t power_supply_show_property(struct device *dev,
116 : : struct device_attribute *attr,
117 : : char *buf) {
118 : 0 : ssize_t ret;
119 [ # # ]: 0 : struct power_supply *psy = dev_get_drvdata(dev);
120 : 0 : enum power_supply_property psp = attr - power_supply_attrs;
121 : 0 : union power_supply_propval value;
122 : :
123 [ # # ]: 0 : if (psp == POWER_SUPPLY_PROP_TYPE) {
124 : 0 : value.intval = psy->desc->type;
125 : : } else {
126 : 0 : ret = power_supply_get_property(psy, psp, &value);
127 : :
128 [ # # ]: 0 : if (ret < 0) {
129 [ # # ]: 0 : if (ret == -ENODATA)
130 : : dev_dbg(dev, "driver has no data for `%s' property\n",
131 : : attr->attr.name);
132 [ # # ]: 0 : else if (ret != -ENODEV && ret != -EAGAIN)
133 [ # # ]: 0 : dev_err_ratelimited(dev,
134 : : "driver failed to report `%s' property: %zd\n",
135 : : attr->attr.name, ret);
136 : 0 : return ret;
137 : : }
138 : : }
139 : :
140 [ # # # # : 0 : switch (psp) {
# # # # #
# ]
141 : 0 : case POWER_SUPPLY_PROP_STATUS:
142 : 0 : ret = sprintf(buf, "%s\n",
143 : 0 : power_supply_status_text[value.intval]);
144 : 0 : break;
145 : 0 : case POWER_SUPPLY_PROP_CHARGE_TYPE:
146 : 0 : ret = sprintf(buf, "%s\n",
147 : 0 : power_supply_charge_type_text[value.intval]);
148 : 0 : break;
149 : 0 : case POWER_SUPPLY_PROP_HEALTH:
150 : 0 : ret = sprintf(buf, "%s\n",
151 : 0 : power_supply_health_text[value.intval]);
152 : 0 : break;
153 : 0 : case POWER_SUPPLY_PROP_TECHNOLOGY:
154 : 0 : ret = sprintf(buf, "%s\n",
155 : 0 : power_supply_technology_text[value.intval]);
156 : 0 : break;
157 : 0 : case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
158 : 0 : ret = sprintf(buf, "%s\n",
159 : 0 : power_supply_capacity_level_text[value.intval]);
160 : 0 : break;
161 : 0 : case POWER_SUPPLY_PROP_TYPE:
162 : 0 : ret = sprintf(buf, "%s\n",
163 : 0 : power_supply_type_text[value.intval]);
164 : 0 : break;
165 : 0 : case POWER_SUPPLY_PROP_USB_TYPE:
166 : 0 : ret = power_supply_show_usb_type(dev, psy->desc->usb_types,
167 : 0 : psy->desc->num_usb_types,
168 : : &value, buf);
169 : 0 : break;
170 : 0 : case POWER_SUPPLY_PROP_SCOPE:
171 : 0 : ret = sprintf(buf, "%s\n",
172 : 0 : power_supply_scope_text[value.intval]);
173 : 0 : break;
174 : 0 : case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
175 : 0 : ret = sprintf(buf, "%s\n", value.strval);
176 : 0 : break;
177 : 0 : default:
178 : 0 : ret = sprintf(buf, "%d\n", value.intval);
179 : : }
180 : :
181 : : return ret;
182 : : }
183 : :
184 : 0 : static ssize_t power_supply_store_property(struct device *dev,
185 : : struct device_attribute *attr,
186 : : const char *buf, size_t count) {
187 : 0 : ssize_t ret;
188 [ # # # # : 0 : struct power_supply *psy = dev_get_drvdata(dev);
# # # ]
189 : 0 : enum power_supply_property psp = attr - power_supply_attrs;
190 : 0 : union power_supply_propval value;
191 : :
192 [ # # # # : 0 : switch (psp) {
# # # ]
193 : 0 : case POWER_SUPPLY_PROP_STATUS:
194 : 0 : ret = sysfs_match_string(power_supply_status_text, buf);
195 : 0 : break;
196 : 0 : case POWER_SUPPLY_PROP_CHARGE_TYPE:
197 : 0 : ret = sysfs_match_string(power_supply_charge_type_text, buf);
198 : 0 : break;
199 : 0 : case POWER_SUPPLY_PROP_HEALTH:
200 : 0 : ret = sysfs_match_string(power_supply_health_text, buf);
201 : 0 : break;
202 : 0 : case POWER_SUPPLY_PROP_TECHNOLOGY:
203 : 0 : ret = sysfs_match_string(power_supply_technology_text, buf);
204 : 0 : break;
205 : 0 : case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
206 : 0 : ret = sysfs_match_string(power_supply_capacity_level_text, buf);
207 : 0 : break;
208 : 0 : case POWER_SUPPLY_PROP_SCOPE:
209 : 0 : ret = sysfs_match_string(power_supply_scope_text, buf);
210 : 0 : break;
211 : : default:
212 : : ret = -EINVAL;
213 : : }
214 : :
215 : : /*
216 : : * If no match was found, then check to see if it is an integer.
217 : : * Integer values are valid for enums in addition to the text value.
218 : : */
219 [ # # ]: 0 : if (ret < 0) {
220 : 0 : long long_val;
221 : :
222 : 0 : ret = kstrtol(buf, 10, &long_val);
223 [ # # ]: 0 : if (ret < 0)
224 : 0 : return ret;
225 : :
226 : 0 : ret = long_val;
227 : : }
228 : :
229 : 0 : value.intval = ret;
230 : :
231 : 0 : ret = power_supply_set_property(psy, psp, &value);
232 [ # # ]: 0 : if (ret < 0)
233 : : return ret;
234 : :
235 : 0 : return count;
236 : : }
237 : :
238 : : /* Must be in the same order as POWER_SUPPLY_PROP_* */
239 : : static struct device_attribute power_supply_attrs[] = {
240 : : /* Properties of type `int' */
241 : : POWER_SUPPLY_ATTR(status),
242 : : POWER_SUPPLY_ATTR(charge_type),
243 : : POWER_SUPPLY_ATTR(health),
244 : : POWER_SUPPLY_ATTR(present),
245 : : POWER_SUPPLY_ATTR(online),
246 : : POWER_SUPPLY_ATTR(authentic),
247 : : POWER_SUPPLY_ATTR(technology),
248 : : POWER_SUPPLY_ATTR(cycle_count),
249 : : POWER_SUPPLY_ATTR(voltage_max),
250 : : POWER_SUPPLY_ATTR(voltage_min),
251 : : POWER_SUPPLY_ATTR(voltage_max_design),
252 : : POWER_SUPPLY_ATTR(voltage_min_design),
253 : : POWER_SUPPLY_ATTR(voltage_now),
254 : : POWER_SUPPLY_ATTR(voltage_avg),
255 : : POWER_SUPPLY_ATTR(voltage_ocv),
256 : : POWER_SUPPLY_ATTR(voltage_boot),
257 : : POWER_SUPPLY_ATTR(current_max),
258 : : POWER_SUPPLY_ATTR(current_now),
259 : : POWER_SUPPLY_ATTR(current_avg),
260 : : POWER_SUPPLY_ATTR(current_boot),
261 : : POWER_SUPPLY_ATTR(power_now),
262 : : POWER_SUPPLY_ATTR(power_avg),
263 : : POWER_SUPPLY_ATTR(charge_full_design),
264 : : POWER_SUPPLY_ATTR(charge_empty_design),
265 : : POWER_SUPPLY_ATTR(charge_full),
266 : : POWER_SUPPLY_ATTR(charge_empty),
267 : : POWER_SUPPLY_ATTR(charge_now),
268 : : POWER_SUPPLY_ATTR(charge_avg),
269 : : POWER_SUPPLY_ATTR(charge_counter),
270 : : POWER_SUPPLY_ATTR(constant_charge_current),
271 : : POWER_SUPPLY_ATTR(constant_charge_current_max),
272 : : POWER_SUPPLY_ATTR(constant_charge_voltage),
273 : : POWER_SUPPLY_ATTR(constant_charge_voltage_max),
274 : : POWER_SUPPLY_ATTR(charge_control_limit),
275 : : POWER_SUPPLY_ATTR(charge_control_limit_max),
276 : : POWER_SUPPLY_ATTR(charge_control_start_threshold),
277 : : POWER_SUPPLY_ATTR(charge_control_end_threshold),
278 : : POWER_SUPPLY_ATTR(input_current_limit),
279 : : POWER_SUPPLY_ATTR(input_voltage_limit),
280 : : POWER_SUPPLY_ATTR(input_power_limit),
281 : : POWER_SUPPLY_ATTR(energy_full_design),
282 : : POWER_SUPPLY_ATTR(energy_empty_design),
283 : : POWER_SUPPLY_ATTR(energy_full),
284 : : POWER_SUPPLY_ATTR(energy_empty),
285 : : POWER_SUPPLY_ATTR(energy_now),
286 : : POWER_SUPPLY_ATTR(energy_avg),
287 : : POWER_SUPPLY_ATTR(capacity),
288 : : POWER_SUPPLY_ATTR(capacity_alert_min),
289 : : POWER_SUPPLY_ATTR(capacity_alert_max),
290 : : POWER_SUPPLY_ATTR(capacity_level),
291 : : POWER_SUPPLY_ATTR(temp),
292 : : POWER_SUPPLY_ATTR(temp_max),
293 : : POWER_SUPPLY_ATTR(temp_min),
294 : : POWER_SUPPLY_ATTR(temp_alert_min),
295 : : POWER_SUPPLY_ATTR(temp_alert_max),
296 : : POWER_SUPPLY_ATTR(temp_ambient),
297 : : POWER_SUPPLY_ATTR(temp_ambient_alert_min),
298 : : POWER_SUPPLY_ATTR(temp_ambient_alert_max),
299 : : POWER_SUPPLY_ATTR(time_to_empty_now),
300 : : POWER_SUPPLY_ATTR(time_to_empty_avg),
301 : : POWER_SUPPLY_ATTR(time_to_full_now),
302 : : POWER_SUPPLY_ATTR(time_to_full_avg),
303 : : POWER_SUPPLY_ATTR(type),
304 : : POWER_SUPPLY_ATTR(usb_type),
305 : : POWER_SUPPLY_ATTR(scope),
306 : : POWER_SUPPLY_ATTR(precharge_current),
307 : : POWER_SUPPLY_ATTR(charge_term_current),
308 : : POWER_SUPPLY_ATTR(calibrate),
309 : : /* Properties of type `const char *' */
310 : : POWER_SUPPLY_ATTR(model_name),
311 : : POWER_SUPPLY_ATTR(manufacturer),
312 : : POWER_SUPPLY_ATTR(serial_number),
313 : : };
314 : :
315 : : static struct attribute *
316 : : __power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
317 : :
318 : 0 : static umode_t power_supply_attr_is_visible(struct kobject *kobj,
319 : : struct attribute *attr,
320 : : int attrno)
321 : : {
322 : 0 : struct device *dev = container_of(kobj, struct device, kobj);
323 [ # # ]: 0 : struct power_supply *psy = dev_get_drvdata(dev);
324 : 0 : umode_t mode = S_IRUSR | S_IRGRP | S_IROTH;
325 : 0 : int i;
326 : :
327 [ # # ]: 0 : if (attrno == POWER_SUPPLY_PROP_TYPE)
328 : : return mode;
329 : :
330 [ # # ]: 0 : for (i = 0; i < psy->desc->num_properties; i++) {
331 : 0 : int property = psy->desc->properties[i];
332 : :
333 [ # # ]: 0 : if (property == attrno) {
334 [ # # # # ]: 0 : if (psy->desc->property_is_writeable &&
335 : 0 : psy->desc->property_is_writeable(psy, property) > 0)
336 : 0 : mode |= S_IWUSR;
337 : :
338 : 0 : return mode;
339 : : }
340 : : }
341 : :
342 : : return 0;
343 : : }
344 : :
345 : : static struct attribute_group power_supply_attr_group = {
346 : : .attrs = __power_supply_attrs,
347 : : .is_visible = power_supply_attr_is_visible,
348 : : };
349 : :
350 : : static const struct attribute_group *power_supply_attr_groups[] = {
351 : : &power_supply_attr_group,
352 : : NULL,
353 : : };
354 : :
355 : 13 : void power_supply_init_attrs(struct device_type *dev_type)
356 : : {
357 : 13 : int i;
358 : :
359 : 13 : dev_type->groups = power_supply_attr_groups;
360 : :
361 [ + + ]: 936 : for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++)
362 : 923 : __power_supply_attrs[i] = &power_supply_attrs[i].attr;
363 : 13 : }
364 : :
365 : 0 : static char *kstruprdup(const char *str, gfp_t gfp)
366 : : {
367 : 0 : char *ret, *ustr;
368 : :
369 [ # # ]: 0 : ustr = ret = kmalloc(strlen(str) + 1, gfp);
370 : :
371 [ # # ]: 0 : if (!ret)
372 : : return NULL;
373 : :
374 [ # # ]: 0 : while (*str)
375 [ # # ]: 0 : *ustr++ = toupper(*str++);
376 : :
377 : 0 : *ustr = 0;
378 : :
379 : 0 : return ret;
380 : : }
381 : :
382 : 0 : int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
383 : : {
384 [ # # ]: 0 : struct power_supply *psy = dev_get_drvdata(dev);
385 : 0 : int ret = 0, j;
386 : 0 : char *prop_buf;
387 : 0 : char *attrname;
388 : :
389 [ # # # # ]: 0 : if (!psy || !psy->desc) {
390 : : dev_dbg(dev, "No power supply yet\n");
391 : : return ret;
392 : : }
393 : :
394 : 0 : ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->desc->name);
395 [ # # ]: 0 : if (ret)
396 : : return ret;
397 : :
398 : 0 : prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
399 [ # # ]: 0 : if (!prop_buf)
400 : : return -ENOMEM;
401 : :
402 [ # # ]: 0 : for (j = 0; j < psy->desc->num_properties; j++) {
403 : 0 : struct device_attribute *attr;
404 : 0 : char *line;
405 : :
406 : 0 : attr = &power_supply_attrs[psy->desc->properties[j]];
407 : :
408 : 0 : ret = power_supply_show_property(dev, attr, prop_buf);
409 [ # # ]: 0 : if (ret == -ENODEV || ret == -ENODATA) {
410 : : /* When a battery is absent, we expect -ENODEV. Don't abort;
411 : : send the uevent with at least the the PRESENT=0 property */
412 : 0 : ret = 0;
413 : 0 : continue;
414 : : }
415 : :
416 [ # # ]: 0 : if (ret < 0)
417 : 0 : goto out;
418 : :
419 : 0 : line = strchr(prop_buf, '\n');
420 [ # # ]: 0 : if (line)
421 : 0 : *line = 0;
422 : :
423 : 0 : attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
424 [ # # ]: 0 : if (!attrname) {
425 : 0 : ret = -ENOMEM;
426 : 0 : goto out;
427 : : }
428 : :
429 : 0 : ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf);
430 : 0 : kfree(attrname);
431 [ # # ]: 0 : if (ret)
432 : 0 : goto out;
433 : : }
434 : :
435 : 0 : out:
436 : 0 : free_page((unsigned long)prop_buf);
437 : :
438 : 0 : return ret;
439 : : }
|