From nobody Fri Jun 12 22:34:21 2026 Received: from mail-106120.protonmail.ch (mail-106120.protonmail.ch [79.135.106.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 197FE495510 for ; Tue, 12 May 2026 08:40:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.120 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778575261; cv=none; b=FgsjdzLYOtrQDGsHYSBF32xhGIaG/dxFpYaLtyfbH2TvtFA7TMq6jpszeBumErKfi/g76pK1KS0yuOsnOEin2SRVQ3EHCCJcl462a2tybvCS2cWvQGIC7lnvfCmEwRQi04mXYLP6N6cbdsHfSgq66mKMawX7gZq9SKra7ljHmDY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778575261; c=relaxed/simple; bh=7XALdlDD2bxy31ACEPO5bjgYi1Rc79dWt/+PhmMiHX8=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=NTHZuEzjlN6ldAQh3vQ5dwqbAvsEqQQshMmh70uSbwIzS2yIh4V/MbGRQ9CV9wpP4MXui9As59vHqzks/tT029nftTDPosOwpV7Gs9EzsvA9VTSxNeiqhOw72X/PyFUWA1LYN0I4sUo3ZF1D5HTX28rOniDGqcDv1mEDT4EuAx4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me; spf=pass smtp.mailfrom=pm.me; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b=fDIUcmm6; arc=none smtp.client-ip=79.135.106.120 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pm.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b="fDIUcmm6" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pm.me; s=protonmail3; t=1778575225; x=1778834425; bh=zTNPMecVLgYq0BEuua4u21AHSLFUcdZUWq6J8UsR/iY=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=fDIUcmm6U6b8jP4iSYEyp4fnO/I7dGNLASLoPpE5c4ZAPmkP50JQYGqtzhVykNoVC Pqy5c26hU2COAAUFl2/nApXY/9cxpw5P5FvTG0qaNdBkfaCFLaTBpPx7Iv3rLIvsvW vJ0sbd6ppzBmacvkcqY0nrwy6SfptQjbktUiGBWDqrXgwFxdt+sE3GSoX0nYeZ7hG0 oB2s/5Rle5k0efVCfC/UCBY+QfGz0WDVq3wZFRFpr2bQBpMYDPmlNFMskv8aElQIre +pWb1Um6cTJQnY8p4nmimFTVOwVPq9ju51abkrxQdCAR3pmLM9+urq7uMgtowOedny DS7B1IvlZ2e1g== Date: Tue, 12 May 2026 08:40:16 +0000 To: srinivas.pandruvada@linux.intel.com, fabio.m.de.francesco@linux.intel.com, rafael@kernel.org From: Maciej Wieczor-Retman Cc: noor.u.mubeen@intel.com, vinod.gomathinayagam@intel.com, deepak.sundar@intel.com, mohsin.shariff@intel.com, kloczek@github.com, xiaqinxin@huawei.com, jack@jackkelly.name, rui.zhang@intel.com, baohua@kernel.org, colin.i.king@gmail.com, libin3479@gmail.com, m.szyprowski@samsung.com, ali.erdinc.koroglu@intel.com, admin@ptr1337.dev, maciej.wieczor-retman@intel.com, venkatesh.j@intel.com, ThatQubicFox@protonmail.com, trenn@suse.de, jengelh@inai.de, robert.dower@intel.com, hpa@redhat.com, platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, m.wieczorretman@pm.me Subject: [PATCH v1 1/3] tools/LPMD: Add Intel Low Power Mode Daemon Message-ID: <834c9f22f0f9cf57d681ee52fa235276cefa5bd4.1778560215.git.m.wieczorretman@pm.me> In-Reply-To: References: Feedback-ID: 164464600:user:proton X-Pm-Message-ID: 616a3159a33d15ffa056eecaa76ad07ac73a83cc Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Fabio M. De Francesco 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 Co-developed-by: Ali Erdin=C3=A7 K=C3=B6ro=C4=9Flu Signed-off-by: Ali Erdin=C3=A7 K=C3=B6ro=C4=9Flu Co-developed-by: Bin Li Signed-off-by: Bin Li Co-developed-by: Colin Ian King Signed-off-by: Colin Ian King Co-developed-by: Gomathinayagam, Vinod Signed-off-by: Gomathinayagam, Vinod Co-developed-by: Jack Kelly Signed-off-by: Jack Kelly Co-developed-by: Jan Engelhardt Signed-off-by: Jan Engelhardt Co-developed-by: Kate Hsuan Signed-off-by: Kate Hsuan Co-developed-by: Mohsin Shariff Signed-off-by: Mohsin Shariff Co-developed-by: Mubeen, Noor U Signed-off-by: Mubeen, Noor U Co-developed-by: Peter Jung Signed-off-by: Peter Jung Co-developed-by: Qubic Signed-off-by: Qubic Co-developed-by: Robert Dower Signed-off-by: Robert Dower Co-developed-by: Srinivas Pandruvada Signed-off-by: Srinivas Pandruvada Co-developed-by: Sundar, Deepak Signed-off-by: Sundar, Deepak Co-developed-by: Thomas Renninger Signed-off-by: Thomas Renninger Co-developed-by: Tomasz K=C5=82oczko Signed-off-by: Tomasz K=C5=82oczko Co-developed-by: Venkatesh J Signed-off-by: Venkatesh J Co-developed-by: Zhang Rui Signed-off-by: Zhang Rui Co-developed-by: Maciej Wieczor-Retman Signed-off-by: Maciej Wieczor-Retman --- 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_M1= 70.xml create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M1= 89.xml create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_F6_M2= 04.xml create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_examp= les.xml create mode 100644 tools/power/x86/intel-lpmd/data/intel_lpmd_config_exper= imental.xml create mode 100644 tools/power/x86/intel-lpmd/data/org.freedesktop.intel_l= pmd.conf create mode 100644 tools/power/x86/intel-lpmd/data/org.freedesktop.intel_l= pmd.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_interfac= e.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_pr= oxy.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 =20 +INTEL LOW POWER MODE DAEMON (intel-lpmd) +M: Srinivas Pandruvada +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: tools/power/x86/intel-lpmd/ + INTEL MANAGEMENT ENGINE (mei) M: Alexander Usyskin L: linux-kernel@vger.kernel.org diff --git a/tools/power/x86/intel-lpmd/README.md b/tools/power/x86/intel-l= pmd/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 configurat= ion +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 activat= ing +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. F= or +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-de= v libxml2-dev libnl-3-dev libnl-genl-3-dev libsystemd-dev gtk-doc-tools lib= upower-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/l= ocal`. +If a custom install path is preferred other than system default, make sure +`--localstatedir` and `--sysconfdir` are set to the right path that the sy= stem +can understand. If installed via RPM then artifacts would be under `/usr`. + +Example command for installation using prefix under `/opt/lpmd_install` di= r with +`--localstatedir` and `--sysconfdir` set to system default + +```sh +./autogen.sh prefix=3D/opt/lpmd_install --localstatedir=3D/var --sysconfdi= r=3D/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=3Ddebug +``` + +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 Optimize= r (lpmd)" because it covers more scenarios. +- Fix invalid cgroup setting during probe, in case lpmd doesn't quit smoot= hly 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 unvalid= ated 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 platf= orms 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/secu= rity-center/default.html) for information on how to report a potential secu= rity 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=3D Intel Linux Energy Optimizer (lpmd) Service +Documentation=3Dman:intel_lpmd(8) +ConditionVirtualization=3Dno +StartLimitInterval=3D200 +StartLimitBurst=3D5 + +[Service] +Type=3Ddbus +SuccessExitStatus=3D2 +BusName=3Dorg.freedesktop.intel_lpmd +ExecStart=3D@sbindir@/intel_lpmd --systemd --dbus-enable +Restart=3Don-failure +RestartSec=3D30 +PrivateTmp=3Dyes + +[Install] +WantedBy=3Dmulti-user.target +Alias=3Dorg.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 @@ + + + + + + + + + + 0 + + + -1 + + + -1 + + + -1 + + + 0 + + + 0 + + + 10 + + + 95 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 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 @@ + + + + + + + + + + 1 + + + -1 + + + -1 + + + -1 + + + 0 + + + 1 + + + 1 + + + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + 170 + * + + 1 + WLT_IDLE + 0 + 255 + 15 + 2000 + -1 + -1 + + + 2 + WLT_BATTERY_LIFE + 1 + 178 + 6 + 2000 + -1 + -1 + + + 3 + WLT_SUSTAINED + 2 + 64 + 6 + 2000 + -1 + -1 + + + 4 + WLT_BURSTY + 3 + 64 + 4 + 2000 + -1 + -1 + + + + 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 @@ + + + + + + + + + + + + + 1 + + + -1 + + + -1 + + + -1 + + + 0 + + + 1 + + + 1 + + + 0 + + + 10 + + + 95 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + 6 + 189 + * + + 1 + UTIL_IDLE + 1 + 50 + -1 + 192 + 8 + -1 + 1000 + 500 + 2000 + + + 2 + UTIL_IDLE_SUSTAIN + 75 + 2 + -1 + 64 + 8 + -1 + 1000 + 500 + 2000 + + + 3 + UTIL_IDLE_BURSTY + 3 + 75 + -1 + 64 + 8 + -1 + 1000 + 500 + 2000 + + + 4 + UTIL_IDLE_GFX_BUSY + 100 + -1 + 128 + 8 + -1 + 1000 + 500 + 2000 + + + + 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 @@ + + + + + + + + + + + + + + + -1 + + + -1 + + + -1 + + + 0 + + + 1 + + + 0 + + + 15 + + + 0 + + + + + + + + + + + + + + + 0 + + + 1 + 3 + 0 + 3 + + + 6 + 204 + 4P8E4L-25W + + 1 + UTIL_IDLE + 1 + 12-15 + + + 2 + UTIL_IDLE_SUSTAIN + 2 + 0-15 + + + 3 + UTIL_IDLE_BURSTY + 3 + 0-15 + + + + + 6 + 204 + 4P4E4L-25W + + 1 + UTIL_IDLE + 1 + 8-11 + + + 2 + UTIL_IDLE_SUSTAIN + 2 + 0-11 + + + 3 + UTIL_IDLE_BURSTY + 3 + 0-11 + + + + + 6 + 204 + 4P0E4L-25W + + 1 + UTIL_IDLE + 1 + 4-7 + + + 2 + UTIL_IDLE_SUSTAIN + 2 + 0-7 + + + 3 + UTIL_IDLE_BURSTY + 3 + 0-7 + + + + 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 @@ + + + + + + + + + + -1 + + + 0 + + + -1 + + + -1 + + + -1 + + + 0 + + + 0 + + + 0 + + + 10 + + + 95 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + 6 + 170 + 12P8E2L-28W + + 1 + WLT_IDLE + 0 + 255 + -1 + -1 + + + 2 + WLT_BATTERY_LIFE + 1 + 192 + -1 + -1 + + + 3 + WLT_SUSTAINED + 2 + 64 + -1 + -1 + + + 4 + WLT_BURSTY + 3 + 64 + -1 + -1 + + + + + + 6 + 170 + 4P8E2L-15W + + 1 + LPM_DEEP + 2 + 50 + -1 + -1 + -1 + -1 + 16,17 + 500 + 500 + 2000 + + + 2 + LPM_LOW + 10 + 3 + -1 + -1 + -1 + -1 + 12,13,14,15 + 1000 + 1000 + 3000 + + + 3 + FULL_POWER + + + -1 + -1 + -1 + -1 + 0-17 + 500 + -1 + 2000 + + + 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 @@ + + + + + + + + + + + + + 1 + + + -1 + + + -1 + + + -1 + + + 1 + + + 1 + + + 1 + + + 0 + + + 10 + + + 95 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + 6 + 202 + 4P8E4L-15W + + 1 + WLT_IDLE + 0 + 50 + 5 + 90 + -1 + 192 + 8 + hfi + -1 + 1000 + 500 + 2000 + + + 2 + WLT_BATTERY_LIFE + 1 + 50 + 10 + 95 + -1 + 192 + 8 + hfi + -1 + 1000 + 500 + 2000 + + + 3 + WLT_SUSTAIN + 2 + 30 + 50 + -1 + 64 + 8 + lp + -1 + 1000 + 500 + 2000 + + + 4 + WLT_BURSTY + 3 + 50 + 50 + -1 + 64 + 8 + all + -1 + 1000 + 500 + 2000 + + + 5 + UTIL_IDLE + 50 + 5 + 90 + -1 + 192 + 8 + hfi + -1 + 1000 + 500 + 2000 + + + 6 + UTIL_SUSTAIN + 10 + 50 + -1 + 64 + 8 + lp + -1 + 1000 + 500 + 2000 + + + 7 + UTIL_BURSTY + 50 + 50 + -1 + 64 + 8 + all + -1 + 1000 + 500 + 2000 + + + 8 + UTIL_BUSY + -1 + 32 + 6 + all + -1 + 1000 + 500 + 2000 + + + + diff --git a/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.con= f 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 @@ + + + + + + + + + + + + + + + + + + diff --git a/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.ser= vice.in b/tools/power/x86/intel-lpmd/data/org.freedesktop.intel_lpmd.servic= e.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=3Dorg.freedesktop.intel_lpmd +Exec=3D/bin/false +User=3Droot +SystemdService=3Dorg.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 h= ardware (WLTEnable)). When proxy hints detection (WLTProxyEnable) is enable= d through config option, the hardware WLT hints will be ignored. On detecti= ng workload type, framework takes predefined action with values in config f= ile. + +# 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 |P= erf/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 + 1 + ** WLTProxyEnable set to Yes + 1 + + * 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 + 0 + +# Value add + with Dynamic EPP [based on workload] enabled on Core Ultra Gen 1 [Mete= or Lake H], HP Baymax14W, CDB, PV SKU8W - N15479-021, in Balanced power pro= file, 10% performance improvement observed on Crossmark and Speedometer ben= chmarks. + +## 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 identify= ing single threaded workloads and memory related workloads. Benchmarks like= Geekbench ST, WebXprt, Stream may not show improvement compared to Geekben= ch MT, Speedometer and Crossmark. + +# Workload detection algorithm - pseudo code + * System CPU utilization thresholds and conditions are predefined and m= apped to workload type. + * Operating CPU frequency, Spike count rate, state stay time counter an= d 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 (syst= em 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 uti= l < 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_av= g1 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 countdow= n 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 =E2=80=93 invalid/same state; - not allowed state + +Variables used in state switch condition: +* Multithreaded workload: all applicable CPUs for state utilized more t= hat 10%. Not multithread workload: at least one CPU is utilized < 10%. +* CPU.L0=3D 100 * perf_stats[t].mperf_diff / perf_stats[t].tsc_diff + * Also following values will be calculated based on L0. C0_max , M= in_load[C0.min]; max_load, max_2nd, max_3rd +* sum_c0 =3D 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. c= pu 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 @@ + + + + src/intel_lpmd_dbus_interface.xml= + + 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. Th= is +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=3D +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=3Dinfo --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 sys= tem +reboot. + +.SH SEE ALSO +intel_lpmd_config.xml(5), intel_lpmd_control(8) + +.SH AUTHOR +Written by Zhang Rui + 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 o= ptimal +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 ha= rdware +features are not supported. + +.PP +.B lp_mode_cpus +is a set of active CPUs when system is in Low Power Mode. This usually equ= als a +group of most power efficient CPUs on a platform to achieve best power sav= ing. +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 reque= st. +.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 m= ode. +.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 sy= stem +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 rele= vant +for platforms before Lunar Lake and the debugfs file is not present on lat= er +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 platfo= rms +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 + + + Example CPUs + + + 0|1|2 + + + -1|0|1 + + + -1|0|1 + + + -1|0|1 + + + 0|1 + + + 0|1 + + + Example threshold + + + Example threshold + + + -1 | EPP value + + + +.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 HfiLpmEnab= le 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 + + + + 0 + 0 + 0 + 0 + 0 + -1 + +.PP +.B Example 2: +This is the typical configuration. The utilization thresholds and delays m= ay 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 l= ower than 5%. +.IP \(bu 2 +util_exit_threshold: 95. Exit Low Power Mode when the utilization of any o= f 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 + + + + 0 + 1 + 1 + 5 + 95 + -1 + +.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 u= se +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 u= se +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 t= han +this value, the current enter Low Power Mode request will be ignored becau= se +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 low= er +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/po= wer/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 determ= ine +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 diff --git a/tools/power/x86/intel-lpmd/src/include/lpmd.h b/tools/power/x8= 6/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "thermal.h" + +#define LOG_DEBUG_INFO 1 + +#define LOCKF_SUPPORT + +#ifdef GLIB_SUPPORT +#include +#include +#include + +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 configur= ations */ + DEFAULT_HFI, /* LPM state with CPU isolation based on HFI hints only */ + CONFIG_STATE_BASE, + MAX_STATES =3D CONFIG_STATE_BASE + MAX_CONFIG_STATES, + STATE_NONE =3D 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 =3D 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 =3D CPUMASK_USER + NUM_USER_CPUMASKS, + CPUMASK_NONE =3D 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 =3D 0x%08x ebx =3D 0x%08x ecx =3D 0x%0= 8x edx =3D 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 =3D 0x%08x ebx =3D 0x%0= 8x ecx =3D 0x%08x" \ + "edx =3D 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 =3D 0, + WLT_BATTERY_LIFE =3D 1, + WLT_SUSTAINED =3D 2, + WLT_BURSTY =3D 3, + WLT_INVALID =3D 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 =3D 0, THERMAL_DEVICE_ENABLED, +}; + +enum thermal_trip_type { + THERMAL_TRIP_ACTIVE =3D 0, THERMAL_TRIP_PASSIVE, THERMAL_TRIP_HOT, THERMA= L_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 z= ones 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 +#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 =3D SD_BUS_ERROR_NULL; + sd_bus_message *m =3D NULL; + char buf[MAX_STR_LENGTH]; + sd_bus *bus =3D NULL; + int offset; + int ret; + int i; + + ret =3D sd_bus_open_system(&bus); + if (ret < 0) { + fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-ret)); + goto finish; + } + + ret =3D 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 =3D sd_bus_message_append(m, "sb", unit, 1); + if (ret < 0) { + fprintf(stderr, "Failed to append unit: %s\n", error.message); + goto finish; + } + + ret =3D 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D snprintf(buf, MAX_STR_LENGTH, "\tSending Dbus message to syste= md: %s: ", unit); + for (i =3D 0; i < size; i++) { + if (offset < MAX_STR_LENGTH) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, "0x%02x ", = vals[i]); + } + buf[MAX_STR_LENGTH - 1] =3D '\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 >=3D 0) { + ret =3D 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 =3D 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 =3D get_cgroup_systemd_vals(state->cpumask_idx, &size); + if (!vals) + return -1; + + ret =3D update_allowed_cpus("system.slice", vals, size); + if (ret) + goto restore; + + ret =3D update_allowed_cpus("user.slice", vals, size); + if (ret) + goto restore; + + ret =3D 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_DEBU= G); + } + + 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_i= solation_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_s= tr(state->cpumask_idx), LPMD_LOG_DEBUG)) + return 1; + } else { + if (lpmd_write_str("/sys/fs/cgroup/lpm/cpuset.cpus", get_cpu_isolation_s= tr(CPUMASK_ONLINE), LPMD_LOG_DEBUG)) + return 1; + } + + return 0; +} + +int cgroup_cleanup(void) +{ + DIR *dir =3D 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 =3D=3D 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 =3D=3D CPUMASK_NONE) { + lpmd_log_debug("Ignore cgroup processing\n"); + return 0; + } + + lpmd_log_info("Process Cgroup ...\n"); + if (mode =3D=3D LPM_CPU_CGROUPV2) + return process_cpu_cgroupv2(state); + if (mode =3D=3D 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 +#include + +#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_con= fig_t *config, int idx) +{ + struct lpmd_config_state_t *state =3D &config->config_states[idx]; + xmlNode *cur_node =3D 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 =3D a_node; cur_node; cur_node =3D cur_node->next) { + if (cur_node->type !=3D XML_ELEMENT_NODE) + continue; + + tmp_value =3D (char *)xmlNodeListGetString(doc, cur_node->xmlChildrenNod= e, 1); + if (!tmp_value) + continue; + + if (!strncmp((const char *)cur_node->name, "ID", strlen("ID"))) + state->id =3D 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] =3D '\0'; + } + if (!strncmp((const char *)cur_node->name, "WLTType", strlen("WLTType"))) + state->wlt_type =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "EntrySystemLoadThres", strle= n("EntrySystemLoadThres"))) + state->entry_system_load_thres =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "ExitSystemLoadThres", strlen= ("ExitSystemLoadThres"))) + state->exit_system_load_thres =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "ExitSystemLoadhysteresis", s= trlen("ExitSystemLoadhysteresis"))) + state->exit_system_load_hyst =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "EnterCPULoadThres", strlen("= EnterCPULoadThres"))) + state->enter_cpu_load_thres =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "ExitCPULoadThres", strlen("E= xitCPULoadThres"))) + state->exit_cpu_load_thres =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "EnterGFXLoadThres", strlen("= EnterGFXLoadThres"))) + state->enter_gfx_load_thres =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "ExitGFXLoadThres", strlen("E= xitGFXLoadThres"))) + state->exit_gfx_load_thres =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "MinPollInterval", strlen("Mi= nPollInterval"))) + state->min_poll_interval =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "MaxPollInterval", strlen("Ma= xPollInterval"))) + state->max_poll_interval =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "PollIntervalIncrement", strl= en("PollIntervalIncrement"))) + state->poll_interval_increment =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "EPP", strlen("EPP"))) + state->epp =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "EPB", strlen("EPB"))) + state->epb =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "ITMTState", strlen("ITMTStat= e"))) + state->itmt_state =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "IRQMigrate", strlen("IRQMigr= ate"))) + state->irq_migrate =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "Island0Pcores", strlen("Isla= nd0Pcores"))) + state->island_0_number_p_cores =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "Island0Ecores", strlen("Isla= nd0Ecores"))) + state->island_0_number_e_cores =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "Island1Pcores", strlen("Isla= nd1Pcores"))) + state->island_1_number_p_cores =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "Island1Ecores", strlen("Isla= nd1Ecores"))) + state->island_1_number_e_cores =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "Island2Pcores", strlen("Isla= nd2Pcores"))) + state->island_2_number_p_cores =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "Island2Ecores", strlen("Isla= nd2Ecores"))) + state->island_2_number_e_cores =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "ActiveCPUs", strlen("ActiveC= PUs"))) { + if (!strncmp(tmp_value, "-1", strlen("-1"))) + state->active_cpus[0] =3D '\0'; + else + copy_user_string(tmp_value, state->active_cpus, sizeof(state->active_c= pus)); + } + if (!strncmp((const char *)cur_node->name, "BalanceSliderAC", strlen("Ba= lanceSliderAC"))) + state->balance_slider_ac =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "SliderOffsetAC", strlen("Sli= derOffsetAC"))) + state->slider_offset_ac =3D strtol(tmp_value, &pos, 10); + + if (!strncmp((const char *)cur_node->name, "BalanceSliderDC", strlen("Ba= lanceSliderDC"))) + state->balance_slider_dc =3D strtol(tmp_value, &pos, 10); + if (!strncmp((const char *)cur_node->name, "SliderOffsetDC", strlen("Sli= derOffsetDC"))) + state->slider_offset_dc =3D 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_co= nfig_t *lpmd_config) +{ + int cpu_family =3D -1, cpu_model =3D -1, config_state_count =3D 0; + char cpu_config[MAX_CONFIG_LEN]; + xmlNode *cur_node =3D 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] =3D '\0'; + + for (cur_node =3D a_node; cur_node; cur_node =3D cur_node->next) { + if (cur_node->type !=3D XML_ELEMENT_NODE) + continue; + + if (!cur_node->name) + continue; + + tmp_value =3D (char *)xmlNodeListGetString(doc, cur_node->xmlChildrenNod= e, 1); + if (!strncmp((const char *)cur_node->name, "CPUFamily", strlen("CPUFamil= y"))) { + if (is_wildcard(tmp_value)) + cpu_family =3D lpmd_config->cpu_family; + else + cpu_family =3D strtol(tmp_value, &pos, 10); + } + + if (!strncmp((const char *)cur_node->name, "CPUModel", strlen("CPUModel"= ))) { + if (is_wildcard(tmp_value)) + cpu_model =3D lpmd_config->cpu_model; + else + cpu_model =3D strtol(tmp_value, &pos, 10); + } + + if (!strncmp((const char *)cur_node->name, "CPUConfig", strlen("CPUConfi= g"))) { + 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] =3D '\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 appl= ies */ + if (cpu_family !=3D lpmd_config->cpu_family || + cpu_model !=3D 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,conf= ig:%s\n", + cpu_family, cpu_model, cpu_config); + return; + } + + if (lpmd_config->config_state_count >=3D 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 =3D config_state_count; +} + +static void lpmd_init_config(struct lpmd_config_t *config) +{ + config->performance_def =3D LPM_FORCE_OFF; + config->balanced_def =3D LPM_FORCE_OFF; + config->powersaver_def =3D LPM_FORCE_OFF; + config->lp_mode_epp =3D -1; + config->data.util_sys =3D -1; + config->data.util_cpu =3D -1; + config->data.util_gfx =3D -1; + config->data.wlt_hint =3D -1; + config->balance_slider_def_ac =3D -1; + config->balance_slider_def_dc =3D -1; + config->slider_offset_def_ac =3D -1; + config->slider_offset_def_dc =3D -1; + config->wlt_hint_mask =3D -1; +} + +static int lpmd_fill_config(xmlDoc *doc, xmlNode *a_node, struct lpmd_conf= ig_t *lpmd_config) +{ + xmlNode *cur_node =3D NULL; + char *tmp_value; + char *pos; + + if (!doc || !a_node || !lpmd_config) + return LPMD_ERROR; + + lpmd_init_config(lpmd_config); + + for (cur_node =3D a_node; cur_node; cur_node =3D cur_node->next) { + if (cur_node->type !=3D XML_ELEMENT_NODE) + continue; + + tmp_value =3D (char *)xmlNodeListGetString(doc, cur_node->xmlChildrenNod= e, 1); + if (!tmp_value) + continue; + + if (!strncmp((const char *)cur_node->name, "Mode", strlen("Mode"))) { + errno =3D 0; + lpmd_config->mode =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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 =3D 0; + lpmd_config->hfi_lpm_enable =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\0' || + (lpmd_config->hfi_lpm_enable !=3D 1 && + lpmd_config->hfi_lpm_enable !=3D 0)) + goto err; + } else if (!strncmp((const char *)cur_node->name, + "WLTHintEnable", strlen("WLtHintEnable"))) { + errno =3D 0; + lpmd_config->wlt_hint_enable =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\0' || + (lpmd_config->wlt_hint_enable !=3D 1 && + lpmd_config->wlt_hint_enable !=3D 0)) + goto err; + } else if (!strncmp((const char *)cur_node->name, "WLTHintMask", + strlen("WLTHintMask"))) { + errno =3D 0; + lpmd_config->wlt_hint_mask =3D strtol(tmp_value, &pos, 10); + } else if (!strncmp((const char *)cur_node->name, + "WLTHintPollEnable", + strlen("WLtHintPollEnable"))) { + errno =3D 0; + lpmd_config->wlt_hint_poll_enable =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\0' || + (lpmd_config->wlt_hint_poll_enable !=3D 1 && + lpmd_config->wlt_hint_poll_enable !=3D 0)) + goto err; + } else if (!strncmp((const char *)cur_node->name, + "WLTProxyEnable", + strlen("WLTProxyEnable"))) { + errno =3D 0; + lpmd_config->wlt_proxy_enable =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\0' || + (lpmd_config->wlt_proxy_enable !=3D 1 && + lpmd_config->wlt_proxy_enable !=3D 0)) + goto err; + } else if (!strncmp((const char *)cur_node->name, + "EntryDelayMS", strlen("EntryDelayMS"))) { + errno =3D 0; + lpmd_config->util_entry_delay =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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 =3D 0; + lpmd_config->util_exit_delay =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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 =3D 0; + lpmd_config->util_entry_threshold =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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 =3D 0; + lpmd_config->util_exit_threshold =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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 =3D 0; + lpmd_config->util_entry_hyst =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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 =3D 0; + lpmd_config->util_exit_hyst =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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 =3D 0; + lpmd_config->lp_mode_epp =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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 =3D -1; + } else if (!strncmp((const char *)cur_node->name, "IgnoreITMT", + strlen("IgnoreITMT"))) { + errno =3D 0; + lpmd_config->ignore_itmt =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\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] =3D '\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 =3D 0; + lpmd_config->performance_def =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\0') + goto err; + if (lpmd_config->performance_def =3D=3D -1) + lpmd_config->performance_def =3D LPM_FORCE_OFF; + else if (lpmd_config->performance_def =3D=3D 1) + lpmd_config->performance_def =3D LPM_FORCE_ON; + else if (!lpmd_config->performance_def) + lpmd_config->performance_def =3D LPM_AUTO; + else + goto err; + } else if (!strncmp((const char *)cur_node->name, "BalancedDef", + strlen("BalancedDef"))) { + errno =3D 0; + lpmd_config->balanced_def =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\0') + goto err; + if (lpmd_config->balanced_def =3D=3D -1) + lpmd_config->balanced_def =3D LPM_FORCE_OFF; + else if (lpmd_config->balanced_def =3D=3D 1) + lpmd_config->balanced_def =3D LPM_FORCE_ON; + else if (!lpmd_config->balanced_def) + lpmd_config->balanced_def =3D LPM_AUTO; + else + goto err; + } else if (!strncmp((const char *)cur_node->name, + "PowersaverDef", strlen("PowersaverDef"))) { + errno =3D 0; + lpmd_config->powersaver_def =3D strtol(tmp_value, &pos, 10); + if (errno || *pos !=3D '\0') + goto err; + if (lpmd_config->powersaver_def =3D=3D -1) + lpmd_config->powersaver_def =3D LPM_FORCE_OFF; + else if (lpmd_config->powersaver_def =3D=3D 1) + lpmd_config->powersaver_def =3D LPM_FORCE_ON; + else if (!lpmd_config->powersaver_def) + lpmd_config->powersaver_def =3D LPM_AUTO; + else + goto err; + } else if (!strncmp((const char *)cur_node->name, "States", + strlen("States"))) { + errno =3D 0; + lpmd_parse_states(doc, cur_node->children, lpmd_config); + } else if (!strncmp((const char *)cur_node->name, + "BalancedSliderAC", + strlen("BalancedSliderAC"))) { + errno =3D 0; + lpmd_config->balance_slider_def_ac =3D strtol(tmp_value, &pos, 10); + + } else if (!strncmp((const char *)cur_node->name, + "BalancedSliderDC", + strlen("BalancedSliderDC"))) { + errno =3D 0; + lpmd_config->balance_slider_def_dc =3D strtol(tmp_value, &pos, 10); + } else if (!strncmp((const char *)cur_node->name, + "SliderOffsetAC", + strlen("SliderOffsetAC"))) { + errno =3D 0; + lpmd_config->slider_offset_def_ac =3D strtol(tmp_value, &pos, 10); + } else if (!strncmp((const char *)cur_node->name, + "SliderOffsetDC", + strlen("SliderOffsetDC"))) { + errno =3D 0; + lpmd_config->slider_offset_def_dc =3D strtol(tmp_value, &pos, 10); + } else { + if (!strncmp((const char *)cur_node->name, "HfiSuvEnable", strlen("HfiS= uvEnable"))) { + 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_N= AME); + + 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 =3D xmlReadFile(file_name, NULL, 0); + if (!doc) { + lpmd_log_msg("error: could not parse file %s\n", file_name); + return LPMD_ERROR; + } + + root_element =3D xmlDocGetRootElement(doc); + if (!root_element) { + lpmd_log_warn("error: could not get root element\n"); + return LPMD_ERROR; + } + + cur_node =3D NULL; + + for (cur_node =3D root_element; cur_node; cur_node =3D cur_node->next) { + if (cur_node->type !=3D XML_ELEMENT_NODE) + continue; + + if (strncmp((const char *)cur_node->name, "Configuration", strlen("Confi= guration"))) + continue; + + if (lpmd_fill_config(doc, cur_node->children, lpmd_config) !=3D LPMD_SUC= CESS) { + 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/in= tel-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 + +#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[] =3D { + { 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 !=3D 0x756e6547 || edx !=3D 0x49656e69 || ecx !=3D 0x6c65746e) { + lpmd_log_info("Unsupported vendor\n"); + return -1; + } + + max_level =3D eax; + cpuid(1, eax, ebx, ecx, edx); + family =3D (eax >> 8) & 0xf; + model =3D (eax >> 4) & 0xf; + stepping =3D eax & 0xf; + + if (family =3D=3D 6) + model +=3D ((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 !=3D 2 && val !=3D 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 =3D 0; + while (id_table[val].family) { + if (id_table[val].family =3D=3D family && id_table[val].model =3D=3D mod= el) + break; + val++; + } + + /* Unsupported model */ + if (!id_table[val].family) { + lpmd_log_info("Platform not supported yet.\n"); + lpmd_log_debug("Supported platforms:\n"); + val =3D 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 =3D family; + lpmd_config->cpu_model =3D 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 =3D (eax >> 24) & 0xFF; + + cpu_clear_affinity(); + return type =3D=3D 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 =3D 0; ; subleaf++) { + unsigned int type, level; + + cpuid_count(4, subleaf, eax, ebx, ecx, edx); + + type =3D eax & 0x1f; + level =3D (eax >> 5) & 0x7; + + /* No more caches */ + if (!type) + break; + /* Unified Cache */ + if (type !=3D 3) + continue; + /* L3 */ + if (level !=3D 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 =3D 0; + FILE *filep; + char *pos; + DIR *dir; + + dir =3D opendir(PATH_RAPL); + if (!dir) { + perror("opendir() error"); + return 1; + } + + while ((entry =3D readdir(dir)) !=3D 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_nam= e); + filep =3D fopen(path, "r"); + if (!filep) + continue; + + ret =3D fread(str, 1, MAX_STR_LENGTH, filep); + fclose(filep); + + if (ret <=3D 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 =3D fopen(path, "r"); + if (!filep) + continue; + + ret =3D fread(str, 1, MAX_STR_LENGTH, filep); + fclose(filep); + + if (ret <=3D 0) + continue; + + if (ret >=3D MAX_STR_LENGTH) + ret =3D MAX_STR_LENGTH - 1; + + str[ret] =3D '\0'; + tdp =3D 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 =3D 0; + for (i =3D 0; i < 256; ++i) { + char path[MAX_STR_LENGTH]; + + snprintf(path, sizeof(path), + "/sys/devices/system/cpu/cpu%d/topology/thread_siblings", i); + + filep =3D 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) =3D=3D 1) + max_cpus +=3D 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 =3D detect_max_cpus(); + if (ret) + return ret; + + cpumask_reset(CPUMASK_ONLINE); + pcores =3D 0; + ecores =3D 0; + lcores =3D 0; + + for (i =3D 0; i < get_max_cpus(); i++) { + unsigned int online =3D 0; + + snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/online", i); + filep =3D fopen(path, "r"); + if (filep) { + if (fscanf(filep, "%u", &online) !=3D 1) + lpmd_log_warn("fread failed for %s\n", path); + fclose(filep); + } else if (!i) { + online =3D 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 cgr= oup settings */ + cgroup_cleanup(); + + for (i =3D 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 =3D get_tdp(); + lpmd_log_info("Detected %d Pcores, %d Ecores, %d Lcores, TDP %dW\n", + pcores, ecores, lcores, tdp); + ret =3D snprintf(lpmd_config->cpu_config, MAX_CONFIG_LEN - 1, + " %dP%dE%dL-%dW ", pcores, ecores, lcores, tdp); + + lpmd_config->tdp =3D tdp; + + return 0; +} + +static int detect_lpm_cpus_cmd(char *cmd) +{ + int ret; + + ret =3D cpumask_init_cpus(cmd, CPUMASK_LPM_DEFAULT); + if (ret <=3D 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 =3D get_max_cpus(); i >=3D 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] =3D '\0'; + + filep =3D fopen(path, "r"); + if (!filep) + continue; + + ret =3D fread(str, 1, MAX_STR_LENGTH - 1, filep); + fclose(filep); + + if (ret <=3D 0) + continue; + + str[ret] =3D '\0'; + + if (cpumask_init_cpus(str, CPUMASK_LPM_DEFAULT) <=3D 0) + continue; + + /* An Ecore module contains 4 Atom cores */ + if (cpumask_nr_cpus(CPUMASK_LPM_DEFAULT) =3D=3D 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 =3D 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 =3D NULL; + + if (cmd_cpus && cmd_cpus[0] !=3D '\0') { + ret =3D detect_lpm_cpus_cmd(cmd_cpus); + if (ret <=3D 0) { + lpmd_log_error("\tInvalid -c parameter: %s\n", cmd_cpus); + exit(-1); + } + str =3D "CommandLine"; + goto end; + } + + ret =3D detect_lpm_cpus_lcore(); + if (ret < 0) + return ret; + + if (ret > 0) { + str =3D "Lcores"; + goto end; + } + + if (detect_lpm_cpus_cluster()) { + str =3D "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/x8= 6/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 + +#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] =3D { + [CPUMASK_LPM_DEFAULT] =3D { .name =3D "Low Power", }, + [CPUMASK_ONLINE] =3D { .name =3D "Online", }, + [CPUMASK_HFI] =3D { .name =3D "HFI Low Power", }, + [CPUMASK_HFI_BANNED] =3D { .name =3D "HFI BANNED", }, + [CPUMASK_HFI_LAST] =3D { .name =3D "HFI LAST", }, +}; + +int is_cpu_online(int cpu) +{ + if (cpu < 0 || cpu >=3D 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 =3D num; +} + +int get_max_online_cpu(void) +{ + return max_online_cpu; +} + +void set_max_online_cpu(int num) +{ + max_online_cpu =3D num; +} + +static size_t alloc_cpu_set(cpu_set_t **cpu_set) +{ + cpu_set_t *_cpu_set; + size_t size; + + _cpu_set =3D CPU_ALLOC((topo_max_cpus + 1)); + if (!_cpu_set) + err(3, "CPU_ALLOC"); + size =3D CPU_ALLOC_SIZE((topo_max_cpus + 1)); + CPU_ZERO_S(size, _cpu_set); + + *cpu_set =3D _cpu_set; + + if (!size_cpumask) + size_cpumask =3D size; + + if (size_cpumask && size_cpumask !=3D 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 =3D sched_setaffinity(0, size_cpumask, mask); + CPU_FREE(mask); + + if (ret =3D=3D -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 =3D 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 =3D 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 =3D NULL; + cpumasks[idx].str_reverse =3D NULL; + cpumasks[idx].hexstr =3D NULL; + cpumasks[idx].hexstr_reverse =3D NULL; + cpumasks[idx].hexvals =3D NULL; + return 0; +} + +int cpumask_add_cpu(int cpu, enum cpumask_idx idx) +{ + if (idx !=3D 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 =3D 0; + + if (buf[0] =3D=3D '\0') + return 0; + + next =3D buf; + + while (next && *next) { + if (*next =3D=3D '\n') + *next =3D '\0'; + next++; + } + next =3D buf; + + while (next && *next) { + if (*next =3D=3D '\n') + *next =3D '\0'; + + if (*next =3D=3D '-') /* no negative cpu numbers */ + goto error; + + start =3D strtoul(next, &next, 10); + + cpumask_add_cpu(start, idx); + nr_cpus++; + + if (*next =3D=3D '\0') + break; + + if (*next =3D=3D ',') { + next +=3D 1; + continue; + } + + if (*next =3D=3D '-') { + next +=3D 1; /* start range */ + } else if (*next =3D=3D '.') { + next +=3D 1; + if (*next =3D=3D '.') + next +=3D 1; /* start range */ + else + goto error; + } + + end =3D strtoul(next, &next, 10); + if (end <=3D start) + goto error; + + while (++start <=3D end) { + cpumask_add_cpu(start, idx); + nr_cpus++; + } + + if (*next =3D=3D ',') + next +=3D 1; + else if (*next !=3D '\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 =3D=3D 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 =3D 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 =3D 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 =3D 0; + + buf[0] =3D '\0'; + for (i =3D 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 +=3D snprintf(buf + offset, length - 1 - offset, "%d,", i); + } + if (offset) + buf[offset - 1] =3D '\0'; + return 0; +} + +static char to_hexchar(int val) +{ + if (val <=3D 9) + return val + '0'; + if (val >=3D 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 =3D 0; + char c =3D 0; + + for (cpu =3D 0; cpu < topo_max_cpus; cpu++) { + i =3D cpu % 4; + + if (!i) + c =3D 0; + + if (CPU_ISSET_S(cpu, size_cpumask, mask)) + c +=3D (1 << i); + + if (i =3D=3D 3) { + str[pos] =3D to_hexchar (c); + pos++; + if (pos >=3D size) + return -1; + } + } + str[pos] =3D '\0'; + + pos--; + for (i =3D 0; i <=3D pos / 2; i++) { + c =3D str[i]; + str[i] =3D str[pos - i]; + str[pos - i] =3D 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 =3D 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 =3D calloc(MAX_STR_LENGTH, 1); + if (!cpumasks[idx].hexstr) + err(3, "STR_ALLOC"); + + cpumask_to_hexstr(cpumasks[idx].mask, cpumasks[idx].hexstr, MAX_STR_LENGT= H); + 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 =3D 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 =3D 0; + uint8_t *vals; + + if (!cpumasks[idx].mask) + return NULL; + + if (!CPU_COUNT_S(size_cpumask, cpumasks[idx].mask)) + return NULL; + + *size =3D topo_max_cpus / 8; + + if (cpumasks[idx].hexvals) + return cpumasks[idx].hexvals; + + vals =3D calloc(*size, 1); + if (!vals) + return NULL; + + for (i =3D 0; i < topo_max_cpus; i++) { + j =3D i % 8; + k =3D i / 8; + + if (k >=3D *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 |=3D 1 << j; +set_val: + if (j =3D=3D 7) { + vals[k] =3D v; + v =3D 0; + } + } + + cpumasks[idx].hexvals =3D 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 =3D=3D 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/powe= r/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 +#include +#include +#include + +#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 =3D NULL; + g_autofree gchar *path =3D NULL; + + path =3D g_build_filename("/org/freedesktop/intel_lpmd", filename, NULL); + data =3D g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, erro= r); + 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 =3D PREF_OBJECT(user_data); + g_autoptr(GError) error =3D NULL; + + lpmd_log_debug("Dbus method called %s %s.\n", interface_name, method_name= ); + + if (g_strcmp0(method_name, "Terminate") =3D=3D 0) { + g_dbus_method_invocation_return_value(invocation, NULL); + dbus_interface_terminate(obj, &error); + return; + } + + if (g_strcmp0(method_name, "LPM_FORCE_ON") =3D=3D 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") =3D=3D 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") =3D=3D 0) { + g_dbus_method_invocation_return_value(invocation, NULL); + dbus_interface_l_pm__au_to(obj, &error); + return; + } + + if (g_strcmp0(method_name, "GetState") =3D=3D 0) { + static const char * const state_names[] =3D { + [LPMD_OFF] =3D "OFF", + [LPMD_ON] =3D "ON", + [LPMD_AUTO] =3D "AUTO", + [LPMD_FREEZE] =3D "FREEZE", + [LPMD_RESTORE] =3D "RESTORE", + [LPMD_TERMINATE] =3D "TERMINATE", + }; + int state =3D get_lpmd_state(); + const char *name =3D (state >=3D 0 && state <=3D 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 =3D NULL; + GDBusProxy *proxy_id =3D NULL; + guint registration_id; + GError *error =3D NULL; + + if (!user_data) { + lpmd_log_error("user_data is NULL\n"); + return; + } + + introspection_data =3D 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 =3D g_dbus_connection_register_object(connection, + "/org/freedesktop/intel_lpmd", + introspection_data->interfaces[0], + &interface_vtable, + user_data, + NULL, + &error); + + proxy_id =3D 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 =3D exit_handler; + + value_obj =3D 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 =3D lpmd_dbus_handle_method_call; + interface_vtable.get_property =3D lpmd_dbus_handle_get_property; + interface_vtable.set_property =3D lpmd_dbus_handle_set_property; + + watcher_id =3D 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/x8= 6/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 +#include +#include +#include +#include +#include + +#include "lpmd.h" + +int copy_user_string(char *src, char *dst, int size) +{ + int offset_src, offset_dst; + + for (offset_src =3D 0, offset_dst =3D 0; src[offset_src] !=3D '\0' && off= set_src < size; offset_src++) { + /* Ignore heading spaces */ + if (src[offset_src] =3D=3D ' ' && !offset_dst) + continue; + dst[offset_dst] =3D src[offset_src]; + offset_dst++; + } + dst[offset_dst] =3D '\0'; + + /* Remove tailing spaces */ + while (dst[--offset_dst] =3D=3D ' ') + dst[offset_dst] =3D '\0'; + + return 0; +} + +int lpmd_read_str(char *path, char *str, int size) +{ + FILE *filep; + int ret; + + filep =3D fopen(path, "r"); + if (!filep) { + lpmd_log_error("Cannot open %s\n", path); + return 1; + } + + ret =3D fread(str, 1, size, filep); + fclose(filep); + + if (ret <=3D 0) + return 1; + + if (ret >=3D size) + ret =3D size - 1; + str[ret - 1] =3D '\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 lo= g_level, const char *mode) +{ + char prefix[16]; + FILE *filep; + int i, ret; + + if (print_level >=3D 15) + return 1; + + if (print_level <=3D 0) { + prefix[0] =3D '\0'; + } else { + for (i =3D 0; i < print_level; i++) + prefix[i] =3D '\t'; + prefix[i] =3D '\0'; + } + + filep =3D fopen(name, mode); + if (!filep) { + lpmd_log_error("%sOpen %s failed\n", prefix, name); + return 1; + } + + ret =3D fprintf(filep, "%s", str); + if (ret <=3D 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 =3D { }, tp2 =3D { }; + + if (!name) + return 1; + + clock_gettime(CLOCK_MONOTONIC, &tp1); + + if (print_level >=3D 15) + return 1; + + if (print_level < 0) { + prefix[0] =3D '\0'; + } else { + for (i =3D 0; i < print_level; i++) + prefix[i] =3D '\t'; + prefix[i] =3D '\0'; + } + + filep =3D fopen(name, "r+"); + if (!filep) { + lpmd_log_error("%sOpen %s failed\n", prefix, name); + return 1; + } + + ret =3D fprintf(filep, "%d", val); + if (ret <=3D 0) { + lpmd_log_error("%sWrite \"%d\" to %s failed, ret %d\n", prefix, val, nam= e, 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_nse= c); + 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_ns= ec); + 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 >=3D 15) + return 1; + + if (print_level < 0) { + prefix[0] =3D '\0'; + } else { + for (i =3D 0; i < print_level; i++) + prefix[i] =3D '\t'; + prefix[i] =3D '\0'; + } + + filep =3D fopen(name, "r"); + if (!filep) { + lpmd_log_error("%sOpen %s failed\n", prefix, name); + return 1; + } + + ret =3D fscanf(filep, "%d", &t); + if (ret !=3D 1) { + lpmd_log_error("%sRead %s failed, ret %d\n", prefix, name, ret); + fclose(filep); + return 1; + } + + fclose(filep); + + *val =3D t; + + if (print_level >=3D 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 =3D 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 >=3D 15) + return 1; + + if (print_level < 0) { + prefix[0] =3D '\0'; + } else { + for (i =3D 0; i < print_level; i++) + prefix[i] =3D '\t'; + prefix[i] =3D '\0'; + } + + filep =3D fopen(name, "r"); + if (!filep) { + lpmd_log_error("%sOpen %s failed\n", prefix, name); + return 1; + } + + ret =3D fgetc(filep); + if (ret =3D=3D 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 =3D=3D 'Y') { + *val =3D 1; + } else if (ret =3D=3D 'N') { + *val =3D 0; + } else { + lpmd_log_error("%sRead %s failed, read %c\n", prefix, name, ret); + return 1; + } + + if (print_level >=3D 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 >=3D 15) + return 1; + + if (print_level < 0) { + prefix[0] =3D '\0'; + } else { + for (i =3D 0; i < print_level; i++) + prefix[i] =3D '\t'; + prefix[i] =3D '\0'; + } + + filep =3D fopen(name, "r"); + if (!filep) { + if (print_level >=3D 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 =3D 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 =3D open(msr_file_name, O_RDONLY); + if (fd < 0) + return UINT64_MAX; + + if (pread(fd, &value, sizeof(value), msr) !=3D 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/in= tel-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 =3D arg; + *err =3D 0; + + return NL_STOP; +} + +static int finish_handler(struct nl_msg *msg, void *arg) +{ + int *ret =3D arg; + *ret =3D 0; + + return NL_SKIP; +} + +static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, vo= id *arg) +{ + int *ret =3D arg; + *ret =3D 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 *m= sg, + int (*valid_handler)(struct nl_msg*, void*), + void *valid_data) +{ + struct nl_cb *cb; + int err =3D -ENOMEM; + + cb =3D nl_cb_clone(drv->nl_cb); + if (!cb) + goto out; + + err =3D nl_send_auto_complete(drv->nl_handle, msg); + if (err < 0) + goto out; + + err =3D 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 =3D 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 =3D arg; + struct nlattr *tb[CTRL_ATTR_MAX + 1]; + struct genlmsghdr *gnlh =3D 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])) !=3D 0) + continue; + res->id =3D 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 *fam= ily, + const char *group) +{ + struct nl_msg *msg; + int ret =3D -1; + struct family_data res =3D { group, -ENOENT }; + + msg =3D 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 =3D send_and_recv_msgs(drv, msg, family_handler, &res); + msg =3D NULL; + if (ret =3D=3D 0) + ret =3D 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 =3D=3D 255 + * "SUV". PERF =3D=3D EFF =3D=3D 0, suv bit set. Not supported for now. + * "BAN". PERF =3D=3D EFF =3D=3D 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 =3D=3D 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 =3D=3D 16 && last !=3D 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 +=3D snprintf(buf + *offset, MAX_STR_LENGTH - + *offset, "\tCPU %3d: ", nla_get_u32(cap)); + perf_cap->cpu =3D nla_get_u32(cap); + break; + case 1: + *offset +=3D snprintf(buf + *offset, MAX_STR_LENGTH - + *offset, " PERF [%4d] ", nla_get_u32(cap)); + perf_cap->perf =3D nla_get_u32(cap); + break; + case 2: + *offset +=3D snprintf(buf + *offset, MAX_STR_LENGTH - + *offset, " EFF [%4d] ", nla_get_u32(cap)); + perf_cap->eff =3D nla_get_u32(cap); + break; + default: + break; + } + *index +=3D 1; + + if (*index =3D=3D 3) { + char *str; + + str =3D update_one_cpu(perf_cap); + *offset +=3D snprintf(buf + *offset, MAX_STR_LENGTH - + *offset, " TYPE [%s]", str); + buf[MAX_STR_LENGTH - 1] =3D '\0'; + lpmd_log_debug("\t\t\t%s\n", buf); + + *index =3D 0; + *offset =3D 0; + + if (*first_cpu =3D=3D -1) + *first_cpu =3D perf_cap->cpu; + *last_cpu =3D perf_cap->cpu; + *nr_cpus +=3D 1; + } +} + +static int handle_event(struct nl_msg *n, void *arg) +{ + struct nlmsghdr *nlh =3D nlmsg_hdr(n); + struct genlmsghdr *genlhdr =3D genlmsg_hdr(nlh); + struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1]; + struct nlattr *cap; + struct perf_cap perf_cap; + int first_cpu =3D -1, last_cpu =3D -1, nr_cpus =3D 0; + int j, index =3D 0, offset =3D 0; + char buf[MAX_STR_LENGTH]; + + if (genlhdr->cmd !=3D THERMAL_GENL_EVENT_CAPACITY_CHANGE) + return 0; + + if (genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL)) + return -1; + + perf_cap.eff =3D -1; + perf_cap.perf =3D -1; + perf_cap.cpu =3D -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 =3D 1; + return 0; +} + +int hfi_update(void) +{ + int err =3D 0; + + while (!err) + err =3D 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 =3D 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 =3D sock; + cb =3D nl_cb_alloc(NL_CB_DEFAULT); + drv.nl_cb =3D cb; + if (!drv.nl_cb) { + lpmd_log_error("Failed to allocate netlink callbacks"); + goto err_proc; + } + + mcast_id =3D 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/in= tel-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lpmd.h" + +static char irq_socket_name[64]; + +static int irqbalance_pid =3D -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 =3D &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 =3D snprintf(socket_cmd, MAX_STR_LENGTH, "settings cpus %s", irq_s= tr); + if (offset >=3D MAX_STR_LENGTH) + offset =3D MAX_STR_LENGTH - 1; + + socket_cmd[offset] =3D '\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 =3D 0; i < info->nr_irqs; i++) { + char *str =3D 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 =3D NULL; + size_t size =3D 0; + FILE *filep; + + if (info->nr_irqs >=3D (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 =3D irq; + filep =3D fopen(path, "r"); + if (!filep) + return -1; + + if (getline(&str, &size, filep) <=3D 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 =3D strnlen(info->irq[info->nr_irqs].affinity, MAX_STR_LENGTH); + info->irq[info->nr_irqs].affinity[size - 1] =3D '\0'; + + info->nr_irqs++; + } + + return lpmd_write_str(path, irq_str, LPMD_LOG_DEBUG); +} + +static int native_update_irqs(char *irq_str) +{ + char *line =3D NULL; + size_t size =3D 0; + FILE *filep; + + lpmd_log_debug("\tUpdate IRQ affinity (native)\n"); + + filep =3D 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) <=3D 0) { + perror("Error getline\n"); + free(line); + fclose(filep); + return -1; + } + free(line); + + while (!feof(filep)) { + int number; + char *c; + + line =3D NULL; + size =3D 0; + + if (getline(&line, &size, filep) <=3D 0) { + free(line); + break; + } + + /* lines with letters in front are special, like NMI count. Ignore */ + c =3D line; + while (isblank(*(c))) + c++; + + if (!isdigit(*c)) { + free(line); + break; + } + c =3D strchr(line, ':'); + if (!c) { + free(line); + continue; + } + + *c =3D 0; + number =3D strtoul(line, NULL, 10); + + update_one_irq(number, irq_str); + free(line); + } + + fclose(filep); + + irq_updated =3D 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 =3D=3D -1) + native_restore_irqs(); + else + irqbalance_ban_cpus("NULL"); + return 0; + default: + if (state->cpumask_idx =3D=3D CPUMASK_NONE) + return 0; + if (irqbalance_pid =3D=3D -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 =3D opendir("/run/irqbalance"); + if (dir) { + struct dirent *entry; + + do { + entry =3D readdir(dir); + if (entry) { + if (!strncmp(entry->d_name, "irqbalance", 10)) { + ret =3D sscanf(entry->d_name, "irqbalance%d.sock", &irqbalance_pid); + if (!ret) + irqbalance_pid =3D -1; + } + } + } while ((entry) && (irqbalance_pid =3D=3D -1)); + + closedir(dir); + } + + if (irqbalance_pid =3D=3D -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 =3D socket_init_connection(irq_socket_name); + if (socket_fd < 0) { + lpmd_log_error("Can not connect to irqbalance socket /run/irqbalance/irq= balance%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/i= ntel-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 +#include +#include +#include + +#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 =3D -1; +static const char *lock_file =3D TDRUNDIR "/intel_lpmd.pid"; + +// Default log level +static int lpmd_log_level =3D 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 =3D 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 =3D 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 =3D "[CRIT]"; + syslog_priority =3D LOG_CRIT; + break; + case G_LOG_LEVEL_CRITICAL: + prefix =3D "[ERR]"; + syslog_priority =3D LOG_ERR; + break; + case G_LOG_LEVEL_WARNING: + prefix =3D "[WARN]"; + syslog_priority =3D LOG_WARNING; + break; + case G_LOG_LEVEL_MESSAGE: + prefix =3D "[MSG]"; + syslog_priority =3D LOG_NOTICE; + break; + case G_LOG_LEVEL_DEBUG: + prefix =3D "[DEBUG]"; + syslog_priority =3D LOG_DEBUG; + break; + case G_LOG_LEVEL_INFO: + default: + prefix =3D "[INFO]"; + syslog_priority =3D LOG_INFO; + break; + } + + seconds =3D 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 !=3D -1) { + (void)close(lock_file_handle); + (void)unlink(lock_file); + } +} + +static bool check_intel_lpmd_running(void) +{ + lock_file_handle =3D open(lock_file, O_RDWR | O_CREAT, 0600); + if (lock_file_handle =3D=3D -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) =3D=3D -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 =3D FALSE; + gboolean log_debug =3D FALSE; + gboolean no_daemon =3D FALSE; + gboolean log_info =3D FALSE; + gboolean systemd =3D FALSE; + GOptionContext *opt_ctx; + gboolean success; + int ret; + + intel_lpmd_daemonize =3D TRUE; + dbus_enable =3D FALSE; + use_syslog =3D TRUE; + + GOptionEntry options[] =3D { + { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Print intel_lpm= d version and exit"), NULL }, + { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &no_daemon, N_("Don't become a d= aemon: Default is daemon mode"), NULL }, + { "systemd", 0, 0, G_OPTION_ARG_NONE, &systemd, N_("Assume daemon is sta= rted by systemd, always run in non-daemon mode when using this parameter"),= NULL }, + { "loglevel=3Dinfo", 0, 0, G_OPTION_ARG_NONE, &log_info, N_("Log severit= y: info level and up"), NULL }, + { "loglevel=3Ddebug", 0, 0, G_OPTION_ARG_NONE, &log_debug, N_("Log sever= ity: 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_che= ck, 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 =3D 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 =3D 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() !=3D 0) { + fprintf(stderr, "You must be root to run intel_lpmd!\n"); + exit(EXIT_FAILURE); + } + + if (g_mkdir_with_parents(TDRUNDIR, 0755) !=3D 0) { + fprintf(stderr, "Cannot create '%s': %s", TDRUNDIR, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (g_mkdir_with_parents(TDCONFDIR, 0755) !=3D 0) { + fprintf(stderr, "Cannot create '%s': %s", TDCONFDIR, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (log_info) + lpmd_log_level |=3D G_LOG_LEVEL_INFO; + + if (log_debug) + lpmd_log_level |=3D 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 =3D !no_daemon && !systemd; + use_syslog =3D !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 =3D 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) !=3D 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 =3D lpmd_main(); + + if (ret !=3D LPMD_SUCCESS) { + clean_up_lockfile(); + closelog(); + + if (ret =3D=3D 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/i= ntel-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_enable= d" + +static int has_itmt; +static int saved_itmt =3D SETTING_IGNORE; + +int get_itmt(void) +{ + int val, ret; + + if (!has_itmt) + return -1; + + ret =3D 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 =3D 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 =3D 1; + return; + } + + if (lpmd_read_int(PATH_ITMT_CONTROL, &saved_itmt, -1)) + lpmd_log_debug("ITMT not detected\n"); + else + has_itmt =3D 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 =3D 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 =3D 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/paramet= ers/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 =3D opendir(soc_sld_path); + if (!dir) { + lpmd_log_debug("Cannot find %s\n", soc_sld_path); + goto slider_failed; + } + + while ((entry =3D readdir(dir)) !=3D 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 =3D 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 =3D 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 =3D 1; +} + +static int update_balance_slider(int slider) +{ + int ret; + static int current_slider =3D -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 >=3D 0 && current_slider =3D=3D slider) + return 0; + + ret =3D lpmd_write_int(PATH_SOC_BALANCE_SLIDER, slider, 1); + if (ret) + return ret; + + /* Read the current profile and rewrite to make the module params effecti= ve */ + ret =3D lpmd_read_str(soc_sld_path, soc_sld_profile, MAX_STR_LENGTH); + if (ret) + return ret; + + ret =3D lpmd_write_str(soc_sld_path, soc_sld_profile, 1); + if (ret) + return ret; + + current_slider =3D slider; + + return 0; +} + +static int update_slider_offset(int offset) +{ + int ret; + static int current_slider_offset =3D -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 >=3D 0 && current_slider_offset =3D=3D offset) + return 0; + + ret =3D lpmd_write_int(PATH_SOC_OFFSET, offset, 1); + if (ret) + return ret; + + /* Read the current profile and rewrite to make the module params effecti= ve */ + ret =3D lpmd_read_str(soc_sld_path, soc_sld_profile, MAX_STR_LENGTH); + if (ret) + return ret; + + ret =3D lpmd_write_str(soc_sld_path, soc_sld_profile, 1); + if (ret) + return ret; + + current_slider_offset =3D 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 >=3D 0) + update_balance_slider(config->balance_slider_def_dc); + } else { + if (config->balance_slider_def_ac >=3D 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 >=3D 0) + update_slider_offset(config->slider_offset_def_dc); + } else { + if (config->slider_offset_def_ac >=3D 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 =3D process_balance_slider(state); + if (ret) + process_balance_slider_default_update(config); + + ret =3D 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 =3D fopen(path, "r"); + if (!filep) + return 1; + + ret =3D fscanf(filep, "%d", &epp); + if (ret =3D=3D 1) { + *val =3D epp; + ret =3D 0; + goto end; + } + + ret =3D fread(str, 1, size, filep); + if (ret <=3D 0) { + ret =3D 1; + } else { + if (ret >=3D size) + ret =3D size - 1; + str[ret - 1] =3D '\0'; + ret =3D 0; + } +end: + fclose(filep); + return ret; +} + +static int set_epp(char *path, int val, char *str) +{ + FILE *filep; + int ret; + + filep =3D fopen(path, "r+"); + if (!filep) + return 1; + + if (val >=3D 0) { + ret =3D fprintf(filep, "%d", val); + } else if (str && str[0] !=3D '\0') { + ret =3D fprintf(filep, "%s", str); + } else { + fclose(filep); + return 1; + } + + fclose(filep); + + if (ret <=3D 0) { + if (val >=3D 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 =3D get_ppd_mode(); + + if (ppd_mode =3D=3D PPD_INVALID) + return NULL; + + if (ppd_mode =3D=3D PPD_PERFORMANCE) + return "performance"; + + if (ppd_mode =3D=3D 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 =3D -1; + epp_str[0] =3D '\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] =3D '\0'; + + snprintf(path, MAX_STR_LENGTH, "/sys/devices/system/cpu/cpu%d/power/energ= y_perf_bias", 0); + lpmd_read_int(path, epb, -1); + return 0; +} + +int process_epp_epb(struct lpmd_config_state_t *state) +{ + int max_cpus =3D get_max_cpus(); + char path[MAX_STR_LENGTH]; + int ret; + int c; + + if (state->epp =3D=3D SETTING_IGNORE) + lpmd_log_info("Ignore EPP\n"); + if (state->epb =3D=3D SETTING_IGNORE) + lpmd_log_info("Ignore EPB\n"); + if (state->epp =3D=3D SETTING_IGNORE && state->epb =3D=3D SETTING_IGNORE) + return 0; + + for (c =3D 0; c < max_cpus; c++) { + int val; + char *str =3D NULL; + + if (!is_cpu_online(c)) + continue; + + if (state->epp !=3D SETTING_IGNORE) { + if (state->epp =3D=3D SETTING_RESTORE) { + val =3D -1; + str =3D get_ppd_default_epp(); + if (!str) { + /* Fallback to cached EPP */ + val =3D saved_cpu_info[c].epp; + str =3D saved_cpu_info[c].epp_str; + } + } else { + val =3D state->epp; + } + + snprintf(path, sizeof(path), + "/sys/devices/system/cpu/cpu%d/cpufreq/energy_performance_preference"= , c); + ret =3D set_epp(path, val, str); + if (!ret) { + if (val !=3D -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 !=3D SETTING_IGNORE) { + if (state->epb =3D=3D SETTING_RESTORE) + val =3D saved_cpu_info[c].epb; + else + val =3D state->epb; + + snprintf(path, MAX_STR_LENGTH, + "/sys/devices/system/cpu/cpu%d/power/energy_perf_bias", c); + ret =3D 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 =3D get_max_cpus(); + char path[MAX_STR_LENGTH]; + int ret; + int c; + + saved_cpu_info =3D calloc(max_cpus, sizeof(struct cpu_info)); + + for (c =3D 0; c < max_cpus; c++) { + saved_cpu_info[c].epp_str[0] =3D '\0'; + saved_cpu_info[c].epp =3D -1; + + if (!is_cpu_online(c)) + continue; + + snprintf(path, sizeof(path), + "/sys/devices/system/cpu/cpu%d/cpufreq/energy_performance_preference",= c); + ret =3D 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 !=3D -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 =3D lpmd_read_int(path, &saved_cpu_info[c].epb, -1); + if (ret) { + saved_cpu_info[c].epb =3D -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/i= ntel-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 +#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 =3D 0, ebx =3D 0, ecx =3D 0, edx =3D 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, unsign= ed char *msg) +{ + struct message_capsul_t msg_cap; + int result; + + memset(&msg_cap, 0, sizeof(struct message_capsul_t)); + + msg_cap.msg_id =3D msg_id; + msg_cap.msg_size =3D (size > MAX_MSG_SIZE) ? MAX_MSG_SIZE : size; + if (msg) + memcpy(msg_cap.msg, msg, msg_cap.msg_size); + + result =3D 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 =3D -1; +static int idx_uevent_fd =3D -1; +static int idx_hfi_fd =3D -1; +static int idx_wlt_fd =3D -1; + +#include + +static GDBusProxy *power_profiles_daemon; + +static enum power_profile_daemon_mode ppd_mode =3D PPD_INVALID; + +int get_ppd_mode(void) +{ + return ppd_mode; +} + +static void power_profiles_changed_cb(void) +{ + g_autoptr(GVariant) + active_profile_v =3D NULL; + + active_profile_v =3D g_dbus_proxy_get_cached_property(power_profiles_daem= on, + "ActiveProfile"); + + if (active_profile_v && g_variant_is_of_type(active_profile_v, G_VARIANT_= TYPE_STRING)) { + const char *active_profile =3D g_variant_get_string(active_profile_v, NU= LL); + + lpmd_log_debug("%s: %s\n", __func__, active_profile); + + if (strcmp(active_profile, "power-saver") =3D=3D 0) { + ppd_mode =3D PPD_POWERSAVER; + lpmd_send_message(lpmd_config.powersaver_def, 0, NULL); + } else if (strcmp(active_profile, "performance") =3D=3D 0) { + ppd_mode =3D PPD_PERFORMANCE; + lpmd_send_message(lpmd_config.performance_def, 0, NULL); + } else if (strcmp(active_profile, "balanced") =3D=3D 0) { + ppd_mode =3D 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 =3D DEF_POLLING_INTERVAL; + } +} + +static int connect_to_power_profile_daemon(void) +{ + g_autoptr(GDBusConnection) + bus =3D NULL; + + bus =3D g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); + if (bus) { + power_profiles_daemon =3D + 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 =3D -1; + +int is_on_battery(void) +{ + if (battery_mode < 0) + battery_mode =3D 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 =3D -1; + + battery_mode =3D up_client_get_on_battery(upower_client); + if (mode !=3D battery_mode) { + process_balance_slider_default_update(&lpmd_config); + process_slider_offset_default_update(&lpmd_config); + } + + mode =3D battery_mode; +} + +static void connect_to_upower_daemon(void) +{ + GError *error =3D NULL; + GPtrArray *devices; + UpDevice *device; + int i; + + upower_client =3D 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), N= ULL); + + devices =3D up_client_get_devices2(upower_client); + for (i =3D 0; i < devices->len; i++) { + device =3D 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 =3D 0; + +// if (!in_debug_mode()) + if (1) + return; + + if (idx_pipe_fd !=3D -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 !=3D -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 !=3D -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 !=3D -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 |=3D 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 =3D DEF_POLLING_INTERVAL; + + for (;;) { + if (get_lpmd_state() =3D=3D LPMD_TERMINATE) + break; + + n =3D 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 =3D=3D 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 =3D + read_wlt_proxy(&lpmd_config.data.polling_interval); + } + + /* Check CPU hotplug. Maybe need to freeze lpmd */ + if (idx_uevent_fd >=3D 0 && (poll_fds[idx_uevent_fd].revents & POLLIN)) + check_cpu_hotplug(); + + /* Update CPUMASK_HFI */ + if (idx_hfi_fd >=3D 0 && (poll_fds[idx_hfi_fd].revents & POLLIN)) + hfi_update(); + + /* Update WLT hint */ + if (idx_wlt_fd >=3D 0 && (poll_fds[idx_wlt_fd].revents & POLLPRI)) { + wlt_hint =3D wlt_update(poll_fds[idx_wlt_fd].fd); + if (wlt_hint !=3D lpmd_config.data.wlt_hint) { + lpmd_config.data.wlt_hint =3D wlt_hint; + update_reason(UPDATE_WLT); + } + } + + /* Respond Dbus commands */ + if (idx_pipe_fd >=3D 0 && (poll_fds[idx_pipe_fd].revents & POLLIN)) { +// process message written on pipe here + + result =3D 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 =3D 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 =3D 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 =3D detect_supported_platform(&lpmd_config); + if (ret) + return ret; + + ret =3D detect_cpu_topo(&lpmd_config); + if (ret) + return ret; + +// Call all lpmd related functions here + ret =3D lpmd_get_config(&lpmd_config); + if (ret) + return ret; + + pthread_mutex_init(&lpmd_mutex, NULL); + + ret =3D detect_lpm_cpus(lpmd_config.lp_mode_cpus); + if (ret) + return ret; + + ret =3D cgroup_init(&lpmd_config); + if (ret) + return ret; + + itmt_init(); + + ret =3D epp_epb_init(); + if (ret) + return ret; + + if (!has_hfi_capability()) + lpmd_config.hfi_lpm_enable =3D 0; + + /* Must done after init_cpu() */ + lpmd_build_config_states(&lpmd_config); + + ret =3D irq_init(); + if (ret) + return ret; + + connect_to_upower_daemon(); +// Pipe is used for communication between two processes + ret =3D 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 =3D wake_fds[1]; + + memset(poll_fds, 0, sizeof(poll_fds)); + + idx_pipe_fd =3D poll_fd_cnt; + poll_fds[idx_pipe_fd].fd =3D wake_fds[0]; + poll_fds[idx_pipe_fd].events =3D POLLIN; + poll_fds[idx_pipe_fd].revents =3D 0; + poll_fd_cnt++; + + poll_fds[poll_fd_cnt].fd =3D uevent_init(); + if (poll_fds[poll_fd_cnt].fd > 0) { + idx_uevent_fd =3D poll_fd_cnt; + poll_fds[idx_uevent_fd].events =3D POLLIN; + poll_fds[idx_uevent_fd].revents =3D 0; + poll_fd_cnt++; + } + + if (lpmd_config.hfi_lpm_enable) { + poll_fds[poll_fd_cnt].fd =3D hfi_init(); + if (poll_fds[poll_fd_cnt].fd > 0) { + idx_hfi_fd =3D poll_fd_cnt; + poll_fds[idx_hfi_fd].events =3D POLLIN; + poll_fds[idx_hfi_fd].revents =3D 0; + poll_fd_cnt++; + } + } + + if (lpmd_config.wlt_hint_enable && + lpmd_config.wlt_proxy_enable && + wlt_proxy_init() !=3D LPMD_SUCCESS) { + lpmd_config.wlt_proxy_enable =3D 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 =3D 0; + if (!lpmd_config.wlt_proxy_enable) { + poll_fds[poll_fd_cnt].fd =3D wlt_init(); + if (poll_fds[poll_fd_cnt].fd > 0) { + idx_wlt_fd =3D poll_fd_cnt; + poll_fds[idx_wlt_fd].events =3D POLLPRI; + poll_fds[idx_wlt_fd].revents =3D 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 =3D 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 =3D socket(AF_LOCAL, SOCK_STREAM, 0); + if (socket_fd < 0) { + perror("Error opening socket"); + return 0; + } + addr.sun_family =3D 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 =3D 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 =3D malloc(sizeof(struct ucred)); + if (!credentials) + return NULL; + + credentials->pid =3D getpid(); + credentials->uid =3D geteuid(); + credentials->gid =3D getegid(); + + msg =3D malloc(sizeof(struct msghdr)); + if (!msg) { + free(credentials); + return msg; + } + + memset(msg, 0, sizeof(struct msghdr)); + msg->msg_iovlen =3D 1; + msg->msg_control =3D malloc(CMSG_SPACE(sizeof(struct ucred))); + if (!msg->msg_control) { + free(credentials); + free(msg); + return NULL; + } + + msg->msg_controllen =3D CMSG_SPACE(sizeof(struct ucred)); + + cmsg =3D CMSG_FIRSTHDR(msg); + cmsg->cmsg_level =3D SOL_SOCKET; + cmsg->cmsg_type =3D SCM_CREDENTIALS; + cmsg->cmsg_len =3D 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 =3D socket_init_connection(name); + if (!socket_fd) + return LPMD_ERROR; + + msg =3D create_credentials_msg(); + if (!msg) + return LPMD_ERROR; + + iov.iov_base =3D (void *)data; + iov.iov_len =3D strlen(data); + msg->msg_iov =3D &iov; + + if (sendmsg(socket_fd, msg, 0) < 0) { + free(msg->msg_control); + free(msg); + return LPMD_ERROR; + } + + ret =3D 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/po= wer/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lpmd.h" + +/* LPMD state control: ON/OFF/AUTO/FREEZE/RESTORE/TERMINATE */ +static int lpmd_state =3D LPMD_OFF; +static int saved_lpmd_state =3D LPMD_OFF; + +static char *lpmd_state_name[] =3D { + [LPMD_ON] =3D " ON", + [LPMD_OFF] =3D " OFF", + [LPMD_AUTO] =3D " AUTO", + [LPMD_FREEZE] =3D " FREEZE", + [LPMD_RESTORE] =3D "RESTORE", + [LPMD_TERMINATE] =3D " TERM", +}; + +int update_lpmd_state(int new) +{ + lpmd_lock(); + switch (new) { + case LPMD_FREEZE: + if (lpmd_state =3D=3D LPMD_FREEZE) + break; + lpmd_log_debug("Freeze lpmd\n"); + saved_lpmd_state =3D lpmd_state; + lpmd_state =3D LPMD_FREEZE; + break; + case LPMD_RESTORE: + if (lpmd_state !=3D LPMD_FREEZE) + break; + lpmd_log_debug("Restore lpmd\n"); + lpmd_state =3D saved_lpmd_state; + saved_lpmd_state =3D lpmd_state; + break; + default: + if (lpmd_state =3D=3D LPMD_FREEZE) + saved_lpmd_state =3D new; + else + lpmd_state =3D 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 =3D -1; + state->valid =3D 0; + state->name[0] =3D '\0'; + + state->wlt_type =3D -1; + + state->entry_system_load_thres =3D 0; + state->exit_system_load_thres =3D 0; + state->exit_system_load_hyst =3D 0; + state->enter_cpu_load_thres =3D 0; + state->exit_cpu_load_thres =3D 0; + state->enter_gfx_load_thres =3D 0; + state->exit_gfx_load_thres =3D 0; + + state->min_poll_interval =3D 0; + state->max_poll_interval =3D 0; + state->poll_interval_increment =3D 0; + + state->epp =3D SETTING_IGNORE; + state->epb =3D SETTING_IGNORE; + state->active_cpus[0] =3D '\0'; + state->cpumask_idx =3D CPUMASK_NONE; + + state->island_0_number_p_cores =3D 0; + state->island_0_number_e_cores =3D 0; + state->island_1_number_p_cores =3D 0; + state->island_1_number_e_cores =3D 0; + state->island_2_number_p_cores =3D 0; + state->island_2_number_e_cores =3D 0; + + state->itmt_state =3D SETTING_IGNORE; + state->irq_migrate =3D SETTING_IGNORE; + + state->entry_load_sys =3D 0; + state->entry_load_cpu =3D 0; + state->cpumask_idx =3D CPUMASK_NONE; + + state->balance_slider_ac =3D -1; + state->balance_slider_dc =3D -1; + state->slider_offset_ac =3D -1; + state->slider_offset_dc =3D -1; + + return 0; +} + +static int current_idx =3D DEFAULT_OFF; + +static int config_state_match(struct lpmd_config_t *config, int idx) +{ + struct lpmd_config_state_t *state =3D &config->config_states[idx]; + int bcpu =3D config->data.util_cpu; + int bsys =3D config->data.util_sys; + int bgfx =3D config->data.util_gfx; + int wlt_index =3D config->data.wlt_hint; + + if (!state->valid) + return 0; + + if (state->wlt_type !=3D -1) { + if (config->wlt_hint_mask !=3D -1) + wlt_index &=3D config->wlt_hint_mask; + + if (state->wlt_type !=3D 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 < bs= ys) { + 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) < bs= ys) + 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 =3D &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 =3D -1; + return 0; + } + + /* Always start with minimum polling interval for a new state */ + if (idx !=3D current_idx) { + config->data.polling_interval =3D state->min_poll_interval; + return 0; + } + + /* CPU utilization based adaptive polling */ + if (state->poll_interval_increment =3D=3D -1) { + config->data.polling_interval =3D + state->max_poll_interval * (10000 - config->data.util_cpu) / 10000; + config->data.polling_interval /=3D 100; + config->data.polling_interval *=3D 100; + goto end; + } + + /* lazy polling if load is sustained */ + if (state->poll_interval_increment > 0) + config->data.polling_interval +=3D state->poll_interval_increment; + +end: + /* Adjust based on min/max limitation */ + if (config->data.polling_interval < state->min_poll_interval) + config->data.polling_interval =3D state->min_poll_interval; + if (config->data.polling_interval > state->max_poll_interval) + config->data.polling_interval =3D state->max_poll_interval; + return 0; +} + +static void dump_state(struct lpmd_config_state_t *state, char *str, int d= ebug) +{ + char buf[MAX_STR_LENGTH]; + int offset =3D 0; + + if (debug && !in_debug_mode()) + return; + + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "[%6s] [%s] [%s]: ", str, + lpmd_state_name[lpmd_state], state->name); + + if (state->wlt_type) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "WLT [%2d] ", state->wlt_type); + + if (state->entry_system_load_thres) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "SYS [%6d] ", + state->entry_system_load_thres / 100); + + if (state->enter_cpu_load_thres) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "CPU [%6d] ", + state->enter_cpu_load_thres / 100); + + if (state->enter_gfx_load_thres) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "GFX [%6d] ", + state->enter_gfx_load_thres / 100); + + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "CPUMASK [%d] ", state->cpumask_idx); + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "IRQ [%d] ", state->irq_migrate); + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "ITMT [%d] ", state->itmt_state); + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "EPB [%d] ", state->epb); + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "EPP [%d] ", state->epp); + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "SliderAC [%d] ", state->balance_slider_ac); + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "SliderDC [%d] ", state->balance_slider_ac); + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "OffsetAC [%d] ", state->slider_offset_ac); + offset +=3D 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 =3D CONFIG_STATE_BASE; i < CONFIG_STATE_BASE + config->config_stat= e_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 =3D -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 !=3D 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 =3D &config->config_states[idx]; + + state->entry_load_sys =3D config->data.util_sys; + state->entry_load_cpu =3D 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 =3D 0; + struct lpmd_config_state_t *state =3D &config->config_states[idx]; + + if (!in_debug_mode()) + return; + + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "[ Data] [%s] [%s]: ", lpmd_state_name[lpmd_state], + state->name); + + if (config->wlt_hint_enable) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "WLT [%2d] ", config->data.wlt_hint); + + if (config->util_sys_enable) { + if (config->data.util_sys =3D=3D -1) + offset +=3D snprintf(buf + offset, + MAX_STR_LENGTH - offset, + "SYS [ N/A] "); + else + offset +=3D 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 =3D=3D -1) + offset +=3D snprintf(buf + offset, + MAX_STR_LENGTH - offset, + "CPU [ N/A] "); + else + offset +=3D 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 =3D=3D -1) + offset +=3D snprintf(buf + offset, + MAX_STR_LENGTH - offset, + "GFX [ N/A] "); + else + offset +=3D snprintf(buf + offset, + MAX_STR_LENGTH - offset, + "GFX [%3d.%02d] ", + config->data.util_gfx / 100, + config->data.util_gfx % 100); + } + + if (state->cpumask_idx !=3D CPUMASK_NONE) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "CPUMASK [%s] ", + get_cpus_hexstr(state->cpumask_idx)); + else + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "CPUMASK [%s] ", + get_cpus_hexstr(CPUMASK_ONLINE)); + + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "ITMT [%d] ", get_itmt()); + + get_epp_epb(&epp, epp_str, 32, &epb); + if (epp =3D=3D -1) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "EPB [%d] EPP[%s] ", epb, epp_str); + else + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "EPB [%d] EPP[%d] ", epb, epp); + + if (config->hfi_lpm_enable) + offset +=3D snprintf(buf + offset, MAX_STR_LENGTH - offset, + "UPDATE [%d] ", config->data.need_update); + + offset +=3D 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 =3D get_lpmd_config(); + int idx =3D current_idx; + + lpmd_lock(); + + if (lpmd_state =3D=3D LPMD_FREEZE) { + /* Wait till RESTORE */ + config->data.polling_interval =3D -1; + goto end; + } + + idx =3D choose_next_state(config); + + /* + * After switching power profiles polling gets disabled and needs to be + * updated. + */ + if (config->data.polling_interval =3D=3D -1 && polling_enabled && idx != =3D DEFAULT_OFF) + get_config_state_interval(config, idx); + + /* No action needed, keep previous idx and interval */ + if (idx =3D=3D STATE_NONE) + goto end; + + get_state_interval(config, idx); + + if (need_enter(config, idx)) { + enter_state(config, idx); + current_idx =3D 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_thresh= old); + lpmd_log_info("Util exit threshold:%d\n", lpmd_config->util_exit_threshol= d); + 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_d= ef_ac); + lpmd_log_info("balance_slider_def_dc:%d\n", lpmd_config->balance_slider_d= ef_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 =3D 0; i < MAX_STATES; ++i) { + state =3D &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_t= hres); + lpmd_log_info("\texit_system_load_hyst:%d\n", state->exit_system_load_hy= st); + 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_inc= rement); + 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] !=3D '\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 =3D &config->config_states[DEFAULT_OFF]; + lpmd_init_config_state(state); + state->id =3D -1; + snprintf(state->name, MAX_STATE_NAME, "DEFAULT_OFF"); + state->itmt_state =3D SETTING_RESTORE; + state->irq_migrate =3D SETTING_RESTORE; + state->epp =3D SETTING_RESTORE; + state->epb =3D SETTING_RESTORE; + state->cpumask_idx =3D CPUMASK_ONLINE; + state->steady =3D 1; + state->valid =3D 1; + + state =3D &config->config_states[DEFAULT_ON]; + lpmd_init_config_state(state); + state->id =3D -1; + snprintf(state->name, MAX_STATE_NAME, "DEFAULT_ON"); + state->itmt_state =3D config->ignore_itmt ? SETTING_IGNORE : 0; + state->irq_migrate =3D 1; + state->epp =3D config->lp_mode_epp; + state->epb =3D SETTING_IGNORE; + state->cpumask_idx =3D CPUMASK_LPM_DEFAULT; + state->steady =3D 1; + state->valid =3D 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 =3D &config->config_states[DEFAULT_HFI]; + lpmd_init_config_state(state); + state->id =3D -1; + snprintf(state->name, MAX_STATE_NAME, "DEFAULT_HFI"); + state->itmt_state =3D SETTING_IGNORE; + state->irq_migrate =3D SETTING_IGNORE; + state->epp =3D SETTING_IGNORE; + state->epb =3D SETTING_IGNORE; + state->cpumask_idx =3D CPUMASK_HFI; + state->steady =3D 0; + state->valid =3D 1; + + config->config_state_count =3D 1; + return 0; + } + + /* + * When HFI monitor is not enabled and config states are not used, + * Switch system following global setting based on utilization. + */ + state =3D &config->config_states[CONFIG_STATE_BASE]; + lpmd_init_config_state(state); + state->id =3D 1; + snprintf(state->name, MAX_STATE_NAME, "UTIL_POWER"); + state->entry_system_load_thres =3D config->util_entry_threshold; + state->enter_cpu_load_thres =3D config->util_exit_threshold; + state->itmt_state =3D config->ignore_itmt ? SETTING_IGNORE : 0; + state->irq_migrate =3D 1; + state->min_poll_interval =3D 100; + state->max_poll_interval =3D 1000; + state->poll_interval_increment =3D -1; + state->epp =3D config->lp_mode_epp; + state->epb =3D SETTING_IGNORE; + state->cpumask_idx =3D CPUMASK_LPM_DEFAULT; + state->steady =3D 1; + state->valid =3D 1; + + state =3D &config->config_states[CONFIG_STATE_BASE + 1]; + lpmd_init_config_state(state); + state->id =3D 2; + snprintf(state->name, MAX_STATE_NAME, "UTIL_PERF"); + state->entry_system_load_thres =3D 100; + state->enter_cpu_load_thres =3D 100; + state->itmt_state =3D config->ignore_itmt ? SETTING_IGNORE : SETTING_REST= ORE; + state->irq_migrate =3D 1; + state->min_poll_interval =3D 1000; + state->max_poll_interval =3D 1000; + state->epp =3D config->lp_mode_epp =3D=3D SETTING_IGNORE ? SETTING_IGNORE= : SETTING_RESTORE; + state->epb =3D SETTING_IGNORE; + state->cpumask_idx =3D CPUMASK_ONLINE; + state->steady =3D 1; + state->valid =3D 1; + + config->config_state_count =3D 2; + return 0; +} + +static int config_states_update_config(struct lpmd_config_t *config) +{ + struct lpmd_config_state_t *state; + int i; + + for (i =3D CONFIG_STATE_BASE; i < CONFIG_STATE_BASE + config->config_stat= e_count; i++) { + state =3D &config->config_states[i]; + + if (!state->valid) + continue; + + if (state->cpumask_idx =3D=3D CPUMASK_HFI) + config->hfi_lpm_enable =3D 1; + + if (state->wlt_type !=3D -1) + config->wlt_hint_enable =3D 1; + + if (state->entry_system_load_thres) + config->util_sys_enable =3D 1; + + if (state->enter_cpu_load_thres) + config->util_cpu_enable =3D 1; + + if (state->enter_gfx_load_thres) + config->util_gfx_enable =3D 1; + } + return 0; +} + +static int build_state_cpumask(struct lpmd_config_state_t *state) +{ + state->steady =3D 1; + + if (state->cpumask_idx !=3D CPUMASK_NONE) + return 0; + + if (state->active_cpus[0] =3D=3D '\0') + return 0; + + if (!strncmp(state->active_cpus, "all", sizeof("all")) || + !strncmp(state->active_cpus, "ALL", sizeof("ALL"))) { + state->cpumask_idx =3D CPUMASK_ONLINE; + return 0; + } + + if (!strncmp(state->active_cpus, "lp", sizeof("lp")) || + !strncmp(state->active_cpus, "LP", sizeof("LP"))) { + state->cpumask_idx =3D CPUMASK_LPM_DEFAULT; + return 0; + } + + if (!strncmp(state->active_cpus, "hfi", sizeof("hfi")) || + !strncmp(state->active_cpus, "HFI", sizeof("HFI"))) { + state->cpumask_idx =3D CPUMASK_HFI; + state->steady =3D 0; + return 0; + } + + state->cpumask_idx =3D cpumask_alloc(); + if (state->cpumask_idx =3D=3D CPUMASK_NONE) { + lpmd_log_error("Cannot alloc CPUMASK\n"); + return -1; + } + + if (cpumask_init_cpus(state->active_cpus, state->cpumask_idx) <=3D 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 =3D CONFIG_STATE_BASE; i < CONFIG_STATE_BASE + lpmd_config->config= _state_count; i++) { + state =3D &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 =3D 1; + + if (state->min_poll_interval <=3D 0) + state->min_poll_interval =3D state->max_poll_interval > DEFAULT_POLL_RA= TE_MS ? + DEFAULT_POLL_RATE_MS : state->max_poll_interval; + if (state->max_poll_interval <=3D 0) + state->max_poll_interval =3D state->min_poll_interval > DEFAULT_POLL_RA= TE_MS ? + state->min_poll_interval : DEFAULT_POLL_RATE_MS; + if (state->poll_interval_increment <=3D 0) + state->poll_interval_increment =3D -1; + + if (state->entry_system_load_thres < 0 || state->entry_system_load_thres= > 100) + continue; + else + state->entry_system_load_thres *=3D 100; + + if (state->enter_cpu_load_thres < 0 || state->enter_cpu_load_thres > 100) + continue; + else + state->enter_cpu_load_thres *=3D 100; + + if (state->exit_cpu_load_thres < 0 || state->exit_cpu_load_thres > 100) + continue; + else + state->exit_cpu_load_thres *=3D 100; + + if (state->enter_gfx_load_thres < 0 || state->enter_gfx_load_thres > 100) + continue; + else + state->enter_gfx_load_thres *=3D 100; + + state->valid =3D 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 +#include + +#include "lpmd.h" + +static int uevent_fd =3D -1; + +static int has_cpu_uevent(void) +{ + ssize_t i =3D 0; + ssize_t len; + const char *dev_path =3D "DEVPATH=3D"; + unsigned int dev_path_len =3D strlen(dev_path); + const char *cpu_path =3D "/devices/system/cpu/cpu"; + char buffer[MAX_STR_LENGTH]; + + len =3D recv(uevent_fd, buffer, sizeof(buffer) - 1, MSG_DONTWAIT); + if (len <=3D 0) + return 0; + buffer[len] =3D '\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 +=3D 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 =3D fopen(PATH_PROC_STAT, "r"); + if (!filep) + return 0; + + curr =3D cpumask_alloc(); + if (curr =3D=3D CPUMASK_NONE) + err(3, "ALLOC_CPUMASK"); + + while (!feof(filep)) { + char *tmpline =3D NULL; + size_t size =3D 0; + char *line; + int cpu; + char *p; + int ret; + + tmpline =3D NULL; + size =3D 0; + + if (getline(&tmpline, &size, filep) <=3D 0) { + free(tmpline); + break; + } + + line =3D strdup(tmpline); + + p =3D strtok(line, " "); + + ret =3D sscanf(p, "cpu%d", &cpu); + if (ret !=3D 1) + goto free; + + cpumask_add_cpu(cpu, curr); + +free: + free(tmpline); + free(line); + } + + fclose(filep); + + ret =3D 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 =3D AF_NETLINK; + nls.nl_pid =3D getpid(); + nls.nl_groups =3D -1; + + uevent_fd =3D 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/i= ntel-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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 =3D -1; +static int busy_cpu =3D -1; +static int busy_gfx =3D -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_m= s", R_OK)) + return 1; + + fp =3D fopen("/sys/class/drm/card0/device/tile0/gt0/gtidle/name", "r"); + if (!fp) + return 1; + + ret =3D fread(buf, sizeof(char), 7, fp); + if (!ret) { + fclose(fp); + return 1; + } + + fclose(fp); + + if (ret >=3D 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 =3D "/sys/class/drm/card0/device/tile0/gt0/gtidle/idle_res= idency_ms"; + if (!access("/sys/class/drm/card0/device/tile0/gt1/gtidle/idle_residency= _ms", R_OK)) + path_sam_mc6 =3D "/sys/class/drm/card0/device/tile0/gt1/gtidle/idle_res= idency_ms"; + } else if (ret >=3D strlen("gt0-mc") && !strncmp(buf, "gt0-mc", strlen("g= t0-mc"))) { + if (!access("/sys/class/drm/card0/device/tile0/gt1/gtidle/idle_residency= _ms", R_OK)) + path_gfx_rc6 =3D "/sys/class/drm/card0/device/tile0/gt1/gtidle/idle_res= idency_ms"; + if (!access("/sys/class/drm/card0/device/tile0/gt0/gtidle/idle_residency= _ms", R_OK)) + path_sam_mc6 =3D "/sys/class/drm/card0/device/tile0/gt0/gtidle/idle_res= idency_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 =3D ULLONG_MAX, sam_mc6_prev =3D U= LLONG_MAX; + unsigned long long gfx_rc6 =3D ULLONG_MAX, sam_mc6 =3D ULLONG_MAX; + FILE *fp; + unsigned long long gfx_util, sam_util; + int ret; + + gfx_util =3D -1; + sam_util =3D -1; + + fp =3D fopen(path_gfx_rc6, "r"); + if (fp) { + ret =3D fscanf(fp, "%lld", &gfx_rc6); + if (ret !=3D 1) + gfx_rc6 =3D ULLONG_MAX; + fclose(fp); + } + + fp =3D fopen(path_sam_mc6, "r"); + if (fp) { + ret =3D fscanf(fp, "%lld", &sam_mc6); + if (ret !=3D 1) + sam_mc6 =3D ULLONG_MAX; + fclose(fp); + } + + if (gfx_rc6 =3D=3D ULLONG_MAX && sam_mc6 =3D=3D ULLONG_MAX) + return -1; + + if (gfx_rc6 !=3D ULLONG_MAX) { + if (gfx_rc6_prev !=3D ULLONG_MAX) + gfx_util =3D 10000 - (gfx_rc6 - gfx_rc6_prev) * 10000 / time_ms; + gfx_rc6_prev =3D gfx_rc6; + } + + if (sam_mc6 !=3D ULLONG_MAX) { + if (sam_mc6_prev !=3D ULLONG_MAX) + sam_util =3D 10000 - (sam_mc6 - sam_mc6_prev) * 10000 / time_ms; + sam_mc6_prev =3D 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 =3D 1; + static struct timespec ts_prev; + struct timespec ts_cur; + unsigned long time_ms; + int ret; + + busy_gfx =3D -1; + + if (!gfx_sysfs_available) + return 1; + + clock_gettime(CLOCK_MONOTONIC, &ts_cur); + + if (!ts_prev.tv_sec && !ts_prev.tv_nsec) { + ret =3D probe_gfx_util_sysfs(); + if (ret) { + gfx_sysfs_available =3D 0; + return 1; + } + ts_prev =3D ts_cur; + return 0; + } + + time_ms =3D (ts_cur.tv_sec - ts_prev.tv_sec) * 1000 + + (ts_cur.tv_nsec - ts_prev.tv_nsec) / 1000000; + + ts_prev =3D ts_cur; + busy_gfx =3D 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 =3D -1; + + cpu =3D sched_getcpu(); + tsc =3D read_msr(cpu, MSR_TSC); + if (tsc =3D=3D UINT64_MAX) + goto err; + + val =3D read_msr(cpu, MSR_PKG_ANY_GFXE_C0_RES); + if (val =3D=3D UINT64_MAX) + goto err; + + if (!tsc_prev || !val_prev) { + tsc_prev =3D tsc; + val_prev =3D val; + return 0; + } + + if (val > val_prev && tsc > tsc_prev) { + _busy_gfx =3D abs(val - val_prev) * 10000ULL / abs(tsc - tsc_prev); + if (_busy_gfx < INT_MAX) + busy_gfx =3D (int)_busy_gfx; + } + + tsc_prev =3D tsc; + val_prev =3D 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 =3D 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 =3D 0, total =3D 0; + + for (idx =3D STAT_USER; idx < STAT_MAX; idx++) { + total +=3D (cur->stat[idx] - prev->stat[idx]); +// Align with the "top" utility logic + if (idx !=3D STAT_IDLE && idx !=3D STAT_IOWAIT) + busy +=3D (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 =3D get_max_online_cpu() + 1; + int sys_idx =3D count - 1; + size_t size =3D sizeof(struct proc_stat_info) * count; + + filep =3D fopen(PATH_PROC_STAT, "r"); + if (!filep) + return 1; + + if (!proc_stat_prev) + proc_stat_prev =3D calloc(count, sizeof(struct proc_stat_info)); + + if (!proc_stat_prev) { + fclose(filep); + return 1; + } + + if (!proc_stat_cur) + proc_stat_cur =3D calloc(count, sizeof(struct proc_stat_info)); + + if (!proc_stat_cur) { + free(proc_stat_prev); + fclose(filep); + proc_stat_prev =3D NULL; + return 1; + } + + memcpy(proc_stat_prev, proc_stat_cur, size); + memset(proc_stat_cur, 0, size); + + while (!feof(filep)) { + int idx; + char *tmpline =3D NULL; + struct proc_stat_info *info; + char *line; + int cpu; + char *p; + int ret; + + tmpline =3D NULL; + size =3D 0; + + if (getline(&tmpline, &size, filep) <=3D 0) { + free(tmpline); + break; + } + + line =3D strdup(tmpline); + + p =3D strtok(line, " "); + + if (strncmp(p, "cpu", 3)) { + free(tmpline); + free(line); + continue; + } + + ret =3D sscanf(p, "cpu%d", &cpu); + if (ret =3D=3D -1 && !(strncmp(p, "cpu", 3))) { + /* Read system line */ + info =3D &proc_stat_cur[sys_idx]; + } else if (ret =3D=3D 1) { + info =3D &proc_stat_cur[cpu]; + } else { + free(tmpline); + free(line); + continue; + } + + info->valid =3D 1; + idx =3D STAT_CPU; + + while (p) { + if (idx >=3D STAT_MAX) + break; + + if (idx =3D=3D STAT_CPU) { + idx++; + p =3D strtok(NULL, " "); + continue; + } + + if (sscanf(p, "%llu", &info->stat[idx]) <=3D 0) + lpmd_log_debug("Failed to parse /proc/stat, defer update in next snaps= hot."); + + p =3D strtok(NULL, " "); + idx++; + } + + free(tmpline); + free(line); + } + + fclose(filep); + busy_sys =3D calculate_busypct(&proc_stat_cur[sys_idx], &proc_stat_prev[s= ys_idx]); + + busy_cpu =3D 0; + for (i =3D 1; i <=3D get_max_online_cpu(); i++) { + if (!proc_stat_cur[i].valid) + continue; + + val =3D calculate_busypct(&proc_stat_cur[i], &proc_stat_prev[i]); + if (busy_cpu < val) + busy_cpu =3D 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 =3D busy_sys; + lpmd_config->data.util_cpu =3D busy_cpu; + } + + if (lpmd_config->util_gfx_enable) { + parse_gfx_util(); + lpmd_config->data.util_gfx =3D busy_gfx; + } + + return 0; +} diff --git a/tools/power/x86/intel-lpmd/src/lpmd_wlt.c b/tools/power/x86/in= tel-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:0= 0:04.0/workload_hint/notification_delay_ms" +#define WORKLOAD_ENABLE_ATTRIBUTE "/sys/bus/pci/devices/0000:00:04.0/workl= oad_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 =3D read(fd, index_str, sizeof(index_str)); + if (ret <=3D 0) + return WLT_INVALID; + + ret =3D 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 =3D 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 =3D 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 =3D 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 =3D 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)) >=3D EPSILON) ? 1 : 0) +#define A_GTE_B(A, B) ((((A) - (B)) >=3D 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 +#include //clockid_t +#include //perror +#include +#include //bool + +#include "state_common.h" + +/* + * spike burst refers to continuous spikes in a series of back to back sa= mples. + * burt count and strength (as %) are good indicators to segregate random = noise + * (that doesn't deserve performance) from bursty workload needing perform= ance. + * + * 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 =3D total_spike_time * 100/ MAX_TRACKED_SPIKE_TIME + * spike_rate is defined as spike-time % of some MAX_TRACKED_SPIKE_TIME + * + * spike rate avg =3D spike_rate_total / spike_rate_samples + * spike rate avg is used to control how long "1 min" in bc_reset_min appe= ars to the algo. + * + * bc_rest_min is used to control how long to wait before reset of past s= pike 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 ra= te 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 =3D 90.0; +static int once_flag; +static int strike_count; + +/** increment avg spike rate */ +int update_spike_rate_avg(int sr) +{ + spike_rate_total +=3D sr; + spike_rate_samples++; + return 1; +} + +/** reset avg spike rate */ +int clear_spike_rate_avg(void) +{ + spike_rate_samples =3D 0; + spike_rate_total =3D 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 =3D 1.0; + clockid_t clk =3D CLOCK_MONOTONIC; + struct timespec ts; + + if (clock_gettime(clk, &ts)) { + perror("clock_gettime1"); + return -1; + } + + if (spike_sec_prev) { + minutes =3D (float)(ts.tv_sec - spike_sec_prev) / bc_reset_min; + } else { + spike_sec_prev =3D ts.tv_sec; + return 0; + } + + if (real_spike_burst && (get_cur_state() <=3D MDRT4E_MODE)) { + burst_count++; + spike_sec_prev =3D ts.tv_sec; + } else if ((minutes > 1.0) || (burst_count > MAX_BURST_COUNT)) { + burst_count =3D 0; + spike_sec_prev =3D ts.tv_sec; + } + + if (minutes < 1.0) + burst_rate_per_min =3D burst_count; + else if (minutes && (minutes > 1.0)) + burst_rate_per_min =3D (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 >=3D 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() >=3D BURST_COUNT_THRESHOLD) ? 1 : 0; +} + +/* Calculate spike rate */ +int get_spike_rate(void) +{ + int spike_pct =3D 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 +=3D duration; + + /* spike burst has more than 1 spike */ + if (!spike_burst_flag) { + /* rising edge of spike burst */ + spike_burst_flag =3D true; + } else if (state_demote && !once_flag) { + update_burst_count(1); + once_flag =3D 1; + } + + spike_rate =3D 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 -=3D duration; + total_spike_time =3D (total_spike_time < 0) ? 0 : total_spike_time; + + sr =3D get_spike_rate(); + if (!sr && spike_burst_flag) { + /* falling edge of burst */ + spike_burst_flag =3D false; + avg =3D spike_rate_total / spike_rate_samples; + + if (!once_flag) + update_burst_count(1); + + bc_reset_min =3D 60.0 - (int)SPIKE_TIME_BIAS(avg, bc_reset_min); + clear_spike_rate_avg(); + once_flag =3D 0; + } else { + update_burst_count(0); + once_flag =3D 0; + } + return 1; +} + +/* decrement strike count */ +int strikeout_once(int n) +{ + if (!strike_count) + strike_count =3D n; + else + strike_count -=3D 1; + + if (strike_count < 0) + strike_count =3D 0; + + return strike_count; +} diff --git a/tools/power/x86/intel-lpmd/src/wlt_proxy/state_machine.c b/too= ls/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 +#include + +#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 we= ll, + * 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 =3D get_cur_state(); + + update_perf_diffs(&dummy, 0); + max_util =3D (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 incorre= ct decisions) + */ + if (present_state !=3D RESP_MODE) + state_max_avg(); + + int completed_poll =3D get_last_poll(); + float sum_c0 =3D grp.c0_max + grp.c0_2nd_max + grp.c0_3rd_max; + int mdrt_count; + int perf_count, initial_burst_count; + initial_burst_count =3D get_burst_rate_per_min(); + + mdrt_count =3D get_stay_count(MDRT3E_MODE); + int sr =3D 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 =3D get_stay_count(PERF_MODE); + if (!perf_count && !mdrt_count) + set_stay_count(MDRT3E_MODE, staytime_to_staycount(MDRT3E_MODE)); + + state_demote =3D 0; + int ismt =3D !max_mt_detected(INIT_MODE); + + if (only_once =3D=3D 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 =3D 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 =3D 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_H= ALF)) + 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 utiliz= ation + 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 - no= t 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 utiliz= ation + 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 tre= nd 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 utiliz= ation + 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/too= ls/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 + +#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] =3D { + [INIT_MODE] =3D { .name =3D "Avail cpu: P/E/L", + .poll =3D BASE_POLL_MT, + .poll_order =3D ZEROTH }, + [PERF_MODE] =3D { .name =3D "Perf:non-soc cpu", + .poll =3D BASE_POLL_PERF, + .poll_order =3D ZEROTH }, + [MDRT2E_MODE] =3D { .name =3D "Moderate 2E", + .poll =3D BASE_POLL_MDRT2E, + .poll_order =3D LINEAR }, + [MDRT3E_MODE] =3D { .name =3D "Moderate 3E", + .poll =3D BASE_POLL_MDRT3E, + .poll_order =3D LINEAR }, + [MDRT4E_MODE] =3D { .name =3D "Moderate 4E", + .poll =3D BASE_POLL_MDRT4E, + .poll_order =3D LINEAR }, + [RESP_MODE] =3D { .name =3D "Responsive 2L", + .poll =3D BASE_POLL_RESP, + .poll_order =3D CUBIC }, + [NORM_MODE] =3D { .name =3D "Normal LP 2L", + .poll =3D BASE_POLL_NORM, + .poll_order =3D QUADRATIC }, + [DEEP_MODE] =3D { .name =3D "Deep LP 1L", + .poll =3D BASE_POLL_DEEP, + .poll_order =3D CUBIC }, +}; + +static enum state_idx cur_state =3D NORM_MODE; +static int needs_state_reset =3D 1; + +int state_demote; + +static void set_state_reset(void) +{ + needs_state_reset =3D 1; +} + +enum state_idx get_cur_state(void) +{ + return cur_state; +} + +static void set_cur_state(enum state_idx state) +{ + cur_state =3D state; +} + +static int is_state_valid(enum state_idx state) +{ + return ((state >=3D 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 =3D count); +} + +/* return 1 if stay count reaches 0 */ +int do_countdown(enum state_idx state) +{ + state_info[state].stay_count -=3D 1; + + if (state_info[state].stay_count <=3D 0) { + state_info[state].stay_count =3D 0; + return 1; + } + + return 0; +} + +/* get poll value in microsec */ +int get_state_poll(int util, enum state_idx state) +{ + int poll, scale =3D (100 - util); + float scale2; + int order =3D (int)state_info[state].poll_order; + + /* avoiding fpow() overhead */ + switch (order) { + case ZEROTH: + scale2 =3D (float)1; + break; + case LINEAR: + scale2 =3D (float)scale / 100; + break; + case QUADRATIC: + scale2 =3D (float)scale * scale / 10000; + break; + case CUBIC: + scale2 =3D (float)scale * scale * scale / 1000000; + break; + default: + scale2 =3D (float)scale / 100; + break; + } + + poll =3D (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 =3D v; + return 1; +} + +int set_last_poll(int v) +{ + state_info[cur_state].last_poll =3D 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 =3D 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 =3D 1; + + //proxy: apply state change and get poll interval + apply_state_change(); + + if (likely(is_state_valid(to_state))) + next_proxy_poll =3D get_state_poll(max_util, to_state); + + wlt_type =3D get_state_mapping(to_state); + + return 1; +} + +/* return staycount for the state */ +int staytime_to_staycount(enum state_idx state) +{ + int stay_count =3D 0; + + switch (state) { + case MDRT2E_MODE: + case MDRT3E_MODE: + case MDRT4E_MODE: + stay_count =3D (int)MDRT_MODE_STAY / get_poll_ms(MDRT3E_MODE); + break; + case PERF_MODE: + stay_count =3D (int)PERF_MODE_STAY / get_poll_ms(PERF_MODE); + break; + default: + break; + } + return stay_count; +} + +/* cleanup */ +void uninit_state_manager(void) +{ + for (int idx =3D 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 //perf_event_attr +#include //syscall __NR_perf_event_open +#include +#include //uint64_t +#include //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 =3D 1, + E_CORE =3D 2, + L_CORE =3D 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 inco= rrect. + * https://edc.intel.com/content/www/us/en/design/ipla/software-developmen= t-plat + * forms/client/platforms/alder-lake-desktop/682436/007/errata-details/#AD= L026 + * u644diff() implements a workaround. Assuming real diffs less than MAX(u= int32) + */ +#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 =3D (VARI(last_, scope, instance) =3D=3D 0) ? \ + 0 : u64diff(cur_value, VARI(last_, scope, instance)); \ + VARI(last_, scope, instance) =3D 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 =3D get_max_cpus(); + + perf_stats =3D NULL; + perf_stats =3D calloc(max_cpus, sizeof(struct perf_stats_t)); + if (!perf_stats) { + lpmd_log_error("WLT_Proxy: memory failure\n"); + return 0; + } + + for (int t =3D 0; t < max_cpus; t++) { + if (!is_cpu_online(t)) + continue; + + perf_stats[t].cpu =3D t; + + if (is_cpu_pcore(t)) + perf_stats[t].cpu_type =3D P_CORE; + else if (is_cpu_ecore(t)) + perf_stats[t].cpu_type =3D E_CORE; + else + perf_stats[t].cpu_type =3D 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 !=3D 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 =3D calloc(n, sizeof(uint64_t)); + last_mperf =3D calloc(n, sizeof(uint64_t)); + last_pperf =3D calloc(n, sizeof(uint64_t)); + last_tsc =3D 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 =3D -1; + + fdmt =3D open(path, O_RDONLY, 0); + if (fdmt =3D=3D -1) { + lpmd_log_error("Failed to parse perf counter info %s\n", path); + ret =3D -1; + goto cleanup_and_exit; + } + + bytes_read =3D read(fdmt, buf, sizeof(buf) - 1); + if (bytes_read <=3D 0 || bytes_read >=3D (int)sizeof(buf)) { + lpmd_log_error("Failed to parse perf counter info %s\n", path); + ret =3D -1; + goto cleanup_and_exit; + } + + buf[bytes_read] =3D '\0'; + + if (sscanf(buf, parse_format, value_ptr) !=3D 1) { + lpmd_log_error("Failed to parse perf counter info %s\n", path); + ret =3D -1; + goto cleanup_and_exit; + } + + ret =3D 0; + +cleanup_and_exit: + if (fdmt >=3D 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 =3D read_perf_counter_info(path, parse_format, &v); + if (status) + v =3D -1; + + return v; +} + +/* helper - pperf reading */ +static int read_pperf_config(void) +{ + const char *const path =3D "/sys/bus/event_source/devices/msr/events/pper= f"; + const char *const format =3D "event=3D%x"; + + return read_perf_counter_info_n(path, format); +} + +/* helper - pperf reading */ +static unsigned int read_aperf_config(void) +{ + const char *const path =3D "/sys/bus/event_source/devices/msr/events/aper= f"; + const char *const format =3D "event=3D%x"; + + return read_perf_counter_info_n(path, format); +} + +/* helper - pperf reading */ +static unsigned int read_mperf_config(void) +{ + const char *const path =3D "/sys/bus/event_source/devices/msr/events/mper= f"; + const char *const format =3D "event=3D%x"; + + return read_perf_counter_info_n(path, format); +} + +/* helper - pperf reading */ +static unsigned int read_msr_type(void) +{ + const char *const path =3D "/sys/bus/event_source/devices/msr/type"; + const char *const format =3D "%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 con= fig, + int group_fd, __u64 read_format) +{ + struct perf_event_attr attr; + const pid_t pid =3D -1; + const unsigned long flags =3D 0; + + memset(&attr, 0, sizeof(struct perf_event_attr)); + + attr.type =3D type; + attr.size =3D sizeof(struct perf_event_attr); + attr.config =3D config; + attr.disabled =3D 0; + attr.sample_type =3D PERF_SAMPLE_IDENTIFIER; + attr.read_format =3D read_format; + + const int fd =3D 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 =3D read_msr_type(); + const unsigned int aperf_config =3D read_aperf_config(); + const unsigned int mperf_config =3D read_mperf_config(); + const unsigned int pperf_config =3D read_pperf_config(); + + perf_stats[cpu].aperf_fd =3D open_perf_counter(cpu, msr_type, aperf_confi= g, -1, PERF_FORMAT_GROUP); + perf_stats[cpu].mperf_fd =3D open_perf_counter(cpu, msr_type, mperf_confi= g, perf_stats[cpu].aperf_fd, PERF_FORMAT_GROUP); + perf_stats[cpu].pperf_fd =3D open_perf_counter(cpu, msr_type, pperf_confi= g, 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" : "=3Da" (low), "=3Dd"(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 =3D get_amperf_fd(cpu); + + if (fd_amperf =3D=3D -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 =3D rdtsc(); + + const int n =3D read(fd_amperf, &cnt.as_array[0], sizeof(cnt.as_array)); + + if (n !=3D sizeof(cnt.as_array)) + return -2; + + t->aperf =3D cnt.aperf; + t->mperf =3D cnt.mperf; + t->pperf =3D 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 =3D 0, max_2nd_load =3D 0, max_3rd_load =3D 0, next_load = =3D 0; + float min_load =3D 100.0, min_s0 =3D 1.0, next_s0 =3D 1.0; + int t, min_s0_cpu =3D 0, first_pass =3D 1; + struct thread_data tdata; + int maxed_cpu =3D -1; + + for (t =3D 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) !=3D LPMD_SUCCESS) { + lpmd_log_error("read_aperf_mperf_tsc_perf failed for cpu =3D %d\n", t); + continue; + } + perf_stats[t].pperf_diff =3D cpu_get_diff_pperf(tdata.pperf, t); + perf_stats[t].aperf_diff =3D cpu_get_diff_aperf(tdata.aperf, t); + perf_stats[t].mperf_diff =3D cpu_get_diff_mperf(tdata.mperf, t); + perf_stats[t].tsc_diff =3D 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 =3D delta_mperf/delta_tsc, we can rewrite + * as given below. + */ + if (perf_stats[t].tsc_diff) { + next_load =3D (float)100 * perf_stats[t].mperf_diff / + perf_stats[t].tsc_diff; + perf_stats[t].l0 =3D next_load; + } + + if (A_LTE_B(max_load, next_load)) { + max_load =3D next_load; + maxed_cpu =3D perf_stats[t].cpu; + } else if (A_LTE_B(max_2nd_load, next_load)) { + max_2nd_load =3D next_load; + } else if (A_LTE_B(max_3rd_load, next_load)) { + max_3rd_load =3D next_load; + } + /* min scalability */ + if (perf_stats[t].aperf_diff) { + next_s0 =3D (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 =3D (next_s0 >=3D 1) ? (1 - EPSILON) : next_s0; + } + if (A_LTE_B(next_s0, min_s0) || first_pass) { + min_s0 =3D next_s0; + min_s0_cpu =3D perf_stats[t].cpu; + } + + if (A_GT_B(min_load, next_load)) + min_load =3D next_load; + + first_pass =3D 0; + } + + if (stat_init_only) + return 0; + + grp.worst_stall =3D min_s0; + grp.worst_stall_cpu =3D min_s0_cpu; + + grp.c0_max =3D max_load; + grp.c0_2nd_max =3D max_2nd_load; + grp.c0_3rd_max =3D max_3rd_load; + grp.c0_min =3D 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 =3D get_max_cpus(); + + if (perf_stats) { + for (size_t i =3D 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 =3D 0; i < SMA_CPU_COUNT; i++) { + grp.sma_sum[i] =3D -1; + for (int j =3D 0; j < SMA_LENGTH; j++) + sample[i][j] =3D 0; + } + grp.sma_pos =3D -1; +} + +/* Helper avg calculation */ +static int do_sum(int *sam, int len) +{ + int sum =3D 0; + + for (int i =3D 0; i < len; i++) + sum +=3D sam[i]; + return sum; +} + +/* average cpu usage */ +int state_max_avg(void) +{ + grp.sma_pos +=3D 1; + + int v1 =3D (int)round(grp.c0_max * SCALE_DECIMAL); + int v2 =3D (int)round(grp.c0_2nd_max * SCALE_DECIMAL); + int v3 =3D (int)round(grp.c0_3rd_max * SCALE_DECIMAL); + + if (grp.sma_pos =3D=3D SMA_LENGTH) + grp.sma_pos =3D 0; + if (grp.sma_sum[0] =3D=3D -1) { + sample[0][grp.sma_pos] =3D v1; + sample[1][grp.sma_pos] =3D v2; + sample[2][grp.sma_pos] =3D v3; + if (grp.sma_pos =3D=3D SMA_LENGTH - 1) { + grp.sma_sum[0] =3D do_sum(sample[0], SMA_LENGTH); + grp.sma_sum[1] =3D do_sum(sample[1], SMA_LENGTH); + grp.sma_sum[2] =3D do_sum(sample[2], SMA_LENGTH); + } + } else { + grp.sma_sum[0] =3D grp.sma_sum[0] - sample[0][grp.sma_pos] + v1; + grp.sma_sum[1] =3D grp.sma_sum[1] - sample[1][grp.sma_pos] + v2; + grp.sma_sum[2] =3D grp.sma_sum[2] - sample[2][grp.sma_pos] + v3; + sample[0][grp.sma_pos] =3D v1; + sample[1][grp.sma_pos] =3D v2; + sample[2][grp.sma_pos] =3D v3; + } + + grp.sma_avg1 =3D (int)round((double)grp.sma_sum[0] / + (double)(SMA_LENGTH * SCALE_DECIMAL)); + grp.sma_avg2 =3D (int)round((double)grp.sma_sum[1] / + (double)(SMA_LENGTH * SCALE_DECIMAL)); + grp.sma_avg3 =3D (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 =3D 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/p= ower/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 =3D 1000; + +/* wlt_proxy hint - updated at every state change */ +int wlt_type =3D 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 =3D 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)" !=3D "root" ] +then + echo "This script must be run as root" + exit 1 +fi + +opt_no=3D +if [ ! -z "$1" ] +then + opt_no=3D$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=3D0-$max_cpu + sudo systemctl set-property --runtime system.slice AllowedCPUs=3D0-$max_= cpu + ;; + 1) + echo "1 : Terminate" + dbus-send --system --dest=3Dorg.freedesktop.intel_lpmd --print-reply /or= g/freedesktop/intel_lpmd org.freedesktop.intel_lpmd.Terminate + ;; + 2) + echo "2 : LPM force on" + dbus-send --system --dest=3Dorg.freedesktop.intel_lpmd --print-reply /or= g/freedesktop/intel_lpmd org.freedesktop.intel_lpmd.LPM_FORCE_ON + ;; + 3) + echo "3 : LPM force off" + dbus-send --system --dest=3Dorg.freedesktop.intel_lpmd --print-reply /or= g/freedesktop/intel_lpmd org.freedesktop.intel_lpmd.LPM_FORCE_OFF + ;; + 4) + echo "4 : LPM auto" + dbus-send --system --dest=3Dorg.freedesktop.intel_lpmd --print-reply /or= g/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 +#include +#include +#include +#include + +#include +#include + +#include +#include + +#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 =3D NULL; + g_autoptr(GString) command =3D NULL; + g_autoptr(GVariant) result =3D NULL; + GError *error =3D 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 =3D g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!connection) + return FALSE; + + if (!strncmp(argv[1], "STATUS", 6)) { + result =3D 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 =3D g_string_new("LPM_FORCE_ON"); + } else if (!strncmp(argv[1], "OFF", 3)) { + command =3D g_string_new("LPM_FORCE_OFF"); + } else if (!strncmp(argv[1], "AUTO", 4)) { + command =3D 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; +} --=20 2.53.0 From nobody Fri Jun 12 22:34:21 2026 Received: from mail-106118.protonmail.ch (mail-106118.protonmail.ch [79.135.106.118]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CDAAB49552C for ; Tue, 12 May 2026 08:40:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.118 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778575247; cv=none; b=TGU5XmtdybQ29Kstxz7I6WcHCbjyPfLDSmrL8dWaAj7jq4apr1ry0T8WKg1eBtYRAL4lBOYrLJfnFyYj6rh7HhkRG+COrWLVz5fUDzAh1QsOpFXX2CiIL8XZmamb+ar+NFWA38cNbXYOMAx317F/CQeYNmz8qDz3jqKJDYKXZbY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778575247; c=relaxed/simple; bh=5FNpAGUCE+n9PljeCkXsU6XTSJtv8S6GdPImyyDNYug=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Z371kBoMON4zlfuquJAlEhnfJgDQCcb2WKeCnFya6P1wj93tL6T/Fe+sBwsxo04822xVWjkZpypVulWnaiIOrYyqCJIFEwAGvgB3eaQOCcV3LO7XhP7ZH2N0LYcmGnaVLLAkwNqQzJGdLIca31Y6En4yTc5zgf44gYGOsNBB/QY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me; spf=pass smtp.mailfrom=pm.me; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b=m92O9k5Q; arc=none smtp.client-ip=79.135.106.118 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pm.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b="m92O9k5Q" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pm.me; s=protonmail3; t=1778575235; x=1778834435; bh=RHPwAqGvxbrntkywrX7FlkwyjjAJDmjo8jpMTkDe/gI=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=m92O9k5QN49amwrkdZNz2qXXlQBEthBMVOJEQQQLSTkTbYpzUDEIP/TGygPWwgFit 9N7MKQk/ydscEeyVb7cqfb2udh0miypxvv51S4wpx8oL2peYOe5d5yFs6N2gRv2BL6 AH9fyjZnaDnMIT2J0iyMCNowkQ1W0OXabRRdIHkPkIenaW8ixSsw2eoYB7LtFliePe QomOZ0pdxyiBOa+kvfo3uBnNMWIhQtrHaj4JTtLpDDd+9K5HybWS8qLBtEKpHGBseS ygYr5qbxJ7ZVeLfE/NkTFMYtwq5ahvGvGGUWNjtFBukjeAVSPTKNhQ4dwsnffhdaQP kJdZqKi4nkVqQ== Date: Tue, 12 May 2026 08:40:30 +0000 To: srinivas.pandruvada@linux.intel.com, fabio.m.de.francesco@linux.intel.com, rafael@kernel.org From: Maciej Wieczor-Retman Cc: noor.u.mubeen@intel.com, vinod.gomathinayagam@intel.com, deepak.sundar@intel.com, mohsin.shariff@intel.com, kloczek@github.com, xiaqinxin@huawei.com, jack@jackkelly.name, rui.zhang@intel.com, baohua@kernel.org, colin.i.king@gmail.com, libin3479@gmail.com, m.szyprowski@samsung.com, ali.erdinc.koroglu@intel.com, admin@ptr1337.dev, maciej.wieczor-retman@intel.com, venkatesh.j@intel.com, ThatQubicFox@protonmail.com, trenn@suse.de, jengelh@inai.de, robert.dower@intel.com, hpa@redhat.com, platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, m.wieczorretman@pm.me Subject: [PATCH v1 2/3] tools/LPMD: Add Makefile Message-ID: In-Reply-To: References: Feedback-ID: 164464600:user:proton X-Pm-Message-ID: eedfbdf3dc77cef6c7caa608b5e54ada0521e99d Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Fabio M. De Francesco The Makefile builds both intel_lpmd and intel_lpmd_control, generates config.h and lpmd-resource.c at build time, and installs binaries, systemd unit, D-Bus policy, and XML configuration files. The build warns and fails if the dependencies listed in tools/power/x86/intel-lpmd/README.md are not installed. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Fabio M. De Francesco Tested-by: Maciej Wieczor-Retman Signed-off-by: Maciej Wieczor-Retman --- tools/power/x86/intel-lpmd/Makefile | 155 ++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tools/power/x86/intel-lpmd/Makefile diff --git a/tools/power/x86/intel-lpmd/Makefile b/tools/power/x86/intel-lp= md/Makefile new file mode 100644 index 000000000000..de7c744ce656 --- /dev/null +++ b/tools/power/x86/intel-lpmd/Makefile @@ -0,0 +1,155 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../../../scripts/Makefile.include + +# Install paths (matching automake defaults) +prefix ?=3D /usr/local +sbindir ?=3D $(prefix)/sbin +confdir ?=3D $(prefix)/etc/intel_lpmd +rundir ?=3D $(prefix)/var/run/intel_lpmd +systemd_unitdir ?=3D /usr/lib/systemd/system +dbus_sysdir ?=3D /etc/dbus-1/system.d + +ifeq ($(srctree),) +srctree :=3D $(patsubst %/,%,$(dir $(CURDIR))) +srctree :=3D $(patsubst %/,%,$(dir $(srctree))) +srctree :=3D $(patsubst %/,%,$(dir $(srctree))) +srctree :=3D $(patsubst %/,%,$(dir $(srctree))) +endif + +# Do not use make's built-in rules +MAKEFLAGS +=3D -r + +VERSION =3D 0.1.0 + +# pkg-config dependencies +ifeq ($(shell pkg-config --exists glib-2.0 gio-2.0 gio-unix-2.0 gmodule-2.= 0 && echo yes),yes) + GLIB_CFLAGS :=3D $(shell pkg-config --cflags glib-2.0 gio-2.0 gio-unix-2= .0 gmodule-2.0) + GLIB_LIBS :=3D $(shell pkg-config --libs glib-2.0 gio-2.0 gio-unix-2= .0 gmodule-2.0) +else + $(error No glib-2.0 pkg-config found, please install libglib2.0-dev/glib= 2-devel) +endif + +ifeq ($(shell pkg-config --exists libxml-2.0 && echo yes),yes) + XML2_CFLAGS :=3D $(shell pkg-config --cflags libxml-2.0) + XML2_LIBS :=3D $(shell pkg-config --libs libxml-2.0) +else + $(error No libxml-2.0 pkg-config found, please install libxml2-dev/libxm= l2-devel) +endif + +ifeq ($(shell pkg-config --exists upower-glib && echo yes),yes) + UPOWER_CFLAGS :=3D $(shell pkg-config --cflags upower-glib) + UPOWER_LIBS :=3D $(shell pkg-config --libs upower-glib) +else + $(error No upower-glib pkg-config found, please install libupower-glib-d= ev/upower-devel) +endif + +ifeq ($(shell pkg-config --exists libnl-3.0 libnl-genl-3.0 && echo yes),ye= s) + NL3_CFLAGS :=3D $(shell pkg-config --cflags libnl-3.0 libnl-genl-3.0) + NL3_LIBS :=3D $(shell pkg-config --libs libnl-3.0 libnl-genl-3.0) +else + $(error No libnl-3.0 pkg-config found, please install libnl-3-dev libnl-= genl-3-dev/libnl3-devel) +endif + +ifeq ($(shell pkg-config --exists libsystemd && echo yes),yes) + SYSTEMD_CFLAGS :=3D $(shell pkg-config --cflags libsystemd) + SYSTEMD_LIBS :=3D $(shell pkg-config --libs libsystemd) +else + $(error No libsystemd pkg-config found, please install libsystemd-dev/sy= stemd-devel) +endif + +override CFLAGS +=3D -O2 -Wall -g -DGDBUS=3D1 \ + -DGLIB_SUPPORT \ + -DGETTEXT_PACKAGE=3D\"intel_lpmd\" \ + -DPACKAGE_VERSION=3D\"$(VERSION)\" \ + -DTDCONFDIR=3D\"$(confdir)\" \ + -DTDRUNDIR=3D\"$(rundir)\" \ + -DTDLOCALEDIR=3D\"$(prefix)/share/locale\" \ + -I. -Isrc/include -Isrc/wlt_proxy/include \ + $(GLIB_CFLAGS) $(XML2_CFLAGS) $(UPOWER_CFLAGS) $(NL3_CFLAGS) $(SYSTEMD_CF= LAGS) + +override LDFLAGS +=3D \ + $(GLIB_LIBS) $(XML2_LIBS) $(UPOWER_LIBS) $(NL3_LIBS) $(SYSTEMD_LIBS) -lm + +# Source files +LPMD_SRCS =3D \ + src/lpmd_cgroup.c \ + src/lpmd_config.c \ + src/lpmd_cpu.c \ + src/lpmd_cpumask.c \ + src/lpmd_dbus_server.c \ + src/lpmd_helpers.c \ + src/lpmd_hfi.c \ + src/lpmd_irq.c \ + src/lpmd_main.c \ + src/lpmd_misc.c \ + src/lpmd_proc.c \ + src/lpmd_socket.c \ + src/lpmd_state_machine.c \ + src/lpmd_uevent.c \ + src/lpmd_util.c \ + src/lpmd_wlt.c \ + src/wlt_proxy/spike_mgmt.c \ + src/wlt_proxy/state_machine.c \ + src/wlt_proxy/state_manager.c \ + src/wlt_proxy/state_util.c \ + src/wlt_proxy/wlt_proxy.c + +LPMD_OBJS =3D $(LPMD_SRCS:.c=3D.o) lpmd-resource.o +CTRL_OBJS =3D tools/intel_lpmd_control.o + +ALL_TARGETS :=3D intel_lpmd intel_lpmd_control +ALL_PROGRAMS :=3D $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +all: $(ALL_PROGRAMS) + +# Generated files +config.h: FORCE + $(QUIET_GEN)echo '/* Auto-generated - do not edit */' > $@ && \ + echo '#define GDBUS 1' >> $@ && \ + echo '#define GETTEXT_PACKAGE "intel_lpmd"' >> $@ && \ + echo '#define PACKAGE_VERSION "$(VERSION)"' >> $@ + +lpmd-resource.c: lpmd-resource.gresource.xml src/intel_lpmd_dbus_interface= .xml + $(QUIET_GEN)glib-compile-resources --sourcedir=3D. --generate-source \ + --target=3D$@ $< + +prepare: config.h lpmd-resource.c + +%.o: %.c | prepare + $(Q)mkdir -p $(dir $@) + $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $< + +$(OUTPUT)intel_lpmd: $(LPMD_OBJS) + $(QUIET_LINK)$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ + +$(OUTPUT)intel_lpmd_control: $(CTRL_OBJS) + $(QUIET_LINK)$(CC) $(CFLAGS) $< $(GLIB_LIBS) -o $@ + +DATA_CONFIGS =3D \ + data/intel_lpmd_config.xml \ + data/intel_lpmd_config_examples.xml \ + data/intel_lpmd_config_experimental.xml \ + data/intel_lpmd_config_F6_M170.xml \ + data/intel_lpmd_config_F6_M189.xml \ + data/intel_lpmd_config_F6_M204.xml + +install: $(ALL_PROGRAMS) + install -d -m 755 $(DESTDIR)$(sbindir) + install $(OUTPUT)intel_lpmd $(DESTDIR)$(sbindir) + install $(OUTPUT)intel_lpmd_control $(DESTDIR)$(sbindir) + install -d -m 755 $(DESTDIR)$(confdir) + install -m 644 $(DATA_CONFIGS) $(DESTDIR)$(confdir) + install -d -m 755 $(DESTDIR)$(rundir) + install -d -m 755 $(DESTDIR)$(systemd_unitdir) + sed 's|@sbindir@|$(sbindir)|g' data/intel_lpmd.service.in > \ + $(DESTDIR)$(systemd_unitdir)/intel_lpmd.service + install -d -m 755 $(DESTDIR)$(dbus_sysdir) + install -m 644 data/org.freedesktop.intel_lpmd.conf $(DESTDIR)$(dbus_sysd= ir) + +clean: + rm -f $(ALL_PROGRAMS) config.h lpmd-resource.c + find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete + +FORCE: + +.PHONY: all install clean FORCE prepare --=20 2.53.0 From nobody Fri Jun 12 22:34:21 2026 Received: from mail-106121.protonmail.ch (mail-106121.protonmail.ch [79.135.106.121]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 95A18368D71 for ; Tue, 12 May 2026 08:40:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=79.135.106.121 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778575258; cv=none; b=NcufB5rezeZeKrceEwJYc6uOz8KQ/MNe8hIrqs8nluIxQJdCUB2HnAIXNuTMsceZ6DpuBQM/0PvccxypmKiV4mYsvDBQkJ94/zC59FU0YEewfDRY6bl5RvAewUZGWcBqMIlo9UN19pZmwDpI1ddqgReEuprMJgUOE0zOESFiTPo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778575258; c=relaxed/simple; bh=Hav2E2bid6gjY1+YPA2luxbaDGNu1MqlX+OUV/ty2cw=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=GZ/UQVb3dbWIYBHeeN1lX7gpgaOLt5Ipza0lXP9fDIwJG94fOmT8DFctJl6QUGmHrfut/YB2B/kF2M8V83u11Hg2/ggC3wt3LfSKp6pMdpLGiKC2z0AgTI2m0D7Nc4M4lL+e269bUGiNLRtUl9oq33mz/crsz1H7uxa6IfAQpHo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me; spf=pass smtp.mailfrom=pm.me; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b=RKgq1t3M; arc=none smtp.client-ip=79.135.106.121 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pm.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b="RKgq1t3M" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pm.me; s=protonmail3; t=1778575244; x=1778834444; bh=4gLCcbBFiFpZ4Zatky/DyP5thont4W1FOaP1QH6DD94=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=RKgq1t3MVRX6NSC/kI6AlPqhgQ/GAOQkgFyvjCYcRl/9bLDq9ifeJz/vv2jSz89/L q/YZtjxUMGXu/8IJJ1Qi+SSQ8V5nn87zUrzX5w6JY0YnsiZbyKsJ4lxas714uUzEYF tLKbGhG+/t/FTJ0tlXzyDodJr8fDrWmmqu3sgc5L78DuxcPSyn1E+xzwb3MUbKBhuk xoYmoIMTJV8QqI9DrGIkWrAkWD+p8rFi0dYG8kXePX3Co84MgPYosffHp6CtxW4ywK 4HQctJbImPxADwxS/F4xwRFfetkY3ouYELULP1rth1EAmBEejz3CGLYoV6Gr+dEllq YqUVJPg4/aX0w== Date: Tue, 12 May 2026 08:40:37 +0000 To: srinivas.pandruvada@linux.intel.com, fabio.m.de.francesco@linux.intel.com, rafael@kernel.org From: Maciej Wieczor-Retman Cc: noor.u.mubeen@intel.com, vinod.gomathinayagam@intel.com, deepak.sundar@intel.com, mohsin.shariff@intel.com, kloczek@github.com, xiaqinxin@huawei.com, jack@jackkelly.name, rui.zhang@intel.com, baohua@kernel.org, colin.i.king@gmail.com, libin3479@gmail.com, m.szyprowski@samsung.com, ali.erdinc.koroglu@intel.com, admin@ptr1337.dev, maciej.wieczor-retman@intel.com, venkatesh.j@intel.com, ThatQubicFox@protonmail.com, trenn@suse.de, jengelh@inai.de, robert.dower@intel.com, hpa@redhat.com, platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, m.wieczorretman@pm.me Subject: [PATCH v1 3/3] tools: Add LPMD entry to tools Makefile Message-ID: In-Reply-To: References: Feedback-ID: 164464600:user:proton X-Pm-Message-ID: 7a9f53bf89819dd5634147b7f9e5311481cf77f8 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Fabio M. De Francesco Make LPMD buildable from the tools/power/x86/intel-lpmd directory. Signed-off-by: Fabio M. De Francesco Tested-by: Maciej Wieczor-Retman Signed-off-by: Maciej Wieczor-Retman --- tools/Makefile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/Makefile b/tools/Makefile index cb40961a740f..fe4a31a9075e 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -21,6 +21,7 @@ help: @echo ' gpio - GPIO tools' @echo ' hv - tools used when in Hyper-V clients' @echo ' iio - IIO tools' + @echo ' intel-lpmd - Intel Low Power Mode Daemon tool' @echo ' intel-speed-select - Intel Speed Select tool' @echo ' kvm_stat - top-like utility for displaying kvm sta= tistics' @echo ' leds - LEDs tools' @@ -102,7 +103,7 @@ selftests: FORCE thermal: FORCE $(call descend,lib/$@) =20 -turbostat x86_energy_perf_policy intel-speed-select: FORCE +turbostat x86_energy_perf_policy intel-lpmd intel-speed-select: FORCE $(call descend,power/x86/$@) =20 tmon: FORCE @@ -144,7 +145,7 @@ selftests_install: thermal_install: $(call descend,lib/$(@:_install=3D),install) =20 -turbostat_install x86_energy_perf_policy_install intel-speed-select_instal= l: +turbostat_install x86_energy_perf_policy_install intel-lpmd_install intel-= speed-select_install: $(call descend,power/x86/$(@:_install=3D),install) =20 tmon_install: @@ -170,7 +171,7 @@ install: acpi_install counter_install cpupower_install = dma_install gpio_install perf_install selftests_install turbostat_install usb_install \ virtio_install mm_install bpf_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install \ - wmi_install debugging_install intel-speed-select_install \ + wmi_install debugging_install intel-lpmd_install intel-speed-select_inst= all \ tracing_install thermometer_install thermal-engine_install ynl_install =20 acpi_clean: @@ -204,7 +205,7 @@ selftests_clean: thermal_clean: $(call descend,lib/thermal,clean) =20 -turbostat_clean x86_energy_perf_policy_clean intel-speed-select_clean: +turbostat_clean x86_energy_perf_policy_clean intel-lpmd_clean intel-speed-= select_clean: $(call descend,power/x86/$(@:_clean=3D),clean) =20 thermometer_clean: @@ -230,7 +231,7 @@ clean: acpi_clean counter_clean cpupower_clean dma_clea= n hv_clean firewire_clean mm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean \ gpio_clean objtool_clean leds_clean wmi_clean firmware_clean debugging_c= lean \ - intel-speed-select_clean tracing_clean thermal_clean thermometer_clean t= hermal-engine_clean \ - sched_ext_clean ynl_clean + intel-lpmd_clean intel-speed-select_clean tracing_clean thermal_clean \ + thermometer_clean thermal-engine_clean sched_ext_clean ynl_clean =20 .PHONY: FORCE --=20 2.53.0