]>
Commit | Line | Data |
---|---|---|
af4e8e86 LP |
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 | ||
319c4648 LP |
10 | #define BATTERY_LOW_CAPACITY_LEVEL 5 |
11 | ||
af4e8e86 LP |
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 | } | |
319c4648 LP |
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 | sd_device *dev; | |
237 | int r; | |
238 | ||
239 | /* We have not used battery capacity_level since value is set to full | |
240 | * or Normal in case ACPI is not working properly. In case of no battery | |
241 | * 0 will be returned and system will be suspended for 1st cycle then hibernated */ | |
242 | ||
243 | r = on_ac_power(); | |
244 | if (r < 0) | |
245 | log_debug_errno(r, "Failed to check if the system is running on AC, assuming it is not: %m"); | |
246 | if (r > 0) | |
247 | return false; | |
248 | ||
249 | r = battery_enumerator_new(&e); | |
250 | if (r < 0) | |
251 | return log_debug_errno(r, "Failed to initialize battery enumerator: %m"); | |
252 | ||
253 | FOREACH_DEVICE(e, dev) | |
254 | if (battery_read_capacity_percentage(dev) > BATTERY_LOW_CAPACITY_LEVEL) | |
255 | return false; | |
256 | ||
257 | return true; | |
258 | } |