]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/battery-util.c
Merge pull request #27926 from DaanDeMeyer/repart-offline
[thirdparty/systemd.git] / src / shared / battery-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "sd-device.h"
4
5 #include "device-private.h"
6 #include "device-util.h"
7 #include "string-util.h"
8 #include "battery-util.h"
9
10 #define BATTERY_LOW_CAPACITY_LEVEL 5
11
12 static int device_is_power_sink(sd_device *device) {
13 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
14 bool found_source = false, found_sink = false;
15 sd_device *parent, *d;
16 int r;
17
18 assert(device);
19
20 /* USB-C power supply device has two power roles: source or sink. See,
21 * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-typec */
22
23 r = sd_device_enumerator_new(&e);
24 if (r < 0)
25 return r;
26
27 r = sd_device_enumerator_allow_uninitialized(e);
28 if (r < 0)
29 return r;
30
31 r = sd_device_enumerator_add_match_subsystem(e, "typec", true);
32 if (r < 0)
33 return r;
34
35 r = sd_device_get_parent(device, &parent);
36 if (r < 0)
37 return r;
38
39 r = sd_device_enumerator_add_match_parent(e, parent);
40 if (r < 0)
41 return r;
42
43 FOREACH_DEVICE(e, d) {
44 const char *val;
45
46 r = sd_device_get_sysattr_value(d, "power_role", &val);
47 if (r < 0) {
48 if (r != -ENOENT)
49 log_device_debug_errno(d, r, "Failed to read 'power_role' sysfs attribute, ignoring: %m");
50 continue;
51 }
52
53 if (strstr(val, "[source]")) {
54 found_source = true;
55 log_device_debug(d, "The USB type-C port is in power source mode.");
56 } else if (strstr(val, "[sink]")) {
57 found_sink = true;
58 log_device_debug(d, "The USB type-C port is in power sink mode.");
59 }
60 }
61
62 if (found_sink)
63 log_device_debug(device, "The USB type-C device has at least one port in power sink mode.");
64 else if (!found_source)
65 log_device_debug(device, "The USB type-C device has no port in power source mode, assuming the device is in power sink mode.");
66 else
67 log_device_debug(device, "All USB type-C ports are in power source mode.");
68
69 return found_sink || !found_source;
70 }
71
72 static bool battery_is_discharging(sd_device *d) {
73 const char *val;
74 int r;
75
76 assert(d);
77
78 r = sd_device_get_sysattr_value(d, "scope", &val);
79 if (r < 0) {
80 if (r != -ENOENT)
81 log_device_debug_errno(d, r, "Failed to read 'scope' sysfs attribute, ignoring: %m");
82 } else if (streq(val, "Device")) {
83 log_device_debug(d, "The power supply is a device battery, ignoring device.");
84 return false;
85 }
86
87 r = device_get_sysattr_bool(d, "present");
88 if (r < 0)
89 log_device_debug_errno(d, r, "Failed to read 'present' sysfs attribute, assuming the battery is present: %m");
90 else if (r == 0) {
91 log_device_debug(d, "The battery is not present, ignoring the power supply.");
92 return false;
93 }
94
95 /* Possible values: "Unknown", "Charging", "Discharging", "Not charging", "Full" */
96 r = sd_device_get_sysattr_value(d, "status", &val);
97 if (r < 0) {
98 log_device_debug_errno(d, r, "Failed to read 'status' sysfs attribute, assuming the battery is discharging: %m");
99 return true;
100 }
101 if (!streq(val, "Discharging")) {
102 log_device_debug(d, "The battery status is '%s', assuming the battery is not used as a power source of this machine.", val);
103 return false;
104 }
105
106 return true;
107 }
108
109 int on_ac_power(void) {
110 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
111 bool found_ac_online = false, found_discharging_battery = false;
112 sd_device *d;
113 int r;
114
115 r = sd_device_enumerator_new(&e);
116 if (r < 0)
117 return r;
118
119 r = sd_device_enumerator_allow_uninitialized(e);
120 if (r < 0)
121 return r;
122
123 r = sd_device_enumerator_add_match_subsystem(e, "power_supply", true);
124 if (r < 0)
125 return r;
126
127 FOREACH_DEVICE(e, d) {
128 /* See
129 * https://github.com/torvalds/linux/blob/4eef766b7d4d88f0b984781bc1bcb574a6eafdc7/include/linux/power_supply.h#L176
130 * for defined power source types. Also see:
131 * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-power */
132
133 const char *val;
134 r = sd_device_get_sysattr_value(d, "type", &val);
135 if (r < 0) {
136 log_device_debug_errno(d, r, "Failed to read 'type' sysfs attribute, ignoring device: %m");
137 continue;
138 }
139
140 /* Ignore USB-C power supply in source mode. See issue #21988. */
141 if (streq(val, "USB")) {
142 r = device_is_power_sink(d);
143 if (r <= 0) {
144 if (r < 0)
145 log_device_debug_errno(d, r, "Failed to determine the current power role, ignoring device: %m");
146 else
147 log_device_debug(d, "USB power supply is in source mode, ignoring device.");
148 continue;
149 }
150 }
151
152 if (streq(val, "Battery")) {
153 if (battery_is_discharging(d)) {
154 found_discharging_battery = true;
155 log_device_debug(d, "The power supply is a battery and currently discharging.");
156 }
157 continue;
158 }
159
160 r = device_get_sysattr_unsigned(d, "online", NULL);
161 if (r < 0) {
162 log_device_debug_errno(d, r, "Failed to query 'online' sysfs attribute, ignoring device: %m");
163 continue;
164 } else if (r > 0) /* At least 1 and 2 are defined as different types of 'online' */
165 found_ac_online = true;
166
167 log_device_debug(d, "The power supply is currently %s.", r > 0 ? "online" : "offline");
168 }
169
170 if (found_ac_online) {
171 log_debug("Found at least one online non-battery power supply, system is running on AC.");
172 return true;
173 } else if (found_discharging_battery) {
174 log_debug("Found at least one discharging battery and no online power sources, assuming system is running from battery.");
175 return false;
176 } else {
177 log_debug("No power supply reported online and no discharging battery found, assuming system is running on AC.");
178 return true;
179 }
180 }
181
182 /* Get the list of batteries */
183 int battery_enumerator_new(sd_device_enumerator **ret) {
184 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
185 int r;
186
187 assert(ret);
188
189 r = sd_device_enumerator_new(&e);
190 if (r < 0)
191 return r;
192
193 r = sd_device_enumerator_add_match_subsystem(e, "power_supply", /* match = */ true);
194 if (r < 0)
195 return r;
196
197 r = sd_device_enumerator_allow_uninitialized(e);
198 if (r < 0)
199 return r;
200
201 r = sd_device_enumerator_add_match_sysattr(e, "type", "Battery", /* match = */ true);
202 if (r < 0)
203 return r;
204
205 r = sd_device_enumerator_add_match_sysattr(e, "present", "1", /* match = */ true);
206 if (r < 0)
207 return r;
208
209 r = sd_device_enumerator_add_match_sysattr(e, "scope", "Device", /* match = */ false);
210 if (r < 0)
211 return r;
212
213 *ret = TAKE_PTR(e);
214 return 0;
215 }
216
217 /* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */
218 int battery_read_capacity_percentage(sd_device *dev) {
219 int battery_capacity, r;
220
221 assert(dev);
222
223 r = device_get_sysattr_int(dev, "capacity", &battery_capacity);
224 if (r < 0)
225 return log_device_debug_errno(dev, r, "Failed to read/parse POWER_SUPPLY_CAPACITY: %m");
226
227 if (battery_capacity < 0 || battery_capacity > 100)
228 return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity");
229
230 return battery_capacity;
231 }
232
233 /* If a battery whose percentage capacity is <= 5% exists, and we're not on AC power, return success */
234 int battery_is_discharging_and_low(void) {
235 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
236 bool unsure = false, found_low = false;
237 sd_device *dev;
238 int r;
239
240 /* We have not used battery capacity_level since value is set to full
241 * or Normal in case ACPI is not working properly. In case of no battery
242 * 0 will be returned and system will be suspended for 1st cycle then hibernated */
243
244 r = on_ac_power();
245 if (r < 0)
246 log_debug_errno(r, "Failed to check if the system is running on AC, assuming it is not: %m");
247 if (r > 0)
248 return false;
249
250 r = battery_enumerator_new(&e);
251 if (r < 0)
252 return log_debug_errno(r, "Failed to initialize battery enumerator: %m");
253
254 FOREACH_DEVICE(e, dev) {
255 int level;
256
257 level = battery_read_capacity_percentage(dev);
258 if (level < 0) {
259 unsure = true;
260 continue;
261 }
262
263 if (level > BATTERY_LOW_CAPACITY_LEVEL) /* Found a charged battery */
264 return false;
265
266 found_low = true;
267 }
268
269 /* If we found a battery whose state we couldn't read, don't assume we are in low battery state */
270 if (unsure)
271 return false;
272
273 /* Found no charged battery, but did find low batteries */
274 if (found_low)
275 return true;
276
277 /* Found neither charged nor low batteries? let's return that we aren't on low battery state */
278 return false;
279 }