Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2020 Bastien Nocera <hadess@hadess.net> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify it | ||
5 | * under the terms of the GNU General Public License version 3 as published by | ||
6 | * the Free Software Foundation. | ||
7 | * | ||
8 | */ | ||
9 | |||
10 | #define G_LOG_DOMAIN "PlatformDriver" | ||
11 | |||
12 | #include <gudev/gudev.h> | ||
13 | #include <gio/gio.h> | ||
14 | |||
15 | #include "ppd-driver-platform-profile.h" | ||
16 | #include "ppd-utils.h" | ||
17 | |||
18 | #define LAPMODE_SYSFS_NAME "dytc_lapmode" | ||
19 | #define ACPI_PLATFORM_PROFILE_PATH "/sys/firmware/acpi/platform_profile" | ||
20 | #define ACPI_PLATFORM_PROFILE_CHOICES_PATH "/sys/firmware/acpi/platform_profile_choices" | ||
21 | |||
22 | struct _PpdDriverPlatformProfile | ||
23 | { | ||
24 | PpdDriver parent_instance; | ||
25 | |||
26 | PpdProbeResult probe_result; | ||
27 | GUdevDevice *device; | ||
28 | int lapmode; | ||
29 | PpdProfile acpi_platform_profile; | ||
30 | char **profile_choices; | ||
31 | gboolean has_low_power; | ||
32 | GFileMonitor *lapmode_mon; | ||
33 | GFileMonitor *acpi_platform_profile_mon; | ||
34 | gulong acpi_platform_profile_changed_id; | ||
35 | }; | ||
36 | |||
37 |
4/5✓ Branch 0 taken 130 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 130 times.
✓ Branch 3 taken 130 times.
✗ Branch 4 not taken.
|
788 | G_DEFINE_TYPE (PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD_TYPE_DRIVER_PLATFORM) |
38 | |||
39 | static GObject* | ||
40 | 134 | ppd_driver_platform_profile_constructor (GType type, | |
41 | guint n_construct_params, | ||
42 | GObjectConstructParam *construct_params) | ||
43 | { | ||
44 | 134 | GObject *object; | |
45 | |||
46 | 134 | object = G_OBJECT_CLASS (ppd_driver_platform_profile_parent_class)->constructor (type, | |
47 | n_construct_params, | ||
48 | construct_params); | ||
49 | 134 | g_object_set (object, | |
50 | "driver-name", "platform_profile", | ||
51 | "profiles", PPD_PROFILE_PERFORMANCE | PPD_PROFILE_BALANCED | PPD_PROFILE_POWER_SAVER, | ||
52 | NULL); | ||
53 | |||
54 | 134 | return object; | |
55 | } | ||
56 | |||
57 | static const char * | ||
58 | 294 | profile_to_acpi_platform_profile_value (PpdDriverPlatformProfile *self, | |
59 | PpdProfile profile) | ||
60 | { | ||
61 |
3/4✓ Branch 0 taken 66 times.
✓ Branch 1 taken 148 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 80 times.
|
294 | switch (profile) { |
62 | 66 | case PPD_PROFILE_POWER_SAVER: | |
63 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 64 times.
|
66 | if (!self->has_low_power) |
64 | return "balanced"; | ||
65 |
2/2✓ Branch 1 taken 60 times.
✓ Branch 2 taken 4 times.
|
64 | if (g_strv_contains ((const char * const*) self->profile_choices, "low-power")) |
66 | 60 | return "low-power"; | |
67 | return "quiet"; | ||
68 | case PPD_PROFILE_BALANCED: | ||
69 | return "balanced"; | ||
70 | case PPD_PROFILE_PERFORMANCE: | ||
71 | return "performance"; | ||
72 | } | ||
73 | |||
74 | ✗ | g_debug ("Unhandled ACPI platform profile '%d'", profile); | |
75 | ✗ | g_return_val_if_reached (NULL); | |
76 | } | ||
77 | |||
78 | static PpdProfile | ||
79 | 344 | acpi_platform_profile_value_to_profile (const char *str) | |
80 | { | ||
81 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 344 times.
|
344 | if (str == NULL) |
82 | return PPD_PROFILE_UNSET; | ||
83 | |||
84 |
3/4✓ Branch 0 taken 152 times.
✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 66 times.
|
344 | switch (str[0]) { |
85 | case 'l': /* low-power */ | ||
86 | case 'q': /* quiet */ | ||
87 | return PPD_PROFILE_POWER_SAVER; | ||
88 | 152 | case 'c': /* cool */ | |
89 | case 'b': | ||
90 | 152 | return PPD_PROFILE_BALANCED; | |
91 | 126 | case 'p': | |
92 | 126 | return PPD_PROFILE_PERFORMANCE; | |
93 | ✗ | default: | |
94 | ✗ | g_debug ("Unhandled ACPI platform profile '%c'", str[0]); | |
95 | ✗ | g_return_val_if_reached (PPD_PROFILE_UNSET); | |
96 | } | ||
97 | } | ||
98 | |||
99 | static PpdProfile | ||
100 | 196 | read_platform_profile (void) | |
101 | { | ||
102 | 392 | g_autofree char *platform_profile_path = NULL; | |
103 | 196 | g_autofree char *new_profile_str = NULL; | |
104 | 196 | g_autoptr(GError) error = NULL; | |
105 | 196 | PpdProfile new_profile; | |
106 | |||
107 | 196 | platform_profile_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_PATH); | |
108 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 196 times.
|
196 | if (!g_file_get_contents (platform_profile_path, |
109 | &new_profile_str, NULL, &error)) { | ||
110 | ✗ | g_debug ("Failed to get contents for '%s': %s", | |
111 | platform_profile_path, | ||
112 | error->message); | ||
113 | ✗ | return PPD_PROFILE_UNSET; | |
114 | } | ||
115 | |||
116 | 196 | new_profile = acpi_platform_profile_value_to_profile (new_profile_str); | |
117 | 196 | g_debug ("ACPI performance_profile is now %c, so profile is detected as %s", | |
118 | new_profile_str[0], | ||
119 | ppd_profile_to_str (new_profile)); | ||
120 | 196 | return new_profile; | |
121 | } | ||
122 | |||
123 | static gboolean | ||
124 | 54 | save_platform_profile_choices (PpdDriverPlatformProfile *self) | |
125 | { | ||
126 | 108 | g_autofree char *platform_profile_choices_path = NULL; | |
127 | 54 | g_autofree char *choices_str = NULL; | |
128 | 54 | g_autoptr(GError) error = NULL; | |
129 | |||
130 | 54 | platform_profile_choices_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_CHOICES_PATH); | |
131 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 54 times.
|
54 | if (!g_file_get_contents (platform_profile_choices_path, |
132 | &choices_str, NULL, &error)) { | ||
133 | ✗ | g_debug ("Failed to get contents for '%s': %s", | |
134 | platform_profile_choices_path, | ||
135 | error->message); | ||
136 | ✗ | return FALSE; | |
137 | } | ||
138 | |||
139 | 54 | self->profile_choices = g_strsplit_set (choices_str, " \n", -1); | |
140 | 54 | return TRUE; | |
141 | } | ||
142 | |||
143 | static PpdProbeResult | ||
144 | 54 | verify_acpi_platform_profile_choices (PpdDriverPlatformProfile *self) | |
145 | { | ||
146 | 54 | const char * const *choices = (const char * const*) self->profile_choices; | |
147 | |||
148 |
3/4✓ Branch 1 taken 50 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 50 times.
|
104 | if (g_strv_contains (choices, "balanced") && |
149 | 50 | g_strv_contains (choices, "performance")) { | |
150 |
4/4✓ Branch 1 taken 4 times.
✓ Branch 2 taken 46 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
|
54 | if (g_strv_contains (choices, "low-power") || |
151 | 4 | g_strv_contains (choices, "quiet")) | |
152 | 48 | self->has_low_power = TRUE; | |
153 | else | ||
154 | 2 | g_debug ("No \"low-power\" profile for device, will be emulated"); | |
155 | 50 | return PPD_PROBE_RESULT_SUCCESS; | |
156 | } | ||
157 | return PPD_PROBE_RESULT_DEFER; | ||
158 | } | ||
159 | |||
160 | static void | ||
161 | 16 | update_dytc_lapmode_state (PpdDriverPlatformProfile *self) | |
162 | { | ||
163 | 16 | int new_lapmode; | |
164 | |||
165 | 16 | new_lapmode = g_udev_device_get_sysfs_attr_as_int_uncached (self->device, LAPMODE_SYSFS_NAME); | |
166 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
|
16 | if (new_lapmode == self->lapmode) |
167 | return; | ||
168 | |||
169 | 8 | self->lapmode = new_lapmode; | |
170 |
4/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 6 times.
|
12 | g_debug ("dytc_lapmode is now %s, so profile is %s", |
171 | self->lapmode ? "on" : "off", | ||
172 | self->lapmode ? "degraded" : "not degraded"); | ||
173 | 8 | g_object_set (G_OBJECT (self), | |
174 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
|
8 | "performance-degraded", self->lapmode ? "lap-detected" : NULL, |
175 | NULL); | ||
176 | } | ||
177 | |||
178 | static void | ||
179 | 196 | update_acpi_platform_profile_state (PpdDriverPlatformProfile *self) | |
180 | { | ||
181 | 196 | PpdProfile new_profile; | |
182 | |||
183 | 196 | new_profile = read_platform_profile (); | |
184 |
1/2✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
|
196 | if (new_profile == PPD_PROFILE_UNSET || |
185 |
2/2✓ Branch 0 taken 52 times.
✓ Branch 1 taken 144 times.
|
196 | new_profile == self->acpi_platform_profile) |
186 | return; | ||
187 | |||
188 | 52 | self->acpi_platform_profile = new_profile; | |
189 | 52 | ppd_driver_emit_profile_changed (PPD_DRIVER (self), new_profile); | |
190 | } | ||
191 | |||
192 | static void | ||
193 | 24 | lapmode_changed (GFileMonitor *monitor, | |
194 | GFile *file, | ||
195 | GFile *other_file, | ||
196 | GFileMonitorEvent event_type, | ||
197 | gpointer user_data) | ||
198 | { | ||
199 | 24 | PpdDriverPlatformProfile *self = user_data; | |
200 | |||
201 | 24 | g_debug (LAPMODE_SYSFS_NAME " attribute changed (event: %d)", event_type); | |
202 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | g_return_if_fail (event_type != G_FILE_MONITOR_EVENT_DELETED); |
203 | |||
204 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
|
24 | if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) |
205 | 8 | update_dytc_lapmode_state (self); | |
206 | } | ||
207 | |||
208 | static void | ||
209 | 445 | acpi_platform_profile_changed (GFileMonitor *monitor, | |
210 | GFile *file, | ||
211 | GFile *other_file, | ||
212 | GFileMonitorEvent event_type, | ||
213 | gpointer user_data) | ||
214 | { | ||
215 | 445 | PpdDriverPlatformProfile *self = user_data; | |
216 | |||
217 | 445 | g_debug (ACPI_PLATFORM_PROFILE_PATH " changed (%d)", event_type); | |
218 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 441 times.
|
445 | if (self->probe_result == PPD_PROBE_RESULT_DEFER) { |
219 | 4 | g_signal_emit_by_name (G_OBJECT (self), "probe-request", 0); | |
220 | 4 | return; | |
221 | } | ||
222 | |||
223 |
1/2✓ Branch 0 taken 441 times.
✗ Branch 1 not taken.
|
441 | g_return_if_fail (event_type != G_FILE_MONITOR_EVENT_DELETED); |
224 | |||
225 |
2/2✓ Branch 0 taken 146 times.
✓ Branch 1 taken 295 times.
|
441 | if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) |
226 | 146 | update_acpi_platform_profile_state (self); | |
227 | } | ||
228 | |||
229 | static gboolean | ||
230 | 154 | ppd_driver_platform_profile_activate_profile (PpdDriver *driver, | |
231 | PpdProfile profile, | ||
232 | PpdProfileActivationReason reason, | ||
233 | GError **error) | ||
234 | { | ||
235 | 154 | PpdDriverPlatformProfile *self = PPD_DRIVER_PLATFORM_PROFILE (driver); | |
236 | 308 | g_autoptr(GError) local_error = NULL; | |
237 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 154 times.
|
154 | g_autofree char *platform_profile_path = NULL; |
238 | 154 | const char *platform_profile_value; | |
239 | |||
240 |
1/2✓ Branch 0 taken 154 times.
✗ Branch 1 not taken.
|
154 | g_return_val_if_fail (self->acpi_platform_profile_mon, FALSE); |
241 | |||
242 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 148 times.
|
154 | if (self->acpi_platform_profile == profile) { |
243 | 6 | g_debug ("Can't switch to %s mode, already there", | |
244 | ppd_profile_to_str (profile)); | ||
245 | 6 | return TRUE; | |
246 | } | ||
247 | |||
248 | 148 | platform_profile_value = profile_to_acpi_platform_profile_value (self, profile); | |
249 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 146 times.
|
148 | if (self->acpi_platform_profile == acpi_platform_profile_value_to_profile (platform_profile_value)) { |
250 | 2 | g_debug ("Not switching to platform_profile %s, emulating for %s, already there", | |
251 | platform_profile_value, | ||
252 | ppd_profile_to_str (profile)); | ||
253 | 2 | return TRUE; | |
254 | } | ||
255 | |||
256 | 146 | g_signal_handler_block (G_OBJECT (self->acpi_platform_profile_mon), self->acpi_platform_profile_changed_id); | |
257 | 146 | platform_profile_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_PATH); | |
258 |
2/2✓ Branch 2 taken 2 times.
✓ Branch 3 taken 144 times.
|
146 | if (!ppd_utils_write (platform_profile_path, |
259 | profile_to_acpi_platform_profile_value (self, profile), &local_error)) { | ||
260 | 2 | g_debug ("Failed to write to acpi_platform_profile: %s", local_error->message); | |
261 | 2 | g_propagate_prefixed_error (error, g_steal_pointer (&local_error), | |
262 | "Failed to write to acpi_platform_profile: "); | ||
263 | 2 | g_signal_handler_unblock (G_OBJECT (self->acpi_platform_profile_mon), self->acpi_platform_profile_changed_id); | |
264 | 2 | return FALSE; | |
265 | } | ||
266 | 144 | g_signal_handler_unblock (G_OBJECT (self->acpi_platform_profile_mon), self->acpi_platform_profile_changed_id); | |
267 | |||
268 | 144 | g_debug ("Successfully switched to profile %s", ppd_profile_to_str (profile)); | |
269 | 144 | self->acpi_platform_profile = profile; | |
270 | 144 | return TRUE; | |
271 | } | ||
272 | |||
273 | static int | ||
274 | 8 | find_dytc (GUdevDevice *dev, | |
275 | gpointer user_data) | ||
276 | { | ||
277 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (g_strcmp0 (g_udev_device_get_name (dev), "thinkpad_acpi") != 0) |
278 | return 1; | ||
279 | |||
280 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
8 | if (!g_udev_device_get_sysfs_attr (dev, LAPMODE_SYSFS_NAME)) |
281 | return 1; | ||
282 | |||
283 | return 0; | ||
284 | } | ||
285 | |||
286 | static PpdProbeResult | ||
287 | 128 | ppd_driver_platform_profile_probe (PpdDriver *driver) | |
288 | { | ||
289 | 128 | PpdDriverPlatformProfile *self = PPD_DRIVER_PLATFORM_PROFILE (driver); | |
290 | 256 | g_autoptr(GFile) acpi_platform_profile = NULL; | |
291 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 74 times.
|
128 | g_autofree char *platform_profile_path = NULL; |
292 | |||
293 |
1/2✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
|
128 | g_return_val_if_fail (self->probe_result == PPD_PROBE_RESULT_UNSET, PPD_PROBE_RESULT_FAIL); |
294 | |||
295 | /* Profile interface */ | ||
296 | 128 | platform_profile_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_PATH); | |
297 |
2/2✓ Branch 1 taken 74 times.
✓ Branch 2 taken 54 times.
|
128 | if (!g_file_test (platform_profile_path, G_FILE_TEST_EXISTS)) { |
298 | 74 | g_debug ("No platform_profile sysfs file"); | |
299 | 74 | return PPD_PROBE_RESULT_FAIL; | |
300 | } | ||
301 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 54 times.
|
54 | if (!save_platform_profile_choices (self)) |
302 | return PPD_PROBE_RESULT_FAIL; | ||
303 | 54 | self->probe_result = verify_acpi_platform_profile_choices (self); | |
304 | 54 | if (self->probe_result == PPD_PROBE_RESULT_FAIL) { | |
305 | g_debug ("No supported platform_profile choices"); | ||
306 | return self->probe_result; | ||
307 | } | ||
308 | |||
309 | 54 | acpi_platform_profile = g_file_new_for_path (platform_profile_path); | |
310 | 54 | self->acpi_platform_profile_mon = g_file_monitor (acpi_platform_profile, | |
311 | G_FILE_MONITOR_NONE, | ||
312 | NULL, | ||
313 | NULL); | ||
314 | 108 | self->acpi_platform_profile_changed_id = | |
315 | 54 | g_signal_connect (G_OBJECT (self->acpi_platform_profile_mon), "changed", | |
316 | G_CALLBACK (acpi_platform_profile_changed), self); | ||
317 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 50 times.
|
54 | if (self->probe_result == PPD_PROBE_RESULT_DEFER) { |
318 | 4 | g_debug ("Monitoring platform_profile sysfs file"); | |
319 | 4 | return self->probe_result; | |
320 | } | ||
321 | |||
322 | /* Lenovo-specific proximity sensor */ | ||
323 | 50 | self->device = ppd_utils_find_device ("platform", | |
324 | (GCompareFunc) find_dytc, | ||
325 | NULL); | ||
326 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 8 times.
|
50 | if (!self->device) |
327 | 42 | goto out; | |
328 | |||
329 | 8 | self->lapmode_mon = ppd_utils_monitor_sysfs_attr (self->device, | |
330 | LAPMODE_SYSFS_NAME, | ||
331 | NULL); | ||
332 | 8 | g_signal_connect_object (G_OBJECT (self->lapmode_mon), "changed", | |
333 | G_CALLBACK (lapmode_changed), self, 0); | ||
334 | 8 | update_dytc_lapmode_state (self); | |
335 | |||
336 | 50 | out: | |
337 | 50 | update_acpi_platform_profile_state (self); | |
338 | |||
339 |
2/2✓ Branch 0 taken 42 times.
✓ Branch 1 taken 8 times.
|
50 | g_debug ("%s a dytc_lapmode sysfs attribute to thinkpad_acpi", |
340 | self->device ? "Found" : "Didn't find"); | ||
341 | 50 | return PPD_PROBE_RESULT_SUCCESS; | |
342 | } | ||
343 | |||
344 | static void | ||
345 | 134 | ppd_driver_platform_profile_finalize (GObject *object) | |
346 | { | ||
347 | 134 | PpdDriverPlatformProfile *driver; | |
348 | |||
349 | 134 | driver = PPD_DRIVER_PLATFORM_PROFILE (object); | |
350 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 80 times.
|
134 | g_clear_signal_handler (&driver->acpi_platform_profile_changed_id, |
351 | driver->acpi_platform_profile_mon); | ||
352 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 80 times.
|
134 | g_clear_pointer (&driver->profile_choices, g_strfreev); |
353 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 126 times.
|
134 | g_clear_object (&driver->device); |
354 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 126 times.
|
134 | g_clear_object (&driver->lapmode_mon); |
355 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 80 times.
|
134 | g_clear_object (&driver->acpi_platform_profile_mon); |
356 | 134 | G_OBJECT_CLASS (ppd_driver_platform_profile_parent_class)->finalize (object); | |
357 | 134 | } | |
358 | |||
359 | static void | ||
360 | 130 | ppd_driver_platform_profile_class_init (PpdDriverPlatformProfileClass *klass) | |
361 | { | ||
362 | 130 | GObjectClass *object_class; | |
363 | 130 | PpdDriverClass *driver_class; | |
364 | |||
365 | 130 | object_class = G_OBJECT_CLASS (klass); | |
366 | 130 | object_class->constructor = ppd_driver_platform_profile_constructor; | |
367 | 130 | object_class->finalize = ppd_driver_platform_profile_finalize; | |
368 | |||
369 | 130 | driver_class = PPD_DRIVER_CLASS (klass); | |
370 | 130 | driver_class->probe = ppd_driver_platform_profile_probe; | |
371 | 130 | driver_class->activate_profile = ppd_driver_platform_profile_activate_profile; | |
372 | } | ||
373 | |||
374 | static void | ||
375 | 134 | ppd_driver_platform_profile_init (PpdDriverPlatformProfile *self) | |
376 | { | ||
377 | 134 | self->probe_result = PPD_PROBE_RESULT_UNSET; | |
378 | 134 | } | |
379 |