[neon/backports-jammy/power-profiles-daemon/Neon/unstable] /: 0.20-1 (patches unapplied)
git-ubuntu importer
null at kde.org
Tue Sep 24 23:21:42 BST 2024
Git commit 8448eb4d7e15231a4a59d04b8ddc4ce0be5af2ca by git-ubuntu importer, on behalf of Marco Trevisan (Treviño).
Committed on 15/02/2024 at 10:34.
Pushed by carlosdem into branch 'Neon/unstable'.
0.20-1 (patches unapplied)
Imported using git-ubuntu import.
M +25 -11 .ci/fail_skipped_tests.py
M +2 -1 .gitignore
M +96 -10 .gitlab-ci.yml
A +10 -0 .markdownlint.json
A +39 -0 .pre-commit-config.yaml
M +20 -0 NEWS
M +84 -38 README.md
M +8 -8 check-news.sh
M +24 -16 data/meson.build
D +0 -21 data/net.hadess.PowerProfiles.conf.in
A +21 -0 data/power-profiles-daemon.dbus.conf.in
R +1 -1 data/power-profiles-daemon.dbus.service.in [from: data/net.hadess.PowerProfiles.service - 091% similarity]
R +2 -2 data/power-profiles-daemon.policy [from: data/net.hadess.PowerProfiles.policy - 087% similarity]
M +1 -1 data/power-profiles-daemon.service.in
M +22 -0 debian/changelog
M +8 -4 debian/control
M +10 -2 debian/copyright
A +32 -0 debian/patches/build-Expose-powerprofilesctl-script-and-load-it-using-fi.patch
A +108 -0 debian/patches/powerprofilectl-Generate-manpage-using-argparse-manpage.patch
M +13 -5 debian/patches/remove_tlp_conflict.patch
M +2 -0 debian/patches/series
M +8 -3 debian/rules
M +4 -4 debian/upstream/metadata
M +1 -1 debian/watch
M +5 -6 docs/meson.build
M +4 -1 docs/power-profiles-daemon-docs.xml
M +32 -0 docs/power-profiles-daemon-sections.txt
M +36 -7 meson.build
M +1 -1 meson_options.txt
M +54 -18 src/meson.build
M +482 -128 src/power-profiles-daemon.c
R +10 -3 src/power-profiles-daemon.dbus.xml.in [from: src/net.hadess.PowerProfiles.xml - 095% similarity]
D +0 -7 src/power-profiles-daemon.gresource.xml
A +6 -0 src/power-profiles-daemon.gresource.xml.in
A +227 -0 src/powerprofilesctl
D +0 -282 src/powerprofilesctl.in
A +350 -0 src/ppd-action-amdgpu-panel-power.c [License: GPL (v3)]
A +15 -0 src/ppd-action-amdgpu-panel-power.h [License: GPL (v3)]
M +4 -2 src/ppd-action-trickle-charge.c
M +2 -2 src/ppd-action-trickle-charge.h
M +8 -6 src/ppd-action.c
M +4 -4 src/ppd-action.h
M +86 -25 src/ppd-driver-amd-pstate.c
M +3 -3 src/ppd-driver-amd-pstate.h
A +47 -0 src/ppd-driver-cpu.c [License: GPL (v3)]
A +27 -0 src/ppd-driver-cpu.h [License: GPL (v3)]
M +11 -7 src/ppd-driver-fake.c
M +3 -3 src/ppd-driver-fake.h
M +22 -12 src/ppd-driver-intel-pstate.c
M +3 -3 src/ppd-driver-intel-pstate.h
M +5 -3 src/ppd-driver-placeholder.c
M +3 -3 src/ppd-driver-placeholder.h
M +5 -3 src/ppd-driver-platform-profile.c
M +3 -3 src/ppd-driver-platform-profile.h
A +58 -0 src/ppd-driver-platform.c [License: GPL (v3)]
A +27 -0 src/ppd-driver-platform.h [License: GPL (v3)]
M +17 -18 src/ppd-driver.c
M +5 -22 src/ppd-driver.h
M +18 -0 src/ppd-profile.h
M +14 -1 src/ppd-utils.c
M +4 -0 src/ppd-utils.h
D +0 -1264 tests/integration-test.py
A +1993 -0 tests/integration_test.py
M +75 -7 tests/meson.build
M +6 -4 tests/unittest_inspector.py
https://invent.kde.org/neon/backports-jammy/power-profiles-daemon/-/commit/8448eb4d7e15231a4a59d04b8ddc4ce0be5af2ca
diff --git a/.ci/fail_skipped_tests.py b/.ci/fail_skipped_tests.py
index 6349921..868fd06 100755
--- a/.ci/fail_skipped_tests.py
+++ b/.ci/fail_skipped_tests.py
@@ -3,23 +3,37 @@
from lxml import etree
import sys
+
def format_title(title):
"""Put title in a box"""
box = {
- 'tl': '╔', 'tr': '╗', 'bl': '╚', 'br': '╝', 'h': '═', 'v': '║',
+ "tl": "╔",
+ "tr": "╗",
+ "bl": "╚",
+ "br": "╝",
+ "h": "═",
+ "v": "║",
}
- hline = box['h'] * (len(title) + 2)
+ hline = box["h"] * (len(title) + 2)
+
+ return "\n".join(
+ [
+ f"{box['tl']}{hline}{box['tr']}",
+ f"{box['v']} {title} {box['v']}",
+ f"{box['bl']}{hline}{box['br']}",
+ ]
+ )
- return '\n'.join([
- f"{box['tl']}{hline}{box['tr']}",
- f"{box['v']} {title} {box['v']}",
- f"{box['bl']}{hline}{box['br']}",
- ])
tree = etree.parse(sys.argv[1])
-for suite in tree.xpath('/testsuites/testsuite'):
- skipped = suite.get('skipped')
+for suite in tree.xpath("/testsuites/testsuite"):
+ skipped = suite.get("skipped")
if int(skipped) != 0:
- print(format_title('Tests were skipped when they should not have been. All the tests must be run in the CI'),
- end='\n\n', flush=True)
+ print(
+ format_title(
+ "Tests were skipped when they should not have been. All the tests must be run in the CI"
+ ),
+ end="\n\n",
+ flush=True,
+ )
sys.exit(1)
diff --git a/.gitignore b/.gitignore
index b8dbfe9..f07a5a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
power-profiles-daemon
-data/net.hadess.PowerProfiles.conf
+__pycache__
+.vscode
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ddec8d0..81fc905 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,6 +2,7 @@ image: fedora:rawhide
variables:
DEPENDENCIES: gcc
+ gcovr
gtk-doc
pkgconfig(udev)
pkgconfig(systemd)
@@ -14,6 +15,7 @@ variables:
git
python3-gobject
python3-dbusmock
+ python3-packaging
python3-pylint
umockdev
e2fsprogs
@@ -23,21 +25,105 @@ workflow:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_PIPELINE_SOURCE == 'push'
-build_stable:
+.install-deps:
+ variables:
+ TMPDIR: $CI_BUILDS_DIR/tmpdir
before_script:
- - dnf upgrade -y --nogpgcheck fedora-release fedora-repos*
- - dnf update -y && dnf install -y $DEPENDENCIES
- - mkdir tmpdir/
+ - echo 8096000 > /proc/sys/fs/inotify/max_user_instances
+ - mkdir -m 700 $TMPDIR -p
+ - dnf update -y
+ - if [ -x /bin/dnf ]; then
+ dnf install -y $DEPENDENCIES $JOB_DEPS;
+ else
+ dnf5 install -y $DEPENDENCIES $JOB_DEPS;
+ fi
+
+pre_commit:
+ variables:
+ DEPENDENCIES: {}
+ JOB_DEPS: pre-commit
+ git
+ extends:
+ - .install-deps
+ script:
+ - pre-commit run --all-files
+
+build_stable:
+ extends:
+ - .install-deps
script:
- - meson -Dgtk_doc=true -Dpylint=true -Dtests=true _build
- - ninja -v -C _build
- - ninja -v -C _build install
- - ninja -v -C _build uninstall
- - TMPDIR=$(pwd)/tmpdir meson test -C _build
+ - meson setup
+ --werror
+ --fatal-meson-warnings
+ --warnlevel 2
+ -Dgtk_doc=true
+ -Dpylint=true
+ -Db_coverage=true
+ _build
+ - meson test -C _build --print-errorlogs
- .ci/fail_skipped_tests.py _build/meson-logs/testlog.junit.xml
- - TMPDIR=$(pwd)/tmpdir ninja -v -C _build dist
+ - ninja -C _build coverage
+ - cat _build/meson-logs/coverage.txt || true
+ - meson install -C _build
+ - ninja -C _build uninstall -v
+ - meson dist -C _build
artifacts:
when: always
paths:
- _build/meson-logs/*.txt
- _build/meson-dist/*
+ - _build/meson-logs/coveragereport/index.html
+ reports:
+ junit:
+ - _build/meson-logs/testlog.junit.xml
+ coverage_report:
+ coverage_format: cobertura
+ path: _build/meson-logs/coverage.xml
+ coverage: '/^TOTAL.*\s+(\d+\%)$/'
+
+address_sanitizer:
+ variables:
+ JOB_DEPS: libasan
+ libubsan
+ extends:
+ - .install-deps
+ script:
+ - meson setup
+ --werror
+ --buildtype=debug
+ _build
+ -Db_sanitize=address,undefined
+ - meson test -C _build --print-errorlogs -t 3
+ artifacts:
+ when: on_failure
+ paths:
+ - _build/meson-logs/*.txt
+
+valgrind:
+ variables:
+ JOB_DEPS: valgrind
+ extends:
+ - .install-deps
+ script:
+ - meson setup
+ --werror
+ --buildtype=debug
+ _build
+ - meson test -C _build --print-errorlogs --setup=valgrind
+ artifacts:
+ when: on_failure
+ paths:
+ - _build/meson-logs/*.txt
+
+scan_build:
+ variables:
+ JOB_DEPS: clang-analyzer
+ extends:
+ - .install-deps
+ script:
+ - meson setup _build
+ - env SCANBUILD=$(which scan-build) ninja -C _build scan-build
+ artifacts:
+ when: on_failure
+ paths:
+ - _build/meson-logs
diff --git a/.markdownlint.json b/.markdownlint.json
new file mode 100644
index 0000000..3d29c36
--- /dev/null
+++ b/.markdownlint.json
@@ -0,0 +1,10 @@
+{
+ "default": true,
+ "MD033": false,
+ "MD041": false,
+ "MD036": false,
+ "MD013": {
+ "tables": false,
+ "line_length": 1000
+ }
+}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..b44d998
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,39 @@
+default_stages: [commit]
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: no-commit-to-branch
+ args: [--branch, main]
+ - id: check-added-large-files
+ - id: check-byte-order-marker
+ - id: check-executables-have-shebangs
+ - id: forbid-new-submodules
+ - id: check-yaml
+ - id: check-json
+ - id: pretty-format-json
+ args: ['--no-sort-keys']
+ - id: check-symlinks
+ - id: check-xml
+ - id: end-of-file-fixer
+ types_or: [c, shell, python]
+ - id: trailing-whitespace
+ types_or: [c, shell, python, xml]
+ - id: check-docstring-first
+ - id: check-merge-conflict
+ - id: mixed-line-ending
+ args: [--fix=lf]
+- repo: https://github.com/codespell-project/codespell
+ rev: v2.2.6
+ hooks:
+ - id: codespell
+ args: ['--write-changes']
+- repo: https://github.com/ambv/black
+ rev: 23.12.0
+ hooks:
+ - id: black
+- repo: https://github.com/igorshubovych/markdownlint-cli
+ rev: v0.38.0
+ hooks:
+ - id: markdownlint
+ args: ['--fix']
diff --git a/NEWS b/NEWS
index 59ce68c..247eb8d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,23 @@
+0.20
+----
+
+The project has moved under the freedesktop 'Upower' group. The service is
+now advertised as 'org.freedesktop.UPower.PowerProfiles' in addition to the
+previous 'net.hadess.PowerProfiles' for compatibility reasons.
+
+This release adds support for:
+
+* Multiple power-profiles-daemon drivers to load simultaneously. This notably
+ allows both CPU based control with amd-pstate or intel-pstate as well as
+ ACPI platform profile based control.
+
+* amdgpu panel power savings which uses dedicated hardware in systems with
+ integrated Radeon graphics to decrease panel power consumption when the
+ system is on battery.
+
+This release also enables the test suite by default, so distribution vendors
+should update packaging accordingly.
+
0.13
----
diff --git a/README.md b/README.md
index 8ab25a3..90e032c 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,17 @@
-power-profiles-daemon
-=====================
+# power-profiles-daemon
Makes power profiles handling available over D-Bus.
-Installation
-------------
+## Installation
+
```sh
-$ meson _build -Dprefix=/usr
-$ ninja -v -C _build install
+meson setup _build -Dprefix=/usr
+ninja -C _build install
```
+
It requires libgudev, systemd and polkit-gobject.
-Introduction
-------------
+## Introduction
power-profiles-daemon offers to modify system behaviour based upon user-selected
power profiles. There are 3 different power profiles, a "balanced" default mode,
@@ -30,8 +29,7 @@ they are also expected to adjust the behaviour of the desktop depending on the m
such as turning the screen off after inaction more aggressively when in power-saver
mode.
-How to use
-----------
+## How to use
There are interfaces to switch profiles in the latest versions of KDE and GNOME. Those
desktops also include more thorough integration with its low-power mode. Please check
@@ -44,6 +42,7 @@ or the power-saver profile.
For example, this will be useful to avoid manual switching profiles while compiling
large projects:
+
```sh
powerprofilesctl launch make
```
@@ -52,8 +51,7 @@ If you're a developer, you might also want to use GLib's [`GPowerProfileMonitor`
through C, or one of its bindings, so your application can react to the user requesting
a low-power mode.
-Conflicts
----------
+## Conflicts
If `power-profiles-daemon` refuses to start, it's likely that you have [a conflicting
service installed and running](data/power-profiles-daemon.service.in#L3), or your
@@ -65,8 +63,7 @@ systemctl unmask power-profiles-daemon.service
systemctl start power-profiles-daemon.service
```
-Debugging
----------
+## Debugging
You can now check which mode is in use, and which ones are available by running:
@@ -86,9 +83,13 @@ reboot in `/var/lib/power-profiles-daemon/state.ini`.
Those commands are also available through the D-Bus interface:
-```
-gdbus introspect --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles
-gdbus call --system --dest net.hadess.PowerProfiles --object-path /net/hadess/PowerProfiles --method org.freedesktop.DBus.Properties.Set 'net.hadess.PowerProfiles' 'ActiveProfile' "<'power-saver'>"
+```sh
+gdbus introspect --system --dest org.freedesktop.UPower.PowerProfiles \
+ --object-path /org/freedesktop/UPower/PowerProfiles
+gdbus call --system --dest org.freedesktop.UPower.PowerProfiles \
+ --object-path /org/freedesktop/UPower/PowerProfiles \
+ --method org.freedesktop.DBus.Properties.Set 'org.freedesktop.UPower.PowerProfiles' \
+ 'ActiveProfile' "<'power-saver'>"
```
If that doesn't work, please file an issue, attach the output of:
@@ -97,19 +98,19 @@ If that doesn't work, please file an issue, attach the output of:
sudo G_MESSAGES_DEBUG=all /usr/libexec/power-profiles-daemon -r -v
```
-Operations on Intel-based machines
-----------------------------------
+## Operations on Intel-based machines
The "driver" for making the hardware act on the user-selected power profile on Intel
CPU-based machines is based on the [Intel P-State scaling driver](https://www.kernel.org/doc/html/v5.17/admin-guide/pm/intel_pstate.html)
or the Energy Performance Bias (EPB) feature if available.
-It is only used if a `platform_profile` driver isn't available for the system, and the
-CPU supports either hardware-managed P-states (HWP) or Energy Performance Bias (EPB).
+It is only used if the CPU supports either hardware-managed P-states (HWP)
+or Energy Performance Bias (EPB).
Example of a system without `platform_profile support` but with `active` P-State
operation mode:
-```
+
+```sh
$ cat /sys/firmware/acpi/platform_profile_choices
cat: /sys/firmware/acpi/platform_profile_choices: No such file or directory
$ cat /sys/devices/system/cpu/intel_pstate/status
@@ -117,13 +118,14 @@ active
```
Example of a system with `EPB` support:
-```
+
+```sh
$ cat /sys/devices/system/cpu/cpu0/power/energy_perf_bias
0
```
If the Intel P-State scaling driver is in `passive` mode, either because the system doesn't
-support HWP, or the administator has disabled it, and `EPB` isn't available, then the
+support HWP, or the administrator has disabled it, and `EPB` isn't available, then the
placeholder driver will be used, and there won't be a performance mode.
Finally, if the Intel P-State scaling driver is used in `active` mode, the P-State
@@ -134,20 +136,22 @@ 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
-----------------------------------
+## Operations on AMD-based machines
+
+### CPU power savings
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.
+It is only used if the CPU supports Collaborative Processor Performance
+Control (CPPC), the machine is a laptop or workstation 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:
-```
+
+```sh
$ 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
@@ -163,8 +167,52 @@ governor that allows for the "Energy vs Performance Hints" to be taken into cons
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
--------
+### Panel power savings
+
+Laptops with integrated Radeon graphics have a dedicated hardware function
+to decrease panel power consumption in exchange for color accuracy. This
+function is used when the system is on battery and the user has selected
+the "balanced" or "power-saver" profiles.
+
+If you decide that you don't like how this behaves, you can disable the function
+in one of two ways:
+
+1. Adding `amdgpu.abmlevel=0` to the kernel command line. This will disable abm
+ value changes entirely.
+2. By using `POWER_PROFILE_DAEMON_ACTION_BLOCK=amdgpu_panel_power` in the
+ `power-profiles-daemon` environment as described below. This will allow you to
+ still change values manually in sysfs but `power-profiles-daemon` will not
+ change anything.
+
+## Multiple driver and multiple action operations
+
+Power-profiles daemon will load all supported drivers and actions by default.
+If you have a problem with a given driver or action, you can disable it by
+populating the `POWER_PROFILE_DAEMON_DRIVER_BLOCK` or `POWER_PROFILE_DAEMON_ACTION_BLOCK`
+environment variables with the name of the driver or action you want to disable
+in the environment that launches the daemon (such as the systemd unit file).
+
+For example to edit the unit:
+
+```sh
+sudo systemctl edit power-profiles-daemon.service
+```
+
+Then add to the drop-in file:
+
+```text
+[Service]
+Environment=POWER_PROFILE_DAEMON_DRIVER_BLOCK=xxx
+Environment=POWER_PROFILE_DAEMON_ACTION_BLOCK=yyy
+```
+
+Then restart the service:
+
+```sh
+sudo systemctl try-restart power-profiles-daemon.service
+```
+
+## Testing
If you don't have hardware that can support the performance mode, or the degraded mode
you can manually run the `power-profiles-daemon` binary as `root` with the environment
@@ -174,8 +222,7 @@ variable `POWER_PROFILE_DAEMON_FAKE_DRIVER` set to 1. For example:
sudo POWER_PROFILE_DAEMON_FAKE_DRIVER=1 /usr/libexec/power-profiles-daemon -r -v
```
-References
-----------
+## References
- [Use Low Power Mode to save battery life on your iPhone (iOS)](https://support.apple.com/en-us/HT205234)
- [lowPowerModeEnabled (iOS)](https://developer.apple.com/documentation/foundation/nsprocessinfo/1617047-lowpowermodeenabled?language=objc)
@@ -183,8 +230,7 @@ References
- [[S]ettings that use less battery (Android)](https://support.google.com/android/answer/7664692?hl=en&visit_id=637297348326801871-2263015427&rd=1)
- [EnergySaverStatus Enum (Windows)](https://docs.microsoft.com/en-us/uwp/api/windows.system.power.energysaverstatus?view=winrt-19041)
-Why power-profiles-daemon
--------------------------
+## Why power-profiles-daemon
The power-profiles-daemon project was created to help provide a solution for
two separate use cases, for desktops, laptops, and other devices running a
@@ -208,8 +254,7 @@ and make its API available over D-Bus, as has been customary for more than
10 years. We would also design that API to be as easily usable to build
graphical interfaces as possible.
-Why not...
-----------
+## Why not
This section will contain explanations of why this new daemon was written
rather than re-using, or modifying an existing one. Each project obviously
@@ -251,6 +296,7 @@ of goes against a user's wishes as a user might still want to conserve as
much energy as possible under high-CPU usage.
### [slimbookbattery](https://launchpad.net/~slimbook)
+
This is **not** free software (*Source code available but not modifiable
without express authorization.*). The application does a lot of things in
addition to the "3 profiles" selection:
diff --git a/check-news.sh b/check-news.sh
index 11ae861..cd6f819 100644
--- a/check-news.sh
+++ b/check-news.sh
@@ -24,13 +24,13 @@
#
# Checks NEWS for the version number:
# meson.add_dist_script(
-# find_program('check-news.sh').path(),
+# find_program('check-news.sh').full_path(),
# '@0@'.format(meson.project_version())
# )
#
# Checks NEWS and data/foo.appdata.xml for the version number:
# meson.add_dist_script(
-# find_program('check-news.sh').path(),
+# find_program('check-news.sh').full_path(),
# '@0@'.format(meson.project_version()),
# 'NEWS',
# 'data/foo.appdata.xml'
@@ -48,9 +48,9 @@ check_version()
# Look in the first 15 lines for NEWS files, but look
# everywhere for other types of files
if [ "$2" = "NEWS" ]; then
- DATA=`sed 15q $SRC_ROOT/"$2"`
+ DATA=$(sed 15q "$SRC_ROOT/$2")
else
- DATA=`cat $SRC_ROOT/"$2"`
+ DATA=$(cat "$SRC_ROOT/$2")
fi
case "$DATA" in
*"$VERSION"*)
@@ -71,12 +71,12 @@ VERSION=$1
shift
if [ $# -eq 0 ] ; then
- check_version $VERSION 'NEWS'
+ check_version "$VERSION" 'NEWS'
exit 0
fi
-for i in $@ ; do
- check_version $VERSION "$i"
+for i in "$@"; do
+ check_version "$VERSION" "$i"
done
-exit 0
\ No newline at end of file
+exit 0
diff --git a/data/meson.build b/data/meson.build
index 3d1b466..a42e80f 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -1,32 +1,40 @@
-data_conf = configuration_data()
-data_conf.set('libexecdir', libexecdir)
-
configure_file(
input: 'power-profiles-daemon.service.in',
output: 'power-profiles-daemon.service',
- configuration: data_conf,
+ configuration: {
+ 'libexecdir': libexecdir,
+ },
install_dir: systemd_system_unit_dir,
)
-configure_file(
- input: 'net.hadess.PowerProfiles.conf.in',
- output: 'net.hadess.PowerProfiles.conf',
- configuration: data_conf,
- install_dir: dbusconfdir
-)
+foreach name, _: bus_names
+ config = {
+ 'dbus_name': name,
+ 'dbus_iface': name,
+ }
-install_data(
- 'net.hadess.PowerProfiles.service',
- install_dir: dbusservicedir
-)
+ configure_file(
+ input: 'power-profiles-daemon.dbus.conf.in',
+ output: name + '.conf',
+ configuration: config,
+ install_dir: dbusconfdir
+ )
+
+ configure_file(
+ input: 'power-profiles-daemon.dbus.service.in',
+ output: name + '.service',
+ configuration: config,
+ install_dir: dbusservicedir
+ )
+endforeach
-polkit_policy = 'net.hadess.PowerProfiles.policy'
+polkit_policy = 'power-profiles-daemon.policy'
if xmllint.found()
test(polkit_policy,
xmllint,
args: [
'--noout',
- meson.source_root() / 'data' / polkit_policy,
+ meson.project_source_root() / 'data' / polkit_policy,
])
endif
diff --git a/data/net.hadess.PowerProfiles.conf.in b/data/net.hadess.PowerProfiles.conf.in
deleted file mode 100644
index a01d2d4..0000000
--- a/data/net.hadess.PowerProfiles.conf.in
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
-
-<!DOCTYPE busconfig PUBLIC
- "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
- "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
-<busconfig>
-
- <!-- Only root can own the service -->
- <policy user="root">
- <allow own="net.hadess.PowerProfiles"/>
- </policy>
-
- <!-- Anyone can talk to the main interface -->
- <policy context="default">
- <allow send_destination="net.hadess.PowerProfiles" send_interface="net.hadess.PowerProfiles"/>
- <allow send_destination="net.hadess.PowerProfiles" send_interface="org.freedesktop.DBus.Introspectable"/>
- <allow send_destination="net.hadess.PowerProfiles" send_interface="org.freedesktop.DBus.Properties"/>
- <allow send_destination="net.hadess.PowerProfiles" send_interface="org.freedesktop.DBus.Peer"/>
- </policy>
-
-</busconfig>
diff --git a/data/power-profiles-daemon.dbus.conf.in b/data/power-profiles-daemon.dbus.conf.in
new file mode 100644
index 0000000..844277f
--- /dev/null
+++ b/data/power-profiles-daemon.dbus.conf.in
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
+
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+ <!-- Only root can own the service -->
+ <policy user="root">
+ <allow own="@dbus_name@"/>
+ </policy>
+
+ <!-- Anyone can talk to the main interface -->
+ <policy context="default">
+ <allow send_destination="@dbus_name@" send_interface="@dbus_iface@"/>
+ <allow send_destination="@dbus_name@" send_interface="org.freedesktop.DBus.Introspectable"/>
+ <allow send_destination="@dbus_name@" send_interface="org.freedesktop.DBus.Properties"/>
+ <allow send_destination="@dbus_name@" send_interface="org.freedesktop.DBus.Peer"/>
+ </policy>
+
+</busconfig>
diff --git a/data/net.hadess.PowerProfiles.service b/data/power-profiles-daemon.dbus.service.in
similarity index 91%
rename from data/net.hadess.PowerProfiles.service
rename to data/power-profiles-daemon.dbus.service.in
index 724f631..5baefa1 100644
--- a/data/net.hadess.PowerProfiles.service
+++ b/data/power-profiles-daemon.dbus.service.in
@@ -5,7 +5,7 @@
# the Free Software Foundation.
[D-BUS Service]
-Name=net.hadess.PowerProfiles
+Name=@dbus_name@
Exec=/bin/false
User=root
SystemdService=power-profiles-daemon.service
diff --git a/data/net.hadess.PowerProfiles.policy b/data/power-profiles-daemon.policy
similarity index 87%
rename from data/net.hadess.PowerProfiles.policy
rename to data/power-profiles-daemon.policy
index a332b42..722e758 100644
--- a/data/net.hadess.PowerProfiles.policy
+++ b/data/power-profiles-daemon.policy
@@ -8,7 +8,7 @@
<vendor>power-profiles-daemon</vendor>
<vendor_url>https://gitlab.freedesktop.org/hadess/power-profiles-daemon</vendor_url>
- <action id="net.hadess.PowerProfiles.switch-profile">
+ <action id="org.freedesktop.UPower.PowerProfiles.switch-profile">
<description>Switch Power Profile</description>
<message>Privileges are required to switch power profiles.</message>
<defaults>
@@ -18,7 +18,7 @@
</defaults>
</action>
- <action id="net.hadess.PowerProfiles.hold-profile">
+ <action id="org.freedesktop.UPower.PowerProfiles.hold-profile">
<description>Hold Power Profile</description>
<message>Privileges are required to hold power profiles.</message>
<defaults>
diff --git a/data/power-profiles-daemon.service.in b/data/power-profiles-daemon.service.in
index dcd3503..17ec1d2 100644
--- a/data/power-profiles-daemon.service.in
+++ b/data/power-profiles-daemon.service.in
@@ -5,7 +5,7 @@ Before=multi-user.target display-manager.target
[Service]
Type=dbus
-BusName=net.hadess.PowerProfiles
+BusName=org.freedesktop.UPower.PowerProfiles
ExecStart=@libexecdir@/power-profiles-daemon
Restart=on-failure
# This always corresponds to /var/lib/power-profiles-daemon
diff --git a/debian/changelog b/debian/changelog
index 3ae1b0e..b8094bf 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,25 @@
+power-profiles-daemon (0.20-1) unstable; urgency=medium
+
+ * New upstream release:
+ - New default bus name is org.freedesktop.UPower.PowerProfiles
+ - Support multiple drivers (ACPI + amd/intel ones)
+ * debian/watch: Update reference to new upstream repository
+ * debian: Update references to new repository under UPower namespace
+ * debian/copyright: Update copyright to new maintainers
+ * debian/control: Add myself to uploaders
+ * debian/control: Add python dependencies
+ While the daemon does not require python at runtime, the control tool
+ does so ensure debian picks them
+ * debian/patches: Refresh
+ * debian/rules: drop dh_missing override for --fail-missing.
+ It's default for some time now
+ * debian/patches: Add upstream patch to generate powerprofilectl manfile
+ * debian/control: Add manpage generation dependencies
+ * debian/rules: Explicitly enable manpage feature
+ * debian/rules: Fix generated manpage entries
+
+ -- Marco Trevisan (Treviño) <marco at ubuntu.com> Thu, 15 Feb 2024 05:38:30 +0100
+
power-profiles-daemon (0.13-2) unstable; urgency=medium
[ Debian Janitor ]
diff --git a/debian/control b/debian/control
index ffffaba..6e0008f 100644
--- a/debian/control
+++ b/debian/control
@@ -2,8 +2,9 @@ Source: power-profiles-daemon
Section: admin
Priority: optional
Maintainer: Debian freedesktop.org maintainers <pkg-freedesktop-maintainers at lists.alioth.debian.org>
-Uploaders: Sebastien Bacher <seb128 at ubuntu.com>
+Uploaders: Sebastien Bacher <seb128 at ubuntu.com>, Marco Trevisan (Treviño) <marco at ubuntu.com>
Build-Depends: debhelper-compat (= 13),
+ dh-python,
libglib2.0-dev,
libgudev-1.0-dev,
libpolkit-gobject-1-dev,
@@ -12,19 +13,22 @@ Build-Depends: debhelper-compat (= 13),
libumockdev-dev,
libxml2-utils,
meson,
+ python3,
+ python3-argparse-manpage,
python3-dbus <!nocheck>,
python3-dbusmock <!nocheck>,
- python3-gi <!nocheck>,
+ python3-gi,
systemd,
umockdev <!nocheck>,
Standards-Version: 4.6.2
Vcs-Browser: https://salsa.debian.org/freedesktop-team/power-profiles-daemon
Vcs-Git: https://salsa.debian.org/freedesktop-team/power-profiles-daemon.git
-Homepage: https://gitlab.freedesktop.org/hadess/power-profiles-daemon
+Homepage: https://gitlab.freedesktop.org/upower/power-profiles-daemon
+X-Python3-Version: >= 3.11
Package: power-profiles-daemon
Architecture: linux-any
-Depends: ${misc:Depends}, ${shlibs:Depends},
+Depends: ${misc:Depends}, ${shlibs:Depends}, ${python3:Depends}, python3-gi
Description: Makes power profiles handling available over D-Bus.
power-profiles-daemon offers to modify system behaviour based upon
user-selected power profiles. There are 3 different power profiles, a
diff --git a/debian/copyright b/debian/copyright
index 24ea099..d82f806 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,18 +1,26 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: power-profiles-daemon
-Source: https://gitlab.freedesktop.org/hadess/power-profiles-daemon
+Source: https://gitlab.freedesktop.org/upower/power-profiles-daemon
Files: *
Copyright: 2014-2016, 2020 Bastien Nocera <hadess at hadess.net>
+ 2024 Marco Trevisan <marco at ubuntu.com>
+ 2024 Mario Limonciello <mario.limonciello at amd.com>
License: GPL-3
Files: debian/*
Copyright: 2021 Sebastien Bacher <seb128 at ubuntu.com>
License: GPL-3
-Files: tests/integration-test
+Files: tests/integration_test.py
Copyright: 2011 Martin Pitt <martin.pitt at ubuntu.com>
2020 Bastien Nocera <hadess at hadess.net>
+ 2024 Marco Trevisan <marco at ubuntu.com>
+ 2024 Mario Limonciello <mario.limonciello at amd.com>
+License: GPL-2+
+
+Files: tests/unittest_inspector.py
+Copyright: Marco Trevisan <marco.trevisan at canonical.com>
License: GPL-2+
Files: docs/power-profiles-daemon-docs.xml
diff --git a/debian/patches/build-Expose-powerprofilesctl-script-and-load-it-using-fi.patch b/debian/patches/build-Expose-powerprofilesctl-script-and-load-it-using-fi.patch
new file mode 100644
index 0000000..36ceed9
--- /dev/null
+++ b/debian/patches/build-Expose-powerprofilesctl-script-and-load-it-using-fi.patch
@@ -0,0 +1,32 @@
+From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= <mail at 3v1n0.net>
+Date: Thu, 15 Feb 2024 03:22:52 +0100
+Subject: build: Expose powerprofilesctl script and load it using files
+
+Origin: https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/merge_requests/163
+---
+ src/meson.build | 7 +++----
+ 1 file changed, 3 insertions(+), 4 deletions(-)
+
+diff --git a/src/meson.build b/src/meson.build
+index 88b8ff6..3f59dd3 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -104,15 +104,14 @@ executable('power-profiles-daemon',
+ install_dir: libexecdir
+ )
+
+-script = 'powerprofilesctl'
+-install_data(script,
++powerprofilesctl = files('powerprofilesctl')
++install_data(powerprofilesctl,
+ install_dir: get_option('bindir')
+ )
+-script = join_paths(meson.current_source_dir(), script)
+ if get_option('pylint')
+ test('pylint-powerprofilesctl',
+ pylint,
+- args: pylint_flags + [ script ],
++ args: pylint_flags + [ powerprofilesctl ],
+ env: nomalloc,
+ )
+ endif
diff --git a/debian/patches/powerprofilectl-Generate-manpage-using-argparse-manpage.patch b/debian/patches/powerprofilectl-Generate-manpage-using-argparse-manpage.patch
new file mode 100644
index 0000000..fdd0858
--- /dev/null
+++ b/debian/patches/powerprofilectl-Generate-manpage-using-argparse-manpage.patch
@@ -0,0 +1,108 @@
+From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= <mail at 3v1n0.net>
+Date: Thu, 15 Feb 2024 04:05:37 +0100
+Subject: powerprofilectl: Generate manpage using argparse-manpage
+
+Add an option to toggle this feature so that it can be either
+required or not
+
+Origin: https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/merge_requests/163
+---
+ meson.build | 5 +++++
+ meson_options.txt | 4 ++++
+ src/meson.build | 31 +++++++++++++++++++++++++++++++
+ src/powerprofilesctl | 8 ++++++--
+ 4 files changed, 46 insertions(+), 2 deletions(-)
+
+diff --git a/meson.build b/meson.build
+index cd35078..1a4d116 100644
+--- a/meson.build
++++ b/meson.build
+@@ -56,6 +56,11 @@ if get_option('pylint')
+ endif
+ xmllint = find_program('xmllint', required: false)
+
++manpage = get_option('manpage')
++if not manpage.disabled()
++ argparse_manpage = find_program('argparse-manpage', required: manpage.enabled())
++endif
++
+ bus_names = {
+ 'org.freedesktop.UPower.PowerProfiles': '/org/freedesktop/UPower/PowerProfiles',
+ 'net.hadess.PowerProfiles': '/net/hadess/PowerProfiles',
+diff --git a/meson_options.txt b/meson_options.txt
+index 5e9a7ce..0de978c 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -14,3 +14,7 @@ option('tests',
+ description: 'Whether to run tests',
+ type: 'boolean',
+ value: true)
++option('manpage',
++ description: 'gemerate powerprofilesctl man page',
++ type: 'feature',
++ value: 'auto')
+diff --git a/src/meson.build b/src/meson.build
+index 3f59dd3..65ddcc4 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -115,3 +115,34 @@ if get_option('pylint')
+ env: nomalloc,
+ )
+ endif
++
++if not manpage.disabled() and argparse_manpage.found()
++ argparse_features = run_command(argparse_manpage, '--help',
++ check: true).stdout().strip()
++
++ install_man(configure_file(
++ command: [
++ argparse_manpage,
++ '--pyfile', powerprofilesctl,
++ '--function', 'get_parser',
++ argparse_features.contains('--author') ?
++ ['--author', 'Bastien Nocera'] : [],
++ argparse_features.contains('--author-email') ?
++ ['--author-email', 'hadess at hadess.net'] : [],
++ argparse_features.contains('--project-name') ?
++ ['--project-name', meson.project_name()] : [],
++ argparse_features.contains('--version') ?
++ ['--version', meson.project_version()] : [],
++ argparse_features.contains('--url') ?
++ ['--url', 'https://gitlab.freedesktop.org/upower/power-profiles-daemon'] : [],
++ argparse_features.contains('--description') ?
++ ['--manual-title', 'Power Profiles Daemon Control Program'] : [],
++ argparse_features.contains('--description') ?
++ ['--description', 'Command line utility to control Power Profiles Daemon'] : [],
++ argparse_features.contains('--format') ?
++ ['--format', 'single-commands-section'] : [],
++ ],
++ capture: true,
++ output: 'powerprofilesctl.1',
++ ))
++endif
+diff --git a/src/powerprofilesctl b/src/powerprofilesctl
+index af4dfe1..051538d 100755
+--- a/src/powerprofilesctl
++++ b/src/powerprofilesctl
+@@ -164,7 +164,7 @@ def _launch(args):
+ return ret
+
+
+-def main():
++def get_parser():
+ parser = argparse.ArgumentParser(
+ epilog="Use “powerprofilesctl COMMAND --help” to get detailed help for individual commands",
+ )
+@@ -216,7 +216,11 @@ def main():
+ )
+ parser_version.set_defaults(func=_version)
+
+- args = parser.parse_args()
++ return parser
++
++
++def main():
++ args = get_parser().parse_args()
+ # default behavior is to run list if no command is given
+ if not args.command:
+ args.func = _list
diff --git a/debian/patches/remove_tlp_conflict.patch b/debian/patches/remove_tlp_conflict.patch
index e334e26..5aced1f 100644
--- a/debian/patches/remove_tlp_conflict.patch
+++ b/debian/patches/remove_tlp_conflict.patch
@@ -1,13 +1,21 @@
-Description: remove the systemd unit conflict on tlp.service
+From: "Debian freedesktop.org maintainers"
+ <pkg-freedesktop-maintainers at lists.alioth.debian.org>
+Date: Thu, 15 Feb 2024 04:23:10 +0100
+Subject: remove the systemd unit conflict on tlp.service
+
we don't want that in Ubuntu since we patch tlp to not do performance modes
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
-===================================================================
---- power-profiles-daemon.orig/data/power-profiles-daemon.service.in
-+++ power-profiles-daemon/data/power-profiles-daemon.service.in
+---
+ data/power-profiles-daemon.service.in | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/data/power-profiles-daemon.service.in b/data/power-profiles-daemon.service.in
+index 17ec1d2..2b913fa 100644
+--- a/data/power-profiles-daemon.service.in
++++ b/data/power-profiles-daemon.service.in
@@ -1,6 +1,6 @@
[Unit]
Description=Power Profiles daemon
diff --git a/debian/patches/series b/debian/patches/series
index a0d0401..ee18d0d 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1,3 @@
remove_tlp_conflict.patch
+build-Expose-powerprofilesctl-script-and-load-it-using-fi.patch
+powerprofilectl-Generate-manpage-using-argparse-manpage.patch
diff --git a/debian/rules b/debian/rules
index 9573e7d..6494fef 100755
--- a/debian/rules
+++ b/debian/rules
@@ -4,7 +4,12 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all
export DEB_LDFLAGS_MAINT_APPEND = -Wl,-z,defs
%:
- dh $@
+ dh $@ --with python3
-override_dh_missing:
- dh_missing --fail-missing
+override_dh_auto_configure:
+ dh_auto_configure -- \
+ -Dmanpage=enabled
+ # Fix contents of manpage, can be dropped when newer argparse-manpage lands
+ # in debian
+ sed -i s,argparse-manpage,powerprofilesctl,g \
+ $(CURDIR)/obj-$(DEB_HOST_GNU_TYPE)/src/powerprofilesctl.1
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
index b105363..dd970b0 100644
--- a/debian/upstream/metadata
+++ b/debian/upstream/metadata
@@ -1,5 +1,5 @@
---
-Bug-Database: https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/issues
-Bug-Submit: https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/issues/new
-Repository: https://gitlab.freedesktop.org/hadess/power-profiles-daemon.git
-Repository-Browse: https://gitlab.freedesktop.org/hadess/power-profiles-daemon
+Bug-Database: https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/issues
+Bug-Submit: https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/issues/new
+Repository: https://gitlab.freedesktop.org/upower/power-profiles-daemon.git
+Repository-Browse: https://gitlab.freedesktop.org/upower/power-profiles-daemon
diff --git a/debian/watch b/debian/watch
index 506172d..e845cda 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,3 +1,3 @@
version=4
-https://gitlab.freedesktop.org/hadess/power-profiles-daemon/tags \
+https://gitlab.freedesktop.org/upower/power-profiles-daemon/tags \
.*/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@
diff --git a/docs/meson.build b/docs/meson.build
index 4de0210..ead4574 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -11,8 +11,8 @@ content_files += configure_file(
content_files += gnome.gdbus_codegen(
meson.project_name(),
- sources: meson.source_root() / 'src' / 'net.hadess.PowerProfiles.xml',
- interface_prefix: 'net.hadess',
+ sources: dbus_xml['org.freedesktop.UPower.PowerProfiles'],
+ interface_prefix: 'org.freedesktop.UPower',
namespace: 'PowerProfiles',
docbook: 'docs',
build_by_default: true,
@@ -21,13 +21,12 @@ content_files += gnome.gdbus_codegen(
private_headers = [
'power-profiles-daemon.h',
'ppd-action-trickle-charge.h',
- 'ppd-driver-balanced.h',
+ 'ppd-action-amdgpu-panel-power.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',
'ppd-utils.h',
'power-profiles-daemon-resources.h',
]
@@ -39,8 +38,8 @@ gnome.gtkdoc(
dependencies: libpower_profiles_daemon_dep,
gobject_typesfile: [join_paths(meson.current_source_dir(), 'power-profiles-daemon.types')],
src_dir: [
- meson.source_root() /'src',
- meson.build_root() / 'src',
+ meson.project_source_root() /'src',
+ meson.project_build_root() / 'src',
],
ignore_headers: private_headers,
)
diff --git a/docs/power-profiles-daemon-docs.xml b/docs/power-profiles-daemon-docs.xml
index 0a2c627..e903706 100644
--- a/docs/power-profiles-daemon-docs.xml
+++ b/docs/power-profiles-daemon-docs.xml
@@ -63,14 +63,17 @@
Power Profiles daemon.
</para>
</partintro>
- <xi:include href="docs-net.hadess.PowerProfiles.xml"/>
+ <xi:include href="docs-org.freedesktop.UPower.PowerProfiles.xml"/>
</reference>
<reference id="internal-api">
<title>Internal API</title>
<xi:include href="xml/ppd-profile.xml"/>
<xi:include href="xml/ppd-driver.xml"/>
+ <xi:include href="xml/ppd-driver-cpu.xml"/>
+ <xi:include href="xml/ppd-driver-platform.xml"/>
<xi:include href="xml/ppd-action.xml"/>
+ <xi:include href="xml/ppd-action-amdgpu-panel-power.xml"/>
</reference>
<index>
diff --git a/docs/power-profiles-daemon-sections.txt b/docs/power-profiles-daemon-sections.txt
index 0063310..3c88528 100644
--- a/docs/power-profiles-daemon-sections.txt
+++ b/docs/power-profiles-daemon-sections.txt
@@ -1,3 +1,10 @@
+<SECTION>
+<FILE>config</FILE>
+<SUBSECTION Private>
+VERSION
+</SECTION>
+</SECTION>
+
<SECTION>
<FILE>ppd-action</FILE>
<TITLE>Profile Actions</TITLE>
@@ -7,6 +14,15 @@ PpdAction
PPD_TYPE_ACTION
</SECTION>
+<SECTION>
+<FILE>ppd-action-amdgpu-panel-power</FILE>
+<TITLE>AMDGPU Power Panel Saving Action</TITLE>
+PpdActionAmdgpuPanelPowerClass
+_PpdActionAmdgpuPanelPower
+<SUBSECTION Private>
+PPD_TYPE_ACTION
+</SECTION>
+
<SECTION>
<FILE>ppd-driver</FILE>
<TITLE>Profile Drivers</TITLE>
@@ -16,6 +32,22 @@ PpdProbeResult
PpdProfileActivationReason
<SUBSECTION Private>
PPD_TYPE_DRIVER
+PPD_TYPE_DRIVER_CPU
+PPD_TYPE_DRIVER_PLATFORM
+</SECTION>
+
+<SECTION>
+<FILE>ppd-driver-cpu</FILE>
+<TITLE>CPU Profile Drivers</TITLE>
+PpdDriverCpuClass
+PpdDriverCpu
+</SECTION>
+
+<SECTION>
+<FILE>ppd-driver-platform</FILE>
+<TITLE>Platform Profile Drivers</TITLE>
+PpdDriverPlatformClass
+PpdDriverPlatform
</SECTION>
<SECTION>
diff --git a/meson.build b/meson.build
index 47e982b..cd35078 100644
--- a/meson.build
+++ b/meson.build
@@ -1,12 +1,12 @@
project('power-profiles-daemon', [ 'c' ],
- version: '0.13',
+ version: '0.20',
license: 'GPLv3+',
default_options: [
'buildtype=debugoptimized',
'warning_level=1',
'c_std=c99',
],
- meson_version: '>= 0.54.0')
+ meson_version: '>= 0.59.0')
cc = meson.get_compiler('c')
@@ -17,7 +17,12 @@ common_cflags = cc.get_supported_arguments([
'-Wstrict-prototypes',
'-Werror-implicit-function-declaration',
'-Wno-pointer-sign',
- '-Wshadow'
+ '-Wshadow',
+ '-Wno-sign-compare',
+ '-Wno-cast-function-type',
+ '-Wno-unused-parameter',
+ '-Wno-missing-field-initializers',
+ '-Wno-type-limits',
])
prefix = get_option('prefix')
@@ -29,13 +34,15 @@ dbusservicedir = get_option('datadir') / 'dbus-1' / 'system-services'
systemd_system_unit_dir = get_option('systemdsystemunitdir')
if systemd_system_unit_dir == 'auto'
systemd_dep = dependency('systemd')
- systemd_system_unit_dir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir')
+ systemd_system_unit_dir = systemd_dep.get_variable('systemdsystemunitdir')
endif
+glib_dep = dependency('glib-2.0')
+gio_unix_dep = dependency('gio-unix-2.0')
gio_dep = dependency('gio-2.0')
gudev_dep = dependency('gudev-1.0', version: '>= 234')
upower_dep = dependency('upower-glib')
-polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.114')
-polkit_policy_directory = polkit_gobject_dep.get_pkgconfig_variable('policydir')
+polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.91')
+polkit_policy_directory = polkit_gobject_dep.get_variable('policydir')
gnome = import('gnome')
@@ -49,6 +56,15 @@ if get_option('pylint')
endif
xmllint = find_program('xmllint', required: false)
+bus_names = {
+ 'org.freedesktop.UPower.PowerProfiles': '/org/freedesktop/UPower/PowerProfiles',
+ 'net.hadess.PowerProfiles': '/net/hadess/PowerProfiles',
+}
+
+address_sanitizer = get_option('b_sanitize') == 'address' or \
+ get_option('b_sanitize') == 'address,undefined' or \
+ get_option('b_sanitize') == 'leak'
+
subdir('src')
subdir('data')
@@ -65,6 +81,11 @@ endif
if get_option('tests')
# Python 3 required modules
python3_required_modules = ['dbusmock', 'gi']
+ gi_required_modules = {
+ 'GLib': '2.0',
+ 'Gio': '2.0',
+ 'UMockdev': '1.0',
+ }
python = import('python')
python3 = python.find_installation('python3')
@@ -76,10 +97,18 @@ if get_option('tests')
endif
endforeach
+ foreach module, version : gi_required_modules
+ script = 'import gi; gi.require_version("@0@", "@1@")'.format(module, version)
+ if run_command(python3, '-c', script, check: false).returncode() != 0
+ error('''GObject Introspection module '@0@' version @1@ required for running tests but not found'''.format(
+ module, version))
+ endif
+ endforeach
+
subdir('tests')
endif
meson.add_dist_script(
- find_program('check-news.sh').path(),
+ find_program('check-news.sh').full_path(),
'@0@'.format(meson.project_version())
)
diff --git a/meson_options.txt b/meson_options.txt
index 307bc6a..5e9a7ce 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -13,4 +13,4 @@ option('pylint',
option('tests',
description: 'Whether to run tests',
type: 'boolean',
- value: false)
+ value: true)
diff --git a/src/meson.build b/src/meson.build
index 6cf5113..88b8ff6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,17 +1,59 @@
-deps = [ gio_dep, gudev_dep, upower_dep, polkit_gobject_dep ]
+deps = [
+ gio_dep,
+ gio_unix_dep,
+ gudev_dep,
+ polkit_gobject_dep,
+ upower_dep,
+]
config_h = configuration_data()
config_h.set_quoted('VERSION', meson.project_version())
+config_h.set('POLKIT_HAS_AUTOPOINTERS', polkit_gobject_dep.version().version_compare('>= 0.114'))
config_h_files = configure_file(
output: 'config.h',
configuration: config_h
)
+dbus_xml = {}
+dbus_xml_sources = []
+resources_contents = []
+
+foreach name, path: bus_names
+ config = {
+ 'dbus_name': name,
+ 'dbus_iface': name,
+ 'dbus_path': path,
+ }
+
+ xml_source = configure_file(
+ input: 'power-profiles-daemon.dbus.xml.in',
+ output: name + '.xml',
+ configuration: config,
+ )
+ dbus_xml_sources += xml_source
+ dbus_xml += {name: xml_source}
+
+ resources_contents += '<file preprocess="xml-stripblanks">@0 at .xml</file>'.format(name)
+endforeach
+
+resources_xml = configure_file(
+ input: 'power-profiles-daemon.gresource.xml.in',
+ output: '@BASENAME@',
+ configuration: {
+ 'prefix': bus_names['org.freedesktop.UPower.PowerProfiles'],
+ 'contents': '\n'.join(resources_contents),
+ },
+)
+
resources = gnome.compile_resources(
- 'power-profiles-daemon-resources', 'power-profiles-daemon.gresource.xml',
- c_name: 'power_profiles_daemon',
- source_dir: '.',
- export: true
+ 'power-profiles-daemon-resources', resources_xml,
+ c_name: 'power_profiles_daemon',
+ dependencies: dbus_xml_sources,
+ source_dir: [
+ meson.current_source_dir(),
+ meson.current_build_dir(),
+ ],
+ export: true
)
sources = [
@@ -19,6 +61,8 @@ sources = [
'ppd-utils.c',
'ppd-action.c',
'ppd-driver.c',
+ 'ppd-driver-cpu.c',
+ 'ppd-driver-platform.c',
resources,
]
@@ -45,6 +89,7 @@ libpower_profiles_daemon_dep = declare_dependency(
sources += [
'power-profiles-daemon.c',
'ppd-action-trickle-charge.c',
+ 'ppd-action-amdgpu-panel-power.c',
'ppd-driver-intel-pstate.c',
'ppd-driver-amd-pstate.c',
'ppd-driver-platform-profile.c',
@@ -59,20 +104,11 @@ executable('power-profiles-daemon',
install_dir: libexecdir
)
-python = import('python')
-py_installation = python.find_installation('python3', required: true)
-
-ppd_conf = configuration_data()
-ppd_conf.set('VERSION', meson.project_version())
-ppd_conf.set('PYTHON3', py_installation.path())
-
-script = configure_file(
- input: 'powerprofilesctl.in',
- output: 'powerprofilesctl',
- configuration: ppd_conf,
- install_dir: get_option('bindir')
+script = 'powerprofilesctl'
+install_data(script,
+ install_dir: get_option('bindir')
)
-
+script = join_paths(meson.current_source_dir(), script)
if get_option('pylint')
test('pylint-powerprofilesctl',
pylint,
diff --git a/src/power-profiles-daemon.c b/src/power-profiles-daemon.c
index 2a3c26b..eb673cf 100644
--- a/src/power-profiles-daemon.c
+++ b/src/power-profiles-daemon.c
@@ -8,26 +8,45 @@
*
*/
+#define G_LOG_DOMAIN "Core"
+
#include "config.h"
+#include <glib-unix.h>
#include <locale.h>
#include <polkit/polkit.h>
+#include <stdio.h>
#include "power-profiles-daemon-resources.h"
#include "power-profiles-daemon.h"
-#include "ppd-driver.h"
+#include "ppd-driver-cpu.h"
+#include "ppd-driver-platform.h"
#include "ppd-action.h"
#include "ppd-enums.h"
-#define POWER_PROFILES_DBUS_NAME "net.hadess.PowerProfiles"
-#define POWER_PROFILES_DBUS_PATH "/net/hadess/PowerProfiles"
+#define POWER_PROFILES_DBUS_NAME "org.freedesktop.UPower.PowerProfiles"
+#define POWER_PROFILES_DBUS_PATH "/org/freedesktop/UPower/PowerProfiles"
#define POWER_PROFILES_IFACE_NAME POWER_PROFILES_DBUS_NAME
+#define POWER_PROFILES_LEGACY_DBUS_NAME "net.hadess.PowerProfiles"
+#define POWER_PROFILES_LEGACY_DBUS_PATH "/net/hadess/PowerProfiles"
+#define POWER_PROFILES_LEGACY_IFACE_NAME POWER_PROFILES_LEGACY_DBUS_NAME
+
+#define POWER_PROFILES_POLICY_NAMESPACE "org.freedesktop.UPower.PowerProfiles"
+
+#define POWER_PROFILES_RESOURCES_PATH "/org/freedesktop/UPower/PowerProfiles"
+
+#ifndef POLKIT_HAS_AUTOPOINTERS
+/* FIXME: Remove this once we're fine to depend on polkit 0.114 */
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAuthorizationResult, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitSubject, g_object_unref)
+#endif
+
typedef struct {
GMainLoop *main_loop;
- GDBusNodeInfo *introspection_data;
GDBusConnection *connection;
guint name_id;
+ guint legacy_name_id;
gboolean was_started;
int ret;
@@ -39,9 +58,11 @@ typedef struct {
PpdProfile active_profile;
PpdProfile selected_profile;
GPtrArray *probed_drivers;
- PpdDriver *driver;
+ PpdDriverCpu *cpu_driver;
+ PpdDriverPlatform *platform_driver;
GPtrArray *actions;
GHashTable *profile_holds;
+ GLogLevelFlags log_level;
} PpdApp;
typedef struct {
@@ -49,6 +70,7 @@ typedef struct {
char *reason;
char *application_id;
char *requester;
+ char *requester_iface;
} ProfileHold;
static void
@@ -59,6 +81,7 @@ profile_hold_free (ProfileHold *hold)
g_free (hold->reason);
g_free (hold->application_id);
g_free (hold->requester);
+ g_free (hold->requester_iface);
g_free (hold);
}
@@ -67,11 +90,9 @@ static PpdApp *ppd_app = NULL;
static void stop_profile_drivers (PpdApp *data);
static void start_profile_drivers (PpdApp *data);
-#define GET_DRIVER(p) (ppd_driver_get_profiles (data->driver) & p ? data->driver : NULL)
-#define ACTIVE_DRIVER (data->driver)
-
/* profile drivers and actions */
#include "ppd-action-trickle-charge.h"
+#include "ppd-action-amdgpu-panel-power.h"
#include "ppd-driver-placeholder.h"
#include "ppd-driver-platform-profile.h"
#include "ppd-driver-intel-pstate.h"
@@ -92,6 +113,7 @@ static GTypeGetFunc objects[] = {
/* Actions */
ppd_action_trickle_charge_get_type,
+ ppd_action_amdgpu_panel_power_get_type,
};
typedef enum {
@@ -100,19 +122,33 @@ typedef enum {
PROP_PROFILES = 1 << 2,
PROP_ACTIONS = 1 << 3,
PROP_DEGRADED = 1 << 4,
- PROP_ACTIVE_PROFILE_HOLDS = 1 << 5
+ PROP_ACTIVE_PROFILE_HOLDS = 1 << 5,
+ PROP_VERSION = 1 << 6,
} PropertiesMask;
-#define PROP_ALL (PROP_ACTIVE_PROFILE | PROP_INHIBITED | PROP_PROFILES | PROP_ACTIONS | PROP_DEGRADED | PROP_ACTIVE_PROFILE_HOLDS)
+#define PROP_ALL (PROP_ACTIVE_PROFILE | \
+ PROP_INHIBITED | \
+ PROP_PROFILES | \
+ PROP_ACTIONS | \
+ PROP_DEGRADED | \
+ PROP_ACTIVE_PROFILE_HOLDS | \
+ PROP_VERSION)
+
+static gboolean
+driver_profile_support (PpdDriver *driver,
+ PpdProfile profile)
+{
+ if (!PPD_IS_DRIVER (driver))
+ return FALSE;
+ return (ppd_driver_get_profiles (driver) & profile) != 0;
+}
static gboolean
get_profile_available (PpdApp *data,
PpdProfile profile)
{
- PpdDriver *driver;
-
- driver = GET_DRIVER(profile);
- return driver != NULL;
+ return driver_profile_support (PPD_DRIVER (data->cpu_driver), profile) ||
+ driver_profile_support (PPD_DRIVER (data->platform_driver), profile);
}
static const char *
@@ -121,18 +157,28 @@ get_active_profile (PpdApp *data)
return ppd_profile_to_str (data->active_profile);
}
-static const char *
+static char *
get_performance_degraded (PpdApp *data)
{
- const char *ret;
- PpdDriver *driver;
-
- driver = GET_DRIVER(PPD_PROFILE_PERFORMANCE);
- if (!driver)
- return "";
- ret = ppd_driver_get_performance_degraded (driver);
- g_assert (ret != NULL);
- return ret;
+ const gchar *cpu_degraded = NULL;
+ const gchar *platform_degraded = NULL;
+
+ if (driver_profile_support (PPD_DRIVER (data->platform_driver), PPD_PROFILE_PERFORMANCE))
+ platform_degraded = ppd_driver_get_performance_degraded (PPD_DRIVER (data->platform_driver));
+
+ if (driver_profile_support (PPD_DRIVER (data->cpu_driver), PPD_PROFILE_PERFORMANCE))
+ cpu_degraded = ppd_driver_get_performance_degraded (PPD_DRIVER (data->cpu_driver));
+
+ if (!cpu_degraded && !platform_degraded)
+ return g_strdup ("");
+
+ if (!cpu_degraded)
+ return g_strdup (platform_degraded);
+
+ if (!platform_degraded)
+ return g_strdup (cpu_degraded);
+
+ return g_strjoin (",", cpu_degraded, platform_degraded, NULL);
}
static GVariant *
@@ -144,17 +190,40 @@ get_profiles_variant (PpdApp *data)
g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
for (i = 0; i < NUM_PROFILES; i++) {
- PpdDriver *driver = GET_DRIVER(1 << i);
+ PpdDriver *platform_driver = PPD_DRIVER (data->platform_driver);
+ PpdDriver *cpu_driver = PPD_DRIVER (data->cpu_driver);
+ PpdProfile profile = 1 << i;
GVariantBuilder asv_builder;
+ gboolean cpu, platform;
+ const gchar *driver = NULL;
- if (driver == NULL)
+ /* check if any of the drivers support */
+ if (!get_profile_available (data, profile))
continue;
g_variant_builder_init (&asv_builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&asv_builder, "{sv}", "Profile",
- g_variant_new_string (ppd_profile_to_str (1 << i)));
- g_variant_builder_add (&asv_builder, "{sv}", "Driver",
- g_variant_new_string (ppd_driver_get_driver_name (driver)));
+ g_variant_new_string (ppd_profile_to_str (profile)));
+ cpu = driver_profile_support (cpu_driver, profile);
+ platform = driver_profile_support (platform_driver, profile);
+ if (cpu)
+ g_variant_builder_add (&asv_builder, "{sv}", "CpuDriver",
+ g_variant_new_string (ppd_driver_get_driver_name (cpu_driver)));
+ if (platform)
+ g_variant_builder_add (&asv_builder, "{sv}", "PlatformDriver",
+ g_variant_new_string (ppd_driver_get_driver_name (platform_driver)));
+
+ /* compatibility with older API */
+ if (cpu && platform)
+ driver = "multiple";
+ else if (cpu)
+ driver = ppd_driver_get_driver_name (cpu_driver);
+ else if (platform)
+ driver = ppd_driver_get_driver_name (platform_driver);
+
+ if (driver)
+ g_variant_builder_add (&asv_builder, "{sv}", "Driver",
+ g_variant_new_string (driver));
g_variant_builder_add (&builder, "a{sv}", &asv_builder);
}
@@ -207,8 +276,10 @@ get_profile_holds_variant (PpdApp *data)
}
static void
-send_dbus_event (PpdApp *data,
- PropertiesMask mask)
+send_dbus_event_iface (PpdApp *data,
+ PropertiesMask mask,
+ const gchar *iface,
+ const gchar *path)
{
GVariantBuilder props_builder;
GVariant *props_changed = NULL;
@@ -231,8 +302,9 @@ send_dbus_event (PpdApp *data,
g_variant_new_string (""));
}
if (mask & PROP_DEGRADED) {
+ gchar *degraded = get_performance_degraded (data);
g_variant_builder_add (&props_builder, "{sv}", "PerformanceDegraded",
- g_variant_new_string (get_performance_degraded (data)));
+ g_variant_new_take_string (g_steal_pointer (°raded)));
}
if (mask & PROP_PROFILES) {
g_variant_builder_add (&props_builder, "{sv}", "Profiles",
@@ -246,26 +318,53 @@ send_dbus_event (PpdApp *data,
g_variant_builder_add (&props_builder, "{sv}", "ActiveProfileHolds",
get_profile_holds_variant (data));
}
+ if (mask & PROP_VERSION) {
+ g_variant_builder_add (&props_builder, "{sv}", "Version",
+ g_variant_new_string (VERSION));
+ }
- props_changed = g_variant_new ("(s at a{sv}@as)", POWER_PROFILES_IFACE_NAME,
+ props_changed = g_variant_new ("(s at a{sv}@as)", iface,
g_variant_builder_end (&props_builder),
g_variant_new_strv (NULL, 0));
g_dbus_connection_emit_signal (data->connection,
NULL,
- POWER_PROFILES_DBUS_PATH,
+ path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
props_changed, NULL);
}
+static void
+send_dbus_event (PpdApp *data,
+ PropertiesMask mask)
+{
+ send_dbus_event_iface (data, mask,
+ POWER_PROFILES_IFACE_NAME,
+ POWER_PROFILES_DBUS_PATH);
+ send_dbus_event_iface (data, mask,
+ POWER_PROFILES_LEGACY_IFACE_NAME,
+ POWER_PROFILES_LEGACY_DBUS_PATH);
+}
+
static void
save_configuration (PpdApp *data)
{
g_autoptr(GError) error = NULL;
- g_key_file_set_string (data->config, "State", "Driver", ppd_driver_get_driver_name (data->driver));
- g_key_file_set_string (data->config, "State", "Profile", ppd_profile_to_str (data->active_profile));
+ if (PPD_IS_DRIVER_CPU (data->cpu_driver)) {
+ g_key_file_set_string (data->config, "State", "CpuDriver",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)));
+ }
+
+ if (PPD_IS_DRIVER_PLATFORM (data->platform_driver)) {
+ g_key_file_set_string (data->config, "State", "PlatformDriver",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver)));
+ }
+
+ g_key_file_set_string (data->config, "State", "Profile",
+ ppd_profile_to_str (data->active_profile));
+
if (!g_key_file_save_to_file (data->config, data->config_path, &error))
g_warning ("Could not save configuration file '%s': %s", data->config_path, error->message);
}
@@ -273,16 +372,26 @@ save_configuration (PpdApp *data)
static gboolean
apply_configuration (PpdApp *data)
{
- g_autofree char *driver = NULL;
+ g_autofree char *platform_driver = NULL;
g_autofree char *profile_str = NULL;
+ g_autofree char *cpu_driver = NULL;
PpdProfile profile;
- driver = g_key_file_get_string (data->config, "State", "Driver", NULL);
- if (g_strcmp0 (ppd_driver_get_driver_name (data->driver), driver) != 0)
+ cpu_driver = g_key_file_get_string (data->config, "State", "CpuDriver", NULL);
+ if (PPD_IS_DRIVER_CPU (data->cpu_driver) &&
+ g_strcmp0 (ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)), cpu_driver) != 0)
+ return FALSE;
+
+ platform_driver = g_key_file_get_string (data->config, "State", "PlatformDriver", NULL);
+
+ if (PPD_IS_DRIVER_PLATFORM (data->platform_driver) &&
+ g_strcmp0 (ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver)), platform_driver) != 0)
return FALSE;
+
profile_str = g_key_file_get_string (data->config, "State", "Profile", NULL);
if (profile_str == NULL)
return FALSE;
+
profile = ppd_profile_from_str (profile_str);
if (profile == PPD_PROFILE_UNSET) {
g_debug ("Resetting invalid configuration profile '%s'", profile_str);
@@ -339,18 +448,55 @@ activate_target_profile (PpdApp *data,
PpdProfileActivationReason reason,
GError **error)
{
- GError *internal_error = NULL;
+ PpdProfile current_profile = data->active_profile;
+ gboolean cpu_set = TRUE;
+ gboolean platform_set = TRUE;
g_debug ("Setting active profile '%s' for reason '%s' (current: '%s')",
ppd_profile_to_str (target_profile),
ppd_profile_activation_reason_to_str (reason),
- ppd_profile_to_str (data->active_profile));
+ ppd_profile_to_str (current_profile));
+
+ /* Try CPU first */
+ if (driver_profile_support (PPD_DRIVER (data->cpu_driver), target_profile))
+ cpu_set = ppd_driver_activate_profile (PPD_DRIVER (data->cpu_driver),
+ target_profile, reason, error);
+
+ if (!cpu_set) {
+ g_prefix_error (error, "Failed to activate CPU driver '%s': ",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)));
+ return FALSE;
+ }
+
+ /* Then try platform */
+ if (driver_profile_support (PPD_DRIVER (data->platform_driver), target_profile)) {
+ platform_set = ppd_driver_activate_profile (PPD_DRIVER (data->platform_driver),
+ target_profile, reason, error);
+ }
+
+ if (!platform_set) {
+ g_prefix_error (error, "Failed to activate platform driver '%s': ",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver)));
+
+ /* Try to recover */
+ if (cpu_set) {
+ g_autoptr(GError) recovery_error = NULL;
+
+ g_debug ("Reverting CPU driver '%s' to profile '%s'",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)),
+ ppd_profile_to_str (current_profile));
+
+ if (!ppd_driver_activate_profile (PPD_DRIVER (data->cpu_driver),
+ current_profile, PPD_PROFILE_ACTIVATION_REASON_INTERNAL,
+ &recovery_error)) {
+ g_prefix_error (error, "Failed to revert CPU driver '%s': ",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)));
+ g_warning ("Failed to revert CPU driver '%s': %s",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)),
+ recovery_error->message);
+ }
+ }
- if (!ppd_driver_activate_profile (data->driver, target_profile, reason, &internal_error)) {
- g_warning ("Failed to activate driver '%s': %s",
- ppd_driver_get_driver_name (data->driver),
- internal_error->message);
- g_propagate_error (error, internal_error);
return FALSE;
}
@@ -365,6 +511,21 @@ activate_target_profile (PpdApp *data,
return TRUE;
}
+static void
+release_hold_notify (PpdApp *data,
+ ProfileHold *hold,
+ guint cookie)
+{
+ const char *req_path = POWER_PROFILES_DBUS_PATH;
+
+ if (g_strcmp0 (hold->requester_iface, POWER_PROFILES_LEGACY_IFACE_NAME) == 0)
+ req_path = POWER_PROFILES_LEGACY_DBUS_PATH;
+
+ g_dbus_connection_emit_signal (data->connection, hold->requester, req_path,
+ hold->requester_iface, "ProfileReleased",
+ g_variant_new ("(u)", cookie), NULL);
+}
+
static void
release_all_profile_holds (PpdApp *data)
{
@@ -376,9 +537,7 @@ release_all_profile_holds (PpdApp *data)
ProfileHold *hold = value;
guint cookie = GPOINTER_TO_UINT (key);
- g_dbus_connection_emit_signal (data->connection, hold->requester, POWER_PROFILES_DBUS_PATH,
- POWER_PROFILES_IFACE_NAME, "ProfileReleased",
- g_variant_new ("(u)", cookie), NULL);
+ release_hold_notify (data, hold, cookie);
g_bus_unwatch_name (cookie);
}
g_hash_table_remove_all (data->profile_holds);
@@ -496,12 +655,13 @@ release_profile_hold (PpdApp *data,
hold = g_hash_table_lookup (data->profile_holds, GUINT_TO_POINTER (cookie));
if (!hold) {
- g_debug("No hold with cookie %d", cookie);
+ g_debug ("No hold with cookie %d", cookie);
return;
}
g_bus_unwatch_name (cookie);
hold_profile = hold->profile;
+ release_hold_notify (data, hold, cookie);
g_hash_table_remove (data->profile_holds, GUINT_TO_POINTER (cookie));
if (g_hash_table_size (data->profile_holds) == 0 &&
@@ -587,8 +747,9 @@ hold_profile (PpdApp *data,
hold->reason = g_strdup (reason);
hold->application_id = g_strdup (application_id);
hold->requester = g_strdup (g_dbus_method_invocation_get_sender (invocation));
+ hold->requester_iface = g_strdup (g_dbus_method_invocation_get_interface_name (invocation));
- g_debug ("%s(%s) requesting to hold profile '%s', reason: '%s'", application_id,
+ g_debug ("%s (%s) requesting to hold profile '%s', reason: '%s'", application_id,
hold->requester, profile_name, reason);
watch_id = g_bus_watch_name_on_connection (data->connection, hold->requester,
G_BUS_NAME_WATCHER_FLAGS_NONE, NULL,
@@ -676,10 +837,14 @@ handle_get_property (GDBusConnection *connection,
return get_profiles_variant (data);
if (g_strcmp0 (property_name, "Actions") == 0)
return get_actions_variant (data);
- if (g_strcmp0 (property_name, "PerformanceDegraded") == 0)
- return g_variant_new_string (get_performance_degraded (data));
+ if (g_strcmp0 (property_name, "PerformanceDegraded") == 0) {
+ gchar *degraded = get_performance_degraded (data);
+ return g_variant_new_take_string (g_steal_pointer (°raded));
+ }
if (g_strcmp0 (property_name, "ActiveProfileHolds") == 0)
return get_profile_holds_variant (data);
+ if (g_strcmp0 (property_name, "Version") == 0)
+ return g_variant_new_string (VERSION);
return NULL;
}
@@ -703,7 +868,9 @@ handle_set_property (GDBusConnection *connection,
"No such property: %s", property_name);
return FALSE;
}
- if (!check_action_permission (data, sender, "net.hadess.PowerProfiles.switch-profile", error))
+ if (!check_action_permission (data, sender,
+ POWER_PROFILES_POLICY_NAMESPACE ".switch-profile",
+ error))
return FALSE;
g_variant_get (value, "&s", &profile);
@@ -723,7 +890,8 @@ handle_method_call (GDBusConnection *connection,
PpdApp *data = user_data;
g_assert (data->connection);
- if (g_strcmp0 (interface_name, POWER_PROFILES_IFACE_NAME) != 0) {
+ if (!g_str_equal (interface_name, POWER_PROFILES_IFACE_NAME) &&
+ !g_str_equal (interface_name, POWER_PROFILES_LEGACY_IFACE_NAME)) {
g_dbus_method_invocation_return_error (invocation,G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE,
"Unknown interface %s", interface_name);
return;
@@ -733,7 +901,7 @@ handle_method_call (GDBusConnection *connection,
g_autoptr(GError) local_error = NULL;
if (!check_action_permission (data,
g_dbus_method_invocation_get_sender (invocation),
- "net.hadess.PowerProfiles.hold-profile",
+ POWER_PROFILES_POLICY_NAMESPACE ".hold-profile",
&local_error)) {
g_dbus_method_invocation_return_gerror (invocation, local_error);
return;
@@ -756,16 +924,42 @@ static const GDBusInterfaceVTable interface_vtable =
handle_set_property
};
+typedef struct {
+ PpdApp *app;
+ GBusNameOwnerFlags flags;
+ GDBusInterfaceInfo *interface;
+ GDBusInterfaceInfo *legacy_interface;
+} PpdBusOwnData;
+
+static void
+ppd_bus_own_data_free (PpdBusOwnData *data)
+{
+ g_clear_pointer (&data->interface, g_dbus_interface_info_unref);
+ g_clear_pointer (&data->legacy_interface, g_dbus_interface_info_unref);
+ g_free (data);
+}
+
static void
name_lost_handler (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
- PpdApp *data = user_data;
+ PpdBusOwnData *data = user_data;
+ PpdApp *app = data->app;
+
g_debug ("power-profiles-daemon is already running, or it cannot own its D-Bus name. Verify installation.");
- if (!data->was_started)
- data->ret = 1;
- g_main_loop_quit (data->main_loop);
+ if (!app->was_started)
+ app->ret = 1;
+
+ g_main_loop_quit (app->main_loop);
+}
+
+static void
+legacy_name_acquired_handler (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_debug ("Name '%s' acquired", name);
}
static void
@@ -773,29 +967,43 @@ bus_acquired_handler (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
- PpdApp *data = user_data;
+ PpdBusOwnData *data = user_data;
g_dbus_connection_register_object (connection,
POWER_PROFILES_DBUS_PATH,
- data->introspection_data->interfaces[0],
+ data->interface,
&interface_vtable,
- data,
+ data->app,
NULL,
NULL);
- data->connection = g_object_ref (connection);
+ g_dbus_connection_register_object (connection,
+ POWER_PROFILES_LEGACY_DBUS_PATH,
+ data->legacy_interface,
+ &interface_vtable,
+ data->app,
+ NULL,
+ NULL);
+
+ data->app->legacy_name_id = g_bus_own_name_on_connection (connection,
+ POWER_PROFILES_LEGACY_DBUS_NAME,
+ data->flags,
+ legacy_name_acquired_handler,
+ name_lost_handler,
+ data,
+ NULL);
+
+ data->app->connection = g_object_ref (connection);
}
static gboolean
has_required_drivers (PpdApp *data)
{
- PpdDriver *driver;
-
- driver = GET_DRIVER (PPD_PROFILE_BALANCED);
- if (!driver || !G_IS_OBJECT (driver))
+ if (!PPD_IS_DRIVER_CPU (data->cpu_driver) &&
+ !PPD_IS_DRIVER_PLATFORM (data->platform_driver))
return FALSE;
- driver = GET_DRIVER (PPD_PROFILE_POWER_SAVER);
- if (!driver || !G_IS_OBJECT (driver))
+
+ if (!get_profile_available (data, PPD_PROFILE_BALANCED | PPD_PROFILE_POWER_SAVER))
return FALSE;
return TRUE;
@@ -817,28 +1025,71 @@ stop_profile_drivers (PpdApp *data)
release_all_profile_holds (data);
g_ptr_array_set_size (data->probed_drivers, 0);
g_ptr_array_set_size (data->actions, 0);
- g_clear_object (&data->driver);
+ g_clear_object (&data->cpu_driver);
+ g_clear_object (&data->platform_driver);
+}
+
+static gboolean
+action_blocked (PpdAction *action)
+{
+ const gchar *action_name = ppd_action_get_action_name (action);
+ const gchar *env = g_getenv ("POWER_PROFILE_DAEMON_ACTION_BLOCK");
+
+ if (env == NULL)
+ return FALSE;
+
+ g_auto(GStrv) actions = NULL;
+
+ actions = g_strsplit (env, ",", -1);
+ return g_strv_contains ((const gchar *const *)actions, action_name);
+}
+
+static gboolean
+driver_blocked (PpdDriver *driver)
+{
+ const gchar *driver_name = ppd_driver_get_driver_name (driver);
+ const gchar *env = g_getenv ("POWER_PROFILE_DAEMON_DRIVER_BLOCK");
+
+ if (env == NULL)
+ return FALSE;
+
+ g_auto(GStrv) drivers = NULL;
+ drivers = g_strsplit (env, ",", -1);
+ return g_strv_contains ((const char **) drivers, driver_name);
}
static void
start_profile_drivers (PpdApp *data)
{
guint i;
+ g_autoptr(GError) initial_error = NULL;
for (i = 0; i < G_N_ELEMENTS (objects); i++) {
- GObject *object;
+ g_autoptr(GObject) object = NULL;
+
+ object = g_object_new (objects[i] (), NULL);
- object = g_object_new (objects[i](), NULL);
if (PPD_IS_DRIVER (object)) {
- PpdDriver *driver = PPD_DRIVER (object);
+ g_autoptr(PpdDriver) driver = PPD_DRIVER (g_steal_pointer (&object));
PpdProfile profiles;
PpdProbeResult result;
g_debug ("Handling driver '%s'", ppd_driver_get_driver_name (driver));
+ if (driver_blocked (driver)) {
+ g_debug ("Driver '%s' is blocked, skipping", ppd_driver_get_driver_name (driver));
+ continue;
+ }
+
+ if (PPD_IS_DRIVER_CPU (data->cpu_driver) && PPD_IS_DRIVER_CPU (driver)) {
+ g_debug ("CPU driver '%s' already probed, skipping driver '%s'",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->cpu_driver)),
+ ppd_driver_get_driver_name (driver));
+ continue;
+ }
- if (data->driver != NULL) {
- g_debug ("Driver '%s' already probed, skipping driver '%s'",
- ppd_driver_get_driver_name (data->driver),
+ if (PPD_IS_DRIVER_PLATFORM (data->platform_driver) && PPD_IS_DRIVER_PLATFORM (driver)) {
+ g_debug ("Platform driver '%s' already probed, skipping driver '%s'",
+ ppd_driver_get_driver_name (PPD_DRIVER (data->platform_driver)),
ppd_driver_get_driver_name (driver));
continue;
}
@@ -848,45 +1099,58 @@ start_profile_drivers (PpdApp *data)
g_warning ("Profile Driver '%s' implements invalid profiles '0x%X'",
ppd_driver_get_driver_name (driver),
profiles);
- g_object_unref (object);
continue;
}
result = ppd_driver_probe (driver);
if (result == PPD_PROBE_RESULT_FAIL) {
- g_debug ("probe() failed for driver %s, skipping",
+ g_debug ("probe () failed for driver %s, skipping",
ppd_driver_get_driver_name (driver));
- g_object_unref (object);
continue;
- } else if (result == PPD_PROBE_RESULT_DEFER) {
+ }
+
+ if (result == PPD_PROBE_RESULT_DEFER) {
g_signal_connect (G_OBJECT (driver), "probe-request",
G_CALLBACK (driver_probe_request_cb), data);
- g_ptr_array_add (data->probed_drivers, driver);
+ g_ptr_array_add (data->probed_drivers, g_steal_pointer (&driver));
continue;
}
- data->driver = driver;
+ if (PPD_IS_DRIVER_CPU (driver))
+ g_set_object (&data->cpu_driver, PPD_DRIVER_CPU (driver));
+ else if (PPD_IS_DRIVER_PLATFORM (driver))
+ g_set_object (&data->platform_driver, PPD_DRIVER_PLATFORM (driver));
+ else
+ g_assert_not_reached ();
g_signal_connect (G_OBJECT (driver), "notify::performance-degraded",
G_CALLBACK (driver_performance_degraded_changed_cb), data);
g_signal_connect (G_OBJECT (driver), "profile-changed",
G_CALLBACK (driver_profile_changed_cb), data);
- } else if (PPD_IS_ACTION (object)) {
- PpdAction *action = PPD_ACTION (object);
+ continue;
+ }
+
+ if (PPD_IS_ACTION (object)) {
+ g_autoptr(PpdAction) action = PPD_ACTION (g_steal_pointer (&object));
g_debug ("Handling action '%s'", ppd_action_get_action_name (action));
- if (!ppd_action_probe (action)) {
- g_debug ("probe() failed for action '%s', skipping",
+ if (action_blocked (action)) {
+ g_debug ("Action '%s' is blocked, skipping", ppd_action_get_action_name (action));
+ continue;
+ }
+
+ if (ppd_action_probe (action) == PPD_PROBE_RESULT_FAIL) {
+ g_debug ("probe () failed for action '%s', skipping",
ppd_action_get_action_name (action));
- g_object_unref (object);
continue;
}
- g_ptr_array_add (data->actions, action);
- } else {
- g_assert_not_reached ();
+ g_ptr_array_add (data->actions, g_steal_pointer (&action));
+ continue;
}
+
+ g_assert_not_reached ();
}
if (!has_required_drivers (data)) {
@@ -896,7 +1160,8 @@ start_profile_drivers (PpdApp *data)
/* Set initial state either from configuration, or using the currently selected profile */
apply_configuration (data);
- activate_target_profile (data, data->active_profile, PPD_PROFILE_ACTIVATION_REASON_RESET, NULL);
+ if (!activate_target_profile (data, data->active_profile, PPD_PROFILE_ACTIVATION_REASON_RESET, &initial_error))
+ g_warning ("Failed to activate initial profile: %s", initial_error->message);
send_dbus_event (data, PROP_ALL);
@@ -922,37 +1187,65 @@ name_acquired_handler (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
- PpdApp *data = user_data;
+ PpdBusOwnData *data = user_data;
- start_profile_drivers (data);
+ g_debug ("Name '%s' acquired", name);
+
+ start_profile_drivers (data->app);
}
static gboolean
-setup_dbus (PpdApp *data,
- gboolean replace)
+setup_dbus (PpdApp *data,
+ gboolean replace,
+ GError **error)
{
- GBytes *bytes;
- GBusNameOwnerFlags flags;
+ g_autoptr(GBytes) iface_data = NULL;
+ g_autoptr(GBytes) legacy_iface_data = NULL;
+ g_autoptr(GDBusNodeInfo) introspection_data = NULL;
+ g_autoptr(GDBusNodeInfo) legacy_introspection_data = NULL;
+ PpdBusOwnData *own_data;
+
+ iface_data = g_resources_lookup_data (POWER_PROFILES_RESOURCES_PATH "/"
+ POWER_PROFILES_DBUS_NAME ".xml",
+ G_RESOURCE_LOOKUP_FLAGS_NONE,
+ error);
+ if (!iface_data)
+ return FALSE;
+
+ legacy_iface_data = g_resources_lookup_data (POWER_PROFILES_RESOURCES_PATH "/"
+ POWER_PROFILES_LEGACY_DBUS_NAME ".xml",
+ G_RESOURCE_LOOKUP_FLAGS_NONE,
+ error);
+ if (!legacy_iface_data)
+ return FALSE;
+
+ introspection_data = g_dbus_node_info_new_for_xml (g_bytes_get_data (iface_data, NULL),
+ error);
+ if (!introspection_data)
+ return FALSE;
- bytes = g_resources_lookup_data ("/net/hadess/PowerProfiles/net.hadess.PowerProfiles.xml",
- G_RESOURCE_LOOKUP_FLAGS_NONE,
- NULL);
- data->introspection_data = g_dbus_node_info_new_for_xml (g_bytes_get_data (bytes, NULL), NULL);
- g_bytes_unref (bytes);
- g_assert (data->introspection_data != NULL);
+ legacy_introspection_data = g_dbus_node_info_new_for_xml (g_bytes_get_data (legacy_iface_data, NULL),
+ error);
+ if (!legacy_introspection_data)
+ return FALSE;
+
+ own_data = g_new0 (PpdBusOwnData, 1);
+ own_data->app = data;
+ own_data->interface = g_dbus_interface_info_ref (introspection_data->interfaces[0]);
+ own_data->legacy_interface = g_dbus_interface_info_ref (legacy_introspection_data->interfaces[0]);
- flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+ own_data->flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
if (replace)
- flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
+ own_data->flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
data->name_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
POWER_PROFILES_DBUS_NAME,
- flags,
+ own_data->flags,
bus_acquired_handler,
name_acquired_handler,
name_lost_handler,
- data,
- NULL);
+ own_data,
+ (GDestroyNotify) ppd_bus_own_data_free);
return TRUE;
}
@@ -963,37 +1256,92 @@ free_app_data (PpdApp *data)
if (data == NULL)
return;
- if (data->name_id != 0) {
- g_bus_unown_name (data->name_id);
- data->name_id = 0;
- }
+ g_clear_handle_id (&data->name_id, g_bus_unown_name);
+ g_clear_handle_id (&data->legacy_name_id, g_bus_unown_name);
g_clear_pointer (&data->config_path, g_free);
g_clear_pointer (&data->config, g_key_file_unref);
g_ptr_array_free (data->probed_drivers, TRUE);
g_ptr_array_free (data->actions, TRUE);
- g_clear_object (&data->driver);
+ g_clear_object (&data->cpu_driver);
+ g_clear_object (&data->platform_driver);
g_hash_table_destroy (data->profile_holds);
g_clear_object (&data->auth);
g_clear_pointer (&data->main_loop, g_main_loop_unref);
- g_clear_pointer (&data->introspection_data, g_dbus_node_info_unref);
g_clear_object (&data->connection);
g_free (data);
ppd_app = NULL;
}
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (PpdApp, free_app_data)
+
void
main_loop_quit (void)
{
g_main_loop_quit (ppd_app->main_loop);
}
+static inline gboolean
+use_colored_ouput (void)
+{
+ if (g_getenv ("NO_COLOR"))
+ return FALSE;
+ return isatty (fileno (stdout));
+}
+
+static void
+debug_handler_cb (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ PpdApp *data = user_data;
+ g_autoptr(GString) domain = NULL;
+ gboolean use_color;
+ gint color;
+
+ /* not in verbose mode */
+ if (log_level > data->log_level)
+ return;
+
+ domain = g_string_new (log_domain);
+ use_color = use_colored_ouput ();
+
+ for (gsize i = domain->len; i < 15; i++)
+ g_string_append_c (domain, ' ');
+ g_print ("%s", domain->str);
+
+ switch (log_level) {
+ case G_LOG_LEVEL_ERROR:
+ case G_LOG_LEVEL_CRITICAL:
+ case G_LOG_LEVEL_WARNING:
+ color = 31; /* red */
+ break;
+ default:
+ color = 34; /* blue */
+ break;
+ }
+
+ if (use_color)
+ g_print ("%c[%dm%s\n%c[%dm", 0x1B, color, message, 0x1B, 0);
+ else
+ g_print ("%s\n", message);
+}
+
+static gboolean
+quit_signal_callback (gpointer user_data)
+{
+ PpdApp *data = user_data;
+
+ g_main_loop_quit (data->main_loop);
+ return FALSE;
+}
+
int main (int argc, char **argv)
{
- PpdApp *data;
- int ret = 0;
+ g_autoptr (PpdApp) data = NULL;
g_autoptr(GOptionContext) option_context = NULL;
g_autoptr(GError) error = NULL;
gboolean verbose = FALSE;
@@ -1008,17 +1356,11 @@ int main (int argc, char **argv)
option_context = g_option_context_new ("");
g_option_context_add_main_entries (option_context, options, NULL);
- ret = g_option_context_parse (option_context, &argc, &argv, &error);
- if (!ret) {
+ if (!g_option_context_parse (option_context, &argc, &argv, &error)) {
g_print ("Failed to parse arguments: %s\n", error->message);
return EXIT_FAILURE;
}
- if (verbose)
- g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
-
- g_debug ("Starting power-profiles-daemon version "VERSION);
-
data = g_new0 (PpdApp, 1);
data->main_loop = g_main_loop_new (NULL, TRUE);
data->auth = polkit_authority_get_sync (NULL, NULL);
@@ -1027,15 +1369,27 @@ int main (int argc, char **argv)
data->profile_holds = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) profile_hold_free);
data->active_profile = PPD_PROFILE_BALANCED;
data->selected_profile = PPD_PROFILE_BALANCED;
+ data->log_level = verbose ? G_LOG_LEVEL_DEBUG : G_LOG_LEVEL_MESSAGE;
+
+ g_unix_signal_add (SIGTERM, quit_signal_callback, data);
+ g_unix_signal_add (SIGINT, quit_signal_callback, data);
+
+ /* redirect all domains */
+ if (verbose && !g_log_writer_is_journald (fileno (stdout)))
+ g_log_set_default_handler (debug_handler_cb, data);
+
+ g_debug ("Starting power-profiles-daemon version "VERSION);
+
load_configuration (data);
ppd_app = data;
/* Set up D-Bus */
- setup_dbus (data, replace);
+ if (!setup_dbus (data, replace, &error)) {
+ g_error ("Failed to start dbus: %s", error->message);
+ return 1;
+ }
g_main_loop_run (data->main_loop);
- ret = data->ret;
- free_app_data (data);
- return ret;
+ return data->ret;
}
diff --git a/src/net.hadess.PowerProfiles.xml b/src/power-profiles-daemon.dbus.xml.in
similarity index 95%
rename from src/net.hadess.PowerProfiles.xml
rename to src/power-profiles-daemon.dbus.xml.in
index fcfc1d4..04cfa72 100644
--- a/src/net.hadess.PowerProfiles.xml
+++ b/src/power-profiles-daemon.dbus.xml.in
@@ -4,7 +4,7 @@
<node>
<!--
- net.hadess.PowerProfiles:
+ @dbus_iface@:
@short_description: Power Profiles daemon
The power-profiles-daemon API is meant to be used by parts of the OS or
@@ -21,9 +21,9 @@
existing projects is explained <ulink href=" https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/blob/master/README.md">
in the project's README file</ulink>.
- The object path will be "/net/hadess/PowerProfiles".
+ The object path will be "@dbus_path@".
-->
- <interface name="net.hadess.PowerProfiles">
+ <interface name="@dbus_iface@">
<!--
HoldProfile:
@@ -136,5 +136,12 @@
-->
<property name="ActiveProfileHolds" type="aa{sv}" access="read"/>
+ <!--
+ Version:
+
+ The version of the power-profiles-daemon software.
+ -->
+ <property name="Version" type="s" access="read"/>
+
</interface>
</node>
diff --git a/src/power-profiles-daemon.gresource.xml b/src/power-profiles-daemon.gresource.xml
deleted file mode 100644
index ae1ee65..0000000
--- a/src/power-profiles-daemon.gresource.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<gresources>
- <gresource prefix="/net/hadess/PowerProfiles">
- <file preprocess="xml-stripblanks">net.hadess.PowerProfiles.xml</file>
- </gresource>
-</gresources>
-
diff --git a/src/power-profiles-daemon.gresource.xml.in b/src/power-profiles-daemon.gresource.xml.in
new file mode 100644
index 0000000..aaa90f9
--- /dev/null
+++ b/src/power-profiles-daemon.gresource.xml.in
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="@prefix@">
+ @contents@
+ </gresource>
+</gresources>
diff --git a/src/powerprofilesctl b/src/powerprofilesctl
new file mode 100755
index 0000000..af4dfe1
--- /dev/null
+++ b/src/powerprofilesctl
@@ -0,0 +1,227 @@
+#!/usr/bin/env python3
+
+import argparse
+import signal
+import subprocess
+import sys
+from gi.repository import Gio, GLib
+
+PP_NAME = "org.freedesktop.UPower.PowerProfiles"
+PP_PATH = "/org/freedesktop/UPower/PowerProfiles"
+PP_IFACE = "org.freedesktop.UPower.PowerProfiles"
+PROPERTIES_IFACE = "org.freedesktop.DBus.Properties"
+
+
+def get_proxy():
+ try:
+ bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
+ proxy = Gio.DBusProxy.new_sync(
+ bus, Gio.DBusProxyFlags.NONE, None, PP_NAME, PP_PATH, PROPERTIES_IFACE, None
+ )
+ except:
+ raise
+ return proxy
+
+
+def command(func):
+ def wrapper(*args, **kwargs):
+ try:
+ func(*args, **kwargs)
+ except GLib.Error as error:
+ sys.stderr.write(
+ f"Failed to communicate with power-profiles-daemon: {error}\n"
+ )
+ sys.exit(1)
+ except ValueError as error:
+ sys.stderr.write(f"Error: {error}\n")
+ sys.exit(1)
+
+ return wrapper
+
+
+ at command
+def _version(args): # pylint: disable=unused-argument
+ proxy = get_proxy()
+ ver = proxy.Get("(ss)", PP_IFACE, "Version")
+ print(ver)
+
+
+ at command
+def _get(args): # pylint: disable=unused-argument
+ proxy = get_proxy()
+ profile = proxy.Get("(ss)", PP_IFACE, "ActiveProfile")
+ print(profile)
+
+
+ at command
+def _set(args):
+ try:
+ proxy = get_proxy()
+ proxy.Set(
+ "(ssv)", PP_IFACE, "ActiveProfile", GLib.Variant.new_string(args.profile[0])
+ )
+ except:
+ raise
+
+
+def get_profiles_property(prop):
+ try:
+ proxy = get_proxy()
+ except:
+ raise
+
+ profiles = None
+ try:
+ profiles = proxy.Get("(ss)", PP_IFACE, prop)
+ except:
+ raise
+ return profiles
+
+
+ at command
+def _list(args): # pylint: disable=unused-argument
+ try:
+ profiles = get_profiles_property("Profiles")
+ reason = get_proxy().Get("(ss)", PP_IFACE, "PerformanceDegraded")
+ degraded = reason != ""
+ active = get_proxy().Get("(ss)", PP_IFACE, "ActiveProfile")
+ except:
+ raise
+
+ index = 0
+ for profile in reversed(profiles):
+ if index > 0:
+ print("")
+ marker = "*" if profile["Profile"] == active else " "
+ print(f'{marker} {profile["Profile"]}:')
+ for driver in ["CpuDriver", "PlatformDriver"]:
+ if driver not in profile:
+ continue
+ value = profile[driver]
+ print(f" {driver}:\t{value}")
+ if profile["Profile"] == "performance":
+ print(" Degraded: ", f"yes ({reason})" if degraded else "no")
+ index += 1
+
+
+ at command
+def _list_holds(args): # pylint: disable=unused-argument
+ try:
+ holds = get_profiles_property("ActiveProfileHolds")
+ except:
+ raise
+
+ 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
+
+
+ at command
+def _launch(args):
+ reason = args.reason
+ profile = args.profile
+ appid = args.appid
+ if not args.arguments:
+ raise ValueError("No command to launch")
+ if not args.appid:
+ appid = args.arguments[0]
+ if not profile:
+ profile = "performance"
+ if not reason:
+ reason = f"Running {args.appid}"
+ ret = 0
+ try:
+ bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
+ proxy = Gio.DBusProxy.new_sync(
+ bus, Gio.DBusProxyFlags.NONE, None, PP_NAME, PP_PATH, PP_IFACE, None
+ )
+ except:
+ raise
+
+ cookie = proxy.HoldProfile("(sss)", profile, reason, appid)
+
+ # Kill child when we go away
+ def receive_signal(_signum, _stack):
+ launched_app.terminate()
+
+ signal.signal(signal.SIGTERM, receive_signal)
+
+ # print (f'Got {cookie} for {profile} hold')
+ with subprocess.Popen(args.arguments) as launched_app:
+ try:
+ launched_app.wait()
+ ret = launched_app.returncode
+ except KeyboardInterrupt:
+ ret = launched_app.returncode
+ proxy.ReleaseProfile("(u)", cookie)
+
+ return ret
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ epilog="Use “powerprofilesctl COMMAND --help” to get detailed help for individual commands",
+ )
+ subparsers = parser.add_subparsers(help="Individual command help", dest="command")
+ parser_list = subparsers.add_parser("list", help="List available power profiles")
+ parser_list.set_defaults(func=_list)
+ parser_list_holds = subparsers.add_parser(
+ "list-holds", help="List current power profile holds"
+ )
+ parser_list_holds.set_defaults(func=_list_holds)
+ parser_get = subparsers.add_parser(
+ "get", help="Print the currently active power profile"
+ )
+ parser_get.set_defaults(func=_get)
+ parser_set = subparsers.add_parser(
+ "set", help="Set the currently active power profile"
+ )
+ parser_set.add_argument(
+ "profile",
+ nargs=1,
+ help="Profile to use for set command",
+ )
+ parser_set.set_defaults(func=_set)
+ parser_launch = subparsers.add_parser(
+ "launch",
+ help="Launch a command while holding a power profile",
+ description="Launch the command while holding a power profile,"
+ "either performance, or power-saver. By default, the profile hold "
+ "is for the performance profile, but it might not be available on "
+ "all systems. See the list command for a list of available profiles.",
+ )
+ parser_launch.add_argument(
+ "arguments",
+ nargs="*",
+ help="Command to launch",
+ )
+ parser_launch.add_argument(
+ "--profile", "-p", required=False, help="Profile to use for launch command"
+ )
+ parser_launch.add_argument(
+ "--reason", "-r", required=False, help="Reason to use for launch command"
+ )
+ parser_launch.add_argument(
+ "--appid", "-i", required=False, help="AppId to use for launch command"
+ )
+ parser_launch.set_defaults(func=_launch)
+ parser_version = subparsers.add_parser(
+ "version", help="Print version information and exit"
+ )
+ parser_version.set_defaults(func=_version)
+
+ args = parser.parse_args()
+ # default behavior is to run list if no command is given
+ if not args.command:
+ args.func = _list
+ args.func(args)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/powerprofilesctl.in b/src/powerprofilesctl.in
deleted file mode 100755
index b1ea675..0000000
--- a/src/powerprofilesctl.in
+++ /dev/null
@@ -1,282 +0,0 @@
-#!@PYTHON3@
-
-import signal
-import subprocess
-import sys
-from gi.repository import Gio, GLib
-
-VERSION = '@VERSION@'
-
-def usage_main():
- print('Usage:')
- print(' powerprofilesctl COMMAND [ARGS…]')
- print('')
- print('Commands:')
- print(' help Print help')
- print(' version Print version')
- print(' get Print the currently active power profile')
- print(' set Set the currently active power profile')
- print(' list List available power profiles')
- print(' list-holds List current power profile holds')
- print(' launch Launch a command while holding a power profile')
- print('')
- print('Use “powerprofilesctl help COMMAND” to get detailed help.')
-
-def usage_version():
- print('Usage:')
- print(' powerprofilesctl version')
- print('')
- print('Print version information and exit.')
-
-def usage_get():
- print('Usage:')
- print(' powerprofilesctl get')
- print('')
- print('Print the currently active power profile.')
-
-def usage_set():
- print('Usage:')
- print(' powerprofilesctl set PROFILE')
- print('')
- print('Set the currently active power profile. Must be one of the ')
- print('available profiles.')
-
-def usage_list():
- print('Usage:')
- print(' powerprofilesctl list')
- print('')
- print('List available power profiles.')
-
-def usage_list_holds():
- print('Usage:')
- print(' powerprofilesctl list-holds')
- print('')
- print('List current power profile holds.')
-
-def usage_launch():
- print('Usage:')
- print(' powerprofilesctl launch [COMMAND…]')
- print('')
- print('Launch a command while holding a power profile.')
- print('')
- print('Options:')
- print(' -p, --profile=PROFILE The power profile to hold')
- print(' -r, --reason=REASON The reason for the profile hold')
- print(' -i, --appid=APP-ID The application ID for the profile hold')
- print('')
- print('Launch the command while holding a power profile, either performance, ')
- print('or power-saver. By default, the profile hold is for the performance ')
- print('profile, but it might not be available on all systems. See the list ')
- print('command for a list of available profiles.')
-
-def usage(_command=None):
- if not _command:
- usage_main()
- elif _command == 'get':
- usage_get()
- elif _command == 'set':
- usage_set()
- elif _command == 'list':
- usage_list()
- elif _command == 'list-holds':
- usage_list_holds()
- elif _command == 'launch':
- usage_launch()
- elif _command == 'version':
- usage_version()
- else:
- usage_main()
-
-def version():
- print (VERSION)
-
-def get_proxy():
- try:
- bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
- proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None,
- 'net.hadess.PowerProfiles',
- '/net/hadess/PowerProfiles',
- 'org.freedesktop.DBus.Properties', None)
- except:
- raise
- return proxy
-
-def _get():
- proxy = get_proxy()
- profile = proxy.Get('(ss)', 'net.hadess.PowerProfiles', 'ActiveProfile')
- print(profile)
-
-def _set(profile):
- try:
- proxy = get_proxy()
- proxy.Set('(ssv)',
- 'net.hadess.PowerProfiles',
- 'ActiveProfile',
- GLib.Variant.new_string(profile))
- except:
- raise
-
-def get_profiles_property(prop):
- try:
- proxy = get_proxy()
- except:
- raise
-
- profiles = None
- try:
- profiles = proxy.Get('(ss)', 'net.hadess.PowerProfiles', prop)
- except:
- raise
- return profiles
-
-def _list():
- try:
- profiles = get_profiles_property('Profiles')
- reason = get_proxy().Get('(ss)', 'net.hadess.PowerProfiles', 'PerformanceDegraded')
- degraded = reason != ''
- active = get_proxy().Get('(ss)', 'net.hadess.PowerProfiles', 'ActiveProfile')
- except:
- raise
-
- 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
-
- 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:
- bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
- proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None,
- 'net.hadess.PowerProfiles',
- '/net/hadess/PowerProfiles',
- 'net.hadess.PowerProfiles', None)
- except:
- raise
-
- cookie = proxy.HoldProfile('(sss)', profile, reason, appid)
-
- # Kill child when we go away
- def receive_signal(_signum, _stack):
- launched_app.terminate()
- signal.signal(signal.SIGTERM, receive_signal)
-
- # print (f'Got {cookie} for {profile} hold')
- with subprocess.Popen(args) as launched_app:
- launched_app.wait()
- proxy.ReleaseProfile('(u)', cookie)
-
-def main(): # pylint: disable=too-many-branches, disable=too-many-statements
- args = None
- if len(sys.argv) == 1:
- command = 'list'
- elif len(sys.argv) >= 2:
- command = sys.argv[1]
- if command == '--help':
- command = 'help'
- if command == '--version':
- command = 'version'
- else:
- args = sys.argv[2:]
-
- if command == 'help':
- if len(args) > 0:
- usage(args[0])
- else:
- usage(None)
- elif command == 'version':
- version()
- elif command == 'get':
- try:
- _get()
- except GLib.Error as error:
- sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n')
- sys.exit(1)
- elif command == 'set':
- if len(args) != 1:
- usage_set()
- sys.exit(1)
- try:
- _set(args[0])
- except GLib.Error as error:
- sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n')
- sys.exit(1)
- elif command == 'list':
- try:
- _list()
- except GLib.Error as error:
- sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n')
- sys.exit(1)
- elif command == 'list-holds':
- try:
- _list_holds()
- except GLib.Error as error:
- sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n')
- sys.exit(1)
- elif command == 'launch':
- if len(args) == 0:
- sys.exit(0)
- profile = None
- reason = None
- appid = None
- while True:
- if args[0] == '--':
- args = args[1:]
- break
- if args[0][:9] == '--profile' or args[0] == '-p':
- if args[0][:10] == '--profile=':
- args = args[0].split('=') + args[1:]
- profile = args[1]
- args = args[2:]
- continue
- if args[0][:8] == '--reason' or args[0] == '-r':
- if args[0][:9] == '--reason=':
- args = args[0].split('=') + args[1:]
- reason = args[1]
- args = args[2:]
- continue
- if args[0][:7] == '--appid' or args[0] == '-i':
- if args[0][:8] == '--appid=':
- args = args[0].split('=') + args[1:]
- appid = args[1]
- args = args[2:]
- continue
- break
-
- if len(args) < 1:
- sys.exit(0)
- if not appid:
- appid = args[0]
- if not reason:
- reason = 'Running ' + appid
- if not profile:
- profile = 'performance'
- try:
- _launch(args, profile, appid, reason)
- except GLib.Error as error:
- sys.stderr.write(f'Failed to communicate with power-profiles-daemon: {format(error)}\n')
- sys.exit(1)
-
-if __name__ == '__main__':
- main()
diff --git a/src/ppd-action-amdgpu-panel-power.c b/src/ppd-action-amdgpu-panel-power.c
new file mode 100644
index 0000000..c2e824b
--- /dev/null
+++ b/src/ppd-action-amdgpu-panel-power.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2024 Advanced Micro Devices
+ *
+ * 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.
+ *
+ */
+
+#define G_LOG_DOMAIN "AmdgpuAction"
+
+#include "config.h"
+
+#include <gudev/gudev.h>
+
+#include "ppd-action-amdgpu-panel-power.h"
+#include "ppd-profile.h"
+#include "ppd-utils.h"
+
+#define PROC_CPUINFO_PATH "/proc/cpuinfo"
+
+#define PANEL_POWER_SYSFS_NAME "amdgpu/panel_power_savings"
+#define PANEL_STATUS_SYSFS_NAME "status"
+
+#define UPOWER_DBUS_NAME "org.freedesktop.UPower"
+#define UPOWER_DBUS_PATH "/org/freedesktop/UPower"
+#define UPOWER_DBUS_INTERFACE "org.freedesktop.UPower"
+
+/**
+ * SECTION:ppd-action-amdgpu-panel-power
+ * @Short_description: Power savings for eDP connected displays
+ * @Title: AMDGPU Panel power action
+ *
+ * The AMDGPU panel power action utilizes the sysfs attribute present on some DRM
+ * connectors for amdgpu called "panel_power_savings". This will use an AMD specific
+ * hardware feature for a power savings profile for the panel.
+ *
+ */
+
+struct _PpdActionAmdgpuPanelPower
+{
+ PpdAction parent_instance;
+ PpdProfile last_profile;
+
+ GUdevClient *client;
+ GDBusProxy *proxy;
+ guint watcher_id;
+
+ gint panel_power_saving;
+ gboolean on_battery;
+};
+
+G_DEFINE_TYPE (PpdActionAmdgpuPanelPower, ppd_action_amdgpu_panel_power, PPD_TYPE_ACTION)
+
+static GObject*
+ppd_action_amdgpu_panel_power_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+
+ object = G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->constructor (type,
+ n_construct_params,
+ construct_params);
+ g_object_set (object,
+ "action-name", "amdgpu_panel_power",
+ NULL);
+
+ return object;
+}
+
+static gboolean
+panel_connected (GUdevDevice *device)
+{
+ const char *value;
+ g_autofree gchar *stripped = NULL;
+
+ value = g_udev_device_get_sysfs_attr_uncached (device, PANEL_STATUS_SYSFS_NAME);
+ if (!value)
+ return FALSE;
+ stripped = g_strchomp (g_strdup (value));
+
+ return g_strcmp0 (stripped, "connected") == 0;
+}
+
+static gboolean
+set_panel_power (PpdActionAmdgpuPanelPower *self, gint power, GError **error)
+{
+ GList *devices, *l;
+
+ devices = g_udev_client_query_by_subsystem (self->client, "drm");
+ if (devices == NULL) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ "no drm devices found");
+ return FALSE;
+ }
+
+ for (l = devices; l != NULL; l = l->next) {
+ GUdevDevice *dev = l->data;
+ const char *value;
+ guint64 parsed;
+
+ value = g_udev_device_get_devtype (dev);
+ if (g_strcmp0 (value, "drm_connector") != 0)
+ continue;
+
+ if (!panel_connected (dev))
+ continue;
+
+ value = g_udev_device_get_sysfs_attr_uncached (dev, PANEL_POWER_SYSFS_NAME);
+ if (!value)
+ continue;
+
+ parsed = g_ascii_strtoull (value, NULL, 10);
+
+ /* overflow check */
+ if (parsed == G_MAXUINT64) {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "cannot parse %s as caused overflow",
+ value);
+ return FALSE;
+ }
+
+ if (parsed == power)
+ continue;
+
+ if (!ppd_utils_write_sysfs_int (dev, PANEL_POWER_SYSFS_NAME, power, error))
+ return FALSE;
+
+ break;
+ }
+
+ g_list_free_full (devices, g_object_unref);
+
+ return TRUE;
+}
+
+static gboolean
+ppd_action_amdgpu_panel_update_target (PpdActionAmdgpuPanelPower *self,
+ GError **error)
+{
+ gint target = 0;
+
+ /* only activate if we know that we're on battery */
+ if (self->on_battery) {
+ switch (self->last_profile) {
+ case PPD_PROFILE_POWER_SAVER:
+ target = 3;
+ break;
+ case PPD_PROFILE_BALANCED:
+ target = 1;
+ break;
+ case PPD_PROFILE_PERFORMANCE:
+ target = 0;
+ break;
+ }
+ }
+
+ if (!set_panel_power (self, target, error))
+ return FALSE;
+ self->panel_power_saving = target;
+
+ return TRUE;
+}
+
+static gboolean
+ppd_action_amdgpu_panel_power_activate_profile (PpdAction *action,
+ PpdProfile profile,
+ GError **error)
+{
+ PpdActionAmdgpuPanelPower *self = PPD_ACTION_AMDGPU_PANEL_POWER (action);
+ self->last_profile = profile;
+
+ if (self->proxy == NULL) {
+ g_debug ("upower not available; battery data might be stale");
+ return TRUE;
+ }
+
+ return ppd_action_amdgpu_panel_update_target (self, error);
+}
+
+
+static void
+upower_properties_changed (GDBusProxy *proxy,
+ GVariant *changed_properties,
+ GStrv invalidated_properties,
+ PpdActionAmdgpuPanelPower *self)
+{
+ g_autoptr (GVariant) battery_val = NULL;
+ g_autoptr (GError) error = NULL;
+ gboolean new_on_battery;
+
+ if (proxy != NULL)
+ battery_val = g_dbus_proxy_get_cached_property (proxy, "OnBattery");
+ new_on_battery = battery_val ? g_variant_get_boolean (battery_val) : FALSE;
+
+ if (self->on_battery == new_on_battery)
+ return;
+
+ g_debug ("OnBattery: %d -> %d", self->on_battery, new_on_battery);
+ self->on_battery = new_on_battery;
+ if (!ppd_action_amdgpu_panel_update_target (self, &error))
+ g_warning ("failed to update target: %s", error->message);
+}
+
+static void
+udev_uevent_cb (GUdevClient *client,
+ gchar *action,
+ GUdevDevice *device,
+ gpointer user_data)
+{
+ PpdActionAmdgpuPanelPower *self = user_data;
+
+ if (!g_str_equal (action, "add"))
+ return;
+
+ if (!g_udev_device_has_sysfs_attr (device, PANEL_POWER_SYSFS_NAME))
+ return;
+
+ if (!panel_connected (device))
+ return;
+
+ g_debug ("Updating panel power saving for '%s' to '%d'",
+ g_udev_device_get_sysfs_path (device),
+ self->panel_power_saving);
+ ppd_utils_write_sysfs_int (device, PANEL_POWER_SYSFS_NAME,
+ self->panel_power_saving, NULL);
+}
+
+static PpdProbeResult
+ppd_action_amdgpu_panel_power_probe (PpdAction *action)
+{
+ g_autofree gchar *cpuinfo_path = NULL;
+ g_autofree gchar *cpuinfo = NULL;
+ g_auto(GStrv) lines = NULL;
+
+ cpuinfo_path = ppd_utils_get_sysfs_path (PROC_CPUINFO_PATH);
+ if (!g_file_get_contents (cpuinfo_path, &cpuinfo, NULL, NULL))
+ return PPD_PROBE_RESULT_FAIL;
+
+ lines = g_strsplit (cpuinfo, "\n", -1);
+
+ for (gchar **line = lines; *line != NULL; line++) {
+ if (g_str_has_prefix (*line, "vendor_id") &&
+ strchr (*line, ':')) {
+ g_auto(GStrv) sections = g_strsplit (*line, ":", 2);
+
+ if (g_strv_length (sections) < 2)
+ continue;
+ if (g_strcmp0 (g_strstrip (sections[1]), "AuthenticAMD") == 0)
+ return PPD_PROBE_RESULT_SUCCESS;
+ }
+ }
+
+
+ return PPD_PROBE_RESULT_FAIL;
+}
+
+static void
+upower_name_vanished (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ PpdActionAmdgpuPanelPower *self = user_data;
+
+ g_debug ("%s vanished", UPOWER_DBUS_NAME);
+
+ /* reset */
+ g_clear_pointer (&self->proxy, g_object_unref);
+ upower_properties_changed (NULL, NULL, NULL, self);
+}
+
+static void
+upower_name_appeared (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ PpdActionAmdgpuPanelPower *self = user_data;
+ g_autoptr (GError) error = NULL;
+
+ g_debug ("%s appeared", UPOWER_DBUS_NAME);
+ self->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ UPOWER_DBUS_NAME,
+ UPOWER_DBUS_PATH,
+ UPOWER_DBUS_INTERFACE,
+ NULL,
+ &error);
+ if (self->proxy == NULL) {
+ g_debug ("failed to connect to upower: %s", error->message);
+ return;
+ }
+
+ g_signal_connect (self->proxy,
+ "g-properties-changed",
+ G_CALLBACK(upower_properties_changed),
+ self);
+ upower_properties_changed (self->proxy, NULL, NULL, self);
+}
+
+static void
+ppd_action_amdgpu_panel_power_finalize (GObject *object)
+{
+ PpdActionAmdgpuPanelPower *action;
+
+ action = PPD_ACTION_AMDGPU_PANEL_POWER (object);
+ g_clear_handle_id (&action->watcher_id, g_bus_unwatch_name);
+ g_clear_object (&action->client);
+ g_clear_pointer (&action->proxy, g_object_unref);
+ G_OBJECT_CLASS (ppd_action_amdgpu_panel_power_parent_class)->finalize (object);
+}
+
+static void
+ppd_action_amdgpu_panel_power_class_init (PpdActionAmdgpuPanelPowerClass *klass)
+{
+ GObjectClass *object_class;
+ PpdActionClass *driver_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->constructor = ppd_action_amdgpu_panel_power_constructor;
+ object_class->finalize = ppd_action_amdgpu_panel_power_finalize;
+
+ driver_class = PPD_ACTION_CLASS(klass);
+ driver_class->probe = ppd_action_amdgpu_panel_power_probe;
+ driver_class->activate_profile = ppd_action_amdgpu_panel_power_activate_profile;
+}
+
+static void
+ppd_action_amdgpu_panel_power_init (PpdActionAmdgpuPanelPower *self)
+{
+ const gchar * const subsystem[] = { "drm", NULL };
+
+ self->client = g_udev_client_new (subsystem);
+ g_signal_connect (G_OBJECT (self->client), "uevent",
+ G_CALLBACK (udev_uevent_cb), self);
+
+ self->watcher_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
+ UPOWER_DBUS_NAME,
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ upower_name_appeared,
+ upower_name_vanished,
+ self,
+ NULL);
+}
diff --git a/src/ppd-action-amdgpu-panel-power.h b/src/ppd-action-amdgpu-panel-power.h
new file mode 100644
index 0000000..9c0e797
--- /dev/null
+++ b/src/ppd-action-amdgpu-panel-power.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2024 Advanced Micro Devices
+ *
+ * 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-action.h"
+
+#define PPD_TYPE_ACTION_AMDGPU_PANEL_POWER (ppd_action_amdgpu_panel_power_get_type())
+G_DECLARE_FINAL_TYPE(PpdActionAmdgpuPanelPower, ppd_action_amdgpu_panel_power, PPD, ACTION_AMDGPU_PANEL_POWER, PpdAction)
diff --git a/src/ppd-action-trickle-charge.c b/src/ppd-action-trickle-charge.c
index 629f21d..8347727 100644
--- a/src/ppd-action-trickle-charge.c
+++ b/src/ppd-action-trickle-charge.c
@@ -7,6 +7,8 @@
*
*/
+#define G_LOG_DOMAIN "TrickleChargeAction"
+
#include <gudev/gudev.h>
#include "ppd-action-trickle-charge.h"
@@ -130,11 +132,11 @@ ppd_action_trickle_charge_class_init (PpdActionTrickleChargeClass *klass)
GObjectClass *object_class;
PpdActionClass *driver_class;
- object_class = G_OBJECT_CLASS(klass);
+ object_class = G_OBJECT_CLASS (klass);
object_class->constructor = ppd_action_trickle_charge_constructor;
object_class->finalize = ppd_action_trickle_charge_finalize;
- driver_class = PPD_ACTION_CLASS(klass);
+ driver_class = PPD_ACTION_CLASS (klass);
driver_class->activate_profile = ppd_action_trickle_charge_activate_profile;
}
diff --git a/src/ppd-action-trickle-charge.h b/src/ppd-action-trickle-charge.h
index 78ffd9b..e6b8850 100644
--- a/src/ppd-action-trickle-charge.h
+++ b/src/ppd-action-trickle-charge.h
@@ -11,5 +11,5 @@
#include "ppd-action.h"
-#define PPD_TYPE_ACTION_TRICKLE_CHARGE (ppd_action_trickle_charge_get_type())
-G_DECLARE_FINAL_TYPE(PpdActionTrickleCharge, ppd_action_trickle_charge, PPD, ACTION_TRICKLE_CHARGE, PpdAction)
+#define PPD_TYPE_ACTION_TRICKLE_CHARGE (ppd_action_trickle_charge_get_type ())
+G_DECLARE_FINAL_TYPE (PpdActionTrickleCharge, ppd_action_trickle_charge, PPD, ACTION_TRICKLE_CHARGE, PpdAction)
diff --git a/src/ppd-action.c b/src/ppd-action.c
index 92ef128..e550bcc 100644
--- a/src/ppd-action.c
+++ b/src/ppd-action.c
@@ -7,6 +7,8 @@
*
*/
+#define G_LOG_DOMAIN "Action"
+
#include "ppd-action.h"
#include "ppd-enums.h"
@@ -62,7 +64,7 @@ ppd_action_set_property (GObject *object,
priv->action_name = g_value_dup_string (value);
break;
default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
@@ -80,7 +82,7 @@ ppd_action_get_property (GObject *object,
g_value_set_string (value, priv->action_name);
break;
default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
@@ -100,7 +102,7 @@ ppd_action_class_init (PpdActionClass *klass)
{
GObjectClass *object_class;
- object_class = G_OBJECT_CLASS(klass);
+ object_class = G_OBJECT_CLASS (klass);
object_class->finalize = ppd_action_finalize;
object_class->get_property = ppd_action_get_property;
object_class->set_property = ppd_action_set_property;
@@ -111,7 +113,7 @@ ppd_action_class_init (PpdActionClass *klass)
* A unique action name, only used for debugging.
*/
g_object_class_install_property (object_class, PROP_ACTION_NAME,
- g_param_spec_string("action-name",
+ g_param_spec_string ("action-name",
"Action name",
"Action name",
NULL,
@@ -123,13 +125,13 @@ ppd_action_init (PpdAction *self)
{
}
-gboolean
+PpdProbeResult
ppd_action_probe (PpdAction *action)
{
g_return_val_if_fail (PPD_IS_ACTION (action), FALSE);
if (!PPD_ACTION_GET_CLASS (action)->probe)
- return TRUE;
+ return PPD_PROBE_RESULT_SUCCESS;
return PPD_ACTION_GET_CLASS (action)->probe (action);
}
diff --git a/src/ppd-action.h b/src/ppd-action.h
index dd5870a..9b74ce3 100644
--- a/src/ppd-action.h
+++ b/src/ppd-action.h
@@ -12,8 +12,8 @@
#include <glib-object.h>
#include "ppd-profile.h"
-#define PPD_TYPE_ACTION (ppd_action_get_type())
-G_DECLARE_DERIVABLE_TYPE(PpdAction, ppd_action, PPD, ACTION, GObject)
+#define PPD_TYPE_ACTION (ppd_action_get_type ())
+G_DECLARE_DERIVABLE_TYPE (PpdAction, ppd_action, PPD, ACTION, GObject)
/**
* PpdActionClass:
@@ -28,14 +28,14 @@ struct _PpdActionClass
{
GObjectClass parent_class;
- gboolean (* probe) (PpdAction *action);
+ PpdProbeResult (* probe) (PpdAction *action);
gboolean (* activate_profile) (PpdAction *action,
PpdProfile profile,
GError **error);
};
#ifndef __GTK_DOC_IGNORE__
-gboolean ppd_action_probe (PpdAction *action);
+PpdProbeResult ppd_action_probe (PpdAction *action);
gboolean ppd_action_activate_profile (PpdAction *action, PpdProfile profile, GError **error);
const char *ppd_action_get_action_name (PpdAction *action);
#endif
diff --git a/src/ppd-driver-amd-pstate.c b/src/ppd-driver-amd-pstate.c
index 825caca..b1f6b11 100644
--- a/src/ppd-driver-amd-pstate.c
+++ b/src/ppd-driver-amd-pstate.c
@@ -8,24 +8,39 @@
*
*/
+#define G_LOG_DOMAIN "CpuDriver"
+
#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"
+#define ACPI_PM_PROFILE "/sys/firmware/acpi/pm_profile"
+
+enum acpi_preferred_pm_profiles {
+ PM_UNSPECIFIED = 0,
+ PM_DESKTOP = 1,
+ PM_MOBILE = 2,
+ PM_WORKSTATION = 3,
+ PM_ENTERPRISE_SERVER = 4,
+ PM_SOHO_SERVER = 5,
+ PM_APPLIANCE_PC = 6,
+ PM_PERFORMANCE_SERVER = 7,
+ PM_TABLET = 8,
+ NR_PM_PROFILES = 9
+};
struct _PpdDriverAmdPstate
{
- PpdDriver parent_instance;
+ PpdDriverCpu parent_instance;
PpdProfile activated_profile;
GList *epp_devices; /* GList of paths */
};
-G_DEFINE_TYPE (PpdDriverAmdPstate, ppd_driver_amd_pstate, PPD_TYPE_DRIVER)
+G_DEFINE_TYPE (PpdDriverAmdPstate, ppd_driver_amd_pstate, PPD_TYPE_DRIVER_CPU)
static gboolean ppd_driver_amd_pstate_activate_profile (PpdDriver *driver,
PpdProfile profile,
@@ -57,6 +72,9 @@ probe_epp (PpdDriverAmdPstate *pstate)
g_autofree char *policy_dir = NULL;
g_autofree char *pstate_status_path = NULL;
g_autofree char *status = NULL;
+ g_autofree char *pm_profile_path = NULL;
+ g_autofree char *pm_profile_str = NULL;
+ guint64 pm_profile;
const char *dirname;
PpdProbeResult ret = PPD_PROBE_RESULT_FAIL;
@@ -77,29 +95,38 @@ probe_epp (PpdDriverAmdPstate *pstate)
return ret;
}
+ /* only run on things that we know aren't servers */
+ pm_profile_path = ppd_utils_get_sysfs_path (ACPI_PM_PROFILE);
+ if (!g_file_get_contents (pm_profile_path, &pm_profile_str, NULL, NULL))
+ return ret;
+ pm_profile = g_ascii_strtoull (pm_profile_str, NULL, 10);
+ switch (pm_profile) {
+ case PM_UNSPECIFIED:
+ case PM_ENTERPRISE_SERVER:
+ case PM_SOHO_SERVER:
+ case PM_PERFORMANCE_SERVER:
+ g_debug ("AMD-P-State not supported on PM profile %" G_GUINT64_FORMAT, pm_profile);
+ return ret;
+ default:
+ break;
+ }
+
while ((dirname = g_dir_read_name (dir)) != NULL) {
+ g_autofree char *base = NULL;
g_autofree char *path = NULL;
- g_autofree char *gov_path = NULL;
g_autoptr(GError) error = NULL;
- path = g_build_filename (policy_dir,
+ base = g_build_filename (policy_dir,
dirname,
+ NULL);
+
+ path = g_build_filename (base,
"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));
+ pstate->epp_devices = g_list_prepend (pstate->epp_devices, g_steal_pointer (&base));
ret = PPD_PROBE_RESULT_SUCCESS;
}
@@ -123,6 +150,21 @@ out:
return ret;
}
+static const char *
+profile_to_gov_pref (PpdProfile profile)
+{
+ switch (profile) {
+ case PPD_PROFILE_POWER_SAVER:
+ return "powersave";
+ case PPD_PROFILE_BALANCED:
+ return "powersave";
+ case PPD_PROFILE_PERFORMANCE:
+ return "performance";
+ }
+
+ g_assert_not_reached ();
+}
+
static const char *
profile_to_epp_pref (PpdProfile profile)
{
@@ -142,16 +184,30 @@ profile_to_epp_pref (PpdProfile profile)
static gboolean
apply_pref_to_devices (GList *devices,
- const char *pref,
+ PpdProfile profile,
GError **error)
{
gboolean ret = TRUE;
GList *l;
for (l = devices; l != NULL; l = l->next) {
- const char *path = l->data;
+ const char *base = l->data;
+ g_autofree char *epp = NULL;
+ g_autofree char *gov = NULL;
- ret = ppd_utils_write (path, pref, error);
+ gov = g_build_filename (base,
+ "scaling_governor",
+ NULL);
+
+ ret = ppd_utils_write (gov, profile_to_gov_pref (profile), error);
+ if (!ret)
+ break;
+
+ epp = g_build_filename (base,
+ "energy_performance_preference",
+ NULL);
+
+ ret = ppd_utils_write (epp, profile_to_epp_pref (profile), error);
if (!ret)
break;
}
@@ -167,15 +223,20 @@ ppd_driver_amd_pstate_activate_profile (PpdDriver *driver,
{
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)
+ ret = apply_pref_to_devices (pstate->epp_devices, profile, error);
+ if (!ret && pstate->activated_profile != PPD_PROFILE_UNSET) {
+ g_autoptr(GError) error_local = NULL;
+ /* reset back to previous */
+ if (!apply_pref_to_devices (pstate->epp_devices,
+ pstate->activated_profile,
+ &error_local))
+ g_warning ("failed to restore previous profile: %s", error_local->message);
return ret;
+ }
}
if (ret)
@@ -200,11 +261,11 @@ ppd_driver_amd_pstate_class_init (PpdDriverAmdPstateClass *klass)
GObjectClass *object_class;
PpdDriverClass *driver_class;
- object_class = G_OBJECT_CLASS(klass);
+ 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 = PPD_DRIVER_CLASS (klass);
driver_class->probe = ppd_driver_amd_pstate_probe;
driver_class->activate_profile = ppd_driver_amd_pstate_activate_profile;
}
diff --git a/src/ppd-driver-amd-pstate.h b/src/ppd-driver-amd-pstate.h
index c4f1690..91f0577 100644
--- a/src/ppd-driver-amd-pstate.h
+++ b/src/ppd-driver-amd-pstate.h
@@ -10,7 +10,7 @@
#pragma once
-#include "ppd-driver.h"
+#include "ppd-driver-cpu.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)
+#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, PpdDriverCpu)
diff --git a/src/ppd-driver-cpu.c b/src/ppd-driver-cpu.c
new file mode 100644
index 0000000..d838193
--- /dev/null
+++ b/src/ppd-driver-cpu.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2023 Mario Limonciello <superm1 at 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.
+ *
+ */
+
+#define G_LOG_DOMAIN "CpuDriver"
+
+#include "ppd-driver-cpu.h"
+
+/**
+ * SECTION:ppd-driver-cpu
+ * @Short_description: CPU Drivers
+ * @Title: CPU Profile Drivers
+ *
+ * Profile drivers are the implementation of the different profiles for
+ * the whole system. A driver will need to implement support for `power-saver`
+ * and `balanced` at a minimum.
+ *
+ * CPU drivers are typically used to change specifically the CPU efficiency
+ * to match the desired platform state.
+ */
+
+G_DEFINE_TYPE (PpdDriverCpu, ppd_driver_cpu, PPD_TYPE_DRIVER)
+
+static void
+ppd_driver_cpu_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (ppd_driver_cpu_parent_class)->finalize (object);
+}
+
+static void
+ppd_driver_cpu_class_init (PpdDriverCpuClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = ppd_driver_cpu_finalize;
+}
+
+static void
+ppd_driver_cpu_init (PpdDriverCpu *self)
+{
+}
diff --git a/src/ppd-driver-cpu.h b/src/ppd-driver-cpu.h
new file mode 100644
index 0000000..3ce3b2b
--- /dev/null
+++ b/src/ppd-driver-cpu.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2023 Mario Limonciello <superm1 at 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_CPU (ppd_driver_cpu_get_type ())
+G_DECLARE_DERIVABLE_TYPE (PpdDriverCpu, ppd_driver_cpu, PPD, DRIVER_CPU, PpdDriver)
+
+/**
+ * PpdDriverCpuClass:
+ * @parent_class: The parent class.
+ *
+ * New CPU drivers should derive from #PpdDriverCpu and implement
+ * both @probe () and @activate_profile.
+ */
+struct _PpdDriverCpuClass
+{
+ PpdDriverClass parent_class;
+};
diff --git a/src/ppd-driver-fake.c b/src/ppd-driver-fake.c
index ecd6154..6f53c26 100644
--- a/src/ppd-driver-fake.c
+++ b/src/ppd-driver-fake.c
@@ -18,7 +18,7 @@ void restart_profile_drivers (void);
struct _PpdDriverFake
{
- PpdDriver parent_instance;
+ PpdDriverPlatform parent_instance;
gboolean tio_set;
struct termios old_tio;
@@ -27,7 +27,7 @@ struct _PpdDriverFake
gboolean degraded;
};
-G_DEFINE_TYPE (PpdDriverFake, ppd_driver_fake, PPD_TYPE_DRIVER)
+G_DEFINE_TYPE (PpdDriverFake, ppd_driver_fake, PPD_TYPE_DRIVER_PLATFORM)
static GObject*
ppd_driver_fake_constructor (GType type,
@@ -107,10 +107,10 @@ setup_keyboard (PpdDriverFake *fake)
{
struct termios new_tio;
- tcgetattr(STDIN_FILENO, &fake->old_tio);
+ tcgetattr (STDIN_FILENO, &fake->old_tio);
new_tio = fake->old_tio;
new_tio.c_lflag &=(~ICANON & ~ECHO);
- tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
+ tcsetattr (STDIN_FILENO, TCSANOW, &new_tio);
fake->channel = g_io_channel_unix_new (STDIN_FILENO);
if (!fake->channel) {
@@ -150,6 +150,10 @@ ppd_driver_fake_probe (PpdDriver *driver)
if (!envvar_set ("POWER_PROFILE_DAEMON_FAKE_DRIVER"))
return PPD_PROBE_RESULT_FAIL;
+ /* don't activate stdin unless interactive */
+ if (isatty (fileno (stdout)) == 0)
+ return PPD_PROBE_RESULT_SUCCESS;
+
fake = PPD_DRIVER_FAKE (driver);
if (!setup_keyboard (fake))
return PPD_PROBE_RESULT_FAIL;
@@ -180,7 +184,7 @@ ppd_driver_fake_finalize (GObject *object)
g_clear_pointer (&fake->channel, g_io_channel_unref);
g_clear_handle_id (&fake->watch_id, g_source_remove);
if (fake->tio_set)
- tcsetattr(STDIN_FILENO, TCSANOW, &fake->old_tio);
+ tcsetattr (STDIN_FILENO, TCSANOW, &fake->old_tio);
G_OBJECT_CLASS (ppd_driver_fake_parent_class)->finalize (object);
}
@@ -190,11 +194,11 @@ ppd_driver_fake_class_init (PpdDriverFakeClass *klass)
GObjectClass *object_class;
PpdDriverClass *driver_class;
- object_class = G_OBJECT_CLASS(klass);
+ object_class = G_OBJECT_CLASS (klass);
object_class->constructor = ppd_driver_fake_constructor;
object_class->finalize = ppd_driver_fake_finalize;
- driver_class = PPD_DRIVER_CLASS(klass);
+ driver_class = PPD_DRIVER_CLASS (klass);
driver_class->probe = ppd_driver_fake_probe;
driver_class->activate_profile = ppd_driver_fake_activate_profile;
}
diff --git a/src/ppd-driver-fake.h b/src/ppd-driver-fake.h
index 6ab2f82..4e3e2e7 100644
--- a/src/ppd-driver-fake.h
+++ b/src/ppd-driver-fake.h
@@ -9,7 +9,7 @@
#pragma once
-#include "ppd-driver.h"
+#include "ppd-driver-platform.h"
-#define PPD_TYPE_DRIVER_FAKE (ppd_driver_fake_get_type())
-G_DECLARE_FINAL_TYPE(PpdDriverFake, ppd_driver_fake, PPD, DRIVER_FAKE, PpdDriver)
+#define PPD_TYPE_DRIVER_FAKE (ppd_driver_fake_get_type ())
+G_DECLARE_FINAL_TYPE (PpdDriverFake, ppd_driver_fake, PPD, DRIVER_FAKE, PpdDriverPlatform)
diff --git a/src/ppd-driver-intel-pstate.c b/src/ppd-driver-intel-pstate.c
index 413a0d2..d859b98 100644
--- a/src/ppd-driver-intel-pstate.c
+++ b/src/ppd-driver-intel-pstate.c
@@ -7,6 +7,8 @@
*
*/
+#define G_LOG_DOMAIN "CpuDriver"
+
#include <upower.h>
#include "ppd-utils.h"
@@ -25,7 +27,7 @@
struct _PpdDriverIntelPstate
{
- PpdDriver parent_instance;
+ PpdDriverCpu parent_instance;
PpdProfile activated_profile;
GList *epp_devices; /* GList of paths */
@@ -35,7 +37,7 @@ struct _PpdDriverIntelPstate
char *no_turbo_path;
};
-G_DEFINE_TYPE (PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD_TYPE_DRIVER)
+G_DEFINE_TYPE (PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD_TYPE_DRIVER_CPU)
static gboolean ppd_driver_intel_pstate_activate_profile (PpdDriver *driver,
PpdProfile profile,
@@ -108,7 +110,7 @@ monitor_no_turbo_prop (const char *path)
}
static gboolean
-has_turbo (void)
+sys_has_turbo (void)
{
g_autofree char *turbo_pct_path = NULL;
g_autofree char *contents = NULL;
@@ -265,17 +267,18 @@ ppd_driver_intel_pstate_probe (PpdDriver *driver)
{
PpdDriverIntelPstate *pstate = PPD_DRIVER_INTEL_PSTATE (driver);
PpdProbeResult ret = PPD_PROBE_RESULT_FAIL;
+ PpdProbeResult epp_ret, epb_ret;
+ gboolean has_turbo;
- ret = probe_epp (pstate);
- if (ret == PPD_PROBE_RESULT_SUCCESS)
- probe_epb (pstate);
- else
- ret = probe_epb (pstate);
+ epp_ret = probe_epp (pstate);
+ epb_ret = probe_epb (pstate);
+ ret = (epp_ret == PPD_PROBE_RESULT_SUCCESS) ? epp_ret : epb_ret;
if (ret != PPD_PROBE_RESULT_SUCCESS)
goto out;
- if (has_turbo ()) {
+ has_turbo = sys_has_turbo ();
+ if (has_turbo) {
/* Monitor the first "no_turbo" */
pstate->no_turbo_path = ppd_utils_get_sysfs_path (NO_TURBO_PATH);
pstate->no_turbo_mon = monitor_no_turbo_prop (pstate->no_turbo_path);
@@ -287,8 +290,15 @@ ppd_driver_intel_pstate_probe (PpdDriver *driver)
}
out:
- g_debug ("%s p-state settings",
+ g_debug ("%s Intel p-state settings",
ret == PPD_PROBE_RESULT_SUCCESS ? "Found" : "Didn't find");
+ if (ret == PPD_PROBE_RESULT_SUCCESS) {
+ g_debug ("\tEnergy Performance Preference: %s",
+ epp_ret == PPD_PROBE_RESULT_SUCCESS ? "yes" : "no");
+ g_debug ("\tEnergy Performance Bias: %s",
+ epp_ret == PPD_PROBE_RESULT_SUCCESS ? "yes" : "no");
+ g_debug ("\tHas Turbo: %s", has_turbo ? "yes" : "no");
+ }
return ret;
}
@@ -395,11 +405,11 @@ ppd_driver_intel_pstate_class_init (PpdDriverIntelPstateClass *klass)
GObjectClass *object_class;
PpdDriverClass *driver_class;
- object_class = G_OBJECT_CLASS(klass);
+ object_class = G_OBJECT_CLASS (klass);
object_class->constructor = ppd_driver_intel_pstate_constructor;
object_class->finalize = ppd_driver_intel_pstate_finalize;
- driver_class = PPD_DRIVER_CLASS(klass);
+ driver_class = PPD_DRIVER_CLASS (klass);
driver_class->probe = ppd_driver_intel_pstate_probe;
driver_class->activate_profile = ppd_driver_intel_pstate_activate_profile;
}
diff --git a/src/ppd-driver-intel-pstate.h b/src/ppd-driver-intel-pstate.h
index 42f8cf2..e1c7d20 100644
--- a/src/ppd-driver-intel-pstate.h
+++ b/src/ppd-driver-intel-pstate.h
@@ -9,7 +9,7 @@
#pragma once
-#include "ppd-driver.h"
+#include "ppd-driver-cpu.h"
-#define PPD_TYPE_DRIVER_INTEL_PSTATE (ppd_driver_intel_pstate_get_type())
-G_DECLARE_FINAL_TYPE(PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD, DRIVER_INTEL_PSTATE, PpdDriver)
+#define PPD_TYPE_DRIVER_INTEL_PSTATE (ppd_driver_intel_pstate_get_type ())
+G_DECLARE_FINAL_TYPE (PpdDriverIntelPstate, ppd_driver_intel_pstate, PPD, DRIVER_INTEL_PSTATE, PpdDriverCpu)
diff --git a/src/ppd-driver-placeholder.c b/src/ppd-driver-placeholder.c
index 11d9e93..e0f8021 100644
--- a/src/ppd-driver-placeholder.c
+++ b/src/ppd-driver-placeholder.c
@@ -7,14 +7,16 @@
*
*/
+#define G_LOG_DOMAIN "PlatformDriver"
+
#include "ppd-driver-placeholder.h"
struct _PpdDriverPlaceholder
{
- PpdDriver parent_instance;
+ PpdDriverPlatform parent_instance;
};
-G_DEFINE_TYPE (PpdDriverPlaceholder, ppd_driver_placeholder, PPD_TYPE_DRIVER)
+G_DEFINE_TYPE (PpdDriverPlaceholder, ppd_driver_placeholder, PPD_TYPE_DRIVER_PLATFORM)
static GObject*
ppd_driver_placeholder_constructor (GType type,
@@ -39,7 +41,7 @@ ppd_driver_placeholder_class_init (PpdDriverPlaceholderClass *klass)
{
GObjectClass *object_class;
- object_class = G_OBJECT_CLASS(klass);
+ object_class = G_OBJECT_CLASS (klass);
object_class->constructor = ppd_driver_placeholder_constructor;
}
diff --git a/src/ppd-driver-placeholder.h b/src/ppd-driver-placeholder.h
index a822790..5fb49fb 100644
--- a/src/ppd-driver-placeholder.h
+++ b/src/ppd-driver-placeholder.h
@@ -9,7 +9,7 @@
#pragma once
-#include "ppd-driver.h"
+#include "ppd-driver-platform.h"
-#define PPD_TYPE_DRIVER_PLACEHOLDER (ppd_driver_placeholder_get_type())
-G_DECLARE_FINAL_TYPE(PpdDriverPlaceholder, ppd_driver_placeholder, PPD, DRIVER_PLACEHOLDER, PpdDriver)
+#define PPD_TYPE_DRIVER_PLACEHOLDER (ppd_driver_placeholder_get_type ())
+G_DECLARE_FINAL_TYPE (PpdDriverPlaceholder, ppd_driver_placeholder, PPD, DRIVER_PLACEHOLDER, PpdDriverPlatform)
diff --git a/src/ppd-driver-platform-profile.c b/src/ppd-driver-platform-profile.c
index aebc655..6b53c4d 100644
--- a/src/ppd-driver-platform-profile.c
+++ b/src/ppd-driver-platform-profile.c
@@ -7,6 +7,8 @@
*
*/
+#define G_LOG_DOMAIN "PlatformDriver"
+
#include <gudev/gudev.h>
#include <gio/gio.h>
@@ -32,7 +34,7 @@ struct _PpdDriverPlatformProfile
guint acpi_platform_profile_changed_id;
};
-G_DEFINE_TYPE (PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD_TYPE_DRIVER)
+G_DEFINE_TYPE (PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD_TYPE_DRIVER_PLATFORM)
static GObject*
ppd_driver_platform_profile_constructor (GType type,
@@ -345,11 +347,11 @@ ppd_driver_platform_profile_class_init (PpdDriverPlatformProfileClass *klass)
GObjectClass *object_class;
PpdDriverClass *driver_class;
- object_class = G_OBJECT_CLASS(klass);
+ object_class = G_OBJECT_CLASS (klass);
object_class->constructor = ppd_driver_platform_profile_constructor;
object_class->finalize = ppd_driver_platform_profile_finalize;
- driver_class = PPD_DRIVER_CLASS(klass);
+ driver_class = PPD_DRIVER_CLASS (klass);
driver_class->probe = ppd_driver_platform_profile_probe;
driver_class->activate_profile = ppd_driver_platform_profile_activate_profile;
}
diff --git a/src/ppd-driver-platform-profile.h b/src/ppd-driver-platform-profile.h
index 916fd7e..5ac7eda 100644
--- a/src/ppd-driver-platform-profile.h
+++ b/src/ppd-driver-platform-profile.h
@@ -9,7 +9,7 @@
#pragma once
-#include "ppd-driver.h"
+#include "ppd-driver-platform.h"
-#define PPD_TYPE_DRIVER_PLATFORM_PROFILE (ppd_driver_platform_profile_get_type())
-G_DECLARE_FINAL_TYPE(PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD, DRIVER_PLATFORM_PROFILE, PpdDriver)
+#define PPD_TYPE_DRIVER_PLATFORM_PROFILE (ppd_driver_platform_profile_get_type ())
+G_DECLARE_FINAL_TYPE (PpdDriverPlatformProfile, ppd_driver_platform_profile, PPD, DRIVER_PLATFORM_PROFILE, PpdDriverPlatform)
diff --git a/src/ppd-driver-platform.c b/src/ppd-driver-platform.c
new file mode 100644
index 0000000..33dc955
--- /dev/null
+++ b/src/ppd-driver-platform.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2023 Mario Limonciello <superm1 at 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.
+ *
+ */
+
+#define G_LOG_DOMAIN "PlatformDriver"
+
+#include "ppd-driver-platform.h"
+
+G_DEFINE_TYPE (PpdDriverPlatform, ppd_driver_platform, PPD_TYPE_DRIVER)
+
+/**
+ * SECTION:ppd-driver-platform
+ * @Short_description: Profile Drivers
+ * @Title: Platform Profile Drivers
+ *
+ * Platform drivers are the implementation of the different profiles for
+ * the whole system. A driver will need to implement support for `power-saver`
+ * and `balanced` at a minimum.
+ *
+ * If no system-specific platform driver is available, a placeholder driver
+ * will be put in place, and the `performance` profile will be unavailable.
+ *
+ * There should not be a need to implement system-specific drivers, as the
+ * [`platform_profile`] (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/ABI/testing/sysfs-platform_profile)
+ * kernel API offers a way to implement system-specific profiles which
+ * `power-profiles-daemon` can consume.
+ *
+ * When a driver implements the `performance` profile, it might set the
+ * #PpdDriver:performance-degraded property if the profile isn't running to
+ * its fullest performance for any reason, such as thermal limits being
+ * reached, or because a part of the user's body is too close for safety,
+ * for example.
+ */
+
+static void
+ppd_driver_platform_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (ppd_driver_platform_parent_class)->finalize (object);
+}
+
+static void
+ppd_driver_platform_class_init (PpdDriverPlatformClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = ppd_driver_platform_finalize;
+}
+
+static void
+ppd_driver_platform_init (PpdDriverPlatform *self)
+{
+}
diff --git a/src/ppd-driver-platform.h b/src/ppd-driver-platform.h
new file mode 100644
index 0000000..9dbd247
--- /dev/null
+++ b/src/ppd-driver-platform.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2023 Mario Limonciello <superm1 at 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_PLATFORM (ppd_driver_platform_get_type ())
+G_DECLARE_DERIVABLE_TYPE (PpdDriverPlatform, ppd_driver_platform, PPD, DRIVER_PLATFORM, PpdDriver)
+
+/**
+ * PpdDriverPlatformClass:
+ * @parent_class: The parent class.
+ *
+ * New Platform drivers should derive from #PpdDriverPlatform and implement
+ * at least one of @probe () and @activate_profile.
+ */
+struct _PpdDriverPlatformClass
+{
+ PpdDriverClass parent_class;
+};
diff --git a/src/ppd-driver.c b/src/ppd-driver.c
index 9a1f369..435e9e9 100644
--- a/src/ppd-driver.c
+++ b/src/ppd-driver.c
@@ -19,13 +19,7 @@
* the whole system. A driver will need to implement support `power-saver`
* and `balanced` at a minimum.
*
- * If no system-specific driver is available, a placeholder driver
- * will be put in place, and the `performance` profile will be unavailable.
- *
- * There should not be a need to implement system-specific drivers, as the
- * [`platform_profile`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/ABI/testing/sysfs-platform_profile)
- * kernel API offers a way to implement system-specific profiles which
- * `power-profiles-daemon` can consume.
+ * All drivers should be derived from either #PpdDriverCpu or #PpdDriverPlatform
*
* When a driver implements the `performance` profile, it might set the
* #PpdDriver:performance-degraded property if the profile isn't running to
@@ -78,11 +72,17 @@ ppd_driver_set_property (GObject *object,
priv->profiles = g_value_get_flags (value);
break;
case PROP_PERFORMANCE_DEGRADED:
- g_clear_pointer (&priv->performance_degraded, g_free);
- priv->performance_degraded = g_value_dup_string (value);
+ {
+ const char *degraded = g_value_get_string (value);
+
+ g_clear_pointer (&priv->performance_degraded, g_free);
+
+ if (degraded != NULL && *degraded != '\0')
+ priv->performance_degraded = g_strdup (degraded);
+ }
break;
default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
@@ -106,7 +106,7 @@ ppd_driver_get_property (GObject *object,
g_value_set_string (value, priv->performance_degraded);
break;
default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
@@ -127,7 +127,7 @@ ppd_driver_class_init (PpdDriverClass *klass)
{
GObjectClass *object_class;
- object_class = G_OBJECT_CLASS(klass);
+ object_class = G_OBJECT_CLASS (klass);
object_class->finalize = ppd_driver_finalize;
object_class->get_property = ppd_driver_get_property;
object_class->set_property = ppd_driver_set_property;
@@ -173,7 +173,7 @@ ppd_driver_class_init (PpdDriverClass *klass)
* A unique driver name, only used for debugging.
*/
g_object_class_install_property (object_class, PROP_DRIVER_NAME,
- g_param_spec_string("driver-name",
+ g_param_spec_string ("driver-name",
"Driver name",
"Profile driver name",
NULL,
@@ -185,21 +185,20 @@ ppd_driver_class_init (PpdDriverClass *klass)
* The bitmask of #PpdProfile<!-- -->s implemented by this driver.
*/
g_object_class_install_property (object_class, PROP_PROFILES,
- g_param_spec_flags("profiles",
+ g_param_spec_flags ("profiles",
"Profiles",
"Profiles implemented by this driver",
PPD_TYPE_PROFILE,
0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
/**
- * PpdDriver:performance-degraded:
+ * PpdPlatformDriver:performance-degraded:
*
* If set to a non-%NULL value, the reason why the performance profile is unavailable.
* The value must be one of the options listed in the D-Bus API reference.
*/
g_object_class_install_property (object_class, PROP_PERFORMANCE_DEGRADED,
- g_param_spec_string("performance-degraded",
+ g_param_spec_string ("performance-degraded",
"Performance Degraded",
"Why the performance profile is degraded, if set",
NULL,
@@ -278,7 +277,7 @@ ppd_driver_get_performance_degraded (PpdDriver *driver)
g_return_val_if_fail (PPD_IS_DRIVER (driver), NULL);
priv = PPD_DRIVER_GET_PRIVATE (driver);
- return priv->performance_degraded ? priv->performance_degraded : "";
+ return priv->performance_degraded;
}
gboolean
diff --git a/src/ppd-driver.h b/src/ppd-driver.h
index f1e0f63..f02bd4d 100644
--- a/src/ppd-driver.h
+++ b/src/ppd-driver.h
@@ -12,26 +12,8 @@
#include <glib-object.h>
#include "ppd-profile.h"
-#define PPD_TYPE_DRIVER (ppd_driver_get_type())
-G_DECLARE_DERIVABLE_TYPE(PpdDriver, ppd_driver, PPD, DRIVER, GObject)
-
-/**
- * PpdProbeResult:
- * @PPD_PROBE_RESULT_UNSET: unset
- * @PPD_PROBE_RESULT_DEFER: driver should be kept alive, as kernel
- * support might appear.
- * @PPD_PROBE_RESULT_FAIL: driver failed to load.
- * @PPD_PROBE_RESULT_SUCCESS: driver successfully loaded.
- *
- * Those are the three possible values returned by a driver probe,
- * along with an unset value for convenience.
- */
-typedef enum {
- PPD_PROBE_RESULT_UNSET = -2,
- PPD_PROBE_RESULT_DEFER = -1,
- PPD_PROBE_RESULT_FAIL = 0,
- PPD_PROBE_RESULT_SUCCESS = 1
-} PpdProbeResult;
+#define PPD_TYPE_DRIVER (ppd_driver_get_type ())
+G_DECLARE_DERIVABLE_TYPE (PpdDriver, ppd_driver, PPD, DRIVER, GObject)
/**
* PpdProfileActivationReason:
@@ -64,8 +46,9 @@ typedef enum{
* @probe: Called by the daemon on startup.
* @activate_profile: Called by the daemon for every profile change.
*
- * New profile drivers should derive from #PpdDriver and implement
- * at least one of probe() and @activate_profile.
+ * New profile drivers should not derive from #PpdDriver. They should
+ * derive from the child from #PpdDriverCpu or #PpdDriverPlatform drivers
+ * and implement at least one of probe () and @activate_profile.
*/
struct _PpdDriverClass
{
diff --git a/src/ppd-profile.h b/src/ppd-profile.h
index 03c04eb..4af46ad 100644
--- a/src/ppd-profile.h
+++ b/src/ppd-profile.h
@@ -11,6 +11,24 @@
#include <glib.h>
+/**
+ * PpdProbeResult:
+ * @PPD_PROBE_RESULT_UNSET: unset
+ * @PPD_PROBE_RESULT_DEFER: driver should be kept alive, as kernel
+ * support might appear.
+ * @PPD_PROBE_RESULT_FAIL: driver failed to load.
+ * @PPD_PROBE_RESULT_SUCCESS: driver successfully loaded.
+ *
+ * Those are the three possible values returned by a driver probe,
+ * along with an unset value for convenience.
+ */
+typedef enum {
+ PPD_PROBE_RESULT_UNSET = -2,
+ PPD_PROBE_RESULT_DEFER = -1,
+ PPD_PROBE_RESULT_FAIL = 0,
+ PPD_PROBE_RESULT_SUCCESS = 1
+} PpdProbeResult;
+
/**
* PpdProfile:
* @PPD_PROFILE_POWER_SAVER: "power-saver", the battery saving profile
diff --git a/src/ppd-utils.c b/src/ppd-utils.c
index 5ac732f..6f6441b 100644
--- a/src/ppd-utils.c
+++ b/src/ppd-utils.c
@@ -7,6 +7,8 @@
*
*/
+#define G_LOG_DOMAIN "Utils"
+
#include "ppd-utils.h"
#include <gio/gio.h>
#include <stdio.h>
@@ -43,7 +45,7 @@ gboolean ppd_utils_write (const char *filename,
g_debug ("Could not open for writing '%s'", filename);
return FALSE;
}
- setbuf(sysfsfp, NULL);
+ setbuf (sysfsfp, NULL);
ret = fprintf (sysfsfp, "%s", value);
if (ret <= 0) {
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
@@ -75,6 +77,17 @@ gboolean ppd_utils_write_sysfs (GUdevDevice *device,
return ppd_utils_write (filename, value, error);
}
+gboolean ppd_utils_write_sysfs_int (GUdevDevice *device,
+ const char *attribute,
+ gint64 value,
+ GError **error)
+{
+ g_autofree char *str_value = NULL;
+
+ str_value = g_strdup_printf ("%" G_GINT64_FORMAT, value);
+ return ppd_utils_write_sysfs (device, attribute, str_value, error);
+}
+
GFileMonitor *
ppd_utils_monitor_sysfs_attr (GUdevDevice *device,
const char *attribute,
diff --git a/src/ppd-utils.h b/src/ppd-utils.h
index 9b12e5b..5ba8b8d 100644
--- a/src/ppd-utils.h
+++ b/src/ppd-utils.h
@@ -20,6 +20,10 @@ gboolean ppd_utils_write_sysfs (GUdevDevice *device,
const char *attribute,
const char *value,
GError **error);
+gboolean ppd_utils_write_sysfs_int (GUdevDevice *device,
+ const char *attribute,
+ gint64 value,
+ GError **error);
GFileMonitor *ppd_utils_monitor_sysfs_attr (GUdevDevice *device,
const char *attribute,
GError **error);
diff --git a/tests/integration-test.py b/tests/integration-test.py
deleted file mode 100755
index 8e37f69..0000000
--- a/tests/integration-test.py
+++ /dev/null
@@ -1,1264 +0,0 @@
-#!/usr/bin/python3
-
-# power-profiles-daemon integration test suite
-#
-# Run in built tree to test local built binaries, or from anywhere else to test
-# system installed binaries.
-#
-# Copyright: (C) 2011 Martin Pitt <martin.pitt at ubuntu.com>
-# (C) 2020 Bastien Nocera <hadess at hadess.net>
-# (C) 2021 David Redondo <kde at david-redondo.de>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-import os
-import sys
-import dbus
-import tempfile
-import subprocess
-import unittest
-import time
-
-try:
- import gi
- from gi.repository import GLib
- from gi.repository import Gio
-except ImportError as e:
- sys.stderr.write('Skipping tests, PyGobject not available for Python 3, or missing GI typelibs: %s\n' % str(e))
- sys.exit(77)
-
-try:
- gi.require_version('UMockdev', '1.0')
- from gi.repository import UMockdev
-except ImportError:
- sys.stderr.write('Skipping tests, umockdev not available (https://github.com/martinpitt/umockdev)\n')
- sys.exit(77)
-
-try:
- import dbusmock
-except ImportError:
- sys.stderr.write('Skipping tests, python-dbusmock not available (http://pypi.python.org/pypi/python-dbusmock).\n')
- sys.exit(77)
-
-
-PP = 'net.hadess.PowerProfiles'
-PP_PATH = '/net/hadess/PowerProfiles'
-PP_INTERFACE = 'net.hadess.PowerProfiles'
-
-class Tests(dbusmock.DBusTestCase):
- @classmethod
- def setUpClass(cls):
- # run from local build tree if we are in one, otherwise use system instance
- builddir = os.getenv('top_builddir', '.')
- if os.access(os.path.join(builddir, 'src', 'power-profiles-daemon'), os.X_OK):
- cls.daemon_path = os.path.join(builddir, 'src', 'power-profiles-daemon')
- print('Testing binaries from local build tree (%s)' % cls.daemon_path)
- elif os.environ.get('UNDER_JHBUILD', False):
- jhbuild_prefix = os.environ['JHBUILD_PREFIX']
- cls.daemon_path = os.path.join(jhbuild_prefix, 'libexec', 'power-profiles-daemon')
- print('Testing binaries from JHBuild (%s)' % cls.daemon_path)
- else:
- cls.daemon_path = None
- with open('/usr/lib/systemd/system/power-profiles-daemon.service') as f:
- for line in f:
- if line.startswith('ExecStart='):
- cls.daemon_path = line.split('=', 1)[1].strip()
- break
- assert cls.daemon_path, 'could not determine daemon path from systemd .service file'
- print('Testing installed system binary (%s)' % cls.daemon_path)
-
- # fail on CRITICALs on client and server side
- GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_WARNING |
- GLib.LogLevelFlags.LEVEL_ERROR |
- GLib.LogLevelFlags.LEVEL_CRITICAL)
- os.environ['G_DEBUG'] = 'fatal_warnings'
-
- # set up a fake system D-BUS
- cls.test_bus = Gio.TestDBus.new(Gio.TestDBusFlags.NONE)
- cls.test_bus.up()
- try:
- del os.environ['DBUS_SESSION_BUS_ADDRESS']
- except KeyError:
- pass
- os.environ['DBUS_SYSTEM_BUS_ADDRESS'] = cls.test_bus.get_bus_address()
-
- cls.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
- cls.dbus_con = cls.get_dbus(True)
-
- @classmethod
- def tearDownClass(cls):
- cls.test_bus.down()
- dbusmock.DBusTestCase.tearDownClass()
-
- def setUp(self):
- '''Set up a local umockdev testbed.
-
- The testbed is initially empty.
- '''
- self.testbed = UMockdev.Testbed.new()
- self.polkitd, self.obj_polkit = self.spawn_server_template(
- 'polkitd', {}, stdout=subprocess.PIPE)
- self.obj_polkit.SetAllowed(['net.hadess.PowerProfiles.switch-profile',
- 'net.hadess.PowerProfiles.hold-profile'])
-
- self.proxy = None
- self.log = None
- self.daemon = None
-
- # Used for dytc devices
- self.tp_acpi = None
-
- def run(self, result=None):
- super(Tests, self).run(result)
- if result and len(result.errors) + len(result.failures) > 0 and self.log:
- with open(self.log.name) as f:
- sys.stderr.write('\n-------------- daemon log: ----------------\n')
- sys.stderr.write(f.read())
- sys.stderr.write('------------------------------\n')
-
- def tearDown(self):
- del self.testbed
- self.stop_daemon()
-
- if self.polkitd:
- try:
- self.polkitd.kill()
- except OSError:
- pass
- self.polkitd.wait()
- self.polkitd = None
- self.obj_polkit = None
-
- del self.tp_acpi
- try:
- os.remove(self.testbed.get_root_dir() + '/' + 'ppd_test_conf.ini')
- except Exception:
- pass
-
- #
- # Daemon control and D-BUS I/O
- #
-
- def start_daemon(self):
- '''Start daemon and create DBus proxy.
-
- When done, this sets self.proxy as the Gio.DBusProxy for power-profiles-daemon.
- '''
- env = os.environ.copy()
- env['G_DEBUG'] = 'fatal-criticals'
- env['G_MESSAGES_DEBUG'] = 'all'
- # note: Python doesn't propagate the setenv from Testbed.new(), so we
- # have to do that ourselves
- env['UMOCKDEV_DIR'] = self.testbed.get_root_dir()
- self.log = tempfile.NamedTemporaryFile()
- if os.getenv('VALGRIND') != None:
- daemon_path = ['valgrind', self.daemon_path, '-v']
- else:
- daemon_path = [self.daemon_path, '-v']
-
- self.daemon = subprocess.Popen(daemon_path,
- env=env, stdout=self.log,
- stderr=subprocess.STDOUT)
-
- # wait until the daemon gets online
- timeout = 100
- while timeout > 0:
- time.sleep(0.1)
- timeout -= 1
- try:
- self.get_dbus_property('ActiveProfile')
- break
- except GLib.GError:
- pass
- else:
- self.fail('daemon did not start in 10 seconds')
-
- self.proxy = Gio.DBusProxy.new_sync(
- self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP,
- PP_PATH, PP, None)
-
- self.assertEqual(self.daemon.poll(), None, 'daemon crashed')
-
- def stop_daemon(self):
- '''Stop the daemon if it is running.'''
-
- if self.daemon:
- try:
- self.daemon.kill()
- except OSError:
- pass
- self.daemon.wait()
- self.daemon = None
- self.proxy = None
-
- def get_dbus_property(self, name):
- '''Get property value from daemon D-Bus interface.'''
-
- proxy = Gio.DBusProxy.new_sync(
- self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP,
- PP_PATH, 'org.freedesktop.DBus.Properties', None)
- return proxy.Get('(ss)', PP, name)
-
- def set_dbus_property(self, name, value):
- '''Set property value on daemon D-Bus interface.'''
-
- proxy = Gio.DBusProxy.new_sync(
- self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP,
- PP_PATH, 'org.freedesktop.DBus.Properties', None)
- return proxy.Set('(ssv)', PP, name, value)
-
- def call_dbus_method(self, name, parameters):
- '''Call a method of the daemon D-Bus interface.'''
-
- proxy = Gio.DBusProxy.new_sync(
- self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP,
- PP_PATH, PP_INTERFACE, None)
- return proxy.call_sync(name, parameters, Gio.DBusCallFlags.NO_AUTO_START, -1, None)
-
-
- def have_text_in_log(self, text):
- return self.count_text_in_log(text) > 0
-
- def count_text_in_log(self, text):
- with open(self.log.name) as f:
- return f.read().count(text)
-
- def read_sysfs_file(self, path):
- with open(self.testbed.get_root_dir() + '/' + path, 'rb') as f:
- return f.read().rstrip()
- return None
-
- def read_sysfs_attr(self, device, attribute):
- return self.read_sysfs_file(device + '/' + attribute)
-
- def get_mtime(self, device, attribute):
- return os.path.getmtime(self.testbed.get_root_dir() + '/' + device + '/' + attribute)
-
- def read_file(self, path):
- with open(path, 'rb') as f:
- return f.read()
- return None
-
- def create_dytc_device(self):
- self.tp_acpi = self.testbed.add_device('platform', 'thinkpad_acpi', None,
- ['dytc_lapmode', '0\n'],
- [ 'DEVPATH', '/devices/platform/thinkpad_acpi' ]
- )
-
- def create_empty_platform_profile(self):
- 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('\n')
- with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices:
- choices.write('\n')
-
- def create_platform_profile(self):
- 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("performance\n")
- with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices:
- choices.write("low-power balanced performance\n")
-
- def remove_platform_profile(self):
- acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
- os.remove(os.path.join(acpi_dir, "platform_profile_choices"))
- os.remove(os.path.join(acpi_dir, "platform_profile"))
- os.removedirs(acpi_dir)
-
- def assertEventually(self, condition, message=None, timeout=50):
- '''Assert that condition function eventually returns True.
-
- Timeout is in deciseconds, defaulting to 50 (5 seconds). message is
- printed on failure.
- '''
- while timeout >= 0:
- context = GLib.MainContext.default()
- while context.iteration(False):
- pass
- if condition():
- break
- timeout -= 1
- time.sleep(0.1)
- else:
- self.fail(message or 'timed out waiting for ' + str(condition))
-
- #
- # Actual test cases
- #
- def test_dbus_startup_error(self):
- '''D-Bus startup error'''
-
- self.start_daemon()
- out = subprocess.run([self.daemon_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- self.assertEqual(out.returncode, 1, "power-profile-daemon started but should have failed")
- self.stop_daemon()
-
- def test_no_performance_driver(self):
- '''no performance driver'''
-
- self.start_daemon()
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
- self.assertEqual(self.get_dbus_property('PerformanceDegraded'), '')
-
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 2)
- self.assertEqual(profiles[1]['Driver'], 'placeholder')
- self.assertEqual(profiles[0]['Driver'], 'placeholder')
- self.assertEqual(profiles[1]['Profile'], 'balanced')
- self.assertEqual(profiles[0]['Profile'], 'power-saver')
-
- 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'))
-
- self.stop_daemon()
-
- def test_inhibited_property(self):
- '''Test that the inhibited property exists'''
-
- self.create_dytc_device()
- self.create_platform_profile()
- self.start_daemon()
-
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 3)
- self.assertEqual(self.get_dbus_property('PerformanceInhibited'), '')
-
- def test_degraded_transition(self):
- '''Test that transitions work as expected when degraded'''
-
- self.create_dytc_device()
- self.create_platform_profile()
- self.start_daemon()
-
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 3)
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
-
- # Degraded
- self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '1\n')
- self.assertEventually(lambda: self.have_text_in_log('dytc_lapmode is now on'))
- self.assertEqual(self.get_dbus_property('PerformanceDegraded'), 'lap-detected')
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
-
- # Switch to non-performance
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
-
- def test_intel_pstate(self):
- '''Intel 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 Intel P-State configuration
- pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate")
- os.makedirs(pstate_dir)
- with open(os.path.join(pstate_dir, "no_turbo"),'w') as no_turbo:
- no_turbo.write("0\n")
- 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'], 'intel_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')
-
- # Disable turbo
- with open(os.path.join(pstate_dir, "no_turbo"),'w') as no_turbo:
- no_turbo.write("1\n")
-
- self.assertEventually(lambda: self.have_text_in_log('File monitor change happened for '))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
- self.assertEqual(self.get_dbus_property('PerformanceDegraded'), 'high-operating-temperature')
-
- 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_intel_pstate_balance(self):
- '''Intel 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/intel_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'], 'intel_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-intel-pstate.c
- self.assertEqual(contents, b'balance_performance')
-
- self.stop_daemon()
-
- upowerd.terminate()
- upowerd.wait()
- upowerd.stdout.close()
-
- def test_intel_pstate_error(self):
- '''Intel P-State driver in error state'''
-
- pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_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_intel_pstate_passive(self):
- '''Intel 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 Intel P-State configuration
- pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate")
- os.makedirs(pstate_dir)
- with open(os.path.join(pstate_dir, "no_turbo"),'w') as no_turbo:
- no_turbo.write("0\n")
- 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_intel_pstate_passive_with_epb(self):
- '''Intel P-State in passive mode (no HWP) with energy_perf_bias'''
-
- 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/cpu0/power/")
- os.makedirs(dir2)
- with open(os.path.join(dir2, 'energy_perf_bias'), 'w') as epb:
- epb.write("6")
-
- # Create Intel P-State configuration
- pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate")
- os.makedirs(pstate_dir)
- with open(os.path.join(pstate_dir, "no_turbo"),'w') as no_turbo:
- no_turbo.write("0\n")
- 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), 3)
- self.assertEqual(profiles[0]['Driver'], 'intel_pstate')
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- # Set power-saver 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(dir2, "energy_perf_bias"), 'rb') as f:
- contents = f.read()
- self.assertEqual(contents, b'15')
-
- # 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_perf_bias"), 'rb') as f:
- contents = f.read()
- self.assertEqual(contents, b'0')
-
- 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'''
-
- self.create_dytc_device()
- 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')
- self.assertEqual(profiles[0]['Profile'], 'power-saver')
- self.assertEqual(profiles[2]['Driver'], 'platform_profile')
- self.assertEqual(profiles[2]['Profile'], 'performance')
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
-
- # lapmode detected
- self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '1\n')
- self.assertEventually(lambda: self.get_dbus_property('PerformanceDegraded') == 'lap-detected')
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
-
- # Reset lapmode
- self.testbed.set_attribute(self.tp_acpi, 'dytc_lapmode', '0\n')
- self.assertEventually(lambda: self.get_dbus_property('PerformanceDegraded') == '')
-
- # Performance mode didn't change
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
-
- # Switch to power-saver mode
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
- self.assertEventually(lambda: self.read_sysfs_file("sys/firmware/acpi/platform_profile") == b'low-power')
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
-
- # And mimick a user pressing a Fn+H
- with open(os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/platform_profile"), 'w') as platform_profile:
- platform_profile.write('performance\n')
- self.assertEventually(lambda: self.get_dbus_property('ActiveProfile') == 'performance')
-
- def test_fake_driver(self):
- '''Test that the fake driver works'''
-
- os.environ['POWER_PROFILE_DAEMON_FAKE_DRIVER'] = '1'
- self.start_daemon()
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 3)
- self.stop_daemon()
-
- del os.environ['POWER_PROFILE_DAEMON_FAKE_DRIVER']
- self.start_daemon()
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 2)
-
- def test_trickle_charge_system(self):
- '''Trickle power_supply charge type'''
-
- fastcharge = self.testbed.add_device('power_supply', 'bq24190-charger', None,
- [ 'charge_type', 'Trickle', 'scope', 'System' ],
- []
- )
-
- self.start_daemon()
-
- self.assertIn('trickle_charge', self.get_dbus_property('Actions'))
-
- # Verify that charge-type stays untouched
- self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Trickle')
-
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
- self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Trickle')
-
- def test_trickle_charge_mode_no_change(self):
- '''Trickle power_supply charge type'''
-
- fastcharge = self.testbed.add_device('power_supply', 'MFi Fastcharge', None,
- [ 'charge_type', 'Fast', 'scope', 'Device' ],
- []
- )
-
- mtime = self.get_mtime(fastcharge, 'charge_type')
- self.start_daemon()
-
- self.assertIn('trickle_charge', self.get_dbus_property('Actions'))
-
- # Verify that charge-type didn't get touched
- self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Fast')
- self.assertEqual(self.get_mtime(fastcharge, 'charge_type'), mtime)
-
- def test_trickle_charge_mode(self):
- '''Trickle power_supply charge type'''
-
- idevice = self.testbed.add_device('usb', 'iDevice', None,
- [],
- [ 'ID_MODEL', 'iDevice', 'DRIVER', 'apple-mfi-fastcharge' ]
- )
- fastcharge = self.testbed.add_device('power_supply', 'MFi Fastcharge', idevice,
- [ 'charge_type', 'Trickle', 'scope', 'Device' ],
- []
- )
-
- self.start_daemon()
-
- self.assertIn('trickle_charge', self.get_dbus_property('Actions'))
-
- # Verify that charge-type got changed to Fast on startup
- self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Fast')
-
- # Verify that charge-type got changed to Trickle when power saving
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
- self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), b'Trickle')
-
- # FIXME no performance mode
- # Verify that charge-type got changed to Fast in a non-default, non-power save mode
- # self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
- # self.assertEqual(self.read_sysfs_attr(fastcharge, 'charge_type'), 'Fast')
-
- def test_platform_driver_late_load(self):
- '''Test that we can handle the platform_profile driver getting loaded late'''
- self.create_empty_platform_profile()
- self.start_daemon()
-
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 2)
-
- acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
- with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices:
- choices.write("low-power\nbalanced\nperformance\n")
- with open(os.path.join(acpi_dir, "platform_profile"),'w') as profile:
- profile.write("performance\n")
-
- # Wait for profiles to get reloaded
- self.assertEventually(lambda: len(self.get_dbus_property('Profiles')) == 3)
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 3)
- # Was set in platform_profile before we loaded the drivers
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
- self.assertEqual(self.get_dbus_property('PerformanceDegraded'), '')
-
- self.stop_daemon()
-
- def test_hp_wmi(self):
-
- # Uses cool 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")
- with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices:
- choices.write("cool balanced performance\n")
-
- self.start_daemon()
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 3)
- 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'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 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("quiet\n")
- with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices:
- choices.write("quiet balanced balanced-performance performance\n")
-
- self.start_daemon()
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 3)
- 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.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'quiet')
-
- self.stop_daemon()
-
- def test_hold_release_profile(self):
- self.create_platform_profile()
- self.start_daemon()
-
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 3)
-
- cookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', 'testReason', 'testApplication')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
- profileHolds = self.get_dbus_property('ActiveProfileHolds')
- self.assertEqual(len(profileHolds), 1)
- self.assertEqual(profileHolds[0]["Profile"], "performance")
- self.assertEqual(profileHolds[0]["Reason"], "testReason")
- self.assertEqual(profileHolds[0]["ApplicationId"], "testApplication")
-
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", cookie))
- profileHolds = self.get_dbus_property('ActiveProfileHolds')
- self.assertEqual(len(profileHolds), 0)
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- # When the profile is changed manually, holds should be released a
- self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', '')))
- self.assertEqual(len(self.get_dbus_property('ActiveProfileHolds')), 1)
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
-
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('balanced'))
- self.assertEqual(len(self.get_dbus_property('ActiveProfileHolds')), 0)
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- # When all holds are released, the last manually selected profile should be activated
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- cookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", cookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
-
- self.stop_daemon()
-
- def test_vanishing_hold(self):
- self.create_platform_profile()
- self.start_daemon()
-
- builddir = os.getenv('top_builddir', '.')
- tool_path = os.path.join(builddir, 'src', 'powerprofilesctl')
-
- launch_process = subprocess.Popen([tool_path, 'launch', '-p', 'power-saver', 'sleep', '3600'],
- stdout=sys.stdout, stderr=sys.stderr)
- assert launch_process
- time.sleep(1)
- holds = self.get_dbus_property('ActiveProfileHolds')
- self.assertEqual(len(holds), 1)
- hold = holds[0]
- self.assertEqual(hold['Profile'], 'power-saver')
-
- # Make sure to handle vanishing clients
- launch_process.terminate()
- launch_process.wait()
-
- holds = self.get_dbus_property('ActiveProfileHolds')
- self.assertEqual(len(holds), 0)
-
- self.stop_daemon()
-
- def test_hold_priority(self):
- '''power-saver should take priority over performance'''
- self.create_platform_profile()
- self.start_daemon()
-
- profiles = self.get_dbus_property('Profiles')
- self.assertEqual(len(profiles), 3)
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- # Test every order of holding and releasing power-saver and performance
- # hold performance and then power-saver, release in the same order
- performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
- powerSaverCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('power-saver', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", performanceCookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", powerSaverCookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- # hold performance and then power-saver, but release power-saver first
- performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
- powerSaverCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('power-saver', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)",powerSaverCookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", performanceCookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- # hold power-saver and then performance, release in the same order
- powerSaverCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('power-saver', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)",powerSaverCookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", performanceCookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- # hold power-saver and then performance, but release performance first
- powerSaverCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('power-saver', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)",performanceCookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- self.call_dbus_method('ReleaseProfile', GLib.Variant("(u)", powerSaverCookie))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- self.stop_daemon()
-
- def test_save_profile(self):
- '''save profile across runs'''
-
- self.create_platform_profile()
-
- self.start_daemon()
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
- self.stop_daemon()
-
- # sys.stderr.write('\n-------------- config file: ----------------\n')
- # with open(self.testbed.get_root_dir() + '/' + 'ppd_test_conf.ini') as f:
- # sys.stderr.write(f.read())
- # sys.stderr.write('------------------------------\n')
-
- self.start_daemon()
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- # Programmatically set profile aren't saved
- performanceCookie = self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', '')))
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'performance')
- self.stop_daemon()
-
- self.start_daemon()
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'power-saver')
- self.stop_daemon()
-
- def test_save_deferred_load(self):
- '''save profile across runs, but kernel driver loaded after start'''
-
- self.create_platform_profile()
- self.start_daemon()
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
- self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('power-saver'))
- self.stop_daemon()
- self.remove_platform_profile()
-
- # We could verify the contents of the configuration file here
-
- self.create_empty_platform_profile()
- self.start_daemon()
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
- with open(os.path.join(acpi_dir, "platform_profile_choices"),'w') as choices:
- choices.write("low-power\nbalanced\nperformance\n")
- with open(os.path.join(acpi_dir, "platform_profile"),'w') as profile:
- profile.write("performance\n")
-
- self.assertEventually(lambda: self.get_dbus_property('ActiveProfile') == 'power-saver')
- self.stop_daemon()
-
- def test_not_allowed_profile(self):
- '''Check that we get errors when trying to change a profile and not allowed'''
-
- self.obj_polkit.SetAllowed(dbus.Array([], signature='s'))
- self.start_daemon()
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- proxy = Gio.DBusProxy.new_sync(
- self.dbus, Gio.DBusProxyFlags.DO_NOT_AUTO_START, None, PP,
- PP_PATH, 'org.freedesktop.DBus.Properties', None)
- with self.assertRaises(gi.repository.GLib.GError) as cm:
- proxy.Set('(ssv)', PP, 'ActiveProfile', GLib.Variant.new_string('power-saver'))
- self.assertIn('AccessDenied', str(cm.exception))
-
- self.stop_daemon()
-
- def test_not_allowed_hold(self):
- '''Check that we get an error when trying to hold a profile and not allowed'''
-
- self.obj_polkit.SetAllowed(dbus.Array([], signature='s'))
- self.start_daemon()
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
-
- with self.assertRaises(gi.repository.GLib.GError) as cm:
- self.call_dbus_method('HoldProfile', GLib.Variant("(sss)", ('performance', '', '')))
- self.assertIn('AccessDenied', str(cm.exception))
-
- self.assertEqual(self.get_dbus_property('ActiveProfile'), 'balanced')
- self.assertEqual(len(self.get_dbus_property('ActiveProfileHolds')), 0)
-
- self.stop_daemon()
-
- def test_intel_pstate_noturbo(self):
- '''Intel 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)
- 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 Intel P-State configuration
- pstate_dir = os.path.join(self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate")
- os.makedirs(pstate_dir)
- with open(os.path.join(pstate_dir, "no_turbo"),'w') as no_turbo:
- no_turbo.write("1\n")
- with open(os.path.join(pstate_dir, "turbo_pct"),'w') as no_turbo:
- no_turbo.write("0\n")
- 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(self.get_dbus_property('PerformanceDegraded'), '')
-
- self.stop_daemon()
-
- def test_powerprofilesctl_error(self):
- '''Check that powerprofilesctl returns 1 rather than an exception on error'''
-
- builddir = os.getenv('top_builddir', '.')
- tool_path = os.path.join(builddir, 'src', 'powerprofilesctl')
-
- with self.assertRaises(subprocess.CalledProcessError) as cm:
- subprocess.check_output([tool_path, 'list'],
- stderr=subprocess.PIPE,
- universal_newlines=True)
- self.assertNotIn('Traceback', cm.exception.stderr)
-
- with self.assertRaises(subprocess.CalledProcessError) as cm:
- subprocess.check_output([tool_path, 'get'],
- stderr=subprocess.PIPE,
- universal_newlines=True)
- self.assertNotIn('Traceback', cm.exception.stderr)
-
- with self.assertRaises(subprocess.CalledProcessError) as cm:
- subprocess.check_output([tool_path, 'set', 'not-a-profile'],
- stderr=subprocess.PIPE,
- universal_newlines=True)
- self.assertNotIn('Traceback', cm.exception.stderr)
-
- with self.assertRaises(subprocess.CalledProcessError) as cm:
- subprocess.check_output([tool_path, 'list-holds'],
- stderr=subprocess.PIPE,
- universal_newlines=True)
- self.assertNotIn('Traceback', cm.exception.stderr)
-
- with self.assertRaises(subprocess.CalledProcessError) as cm:
- subprocess.check_output([tool_path, 'launch', '-p', 'power-saver', 'sleep', '1'],
- stderr=subprocess.PIPE,
- universal_newlines=True)
- self.assertNotIn('Traceback', cm.exception.stderr)
-
- self.start_daemon()
- with self.assertRaises(subprocess.CalledProcessError) as cm:
- subprocess.check_output([tool_path, 'set', 'not-a-profile'],
- stderr=subprocess.PIPE,
- universal_newlines=True)
- self.assertNotIn('Traceback', cm.exception.stderr)
- self.stop_daemon()
-
- #
- # Helper methods
- #
-
- @classmethod
- def _props_to_str(cls, properties):
- '''Convert a properties dictionary to uevent text representation.'''
-
- prop_str = ''
- if properties:
- for k, v in properties.items():
- prop_str += '%s=%s\n' % (k, v)
- return prop_str
-
-if __name__ == '__main__':
- # run ourselves under umockdev
- if 'umockdev' not in os.environ.get('LD_PRELOAD', ''):
- os.execvp('umockdev-wrapper', ['umockdev-wrapper'] + sys.argv)
-
- unittest.main()
diff --git a/tests/integration_test.py b/tests/integration_test.py
new file mode 100644
index 0000000..f31d6c5
--- /dev/null
+++ b/tests/integration_test.py
@@ -0,0 +1,1993 @@
+#!/usr/bin/python3
+
+# power-profiles-daemon integration test suite
+#
+# Run in built tree to test local built binaries, or from anywhere else to test
+# system installed binaries.
+#
+# Copyright: (C) 2011 Martin Pitt <martin.pitt at ubuntu.com>
+# (C) 2020 Bastien Nocera <hadess at hadess.net>
+# (C) 2021 David Redondo <kde at david-redondo.de>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import os
+import subprocess
+import sys
+import tempfile
+import time
+import unittest
+
+import dbus
+
+try:
+ import gi
+ from gi.repository import GLib
+ from gi.repository import Gio
+except ImportError as e:
+ sys.stderr.write(
+ f"Skipping tests, PyGobject not available for Python 3, or missing GI typelibs: {str(e)}\n"
+ )
+ sys.exit(77)
+
+try:
+ gi.require_version("UMockdev", "1.0")
+ from gi.repository import UMockdev
+except ImportError:
+ sys.stderr.write("Skipping tests, umockdev not available.\n")
+ sys.stderr.write("(https://github.com/martinpitt/umockdev)\n")
+ sys.exit(77)
+
+try:
+ import dbusmock
+except ImportError:
+ sys.stderr.write("Skipping tests, python-dbusmock not available.\n")
+ sys.stderr.write("(http://pypi.python.org/pypi/python-dbusmock)")
+ sys.exit(77)
+
+
+# pylint: disable=too-many-public-methods,too-many-instance-attributes
+class Tests(dbusmock.DBusTestCase):
+ """Dbus based integration unit tests"""
+
+ PP = "org.freedesktop.UPower.PowerProfiles"
+ PP_PATH = "/org/freedesktop/UPower/PowerProfiles"
+ PP_INTERFACE = "org.freedesktop.UPower.PowerProfiles"
+
+ @classmethod
+ def setUpClass(cls):
+ # run from local build tree if we are in one, otherwise use system instance
+ builddir = os.getenv("top_builddir", ".")
+ if os.access(os.path.join(builddir, "src", "power-profiles-daemon"), os.X_OK):
+ cls.daemon_path = os.path.join(builddir, "src", "power-profiles-daemon")
+ print(f"Testing binaries from local build tree {cls.daemon_path}")
+ elif os.environ.get("UNDER_JHBUILD", False):
+ jhbuild_prefix = os.environ["JHBUILD_PREFIX"]
+ cls.daemon_path = os.path.join(
+ jhbuild_prefix, "libexec", "power-profiles-daemon"
+ )
+ print(f"Testing binaries from JHBuild {cls.daemon_path}")
+ else:
+ cls.daemon_path = None
+ with open(
+ "/usr/lib/systemd/system/power-profiles-daemon.service",
+ encoding="utf-8",
+ ) as tmpf:
+ for line in tmpf:
+ if line.startswith("ExecStart="):
+ cls.daemon_path = line.split("=", 1)[1].strip()
+ break
+ assert (
+ cls.daemon_path
+ ), "could not determine daemon path from systemd .service file"
+ print(f"Testing installed system binary {cls.daemon_path}")
+
+ # fail on CRITICALs on client and server side
+ GLib.log_set_always_fatal(
+ GLib.LogLevelFlags.LEVEL_WARNING
+ | GLib.LogLevelFlags.LEVEL_ERROR
+ | GLib.LogLevelFlags.LEVEL_CRITICAL
+ )
+ os.environ["G_DEBUG"] = "fatal_warnings"
+
+ # set up a fake system D-BUS
+ cls.test_bus = Gio.TestDBus.new(Gio.TestDBusFlags.NONE)
+ cls.test_bus.up()
+ try:
+ del os.environ["DBUS_SESSION_BUS_ADDRESS"]
+ except KeyError:
+ pass
+ os.environ["DBUS_SYSTEM_BUS_ADDRESS"] = cls.test_bus.get_bus_address()
+
+ cls.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
+ cls.dbus_con = cls.get_dbus(True)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.test_bus.down()
+ dbusmock.DBusTestCase.tearDownClass()
+
+ def setUp(self):
+ """Set up a local umockdev testbed.
+
+ The testbed is initially empty.
+ """
+ self.testbed = UMockdev.Testbed.new()
+ self.polkitd, self.obj_polkit = self.spawn_server_template(
+ "polkitd", {}, stdout=subprocess.PIPE
+ )
+ self.obj_polkit.SetAllowed(
+ [
+ "org.freedesktop.UPower.PowerProfiles.switch-profile",
+ "org.freedesktop.UPower.PowerProfiles.hold-profile",
+ ]
+ )
+
+ self.proxy = None
+ self.props_proxy = None
+ self.log = None
+ self.daemon = None
+ self.changed_properties = {}
+
+ # Used for dytc devices
+ self.tp_acpi = None
+
+ def run(self, result=None):
+ super().run(result)
+ if not result or not self.log:
+ return
+ if len(result.errors) + len(result.failures) or os.getenv("PPD_TEST_VERBOSE"):
+ with open(self.log.name, encoding="utf-8") as tmpf:
+ sys.stderr.write("\n-------------- daemon log: ----------------\n")
+ sys.stderr.write(tmpf.read())
+ sys.stderr.write("------------------------------\n")
+
+ def tearDown(self):
+ del self.testbed
+ self.stop_daemon()
+
+ if self.polkitd:
+ self.polkitd.stdout.close()
+ try:
+ self.polkitd.kill()
+ except OSError:
+ pass
+ self.polkitd.wait()
+
+ self.obj_polkit = None
+
+ del self.tp_acpi
+ try:
+ os.remove(self.testbed.get_root_dir() + "/" + "ppd_test_conf.ini")
+ except AttributeError:
+ pass
+
+ #
+ # Daemon control and D-BUS I/O
+ #
+
+ def start_daemon(self):
+ """Start daemon and create DBus proxy.
+
+ When done, this sets self.proxy as the Gio.DBusProxy for power-profiles-daemon.
+ """
+ env = os.environ.copy()
+ env["G_DEBUG"] = "fatal-criticals"
+ env["G_MESSAGES_DEBUG"] = "all"
+ # note: Python doesn't propagate the setenv from Testbed.new(), so we
+ # have to do that ourselves
+ env["UMOCKDEV_DIR"] = self.testbed.get_root_dir()
+ env["LD_PRELOAD"] = os.getenv("PPD_LD_PRELOAD") + " " + os.getenv("LD_PRELOAD")
+ self.log = tempfile.NamedTemporaryFile() # pylint: disable=consider-using-with
+ daemon_path = [self.daemon_path, "-v"]
+ if os.getenv("PPD_TEST_WRAPPER"):
+ daemon_path = os.getenv("PPD_TEST_WRAPPER").split(" ") + daemon_path
+ elif os.getenv("VALGRIND"):
+ daemon_path = ["valgrind"] + daemon_path
+
+ # pylint: disable=consider-using-with
+ self.daemon = subprocess.Popen(
+ daemon_path, env=env, stdout=self.log, stderr=subprocess.STDOUT
+ )
+ self.addCleanup(self.daemon.kill)
+
+ def on_proxy_connected(_, res):
+ try:
+ self.proxy = Gio.DBusProxy.new_finish(res)
+ print(f"Proxy to {self.proxy.get_name()} connected")
+ except GLib.Error as exc:
+ self.fail(exc)
+
+ cancellable = Gio.Cancellable()
+ self.addCleanup(cancellable.cancel)
+ Gio.DBusProxy.new(
+ self.dbus,
+ Gio.DBusProxyFlags.DO_NOT_AUTO_START,
+ None,
+ self.PP,
+ self.PP_PATH,
+ self.PP_INTERFACE,
+ cancellable,
+ on_proxy_connected,
+ )
+
+ # wait until the daemon gets online
+ self.assert_eventually(
+ lambda: self.proxy and self.proxy.get_name_owner(),
+ timeout=10 * 1000,
+ message="daemon did not start in 10 seconds",
+ )
+
+ def properties_changed_cb(_, changed_properties, invalidated):
+ self.changed_properties.update(changed_properties.unpack())
+
+ self.addCleanup(
+ self.proxy.disconnect,
+ self.proxy.connect("g-properties-changed", properties_changed_cb),
+ )
+
+ self.assertEqual(self.daemon.poll(), None, "daemon crashed")
+
+ def ensure_dbus_properties_proxies(self):
+ self.props_proxy = Gio.DBusProxy.new_sync(
+ self.dbus,
+ Gio.DBusProxyFlags.DO_NOT_AUTO_START
+ | Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION
+ | Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES
+ | Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS,
+ None,
+ self.PP,
+ self.PP_PATH,
+ "org.freedesktop.DBus.Properties",
+ None,
+ )
+
+ def stop_daemon(self):
+ """Stop the daemon if it is running."""
+
+ if self.daemon:
+ try:
+ self.daemon.terminate()
+ except OSError:
+ pass
+ self.assertEqual(self.daemon.wait(timeout=3000), 0)
+
+ self.daemon = None
+ self.proxy = None
+
+ def get_dbus_property(self, name):
+ """Get property value from daemon D-Bus interface."""
+ self.ensure_dbus_properties_proxies()
+ return self.props_proxy.Get("(ss)", self.PP, name)
+
+ def set_dbus_property(self, name, value):
+ """Set property value on daemon D-Bus interface."""
+ self.ensure_dbus_properties_proxies()
+ return self.props_proxy.Set("(ssv)", self.PP, name, value)
+
+ def call_dbus_method(self, name, parameters):
+ """Call a method of the daemon D-Bus interface."""
+ return self.proxy.call_sync(
+ name, parameters, Gio.DBusCallFlags.NO_AUTO_START, -1, None
+ )
+
+ def have_text_in_log(self, text):
+ return self.count_text_in_log(text) > 0
+
+ def count_text_in_log(self, text):
+ with open(self.log.name, encoding="utf-8") as tmpf:
+ return tmpf.read().count(text)
+
+ def read_file_contents(self, path):
+ """Get the contents of a file"""
+ with open(path, "rb") as tmpf:
+ return tmpf.read()
+
+ def read_sysfs_file(self, path):
+ return self.read_file_contents(
+ self.testbed.get_root_dir() + "/" + path
+ ).rstrip()
+
+ def read_sysfs_attr(self, device, attribute):
+ return self.read_sysfs_file(device + "/" + attribute)
+
+ def get_mtime(self, device, attribute):
+ return os.path.getmtime(
+ self.testbed.get_root_dir() + "/" + device + "/" + attribute
+ )
+
+ def write_file_contents(self, path, contents):
+ """Set the contents of a file"""
+ with open(path, "wb") as tmpf:
+ return tmpf.write(
+ contents if isinstance(contents, bytes) else contents.encode("utf-8")
+ )
+
+ def change_immutable(self, fname, enable):
+ attr = "-"
+ if enable:
+ os.chmod(fname, 0o444)
+ attr = "+"
+ if os.geteuid() == 0:
+ if not GLib.find_program_in_path("chattr"):
+ self.skipTest("chattr is not found")
+
+ subprocess.check_output(["chattr", f"{attr}i", fname])
+ if not enable:
+ os.chmod(fname, 0o666)
+
+ def create_dytc_device(self):
+ self.tp_acpi = self.testbed.add_device(
+ "platform",
+ "thinkpad_acpi",
+ None,
+ ["dytc_lapmode", "0\n"],
+ ["DEVPATH", "/devices/platform/thinkpad_acpi"],
+ )
+
+ def create_amd_apu(self):
+ proc_dir = os.path.join(self.testbed.get_root_dir(), "proc/")
+ os.makedirs(proc_dir)
+ self.write_file_contents(
+ os.path.join(proc_dir, "cpuinfo"), "vendor_id : AuthenticAMD\n"
+ )
+
+ def create_empty_platform_profile(self):
+ acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(acpi_dir)
+ self.write_file_contents(os.path.join(acpi_dir, "platform_profile"), "\n")
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile_choices"), "\n"
+ )
+
+ def create_platform_profile(self):
+ acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(acpi_dir, exist_ok=True)
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile"), "performance\n"
+ )
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile_choices"),
+ "low-power balanced performance\n",
+ )
+
+ def remove_platform_profile(self):
+ acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.remove(os.path.join(acpi_dir, "platform_profile_choices"))
+ os.remove(os.path.join(acpi_dir, "platform_profile"))
+ os.removedirs(acpi_dir)
+
+ def assert_eventually(self, condition, message=None, timeout=5000):
+ """Assert that condition function eventually returns True.
+
+ Timeout is in milliseconds, defaulting to 5000 (5 seconds). message is
+ printed on failure.
+ """
+ if condition():
+ return
+
+ done = False
+
+ def on_timeout_reached():
+ nonlocal done
+ done = True
+
+ source = GLib.timeout_add(timeout, on_timeout_reached)
+ while not done:
+ if condition():
+ GLib.source_remove(source)
+ return
+ GLib.MainContext.default().iteration(False)
+
+ self.fail(message or "timed out waiting for " + str(condition))
+
+ def assert_file_eventually_contains(self, path, contents, timeout=800):
+ """Asserts that file contents eventually matches expectations"""
+ encoded = contents.encode("utf-8")
+ return self.assert_eventually(
+ lambda: self.read_file_contents(path) == encoded,
+ timeout=timeout,
+ message=f"file '{path}' does not contain '{contents}', "
+ + "but '{self.read_file_contents(path)}'",
+ )
+
+ def assert_sysfs_attr_eventually_is(self, device, attribute, contents, timeout=800):
+ """Asserts that file contents eventually matches expectations"""
+ encoded = contents.encode("utf-8")
+ return self.assert_eventually(
+ lambda: self.read_sysfs_attr(device, attribute) == encoded,
+ timeout=timeout,
+ message=f"file {device} '{attribute}' does not contain '{contents}', "
+ + "but '{self.read_sysfs_attr(device, attribute)}'",
+ )
+
+ def assert_dbus_property_eventually_is(self, prop, value, timeout=1200):
+ """Asserts that a dbus property eventually is what expected"""
+ return self.assert_eventually(
+ lambda: self.get_dbus_property(prop) == value,
+ timeout=timeout,
+ message=f"property '{prop}' is not '{value}', but '{self.get_dbus_property(prop)}'",
+ )
+
+ #
+ # Actual test cases
+ #
+ def test_dbus_startup_error(self):
+ """D-Bus startup error"""
+
+ self.start_daemon()
+ daemon_path = [self.daemon_path]
+ if os.getenv("PPD_TEST_WRAPPER"):
+ daemon_path = os.getenv("PPD_TEST_WRAPPER").split(" ") + daemon_path
+ out = subprocess.run(
+ daemon_path,
+ env={
+ "LD_PRELOAD": os.getenv("PPD_LD_PRELOAD")
+ + " "
+ + os.getenv("LD_PRELOAD")
+ },
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ check=False,
+ )
+ self.assertEqual(
+ out.returncode, 1, "power-profile-daemon started but should have failed"
+ )
+ self.stop_daemon()
+
+ def test_no_performance_driver(self):
+ """no performance driver"""
+
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+ self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "")
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 2)
+ self.assertEqual(profiles[1]["Driver"], "placeholder")
+ self.assertEqual(profiles[1]["PlatformDriver"], "placeholder")
+ self.assertEqual(profiles[0]["PlatformDriver"], "placeholder")
+ self.assertEqual(profiles[1]["Profile"], "balanced")
+ self.assertEqual(profiles[0]["Profile"], "power-saver")
+
+ 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")),
+ )
+ assert cookie
+
+ self.stop_daemon()
+
+ def test_inhibited_property(self):
+ """Test that the inhibited property exists"""
+
+ self.create_dytc_device()
+ self.create_platform_profile()
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(self.get_dbus_property("PerformanceInhibited"), "")
+
+ def test_multi_degredation(self):
+ """Test handling of degradation from multiple drivers"""
+ self.create_dytc_device()
+ self.create_platform_profile()
+
+ # Create CPU with preference
+ dir1 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/"
+ )
+ os.makedirs(dir1)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+
+ # Create Intel P-State configuration
+ pstate_dir = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate"
+ )
+ os.makedirs(pstate_dir)
+ self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "0\n")
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ self.start_daemon()
+
+ # Set performance mode
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ # Degraded CPU
+ self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "1\n")
+ self.assert_eventually(
+ lambda: self.have_text_in_log("File monitor change happened for ")
+ )
+
+ self.assertEqual(
+ self.get_dbus_property("PerformanceDegraded"), "high-operating-temperature"
+ )
+
+ # Degraded DYTC
+ self.testbed.set_attribute(self.tp_acpi, "dytc_lapmode", "1\n")
+ self.assert_eventually(lambda: self.have_text_in_log("dytc_lapmode is now on"))
+ self.assertEqual(
+ self.get_dbus_property("PerformanceDegraded"),
+ "high-operating-temperature,lap-detected",
+ )
+
+ def test_degraded_transition(self):
+ """Test that transitions work as expected when degraded"""
+
+ self.create_dytc_device()
+ self.create_platform_profile()
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ # Degraded
+ self.testbed.set_attribute(self.tp_acpi, "dytc_lapmode", "1\n")
+ self.assert_eventually(lambda: self.have_text_in_log("dytc_lapmode is now on"))
+ self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "lap-detected")
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ # Switch to non-performance
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+
+ def test_intel_pstate(self):
+ """Intel 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)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+ dir2 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/"
+ )
+ os.makedirs(dir2)
+ self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir2, "energy_performance_preference"), "performance\n"
+ )
+
+ # Create Intel P-State configuration
+ pstate_dir = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate"
+ )
+ os.makedirs(pstate_dir)
+ self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "0\n")
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "multiple")
+ self.assertEqual(profiles[0]["CpuDriver"], "intel_pstate")
+ self.assertEqual(profiles[0]["Profile"], "power-saver")
+
+ energy_prefs = os.path.join(dir2, "energy_performance_preference")
+ self.assert_file_eventually_contains(energy_prefs, "balance_performance")
+
+ # Set performance mode
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ self.assert_file_eventually_contains(energy_prefs, "performance")
+
+ # Disable turbo
+ self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "1\n")
+
+ self.assert_eventually(
+ lambda: self.have_text_in_log("File monitor change happened for ")
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+ self.assertEqual(
+ self.get_dbus_property("PerformanceDegraded"), "high-operating-temperature"
+ )
+
+ self.stop_daemon()
+
+ # Verify that Lenovo DYTC and Intel P-State drivers are loaded
+ self.create_platform_profile()
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "multiple")
+ self.assertEqual(profiles[0]["CpuDriver"], "intel_pstate")
+ self.assertEqual(profiles[0]["PlatformDriver"], "platform_profile")
+
+ def test_intel_pstate_balance(self):
+ """Intel 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")
+ self.write_file_contents(gov_path, "performance\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+ pstate_dir = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate"
+ )
+ os.makedirs(pstate_dir)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ upowerd, obj_upower = self.spawn_server_template(
+ "upower",
+ {"DaemonVersion": "0.99", "OnBattery": False},
+ stdout=subprocess.PIPE,
+ )
+ self.assertNotEqual(upowerd, None)
+ self.assertNotEqual(obj_upower, None)
+
+ self.start_daemon()
+
+ self.assert_file_eventually_contains(gov_path, "powersave")
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "multiple")
+ self.assertEqual(profiles[0]["CpuDriver"], "intel_pstate")
+ self.assertEqual(profiles[0]["Profile"], "power-saver")
+
+ self.assert_file_eventually_contains(
+ os.path.join(dir1, "energy_performance_preference"), "balance_performance"
+ )
+
+ self.stop_daemon()
+
+ upowerd.terminate()
+ upowerd.wait()
+ upowerd.stdout.close()
+
+ def test_intel_pstate_error(self):
+ """Intel P-State driver in error state"""
+
+ pstate_dir = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate"
+ )
+ os.makedirs(pstate_dir)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ dir1 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/"
+ )
+ os.makedirs(dir1)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ pref_path = os.path.join(dir1, "energy_performance_preference")
+ old_umask = os.umask(0o333)
+ self.write_file_contents(pref_path, "balance_performance\n")
+ os.umask(old_umask)
+ # Make file non-writable to root
+ self.change_immutable(pref_path, True)
+
+ 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")
+
+ energy_prefs = os.path.join(dir1, "energy_performance_preference")
+ self.assert_file_eventually_contains(energy_prefs, "balance_performance\n")
+
+ self.stop_daemon()
+
+ self.change_immutable(pref_path, False)
+
+ def test_intel_pstate_passive(self):
+ """Intel P-State in passive mode -> placeholder"""
+
+ dir1 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/"
+ )
+ os.makedirs(dir1)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+
+ # Create Intel P-State configuration
+ pstate_dir = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate"
+ )
+ os.makedirs(pstate_dir)
+ self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "0\n")
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "passive\n")
+
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 2)
+ self.assertEqual(profiles[0]["Driver"], "placeholder")
+ self.assertEqual(profiles[0]["PlatformDriver"], "placeholder")
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ energy_prefs = os.path.join(dir1, "energy_performance_preference")
+ self.assert_file_eventually_contains(energy_prefs, "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")
+
+ energy_prefs = os.path.join(dir1, "energy_performance_preference")
+ self.assert_file_eventually_contains(energy_prefs, "performance\n")
+
+ self.stop_daemon()
+
+ def test_intel_pstate_passive_with_epb(self):
+ """Intel P-State in passive mode (no HWP) with energy_perf_bias"""
+
+ dir1 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/"
+ )
+ os.makedirs(dir1)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+ dir2 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpu0/power/"
+ )
+ os.makedirs(dir2)
+ self.write_file_contents(os.path.join(dir2, "energy_perf_bias"), "6")
+
+ # Create Intel P-State configuration
+ pstate_dir = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate"
+ )
+ os.makedirs(pstate_dir)
+ self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "0\n")
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "passive\n")
+
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "multiple")
+ self.assertEqual(profiles[0]["CpuDriver"], "intel_pstate")
+ self.assertEqual(profiles[0]["PlatformDriver"], "placeholder")
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # Set power-saver mode
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+
+ energy_perf_bias = os.path.join(dir2, "energy_perf_bias")
+ self.assert_file_eventually_contains(energy_perf_bias, "15")
+
+ # Set performance mode
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ self.assert_file_eventually_contains(energy_perf_bias, "0")
+
+ self.stop_daemon()
+
+ def test_action_blocklist(self):
+ """Test action blocklist works"""
+ self.testbed.add_device(
+ "drm",
+ "card1-eDP",
+ None,
+ ["amdgpu/panel_power_savings", "0"],
+ ["DEVTYPE", "drm_connector"],
+ )
+
+ self.create_amd_apu()
+
+ self.spawn_server_template(
+ "upower",
+ {"DaemonVersion": "0.99", "OnBattery": False},
+ stdout=subprocess.PIPE,
+ )
+
+ # Block panel_power action
+ os.environ["POWER_PROFILE_DAEMON_ACTION_BLOCK"] = "amdgpu_panel_power"
+ self.start_daemon()
+ self.assertNotIn("amdgpu_panel_power", self.get_dbus_property("Actions"))
+
+ def test_driver_blocklist(self):
+ """Test driver blocklist works"""
+ # Create 2 CPUs with preferences
+ dir1 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/"
+ )
+ os.makedirs(dir1)
+ scaling_governor = os.path.join(dir1, "scaling_governor")
+ self.write_file_contents(scaling_governor, "powersave\n")
+
+ prefs1 = os.path.join(dir1, "energy_performance_preference")
+ self.write_file_contents(prefs1, "performance\n")
+
+ dir2 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/"
+ )
+ os.makedirs(dir2)
+ scaling_governor = os.path.join(dir2, "scaling_governor")
+ self.write_file_contents(scaling_governor, "powersave\n")
+ prefs2 = os.path.join(
+ dir2,
+ "energy_performance_preference",
+ )
+ self.write_file_contents(prefs2, "prformance\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)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ # create ACPI platform profile
+ self.create_platform_profile()
+ profile = os.path.join(
+ self.testbed.get_root_dir(), "sys/firmware/acpi/platform_profile"
+ )
+ self.assertNotEqual(profile, None)
+
+ # desktop PM profile
+ dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(dir3, exist_ok=True)
+ self.write_file_contents(os.path.join(dir3, "pm_profile"), "1\n")
+
+ # block platform profile
+ os.environ["POWER_PROFILE_DAEMON_DRIVER_BLOCK"] = "platform_profile"
+
+ # Verify that only amd-pstate is loaded
+ self.start_daemon()
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "multiple")
+ self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate")
+ self.assertEqual(profiles[0]["PlatformDriver"], "placeholder")
+
+ self.stop_daemon()
+
+ # block both drivers
+ os.environ["POWER_PROFILE_DAEMON_DRIVER_BLOCK"] = "amd_pstate,platform_profile"
+
+ # Verify that only placeholder is loaded
+ self.start_daemon()
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 2)
+ self.assertEqual(profiles[0]["PlatformDriver"], "placeholder")
+
+ # pylint: disable=too-many-statements
+ def test_multi_driver_flows(self):
+ """Test corner cases associated with multiple drivers"""
+
+ # Create 2 CPUs with preferences
+ dir1 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/"
+ )
+ os.makedirs(dir1)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ prefs1 = os.path.join(dir1, "energy_performance_preference")
+ self.write_file_contents(prefs1, "performance\n")
+
+ dir2 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/"
+ )
+ os.makedirs(dir2)
+ self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n")
+ prefs2 = os.path.join(dir2, "energy_performance_preference")
+ self.write_file_contents(prefs2, "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)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ # create ACPI platform profile
+ self.create_platform_profile()
+ profile = os.path.join(
+ self.testbed.get_root_dir(), "sys/firmware/acpi/platform_profile"
+ )
+
+ # desktop PM profile
+ dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(dir3, exist_ok=True)
+ self.write_file_contents(os.path.join(dir3, "pm_profile"), "1\n")
+
+ self.start_daemon()
+
+ # Verify that both drivers are loaded
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "multiple")
+ self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate")
+ self.assertEqual(profiles[0]["PlatformDriver"], "platform_profile")
+
+ # test both drivers can switch to power-saver
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+
+ # test both drivers can switch to performance
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ # test both drivers can switch to balanced
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("balanced"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # test when CPU driver fails to write
+ self.change_immutable(prefs1, True)
+ with self.assertRaises(gi.repository.GLib.GError):
+ self.set_dbus_property(
+ "ActiveProfile", GLib.Variant.new_string("power-saver")
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+ self.assertEqual(
+ self.read_sysfs_file("sys/firmware/acpi/platform_profile"), b"balanced"
+ )
+ self.change_immutable(prefs1, False)
+
+ # test when platform driver fails to write
+ self.change_immutable(profile, True)
+ with self.assertRaises(gi.repository.GLib.GError):
+ self.set_dbus_property(
+ "ActiveProfile", GLib.Variant.new_string("power-saver")
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # make sure CPU was undone since platform failed
+ self.assertEqual(
+ self.read_sysfs_file(
+ "sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference"
+ ),
+ b"balance_performance",
+ )
+ self.assertEqual(
+ self.read_sysfs_file(
+ "sys/devices/system/cpu/cpufreq/policy1/energy_performance_preference"
+ ),
+ b"balance_performance",
+ )
+ self.change_immutable(profile, False)
+
+ self.stop_daemon()
+
+ # pylint: disable=too-many-statements
+ 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)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+ dir2 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/"
+ )
+ os.makedirs(dir2)
+ self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir2, "energy_performance_preference"), "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)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ # desktop PM profile
+ dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(dir3)
+ self.write_file_contents(os.path.join(dir3, "pm_profile"), "1\n")
+
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+
+ self.assertEqual(profiles[0]["Driver"], "multiple")
+ self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate")
+ self.assertEqual(profiles[0]["Profile"], "power-saver")
+
+ energy_prefs = os.path.join(dir2, "energy_performance_preference")
+ scaling_governor = os.path.join(dir2, "scaling_governor")
+
+ self.assert_file_eventually_contains(energy_prefs, "balance_performance")
+ self.assert_file_eventually_contains(scaling_governor, "powersave")
+
+ # Set performance mode
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ self.assert_file_eventually_contains(energy_prefs, "performance")
+ self.assert_file_eventually_contains(scaling_governor, "performance")
+
+ # Set powersave mode
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+
+ self.assert_file_eventually_contains(energy_prefs, "power")
+ self.assert_file_eventually_contains(scaling_governor, "powersave")
+
+ self.stop_daemon()
+
+ 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")
+ self.write_file_contents(gov_path, "performance\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+ pstate_dir = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/amd_pstate"
+ )
+ os.makedirs(pstate_dir)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ # desktop PM profile
+ dir2 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(dir2)
+ self.write_file_contents(os.path.join(dir2, "pm_profile"), "1\n")
+
+ upowerd, obj_upower = self.spawn_server_template(
+ "upower",
+ {"DaemonVersion": "0.99", "OnBattery": False},
+ stdout=subprocess.PIPE,
+ )
+ self.assertTrue(upowerd)
+ self.assertTrue(obj_upower)
+
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "multiple")
+ self.assertEqual(profiles[0]["CpuDriver"], "amd_pstate")
+ self.assertEqual(profiles[0]["Profile"], "power-saver")
+
+ # This matches what's written by ppd-driver-amd-pstate.c
+ energy_prefs = os.path.join(dir1, "energy_performance_preference")
+ self.assert_file_eventually_contains(energy_prefs, "balance_performance")
+
+ scaling_governor = os.path.join(dir1, "scaling_governor")
+ self.assert_file_eventually_contains(scaling_governor, "powersave")
+
+ 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)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ dir1 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/"
+ )
+ os.makedirs(dir1)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ pref_path = os.path.join(dir1, "energy_performance_preference")
+ old_umask = os.umask(0o333)
+ self.write_file_contents(pref_path, "balance_performance\n")
+ os.umask(old_umask)
+ # Make file non-writable to root
+ self.change_immutable(pref_path, True)
+
+ # desktop PM profile
+ dir2 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(dir2)
+ self.write_file_contents(os.path.join(dir2, "pm_profile"), "1\n")
+
+ 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")
+
+ energy_prefs = os.path.join(dir1, "energy_performance_preference")
+ self.assert_file_eventually_contains(energy_prefs, "balance_performance\n")
+
+ self.stop_daemon()
+
+ self.change_immutable(pref_path, False)
+
+ 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)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "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)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "passive\n")
+
+ # desktop PM profile
+ dir2 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(dir2)
+ self.write_file_contents(os.path.join(dir2, "pm_profile"), "1\n")
+
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 2)
+ self.assertEqual(profiles[0]["Driver"], "placeholder")
+ self.assertEqual(profiles[0]["PlatformDriver"], "placeholder")
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ energy_prefs = os.path.join(dir1, "energy_performance_preference")
+ self.assert_file_eventually_contains(energy_prefs, "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")
+
+ self.assert_file_eventually_contains(energy_prefs, "performance\n")
+
+ self.stop_daemon()
+
+ def test_amd_pstate_server(self):
+ # Create 2 CPUs with preferences
+ dir1 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy0/"
+ )
+ os.makedirs(dir1)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+ dir2 = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/cpufreq/policy1/"
+ )
+ os.makedirs(dir2)
+ self.write_file_contents(os.path.join(dir2, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir2, "energy_performance_preference"), "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)
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ # server PM profile
+ dir3 = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(dir3)
+ self.write_file_contents(os.path.join(dir3, "pm_profile"), "4\n")
+
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 2)
+ with self.assertRaises(KeyError):
+ print(profiles[0]["CpuDriver"])
+
+ self.stop_daemon()
+
+ def test_dytc_performance_driver(self):
+ """Lenovo DYTC performance driver"""
+
+ self.create_dytc_device()
+ 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")
+ self.assertEqual(profiles[0]["PlatformDriver"], "platform_profile")
+ self.assertEqual(profiles[0]["Profile"], "power-saver")
+ self.assertEqual(profiles[2]["PlatformDriver"], "platform_profile")
+ self.assertEqual(profiles[2]["Profile"], "performance")
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("performance"))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ # lapmode detected
+ self.testbed.set_attribute(self.tp_acpi, "dytc_lapmode", "1\n")
+ self.assert_dbus_property_eventually_is("PerformanceDegraded", "lap-detected")
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ # Reset lapmode
+ self.testbed.set_attribute(self.tp_acpi, "dytc_lapmode", "0\n")
+ self.assert_dbus_property_eventually_is("PerformanceDegraded", "")
+
+ # Performance mode didn't change
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ # Switch to power-saver mode
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assert_eventually(
+ lambda: self.read_sysfs_file("sys/firmware/acpi/platform_profile")
+ == b"low-power"
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+
+ # And mimic a user pressing a Fn+H
+ platform_profile = os.path.join(
+ self.testbed.get_root_dir(), "sys/firmware/acpi/platform_profile"
+ )
+ self.write_file_contents(platform_profile, "performance\n")
+ self.assert_dbus_property_eventually_is("ActiveProfile", "performance")
+
+ def test_fake_driver(self):
+ """Test that the fake driver works"""
+
+ os.environ["POWER_PROFILE_DAEMON_FAKE_DRIVER"] = "1"
+ self.start_daemon()
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.stop_daemon()
+
+ del os.environ["POWER_PROFILE_DAEMON_FAKE_DRIVER"]
+ self.start_daemon()
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 2)
+
+ def test_amdgpu_panel_power(self):
+ """Verify AMDGPU Panel power actions"""
+ amdgpu_panel_power_savings = "amdgpu/panel_power_savings"
+ edp = self.testbed.add_device(
+ "drm",
+ "card1-eDP",
+ None,
+ ["status", "connected\n", amdgpu_panel_power_savings, "0"],
+ ["DEVTYPE", "drm_connector"],
+ )
+
+ self.create_amd_apu()
+
+ self.start_daemon()
+
+ self.assertIn("amdgpu_panel_power", self.get_dbus_property("Actions"))
+
+ # verify it hasn't been updated yet due to missing upower
+ self.assert_sysfs_attr_eventually_is(edp, amdgpu_panel_power_savings, "0")
+
+ # start upower and try again
+ self.stop_daemon()
+ self.spawn_server_template(
+ "upower",
+ {"DaemonVersion": "0.99", "OnBattery": True},
+ stdout=subprocess.PIPE,
+ )
+ self.start_daemon()
+
+ # verify balanced updated it
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("balanced"))
+ self.assert_sysfs_attr_eventually_is(edp, amdgpu_panel_power_savings, "1")
+
+ # verify power saver updated it
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assert_sysfs_attr_eventually_is(edp, amdgpu_panel_power_savings, "3")
+
+ # add another device that supports the feature
+ edp2 = self.testbed.add_device(
+ "drm",
+ "card2-eDP",
+ None,
+ ["status", "connected\n", amdgpu_panel_power_savings, "0"],
+ ["DEVTYPE", "drm_connector"],
+ )
+
+ # verify power saver got updated for it
+ self.assert_sysfs_attr_eventually_is(edp2, amdgpu_panel_power_savings, "3")
+
+ # add another device that supports the feature, but panel is disconnected
+ edp3 = self.testbed.add_device(
+ "drm",
+ "card3-eDP",
+ None,
+ ["status", "disconnected\n", amdgpu_panel_power_savings, "0"],
+ ["DEVTYPE", "drm_connector"],
+ )
+
+ # verify power saver didn't get updated for it
+ self.assert_sysfs_attr_eventually_is(edp3, amdgpu_panel_power_savings, "0")
+
+ def test_trickle_charge_system(self):
+ """Trickle power_supply charge type"""
+
+ fastcharge = self.testbed.add_device(
+ "power_supply",
+ "bq24190-charger",
+ None,
+ ["charge_type", "Trickle", "scope", "System"],
+ [],
+ )
+
+ self.start_daemon()
+
+ self.assertIn("trickle_charge", self.get_dbus_property("Actions"))
+
+ # Verify that charge-type stays untouched
+ self.assertEqual(self.read_sysfs_attr(fastcharge, "charge_type"), b"Trickle")
+
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assertEqual(self.read_sysfs_attr(fastcharge, "charge_type"), b"Trickle")
+
+ def test_trickle_charge_mode_no_change(self):
+ """Trickle power_supply charge type"""
+
+ fastcharge = self.testbed.add_device(
+ "power_supply",
+ "MFi Fastcharge",
+ None,
+ ["charge_type", "Fast", "scope", "Device"],
+ [],
+ )
+
+ mtime = self.get_mtime(fastcharge, "charge_type")
+ self.start_daemon()
+
+ self.assertIn("trickle_charge", self.get_dbus_property("Actions"))
+
+ # Verify that charge-type didn't get touched
+ self.assert_sysfs_attr_eventually_is(fastcharge, "charge_type", "Fast")
+ self.assertEqual(self.get_mtime(fastcharge, "charge_type"), mtime)
+
+ def test_trickle_charge_mode(self):
+ """Trickle power_supply charge type"""
+
+ idevice = self.testbed.add_device(
+ "usb",
+ "iDevice",
+ None,
+ [],
+ ["ID_MODEL", "iDevice", "DRIVER", "apple-mfi-fastcharge"],
+ )
+ fastcharge = self.testbed.add_device(
+ "power_supply",
+ "MFi Fastcharge",
+ idevice,
+ ["charge_type", "Trickle", "scope", "Device"],
+ [],
+ )
+
+ self.start_daemon()
+
+ self.assertIn("trickle_charge", self.get_dbus_property("Actions"))
+
+ # Verify that charge-type got changed to Fast on startup
+ self.assert_sysfs_attr_eventually_is(fastcharge, "charge_type", "Fast")
+
+ # Verify that charge-type got changed to Trickle when power saving
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assert_sysfs_attr_eventually_is(fastcharge, "charge_type", "Trickle")
+
+ # FIXME no performance mode
+ # Verify that charge-type got changed to Fast in a non-default, non-power save mode
+ # self.set_dbus_property('ActiveProfile', GLib.Variant.new_string('performance'))
+ # self.assert_sysfs_attr_eventually_is(fastcharge, "charge_type", "Fast")
+
+ def test_platform_driver_late_load(self):
+ """Test that we can handle the platform_profile driver getting loaded late"""
+ self.create_empty_platform_profile()
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 2)
+
+ acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile_choices"),
+ "low-power\nbalanced\nperformance\n",
+ )
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile"), "performance\n"
+ )
+
+ # Wait for profiles to get reloaded
+ self.assert_eventually(lambda: len(self.get_dbus_property("Profiles")) == 3)
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ # Was set in platform_profile before we loaded the drivers
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+ self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "")
+
+ self.stop_daemon()
+
+ def test_hp_wmi(self):
+ # Uses cool instead of low-power
+ acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(acpi_dir)
+ self.write_file_contents(os.path.join(acpi_dir, "platform_profile"), "cool\n")
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile_choices"),
+ "cool balanced performance\n",
+ )
+
+ self.start_daemon()
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "platform_profile")
+ self.assertEqual(profiles[0]["PlatformDriver"], "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"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 quiet instead of low-power
+ acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ os.makedirs(acpi_dir)
+ self.write_file_contents(os.path.join(acpi_dir, "platform_profile"), "quiet\n")
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile_choices"),
+ "quiet balanced balanced-performance performance\n",
+ )
+
+ self.start_daemon()
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(profiles[0]["Driver"], "platform_profile")
+ self.assertEqual(profiles[0]["PlatformDriver"], "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.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"quiet"
+ )
+
+ self.stop_daemon()
+
+ def test_hold_release_profile(self):
+ self.create_platform_profile()
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+
+ cookie = self.call_dbus_method(
+ "HoldProfile",
+ GLib.Variant("(sss)", ("performance", "testReason", "testApplication")),
+ )
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfile") == "performance"
+ )
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfileHolds")
+ == [
+ {
+ "ApplicationId": "testApplication",
+ "Profile": "performance",
+ "Reason": "testReason",
+ }
+ ]
+ )
+
+ released_cookie = None
+
+ def signal_cb(_, sender, signal, params):
+ nonlocal released_cookie
+ if signal == "ProfileReleased":
+ released_cookie = params
+
+ self.addCleanup(
+ self.proxy.disconnect, self.proxy.connect("g-signal", signal_cb)
+ )
+
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+ profile_holds = self.get_dbus_property("ActiveProfileHolds")
+ self.assertEqual(len(profile_holds), 1)
+ self.assertEqual(profile_holds[0]["Profile"], "performance")
+ self.assertEqual(profile_holds[0]["Reason"], "testReason")
+ self.assertEqual(profile_holds[0]["ApplicationId"], "testApplication")
+
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", cookie))
+ self.assert_eventually(lambda: released_cookie == cookie)
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfile") == "balanced"
+ )
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfileHolds") == []
+ )
+ profile_holds = self.get_dbus_property("ActiveProfileHolds")
+ self.assertEqual(len(profile_holds), 0)
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # When the profile is changed manually, holds should be released a
+ self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("performance", "", ""))
+ )
+ self.assertEqual(len(self.get_dbus_property("ActiveProfileHolds")), 1)
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfile") == "performance"
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("balanced"))
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfile") == "balanced"
+ )
+ self.assertEqual(len(self.get_dbus_property("ActiveProfileHolds")), 0)
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # When all holds are released, the last manually selected profile should be activated
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfile") == "power-saver"
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("performance", "", ""))
+ )
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfileHolds")
+ == [
+ {
+ "ApplicationId": "",
+ "Profile": "performance",
+ "Reason": "",
+ }
+ ]
+ )
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfile") == "performance"
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", cookie))
+ self.assert_eventually(lambda: released_cookie == cookie)
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfileHolds") == []
+ )
+ self.assert_eventually(
+ lambda: self.changed_properties.get("ActiveProfile") == "power-saver"
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+
+ self.stop_daemon()
+
+ def test_vanishing_hold(self):
+ self.create_platform_profile()
+ self.start_daemon()
+
+ sourcedir = os.getenv("top_srcdir", ".")
+ tool_path = os.path.join(sourcedir, "src", "powerprofilesctl")
+
+ with subprocess.Popen(
+ [tool_path, "launch", "-p", "power-saver", "sleep", "3600"],
+ stdout=sys.stdout,
+ stderr=sys.stderr,
+ ) as launch_process:
+ self.assertTrue(launch_process)
+ time.sleep(1)
+ holds = self.get_dbus_property("ActiveProfileHolds")
+ self.assertEqual(len(holds), 1)
+ hold = holds[0]
+ self.assertEqual(hold["Profile"], "power-saver")
+
+ # Make sure to handle vanishing clients
+ launch_process.terminate()
+ self.assertEqual(launch_process.wait(), 0)
+
+ holds = self.get_dbus_property("ActiveProfileHolds")
+ self.assertEqual(len(holds), 0)
+
+ self.stop_daemon()
+
+ def test_hold_priority(self):
+ """power-saver should take priority over performance"""
+ self.create_platform_profile()
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # Test every order of holding and releasing power-saver and performance
+ # hold performance and then power-saver, release in the same order
+ performance_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("performance", "", ""))
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+ powersaver_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("power-saver", "", ""))
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", performance_cookie))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", powersaver_cookie))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # hold performance and then power-saver, but release power-saver first
+ performance_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("performance", "", ""))
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+ powersaver_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("power-saver", "", ""))
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", powersaver_cookie))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", performance_cookie))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # hold power-saver and then performance, release in the same order
+ powersaver_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("power-saver", "", ""))
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ performance_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("performance", "", ""))
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", powersaver_cookie))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", performance_cookie))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ # hold power-saver and then performance, but release performance first
+ powersaver_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("power-saver", "", ""))
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ performance_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("performance", "", ""))
+ )
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", performance_cookie))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ self.call_dbus_method("ReleaseProfile", GLib.Variant("(u)", powersaver_cookie))
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ self.stop_daemon()
+
+ def test_save_profile(self):
+ """save profile across runs"""
+
+ self.create_platform_profile()
+
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.stop_daemon()
+
+ # sys.stderr.write('\n-------------- config file: ----------------\n')
+ # with open(self.testbed.get_root_dir() + '/' + 'ppd_test_conf.ini') as tmpf:
+ # sys.stderr.write(tmpf.read())
+ # sys.stderr.write('------------------------------\n')
+
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ # Programmatically set profile aren't saved
+ performance_cookie = self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("performance", "", ""))
+ )
+ self.assertTrue(performance_cookie)
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "performance")
+ self.stop_daemon()
+
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+ self.stop_daemon()
+
+ def test_save_deferred_load(self):
+ """save profile across runs, but kernel driver loaded after start"""
+
+ self.create_platform_profile()
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+ self.set_dbus_property("ActiveProfile", GLib.Variant.new_string("power-saver"))
+ self.stop_daemon()
+ self.remove_platform_profile()
+
+ # We could verify the contents of the configuration file here
+
+ self.create_empty_platform_profile()
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ acpi_dir = os.path.join(self.testbed.get_root_dir(), "sys/firmware/acpi/")
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile_choices"),
+ "low-power\nbalanced\nperformance\n",
+ )
+ self.write_file_contents(
+ os.path.join(acpi_dir, "platform_profile"), "performance\n"
+ )
+
+ self.assert_dbus_property_eventually_is("ActiveProfile", "power-saver")
+ self.stop_daemon()
+
+ def test_not_allowed_profile(self):
+ """Check that we get errors when trying to change a profile and not allowed"""
+
+ self.obj_polkit.SetAllowed(dbus.Array([], signature="s"))
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ proxy = Gio.DBusProxy.new_sync(
+ self.dbus,
+ Gio.DBusProxyFlags.DO_NOT_AUTO_START,
+ None,
+ self.PP,
+ self.PP_PATH,
+ "org.freedesktop.DBus.Properties",
+ None,
+ )
+ with self.assertRaises(gi.repository.GLib.GError) as error:
+ proxy.Set(
+ "(ssv)",
+ self.PP,
+ "ActiveProfile",
+ GLib.Variant.new_string("power-saver"),
+ )
+ self.assertIn("AccessDenied", str(error.exception))
+
+ self.stop_daemon()
+
+ def test_not_allowed_hold(self):
+ """Check that we get an error when trying to hold a profile and not allowed"""
+
+ self.obj_polkit.SetAllowed(dbus.Array([], signature="s"))
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ with self.assertRaises(gi.repository.GLib.GError) as error:
+ self.call_dbus_method(
+ "HoldProfile", GLib.Variant("(sss)", ("performance", "", ""))
+ )
+ self.assertIn("AccessDenied", str(error.exception))
+
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+ self.assertEqual(len(self.get_dbus_property("ActiveProfileHolds")), 0)
+
+ self.stop_daemon()
+
+ def test_get_version_prop(self):
+ """Checks that the version property is advertised"""
+ self.start_daemon()
+ self.assertTrue(self.get_dbus_property("Version"))
+
+ def test_intel_pstate_noturbo(self):
+ """Intel 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)
+ self.write_file_contents(os.path.join(dir1, "scaling_governor"), "powersave\n")
+ self.write_file_contents(
+ os.path.join(dir1, "energy_performance_preference"), "performance\n"
+ )
+
+ # Create Intel P-State configuration
+ pstate_dir = os.path.join(
+ self.testbed.get_root_dir(), "sys/devices/system/cpu/intel_pstate"
+ )
+ os.makedirs(pstate_dir)
+ self.write_file_contents(os.path.join(pstate_dir, "no_turbo"), "1\n")
+ self.write_file_contents(os.path.join(pstate_dir, "turbo_pct"), "0\n")
+ self.write_file_contents(os.path.join(pstate_dir, "status"), "active\n")
+
+ self.start_daemon()
+
+ profiles = self.get_dbus_property("Profiles")
+ self.assertEqual(len(profiles), 3)
+ self.assertEqual(self.get_dbus_property("PerformanceDegraded"), "")
+
+ self.stop_daemon()
+
+ def test_powerprofilesctl_version_command(self):
+ """Check powerprofilesctl version command works"""
+
+ self.start_daemon()
+
+ sourcedir = os.getenv("top_srcdir", ".")
+ tool_path = os.path.join(sourcedir, "src", "powerprofilesctl")
+
+ cmd = subprocess.run([tool_path, "version"], check=True)
+ self.assertEqual(cmd.returncode, 0)
+
+ def test_powerprofilesctl_list_command(self):
+ """Check powerprofilesctl list command works"""
+
+ self.start_daemon()
+
+ sourcedir = os.getenv("top_srcdir", ".")
+ tool_path = os.path.join(sourcedir, "src", "powerprofilesctl")
+
+ cmd = subprocess.run([tool_path, "list"], capture_output=True, check=True)
+ self.assertEqual(cmd.returncode, 0)
+ self.assertIn("* balanced", cmd.stdout.decode("utf-8"))
+
+ def test_powerprofilesctl_set_get_commands(self):
+ """Check powerprofilesctl set/get command works"""
+
+ self.start_daemon()
+
+ sourcedir = os.getenv("top_srcdir", ".")
+ tool_path = os.path.join(sourcedir, "src", "powerprofilesctl")
+
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "balanced")
+
+ cmd = subprocess.run([tool_path, "get"], capture_output=True, check=True)
+ self.assertEqual(cmd.returncode, 0)
+ self.assertEqual(cmd.stdout, b"balanced\n")
+
+ cmd = subprocess.run(
+ [tool_path, "set", "power-saver"], capture_output=True, check=True
+ )
+ self.assertEqual(cmd.returncode, 0)
+
+ self.assertEqual(self.get_dbus_property("ActiveProfile"), "power-saver")
+
+ cmd = subprocess.run([tool_path, "get"], capture_output=True, check=True)
+ self.assertEqual(cmd.returncode, 0)
+ self.assertEqual(cmd.stdout, b"power-saver\n")
+
+ def test_powerprofilesctl_error(self):
+ """Check that powerprofilesctl returns 1 rather than an exception on error"""
+
+ sourcedir = os.getenv("top_srcdir", ".")
+ tool_path = os.path.join(sourcedir, "src", "powerprofilesctl")
+
+ with self.assertRaises(subprocess.CalledProcessError) as error:
+ subprocess.check_output(
+ [tool_path, "list"], stderr=subprocess.PIPE, universal_newlines=True
+ )
+ self.assertNotIn("Traceback", error.exception.stderr)
+
+ with self.assertRaises(subprocess.CalledProcessError) as error:
+ subprocess.check_output(
+ [tool_path, "get"], stderr=subprocess.PIPE, universal_newlines=True
+ )
+ self.assertNotIn("Traceback", error.exception.stderr)
+
+ with self.assertRaises(subprocess.CalledProcessError) as error:
+ subprocess.check_output(
+ [tool_path, "set", "not-a-profile"],
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
+ self.assertNotIn("Traceback", error.exception.stderr)
+
+ with self.assertRaises(subprocess.CalledProcessError) as error:
+ subprocess.check_output(
+ [tool_path, "list-holds"],
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
+ self.assertNotIn("Traceback", error.exception.stderr)
+
+ with self.assertRaises(subprocess.CalledProcessError) as error:
+ subprocess.check_output(
+ [tool_path, "launch", "-p", "power-saver", "sleep", "1"],
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
+ self.assertNotIn("Traceback", error.exception.stderr)
+
+ self.start_daemon()
+ with self.assertRaises(subprocess.CalledProcessError) as error:
+ subprocess.check_output(
+ [tool_path, "set", "not-a-profile"],
+ stderr=subprocess.PIPE,
+ universal_newlines=True,
+ )
+ self.assertNotIn("Traceback", error.exception.stderr)
+ self.stop_daemon()
+
+ #
+ # Helper methods
+ #
+
+ @classmethod
+ def _props_to_str(cls, properties):
+ """Convert a properties dictionary to uevent text representation."""
+
+ prop_str = ""
+ if properties:
+ for key, val in properties.items():
+ prop_str += f"{key}={val}\n"
+ return prop_str
+
+
+class LegacyDBusNameTests(Tests):
+ """This will repeats all the tests in the Tests class using the legacy dbus name"""
+
+ PP = "net.hadess.PowerProfiles"
+ PP_PATH = "/net/hadess/PowerProfiles"
+ PP_INTERFACE = "net.hadess.PowerProfiles"
+
+
+if __name__ == "__main__":
+ # run ourselves under umockdev
+ if "umockdev" not in os.environ.get("LD_PRELOAD", ""):
+ os.execvp("umockdev-wrapper", ["umockdev-wrapper", sys.executable] + sys.argv)
+
+ prog = unittest.main(exit=False)
+ if prog.result.errors or prog.result.failures:
+ sys.exit(1)
+
+ # Translate to skip error
+ if prog.result.testsRun == len(prog.result.skipped):
+ sys.exit(77)
diff --git a/tests/meson.build b/tests/meson.build
index e51e90f..20e6eb3 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,18 +1,86 @@
envs = environment()
-envs.set ('top_builddir', meson.build_root())
-envs.set ('top_srcdir', meson.source_root())
+envs.set('PPD_TEST_VERBOSE', 'true')
+envs.set ('top_builddir', meson.project_build_root())
+envs.set ('top_srcdir', meson.project_source_root())
-python3 = find_program('python3')
unittest_inspector = find_program('unittest_inspector.py')
-r = run_command(unittest_inspector, files('integration-test.py'), check: true)
+integration_tests = files('integration_test.py')
+r = run_command(python3, unittest_inspector, integration_tests, check: true)
unit_tests = r.stdout().strip().split('\n')
+valgrind = find_program('valgrind', required: false)
+if valgrind.found()
+ glib_share = glib_dep.get_variable('prefix') / 'share' / glib_dep.name()
+ glib_suppressions = glib_share + '/valgrind/glib.supp'
+ libfprint_wrapper = [
+ valgrind.full_path(),
+ '--tool=memcheck',
+ '--leak-check=full',
+ '--leak-resolution=high',
+ '--error-exitcode=1',
+ '--errors-for-leak-kinds=definite',
+ '--track-origins=yes',
+ '--show-leak-kinds=definite,possible',
+ '--show-error-list=yes',
+ '--gen-suppressions=all',
+ '--suppressions=' + glib_suppressions,
+ ]
+ add_test_setup('valgrind',
+ timeout_multiplier: 5,
+ env: [
+ 'G_SLICE=always-malloc',
+ 'UNDER_VALGRIND=1',
+ 'PPD_TEST_WRAPPER=' + ' '.join(libfprint_wrapper),
+ ])
+endif
+
+preloaded_libs = []
+ppd_tests_ld_preload = []
+
+if address_sanitizer
+ # ASAN has to be the first in list
+ preloaded_libs += 'asan'
+endif
+
+foreach libname: preloaded_libs
+ lib = run_command(meson.get_compiler('c'),
+ '-print-file-name=lib at 0@.so'.format(libname),
+ check: true,
+ ).stdout().strip()
+
+ # Support linker script files
+ if run_command('grep', '-qI', '^INPUT', files(lib), check: false).returncode() == 0
+ out = run_command('cat', lib, check: true).stdout()
+ lib = out.split('(')[1].split(')')[0].strip()
+ endif
+
+ if lib != '' and lib[0] == '/'
+ message('Found library @0@ as @1@'.format(libname, lib))
+ ppd_tests_ld_preload += '@0@'.format(files(lib)[0])
+ else
+ tests = []
+ warning('No library found for ' + libname + ', skipping PAM tests')
+ endif
+endforeach
+
+envs.set('PPD_LD_PRELOAD', ' '.join(ppd_tests_ld_preload))
+
foreach ut: unit_tests
- ut_args = files('integration-test.py')
- ut_args += ut
test(ut,
python3,
- args: ut_args,
+ args: [
+ integration_tests,
+ ut,
+ ],
env: envs,
)
endforeach
+
+if get_option('pylint')
+ integration_pylint_flags = ['-d', 'W0511', '-d', 'C0302'] + pylint_flags
+ test('pylint-integration-tests',
+ pylint,
+ args: integration_pylint_flags + integration_tests,
+ env: nomalloc,
+ )
+endif
diff --git a/tests/unittest_inspector.py b/tests/unittest_inspector.py
old mode 100755
new mode 100644
index 0d5d3a6..db595a4
--- a/tests/unittest_inspector.py
+++ b/tests/unittest_inspector.py
@@ -22,23 +22,25 @@ import inspect
import os
import unittest
+
def list_tests(module):
tests = []
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, unittest.TestCase):
cases = unittest.defaultTestLoader.getTestCaseNames(obj)
- tests += [ (obj, '{}.{}'.format(name, t)) for t in cases ]
+ tests += [(obj, "{}.{}".format(name, t)) for t in cases]
return tests
-if __name__ == '__main__':
+if __name__ == "__main__":
parser = argparse.ArgumentParser()
- parser.add_argument('unittest_source', type=argparse.FileType('r'))
+ parser.add_argument("unittest_source", type=argparse.FileType("r"))
args = parser.parse_args()
source_path = args.unittest_source.name
spec = importlib.util.spec_from_file_location(
- os.path.basename(source_path), source_path)
+ os.path.basename(source_path), source_path
+ )
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
More information about the Neon-commits
mailing list