GCC Code Coverage Report


Directory: ./
File: src/ppd-action-amdgpu-panel-power.c
Date: 2025-03-30 20:28:01
Exec Total Coverage
Lines: 103 115 89.6%
Functions: 14 14 100.0%
Branches: 39 58 67.2%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2024 Advanced Micro Devices
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 "AmdgpuPanel"
11
12 #include "config.h"
13
14 #include <gudev/gudev.h>
15
16 #include "ppd-action-amdgpu-panel-power.h"
17 #include "ppd-profile.h"
18 #include "ppd-utils.h"
19
20 #define PANEL_POWER_SYSFS_NAME "amdgpu/panel_power_savings"
21 #define PANEL_STATUS_SYSFS_NAME "status"
22
23 /**
24 * SECTION:ppd-action-amdgpu-panel-power
25 * @Short_description: Power savings for eDP connected displays
26 * @Title: AMDGPU Panel power action
27 *
28 * The AMDGPU panel power action utilizes the sysfs attribute present on some DRM
29 * connectors for amdgpu called "panel_power_savings". This will use an AMD specific
30 * hardware feature for a power savings profile for the panel.
31 *
32 */
33
34 struct _PpdActionAmdgpuPanelPower
35 {
36 PpdAction parent_instance;
37 PpdProfile last_profile;
38
39 GUdevClient *client;
40
41 gint panel_power_saving;
42 gboolean valid_battery;
43 gboolean on_battery;
44 gdouble battery_level;
45 };
46
47
4/5
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 142 times.
✓ Branch 3 taken 142 times.
✗ Branch 4 not taken.
884 G_DEFINE_TYPE (PpdActionAmdgpuPanelPower, ppd_action_amdgpu_panel_power, PPD_TYPE_ACTION)
48
49 static GObject*
50 158 ppd_action_amdgpu_panel_power_constructor (GType type,
51 guint n_construct_params,
52 GObjectConstructParam *construct_params)
53 {
54 158 GObject *object;
55
56 158 object = G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->constructor (type,
57 n_construct_params,
58 construct_params);
59 158 g_object_set (object,
60 "action-name", "amdgpu_panel_power",
61 "action-description", "Panel Power Savings (may affect color quality)",
62 "action-optin", TRUE,
63 NULL);
64
65 158 return object;
66 }
67
68 static gboolean
69 9 panel_connected (GUdevDevice *device)
70 {
71 9 const char *value;
72 18 g_autofree gchar *stripped = NULL;
73
74 9 value = g_udev_device_get_sysfs_attr_uncached (device, PANEL_STATUS_SYSFS_NAME);
75
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (!value)
76 return FALSE;
77
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
18 stripped = g_strchomp (g_strdup (value));
78
79 9 return g_strcmp0 (stripped, "connected") == 0;
80 }
81
82 static gboolean
83 7 set_panel_power (PpdActionAmdgpuPanelPower *self, gint power, GError **error)
84 {
85 7 GList *devices, *l;
86
87 7 devices = g_udev_client_query_by_subsystem (self->client, "drm");
88
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (devices == NULL) {
89 g_set_error_literal (error,
90 G_IO_ERROR,
91 G_IO_ERROR_NOT_FOUND,
92 "no drm devices found");
93 return FALSE;
94 }
95
96
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 3 times.
10 for (l = devices; l != NULL; l = l->next) {
97 7 GUdevDevice *dev = l->data;
98 7 const char *value;
99 7 guint64 parsed;
100
101 7 value = g_udev_device_get_devtype (dev);
102
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
7 if (g_strcmp0 (value, "drm_connector") != 0)
103 continue;
104
105
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
7 if (!panel_connected (dev))
106 continue;
107
108 7 value = g_udev_device_get_sysfs_attr_uncached (dev, PANEL_POWER_SYSFS_NAME);
109
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!value)
110 continue;
111
112 7 parsed = g_ascii_strtoull (value, NULL, 10);
113
114 /* overflow check */
115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (parsed == G_MAXUINT64) {
116 g_set_error (error,
117 G_IO_ERROR,
118 G_IO_ERROR_INVALID_DATA,
119 "cannot parse %s as caused overflow",
120 value);
121 return FALSE;
122 }
123
124
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if (parsed == power)
125 3 continue;
126
127
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if (!ppd_utils_write_sysfs_int (dev, PANEL_POWER_SYSFS_NAME, power, error))
128 return FALSE;
129
130 break;
131 }
132
133 7 g_list_free_full (devices, g_object_unref);
134
135 7 return TRUE;
136 }
137
138 static gboolean
139 7 ppd_action_amdgpu_panel_update_target (PpdActionAmdgpuPanelPower *self,
140 GError **error)
141 {
142 7 gint target = 0;
143
144 /* only activate if we know that we're on battery */
145
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (self->on_battery) {
146
2/3
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
7 switch (self->last_profile) {
147 3 case PPD_PROFILE_POWER_SAVER:
148
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
3 if (!self->battery_level || self->battery_level >= 50)
149 target = 0;
150
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 else if (self->battery_level > 30)
151 target = 1;
152
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 else if (self->battery_level > 20 && self->battery_level <= 30)
153 target = 2;
154 else /* < 20 */
155 1 target = 3;
156 break;
157 4 case PPD_PROFILE_BALANCED:
158
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
4 if (!self->battery_level || self->battery_level >= 30)
159 target = 0;
160 else
161 2 target = 1;
162 break;
163 case PPD_PROFILE_PERFORMANCE:
164 target = 0;
165 break;
166 }
167 }
168
169 7 g_info("Updating panel to %d due to 🔋 %d (%f)", target, self->on_battery, self->battery_level);
170
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 if (!set_panel_power (self, target, error))
171 return FALSE;
172 7 self->panel_power_saving = target;
173
174 7 return TRUE;
175 }
176
177 static gboolean
178 3 ppd_action_amdgpu_panel_power_activate_profile (PpdAction *action,
179 PpdProfile profile,
180 GError **error)
181 {
182 3 PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action);
183 3 self->last_profile = profile;
184
185
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (!self->valid_battery) {
186 2 g_debug ("upower not available; battery data might be stale");
187 2 return TRUE;
188 }
189
190 1 return ppd_action_amdgpu_panel_update_target (self, error);
191 }
192
193 static gboolean
194 2 ppd_action_amdgpu_panel_power_power_changed (PpdAction *action,
195 PpdPowerChangedReason reason,
196 GError **error)
197 {
198 2 PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action);
199
200
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 switch (reason) {
201 1 case PPD_POWER_CHANGED_REASON_UNKNOWN:
202 1 self->valid_battery = FALSE;
203 1 return TRUE;
204 case PPD_POWER_CHANGED_REASON_AC:
205 self->on_battery = FALSE;
206 break;
207 1 case PPD_POWER_CHANGED_REASON_BATTERY:
208 1 self->on_battery = TRUE;
209 1 break;
210 default:
211 g_assert_not_reached ();
212 }
213
214 1 self->valid_battery = TRUE;
215
216 1 return ppd_action_amdgpu_panel_update_target (self, error);
217 }
218
219 static gboolean
220 5 ppd_action_amdgpu_panel_power_battery_changed (PpdAction *action,
221 gdouble val,
222 GError **error)
223 {
224 5 PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action);
225
226 5 self->battery_level = val;
227
228 5 return ppd_action_amdgpu_panel_update_target (self, error);
229 }
230
231 static void
232 2 udev_uevent_cb (GUdevClient *client,
233 gchar *action,
234 GUdevDevice *device,
235 gpointer user_data)
236 {
237 2 PpdActionAmdgpuPanelPower *self = user_data;
238
239
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!g_str_equal (action, "add"))
240 return;
241
242
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (!g_udev_device_has_sysfs_attr (device, PANEL_POWER_SYSFS_NAME))
243 return;
244
245
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 if (!panel_connected (device))
246 return;
247
248 1 g_debug ("Updating panel power saving for '%s' to '%d'",
249 g_udev_device_get_sysfs_path (device),
250 self->panel_power_saving);
251 1 ppd_utils_write_sysfs_int (device, PANEL_POWER_SYSFS_NAME,
252 1 self->panel_power_saving, NULL);
253 }
254
255 static PpdProbeResult
256 2 ppd_action_amdgpu_panel_power_probe (PpdAction *action)
257 {
258 2 return ppd_utils_match_cpu_vendor ("AuthenticAMD") ? PPD_PROBE_RESULT_SUCCESS : PPD_PROBE_RESULT_FAIL;
259 }
260
261 static void
262 158 ppd_action_amdgpu_panel_power_finalize (GObject *object)
263 {
264 158 PpdActionAmdgpuPanelPower *action;
265
266 158 action = PPD_ACTION_AMDGPU_PANEL_POWER (object);
267
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 g_clear_object (&action->client);
268 158 G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->finalize (object);
269 158 }
270
271 static void
272 142 ppd_action_amdgpu_panel_power_class_init (PpdActionAmdgpuPanelPowerClass *klass)
273 {
274 142 GObjectClass *object_class;
275 142 PpdActionClass *driver_class;
276
277 142 object_class = G_OBJECT_CLASS(klass);
278 142 object_class->constructor = ppd_action_amdgpu_panel_power_constructor;
279 142 object_class->finalize = ppd_action_amdgpu_panel_power_finalize;
280
281 142 driver_class = PPD_ACTION_CLASS(klass);
282 142 driver_class->probe = ppd_action_amdgpu_panel_power_probe;
283 142 driver_class->activate_profile = ppd_action_amdgpu_panel_power_activate_profile;
284 142 driver_class->power_changed = ppd_action_amdgpu_panel_power_power_changed;
285 142 driver_class->battery_changed = ppd_action_amdgpu_panel_power_battery_changed;
286 }
287
288 static void
289 158 ppd_action_amdgpu_panel_power_init (PpdActionAmdgpuPanelPower *self)
290 {
291 158 const gchar * const subsystem[] = { "drm", NULL };
292
293 158 self->client = g_udev_client_new (subsystem);
294 158 g_signal_connect_object (G_OBJECT (self->client), "uevent",
295 G_CALLBACK (udev_uevent_cb), self, 0);
296 158 }
297