New Upstream Release - power-profiles-daemon

Ready changes

Summary

Merged new upstream version: 0.13 (was: 0.12).

Diff

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 714a85c..ddec8d0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,6 +18,11 @@ variables:
                 umockdev
                 e2fsprogs
 
+workflow:
+  rules:
+    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
+    - if: $CI_PIPELINE_SOURCE == 'push'
+
 build_stable:
   before_script:
     - dnf upgrade -y --nogpgcheck fedora-release fedora-repos*
diff --git a/NEWS b/NEWS
index 8b75a65..59ce68c 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,10 @@
+0.13
+----
+
+This release adds support for the AMD P-State driver that's been added to the
+6.3 Linux kernel. This release also fixes mismatched profiles on some HP
+laptops and some miscellaneous bug fixes.
+
 0.12
 ----
 
diff --git a/README.md b/README.md
index 1093b19..8ab25a3 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,35 @@ ie. the only P-State scaling governor that allows HWP to work.
 For more information, please refer to the [Intel P-State scaling driver documentation](https://www.kernel.org/doc/html/v5.17/admin-guide/pm/intel_pstate.html)
 and the [Intel Performance and Energy Bias Hint](https://www.kernel.org/doc/html/v5.17/admin-guide/pm/intel_epb.html).
 
+Operations on AMD-based machines
+----------------------------------
+
+The "driver" for making the hardware act on the user-selected power profile on AMD CPU-based
+machines is based on the [AMD P-State scaling driver](https://www.kernel.org/doc/html/v6.3/admin-guide/pm/amd-pstate.html)
+if available.
+
+It is only used if a `platform_profile` driver isn't available for the system, the
+CPU supports Collaborative Processor Performance Control (CPPC), and the AMD P-State
+scaling driver is in `active` mode.
+
+Example of a system without `platform_profile` support but with `active` P-State
+operation mode:
+```
+$ cat /sys/firmware/acpi/platform_profile_choices
+cat: /sys/firmware/acpi/platform_profile_choices: No such file or directory
+$ cat /sys/devices/system/cpu/amd_pstate/status
+active
+```
+
+If the AMD P-State scaling driver is not loaded or is not in `active` mode, then
+the placeholder driver will be used, and there won't be a performance mode.
+
+Finally, if the AMD P-State scaling driver is used in `active` mode, the P-State
+scaling governor will be changed to `powersave` as it is the only P-State scaling
+governor that allows for the "Energy vs Performance Hints" to be taken into consideration.
+
+For more information, please refer to the [AMD P-State scaling driver documentation](https://www.kernel.org/doc/html/v6.3/admin-guide/pm/amd-pstate.html).
+
 Testing
 -------
 
diff --git a/debian/changelog b/debian/changelog
index 534c710..114d090 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+power-profiles-daemon (0.13-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 05 May 2023 01:51:18 -0000
+
 power-profiles-daemon (0.12-1) unstable; urgency=medium
 
   * Team upload
diff --git a/debian/patches/build_older_polkit.patch b/debian/patches/build_older_polkit.patch
index b05b5ae..7cb5b3c 100644
--- a/debian/patches/build_older_polkit.patch
+++ b/debian/patches/build_older_polkit.patch
@@ -1,10 +1,10 @@
-Index: power-profiles-daemon/src/power-profiles-daemon.c
+Index: power-profiles-daemon.git/src/power-profiles-daemon.c
 ===================================================================
---- power-profiles-daemon.orig/src/power-profiles-daemon.c
-+++ power-profiles-daemon/src/power-profiles-daemon.c
-@@ -103,6 +103,13 @@ typedef enum {
- 
- #define PROP_ALL (PROP_ACTIVE_PROFILE | PROP_INHIBITED | PROP_PROFILES | PROP_ACTIONS | PROP_DEGRADED | PROP_ACTIVE_PROFILE_HOLDS)
+--- power-profiles-daemon.git.orig/src/power-profiles-daemon.c
++++ power-profiles-daemon.git/src/power-profiles-daemon.c
+@@ -115,6 +115,13 @@ get_profile_available (PpdApp     *data,
+     return driver != NULL;
+ }
  
 +/* This uses a weird Auto prefix to avoid conflicts with later added polkit types. */
 +typedef PolkitAuthorizationResult AutoPolkitAuthorizationResult;
@@ -16,7 +16,7 @@ Index: power-profiles-daemon/src/power-profiles-daemon.c
  static const char *
  get_active_profile (PpdApp *data)
  {
-@@ -609,8 +616,8 @@ check_action_permission (PpdApp
+@@ -632,8 +639,8 @@ check_action_permission (PpdApp
                           GError               **error)
  {
    g_autoptr(GError) local_error = NULL;
@@ -27,10 +27,10 @@ Index: power-profiles-daemon/src/power-profiles-daemon.c
  
    subject = polkit_system_bus_name_new (sender);
    result = polkit_authority_check_authorization_sync (data->auth,
-Index: power-profiles-daemon/meson.build
+Index: power-profiles-daemon.git/meson.build
 ===================================================================
---- power-profiles-daemon.orig/meson.build
-+++ power-profiles-daemon/meson.build
+--- power-profiles-daemon.git.orig/meson.build
++++ power-profiles-daemon.git/meson.build
 @@ -34,7 +34,7 @@ endif
  gio_dep = dependency('gio-2.0')
  gudev_dep = dependency('gudev-1.0', version: '>= 234')
diff --git a/debian/patches/remove_tlp_conflict.patch b/debian/patches/remove_tlp_conflict.patch
index e334e26..a709072 100644
--- a/debian/patches/remove_tlp_conflict.patch
+++ b/debian/patches/remove_tlp_conflict.patch
@@ -4,10 +4,10 @@ changes when power-profiles-daemon is active but the change can be
 included in Debian also since the tlp maintainer made the packages conflict
 which means we can't end up installed together
 forwarded: not-needed
-Index: power-profiles-daemon/data/power-profiles-daemon.service.in
+Index: power-profiles-daemon.git/data/power-profiles-daemon.service.in
 ===================================================================
---- power-profiles-daemon.orig/data/power-profiles-daemon.service.in
-+++ power-profiles-daemon/data/power-profiles-daemon.service.in
+--- power-profiles-daemon.git.orig/data/power-profiles-daemon.service.in
++++ power-profiles-daemon.git/data/power-profiles-daemon.service.in
 @@ -1,6 +1,6 @@
  [Unit]
  Description=Power Profiles daemon
diff --git a/docs/meson.build b/docs/meson.build
index 489658b..4de0210 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -24,6 +24,7 @@ private_headers = [
   'ppd-driver-balanced.h',
   'ppd-driver-fake.h',
   'ppd-driver-intel-pstate.h',
+  'ppd-driver-amd-pstate.h',
   'ppd-driver-placeholder.h',
   'ppd-driver-platform-profile.h',
   'ppd-driver-power-saver.h',
diff --git a/meson.build b/meson.build
index 7f31046..47e982b 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('power-profiles-daemon', [ 'c' ],
-        version: '0.12',
+        version: '0.13',
         license: 'GPLv3+',
         default_options: [
           'buildtype=debugoptimized',
diff --git a/src/meson.build b/src/meson.build
index 2094079..6cf5113 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -46,6 +46,7 @@ sources += [
   'power-profiles-daemon.c',
   'ppd-action-trickle-charge.c',
   'ppd-driver-intel-pstate.c',
+  'ppd-driver-amd-pstate.c',
   'ppd-driver-platform-profile.c',
   'ppd-driver-placeholder.c',
   'ppd-driver-fake.c',
diff --git a/src/power-profiles-daemon.c b/src/power-profiles-daemon.c
index ab1b095..2a3c26b 100644
--- a/src/power-profiles-daemon.c
+++ b/src/power-profiles-daemon.c
@@ -75,6 +75,7 @@ static void start_profile_drivers (PpdApp *data);
 #include "ppd-driver-placeholder.h"
 #include "ppd-driver-platform-profile.h"
 #include "ppd-driver-intel-pstate.h"
+#include "ppd-driver-amd-pstate.h"
 #include "ppd-driver-fake.h"
 
 typedef GType (*GTypeGetFunc) (void);
@@ -84,6 +85,7 @@ static GTypeGetFunc objects[] = {
   ppd_driver_fake_get_type,
   ppd_driver_platform_profile_get_type,
   ppd_driver_intel_pstate_get_type,
+  ppd_driver_amd_pstate_get_type,
 
   /* Generic profile driver */
   ppd_driver_placeholder_get_type,
@@ -103,6 +105,16 @@ typedef enum {
 
 #define PROP_ALL (PROP_ACTIVE_PROFILE | PROP_INHIBITED | PROP_PROFILES | PROP_ACTIONS | PROP_DEGRADED | PROP_ACTIVE_PROFILE_HOLDS)
 
+static gboolean
+get_profile_available (PpdApp     *data,
+                       PpdProfile  profile)
+{
+    PpdDriver *driver;
+
+    driver = GET_DRIVER(profile);
+    return driver != NULL;
+}
+
 static const char *
 get_active_profile (PpdApp *data)
 {
@@ -386,6 +398,11 @@ set_active_profile (PpdApp      *data,
                  "Invalid profile name '%s'", profile);
     return FALSE;
   }
+  if (!get_profile_available (data, target_profile)) {
+    g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                 "Cannot switch to unavailable profile '%s'", profile);
+    return FALSE;
+  }
 
   if (target_profile == data->active_profile)
     return TRUE;
@@ -554,8 +571,14 @@ hold_profile (PpdApp                *data,
   profile = ppd_profile_from_str (profile_name);
   if (profile != PPD_PROFILE_PERFORMANCE &&
       profile != PPD_PROFILE_POWER_SAVER) {
+    g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+                                                   "Only profiles 'performance' and 'power-saver' can be a hold profile");
+    return;
+  }
+  if (!get_profile_available (data, profile)) {
     g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
-                                           "Only profiles 'performance' and 'power-saver' can be a hold profile");
+                                           "Cannot hold profile '%s' as it is not available",
+                                           profile_name);
     return;
   }
 
diff --git a/src/powerprofilesctl.in b/src/powerprofilesctl.in
index fac2529..b1ea675 100755
--- a/src/powerprofilesctl.in
+++ b/src/powerprofilesctl.in
@@ -127,44 +127,43 @@ def get_profiles_property(prop):
         profiles = proxy.Get('(ss)', 'net.hadess.PowerProfiles', prop)
     except:
         raise
-    else:
-        return profiles
+    return profiles
 
 def _list():
     try:
         profiles = get_profiles_property('Profiles')
         reason = get_proxy().Get('(ss)', 'net.hadess.PowerProfiles', 'PerformanceDegraded')
-        degraded = (reason != '')
+        degraded = reason != ''
         active = get_proxy().Get('(ss)', 'net.hadess.PowerProfiles', 'ActiveProfile')
     except:
         raise
-    else:
-        index = 0
-        for profile in reversed(profiles):
-            if index > 0:
-                print('')
-            marker = '*' if profile['Profile'] == active else ' '
-            print(f'{marker} {profile["Profile"]}:')
-            print('    Driver:    ', profile['Driver'])
-            if profile['Profile'] == 'performance':
-                print('    Degraded:  ', f'yes ({reason})' if degraded else 'no')
-            index += 1
+
+    index = 0
+    for profile in reversed(profiles):
+        if index > 0:
+            print('')
+        marker = '*' if profile['Profile'] == active else ' '
+        print(f'{marker} {profile["Profile"]}:')
+        print('    Driver:    ', profile['Driver'])
+        if profile['Profile'] == 'performance':
+            print('    Degraded:  ', f'yes ({reason})' if degraded else 'no')
+        index += 1
 
 def _list_holds():
     try:
         holds = get_profiles_property('ActiveProfileHolds')
     except:
         raise
-    else:
-        index = 0
-        for hold in holds:
-            if index > 0:
-                print('')
-            print('Hold:')
-            print('  Profile:        ', hold['Profile'])
-            print('  Application ID: ', hold['ApplicationId'])
-            print('  Reason:         ', hold['Reason'])
-            index += 1
+
+    index = 0
+    for hold in holds:
+        if index > 0:
+            print('')
+        print('Hold:')
+        print('  Profile:        ', hold['Profile'])
+        print('  Application ID: ', hold['ApplicationId'])
+        print('  Reason:         ', hold['Reason'])
+        index += 1
 
 def _launch(args, profile, appid, reason):
     try:
diff --git a/src/ppd-driver-amd-pstate.c b/src/ppd-driver-amd-pstate.c
new file mode 100644
index 0000000..825caca
--- /dev/null
+++ b/src/ppd-driver-amd-pstate.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2020 Bastien Nocera <hadess@hadess.net>
+ * Copyright (c) 2022 Prajna Sariputra <putr4.s@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ */
+
+#include <upower.h>
+
+#include "ppd-utils.h"
+#include "ppd-driver-amd-pstate.h"
+
+#define CPUFREQ_POLICY_DIR "/sys/devices/system/cpu/cpufreq/"
+#define DEFAULT_CPU_FREQ_SCALING_GOV "powersave"
+#define PSTATE_STATUS_PATH "/sys/devices/system/cpu/amd_pstate/status"
+
+struct _PpdDriverAmdPstate
+{
+  PpdDriver  parent_instance;
+
+  PpdProfile activated_profile;
+  GList *epp_devices; /* GList of paths */
+};
+
+G_DEFINE_TYPE (PpdDriverAmdPstate, ppd_driver_amd_pstate, PPD_TYPE_DRIVER)
+
+static gboolean ppd_driver_amd_pstate_activate_profile (PpdDriver                   *driver,
+                                                        PpdProfile                   profile,
+                                                        PpdProfileActivationReason   reason,
+                                                        GError                     **error);
+
+static GObject*
+ppd_driver_amd_pstate_constructor (GType                  type,
+                                   guint                  n_construct_params,
+                                   GObjectConstructParam *construct_params)
+{
+  GObject *object;
+
+  object = G_OBJECT_CLASS (ppd_driver_amd_pstate_parent_class)->constructor (type,
+                                                                             n_construct_params,
+                                                                             construct_params);
+  g_object_set (object,
+                "driver-name", "amd_pstate",
+                "profiles", PPD_PROFILE_PERFORMANCE | PPD_PROFILE_BALANCED | PPD_PROFILE_POWER_SAVER,
+                NULL);
+
+  return object;
+}
+
+static PpdProbeResult
+probe_epp (PpdDriverAmdPstate *pstate)
+{
+  g_autoptr(GDir) dir = NULL;
+  g_autofree char *policy_dir = NULL;
+  g_autofree char *pstate_status_path = NULL;
+  g_autofree char *status = NULL;
+  const char *dirname;
+  PpdProbeResult ret = PPD_PROBE_RESULT_FAIL;
+
+  /* Verify that AMD P-State is running in active mode */
+  pstate_status_path = ppd_utils_get_sysfs_path (PSTATE_STATUS_PATH);
+  if (!g_file_get_contents (pstate_status_path, &status, NULL, NULL))
+    return ret;
+  status = g_strchomp (status);
+  if (g_strcmp0 (status, "active") != 0) {
+    g_debug ("AMD P-State is not running in active mode");
+    return ret;
+  }
+
+  policy_dir = ppd_utils_get_sysfs_path (CPUFREQ_POLICY_DIR);
+  dir = g_dir_open (policy_dir, 0, NULL);
+  if (!dir) {
+    g_debug ("Could not open %s", policy_dir);
+    return ret;
+  }
+
+  while ((dirname = g_dir_read_name (dir)) != NULL) {
+    g_autofree char *path = NULL;
+    g_autofree char *gov_path = NULL;
+    g_autoptr(GError) error = NULL;
+
+    path = g_build_filename (policy_dir,
+                             dirname,
+                             "energy_performance_preference",
+                             NULL);
+    if (!g_file_test (path, G_FILE_TEST_EXISTS))
+      continue;
+
+    /* Force a scaling_governor where the preference can be written */
+    gov_path = g_build_filename (policy_dir,
+                                 dirname,
+                                 "scaling_governor",
+                                 NULL);
+    if (!ppd_utils_write (gov_path, DEFAULT_CPU_FREQ_SCALING_GOV, &error)) {
+      g_warning ("Could not change scaling governor %s to '%s'", dirname, DEFAULT_CPU_FREQ_SCALING_GOV);
+      continue;
+    }
+
+    pstate->epp_devices = g_list_prepend (pstate->epp_devices, g_steal_pointer (&path));
+    ret = PPD_PROBE_RESULT_SUCCESS;
+  }
+
+  return ret;
+}
+
+static PpdProbeResult
+ppd_driver_amd_pstate_probe (PpdDriver  *driver)
+{
+  PpdDriverAmdPstate *pstate = PPD_DRIVER_AMD_PSTATE (driver);
+  PpdProbeResult ret = PPD_PROBE_RESULT_FAIL;
+
+  ret = probe_epp (pstate);
+
+  if (ret != PPD_PROBE_RESULT_SUCCESS)
+    goto out;
+
+out:
+  g_debug ("%s p-state settings",
+           ret == PPD_PROBE_RESULT_SUCCESS ? "Found" : "Didn't find");
+  return ret;
+}
+
+static const char *
+profile_to_epp_pref (PpdProfile profile)
+{
+  /* Note that we don't check "energy_performance_available_preferences"
+   * as all the values are always available */
+  switch (profile) {
+  case PPD_PROFILE_POWER_SAVER:
+    return "power";
+  case PPD_PROFILE_BALANCED:
+    return "balance_performance";
+  case PPD_PROFILE_PERFORMANCE:
+    return "performance";
+  }
+
+  g_assert_not_reached ();
+}
+
+static gboolean
+apply_pref_to_devices (GList       *devices,
+                       const char  *pref,
+                       GError     **error)
+{
+  gboolean ret = TRUE;
+  GList *l;
+
+  for (l = devices; l != NULL; l = l->next) {
+    const char *path = l->data;
+
+    ret = ppd_utils_write (path, pref, error);
+    if (!ret)
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+ppd_driver_amd_pstate_activate_profile (PpdDriver                    *driver,
+                                          PpdProfile                   profile,
+                                          PpdProfileActivationReason   reason,
+                                          GError                     **error)
+{
+  PpdDriverAmdPstate *pstate = PPD_DRIVER_AMD_PSTATE (driver);
+  gboolean ret = FALSE;
+  const char *pref;
+
+  g_return_val_if_fail (pstate->epp_devices != NULL, FALSE);
+
+  if (pstate->epp_devices) {
+    pref = profile_to_epp_pref (profile);
+    ret = apply_pref_to_devices (pstate->epp_devices, pref, error);
+    if (!ret)
+      return ret;
+  }
+
+  if (ret)
+    pstate->activated_profile = profile;
+
+  return ret;
+}
+
+static void
+ppd_driver_amd_pstate_finalize (GObject *object)
+{
+  PpdDriverAmdPstate *driver;
+
+  driver = PPD_DRIVER_AMD_PSTATE (object);
+  g_clear_list (&driver->epp_devices, g_free);
+  G_OBJECT_CLASS (ppd_driver_amd_pstate_parent_class)->finalize (object);
+}
+
+static void
+ppd_driver_amd_pstate_class_init (PpdDriverAmdPstateClass *klass)
+{
+  GObjectClass *object_class;
+  PpdDriverClass *driver_class;
+
+  object_class = G_OBJECT_CLASS(klass);
+  object_class->constructor = ppd_driver_amd_pstate_constructor;
+  object_class->finalize = ppd_driver_amd_pstate_finalize;
+
+  driver_class = PPD_DRIVER_CLASS(klass);
+  driver_class->probe = ppd_driver_amd_pstate_probe;
+  driver_class->activate_profile = ppd_driver_amd_pstate_activate_profile;
+}
+
+static void
+ppd_driver_amd_pstate_init (PpdDriverAmdPstate *self)
+{
+}
diff --git a/src/ppd-driver-amd-pstate.h b/src/ppd-driver-amd-pstate.h
new file mode 100644
index 0000000..c4f1690
--- /dev/null
+++ b/src/ppd-driver-amd-pstate.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2020 Bastien Nocera <hadess@hadess.net>
+ * Copyright (c) 2022 Prajna Sariputra <putr4.s@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published by
+ * the Free Software Foundation.
+ *
+ */
+
+#pragma once
+
+#include "ppd-driver.h"
+
+#define PPD_TYPE_DRIVER_AMD_PSTATE (ppd_driver_amd_pstate_get_type())
+G_DECLARE_FINAL_TYPE(PpdDriverAmdPstate, ppd_driver_amd_pstate, PPD, DRIVER_AMD_PSTATE, PpdDriver)
diff --git a/src/ppd-driver-intel-pstate.c b/src/ppd-driver-intel-pstate.c
index 17b6af4..413a0d2 100644
--- a/src/ppd-driver-intel-pstate.c
+++ b/src/ppd-driver-intel-pstate.c
@@ -171,7 +171,6 @@ probe_epb (PpdDriverIntelPstate *pstate)
 
   while ((dirname = g_dir_read_name (dir)) != NULL) {
     g_autofree char *path = NULL;
-    g_autofree char *gov_path = NULL;
 
     path = g_build_filename (policy_dir,
                              dirname,
diff --git a/src/ppd-driver-platform-profile.c b/src/ppd-driver-platform-profile.c
index 8111251..aebc655 100644
--- a/src/ppd-driver-platform-profile.c
+++ b/src/ppd-driver-platform-profile.c
@@ -26,6 +26,7 @@ struct _PpdDriverPlatformProfile
   int lapmode;
   PpdProfile acpi_platform_profile;
   char **profile_choices;
+  gboolean has_low_power;
   GFileMonitor *lapmode_mon;
   GFileMonitor *acpi_platform_profile_mon;
   guint acpi_platform_profile_changed_id;
@@ -57,10 +58,10 @@ profile_to_acpi_platform_profile_value (PpdDriverPlatformProfile *self,
 {
   switch (profile) {
   case PPD_PROFILE_POWER_SAVER:
+    if (!self->has_low_power)
+      return "balanced";
     if (g_strv_contains ((const char * const*) self->profile_choices, "low-power"))
       return "low-power";
-    if (g_strv_contains ((const char * const*) self->profile_choices, "cool"))
-      return "cool";
     return "quiet";
   case PPD_PROFILE_BALANCED:
     return "balanced";
@@ -79,9 +80,9 @@ acpi_platform_profile_value_to_profile (const char *str)
 
   switch (str[0]) {
   case 'l': /* low-power */
-  case 'c': /* cool */
   case 'q': /* quiet */
     return PPD_PROFILE_POWER_SAVER;
+  case 'c': /* cool */
   case 'b':
     return PPD_PROFILE_BALANCED;
   case 'p':
@@ -142,12 +143,15 @@ verify_acpi_platform_profile_choices (PpdDriverPlatformProfile *self)
 {
   const char * const *choices = (const char * const*) self->profile_choices;
 
-  if ((g_strv_contains (choices, "low-power") ||
-       g_strv_contains (choices, "cool") ||
-       g_strv_contains (choices, "quiet")) &&
-      g_strv_contains (choices, "balanced") &&
-      g_strv_contains (choices, "performance"))
+  if (g_strv_contains (choices, "balanced") &&
+      g_strv_contains (choices, "performance")) {
+    if (g_strv_contains (choices, "low-power") ||
+        g_strv_contains (choices, "quiet"))
+      self->has_low_power = TRUE;
+    else
+      g_debug ("No \"low-power\" profile for device, will be emulated");
     return PPD_PROBE_RESULT_SUCCESS;
+  }
   return PPD_PROBE_RESULT_DEFER;
 }
 
@@ -203,7 +207,7 @@ acpi_platform_profile_changed (GFileMonitor      *monitor,
                                gpointer           user_data)
 {
   PpdDriverPlatformProfile *self = user_data;
-  g_debug (ACPI_PLATFORM_PROFILE_PATH " changed");
+  g_debug (ACPI_PLATFORM_PROFILE_PATH " changed (%d)", event_type);
   if (self->probe_result == PPD_PROBE_RESULT_DEFER) {
     g_signal_emit_by_name (G_OBJECT (self), "probe-request", 0);
     return;
@@ -219,6 +223,7 @@ ppd_driver_platform_profile_activate_profile (PpdDriver                   *drive
 {
   PpdDriverPlatformProfile *self = PPD_DRIVER_PLATFORM_PROFILE (driver);
   g_autofree char *platform_profile_path = NULL;
+  const char *platform_profile_value;
 
   g_return_val_if_fail (self->acpi_platform_profile_mon, FALSE);
 
@@ -228,6 +233,14 @@ ppd_driver_platform_profile_activate_profile (PpdDriver                   *drive
     return TRUE;
   }
 
+  platform_profile_value = profile_to_acpi_platform_profile_value (self, profile);
+  if (self->acpi_platform_profile == acpi_platform_profile_value_to_profile (platform_profile_value)) {
+    g_debug ("Not switching to platform_profile %s, emulating for %s, already there",
+             platform_profile_value,
+             ppd_profile_to_str (profile));
+    return TRUE;
+  }
+
   g_signal_handler_block (G_OBJECT (self->acpi_platform_profile_mon), self->acpi_platform_profile_changed_id);
   platform_profile_path = ppd_utils_get_sysfs_path (ACPI_PLATFORM_PROFILE_PATH);
   if (!ppd_utils_write (platform_profile_path, profile_to_acpi_platform_profile_value (self, profile), error)) {
diff --git a/src/ppd-utils.c b/src/ppd-utils.c
index 89b56e3..5ac732f 100644
--- a/src/ppd-utils.c
+++ b/src/ppd-utils.c
@@ -85,6 +85,7 @@ ppd_utils_monitor_sysfs_attr (GUdevDevice  *device,
 
   path = g_build_filename (g_udev_device_get_sysfs_path (device), attribute, NULL);
   file = g_file_new_for_path (path);
+  g_debug ("Monitoring file %s for changes", path);
   return g_file_monitor_file (file,
                               G_FILE_MONITOR_NONE,
                               NULL,
diff --git a/tests/integration-test.py b/tests/integration-test.py
index 77e05c0..8e37f69 100755
--- a/tests/integration-test.py
+++ b/tests/integration-test.py
@@ -320,6 +320,13 @@ class Tests(dbusmock.DBusTestCase):
       self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
 
+      with self.assertRaises(gi.repository.GLib.GError):
+        self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
+
+      with self.assertRaises(gi.repository.GLib.GError):
+        cookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', 'testReason', 'testApplication')))
+
       # process = subprocess.Popen(['gdbus', 'introspect', '--system', '--dest', 'net.hadess.PowerProfiles', '--object-path', '/net/hadess/PowerProfiles'])
       # print (self.get_dbus_property('GPUs'))
 
@@ -598,6 +605,183 @@ class Tests(dbusmock.DBusTestCase):
 
       self.stop_daemon()
 
+    def test_amd_pstate(self):
+      '''AMD P-State driver (no UPower)'''
+
+      # Create 2 CPUs with preferences
+      dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/")
+      os.makedirs(dir1)
+      with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov:
+        gov.write('powersave\n')
+      with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs:
+        prefs.write("performance\n")
+      dir2 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/")
+      os.makedirs(dir2)
+      with open(os.path.join(dir2, 'scaling_governor'), 'w') as gov:
+        gov.write('powersave\n')
+      with open(os.path.join(dir2, "energy_performance_preference"),'w') as prefs:
+        prefs.write("performance\n")
+
+      # Create AMD P-State configuration
+      pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate")
+      os.makedirs(pstate_dir)
+      with open(os.path.join(pstate_dir, "status"),'w') as status:
+        status.write("active\n")
+
+      self.start_daemon()
+
+      profiles = self.get_dbus_property('Profiles')
+      self.assertEqual(len(profiles), 3)
+      self.assertEqual(profiles[0]['Driver'], 'amd_pstate')
+      self.assertEqual(profiles[0]['Profile'], 'power-saver')
+
+      contents = None
+      with open(os.path.join(dir2, "energy_performance_preference"), 'rb') as f:
+        contents = f.read()
+      self.assertEqual(contents, b'balance_performance')
+
+      # Set performance mode
+      self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
+
+      contents = None
+      with open(os.path.join(dir2, "energy_performance_preference"), 'rb') as f:
+        contents = f.read()
+      self.assertEqual(contents, b'performance')
+
+      self.stop_daemon()
+
+      # Verify that the Lenovo DYTC driver still gets preferred
+      self.create_platform_profile()
+      self.start_daemon()
+
+      profiles = self.get_dbus_property('Profiles')
+      self.assertEqual(len(profiles), 3)
+      self.assertEqual(profiles[0]['Driver'], 'platform_profile')
+
+    def test_amd_pstate_balance(self):
+      '''AMD P-State driver (balance)'''
+
+      # Create CPU with preference
+      dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/")
+      os.makedirs(dir1)
+      gov_path = os.path.join(dir1, 'scaling_governor')
+      with open(gov_path, 'w') as gov:
+        gov.write('performance\n')
+      with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs:
+        prefs.write("performance\n")
+      pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate")
+      os.makedirs(pstate_dir)
+      with open(os.path.join(pstate_dir, "status"),'w') as status:
+        status.write("active\n")
+
+      upowerd, obj_upower = self.spawn_server_template(
+            'upower', {'DaemonVersion': '0.99', 'OnBattery': False}, stdout=subprocess.PIPE)
+
+      self.start_daemon()
+
+      with open(gov_path, 'rb') as f:
+        contents = f.read()
+        self.assertEqual(contents, b'powersave')
+
+      profiles = self.get_dbus_property('Profiles')
+      self.assertEqual(len(profiles), 3)
+      self.assertEqual(profiles[0]['Driver'], 'amd_pstate')
+      self.assertEqual(profiles[0]['Profile'], 'power-saver')
+
+      contents = None
+      with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f:
+        contents = f.read()
+      # This matches what's written by ppd-driver-amd-pstate.c
+      self.assertEqual(contents, b'balance_performance')
+
+      self.stop_daemon()
+
+      upowerd.terminate()
+      upowerd.wait()
+      upowerd.stdout.close()
+
+    def test_amd_pstate_error(self):
+      '''AMD P-State driver in error state'''
+
+      pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate")
+      os.makedirs(pstate_dir)
+      with open(os.path.join(pstate_dir, "status"),'w') as status:
+        status.write("active\n")
+
+      dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/")
+      os.makedirs(dir1)
+      with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov:
+        gov.write('powersave\n')
+      pref_path = os.path.join(dir1, "energy_performance_preference")
+      old_umask = os.umask(0o333)
+      with open(pref_path,'w') as prefs:
+        prefs.write("balance_performance\n")
+      os.umask(old_umask)
+      # Make file non-writable to root
+      if os.geteuid() == 0:
+        if not GLib.find_program_in_path('chattr'):
+          os._exit(77)
+        subprocess.check_output(['chattr', '+i', pref_path])
+
+      self.start_daemon()
+
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
+
+      # Error when setting performance mode
+      with self.assertRaises(gi.repository.GLib.GError):
+        self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
+
+      contents = None
+      with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f:
+        contents = f.read()
+      self.assertEqual(contents, b'balance_performance\n')
+
+      self.stop_daemon()
+
+      if os.geteuid() == 0:
+        subprocess.check_output(['chattr', '-i', pref_path])
+
+    def test_amd_pstate_passive(self):
+      '''AMD P-State in passive mode -> placeholder'''
+
+      dir1 = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/")
+      os.makedirs(dir1)
+      with open(os.path.join(dir1, 'scaling_governor'), 'w') as gov:
+        gov.write('powersave\n')
+      with open(os.path.join(dir1, "energy_performance_preference"),'w') as prefs:
+        prefs.write("performance\n")
+
+      # Create AMD P-State configuration
+      pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate")
+      os.makedirs(pstate_dir)
+      with open(os.path.join(pstate_dir, "status"),'w') as status:
+        status.write("passive\n")
+
+      self.start_daemon()
+
+      profiles = self.get_dbus_property('Profiles')
+      self.assertEqual(len(profiles), 2)
+      self.assertEqual(profiles[0]['Driver'], 'placeholder')
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
+
+      contents = None
+      with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f:
+        contents = f.read()
+      self.assertEqual(contents, b'performance\n')
+
+      # Set performance mode
+      self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
+      self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
+
+      contents = None
+      with open(os.path.join(dir1, "energy_performance_preference"), 'rb') as f:
+        contents = f.read()
+      self.assertEqual(contents, b'performance\n')
+
+      self.stop_daemon()
+
     def test_dytc_performance_driver(self):
       '''Lenovo DYTC performance driver'''
 
@@ -753,19 +937,23 @@ class Tests(dbusmock.DBusTestCase):
       self.assertEqual(profiles[0]['Driver'], 'platform_profile')
       self.assertEqual(profiles[0]['Profile'], 'power-saver')
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-      self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'balanced')
+      self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'cool')
       self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
       self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
       self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'cool')
 
+      self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
+      self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('balanced'))
+      self.assertEqual(self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b'balanced')
+
       self.stop_daemon()
 
     def test_quiet(self):
-      # Uses cool instead of low-power
+      # Uses quiet instead of low-power
       acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
       os.makedirs(acpi_dir)
       with open(os.path.join(acpi_dir, "platform_profile"),'w') as profile:
-        profile.write("cool\n")
+        profile.write("quiet\n")
       with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices:
         choices.write("quiet balanced balanced-performance performance\n")
 

More details

Full run details

Historical runs