[PATCH v1 1/3] tools/LPMD: Add Intel Low Power Mode Daemon
From: Maciej Wieczor-Retman
Date: Tue May 12 2026 - 04:45:06 EST
From: Fabio M. De Francesco <fabio.m.de.francesco@xxxxxxxxxxxxxxx>
Add the Intel Low Power Mode Daemon (intel-lpmd) userspace tool.
Intel LPMD monitors system activity and dynamically constrains
tasks to the most power-efficient CPU cores during low utilization
periods, reducing package power consumption.
Signed-off-by: Fabio M. De Francesco <fabio.m.de.francesco@xxxxxxxxxxxxxxx>
Co-developed-by: Ali Erdinç Köroğlu <ali.erdinc.koroglu@xxxxxxxxx>
Signed-off-by: Ali Erdinç Köroğlu <ali.erdinc.koroglu@xxxxxxxxx>
Co-developed-by: Bin Li <libin3479@xxxxxxxxx>
Signed-off-by: Bin Li <libin3479@xxxxxxxxx>
Co-developed-by: Colin Ian King <colin.i.king@xxxxxxxxx>
Signed-off-by: Colin Ian King <colin.i.king@xxxxxxxxx>
Co-developed-by: Gomathinayagam, Vinod <vinod.gomathinayagam@xxxxxxxxx>
Signed-off-by: Gomathinayagam, Vinod <vinod.gomathinayagam@xxxxxxxxx>
Co-developed-by: Jack Kelly <jack@xxxxxxxxxxxxxx>
Signed-off-by: Jack Kelly <jack@xxxxxxxxxxxxxx>
Co-developed-by: Jan Engelhardt <jengelh@xxxxxxx>
Signed-off-by: Jan Engelhardt <jengelh@xxxxxxx>
Co-developed-by: Kate Hsuan <hpa@xxxxxxxxxx>
Signed-off-by: Kate Hsuan <hpa@xxxxxxxxxx>
Co-developed-by: Mohsin Shariff <mohsin.shariff@xxxxxxxxx>
Signed-off-by: Mohsin Shariff <mohsin.shariff@xxxxxxxxx>
Co-developed-by: Mubeen, Noor U <noor.u.mubeen@xxxxxxxxx>
Signed-off-by: Mubeen, Noor U <noor.u.mubeen@xxxxxxxxx>
Co-developed-by: Peter Jung <admin@xxxxxxxxxxx>
Signed-off-by: Peter Jung <admin@xxxxxxxxxxx>
Co-developed-by: Qubic <ThatQubicFox@xxxxxxxxxxxxxx>
Signed-off-by: Qubic <ThatQubicFox@xxxxxxxxxxxxxx>
Co-developed-by: Robert Dower <robert.dower@xxxxxxxxx>
Signed-off-by: Robert Dower <robert.dower@xxxxxxxxx>
Co-developed-by: Srinivas Pandruvada <srinivas.pandruvada@xxxxxxxxxxxxxxx>
Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@xxxxxxxxxxxxxxx>
Co-developed-by: Sundar, Deepak <deepak.sundar@xxxxxxxxx>
Signed-off-by: Sundar, Deepak <deepak.sundar@xxxxxxxxx>
Co-developed-by: Thomas Renninger <trenn@xxxxxxx>
Signed-off-by: Thomas Renninger <trenn@xxxxxxx>
Co-developed-by: Tomasz Kłoczko <kloczek@xxxxxxxxxx>
Signed-off-by: Tomasz Kłoczko <kloczek@xxxxxxxxxx>
Co-developed-by: Venkatesh J <venkatesh.j@xxxxxxxxx>
Signed-off-by: Venkatesh J <venkatesh.j@xxxxxxxxx>
Co-developed-by: Zhang Rui <rui.zhang@xxxxxxxxx>
Signed-off-by: Zhang Rui <rui.zhang@xxxxxxxxx>
Co-developed-by: Maciej Wieczor-Retman <maciej.wieczor-retman@xxxxxxxxx>
Signed-off-by: Maciej Wieczor-Retman <maciej.wieczor-retman@xxxxxxxxx>
---
MAINTAINERS | 6 +
tools/power/x86/intel-lpmd/README.md | 182 +++++
.../x86/intel-lpmd/data/intel_lpmd.service.in | 19 +
.../x86/intel-lpmd/data/intel_lpmd_config.xml | 106 +++
.../data/intel_lpmd_config_F6_M170.xml | 166 ++++
.../data/intel_lpmd_config_F6_M189.xml | 188 +++++
.../data/intel_lpmd_config_F6_M204.xml | 213 +++++
.../data/intel_lpmd_config_examples.xml | 213 +++++
.../data/intel_lpmd_config_experimental.xml | 252 ++++++
.../data/org.freedesktop.intel_lpmd.conf | 20 +
.../org.freedesktop.intel_lpmd.service.in | 5 +
tools/power/x86/intel-lpmd/doc/WLT_proxy.md | 84 ++
.../intel-lpmd/lpmd-resource.gresource.xml | 6 +
tools/power/x86/intel-lpmd/man/intel_lpmd.8 | 79 ++
.../intel-lpmd/man/intel_lpmd_config.xml.5 | 329 ++++++++
.../x86/intel-lpmd/man/intel_lpmd_control.8 | 44 ++
tools/power/x86/intel-lpmd/src/include/lpmd.h | 408 ++++++++++
.../x86/intel-lpmd/src/include/thermal.h | 94 +++
.../src/intel_lpmd_dbus_interface.xml | 27 +
tools/power/x86/intel-lpmd/src/lpmd_cgroup.c | 213 +++++
tools/power/x86/intel-lpmd/src/lpmd_config.c | 493 ++++++++++++
tools/power/x86/intel-lpmd/src/lpmd_cpu.c | 481 +++++++++++
tools/power/x86/intel-lpmd/src/lpmd_cpumask.c | 479 +++++++++++
.../x86/intel-lpmd/src/lpmd_dbus_server.c | 274 +++++++
tools/power/x86/intel-lpmd/src/lpmd_helpers.c | 385 +++++++++
tools/power/x86/intel-lpmd/src/lpmd_hfi.c | 373 +++++++++
tools/power/x86/intel-lpmd/src/lpmd_irq.c | 262 ++++++
tools/power/x86/intel-lpmd/src/lpmd_main.c | 297 +++++++
tools/power/x86/intel-lpmd/src/lpmd_misc.c | 483 +++++++++++
tools/power/x86/intel-lpmd/src/lpmd_proc.c | 492 ++++++++++++
tools/power/x86/intel-lpmd/src/lpmd_socket.c | 149 ++++
.../x86/intel-lpmd/src/lpmd_state_machine.c | 747 ++++++++++++++++++
tools/power/x86/intel-lpmd/src/lpmd_uevent.c | 133 ++++
tools/power/x86/intel-lpmd/src/lpmd_util.c | 369 +++++++++
tools/power/x86/intel-lpmd/src/lpmd_wlt.c | 107 +++
.../src/wlt_proxy/include/state_common.h | 106 +++
.../src/wlt_proxy/include/wlt_proxy.h | 10 +
.../x86/intel-lpmd/src/wlt_proxy/spike_mgmt.c | 202 +++++
.../intel-lpmd/src/wlt_proxy/state_machine.c | 314 ++++++++
.../intel-lpmd/src/wlt_proxy/state_manager.c | 302 +++++++
.../x86/intel-lpmd/src/wlt_proxy/state_util.c | 584 ++++++++++++++
.../x86/intel-lpmd/src/wlt_proxy/wlt_proxy.c | 31 +
.../intel-lpmd/tests/lpm_test_interface.sh | 64 ++
.../x86/intel-lpmd/tools/intel_lpmd_control.c | 97 +++
44 files changed, 9888 insertions(+)
create mode 100644 tools/power/x86/intel-lpmd/README.md
create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd.service.in
create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config.xml
create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M170.xml
create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M189.xml
create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M204.xml
create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_examples.xml
create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_experimental.xml
create mode 100644 tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.conf
create mode 100644 tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.service.in
create mode 100644 tools/power/x86/intel-lpmd/doc/WLT_proxy.md
create mode 100644 tools/power/x86/intel-lpmd/lpmd-resource.gresource.xml
create mode 100644 tools/power/x86/intel-lpmd/man/intel_lpmd.8
create mode 100644 tools/power/x86/intel-lpmd/man/intel_lpmd_config.xml.5
create mode 100644 tools/power/x86/intel-lpmd/man/intel_lpmd_control.8
create mode 100644 tools/power/x86/intel-lpmd/src/include/lpmd.h
create mode 100644 tools/power/x86/intel-lpmd/src/include/thermal.h
create mode 100644 tools/power/x86/intel-lpmd/src/intel_lpmd_dbus_interface.xml
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_cgroup.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_config.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_cpu.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_cpumask.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_dbus_server.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_helpers.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_hfi.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_irq.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_main.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_misc.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_proc.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_socket.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_state_machine.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_uevent.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_util.c
create mode 100644 tools/power/x86/intel-lpmd/src/lpmd_wlt.c
create mode 100644 tools/power/x86/intel-lpmd/src/wlt_proxy/include/state_common.h
create mode 100644 tools/power/x86/intel-lpmd/src/wlt_proxy/include/wlt_proxy.h
create mode 100644 tools/power/x86/intel-lpmd/src/wlt_proxy/spike_mgmt.c
create mode 100644 tools/power/x86/intel-lpmd/src/wlt_proxy/state_machine.c
create mode 100644 tools/power/x86/intel-lpmd/src/wlt_proxy/state_manager.c
create mode 100644 tools/power/x86/intel-lpmd/src/wlt_proxy/state_util.c
create mode 100644 tools/power/x86/intel-lpmd/src/wlt_proxy/wlt_proxy.c
create mode 100755 tools/power/x86/intel-lpmd/tests/lpm_test_interface.sh
create mode 100644 tools/power/x86/intel-lpmd/tools/intel_lpmd_control.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..f7181ff4ad8c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13099,6 +13099,12 @@ F: drivers/spi/spi-ljca.c
F: drivers/usb/misc/usb-ljca.c
F: include/linux/usb/ljca.h
+INTEL LOW POWER MODE DAEMON (intel-lpmd)
+M: Srinivas Pandruvada <srinivas.pandruvada@xxxxxxxxxxxxxxx>
+L: platform-driver-x86@xxxxxxxxxxxxxxx
+S: Maintained
+F: tools/power/x86/intel-lpmd/
+
INTEL MANAGEMENT ENGINE (mei)
M: Alexander Usyskin <alexander.usyskin@xxxxxxxxx>
L: linux-kernel@xxxxxxxxxxxxxxx
diff --git a/tools/power/x86/intel-lpmd/README.md b/tools/power/x86/intel-lpmd/README.md
new file mode 100644
index 000000000000..fb420e8184a1
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/README.md
@@ -0,0 +1,182 @@
+# Intel Low Power Mode Daemon
+
+Intel Low Power Mode Daemon (lpmd) is a Linux daemon designed to optimize active
+idle power. It selects the most power-efficient CPUs based on a configuration
+file or CPU topology. Depending on system utilization and other hints, it puts
+the system into Low Power Mode by activating the power-efficient CPUs and
+disabling the rest, and restores the system from Low Power Mode by activating
+all CPUs.
+
+## Before You Start
+
+**Please note** that the installed configuration files serve as templates of
+best practices for specific platform models and disable lpmd by default. For
+LPMD to start the user is expected to either edit the main
+"intel_lpmd_config.xml" config file or after starting the program, enable LPMD
+by using the intel_lpmd_control tool.
+
+Refer to the man pages for command line arguments and XML configurations:
+
+```sh
+man intel_lpmd
+man intel_lpmd_control
+man intel_lpmd_config.xml
+```
+
+## Install Dependencies
+
+### Fedora
+
+```sh
+dnf install automake autoconf-archive gcc glib2-devel libxml2-devel libnl3-devel systemd-devel gtk-doc upower-devel
+```
+
+### Ubuntu
+
+```sh
+sudo apt install autoconf autoconf-archive gcc libglib2.0-dev libdbus-1-dev libxml2-dev libnl-3-dev libnl-genl-3-dev libsystemd-dev gtk-doc-tools libupower-glib-dev
+```
+
+### OpenSUSE
+
+```sh
+zypper in automake gcc
+```
+
+## Build and Install
+
+```sh
+./autogen.sh
+make
+sudo make install
+```
+
+The generated artifacts are copied to respective directories under `/usr/local`.
+If a custom install path is preferred other than system default, make sure
+`--localstatedir` and `--sysconfdir` are set to the right path that the system
+can understand. If installed via RPM then artifacts would be under `/usr`.
+
+Example command for installation using prefix under `/opt/lpmd_install` dir with
+`--localstatedir` and `--sysconfdir` set to system default
+
+```sh
+./autogen.sh prefix=/opt/lpmd_install --localstatedir=/var --sysconfdir=/etc
+```
+
+## Run
+
+### Start Service
+
+```sh
+sudo systemctl start intel_lpmd.service
+```
+
+### Get Status
+
+```sh
+sudo systemctl status intel_lpmd.service
+```
+
+### Stop Service
+
+```sh
+sudo systemctl stop intel_lpmd.service
+```
+
+### Terminate using DBUS Interface
+
+```sh
+sudo tests/lpm_test_interface.sh 1
+```
+
+## Testing Installation from Source
+
+Launch `lpmd` in no-daemon mode:
+```sh
+./intel_lpmd --no-daemon --dbus-enable --loglevel=debug
+```
+
+Start `lpmd` using:
+```sh
+sudo sh tests/lpm_test_interface.sh 4
+```
+
+Run a workload and monitor `lpmd` to ensure it puts the system in the
+appropriate state based on the load.
+
+## Releases
+
+### Release 0.1.0
+- Add support for Panther Lake
+
+### Release 0.0.9
+
+- Fix lpmd from processing HFI/WLT updates when it is not in auto mode.
+- Improve README and other documents.
+- Add support for graphics utilization detection.
+- Add support for config states based on both WLT and graphics utilization.
+- Introduce LunarLake platform specific config file.
+- Minor fixes and cleanups.
+
+### Release 0.0.8
+
+- Introduce workload type proxy support.
+- Add support for model/sku specific config file.
+- Add detection for AC/DC status.
+- Honor power profile daemon default EPP when restoring.
+- Introduce MeteorLake-P platform specific config file.
+- Minor fixes and cleanups.
+
+### Release 0.0.7
+
+- Change lpmd description from "Low Power Mode Daemon" to "Energy Optimizer (lpmd)" because it covers more scenarios.
+- Fix invalid cgroup setting during probe, in case lpmd doesn't quit smoothly and cleanups are not done properly in the previous run.
+- Introduce a new parameter `--ignore-platform-check`.
+- Provide more detailed information when lpmd fails to probe on an unvalidated platform.
+- Various fixes for array bound check, potential memory leak, etc.
+- Autotool improvements.
+
+### Release 0.0.6
+
+- Remove automake and autoconf improvements due to a regression.
+- Deprecate the dbus-glib dependency.
+
+### Release 0.0.5
+
+- Fix compiling errors with `-Wall`.
+- Remove unintended default config file change to keep it unchanged since v0.0.3.
+
+### Release 0.0.4
+
+- Enhance HFI monitor to handle back-to-back HFI LPM hints.
+- Enhance HFI monitor to handle HFI hints for banned CPUs.
+- Introduce support for multiple Low Power states.
+- Introduce support for workload type hint.
+- Allow change EPP during Low Power modes transition.
+- Minor fixes and cleanups.
+
+### Release 0.0.3
+
+- Convert from glib-dbus to GDBus.
+- Add handling for CPU hotplug.
+- Use strict CPU model check to allow intel_lpmd to run on validated platforms only, including ADL/RPL/MTL for now.
+- CPUID.7 Hybrid bit is set
+- /sys/firmware/acpi/pm_profile returns 2 (mobile platform)
+- Use `cpuid()` to detect Lcores instead of using cache sysfs.
+- Enhance Ecore module detection.
+- Fix pthread error handling, suggested by ColinIanKing.
+- Werror fixes from aekoroglu.
+
+### Release 0.0.2
+
+- Various fixes and cleanups.
+
+### Release 0.0.1
+
+- Add initial lpmd support.
+
+## Security
+
+See Intel's [Security Center](https://www.intel.com/content/www/us/en/security-center/default.html) for information on how to report a potential security issue or vulnerability.
+
+See also: [Security Policy](security.md)
diff --git a/tools/power/x86/intel-lpmd/data/intel_lpmd.service.in b/tools/power/x86/intel-lpmd/data/intel_lpmd.service.in
new file mode 100644
index 000000000000..a87c231d8594
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/intel_lpmd.service.in
@@ -0,0 +1,19 @@
+[Unit]
+Description= Intel Linux Energy Optimizer (lpmd) Service
+Documentation=man:intel_lpmd(8)
+ConditionVirtualization=no
+StartLimitInterval=200
+StartLimitBurst=5
+
+[Service]
+Type=dbus
+SuccessExitStatus=2
+BusName=org.freedesktop.intel_lpmd
+ExecStart=@sbindir@/intel_lpmd --systemd --dbus-enable
+Restart=on-failure
+RestartSec=30
+PrivateTmp=yes
+
+[Install]
+WantedBy=multi-user.target
+Alias=org.freedesktop.intel_lpmd.service
diff --git a/tools/power/x86/intel-lpmd/data/intel_lpmd_config.xml b/tools/power/x86/intel-lpmd/data/intel_lpmd_config.xml
new file mode 100644
index 000000000000..2ef361a7f706
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/intel_lpmd_config.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+
+<!--
+Specifies the configuration data
+for Intel Energy Optimizer (LPMD) daemon
+-->
+
+<Configuration>
+ <!--
+ CPU format example: 1,2,4..6,8-10
+ -->
+ <lp_mode_cpus></lp_mode_cpus>
+
+ <!--
+ Mode values
+ 0: Cgroup v2
+ 1: Cgroup v2 isolate
+ 2: CPU idle injection
+ -->
+ <Mode>0</Mode>
+
+ <!--
+ Default behavior when Performance power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PerformanceDef>-1</PerformanceDef>
+
+ <!--
+ Default behavior when Balanced power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <BalancedDef>-1</BalancedDef>
+
+ <!--
+ Default behavior when Power saver setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PowersaverDef>-1</PowersaverDef>
+
+ <!--
+ Use HFI LPM hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiLpmEnable>0</HfiLpmEnable>
+
+ <!--
+ Use HFI SUV hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiSuvEnable>0</HfiSuvEnable>
+
+ <!--
+ System utilization threshold to enter LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_entry_threshold>10</util_entry_threshold>
+
+ <!--
+ System utilization threshold to exit LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_exit_threshold>95</util_exit_threshold>
+
+ <!--
+ Entry delay. Minimum delay in non Low Power mode to
+ enter LPM mode.
+ -->
+ <EntryDelayMS>0</EntryDelayMS>
+
+ <!--
+ Exit delay. Minimum delay in Low Power mode to
+ exit LPM mode.
+ -->
+ <ExitDelayMS>0</ExitDelayMS>
+
+ <!--
+ Lowest hysteresis average in-LP-mode time in msec to enter LP mode
+ 0: to disable hysteresis support
+ -->
+ <EntryHystMS>0</EntryHystMS>
+
+ <!--
+ Lowest hysteresis average out-of-LP-mode time in msec to exit LP mode
+ 0: to disable hysteresis support
+ -->
+ <ExitHystMS>0</ExitHystMS>
+
+ <!--
+ Ignore ITMT setting during LP-mode enter/exit
+ 0: disable ITMT upon LP-mode enter and re-enable ITMT upon LP-mode exit
+ 1: do not touch ITMT setting during LP-mode enter/exit
+ -->
+ <IgnoreITMT>0</IgnoreITMT>
+
+</Configuration>
+
diff --git a/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M170.xml b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M170.xml
new file mode 100644
index 000000000000..e6e6d061ff00
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M170.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0"?>
+
+<!--
+Specifies the configuration data
+for Intel Energy Optimizer (LPMD) daemon
+Proxy WLT enabled with MTL specific epp values.
+-->
+
+<Configuration>
+ <!--
+ CPU format example: 1,2,4..6,8-10
+ -->
+ <lp_mode_cpus></lp_mode_cpus>
+
+ <!--
+ Mode values
+ 0: Cgroup v2
+ 1: Cgroup v2 isolate
+ 2: CPU idle injection
+ -->
+ <Mode>1</Mode>
+
+ <!--
+ Default behavior when Performance power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PerformanceDef>-1</PerformanceDef>
+
+ <!--
+ Default behavior when Balanced power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <BalancedDef>-1</BalancedDef>
+
+ <!--
+ Default behavior when Power saver setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PowersaverDef>-1</PowersaverDef>
+
+ <!--
+ Use HFI LPM hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiLpmEnable>0</HfiLpmEnable>
+
+ <!--
+ Use WLT hints
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTHintEnable>1</WLTHintEnable>
+
+ <!--
+ Use WLT software proxy hints
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTProxyEnable>1</WLTProxyEnable>
+
+ <!--
+ Use HFI SUV hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiSuvEnable>0</HfiSuvEnable>
+
+ <!--
+ System utilization threshold to enter LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_entry_threshold></util_entry_threshold>
+
+ <!--
+ System utilization threshold to exit LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_exit_threshold></util_exit_threshold>
+
+ <!--
+ Entry delay. Minimum delay in non Low Power mode to
+ enter LPM mode.
+ -->
+ <EntryDelayMS>0</EntryDelayMS>
+
+ <!--
+ Exit delay. Minimum delay in Low Power mode to
+ exit LPM mode.
+ -->
+ <ExitDelayMS>0</ExitDelayMS>
+
+ <!--
+ Lowest hysteresis average in-LP-mode time in msec to enter LP mode
+ 0: to disable hysteresis support
+ -->
+ <EntryHystMS>0</EntryHystMS>
+
+ <!--
+ Lowest hysteresis average out-of-LP-mode time in msec to exit LP mode
+ 0: to disable hysteresis support
+ -->
+ <ExitHystMS>0</ExitHystMS>
+
+ <!--
+ Ignore ITMT setting during LP-mode enter/exit
+ 0: disable ITMT upon LP-mode enter and re-enable ITMT upon LP-mode exit
+ 1: do not touch ITMT setting during LP-mode enter/exit
+ -->
+ <IgnoreITMT>0</IgnoreITMT>
+
+ <States>
+ <CPUFamily> 6 </CPUFamily>
+ <CPUModel> 170 </CPUModel>
+ <CPUConfig> * </CPUConfig>
+ <State>
+ <ID> 1 </ID> <!-- no significance. number can be anything -->
+ <Name> WLT_IDLE </Name>
+ <WLTType> 0 </WLTType> <!-- WLTType mapped to Name -->
+ <EPP> 255 </EPP>
+ <EPB> 15 </EPB>
+ <MinPollInterval>2000</MinPollInterval>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ </State>
+ <State>
+ <ID> 2 </ID>
+ <Name> WLT_BATTERY_LIFE </Name>
+ <WLTType> 1 </WLTType>
+ <EPP> 178 </EPP>
+ <EPB> 6 </EPB>
+ <MinPollInterval>2000</MinPollInterval>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ </State>
+ <State>
+ <ID> 3 </ID>
+ <Name> WLT_SUSTAINED </Name>
+ <WLTType> 2 </WLTType>
+ <EPP> 64 </EPP>
+ <EPB> 6 </EPB>
+ <MinPollInterval>2000</MinPollInterval>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ </State>
+ <State>
+ <ID> 4 </ID>
+ <Name> WLT_BURSTY </Name>
+ <WLTType> 3 </WLTType>
+ <EPP> 64 </EPP>
+ <EPB> 4 </EPB>
+ <MinPollInterval>2000</MinPollInterval>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ </State>
+</States>
+
+</Configuration>
diff --git a/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M189.xml b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M189.xml
new file mode 100644
index 000000000000..b9f1fa46f454
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M189.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0"?>
+
+<!--
+Specifies the configuration data
+for Intel Energy Optimizer (LPMD) daemon
+-->
+
+<Configuration>
+ <!--
+ CPU format example: 1,2,4..6,8-10
+ -->
+ <lp_mode_cpus></lp_mode_cpus>
+
+ <!--
+ EPP to use in Low Power Mode
+ 0-255: Valid EPP value to use in Low Power Mode
+ -1: Don't change EPP in Low Power Mode
+ -->
+ <lp_mode_epp></lp_mode_epp>
+
+ <!--
+ Mode values
+ 0: Cgroup v2
+ 1: Cgroup v2 isolate
+ 2: CPU idle injection
+ -->
+ <Mode>1</Mode>
+
+ <!--
+ Default behavior when Performance power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PerformanceDef>-1</PerformanceDef>
+
+ <!--
+ Default behavior when Balanced power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <BalancedDef>-1</BalancedDef>
+
+ <!--
+ Default behavior when Power saver setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PowersaverDef>-1</PowersaverDef>
+
+ <!--
+ Use HFI LPM hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiLpmEnable>0</HfiLpmEnable>
+
+ <!--
+ Use WLT hints
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTHintEnable>1</WLTHintEnable>
+
+ <!--
+ Use WLT hint Poll enable
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTHintPollEnable>1</WLTHintPollEnable>
+
+ <!--
+ Use HFI SUV hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiSuvEnable>0</HfiSuvEnable>
+
+ <!--
+ System utilization threshold to enter LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_entry_threshold>10</util_entry_threshold>
+
+ <!--
+ System utilization threshold to exit LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_exit_threshold>95</util_exit_threshold>
+
+ <!--
+ Entry delay. Minimum delay in non Low Power mode to
+ enter LPM mode.
+ -->
+ <EntryDelayMS>0</EntryDelayMS>
+
+ <!--
+ Exit delay. Minimum delay in Low Power mode to
+ exit LPM mode.
+ -->
+ <ExitDelayMS>0</ExitDelayMS>
+
+ <!--
+ Lowest hysteresis average in-LP-mode time in msec to enter LP mode
+ 0: to disable hysteresis support
+ -->
+ <EntryHystMS>0</EntryHystMS>
+
+ <!--
+ Lowest hysteresis average out-of-LP-mode time in msec to exit LP mode
+ 0: to disable hysteresis support
+ -->
+ <ExitHystMS>0</ExitHystMS>
+
+ <!--
+ Ignore ITMT setting during LP-mode enter/exit
+ 0: disable ITMT upon LP-mode enter and re-enable ITMT upon LP-mode exit
+ 1: do not touch ITMT setting during LP-mode enter/exit
+ -->
+ <IgnoreITMT>0</IgnoreITMT>
+
+ <!--
+ Example WorkLoad Type hints based config states applied to
+ 12Pcore-8Ecore-2Lcore 28W TDP Meteor Lake platform.
+ Need to set WLTHintEnable to make it work.
+ -->
+ <States>
+ <CPUFamily> 6 </CPUFamily>
+ <CPUModel> 189 </CPUModel>
+ <CPUConfig> * </CPUConfig>
+ <State>
+ <ID> 1 </ID>
+ <Name> UTIL_IDLE </Name>
+ <WLTType> 1 </WLTType>
+ <EnterGFXLoadThres>50</EnterGFXLoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 192 </EPP>
+ <EPB> 8 </EPB>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 2 </ID>
+ <Name> UTIL_IDLE_SUSTAIN </Name>
+ <EnterGFXLoadThres>75</EnterGFXLoadThres>
+ <WLTType> 2 </WLTType>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 64 </EPP>
+ <EPB> 8 </EPB>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 3 </ID>
+ <Name> UTIL_IDLE_BURSTY </Name>
+ <WLTType> 3 </WLTType>
+ <EnterGFXLoadThres>75</EnterGFXLoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 64 </EPP>
+ <EPB> 8 </EPB>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 4 </ID>
+ <Name> UTIL_IDLE_GFX_BUSY </Name>
+ <EnterGFXLoadThres>100</EnterGFXLoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 128 </EPP>
+ <EPB> 8 </EPB>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ </States>
+
+</Configuration>
diff --git a/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M204.xml b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M204.xml
new file mode 100644
index 000000000000..123daef5f47c
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M204.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0"?>
+
+<!--
+Specifies the configuration data
+for Intel Energy Optimizer (LPMD) daemon
+-->
+
+<Configuration>
+ <!--
+ CPU format example: 1,2,4..6,8-10
+ -->
+ <lp_mode_cpus></lp_mode_cpus>
+
+ <!--
+ EPP to use in Low Power Mode
+ 0-255: Valid EPP value to use in Low Power Mode
+ -1: Don't change EPP in Low Power Mode
+ -->
+ <lp_mode_epp></lp_mode_epp>
+
+ <!--
+ Mode values
+ 0: Cgroup v2
+ 1: Cgroup v2 isolate
+ 2: CPU idle injection
+ <Mode>0</Mode>
+ -->
+
+ <!--
+ Default behavior when Performance power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PerformanceDef>-1</PerformanceDef>
+
+ <!--
+ Default behavior when Balanced power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <BalancedDef>-1</BalancedDef>
+
+ <!--
+ Default behavior when Power saver setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PowersaverDef>-1</PowersaverDef>
+
+ <!--
+ Use HFI LPM hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiLpmEnable>0</HfiLpmEnable>
+
+ <!--
+ Use WLT hints
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTHintEnable>1</WLTHintEnable>
+
+ <!--
+ Use WLT hint Poll enable
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTHintPollEnable>0</WLTHintPollEnable>
+
+ <!--
+ Use WLT hint Mask
+ 0 : Non
+ * : Value to AND with the hardware hint
+ -->
+ <WLTHintMask>15</WLTHintMask>
+
+ <!--
+ Use HFI SUV hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiSuvEnable>0</HfiSuvEnable>
+
+ <!--
+ System utilization threshold to enter LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ <util_entry_threshold>0</util_entry_threshold>
+ -->
+
+ <!--
+ System utilization threshold to exit LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ <util_exit_threshold>0</util_exit_threshold>
+ -->
+
+ <!--
+ Entry delay. Minimum delay in non Low Power mode to
+ enter LPM mode.
+ <EntryDelayMS>0</EntryDelayMS>
+ -->
+
+ <!--
+ Exit delay. Minimum delay in Low Power mode to
+ exit LPM mode.
+ <ExitDelayMS>0</ExitDelayMS>
+ -->
+
+ <!--
+ Lowest hysteresis average in-LP-mode time in msec to enter LP mode
+ 0: to disable hysteresis support
+ <EntryHystMS>0</EntryHystMS>
+ -->
+
+ <!--
+ Lowest hysteresis average out-of-LP-mode time in msec to exit LP mode
+ 0: to disable hysteresis support
+ <ExitHystMS>0</ExitHystMS>
+ -->
+
+ <!--
+ Ignore ITMT setting during LP-mode enter/exit
+ 0: disable ITMT upon LP-mode enter and re-enable ITMT upon LP-mode exit
+ 1: do not touch ITMT setting during LP-mode enter/exit
+ -->
+ <IgnoreITMT>0</IgnoreITMT>
+
+ <!--
+ Slider default configuration for AC and DC
+ -->
+ <BalancedSliderAC>1</BalancedSliderAC>
+ <BalancedSliderDC>3</BalancedSliderDC>
+ <SliderOffsetAC>0</SliderOffsetAC>
+ <SliderOffsetDC>3</SliderOffsetDC>
+
+ <States>
+ <CPUFamily> 6 </CPUFamily>
+ <CPUModel> 204 </CPUModel>
+ <CPUConfig> 4P8E4L-25W </CPUConfig>
+ <State>
+ <ID> 1 </ID>
+ <Name> UTIL_IDLE </Name>
+ <WLTType> 1 </WLTType>
+ <ActiveCPUs>12-15</ActiveCPUs>
+ </State>
+ <State>
+ <ID> 2 </ID>
+ <Name> UTIL_IDLE_SUSTAIN </Name>
+ <WLTType> 2 </WLTType>
+ <ActiveCPUs>0-15</ActiveCPUs>
+ </State>
+ <State>
+ <ID> 3 </ID>
+ <Name> UTIL_IDLE_BURSTY </Name>
+ <WLTType> 3 </WLTType>
+ <ActiveCPUs>0-15</ActiveCPUs>
+ </State>
+ </States>
+
+ <States>
+ <CPUFamily> 6 </CPUFamily>
+ <CPUModel> 204 </CPUModel>
+ <CPUConfig> 4P4E4L-25W </CPUConfig>
+ <State>
+ <ID> 1 </ID>
+ <Name> UTIL_IDLE </Name>
+ <WLTType> 1 </WLTType>
+ <ActiveCPUs>8-11</ActiveCPUs>
+ </State>
+ <State>
+ <ID> 2 </ID>
+ <Name> UTIL_IDLE_SUSTAIN </Name>
+ <WLTType> 2 </WLTType>
+ <ActiveCPUs>0-11</ActiveCPUs>
+ </State>
+ <State>
+ <ID> 3 </ID>
+ <Name> UTIL_IDLE_BURSTY </Name>
+ <WLTType> 3 </WLTType>
+ <ActiveCPUs>0-11</ActiveCPUs>
+ </State>
+ </States>
+
+ <States>
+ <CPUFamily> 6 </CPUFamily>
+ <CPUModel> 204 </CPUModel>
+ <CPUConfig> 4P0E4L-25W </CPUConfig>
+ <State>
+ <ID> 1 </ID>
+ <Name> UTIL_IDLE </Name>
+ <WLTType> 1 </WLTType>
+ <ActiveCPUs>4-7</ActiveCPUs>
+ </State>
+ <State>
+ <ID> 2 </ID>
+ <Name> UTIL_IDLE_SUSTAIN </Name>
+ <WLTType> 2 </WLTType>
+ <ActiveCPUs>0-7</ActiveCPUs>
+ </State>
+ <State>
+ <ID> 3 </ID>
+ <Name> UTIL_IDLE_BURSTY </Name>
+ <WLTType> 3 </WLTType>
+ <ActiveCPUs>0-7</ActiveCPUs>
+ </State>
+ </States>
+
+</Configuration>
diff --git a/tools/power/x86/intel-lpmd/data/intel_lpmd_config_examples.xml b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_examples.xml
new file mode 100644
index 000000000000..fcfbe8806558
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_examples.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0"?>
+
+<!--
+Specifies the configuration data
+for Intel Energy Optimizer (LPMD) daemon
+-->
+
+<Configuration>
+ <!--
+ CPU format example: 1,2,4..6,8-10
+ -->
+ <lp_mode_cpus></lp_mode_cpus>
+
+ <!--
+ EPP to use in Low Power Mode
+ 0-255: Valid EPP value to use in Low Power Mode
+ -1: Don't change EPP in Low Power Mode
+ -->
+ <lp_mode_epp>-1</lp_mode_epp>
+
+ <!--
+ Mode values
+ 0: Cgroup v2
+ 1: Cgroup v2 isolate
+ 2: CPU idle injection
+ -->
+ <Mode>0</Mode>
+
+ <!--
+ Default behavior when Performance power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PerformanceDef>-1</PerformanceDef>
+
+ <!--
+ Default behavior when Balanced power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <BalancedDef>-1</BalancedDef>
+
+ <!--
+ Default behavior when Power saver setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PowersaverDef>-1</PowersaverDef>
+
+ <!--
+ Use HFI LPM hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiLpmEnable>0</HfiLpmEnable>
+
+ <!--
+ Use WLT hints
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTHintEnable>0</WLTHintEnable>
+
+ <!--
+ Use HFI SUV hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiSuvEnable>0</HfiSuvEnable>
+
+ <!--
+ System utilization threshold to enter LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_entry_threshold>10</util_entry_threshold>
+
+ <!--
+ System utilization threshold to exit LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_exit_threshold>95</util_exit_threshold>
+
+ <!--
+ Entry delay. Minimum delay in non Low Power mode to
+ enter LPM mode.
+ -->
+ <EntryDelayMS>0</EntryDelayMS>
+
+ <!--
+ Exit delay. Minimum delay in Low Power mode to
+ exit LPM mode.
+ -->
+ <ExitDelayMS>0</ExitDelayMS>
+
+ <!--
+ Lowest hysteresis average in-LP-mode time in msec to enter LP mode
+ 0: to disable hysteresis support
+ -->
+ <EntryHystMS>0</EntryHystMS>
+
+ <!--
+ Lowest hysteresis average out-of-LP-mode time in msec to exit LP mode
+ 0: to disable hysteresis support
+ -->
+ <ExitHystMS>0</ExitHystMS>
+
+ <!--
+ Ignore ITMT setting during LP-mode enter/exit
+ 0: disable ITMT upon LP-mode enter and re-enable ITMT upon LP-mode exit
+ 1: do not touch ITMT setting during LP-mode enter/exit
+ -->
+ <IgnoreITMT>0</IgnoreITMT>
+
+ <!--
+ Example WorkLoad Type hints based config states applied to
+ 12Pcore-8Ecore-2Lcore 28W TDP Meteor Lake platform.
+ Need to set WLTHintEnable to make it work.
+ -->
+ <States>
+ <CPUFamily> 6 </CPUFamily>
+ <CPUModel> 170 </CPUModel>
+ <CPUConfig> 12P8E2L-28W </CPUConfig>
+ <State>
+ <ID> 1 </ID>
+ <Name> WLT_IDLE </Name>
+ <WLTType> 0 </WLTType>
+ <EPP> 255 </EPP>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ </State>
+ <State>
+ <ID> 2 </ID>
+ <Name> WLT_BATTERY_LIFE </Name>
+ <WLTType> 1 </WLTType>
+ <EPP> 192 </EPP>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ </State>
+ <State>
+ <ID> 3 </ID>
+ <Name> WLT_SUSTAINED </Name>
+ <WLTType> 2 </WLTType>
+ <EPP> 64 </EPP>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ </State>
+ <State>
+ <ID> 4 </ID>
+ <Name> WLT_BURSTY </Name>
+ <WLTType> 3 </WLTType>
+ <EPP> 64 </EPP>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ </State>
+ </States>
+
+ <!--
+ Example Utilization based config states applied to
+ 4Pcore-8Ecore-2Lcore 15W TDP Meteor Lake platform.
+ -->
+ <States>
+ <CPUFamily> 6 </CPUFamily>
+ <CPUModel> 170 </CPUModel>
+ <CPUConfig> 4P8E2L-15W </CPUConfig>
+ <State>
+ <ID> 1 </ID>
+ <Name> LPM_DEEP </Name>
+ <EntrySystemLoadThres> 2 </EntrySystemLoadThres>
+ <EnterCPULoadThres> 50 </EnterCPULoadThres>
+ <EPP> -1 </EPP>
+ <EPB> -1 </EPB>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ <ActiveCPUs>16,17</ActiveCPUs>
+ <MinPollInterval> 500 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 2 </ID>
+ <Name> LPM_LOW </Name>
+ <EntrySystemLoadThres> 10 </EntrySystemLoadThres>
+ <ExitSystemLoadhysteresis> 3 </ExitSystemLoadhysteresis>
+ <EPP> -1 </EPP>
+ <EPB> -1 </EPB>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ <ActiveCPUs>12,13,14,15</ActiveCPUs>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 1000 </PollIntervalIncrement>
+ <MaxPollInterval> 3000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 3 </ID>
+ <Name> FULL_POWER </Name>
+ <EntrySystemLoadThres></EntrySystemLoadThres>
+ <EnterCPULoadThres></EnterCPULoadThres>
+ <EPP> -1 </EPP>
+ <EPB> -1 </EPB>
+ <ITMTState> -1 </ITMTState>
+ <IRQMigrate> -1 </IRQMigrate>
+ <ActiveCPUs>0-17</ActiveCPUs>
+ <MinPollInterval> 500 </MinPollInterval>
+ <PollIntervalIncrement> -1 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ </States>
+</Configuration>
diff --git a/tools/power/x86/intel-lpmd/data/intel_lpmd_config_experimental.xml b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_experimental.xml
new file mode 100644
index 000000000000..3aa8c75a1ffd
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/intel_lpmd_config_experimental.xml
@@ -0,0 +1,252 @@
+<?xml version="1.0"?>
+
+<!--
+Specifies the configuration data
+for Intel Energy Optimizer (LPMD) daemon
+-->
+
+<Configuration>
+ <!--
+ CPU format example: 1,2,4..6,8-10
+ -->
+ <lp_mode_cpus></lp_mode_cpus>
+
+ <!--
+ EPP to use in Low Power Mode
+ 0-255: Valid EPP value to use in Low Power Mode
+ -1: Don't change EPP in Low Power Mode
+ -->
+ <lp_mode_epp></lp_mode_epp>
+
+ <!--
+ Mode values
+ 0: Cgroup v2
+ 1: Cgroup v2 isolate
+ 2: CPU idle injection
+ -->
+ <Mode>1</Mode>
+
+ <!--
+ Default behavior when Performance power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PerformanceDef>-1</PerformanceDef>
+
+ <!--
+ Default behavior when Balanced power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <BalancedDef>-1</BalancedDef>
+
+ <!--
+ Default behavior when Power saver setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PowersaverDef>-1</PowersaverDef>
+
+ <!--
+ Use HFI LPM hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiLpmEnable>1</HfiLpmEnable>
+
+ <!--
+ Use WLT hints
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTHintEnable>1</WLTHintEnable>
+
+ <!--
+ Use WLT hint Poll enable
+ 0 : No
+ 1 : Yes
+ -->
+ <WLTHintPollEnable>1</WLTHintPollEnable>
+
+ <!--
+ Use HFI SUV hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiSuvEnable>0</HfiSuvEnable>
+
+ <!--
+ System utilization threshold to enter LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_entry_threshold>10</util_entry_threshold>
+
+ <!--
+ System utilization threshold to exit LP mode
+ from 0 - 100
+ clear both util_entry_threshold and util_exit_threshold to disable util monitor
+ -->
+ <util_exit_threshold>95</util_exit_threshold>
+
+ <!--
+ Entry delay. Minimum delay in non Low Power mode to
+ enter LPM mode.
+ -->
+ <EntryDelayMS>0</EntryDelayMS>
+
+ <!--
+ Exit delay. Minimum delay in Low Power mode to
+ exit LPM mode.
+ -->
+ <ExitDelayMS>0</ExitDelayMS>
+
+ <!--
+ Lowest hysteresis average in-LP-mode time in msec to enter LP mode
+ 0: to disable hysteresis support
+ -->
+ <EntryHystMS>0</EntryHystMS>
+
+ <!--
+ Lowest hysteresis average out-of-LP-mode time in msec to exit LP mode
+ 0: to disable hysteresis support
+ -->
+ <ExitHystMS>0</ExitHystMS>
+
+ <!--
+ Ignore ITMT setting during LP-mode enter/exit
+ 0: disable ITMT upon LP-mode enter and re-enable ITMT upon LP-mode exit
+ 1: do not touch ITMT setting during LP-mode enter/exit
+ -->
+ <IgnoreITMT>0</IgnoreITMT>
+
+ <!--
+ config states for PantherLake 2P + 4E + 4L + 25W TDP platform in Client Power Lab
+ -->
+ <States>
+ <CPUFamily> 6 </CPUFamily>
+ <CPUModel> 202 </CPUModel>
+ <CPUConfig> 4P8E4L-15W </CPUConfig>
+ <State>
+ <ID> 1 </ID>
+ <Name> WLT_IDLE </Name>
+ <WLTType> 0 </WLTType>
+ <EnterGFXLoadThres>50</EnterGFXLoadThres>
+ <EntrySystemLoadThres>5</EntrySystemLoadThres>
+ <EnterCPULoadThres>90</EnterCPULoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 192 </EPP>
+ <EPB> 8 </EPB>
+ <ActiveCPUs> hfi </ActiveCPUs>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 2 </ID>
+ <Name> WLT_BATTERY_LIFE </Name>
+ <WLTType> 1 </WLTType>
+ <EnterGFXLoadThres>50</EnterGFXLoadThres>
+ <EntrySystemLoadThres>10</EntrySystemLoadThres>
+ <EnterCPULoadThres>95</EnterCPULoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 192 </EPP>
+ <EPB> 8 </EPB>
+ <ActiveCPUs> hfi </ActiveCPUs>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 3 </ID>
+ <Name> WLT_SUSTAIN </Name>
+ <WLTType> 2 </WLTType>
+ <EntrySystemLoadThres>30</EntrySystemLoadThres>
+ <EnterGFXLoadThres>50</EnterGFXLoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 64 </EPP>
+ <EPB> 8 </EPB>
+ <ActiveCPUs> lp </ActiveCPUs>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 4 </ID>
+ <Name> WLT_BURSTY </Name>
+ <WLTType> 3 </WLTType>
+ <EnterGFXLoadThres>50</EnterGFXLoadThres>
+ <EntrySystemLoadThres>50</EntrySystemLoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 64 </EPP>
+ <EPB> 8 </EPB>
+ <ActiveCPUs> all </ActiveCPUs>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 5 </ID>
+ <Name> UTIL_IDLE </Name>
+ <EnterGFXLoadThres>50</EnterGFXLoadThres>
+ <EntrySystemLoadThres>5</EntrySystemLoadThres>
+ <EnterCPULoadThres>90</EnterCPULoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 192 </EPP>
+ <EPB> 8 </EPB>
+ <ActiveCPUs> hfi </ActiveCPUs>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 6 </ID>
+ <Name> UTIL_SUSTAIN </Name>
+ <EntrySystemLoadThres>10</EntrySystemLoadThres>
+ <EnterGFXLoadThres>50</EnterGFXLoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 64 </EPP>
+ <EPB> 8 </EPB>
+ <ActiveCPUs> lp </ActiveCPUs>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 7 </ID>
+ <Name> UTIL_BURSTY </Name>
+ <EnterGFXLoadThres>50</EnterGFXLoadThres>
+ <EntrySystemLoadThres>50</EntrySystemLoadThres>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 64 </EPP>
+ <EPB> 8 </EPB>
+ <ActiveCPUs> all </ActiveCPUs>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ <State>
+ <ID> 8 </ID>
+ <Name> UTIL_BUSY </Name>
+ <IRQMigrate> -1 </IRQMigrate>
+ <EPP> 32 </EPP>
+ <EPB> 6 </EPB>
+ <ActiveCPUs> all </ActiveCPUs>
+ <ITMTState> -1 </ITMTState>
+ <MinPollInterval> 1000 </MinPollInterval>
+ <PollIntervalIncrement> 500 </PollIntervalIncrement>
+ <MaxPollInterval> 2000 </MaxPollInterval>
+ </State>
+ </States>
+
+</Configuration>
diff --git a/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.conf b/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.conf
new file mode 100644
index 000000000000..f06fd34e4e6f
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.conf
@@ -0,0 +1,20 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+
+ <policy user="root">
+ <allow own="org.freedesktop.intel_lpmd"/>
+ <allow send_destination="org.freedesktop.intel_lpmd"/>
+ <allow receive_sender="org.freedesktop.intel_lpmd"/>
+ </policy>
+
+ <!-- Only allow members of the power group to communicate
+ with the daemon -->
+ <policy context="default">
+ <deny send_destination="org.freedesktop.intel_lpmd"/>
+ <allow receive_sender="org.freedesktop.intel_lpmd"/>
+ </policy>
+
+</busconfig>
diff --git a/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.service.in b/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.service.in
new file mode 100644
index 000000000000..ec41c6b47595
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.service.in
@@ -0,0 +1,5 @@
+[D-BUS Service]
+Name=org.freedesktop.intel_lpmd
+Exec=/bin/false
+User=root
+SystemdService=org.freedesktop.intel_lpmd.service
diff --git a/tools/power/x86/intel-lpmd/doc/WLT_proxy.md b/tools/power/x86/intel-lpmd/doc/WLT_proxy.md
new file mode 100644
index 000000000000..ffd06fc44994
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/doc/WLT_proxy.md
@@ -0,0 +1,84 @@
+
+WLT (workload type) proxy hints use predefined CPU utilization thresholds and software algorithm to retrieve and detect WLT (same as WLT hints from hardware (WLTEnable)). When proxy hints detection (WLTProxyEnable) is enabled through config option, the hardware WLT hints will be ignored. On detecting workload type, framework takes predefined action with values in config file.
+
+# Pre-requisite
+
+|Workload Type (WLT) |Description |Internal states |EPP/EPB|
+| :---: | :---: |:---: |:---: |
+|Idle |Very low system usage and low power consumption |DEEP_MODE|PS/BAT |
+|BL(Battery Life)|Continuous light system usage with low power consumption |NORM_MODE, RESP_MODE |PS/BAT (platform optimal value)|
+|Sustained |Continuous heavy tasks without idleness|MDRT_MODE INIT_MODE |Perf/AC (platform optimal value)|
+|Bursty |Heavy short tasks with idleness in between |PERF_MODE |Perf/AC|
+
+PS - Power saver; Perf - performance; BAT - Battery bias; AC - AC bias.
+
+# Enabling and leveraging wlt proxy hints for dynamic energy optimization.
+ * Software based WLT Proxy hint overwrites hardware based WLT Hint. To enable proxy hints, both WLT Hint and WLTProxy has to be enabled.
+ ** WLTHintEnable set to Yes
+ <WLTHintEnable>1</WLTHintEnable>
+ ** WLTProxyEnable set to Yes
+ <WLTProxyEnable>1</WLTProxyEnable>
+
+ * WLT Proxy hints are calculated and dynamic energy optimizations are applied only in balanced power profile. Set to auto or force on.
+ ** BalancedDef set to AUTO
+ <BalancedDef>0<BalancedDef>
+
+# Value add
+ with Dynamic EPP [based on workload] enabled on Core Ultra Gen 1 [Meteor Lake H], HP Baymax14W, CDB, PV SKU8W - N15479-021, in Balanced power profile, 10% performance improvement observed on Crossmark and Speedometer benchmarks.
+
+## Known issues
+ * Performance may suffer on some use cases.
+ ** As algorithm currently takes average of all p-cores and e-cores utilization to identify workload type, solution has limitation of identifying single threaded workloads and memory related workloads. Benchmarks like Geekbench ST, WebXprt, Stream may not show improvement compared to Geekbench MT, Speedometer and Crossmark.
+
+# Workload detection algorithm - pseudo code
+ * System CPU utilization thresholds and conditions are predefined and mapped to workload type.
+ * Operating CPU frequency, Spike count rate, state stay time counter and operating frequency-voltage points makes up state switch conditions.
+ * Enabling WLT proxy through config file calls WLT proxy handler.
+ * On WLT Proxy handler,
+ ** CPU load retrieved from system through perf MSR registers (system snapshot)
+ ** State machine, switch state when predefined conditions are met
+ ** Apply state actions
+ ** Calculate/set new timer interval
+ * When timer expires, WLT Proxy handler called again
+
+# WLT proxy states
+| | Init | Perf | Mod4e | Mod3e | Mod2e | Resp | Normal | deep |
+| :---: |:---: |:---: | :---: | :---: | :---: | :---: | :---: | :---: |
+| init | x | [1 cpu].lo < 10 utilization| -| -| -| -| -| - |
+| Perf | [all cpu].lo > 10 utilization| x| -| C0 max < 10%| -| sum_c0 util < 20% && sma avg < 70 %| -| - |
+| MOD4E | - | C0_max > 90%| x| -| -| worst_stall < 70%| sma_avg1 < 25 AND sma_avg2 < 25 AND sum_c0 < 50%| - |
+| MOD3E | - | C0_max > 90%| sma_avg1 > 25 AND sma_avg2 > 20| x| sma_avg1 b.w 4 and 25 AND sma_avg2 b/w 4 and 25| worst_stall < 70%| sma_avg1 < 4 AND sma_avg2 < 2 AND sma_avg3 < 2| - |
+| MOD2E | - | - | -| C0_max > 90% OR sma_avg1 > 25 AND sma_avg2 > 15| x| sorst_stall < 70%| sma_avg1 b/w 4 and 25 AND sma_avg2 < 25 countdown and switch| - |
+| Resp | - | C0_max > 70% && sma_avg1 > 40%| -| worst stall > 70%| -| x| -| - |
+| Normal| - | - | -| -| C0_max > 50% OR sma_avg1 > 40| worst stall < 70%| x| C0_max < 10% AND C0_2ndMax < 1% OR sma_avg1 < 2%; countdown and switch |
+| Deep | - | - | -| -| -| worst_stall < 70%| C0_max > 35%| x |
+
+x – invalid/same state; - not allowed state
+
+Variables used in state switch condition:
+* Multithreaded workload: all applicable CPUs for state utilized more that 10%. Not multithread workload: at least one CPU is utilized < 10%.
+* CPU.L0= 100 * perf_stats[t].mperf_diff / perf_stats[t].tsc_diff
+ * Also following values will be calculated based on L0. C0_max , Min_load[C0.min]; max_load, max_2nd, max_3rd
+* sum_c0 = grp.c0_max + grp.c0_2nd_max + grp.c0_3rd_max
+* sma - simple moving average [tracks 3 max utilizations]
+* worst stall - perf_stats[t].pperf_diff / perf_stats[t].aperf_diff. cpu in wait due to memory of other dependency.
+
+# Files & functionality
+
+## wlt_proxy.c
+ * wlt proxy detection interface file
+ * handles wlt_proxy enable/disable; entry/exit, timer expiry handler
+## state_machine.c
+ * handle current state
+ * determine state change
+## spike_mgmt.c
+ * counts CPU utilization spike counts in given period
+ * handles bursty workload type detection entry and exit
+## state_util.c
+ * retrieval of pref, HFM and SMA
+ * calculations for state switch
+## state_manager.c
+ * state definition & management
+ * state initialization/deinitialization
+ * mapping state to workload type
+ * polling frequency calculations
diff --git a/tools/power/x86/intel-lpmd/lpmd-resource.gresource.xml b/tools/power/x86/intel-lpmd/lpmd-resource.gresource.xml
new file mode 100644
index 000000000000..fe1c028f9858
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/lpmd-resource.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/freedesktop/intel_lpmd">
+ <file preprocess="xml-stripblanks">src/intel_lpmd_dbus_interface.xml</file>
+ </gresource>
+</gresources>
diff --git a/tools/power/x86/intel-lpmd/man/intel_lpmd.8 b/tools/power/x86/intel-lpmd/man/intel_lpmd.8
new file mode 100644
index 000000000000..9d2484e3adff
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/man/intel_lpmd.8
@@ -0,0 +1,79 @@
+.\" SPDX-License-Identifier: GPL-2.0-only
+.\" Copyright (C) 2026 Intel Corporation */
+.TH intel_lpmd "8" "08 Apr 2026"
+
+.SH NAME
+intel_lpmd \- Intel Energy Optimizer (LPMD) Daemon
+.SH SYNOPSIS
+.B intel_lpmd
+.RI " [ " OPTIONS " ]
+
+.SH DESCRIPTION
+.B intel_lpmd
+is a Linux daemon used for energy optimization on Intel hybrid systems. This
+daemon uses a configuration file called "intel_lpmd_config.xml". The other
+installed configuration files are to be treated as best practice templates. The
+main configuration file should be edited by the user to enable LPMD and it's
+desired functionalities. Based on the configuration and system utilization LPMD
+can:
+
+- choose the best set of CPU cores to enable for a given moment in time
+
+- choose the best EPP (energe performance preference) and EPB (energy
+performance bias) value
+
+- pick the best SoC power slider settings
+
+There is a control utility distributed along with this daemon. It's called
+"intel_lpmd_control" and it can be used to change modes in the running LPMD
+instance.
+
+.SH OPTIONS
+.TP
+.B -h --help
+Print the help message
+
+.TP
+.B --version
+Print intel_lpmd version and exit
+
+.TP
+.B --no-daemon
+Don't run as a daemon: Default is daemon mode
+
+.TP
+.B --systemd
+Assume daemon is started by systemd
+
+.TP
+.B --loglevel=
+log severity: can be info or debug
+
+.TP
+.B --dbus-enable
+Enable Dbus server to receive requests via Dbus
+
+.TP
+.B --ignore-platform-check
+Ignore platform check
+
+.SH EXAMPLES
+.TP
+.B intel_lpmd --loglevel=info --no-daemon --dbus-enable
+Run intel_lpmd with log directed to stdout
+
+.TP
+.B intel_lpmd --systemd --dbus-enable
+Run intel_lpmd as a service with logs directed to system journal
+
+.TP
+.B sudo systemctl enable intel_lpmd.service --now
+Enable and run intel_lpmd as a systemd service that's persistent after system
+reboot.
+
+.SH SEE ALSO
+intel_lpmd_config.xml(5), intel_lpmd_control(8)
+
+.SH AUTHOR
+Written by Zhang Rui <rui.zhang@xxxxxxxxx>
+
diff --git a/tools/power/x86/intel-lpmd/man/intel_lpmd_config.xml.5 b/tools/power/x86/intel-lpmd/man/intel_lpmd_config.xml.5
new file mode 100644
index 000000000000..45320307fd24
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/man/intel_lpmd_config.xml.5
@@ -0,0 +1,329 @@
+.\" SPDX-License-Identifier: GPL-2.0-only
+.\" Copyright (C) 2026 Intel Corporation */
+.TH intel_lpmd_config.xml "5" "8 Apr 2026"
+
+.SH NAME
+intel_lpmd_config.xml \- Configuration file for intel_lpmd
+.SH SYNOPSIS
+$(TDCONFDIR)/etc/intel_lpmd/intel_lpmd_config.xml
+
+.SH DESCRIPTION
+.B intel_lpmd_config.xml
+is a configuration file for the Intel Low Power Mode Daemon. It's used to
+describe states and rules of switching between the states. These states are
+custom, user defined sets of parameters that the platform should use for optimal
+performance and energy saving. The rules of switching refer to what system
+information dictates whether a change of state is needed or not. It can be
+either information from a hardware source or software calculated if the hardware
+features are not supported.
+
+.PP
+.B lp_mode_cpus
+is a set of active CPUs when system is in Low Power Mode. This usually equals a
+group of most power efficient CPUs on a platform to achieve best power saving.
+When not specified, intel_lpmd tool can detect this automatically. E.g. it uses
+L-cores on Intel PantherLake platform, the E-core Module on Intel Alderlake
+platform, and it uses the Low Power E-cores on SoC Die on Intel Meteorlake
+platform.
+.PP
+.B Mode
+specifies the way to migrate the tasks to the lp_mode_cpus.
+.IP \(bu 2
+Mode 0: set cpuset to the lp_mode_cpus for systemd. All tasks created by
+systemd will run on these CPUs only. This is supported for cgroup v2 based
+systemd only.
+.IP \(bu 2
+Mode 1: Isolate the non-lp_mode_cpus so that tasks are scheduled to the
+lp_mode_cpus only.
+.IP \(bu 2
+Mode 2: Force idle injection to the non-lp_mode_cpus and leverage the
+scheduler to schedule the other tasks to the lp_mode_cpus.
+.PP
+.B PerformanceDef / BalancedDef / PowersaverDef
+specifies the default behavior for a given power profile.
+.IP \(bu 2
+-1 : Never enter Low Power Mode.
+.IP \(bu 2
+0 : opportunistic Low Power Mode enter/exit based on HFI/Utilization request.
+.IP \(bu 2
+1 : Always stay in Low Power Mode.
+.PP
+.B HfiLpmEnable
+specifies if the HFI monitor can capture the HFI hints for Low Power Mode.
+.PP
+.B HfiSuvEnable
+specifies if the HFI monitor can capture the HFI hints for survivability mode.
+.PP
+.B WLTHintEnable
+Enable use of hardware Workload type hints.
+.PP
+.B WLTProxyEnable
+Enable use of Proxy Workload type hints.
+.PP
+.B util_entry_threshold
+specifies the system utilization threshold for entering Low Power Mode.
+The system workload is considered to fit the lp_mode_cpus capacity when system
+utilization is under this threshold.
+Setting to 0 or leaving this empty disables the utilization monitor.
+.PP
+.B util_exit_threshold
+specifies the CPU utilization threshold for exiting Low Power Mode.
+The system workload is considered to not fit the lp_mode_cpus capacity when
+the utilization of the busiest lp_mode_cpus is above this threshold.
+Setting to 0 or leaving this empty disables the utilization monitor.
+.PP
+.B IgnoreITMT
+Avoid changing scheduler ITMT flag. This means that during transition to
+low power mode, ITMT flag is not changed. This reduces latency during
+switching. This flag is not used when configuration uses "State" based
+configuration, where this flag can be defined per state. ITMT is only relevant
+for platforms before Lunar Lake and the debugfs file is not present on later
+platforms.
+.PP
+.B States
+Allows one to define per platform low power states. Each state defines
+has an entry condition and set of parameters to use.
+
+.SH State Definition
+There can be multiple state configurations present. Each configuration is valid
+for a platform. A state header defines parameters, which are used to match a
+platform.
+.B CPUFamily
+CPU generation to match.
+.PP
+.B CPUModel
+CPU model to match.
+.PP
+.B CPUConfig
+Define a configuration of CPUs and TDP to match different skews for the
+same CPU model and family. CPU configuration string format is:
+xPyEzL-tdpW. For example: 12P8E2L-28W, defines a platform with 6 P-cores
+with hyper threading enabled, 8 E cores, 2 LPE cores and the TDP is 28W.
+This configuration allows wildcard "*" to match any combination.
+
+.SH Per State Definition
+Each "State" defines entry criteria and parameters to use.
+.B ID
+A unique ID for the state.
+.PP
+.B Name
+A name for the state.
+.PP
+.B EntrySystemLoadThres
+System Entry load threshold in percent. System utilization is different
+based on the number of active CPUs in a configuration. This value
+is calculated from /proc/stat sysfs. To enter into this state, the
+system utilization must be less or equal to this value.
+.PP
+.B EnterCPULoadThres
+CPU Entry load threshold in percent. Per CPU utilization is also obtained
+from /proc/stat. To enter into this state any active CPU utilization must
+be less or equal to this value.
+EnterCPULoadThres is checked before EntrySystemLoadThres to match a state.
+.PP
+.B WLTType
+Workload type value to enter into this state. If this value is defined
+then utilization based entry triggers are not used. To use this
+WLTHintEnable must be enabled, so that hardware notifications are enabled.
+.PP
+.B ActiveCPUs
+Active CPUs in this state. The list can be comma separated or use "-" for
+a range. This is optional to have active CPUs in a state.
+.PP
+.B EPP
+EPP to apply for this state. -1 to ignore.
+.PP
+.B EPB
+EPB to apply for this state. -1 to ignore.
+.PP
+.B ITMTState
+Set the state of ITMT flag. -1 to ignore. ITMT is only relevant for platforms
+before MTL and the debugfs file is not present on later platforms.
+.PP
+.B IRQMigrate
+Migrate IRQs to the active CPUs in this state. -1 to ignore.
+.PP
+.B MinPollInterval
+Minimum polling interval in milliseconds.
+.PP
+.B MaxPollInterval
+Maximum polling interval in milli seconds. This is optional,
+if there is no maximum is desired.
+.PP
+.B PollIntervalIncrement
+Polling interval increment in milli seconds. If this value
+is -1, then polling increment is adaptive based on the utilization.
+
+
+.SH FILE FORMAT
+The configuration file format conforms to XML specifications.
+.sp 1
+.EX
+<Configuration>
+ <!--
+ CPU format example: 1,2,4..6,8-10
+ -->
+ <lp_mode_cpus>Example CPUs</lp_mode_cpus>
+
+ <!--
+ Mode values
+ 0: Cgroup v2
+ 1: Cgroup v2 isolate
+ 2: CPU idle injection
+ -->
+ <Mode>0|1|2</Mode>
+
+ <!--
+ Default behavior when Performance power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PerformanceDef>-1|0|1</PerformanceDef>
+
+ <!--
+ Default behavior when Balanced power setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <BalancedDef>-1|0|1</BalancedDef>
+
+ <!--
+ Default behavior when Power saver setting is used
+ -1: force off. (Never enter Low Power Mode)
+ 1: force on. (Always stay in Low Power Mode)
+ 0: auto. (opportunistic Low Power Mode enter/exit)
+ -->
+ <PowersaverDef>-1|0|1</PowersaverDef>
+
+ <!--
+ Use HFI LPM hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiLpmEnable>0|1</HfiLpmEnable>
+
+ <!--
+ Use HFI SUV hints
+ 0 : No
+ 1 : Yes
+ -->
+ <HfiSuvEnable>0|1</HfiSuvEnable>
+
+ <!--
+ System utilization threshold to enter LP mode
+ from 0 - 100
+ -->
+ <util_entry_threshold>Example threshold</util_entry_threshold>
+
+ <!--
+ System utilization threshold to exit LP mode
+ from 0 - 100
+ -->
+ <util_exit_threshold>Example threshold</util_exit_threshold>
+
+ <!--
+ EPP to use in Low Power Mode
+ 0-255: Valid EPP value to use in Low Power Mode
+ -1: Don't change EPP in Low Power Mode
+ -->
+ <lp_mode_epp>-1 | EPP value</lp_mode_epp>
+
+</Configuration>
+
+.EE
+.SH EXAMPLE CONFIGURATIONS
+.PP
+.B Example 1:
+This is the minimum configuration.
+.IP \(bu 2
+lp_mode_cpus: not set. Detects the lp_mode_cpus automatically.
+.IP \(bu 2
+Mode: 0. Use cgroup-v2 systemd for task migration.
+.IP \(bu 2
+HfiLpmEnable: 0. Ignore HFI Low Power mode hints.
+.IP \(bu 2
+HfiSuvEnable: 0. Ignore HFI Survivability mode hints. With both HfiLpmEnable and HfiSuvEnable cleared, the HFI monitor will be disabled.
+.IP \(bu 2
+util_entry_threshold: 0. Disable utilization monitor.
+.IP \(bu 2
+util_exit_threshold: 0. Disable utilization monitor.
+.IP \(bu 2
+lp_mode_epp: -1. Do not change EPP when entering Low Power Mode.
+
+.sp 1
+.EX
+<?xml version="1.0"?>
+<Configuration>
+ <lp_mode_cpus></lp_mode_cpus>
+ <Mode>0</Mode>
+ <HfiLpmEnable>0</HfiLpmEnable>
+ <HfiSuvEnable>0</HfiSuvEnable>
+ <util_entry_threshold>0</util_entry_threshold>
+ <util_exit_threshold>0</util_exit_threshold>
+ <lp_mode_epp>-1</lp_mode_epp>
+</Configuration>
+.PP
+.B Example 2:
+This is the typical configuration. The utilization thresholds and delays may be different based on requirement.
+.IP \(bu 2
+lp_mode_cpus: not set. Detects the lp_mode_cpus automatically.
+.IP \(bu 2
+Mode: 0. Use cgroup-v2 systemd for task migration.
+.IP \(bu 2
+HfiLpmEnable: 1. Enter/Exit Low Power Mode based on HFI hints.
+.IP \(bu 2
+HfiSuvEnable: 1. Enter/Exit Survivability mode based on HFI hints.
+.IP \(bu 2
+util_entry_threshold: 5. Enter Low Power Mode when system utilization is lower than 5%.
+.IP \(bu 2
+util_exit_threshold: 95. Exit Low Power Mode when the utilization of any of the lp_mode_cpus is higher than 95%.
+.IP \(bu 2
+lp_mode_epp: -1. Do not change EPP when entering Low Power Mode.
+
+.sp 1
+.EX
+<?xml version="1.0"?>
+<Configuration>
+ <lp_mode_cpus></lp_mode_cpus>
+ <Mode>0</Mode>
+ <HfiLpmEnable>1</HfiLpmEnable>
+ <HfiSuvEnable>1</HfiSuvEnable>
+ <util_entry_threshold>5</util_entry_threshold>
+ <util_exit_threshold>95</util_exit_threshold>
+ <lp_mode_epp>-1</lp_mode_epp>
+</Configuration>
+.EE
+
+.SH Currently unimplemented options
+.PP
+.B EntryDelayMS
+specifies the sample interval used by the utilization Monitor when system
+wants to enter Low Power Mode based on system utilization.
+Setting to 0 or leaving this empty will cause the utilization Monitor to use
+the default interval, 1000 milli seconds.
+.PP
+.B ExitDelayMS
+specifies the sample interval used by the utilization Monitor when system
+wants to exit Low Power Mode based on CPU utilization.
+Setting to 0 or leaving this empty will cause the utilization Monitor to use
+the adaptive value. The adaptive interval is based on CPU utilization.
+The busier the CPU is, the shorter interval the utilization monitor uses.
+.PP
+.B EntryHystMS
+specifies a hysteresis threshold when system is in Low Power Mode.
+If set, when the previous average time stayed in Low Power Mode is lower than
+this value, the current enter Low Power Mode request will be ignored because
+it is expected that the system will exit Low Power Mode soon.
+Setting to 0 or leaving this empty disables this hysteresis algorithm.
+.PP
+.B ExitHystMS
+specifies a hysteresis threshold when system is not in Low Power Mode.
+If set, when the previous average time stayed out of Low-Power-Mode is lower
+than this value, the current exit Low Power Mode request will be ignored
+because it is expected that the system will enter Low Power Mode soon.
+Setting to 0 or leaving this empty disables this hysteresis algorithm.
+
+.SH SEE ALSO
+intel_lpmd(8), intel_lpmd_control(8)
diff --git a/tools/power/x86/intel-lpmd/man/intel_lpmd_control.8 b/tools/power/x86/intel-lpmd/man/intel_lpmd_control.8
new file mode 100644
index 000000000000..0a4de5a7f834
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/man/intel_lpmd_control.8
@@ -0,0 +1,44 @@
+.\" SPDX-License-Identifier: GPL-2.0-only
+.\" Copyright (C) 2026 Intel Corporation */
+.TH intel_lpmd_control "8" "8 Apr 2026"
+
+.SH NAME
+intel_lpmd_control \- Control utility for the Intel Low Power Mode Daemon (LPMD)
+.SH SYNOPSIS
+.B intel_lpmd_control
+.RI " [ " OPTIONS " ]
+
+.SH DESCRIPTION
+.B intel_lpmd_control
+is a command-line utility used to control the Intel Low Power Mode Daemon
+(LPMD). It allows users to enable, disable, or set the daemon to automatic mode
+based on system utilization.
+
+.SH OPTIONS
+.TP
+.B ON
+Enables low power mode operation.
+.TP
+.B OFF
+Disables low power mode operation.
+.TP
+.B AUTO
+Enables operation in automatic mode, allowing system utilization to determine
+low power state activation.
+
+.SH EXAMPLES
+.TP
+.B intel_lpmd_control ON
+Turns on low power mode operation.
+.TP
+.B intel_lpmd_control OFF
+Turns off low power mode operation.
+.TP
+.B intel_lpmd_control AUTO
+Turns on low power mode operation in automatic mode.
+
+.SH SEE ALSO
+.B intel_lpmd_config.xml(5), intel_lpmd(8)
+
+.SH AUTHOR
+Written by Deepak Sundar <deepak.sundar@xxxxxxxxx>
diff --git a/tools/power/x86/intel-lpmd/src/include/lpmd.h b/tools/power/x86/intel-lpmd/src/include/lpmd.h
new file mode 100644
index 000000000000..996ca082f29d
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/include/lpmd.h
@@ -0,0 +1,408 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef LPMD_INTEL_LPMD_H
+#define LPMD_INTEL_LPMD_H
+
+#include <stdio.h>
+#include <getopt.h>
+#include <locale.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <signal.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <string.h>
+#include <signal.h>
+#include <poll.h>
+#include <cpuid.h>
+
+#include "config.h"
+#include "thermal.h"
+
+#define LOG_DEBUG_INFO 1
+
+#define LOCKF_SUPPORT
+
+#ifdef GLIB_SUPPORT
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gmodule.h>
+
+extern gint watcher_id;
+
+// Log macros
+enum log_level {
+ LPMD_LOG_NONE,
+ LPMD_LOG_INFO,
+ LPMD_LOG_DEBUG,
+ LPMD_LOG_MSG,
+ LPMD_LOG_WARN,
+ LPMD_LOG_ERROR,
+ LPMD_LOG_FATAL,
+};
+
+#define lpmd_log_fatal g_error // Print error and terminate
+#define lpmd_log_error g_critical
+#define lpmd_log_warn g_warning
+#define lpmd_log_msg g_message
+#define lpmd_log_debug g_debug
+#define lpmd_log_info(...) g_log(NULL, G_LOG_LEVEL_INFO, __VA_ARGS__)
+#else
+static int dummy_printf(const char *__restrict __format, ...)
+{
+ return 0;
+}
+
+#define lpmd_log_fatal printf
+#define lpmd_log_error printf
+#define lpmd_log_warn printf
+#define lpmd_log_msg printf
+#define lpmd_log_debug dummy_printf
+#define lpmd_log_info printf
+#endif
+
+// Common return value defines
+#define LPMD_SUCCESS 0
+#define LPMD_ERROR -1
+#define LPMD_FATAL_ERROR -2
+
+// Dbus related
+/* Well-known name for this service. */
+#define INTEL_LPMD_SERVICE_NAME "org.freedesktop.intel_lpmd"
+#define INTEL_LPMD_SERVICE_OBJECT_PATH "/org/freedesktop/intel_lpmd"
+#define INTEL_LPMD_SERVICE_INTERFACE "org.freedesktop.intel_lpmd"
+
+enum message_name_t {
+ TERMINATE, LPM_FORCE_ON, LPM_FORCE_OFF, LPM_AUTO, HFI_EVENT,
+};
+
+#define MAX_MSG_SIZE 512
+
+struct message_capsul_t {
+ enum message_name_t msg_id;
+ int msg_size;
+ unsigned long msg[MAX_MSG_SIZE];
+};
+
+#define MAX_STR_LENGTH 256
+#define MAX_CONFIG_STATES 10
+#define MAX_STATE_NAME 32
+#define MAX_CONFIG_LEN 64
+
+enum lpmd_states {
+ LPMD_OFF,
+ LPMD_ON,
+ LPMD_AUTO,
+ LPMD_FREEZE,
+ LPMD_RESTORE,
+ LPMD_TERMINATE,
+};
+
+enum lpmd_update_reason {
+ UPDATE_USER,
+ UPDATE_UTIL,
+ UPDATE_HFI,
+ UPDATE_CPUHOTPLUG,
+ UPDATE_WLT,
+};
+
+struct lpmd_data_t {
+ int util_cpu; /* From Util monitor */
+ int util_sys; /* From Util monitor */
+ int util_gfx; /* From Util monitor */
+ int wlt_hint; /* From WLT monitor */
+ int polling_interval;
+ int need_update;
+};
+
+enum default_config_state {
+ DEFAULT_OFF, /* lpmd force off: state with all default power settings */
+ DEFAULT_ON, /* lpmd force on: state with global CPU/IRQ/ITMT/EPP configurations */
+ DEFAULT_HFI, /* LPM state with CPU isolation based on HFI hints only */
+ CONFIG_STATE_BASE,
+ MAX_STATES = CONFIG_STATE_BASE + MAX_CONFIG_STATES,
+ STATE_NONE = MAX_STATES,
+};
+
+struct lpmd_config_state_t {
+ int id;
+ int valid;
+ char name[MAX_STATE_NAME];
+ int wlt_type;
+ int entry_system_load_thres;
+ int exit_system_load_thres;
+ int exit_system_load_hyst;
+ int enter_cpu_load_thres;
+ int exit_cpu_load_thres;
+ int enter_gfx_load_thres;
+ int exit_gfx_load_thres;
+ int min_poll_interval;
+ int max_poll_interval;
+ int poll_interval_increment;
+ int epp;
+ int epb;
+ char active_cpus[MAX_STR_LENGTH];
+ // If active CPUs are specified then
+ // the below counts don't matter
+ int island_0_number_p_cores;
+ int island_0_number_e_cores;
+ int island_1_number_p_cores;
+ int island_1_number_e_cores;
+ int island_2_number_p_cores;
+ int island_2_number_e_cores;
+
+ int itmt_state;
+ int irq_migrate;
+
+ int balance_slider_ac;
+ int slider_offset_ac;
+ int balance_slider_dc;
+ int slider_offset_dc;
+
+ // Private state variables, not configurable
+ int entry_load_sys;
+ int entry_load_cpu;
+ int cpumask_idx;
+ int steady;
+};
+
+// lpmd config data
+struct lpmd_config_t {
+ int mode;
+ int performance_def;
+ int balanced_def;
+ int powersaver_def;
+ int hfi_lpm_enable;
+ int wlt_hint_enable;
+ int wlt_hint_poll_enable;
+ int wlt_proxy_enable;
+ int wlt_hint_mask;
+
+ union {
+ struct {
+ uint32_t util_sys_enable:1;
+ uint32_t util_cpu_enable:1;
+ uint32_t util_gfx_enable:1;
+ uint32_t util_reserved:29;
+ };
+ uint32_t util_enable;
+ };
+ int util_entry_threshold;
+ int util_exit_threshold;
+ int util_entry_delay;
+ int util_exit_delay;
+ int util_entry_hyst;
+ int util_exit_hyst;
+ int ignore_itmt;
+ int lp_mode_epp;
+ char lp_mode_cpus[MAX_STR_LENGTH];
+ int cpu_family;
+ int cpu_model;
+ char cpu_config[MAX_CONFIG_LEN];
+ int config_state_count;
+ int tdp;
+
+ int balance_slider_def_ac;
+ int slider_offset_def_ac;
+ int balance_slider_def_dc;
+ int slider_offset_def_dc;
+
+ struct lpmd_config_state_t config_states[MAX_STATES];
+ struct lpmd_data_t data;
+};
+
+enum lpm_cpu_process_mode {
+ LPM_CPU_CGROUPV2,
+ LPM_CPU_ISOLATE,
+ LPM_CPU_POWERCLAMP,
+ LPM_CPU_OFFLINE,
+ LPM_CPU_MODE_MAX = LPM_CPU_POWERCLAMP,
+};
+
+#define NUM_USER_CPUMASKS 10
+enum cpumask_idx {
+ CPUMASK_LPM_DEFAULT,
+ CPUMASK_ONLINE,
+ CPUMASK_HFI,
+ CPUMASK_HFI_BANNED,
+ CPUMASK_HFI_LAST,
+ CPUMASK_UTIL,
+ CPUMASK_USER,
+ CPUMASK_MAX = CPUMASK_USER + NUM_USER_CPUMASKS,
+ CPUMASK_NONE = CPUMASK_MAX,
+};
+
+#define UTIL_DELAY_MAX 5000
+#define UTIL_HYST_MAX 10000
+
+#define cpuid(leaf, eax, ebx, ecx, edx) \
+ do { \
+ __cpuid(leaf, eax, ebx, ecx, edx); \
+ lpmd_log_debug("CPUID 0x%08x: eax = 0x%08x ebx = 0x%08x ecx = 0x%08x edx = 0x%08x\n", \
+ leaf, eax, ebx, ecx, edx); \
+ } while (0)
+
+#define cpuid_count(leaf, subleaf, eax, ebx, ecx, edx) \
+ do { \
+ __cpuid_count(leaf, subleaf, eax, ebx, ecx, edx); \
+ lpmd_log_debug("CPUID 0x%08x subleaf 0x%08x: eax = 0x%08x ebx = 0x%08x ecx = 0x%08x" \
+ "edx = 0x%08x\n", leaf, subleaf, eax, ebx, ecx, edx); \
+ } while (0)
+
+#define SETTING_RESTORE -2
+#define SETTING_IGNORE -1
+
+/* WLT hints parsing */
+enum wlt_type_t {
+ WLT_IDLE = 0,
+ WLT_BATTERY_LIFE = 1,
+ WLT_SUSTAINED = 2,
+ WLT_BURSTY = 3,
+ WLT_INVALID = 4,
+};
+
+enum power_profile_daemon_mode {
+ PPD_PERFORMANCE,
+ PPD_BALANCED,
+ PPD_POWERSAVER,
+ PPD_INVALID
+};
+
+#define DEF_POLLING_INTERVAL 100
+
+/* lpmd_main.c */
+int in_debug_mode(void);
+int do_platform_check(void);
+
+/* lpmd_proc.c: interfaces */
+struct lpmd_config_t *get_lpmd_config(void);
+
+int lpmd_lock(void);
+int lpmd_unlock(void);
+
+void lpmd_terminate(void);
+void lpmd_force_on(void);
+void lpmd_force_off(void);
+void lpmd_set_auto(void);
+
+int is_on_battery(void);
+int get_ppd_mode(void);
+
+/* lpmd_proc.c: init func */
+int lpmd_main(void);
+void update_reason(int reason);
+
+/* lpmd_dbus_server.c */
+int intel_dbus_server_init(gboolean (*exit_handler)(void));
+
+/* lpmd_config.c */
+int lpmd_get_config(struct lpmd_config_t *lpmd_config);
+
+/* lpmd_state_machine.c */
+int update_lpmd_state(int state);
+int get_lpmd_state(void);
+int lpmd_init_config_state(struct lpmd_config_state_t *state);
+int lpmd_build_config_states(struct lpmd_config_t *config);
+int lpmd_enter_next_state(void);
+
+/* lpmd_util.c */
+int util_update(struct lpmd_config_t *lpmd_config);
+
+/* lpmd_hfi.c */
+int hfi_init(void);
+int hfi_kill(void);
+int hfi_update(void);
+
+/* lpmd_wlt.c */
+int wlt_init(void);
+int wlt_exit(void);
+int wlt_update(int fd);
+
+/* lpmd_misc.c */
+void itmt_init(void);
+int get_itmt(void);
+int process_itmt(struct lpmd_config_state_t *state);
+
+int epp_epb_init(void);
+int get_epp_epb(int *epp, char *epp_str, int size, int *epb);
+int process_epp_epb(struct lpmd_config_state_t *state);
+
+void process_balance_slider_default_update(struct lpmd_config_t *config);
+void process_slider_offset_default_update(struct lpmd_config_t *config);
+void process_slider(struct lpmd_config_t *config, struct lpmd_config_state_t *state);
+
+/* lpmd_irq.c */
+int irq_init(void);
+int process_irq(struct lpmd_config_state_t *state);
+
+/* lpmd_cgroup.c*/
+int cgroup_init(struct lpmd_config_t *config);
+int cgroup_cleanup(void);
+int process_cgroup(struct lpmd_config_state_t *state, enum lpm_cpu_process_mode mode);
+
+/* lpmd_uevent.c */
+int uevent_init(void);
+int check_cpu_hotplug(void);
+
+/* lpmd_cpu.c */
+int detect_supported_platform(struct lpmd_config_t *lpmd_config);
+int detect_cpu_topo(struct lpmd_config_t *lpmd_config);
+int detect_lpm_cpus(char *cmd_cpus);
+
+int is_cpu_ecore(int cpu);
+int is_cpu_pcore(int cpu);
+
+/* lpmd_cpumask.c */
+int is_cpu_online(int cpu);
+int get_max_cpus(void);
+void set_max_cpus(int num);
+int get_max_online_cpu(void);
+void set_max_online_cpu(int num);
+int cpu_migrate(int cpu);
+int cpu_clear_affinity(void);
+
+int cpumask_alloc(void);
+int cpumask_free(enum cpumask_idx idx);
+int cpumask_reset(enum cpumask_idx idx);
+
+int cpumask_add_cpu(int cpu, enum cpumask_idx idx);
+int cpumask_init_cpus(char *buf, enum cpumask_idx idx);
+int cpumask_nr_cpus(enum cpumask_idx idx);
+int cpumask_has_cpu(enum cpumask_idx idx);
+
+int cpumask_equal(enum cpumask_idx idx1, enum cpumask_idx idx2);
+void cpumask_copy(enum cpumask_idx source, enum cpumask_idx dest);
+void cpumask_exclude_copy(enum cpumask_idx source, enum cpumask_idx dest, enum cpumask_idx exclude);
+
+char *get_cpus_str(enum cpumask_idx idx);
+char *get_cpus_hexstr(enum cpumask_idx idx);
+char *get_proc_irq_str(enum cpumask_idx idx);
+char *get_irqbalance_str(enum cpumask_idx idx);
+char *get_cpu_isolation_str(enum cpumask_idx idx);
+uint8_t *get_cgroup_systemd_vals(enum cpumask_idx idx, int *size);
+
+/* socket.c */
+int socket_init_connection(char *name);
+int socket_send_cmd(char *name, char *data);
+
+/* helper */
+int copy_user_string(char *src, char *dst, int size);
+int lpmd_read_str(char *path, char *str, int size);
+int lpmd_write_str(const char *name, char *str, int print_level);
+int lpmd_write_str_verbose(const char *name, char *str, int print_level);
+int lpmd_write_str_append(const char *name, char *str, int print_level);
+int lpmd_write_int(const char *name, int val, int print_level);
+int lpmd_open(const char *name, int print_level);
+int lpmd_read_int(const char *name, int *val, int print_level);
+int lpmd_read_yn(const char *name, int *val, int print_level);
+int lpmd_write_yn(const char *name, int val, int print_level);
+char *get_time(void);
+void time_start(void);
+char *time_delta(void);
+uint64_t read_msr(int cpu, uint32_t msr);
+#endif
diff --git a/tools/power/x86/intel-lpmd/src/include/thermal.h b/tools/power/x86/intel-lpmd/src/include/thermal.h
new file mode 100644
index 000000000000..e2d2eb1eb0b6
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/include/thermal.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _UAPI_LINUX_THERMAL_H
+#define _UAPI_LINUX_THERMAL_H
+
+#define THERMAL_NAME_LENGTH 20
+
+enum thermal_device_mode {
+ THERMAL_DEVICE_DISABLED = 0, THERMAL_DEVICE_ENABLED,
+};
+
+enum thermal_trip_type {
+ THERMAL_TRIP_ACTIVE = 0, THERMAL_TRIP_PASSIVE, THERMAL_TRIP_HOT, THERMAL_TRIP_CRITICAL,
+};
+
+/* Adding event notification support elements */
+#define THERMAL_GENL_FAMILY_NAME "thermal"
+#define THERMAL_GENL_VERSION 0x01
+#define THERMAL_GENL_SAMPLING_GROUP_NAME "sampling"
+#define THERMAL_GENL_EVENT_GROUP_NAME "event"
+
+/* Attributes of thermal_genl_family */
+enum thermal_genl_attr {
+ THERMAL_GENL_ATTR_UNSPEC,
+ THERMAL_GENL_ATTR_TZ,
+ THERMAL_GENL_ATTR_TZ_ID,
+ THERMAL_GENL_ATTR_TZ_TEMP,
+ THERMAL_GENL_ATTR_TZ_TRIP,
+ THERMAL_GENL_ATTR_TZ_TRIP_ID,
+ THERMAL_GENL_ATTR_TZ_TRIP_TYPE,
+ THERMAL_GENL_ATTR_TZ_TRIP_TEMP,
+ THERMAL_GENL_ATTR_TZ_TRIP_HYST,
+ THERMAL_GENL_ATTR_TZ_MODE,
+ THERMAL_GENL_ATTR_TZ_NAME,
+ THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT,
+ THERMAL_GENL_ATTR_TZ_GOV,
+ THERMAL_GENL_ATTR_TZ_GOV_NAME,
+ THERMAL_GENL_ATTR_CDEV,
+ THERMAL_GENL_ATTR_CDEV_ID,
+ THERMAL_GENL_ATTR_CDEV_CUR_STATE,
+ THERMAL_GENL_ATTR_CDEV_MAX_STATE,
+ THERMAL_GENL_ATTR_CDEV_NAME,
+ THERMAL_GENL_ATTR_GOV_NAME,
+ THERMAL_GENL_ATTR_CAPACITY,
+ THERMAL_GENL_ATTR_CAPACITY_CPU_COUNT,
+ THERMAL_GENL_ATTR_CAPACITY_CPU_ID,
+ THERMAL_GENL_ATTR_CAPACITY_CPU_PERF,
+ THERMAL_GENL_ATTR_CAPACITY_CPU_EFF,
+ __THERMAL_GENL_ATTR_MAX,
+};
+
+#define THERMAL_GENL_ATTR_MAX (__THERMAL_GENL_ATTR_MAX - 1)
+
+enum thermal_genl_sampling {
+ THERMAL_GENL_SAMPLING_TEMP, __THERMAL_GENL_SAMPLING_MAX,
+};
+
+#define THERMAL_GENL_SAMPLING_MAX (__THERMAL_GENL_SAMPLING_MAX - 1)
+
+/* Events of thermal_genl_family */
+enum thermal_genl_event {
+ THERMAL_GENL_EVENT_UNSPEC, THERMAL_GENL_EVENT_TZ_CREATE, /* Thermal zone creation */
+ THERMAL_GENL_EVENT_TZ_DELETE, /* Thermal zone deletion */
+ THERMAL_GENL_EVENT_TZ_DISABLE, /* Thermal zone disabled */
+ THERMAL_GENL_EVENT_TZ_ENABLE, /* Thermal zone enabled */
+ THERMAL_GENL_EVENT_TZ_TRIP_UP, /* Trip point crossed the way up */
+ THERMAL_GENL_EVENT_TZ_TRIP_DOWN, /* Trip point crossed the way down */
+ THERMAL_GENL_EVENT_TZ_TRIP_CHANGE, /* Trip point changed */
+ THERMAL_GENL_EVENT_TZ_TRIP_ADD, /* Trip point added */
+ THERMAL_GENL_EVENT_TZ_TRIP_DELETE, /* Trip point deleted */
+ THERMAL_GENL_EVENT_CDEV_ADD, /* Cdev bound to the thermal zone */
+ THERMAL_GENL_EVENT_CDEV_DELETE, /* Cdev unbound */
+ THERMAL_GENL_EVENT_CDEV_STATE_UPDATE, /* Cdev state updated */
+ THERMAL_GENL_EVENT_TZ_GOV_CHANGE, /* Governor policy changed */
+ THERMAL_GENL_EVENT_CAPACITY_CHANGE, /* CPU capacity changed */
+ __THERMAL_GENL_EVENT_MAX,
+};
+
+#define THERMAL_GENL_EVENT_MAX (__THERMAL_GENL_EVENT_MAX - 1)
+
+/* Commands supported by the thermal_genl_family */
+enum thermal_genl_cmd {
+ THERMAL_GENL_CMD_UNSPEC, THERMAL_GENL_CMD_TZ_GET_ID, /* List of thermal zones id */
+ THERMAL_GENL_CMD_TZ_GET_TRIP, /* List of thermal trips */
+ THERMAL_GENL_CMD_TZ_GET_TEMP, /* Get the thermal zone temperature */
+ THERMAL_GENL_CMD_TZ_GET_GOV, /* Get the thermal zone governor */
+ THERMAL_GENL_CMD_TZ_GET_MODE, /* Get the thermal zone mode */
+ THERMAL_GENL_CMD_CDEV_GET, /* List of cdev id */
+ __THERMAL_GENL_CMD_MAX,
+};
+
+#define THERMAL_GENL_CMD_MAX (__THERMAL_GENL_CMD_MAX - 1)
+
+#endif /* _UAPI_LINUX_THERMAL_H */
diff --git a/tools/power/x86/intel-lpmd/src/intel_lpmd_dbus_interface.xml b/tools/power/x86/intel-lpmd/src/intel_lpmd_dbus_interface.xml
new file mode 100644
index 000000000000..b723034ca107
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/intel_lpmd_dbus_interface.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE node PUBLIC
+ "-//freedesktop//DTD D-Bus Object Introspection 1.0//EN"
+ "http://standards.freedesktop.org/dbus/1.0/introspect.dtd">
+
+<node>
+ <interface name="org.freedesktop.intel_lpmd">
+
+ <method name="Terminate">
+ </method>
+
+ <method name="LPM_FORCE_ON">
+ </method>
+
+ <method name="LPM_FORCE_OFF">
+ </method>
+
+ <method name="LPM_AUTO">
+ </method>
+
+ <method name="GetState">
+ <arg type="s" name="state" direction="out"/>
+ </method>
+
+ </interface>
+</node>
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_cgroup.c b/tools/power/x86/intel-lpmd/src/lpmd_cgroup.c
new file mode 100644
index 000000000000..679b80387766
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_cgroup.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#define _GNU_SOURCE
+#include <systemd/sd-bus.h>
+#include "lpmd.h"
+
+/* Support for LPM_CPU_CGROUPV2 */
+#define PATH_CGROUP "/sys/fs/cgroup"
+#define PATH_CG2_SUBTREE_CONTROL PATH_CGROUP "/cgroup.subtree_control"
+
+static int update_allowed_cpus(const char *unit, uint8_t *vals, int size)
+{
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus_message *m = NULL;
+ char buf[MAX_STR_LENGTH];
+ sd_bus *bus = NULL;
+ int offset;
+ int ret;
+ int i;
+
+ ret = sd_bus_open_system(&bus);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-ret));
+ goto finish;
+ }
+
+ ret = sd_bus_message_new_method_call(bus, &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager", "SetUnitProperties");
+ if (ret < 0) {
+ fprintf(stderr, "Failed to issue method call: %s\n", error.message);
+ goto finish;
+ }
+
+ ret = sd_bus_message_append(m, "sb", unit, 1);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to append unit: %s\n", error.message);
+ goto finish;
+ }
+
+ ret = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, "(sv)");
+ if (ret < 0) {
+ fprintf(stderr, "Failed to append array: %s\n", error.message);
+ goto finish;
+ }
+
+ ret = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+ if (ret < 0) {
+ fprintf(stderr, "Failed to open container struct: %s\n", error.message);
+ goto finish;
+ }
+
+ ret = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, "AllowedCPUs");
+ if (ret < 0) {
+ fprintf(stderr, "Failed to append string: %s\n", error.message);
+ goto finish_1;
+ }
+
+ ret = sd_bus_message_open_container(m, 'v', "ay");
+ if (ret < 0) {
+ fprintf(stderr, "Failed to open container: %s\n", error.message);
+ goto finish_2;
+ }
+
+ ret = sd_bus_message_append_array(m, 'y', vals, size);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to append allowed_cpus: %s\n", error.message);
+ goto finish_2;
+ }
+
+ offset = snprintf(buf, MAX_STR_LENGTH, "\tSending Dbus message to systemd: %s: ", unit);
+ for (i = 0; i < size; i++) {
+ if (offset < MAX_STR_LENGTH)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset, "0x%02x ", vals[i]);
+ }
+ buf[MAX_STR_LENGTH - 1] = '\0';
+ lpmd_log_info("%s\n", buf);
+
+ sd_bus_message_close_container(m);
+
+finish_2:
+ sd_bus_message_close_container(m);
+
+finish_1:
+ sd_bus_message_close_container(m);
+
+finish:
+ if (ret >= 0) {
+ ret = sd_bus_call(bus, m, 0, &error, NULL);
+ if (ret < 0)
+ fprintf(stderr, "Failed to call: %s\n", error.message);
+ }
+
+ sd_bus_error_free(&error);
+ sd_bus_message_unref(m);
+ sd_bus_unref(bus);
+
+ return ret < 0 ? -1 : 0;
+}
+
+static int restore_systemd_cgroup(void)
+{
+ int size;
+ uint8_t *vals;
+
+ vals = get_cgroup_systemd_vals(CPUMASK_ONLINE, &size);
+ if (!vals)
+ return -1;
+
+ update_allowed_cpus("system.slice", vals, size);
+ update_allowed_cpus("user.slice", vals, size);
+ update_allowed_cpus("machine.slice", vals, size);
+
+ return 0;
+}
+
+static int update_systemd_cgroup(struct lpmd_config_state_t *state)
+{
+ int size;
+ uint8_t *vals;
+ int ret;
+
+ vals = get_cgroup_systemd_vals(state->cpumask_idx, &size);
+ if (!vals)
+ return -1;
+
+ ret = update_allowed_cpus("system.slice", vals, size);
+ if (ret)
+ goto restore;
+
+ ret = update_allowed_cpus("user.slice", vals, size);
+ if (ret)
+ goto restore;
+
+ ret = update_allowed_cpus("machine.slice", vals, size);
+ if (ret)
+ goto restore;
+
+ return 0;
+
+restore:
+ restore_systemd_cgroup();
+ return ret;
+}
+
+static int process_cpu_cgroupv2(struct lpmd_config_state_t *state)
+{
+ if (cpumask_equal(state->cpumask_idx, CPUMASK_ONLINE)) {
+ restore_systemd_cgroup();
+ return lpmd_write_str(PATH_CG2_SUBTREE_CONTROL, "-cpuset", LPMD_LOG_DEBUG);
+ }
+
+ if (lpmd_write_str(PATH_CG2_SUBTREE_CONTROL, "+cpuset", LPMD_LOG_DEBUG))
+ return 1;
+ return update_systemd_cgroup(state);
+}
+
+/* Support for cgroup based cpu isolation */
+static int process_cpu_isolate(struct lpmd_config_state_t *state)
+{
+ if (lpmd_write_str("/sys/fs/cgroup/lpm/cpuset.cpus.partition", "member", LPMD_LOG_DEBUG))
+ return 1;
+
+ if (!cpumask_equal(state->cpumask_idx, CPUMASK_ONLINE)) {
+ if (lpmd_write_str("/sys/fs/cgroup/lpm/cpuset.cpus.exclusive", get_cpu_isolation_str(state->cpumask_idx), LPMD_LOG_DEBUG))
+ return 1;
+ if (lpmd_write_str("/sys/fs/cgroup/lpm/cpuset.cpus.partition", "isolated", LPMD_LOG_DEBUG))
+ return 1;
+ if (lpmd_write_str("/sys/fs/cgroup/lpm/cpuset.cpus", get_cpu_isolation_str(state->cpumask_idx), LPMD_LOG_DEBUG))
+ return 1;
+ } else {
+ if (lpmd_write_str("/sys/fs/cgroup/lpm/cpuset.cpus", get_cpu_isolation_str(CPUMASK_ONLINE), LPMD_LOG_DEBUG))
+ return 1;
+ }
+
+ return 0;
+}
+
+int cgroup_cleanup(void)
+{
+ DIR *dir = opendir("/sys/fs/cgroup/lpm");
+
+ if (dir) {
+ closedir(dir);
+ rmdir("/sys/fs/cgroup/lpm");
+ }
+ restore_systemd_cgroup();
+ return 0;
+}
+
+int cgroup_init(struct lpmd_config_t *config)
+{
+ if (lpmd_write_str(PATH_CG2_SUBTREE_CONTROL, "+cpuset", LPMD_LOG_DEBUG))
+ return 1;
+ if (config->mode == LPM_CPU_ISOLATE)
+ return mkdir("/sys/fs/cgroup/lpm", 0744);
+ return 0;
+}
+
+int process_cgroup(struct lpmd_config_state_t *state, enum lpm_cpu_process_mode mode)
+{
+ if (state->cpumask_idx == CPUMASK_NONE) {
+ lpmd_log_debug("Ignore cgroup processing\n");
+ return 0;
+ }
+
+ lpmd_log_info("Process Cgroup ...\n");
+ if (mode == LPM_CPU_CGROUPV2)
+ return process_cpu_cgroupv2(state);
+ if (mode == LPM_CPU_ISOLATE)
+ return process_cpu_isolate(state);
+ return 0;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_config.c b/tools/power/x86/intel-lpmd/src/lpmd_config.c
new file mode 100644
index 000000000000..56545876498c
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_config.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#include "lpmd.h"
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#define CONFIG_FILE_NAME "intel_lpmd_config.xml"
+#define MAX_FILE_NAME_PATH 128
+
+static void lpmd_parse_state(xmlDoc *doc, xmlNode *a_node, struct lpmd_config_t *config, int idx)
+{
+ struct lpmd_config_state_t *state = &config->config_states[idx];
+ xmlNode *cur_node = NULL;
+ char *tmp_value;
+ char *pos;
+
+ if (!doc || !a_node || !state)
+ return;
+
+ lpmd_log_debug("%s: %d\n", __func__, idx);
+ lpmd_init_config_state(state);
+
+ for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
+ if (cur_node->type != XML_ELEMENT_NODE)
+ continue;
+
+ tmp_value = (char *)xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1);
+ if (!tmp_value)
+ continue;
+
+ if (!strncmp((const char *)cur_node->name, "ID", strlen("ID")))
+ state->id = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "Name", strlen("Name"))) {
+ snprintf(state->name, MAX_STATE_NAME - 1, "%s", tmp_value);
+ state->name[MAX_STATE_NAME - 1] = '\0';
+ }
+ if (!strncmp((const char *)cur_node->name, "WLTType", strlen("WLTType")))
+ state->wlt_type = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "EntrySystemLoadThres", strlen("EntrySystemLoadThres")))
+ state->entry_system_load_thres = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "ExitSystemLoadThres", strlen("ExitSystemLoadThres")))
+ state->exit_system_load_thres = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "ExitSystemLoadhysteresis", strlen("ExitSystemLoadhysteresis")))
+ state->exit_system_load_hyst = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "EnterCPULoadThres", strlen("EnterCPULoadThres")))
+ state->enter_cpu_load_thres = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "ExitCPULoadThres", strlen("ExitCPULoadThres")))
+ state->exit_cpu_load_thres = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "EnterGFXLoadThres", strlen("EnterGFXLoadThres")))
+ state->enter_gfx_load_thres = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "ExitGFXLoadThres", strlen("ExitGFXLoadThres")))
+ state->exit_gfx_load_thres = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "MinPollInterval", strlen("MinPollInterval")))
+ state->min_poll_interval = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "MaxPollInterval", strlen("MaxPollInterval")))
+ state->max_poll_interval = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "PollIntervalIncrement", strlen("PollIntervalIncrement")))
+ state->poll_interval_increment = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "EPP", strlen("EPP")))
+ state->epp = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "EPB", strlen("EPB")))
+ state->epb = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "ITMTState", strlen("ITMTState")))
+ state->itmt_state = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "IRQMigrate", strlen("IRQMigrate")))
+ state->irq_migrate = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "Island0Pcores", strlen("Island0Pcores")))
+ state->island_0_number_p_cores = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "Island0Ecores", strlen("Island0Ecores")))
+ state->island_0_number_e_cores = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "Island1Pcores", strlen("Island1Pcores")))
+ state->island_1_number_p_cores = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "Island1Ecores", strlen("Island1Ecores")))
+ state->island_1_number_e_cores = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "Island2Pcores", strlen("Island2Pcores")))
+ state->island_2_number_p_cores = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "Island2Ecores", strlen("Island2Ecores")))
+ state->island_2_number_e_cores = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "ActiveCPUs", strlen("ActiveCPUs"))) {
+ if (!strncmp(tmp_value, "-1", strlen("-1")))
+ state->active_cpus[0] = '\0';
+ else
+ copy_user_string(tmp_value, state->active_cpus, sizeof(state->active_cpus));
+ }
+ if (!strncmp((const char *)cur_node->name, "BalanceSliderAC", strlen("BalanceSliderAC")))
+ state->balance_slider_ac = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "SliderOffsetAC", strlen("SliderOffsetAC")))
+ state->slider_offset_ac = strtol(tmp_value, &pos, 10);
+
+ if (!strncmp((const char *)cur_node->name, "BalanceSliderDC", strlen("BalanceSliderDC")))
+ state->balance_slider_dc = strtol(tmp_value, &pos, 10);
+ if (!strncmp((const char *)cur_node->name, "SliderOffsetDC", strlen("SliderOffsetDC")))
+ state->slider_offset_dc = strtol(tmp_value, &pos, 10);
+
+ xmlFree(tmp_value);
+ }
+}
+
+static int is_wildcard(char *str)
+{
+ if (!str)
+ return 1;
+ if (!strncmp(str, "*", strlen("*")))
+ return 1;
+ if (!strncmp(str, " * ", strlen(" * ")))
+ return 1;
+
+ return 0;
+}
+
+static void lpmd_parse_states(xmlDoc *doc, xmlNode *a_node, struct lpmd_config_t *lpmd_config)
+{
+ int cpu_family = -1, cpu_model = -1, config_state_count = 0;
+ char cpu_config[MAX_CONFIG_LEN];
+ xmlNode *cur_node = NULL;
+ char *tmp_value;
+ char *pos;
+
+ if (!doc || !a_node || !lpmd_config)
+ return;
+
+ /* A valid states table has been parsed */
+ if (lpmd_config->config_state_count)
+ return;
+
+ cpu_config[0] = '\0';
+
+ for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
+ if (cur_node->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (!cur_node->name)
+ continue;
+
+ tmp_value = (char *)xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1);
+ if (!strncmp((const char *)cur_node->name, "CPUFamily", strlen("CPUFamily"))) {
+ if (is_wildcard(tmp_value))
+ cpu_family = lpmd_config->cpu_family;
+ else
+ cpu_family = strtol(tmp_value, &pos, 10);
+ }
+
+ if (!strncmp((const char *)cur_node->name, "CPUModel", strlen("CPUModel"))) {
+ if (is_wildcard(tmp_value))
+ cpu_model = lpmd_config->cpu_model;
+ else
+ cpu_model = strtol(tmp_value, &pos, 10);
+ }
+
+ if (!strncmp((const char *)cur_node->name, "CPUConfig", strlen("CPUConfig"))) {
+ if (is_wildcard(tmp_value))
+ strncpy(cpu_config, lpmd_config->cpu_config, MAX_CONFIG_LEN);
+ else
+ snprintf(cpu_config, MAX_CONFIG_LEN - 1, "%s", tmp_value);
+ cpu_config[MAX_CONFIG_LEN - 1] = '\0';
+ }
+
+ if (tmp_value)
+ xmlFree(tmp_value);
+
+ if (strncmp((const char *)cur_node->name, "State", strlen("State")))
+ continue;
+
+ /* Must check cpu family/model/config first to make sure the states applies */
+ if (cpu_family != lpmd_config->cpu_family ||
+ cpu_model != lpmd_config->cpu_model ||
+ strncmp(cpu_config, lpmd_config->cpu_config,
+ MAX_CONFIG_LEN)) {
+ lpmd_log_info("Ignore unsupported states for CPU family:%d,model%d,config:%s\n",
+ cpu_family, cpu_model, cpu_config);
+ return;
+ }
+
+ if (lpmd_config->config_state_count >= MAX_CONFIG_STATES)
+ break;
+ lpmd_parse_state(doc, cur_node->children, lpmd_config,
+ CONFIG_STATE_BASE + config_state_count);
+ config_state_count++;
+ }
+ lpmd_log_debug("Found %d config states\n", config_state_count);
+ lpmd_config->config_state_count = config_state_count;
+}
+
+static void lpmd_init_config(struct lpmd_config_t *config)
+{
+ config->performance_def = LPM_FORCE_OFF;
+ config->balanced_def = LPM_FORCE_OFF;
+ config->powersaver_def = LPM_FORCE_OFF;
+ config->lp_mode_epp = -1;
+ config->data.util_sys = -1;
+ config->data.util_cpu = -1;
+ config->data.util_gfx = -1;
+ config->data.wlt_hint = -1;
+ config->balance_slider_def_ac = -1;
+ config->balance_slider_def_dc = -1;
+ config->slider_offset_def_ac = -1;
+ config->slider_offset_def_dc = -1;
+ config->wlt_hint_mask = -1;
+}
+
+static int lpmd_fill_config(xmlDoc *doc, xmlNode *a_node, struct lpmd_config_t *lpmd_config)
+{
+ xmlNode *cur_node = NULL;
+ char *tmp_value;
+ char *pos;
+
+ if (!doc || !a_node || !lpmd_config)
+ return LPMD_ERROR;
+
+ lpmd_init_config(lpmd_config);
+
+ for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
+ if (cur_node->type != XML_ELEMENT_NODE)
+ continue;
+
+ tmp_value = (char *)xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1);
+ if (!tmp_value)
+ continue;
+
+ if (!strncmp((const char *)cur_node->name, "Mode", strlen("Mode"))) {
+ errno = 0;
+ lpmd_config->mode = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->mode > LPM_CPU_MODE_MAX ||
+ lpmd_config->mode < 0)
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name,
+ "HfiLpmEnable", strlen("HfiLpmEnable"))) {
+ errno = 0;
+ lpmd_config->hfi_lpm_enable = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ (lpmd_config->hfi_lpm_enable != 1 &&
+ lpmd_config->hfi_lpm_enable != 0))
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name,
+ "WLTHintEnable", strlen("WLtHintEnable"))) {
+ errno = 0;
+ lpmd_config->wlt_hint_enable = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ (lpmd_config->wlt_hint_enable != 1 &&
+ lpmd_config->wlt_hint_enable != 0))
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name, "WLTHintMask",
+ strlen("WLTHintMask"))) {
+ errno = 0;
+ lpmd_config->wlt_hint_mask = strtol(tmp_value, &pos, 10);
+ } else if (!strncmp((const char *)cur_node->name,
+ "WLTHintPollEnable",
+ strlen("WLtHintPollEnable"))) {
+ errno = 0;
+ lpmd_config->wlt_hint_poll_enable = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ (lpmd_config->wlt_hint_poll_enable != 1 &&
+ lpmd_config->wlt_hint_poll_enable != 0))
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name,
+ "WLTProxyEnable",
+ strlen("WLTProxyEnable"))) {
+ errno = 0;
+ lpmd_config->wlt_proxy_enable = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ (lpmd_config->wlt_proxy_enable != 1 &&
+ lpmd_config->wlt_proxy_enable != 0))
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name,
+ "EntryDelayMS", strlen("EntryDelayMS"))) {
+ errno = 0;
+ lpmd_config->util_entry_delay = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->util_entry_delay < 0 ||
+ lpmd_config->util_entry_delay > UTIL_DELAY_MAX)
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name, "ExitDelayMS",
+ strlen("ExitDelayMS"))) {
+ errno = 0;
+ lpmd_config->util_exit_delay = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->util_exit_delay < 0 ||
+ lpmd_config->util_exit_delay > UTIL_DELAY_MAX)
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name,
+ "util_entry_threshold",
+ strlen("util_entry_threshold"))) {
+ errno = 0;
+ lpmd_config->util_entry_threshold = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->util_entry_threshold < 0 ||
+ lpmd_config->util_entry_threshold > 100)
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name,
+ "util_exit_threshold",
+ strlen("util_exit_threshold"))) {
+ errno = 0;
+ lpmd_config->util_exit_threshold = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->util_exit_threshold < 0 ||
+ lpmd_config->util_exit_threshold > 100)
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name, "EntryHystMS",
+ strlen("EntryHystMS"))) {
+ errno = 0;
+ lpmd_config->util_entry_hyst = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->util_entry_hyst < 0 ||
+ lpmd_config->util_entry_hyst > UTIL_HYST_MAX)
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name, "ExitHystMS",
+ strlen("ExitHystMS"))) {
+ errno = 0;
+ lpmd_config->util_exit_hyst = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->util_exit_hyst < 0 ||
+ lpmd_config->util_exit_hyst > UTIL_HYST_MAX)
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name, "lp_mode_epp",
+ strlen("lp_mode_epp"))) {
+ errno = 0;
+ lpmd_config->lp_mode_epp = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->lp_mode_epp > 255 ||
+ lpmd_config->lp_mode_epp < -1)
+ goto err;
+ if (lpmd_config->lp_mode_epp < 0)
+ lpmd_config->lp_mode_epp = -1;
+ } else if (!strncmp((const char *)cur_node->name, "IgnoreITMT",
+ strlen("IgnoreITMT"))) {
+ errno = 0;
+ lpmd_config->ignore_itmt = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0' ||
+ lpmd_config->ignore_itmt < 0 ||
+ lpmd_config->ignore_itmt > 1)
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name,
+ "lp_mode_cpus", strlen("lp_mode_cpus"))) {
+ if (!strncmp(tmp_value, "-1", strlen("-1")))
+ lpmd_config->lp_mode_cpus[0] = '\0';
+ else
+ copy_user_string(tmp_value, lpmd_config->lp_mode_cpus,
+ sizeof(lpmd_config->lp_mode_cpus));
+ } else if (!strncmp((const char *)cur_node->name,
+ "PerformanceDef",
+ strlen("PerformanceDef"))) {
+ errno = 0;
+ lpmd_config->performance_def = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0')
+ goto err;
+ if (lpmd_config->performance_def == -1)
+ lpmd_config->performance_def = LPM_FORCE_OFF;
+ else if (lpmd_config->performance_def == 1)
+ lpmd_config->performance_def = LPM_FORCE_ON;
+ else if (!lpmd_config->performance_def)
+ lpmd_config->performance_def = LPM_AUTO;
+ else
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name, "BalancedDef",
+ strlen("BalancedDef"))) {
+ errno = 0;
+ lpmd_config->balanced_def = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0')
+ goto err;
+ if (lpmd_config->balanced_def == -1)
+ lpmd_config->balanced_def = LPM_FORCE_OFF;
+ else if (lpmd_config->balanced_def == 1)
+ lpmd_config->balanced_def = LPM_FORCE_ON;
+ else if (!lpmd_config->balanced_def)
+ lpmd_config->balanced_def = LPM_AUTO;
+ else
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name,
+ "PowersaverDef", strlen("PowersaverDef"))) {
+ errno = 0;
+ lpmd_config->powersaver_def = strtol(tmp_value, &pos, 10);
+ if (errno || *pos != '\0')
+ goto err;
+ if (lpmd_config->powersaver_def == -1)
+ lpmd_config->powersaver_def = LPM_FORCE_OFF;
+ else if (lpmd_config->powersaver_def == 1)
+ lpmd_config->powersaver_def = LPM_FORCE_ON;
+ else if (!lpmd_config->powersaver_def)
+ lpmd_config->powersaver_def = LPM_AUTO;
+ else
+ goto err;
+ } else if (!strncmp((const char *)cur_node->name, "States",
+ strlen("States"))) {
+ errno = 0;
+ lpmd_parse_states(doc, cur_node->children, lpmd_config);
+ } else if (!strncmp((const char *)cur_node->name,
+ "BalancedSliderAC",
+ strlen("BalancedSliderAC"))) {
+ errno = 0;
+ lpmd_config->balance_slider_def_ac = strtol(tmp_value, &pos, 10);
+
+ } else if (!strncmp((const char *)cur_node->name,
+ "BalancedSliderDC",
+ strlen("BalancedSliderDC"))) {
+ errno = 0;
+ lpmd_config->balance_slider_def_dc = strtol(tmp_value, &pos, 10);
+ } else if (!strncmp((const char *)cur_node->name,
+ "SliderOffsetAC",
+ strlen("SliderOffsetAC"))) {
+ errno = 0;
+ lpmd_config->slider_offset_def_ac = strtol(tmp_value, &pos, 10);
+ } else if (!strncmp((const char *)cur_node->name,
+ "SliderOffsetDC",
+ strlen("SliderOffsetDC"))) {
+ errno = 0;
+ lpmd_config->slider_offset_def_dc = strtol(tmp_value, &pos, 10);
+ } else {
+ if (!strncmp((const char *)cur_node->name, "HfiSuvEnable", strlen("HfiSuvEnable"))) {
+ lpmd_log_debug("Ignore deprecated HfiSuvEnable setting\n");
+ } else {
+ lpmd_log_info("Invalid configuration data\n");
+ goto err;
+ }
+ }
+ xmlFree(tmp_value);
+ continue;
+err:
+ xmlFree(tmp_value);
+ lpmd_log_error("node type: Element, name: %s value: %s\n",
+ cur_node->name, tmp_value);
+ return LPMD_ERROR;
+ }
+
+ return LPMD_SUCCESS;
+}
+
+int lpmd_get_config(struct lpmd_config_t *lpmd_config)
+{
+ char file_name[MAX_FILE_NAME_PATH];
+ xmlNode *root_element;
+ xmlNode *cur_node;
+ struct stat s;
+ xmlDoc *doc;
+
+ if (!lpmd_config)
+ return LPMD_ERROR;
+
+ snprintf(file_name, MAX_FILE_NAME_PATH, "%s/intel_lpmd_config_F%d_M%d_T%d.xml",
+ TDCONFDIR, lpmd_config->cpu_family, lpmd_config->cpu_model, lpmd_config->tdp);
+
+ lpmd_log_msg("Looking for config file %s\n", file_name);
+ if (!stat(file_name, &s))
+ goto process_xml;
+
+ snprintf(file_name, MAX_FILE_NAME_PATH, "%s/intel_lpmd_config_F%d_M%d.xml",
+ TDCONFDIR, lpmd_config->cpu_family, lpmd_config->cpu_model);
+ lpmd_log_msg("Looking for config file %s\n", file_name);
+ if (!stat(file_name, &s))
+ goto process_xml;
+
+ snprintf(file_name, MAX_FILE_NAME_PATH, "%s/%s", TDCONFDIR, CONFIG_FILE_NAME);
+
+ lpmd_log_msg("Reading configuration file %s\n", file_name);
+
+ if (stat(file_name, &s)) {
+ lpmd_log_msg("error: could not find file %s\n", file_name);
+ return LPMD_ERROR;
+ }
+
+process_xml:
+ doc = xmlReadFile(file_name, NULL, 0);
+ if (!doc) {
+ lpmd_log_msg("error: could not parse file %s\n", file_name);
+ return LPMD_ERROR;
+ }
+
+ root_element = xmlDocGetRootElement(doc);
+ if (!root_element) {
+ lpmd_log_warn("error: could not get root element\n");
+ return LPMD_ERROR;
+ }
+
+ cur_node = NULL;
+
+ for (cur_node = root_element; cur_node; cur_node = cur_node->next) {
+ if (cur_node->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (strncmp((const char *)cur_node->name, "Configuration", strlen("Configuration")))
+ continue;
+
+ if (lpmd_fill_config(doc, cur_node->children, lpmd_config) != LPMD_SUCCESS) {
+ xmlFreeDoc(doc);
+ return LPMD_ERROR;
+ }
+ }
+
+ xmlFreeDoc(doc);
+
+ return LPMD_SUCCESS;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_cpu.c b/tools/power/x86/intel-lpmd/src/lpmd_cpu.c
new file mode 100644
index 000000000000..0098542613e8
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_cpu.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#define _GNU_SOURCE
+#include <err.h>
+
+#include "lpmd.h"
+
+/* Bit 15 of CPUID.7 EDX stands for Hybrid support */
+#define CPUFEATURE_HYBRID (1 << 15)
+#define PATH_PM_PROFILE "/sys/firmware/acpi/pm_profile"
+
+struct cpu_model_entry {
+ unsigned int family;
+ unsigned int model;
+};
+
+static struct cpu_model_entry id_table[] = {
+ { 6, 0x97 }, // Alderlake
+ { 6, 0x9a }, // Alderlake
+ { 6, 0xb7 }, // Raptorlake
+ { 6, 0xba }, // Raptorlake
+ { 6, 0xbf }, // Raptorlake S
+ { 6, 0xaa }, // Meteorlake
+ { 6, 0xac }, // Meteorlake
+ { 6, 0xbd }, // Lunarlake
+ { 6, 0xcc }, // Pantherlake
+ { 0, 0 } // Last Invalid entry
+};
+
+int detect_supported_platform(struct lpmd_config_t *lpmd_config)
+{
+ unsigned int max_level, family, model, stepping;
+ unsigned int eax, ebx, ecx, edx;
+ int val;
+
+ cpuid(0, eax, ebx, ecx, edx);
+
+ /* Unsupported vendor */
+ if (ebx != 0x756e6547 || edx != 0x49656e69 || ecx != 0x6c65746e) {
+ lpmd_log_info("Unsupported vendor\n");
+ return -1;
+ }
+
+ max_level = eax;
+ cpuid(1, eax, ebx, ecx, edx);
+ family = (eax >> 8) & 0xf;
+ model = (eax >> 4) & 0xf;
+ stepping = eax & 0xf;
+
+ if (family == 6)
+ model += ((eax >> 16) & 0xf) << 4;
+
+ lpmd_log_info("%u CPUID levels; family:model:stepping 0x%x:%x:%x (%u:%u:%u)\n",
+ max_level, family, model, stepping, family, model, stepping);
+
+ if (!do_platform_check()) {
+ lpmd_log_info("Ignore platform check\n");
+ goto end;
+ }
+
+ /* Need CPUID.1A to detect CPU core type */
+ if (max_level < 0x1a) {
+ lpmd_log_info("CPUID leaf 0x1a not supported, unable to detect CPU type\n");
+ return -1;
+ }
+
+ cpuid_count(7, 0, eax, ebx, ecx, edx);
+
+ /* Run on Hybrid platforms only */
+ if (!(edx & CPUFEATURE_HYBRID)) {
+ lpmd_log_info("Non-Hybrid platform detected\n");
+ return -1;
+ }
+
+ /* /sys/firmware/acpi/pm_profile is mandatory */
+ if (lpmd_read_int(PATH_PM_PROFILE, &val, -1)) {
+ lpmd_log_info("Failed to read PM profile %s\n", PATH_PM_PROFILE);
+ return -1;
+ }
+
+ if (val != 2 && val != 8) {
+ lpmd_log_info("Non-Mobile PM profile detected. %s returns %d\n",
+ PATH_PM_PROFILE, val);
+ return -1;
+ }
+
+ /* Platform meets all the criteria for lpmd to run, check the allow list */
+ val = 0;
+ while (id_table[val].family) {
+ if (id_table[val].family == family && id_table[val].model == model)
+ break;
+ val++;
+ }
+
+ /* Unsupported model */
+ if (!id_table[val].family) {
+ lpmd_log_info("Platform not supported yet.\n");
+ lpmd_log_debug("Supported platforms:\n");
+ val = 0;
+ while (id_table[val].family) {
+ lpmd_log_debug("\tfamily %d model %d\n",
+ id_table[val].family,
+ id_table[val].model);
+ val++;
+ }
+ return -1;
+ }
+
+end:
+ lpmd_config->cpu_family = family;
+ lpmd_config->cpu_model = model;
+
+ return 0;
+}
+
+/*
+ * Use one Ecore Module as LPM CPUs.
+ * Applies on Hybrid platforms like AlderLake/RaptorLake.
+ */
+int is_cpu_atom(int cpu)
+{
+ unsigned int eax, ebx, ecx, edx;
+ unsigned int type;
+
+ if (cpu_migrate(cpu) < 0) {
+ lpmd_log_error("Failed to migrated to cpu%d\n", cpu);
+ return -1;
+ }
+
+ cpuid(0x1a, eax, ebx, ecx, edx);
+
+ type = (eax >> 24) & 0xFF;
+
+ cpu_clear_affinity();
+ return type == 0x20;
+}
+
+static int is_cpu_in_l3(int cpu)
+{
+ unsigned int eax, ebx, ecx, edx, subleaf;
+
+ if (cpu_migrate(cpu) < 0) {
+ lpmd_log_error("Failed to migrated to cpu%d\n", cpu);
+ err(1, "cpu migrate");
+ }
+
+ for (subleaf = 0; ; subleaf++) {
+ unsigned int type, level;
+
+ cpuid_count(4, subleaf, eax, ebx, ecx, edx);
+
+ type = eax & 0x1f;
+ level = (eax >> 5) & 0x7;
+
+ /* No more caches */
+ if (!type)
+ break;
+ /* Unified Cache */
+ if (type != 3)
+ continue;
+ /* L3 */
+ if (level != 3)
+ continue;
+
+ cpu_clear_affinity();
+ return 1;
+ }
+
+ cpu_clear_affinity();
+ return 0;
+}
+
+int is_cpu_pcore(int cpu)
+{
+ return !is_cpu_atom(cpu);
+}
+
+int is_cpu_ecore(int cpu)
+{
+ if (!is_cpu_atom(cpu))
+ return 0;
+ return is_cpu_in_l3(cpu);
+}
+
+int is_cpu_lcore(int cpu)
+{
+ if (!is_cpu_atom(cpu))
+ return 0;
+ return !is_cpu_in_l3(cpu);
+}
+
+#define PATH_RAPL "/sys/class/powercap"
+static int get_tdp(void)
+{
+ char path[MAX_STR_LENGTH * 2];
+ char str[MAX_STR_LENGTH];
+ struct dirent *entry;
+ int ret, tdp = 0;
+ FILE *filep;
+ char *pos;
+ DIR *dir;
+
+ dir = opendir(PATH_RAPL);
+ if (!dir) {
+ perror("opendir() error");
+ return 1;
+ }
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (strlen(entry->d_name) > 100)
+ continue;
+
+ if (strncmp(entry->d_name, "intel-rapl", strlen("intel-rapl")))
+ continue;
+
+ snprintf(path, MAX_STR_LENGTH * 2, "%s/%s/name", PATH_RAPL, entry->d_name);
+ filep = fopen(path, "r");
+ if (!filep)
+ continue;
+
+ ret = fread(str, 1, MAX_STR_LENGTH, filep);
+ fclose(filep);
+
+ if (ret <= 0)
+ continue;
+
+ if (strncmp(str, "package", strlen("package")))
+ continue;
+
+ snprintf(path, MAX_STR_LENGTH * 2,
+ "%s/%s/constraint_0_max_power_uw", PATH_RAPL,
+ entry->d_name);
+ filep = fopen(path, "r");
+ if (!filep)
+ continue;
+
+ ret = fread(str, 1, MAX_STR_LENGTH, filep);
+ fclose(filep);
+
+ if (ret <= 0)
+ continue;
+
+ if (ret >= MAX_STR_LENGTH)
+ ret = MAX_STR_LENGTH - 1;
+
+ str[ret] = '\0';
+ tdp = strtol(str, &pos, 10);
+ break;
+ }
+ closedir(dir);
+
+ return tdp / 1000000;
+}
+
+#define BITMASK_SIZE 32
+int detect_max_cpus(void)
+{
+ unsigned long dummy;
+ int max_cpus, i;
+ FILE *filep;
+
+ max_cpus = 0;
+ for (i = 0; i < 256; ++i) {
+ char path[MAX_STR_LENGTH];
+
+ snprintf(path, sizeof(path),
+ "/sys/devices/system/cpu/cpu%d/topology/thread_siblings", i);
+
+ filep = fopen(path, "r");
+ if (filep)
+ break;
+ }
+
+ if (!filep) {
+ lpmd_log_error("Can't get max cpu number\n");
+ return -1;
+ }
+
+ while (fscanf(filep, "%lx,", &dummy) == 1)
+ max_cpus += BITMASK_SIZE;
+ fclose(filep);
+
+ lpmd_log_debug("\t%d CPUs supported in maximum\n", max_cpus);
+
+ set_max_cpus(max_cpus);
+ return 0;
+}
+
+int detect_cpu_topo(struct lpmd_config_t *lpmd_config)
+{
+ FILE *filep;
+ int i;
+ char path[MAX_STR_LENGTH];
+ int ret;
+ int pcores, ecores, lcores;
+ int tdp;
+
+ ret = detect_max_cpus();
+ if (ret)
+ return ret;
+
+ cpumask_reset(CPUMASK_ONLINE);
+ pcores = 0;
+ ecores = 0;
+ lcores = 0;
+
+ for (i = 0; i < get_max_cpus(); i++) {
+ unsigned int online = 0;
+
+ snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/online", i);
+ filep = fopen(path, "r");
+ if (filep) {
+ if (fscanf(filep, "%u", &online) != 1)
+ lpmd_log_warn("fread failed for %s\n", path);
+ fclose(filep);
+ } else if (!i) {
+ online = 1;
+ } else {
+ break;
+ }
+
+ if (!online)
+ continue;
+
+ cpumask_add_cpu(i, CPUMASK_ONLINE);
+ set_max_online_cpu(i);
+ }
+
+ /* Here it is the first time we migrate CPUs, must clear the previous cgroup settings */
+ cgroup_cleanup();
+
+ for (i = 0; i < get_max_cpus(); i++) {
+ if (!is_cpu_online(i))
+ continue;
+ if (is_cpu_pcore(i))
+ pcores++;
+ else if (is_cpu_ecore(i))
+ ecores++;
+ else if (is_cpu_lcore(i))
+ lcores++;
+ }
+
+ tdp = get_tdp();
+ lpmd_log_info("Detected %d Pcores, %d Ecores, %d Lcores, TDP %dW\n",
+ pcores, ecores, lcores, tdp);
+ ret = snprintf(lpmd_config->cpu_config, MAX_CONFIG_LEN - 1,
+ " %dP%dE%dL-%dW ", pcores, ecores, lcores, tdp);
+
+ lpmd_config->tdp = tdp;
+
+ return 0;
+}
+
+static int detect_lpm_cpus_cmd(char *cmd)
+{
+ int ret;
+
+ ret = cpumask_init_cpus(cmd, CPUMASK_LPM_DEFAULT);
+ if (ret <= 0)
+ cpumask_reset(CPUMASK_LPM_DEFAULT);
+
+ return ret;
+}
+
+static int detect_lpm_cpus_cluster(void)
+{
+ char path[MAX_STR_LENGTH];
+ char str[MAX_STR_LENGTH];
+ FILE *filep;
+ int i, ret;
+
+ for (i = get_max_cpus(); i >= 0; i--) {
+ if (!is_cpu_online(i))
+ continue;
+
+ snprintf(path, sizeof(path),
+ "/sys/devices/system/cpu/cpu%d/topology/cluster_cpus_list", i);
+ path[MAX_STR_LENGTH - 1] = '\0';
+
+ filep = fopen(path, "r");
+ if (!filep)
+ continue;
+
+ ret = fread(str, 1, MAX_STR_LENGTH - 1, filep);
+ fclose(filep);
+
+ if (ret <= 0)
+ continue;
+
+ str[ret] = '\0';
+
+ if (cpumask_init_cpus(str, CPUMASK_LPM_DEFAULT) <= 0)
+ continue;
+
+ /* An Ecore module contains 4 Atom cores */
+ if (cpumask_nr_cpus(CPUMASK_LPM_DEFAULT) == 4 && is_cpu_atom(i))
+ break;
+
+ cpumask_reset(CPUMASK_LPM_DEFAULT);
+ }
+
+ if (!cpumask_has_cpu(CPUMASK_LPM_DEFAULT))
+ return 0;
+
+ return cpumask_nr_cpus(CPUMASK_LPM_DEFAULT);
+}
+
+static int detect_cpu_lcore(int cpu)
+{
+ if (is_cpu_lcore(cpu))
+ cpumask_add_cpu(cpu, CPUMASK_LPM_DEFAULT);
+ return 0;
+}
+
+/*
+ * Use Lcore CPUs as LPM CPUs.
+ * Applies on platforms like MeteorLake.
+ */
+static int detect_lpm_cpus_lcore(void)
+{
+ int i;
+
+ for (i = 0; i < get_max_cpus(); i++) {
+ if (!is_cpu_online(i))
+ continue;
+ if (detect_cpu_lcore(i) < 0)
+ return -1;
+ }
+
+ /* All cpus has L3 */
+ if (!cpumask_has_cpu(CPUMASK_LPM_DEFAULT))
+ return 0;
+
+ /* All online cpus don't have L3 */
+ if (cpumask_equal(CPUMASK_LPM_DEFAULT, CPUMASK_ONLINE))
+ goto err;
+
+ return cpumask_nr_cpus(CPUMASK_LPM_DEFAULT);
+
+err:
+ cpumask_reset(CPUMASK_LPM_DEFAULT);
+ return 0;
+}
+
+int detect_lpm_cpus(char *cmd_cpus)
+{
+ int ret;
+ char *str = NULL;
+
+ if (cmd_cpus && cmd_cpus[0] != '\0') {
+ ret = detect_lpm_cpus_cmd(cmd_cpus);
+ if (ret <= 0) {
+ lpmd_log_error("\tInvalid -c parameter: %s\n", cmd_cpus);
+ exit(-1);
+ }
+ str = "CommandLine";
+ goto end;
+ }
+
+ ret = detect_lpm_cpus_lcore();
+ if (ret < 0)
+ return ret;
+
+ if (ret > 0) {
+ str = "Lcores";
+ goto end;
+ }
+
+ if (detect_lpm_cpus_cluster()) {
+ str = "Ecores";
+ goto end;
+ }
+
+end:
+ if (cpumask_has_cpu(CPUMASK_LPM_DEFAULT))
+ lpmd_log_info("\tUse CPU %s as Default Low Power CPUs (%s)\n",
+ get_cpus_str(CPUMASK_LPM_DEFAULT), str);
+
+ return 0;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_cpumask.c b/tools/power/x86/intel-lpmd/src/lpmd_cpumask.c
new file mode 100644
index 000000000000..62d0f8978889
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_cpumask.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <err.h>
+
+#include "lpmd.h"
+
+static int topo_max_cpus;
+static int max_online_cpu;
+static size_t size_cpumask;
+
+struct lpm_cpus {
+ cpu_set_t *mask;
+ char *name;
+ char *str;
+ char *str_reverse;
+ char *hexstr;
+ char *hexstr_reverse;
+ uint8_t *hexvals;
+};
+
+static struct lpm_cpus cpumasks[CPUMASK_MAX] = {
+ [CPUMASK_LPM_DEFAULT] = { .name = "Low Power", },
+ [CPUMASK_ONLINE] = { .name = "Online", },
+ [CPUMASK_HFI] = { .name = "HFI Low Power", },
+ [CPUMASK_HFI_BANNED] = { .name = "HFI BANNED", },
+ [CPUMASK_HFI_LAST] = { .name = "HFI LAST", },
+};
+
+int is_cpu_online(int cpu)
+{
+ if (cpu < 0 || cpu >= topo_max_cpus)
+ return 0;
+
+ if (!cpumasks[CPUMASK_ONLINE].mask)
+ return 0;
+
+ return CPU_ISSET_S(cpu, size_cpumask, cpumasks[CPUMASK_ONLINE].mask);
+}
+
+int get_max_cpus(void)
+{
+ return topo_max_cpus;
+}
+
+void set_max_cpus(int num)
+{
+ topo_max_cpus = num;
+}
+
+int get_max_online_cpu(void)
+{
+ return max_online_cpu;
+}
+
+void set_max_online_cpu(int num)
+{
+ max_online_cpu = num;
+}
+
+static size_t alloc_cpu_set(cpu_set_t **cpu_set)
+{
+ cpu_set_t *_cpu_set;
+ size_t size;
+
+ _cpu_set = CPU_ALLOC((topo_max_cpus + 1));
+ if (!_cpu_set)
+ err(3, "CPU_ALLOC");
+ size = CPU_ALLOC_SIZE((topo_max_cpus + 1));
+ CPU_ZERO_S(size, _cpu_set);
+
+ *cpu_set = _cpu_set;
+
+ if (!size_cpumask)
+ size_cpumask = size;
+
+ if (size_cpumask && size_cpumask != size) {
+ lpmd_log_error("Conflict cpumask size %zu vs. %zu\n", size, size_cpumask);
+ exit(-1);
+ }
+ return size;
+}
+
+int cpu_migrate(int cpu)
+{
+ cpu_set_t *mask;
+ int ret;
+
+ alloc_cpu_set(&mask);
+ CPU_SET_S(cpu, size_cpumask, mask);
+ ret = sched_setaffinity(0, size_cpumask, mask);
+ CPU_FREE(mask);
+
+ if (ret == -1)
+ return -1;
+ else
+ return 0;
+}
+
+int cpu_clear_affinity(void)
+{
+ return sched_setaffinity(0, size_cpumask, cpumasks[CPUMASK_ONLINE].mask);
+}
+
+int cpumask_alloc(void)
+{
+ int idx;
+
+ for (idx = CPUMASK_USER; idx < CPUMASK_MAX; idx++) {
+ if (!cpumasks[idx].mask) {
+ alloc_cpu_set(&cpumasks[idx].mask);
+ break;
+ }
+ }
+ return idx;
+}
+
+int cpumask_free(enum cpumask_idx idx)
+{
+ if (!cpumasks[idx].mask)
+ return 0;
+
+ cpumask_reset(idx);
+ free(cpumasks[idx].mask);
+ cpumasks[idx].mask = NULL;
+ return 0;
+}
+
+int cpumask_reset(enum cpumask_idx idx)
+{
+ if (!cpumasks[idx].mask)
+ alloc_cpu_set(&cpumasks[idx].mask);
+ else
+ CPU_ZERO_S(size_cpumask, cpumasks[idx].mask);
+
+ free(cpumasks[idx].str);
+ free(cpumasks[idx].str_reverse);
+ free(cpumasks[idx].hexstr);
+ free(cpumasks[idx].hexstr_reverse);
+ free(cpumasks[idx].hexvals);
+ cpumasks[idx].str = NULL;
+ cpumasks[idx].str_reverse = NULL;
+ cpumasks[idx].hexstr = NULL;
+ cpumasks[idx].hexstr_reverse = NULL;
+ cpumasks[idx].hexvals = NULL;
+ return 0;
+}
+
+int cpumask_add_cpu(int cpu, enum cpumask_idx idx)
+{
+ if (idx != CPUMASK_ONLINE && !is_cpu_online(cpu))
+ return 0;
+
+ if (!cpumasks[idx].mask)
+ alloc_cpu_set(&cpumasks[idx].mask);
+
+ CPU_SET_S(cpu, size_cpumask, cpumasks[idx].mask);
+
+ return LPMD_SUCCESS;
+}
+
+int cpumask_init_cpus(char *buf, enum cpumask_idx idx)
+{
+ unsigned int start, end;
+ char *next;
+ int nr_cpus = 0;
+
+ if (buf[0] == '\0')
+ return 0;
+
+ next = buf;
+
+ while (next && *next) {
+ if (*next == '\n')
+ *next = '\0';
+ next++;
+ }
+ next = buf;
+
+ while (next && *next) {
+ if (*next == '\n')
+ *next = '\0';
+
+ if (*next == '-') /* no negative cpu numbers */
+ goto error;
+
+ start = strtoul(next, &next, 10);
+
+ cpumask_add_cpu(start, idx);
+ nr_cpus++;
+
+ if (*next == '\0')
+ break;
+
+ if (*next == ',') {
+ next += 1;
+ continue;
+ }
+
+ if (*next == '-') {
+ next += 1; /* start range */
+ } else if (*next == '.') {
+ next += 1;
+ if (*next == '.')
+ next += 1; /* start range */
+ else
+ goto error;
+ }
+
+ end = strtoul(next, &next, 10);
+ if (end <= start)
+ goto error;
+
+ while (++start <= end) {
+ cpumask_add_cpu(start, idx);
+ nr_cpus++;
+ }
+
+ if (*next == ',')
+ next += 1;
+ else if (*next != '\0')
+ goto error;
+ }
+
+ return nr_cpus;
+error:
+ lpmd_log_error("CPU string malformed: %s\n", buf);
+ return -1;
+}
+
+int cpumask_nr_cpus(enum cpumask_idx idx)
+{
+ if (idx == CPUMASK_NONE)
+ return 0;
+
+ if (!cpumasks[idx].mask)
+ return 0;
+
+ return CPU_COUNT_S(size_cpumask, cpumasks[idx].mask);
+}
+
+int cpumask_has_cpu(enum cpumask_idx idx)
+{
+ return cpumask_nr_cpus(idx);
+}
+
+int cpumask_equal(enum cpumask_idx idx1, enum cpumask_idx idx2)
+{
+ if (!cpumasks[idx1].mask || !cpumasks[idx2].mask)
+ return 0;
+
+ if (CPU_EQUAL_S(size_cpumask, cpumasks[idx1].mask, cpumasks[idx2].mask))
+ return 1;
+
+ return 0;
+}
+
+void cpumask_copy(enum cpumask_idx source, enum cpumask_idx dest)
+{
+ int i;
+
+ cpumask_reset(dest);
+ for (i = 0; i < topo_max_cpus; i++) {
+ if (!CPU_ISSET_S(i, size_cpumask, cpumasks[source].mask))
+ continue;
+
+ cpumask_add_cpu(i, dest);
+ }
+}
+
+void cpumask_exclude_copy(enum cpumask_idx source, enum cpumask_idx dest, enum cpumask_idx exclude)
+{
+ int i;
+
+ cpumask_reset(dest);
+ for (i = 0; i < topo_max_cpus; i++) {
+ if (!CPU_ISSET_S(i, size_cpumask, cpumasks[source].mask))
+ continue;
+
+ if (CPU_ISSET_S(i, size_cpumask, cpumasks[exclude].mask))
+ continue;
+ }
+}
+
+static int cpumask_to_str(cpu_set_t *mask, char *buf, int length)
+{
+ int i;
+ int offset = 0;
+
+ buf[0] = '\0';
+ for (i = 0; i < topo_max_cpus; i++) {
+ if (!CPU_ISSET_S(i, size_cpumask, mask))
+ continue;
+ if (length - 1 < offset) {
+ lpmd_log_debug("%s: Too many cpus\n", __func__);
+ return 1;
+ }
+ offset += snprintf(buf + offset, length - 1 - offset, "%d,", i);
+ }
+ if (offset)
+ buf[offset - 1] = '\0';
+ return 0;
+}
+
+static char to_hexchar(int val)
+{
+ if (val <= 9)
+ return val + '0';
+ if (val >= 16)
+ return -1;
+ return val - 10 + 'a';
+}
+
+static int cpumask_to_hexstr(cpu_set_t *mask, char *str, int size)
+{
+ int cpu;
+ int i;
+ int pos = 0;
+ char c = 0;
+
+ for (cpu = 0; cpu < topo_max_cpus; cpu++) {
+ i = cpu % 4;
+
+ if (!i)
+ c = 0;
+
+ if (CPU_ISSET_S(cpu, size_cpumask, mask))
+ c += (1 << i);
+
+ if (i == 3) {
+ str[pos] = to_hexchar (c);
+ pos++;
+ if (pos >= size)
+ return -1;
+ }
+ }
+ str[pos] = '\0';
+
+ pos--;
+ for (i = 0; i <= pos / 2; i++) {
+ c = str[i];
+ str[i] = str[pos - i];
+ str[pos - i] = c;
+ }
+
+ return 0;
+}
+
+char *get_cpus_str(enum cpumask_idx idx)
+{
+ if (!cpumasks[idx].mask)
+ return NULL;
+
+ if (!CPU_COUNT_S(size_cpumask, cpumasks[idx].mask))
+ return NULL;
+
+ if (cpumasks[idx].str)
+ return cpumasks[idx].str;
+
+ cpumasks[idx].str = calloc(MAX_STR_LENGTH, 1);
+ if (!cpumasks[idx].str)
+ err(3, "STR_ALLOC");
+
+ cpumask_to_str(cpumasks[idx].mask, cpumasks[idx].str, MAX_STR_LENGTH);
+ return cpumasks[idx].str;
+}
+
+char *get_cpus_hexstr(enum cpumask_idx idx)
+{
+ if (!cpumasks[idx].mask)
+ return NULL;
+
+ if (!CPU_COUNT_S(size_cpumask, cpumasks[idx].mask))
+ return NULL;
+
+ if (cpumasks[idx].hexstr)
+ return cpumasks[idx].hexstr;
+
+ cpumasks[idx].hexstr = calloc(MAX_STR_LENGTH, 1);
+ if (!cpumasks[idx].hexstr)
+ err(3, "STR_ALLOC");
+
+ cpumask_to_hexstr(cpumasks[idx].mask, cpumasks[idx].hexstr, MAX_STR_LENGTH);
+ return cpumasks[idx].hexstr;
+}
+
+static char *get_cpus_str_reverse(enum cpumask_idx idx)
+{
+ cpu_set_t *mask;
+
+ if (!cpumasks[idx].mask)
+ return NULL;
+
+ if (!CPU_COUNT_S(size_cpumask, cpumasks[idx].mask))
+ return NULL;
+
+ if (cpumasks[idx].str_reverse)
+ return cpumasks[idx].str_reverse;
+
+ cpumasks[idx].str_reverse = calloc(MAX_STR_LENGTH, 1);
+ if (!cpumasks[idx].str_reverse)
+ err(3, "STR_ALLOC");
+
+ alloc_cpu_set(&mask);
+ CPU_XOR_S(size_cpumask, mask, cpumasks[idx].mask, cpumasks[CPUMASK_ONLINE].mask);
+ cpumask_to_str(mask, cpumasks[idx].str_reverse, MAX_STR_LENGTH);
+ CPU_FREE(mask);
+
+ return cpumasks[idx].str_reverse;
+}
+
+static uint8_t *get_cpus_hexvals(enum cpumask_idx idx, int *size)
+{
+ int i, j, k;
+ uint8_t v = 0;
+ uint8_t *vals;
+
+ if (!cpumasks[idx].mask)
+ return NULL;
+
+ if (!CPU_COUNT_S(size_cpumask, cpumasks[idx].mask))
+ return NULL;
+
+ *size = topo_max_cpus / 8;
+
+ if (cpumasks[idx].hexvals)
+ return cpumasks[idx].hexvals;
+
+ vals = calloc(*size, 1);
+ if (!vals)
+ return NULL;
+
+ for (i = 0; i < topo_max_cpus; i++) {
+ j = i % 8;
+ k = i / 8;
+
+ if (k >= *size) {
+ lpmd_log_error("size too big\n");
+ free(vals);
+ return NULL;
+ }
+
+ if (!CPU_ISSET_S(i, size_cpumask, cpumasks[idx].mask))
+ goto set_val;
+
+ v |= 1 << j;
+set_val:
+ if (j == 7) {
+ vals[k] = v;
+ v = 0;
+ }
+ }
+
+ cpumasks[idx].hexvals = vals;
+ return cpumasks[idx].hexvals;
+}
+
+char *get_proc_irq_str(enum cpumask_idx idx)
+{
+ return get_cpus_hexstr(idx);
+}
+
+char *get_irqbalance_str(enum cpumask_idx idx)
+{
+ return get_cpus_str_reverse(idx);
+}
+
+char *get_cpu_isolation_str(enum cpumask_idx idx)
+{
+ if (idx == CPUMASK_ONLINE)
+ return get_cpus_str(idx);
+ else
+ return get_cpus_str_reverse(idx);
+}
+
+uint8_t *get_cgroup_systemd_vals(enum cpumask_idx idx, int *size)
+{
+ return get_cpus_hexvals(idx, size);
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_dbus_server.c b/tools/power/x86/intel-lpmd/src/lpmd_dbus_server.c
new file mode 100644
index 000000000000..5e02e7eb22ae
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_dbus_server.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib-object.h>
+
+#include "lpmd.h"
+
+struct _PrefObject {
+ GObject parent;
+};
+
+#define PREF_TYPE_OBJECT (pref_object_get_type())
+G_DECLARE_FINAL_TYPE(PrefObject, pref_object, PREF, OBJECT, GObject)
+
+#define MAX_DBUS_REPLY_STR_LEN 100
+G_DEFINE_TYPE(PrefObject, pref_object, G_TYPE_OBJECT)
+
+static gboolean
+dbus_interface_terminate(PrefObject *obj, GError **error);
+
+static gboolean
+dbus_interface_l_pm__fo_rc_e__on(PrefObject *obj, GError **error);
+
+static gboolean
+dbus_interface_l_pm__fo_rc_e__of_f(PrefObject *obj, GError **error);
+
+static gboolean
+dbus_interface_l_pm__au_to(PrefObject *obj, GError **error);
+
+static gboolean
+(*intel_lpmd_dbus_exit_callback)(void);
+
+// Dbus object initialization
+static void pref_object_init(PrefObject *obj)
+{
+ g_assert(obj);
+}
+
+// Dbus object class initialization
+static void pref_object_class_init(PrefObjectClass *_class)
+{
+ g_assert(_class);
+}
+
+static gboolean dbus_interface_terminate(PrefObject *obj, GError **error)
+{
+ lpmd_log_debug("intel_lpmd_dbus_interface_terminate\n");
+ lpmd_terminate();
+ if (intel_lpmd_dbus_exit_callback)
+ intel_lpmd_dbus_exit_callback();
+
+ return TRUE;
+}
+
+static gboolean dbus_interface_l_pm__fo_rc_e__on(PrefObject *obj, GError **error)
+{
+ lpmd_log_debug("intel_lpmd_dbus_interface_lpm_enter\n");
+ lpmd_force_on();
+
+ return TRUE;
+}
+
+static gboolean dbus_interface_l_pm__fo_rc_e__of_f(PrefObject *obj, GError **error)
+{
+ lpmd_log_debug("intel_lpmd_dbus_interface_lpm_exit\n");
+ lpmd_force_off();
+
+ return TRUE;
+}
+
+static gboolean dbus_interface_l_pm__au_to(PrefObject *obj, GError **error)
+{
+ lpmd_set_auto();
+ return TRUE;
+}
+
+#pragma GCC diagnostic push
+
+static GDBusInterfaceVTable interface_vtable;
+
+static GDBusNodeInfo *
+lpmd_dbus_load_introspection(const gchar *filename, GError **error)
+{
+ g_autoptr(GBytes) data = NULL;
+ g_autofree gchar *path = NULL;
+
+ path = g_build_filename("/org/freedesktop/intel_lpmd", filename, NULL);
+ data = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, error);
+ if (!data)
+ return NULL;
+
+ return g_dbus_node_info_new_for_xml((gchar *)g_bytes_get_data(data, NULL), error);
+}
+
+static void
+lpmd_dbus_handle_method_call(GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ PrefObject *obj = PREF_OBJECT(user_data);
+ g_autoptr(GError) error = NULL;
+
+ lpmd_log_debug("Dbus method called %s %s.\n", interface_name, method_name);
+
+ if (g_strcmp0(method_name, "Terminate") == 0) {
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ dbus_interface_terminate(obj, &error);
+ return;
+ }
+
+ if (g_strcmp0(method_name, "LPM_FORCE_ON") == 0) {
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ dbus_interface_l_pm__fo_rc_e__on(obj, &error);
+ return;
+ }
+
+ if (g_strcmp0(method_name, "LPM_FORCE_OFF") == 0) {
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ dbus_interface_l_pm__fo_rc_e__of_f(obj, &error);
+ return;
+ }
+ if (g_strcmp0(method_name, "LPM_AUTO") == 0) {
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ dbus_interface_l_pm__au_to(obj, &error);
+ return;
+ }
+
+ if (g_strcmp0(method_name, "GetState") == 0) {
+ static const char * const state_names[] = {
+ [LPMD_OFF] = "OFF",
+ [LPMD_ON] = "ON",
+ [LPMD_AUTO] = "AUTO",
+ [LPMD_FREEZE] = "FREEZE",
+ [LPMD_RESTORE] = "RESTORE",
+ [LPMD_TERMINATE] = "TERMINATE",
+ };
+ int state = get_lpmd_state();
+ const char *name = (state >= 0 && state <= LPMD_TERMINATE) ? state_names[state] : "UNKNOWN";
+
+ g_dbus_method_invocation_return_value(invocation,
+ g_variant_new("(s)", name));
+ return;
+ }
+
+ g_set_error(&error,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_UNKNOWN_METHOD,
+ "no such method %s",
+ method_name);
+ g_dbus_method_invocation_return_gerror(invocation, error);
+}
+
+static GVariant *
+lpmd_dbus_handle_get_property(GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ return NULL;
+}
+
+static gboolean
+lpmd_dbus_handle_set_property(GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GVariant *value,
+ GError **error,
+ gpointer user_data)
+{
+ return TRUE;
+}
+
+static void
+lpmd_dbus_on_bus_acquired(GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GDBusNodeInfo *introspection_data = NULL;
+ GDBusProxy *proxy_id = NULL;
+ guint registration_id;
+ GError *error = NULL;
+
+ if (!user_data) {
+ lpmd_log_error("user_data is NULL\n");
+ return;
+ }
+
+ introspection_data = lpmd_dbus_load_introspection("src/intel_lpmd_dbus_interface.xml",
+ &error);
+ if (!introspection_data || error) {
+ lpmd_log_error("Couldn't create introspection data: %s:\n",
+ error->message);
+ return;
+ }
+
+ registration_id = g_dbus_connection_register_object(connection,
+ "/org/freedesktop/intel_lpmd",
+ introspection_data->interfaces[0],
+ &interface_vtable,
+ user_data,
+ NULL,
+ &error);
+
+ proxy_id = g_dbus_proxy_new_sync(connection,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+ NULL,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ NULL,
+ &error);
+ g_assert(registration_id > 0);
+ g_assert(proxy_id);
+}
+
+static void
+lpmd_dbus_on_name_acquired(GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+}
+
+static void
+lpmd_dbus_on_name_lost(GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_warning("Lost the name %s\n", name);
+ exit(1);
+}
+
+// Set up Dbus server with GDBus
+int intel_dbus_server_init(gboolean (*exit_handler)(void))
+{
+ PrefObject *value_obj;
+
+ intel_lpmd_dbus_exit_callback = exit_handler;
+
+ value_obj = PREF_OBJECT(g_object_new(PREF_TYPE_OBJECT, NULL));
+ if (!value_obj) {
+ lpmd_log_error("Failed to create one Value instance:\n");
+ return LPMD_FATAL_ERROR;
+ }
+
+ interface_vtable.method_call = lpmd_dbus_handle_method_call;
+ interface_vtable.get_property = lpmd_dbus_handle_get_property;
+ interface_vtable.set_property = lpmd_dbus_handle_set_property;
+
+ watcher_id = g_bus_own_name(G_BUS_TYPE_SYSTEM,
+ "org.freedesktop.intel_lpmd",
+ G_BUS_NAME_OWNER_FLAGS_REPLACE,
+ lpmd_dbus_on_bus_acquired,
+ lpmd_dbus_on_name_acquired,
+ lpmd_dbus_on_name_lost,
+ g_object_ref(value_obj),
+ NULL);
+
+ return LPMD_SUCCESS;
+}
+
+#pragma GCC diagnostic pop
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_helpers.c b/tools/power/x86/intel-lpmd/src/lpmd_helpers.c
new file mode 100644
index 000000000000..4cae4e885f5c
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_helpers.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+
+#include "lpmd.h"
+
+int copy_user_string(char *src, char *dst, int size)
+{
+ int offset_src, offset_dst;
+
+ for (offset_src = 0, offset_dst = 0; src[offset_src] != '\0' && offset_src < size; offset_src++) {
+ /* Ignore heading spaces */
+ if (src[offset_src] == ' ' && !offset_dst)
+ continue;
+ dst[offset_dst] = src[offset_src];
+ offset_dst++;
+ }
+ dst[offset_dst] = '\0';
+
+ /* Remove tailing spaces */
+ while (dst[--offset_dst] == ' ')
+ dst[offset_dst] = '\0';
+
+ return 0;
+}
+
+int lpmd_read_str(char *path, char *str, int size)
+{
+ FILE *filep;
+ int ret;
+
+ filep = fopen(path, "r");
+ if (!filep) {
+ lpmd_log_error("Cannot open %s\n", path);
+ return 1;
+ }
+
+ ret = fread(str, 1, size, filep);
+ fclose(filep);
+
+ if (ret <= 0)
+ return 1;
+
+ if (ret >= size)
+ ret = size - 1;
+ str[ret - 1] = '\0';
+
+ lpmd_log_debug("Read \"%s\" from %s\n", str, path);
+ return 0;
+}
+
+static int _write_str(const char *name, char *str, int print_level, int log_level, const char *mode)
+{
+ char prefix[16];
+ FILE *filep;
+ int i, ret;
+
+ if (print_level >= 15)
+ return 1;
+
+ if (print_level <= 0) {
+ prefix[0] = '\0';
+ } else {
+ for (i = 0; i < print_level; i++)
+ prefix[i] = '\t';
+ prefix[i] = '\0';
+ }
+
+ filep = fopen(name, mode);
+ if (!filep) {
+ lpmd_log_error("%sOpen %s failed\n", prefix, name);
+ return 1;
+ }
+
+ ret = fprintf(filep, "%s", str);
+ if (ret <= 0) {
+ lpmd_log_error("%sWrite \"%s\" to %s failed, strlen %zu, ret %d\n",
+ prefix, str, name, strlen(str), ret);
+ fclose(filep);
+ return 1;
+ }
+
+ switch (print_level) {
+ case LPMD_LOG_INFO:
+ lpmd_log_info("%sWrite \"%s\" to %s\n", prefix, str, name);
+ break;
+ case LPMD_LOG_DEBUG:
+ lpmd_log_debug("%sWrite \"%s\" to %s\n", prefix, str, name);
+ break;
+ case LPMD_LOG_MSG:
+ lpmd_log_msg("%sWrite \"%s\" to %s\n", prefix, str, name);
+ break;
+ default:
+ break;
+ }
+
+ fclose(filep);
+ return 0;
+}
+
+int lpmd_write_str(const char *name, char *str, int print_level)
+{
+ if (!name || !str)
+ return 0;
+
+ return _write_str(name, str, print_level, 2, "r+");
+}
+
+int lpmd_write_str_append(const char *name, char *str, int print_level)
+{
+ if (!name || !str)
+ return 0;
+
+ return _write_str(name, str, print_level, 2, "a+");
+}
+
+int lpmd_write_str_verbose(const char *name, char *str, int print_level)
+{
+ if (!name || !str)
+ return 0;
+
+ return _write_str(name, str, print_level, 3, "r+");
+}
+
+int lpmd_write_int(const char *name, int val, int print_level)
+{
+ FILE *filep;
+ char prefix[16];
+ int i, ret;
+ struct timespec tp1 = { }, tp2 = { };
+
+ if (!name)
+ return 1;
+
+ clock_gettime(CLOCK_MONOTONIC, &tp1);
+
+ if (print_level >= 15)
+ return 1;
+
+ if (print_level < 0) {
+ prefix[0] = '\0';
+ } else {
+ for (i = 0; i < print_level; i++)
+ prefix[i] = '\t';
+ prefix[i] = '\0';
+ }
+
+ filep = fopen(name, "r+");
+ if (!filep) {
+ lpmd_log_error("%sOpen %s failed\n", prefix, name);
+ return 1;
+ }
+
+ ret = fprintf(filep, "%d", val);
+ if (ret <= 0) {
+ lpmd_log_error("%sWrite \"%d\" to %s failed, ret %d\n", prefix, val, name, ret);
+ fclose(filep);
+ return 1;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &tp2);
+
+ switch (print_level) {
+ case LPMD_LOG_INFO:
+ lpmd_log_info("%sWrite \"%d\" to %s (%lu ns)\n", prefix, val, name,
+ 1000000000 * (tp2.tv_sec - tp1.tv_sec) + tp2.tv_nsec - tp1.tv_nsec);
+ break;
+ case LPMD_LOG_DEBUG:
+ lpmd_log_debug("%sWrite \"%d\" to %s (%lu ns)\n", prefix, val, name,
+ 1000000000 * (tp2.tv_sec - tp1.tv_sec) + tp2.tv_nsec - tp1.tv_nsec);
+ break;
+ case LPMD_LOG_MSG:
+ lpmd_log_msg("%sWrite \"%d\" to %s (%lu ns)\n", prefix, val, name,
+ 1000000000 * (tp2.tv_sec - tp1.tv_sec) + tp2.tv_nsec - tp1.tv_nsec);
+ break;
+ default:
+ break;
+ }
+
+ fclose(filep);
+ return 0;
+}
+
+int lpmd_read_int(const char *name, int *val, int print_level)
+{
+ char prefix[16];
+ int i, t, ret;
+ FILE *filep;
+
+ if (!name || !val)
+ return 1;
+
+ if (print_level >= 15)
+ return 1;
+
+ if (print_level < 0) {
+ prefix[0] = '\0';
+ } else {
+ for (i = 0; i < print_level; i++)
+ prefix[i] = '\t';
+ prefix[i] = '\0';
+ }
+
+ filep = fopen(name, "r");
+ if (!filep) {
+ lpmd_log_error("%sOpen %s failed\n", prefix, name);
+ return 1;
+ }
+
+ ret = fscanf(filep, "%d", &t);
+ if (ret != 1) {
+ lpmd_log_error("%sRead %s failed, ret %d\n", prefix, name, ret);
+ fclose(filep);
+ return 1;
+ }
+
+ fclose(filep);
+
+ *val = t;
+
+ if (print_level >= 0)
+ lpmd_log_debug("%sRead \"%d\" from %s\n", prefix, *val, name);
+
+ return 0;
+}
+
+int lpmd_write_yn(const char *name, int val, int print_level)
+{
+ char str[5];
+ int ret;
+
+ if (!name)
+ return 0;
+
+ ret = snprintf(str, 4, "%d", val);
+ if (ret < 0)
+ return 1;
+
+ return _write_str(name, str, print_level, 2, "r+");
+}
+
+int lpmd_read_yn(const char *name, int *val, int print_level)
+{
+ char prefix[16];
+ FILE *filep;
+ int i, ret;
+
+ if (!name || !val)
+ return 1;
+
+ if (print_level >= 15)
+ return 1;
+
+ if (print_level < 0) {
+ prefix[0] = '\0';
+ } else {
+ for (i = 0; i < print_level; i++)
+ prefix[i] = '\t';
+ prefix[i] = '\0';
+ }
+
+ filep = fopen(name, "r");
+ if (!filep) {
+ lpmd_log_error("%sOpen %s failed\n", prefix, name);
+ return 1;
+ }
+
+ ret = fgetc(filep);
+ if (ret == EOF) {
+ if (feof(filep))
+ lpmd_log_error("%sRead %s failed due to EOF\n", prefix,
+ name);
+ else if (ferror(filep))
+ lpmd_log_error("%sRead %s failed, error %s\n", prefix,
+ name, strerror(errno));
+ fclose(filep);
+ return 1;
+ }
+
+ fclose(filep);
+
+ if (ret == 'Y') {
+ *val = 1;
+ } else if (ret == 'N') {
+ *val = 0;
+ } else {
+ lpmd_log_error("%sRead %s failed, read %c\n", prefix, name, ret);
+ return 1;
+ }
+
+ if (print_level >= 0)
+ lpmd_log_debug("%sRead \"%c\" from %s\n", prefix, *val, name);
+
+ return 0;
+}
+
+/*
+ * lpmd_open does not require print on success
+ * print_level: -1: don't print on error
+ */
+int lpmd_open(const char *name, int print_level)
+{
+ FILE *filep;
+ char prefix[16];
+ int i;
+
+ if (!name)
+ return 1;
+
+ if (print_level >= 15)
+ return 1;
+
+ if (print_level < 0) {
+ prefix[0] = '\0';
+ } else {
+ for (i = 0; i < print_level; i++)
+ prefix[i] = '\t';
+ prefix[i] = '\0';
+ }
+
+ filep = fopen(name, "r");
+ if (!filep) {
+ if (print_level >= 0)
+ lpmd_log_error("%sOpen %s failed\n", prefix, name);
+ return 1;
+ }
+
+ fclose(filep);
+ return 0;
+}
+
+char *get_time(void)
+{
+ static time_t time_cur;
+
+ time_cur = time(NULL);
+ return ctime(&time_cur);
+}
+
+static struct timespec timespec;
+static char time_buf[MAX_STR_LENGTH];
+void time_start(void)
+{
+ clock_gettime(CLOCK_MONOTONIC, ×pec);
+}
+
+char *time_delta(void)
+{
+ static struct timespec tp1;
+
+ clock_gettime(CLOCK_MONOTONIC, &tp1);
+ snprintf(time_buf, MAX_STR_LENGTH, "%ld ns",
+ 1000000000 * (tp1.tv_sec - timespec.tv_sec) + tp1.tv_nsec - timespec.tv_nsec);
+ memset(×pec, 0, sizeof(timespec));
+ return time_buf;
+}
+
+uint64_t read_msr(int cpu, uint32_t msr)
+{
+ char msr_file_name[64];
+ int fd;
+ uint64_t value;
+
+ snprintf(msr_file_name, sizeof(msr_file_name), "/dev/cpu/%d/msr", cpu);
+
+ fd = open(msr_file_name, O_RDONLY);
+ if (fd < 0)
+ return UINT64_MAX;
+
+ if (pread(fd, &value, sizeof(value), msr) != sizeof(value)) {
+ close(fd);
+ return UINT64_MAX;
+ }
+
+ close(fd);
+
+ return value;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_hfi.c b/tools/power/x86/intel-lpmd/src/lpmd_hfi.c
new file mode 100644
index 000000000000..0b52feeef995
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_hfi.c
@@ -0,0 +1,373 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2023 Intel Corporation */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sched.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pthread.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/ctrl.h>
+
+#include "thermal.h"
+#include "lpmd.h"
+
+struct hfi_event_data {
+ struct nl_sock *nl_handle;
+ struct nl_cb *nl_cb;
+};
+
+struct hfi_event_data drv;
+
+static int ack_handler(struct nl_msg *msg, void *arg)
+{
+ int *err = arg;
+ *err = 0;
+
+ return NL_STOP;
+}
+
+static int finish_handler(struct nl_msg *msg, void *arg)
+{
+ int *ret = arg;
+ *ret = 0;
+
+ return NL_SKIP;
+}
+
+static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
+{
+ int *ret = arg;
+ *ret = err->error;
+
+ return NL_SKIP;
+}
+
+static int seq_check_handler(struct nl_msg *msg, void *arg)
+{
+ return NL_OK;
+}
+
+static int send_and_recv_msgs(struct hfi_event_data *drv, struct nl_msg *msg,
+ int (*valid_handler)(struct nl_msg*, void*),
+ void *valid_data)
+{
+ struct nl_cb *cb;
+ int err = -ENOMEM;
+
+ cb = nl_cb_clone(drv->nl_cb);
+ if (!cb)
+ goto out;
+
+ err = nl_send_auto_complete(drv->nl_handle, msg);
+ if (err < 0)
+ goto out;
+
+ err = 1;
+
+ nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err);
+ nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err);
+ nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err);
+
+ if (valid_handler)
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, valid_data);
+
+ while (err > 0)
+ err = nl_recvmsgs(drv->nl_handle, cb);
+out: nl_cb_put(cb);
+ nlmsg_free(msg);
+ return err;
+}
+
+struct family_data {
+ const char *group;
+ int id;
+};
+
+static int family_handler(struct nl_msg *msg, void *arg)
+{
+ struct family_data *res = arg;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *mcgrp;
+ int i;
+
+ nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+ if (!tb[CTRL_ATTR_MCAST_GROUPS])
+ return NL_SKIP;
+
+ nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], i) {
+ struct nlattr *tb2[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+ nla_parse(tb2, CTRL_ATTR_MCAST_GRP_MAX,
+ nla_data(mcgrp), nla_len(mcgrp), NULL);
+ if (!tb2[CTRL_ATTR_MCAST_GRP_NAME] ||
+ !tb2[CTRL_ATTR_MCAST_GRP_ID] ||
+ strncmp(nla_data(tb2[CTRL_ATTR_MCAST_GRP_NAME]), res->group,
+ nla_len(tb2[CTRL_ATTR_MCAST_GRP_NAME])) != 0)
+ continue;
+ res->id = nla_get_u32(tb2[CTRL_ATTR_MCAST_GRP_ID]);
+ break;
+ };
+
+ return 0;
+}
+
+static int nl_get_multicast_id(struct hfi_event_data *drv, const char *family,
+ const char *group)
+{
+ struct nl_msg *msg;
+ int ret = -1;
+ struct family_data res = { group, -ENOENT };
+
+ msg = nlmsg_alloc();
+ if (!msg)
+ return -ENOMEM;
+ genlmsg_put(msg, 0, 0, genl_ctrl_resolve(drv->nl_handle, "nlctrl"),
+ 0, 0, CTRL_CMD_GETFAMILY, 0);
+ NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family);
+
+ ret = send_and_recv_msgs(drv, msg, family_handler, &res);
+ msg = NULL;
+ if (ret == 0)
+ ret = res.id;
+
+nla_put_failure: nlmsg_free(msg);
+ return ret;
+}
+
+/* Process HFI event */
+struct perf_cap {
+ int cpu;
+ int perf;
+ int eff;
+};
+
+/*
+ * Detect different kinds of CPU HFI hint
+ * "LPM". EFF == 255
+ * "SUV". PERF == EFF == 0, suv bit set. Not supported for now.
+ * "BAN". PERF == EFF == 0, suv bit not set.
+ * "NOR".
+ */
+static char *update_one_cpu(struct perf_cap *perf_cap)
+{
+ if (perf_cap->cpu < 0)
+ return NULL;
+
+ if (!perf_cap->cpu) {
+ cpumask_reset(CPUMASK_HFI);
+ cpumask_reset(CPUMASK_HFI_BANNED);
+ }
+
+ if (perf_cap->eff == 255 * 4) {
+ cpumask_add_cpu(perf_cap->cpu, CPUMASK_HFI);
+ return "LPM";
+ }
+ if (!perf_cap->perf && !perf_cap->eff) {
+ cpumask_add_cpu(perf_cap->cpu, CPUMASK_HFI_BANNED);
+ return "BAN";
+ }
+ return "NOR";
+}
+
+static void process_one_event(int first, int last, int nr)
+{
+ /* Need to update more CPUs */
+ if (nr == 16 && last != get_max_online_cpu())
+ return;
+
+ if (cpumask_has_cpu(CPUMASK_HFI)) {
+ /* Ignore duplicate event */
+ if (cpumask_equal(CPUMASK_HFI_LAST, CPUMASK_HFI)) {
+ lpmd_log_debug("\tDuplicated HFI LPM hints ignored\n\n");
+ return;
+ }
+ lpmd_log_debug("\tDetect HFI LPM event\n");
+ update_reason(UPDATE_HFI);
+ cpumask_copy(CPUMASK_HFI, CPUMASK_HFI_LAST);
+ } else if (cpumask_has_cpu(CPUMASK_HFI_BANNED)) {
+ cpumask_exclude_copy(CPUMASK_ONLINE, CPUMASK_HFI, CPUMASK_HFI_BANNED);
+ /* Ignore duplicate event */
+ if (cpumask_equal(CPUMASK_HFI_LAST, CPUMASK_HFI)) {
+ lpmd_log_debug("\tDuplicated HFI BANNED hints ignored\n\n");
+ return;
+ }
+ lpmd_log_debug("\tDetect HFI LPM event with banned CPUs\n");
+ update_reason(UPDATE_HFI);
+ cpumask_copy(CPUMASK_HFI, CPUMASK_HFI_LAST);
+ } else if (cpumask_has_cpu(CPUMASK_HFI_LAST)) {
+ lpmd_log_debug("\tHFI LPM recover\n");
+// Don't override the DETECT_LPM_CPU_DEFAULT so it is auto recovered
+ cpumask_copy(CPUMASK_ONLINE, CPUMASK_HFI);
+ update_reason(UPDATE_HFI);
+ cpumask_reset(CPUMASK_HFI_LAST);
+ } else {
+ lpmd_log_info("\t\t\tUnsupported HFI event ignored\n");
+ }
+}
+
+static void __handle_event(struct nlattr *cap, int *index,
+ int *offset, char *buf, struct perf_cap *perf_cap,
+ int *first_cpu, int *last_cpu, int *nr_cpus)
+{
+ switch (*index) {
+ case 0:
+ *offset += snprintf(buf + *offset, MAX_STR_LENGTH -
+ *offset, "\tCPU %3d: ", nla_get_u32(cap));
+ perf_cap->cpu = nla_get_u32(cap);
+ break;
+ case 1:
+ *offset += snprintf(buf + *offset, MAX_STR_LENGTH -
+ *offset, " PERF [%4d] ", nla_get_u32(cap));
+ perf_cap->perf = nla_get_u32(cap);
+ break;
+ case 2:
+ *offset += snprintf(buf + *offset, MAX_STR_LENGTH -
+ *offset, " EFF [%4d] ", nla_get_u32(cap));
+ perf_cap->eff = nla_get_u32(cap);
+ break;
+ default:
+ break;
+ }
+ *index += 1;
+
+ if (*index == 3) {
+ char *str;
+
+ str = update_one_cpu(perf_cap);
+ *offset += snprintf(buf + *offset, MAX_STR_LENGTH -
+ *offset, " TYPE [%s]", str);
+ buf[MAX_STR_LENGTH - 1] = '\0';
+ lpmd_log_debug("\t\t\t%s\n", buf);
+
+ *index = 0;
+ *offset = 0;
+
+ if (*first_cpu == -1)
+ *first_cpu = perf_cap->cpu;
+ *last_cpu = perf_cap->cpu;
+ *nr_cpus += 1;
+ }
+}
+
+static int handle_event(struct nl_msg *n, void *arg)
+{
+ struct nlmsghdr *nlh = nlmsg_hdr(n);
+ struct genlmsghdr *genlhdr = genlmsg_hdr(nlh);
+ struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1];
+ struct nlattr *cap;
+ struct perf_cap perf_cap;
+ int first_cpu = -1, last_cpu = -1, nr_cpus = 0;
+ int j, index = 0, offset = 0;
+ char buf[MAX_STR_LENGTH];
+
+ if (genlhdr->cmd != THERMAL_GENL_EVENT_CAPACITY_CHANGE)
+ return 0;
+
+ if (genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL))
+ return -1;
+
+ perf_cap.eff = -1;
+ perf_cap.perf = -1;
+ perf_cap.cpu = -1;
+
+ nla_for_each_nested(cap, attrs[THERMAL_GENL_ATTR_CAPACITY], j)
+ __handle_event(cap, &index, &offset, buf, &perf_cap,
+ &first_cpu, &last_cpu, &nr_cpus);
+ process_one_event(first_cpu, last_cpu, nr_cpus);
+
+ return 0;
+}
+
+static int done;
+
+int hfi_kill(void)
+{
+ nl_socket_free(drv.nl_handle);
+ done = 1;
+ return 0;
+}
+
+int hfi_update(void)
+{
+ int err = 0;
+
+ while (!err)
+ err = nl_recvmsgs(drv.nl_handle, drv.nl_cb);
+
+ return 0;
+}
+
+int hfi_init(void)
+{
+ struct nl_sock *sock;
+ struct nl_cb *cb;
+ int mcast_id;
+
+ cpumask_reset(CPUMASK_HFI_LAST);
+
+ signal(SIGPIPE, SIG_IGN);
+
+ sock = nl_socket_alloc();
+ if (!sock) {
+ lpmd_log_error("nl_socket_alloc failed\n");
+ goto err_proc;
+ }
+
+ if (genl_connect(sock)) {
+ lpmd_log_error("genl_connect(sk_event) failed\n");
+ goto err_proc;
+ }
+
+ drv.nl_handle = sock;
+ cb = nl_cb_alloc(NL_CB_DEFAULT);
+ drv.nl_cb = cb;
+ if (!drv.nl_cb) {
+ lpmd_log_error("Failed to allocate netlink callbacks");
+ goto err_proc;
+ }
+
+ mcast_id = nl_get_multicast_id(&drv, THERMAL_GENL_FAMILY_NAME,
+ THERMAL_GENL_EVENT_GROUP_NAME);
+ if (mcast_id < 0) {
+ lpmd_log_error("nl_get_multicast_id failed\n");
+ goto err_proc;
+ }
+
+ if (nl_socket_add_membership(sock, mcast_id)) {
+ lpmd_log_error("nl_socket_add_membership failed");
+ goto err_proc;
+ }
+
+ nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, seq_check_handler, &done);
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, handle_event, NULL);
+
+ nl_socket_set_nonblocking(sock);
+
+ if (drv.nl_handle)
+ return nl_socket_get_fd(drv.nl_handle);
+
+err_proc: return -1;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_irq.c b/tools/power/x86/intel-lpmd/src/lpmd_irq.c
new file mode 100644
index 000000000000..cb8a3a55da32
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_irq.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sched.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <signal.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/family.h>
+#include <netlink/genl/ctrl.h>
+
+#include "lpmd.h"
+
+static char irq_socket_name[64];
+
+static int irqbalance_pid = -1;
+
+#define MAX_IRQS 128
+
+struct info_irq {
+ int irq;
+ char affinity[MAX_STR_LENGTH];
+};
+
+struct info_irqs {
+ /* Cached IRQ smp_affinity info */
+ int nr_irqs;
+ struct info_irq irq[MAX_IRQS];
+};
+
+struct info_irqs info_irqs;
+struct info_irqs *info = &info_irqs;
+
+/* Interrupt Management */
+#define SOCKET_PATH "irqbalance"
+#define SOCKET_TMPFS "/run/irqbalance"
+
+static int irqbalance_ban_cpus(char *irq_str)
+{
+ char socket_cmd[MAX_STR_LENGTH];
+ int offset;
+
+ lpmd_log_debug("\tUpdate IRQ affinity (irqbalance)\n");
+ offset = snprintf(socket_cmd, MAX_STR_LENGTH, "settings cpus %s", irq_str);
+ if (offset >= MAX_STR_LENGTH)
+ offset = MAX_STR_LENGTH - 1;
+
+ socket_cmd[offset] = '\0';
+ socket_send_cmd(irq_socket_name, socket_cmd);
+
+ lpmd_log_debug("\tSend socket command %s\n", socket_cmd);
+ return 0;
+}
+
+static int native_restore_irqs(void)
+{
+ char path[MAX_STR_LENGTH];
+ int i;
+
+ lpmd_log_debug("\tRestore IRQ affinity (native)\n");
+
+ for (i = 0; i < info->nr_irqs; i++) {
+ char *str = info->irq[i].affinity;
+
+ snprintf(path, MAX_STR_LENGTH, "/proc/irq/%i/smp_affinity", info->irq[i].irq);
+
+ lpmd_write_str(path, str, LPMD_LOG_DEBUG);
+ }
+ memset(info, 0, sizeof(*info));
+ return 0;
+}
+
+static int irq_updated;
+
+static int update_one_irq(int irq, char *irq_str)
+{
+ char path[MAX_STR_LENGTH];
+ char *str = NULL;
+ size_t size = 0;
+ FILE *filep;
+
+ if (info->nr_irqs >= (MAX_IRQS - 1)) {
+ lpmd_log_error("Too many IRQs\n");
+ return -1;
+ }
+
+ snprintf(path, MAX_STR_LENGTH, "/proc/irq/%i/smp_affinity", irq);
+
+ if (!irq_updated) {
+ info->irq[info->nr_irqs].irq = irq;
+ filep = fopen(path, "r");
+ if (!filep)
+ return -1;
+
+ if (getline(&str, &size, filep) <= 0) {
+ lpmd_log_error("Failed to get IRQ%d smp_affinity\n", irq);
+ free(str);
+ fclose(filep);
+ return -1;
+ }
+
+ fclose(filep);
+
+ snprintf(info->irq[info->nr_irqs].affinity, MAX_STR_LENGTH, "%s", str);
+
+ free(str);
+
+ /* Remove the Newline */
+ size = strnlen(info->irq[info->nr_irqs].affinity, MAX_STR_LENGTH);
+ info->irq[info->nr_irqs].affinity[size - 1] = '\0';
+
+ info->nr_irqs++;
+ }
+
+ return lpmd_write_str(path, irq_str, LPMD_LOG_DEBUG);
+}
+
+static int native_update_irqs(char *irq_str)
+{
+ char *line = NULL;
+ size_t size = 0;
+ FILE *filep;
+
+ lpmd_log_debug("\tUpdate IRQ affinity (native)\n");
+
+ filep = fopen("/proc/interrupts", "r");
+ if (!filep) {
+ perror("Error open /proc/interrupts\n");
+ return -1;
+ }
+
+ /* first line is the header we don't need; nuke it */
+ if (getline(&line, &size, filep) <= 0) {
+ perror("Error getline\n");
+ free(line);
+ fclose(filep);
+ return -1;
+ }
+ free(line);
+
+ while (!feof(filep)) {
+ int number;
+ char *c;
+
+ line = NULL;
+ size = 0;
+
+ if (getline(&line, &size, filep) <= 0) {
+ free(line);
+ break;
+ }
+
+ /* lines with letters in front are special, like NMI count. Ignore */
+ c = line;
+ while (isblank(*(c)))
+ c++;
+
+ if (!isdigit(*c)) {
+ free(line);
+ break;
+ }
+ c = strchr(line, ':');
+ if (!c) {
+ free(line);
+ continue;
+ }
+
+ *c = 0;
+ number = strtoul(line, NULL, 10);
+
+ update_one_irq(number, irq_str);
+ free(line);
+ }
+
+ fclose(filep);
+
+ irq_updated = 1;
+ return 0;
+}
+
+int process_irq(struct lpmd_config_state_t *state)
+{
+ switch (state->irq_migrate) {
+ case SETTING_IGNORE:
+ lpmd_log_info("Ignore IRQ migration\n");
+ return 0;
+ case SETTING_RESTORE:
+ if (irqbalance_pid == -1)
+ native_restore_irqs();
+ else
+ irqbalance_ban_cpus("NULL");
+ return 0;
+ default:
+ if (state->cpumask_idx == CPUMASK_NONE)
+ return 0;
+ if (irqbalance_pid == -1)
+ native_update_irqs(get_proc_irq_str(state->cpumask_idx));
+ else
+ irqbalance_ban_cpus(get_irqbalance_str(state->cpumask_idx));
+ return 0;
+ }
+ return 0;
+}
+
+int irq_init(void)
+{
+ DIR *dir;
+ int socket_fd;
+ int ret;
+
+ lpmd_log_info("Detecting IRQs ...\n");
+
+ dir = opendir("/run/irqbalance");
+ if (dir) {
+ struct dirent *entry;
+
+ do {
+ entry = readdir(dir);
+ if (entry) {
+ if (!strncmp(entry->d_name, "irqbalance", 10)) {
+ ret = sscanf(entry->d_name, "irqbalance%d.sock", &irqbalance_pid);
+ if (!ret)
+ irqbalance_pid = -1;
+ }
+ }
+ } while ((entry) && (irqbalance_pid == -1));
+
+ closedir(dir);
+ }
+
+ if (irqbalance_pid == -1) {
+ lpmd_log_info("\tirqbalance not running, run in native mode\n");
+ return LPMD_SUCCESS;
+ }
+
+ snprintf(irq_socket_name, 64, "%s/%s%d.sock", SOCKET_TMPFS, SOCKET_PATH, irqbalance_pid);
+ socket_fd = socket_init_connection(irq_socket_name);
+ if (socket_fd < 0) {
+ lpmd_log_error("Can not connect to irqbalance socket /run/irqbalance/irqbalance%d.sock\n",
+ irqbalance_pid);
+ return LPMD_ERROR;
+ }
+ close(socket_fd);
+ lpmd_log_info("\tFind irqbalance socket %s\n", irq_socket_name);
+ return LPMD_SUCCESS;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_main.c b/tools/power/x86/intel-lpmd/src/lpmd_main.c
new file mode 100644
index 000000000000..56a163c80ff5
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_main.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-unix.h>
+#include <syslog.h>
+
+#include "lpmd.h"
+
+#if !defined(INTEL_LPMD_DIST_VERSION)
+#define INTEL_LPMD_DIST_VERSION PACKAGE_VERSION
+#endif
+
+#define EXIT_UNSUPPORTED 2
+
+// Lock file
+static int lock_file_handle = -1;
+static const char *lock_file = TDRUNDIR "/intel_lpmd.pid";
+
+// Default log level
+static int lpmd_log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING
+ | G_LOG_LEVEL_MESSAGE;
+
+int in_debug_mode(void)
+{
+ return !!(lpmd_log_level & G_LOG_LEVEL_DEBUG);
+}
+
+// Daemonize or not
+static gboolean intel_lpmd_daemonize;
+static gboolean use_syslog;
+
+// Disable dbus
+static gboolean ignore_platform_check = FALSE;
+static gboolean dbus_enable;
+
+int do_platform_check(void)
+{
+ if (ignore_platform_check)
+ return 0;
+
+ return 1;
+}
+
+static GMainLoop *g_main_loop;
+
+#ifdef GDBUS
+gint watcher_id = 0;
+#endif
+
+// g_log handler. All logs will be directed here
+static void intel_lpmd_logger(const gchar *log_domain, GLogLevelFlags log_level,
+ const gchar *message, gpointer user_data)
+{
+ int syslog_priority;
+ const char *prefix;
+ time_t seconds;
+
+ if (!(lpmd_log_level & log_level))
+ return;
+
+ switch (log_level) {
+ case G_LOG_LEVEL_ERROR:
+ prefix = "[CRIT]";
+ syslog_priority = LOG_CRIT;
+ break;
+ case G_LOG_LEVEL_CRITICAL:
+ prefix = "[ERR]";
+ syslog_priority = LOG_ERR;
+ break;
+ case G_LOG_LEVEL_WARNING:
+ prefix = "[WARN]";
+ syslog_priority = LOG_WARNING;
+ break;
+ case G_LOG_LEVEL_MESSAGE:
+ prefix = "[MSG]";
+ syslog_priority = LOG_NOTICE;
+ break;
+ case G_LOG_LEVEL_DEBUG:
+ prefix = "[DEBUG]";
+ syslog_priority = LOG_DEBUG;
+ break;
+ case G_LOG_LEVEL_INFO:
+ default:
+ prefix = "[INFO]";
+ syslog_priority = LOG_INFO;
+ break;
+ }
+
+ seconds = time(NULL);
+
+ if (use_syslog)
+ syslog(syslog_priority, "%s", message);
+ else
+ g_print("[%lld]%s%s", (long long)seconds, prefix, message);
+}
+
+static void clean_up_lockfile(void)
+{
+ if (lock_file_handle != -1) {
+ (void)close(lock_file_handle);
+ (void)unlink(lock_file);
+ }
+}
+
+static bool check_intel_lpmd_running(void)
+{
+ lock_file_handle = open(lock_file, O_RDWR | O_CREAT, 0600);
+ if (lock_file_handle == -1) {
+// Couldn't open lock file
+ lpmd_log_error("Could not open PID lock file %s, exiting\n", lock_file);
+ return false;
+ }
+// Try to lock file
+ if (lockf(lock_file_handle, F_TLOCK, 0) == -1) {
+// Couldn't get lock on lock file
+ lpmd_log_error("Couldn't get lock file %d\n", getpid());
+ close(lock_file_handle);
+ return true;
+ }
+
+ return false;
+}
+
+// SIGTERM & SIGINT handler
+static gboolean sig_int_handler(void)
+{
+// Call terminate function
+ lpmd_terminate();
+
+ sleep(1);
+
+ if (g_main_loop)
+ g_main_loop_quit(g_main_loop);
+
+// Clean up if any
+ clean_up_lockfile();
+
+ exit(EXIT_SUCCESS);
+
+ return FALSE;
+}
+
+int main(int argc, char *argv[])
+{
+ gboolean show_version = FALSE;
+ gboolean log_debug = FALSE;
+ gboolean no_daemon = FALSE;
+ gboolean log_info = FALSE;
+ gboolean systemd = FALSE;
+ GOptionContext *opt_ctx;
+ gboolean success;
+ int ret;
+
+ intel_lpmd_daemonize = TRUE;
+ dbus_enable = FALSE;
+ use_syslog = TRUE;
+
+ GOptionEntry options[] = {
+ { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Print intel_lpmd version and exit"), NULL },
+ { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &no_daemon, N_("Don't become a daemon: Default is daemon mode"), NULL },
+ { "systemd", 0, 0, G_OPTION_ARG_NONE, &systemd, N_("Assume daemon is started by systemd, always run in non-daemon mode when using this parameter"), NULL },
+ { "loglevel=info", 0, 0, G_OPTION_ARG_NONE, &log_info, N_("Log severity: info level and up"), NULL },
+ { "loglevel=debug", 0, 0, G_OPTION_ARG_NONE, &log_debug, N_("Log severity: debug level and up: Max logging"), NULL },
+ { "dbus-enable", 0, 0, G_OPTION_ARG_NONE, &dbus_enable, N_("Enable Dbus"), NULL },
+ { "ignore-platform-check", 0, 0, G_OPTION_ARG_NONE, &ignore_platform_check, N_("Ignore platform check"), NULL },
+ { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
+ };
+
+ if (!g_module_supported()) {
+ fprintf(stderr, "GModules are not supported on your platform!\n");
+ exit(EXIT_FAILURE);
+ }
+
+// Set locale to be able to use environment variables
+ setlocale(LC_ALL, "");
+
+ bindtextdomain(GETTEXT_PACKAGE, TDLOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+ textdomain(GETTEXT_PACKAGE);
+
+// Parse options
+ opt_ctx = g_option_context_new(NULL);
+ g_option_context_set_translation_domain(opt_ctx, GETTEXT_PACKAGE);
+ g_option_context_set_ignore_unknown_options(opt_ctx, FALSE);
+ g_option_context_set_help_enabled(opt_ctx, TRUE);
+ g_option_context_add_main_entries(opt_ctx, options, NULL);
+
+ g_option_context_set_summary(opt_ctx,
+ "Intel Energy Optimizer (LPMD) Daemon based on system usage takes action "
+ "to improve energy efficiency the system.\n\n"
+ "Copyright (c) 2024, Intel Corporation\n"
+ "This program comes with ABSOLUTELY NO WARRANTY.\n"
+ "This work is licensed under GPL v2.\n\n"
+ "Use \"man intel_lpmd\" to get more details.");
+
+ success = g_option_context_parse(opt_ctx, &argc, &argv, NULL);
+ g_option_context_free(opt_ctx);
+
+ if (!success) {
+ fprintf(stderr,
+ "Invalid option. Please use --help to see a list of valid options.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (show_version) {
+ fprintf(stdout, INTEL_LPMD_DIST_VERSION "\n");
+ exit(EXIT_SUCCESS);
+ }
+
+ if (getuid() != 0) {
+ fprintf(stderr, "You must be root to run intel_lpmd!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (g_mkdir_with_parents(TDRUNDIR, 0755) != 0) {
+ fprintf(stderr, "Cannot create '%s': %s", TDRUNDIR, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ if (g_mkdir_with_parents(TDCONFDIR, 0755) != 0) {
+ fprintf(stderr, "Cannot create '%s': %s", TDCONFDIR, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ if (log_info)
+ lpmd_log_level |= G_LOG_LEVEL_INFO;
+
+ if (log_debug)
+ lpmd_log_level |= G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG;
+
+ openlog("intel_lpmd", LOG_PID, LOG_USER | LOG_DAEMON | LOG_SYSLOG);
+// Don't care return val
+
+ intel_lpmd_daemonize = !no_daemon && !systemd;
+ use_syslog = !no_daemon || systemd;
+ g_log_set_handler(NULL, G_LOG_LEVEL_MASK, intel_lpmd_logger, NULL);
+
+ if (check_intel_lpmd_running()) {
+ lpmd_log_error("An instance of intel_lpmd is already running, exiting ...\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!intel_lpmd_daemonize) {
+ g_unix_signal_add(SIGINT, G_SOURCE_FUNC(sig_int_handler), NULL);
+ g_unix_signal_add(SIGTERM, G_SOURCE_FUNC(sig_int_handler), NULL);
+ }
+
+ // Create a main loop that will dispatch callbacks
+ g_main_loop = g_main_loop_new(NULL, FALSE);
+ if (!g_main_loop) {
+ clean_up_lockfile();
+ lpmd_log_error("Couldn't create GMainLoop:\n");
+ return LPMD_FATAL_ERROR;
+ }
+
+ if (intel_lpmd_daemonize) {
+ printf("Ready to serve requests: Daemonizing..\n");
+ lpmd_log_info("intel_lpmd ver %s: Ready to serve requests: Daemonizing..\n",
+ INTEL_LPMD_DIST_VERSION);
+
+ if (daemon(0, 0) != 0) {
+ clean_up_lockfile();
+ lpmd_log_error("Failed to daemonize.\n");
+ return LPMD_FATAL_ERROR;
+ }
+ }
+
+ if (dbus_enable)
+ intel_dbus_server_init(sig_int_handler);
+
+ ret = lpmd_main();
+
+ if (ret != LPMD_SUCCESS) {
+ clean_up_lockfile();
+ closelog();
+
+ if (ret == LPMD_ERROR)
+ exit(EXIT_UNSUPPORTED);
+ else
+ exit(EXIT_FAILURE);
+ }
+
+// Start service requests on the D-Bus
+ lpmd_log_debug("Start main loop\n");
+ g_main_loop_run(g_main_loop);
+ lpmd_log_warn("Oops g main loop exit..\n");
+
+#ifdef GDBUS
+ g_bus_unwatch_name(watcher_id);
+#endif
+
+ fprintf(stdout, "Exiting ..\n");
+ clean_up_lockfile();
+ closelog();
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_misc.c b/tools/power/x86/intel-lpmd/src/lpmd_misc.c
new file mode 100644
index 000000000000..2618f7d3e25f
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_misc.c
@@ -0,0 +1,483 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include "lpmd.h"
+
+/* ITMT Management */
+#define PATH_ITMT_CONTROL "/proc/sys/kernel/sched_itmt_enabled"
+#define PATH_ITMT_CONTROL_DEBUGFS "/sys/kernel/debug/x86/sched_itmt_enabled"
+
+static int has_itmt;
+static int saved_itmt = SETTING_IGNORE;
+
+int get_itmt(void)
+{
+ int val, ret;
+
+ if (!has_itmt)
+ return -1;
+
+ ret = lpmd_read_yn(PATH_ITMT_CONTROL_DEBUGFS, &val, -1);
+ if (!ret)
+ return val;
+
+ lpmd_log_debug("Read ITMT debugfs failed, fallback to sysctl\n");
+ ret = lpmd_read_int(PATH_ITMT_CONTROL, &val, -1);
+ if (ret) {
+ lpmd_log_error("Read ITMT sysctl failed\n");
+ return -1;
+ }
+
+ return val;
+}
+
+void itmt_init(void)
+{
+ if (lpmd_read_yn(PATH_ITMT_CONTROL_DEBUGFS, &saved_itmt, -1)) {
+ lpmd_log_debug("ITMT debugfs not detected\n");
+ } else {
+ has_itmt = 1;
+ return;
+ }
+
+ if (lpmd_read_int(PATH_ITMT_CONTROL, &saved_itmt, -1))
+ lpmd_log_debug("ITMT not detected\n");
+ else
+ has_itmt = 1;
+}
+
+int process_itmt(struct lpmd_config_state_t *state)
+{
+ int ret;
+
+ if (!has_itmt)
+ return 0;
+
+ switch (state->itmt_state) {
+ case SETTING_IGNORE:
+ lpmd_log_debug("Ignore ITMT\n");
+ return 0;
+ case SETTING_RESTORE:
+ ret = lpmd_write_yn(PATH_ITMT_CONTROL_DEBUGFS, saved_itmt, -1);
+ if (ret)
+ return lpmd_write_int(PATH_ITMT_CONTROL, saved_itmt, -1);
+ return ret;
+ default:
+ lpmd_log_debug("%s ITMT\n", state->itmt_state ? "Enable" : "Disable");
+ ret = lpmd_write_yn(PATH_ITMT_CONTROL_DEBUGFS, state->itmt_state, -1);
+ if (ret)
+ return lpmd_write_int(PATH_ITMT_CONTROL, state->itmt_state, -1);
+ return ret;
+ }
+}
+
+/* Slider Management */
+
+#define PATH_PLATFORM_PROFILE "/sys/class/platform-profile"
+#define NAME_SOC_SLD "SoC Power Slider"
+
+static char soc_sld_path[MAX_STR_LENGTH];
+static char soc_sld_profile[MAX_STR_LENGTH];
+static int slider_available;
+static int slider_unavailable;
+
+#define PATH_SOC_BALANCE_SLIDER "/sys/module/processor_thermal_soc_slider/parameters/slider_balance"
+#define PATH_SOC_OFFSET "/sys/module/processor_thermal_soc_slider/parameters/slider_offset"
+
+static void init_slider_path(void)
+{
+ struct dirent *entry;
+ DIR *dir;
+ int ret;
+
+ snprintf(soc_sld_path, MAX_STR_LENGTH, "%s", PATH_PLATFORM_PROFILE);
+
+ dir = opendir(soc_sld_path);
+ if (!dir) {
+ lpmd_log_debug("Cannot find %s\n", soc_sld_path);
+ goto slider_failed;
+ }
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (strlen(entry->d_name) > 100)
+ continue;
+ if (strncmp(entry->d_name, "platform-profile",
+ strlen("platform-profile")))
+ continue;
+
+ snprintf(soc_sld_path, MAX_STR_LENGTH, "%s/%s/name",
+ PATH_PLATFORM_PROFILE, entry->d_name);
+
+ ret = lpmd_read_str(soc_sld_path, soc_sld_profile, MAX_STR_LENGTH);
+ if (ret)
+ continue;
+
+ if (!strncmp(soc_sld_profile, NAME_SOC_SLD, strlen(NAME_SOC_SLD))) {
+ snprintf(soc_sld_path, MAX_STR_LENGTH, "%s/%s/profile",
+ PATH_PLATFORM_PROFILE, entry->d_name);
+ slider_available = 1;
+ break;
+ }
+ }
+
+ closedir(dir);
+
+ if (!entry) {
+ lpmd_log_debug("\tCannot find %s\n", NAME_SOC_SLD);
+ goto slider_failed;
+ }
+
+ lpmd_log_info("\tAvailable at %s/%s, use profile [%s]\n",
+ PATH_PLATFORM_PROFILE, entry->d_name, soc_sld_profile);
+
+ return;
+
+slider_failed:
+ lpmd_log_debug("\tIgnore soc_sld/sld_offset setting\n");
+ slider_unavailable = 1;
+}
+
+static int update_balance_slider(int slider)
+{
+ int ret;
+ static int current_slider = -1;
+
+ lpmd_log_debug("%s\n", __func__);
+
+ if (slider < 0)
+ return 0;
+
+ if (slider_unavailable)
+ return -1;
+
+ if (!slider_available) {
+ init_slider_path();
+ if (slider_unavailable)
+ return -1;
+ }
+
+ if (current_slider >= 0 && current_slider == slider)
+ return 0;
+
+ ret = lpmd_write_int(PATH_SOC_BALANCE_SLIDER, slider, 1);
+ if (ret)
+ return ret;
+
+ /* Read the current profile and rewrite to make the module params effective */
+ ret = lpmd_read_str(soc_sld_path, soc_sld_profile, MAX_STR_LENGTH);
+ if (ret)
+ return ret;
+
+ ret = lpmd_write_str(soc_sld_path, soc_sld_profile, 1);
+ if (ret)
+ return ret;
+
+ current_slider = slider;
+
+ return 0;
+}
+
+static int update_slider_offset(int offset)
+{
+ int ret;
+ static int current_slider_offset = -1;
+
+ if (slider_unavailable)
+ return -1;
+
+ if (offset < 0)
+ return 0;
+
+ if (!slider_available) {
+ init_slider_path();
+ if (slider_unavailable)
+ return -1;
+ }
+
+ if (current_slider_offset >= 0 && current_slider_offset == offset)
+ return 0;
+
+ ret = lpmd_write_int(PATH_SOC_OFFSET, offset, 1);
+ if (ret)
+ return ret;
+
+ /* Read the current profile and rewrite to make the module params effective */
+ ret = lpmd_read_str(soc_sld_path, soc_sld_profile, MAX_STR_LENGTH);
+ if (ret)
+ return ret;
+
+ ret = lpmd_write_str(soc_sld_path, soc_sld_profile, 1);
+ if (ret)
+ return ret;
+
+ current_slider_offset = offset;
+
+ return 0;
+}
+
+void process_balance_slider_default_update(struct lpmd_config_t *config)
+{
+ lpmd_log_debug("%s\n", __func__);
+
+ if (is_on_battery()) {
+ if (config->balance_slider_def_dc >= 0)
+ update_balance_slider(config->balance_slider_def_dc);
+ } else {
+ if (config->balance_slider_def_ac >= 0)
+ update_balance_slider(config->balance_slider_def_ac);
+ }
+}
+
+void process_slider_offset_default_update(struct lpmd_config_t *config)
+{
+ lpmd_log_debug("%s\n", __func__);
+
+ if (is_on_battery()) {
+ if (config->slider_offset_def_dc >= 0)
+ update_slider_offset(config->slider_offset_def_dc);
+ } else {
+ if (config->slider_offset_def_ac >= 0)
+ update_slider_offset(config->slider_offset_def_ac);
+ }
+}
+
+static int process_balance_slider(struct lpmd_config_state_t *state)
+{
+ lpmd_log_debug("%s\n", __func__);
+
+ if (is_on_battery())
+ return update_balance_slider(state->balance_slider_dc);
+ else
+ return update_balance_slider(state->balance_slider_ac);
+}
+
+static int process_slider_offset(struct lpmd_config_state_t *state)
+{
+ lpmd_log_debug("%s\n", __func__);
+
+ if (is_on_battery())
+ return update_slider_offset(state->slider_offset_dc);
+ else
+ return update_slider_offset(state->slider_offset_ac);
+}
+
+void process_slider(struct lpmd_config_t *config, struct lpmd_config_state_t *state)
+{
+ int ret;
+
+ ret = process_balance_slider(state);
+ if (ret)
+ process_balance_slider_default_update(config);
+
+ ret = process_slider_offset(state);
+ if (ret)
+ process_slider_offset_default_update(config);
+}
+
+/* EPP/EPB Management */
+#define MAX_EPP_STRING_LENGTH 32
+struct cpu_info {
+ char epp_str[MAX_EPP_STRING_LENGTH];
+ int epp;
+ int epb;
+};
+
+static struct cpu_info *saved_cpu_info;
+
+static int get_epp(char *path, int *val, char *str, int size)
+{
+ FILE *filep;
+ int epp;
+ int ret;
+
+ filep = fopen(path, "r");
+ if (!filep)
+ return 1;
+
+ ret = fscanf(filep, "%d", &epp);
+ if (ret == 1) {
+ *val = epp;
+ ret = 0;
+ goto end;
+ }
+
+ ret = fread(str, 1, size, filep);
+ if (ret <= 0) {
+ ret = 1;
+ } else {
+ if (ret >= size)
+ ret = size - 1;
+ str[ret - 1] = '\0';
+ ret = 0;
+ }
+end:
+ fclose(filep);
+ return ret;
+}
+
+static int set_epp(char *path, int val, char *str)
+{
+ FILE *filep;
+ int ret;
+
+ filep = fopen(path, "r+");
+ if (!filep)
+ return 1;
+
+ if (val >= 0) {
+ ret = fprintf(filep, "%d", val);
+ } else if (str && str[0] != '\0') {
+ ret = fprintf(filep, "%s", str);
+ } else {
+ fclose(filep);
+ return 1;
+ }
+
+ fclose(filep);
+
+ if (ret <= 0) {
+ if (val >= 0)
+ lpmd_log_error("Write \"%d\" to %s failed, ret %d\n", val, path, ret);
+ else
+ lpmd_log_error("Write \"%s\" to %s failed, ret %d\n", str, path, ret);
+ }
+ return !(ret > 0);
+}
+
+static char *get_ppd_default_epp(void)
+{
+ int ppd_mode = get_ppd_mode();
+
+ if (ppd_mode == PPD_INVALID)
+ return NULL;
+
+ if (ppd_mode == PPD_PERFORMANCE)
+ return "performance";
+
+ if (ppd_mode == PPD_POWERSAVER)
+ return "power";
+
+ if (is_on_battery())
+ return "balance_power";
+
+ return "balance_performance";
+}
+
+int get_epp_epb(int *epp, char *epp_str, int size, int *epb)
+{
+ char path[MAX_STR_LENGTH];
+
+ *epp = -1;
+ epp_str[0] = '\0';
+ /* CPU0 is always online */
+ snprintf(path, sizeof(path),
+ "/sys/devices/system/cpu/cpu%d/cpufreq/energy_performance_preference", 0);
+ get_epp(path, epp, epp_str, size);
+ epp_str[size - 1] = '\0';
+
+ snprintf(path, MAX_STR_LENGTH, "/sys/devices/system/cpu/cpu%d/power/energy_perf_bias", 0);
+ lpmd_read_int(path, epb, -1);
+ return 0;
+}
+
+int process_epp_epb(struct lpmd_config_state_t *state)
+{
+ int max_cpus = get_max_cpus();
+ char path[MAX_STR_LENGTH];
+ int ret;
+ int c;
+
+ if (state->epp == SETTING_IGNORE)
+ lpmd_log_info("Ignore EPP\n");
+ if (state->epb == SETTING_IGNORE)
+ lpmd_log_info("Ignore EPB\n");
+ if (state->epp == SETTING_IGNORE && state->epb == SETTING_IGNORE)
+ return 0;
+
+ for (c = 0; c < max_cpus; c++) {
+ int val;
+ char *str = NULL;
+
+ if (!is_cpu_online(c))
+ continue;
+
+ if (state->epp != SETTING_IGNORE) {
+ if (state->epp == SETTING_RESTORE) {
+ val = -1;
+ str = get_ppd_default_epp();
+ if (!str) {
+ /* Fallback to cached EPP */
+ val = saved_cpu_info[c].epp;
+ str = saved_cpu_info[c].epp_str;
+ }
+ } else {
+ val = state->epp;
+ }
+
+ snprintf(path, sizeof(path),
+ "/sys/devices/system/cpu/cpu%d/cpufreq/energy_performance_preference", c);
+ ret = set_epp(path, val, str);
+ if (!ret) {
+ if (val != -1)
+ lpmd_log_debug("Set CPU%d EPP to 0x%x\n",
+ c, val);
+ else
+ lpmd_log_debug("Set CPU%d EPP to %s\n",
+ c, saved_cpu_info[c].epp_str);
+ }
+ }
+
+ if (state->epb != SETTING_IGNORE) {
+ if (state->epb == SETTING_RESTORE)
+ val = saved_cpu_info[c].epb;
+ else
+ val = state->epb;
+
+ snprintf(path, MAX_STR_LENGTH,
+ "/sys/devices/system/cpu/cpu%d/power/energy_perf_bias", c);
+ ret = lpmd_write_int(path, val, -1);
+ if (!ret)
+ lpmd_log_debug("Set CPU%d EPB to 0x%x\n", c, val);
+ }
+ }
+ return 0;
+}
+
+int epp_epb_init(void)
+{
+ int max_cpus = get_max_cpus();
+ char path[MAX_STR_LENGTH];
+ int ret;
+ int c;
+
+ saved_cpu_info = calloc(max_cpus, sizeof(struct cpu_info));
+
+ for (c = 0; c < max_cpus; c++) {
+ saved_cpu_info[c].epp_str[0] = '\0';
+ saved_cpu_info[c].epp = -1;
+
+ if (!is_cpu_online(c))
+ continue;
+
+ snprintf(path, sizeof(path),
+ "/sys/devices/system/cpu/cpu%d/cpufreq/energy_performance_preference", c);
+ ret = get_epp(path, &saved_cpu_info[c].epp,
+ saved_cpu_info[c].epp_str, MAX_EPP_STRING_LENGTH);
+ if (!ret) {
+ if (saved_cpu_info[c].epp != -1)
+ lpmd_log_debug("CPU%d EPP: 0x%x\n", c, saved_cpu_info[c].epp);
+ else
+ lpmd_log_debug("CPU%d EPP: %s\n", c, saved_cpu_info[c].epp_str);
+ }
+
+ snprintf(path, MAX_STR_LENGTH,
+ "/sys/devices/system/cpu/cpu%d/power/energy_perf_bias", c);
+ ret = lpmd_read_int(path, &saved_cpu_info[c].epb, -1);
+ if (ret) {
+ saved_cpu_info[c].epb = -1;
+ continue;
+ }
+ lpmd_log_debug("CPU%d EPB: 0x%x\n", c, saved_cpu_info[c].epb);
+ }
+ return 0;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_proc.c b/tools/power/x86/intel-lpmd/src/lpmd_proc.c
new file mode 100644
index 000000000000..e34f631bc2e2
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_proc.c
@@ -0,0 +1,492 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "lpmd.h"
+#include <upower.h>
+#include "wlt_proxy.h"
+
+static struct lpmd_config_t lpmd_config;
+
+struct lpmd_config_t *get_lpmd_config(void)
+{
+ return &lpmd_config;
+}
+
+static UpClient *upower_client;
+
+static pthread_mutex_t lpmd_mutex;
+
+int lpmd_lock(void)
+{
+ return pthread_mutex_lock(&lpmd_mutex);
+}
+
+int lpmd_unlock(void)
+{
+ return pthread_mutex_unlock(&lpmd_mutex);
+}
+
+static int has_hfi_capability(void)
+{
+ unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0;
+
+ cpuid(6, eax, ebx, ecx, edx);
+ if (eax & (1 << 19)) {
+ lpmd_log_info("HFI capability detected\n");
+ return 1;
+ }
+ return 0;
+}
+
+/* Main functions */
+
+static int write_pipe_fd;
+
+static void lpmd_send_message(enum message_name_t msg_id, int size, unsigned char *msg)
+{
+ struct message_capsul_t msg_cap;
+ int result;
+
+ memset(&msg_cap, 0, sizeof(struct message_capsul_t));
+
+ msg_cap.msg_id = msg_id;
+ msg_cap.msg_size = (size > MAX_MSG_SIZE) ? MAX_MSG_SIZE : size;
+ if (msg)
+ memcpy(msg_cap.msg, msg, msg_cap.msg_size);
+
+ result = write(write_pipe_fd, &msg_cap, sizeof(struct message_capsul_t));
+ if (result < 0)
+ lpmd_log_warn("Write to pipe failed\n");
+}
+
+void lpmd_terminate(void)
+{
+ lpmd_send_message(TERMINATE, 0, NULL);
+ sleep(1);
+ if (upower_client)
+ g_clear_object(&upower_client);
+}
+
+void lpmd_force_on(void)
+{
+ lpmd_send_message(LPM_FORCE_ON, 0, NULL);
+}
+
+void lpmd_force_off(void)
+{
+ lpmd_send_message(LPM_FORCE_OFF, 0, NULL);
+}
+
+void lpmd_set_auto(void)
+{
+ lpmd_send_message(LPM_AUTO, 0, NULL);
+}
+
+#define LPMD_NUM_OF_POLL_FDS 5
+
+static pthread_t lpmd_core_main;
+static pthread_attr_t lpmd_attr;
+
+static struct pollfd poll_fds[LPMD_NUM_OF_POLL_FDS];
+static int poll_fd_cnt;
+
+static int idx_pipe_fd = -1;
+static int idx_uevent_fd = -1;
+static int idx_hfi_fd = -1;
+static int idx_wlt_fd = -1;
+
+#include <gio/gio.h>
+
+static GDBusProxy *power_profiles_daemon;
+
+static enum power_profile_daemon_mode ppd_mode = PPD_INVALID;
+
+int get_ppd_mode(void)
+{
+ return ppd_mode;
+}
+
+static void power_profiles_changed_cb(void)
+{
+ g_autoptr(GVariant)
+ active_profile_v = NULL;
+
+ active_profile_v = g_dbus_proxy_get_cached_property(power_profiles_daemon,
+ "ActiveProfile");
+
+ if (active_profile_v && g_variant_is_of_type(active_profile_v, G_VARIANT_TYPE_STRING)) {
+ const char *active_profile = g_variant_get_string(active_profile_v, NULL);
+
+ lpmd_log_debug("%s: %s\n", __func__, active_profile);
+
+ if (strcmp(active_profile, "power-saver") == 0) {
+ ppd_mode = PPD_POWERSAVER;
+ lpmd_send_message(lpmd_config.powersaver_def, 0, NULL);
+ } else if (strcmp(active_profile, "performance") == 0) {
+ ppd_mode = PPD_PERFORMANCE;
+ lpmd_send_message(lpmd_config.performance_def, 0, NULL);
+ } else if (strcmp(active_profile, "balanced") == 0) {
+ ppd_mode = PPD_BALANCED;
+ lpmd_send_message(lpmd_config.balanced_def, 0, NULL);
+ } else {
+ lpmd_log_warn("Ignore unsupported power profile: %s\n", active_profile);
+ }
+
+ if (lpmd_config.wlt_proxy_enable)
+ lpmd_config.data.polling_interval = DEF_POLLING_INTERVAL;
+ }
+}
+
+static int connect_to_power_profile_daemon(void)
+{
+ g_autoptr(GDBusConnection)
+ bus = NULL;
+
+ bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
+ if (bus) {
+ power_profiles_daemon =
+ g_dbus_proxy_new_sync(bus, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL, "net.hadess.PowerProfiles",
+ "/net/hadess/PowerProfiles",
+ "net.hadess.PowerProfiles", NULL, NULL);
+
+ if (power_profiles_daemon) {
+ g_signal_connect_swapped(power_profiles_daemon,
+ "g-properties-changed",
+ (GCallback)power_profiles_changed_cb,
+ NULL);
+ power_profiles_changed_cb();
+ return 0;
+ }
+ lpmd_log_info("Could not setup DBus watch for power-profiles-daemon");
+ }
+ return 1;
+}
+
+static int battery_mode = -1;
+
+int is_on_battery(void)
+{
+ if (battery_mode < 0)
+ battery_mode = up_client_get_on_battery(upower_client);
+
+ return battery_mode;
+}
+
+static void upower_daemon_cb(UpClient *client, GParamSpec *pspec, gpointer user_data)
+{
+ static int mode = -1;
+
+ battery_mode = up_client_get_on_battery(upower_client);
+ if (mode != battery_mode) {
+ process_balance_slider_default_update(&lpmd_config);
+ process_slider_offset_default_update(&lpmd_config);
+ }
+
+ mode = battery_mode;
+}
+
+static void connect_to_upower_daemon(void)
+{
+ GError *error = NULL;
+ GPtrArray *devices;
+ UpDevice *device;
+ int i;
+
+ upower_client = up_client_new_full(NULL, &error);
+ if (!upower_client) {
+ g_warning("Cannot connect to upowerd: %s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ lpmd_log_info("connected to upower daemon\n");
+ g_signal_connect(upower_client, "notify", G_CALLBACK(upower_daemon_cb), NULL);
+
+ devices = up_client_get_devices2(upower_client);
+ for (i = 0; i < devices->len; i++) {
+ device = g_ptr_array_index(devices, i);
+ g_signal_connect(device, "notify", G_CALLBACK(upower_daemon_cb), NULL);
+ }
+}
+
+/* Poll time out default */
+#define POLL_TIMEOUT_DEFAULT_SECONDS 1
+
+// called from LPMD main thread to process user and system messages
+static int proc_message(struct message_capsul_t *msg)
+{
+ lpmd_log_debug("Received message %d\n", msg->msg_id);
+ switch (msg->msg_id) {
+ case TERMINATE:
+ lpmd_log_msg("Terminating ...\n");
+ update_lpmd_state(LPMD_TERMINATE);
+ break;
+ case LPM_FORCE_ON:
+ // Always stay in LPM mode
+ update_lpmd_state(LPMD_ON);
+ break;
+ case LPM_FORCE_OFF:
+ // Never enter LPM mode
+ update_lpmd_state(LPMD_OFF);
+ break;
+ case LPM_AUTO:
+ // Enable oppotunistic LPM
+ update_lpmd_state(LPMD_AUTO);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void dump_poll_results(int ret)
+{
+ int i = 0;
+
+// if (!in_debug_mode())
+ if (1)
+ return;
+
+ if (idx_pipe_fd != -1) {
+ lpmd_log_debug("poll_fds[%s]: event %d, revent %d\n", " Pipe",
+ poll_fds[i].events, poll_fds[i].revents);
+ i++;
+ }
+
+ if (idx_uevent_fd != -1) {
+ lpmd_log_debug("poll_fds[%s]: event %d, revent %d\n", "Uevent",
+ poll_fds[i].events, poll_fds[i].revents);
+ i++;
+ }
+
+ if (idx_hfi_fd != -1) {
+ lpmd_log_debug("poll_fds[%s]: event %d, revent %d\n", " HFI",
+ poll_fds[i].events, poll_fds[i].revents);
+ i++;
+ }
+
+ if (idx_wlt_fd != -1) {
+ lpmd_log_debug("poll_fds[%s]: event %d, revent %d\n", " WLT",
+ poll_fds[i].events, poll_fds[i].revents);
+ i++;
+ }
+}
+
+void update_reason(int reason)
+{
+ lpmd_config.data.need_update |= 1 << reason;
+}
+
+// LPMD processing thread. This is callback to pthread lpmd_core_main
+static void *lpmd_core_main_loop(void *arg)
+{
+ struct message_capsul_t msg;
+ int wlt_hint, result, n;
+
+ lpmd_config.data.polling_interval = DEF_POLLING_INTERVAL;
+
+ for (;;) {
+ if (get_lpmd_state() == LPMD_TERMINATE)
+ break;
+
+ n = poll(poll_fds, poll_fd_cnt, lpmd_config.data.polling_interval);
+ if (n < 0) {
+ lpmd_log_warn("Write to pipe failed\n");
+ continue;
+ }
+ dump_poll_results(n);
+
+ /* Polling time out, update polling data */
+ if (n == 0 && lpmd_config.data.polling_interval > 0) {
+ update_reason(UPDATE_UTIL);
+ util_update(&lpmd_config);
+
+ if (lpmd_config.wlt_proxy_enable)
+ lpmd_config.data.wlt_hint =
+ read_wlt_proxy(&lpmd_config.data.polling_interval);
+ }
+
+ /* Check CPU hotplug. Maybe need to freeze lpmd */
+ if (idx_uevent_fd >= 0 && (poll_fds[idx_uevent_fd].revents & POLLIN))
+ check_cpu_hotplug();
+
+ /* Update CPUMASK_HFI */
+ if (idx_hfi_fd >= 0 && (poll_fds[idx_hfi_fd].revents & POLLIN))
+ hfi_update();
+
+ /* Update WLT hint */
+ if (idx_wlt_fd >= 0 && (poll_fds[idx_wlt_fd].revents & POLLPRI)) {
+ wlt_hint = wlt_update(poll_fds[idx_wlt_fd].fd);
+ if (wlt_hint != lpmd_config.data.wlt_hint) {
+ lpmd_config.data.wlt_hint = wlt_hint;
+ update_reason(UPDATE_WLT);
+ }
+ }
+
+ /* Respond Dbus commands */
+ if (idx_pipe_fd >= 0 && (poll_fds[idx_pipe_fd].revents & POLLIN)) {
+// process message written on pipe here
+
+ result = read(poll_fds[idx_pipe_fd].fd, &msg,
+ sizeof(struct message_capsul_t));
+ if (result < 0) {
+ lpmd_log_warn("read on wakeup fd failed\n");
+ poll_fds[idx_pipe_fd].revents = 0;
+ continue;
+ }
+ if (proc_message(&msg) < 0)
+ lpmd_log_debug("Terminating thread..\n");
+ update_reason(UPDATE_USER);
+ }
+
+ if (lpmd_config.data.need_update) {
+ /* Enter next state after collecting all system statistics */
+ lpmd_enter_next_state();
+ lpmd_config.data.need_update = 0;
+ }
+ }
+
+ if (lpmd_config.wlt_proxy_enable)
+ wlt_proxy_uninit();
+ hfi_kill();
+ cgroup_cleanup();
+
+ return NULL;
+}
+
+int lpmd_main(void)
+{
+ int wake_fds[2];
+ int ret;
+
+ lpmd_log_debug("%s begin\n", __func__);
+
+ ret = detect_supported_platform(&lpmd_config);
+ if (ret)
+ return ret;
+
+ ret = detect_cpu_topo(&lpmd_config);
+ if (ret)
+ return ret;
+
+// Call all lpmd related functions here
+ ret = lpmd_get_config(&lpmd_config);
+ if (ret)
+ return ret;
+
+ pthread_mutex_init(&lpmd_mutex, NULL);
+
+ ret = detect_lpm_cpus(lpmd_config.lp_mode_cpus);
+ if (ret)
+ return ret;
+
+ ret = cgroup_init(&lpmd_config);
+ if (ret)
+ return ret;
+
+ itmt_init();
+
+ ret = epp_epb_init();
+ if (ret)
+ return ret;
+
+ if (!has_hfi_capability())
+ lpmd_config.hfi_lpm_enable = 0;
+
+ /* Must done after init_cpu() */
+ lpmd_build_config_states(&lpmd_config);
+
+ ret = irq_init();
+ if (ret)
+ return ret;
+
+ connect_to_upower_daemon();
+// Pipe is used for communication between two processes
+ ret = pipe(wake_fds);
+ if (ret) {
+ lpmd_log_error("pipe creation failed %d:\n", ret);
+ return LPMD_FATAL_ERROR;
+ }
+ if (fcntl(wake_fds[0], F_SETFL, O_NONBLOCK) < 0) {
+ lpmd_log_error("Cannot set non-blocking on pipe: %s\n", strerror(errno));
+ (void)close(wake_fds[0]);
+ (void)close(wake_fds[1]);
+ return LPMD_FATAL_ERROR;
+ }
+ if (fcntl(wake_fds[1], F_SETFL, O_NONBLOCK) < 0) {
+ lpmd_log_error("Cannot set non-blocking on pipe: %s\n", strerror(errno));
+ (void)close(wake_fds[0]);
+ (void)close(wake_fds[1]);
+ return LPMD_FATAL_ERROR;
+ }
+ write_pipe_fd = wake_fds[1];
+
+ memset(poll_fds, 0, sizeof(poll_fds));
+
+ idx_pipe_fd = poll_fd_cnt;
+ poll_fds[idx_pipe_fd].fd = wake_fds[0];
+ poll_fds[idx_pipe_fd].events = POLLIN;
+ poll_fds[idx_pipe_fd].revents = 0;
+ poll_fd_cnt++;
+
+ poll_fds[poll_fd_cnt].fd = uevent_init();
+ if (poll_fds[poll_fd_cnt].fd > 0) {
+ idx_uevent_fd = poll_fd_cnt;
+ poll_fds[idx_uevent_fd].events = POLLIN;
+ poll_fds[idx_uevent_fd].revents = 0;
+ poll_fd_cnt++;
+ }
+
+ if (lpmd_config.hfi_lpm_enable) {
+ poll_fds[poll_fd_cnt].fd = hfi_init();
+ if (poll_fds[poll_fd_cnt].fd > 0) {
+ idx_hfi_fd = poll_fd_cnt;
+ poll_fds[idx_hfi_fd].events = POLLIN;
+ poll_fds[idx_hfi_fd].revents = 0;
+ poll_fd_cnt++;
+ }
+ }
+
+ if (lpmd_config.wlt_hint_enable &&
+ lpmd_config.wlt_proxy_enable &&
+ wlt_proxy_init() != LPMD_SUCCESS) {
+ lpmd_config.wlt_proxy_enable = 0;
+ lpmd_log_error("Error setting up WLT Proxy. wlt_proxy_enable disabled\n");
+ }
+
+ if (lpmd_config.wlt_hint_enable && !lpmd_config.hfi_lpm_enable) {
+ lpmd_config.util_enable = 0;
+ if (!lpmd_config.wlt_proxy_enable) {
+ poll_fds[poll_fd_cnt].fd = wlt_init();
+ if (poll_fds[poll_fd_cnt].fd > 0) {
+ idx_wlt_fd = poll_fd_cnt;
+ poll_fds[idx_wlt_fd].events = POLLPRI;
+ poll_fds[idx_wlt_fd].revents = 0;
+ poll_fd_cnt++;
+ }
+ }
+ }
+
+ pthread_attr_init(&lpmd_attr);
+ pthread_attr_setdetachstate(&lpmd_attr, PTHREAD_CREATE_DETACHED);
+
+ /* Enable lpmd auto run when power profile daemon is not connected */
+ if (connect_to_power_profile_daemon())
+ lpmd_set_auto();
+
+ process_balance_slider_default_update(&lpmd_config);
+ process_slider_offset_default_update(&lpmd_config);
+
+ /*
+ * lpmd_core_main_loop: is the thread where all LPMD actions take place.
+ * All other thread send message via pipe to trigger processing
+ */
+ ret = pthread_create(&lpmd_core_main, &lpmd_attr, lpmd_core_main_loop, NULL);
+ if (ret)
+ return LPMD_FATAL_ERROR;
+
+ lpmd_log_debug("lpmd_init succeeds\n");
+
+ return LPMD_SUCCESS;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_socket.c b/tools/power/x86/intel-lpmd/src/lpmd_socket.c
new file mode 100644
index 000000000000..eae0d1615d45
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_socket.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2023 Intel Corporation. All rights reserved. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sched.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <signal.h>
+#include <pthread.h>
+
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <malloc.h>
+#include <syslog.h>
+#include <signal.h>
+#include <time.h>
+#include <inttypes.h>
+
+#include "lpmd.h"
+
+/* socket helpers */
+int socket_init_connection(char *name)
+{
+ struct sockaddr_un addr;
+ static int socket_fd;
+
+ if (!name)
+ return 0;
+
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+ if (socket_fd < 0) {
+ perror("Error opening socket");
+ return 0;
+ }
+ addr.sun_family = AF_UNIX;
+
+ snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", name);
+
+ if (connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ /* Try connect to abstract */
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ if (connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ close(socket_fd);
+ return 0;
+ }
+ }
+
+ return socket_fd;
+}
+
+static struct msghdr *create_credentials_msg(void)
+{
+ struct ucred *credentials;
+ struct msghdr *msg;
+ struct cmsghdr *cmsg;
+
+ credentials = malloc(sizeof(struct ucred));
+ if (!credentials)
+ return NULL;
+
+ credentials->pid = getpid();
+ credentials->uid = geteuid();
+ credentials->gid = getegid();
+
+ msg = malloc(sizeof(struct msghdr));
+ if (!msg) {
+ free(credentials);
+ return msg;
+ }
+
+ memset(msg, 0, sizeof(struct msghdr));
+ msg->msg_iovlen = 1;
+ msg->msg_control = malloc(CMSG_SPACE(sizeof(struct ucred)));
+ if (!msg->msg_control) {
+ free(credentials);
+ free(msg);
+ return NULL;
+ }
+
+ msg->msg_controllen = CMSG_SPACE(sizeof(struct ucred));
+
+ cmsg = CMSG_FIRSTHDR(msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_CREDENTIALS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ memcpy(CMSG_DATA(cmsg), credentials, sizeof(struct ucred));
+
+ free(credentials);
+ return msg;
+}
+
+int socket_send_cmd(char *name, char *data)
+{
+ int socket_fd;
+ struct msghdr *msg;
+ struct iovec iov;
+ char buf[MAX_STR_LENGTH];
+ int ret;
+
+ if (!name || !data)
+ return LPMD_ERROR;
+
+ socket_fd = socket_init_connection(name);
+ if (!socket_fd)
+ return LPMD_ERROR;
+
+ msg = create_credentials_msg();
+ if (!msg)
+ return LPMD_ERROR;
+
+ iov.iov_base = (void *)data;
+ iov.iov_len = strlen(data);
+ msg->msg_iov = &iov;
+
+ if (sendmsg(socket_fd, msg, 0) < 0) {
+ free(msg->msg_control);
+ free(msg);
+ return LPMD_ERROR;
+ }
+
+ ret = read(socket_fd, buf, MAX_STR_LENGTH);
+ if (ret < 0)
+ lpmd_log_debug("read failed\n");
+
+ close(socket_fd);
+ free(msg->msg_control);
+ free(msg);
+ return LPMD_SUCCESS;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_state_machine.c b/tools/power/x86/intel-lpmd/src/lpmd_state_machine.c
new file mode 100644
index 000000000000..b6fb31cad19d
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_state_machine.c
@@ -0,0 +1,747 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include "lpmd.h"
+
+/* LPMD state control: ON/OFF/AUTO/FREEZE/RESTORE/TERMINATE */
+static int lpmd_state = LPMD_OFF;
+static int saved_lpmd_state = LPMD_OFF;
+
+static char *lpmd_state_name[] = {
+ [LPMD_ON] = " ON",
+ [LPMD_OFF] = " OFF",
+ [LPMD_AUTO] = " AUTO",
+ [LPMD_FREEZE] = " FREEZE",
+ [LPMD_RESTORE] = "RESTORE",
+ [LPMD_TERMINATE] = " TERM",
+};
+
+int update_lpmd_state(int new)
+{
+ lpmd_lock();
+ switch (new) {
+ case LPMD_FREEZE:
+ if (lpmd_state == LPMD_FREEZE)
+ break;
+ lpmd_log_debug("Freeze lpmd\n");
+ saved_lpmd_state = lpmd_state;
+ lpmd_state = LPMD_FREEZE;
+ break;
+ case LPMD_RESTORE:
+ if (lpmd_state != LPMD_FREEZE)
+ break;
+ lpmd_log_debug("Restore lpmd\n");
+ lpmd_state = saved_lpmd_state;
+ saved_lpmd_state = lpmd_state;
+ break;
+ default:
+ if (lpmd_state == LPMD_FREEZE)
+ saved_lpmd_state = new;
+ else
+ lpmd_state = new;
+ break;
+ }
+ lpmd_unlock();
+ return 0;
+}
+
+int get_lpmd_state(void)
+{
+ return lpmd_state;
+}
+
+/* LPMD config states control */
+
+int lpmd_init_config_state(struct lpmd_config_state_t *state)
+{
+ state->id = -1;
+ state->valid = 0;
+ state->name[0] = '\0';
+
+ state->wlt_type = -1;
+
+ state->entry_system_load_thres = 0;
+ state->exit_system_load_thres = 0;
+ state->exit_system_load_hyst = 0;
+ state->enter_cpu_load_thres = 0;
+ state->exit_cpu_load_thres = 0;
+ state->enter_gfx_load_thres = 0;
+ state->exit_gfx_load_thres = 0;
+
+ state->min_poll_interval = 0;
+ state->max_poll_interval = 0;
+ state->poll_interval_increment = 0;
+
+ state->epp = SETTING_IGNORE;
+ state->epb = SETTING_IGNORE;
+ state->active_cpus[0] = '\0';
+ state->cpumask_idx = CPUMASK_NONE;
+
+ state->island_0_number_p_cores = 0;
+ state->island_0_number_e_cores = 0;
+ state->island_1_number_p_cores = 0;
+ state->island_1_number_e_cores = 0;
+ state->island_2_number_p_cores = 0;
+ state->island_2_number_e_cores = 0;
+
+ state->itmt_state = SETTING_IGNORE;
+ state->irq_migrate = SETTING_IGNORE;
+
+ state->entry_load_sys = 0;
+ state->entry_load_cpu = 0;
+ state->cpumask_idx = CPUMASK_NONE;
+
+ state->balance_slider_ac = -1;
+ state->balance_slider_dc = -1;
+ state->slider_offset_ac = -1;
+ state->slider_offset_dc = -1;
+
+ return 0;
+}
+
+static int current_idx = DEFAULT_OFF;
+
+static int config_state_match(struct lpmd_config_t *config, int idx)
+{
+ struct lpmd_config_state_t *state = &config->config_states[idx];
+ int bcpu = config->data.util_cpu;
+ int bsys = config->data.util_sys;
+ int bgfx = config->data.util_gfx;
+ int wlt_index = config->data.wlt_hint;
+
+ if (!state->valid)
+ return 0;
+
+ if (state->wlt_type != -1) {
+ if (config->wlt_hint_mask != -1)
+ wlt_index &= config->wlt_hint_mask;
+
+ if (state->wlt_type != wlt_index)
+ return 0;
+ }
+
+ if (state->enter_cpu_load_thres && state->enter_cpu_load_thres < bcpu)
+ return 0;
+
+ if (state->enter_gfx_load_thres && state->enter_gfx_load_thres < bgfx)
+ return 0;
+
+ if (state->entry_system_load_thres && state->entry_system_load_thres < bsys) {
+ if (!state->exit_system_load_hyst)
+ return 0;
+ if ((state->entry_load_sys + state->exit_system_load_hyst) < bsys ||
+ (state->entry_system_load_thres + state->exit_system_load_hyst) < bsys)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int polling_enabled;
+
+static int get_config_state_interval(struct lpmd_config_t *config, int idx)
+{
+ struct lpmd_config_state_t *state = &config->config_states[idx];
+
+ /* wlt proxy updates polling separately */
+ if (config->wlt_proxy_enable)
+ return 0;
+
+ /* Start polling only when needed */
+ if (!polling_enabled) {
+ config->data.polling_interval = -1;
+ return 0;
+ }
+
+ /* Always start with minimum polling interval for a new state */
+ if (idx != current_idx) {
+ config->data.polling_interval = state->min_poll_interval;
+ return 0;
+ }
+
+ /* CPU utilization based adaptive polling */
+ if (state->poll_interval_increment == -1) {
+ config->data.polling_interval =
+ state->max_poll_interval * (10000 - config->data.util_cpu) / 10000;
+ config->data.polling_interval /= 100;
+ config->data.polling_interval *= 100;
+ goto end;
+ }
+
+ /* lazy polling if load is sustained */
+ if (state->poll_interval_increment > 0)
+ config->data.polling_interval += state->poll_interval_increment;
+
+end:
+ /* Adjust based on min/max limitation */
+ if (config->data.polling_interval < state->min_poll_interval)
+ config->data.polling_interval = state->min_poll_interval;
+ if (config->data.polling_interval > state->max_poll_interval)
+ config->data.polling_interval = state->max_poll_interval;
+ return 0;
+}
+
+static void dump_state(struct lpmd_config_state_t *state, char *str, int debug)
+{
+ char buf[MAX_STR_LENGTH];
+ int offset = 0;
+
+ if (debug && !in_debug_mode())
+ return;
+
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "[%6s] [%s] [%s]: ", str,
+ lpmd_state_name[lpmd_state], state->name);
+
+ if (state->wlt_type)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "WLT [%2d] ", state->wlt_type);
+
+ if (state->entry_system_load_thres)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "SYS [%6d] ",
+ state->entry_system_load_thres / 100);
+
+ if (state->enter_cpu_load_thres)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "CPU [%6d] ",
+ state->enter_cpu_load_thres / 100);
+
+ if (state->enter_gfx_load_thres)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "GFX [%6d] ",
+ state->enter_gfx_load_thres / 100);
+
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "CPUMASK [%d] ", state->cpumask_idx);
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "IRQ [%d] ", state->irq_migrate);
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "ITMT [%d] ", state->itmt_state);
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "EPB [%d] ", state->epb);
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "EPP [%d] ", state->epp);
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "SliderAC [%d] ", state->balance_slider_ac);
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "SliderDC [%d] ", state->balance_slider_ac);
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "OffsetAC [%d] ", state->slider_offset_ac);
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "OffsetDC [%d] ", state->slider_offset_dc);
+
+ if (debug)
+ lpmd_log_debug("%s\n", buf);
+ else
+ lpmd_log_info("%s\n", buf);
+}
+
+static int choose_next_state(struct lpmd_config_t *config)
+{
+ int i;
+
+ switch (lpmd_state) {
+ case LPMD_ON:
+ return DEFAULT_ON;
+ case LPMD_OFF:
+ case LPMD_TERMINATE:
+ return DEFAULT_OFF;
+ }
+
+ /*
+ * DEFAULT_HFI is enabled only if HFI monitor is enabled
+ * and there is no user config states defined in the config file
+ */
+ if (config->config_states[DEFAULT_HFI].valid)
+ return DEFAULT_HFI;
+
+ /* Choose a config state */
+ for (i = CONFIG_STATE_BASE; i < CONFIG_STATE_BASE + config->config_state_count; ++i) {
+ if (config_state_match(config, i)) {
+ dump_state(&config->config_states[i], "Choose", 1);
+ return i;
+ }
+ dump_state(&config->config_states[i], "Ignore", 1);
+ }
+
+ return STATE_NONE;
+}
+
+static int get_state_interval(struct lpmd_config_t *config, int idx)
+{
+ switch (idx) {
+ case DEFAULT_ON:
+ case DEFAULT_OFF:
+ case DEFAULT_HFI:
+ config->data.polling_interval = -1;
+ return 0;
+ default:
+ get_config_state_interval(config, idx);
+ return 0;
+ }
+}
+
+static int need_enter(struct lpmd_config_t *config, int idx)
+{
+ if (idx != current_idx)
+ return 1;
+ if (!config->config_states[idx].steady)
+ return 1;
+
+ return 0;
+}
+
+static int enter_state(struct lpmd_config_t *config, int idx)
+{
+ struct lpmd_config_state_t *state = &config->config_states[idx];
+
+ state->entry_load_sys = config->data.util_sys;
+ state->entry_load_cpu = config->data.util_cpu;
+
+ process_slider(config, state);
+
+ process_itmt(state);
+
+ process_epp_epb(state);
+
+ process_irq(state);
+
+ process_cgroup(state, config->mode);
+
+ return 0;
+}
+
+static void dump_data(struct lpmd_config_t *config, int idx)
+{
+ char buf[MAX_STR_LENGTH];
+ char epp_str[32];
+ int epp, epb;
+ int offset = 0;
+ struct lpmd_config_state_t *state = &config->config_states[idx];
+
+ if (!in_debug_mode())
+ return;
+
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "[ Data] [%s] [%s]: ", lpmd_state_name[lpmd_state],
+ state->name);
+
+ if (config->wlt_hint_enable)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "WLT [%2d] ", config->data.wlt_hint);
+
+ if (config->util_sys_enable) {
+ if (config->data.util_sys == -1)
+ offset += snprintf(buf + offset,
+ MAX_STR_LENGTH - offset,
+ "SYS [ N/A] ");
+ else
+ offset += snprintf(buf + offset,
+ MAX_STR_LENGTH - offset,
+ "SYS [%3d.%02d] ",
+ config->data.util_sys / 100,
+ config->data.util_sys % 100);
+ }
+
+ if (config->util_cpu_enable) {
+ if (config->data.util_cpu == -1)
+ offset += snprintf(buf + offset,
+ MAX_STR_LENGTH - offset,
+ "CPU [ N/A] ");
+ else
+ offset += snprintf(buf + offset,
+ MAX_STR_LENGTH - offset,
+ "CPU [%3d.%02d] ",
+ config->data.util_cpu / 100,
+ config->data.util_cpu % 100);
+ }
+
+ if (config->util_gfx_enable) {
+ if (config->data.util_gfx == -1)
+ offset += snprintf(buf + offset,
+ MAX_STR_LENGTH - offset,
+ "GFX [ N/A] ");
+ else
+ offset += snprintf(buf + offset,
+ MAX_STR_LENGTH - offset,
+ "GFX [%3d.%02d] ",
+ config->data.util_gfx / 100,
+ config->data.util_gfx % 100);
+ }
+
+ if (state->cpumask_idx != CPUMASK_NONE)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "CPUMASK [%s] ",
+ get_cpus_hexstr(state->cpumask_idx));
+ else
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "CPUMASK [%s] ",
+ get_cpus_hexstr(CPUMASK_ONLINE));
+
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "ITMT [%d] ", get_itmt());
+
+ get_epp_epb(&epp, epp_str, 32, &epb);
+ if (epp == -1)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "EPB [%d] EPP[%s] ", epb, epp_str);
+ else
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "EPB [%d] EPP[%d] ", epb, epp);
+
+ if (config->hfi_lpm_enable)
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "UPDATE [%d] ", config->data.need_update);
+
+ offset += snprintf(buf + offset, MAX_STR_LENGTH - offset,
+ "Interval [%d]", config->data.polling_interval);
+
+ lpmd_log_debug("%s\n", buf);
+}
+
+int lpmd_enter_next_state(void)
+{
+ struct lpmd_config_t *config = get_lpmd_config();
+ int idx = current_idx;
+
+ lpmd_lock();
+
+ if (lpmd_state == LPMD_FREEZE) {
+ /* Wait till RESTORE */
+ config->data.polling_interval = -1;
+ goto end;
+ }
+
+ idx = choose_next_state(config);
+
+ /*
+ * After switching power profiles polling gets disabled and needs to be
+ * updated.
+ */
+ if (config->data.polling_interval == -1 && polling_enabled && idx != DEFAULT_OFF)
+ get_config_state_interval(config, idx);
+
+ /* No action needed, keep previous idx and interval */
+ if (idx == STATE_NONE)
+ goto end;
+
+ get_state_interval(config, idx);
+
+ if (need_enter(config, idx)) {
+ enter_state(config, idx);
+ current_idx = idx;
+ dump_state(&config->config_states[idx], "Enter", 0);
+ }
+
+end:
+ dump_data(config, current_idx);
+ lpmd_unlock();
+
+ return 0;
+}
+
+static void dump_states(struct lpmd_config_t *lpmd_config)
+{
+ int i;
+ struct lpmd_config_state_t *state;
+
+ if (!lpmd_config)
+ return;
+
+ lpmd_log_info("Mode:%d\n", lpmd_config->mode);
+ lpmd_log_info("HFI LPM Enable:%d\n", lpmd_config->hfi_lpm_enable);
+ lpmd_log_info("WLT Hint Enable:%d\n", lpmd_config->wlt_hint_enable);
+ lpmd_log_info("WLT Proxy Enable:%d\n", lpmd_config->wlt_proxy_enable);
+ lpmd_log_info("WLT Proxy Enable:%d\n", lpmd_config->wlt_hint_poll_enable);
+ lpmd_log_info("WLT Hint mask:%d\n", lpmd_config->wlt_hint_mask);
+ lpmd_log_info("Util Enable:%d\n", lpmd_config->util_enable);
+ lpmd_log_info("Util entry threshold:%d\n", lpmd_config->util_entry_threshold);
+ lpmd_log_info("Util exit threshold:%d\n", lpmd_config->util_exit_threshold);
+ lpmd_log_info("Util LP Mode CPUs:%s\n", lpmd_config->lp_mode_cpus);
+ lpmd_log_info("EPP in LP Mode:%d\n", lpmd_config->lp_mode_epp);
+ lpmd_log_info("CPU Family:%d\n", lpmd_config->cpu_family);
+ lpmd_log_info("CPU Model:%d\n", lpmd_config->cpu_model);
+ lpmd_log_info("CPU Config:%s\n", lpmd_config->cpu_config);
+
+ lpmd_log_info("balance_slider_def_ac:%d\n", lpmd_config->balance_slider_def_ac);
+ lpmd_log_info("balance_slider_def_dc:%d\n", lpmd_config->balance_slider_def_dc);
+ lpmd_log_info("slider_offset_def_ac:%d\n", lpmd_config->slider_offset_def_ac);
+ lpmd_log_info("slider_offset_def_dc:%d\n", lpmd_config->slider_offset_def_dc);
+
+ for (i = 0; i < MAX_STATES; ++i) {
+ state = &lpmd_config->config_states[i];
+
+ if (!state->valid)
+ continue;
+ lpmd_log_info("Index:%d\n", i);
+ lpmd_log_info("\tID:%d\n", state->id);
+ lpmd_log_info("\tName:%s\n", state->name);
+ lpmd_log_info("\tentry_system_load_thres:%d\n", state->entry_system_load_thres);
+ lpmd_log_info("\texit_system_load_thres:%d\n", state->exit_system_load_thres);
+ lpmd_log_info("\texit_system_load_hyst:%d\n", state->exit_system_load_hyst);
+ lpmd_log_info("\tentry_cpu_load_thres:%d\n", state->enter_cpu_load_thres);
+ lpmd_log_info("\texit_cpu_load_thres:%d\n", state->exit_cpu_load_thres);
+ lpmd_log_info("\tentry_gfx_load_thres:%d\n", state->enter_gfx_load_thres);
+ lpmd_log_info("\texit_gfx_load_thres:%d\n", state->exit_gfx_load_thres);
+ lpmd_log_info("\tWLT Type:%d\n", state->wlt_type);
+ lpmd_log_info("\tmin_poll_interval:%d\n", state->min_poll_interval);
+ lpmd_log_info("\tmax_poll_interval:%d\n", state->max_poll_interval);
+ lpmd_log_info("\tpoll_interval_increment:%d\n", state->poll_interval_increment);
+ lpmd_log_info("\tEPP:%d\n", state->epp);
+ lpmd_log_info("\tEPB:%d\n", state->epb);
+ lpmd_log_info("\tITMTState:%d\n", state->itmt_state);
+ lpmd_log_info("\tIRQMigrate:%d\n", state->irq_migrate);
+ if (state->active_cpus[0] != '\0')
+ lpmd_log_info("\tactive_cpus:%s\n", state->active_cpus);
+ lpmd_log_info("\tCPUMASK idx:%d\n", state->cpumask_idx);
+ lpmd_log_info("\tisland_0_number_p_cores:%d\n", state->island_0_number_p_cores);
+ lpmd_log_info("\tisland_0_number_e_cores:%d\n", state->island_0_number_e_cores);
+ lpmd_log_info("\tisland_1_number_p_cores:%d\n", state->island_1_number_p_cores);
+ lpmd_log_info("\tisland_1_number_e_cores:%d\n", state->island_1_number_e_cores);
+ lpmd_log_info("\tisland_2_number_p_cores:%d\n", state->island_2_number_p_cores);
+ lpmd_log_info("\tisland_2_number_e_cores:%d\n", state->island_2_number_e_cores);
+ lpmd_log_info("\tBalancedSliderAC:%d\n", state->balance_slider_ac);
+ lpmd_log_info("\tBalancedSliderDC:%d\n", state->balance_slider_dc);
+ lpmd_log_info("\tSliderOffsetAC:%d\n", state->slider_offset_ac);
+ lpmd_log_info("\tSliderOffsetDC:%d\n", state->slider_offset_dc);
+ }
+}
+
+static int build_default_states(struct lpmd_config_t *config)
+{
+ struct lpmd_config_state_t *state;
+
+ state = &config->config_states[DEFAULT_OFF];
+ lpmd_init_config_state(state);
+ state->id = -1;
+ snprintf(state->name, MAX_STATE_NAME, "DEFAULT_OFF");
+ state->itmt_state = SETTING_RESTORE;
+ state->irq_migrate = SETTING_RESTORE;
+ state->epp = SETTING_RESTORE;
+ state->epb = SETTING_RESTORE;
+ state->cpumask_idx = CPUMASK_ONLINE;
+ state->steady = 1;
+ state->valid = 1;
+
+ state = &config->config_states[DEFAULT_ON];
+ lpmd_init_config_state(state);
+ state->id = -1;
+ snprintf(state->name, MAX_STATE_NAME, "DEFAULT_ON");
+ state->itmt_state = config->ignore_itmt ? SETTING_IGNORE : 0;
+ state->irq_migrate = 1;
+ state->epp = config->lp_mode_epp;
+ state->epb = SETTING_IGNORE;
+ state->cpumask_idx = CPUMASK_LPM_DEFAULT;
+ state->steady = 1;
+ state->valid = 1;
+
+ if (config->config_state_count)
+ return 0;
+
+ /*
+ * When HFI monitor is enabled and config states are not used,
+ * Switch system with different CPU affinity based on HFI hints
+ */
+ if (config->hfi_lpm_enable) {
+ state = &config->config_states[DEFAULT_HFI];
+ lpmd_init_config_state(state);
+ state->id = -1;
+ snprintf(state->name, MAX_STATE_NAME, "DEFAULT_HFI");
+ state->itmt_state = SETTING_IGNORE;
+ state->irq_migrate = SETTING_IGNORE;
+ state->epp = SETTING_IGNORE;
+ state->epb = SETTING_IGNORE;
+ state->cpumask_idx = CPUMASK_HFI;
+ state->steady = 0;
+ state->valid = 1;
+
+ config->config_state_count = 1;
+ return 0;
+ }
+
+ /*
+ * When HFI monitor is not enabled and config states are not used,
+ * Switch system following global setting based on utilization.
+ */
+ state = &config->config_states[CONFIG_STATE_BASE];
+ lpmd_init_config_state(state);
+ state->id = 1;
+ snprintf(state->name, MAX_STATE_NAME, "UTIL_POWER");
+ state->entry_system_load_thres = config->util_entry_threshold;
+ state->enter_cpu_load_thres = config->util_exit_threshold;
+ state->itmt_state = config->ignore_itmt ? SETTING_IGNORE : 0;
+ state->irq_migrate = 1;
+ state->min_poll_interval = 100;
+ state->max_poll_interval = 1000;
+ state->poll_interval_increment = -1;
+ state->epp = config->lp_mode_epp;
+ state->epb = SETTING_IGNORE;
+ state->cpumask_idx = CPUMASK_LPM_DEFAULT;
+ state->steady = 1;
+ state->valid = 1;
+
+ state = &config->config_states[CONFIG_STATE_BASE + 1];
+ lpmd_init_config_state(state);
+ state->id = 2;
+ snprintf(state->name, MAX_STATE_NAME, "UTIL_PERF");
+ state->entry_system_load_thres = 100;
+ state->enter_cpu_load_thres = 100;
+ state->itmt_state = config->ignore_itmt ? SETTING_IGNORE : SETTING_RESTORE;
+ state->irq_migrate = 1;
+ state->min_poll_interval = 1000;
+ state->max_poll_interval = 1000;
+ state->epp = config->lp_mode_epp == SETTING_IGNORE ? SETTING_IGNORE : SETTING_RESTORE;
+ state->epb = SETTING_IGNORE;
+ state->cpumask_idx = CPUMASK_ONLINE;
+ state->steady = 1;
+ state->valid = 1;
+
+ config->config_state_count = 2;
+ return 0;
+}
+
+static int config_states_update_config(struct lpmd_config_t *config)
+{
+ struct lpmd_config_state_t *state;
+ int i;
+
+ for (i = CONFIG_STATE_BASE; i < CONFIG_STATE_BASE + config->config_state_count; i++) {
+ state = &config->config_states[i];
+
+ if (!state->valid)
+ continue;
+
+ if (state->cpumask_idx == CPUMASK_HFI)
+ config->hfi_lpm_enable = 1;
+
+ if (state->wlt_type != -1)
+ config->wlt_hint_enable = 1;
+
+ if (state->entry_system_load_thres)
+ config->util_sys_enable = 1;
+
+ if (state->enter_cpu_load_thres)
+ config->util_cpu_enable = 1;
+
+ if (state->enter_gfx_load_thres)
+ config->util_gfx_enable = 1;
+ }
+ return 0;
+}
+
+static int build_state_cpumask(struct lpmd_config_state_t *state)
+{
+ state->steady = 1;
+
+ if (state->cpumask_idx != CPUMASK_NONE)
+ return 0;
+
+ if (state->active_cpus[0] == '\0')
+ return 0;
+
+ if (!strncmp(state->active_cpus, "all", sizeof("all")) ||
+ !strncmp(state->active_cpus, "ALL", sizeof("ALL"))) {
+ state->cpumask_idx = CPUMASK_ONLINE;
+ return 0;
+ }
+
+ if (!strncmp(state->active_cpus, "lp", sizeof("lp")) ||
+ !strncmp(state->active_cpus, "LP", sizeof("LP"))) {
+ state->cpumask_idx = CPUMASK_LPM_DEFAULT;
+ return 0;
+ }
+
+ if (!strncmp(state->active_cpus, "hfi", sizeof("hfi")) ||
+ !strncmp(state->active_cpus, "HFI", sizeof("HFI"))) {
+ state->cpumask_idx = CPUMASK_HFI;
+ state->steady = 0;
+ return 0;
+ }
+
+ state->cpumask_idx = cpumask_alloc();
+ if (state->cpumask_idx == CPUMASK_NONE) {
+ lpmd_log_error("Cannot alloc CPUMASK\n");
+ return -1;
+ }
+
+ if (cpumask_init_cpus(state->active_cpus, state->cpumask_idx) <= 0) {
+ cpumask_free(state->cpumask_idx);
+ lpmd_log_error("Cannot parse cpumask string: %s\n", state->active_cpus);
+ return -1;
+ }
+
+ return 0;
+}
+
+#define DEFAULT_POLL_RATE_MS 1000
+
+int lpmd_build_config_states(struct lpmd_config_t *lpmd_config)
+{
+ struct lpmd_config_state_t *state;
+ int i;
+
+ build_default_states(lpmd_config);
+
+ for (i = CONFIG_STATE_BASE; i < CONFIG_STATE_BASE + lpmd_config->config_state_count; i++) {
+ state = &lpmd_config->config_states[i];
+
+ if (build_state_cpumask(state))
+ continue;
+
+ if (state->entry_system_load_thres ||
+ state->enter_cpu_load_thres || state->enter_gfx_load_thres)
+ polling_enabled = 1;
+
+ if (state->min_poll_interval <= 0)
+ state->min_poll_interval = state->max_poll_interval > DEFAULT_POLL_RATE_MS ?
+ DEFAULT_POLL_RATE_MS : state->max_poll_interval;
+ if (state->max_poll_interval <= 0)
+ state->max_poll_interval = state->min_poll_interval > DEFAULT_POLL_RATE_MS ?
+ state->min_poll_interval : DEFAULT_POLL_RATE_MS;
+ if (state->poll_interval_increment <= 0)
+ state->poll_interval_increment = -1;
+
+ if (state->entry_system_load_thres < 0 || state->entry_system_load_thres > 100)
+ continue;
+ else
+ state->entry_system_load_thres *= 100;
+
+ if (state->enter_cpu_load_thres < 0 || state->enter_cpu_load_thres > 100)
+ continue;
+ else
+ state->enter_cpu_load_thres *= 100;
+
+ if (state->exit_cpu_load_thres < 0 || state->exit_cpu_load_thres > 100)
+ continue;
+ else
+ state->exit_cpu_load_thres *= 100;
+
+ if (state->enter_gfx_load_thres < 0 || state->enter_gfx_load_thres > 100)
+ continue;
+ else
+ state->enter_gfx_load_thres *= 100;
+
+ state->valid = 1;
+ }
+
+ config_states_update_config(lpmd_config);
+ dump_states(lpmd_config);
+
+ return 0;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_uevent.c b/tools/power/x86/intel-lpmd/src/lpmd_uevent.c
new file mode 100644
index 000000000000..3fd34375922e
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_uevent.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2025 Intel Corporation. All rights reserved. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <err.h>
+#include <netlink/genl/ctrl.h>
+
+#include "lpmd.h"
+
+static int uevent_fd = -1;
+
+static int has_cpu_uevent(void)
+{
+ ssize_t i = 0;
+ ssize_t len;
+ const char *dev_path = "DEVPATH=";
+ unsigned int dev_path_len = strlen(dev_path);
+ const char *cpu_path = "/devices/system/cpu/cpu";
+ char buffer[MAX_STR_LENGTH];
+
+ len = recv(uevent_fd, buffer, sizeof(buffer) - 1, MSG_DONTWAIT);
+ if (len <= 0)
+ return 0;
+ buffer[len] = '\0';
+
+ lpmd_log_debug("Receive uevent: %s\n", buffer);
+
+ while (i < len) {
+ if (strlen(buffer + i) > dev_path_len &&
+ !strncmp(buffer + i, dev_path, dev_path_len)) {
+ if (!strncmp(buffer + i + dev_path_len, cpu_path,
+ strlen(cpu_path))) {
+ lpmd_log_debug("\tMatches: %s\n",
+ buffer + i + dev_path_len);
+ return 1;
+ }
+ }
+ i += strlen(buffer + i) + 1;
+ }
+
+ return 0;
+}
+
+#define PATH_PROC_STAT "/proc/stat"
+
+int check_cpu_hotplug(void)
+{
+ FILE *filep;
+ int curr;
+ int ret;
+
+ if (!has_cpu_uevent())
+ return 0;
+
+ filep = fopen(PATH_PROC_STAT, "r");
+ if (!filep)
+ return 0;
+
+ curr = cpumask_alloc();
+ if (curr == CPUMASK_NONE)
+ err(3, "ALLOC_CPUMASK");
+
+ while (!feof(filep)) {
+ char *tmpline = NULL;
+ size_t size = 0;
+ char *line;
+ int cpu;
+ char *p;
+ int ret;
+
+ tmpline = NULL;
+ size = 0;
+
+ if (getline(&tmpline, &size, filep) <= 0) {
+ free(tmpline);
+ break;
+ }
+
+ line = strdup(tmpline);
+
+ p = strtok(line, " ");
+
+ ret = sscanf(p, "cpu%d", &cpu);
+ if (ret != 1)
+ goto free;
+
+ cpumask_add_cpu(cpu, curr);
+
+free:
+ free(tmpline);
+ free(line);
+ }
+
+ fclose(filep);
+
+ ret = cpumask_equal(curr, CPUMASK_ONLINE);
+ cpumask_free(curr);
+
+ update_reason(UPDATE_CPUHOTPLUG);
+
+ if (ret)
+ return update_lpmd_state(LPMD_RESTORE);
+
+ return update_lpmd_state(LPMD_FREEZE);
+}
+
+int uevent_init(void)
+{
+ struct sockaddr_nl nls;
+
+ memset(&nls, 0, sizeof(struct sockaddr_nl));
+
+ nls.nl_family = AF_NETLINK;
+ nls.nl_pid = getpid();
+ nls.nl_groups = -1;
+
+ uevent_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
+ if (uevent_fd < 0)
+ return uevent_fd;
+
+ if (bind(uevent_fd, (struct sockaddr *)&nls,
+ sizeof(struct sockaddr_nl))) {
+ lpmd_log_warn("kob_uevent bind failed\n");
+ close(uevent_fd);
+ return -1;
+ }
+
+ lpmd_log_debug("Uevent binded\n");
+ return uevent_fd;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_util.c b/tools/power/x86/intel-lpmd/src/lpmd_util.c
new file mode 100644
index 000000000000..292190a580e8
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_util.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <err.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include "lpmd.h"
+
+#define PATH_PROC_STAT "/proc/stat"
+
+enum type_stat {
+ STAT_CPU,
+ STAT_USER,
+ STAT_NICE,
+ STAT_SYSTEM,
+ STAT_IDLE,
+ STAT_IOWAIT,
+ STAT_IRQ,
+ STAT_SOFTIRQ,
+ STAT_STEAL,
+ STAT_GUEST,
+ STAT_GUEST_NICE,
+ STAT_MAX,
+};
+
+struct proc_stat_info {
+ int cpu;
+ int valid;
+ unsigned long long stat[STAT_MAX];
+};
+
+struct proc_stat_info *proc_stat_prev;
+struct proc_stat_info *proc_stat_cur;
+
+static int busy_sys = -1;
+static int busy_cpu = -1;
+static int busy_gfx = -1;
+
+char *path_gfx_rc6;
+char *path_sam_mc6;
+
+static int probe_gfx_util_sysfs(void)
+{
+ FILE *fp;
+ char buf[8];
+ int ret;
+
+ if (access("/sys/class/drm/card0/device/tile0/gt0/gtidle/idle_residency_ms", R_OK))
+ return 1;
+
+ fp = fopen("/sys/class/drm/card0/device/tile0/gt0/gtidle/name", "r");
+ if (!fp)
+ return 1;
+
+ ret = fread(buf, sizeof(char), 7, fp);
+ if (!ret) {
+ fclose(fp);
+ return 1;
+ }
+
+ fclose(fp);
+
+ if (ret >= strlen("gt0-rc") && !strncmp(buf, "gt0-rc", strlen("gt0-rc"))) {
+ if (!access("/sys/class/drm/card0/device/tile0/gt0/gtidle/idle_residency_ms", R_OK))
+ path_gfx_rc6 = "/sys/class/drm/card0/device/tile0/gt0/gtidle/idle_residency_ms";
+ if (!access("/sys/class/drm/card0/device/tile0/gt1/gtidle/idle_residency_ms", R_OK))
+ path_sam_mc6 = "/sys/class/drm/card0/device/tile0/gt1/gtidle/idle_residency_ms";
+ } else if (ret >= strlen("gt0-mc") && !strncmp(buf, "gt0-mc", strlen("gt0-mc"))) {
+ if (!access("/sys/class/drm/card0/device/tile0/gt1/gtidle/idle_residency_ms", R_OK))
+ path_gfx_rc6 = "/sys/class/drm/card0/device/tile0/gt1/gtidle/idle_residency_ms";
+ if (!access("/sys/class/drm/card0/device/tile0/gt0/gtidle/idle_residency_ms", R_OK))
+ path_sam_mc6 = "/sys/class/drm/card0/device/tile0/gt0/gtidle/idle_residency_ms";
+ }
+ lpmd_log_debug("Use %s for gfx rc6\n", path_gfx_rc6);
+ lpmd_log_debug("Use %s for sam mc6\n", path_sam_mc6);
+ return 0;
+}
+
+static int get_gfx_util_sysfs(unsigned long long time_ms)
+{
+ static unsigned long long gfx_rc6_prev = ULLONG_MAX, sam_mc6_prev = ULLONG_MAX;
+ unsigned long long gfx_rc6 = ULLONG_MAX, sam_mc6 = ULLONG_MAX;
+ FILE *fp;
+ unsigned long long gfx_util, sam_util;
+ int ret;
+
+ gfx_util = -1;
+ sam_util = -1;
+
+ fp = fopen(path_gfx_rc6, "r");
+ if (fp) {
+ ret = fscanf(fp, "%lld", &gfx_rc6);
+ if (ret != 1)
+ gfx_rc6 = ULLONG_MAX;
+ fclose(fp);
+ }
+
+ fp = fopen(path_sam_mc6, "r");
+ if (fp) {
+ ret = fscanf(fp, "%lld", &sam_mc6);
+ if (ret != 1)
+ sam_mc6 = ULLONG_MAX;
+ fclose(fp);
+ }
+
+ if (gfx_rc6 == ULLONG_MAX && sam_mc6 == ULLONG_MAX)
+ return -1;
+
+ if (gfx_rc6 != ULLONG_MAX) {
+ if (gfx_rc6_prev != ULLONG_MAX)
+ gfx_util = 10000 - (gfx_rc6 - gfx_rc6_prev) * 10000 / time_ms;
+ gfx_rc6_prev = gfx_rc6;
+ }
+
+ if (sam_mc6 != ULLONG_MAX) {
+ if (sam_mc6_prev != ULLONG_MAX)
+ sam_util = 10000 - (sam_mc6 - sam_mc6_prev) * 10000 / time_ms;
+ sam_mc6_prev = sam_mc6;
+ }
+
+ return gfx_util > sam_util ? gfx_util : sam_util;
+}
+
+/* Get GFX_RC6 and SAM_MC6 from sysfs and calculate gfx util based on this */
+static int parse_gfx_util_sysfs(void)
+{
+ static int gfx_sysfs_available = 1;
+ static struct timespec ts_prev;
+ struct timespec ts_cur;
+ unsigned long time_ms;
+ int ret;
+
+ busy_gfx = -1;
+
+ if (!gfx_sysfs_available)
+ return 1;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts_cur);
+
+ if (!ts_prev.tv_sec && !ts_prev.tv_nsec) {
+ ret = probe_gfx_util_sysfs();
+ if (ret) {
+ gfx_sysfs_available = 0;
+ return 1;
+ }
+ ts_prev = ts_cur;
+ return 0;
+ }
+
+ time_ms = (ts_cur.tv_sec - ts_prev.tv_sec) * 1000 +
+ (ts_cur.tv_nsec - ts_prev.tv_nsec) / 1000000;
+
+ ts_prev = ts_cur;
+ busy_gfx = get_gfx_util_sysfs(time_ms);
+
+ return 0;
+}
+
+#define MSR_TSC 0x10
+#define MSR_PKG_ANY_GFXE_C0_RES 0x65A
+static int parse_gfx_util_msr(void)
+{
+ static uint64_t val_prev, tsc_prev;
+ uint64_t _busy_gfx, val, tsc;
+ int cpu;
+
+ busy_gfx = -1;
+
+ cpu = sched_getcpu();
+ tsc = read_msr(cpu, MSR_TSC);
+ if (tsc == UINT64_MAX)
+ goto err;
+
+ val = read_msr(cpu, MSR_PKG_ANY_GFXE_C0_RES);
+ if (val == UINT64_MAX)
+ goto err;
+
+ if (!tsc_prev || !val_prev) {
+ tsc_prev = tsc;
+ val_prev = val;
+ return 0;
+ }
+
+ if (val > val_prev && tsc > tsc_prev) {
+ _busy_gfx = abs(val - val_prev) * 10000ULL / abs(tsc - tsc_prev);
+ if (_busy_gfx < INT_MAX)
+ busy_gfx = (int)_busy_gfx;
+ }
+
+ tsc_prev = tsc;
+ val_prev = val;
+ return 0;
+err:
+ lpmd_log_debug("%s failed\n", __func__);
+ return 1;
+}
+
+static int parse_gfx_util(void)
+{
+ int ret;
+
+ /* Prefer to get graphics utilization from GFX/SAM RC6 sysfs */
+ ret = parse_gfx_util_sysfs();
+ if (!ret)
+ return 0;
+
+ /* Fallback to MSR */
+ return parse_gfx_util_msr();
+}
+
+static int calculate_busypct(struct proc_stat_info *cur, struct proc_stat_info *prev)
+{
+ int idx;
+ unsigned long long busy = 0, total = 0;
+
+ for (idx = STAT_USER; idx < STAT_MAX; idx++) {
+ total += (cur->stat[idx] - prev->stat[idx]);
+// Align with the "top" utility logic
+ if (idx != STAT_IDLE && idx != STAT_IOWAIT)
+ busy += (cur->stat[idx] - prev->stat[idx]);
+ }
+
+ if (total)
+ return busy * 10000 / total;
+ else
+ return 0;
+}
+
+static int parse_proc_stat(void)
+{
+ FILE *filep;
+ int i;
+ int val;
+ int count = get_max_online_cpu() + 1;
+ int sys_idx = count - 1;
+ size_t size = sizeof(struct proc_stat_info) * count;
+
+ filep = fopen(PATH_PROC_STAT, "r");
+ if (!filep)
+ return 1;
+
+ if (!proc_stat_prev)
+ proc_stat_prev = calloc(count, sizeof(struct proc_stat_info));
+
+ if (!proc_stat_prev) {
+ fclose(filep);
+ return 1;
+ }
+
+ if (!proc_stat_cur)
+ proc_stat_cur = calloc(count, sizeof(struct proc_stat_info));
+
+ if (!proc_stat_cur) {
+ free(proc_stat_prev);
+ fclose(filep);
+ proc_stat_prev = NULL;
+ return 1;
+ }
+
+ memcpy(proc_stat_prev, proc_stat_cur, size);
+ memset(proc_stat_cur, 0, size);
+
+ while (!feof(filep)) {
+ int idx;
+ char *tmpline = NULL;
+ struct proc_stat_info *info;
+ char *line;
+ int cpu;
+ char *p;
+ int ret;
+
+ tmpline = NULL;
+ size = 0;
+
+ if (getline(&tmpline, &size, filep) <= 0) {
+ free(tmpline);
+ break;
+ }
+
+ line = strdup(tmpline);
+
+ p = strtok(line, " ");
+
+ if (strncmp(p, "cpu", 3)) {
+ free(tmpline);
+ free(line);
+ continue;
+ }
+
+ ret = sscanf(p, "cpu%d", &cpu);
+ if (ret == -1 && !(strncmp(p, "cpu", 3))) {
+ /* Read system line */
+ info = &proc_stat_cur[sys_idx];
+ } else if (ret == 1) {
+ info = &proc_stat_cur[cpu];
+ } else {
+ free(tmpline);
+ free(line);
+ continue;
+ }
+
+ info->valid = 1;
+ idx = STAT_CPU;
+
+ while (p) {
+ if (idx >= STAT_MAX)
+ break;
+
+ if (idx == STAT_CPU) {
+ idx++;
+ p = strtok(NULL, " ");
+ continue;
+ }
+
+ if (sscanf(p, "%llu", &info->stat[idx]) <= 0)
+ lpmd_log_debug("Failed to parse /proc/stat, defer update in next snapshot.");
+
+ p = strtok(NULL, " ");
+ idx++;
+ }
+
+ free(tmpline);
+ free(line);
+ }
+
+ fclose(filep);
+ busy_sys = calculate_busypct(&proc_stat_cur[sys_idx], &proc_stat_prev[sys_idx]);
+
+ busy_cpu = 0;
+ for (i = 1; i <= get_max_online_cpu(); i++) {
+ if (!proc_stat_cur[i].valid)
+ continue;
+
+ val = calculate_busypct(&proc_stat_cur[i], &proc_stat_prev[i]);
+ if (busy_cpu < val)
+ busy_cpu = val;
+ }
+
+ return 0;
+}
+
+int util_update(struct lpmd_config_t *lpmd_config)
+{
+ if (lpmd_config->util_sys_enable || lpmd_config->util_cpu_enable) {
+ parse_proc_stat();
+ lpmd_config->data.util_sys = busy_sys;
+ lpmd_config->data.util_cpu = busy_cpu;
+ }
+
+ if (lpmd_config->util_gfx_enable) {
+ parse_gfx_util();
+ lpmd_config->data.util_gfx = busy_gfx;
+ }
+
+ return 0;
+}
diff --git a/tools/power/x86/intel-lpmd/src/lpmd_wlt.c b/tools/power/x86/intel-lpmd/src/lpmd_wlt.c
new file mode 100644
index 000000000000..98a2ab9bf1d5
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/lpmd_wlt.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2025 Intel Corporation. All rights reserved. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "lpmd.h"
+
+// Workload type classification
+#define WORKLOAD_NOTIFICATION_DELAY_ATTRIBUTE "/sys/bus/pci/devices/0000:00:04.0/workload_hint/notification_delay_ms"
+#define WORKLOAD_ENABLE_ATTRIBUTE "/sys/bus/pci/devices/0000:00:04.0/workload_hint/workload_hint_enable"
+#define WORKLOAD_TYPE_INDEX_ATTRIBUTE "/sys/bus/pci/devices/0000:00:04.0/workload_hint/workload_type_index"
+
+#define NOTIFICATION_DELAY 100
+
+// Read current Workload type
+int wlt_update(int fd)
+{
+ char index_str[4];
+ int index, ret;
+
+ if (fd < 0)
+ return WLT_INVALID;
+
+ if ((lseek(fd, 0L, SEEK_SET)) < 0)
+ return WLT_INVALID;
+
+ ret = read(fd, index_str, sizeof(index_str));
+ if (ret <= 0)
+ return WLT_INVALID;
+
+ ret = sscanf(index_str, "%d", &index);
+ if (ret < 0)
+ return WLT_INVALID;
+
+ lpmd_log_debug("wlt: %d\n", index);
+
+ return index;
+}
+
+// Clear workload type notifications
+int wlt_exit(void)
+{
+ int fd;
+
+ /* Disable feature via sysfs knob */
+ fd = open(WORKLOAD_ENABLE_ATTRIBUTE, O_RDWR);
+ if (fd < 0)
+ return 0;
+
+ // Disable WLT notification
+ if (write(fd, "0\n", 2) < 0) {
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+
+ return 0;
+}
+
+// Initialize Workload type notifications
+int wlt_init(void)
+{
+ char delay_str[64];
+ int fd;
+
+ lpmd_log_debug("init_wlt begin\n");
+
+ // Set notification delay
+ fd = open(WORKLOAD_NOTIFICATION_DELAY_ATTRIBUTE, O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ sprintf(delay_str, "%d\n", NOTIFICATION_DELAY);
+
+ if (write(fd, delay_str, strlen(delay_str)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+
+ // Enable WLT notification
+ fd = open(WORKLOAD_ENABLE_ATTRIBUTE, O_RDWR);
+ if (fd < 0)
+ return fd;
+
+ if (write(fd, "1\n", 2) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+
+ // Open FD for workload type attribute
+ fd = open(WORKLOAD_TYPE_INDEX_ATTRIBUTE, O_RDONLY);
+ if (fd < 0) {
+ wlt_exit();
+ return fd;
+ }
+
+ lpmd_log_debug("init_wlt end wlt fd:%d\n", fd);
+
+ return fd;
+}
diff --git a/tools/power/x86/intel-lpmd/src/wlt_proxy/include/state_common.h b/tools/power/x86/intel-lpmd/src/wlt_proxy/include/state_common.h
new file mode 100644
index 000000000000..5d89eb98d77a
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/wlt_proxy/include/state_common.h
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _WLT_PROXY_COMMON_H_
+#define _WLT_PROXY_COMMON_H_
+
+/* threshold (%) for instantaneous utilizations */
+#define UTIL_LOWEST 1
+#define UTIL_LOWER 2
+#define UTIL_LOW 10
+#define UTIL_FILL_START 35
+#define UTIL_BELOW_HALF 40
+#define UTIL_HALF 50
+#define UTIL_ABOVE_HALF 70
+#define UTIL_NEAR_FULL 90
+
+/* floating point comparison */
+#define EPSILON (0.01)
+#define A_LTE_B(A, B) ((((B) - (A)) >= EPSILON) ? 1 : 0)
+#define A_GTE_B(A, B) ((((A) - (B)) >= EPSILON) ? 1 : 0)
+#define A_GT_B(A, B) ((((A) - (B)) > EPSILON) ? 1 : 0)
+
+/* state indexes for WLT proxy detection based cpu usage high to low */
+enum state_idx {
+ INIT_MODE,
+ PERF_MODE,
+ MDRT4E_MODE,
+ MDRT3E_MODE,
+ MDRT2E_MODE,
+ RESP_MODE,
+ NORM_MODE,
+ DEEP_MODE
+};
+
+extern int state_demote; /* from state_manager.c */
+extern int burst_count; /* from spike_mgmt.c */
+extern struct group_util grp; /* from state_util.c */
+extern int next_proxy_poll; /* from wlt_proxy.c */
+extern int max_util; /* from state_machine.c */
+extern int wlt_type; /* from wlt_proxy.c */
+
+#define MAX_MODE 8
+
+struct group_util {
+ /* top 3 max utils and last (min) util */
+ float c0_max;
+ float c0_min;
+ float worst_stall;
+ int worst_stall_cpu;
+ float c0_2nd_max;
+ float c0_3rd_max;
+ int delta;
+
+ /* simple moving average for top 3 utils */
+ int sma_sum[3];
+ int sma_avg1;
+ int sma_avg2;
+ int sma_avg3;
+ int sma_pos;
+};
+
+/* feature states */
+#define DEACTIVATED (-1)
+#define UNDEFINED (0)
+#define RUNNING (1)
+#define ACTIVATED (2)
+#define PAUSE (3)
+
+/* state_manager.c */
+void uninit_state_manager(void);
+
+enum state_idx get_cur_state(void);
+
+int get_last_poll(void);
+int get_poll_ms(enum state_idx);
+int get_state_poll(int util, enum state_idx);
+
+int set_stay_count(enum state_idx, int count);
+int get_stay_count(enum state_idx);
+
+int staytime_to_staycount(enum state_idx state);
+int prep_state_change(enum state_idx, enum state_idx, int reset);
+
+int do_countdown(enum state_idx);
+
+/* state_util.c */
+int util_init_proxy(void);
+void util_uninit_proxy(void);
+
+int state_max_avg(void);
+int update_perf_diffs(float *sum_norm_perf, int stat_init_only);
+
+int max_mt_detected(enum state_idx);
+
+/* state_machine.c */
+int state_machine_auto(void);
+
+/* spike_mgmt.c */
+int add_spike_time(int duration);
+int add_non_spike_time(int duration);
+int get_spike_rate(void);
+int get_burst_rate_per_min(void);
+int fresh_burst_response(int initial_val);
+int burst_rate_breach(void);
+int strikeout_once(int n);
+
+#endif /* _WLT_PROXY_COMMON_H_ */
diff --git a/tools/power/x86/intel-lpmd/src/wlt_proxy/include/wlt_proxy.h b/tools/power/x86/intel-lpmd/src/wlt_proxy/include/wlt_proxy.h
new file mode 100644
index 000000000000..4f36d0c7514f
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/wlt_proxy/include/wlt_proxy.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _WLT_PROXY_H_
+#define _WLT_PROXY_H_
+
+int read_wlt_proxy(int *interval);
+int wlt_proxy_init(void);
+void wlt_proxy_uninit(void);
+
+#endif/* _WLT_PROXY_H_ */
diff --git a/tools/power/x86/intel-lpmd/src/wlt_proxy/spike_mgmt.c b/tools/power/x86/intel-lpmd/src/wlt_proxy/spike_mgmt.c
new file mode 100644
index 000000000000..dd54bd4b9510
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/wlt_proxy/spike_mgmt.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <time.h> //clockid_t
+#include <err.h> //perror
+#include <errno.h>
+#include <stdbool.h> //bool
+
+#include "state_common.h"
+
+/*
+ * spike burst refers to continuous spikes in a series of back to back samples.
+ * burt count and strength (as %) are good indicators to segregate random noise
+ * (that doesn't deserve performance) from bursty workload needing performance.
+ *
+ * Example of spike burst (|) and non-spike (.) sampling:
+ * ...||..||||...|...|||.....
+ * - here, first burst has two spikes.
+ * - second and third burst have 4 and 3 spikes respectively
+ * - the single spike in between is not considered as burst
+ *
+ * Using this a few indicators are derived:
+ * spike rate = total_spike_time * 100/ MAX_TRACKED_SPIKE_TIME
+ * spike_rate is defined as spike-time % of some MAX_TRACKED_SPIKE_TIME
+ *
+ * spike rate avg = spike_rate_total / spike_rate_samples
+ * spike rate avg is used to control how long "1 min" in bc_reset_min appears to the algo.
+ *
+ * bc_rest_min is used to control how long to wait before reset of past spike burst count
+ * SPIKE_TIME_BIAS macro will bias this to be longer or shorter based on
+ * recent history (i.e more prominent the spiking, the longer it will be remembered)
+ */
+#define MAX_TRACKED_SPIKE_TIME 1000
+#define MAX_BURST_COUNT 1000
+#define BURST_COUNT_THRESHOLD 3
+
+//shorten time by 50% if spike rate was as low as 0. No change if spike rate was 100
+#define SPIKE_TIME_BIAS(avg, min) ((100 - (avg)) * (min) / (2 * 100))
+
+int burst_count;
+
+/*local variables*/
+static int total_spike_time;
+static int spike_sec_prev;
+static int spike_rate_total;
+static int spike_rate_samples;
+static int burst_rate_per_min;
+static bool spike_burst_flag;
+static float bc_reset_min = 90.0;
+static int once_flag;
+static int strike_count;
+
+/** increment avg spike rate */
+int update_spike_rate_avg(int sr)
+{
+ spike_rate_total += sr;
+ spike_rate_samples++;
+ return 1;
+}
+
+/** reset avg spike rate */
+int clear_spike_rate_avg(void)
+{
+ spike_rate_samples = 0;
+ spike_rate_total = 0;
+ return 1;
+}
+
+/**
+ * burst count determines number of bursts occurred in recent past (1 min)
+ * arg real_spike specifies if invoked to update actual spike (1) or
+ * just a refresh to burst_count (0)
+ * burst count is decremented if no spikes in last 1 min
+ */
+static int update_burst_count(int real_spike_burst)
+{
+ float minutes = 1.0;
+ clockid_t clk = CLOCK_MONOTONIC;
+ struct timespec ts;
+
+ if (clock_gettime(clk, &ts)) {
+ perror("clock_gettime1");
+ return -1;
+ }
+
+ if (spike_sec_prev) {
+ minutes = (float)(ts.tv_sec - spike_sec_prev) / bc_reset_min;
+ } else {
+ spike_sec_prev = ts.tv_sec;
+ return 0;
+ }
+
+ if (real_spike_burst && (get_cur_state() <= MDRT4E_MODE)) {
+ burst_count++;
+ spike_sec_prev = ts.tv_sec;
+ } else if ((minutes > 1.0) || (burst_count > MAX_BURST_COUNT)) {
+ burst_count = 0;
+ spike_sec_prev = ts.tv_sec;
+ }
+
+ if (minutes < 1.0)
+ burst_rate_per_min = burst_count;
+ else if (minutes && (minutes > 1.0))
+ burst_rate_per_min = (int)((float)burst_count / minutes);
+
+ return burst_rate_per_min;
+}
+
+int get_burst_rate_per_min(void)
+{
+ return burst_rate_per_min;
+}
+
+int fresh_burst_response(int initial_burst_rate)
+{
+ if (!initial_burst_rate)
+ return 0;
+ if (initial_burst_rate >= BURST_COUNT_THRESHOLD ||
+ get_burst_rate_per_min() > initial_burst_rate)
+ return 1;
+ return 0;
+}
+
+int burst_rate_breach(void)
+{
+ return (get_burst_rate_per_min() >= BURST_COUNT_THRESHOLD) ? 1 : 0;
+}
+
+/* Calculate spike rate */
+int get_spike_rate(void)
+{
+ int spike_pct = total_spike_time * 100 / MAX_TRACKED_SPIKE_TIME;
+
+ return (spike_pct > 100) ? 100 : spike_pct;
+}
+
+/* count spikes */
+int add_spike_time(int duration)
+{
+ int spike_rate;
+
+ if (total_spike_time < MAX_TRACKED_SPIKE_TIME)
+ total_spike_time += duration;
+
+ /* spike burst has more than 1 spike */
+ if (!spike_burst_flag) {
+ /* rising edge of spike burst */
+ spike_burst_flag = true;
+ } else if (state_demote && !once_flag) {
+ update_burst_count(1);
+ once_flag = 1;
+ }
+
+ spike_rate = get_spike_rate();
+ update_spike_rate_avg(spike_rate);
+ return 1;
+}
+
+/* count idleness / non spike times */
+int add_non_spike_time(int duration)
+{
+ float avg;
+ int sr;
+
+ if (total_spike_time > 0)
+ total_spike_time -= duration;
+ total_spike_time = (total_spike_time < 0) ? 0 : total_spike_time;
+
+ sr = get_spike_rate();
+ if (!sr && spike_burst_flag) {
+ /* falling edge of burst */
+ spike_burst_flag = false;
+ avg = spike_rate_total / spike_rate_samples;
+
+ if (!once_flag)
+ update_burst_count(1);
+
+ bc_reset_min = 60.0 - (int)SPIKE_TIME_BIAS(avg, bc_reset_min);
+ clear_spike_rate_avg();
+ once_flag = 0;
+ } else {
+ update_burst_count(0);
+ once_flag = 0;
+ }
+ return 1;
+}
+
+/* decrement strike count */
+int strikeout_once(int n)
+{
+ if (!strike_count)
+ strike_count = n;
+ else
+ strike_count -= 1;
+
+ if (strike_count < 0)
+ strike_count = 0;
+
+ return strike_count;
+}
diff --git a/tools/power/x86/intel-lpmd/src/wlt_proxy/state_machine.c b/tools/power/x86/intel-lpmd/src/wlt_proxy/state_machine.c
new file mode 100644
index 000000000000..00666d014093
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/wlt_proxy/state_machine.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <math.h>
+
+#include "state_common.h"
+#include "lpmd.h" //logs
+
+/*
+ * stall scalability refer to non-stallable percentage of utilization.
+ * e.g due to memory or other dependency. If work is reasonably scaling well,
+ * values in 80 to 90+% is expected
+ */
+#define STALL_SCALE_LOWER_MARK 60
+
+#define N_STRIKE (10)
+
+/* threshold (%) for sustained (avg) utilizations */
+#define SUS_LOWER 2
+#define SUS_LOW_RANGE_START 4
+#define SUS_LOW_RANGE_END 25
+
+int max_util;
+
+static int only_once;
+
+/* function checks conditions for state switch */
+int state_machine_auto(void)
+{
+ float dummy;
+ int present_state = get_cur_state();
+
+ update_perf_diffs(&dummy, 0);
+ max_util = (int)round(grp.c0_max); //end
+
+ /*
+ * we do not want to track avg util for following case:
+ * a) Responsive transit mode (fast poll can flood avg leading to incorrect decisions)
+ */
+ if (present_state != RESP_MODE)
+ state_max_avg();
+
+ int completed_poll = get_last_poll();
+ float sum_c0 = grp.c0_max + grp.c0_2nd_max + grp.c0_3rd_max;
+ int mdrt_count;
+ int perf_count, initial_burst_count;
+ initial_burst_count = get_burst_rate_per_min();
+
+ mdrt_count = get_stay_count(MDRT3E_MODE);
+ int sr = get_spike_rate();
+
+ if (A_LTE_B(grp.c0_max, UTIL_NEAR_FULL))
+ add_non_spike_time(completed_poll);
+ else if (A_GT_B(grp.c0_max, UTIL_NEAR_FULL) || sr)
+ add_spike_time(completed_poll);
+
+ /* should we reset perf-count due to new burst? */
+ if (fresh_burst_response(initial_burst_count)) {
+ set_stay_count(PERF_MODE, staytime_to_staycount(PERF_MODE));
+ set_stay_count(MDRT3E_MODE, 0);
+ }
+
+ perf_count = get_stay_count(PERF_MODE);
+ if (!perf_count && !mdrt_count)
+ set_stay_count(MDRT3E_MODE, staytime_to_staycount(MDRT3E_MODE));
+
+ state_demote = 0;
+ int ismt = !max_mt_detected(INIT_MODE);
+
+ if (only_once == 0) {
+ lpmd_log_debug("present_state, isMT, C0_max, C0_2ndMax, sum_c0, sma avg1, sma avg2, sma avg3, worst_stall, next_proxy_poll\n");
+ only_once = 1;
+ }
+
+ lpmd_log_debug("%d, %d, %.2f, %.2f, %.2f, %d, %d, %d, %.2f, %d\n",
+ present_state,
+ ismt,
+ grp.c0_max,
+ grp.c0_2nd_max,
+ sum_c0,
+ grp.sma_avg1,
+ grp.sma_avg2,
+ grp.sma_avg3,
+ grp.worst_stall,
+ next_proxy_poll);
+
+ switch (present_state) {
+ case INIT_MODE:
+ // init mode is super-set of all default/available cpu on the system
+ // promote -- if not high multi-thread trend
+ if (!max_mt_detected(INIT_MODE)) {
+ lpmd_log_debug("INIT_MODE to PERF_MODE\n");
+ prep_state_change(INIT_MODE, PERF_MODE, 0);
+ break;
+ }
+ // stay -- full MT
+ break;
+
+ case PERF_MODE:
+ // Demote -- if highly MT
+ if (max_mt_detected(PERF_MODE)) {
+ lpmd_log_debug("PERF_MODE to INIT_MODE = mt detected.\n");
+ prep_state_change(PERF_MODE, INIT_MODE, 0);
+ break;
+ }
+ // Stay -- if there was recent perf/resp bursts
+ if (burst_count > 0 && !do_countdown(PERF_MODE)) {
+ lpmd_log_debug("PERF_MODE: burst_count is %d > 0 && !do_countdown\n",
+ burst_count);
+ break;
+ }
+ // Promote but through responsive watch -- if top sampled util and their avg are receding.
+ if (A_LTE_B(sum_c0, (2 * UTIL_LOW)) &&
+ A_LTE_B(grp.sma_avg1, UTIL_ABOVE_HALF)) {
+ lpmd_log_debug("PERF_MODE to RESP_MODE\n");
+ prep_state_change(PERF_MODE, RESP_MODE, 0);
+ break;
+ }
+ // Promote -- to moderate (3) MT state
+ if (!burst_rate_breach() &&
+ A_LTE_B(grp.c0_max, UTIL_LOW)) { // && A_LTE_B(sum_avg, UTIL_BELOW_HALF))
+ set_stay_count(MDRT3E_MODE, 0);
+ lpmd_log_debug("PERF_MODE to MDRT3E_MODE\n");
+ prep_state_change(PERF_MODE, MDRT3E_MODE, 0);
+ break;
+ }
+ //Stay -- all else
+ break;
+
+ case RESP_MODE:
+ // Demote -- if ST above halfway mark and avg trending higher
+ if (A_GT_B(grp.c0_max, UTIL_ABOVE_HALF) &&
+ A_GT_B(grp.sma_avg1, UTIL_BELOW_HALF)) {
+ lpmd_log_debug("RESP_MODE to PERF_MODE\n");
+ prep_state_change(RESP_MODE, PERF_MODE, 0);
+ break;
+ }
+ // Stay -- if there were recent burst of spikes
+ if (perf_count && burst_rate_breach())
+ break;
+
+ // Promote -- all else
+ if (A_LTE_B(grp.worst_stall * 100, STALL_SCALE_LOWER_MARK)) {
+ lpmd_log_debug("worst stall is less than STALL_SCALE_LOWER_MARK -- stay here.\n");
+ } else {
+ lpmd_log_debug("RESP_MODE to MDRT3E_MODE\n");
+ prep_state_change(RESP_MODE, MDRT3E_MODE, 0);
+ }
+ break;
+
+ case MDRT4E_MODE:
+ if (A_LTE_B(grp.worst_stall * 100, STALL_SCALE_LOWER_MARK)) {
+ lpmd_log_debug("MDRT4E_MODE to RESP_MODE\n");
+ prep_state_change(MDRT4E_MODE, RESP_MODE, 0);
+ break;
+ }
+ // Demote
+ if (A_GT_B(grp.c0_max, UTIL_NEAR_FULL)) {
+ if (!burst_rate_breach() && strikeout_once(N_STRIKE))
+ break;
+ lpmd_log_debug("MDRT4E_MODE to PERF_MODE\n");
+ prep_state_change(MDRT4E_MODE, PERF_MODE, 0);
+ break;
+ }
+ // promote
+ if (A_LTE_B(grp.sma_avg1, SUS_LOW_RANGE_END) &&
+ A_LTE_B(grp.sma_avg2, SUS_LOW_RANGE_END) &&
+ A_LTE_B(sum_c0, UTIL_HALF)) {
+ if (!do_countdown(MDRT4E_MODE))
+ break;
+ lpmd_log_debug("MDRT4E_MODE to NORM_MODE\n");
+ prep_state_change(MDRT4E_MODE, NORM_MODE, 0);
+ break;
+ }
+ // stay
+ break;
+
+ case MDRT3E_MODE:
+ // Demote -- if mem bound work is stalling but didn't show higher utilization
+ if (A_LTE_B(grp.worst_stall * 100, STALL_SCALE_LOWER_MARK)) {
+ lpmd_log_debug("MDRT3E_MODE to RESP_MODE %.2f < %d\n",
+ grp.worst_stall, STALL_SCALE_LOWER_MARK);
+ prep_state_change(MDRT3E_MODE, RESP_MODE, 0);
+ break;
+ }
+ // Demote to perf
+ if (A_GT_B(grp.c0_max, UTIL_NEAR_FULL)) {
+ if (!burst_rate_breach() && strikeout_once(N_STRIKE)) {
+ lpmd_log_debug("MDRT3E_MODE: burst_rate_breach AND strikeout_once - not met\n");
+ break;
+ }
+ lpmd_log_debug("MDRT3E_MODE to PERF_MODE\n");
+ prep_state_change(MDRT3E_MODE, PERF_MODE, 0);
+ break;
+ }
+
+ // Demote to 4 thread sustained
+ if (A_GTE_B(grp.sma_avg1, SUS_LOW_RANGE_END) &&
+ A_GTE_B(grp.sma_avg2, (SUS_LOW_RANGE_END - 5))) {
+ lpmd_log_debug("MDRT3E_MODE to MDRT4E_MODE %d > %d\n",
+ grp.sma_avg1, SUS_LOW_RANGE_END);
+ prep_state_change(MDRT3E_MODE, MDRT4E_MODE, 0);
+ break;
+ }
+ // promote
+ if ((A_GT_B(grp.sma_avg1, SUS_LOW_RANGE_START) &&
+ A_LTE_B(grp.sma_avg1, SUS_LOW_RANGE_END)) &&
+ (A_GT_B(grp.sma_avg2, SUS_LOW_RANGE_START) &&
+ A_LTE_B(grp.sma_avg2, SUS_LOW_RANGE_END))) {
+ if (!do_countdown(MDRT3E_MODE)) {
+ lpmd_log_debug("MDRT3E_MODE: to MDRT2E_MODE - do countdown not met\n");
+ break;
+ }
+ lpmd_log_debug("MDRT3E_MODE to MDRT2E_MODE %d < %d\n",
+ grp.sma_avg1, MDRT2E_MODE);
+ prep_state_change(MDRT3E_MODE, MDRT2E_MODE, 0);
+ break;
+ }
+ // Promote -- if top three avg util are trending lower.
+ if (A_LTE_B(grp.sma_avg1, SUS_LOW_RANGE_END) &&
+ (A_LTE_B(grp.sma_avg2, SUS_LOWER) &&
+ A_LTE_B(grp.sma_avg3, SUS_LOWER))) {
+ if (!do_countdown(MDRT3E_MODE)) {
+ lpmd_log_debug("MDRT3E_MODE: to NORM_MODE - do countdown not met\n");
+ break;
+ }
+ lpmd_log_debug("MDRT3E_MODE to NORM_MODE\n");
+ prep_state_change(MDRT3E_MODE, NORM_MODE, 0);
+ break;
+ }
+
+ lpmd_log_debug("MDRT3E_MODE: stay\n");
+ break;
+
+ case MDRT2E_MODE:
+ // Demote -- if mem bound work is stalling but didn't show higher utilization
+ if (A_LTE_B(grp.worst_stall * 100, STALL_SCALE_LOWER_MARK)) {
+ lpmd_log_debug("MDRT2E_MODE to RESP_MODE\n");
+ prep_state_change(MDRT2E_MODE, RESP_MODE, 0);
+ break;
+ }
+ // Demote -- if instant util nearing full or sustained moderate avg1 trend with avg2 trailing closeby
+ if (A_GT_B(grp.c0_max, UTIL_NEAR_FULL) ||
+ (A_GTE_B(grp.sma_avg1, SUS_LOW_RANGE_END) &&
+ A_GTE_B(grp.sma_avg2, SUS_LOW_RANGE_END - 10))) {
+ if (!burst_rate_breach() && strikeout_once(N_STRIKE))
+ break;
+ lpmd_log_debug("MDRT2E_MODE to MDRT3E_MODE\n");
+ prep_state_change(MDRT2E_MODE, MDRT3E_MODE, 0);
+ break;
+ }
+ // Promote -- if top two avg util are trending lower.
+ if ((A_GT_B(grp.sma_avg1, SUS_LOW_RANGE_START) &&
+ A_LTE_B(grp.sma_avg1, SUS_LOW_RANGE_END)) &&
+ A_LTE_B(grp.sma_avg2, SUS_LOW_RANGE_END)) {
+ if (!do_countdown(MDRT2E_MODE))
+ break;
+ lpmd_log_debug("MDRT2E_MODE to NORM_MODE\n");
+ prep_state_change(MDRT2E_MODE, NORM_MODE, 0);
+ break;
+ }
+ // stay
+ break;
+
+ case NORM_MODE:
+ // Demote -- if mem bound work is stalling but didn't show higher utilization
+ if (A_LTE_B(grp.worst_stall * 100, STALL_SCALE_LOWER_MARK)) {
+ lpmd_log_debug("NORM_MODE to RESP_MODE\n");
+ prep_state_change(NORM_MODE, RESP_MODE, 0);
+ break;
+ }
+ // Demote -- if instant util more than half or if signs of sustained ST activity.
+ if (A_GT_B(grp.c0_max, UTIL_HALF) ||
+ (A_GT_B(grp.sma_avg1, UTIL_BELOW_HALF))) {
+ /* In this state its better to absorb few spike (noise) before reacting */
+ if (!burst_rate_breach() && strikeout_once(N_STRIKE))
+ break;
+ lpmd_log_debug("NORM_MODE to MDRT2E_MODE\n");
+ prep_state_change(NORM_MODE, MDRT2E_MODE, 0);
+ break;
+ }
+ // Promote -- if top few instant util or top avg is trending lower.
+ if ((A_LTE_B(grp.c0_max, UTIL_LOW) &&
+ A_LTE_B(grp.c0_2nd_max, UTIL_LOWEST)) ||
+ A_LTE_B(grp.sma_avg1, SUS_LOWER)) {
+ /* its better to absorb few dips before reacting out of a steady-state */
+ if (!do_countdown(NORM_MODE))
+ break;
+ lpmd_log_debug("NORM_MODE to DEEP_MODE\n");
+ prep_state_change(NORM_MODE, DEEP_MODE, 0);
+ break;
+ }
+ break;
+
+ case DEEP_MODE:
+ // Demote -- if mem bound work is stalling but didn't show higher util.
+ if (A_LTE_B(grp.worst_stall * 100, STALL_SCALE_LOWER_MARK)) {
+ lpmd_log_debug("DEEP_MODE to RESP_MODE\n");
+ prep_state_change(DEEP_MODE, RESP_MODE, 0);
+ break;
+ }
+ // Demote -- if there are early signs of instantaneous utilization build-up.
+ if (A_GT_B(grp.c0_max, UTIL_FILL_START)) {
+ lpmd_log_debug("DEEP_MODE to NORM_MODE\n");
+ prep_state_change(DEEP_MODE, NORM_MODE, 0);
+ break;
+ }
+
+ break;
+ }
+
+ return 1;
+}
diff --git a/tools/power/x86/intel-lpmd/src/wlt_proxy/state_manager.c b/tools/power/x86/intel-lpmd/src/wlt_proxy/state_manager.c
new file mode 100644
index 000000000000..d80d1eb405fc
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/wlt_proxy/state_manager.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <sched.h>
+
+#include "lpmd.h" //logs
+#include "state_common.h"
+#include "wlt_proxy.h" //set_workload_hint
+
+#ifdef __GNUC__
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#else
+#define likely(x) (x)
+#define unlikely(x) (x)
+#endif
+
+/*
+ * If polling is too fast some of the stats (such as util)
+ * could be momentarily high owing to state change disturbances.
+ * avoid unexpected decision due to this as it may not be tied to workload per-se.
+ * any setting below, say 100ms, needs careful assessment.
+ */
+#define MIN_POLL_PERIOD 100
+
+#define BASE_POLL_RESP 96
+#define BASE_POLL_MT 100
+#define BASE_POLL_PERF 280
+#define BASE_POLL_MDRT4E 600 // e.g., 4E cores of a module
+#define BASE_POLL_MDRT3E 800 // e.g., 3E cores of a module
+#define BASE_POLL_MDRT2E 1000 // e.g., 2E cores of a module
+#define BASE_POLL_NORM 1200
+#define BASE_POLL_DEEP 1800
+
+/* hold period (ms) before moving to deeper state */
+#define MDRT_MODE_STAY (4000)
+#define PERF_MODE_STAY (10000)
+
+/* poll interval type */
+enum elastic_poll {
+ ZEROTH,
+ LINEAR,
+ QUADRATIC,
+ CUBIC,
+};
+
+/* state properties */
+struct st_state {
+ bool disabled;
+ char *name;
+ char *str;
+ char *str_reverse;
+ char *hexstr;
+ char *hexstr_reverse;
+ int poll;
+ enum elastic_poll poll_order;
+ int stay_count;
+ int stay_count_update_sec;
+ int stay_count_update_sec_prev;
+ int spike_type;
+ float stay_scalar;
+ int ppw_enabled;
+ int last_max_util;
+ int last_poll;
+};
+
+static struct st_state state_info[MAX_MODE] = {
+ [INIT_MODE] = { .name = "Avail cpu: P/E/L",
+ .poll = BASE_POLL_MT,
+ .poll_order = ZEROTH },
+ [PERF_MODE] = { .name = "Perf:non-soc cpu",
+ .poll = BASE_POLL_PERF,
+ .poll_order = ZEROTH },
+ [MDRT2E_MODE] = { .name = "Moderate 2E",
+ .poll = BASE_POLL_MDRT2E,
+ .poll_order = LINEAR },
+ [MDRT3E_MODE] = { .name = "Moderate 3E",
+ .poll = BASE_POLL_MDRT3E,
+ .poll_order = LINEAR },
+ [MDRT4E_MODE] = { .name = "Moderate 4E",
+ .poll = BASE_POLL_MDRT4E,
+ .poll_order = LINEAR },
+ [RESP_MODE] = { .name = "Responsive 2L",
+ .poll = BASE_POLL_RESP,
+ .poll_order = CUBIC },
+ [NORM_MODE] = { .name = "Normal LP 2L",
+ .poll = BASE_POLL_NORM,
+ .poll_order = QUADRATIC },
+ [DEEP_MODE] = { .name = "Deep LP 1L",
+ .poll = BASE_POLL_DEEP,
+ .poll_order = CUBIC },
+};
+
+static enum state_idx cur_state = NORM_MODE;
+static int needs_state_reset = 1;
+
+int state_demote;
+
+static void set_state_reset(void)
+{
+ needs_state_reset = 1;
+}
+
+enum state_idx get_cur_state(void)
+{
+ return cur_state;
+}
+
+static void set_cur_state(enum state_idx state)
+{
+ cur_state = state;
+}
+
+static int is_state_valid(enum state_idx state)
+{
+ return ((state >= INIT_MODE) && (state < MAX_MODE) &&
+ !state_info[state].disabled);
+}
+
+int get_poll_ms(enum state_idx state)
+{
+ return state_info[state].poll;
+}
+
+int get_stay_count(enum state_idx state)
+{
+ return state_info[state].stay_count;
+}
+
+int set_stay_count(enum state_idx state, int count)
+{
+ return (state_info[state].stay_count = count);
+}
+
+/* return 1 if stay count reaches 0 */
+int do_countdown(enum state_idx state)
+{
+ state_info[state].stay_count -= 1;
+
+ if (state_info[state].stay_count <= 0) {
+ state_info[state].stay_count = 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+/* get poll value in microsec */
+int get_state_poll(int util, enum state_idx state)
+{
+ int poll, scale = (100 - util);
+ float scale2;
+ int order = (int)state_info[state].poll_order;
+
+ /* avoiding fpow() overhead */
+ switch (order) {
+ case ZEROTH:
+ scale2 = (float)1;
+ break;
+ case LINEAR:
+ scale2 = (float)scale / 100;
+ break;
+ case QUADRATIC:
+ scale2 = (float)scale * scale / 10000;
+ break;
+ case CUBIC:
+ scale2 = (float)scale * scale * scale / 1000000;
+ break;
+ default:
+ scale2 = (float)scale / 100;
+ break;
+ }
+
+ poll = (int)(state_info[cur_state].poll * scale2);
+
+ /* limiting min poll to MIN_POLL_PERIOD ms */
+ if (poll < MIN_POLL_PERIOD)
+ return MIN_POLL_PERIOD;
+
+ return poll;
+}
+
+int get_last_maxutil(void)
+{
+ return state_info[cur_state].last_max_util;
+}
+
+static int set_last_maxutil(int v)
+{
+ state_info[cur_state].last_max_util = v;
+ return 1;
+}
+
+int set_last_poll(int v)
+{
+ state_info[cur_state].last_poll = v;
+ return 1;
+}
+
+int get_last_poll(void)
+{
+ return state_info[cur_state].last_poll;
+}
+
+/* initiate state change */
+static int apply_state_change(void)
+{
+ float test;
+
+ if (!needs_state_reset)
+ return 0;
+
+ update_perf_diffs(&test, 1);
+ needs_state_reset = 0;
+
+ return 1;
+}
+
+/* Internal state to WLT mapping*/
+static int get_state_mapping(enum state_idx state)
+{
+ switch (state) {
+ case PERF_MODE:
+ return WLT_BURSTY;
+
+ case RESP_MODE:
+ case NORM_MODE:
+ return WLT_BATTERY_LIFE;
+
+ case DEEP_MODE:
+ return WLT_IDLE;
+
+ case INIT_MODE:
+ case MDRT4E_MODE:
+ case MDRT3E_MODE:
+ case MDRT2E_MODE:
+ return WLT_SUSTAINED;
+
+ default:
+ return WLT_IDLE;
+ }
+}
+
+/* prepare for state change */
+int prep_state_change(enum state_idx from_state, enum state_idx to_state,
+ int reset)
+{
+ set_cur_state(to_state);
+ set_state_reset();
+ set_last_maxutil(DEACTIVATED);
+
+ if (to_state < from_state)
+ state_demote = 1;
+
+ //proxy: apply state change and get poll interval
+ apply_state_change();
+
+ if (likely(is_state_valid(to_state)))
+ next_proxy_poll = get_state_poll(max_util, to_state);
+
+ wlt_type = get_state_mapping(to_state);
+
+ return 1;
+}
+
+/* return staycount for the state */
+int staytime_to_staycount(enum state_idx state)
+{
+ int stay_count = 0;
+
+ switch (state) {
+ case MDRT2E_MODE:
+ case MDRT3E_MODE:
+ case MDRT4E_MODE:
+ stay_count = (int)MDRT_MODE_STAY / get_poll_ms(MDRT3E_MODE);
+ break;
+ case PERF_MODE:
+ stay_count = (int)PERF_MODE_STAY / get_poll_ms(PERF_MODE);
+ break;
+ default:
+ break;
+ }
+ return stay_count;
+}
+
+/* cleanup */
+void uninit_state_manager(void)
+{
+ for (int idx = INIT_MODE; idx < MAX_MODE; idx++) {
+ if (state_info[idx].str)
+ free(state_info[idx].str);
+
+ if (state_info[idx].str_reverse)
+ free(state_info[idx].str_reverse);
+
+ if (state_info[idx].hexstr)
+ free(state_info[idx].hexstr);
+
+ if (state_info[idx].hexstr_reverse)
+ free(state_info[idx].hexstr_reverse);
+ }
+}
diff --git a/tools/power/x86/intel-lpmd/src/wlt_proxy/state_util.c b/tools/power/x86/intel-lpmd/src/wlt_proxy/state_util.c
new file mode 100644
index 000000000000..9a9a8428c139
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/wlt_proxy/state_util.c
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/perf_event.h> //perf_event_attr
+#include <asm/unistd.h> //syscall __NR_perf_event_open
+#include <stdio.h>
+#include <stdint.h> //uint64_t
+#include <math.h> //round
+
+#include "lpmd.h"
+#include "state_common.h"
+
+/*
+ * simple moving average (sma), event count based - not time.
+ * updated for up to top 3 max util streams.
+ * exact cpu # is not tracked; only the max since continuum of task
+ * keeps switching cpus anyway.
+ * array implementation with SMA_LENGTH number of values.
+ */
+#define SMA_LENGTH (25)
+#define SMA_CPU_COUNT (3)
+#define SCALE_DECIMAL (100)
+
+static int sample[3][SMA_LENGTH];
+
+enum core_type {
+ P_CORE = 1,
+ E_CORE = 2,
+ L_CORE = 3
+};
+
+struct perf_stats_t {
+ int cpu;
+
+ enum core_type cpu_type;
+
+ int aperf_fd;
+ int mperf_fd;
+ int pperf_fd;
+
+ uint64_t aperf_diff;
+ uint64_t mperf_diff;
+ uint64_t pperf_diff;
+ uint64_t tsc_diff;
+
+ uint64_t nperf;
+ /*
+ * As initial freq f0 changes to some other value
+ * in the next cycle, it influences the initial
+ * load l0 and associated stall-factor (1-s0)
+ * track them for perf-per-watt evaluation.
+ */
+ float f0;
+ float l0;
+ float s0;
+};
+
+struct perf_stats_t *perf_stats;
+struct group_util grp;
+
+struct thread_data {
+ unsigned long long tsc;
+ unsigned long long aperf;
+ unsigned long long mperf;
+ unsigned long long pperf;
+} *thread_even, *thread_odd;
+
+static uint64_t *last_aperf;
+static uint64_t *last_mperf;
+static uint64_t *last_pperf;
+static uint64_t *last_tsc;
+
+/*
+ * Intel Alderlake hardware errata #ADL026: pperf bits 31:64 could be incorrect.
+ * https://edc.intel.com/content/www/us/en/design/ipla/software-development-plat
+ * forms/client/platforms/alder-lake-desktop/682436/007/errata-details/#ADL026
+ * u644diff() implements a workaround. Assuming real diffs less than MAX(uint32)
+ */
+#define u64diff(b, a) (((uint64_t)b < (uint64_t)a) ? \
+ (uint64_t)((uint32_t)~0UL - (uint32_t)a + (uint32_t)b) : \
+ ((uint64_t)b - (uint64_t)a))
+
+/* routine to evaluate & store a per-cpu msr value's diff */
+#define VARI(a, b, i) (a##b[i])
+#define cpu_generate_msr_diff(scope) \
+uint64_t cpu_get_diff_##scope(uint64_t cur_value, int instance) \
+{ \
+ uint64_t diff; \
+ diff = (VARI(last_, scope, instance) == 0) ? \
+ 0 : u64diff(cur_value, VARI(last_, scope, instance)); \
+ VARI(last_, scope, instance) = cur_value; \
+ return diff; \
+}
+
+/********************Perf calculation - begin *****************************************/
+
+cpu_generate_msr_diff(aperf);
+cpu_generate_msr_diff(mperf);
+cpu_generate_msr_diff(pperf);
+cpu_generate_msr_diff(tsc);
+
+/* initialize perf_stat structure */
+static int perf_stat_init(void)
+{
+ int max_cpus = get_max_cpus();
+
+ perf_stats = NULL;
+ perf_stats = calloc(max_cpus, sizeof(struct perf_stats_t));
+ if (!perf_stats) {
+ lpmd_log_error("WLT_Proxy: memory failure\n");
+ return 0;
+ }
+
+ for (int t = 0; t < max_cpus; t++) {
+ if (!is_cpu_online(t))
+ continue;
+
+ perf_stats[t].cpu = t;
+
+ if (is_cpu_pcore(t))
+ perf_stats[t].cpu_type = P_CORE;
+ else if (is_cpu_ecore(t))
+ perf_stats[t].cpu_type = E_CORE;
+ else
+ perf_stats[t].cpu_type = L_CORE;
+ }
+
+ return 1;
+}
+
+/* is cpu applicable for the given state*/
+static int cpu_applicable(int cpu, enum state_idx state)
+{
+ switch (state) {
+ case INIT_MODE:
+ //for INIT mode need all cores [P,E,L]
+ return 1;
+ case NORM_MODE: // 2 L cores
+ case DEEP_MODE: // 1 L core
+ case RESP_MODE: // all L core
+ case MDRT2E_MODE: // 2 E cores
+ case MDRT3E_MODE: // 3 E cores
+ case MDRT4E_MODE: // 4 E cores
+ case PERF_MODE:
+ if (perf_stats[cpu].cpu_type != L_CORE)
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int init_perf_calculations(int n)
+{
+ if (!perf_stat_init()) {
+ lpmd_log_error("\nerror initiating cpu proxy\n");
+ return -1;
+ }
+
+ last_aperf = calloc(n, sizeof(uint64_t));
+ last_mperf = calloc(n, sizeof(uint64_t));
+ last_pperf = calloc(n, sizeof(uint64_t));
+ last_tsc = calloc(n, sizeof(uint64_t));
+ if (!last_aperf || !last_mperf || !last_mperf || !last_tsc) {
+ lpmd_log_error("calloc failure perf vars\n");
+ return -2;
+ }
+
+ return LPMD_SUCCESS;
+}
+
+/* helper - pperf reading */
+static int read_perf_counter_info(const char *const path,
+ const char *const parse_format,
+ void *value_ptr)
+{
+ int fdmt;
+ int bytes_read;
+ char buf[64];
+ int ret = -1;
+
+ fdmt = open(path, O_RDONLY, 0);
+ if (fdmt == -1) {
+ lpmd_log_error("Failed to parse perf counter info %s\n", path);
+ ret = -1;
+ goto cleanup_and_exit;
+ }
+
+ bytes_read = read(fdmt, buf, sizeof(buf) - 1);
+ if (bytes_read <= 0 || bytes_read >= (int)sizeof(buf)) {
+ lpmd_log_error("Failed to parse perf counter info %s\n", path);
+ ret = -1;
+ goto cleanup_and_exit;
+ }
+
+ buf[bytes_read] = '\0';
+
+ if (sscanf(buf, parse_format, value_ptr) != 1) {
+ lpmd_log_error("Failed to parse perf counter info %s\n", path);
+ ret = -1;
+ goto cleanup_and_exit;
+ }
+
+ ret = 0;
+
+cleanup_and_exit:
+ if (fdmt >= 0)
+ close(fdmt);
+
+ return ret;
+}
+
+/* helper - pperf reading */
+static unsigned int read_perf_counter_info_n(const char *const path,
+ const char *const parse_format)
+{
+ unsigned int v;
+ int status;
+
+ status = read_perf_counter_info(path, parse_format, &v);
+ if (status)
+ v = -1;
+
+ return v;
+}
+
+/* helper - pperf reading */
+static int read_pperf_config(void)
+{
+ const char *const path = "/sys/bus/event_source/devices/msr/events/pperf";
+ const char *const format = "event=%x";
+
+ return read_perf_counter_info_n(path, format);
+}
+
+/* helper - pperf reading */
+static unsigned int read_aperf_config(void)
+{
+ const char *const path = "/sys/bus/event_source/devices/msr/events/aperf";
+ const char *const format = "event=%x";
+
+ return read_perf_counter_info_n(path, format);
+}
+
+/* helper - pperf reading */
+static unsigned int read_mperf_config(void)
+{
+ const char *const path = "/sys/bus/event_source/devices/msr/events/mperf";
+ const char *const format = "event=%x";
+
+ return read_perf_counter_info_n(path, format);
+}
+
+/* helper - pperf reading */
+static unsigned int read_msr_type(void)
+{
+ const char *const path = "/sys/bus/event_source/devices/msr/type";
+ const char *const format = "%u";
+
+ return read_perf_counter_info_n(path, format);
+}
+
+/* helper - pperf reading */
+static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
+ int cpu, int group_fd, unsigned long flags)
+{
+ return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
+}
+
+/* helper - pperf reading */
+static long open_perf_counter(int cpu, unsigned int type, unsigned int config,
+ int group_fd, __u64 read_format)
+{
+ struct perf_event_attr attr;
+ const pid_t pid = -1;
+ const unsigned long flags = 0;
+
+ memset(&attr, 0, sizeof(struct perf_event_attr));
+
+ attr.type = type;
+ attr.size = sizeof(struct perf_event_attr);
+ attr.config = config;
+ attr.disabled = 0;
+ attr.sample_type = PERF_SAMPLE_IDENTIFIER;
+ attr.read_format = read_format;
+
+ const int fd = perf_event_open(&attr, pid, cpu, group_fd, flags);
+
+ return fd;
+}
+
+/* helper - pperf reading */
+static void open_amperf_fd(int cpu)
+{
+ const unsigned int msr_type = read_msr_type();
+ const unsigned int aperf_config = read_aperf_config();
+ const unsigned int mperf_config = read_mperf_config();
+ const unsigned int pperf_config = read_pperf_config();
+
+ perf_stats[cpu].aperf_fd = open_perf_counter(cpu, msr_type, aperf_config, -1, PERF_FORMAT_GROUP);
+ perf_stats[cpu].mperf_fd = open_perf_counter(cpu, msr_type, mperf_config, perf_stats[cpu].aperf_fd, PERF_FORMAT_GROUP);
+ perf_stats[cpu].pperf_fd = open_perf_counter(cpu, msr_type, pperf_config, perf_stats[cpu].aperf_fd, PERF_FORMAT_GROUP);
+}
+
+/* helper - pperf reading */
+static int get_amperf_fd(int cpu)
+{
+ if (perf_stats[cpu].aperf_fd)
+ return perf_stats[cpu].aperf_fd;
+
+ open_amperf_fd(cpu);
+
+ return perf_stats[cpu].aperf_fd;
+}
+
+/* helper - pperf reading */
+static unsigned long long rdtsc(void)
+{
+ unsigned int low, high;
+
+ asm volatile ("rdtsc" : "=a" (low), "=d"(high));
+
+ return low | ((unsigned long long)high) << 32;
+}
+
+/*
+ * Helper for - Reading APERF, MPERF and TSC using the perf API.
+ * Calc perf [cpu utilization per core] difference from MSR registers
+ */
+static int read_aperf_mperf_tsc_perf(struct thread_data *t, int cpu)
+{
+ union {
+ struct {
+ unsigned long nr_entries;
+ unsigned long aperf;
+ unsigned long mperf;
+ unsigned long pperf;
+ };
+
+ unsigned long as_array[4];
+ } cnt;
+ const int fd_amperf = get_amperf_fd(cpu);
+
+ if (fd_amperf == -1)
+ return LPMD_ERROR;
+
+ /*
+ * Read the TSC with rdtsc, because we want the absolute value and not
+ * the offset from the start of the counter.
+ */
+ t->tsc = rdtsc();
+
+ const int n = read(fd_amperf, &cnt.as_array[0], sizeof(cnt.as_array));
+
+ if (n != sizeof(cnt.as_array))
+ return -2;
+
+ t->aperf = cnt.aperf;
+ t->mperf = cnt.mperf;
+ t->pperf = cnt.pperf;
+
+ return LPMD_SUCCESS;
+}
+
+/* Calc perf [cpu utilization per core] difference from MSR registers */
+int update_perf_diffs(float *sum_norm_perf, int stat_init_only)
+{
+ float max_load = 0, max_2nd_load = 0, max_3rd_load = 0, next_load = 0;
+ float min_load = 100.0, min_s0 = 1.0, next_s0 = 1.0;
+ int t, min_s0_cpu = 0, first_pass = 1;
+ struct thread_data tdata;
+ int maxed_cpu = -1;
+
+ for (t = 0; t < get_max_online_cpu(); t++) {
+ if (!cpu_applicable(t, get_cur_state()))
+ continue;
+
+ /*reading through perf api*/
+ if (read_aperf_mperf_tsc_perf(&tdata, t) != LPMD_SUCCESS) {
+ lpmd_log_error("read_aperf_mperf_tsc_perf failed for cpu = %d\n", t);
+ continue;
+ }
+ perf_stats[t].pperf_diff = cpu_get_diff_pperf(tdata.pperf, t);
+ perf_stats[t].aperf_diff = cpu_get_diff_aperf(tdata.aperf, t);
+ perf_stats[t].mperf_diff = cpu_get_diff_mperf(tdata.mperf, t);
+ perf_stats[t].tsc_diff = cpu_get_diff_tsc(tdata.tsc, t);
+
+ if (stat_init_only)
+ continue;
+
+ /*
+ * Normalized perf metric defined as pperf per load per time.
+ * The rationale is detailed here:
+ * github.com/intel/psst >whitepapers >Generic_perf_per_watt.pdf
+ * Given that delta_load = delta_mperf/delta_tsc, we can rewrite
+ * as given below.
+ */
+ if (perf_stats[t].tsc_diff) {
+ next_load = (float)100 * perf_stats[t].mperf_diff /
+ perf_stats[t].tsc_diff;
+ perf_stats[t].l0 = next_load;
+ }
+
+ if (A_LTE_B(max_load, next_load)) {
+ max_load = next_load;
+ maxed_cpu = perf_stats[t].cpu;
+ } else if (A_LTE_B(max_2nd_load, next_load)) {
+ max_2nd_load = next_load;
+ } else if (A_LTE_B(max_3rd_load, next_load)) {
+ max_3rd_load = next_load;
+ }
+ /* min scalability */
+ if (perf_stats[t].aperf_diff) {
+ next_s0 = (float)perf_stats[t].pperf_diff /
+ perf_stats[t].aperf_diff;
+ /* since aperf/pperf are not read oneshot, ratio > 1 is not ruled out */
+ next_s0 = (next_s0 >= 1) ? (1 - EPSILON) : next_s0;
+ }
+ if (A_LTE_B(next_s0, min_s0) || first_pass) {
+ min_s0 = next_s0;
+ min_s0_cpu = perf_stats[t].cpu;
+ }
+
+ if (A_GT_B(min_load, next_load))
+ min_load = next_load;
+
+ first_pass = 0;
+ }
+
+ if (stat_init_only)
+ return 0;
+
+ grp.worst_stall = min_s0;
+ grp.worst_stall_cpu = min_s0_cpu;
+
+ grp.c0_max = max_load;
+ grp.c0_2nd_max = max_2nd_load;
+ grp.c0_3rd_max = max_3rd_load;
+ grp.c0_min = min_load;
+
+ return maxed_cpu;
+}
+
+/* close perf fd's */
+static void close_amperf_fd(int cpu)
+{
+ if (perf_stats[cpu].aperf_fd)
+ close(perf_stats[cpu].aperf_fd);
+ if (perf_stats[cpu].mperf_fd)
+ close(perf_stats[cpu].mperf_fd);
+ if (perf_stats[cpu].pperf_fd)
+ close(perf_stats[cpu].pperf_fd);
+}
+
+/* cleanup perf_stat structure */
+static void perf_stat_uninit(void)
+{
+ int max_cpus = get_max_cpus();
+
+ if (perf_stats) {
+ for (size_t i = 0; i < max_cpus; ++i) {
+ close_amperf_fd(i);
+ memset(&perf_stats[i], 0, sizeof(struct perf_stats_t));
+ }
+ free(perf_stats);
+ }
+}
+
+static void uninit_perf_calculations(void)
+{
+ perf_stat_uninit();
+
+ if (last_aperf)
+ free(last_aperf);
+ if (last_mperf)
+ free(last_mperf);
+ if (last_pperf)
+ free(last_pperf);
+ if (last_tsc)
+ free(last_tsc);
+}
+
+/********************perf calculation - end *****************************************/
+
+/********************SMA calculation - begin *****************************************/
+
+/* initialize avg calculation variables */
+static void init_sma_calculations(void)
+{
+ for (int i = 0; i < SMA_CPU_COUNT; i++) {
+ grp.sma_sum[i] = -1;
+ for (int j = 0; j < SMA_LENGTH; j++)
+ sample[i][j] = 0;
+ }
+ grp.sma_pos = -1;
+}
+
+/* Helper avg calculation */
+static int do_sum(int *sam, int len)
+{
+ int sum = 0;
+
+ for (int i = 0; i < len; i++)
+ sum += sam[i];
+ return sum;
+}
+
+/* average cpu usage */
+int state_max_avg(void)
+{
+ grp.sma_pos += 1;
+
+ int v1 = (int)round(grp.c0_max * SCALE_DECIMAL);
+ int v2 = (int)round(grp.c0_2nd_max * SCALE_DECIMAL);
+ int v3 = (int)round(grp.c0_3rd_max * SCALE_DECIMAL);
+
+ if (grp.sma_pos == SMA_LENGTH)
+ grp.sma_pos = 0;
+ if (grp.sma_sum[0] == -1) {
+ sample[0][grp.sma_pos] = v1;
+ sample[1][grp.sma_pos] = v2;
+ sample[2][grp.sma_pos] = v3;
+ if (grp.sma_pos == SMA_LENGTH - 1) {
+ grp.sma_sum[0] = do_sum(sample[0], SMA_LENGTH);
+ grp.sma_sum[1] = do_sum(sample[1], SMA_LENGTH);
+ grp.sma_sum[2] = do_sum(sample[2], SMA_LENGTH);
+ }
+ } else {
+ grp.sma_sum[0] = grp.sma_sum[0] - sample[0][grp.sma_pos] + v1;
+ grp.sma_sum[1] = grp.sma_sum[1] - sample[1][grp.sma_pos] + v2;
+ grp.sma_sum[2] = grp.sma_sum[2] - sample[2][grp.sma_pos] + v3;
+ sample[0][grp.sma_pos] = v1;
+ sample[1][grp.sma_pos] = v2;
+ sample[2][grp.sma_pos] = v3;
+ }
+
+ grp.sma_avg1 = (int)round((double)grp.sma_sum[0] /
+ (double)(SMA_LENGTH * SCALE_DECIMAL));
+ grp.sma_avg2 = (int)round((double)grp.sma_sum[1] /
+ (double)(SMA_LENGTH * SCALE_DECIMAL));
+ grp.sma_avg3 = (int)round((double)grp.sma_sum[2] /
+ (double)(SMA_LENGTH * SCALE_DECIMAL));
+
+ return 1;
+}
+
+/********************SMA calculation - end *****************************************/
+
+/* return multi threaded false if at least one cpu is under utilizied */
+int max_mt_detected(enum state_idx state)
+{
+ for (int t = 0; t < get_max_online_cpu(); t++) {
+ if (!cpu_applicable(t, state))
+ continue;
+
+ if A_LTE_B(perf_stats[t].l0, (UTIL_LOW))
+ return 0;
+ }
+ return 1;
+}
+
+/* initialize */
+int util_init_proxy(void)
+{
+ float dummy;
+
+ if (init_perf_calculations(get_max_online_cpu()) < 0) {
+ lpmd_log_error("WLT_Proxy: error initializing perf calculations");
+ return LPMD_ERROR;
+ }
+
+ update_perf_diffs(&dummy, 1);
+
+ init_sma_calculations();
+
+ return LPMD_SUCCESS;
+}
+
+/* cleanup */
+void util_uninit_proxy(void)
+{
+ uninit_perf_calculations();
+ uninit_state_manager();
+}
diff --git a/tools/power/x86/intel-lpmd/src/wlt_proxy/wlt_proxy.c b/tools/power/x86/intel-lpmd/src/wlt_proxy/wlt_proxy.c
new file mode 100644
index 000000000000..04e815eec491
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/src/wlt_proxy/wlt_proxy.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "lpmd.h" //wlt_type
+#include "state_common.h"
+
+/* wlt_proxy polling interval - updated at every state change */
+int next_proxy_poll = 1000;
+
+/* wlt_proxy hint - updated at every state change */
+int wlt_type = WLT_IDLE;
+
+/* called at the configured interval to take action; return next interval and workload type*/
+int read_wlt_proxy(int *interval)
+{
+ state_machine_auto();
+ *interval = next_proxy_poll;
+
+ return wlt_type;
+}
+
+/* Returns success if proxy supported on platform */
+int wlt_proxy_init(void)
+{
+ return util_init_proxy();
+}
+
+/* make sure all resource are properly released and closed */
+void wlt_proxy_uninit(void)
+{
+ util_uninit_proxy();
+}
diff --git a/tools/power/x86/intel-lpmd/tests/lpm_test_interface.sh b/tools/power/x86/intel-lpmd/tests/lpm_test_interface.sh
new file mode 100755
index 000000000000..2371b551e5d3
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/tests/lpm_test_interface.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+# Copyright (C) 2026 Intel Corporation
+
+if [ "$(whoami)" != "root" ]
+then
+ echo "This script must be run as root"
+ exit 1
+fi
+
+opt_no=
+if [ ! -z "$1" ]
+then
+ opt_no=$1
+fi
+
+while true;
+do
+ if [ -z "$1" ]
+ then
+ echo "****options****"
+ echo "0 : Allow All CPUs"
+ echo "1 : Terminate"
+ echo "2 : LPM force on"
+ echo "3 : LPM force off"
+ echo "4 : LPM auto"
+ echo "5 : Quit"
+ echo -n " Enter choice: "
+ read opt_no
+ fi
+
+ case $opt_no in
+ 0)
+ echo "0 : Allow All CPUs"
+ echo -n " Enter Maximum CPU number"
+ read max_cpu
+ sudo systemctl set-property --runtime user.slice AllowedCPUs=0-$max_cpu
+ sudo systemctl set-property --runtime system.slice AllowedCPUs=0-$max_cpu
+ ;;
+ 1)
+ echo "1 : Terminate"
+ dbus-send --system --dest=org.freedesktop.intel_lpmd --print-reply /org/freedesktop/intel_lpmd org.freedesktop.intel_lpmd.Terminate
+ ;;
+ 2)
+ echo "2 : LPM force on"
+ dbus-send --system --dest=org.freedesktop.intel_lpmd --print-reply /org/freedesktop/intel_lpmd org.freedesktop.intel_lpmd.LPM_FORCE_ON
+ ;;
+ 3)
+ echo "3 : LPM force off"
+ dbus-send --system --dest=org.freedesktop.intel_lpmd --print-reply /org/freedesktop/intel_lpmd org.freedesktop.intel_lpmd.LPM_FORCE_OFF
+ ;;
+ 4)
+ echo "4 : LPM auto"
+ dbus-send --system --dest=org.freedesktop.intel_lpmd --print-reply /org/freedesktop/intel_lpmd org.freedesktop.intel_lpmd.LPM_AUTO
+ ;;
+ 5)
+ exit 0
+ ;;
+ *)
+ echo "5 : Quit"
+ echo "Invalid option"
+ esac
+ [ ! -z "$1" ] && break
+done
diff --git a/tools/power/x86/intel-lpmd/tools/intel_lpmd_control.c b/tools/power/x86/intel-lpmd/tools/intel_lpmd_control.c
new file mode 100644
index 000000000000..c69b144bdadb
--- /dev/null
+++ b/tools/power/x86/intel-lpmd/tools/intel_lpmd_control.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 Intel Corporation */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include <gio/gio.h>
+#include <gio/gdbusmessage.h>
+
+#define INTEL_LPMD_SERVICE_NAME "org.freedesktop.intel_lpmd"
+#define INTEL_LPMD_SERVICE_OBJECT_PATH "/org/freedesktop/intel_lpmd"
+#define INTEL_LPMD_SERVICE_INTERFACE "org.freedesktop.intel_lpmd"
+
+int main(int argc, char **argv)
+{
+ g_autoptr(GDBusConnection) connection = NULL;
+ g_autoptr(GString) command = NULL;
+ g_autoptr(GVariant) result = NULL;
+ GError *error = NULL;
+ const gchar *state;
+
+ if (geteuid()) {
+ g_warning("Must run as root");
+ exit(1);
+ }
+
+ if (argc < 2) {
+ fprintf(stderr, "intel_lpmd_control: missing control command\n");
+ fprintf(stderr, "syntax:\n");
+ fprintf(stderr, "intel_lpmd_control ON|OFF|AUTO|STATUS\n");
+ exit(0);
+ }
+
+ connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!connection)
+ return FALSE;
+
+ if (!strncmp(argv[1], "STATUS", 6)) {
+ result = g_dbus_connection_call_sync(connection,
+ INTEL_LPMD_SERVICE_NAME,
+ INTEL_LPMD_SERVICE_OBJECT_PATH,
+ INTEL_LPMD_SERVICE_INTERFACE,
+ "GetState",
+ NULL,
+ G_VARIANT_TYPE("(s)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (error) {
+ g_warning("Fail on connecting lpmd: %s", error->message);
+ exit(1);
+ }
+
+ g_variant_get(result, "(&s)", &state);
+ g_print("%s\n", state);
+
+ return 0;
+ }
+
+ if (!strncmp(argv[1], "ON", 2)) {
+ command = g_string_new("LPM_FORCE_ON");
+ } else if (!strncmp(argv[1], "OFF", 3)) {
+ command = g_string_new("LPM_FORCE_OFF");
+ } else if (!strncmp(argv[1], "AUTO", 4)) {
+ command = g_string_new("LPM_AUTO");
+ } else {
+ g_warning("intel_lpmd_control: Invalid command");
+ exit(1);
+ }
+
+ g_dbus_connection_call_sync(connection,
+ INTEL_LPMD_SERVICE_NAME,
+ INTEL_LPMD_SERVICE_OBJECT_PATH,
+ INTEL_LPMD_SERVICE_INTERFACE,
+ command->str,
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+
+ if (error) {
+ g_warning("Fail on connecting lpmd: %s", error->message);
+ exit(1);
+ }
+
+ return 0;
+}
--
2.53.0