pax_global_header00006660000000000000000000000064152130171530014510gustar00rootroot0000000000000052 comment=9996a1a1cdd01d32cd411459368a177f463d0047 thermal_daemon-2.5.12/000077500000000000000000000000001521301715300145565ustar00rootroot00000000000000thermal_daemon-2.5.12/.clang-tidy000066400000000000000000000017151521301715300166160ustar00rootroot00000000000000Checks: ' bugprone-*, -bugprone-assignment-in-if-condition, -bugprone-branch-clone, -bugprone-casting-through-void, -bugprone-derived-method-shadowing-base-method, -bugprone-easily-swappable-parameters, -bugprone-implicit-widening-of-multiplication-result, -bugprone-invalid-enum-default-initialization, -bugprone-macro-parentheses, -bugprone-multi-level-implicit-pointer-conversion, -bugprone-narrowing-conversions, -bugprone-reserved-identifier, -bugprone-throwing-static-initialization, -bugprone-unchecked-string-to-number-conversion, clang-analyzer-*, -clang-analyzer-optin.core.EnumCastOutOfRange, -clang-analyzer-optin.cplusplus.VirtualCall, -clang-analyzer-security.ArrayBound, -clang-diagnostic-missing-field-initializers, modernize-use-emplace, modernize-use-nullptr, modernize-use-override, performance-*, -performance-no-int-to-ptr, readability-string-compare' thermal_daemon-2.5.12/.github/000077500000000000000000000000001521301715300161165ustar00rootroot00000000000000thermal_daemon-2.5.12/.github/workflows/000077500000000000000000000000001521301715300201535ustar00rootroot00000000000000thermal_daemon-2.5.12/.github/workflows/ci.yml000066400000000000000000000012341521301715300212710ustar00rootroot00000000000000name: thermald CI on: push: pull_request: permissions: read-all jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y \ autoconf \ autoconf-archive \ automake \ clang \ clang-tidy \ g++ \ gtk-doc-tools \ libevdev-dev \ libglib2.0-dev \ libupower-glib-dev \ libxml2-dev - name: Build run: | ./autogen.sh make -j$(nproc) make clang-tidy thermal_daemon-2.5.12/.gitignore000066400000000000000000000006711521301715300165520ustar00rootroot00000000000000*~ .*.swp *.o *.lo *.la *.bz2 *.gir *.typelib *.stamp *-enum-types.[ch] Makefile Makefile.in* configure compile config.* aclocal.m4 depcomp install-sh libtool ltmain.sh missing mkinstalldirs POTFILES stamp-* .deps .libs autom4te.cache intltool-* gtk-doc.make TAGS INSTALL ABOUT-NLS *.pc cscope.*out thermald thd_dbus_interface.h .cproject .project *thermald.service .dirstamp version.xml .qmake.stash moc_*.cpp moc_*.h ui_*.h ThermalMonitor thermal_daemon-2.5.12/Android.mk000066400000000000000000000040771521301715300164770ustar00rootroot00000000000000LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := \ src/android_main.cpp \ src/thd_engine.cpp \ src/thd_cdev.cpp \ src/thd_cdev_therm_sys_fs.cpp \ src/thd_engine_default.cpp \ src/thd_sys_fs.cpp \ src/thd_trip_point.cpp \ src/thd_zone.cpp \ src/thd_zone_cpu.cpp \ src/thd_zone_therm_sys_fs.cpp \ src/thd_zone_dynamic.cpp \ src/thd_preference.cpp \ src/thd_parse.cpp \ src/thd_sensor.cpp \ src/thd_sensor_virtual.cpp \ src/thd_kobj_uevent.cpp \ src/thd_cdev_order_parser.cpp \ src/thd_cdev_gen_sysfs.cpp \ src/thd_pid.cpp \ src/thd_zone_generic.cpp \ src/thd_cdev_cpufreq.cpp \ src/thd_cdev_rapl.cpp \ src/thd_cdev_intel_pstate_driver.cpp \ src/thd_rapl_power_meter.cpp \ src/thd_trt_art_reader.cpp \ src/thd_cdev_rapl_dram.cpp \ src/thd_cpu_default_binding.cpp \ src/thd_cdev_backlight.cpp \ src/thd_int3400.cpp \ src/thd_cdev_kbl_amdgpu.cpp \ src/thd_sensor_kbl_amdgpu_power.cpp \ src/thd_sensor_kbl_amdgpu_thermal.cpp \ src/thd_zone_kbl_g_mcp.cpp \ src/thd_sensor_kbl_g_mcp.cpp \ src/thd_zone_kbl_amdgpu.cpp \ src/thd_sensor_rapl_power.cpp \ src/thd_zone_rapl_power.cpp \ src/thd_engine_adaptive.cpp \ src/thd_lzma_dec.cpp \ src/LzmaDec.c \ src/thd_gddv.cpp \ src/thd_platform.cpp \ src/thd_platform_intel.cpp \ src/thd_platform_arm.cpp \ src/thd_util.cpp \ src/thd_cdev_rapl_restore.cpp \ src/thd_features_parse.cpp LOCAL_C_INCLUDES += external/libxml2/include LOCAL_PROPRIETARY_MODULE := true LOCAL_CFLAGS := \ -DTDRUNDIR='"/data/vendor/thermal-daemon"' \ -DTDCONFDIR='"/system/vendor/etc/thermal-daemon"' \ -Wno-unused-parameter \ -fexceptions\ -Wall \ -Werror \ ifneq (,$(filter eng userdebug, $(TARGET_BUILD_VARIANT))) LOCAL_CFLAGS += -DLOG_DEBUG_INFO=1 endif LOCAL_STATIC_LIBRARIES := libxml2 ifeq ($(BOARD_VNDK_VERSION),current) LOCAL_SHARED_LIBRARIES := liblog libcutils libdl libc++ libutils else LOCAL_SHARED_LIBRARIES := liblog libcutils libdl libc++ libicuuc libutils endif LOCAL_PRELINK_MODULE := false LOCAL_MODULE := thermal-daemon include $(BUILD_EXECUTABLE) thermal_daemon-2.5.12/COPYING000066400000000000000000000431001521301715300156070ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. thermal_daemon-2.5.12/Makefile.am000066400000000000000000000044361521301715300166210ustar00rootroot00000000000000include $(GLIB_MAKEFILE) SUBDIRS = . docs data ACLOCAL_AMFLAGS = # Global C Flags AM_CFLAGS = ${GLIB_CFLAGS} AM_CXXFLAGS = \ $(GLIB_CFLAGS) \ $(XML_CFLAGS) \ $(UPOWER_CFLAGS) \ $(EVDEV_CFLAGS) \ -DTDRUNDIR=\"$(tdrundir)\" \ -DTDCONFDIR=\"$(tdconfdir)\" \ $(CXXFLAGS) \ -I src EXTRA_DIST=Makefile.glib \ thermald.pc.in # Programs to build sbin_PROGRAMS = thermald # Evaluate Table Application thermald_CPPFLAGS = \ -I@top_srcdir@/src \ -DTDLOCALEDIR=\"$(datadir)/locale\" \ -DGLIB_SUPPORT thermald_includedir = @top_srcdir@ thermald_LDADD = \ $(GLIB_LIBS) \ $(LIBNL_LIBS) \ $(LIBM) \ $(LIBDL) \ $(XML_LIBS) \ $(UPOWER_LIBS) \ $(EVDEV_LIBS) thermald_SOURCES = \ src/main.cpp \ src/thd_dbus_interface.cpp \ src/thd_engine.cpp \ src/thd_cdev.cpp \ src/thd_cdev_therm_sys_fs.cpp \ src/thd_engine_default.cpp \ src/thd_engine_adaptive.cpp \ src/thd_sys_fs.cpp \ src/thd_trip_point.cpp \ src/thd_zone.cpp \ src/thd_zone_cpu.cpp \ src/thd_zone_therm_sys_fs.cpp \ src/thd_zone_dynamic.cpp \ src/thd_preference.cpp \ src/thd_parse.cpp \ src/thd_sensor.cpp \ src/thd_sensor_virtual.cpp \ src/thd_kobj_uevent.cpp \ src/thd_cdev_order_parser.cpp \ src/thd_cdev_gen_sysfs.cpp \ src/thd_pid.cpp \ src/thd_zone_generic.cpp \ src/thd_cdev_cpufreq.cpp \ src/thd_cdev_rapl.cpp \ src/thd_cdev_rapl_restore.cpp \ src/thd_cdev_intel_pstate_driver.cpp \ src/thd_rapl_power_meter.cpp \ src/thd_trt_art_reader.cpp \ src/thd_cdev_rapl_dram.cpp \ src/thd_cdev_backlight.cpp \ src/thd_int3400.cpp \ src/thd_sensor_rapl_power.cpp \ src/thd_zone_rapl_power.cpp \ src/thd_gddv.cpp \ thermald-resource.c \ src/thd_lzma_dec.cpp \ src/LzmaDec.c \ src/thd_platform.cpp \ src/thd_platform_intel.cpp \ src/thd_platform_arm.cpp \ src/thd_util.cpp \ src/thd_features_parse.cpp man5_MANS = man/thermal-conf.xml.5 man8_MANS = man/thermald.8 thermald-resource.c: $(top_srcdir)/thermald-resource.gresource.xml $(AM_V_GEN) glib-compile-resources --generate-source --sourcedir=${top_srcdir} $< CLEANFILES = $(BUILT_SOURCES) clang-tidy: clang-tidy -extra-arg-before=-xc++ -header-filter= \ $(filter-out src/7zTypes.h src/LzmaDec.h,$(wildcard src/*.h)) \ $(filter-out src/LzmaDec.c thermald-resource.c,$(thermald_SOURCES)) \ -- $(filter-out -Wclobbered,$(AM_CXXFLAGS)) \ $(thermald_CPPFLAGS) -I . thermal_daemon-2.5.12/README.txt000066400000000000000000000400001521301715300162460ustar00rootroot00000000000000Use man pages to check command line arguments in configuration: man thermald man thermal-conf.xml Prerequisites: Kernel Prefers kernel with Intel RAPL power capping driver : Available from Linux kernel 3.13.rc1 Intel P State driver (Available in Linux kernel stable release) Intel Power clamp driver (Available in Linux kernel stable release) Intel INT340X drivers Intel RAPL-mmio power capping driver: Available from 5.3-rc1 Building and executing on Fedora 1. Install yum install automake yum install autoconf-archive yum install gcc yum install gcc-c++ yum install glib-devel yum install libxml2-devel yum install gtk-doc yum install upower-devel yum install libevdev-devel Replace yum with dnf for later Fedora versions. 2 Build ./autogen.sh prefix=/ make sudo make install The prefix value depends on the distribution version. This can be "/" or "/usr". So please check existing path of thermald install, if present to update and add appropriate prefix. 3 - start service sudo systemctl start thermald.service - Get status sudo systemctl status thermald.service - Stop service sudo systemctl stop thermald.service - To enable service at boot sudo systemctl enable thermald.service 4. Terminate using DBUS I/F sudo test/test_pref.sh and select "TERMINATE" choice. Building on Ubuntu 1. Install sudo apt install autoconf sudo apt install autoconf-archive sudo apt install g++ sudo apt install libglib2.0-dev sudo apt install libxml2-dev sudo apt install gtk-doc-tools sudo apt install libupower-glib-dev sudo apt install libevdev-dev 2 Build ./autogen.sh prefix=/ make sudo make install (It will give error for systemd configuration, but ignore) 3. If using systemd, use - start service sudo systemctl start thermald.service - Get status sudo systemctl status thermald.service - Stop service sudo systemctl stop thermald.service Building and executing on openSUSE 1. Install zypper in automake zypper in gcc zypper in gcc-c++ zypper in glib2-devel zypper in libxml2-devel zypper in automake autoconf-archive zypper in gtk-doc zypper in libupower-glib-devel zypper in libevdev-devel For build, follow the same procedure as Fedora. ------------------------------------------- Releases Release 2.5.12 - Platform and feature expansion: added ARM backend support, Intel platform refactoring, new CPU IDs including NovaLake variants, generic_os/data-vault path enhancements, and feature enable/disable via config. - Maintainability and tooling: significant modernization/cleanup (mutex to C++11, nullptr/constexpr, safer string helpers, clang-tidy/coverity fixes), plus CI and test/script cleanup and removal of deprecated dptfxtract references. - Data-vault and parser improvements: optimized config/data-vault read paths, sanitized firmware-sourced vault data, added structural checks for malformed tables, and improved parser reliability. - Adaptive/power control updates: adaptive-mode behavior was tightened (fail/exit paths, ignore-default-control handling), platform gating was improved (adaptive-only for select new CPUs), and RAPL handling improved with register store/restore on exit plus safer power-limit logic. - Security hardening: tightened D-Bus input validation and error handling, sanitized zone names/paths, restricted writes to sys, expanded O_NOFOLLOW usage, removed mem MMIO workaround, and added stronger XML/config file validation through a shared open_validated_xml_file() path. - Memory-safety and robustness: added broad range/bounds checks (sysfs, cpufreq, XML, APCT/IDSP/data vault), max limits for zones/cooling devices/segments/conditions, fixed lock/unlock and stale mutex issues, guarded container operations, and improved exception safety (e.g., unique_ptr use for virtual sensor links). Release 2.5.11 - Clang-tidy fixes - Wildcat Lake support - CVE related to symbolic links exploitations - dbus interface fixes and remove power group after Thermal monitor support is deprecated Release 2.5.10 - For new ITMT string version 3 support - There was issue in updating path for DPTF files for Panther Lake - Minor print format for time_t value - Don't reset clamping bit for RAPL for MSR rapl zones when disabled - Add confitional power floor processing Release 2.5.9 - Panther Lake support - Intel P-state in passive mode support Release 2.5.8 - Lunar Lake support - Arrow Lake support - Remove coverity errors to minimum - Add a script to plot temperature and trips from debug log - Deprecate modem/KBL-G support - Remove dbus-glib-devel as requirment Release 2.5.7 - Remove dependency on lzma libs - Fix remaining issues with GDBUS transition - Seg fault when no config file for the first time Release 2.5.6 - Fix crash with GDBus port Release 2.5.5 - Use GDBus for dbus - Handle single trip to solve thermal shutdown issue for an Alder Lake desktop Release 2.5.4 - Android support - Workarounds for missing conditions/tables Release 2.5.3 - Support Meteor Lake Release 2.5.2 - Support Alder Lake N - Support ITMT version 2, which is used in some Raptor Lake systems Release 2.5.1 - Static analysis fixes - Missing init, which causes skipping of conditions in a Dell system Release 2.5 - Support of new thermal table for Alder Lake - Add Raptor Lake in the list Release 2.4.9 - Fix performance issues for Dell Latitude 5421 - Fix performance issues for Dell Latitude 7320/7420 (Depend on kernel patch "thermal: int340x: Update OS policy capability handshake") - Adaptive improvements from Benzea - Thermal Monitor fixes and cosmetic updates - Documentation updates from Colin King - Static analysis fixes from Benzea - Fix test for compressed data vaults Release 2.4.8 -Fix Ideapad thermal shutdown issue #328 Release 2.4.7 - Fix AC/DC power limit issue in some HP TigerLake systems - Regression fix for RAPL MSR usage in xml config file - Added Japer Lake and Alder Lake CPU models - Debug scripts for log collection to upload Release 2.4.6 - Fix for Ubuntu bug 1930422 Release 2.4.5 - Address low performance with Dell Latitude 5420 with the latest BIOS Release 2.4.4 - Address low performance with Dell Latitude 5420 Release 2.4.3 - Allow --ingore-cpuid-check to use with --adaptive option Release 2.4.2 - Issue with Dell Latitude 7400. Fix for issue #291 Release 2.4.1 - Minor change for Dell XPS 13 with Tiger Lake. Release 2.4 - Support for Rocket Lake and Commet Lake CPU model - Tiger Lake DPTF tables support - CPU stuck at low frequency on two models (issue 280) - Changes related to PID and exit codes Release 2.3 - Merged changes from mjg59 for adaptive - Requires Linux kernel version 5.8 or later - By default tries --adaptive and fallback to old style - At least some level of success to use adaptive option on: (not expected to be on par with Windows DPTF) Dell XPS 13 9360 Dell XPS 13 9370 Dell XPS 13 9380 Dell XPS 13 7390 2-in-1 Dell Insperion_7386 HP Spectre x360 Convertible 15-ch0xx HP ZBook 15 G5 Lenovo Thinkpad T480 - thermald will not run on Lenovo platforms with lap mode sysfs entry Release 2.2 - Ignore PPCC power limits when max and min power is same - Regression in cpufreq cooling device causing min state to get stuck Release 2.1 - Workaround for invalid PPCC power limits - Reduce polling for power when PPCC is not present Release 2.0 - Tiger Lake support - PL1 limit setting from PPCC as is - Optimize multi step, multi zone control - Add new tag for product identification "product_sku" Release 1.9.1 - Remove default CPU temperature compensation for auto generated configuration from dptfxtract - Minor Android build warnings Release 1.9 - The major change in this version is the active power limits adjustment. This will be useful to improve performance on some newer platform. But this will lead to increase in CPU and other temperatures. Hence this is important to run dptfxtract version 1.4.1 tool to get performance sensitive thermal limits (https://github.com/intel/dptfxtract/commits/v1.4.1). If the default configuration picked up by thermald is not optimal, user can select other less aggressive configuration. Refer to the README here https://github.com/intel/dptfxtract/blob/master/README.txt This power limit adjustment depends on some kernel changes released with kernel version v5.3-rc1. For older kernel release run thermald with --workaround-enabled But this will depend on /dev/mem access, which means that platforms with secure boot must update to newer kernels. - TCC offset limits As reported in some forums that some platforms have issue with high TCC offset settings. Under some special condition this offset is adjusted, but that currently needs msr module loaded to get MSR access from user space. I have submitted a patch to have this exported via sysfs for v5.4+ kernel. - To disable all the above performance optimization, use --disable-active-power. Since Linux Thermal Daemon implementation doesn't have capability to match Intel® Dynamic Platform and Thermal Framework (DPTF) implementation on other Operating systems, this option is very important if the user is experiencing thermal issues. If there is some OEM/manufactures have issue with this implementation, please get back to me for blacklist of platforms. - Added support for Ice Lake platform - ThermalMonitor Cleaned up the plots, so that only active sensors and trips gets plotted. Release 1.8 - Support of KBL-G with discrete GPU - Fast removal of any cooling action which was applied once temperature is normal - Android support - Add Hot trip point, which when reached just calls "suspend" - Adding new tag "DependsOn" which enable/disable trip based on some other trip - Polling interval can be configured via thermal xml config - Per trip PID control - Simplify RAPL cooling device Release 1.7.2 - Workwround for platform with invalid thermal table - Error printing for RAPL constraint sysfs read on failure - thermal-conf.xml.auto can be read from /etc/thermald, which allows user to modify generated thermal-conf.xml from /var/run/thermald and copy to /etc/thermald Release 1.7.1 - Removed dptfxtract binary as there is an issue in packaging this with GPL source for distributions Release 1.7 - Add GeminiLake - Add dptfxtract tool, which converts DPTF tables to thermald tables using best effort - Changes to accommodate dptfxtract tool conversions - Better facility to configure fan controls - PID control optimization - Fix powerlimit write errors because of bad FW settings of power limits - More restrictive compile options and warnings as errors - Improve logging - Android build fixes Release 1.6 - Add Kabylake and missing Broadwell CPU model - Removed deprecated modules - Added passive trip between critical and max, to allow fan to take control first - Fixed clash when multiple zones and trips controlling same cdev 1.5.4 - Use Processor thermal device in lieu of CPU zone when present - Haswell/Skylake PCH sensor - Fix regression in LCD/Backlight path Release 1.5.3 - PCH sensor support Release 1.5.2 - Security bug for bios lock fix Release 1.5.1 - Regression fix for the default config file location Release 1.5 - Default warning level increase so that doesn't print much in logs - Add new feature to set specific target state on reaching a threshold, this allows multiple thresholds (trips) - Android update for build - Additional backlight devices - New option to specify config file via command line - Prevent adding cooling device in /etc via dbus - Whitelist of processor models, to avoid startup on server platforms Release 1.4.3 - One new dbus message to get temp - Fixes to prevent warnings Release 1.4 - Extension of DBUS I/F for developing Monitoring and Control GUI - Added exampled to thermal-conf man page - Support INT340X class of thermal control introduced in kernel 4.0 - Reinit without restart thermald to load new parameters like new control temperature - Fix indexes when Linux thermal sysfs doesn't have contiguous zone numbering - Support for new Intel SoC platforms - Introduce back-light control as the Linux back light cooling device is removed - Restore modified passive trip points in thermal zones on exit - Virtual Sensor definition - Fix loop when uevents floods the system - Error message removal for rapl sysfs traversal - Coverity error Release 1.3 - Auto creation of configuration based on ACPI thermal relationship table - Default CPU bound load check for unbinded thermal sensors Release 1.2 - Several fixes for Klocworks and Coverity scans (0 issues remaining) - Baytrail RAPL support as this doesn't have max power limit value Release 1.1 - Use powercap Intel RAPL driver - Use skin temperature sensor by default if available - Specify thermal relationship - Clean up for MSR related controls as up stream kernel driver are capable now - Override capability of thermal sysfs for a specific sensor or zone - Friendly to new thermal sysfs Release 1.04 - Android and chrome os integration - Minor fixes for buggy max temp Release 1.03 - Allow negative step increments while configuring via XML - Use powercap RAPL driver I/F - Additional cpuids in the list - Add man page with details of usage - Added P state turbo on/off Release 1.02 - Allow user to change the max temperature via dbus message - Allow user to change the cooling method order via an XML configuration - Upstart fixes - Valgrind and zero warnings on build Release 1.01 - Implement RAPL using MSRs. - User can configure cooling device order via XML config file - Fix sensor path configuration for thermal-conf.xml, so that user cn specify custom sensor paths - Use CPU max scaling frequency to control CPU Frequencies - RPM generation scripts - Build and formatting fixes from Alexander Bersenev Release 1.0 - Tested on multiple platforms - Using PID version 0.9 - Replaced netlink with uevents - Fix issue with pre-configured thermal data to control daemon - Use pthreads version 0.8 - Fix RAPL PATH, which is submitted upstream - Handle case when there is no MSR access from user mode - Allow non Intel CPUs version 0.7 - Conditional per cpu control - Family id check - If no max use offset from critical temperature - Switch to hwmon if there is no coretemp - Error handling if MSR support is not enabled in kernel - Code clean up and comments Version 0.6 - Use Intel P state driver to control P states - Use RAPL cooling device - Fix valgrind reported errors and cleanup - Add document Version 0.5 - License update to GPL v2 or later - Change dbus session bus to system - Load thermal-conf.xml data if exact UUID match Version 0.4 - Added power clamp driver interface - Added per cpu controls by trying to calibrate in the background to learn sensor cpu relationship - Optimized p states and turbo states and cleaned up - systemd and service start stop interface Version 0.3 - Added P states t states turbo states as the cooling methods - No longer depend on any thermal sysfs, zone cooling device by default - Uses DTS core temperature and p/turbo/t states to cool system - By default only will use DTS core temperature and p/turbo/t states only - All the previous controls based on the zones/cdevs and XML configuration is only done, when activated via command line - The set points are calculated and stored in a config file when it hits thermal threshold and adjusted based on slope and angular increments to dynamically adjust set point Version 0.2 - Define XML interface to set configuration data. Refer to thermal-conf.xml. This allows overriding buggy Bios thermal comfiguration and also allows extending the capability. - Use platform DMI UUID to index into configuration data. If there is no UUID match, falls back to thermal sysfs - Terminate interface - Takes over control from kernel thermal processing - Clean up of classes. Version 0.1 - Dbus interface to set preferred policy: "performance", "quiet/power", "disabled" - Defines a C++ classes for zones, cooling devices, trip points, thermal engine - Methods can be overridden in a custom class to modify default behaviour - Read thermal zone and cooling devices, trip points etc, - Read temperature via netlink notification or via polling configurable via command line - Once a trip point is crossed, activate the associate cooling devices. Start with min tstate to max tstate for each cooling device. - Based on active or passive settings it decides the cooling devices thermal_daemon-2.5.12/SECURITY.md000066400000000000000000000006511521301715300163510ustar00rootroot00000000000000# Security Policy ## Report a Vulnerability Please report security issues or vulnerabilities to the [Intel Security Center]. For more information on how Intel works to resolve security issues, see [Vulnerability Handling Guidelines]. [Intel Security Center]:https://www.intel.com/security [Vulnerability Handling Guidelines]:https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html thermal_daemon-2.5.12/autogen.sh000077500000000000000000000005221521301715300165560ustar00rootroot00000000000000#!/bin/sh srcdir=`dirname $0` test -z "$srcdir" && srcdir=. olddir=`pwd` cd "$srcdir" aclocal --install || exit 1 gtkdocize --copy --flavour no-tmpl || exit 1 autoreconf --install --verbose || exit 1 cd "$olddir" if test -z "$NO_CONFIGURE"; then $srcdir/configure "$@" && echo "Now type 'make' to compile `basename $srcdir`." fi thermal_daemon-2.5.12/configure.ac000066400000000000000000000067531521301715300170570ustar00rootroot00000000000000AC_PREREQ(1.0) m4_define([td_major_version], [2]) m4_define([td_minor_version], [5.12-rc1]) m4_define([td_version], [td_major_version.td_minor_version]) AC_INIT([thermald], [td_version], [], [thermald]) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR(build-aux) AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([1.11 foreign no-define subdir-objects]) AM_MAINTAINER_MODE([enable]) GTK_DOC_CHECK([1.11],[--flavour no-tmpl]) AC_ARG_WITH(dbus-sys-dir, AS_HELP_STRING([--with-dbus-sys-dir=DIR], [where D-BUS system.d directory is])) if test -n "$with_dbus_sys_dir" ; then DBUS_SYS_DIR="$with_dbus_sys_dir" else DBUS_SYS_DIR="/usr/share/dbus-1/system.d" fi AC_SUBST(DBUS_SYS_DIR) # paths AC_SUBST(tdbinary, "$sbindir/$PACKAGE", [Binary executable]) AC_SUBST(tdconfdir, "$sysconfdir/$PACKAGE", [Configuration directory]) AC_SUBST(tdrundir, "$localstatedir/run/$PACKAGE", [Runtime state directory]) PKG_PROG_PKG_CONFIG AC_ARG_WITH([systemdsystemunitdir], AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]), [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]) if test "x$with_systemdsystemunitdir" != xno; then AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) fi AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ]) # print configuration echo echo "System paths:" echo " prefix: $prefix" echo " exec_prefix: $exec_prefix" echo " systemdunitdir: $with_systemdsystemunitdir" echo " tdbinary: $tdbinary" echo " tdconfdir: $tdconfdir" echo " tdrundir: $tdrundir" echo GETTEXT_PACKAGE=thermald AC_SUBST(GETTEXT_PACKAGE) AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package]) GLIB_VERSION_DEFINES="-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26" DBUS_CFLAGS="$DBUS_CFLAGS $GLIB_VERSION_DEFINES" PKG_CHECK_MODULES(GLIB, gio-unix-2.0 >= 2.22 gmodule-2.0 glib-2.0) GLIB_CFLAGS="$GLIB_CFLAGS $GLIB_VERSION_DEFINES" AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) PKG_CHECK_MODULES(XML, libxml-2.0 >= 2.4) AC_PATH_PROG([GDBUS_CODEGEN],[gdbus-codegen]) PKG_CHECK_MODULES(UPOWER, upower-glib) PKG_CHECK_MODULES(EVDEV, libevdev) AC_PROG_CC AC_PROG_CPP AC_PROG_CXX AC_PROG_INSTALL AC_C_CONST AC_C_INLINE AC_TYPE_SIZE_T m4_ifdef([AX_CHECK_COMPILE_FLAG], [ # Check for -Werror flag support. for flag in \ -Wall \ -Wclobbered \ -Wempty-body \ -Wformat \ -Wignored-qualifiers \ -Wmissing-field-initializers \ -Wreorder \ -Wreturn-type \ -Wsign-compare \ -Wtype-limits \ -Wuninitialized \ -Wunused-but-set-variable \ ; do AX_CHECK_COMPILE_FLAG([-Werror ${flag}], [CXXFLAGS="$CXXFLAGS ${flag}"]) done # Check for c++11 support. AX_CHECK_COMPILE_FLAG([-std=c++11], [CXXFLAGS="$CXXFLAGS -std=c++11"]) ], [AC_MSG_ERROR([AX_CHECK_COMPILE_FLAG not found, you'll need to install autoconf-archive])]) AC_ARG_ENABLE(werror, AS_HELP_STRING([--disable-werror], [Disable -Werror])) AS_IF([test "x$enable_werror" != "xno"], [CXXFLAGS="$CXXFLAGS -Werror"]) AC_CONFIG_FILES([Makefile docs/Makefile docs/version.xml data/Makefile]) AC_ARG_ENABLE(gdbus, [AS_HELP_STRING([--disable-gdbus], [Switch DBus backend to glib-dbus. (Default: GDBus)])], [], [AC_DEFINE([GDBUS], [1], [Enable GDBus support])]) AC_OUTPUT thermal_daemon-2.5.12/data/000077500000000000000000000000001521301715300154675ustar00rootroot00000000000000thermal_daemon-2.5.12/data/Makefile.am000066400000000000000000000016621521301715300175300ustar00rootroot00000000000000include $(GLIB_MAKEFILE) if HAVE_SYSTEMD systemdsystemunit_DATA = \ thermald.service thermald.service: thermald.service.in @$(service_edit) $< >$@ servicedir = $(datadir)/dbus-1/system-services service_in_files = org.freedesktop.thermald.service.in service_DATA = $(service_in_files:.service.in=.service) $(service_DATA): $(service_in_files) Makefile @$(service_edit) $< >$@ service_edit = sed \ -e 's|@bindir[@]|$(bindir)|g' \ -e 's|@sbindir[@]|$(sbindir)|g' \ -e 's|@sysconfdir[@]|$(sysconfdir)|g' \ -e 's|@localstatedir[@]|$(localstatedir)|g' endif # HAVE_SYSTEMD dbusservicedir = $(DBUS_SYS_DIR) dbusservice_DATA = org.freedesktop.thermald.conf tdconfigdir = $(tdconfdir) tdconfig_DATA = \ thermal-cpu-cdev-order.xml \ thermald-features.xml EXTRA_DIST = \ thermald.service.in \ org.freedesktop.thermald.service.in \ $(dbusservice_DATA) \ $(tdconfig_DATA) CLEANFILES = thermald.service org.freedesktop.thermald.service thermal_daemon-2.5.12/data/org.freedesktop.thermald.conf000066400000000000000000000012061521301715300232350ustar00rootroot00000000000000 thermal_daemon-2.5.12/data/org.freedesktop.thermald.service.in000066400000000000000000000001751521301715300243610ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.thermald Exec=/bin/false User=root SystemdService=dbus-org.freedesktop.thermald.service thermal_daemon-2.5.12/data/thermal-conf.xml000066400000000000000000000136511521301715300205760ustar00rootroot00000000000000 Generic X86 Laptop Device EXAMPLE_SYSTEM QUIET TSKN 1 SKIN TSKN 55000 passive SEQUENTIAL 1 rapl_controller 100 16 2 intel_powerclamp 100 12 Example Platform Name Example UUID Example Product Name QUIET example_sensor_1 /some_path 0 example_thermal_sysfs_sensor 1 example_virtual_sensor 1 example_sensor_1 0.5 10 example_virtual_sensor_advanced 1 example_sensor_1 0.7 0.2 0 rapl_pkg_power 0.3 0.5 1 45000 10 70000 2 Example Zone type example_sensor_1 75000 max SEQUENTIAL 1 example_cooling_device 100 12 10 40 example_cooling_device 0 10 0 50 5000 > 0.001 0.0001 0.0001 thermal_daemon-2.5.12/data/thermal-cpu-cdev-order.xml000066400000000000000000000007741521301715300224720ustar00rootroot00000000000000 rapl_controller intel_pstate intel_powerclamp cpufreq Processor thermal_daemon-2.5.12/data/thermald-features.xml000066400000000000000000000004031521301715300216220ustar00rootroot00000000000000 0 1 1 1 thermal_daemon-2.5.12/data/thermald.service.in000066400000000000000000000004651521301715300212630ustar00rootroot00000000000000[Unit] Description=Thermal Daemon Service ConditionVirtualization=no [Service] Type=dbus SuccessExitStatus=2 BusName=org.freedesktop.thermald ExecStart=@sbindir@/thermald --systemd --dbus-enable --adaptive Restart=on-failure [Install] WantedBy=multi-user.target Alias=dbus-org.freedesktop.thermald.service thermal_daemon-2.5.12/distribution_integration/000077500000000000000000000000001521301715300217005ustar00rootroot00000000000000thermal_daemon-2.5.12/distribution_integration/android_dbus_service_information000066400000000000000000000007531521301715300304120ustar00rootroot00000000000000Android Integration Thermald required dbus daemon. To start dbus daemon add the following to your init.rc. service dbus /system/bin/dbus-daemon --system --nofork class main socket dbus stream 660 root root user root group system inet To start thermald as a service service thermald /system/bin/thermald --dbus-enable user root group system class main disabled oneshot By default it is disabled. So need explicit command somewhere: "start thermald" thermal_daemon-2.5.12/distribution_integration/chromeos_gentoo_ebuild_spec000066400000000000000000000012411521301715300273510ustar00rootroot00000000000000# Copyright 1999-2013 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header: $ EAPI=4 inherit autotools systemd DESCRIPTION="Thermal daemon for Intel architectures" HOMEPAGE="https://github.com/intel/thermal_daemon" SRC_URI="https://github.com/intel/thermal_daemon/archive/v${PV}.tar.gz -> ${P}.tar.gz" LICENSE="GPL-2+" SLOT="0" KEYWORDS="amd64 ia64 x86" IUSE="" S=${WORKDIR}/thermal_daemon-${PV} DEPEND="dev-libs/dbus-glib dev-libs/libxml2" RDEPEND="${DEPEND}" DOCS=( ThermalDaemon_Introduction.pdf README.txt ) src_prepare() { eautoreconf } src_configure() { econf --with-systemdsystemunitdir=$(systemd_get_unitdir) } thermal_daemon-2.5.12/distribution_integration/make_rpm.sh000077500000000000000000000015761521301715300240430ustar00rootroot00000000000000#!/bin/bash BRANCH=$(git branch | grep '^\*' | sed 's/^..\(.*\)/\1/') HASH=$(git rev-parse ${BRANCH}) TAG=$(git describe --tags --abbrev=0) RELEASE=$(git describe --tags | cut -d- -f2 | tr - _) SHORT_COMMIT=$(git rev-parse HEAD | cut -c 1-7) sed -i "s,^%global commit.*,%global commit ${HASH}," thermal-daemon.spec sed -i "s,^Version:.*,Version: ${TAG}," thermal-daemon.spec sed -i "s,^Release.*,Release: ${RELEASE}%{?dist}," thermal-daemon.spec rsync -a --exclude='rpms' --exclude='.git' ../../thermal_daemon . mv thermal_daemon thermal_daemon-${SHORT_COMMIT} tar czf ~/rpmbuild/SOURCES/thermal_daemon-${TAG}-${SHORT_COMMIT}.tar.gz thermal_daemon-${SHORT_COMMIT} rpmbuild -bs thermal-daemon.spec # /usr/bin/mock -r fedora-21-x86_64 ~/SRPMS/thermal-daemon-*.src.rpm rpmbuild -ba thermal-daemon.spec rpmlint thermal-daemon.spec ~/rpmbuild/SRPMS/thermal* rm -rf thermal_daemon-${SHORT_COMMIT} thermal_daemon-2.5.12/distribution_integration/thermal-daemon.spec000066400000000000000000000052001521301715300254460ustar00rootroot00000000000000Name: thermald Version: 1.6 Release: 1%{?dist} Summary: The "Linux Thermal Daemon" program from 01.org License: GPLv2+ URL: https://github.com/intel/thermal_daemon %global pkgname thermal_daemon Source0: https://github.com/intel/thermal_daemon/archive/v%{version}.tar.gz BuildRequires: automake BuildRequires: autoconf BuildRequires: glib-devel BuildRequires: dbus-glib-devel BuildRequires: libxml2-devel BuildRequires: systemd Requires(post): systemd-units Requires(preun): systemd-units Requires(postun): systemd-units %description Thermal Daemon monitors and controls platform temperature. %prep %setup -qn %{pkgname}-%{version} %build autoreconf -f -i %configure prefix=%{_prefix} make %{?_smp_mflags} # Although there is a folder test in the upstream repo, this is not for self tests. # Hence check section is not present. %install %make_install DESTDIR=%{buildroot} %post %systemd_post thermald.service %preun %systemd_preun thermald.service %postun %systemd_postun_with_restart thermald.service %files %{_sbindir}/thermald %{_datadir}/dbus-1/system.d/org.freedesktop.thermald.conf %{_datadir}/dbus-1/system-services/org.freedesktop.thermald.service %config(noreplace) %{_sysconfdir}/thermald/thermal-conf.xml %config(noreplace) %{_sysconfdir}/thermald/thermal-cpu-cdev-order.xml %doc COPYING README.txt %{_mandir}/man8/thermald.8.gz %{_mandir}/man5/thermal-conf.xml.5.gz %{_unitdir}/thermald.service %exclude %{_sysconfdir}/init %changelog * Mon Apr 03 2017 James Ye 1.6-1 - Updated to thermal daemon 1.6 * Tue Oct 25 2016 Michael P. Moran <> 1.5-4 - Updated spec file for version 1.5.4 * Tue Mar 29 2016 Alexey Slaykovsky 1.5-3 - Updated spec file for a Thermal Daemon 1.5.3 version * Fri Oct 17 2014 António Meireles 1.3-3 - update spec file * Tue Oct 01 2013 Srinivas Pandruvada 1.03-1 - Upgraded to thermal daemon 1.03 * Mon Jun 24 2013 Srinivas Pandruvada 1.02-5 - Replaced underscore with dash in the package name * Thu Jun 20 2013 Srinivas Pandruvada 1.02-4 - Resolved prefix and RPM_BUILD_ROOT as per review comments * Wed Jun 19 2013 Srinivas Pandruvada 1.02-3 - Removed libxml2 requirement and uses shortcommit in the Source0 * Tue Jun 18 2013 Srinivas Pandruvada 1.02-2 - Update spec file after first review * Fri Jun 14 2013 Srinivas Pandruvada 1.02-1 - Initial package thermal_daemon-2.5.12/docs/000077500000000000000000000000001521301715300155065ustar00rootroot00000000000000thermal_daemon-2.5.12/docs/Makefile.am000066400000000000000000000023111521301715300175370ustar00rootroot00000000000000NULL = AUTOMAKE_OPTIONS = 1.7 # The name of the module. DOC_MODULE=thermal_daemon # The top-level SGML file. DOC_MAIN_SGML_FILE=thermal_daemon-docs.xml # Extra options to supply to gtkdoc-scan SCAN_OPTIONS= \ --ignore-headers="config.h thd*.h thermald.h acpi_*.h" \ --rebuild-sections --rebuild-types # The directory containing the source code. Relative to $(srcdir) DOC_SOURCE_DIR= # Used for dependencies HFILE_GLOB= CFILE_GLOB= # Headers to ignore IGNORE_HFILES= \ $(NULL) GTKDOC_LIBS = # Extra options to supply to gtkdoc-mkdb MKDB_OPTIONS=--sgml-mode --output-format=xml docs-thd_dbus_interface.xml: $(top_srcdir)/src/thd_dbus_interface.xml $(AM_V_GEN)$(GDBUS_CODEGEN) --generate-docbook=docs $< # Non-autogenerated SGML files to be included in $(DOC_MAIN_SGML_FILE) content_files = \ version.xml \ docs-thd_dbus_interface.xml \ $(NULL) # FIXME MAINTAINERCLEANFILES = \ *~ \ Makefile.in \ thermal_daemon.types \ thermal_daemon-*.txt \ $(NULL) include $(top_srcdir)/gtk-doc.make EXTRA_DIST = thermal_daemon-docs.xml CLEANFILES += \ docs-thermal_daemon.xml \ $(NULL) # Version information for marking the documentation EXTRA_DIST += version.xml.in -include $(top_srcdir)/git.mk thermal_daemon-2.5.12/docs/thermal_daemon-docs.xml000066400000000000000000000046641521301715300221470ustar00rootroot00000000000000 ]> Thermal Daemon Reference Manual Version &version; Bastien Nocera
hadess@hadess.net
2020 Red Hat, Inc. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. You may obtain a copy of the GNU Free Documentation License from the Free Software Foundation by visiting their Web site or by writing to:
The Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
Many of the names used by companies to distinguish their products and services are claimed as trademarks. Where those names appear in any GNOME documentation, and those trademarks are made aware to the members of the GNOME Documentation Project, the names have been printed in caps or initial caps.
D-Bus API Reference This part documents the D-Bus interface used to access the Thermal Daemon service. Index License FIXME: MISSING XINCLUDE CONTENT
thermal_daemon-2.5.12/docs/version.xml.in000066400000000000000000000000121521301715300203130ustar00rootroot00000000000000@VERSION@ thermal_daemon-2.5.12/man/000077500000000000000000000000001521301715300153315ustar00rootroot00000000000000thermal_daemon-2.5.12/man/thermal-conf.xml.5000066400000000000000000000504361521301715300206050ustar00rootroot00000000000000.\" thermal-conf.xml(5) manual page .\" .\" This is free documentation; you can redistribute it and/or .\" modify it under the terms of the GNU General Public License as .\" published by the Free Software Foundation; either version 2 of .\" the License, or (at your option) any later version. .\" .\" The GNU General Public License's references to "object code" .\" and "executables" are to be interpreted as the output of any .\" document formatting or typesetting system, including .\" intermediate and printed output. .\" .\" This manual is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU General Public Licence along .\" with this manual; if not, write to the Free Software Foundation, Inc., .\" 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. .\" .\" Copyright (C) 2012 Intel Corporation. All rights reserved. .\" .TH thermal-conf.xml "5" "Dec 18, 2018" .SH NAME thermal-conf.xml \- Configuration file for thermal daemon .SH SYNOPSIS $(TDCONFDIR)/etc/thermald/thermal-conf.xml .SH DESCRIPTION .B thermal-conf.xml is a configuration file for the thermal daemon. It is used to configure thermal sensors, zone and cooling devices. The location of this file depends on the configuration option used during build time. .PP The terminology used in this file conforms to "Advanced Configuration and Power Interface Specification". The ACPI thermal model is based around conceptual platform regions called thermal zones that physically contain devices, thermal sensors, and cooling controls. For example of a thermal zone can be a CPU or a laptop cover. A zone can contain multiple sensors for monitoring temperature. A cooling device provides interface to reduce the temperature of a source device, which causes increase in the temperature. An example of a cooling device is a FAN or some Linux driver which can throttle the source device. .PP A thermal zone configuration includes one or more trip points. A trip point is a temperature at which a cooling device needs to be activated. .PP A cooling device can be either active or passive. An example of an active device is a FAN, which will not reduce performance at the cost of consuming more power and noise. A passive device uses performance throttling to control temperature. In addition to cooling devices present in the thermal sysfs, the following cooling devices are built into the thermald, which can be used as valid cooling device type: .IP \(bu 2 rapl_controller .IP \(bu 2 intel_pstate .IP \(bu 2 cpufreq .IP \(bu 2 LCD .PP The thermal sysfs under Linux (/sys/class/thermal) provides a way to represent per platform ACPI configuration. The kernel thermal governor uses this data to keep the platform thermals under control. But there are some limitations, which thermald tries to resolve. For example: .IP \(bu 2 If the ACPI data is not optimized or buggy. In this case thermal-conf.xml can be used to correct the behavior without change in BIOS. .IP \(bu 2 There may be thermal zones exposed by the thermal sysfs without associated cooling actions. In this case thermal conf.xml can be used to tie the cooling devices to those zones. .IP \(bu 2 The best cooling method may not be in the thermal sysfs. In this case thermal-conf.xml can be used to bind a zone to an external cooling device. .IP \(bu 2 Specify thermal relationships. A zone can be influenced by multiple source devices with varying degrees. In this case thermal-conf.xml can be used to define the relative influence for apply compensation. .SH FILE FORMAT The configuration file format conforms to XML specifications. A set of tags defined to define platform, sensors, zones, cooling devices and trip points. .PP Virtual sensors support two XML forms: .IP \(bu 2 .B SensorLink defines a simple linear transform using one linked sensor: .RS 2 virtual_temp = linked_sensor * Multiplier + Offset .RE .IP \(bu 2 .B LinkSensor defines one contribution to an advanced virtual sensor. Multiple LinkSensor entries may be provided and their contributions are summed. For each entry: .RS 2 avg = Offset * current_sample + (1 - Offset) * previous_average contribution = Coeff * avg virtual_temp = sum of all contributions .RE .PP For advanced virtual sensors, when .B PowerSensor is 0, the linked sensor temperature is converted from milli-degree Celsius to degree Celsius before the averaging and coefficient calculations are applied. Optional .B PollingTableEntry elements can be used to change the virtual sensor polling period based on the current virtual temperature. .sp 1 .EX Example Platform Name Example UUID Example Product Name QUIET|PERFORMANCE example_sensor_1 /some_path 0 example_thermal_sysfs_sensor 1 example_virtual_sensor 1 example_sensor_1 0.5 10 example_virtual_sensor_advanced 1 example_sensor_1 0.7 0.2 0 rapl_pkg_power 0.3 0.5 1 45000 10 70000 2 Example Zone type example_sensor_1 75000 max SEQUENTIAL 1 example_cooling_device 100 12 6 2 8 example_cooling_device 0 10 0 50 5000 0.001 0.0001 0.0001 level .EE .SH EXAMPLE CONFIGURATIONS .B Example 1: This is a very simple configuration, to change the passive limit on the CPU. Instead of default, this new temperature 86C in the configuration is used. This will start cooling, once the temperature reaches 86C. .sp 1 .EX Override CPU default passive * QUIET cpu 86000 passive .EE .PP .B Example 2: In this configuration, we are controlling backlight when some sensor "SEN2" reaches 60C. Here "LCD" is a standard cooling device, which uses Linux backlight sysfs interface. "LCD_Zone" is a valid thermal zone in Linux thermal sysfs on the test platform, hence we don't need to provide path for sysfs for "LCD_Zone". The Linux thermal sysfs is already parsed and loaded by the thermald program. .sp 1 .EX Change Backlight * QUIET LCD_Zone SEN2 60000 passive LCD .EE .PP .B Example 3: In this example Lenovo Thinkpad X220 and fan speed is controlled. Here a cooling device "_Fan", can be controlled via sysfs /sys/devices/platform/thinkpad_hwmon/pwm1. When the x86_pkg_temp reaches 45C, Fan is started with increasing speeds, if the temperature can't be controlled at 45C. .sp 1 .EX Lenovo ThinkPad X220 * QUIET x86_pkg_temp x86_pkg_temp 45000 passive SEQUENTIAL 1 _Fan 100 12 _Fan /sys/devices/platform/thinkpad_hwmon/pwm1 0 30 0 255 5 .EE .PP .B Example 4: The following example shows how PID can be used. Here once temperature exceeds 80C, compensation is calculated using PID using 80C as set point of PID. The compensation depends on error from the set point. Here the default built in processor cooling device is used with min state as 0 and max state as 10. .sp 1 .EX Use PID param * QUIET x86_pkg_temp x86_pkg_temp 80000 passive SEQUENTIAL Processor Processor 0.0002 0 0 .EE .PP .B Example 5: The following example shows how to control Fan when the sysfs expects some string prefix. For example instead of just write a number to fan control sysfs, the interface requires "level " in front of the speed index value. .sp 1 .EX Use Fan control first then CPU throttle * QUIET x86_pkg_temp x86_pkg_temp 80000 passive SEQUENTIAL _fan_ _fan_ /proc/acpi/ibm/fan level 0 5 10 .EE .PP Example 6: Similar to example 5, but write different speeds at different temperatures. .sp 1 .EX Use Fan control first then CPU throttle * QUIET x86_pkg_temp x86_pkg_temp 80000 passive _fan_ 1 x86_pkg_temp 85000 passive _fan_ 2 _fan_ /proc/acpi/ibm/fan level 0 5 10 .EE .PP Example 7: Use RAPL power limits to control. .sp 1 .EX TEST Example_RAPL_Power QUIET 0 15000 2000 20 30 1000 TestZone SEN3 50000 Passive B0D4 3 2147483647 SEN3 52000 Passive B0D4 3 8500000 SEN3 60000 Passive B0D4 3 4500000 SEN3 65000 Passive B0D4 3 .EE thermal_daemon-2.5.12/man/thermald.8000066400000000000000000000121331521301715300172220ustar00rootroot00000000000000.\" thermald (8) manual page .\" .\" This is free documentation; you can redistribute it and/or .\" modify it under the terms of the GNU General Public License as .\" published by the Free Software Foundation; either version 2 of .\" the License, or (at your option) any later version. .\" .\" The GNU General Public License's references to "object code" .\" and "executables" are to be interpreted as the output of any .\" document formatting or typesetting system, including .\" intermediate and printed output. .\" .\" This manual is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU General Public Licence along .\" with this manual; if not, write to the Free Software Foundation, Inc., .\" 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. .\" .\" Copyright (C) 2012 Intel Corporation. All rights reserved. .\" .TH thermald "8" "May 8, 2013" .SH NAME thermald \- start Linux thermal daemon .SH SYNOPSIS .B thermald .RI " [ " OPTIONS " ] .SH DESCRIPTION .B thermald is a Linux daemon used to prevent the overheating of platforms. This daemon monitors temperature and applies compensation using available cooling methods. By default, it monitors CPU temperature using available CPU digital temperature sensors and maintains CPU temperature under control, before HW takes aggressive correction action. Thermal daemon looks for thermal sensors and thermal cooling drivers in the Linux thermal sysfs (/sys/class/thermal) and builds a list of sensors and cooling drivers. Each of the thermal sensors can optionally be binded to a cooling driver by the in kernel drivers. In this case the Linux kernel thermal core can directly take actions based on the temperature trip points, for each sensor and associated cooling device. For example a trip temperature X in a sensor can be associates a cooling driver Y. So when the sensor temperature = X, the cooling driver "Y" is activated. Thermal daemon allows one to change this relationship or add new one via a thermal configuration file (thermal-conf.xml). This file is automatically created (thermal-conf.xml.auto) and used, if the platform has ACPI thermal relationship table. If not this needs to be manually configured. For manual configuration refer to the manual page of the thermal-conf.xml. There can be multiple configuration files. User can select a configuration file via -config-file option to override the default selection. The default selection picks one of the file in the following order: - /etc/thermald/thermal-conf.xml.auto - /var/run/thermald/thermal-conf.xml.auto - /etc/thermald/thermal-conf.xml (*Assuming configure prefix=/ is used during build.) There is another companion tool "ThermalMonitor", which presents a graphical front end. This allows the monitoring of sensors and changing of thermal trips to give the user more control. The source code of "ThermalMonitor" is a part of the thermald github source, in the tools folder. .SH OPTIONS .TP .B \-h, \-\-help Show help options. .TP .B \-\-version Print thermald version and exit. .TP .B \-\-no-daemon Don't become a daemon: Default is daemon mode. .TP .B \-\-loglevel=info log severity: info level and up. .TP .B \-\-loglevel=debug log severity: debug level and up: Max logging. .TP .B \-\-poll-interval Poll interval in seconds: Poll for zone temperature changes. To disable polling, set to zero. Polling can only be disabled, if available temperature sensors can notify temperature change asynchronously. .TP .B \-\-dbus-enable Enable Dbus. .TP .B \-\-exclusive-control Act as exclusive thermal controller. This will use user-space governor for thermal sysfs and take over control. .TP .B \-\-ignore-cpuid-check Ignore cpuid check for supported CPU models. .TP .B \-\-config-file Specify thermal-conf.xml path and ignore default thermal-conf.xml. .TP .B \-\-ignore-default-control Ignore default CPU temperature control. Strictly follow thermal-conf.xml or thermal-conf.xml.auto. .TP .B \-\-workaround-enabled Enable special workarounds for RAPL MMIO power limit and TCC offset every 30 seconds. This helps to disable RAPL MMIO when not used and adjust TCC offset in certain Lenovo laptops. .TP .B \-\-disable-active-power Disable active power management. This will not set active power limits using RAPL MMIO. This may result in constrained performance, if the system boots up with lower power limits. .TP .B \-\-adaptive Use DPTF adaptive tables when present. This will ignore thermald config via xml files. .TP .B \-\-test-mode Force use adaptive mode and exit if not supported, instead of restarting in non adaptive mode. This option is primarily for developers. .TP .B \-\-systemd Assume that thermald is started by systemd. This will prevent running as daemon irrespective of --no-daemon option. .TP .B \-\-ignore-critical-trip If the configuration defined a critical temperature point, which is too low, this option will avoid shutting down the system on reaching this temperature limit. .SH SEE ALSO thermal-conf.xml(5) thermal_daemon-2.5.12/misc/000077500000000000000000000000001521301715300155115ustar00rootroot00000000000000thermal_daemon-2.5.12/misc/valgrind.supp000066400000000000000000000040771521301715300202400ustar00rootroot00000000000000# Valgrind suppressions file for glib, stl string calls { Syscall param write Memcheck:Param ... fun:g_main_loop_run fun:main } { g_type_init_with_debug_flags Memcheck:Leak ... fun:g_type_init_with_debug_flags fun:main } { g_main_loop_run Memcheck:Leak fun:memalign ... fun:g_main_loop_run fun:main } { c_plus_plus_string_hack Memcheck:Leak fun:_Znwm fun:_ZNSs?_Rep?_S_createEmmRKSaIcE fun:_ZNSs??_S_constructIPKcEEPcT_S?_RKSaIcESt??forward_iterator_tag fun:_ZNSsC?EPKcRKSaIcE fun:_ZN?cthd_msrC?Ev fun:_ZN??cthd_engine_dts??read_cooling_devicesEv fun:_ZN??cthd_engine??thd_engine_startEv fun:main } { c_plus_plus_string_hack Memcheck:Leak fun:_Znwm fun:_ZNSs4_Rep9_S_createEmmRKSaIcE fun:_ZNSs12_S_constructIPKcEEPcT_S3_RKSaIcESt20forward_iterator_tag fun:_ZNSsC1EPKcRKSaIcE fun:_ZN8cthd_msrC1Ev fun:_ZN15cthd_engine_dts26check_intel_p_state_driverEv fun:_ZN15cthd_engine_dts20read_cooling_devicesEv fun:_ZN11cthd_engine16thd_engine_startEv fun:main } { c_plus_plus_string_hack Memcheck:Leak fun:_Znwm fun:_ZNSs?_Rep?_S_createEmmRKSaIcE fun:_ZNSs??_S_constructIPKcEEPcT_S?_RKSaIcESt??forward_iterator_tag fun:_ZNSsC?EPKcRKSaIcE fun:_ZN?cthd_msrC1Ev fun:_ZN??c_rapl_interfaceC?Ei fun:_ZN??cthd_engine_dts??read_cooling_devicesEv fun:_ZN??cthd_engine??thd_engine_startEv fun:main } { c_plus_plus_string_hack Memcheck:Leak fun:_Znwm fun:_ZNSs?_Rep?_S_createEmmRKSaIcE fun:_ZNSs??_S_constructIPKcEEPcT_S?_RKSaIcESt??forward_iterator_tag fun:_ZNSsC?EPKcRKSaIcE fun:_ZN?cthd_msrC?Ev fun:_ZN??cthd_engine_dts??check_intel_p_state_driverEv fun:_ZN??cthd_engine_dts??read_cooling_devicesEv fun:_ZN??cthd_engine??thd_engine_startEv fun:main } { c_plus_plus_string_hack Memcheck:Leak fun:_Znwm fun:_ZNSt?vectorIiSaIiEE??_M_insert_auxEN?__gnu_cxx??__normal_iteratorIPiS?_EERKi fun:_ZN??cthd_cdev_pstates?initEv fun:_ZN??cthd_engine_dts??read_cooling_devicesEv fun:_ZN??cthd_engine??thd_engine_startEv fun:main } thermal_daemon-2.5.12/src/000077500000000000000000000000001521301715300153455ustar00rootroot00000000000000thermal_daemon-2.5.12/src/7zTypes.h000066400000000000000000000213471521301715300171120ustar00rootroot00000000000000// From Intel DPTF github project ESIF/Products/ESIF_CMP/Sources // Fix white spaces from the source. /* 7zTypes.h -- Basic types 2018-08-04 : Igor Pavlov : Public domain */ #ifndef __7Z_TYPES_H #define __7Z_TYPES_H #ifdef _WIN32 /* #include */ #endif #include #ifndef EXTERN_C_BEGIN #ifdef __cplusplus #define EXTERN_C_BEGIN extern "C" { #define EXTERN_C_END } #else #define EXTERN_C_BEGIN #define EXTERN_C_END #endif #endif EXTERN_C_BEGIN #define SZ_OK 0 #define SZ_ERROR_DATA 1 #define SZ_ERROR_MEM 2 #define SZ_ERROR_CRC 3 #define SZ_ERROR_UNSUPPORTED 4 #define SZ_ERROR_PARAM 5 #define SZ_ERROR_INPUT_EOF 6 #define SZ_ERROR_OUTPUT_EOF 7 #define SZ_ERROR_READ 8 #define SZ_ERROR_WRITE 9 #define SZ_ERROR_PROGRESS 10 #define SZ_ERROR_FAIL 11 #define SZ_ERROR_THREAD 12 #define SZ_ERROR_ARCHIVE 16 #define SZ_ERROR_NO_ARCHIVE 17 typedef int SRes; #ifdef _WIN32 /* typedef DWORD WRes; */ typedef unsigned WRes; #define MY_SRes_HRESULT_FROM_WRes(x) HRESULT_FROM_WIN32(x) #else typedef int WRes; #define MY__FACILITY_WIN32 7 #define MY__FACILITY__WRes MY__FACILITY_WIN32 #define MY_SRes_HRESULT_FROM_WRes(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (MY__FACILITY__WRes << 16) | 0x80000000))) #endif #ifndef RINOK #define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } #endif typedef unsigned char Byte; typedef short Int16; typedef unsigned short UInt16; #ifdef _LZMA_UINT32_IS_ULONG typedef long Int32; typedef unsigned long UInt32; #else typedef int Int32; typedef unsigned int UInt32; #endif #ifdef _SZ_NO_INT_64 /* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. NOTES: Some code will work incorrectly in that case! */ typedef long Int64; typedef unsigned long UInt64; #else #if defined(_MSC_VER) || defined(__BORLANDC__) typedef __int64 Int64; typedef unsigned __int64 UInt64; #define UINT64_CONST(n) n #else typedef long long int Int64; typedef unsigned long long int UInt64; #define UINT64_CONST(n) n ## ULL #endif #endif #ifdef _LZMA_NO_SYSTEM_SIZE_T typedef UInt32 SizeT; #else typedef size_t SizeT; #endif typedef int BoolInt; /* typedef BoolInt Bool; */ #define True 1 #define False 0 #ifdef _WIN32 #define MY_STD_CALL __stdcall #else #define MY_STD_CALL #endif #ifdef _MSC_VER #if _MSC_VER >= 1300 #define MY_NO_INLINE __declspec(noinline) #else #define MY_NO_INLINE #endif #define MY_FORCE_INLINE __forceinline #define MY_CDECL __cdecl #define MY_FAST_CALL __fastcall #else #define MY_NO_INLINE #define MY_FORCE_INLINE #define MY_CDECL #define MY_FAST_CALL /* inline keyword : for C++ / C99 */ /* GCC, clang: */ /* #if defined (__GNUC__) && (__GNUC__ >= 4) #define MY_FORCE_INLINE __attribute__((always_inline)) #define MY_NO_INLINE __attribute__((noinline)) #endif */ #endif /* The following interfaces use first parameter as pointer to structure */ typedef struct IByteIn IByteIn; struct IByteIn { Byte (*Read)(const IByteIn *p); /* reads one byte, returns 0 in case of EOF or error */ }; #define IByteIn_Read(p) (p)->Read(p) typedef struct IByteOut IByteOut; struct IByteOut { void (*Write)(const IByteOut *p, Byte b); }; #define IByteOut_Write(p, b) (p)->Write(p, b) typedef struct ISeqInStream ISeqInStream; struct ISeqInStream { SRes (*Read)(const ISeqInStream *p, void *buf, size_t *size); /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. (output(*size) < input(*size)) is allowed */ }; #define ISeqInStream_Read(p, buf, size) (p)->Read(p, buf, size) /* it can return SZ_ERROR_INPUT_EOF */ SRes SeqInStream_Read(const ISeqInStream *stream, void *buf, size_t size); SRes SeqInStream_Read2(const ISeqInStream *stream, void *buf, size_t size, SRes errorType); SRes SeqInStream_ReadByte(const ISeqInStream *stream, Byte *buf); typedef struct ISeqOutStream ISeqOutStream; struct ISeqOutStream { size_t (*Write)(const ISeqOutStream *p, const void *buf, size_t size); /* Returns: result - the number of actually written bytes. (result < size) means error */ }; #define ISeqOutStream_Write(p, buf, size) (p)->Write(p, buf, size) typedef enum { SZ_SEEK_SET = 0, SZ_SEEK_CUR = 1, SZ_SEEK_END = 2 } ESzSeek; typedef struct ISeekInStream ISeekInStream; struct ISeekInStream { SRes (*Read)(const ISeekInStream *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ SRes (*Seek)(const ISeekInStream *p, Int64 *pos, ESzSeek origin); }; #define ISeekInStream_Read(p, buf, size) (p)->Read(p, buf, size) #define ISeekInStream_Seek(p, pos, origin) (p)->Seek(p, pos, origin) typedef struct ILookInStream ILookInStream; struct ILookInStream { SRes (*Look)(const ILookInStream *p, const void **buf, size_t *size); /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. (output(*size) > input(*size)) is not allowed (output(*size) < input(*size)) is allowed */ SRes (*Skip)(const ILookInStream *p, size_t offset); /* offset must be <= output(*size) of Look */ SRes (*Read)(const ILookInStream *p, void *buf, size_t *size); /* reads directly (without buffer). It's same as ISeqInStream::Read */ SRes (*Seek)(const ILookInStream *p, Int64 *pos, ESzSeek origin); }; #define ILookInStream_Look(p, buf, size) (p)->Look(p, buf, size) #define ILookInStream_Skip(p, offset) (p)->Skip(p, offset) #define ILookInStream_Read(p, buf, size) (p)->Read(p, buf, size) #define ILookInStream_Seek(p, pos, origin) (p)->Seek(p, pos, origin) SRes LookInStream_LookRead(const ILookInStream *stream, void *buf, size_t *size); SRes LookInStream_SeekTo(const ILookInStream *stream, UInt64 offset); /* reads via ILookInStream::Read */ SRes LookInStream_Read2(const ILookInStream *stream, void *buf, size_t size, SRes errorType); SRes LookInStream_Read(const ILookInStream *stream, void *buf, size_t size); typedef struct { ILookInStream vt; const ISeekInStream *realStream; size_t pos; size_t size; /* it's data size */ /* the following variables must be set outside */ Byte *buf; size_t bufSize; } CLookToRead2; void LookToRead2_CreateVTable(CLookToRead2 *p, int lookahead); #define LookToRead2_Init(p) { (p)->pos = (p)->size = 0; } typedef struct { ISeqInStream vt; const ILookInStream *realStream; } CSecToLook; void SecToLook_CreateVTable(CSecToLook *p); typedef struct { ISeqInStream vt; const ILookInStream *realStream; } CSecToRead; void SecToRead_CreateVTable(CSecToRead *p); typedef struct ICompressProgress ICompressProgress; struct ICompressProgress { SRes (*Progress)(const ICompressProgress *p, UInt64 inSize, UInt64 outSize); /* Returns: result. (result != SZ_OK) means break. Value (UInt64)(Int64)-1 for size means unknown value. */ }; #define ICompressProgress_Progress(p, inSize, outSize) (p)->Progress(p, inSize, outSize) typedef struct ISzAlloc ISzAlloc; typedef const ISzAlloc * ISzAllocPtr; struct ISzAlloc { void *(*Alloc)(ISzAllocPtr p, size_t size); void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */ }; #define ISzAlloc_Alloc(p, size) (p)->Alloc(p, size) #define ISzAlloc_Free(p, a) (p)->Free(p, a) /* deprecated */ #define IAlloc_Alloc(p, size) ISzAlloc_Alloc(p, size) #define IAlloc_Free(p, a) ISzAlloc_Free(p, a) #ifndef MY_offsetof #ifdef offsetof #define MY_offsetof(type, m) offsetof(type, m) /* #define MY_offsetof(type, m) FIELD_OFFSET(type, m) */ #else #define MY_offsetof(type, m) ((size_t)&(((type *)0)->m)) #endif #endif #ifndef MY_container_of /* #define MY_container_of(ptr, type, m) container_of(ptr, type, m) #define MY_container_of(ptr, type, m) CONTAINING_RECORD(ptr, type, m) #define MY_container_of(ptr, type, m) ((type *)((char *)(ptr) - offsetof(type, m))) #define MY_container_of(ptr, type, m) (&((type *)0)->m == (ptr), ((type *)(((char *)(ptr)) - MY_offsetof(type, m)))) */ /* GCC shows warning: "perhaps the 'offsetof' macro was used incorrectly" GCC 3.4.4 : classes with constructor GCC 4.8.1 : classes with non-public variable members" */ #define MY_container_of(ptr, type, m) ((type *)((char *)(1 ? (ptr) : &((type *)0)->m) - MY_offsetof(type, m))) #endif #define CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) ((type *)(ptr)) /* #define CONTAINER_FROM_VTBL(ptr, type, m) CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) */ #define CONTAINER_FROM_VTBL(ptr, type, m) MY_container_of(ptr, type, m) #define CONTAINER_FROM_VTBL_CLS(ptr, type, m) CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) /* #define CONTAINER_FROM_VTBL_CLS(ptr, type, m) CONTAINER_FROM_VTBL(ptr, type, m) */ #ifdef _WIN32 #define CHAR_PATH_SEPARATOR '\\' #define WCHAR_PATH_SEPARATOR L'\\' #define STRING_PATH_SEPARATOR "\\" #define WSTRING_PATH_SEPARATOR L"\\" #else #define CHAR_PATH_SEPARATOR '/' #define WCHAR_PATH_SEPARATOR L'/' #define STRING_PATH_SEPARATOR "/" #define WSTRING_PATH_SEPARATOR L"/" #endif EXTERN_C_END #endif thermal_daemon-2.5.12/src/LzmaDec.c000066400000000000000000000766131521301715300170450ustar00rootroot00000000000000// From Intel DPTF github project ESIF/Products/ESIF_CMP/Sources // Fix white spaces from the source. /* LzmaDec.c -- LZMA Decoder 2018-07-04 : Igor Pavlov : Public domain */ //#include "Precomp.h" #include /* #include "CpuArch.h" */ #include "LzmaDec.h" // For MyMemCpy function only exclude #include "EsifSdl.h" #define MyMemcpy(dst, src, len) memcpy(dst, src, len) #define kNumTopBits 24 #define kTopValue ((UInt32)1 << kNumTopBits) #define kNumBitModelTotalBits 11 #define kBitModelTotal (1 << kNumBitModelTotalBits) #define kNumMoveBits 5 #define RC_INIT_SIZE 5 #define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); } #define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * (UInt32)ttt; if (code < bound) #define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); #define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); #define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \ { UPDATE_0(p); i = (i + i); A0; } else \ { UPDATE_1(p); i = (i + i) + 1; A1; } #define TREE_GET_BIT(probs, i) { GET_BIT2(probs + i, i, ;, ;); } #define REV_BIT(p, i, A0, A1) IF_BIT_0(p + i) \ { UPDATE_0(p + i); A0; } else \ { UPDATE_1(p + i); A1; } #define REV_BIT_VAR( p, i, m) REV_BIT(p, i, i += m; m += m, m += m; i += m; ) #define REV_BIT_CONST(p, i, m) REV_BIT(p, i, i += m; , i += m * 2; ) #define REV_BIT_LAST( p, i, m) REV_BIT(p, i, i -= m , ; ) #define TREE_DECODE(probs, limit, i) \ { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; } /* #define _LZMA_SIZE_OPT */ #ifdef _LZMA_SIZE_OPT #define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i) #else #define TREE_6_DECODE(probs, i) \ { i = 1; \ TREE_GET_BIT(probs, i); \ TREE_GET_BIT(probs, i); \ TREE_GET_BIT(probs, i); \ TREE_GET_BIT(probs, i); \ TREE_GET_BIT(probs, i); \ TREE_GET_BIT(probs, i); \ i -= 0x40; } #endif #define NORMAL_LITER_DEC TREE_GET_BIT(prob, symbol) #define MATCHED_LITER_DEC \ matchByte += matchByte; \ bit = offs; \ offs &= matchByte; \ probLit = prob + (offs + bit + symbol); \ GET_BIT2(probLit, symbol, offs ^= bit; , ;) #define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); } #define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * (UInt32)ttt; if (code < bound) #define UPDATE_0_CHECK range = bound; #define UPDATE_1_CHECK range -= bound; code -= bound; #define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \ { UPDATE_0_CHECK; i = (i + i); A0; } else \ { UPDATE_1_CHECK; i = (i + i) + 1; A1; } #define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;) #define TREE_DECODE_CHECK(probs, limit, i) \ { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; } #define REV_BIT_CHECK(p, i, m) IF_BIT_0_CHECK(p + i) \ { UPDATE_0_CHECK; i += m; m += m; } else \ { UPDATE_1_CHECK; m += m; i += m; } #define kNumPosBitsMax 4 #define kNumPosStatesMax (1 << kNumPosBitsMax) #define kLenNumLowBits 3 #define kLenNumLowSymbols (1 << kLenNumLowBits) #define kLenNumHighBits 8 #define kLenNumHighSymbols (1 << kLenNumHighBits) #define LenLow 0 #define LenHigh (LenLow + 2 * (kNumPosStatesMax << kLenNumLowBits)) #define kNumLenProbs (LenHigh + kLenNumHighSymbols) #define LenChoice LenLow #define LenChoice2 (LenLow + (1 << kLenNumLowBits)) #define kNumStates 12 #define kNumStates2 16 #define kNumLitStates 7 #define kStartPosModelIndex 4 #define kEndPosModelIndex 14 #define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) #define kNumPosSlotBits 6 #define kNumLenToPosStates 4 #define kNumAlignBits 4 #define kAlignTableSize (1 << kNumAlignBits) #define kMatchMinLen 2 #define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols * 2 + kLenNumHighSymbols) /* External ASM code needs same CLzmaProb array layout. So don't change it. */ /* (probs_1664) is faster and better for code size at some platforms */ /* #ifdef MY_CPU_X86_OR_AMD64 */ #define kStartOffset 1664 #define GET_PROBS p->probs_1664 /* #define GET_PROBS p->probs + kStartOffset #else #define kStartOffset 0 #define GET_PROBS p->probs #endif */ #define SpecPos (-kStartOffset) #define IsRep0Long (SpecPos + kNumFullDistances) #define RepLenCoder (IsRep0Long + (kNumStates2 << kNumPosBitsMax)) #define LenCoder (RepLenCoder + kNumLenProbs) #define IsMatch (LenCoder + kNumLenProbs) #define Align (IsMatch + (kNumStates2 << kNumPosBitsMax)) #define IsRep (Align + kAlignTableSize) #define IsRepG0 (IsRep + kNumStates) #define IsRepG1 (IsRepG0 + kNumStates) #define IsRepG2 (IsRepG1 + kNumStates) #define PosSlot (IsRepG2 + kNumStates) #define Literal (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) #define NUM_BASE_PROBS (Literal + kStartOffset) #if Align != 0 && kStartOffset != 0 #error Stop_Compiling_Bad_LZMA_kAlign #endif #if NUM_BASE_PROBS != 1984 #error Stop_Compiling_Bad_LZMA_PROBS #endif #define LZMA_LIT_SIZE 0x300 #define LzmaProps_GetNumProbs(p) (NUM_BASE_PROBS + ((UInt32)LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) #define CALC_POS_STATE(processedPos, pbMask) (((processedPos) & (pbMask)) << 4) #define COMBINED_PS_STATE (posState + state) #define GET_LEN_STATE (posState) #define LZMA_DIC_MIN (1 << 12) /* p->remainLen : shows status of LZMA decoder: < kMatchSpecLenStart : normal remain = kMatchSpecLenStart : finished = kMatchSpecLenStart + 1 : need init range coder = kMatchSpecLenStart + 2 : need init range coder and state */ /* ---------- LZMA_DECODE_REAL ---------- */ /* LzmaDec_DecodeReal_3() can be implemented in external ASM file. 3 - is the code compatibility version of that function for check at link time. */ #define LZMA_DECODE_REAL LzmaDec_DecodeReal_3 /* LZMA_DECODE_REAL() In: RangeCoder is normalized if (p->dicPos == limit) { LzmaDec_TryDummy() was called before to exclude LITERAL and MATCH-REP cases. So first symbol can be only MATCH-NON-REP. And if that MATCH-NON-REP symbol is not END_OF_PAYALOAD_MARKER, then function returns error code. } Processing: first LZMA symbol will be decoded in any case All checks for limits are at the end of main loop, It will decode new LZMA-symbols while (p->buf < bufLimit && dicPos < limit), RangeCoder is still without last normalization when (p->buf < bufLimit) is being checked. Out: RangeCoder is normalized Result: SZ_OK - OK SZ_ERROR_DATA - Error p->remainLen: < kMatchSpecLenStart : normal remain = kMatchSpecLenStart : finished */ #ifdef _LZMA_DEC_OPT int MY_FAST_CALL LZMA_DECODE_REAL(CLzmaDec *p, SizeT limit, const Byte *bufLimit); #else static int MY_FAST_CALL LZMA_DECODE_REAL(CLzmaDec *p, SizeT limit, const Byte *bufLimit) { CLzmaProb *probs = GET_PROBS; unsigned state = (unsigned)p->state; UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3]; unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1; unsigned lc = p->prop.lc; unsigned lpMask = ((unsigned)0x100 << p->prop.lp) - ((unsigned)0x100 >> lc); Byte *dic = p->dic; SizeT dicBufSize = p->dicBufSize; SizeT dicPos = p->dicPos; UInt32 processedPos = p->processedPos; UInt32 checkDicSize = p->checkDicSize; unsigned len = 0; const Byte *buf = p->buf; UInt32 range = p->range; UInt32 code = p->code; do { CLzmaProb *prob; UInt32 bound; unsigned ttt; unsigned posState = CALC_POS_STATE(processedPos, pbMask); prob = probs + IsMatch + COMBINED_PS_STATE; IF_BIT_0(prob) { unsigned symbol; UPDATE_0(prob); prob = probs + Literal; if (processedPos != 0 || checkDicSize != 0) prob += (UInt32)3 * ((((processedPos << 8) + dic[(dicPos == 0 ? dicBufSize : dicPos) - 1]) & lpMask) << lc); processedPos++; if (state < kNumLitStates) { state -= (state < 4) ? state : 3; symbol = 1; #ifdef _LZMA_SIZE_OPT do { NORMAL_LITER_DEC } while (symbol < 0x100); #else NORMAL_LITER_DEC NORMAL_LITER_DEC NORMAL_LITER_DEC NORMAL_LITER_DEC NORMAL_LITER_DEC NORMAL_LITER_DEC NORMAL_LITER_DEC NORMAL_LITER_DEC #endif } else { unsigned matchByte = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; unsigned offs = 0x100; state -= (state < 10) ? 3 : 6; symbol = 1; #ifdef _LZMA_SIZE_OPT do { unsigned bit; CLzmaProb *probLit; MATCHED_LITER_DEC } while (symbol < 0x100); #else { unsigned bit; CLzmaProb *probLit; MATCHED_LITER_DEC MATCHED_LITER_DEC MATCHED_LITER_DEC MATCHED_LITER_DEC MATCHED_LITER_DEC MATCHED_LITER_DEC MATCHED_LITER_DEC MATCHED_LITER_DEC } #endif } dic[dicPos++] = (Byte)symbol; continue; } { UPDATE_1(prob); prob = probs + IsRep + state; IF_BIT_0(prob) { UPDATE_0(prob); state += kNumStates; prob = probs + LenCoder; } else { UPDATE_1(prob); /* // that case was checked before with kBadRepCode if (checkDicSize == 0 && processedPos == 0) return SZ_ERROR_DATA; */ prob = probs + IsRepG0 + state; IF_BIT_0(prob) { UPDATE_0(prob); prob = probs + IsRep0Long + COMBINED_PS_STATE; IF_BIT_0(prob) { UPDATE_0(prob); dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; dicPos++; processedPos++; state = state < kNumLitStates ? 9 : 11; continue; } UPDATE_1(prob); } else { UInt32 distance; UPDATE_1(prob); prob = probs + IsRepG1 + state; IF_BIT_0(prob) { UPDATE_0(prob); distance = rep1; } else { UPDATE_1(prob); prob = probs + IsRepG2 + state; IF_BIT_0(prob) { UPDATE_0(prob); distance = rep2; } else { UPDATE_1(prob); distance = rep3; rep3 = rep2; } rep2 = rep1; } rep1 = rep0; rep0 = distance; } state = state < kNumLitStates ? 8 : 11; prob = probs + RepLenCoder; } #ifdef _LZMA_SIZE_OPT { unsigned lim, offset; CLzmaProb *probLen = prob + LenChoice; IF_BIT_0(probLen) { UPDATE_0(probLen); probLen = prob + LenLow + GET_LEN_STATE; offset = 0; lim = (1 << kLenNumLowBits); } else { UPDATE_1(probLen); probLen = prob + LenChoice2; IF_BIT_0(probLen) { UPDATE_0(probLen); probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); offset = kLenNumLowSymbols; lim = (1 << kLenNumLowBits); } else { UPDATE_1(probLen); probLen = prob + LenHigh; offset = kLenNumLowSymbols * 2; lim = (1 << kLenNumHighBits); } } TREE_DECODE(probLen, lim, len); len += offset; } #else { CLzmaProb *probLen = prob + LenChoice; IF_BIT_0(probLen) { UPDATE_0(probLen); probLen = prob + LenLow + GET_LEN_STATE; len = 1; TREE_GET_BIT(probLen, len); TREE_GET_BIT(probLen, len); TREE_GET_BIT(probLen, len); len -= 8; } else { UPDATE_1(probLen); probLen = prob + LenChoice2; IF_BIT_0(probLen) { UPDATE_0(probLen); probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); len = 1; TREE_GET_BIT(probLen, len); TREE_GET_BIT(probLen, len); TREE_GET_BIT(probLen, len); } else { UPDATE_1(probLen); probLen = prob + LenHigh; TREE_DECODE(probLen, (1 << kLenNumHighBits), len); len += kLenNumLowSymbols * 2; } } } #endif if (state >= kNumStates) { UInt32 distance; prob = probs + PosSlot + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); TREE_6_DECODE(prob, distance); if (distance >= kStartPosModelIndex) { unsigned posSlot = (unsigned)distance; unsigned numDirectBits = (unsigned)(((distance >> 1) - 1)); distance = (2 | (distance & 1)); if (posSlot < kEndPosModelIndex) { distance <<= numDirectBits; prob = probs + SpecPos; { UInt32 m = 1; distance++; do { REV_BIT_VAR(prob, distance, m); } while (--numDirectBits); distance -= m; } } else { numDirectBits -= kNumAlignBits; do { NORMALIZE range >>= 1; { UInt32 t; code -= range; t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */ distance = (distance << 1) + (t + 1); code += range & t; } /* distance <<= 1; if (code >= range) { code -= range; distance |= 1; } */ } while (--numDirectBits); prob = probs + Align; distance <<= kNumAlignBits; { unsigned i = 1; REV_BIT_CONST(prob, i, 1); REV_BIT_CONST(prob, i, 2); REV_BIT_CONST(prob, i, 4); REV_BIT_LAST (prob, i, 8); distance |= i; } if (distance == (UInt32)0xFFFFFFFF) { len = kMatchSpecLenStart; state -= kNumStates; break; } } } rep3 = rep2; rep2 = rep1; rep1 = rep0; rep0 = distance + 1; state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; if (distance >= (checkDicSize == 0 ? processedPos: checkDicSize)) { p->dicPos = dicPos; return SZ_ERROR_DATA; } } len += kMatchMinLen; { SizeT rem; unsigned curLen; SizeT pos = 0; if ((rem = limit - dicPos) == 0) { p->dicPos = dicPos; return SZ_ERROR_DATA; } curLen = ((rem < len) ? (unsigned)rem : len); if (rep0 <= dicPos) pos = dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0); processedPos += (UInt32)curLen; len -= curLen; if (curLen <= dicBufSize - pos) { Byte *dest = dic + dicPos; ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos; const Byte *lim = dest + curLen; dicPos += (SizeT)curLen; do *(dest) = (Byte)*(dest + src); while (++dest != lim); } else { do { dic[dicPos++] = dic[pos]; if (++pos == dicBufSize) pos = 0; } while (--curLen != 0); } } } } while (dicPos < limit && buf < bufLimit); NORMALIZE; p->buf = buf; p->range = range; p->code = code; p->remainLen = (UInt32)len; p->dicPos = dicPos; p->processedPos = processedPos; p->reps[0] = rep0; p->reps[1] = rep1; p->reps[2] = rep2; p->reps[3] = rep3; p->state = (UInt32)state; return SZ_OK; } #endif static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit) { if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart) { Byte *dic = p->dic; SizeT dicPos = p->dicPos; SizeT dicBufSize = p->dicBufSize; unsigned len = (unsigned)p->remainLen; SizeT rep0 = p->reps[0]; /* we use SizeT to avoid the BUG of VC14 for AMD64 */ SizeT rem = limit - dicPos; if (rem < len) len = (unsigned)(rem); if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len) p->checkDicSize = p->prop.dicSize; p->processedPos += (UInt32)len; p->remainLen -= (UInt32)len; while (len != 0) { len--; dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; dicPos++; } p->dicPos = dicPos; } } #define kRange0 0xFFFFFFFF #define kBound0 ((kRange0 >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1)) #define kBadRepCode (kBound0 + (((kRange0 - kBound0) >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1))) #if kBadRepCode != (0xC0000000 - 0x400) #error Stop_Compiling_Bad_LZMA_Check #endif static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit) { do { SizeT limit2 = limit; if (p->checkDicSize == 0) { UInt32 rem = p->prop.dicSize - p->processedPos; if (limit - p->dicPos > rem) limit2 = p->dicPos + rem; if (p->processedPos == 0) if (p->code >= kBadRepCode) return SZ_ERROR_DATA; } RINOK(LZMA_DECODE_REAL(p, limit2, bufLimit)); if (p->checkDicSize == 0 && p->processedPos >= p->prop.dicSize) p->checkDicSize = p->prop.dicSize; LzmaDec_WriteRem(p, limit); } while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart); return 0; } typedef enum { DUMMY_ERROR, /* unexpected end of input stream */ DUMMY_LIT, DUMMY_MATCH, DUMMY_REP } ELzmaDummy; static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize) { UInt32 range = p->range; UInt32 code = p->code; const Byte *bufLimit = buf + inSize; const CLzmaProb *probs = GET_PROBS; unsigned state = (unsigned)p->state; ELzmaDummy res; { const CLzmaProb *prob; UInt32 bound; unsigned ttt; unsigned posState = CALC_POS_STATE(p->processedPos, (1 << p->prop.pb) - 1); prob = probs + IsMatch + COMBINED_PS_STATE; IF_BIT_0_CHECK(prob) { UPDATE_0_CHECK /* if (bufLimit - buf >= 7) return DUMMY_LIT; */ prob = probs + Literal; if (p->checkDicSize != 0 || p->processedPos != 0) prob += ((UInt32)LZMA_LIT_SIZE * ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) + (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc)))); if (state < kNumLitStates) { unsigned symbol = 1; do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100); } else { unsigned matchByte = p->dic[p->dicPos - p->reps[0] + (p->dicPos < p->reps[0] ? p->dicBufSize : 0)]; unsigned offs = 0x100; unsigned symbol = 1; do { unsigned bit; const CLzmaProb *probLit; matchByte += matchByte; bit = offs; offs &= matchByte; probLit = prob + (offs + bit + symbol); GET_BIT2_CHECK(probLit, symbol, offs ^= bit; , ; ) } while (symbol < 0x100); } res = DUMMY_LIT; } else { unsigned len; UPDATE_1_CHECK; prob = probs + IsRep + state; IF_BIT_0_CHECK(prob) { UPDATE_0_CHECK; state = 0; prob = probs + LenCoder; res = DUMMY_MATCH; } else { UPDATE_1_CHECK; res = DUMMY_REP; prob = probs + IsRepG0 + state; IF_BIT_0_CHECK(prob) { UPDATE_0_CHECK; prob = probs + IsRep0Long + COMBINED_PS_STATE; IF_BIT_0_CHECK(prob) { UPDATE_0_CHECK; NORMALIZE_CHECK; return DUMMY_REP; } else { UPDATE_1_CHECK; } } else { UPDATE_1_CHECK; prob = probs + IsRepG1 + state; IF_BIT_0_CHECK(prob) { UPDATE_0_CHECK; } else { UPDATE_1_CHECK; prob = probs + IsRepG2 + state; IF_BIT_0_CHECK(prob) { UPDATE_0_CHECK; } else { UPDATE_1_CHECK; } } } state = kNumStates; prob = probs + RepLenCoder; } { unsigned limit, offset; const CLzmaProb *probLen = prob + LenChoice; IF_BIT_0_CHECK(probLen) { UPDATE_0_CHECK; probLen = prob + LenLow + GET_LEN_STATE; offset = 0; limit = 1 << kLenNumLowBits; } else { UPDATE_1_CHECK; probLen = prob + LenChoice2; IF_BIT_0_CHECK(probLen) { UPDATE_0_CHECK; probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); offset = kLenNumLowSymbols; limit = 1 << kLenNumLowBits; } else { UPDATE_1_CHECK; probLen = prob + LenHigh; offset = kLenNumLowSymbols * 2; limit = 1 << kLenNumHighBits; } } TREE_DECODE_CHECK(probLen, limit, len); len += offset; } if (state < 4) { unsigned posSlot; prob = probs + PosSlot + ((len < kNumLenToPosStates - 1 ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot); if (posSlot >= kStartPosModelIndex) { unsigned numDirectBits = ((posSlot >> 1) - 1); /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */ if (posSlot < kEndPosModelIndex) { prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits); } else { numDirectBits -= kNumAlignBits; do { NORMALIZE_CHECK range >>= 1; code -= range & (((code - range) >> 31) - 1); /* if (code >= range) code -= range; */ } while (--numDirectBits); prob = probs + Align; numDirectBits = kNumAlignBits; } { unsigned i = 1; unsigned m = 1; do { REV_BIT_CHECK(prob, i, m); } while (--numDirectBits); } } } } } NORMALIZE_CHECK; return res; } void LzmaDec_InitDicAndState(CLzmaDec *p, BoolInt initDic, BoolInt initState) { p->remainLen = kMatchSpecLenStart + 1; p->tempBufSize = 0; if (initDic) { p->processedPos = 0; p->checkDicSize = 0; p->remainLen = kMatchSpecLenStart + 2; } if (initState) p->remainLen = kMatchSpecLenStart + 2; } void LzmaDec_Init(CLzmaDec *p) { p->dicPos = 0; LzmaDec_InitDicAndState(p, True, True); } SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) { SizeT inSize = *srcLen; (*srcLen) = 0; *status = LZMA_STATUS_NOT_SPECIFIED; if (p->remainLen > kMatchSpecLenStart) { for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) p->tempBuf[p->tempBufSize++] = *src++; if (p->tempBufSize != 0 && p->tempBuf[0] != 0) return SZ_ERROR_DATA; if (p->tempBufSize < RC_INIT_SIZE) { *status = LZMA_STATUS_NEEDS_MORE_INPUT; return SZ_OK; } p->code = ((UInt32)p->tempBuf[1] << 24) | ((UInt32)p->tempBuf[2] << 16) | ((UInt32)p->tempBuf[3] << 8) | ((UInt32)p->tempBuf[4]); p->range = 0xFFFFFFFF; p->tempBufSize = 0; if (p->remainLen > kMatchSpecLenStart + 1) { SizeT numProbs = LzmaProps_GetNumProbs(&p->prop); SizeT i; CLzmaProb *probs = p->probs; for (i = 0; i < numProbs; i++) probs[i] = kBitModelTotal >> 1; p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; p->state = 0; } p->remainLen = 0; } LzmaDec_WriteRem(p, dicLimit); while (p->remainLen != kMatchSpecLenStart) { int checkEndMarkNow = 0; if (p->dicPos >= dicLimit) { if (p->remainLen == 0 && p->code == 0) { *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK; return SZ_OK; } if (finishMode == LZMA_FINISH_ANY) { *status = LZMA_STATUS_NOT_FINISHED; return SZ_OK; } if (p->remainLen != 0) { *status = LZMA_STATUS_NOT_FINISHED; return SZ_ERROR_DATA; } checkEndMarkNow = 1; } if (p->tempBufSize == 0) { SizeT processed; const Byte *bufLimit; if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) { int dummyRes = LzmaDec_TryDummy(p, src, inSize); if (dummyRes == DUMMY_ERROR) { MyMemcpy(p->tempBuf, src, inSize); p->tempBufSize = (unsigned)inSize; (*srcLen) += inSize; *status = LZMA_STATUS_NEEDS_MORE_INPUT; return SZ_OK; } if (checkEndMarkNow && dummyRes != DUMMY_MATCH) { *status = LZMA_STATUS_NOT_FINISHED; return SZ_ERROR_DATA; } bufLimit = src; } else bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX; p->buf = src; if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0) return SZ_ERROR_DATA; processed = (SizeT)(p->buf - src); (*srcLen) += processed; src += processed; inSize -= processed; } else { unsigned rem = p->tempBufSize, lookAhead = 0; while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize) p->tempBuf[rem++] = src[lookAhead++]; p->tempBufSize = rem; if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) { int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, (SizeT)rem); if (dummyRes == DUMMY_ERROR) { (*srcLen) += (SizeT)lookAhead; *status = LZMA_STATUS_NEEDS_MORE_INPUT; return SZ_OK; } if (checkEndMarkNow && dummyRes != DUMMY_MATCH) { *status = LZMA_STATUS_NOT_FINISHED; return SZ_ERROR_DATA; } } p->buf = p->tempBuf; if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0) return SZ_ERROR_DATA; { unsigned kkk = (unsigned)(p->buf - p->tempBuf); if (rem < kkk) return SZ_ERROR_FAIL; /* some internal error */ rem -= kkk; if (lookAhead < rem) return SZ_ERROR_FAIL; /* some internal error */ lookAhead -= rem; } (*srcLen) += (SizeT)lookAhead; src += lookAhead; inSize -= (SizeT)lookAhead; p->tempBufSize = 0; } } if (p->code != 0) return SZ_ERROR_DATA; *status = LZMA_STATUS_FINISHED_WITH_MARK; return SZ_OK; } SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) { SizeT outSize = *destLen; SizeT inSize = *srcLen; *srcLen = *destLen = 0; for (;;) { SizeT inSizeCur = inSize, outSizeCur, dicPos; ELzmaFinishMode curFinishMode; SRes res; if (p->dicPos == p->dicBufSize) p->dicPos = 0; dicPos = p->dicPos; if (outSize > p->dicBufSize - dicPos) { outSizeCur = p->dicBufSize; curFinishMode = LZMA_FINISH_ANY; } else { outSizeCur = dicPos + outSize; curFinishMode = finishMode; } res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status); src += inSizeCur; inSize -= inSizeCur; *srcLen += inSizeCur; outSizeCur = p->dicPos - dicPos; MyMemcpy(dest, p->dic + dicPos, outSizeCur); dest += outSizeCur; outSize -= outSizeCur; *destLen += outSizeCur; if (res != 0) return res; if (outSizeCur == 0 || outSize == 0) return SZ_OK; } } void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc) { ISzAlloc_Free(alloc, p->probs); p->probs = NULL; } static void LzmaDec_FreeDict(CLzmaDec *p, ISzAllocPtr alloc) { ISzAlloc_Free(alloc, p->dic); p->dic = NULL; } void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr alloc) { LzmaDec_FreeProbs(p, alloc); LzmaDec_FreeDict(p, alloc); } SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size) { UInt32 dicSize; Byte d; if (size < LZMA_PROPS_SIZE) return SZ_ERROR_UNSUPPORTED; else dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24); if (dicSize < LZMA_DIC_MIN) dicSize = LZMA_DIC_MIN; p->dicSize = dicSize; d = data[0]; if (d >= (9 * 5 * 5)) return SZ_ERROR_UNSUPPORTED; p->lc = (Byte)(d % 9); d /= 9; p->pb = (Byte)(d / 5); p->lp = (Byte)(d % 5); return SZ_OK; } static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAllocPtr alloc) { UInt32 numProbs = LzmaProps_GetNumProbs(propNew); if (!p->probs || numProbs != p->numProbs) { LzmaDec_FreeProbs(p, alloc); p->probs = (CLzmaProb *)ISzAlloc_Alloc(alloc, numProbs * sizeof(CLzmaProb)); if (!p->probs) return SZ_ERROR_MEM; p->probs_1664 = p->probs + 1664; p->numProbs = numProbs; } return SZ_OK; } SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc) { CLzmaProps propNew = { 0 }; RINOK(LzmaProps_Decode(&propNew, props, propsSize)); RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); p->prop = propNew; return SZ_OK; } SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc) { CLzmaProps propNew = { 0 }; SizeT dicBufSize; RINOK(LzmaProps_Decode(&propNew, props, propsSize)); RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); { UInt32 dictSize = propNew.dicSize; SizeT mask = ((UInt32)1 << 12) - 1; if (dictSize >= ((UInt32)1 << 30)) mask = ((UInt32)1 << 22) - 1; else if (dictSize >= ((UInt32)1 << 22)) mask = ((UInt32)1 << 20) - 1;; dicBufSize = ((SizeT)dictSize + mask) & ~mask; if (dicBufSize < dictSize) dicBufSize = dictSize; } if (!p->dic || dicBufSize != p->dicBufSize) { LzmaDec_FreeDict(p, alloc); p->dic = (Byte *)ISzAlloc_Alloc(alloc, dicBufSize); if (!p->dic) { LzmaDec_FreeProbs(p, alloc); return SZ_ERROR_MEM; } } p->dicBufSize = dicBufSize; p->prop = propNew; return SZ_OK; } SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAllocPtr alloc) { CLzmaDec p; SRes res; SizeT outSize = *destLen, inSize = *srcLen; *destLen = *srcLen = 0; *status = LZMA_STATUS_NOT_SPECIFIED; if (inSize < RC_INIT_SIZE) return SZ_ERROR_INPUT_EOF; LzmaDec_Construct(&p); RINOK(LzmaDec_AllocateProbs(&p, propData, propSize, alloc)); p.dic = dest; p.dicBufSize = outSize; LzmaDec_Init(&p); *srcLen = inSize; res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status); *destLen = p.dicPos; if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT) res = SZ_ERROR_INPUT_EOF; LzmaDec_FreeProbs(&p, alloc); return res; } thermal_daemon-2.5.12/src/LzmaDec.h000066400000000000000000000155261521301715300170460ustar00rootroot00000000000000// From Intel DPTF github project ESIF/Products/ESIF_CMP/Sources // Fix white spaces from the source. /* LzmaDec.h -- LZMA Decoder 2018-04-21 : Igor Pavlov : Public domain */ #ifndef __LZMA_DEC_H #define __LZMA_DEC_H #include "7zTypes.h" EXTERN_C_BEGIN /* #define _LZMA_PROB32 */ /* _LZMA_PROB32 can increase the speed on some CPUs, but memory usage for CLzmaDec::probs will be doubled in that case */ typedef #ifdef _LZMA_PROB32 UInt32 #else UInt16 #endif CLzmaProb; /* ---------- LZMA Properties ---------- */ #define LZMA_PROPS_SIZE 5 typedef struct _CLzmaProps { Byte lc; Byte lp; Byte pb; Byte _pad_; UInt32 dicSize; } CLzmaProps; /* LzmaProps_Decode - decodes properties Returns: SZ_OK SZ_ERROR_UNSUPPORTED - Unsupported properties */ SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); /* ---------- LZMA Decoder state ---------- */ /* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case. Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */ #define LZMA_REQUIRED_INPUT_MAX 20 typedef struct { /* Don't change this structure. ASM code can use it. */ CLzmaProps prop; CLzmaProb *probs; CLzmaProb *probs_1664; Byte *dic; SizeT dicBufSize; SizeT dicPos; const Byte *buf; UInt32 range; UInt32 code; UInt32 processedPos; UInt32 checkDicSize; UInt32 reps[4]; UInt32 state; UInt32 remainLen; UInt32 numProbs; unsigned tempBufSize; Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; } CLzmaDec; #define LzmaDec_Construct(p) { (p)->dic = NULL; (p)->probs = NULL; } void LzmaDec_Init(CLzmaDec *p); /* There are two types of LZMA streams: - Stream with end mark. That end mark adds about 6 bytes to compressed size. - Stream without end mark. You must know exact uncompressed size to decompress such stream. */ typedef enum { LZMA_FINISH_ANY, /* finish at any point */ LZMA_FINISH_END /* block must be finished at the end */ } ELzmaFinishMode; /* ELzmaFinishMode has meaning only if the decoding reaches output limit !!! You must use LZMA_FINISH_END, when you know that current output buffer covers last bytes of block. In other cases you must use LZMA_FINISH_ANY. If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK, and output value of destLen will be less than output buffer size limit. You can check status result also. You can use multiple checks to test data integrity after full decompression: 1) Check Result and "status" variable. 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize. 3) Check that output(srcLen) = compressedSize, if you know real compressedSize. You must use correct finish mode in that case. */ typedef enum { LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */ LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ LZMA_STATUS_NOT_FINISHED, /* stream was not finished */ LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */ LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */ } ELzmaStatus; /* ELzmaStatus is used only as output value for function call */ /* ---------- Interfaces ---------- */ /* There are 3 levels of interfaces: 1) Dictionary Interface 2) Buffer Interface 3) One Call Interface You can select any of these interfaces, but don't mix functions from different groups for same object. */ /* There are two variants to allocate state for Dictionary Interface: 1) LzmaDec_Allocate / LzmaDec_Free 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs You can use variant 2, if you set dictionary buffer manually. For Buffer Interface you must always use variant 1. LzmaDec_Allocate* can return: SZ_OK SZ_ERROR_MEM - Memory allocation error SZ_ERROR_UNSUPPORTED - Unsupported properties */ SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc); SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr alloc); /* ---------- Dictionary Interface ---------- */ /* You can use it, if you want to eliminate the overhead for data copying from dictionary to some other external buffer. You must work with CLzmaDec variables directly in this interface. STEPS: LzmaDec_Construct() LzmaDec_Allocate() for (each new stream) { LzmaDec_Init() while (it needs more decompression) { LzmaDec_DecodeToDic() use data from CLzmaDec::dic and update CLzmaDec::dicPos } } LzmaDec_Free() */ /* LzmaDec_DecodeToDic The decoding to internal dictionary buffer (CLzmaDec::dic). You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!! finishMode: It has meaning only if the decoding reaches output limit (dicLimit). LZMA_FINISH_ANY - Decode just dicLimit bytes. LZMA_FINISH_END - Stream must be finished after dicLimit. Returns: SZ_OK status: LZMA_STATUS_FINISHED_WITH_MARK LZMA_STATUS_NOT_FINISHED LZMA_STATUS_NEEDS_MORE_INPUT LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK SZ_ERROR_DATA - Data error */ SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); /* ---------- Buffer Interface ---------- */ /* It's zlib-like interface. See LzmaDec_DecodeToDic description for information about STEPS and return results, but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need to work with CLzmaDec variables manually. finishMode: It has meaning only if the decoding reaches output limit (*destLen). LZMA_FINISH_ANY - Decode just destLen bytes. LZMA_FINISH_END - Stream must be finished after (*destLen). */ SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); /* ---------- One Call Interface ---------- */ /* LzmaDecode finishMode: It has meaning only if the decoding reaches output limit (*destLen). LZMA_FINISH_ANY - Decode just destLen bytes. LZMA_FINISH_END - Stream must be finished after (*destLen). Returns: SZ_OK status: LZMA_STATUS_FINISHED_WITH_MARK LZMA_STATUS_NOT_FINISHED LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK SZ_ERROR_DATA - Data error SZ_ERROR_MEM - Memory allocation error SZ_ERROR_UNSUPPORTED - Unsupported properties SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). */ SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAllocPtr alloc); EXTERN_C_END #endif thermal_daemon-2.5.12/src/acpi_thermal_rel_ioct.h000066400000000000000000000061041521301715300220270ustar00rootroot00000000000000/* * acpi_thermal_rel_ioct.h: head file of _ART and _TRT objects * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef __ACPI_ACPI_THERMAL_H #define __ACPI_ACPI_THERMAL_H #include #define ACPI_THERMAL_MAGIC 's' #define ACPI_THERMAL_GET_TRT_LEN _IOR(ACPI_THERMAL_MAGIC, 1, unsigned long) #define ACPI_THERMAL_GET_ART_LEN _IOR(ACPI_THERMAL_MAGIC, 2, unsigned long) #define ACPI_THERMAL_GET_TRT_COUNT _IOR(ACPI_THERMAL_MAGIC, 3, unsigned long) #define ACPI_THERMAL_GET_ART_COUNT _IOR(ACPI_THERMAL_MAGIC, 4, unsigned long) #define ACPI_THERMAL_GET_TRT _IOR(ACPI_THERMAL_MAGIC, 5, unsigned long) #define ACPI_THERMAL_GET_ART _IOR(ACPI_THERMAL_MAGIC, 6, unsigned long) /* * ACPI_THERMAL_GET_PSVT_COUNT = Number of PSVT entries * ACPI_THERMAL_GET_PSVT_LEN = Total return data size (PSVT count x each * PSVT entry size) * ACPI_THERMAL_GET_PSVT = Get the data as an array of psvt_objects */ #define ACPI_THERMAL_GET_PSVT_LEN _IOR(ACPI_THERMAL_MAGIC, 7, unsigned long) #define ACPI_THERMAL_GET_PSVT_COUNT _IOR(ACPI_THERMAL_MAGIC, 8, unsigned long) #define ACPI_THERMAL_GET_PSVT _IOR(ACPI_THERMAL_MAGIC, 9, unsigned long) #ifndef __KERNEL__ #define u64 unsigned long long #endif union art_object { struct { char source_device[8]; /* ACPI single name */ char target_device[8]; /* ACPI single name */ u64 weight; u64 ac0_max_level; u64 ac1_max_level; u64 ac2_max_level; u64 ac3_max_level; u64 ac4_max_level; u64 ac5_max_level; u64 ac6_max_level; u64 ac7_max_level; u64 ac8_max_level; u64 ac9_max_level; }acpi_art_entry; u64 __data[13]; }; union trt_object { struct { char source_device[8]; /* ACPI single name */ char target_device[8]; /* ACPI single name */ u64 influence; u64 sample_period; u64 reserved[4]; } acpi_trt_entry; u64 __data[8]; }; #define ACPI_LIMIT_STR_MAX_LEN 8 #define ACPI_NR_PSVT_ELEMENTS 12 union psvt_object { struct { char source_device[8]; char target_device[8]; u64 priority; u64 sample_period; u64 passive_temp; u64 source_domain; u64 control_knob; union { u64 integer; char string[ACPI_LIMIT_STR_MAX_LEN]; } limit; u64 step_size; u64 limit_coeff; u64 unlimit_coeff; u64 control_knob_type; } acpi_psvt_entry; u64 __data[ACPI_NR_PSVT_ELEMENTS]; }; #ifdef __KERNEL__ int acpi_thermal_rel_misc_device_add(acpi_handle handle); int acpi_thermal_rel_misc_device_remove(acpi_handle handle); #endif #endif /* __ACPI_ACPI_THERMAL_H */ thermal_daemon-2.5.12/src/android_main.cpp000066400000000000000000000176511521301715300205070ustar00rootroot00000000000000/* * android_main.cpp: Thermal Daemon entry point tuned for Android * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * * This is the main entry point for thermal daemon. This has main function * which parses command line arguments, setup logs and starts thermal * engine. */ #include "thermald.h" #include "thd_preference.h" #include "thd_engine.h" #include "thd_engine_adaptive.h" #include "thd_engine_default.h" #include "thd_parse.h" #include #define EXIT_UNSUPPORTED 2 // getdtablesize() is removed from bionic/libc in LPDK*/ // use POSIX alternative available. Otherwise fail # ifdef _POSIX_OPEN_MAX # define getdtablesize() (_POSIX_OPEN_MAX) # endif // for AID_* constatns #include // getdtablesize() is removed from bionic/libc in LPDK*/ // use POSIX alternative available. Otherwise fail # ifdef _POSIX_OPEN_MAX # define getdtablesize() (_POSIX_OPEN_MAX) # endif // poll mode int thd_poll_interval = 4; //in seconds bool thd_ignore_default_control = false; bool workaround_enabled = false; bool disable_active_power = false; bool ignore_critical = false; bool power_floor_enable = false; bool adaptive_perf_enable = false; static int pid_file_handle; // Stop daemon static void daemonShutdown() { if (pid_file_handle) close(pid_file_handle); thd_engine->thd_engine_terminate(); sleep(1); thd_engine.reset(); } // signal handler static void signal_handler(int sig) { switch (sig) { case SIGHUP: thd_log_warn("Received SIGHUP signal.\n"); break; case SIGINT: case SIGTERM: thd_log_info("Daemon exiting\n"); daemonShutdown(); exit(EXIT_SUCCESS); break; default: thd_log_warn("Unhandled signal %s\n", strsignal(sig)); break; } } static void daemonize(char *rundir, char *pidfile) { int pid, sid, i; char str[10]; struct sigaction sig_actions; sigset_t sig_set; if (getppid() == 1) { return; } sigemptyset(&sig_set); sigaddset(&sig_set, SIGCHLD); sigaddset(&sig_set, SIGTSTP); sigaddset(&sig_set, SIGTTOU); sigaddset(&sig_set, SIGTTIN); sigprocmask(SIG_BLOCK, &sig_set, nullptr); sig_actions.sa_handler = signal_handler; sigemptyset(&sig_actions.sa_mask); sig_actions.sa_flags = 0; sigaction(SIGHUP, &sig_actions, nullptr); sigaction(SIGTERM, &sig_actions, nullptr); sigaction(SIGINT, &sig_actions, nullptr); pid = fork(); if (pid < 0) { /* Could not fork */ exit(EXIT_FAILURE); } if (pid > 0) { thd_log_info("Child process created: %d\n", pid); exit(EXIT_SUCCESS); } umask(027); sid = setsid(); if (sid < 0) { exit(EXIT_FAILURE); } /* close all descriptors */ for (i = getdtablesize(); i >= 0; --i) { close(i); } i = open("/dev/null", O_RDWR); dup(i); dup(i); chdir(rundir); csys_fs path(pidfile); if (path.create() == -1) { thd_log_info("Could not create PID lock file %s, exiting\n", pidfile); exit(EXIT_FAILURE); } pid_file_handle = open(pidfile, O_RDWR); if (pid_file_handle == -1) { /* Couldn't open lock file */ thd_log_info("Could not open PID lock file %s, exiting\n", pidfile); exit(EXIT_FAILURE); } /* Try to lock file */ #ifdef LOCKF_SUPPORT if (lockf(pid_file_handle, F_TLOCK, 0) == -1) { #else if (flock(pid_file_handle,LOCK_EX|LOCK_NB) < 0) { #endif /* Couldn't get lock on lock file */ thd_log_info("Couldn't get lock file %d\n", getpid()); exit(EXIT_FAILURE); } thd_log_info("Thermal PID %d\n", getpid()); snprintf(str, sizeof(str), "%d\n", getpid()); write(pid_file_handle, str, strlen(str)); } static void print_usage(FILE* stream, int exit_code) { fprintf(stream, "Usage: thermal-daemon options [ ... ]\n"); fprintf(stream, " --help Display this usage information.\n" " --version Show version.\n" " --no-daemon No daemon.\n" " --poll-interval Poll interval 0 to disable.\n" " --exclusive_control To act as exclusive thermal controller.\n" " --config-file Configuration file to use other than the default config.\n"); exit(exit_code); } int main(int argc, char *argv[]) { int c; int option_index = 0; bool no_daemon = false; bool exclusive_control = false; bool test_mode = false; bool is_privileged_user = false; char *conf_file = nullptr; bool ignore_cpuid_check = false; bool adaptive = false; int ret; const char* const short_options = "hvnp:detc:ia"; static struct option long_options[] = { { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'v' }, { "no-daemon", no_argument, 0, 'n' }, { "poll-interval", required_argument, 0, 'p' }, { "exclusive_control", no_argument, 0, 'e' }, { "test-mode", no_argument, 0, 't' }, { "config-file", required_argument, 0, 'c' }, { "ignore-cpuid-check", no_argument, 0, 'i'}, { "ignore-default-control", no_argument, 0, 'd'}, { "adaptive", no_argument, 0, 'a'}, { nullptr, 0, nullptr, 0 } }; if (argc > 1) { while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) { switch (c) { case 'h': print_usage(stdout, 0); break; case 'v': fprintf(stdout, "1.1\n"); exit(EXIT_SUCCESS); break; case 'n': no_daemon = true; break; case 'p': thd_poll_interval = atoi(optarg); break; case 'e': exclusive_control = true; break; case 't': test_mode = true; break; case 'c': conf_file = optarg; break; case 'i': ignore_cpuid_check = true; break; case 'd': thd_ignore_default_control = true; break; case 'a': adaptive = true; break; case -1: case 0: break; default: break; } } } is_privileged_user = (getuid() == 0) || (getuid() == AID_SYSTEM); if (!is_privileged_user && !test_mode) { thd_log_error("You do not have correct permissions to run thermal daemon!\n"); exit(1); } if (mkdir(TDRUNDIR, 0755) != 0) { if (errno != EEXIST) { fprintf(stderr, "Cannot create '%s': %s\n", TDRUNDIR, strerror(errno)); exit(EXIT_FAILURE); } } mkdir(TDCONFDIR, 0755); // Don't care return value as directory if (!no_daemon) { daemonize((char *) "/data/vendor/thermal-daemon", (char *) "/data/vendor/thermal-daemon/thermald.pid"); } else signal(SIGINT, signal_handler); thd_log_info( "Linux Thermal Daemon is starting mode %d : poll_interval %d :ex_control %d\n", no_daemon, thd_poll_interval, exclusive_control); if (adaptive) { ignore_cpuid_check= true; ret = thd_engine_create_adaptive_engine((bool) ignore_cpuid_check, test_mode); if (ret != THD_SUCCESS) { thd_log_info("--adaptive option failed on this platform\n"); thd_log_info("Ignoring --adaptive option\n"); ret = thd_engine_create_default_engine((bool) ignore_cpuid_check, (bool) exclusive_control, conf_file); } } else { ret = thd_engine_create_default_engine((bool) ignore_cpuid_check, (bool) exclusive_control, conf_file); } if (ret != THD_SUCCESS) { if (ret == THD_ERROR) exit(EXIT_UNSUPPORTED); else exit(EXIT_FAILURE); } #ifdef VALGRIND_TEST // lots of STL lib function don't free memory // when called with exit(). // Here just run for some time and gracefully return. sleep(10); if (pid_file_handle) close(pid_file_handle); thd_engine->thd_engine_terminate(); sleep(1); thd_engine.reset(); #else for (;;) sleep(0xffff); thd_log_info("Linux Thermal Daemon is exiting\n"); #endif return 0; } thermal_daemon-2.5.12/src/main.cpp000066400000000000000000000271301521301715300170000ustar00rootroot00000000000000/* * main.cpp: Thermal Daemon entry point * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * * This is the main entry point for thermal daemon. This has main function * which parses command line arguments, set up dbus server and log related * functions. */ /* This implements main() function. This will parse command line options and * a new instance of cthd_engine object. By default it will create an engine * which uses dts engine, which DTS sensor and use P states to control * temperature, without any configuration. Alternatively if the * thermal-conf.xml has exact UUID match then it can use the zones and * cooling devices defined it to control thermals. This file will allow fine * tune ACPI thermal config or create new thermal config using custom * sensors. * Dbus interface allows user to switch between active/passive thermal controls * if the thermal-conf.xml defines parameters. */ #include #include #include #include "thermald.h" #include "thd_preference.h" #include "thd_engine.h" #include "thd_engine_adaptive.h" #include "thd_engine_default.h" #include "thd_parse.h" #include #if !defined(TD_DIST_VERSION) #define TD_DIST_VERSION PACKAGE_VERSION #endif #define EXIT_UNSUPPORTED 2 extern int thd_dbus_server_init(gboolean (*exit_handler)(void)); // Lock file static int lock_file_handle = -1; static constexpr char lock_file[] = TDRUNDIR "/thermald.pid"; // Default log level static int thd_log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE; // Daemonize or not static gboolean thd_daemonize; static gboolean use_syslog; // Disable dbus static gboolean dbus_enable; // poll mode int thd_poll_interval = 4; //in seconds bool thd_ignore_default_control = false; bool workaround_enabled = false; bool disable_active_power = false; bool ignore_critical = false; bool power_floor_enable = false; bool adaptive_perf_enable = false; // check cpuid static gboolean ignore_cpuid_check = false; gboolean exclusive_control = FALSE; static GMainLoop *g_main_loop; #ifdef GDBUS gint own_id = 0; #endif // g_log handler. All logs will be directed here void thd_logger(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { if (!(thd_log_level & log_level)) return; int syslog_priority; const char *prefix; time_t seconds; switch (log_level) { case G_LOG_LEVEL_ERROR: prefix = "[CRIT]"; syslog_priority = LOG_CRIT; break; case G_LOG_LEVEL_CRITICAL: prefix = "[ERR]"; syslog_priority = LOG_ERR; break; case G_LOG_LEVEL_WARNING: prefix = "[WARN]"; syslog_priority = LOG_WARNING; break; case G_LOG_LEVEL_MESSAGE: prefix = "[MSG]"; syslog_priority = LOG_NOTICE; break; case G_LOG_LEVEL_DEBUG: prefix = "[DEBUG]"; syslog_priority = LOG_DEBUG; break; case G_LOG_LEVEL_INFO: default: prefix = "[INFO]"; syslog_priority = LOG_INFO; break; } seconds = time(nullptr); if (use_syslog) syslog(syslog_priority, "%s", message); else g_print("[%lld]%s%s", (long long) seconds, prefix, message); } void clean_up_lockfile(void) { if (lock_file_handle != -1) { (void) close(lock_file_handle); (void) unlink(lock_file); } } bool check_thermald_running() { lock_file_handle = open(lock_file, O_RDWR | O_CREAT | O_NOFOLLOW, 0600); if (lock_file_handle == -1) { /* Couldn't open lock file */ thd_log_error("Could not open PID lock file %s, exiting\n", lock_file); return false; } /* Try to lock file */ if (lockf(lock_file_handle, F_TLOCK, 0) == -1) { /* Couldn't get lock on lock file */ thd_log_error("Couldn't get lock file %d\n", getpid()); close(lock_file_handle); return true; } return false; } // SIGTERM & SIGINT handler gboolean sig_int_handler(void) { if (thd_engine) thd_engine->thd_engine_terminate(); sleep(1); if (g_main_loop) g_main_loop_quit(g_main_loop); thd_engine.reset(); clean_up_lockfile(); exit(EXIT_SUCCESS); return FALSE; } gboolean log_debug = FALSE; // main function int main(int argc, char *argv[]) { gboolean show_version = FALSE; gboolean log_info = FALSE; gboolean no_daemon = FALSE; gboolean systemd = FALSE; gboolean test_mode = FALSE; gboolean adaptive = FALSE; gboolean ignore_default_control = FALSE; gchar *conf_file = nullptr; gint poll_interval = -1; gboolean success; GOptionContext *opt_ctx; int ret; thd_daemonize = TRUE; use_syslog = TRUE; dbus_enable = FALSE; GOptionEntry options[] = { { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Print thermald version and exit"), nullptr }, { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &no_daemon, N_( "Don't become a daemon: Default is daemon mode"), nullptr }, { "systemd", 0, 0, G_OPTION_ARG_NONE, &systemd, N_( "Assume daemon is started by systemd"), nullptr }, { "loglevel=info", 0, 0, G_OPTION_ARG_NONE, &log_info, N_( "log severity: info level and up"), nullptr }, { "loglevel=debug", 0, 0, G_OPTION_ARG_NONE, &log_debug, N_( "log severity: debug level and up: Max logging"), nullptr }, { "adaptive", 0, 0, G_OPTION_ARG_NONE, &adaptive, N_( "adaptive mode: use adaptive performance tables if available"), nullptr }, { "poll-interval", 0, 0, G_OPTION_ARG_INT, &poll_interval, N_("Poll interval in seconds: Poll for zone temperature changes. " "If want to disable polling set to zero."), nullptr }, { "dbus-enable", 0, 0, G_OPTION_ARG_NONE, &dbus_enable, N_( "Enable Dbus."), nullptr }, { "exclusive-control", 0, 0, G_OPTION_ARG_NONE, &exclusive_control, N_( "Take over thermal control from kernel thermal driver."), nullptr }, { "ignore-cpuid-check", 0, 0, G_OPTION_ARG_NONE, &ignore_cpuid_check, N_("Ignore CPU ID check."), nullptr }, { "config-file", 0, 0, G_OPTION_ARG_STRING, &conf_file, N_( "configuration file"), nullptr }, { "ignore-default-control", 0, 0, G_OPTION_ARG_NONE, &ignore_default_control, N_( "Ignore default CPU temperature control. " "Strictly follow thermal-conf.xml"), nullptr }, { "workaround-enabled", 0, 0, G_OPTION_ARG_NONE, &workaround_enabled, N_( "Enable workarounds for power"), nullptr }, { "disable-active-power", 0, 0, G_OPTION_ARG_NONE, &disable_active_power, N_( "Disable active power settings to reduce thermal impact"), nullptr }, { "ignore-critical-trip", 0, 0, G_OPTION_ARG_NONE, &ignore_critical, N_( "Ignore critical trips for reboot"), nullptr }, { "power-floor-enable", 0, 0, G_OPTION_ARG_NONE, &power_floor_enable, N_( "Handle power floor event"), nullptr }, { nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr } }; if (!g_module_supported()) { fprintf(stderr, "GModules are not supported on your platform!\n"); exit(EXIT_FAILURE); } /* Set locale to be able to use environment variables */ setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, TDLOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); /* Parse options */ opt_ctx = g_option_context_new(nullptr); 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, nullptr); g_option_context_set_summary(opt_ctx, "Thermal daemon monitors temperature sensors and decides the best action\n" "based on the temperature readings and user preferences.\n\n" "Copyright (c) 2022, Intel Corporation\n" "This program comes with ABSOLUTELY NO WARRANTY.\n" "This work is licensed under GPL v2.\n" "Refer to https://github.com/intel/thermal_daemon/blob/master/COPYING."); success = g_option_context_parse(opt_ctx, &argc, &argv, nullptr); 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, TD_DIST_VERSION "\n"); exit(EXIT_SUCCESS); } if (getuid() != 0) { fprintf(stderr, "You must be root to run thermald!\n"); exit(EXIT_FAILURE); } if (g_mkdir_with_parents(TDRUNDIR, 0755) != 0) { fprintf(stderr, "Cannot create '%s': %s", TDRUNDIR, strerror(errno)); exit(EXIT_FAILURE); } if (g_mkdir_with_parents(TDCONFDIR, 0755) != 0) { // Don't care return value as directory fprintf(stderr, "Cannot create '%s': %s", TDCONFDIR, strerror(errno)); } // may already exist if (log_info) { thd_log_level |= G_LOG_LEVEL_INFO; } if (log_debug) { thd_log_level |= G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG; } if (poll_interval >= 0) { fprintf(stdout, "Polling enabled: %d\n", poll_interval); thd_poll_interval = poll_interval; } thd_ignore_default_control = ignore_default_control; openlog("thermald", LOG_PID, LOG_USER | LOG_DAEMON | LOG_SYSLOG); // Don't care return val //setlogmask(LOG_CRIT | LOG_ERR | LOG_WARNING | LOG_NOTICE | LOG_DEBUG | LOG_INFO); thd_daemonize = !no_daemon && !systemd; use_syslog = !no_daemon || systemd; g_log_set_handler(nullptr, G_LOG_LEVEL_MASK, thd_logger, nullptr); if (check_thermald_running()) { thd_log_error( "An instance of thermald is already running, exiting ...\n"); exit(EXIT_FAILURE); } if (!thd_daemonize) { g_unix_signal_add (SIGINT, G_SOURCE_FUNC (sig_int_handler), nullptr); g_unix_signal_add (SIGTERM, G_SOURCE_FUNC (sig_int_handler), nullptr); } // Create a main loop that will dispatch callbacks g_main_loop = g_main_loop_new(nullptr, FALSE); if (g_main_loop == nullptr) { clean_up_lockfile(); thd_log_error("Couldn't create GMainLoop:\n"); return THD_FATAL_ERROR; } if (dbus_enable) thd_dbus_server_init(sig_int_handler); if (thd_daemonize) { printf("Ready to serve requests: Daemonizing.. %d\n", thd_daemonize); thd_log_info( "thermald ver %s: Ready to serve requests: Daemonizing..\n", TD_DIST_VERSION); if (daemon(0, 0) != 0) { clean_up_lockfile(); thd_log_error("Failed to daemonize.\n"); return THD_FATAL_ERROR; } } if (adaptive) { adaptive_perf_enable = true; ret = thd_engine_create_adaptive_engine((bool) ignore_cpuid_check, (bool) test_mode); if (ret != THD_SUCCESS) { adaptive_perf_enable = false; thd_log_info("--adaptive option failed on this platform\n"); thd_log_info("Ignoring --adaptive option\n"); ret = thd_engine_create_default_engine((bool) ignore_cpuid_check, (bool) exclusive_control, conf_file); } } else { ret = thd_engine_create_default_engine((bool) ignore_cpuid_check, (bool) exclusive_control, conf_file); } if (ret != THD_SUCCESS) { clean_up_lockfile(); closelog(); if (ret == THD_ERROR) exit(EXIT_UNSUPPORTED); else exit(EXIT_FAILURE); } // Start service requests on the D-Bus thd_log_debug("Start main loop\n"); g_main_loop_run(g_main_loop); thd_log_warn("Oops g main loop exit..\n"); #ifdef GDBUS g_bus_unown_name (own_id); #endif fprintf(stdout, "Exiting ..\n"); clean_up_lockfile(); closelog(); return 0; } thermal_daemon-2.5.12/src/thd_adaptive_types.h000066400000000000000000000022241521301715300213760ustar00rootroot00000000000000/* * cthd_engine_adaptive.cpp: Adaptive thermal engine * * Copyright (C) 2013 Intel Corporation. All rights reserved. * Copyright 2020 Google LLC * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name Matthew Garrett * */ #ifndef THD_ADAPTIVE_TYPES_H_ #define THD_ADAPTIVE_TYPES_H_ #include #include struct adaptive_target { uint64_t target_id; std::string name; std::string participant; uint64_t domain; std::string code; std::string argument; }; #endif /* THD_ADAPTIVE_TYPES_H_ */ thermal_daemon-2.5.12/src/thd_cdev.cpp000066400000000000000000000420231521301715300176320ustar00rootroot00000000000000/* * thd_cdev.cpp: thermal cooling class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ /* * This is parent class for all cooling devices. Most of the functions * are implemented in interface file except set state. * set_state uses the interface to get the current state, max state and * set the device state. * When state = 0, it causes reduction in the cooling device state * when state = 1, it increments cooling device state * When increments, it goes step by step, unless it finds that the temperature * can't be controlled by previous state with in def_poll_interval, otherwise * it will increase exponentially. * Reduction is always uses step by step to reduce ping pong affect. * */ #include "thd_cdev.h" #include "thd_engine.h" // Clamp to cdev min state or trip specific min if valid int cthd_cdev::thd_clamp_state_min(int _state, int temp_min_state, int temp_max_state) { if (min_state <= max_state) { int _min_state = min_state; if (temp_min_state && temp_min_state > min_state) _min_state = temp_min_state; if (_state < _min_state) _state = _min_state; thd_log_debug("def_min_state:%d curr_min_state:%d _state:%d\n", min_state, temp_min_state, _state); return _state; } int _min_state = min_state; if (temp_min_state && temp_min_state < min_state) _min_state = temp_min_state; if (_state > _min_state) _state = _min_state; thd_log_debug("def_max_state:%d curr_max_state:%d _state:%d\n", max_state, temp_max_state, _state); return _state; } // Clamp to cdev max state or trip specific max if valid int cthd_cdev::thd_clamp_state_max(int _state, int temp_min_state, int temp_max_state) { if (min_state <= max_state) { int _max_state = max_state; if (temp_max_state && temp_max_state < max_state) _max_state = temp_max_state; if (_state > _max_state) _state = _max_state; thd_log_debug("def_max_state:%d curr_max_state:%d _state:%d\n", max_state, temp_max_state, _state); return _state; } int _max_state = max_state; if (temp_max_state && temp_max_state > max_state) _max_state = temp_max_state; if (_state < _max_state) _state = _max_state; thd_log_debug("def_max_state:%d curr_max_state:%d _state:%d\n", min_state, temp_min_state, _state); return _state; } int cthd_cdev::thd_cdev_exponential_controller(int set_point, int target_temp, int temperature, int state, int zone_id, int temp_min_state, int temp_max_state) { int control = state; int _curr_state, _max_state, _state; _max_state = get_max_state(); if (state) { // Get the latest state, which for some devices read the state from the hardware _curr_state = get_curr_state(true); // Clamp the current state to min_state, as we start from min to max for // activation of a cooling device _curr_state = thd_clamp_state_min(_curr_state, temp_min_state, temp_max_state); thd_log_debug( "thd_cdev_set_%d:curr state %d max state %d temp_min:%d temp_max:%d\n", index, _curr_state, _max_state, temp_min_state, temp_max_state); if (inc_val) _state = _curr_state + inc_val; else _state = _curr_state + inc_dec_val; if (trend_increase) { // This means this is a repeat call for activation if (curr_pow == 0) base_pow_state = _curr_state; if (curr_pow < default_max_exponent) ++curr_pow; if (inc_val) _state = base_pow_state + int_2_pow(curr_pow) * inc_val; else _state = base_pow_state + int_2_pow(curr_pow) * inc_dec_val; // Check for the overflow exhaust the valid range if (((inc_val < 0) && (base_pow_state < _state)) || ((inc_val > 0) && (base_pow_state > _state))) _state = max_state; thd_log_info( "cdev index:%d consecutive call, increment exponentially state %d (min %d max %d) (%d:%d)\n", index, _state, min_state, _max_state, base_pow_state, curr_pow); } else { curr_pow = 0; } // Make sure that the state is not beyond max_state _state = thd_clamp_state_max(_state, temp_min_state, temp_max_state); trend_increase = true; thd_log_debug("op->device:%s %d\n", type_str.c_str(), _state); set_curr_state(_state, control); } else { // Get the latest state, which is not the latest from the hardware but last set state to the device _curr_state = get_curr_state(); //_curr_state = thd_clamp_state_max(_curr_state); _curr_state = thd_clamp_state_max(_curr_state, temp_min_state, temp_max_state); thd_log_debug("thd_cdev_set_%d:curr state %d max state %d\n", index, _curr_state, _max_state); curr_pow = 0; trend_increase = false; if (auto_down_adjust == false) { int step = dec_val; // Fallback when dec_val is not provided: pick sign by range direction. if (!step) step = (min_state <= max_state) ? inc_dec_val : -inc_dec_val; // Normalize sign so "reduce cooling" always moves toward min_state. if (min_state <= max_state && step < 0) step = -step; else if (min_state > max_state && step > 0) step = -step; _state = _curr_state - step; // Keep state inside allowed bounds (with temp overrides). _state = thd_clamp_state_min(_state, temp_min_state, temp_max_state); _state = thd_clamp_state_max(_state, temp_min_state, temp_max_state); thd_log_info("op->device:%s %d\n", type_str.c_str(), _state); set_curr_state(_state, control); } else { // Force to effective min, then ensure full range clamp. _state = min_state; _state = thd_clamp_state_min(_state, temp_min_state, temp_max_state); _state = thd_clamp_state_max(_state, temp_min_state, temp_max_state); thd_log_debug("op->device: force min %s %d\n", type_str.c_str(), _state); set_curr_state(_state, control); } } thd_log_info( "Set : threshold:%d, temperature:%d, cdev:%d(%s), curr_state:%d, max_state:%d\n", set_point, temperature, index, type_str.c_str(), get_curr_state(), max_state); thd_log_debug("< limit_2.target_value); } static bool sort_min_max_values_asc(const zone_trip_limits_t& limit_1, const zone_trip_limits_t& limit_2) { return (limit_1._min_state < limit_2._min_state); } static bool sort_min_max_values_dec(const zone_trip_limits_t& limit_1, const zone_trip_limits_t& limit_2) { return (limit_1._max_state > limit_2._max_state); } /* * How the state is set? * If the state set is called before debounce interval, then simply return * success. * It is possible that same cdev is used by two trips in the same zone or * different zone. So if one trip activated a cdev (state = 1), even if the * other trip calls for deactivation (state = 0), the trip shouldn't be * deactivated. This is implemented by adding each activation to push the * zone, trip and target_value to a list if not present and remove on * call for deactivation. After deleting from the list if the list still * has members, that means that this device is still under activation from * some other trip. So in this case simply return without changing the state * of cdev. * * Special handling when a target value is passed: * * In addition a zone trip can call for a particular target value for this * cdev (When it doesn't want the exponential or pid control to use, this * is true when multiple trips wants to use the cdev with different state * values. They way we support this: * When a valid target value is set then we push to the list, sorted using * increasing target values. The passed target value is set, no check is * done here to check the state higher/lower than the current state. This * is done during trip_point_check class. * When off is called for device, then we check if the zone in our list, * if yes, we remove this zone and set the next higher value state from * the list. If this is the current zone is the last then we remove * the zone and set the state to minimum state. */ int cthd_cdev::thd_cdev_set_state(int set_point, int target_temp, int temperature, int hard_target, int state, int zone_id, int trip_id, int target_state_valid, int target_value, pid_param_t *pid_param, cthd_pid &pid, bool force, int min_max_valid, int _min_state, int _max_state) { time_t tm; int ret; if (!state && in_min_state() && zone_trip_limits.size() == 0) { // This means that the there is no device in activated state // There are no entries in the list, cdev is min state and // there is a call for deactivation. return THD_SUCCESS; } time(&tm); thd_log_debug( ">>thd_cdev_set_state temperature %d:%d index:%d state:%d :zone:%d trip_id:%d target_state_valid:%d target_value :%d force:%d min_state:%d max_state:%d\n", target_temp, temperature, index, state, zone_id, trip_id, target_state_valid, target_value, force, _min_state, _max_state); if (state) { bool found = false; bool first_entry = false; if (zone_trip_limits.size() == 0) { first_entry = true; } else { // Search for the zone and trip id in the list for (unsigned int i = 0; i < zone_trip_limits.size(); ++i) { if (zone_trip_limits[i].zone == zone_id && zone_trip_limits[i].trip == trip_id) { found = true; break; } } } // If not found in the list add to the list if (!found) { zone_trip_limits_t limit; limit.zone = zone_id; limit.trip = trip_id; limit.target_state_valid = target_state_valid; limit.target_value = target_value; // Cap the trip min max limits to actual cooling device trips // Here min_state and max_state are for cooling device and // _min_state and _max_state are defined for per trip // in thermal table if (min_max_valid) { if (_min_state == TRIP_PT_INVALID_TARGET_STATE || !_min_state) _min_state = min_state; if (_max_state == TRIP_PT_INVALID_TARGET_STATE || !_max_state) _max_state = max_state; } else { _max_state = 0; _min_state = 0; } limit._max_state = _max_state; limit._min_state = _min_state; limit._min_max_valid = min_max_valid; thd_log_info("Added zone %d trip %d clamp_valid %d clamp %d _min:%d _max:%d\n", limit.zone, limit.trip, limit.target_state_valid, limit.target_value, limit._min_state, limit._max_state); zone_trip_limits.push_back(limit); if (target_state_valid) { if (min_state < max_state) { std::sort(zone_trip_limits.begin(), zone_trip_limits.end(), sort_clamp_values_asc); } else { std::sort(zone_trip_limits.begin(), zone_trip_limits.end(), sort_clamp_values_dec); } } // Target state and min_max_valid or mutually exclusive // in thermal tables. So no need to consolidate min/max with // target // The table will be ordered so that most restrictive is the // first entry if (min_max_valid) { if (min_state > max_state) { std::sort(zone_trip_limits.begin(), zone_trip_limits.end(), sort_min_max_values_dec); } else { std::sort(zone_trip_limits.begin(), zone_trip_limits.end(), sort_min_max_values_asc); } } } zone_trip_limits_t limit; /* The below check to keep static analysis happy. There is no way * zone_trip_limits.size() == 0. * The above !found case ensures atleast size == 1 */ unsigned int zone_trips_size = zone_trip_limits.size(); if (!zone_trips_size) zone_trips_size = 1; limit = zone_trip_limits[zone_trips_size - 1]; target_state_valid = limit.target_state_valid; target_value = limit.target_value; _max_state = limit._max_state; _min_state = limit._min_state; if (!first_entry && target_state_valid && cmp_current_state( map_target_state(target_state_valid, target_value)) <= 0) { thd_log_debug("Already more constraint\n"); return THD_SUCCESS; } if (!force && last_state == state && state && (tm - last_action_time) <= debounce_interval) { thd_log_debug( "Ignore: delay < debounce interval : %d, %d, %d, %d, %d\n", set_point, temperature, index, get_curr_state(), max_state); return THD_SUCCESS; } } else { thd_log_debug("zone_trip_limits.size() %zu\n", (size_t)zone_trip_limits.size()); if (zone_trip_limits.size() > 0) { int length = zone_trip_limits.size(); int _target_state_valid = 0; int i; int erased = 0; zone_trip_limits_t limit; if (length) { limit = zone_trip_limits[zone_trip_limits.size() - 1]; if (limit.zone == zone_id && limit.trip == trip_id) { i = zone_trip_limits.size() - 1; _target_state_valid = limit.target_state_valid; zone_trip_limits.erase(zone_trip_limits.begin() + i); thd_log_info("Erased [%d: %d %d\n", zone_id, trip_id, target_value); erased = 1; } } if (zone_trip_limits.size()) { limit = zone_trip_limits[zone_trip_limits.size() - 1]; target_value = limit.target_value; target_state_valid = limit.target_state_valid; _max_state = limit._max_state; _min_state = limit._min_state; zone_id = limit.zone; trip_id = limit.trip; // If the above loop caused erase of last control // then the next one in the line will be activated. // If not erased, this means that the previous // lower control is still active. if (!erased) { thd_log_debug( "Currently active limit by [%d: %d %d %d]: ignore\n", zone_id, trip_id, target_state_valid, target_value); return THD_SUCCESS; } } else if (_target_state_valid){ // If the deleted entry has a target then on deactivation // set the state to min_state target_value = get_min_state(); } else if (force) { thd_log_info("forced to min_state\n"); target_state_valid = 1; target_value = get_min_state(); } } else { if (force) { thd_log_info("forced to min_state\n"); target_state_valid = 1; target_value = get_min_state(); } else { target_state_valid = 0; } } } last_action_time = tm; last_state = state; curr_state = get_curr_state(); if (curr_state == get_min_state()) { control_begin(); curr_pow = 0; trend_increase = false; } if (target_state_valid) { set_curr_state_raw(target_value, state); curr_state = target_value; ret = THD_SUCCESS; thd_log_info("Set : %d, %d, %d, %d, %d\n", set_point, temperature, index, get_curr_state(), max_state); } else if (hard_target) { ret = get_max_state(); set_curr_state_raw(ret, state); thd_log_info("Set max : %d, %d, %d, %d, %d\n", set_point, temperature, index, get_curr_state(), max_state); ret = THD_SUCCESS; } else if (pid_param && pid_param->valid) { // Handle PID param unique to a trip pid.set_target_temp(target_temp); ret = pid.pid_output(temperature, get_curr_state(true) - get_min_state()); ret += get_min_state(); if (get_min_state() < get_max_state()) { if (ret > get_max_state()) ret = get_max_state(); if (ret < get_min_state()) ret = get_min_state(); } else { if (ret < get_max_state()) ret = get_max_state(); if (ret > get_min_state()) ret = get_min_state(); } set_curr_state_raw(ret, state); thd_log_info("Set pid : %d, %d, %d, %d, %d\n", set_point, temperature, index, get_curr_state(), max_state); ret = THD_SUCCESS; if (state == 0) pid.reset(); } else if (pid_enable) { // Handle PID param common to whole cooling device pid_ctrl.set_target_temp(target_temp); ret = pid_ctrl.pid_output(temperature); ret += get_min_state(); if (get_min_state() < get_max_state()) { if (ret > get_max_state()) ret = get_max_state(); if (ret < get_min_state()) ret = get_min_state(); } else { if (ret < get_max_state()) ret = get_max_state(); if (ret > get_min_state()) ret = get_min_state(); } set_curr_state_raw(ret, state); thd_log_info("Set : %d, %d, %d, %d, %d\n", set_point, temperature, index, get_curr_state(), max_state); ret = THD_SUCCESS; } else { if (state) ret = thd_cdev_exponential_controller(set_point, target_temp, temperature, state, zone_id, _min_state, _max_state); else ret = thd_cdev_exponential_controller(set_point, target_temp, temperature, state, zone_id, 0, 0); } if (curr_state == get_max_state()) { control_end(); } return ret; } int cthd_cdev::thd_cdev_set_min_state(int zone_id, int trip_id) { trend_increase = false; cthd_pid unused; thd_cdev_set_state(0, 0, 0, 0, 0, zone_id, trip_id, 1, min_state, nullptr, unused, true, 0, 0, 0); return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_cdev.h000066400000000000000000000157671521301715300173160ustar00rootroot00000000000000/* * thd_cdev.h: thermal cooling class interface * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_H #define THD_CDEV_H #include #include #include "thd_common.h" #include "thd_sys_fs.h" #include "thd_preference.h" #include "thd_pid.h" #include "thd_adaptive_types.h" typedef struct _zone_trip_limits{ int zone; int trip; int target_state_valid; int target_value; int _min_state; int _max_state; int _min_max_valid; _zone_trip_limits() { zone = 0; trip = 0; target_state_valid = 0; target_value = 0; _min_state = _max_state = _min_max_valid = 0; } _zone_trip_limits(const _zone_trip_limits& x) = default; ~_zone_trip_limits() {} _zone_trip_limits& operator=(const _zone_trip_limits& x) = default; } zone_trip_limits_t; #define ZONE_TRIP_LIMIT_COUNT 12 class cthd_cdev { protected: int index; csys_fs cdev_sysfs; unsigned int trip_point; int max_state; int min_state; int curr_state; int curr_pow; int base_pow_state; int inc_dec_val; bool auto_down_adjust; bool read_back; std::string type_str; std::string alias_str; int debounce_interval; time_t last_action_time; bool trend_increase; bool pid_enable; cthd_pid pid_ctrl; int last_state; std::vector zone_trip_limits; std::string write_prefix; int inc_val; int dec_val; private: unsigned int int_2_pow(int pow) { int i; int _pow = 1; for (i = 0; i < pow; ++i) _pow = _pow * 2; return _pow; } int thd_cdev_exponential_controller(int set_point, int target_temp, int temperature, int state, int arg, int temp_min_state = 0, int temp_max_state = 0); int thd_clamp_state_min(int _state, int temp_min_state = 0, int temp_max_state = 0); int thd_clamp_state_max(int _state, int temp_min_state = 0, int temp_max_state = 0); public: static constexpr int default_debounce_interval = 2; // In seconds static constexpr int default_max_exponent = 20; // Max 2 power (x) is raised cthd_cdev(unsigned int _index, std::string control_path) : index(_index), cdev_sysfs(std::move(control_path)), trip_point(0), max_state( 0), min_state(0), curr_state(0), curr_pow(0), base_pow_state( 0), inc_dec_val(1), auto_down_adjust(false), read_back( true), debounce_interval(default_debounce_interval), last_action_time( 0), trend_increase(false), pid_enable(false), pid_ctrl(), last_state( 0), write_prefix(""), inc_val(0), dec_val(0) { } virtual ~cthd_cdev() { } virtual int thd_cdev_set_state(int set_point, int target_temp, int temperature, int hard_target, int state, int zone_id, int trip_id, int target_state_valid, int target_value, pid_param_t *pid_param, cthd_pid &pid, bool force, int min_max_valid, int _min_state, int _max_state); virtual int thd_cdev_set_min_state(int zone_id, int trip_id); virtual void thd_cdev_set_min_state_param(int arg) { min_state = arg; } virtual void thd_cdev_set_max_state_param(int arg) { max_state = arg; } virtual void thd_cdev_set_read_back_param(bool arg) { read_back = arg; } virtual int thd_cdev_get_index() { return index; } virtual int init() { return 0; } ; virtual int control_begin() { if (pid_enable) { pid_ctrl.reset(); } return 0; } ; virtual int control_end() { return 0; } ; virtual void set_curr_state(int state, int arg) { } virtual void set_curr_state_raw(int state, int arg) { if (state > max_state) state = max_state; if (state < min_state) state = min_state; set_curr_state(state, arg); } virtual int get_curr_state() { return curr_state; } virtual int get_curr_state(bool read_again) { return curr_state; } virtual int get_min_state() { return min_state; } virtual int get_max_state() { return max_state; } virtual int get_phy_max_state() { return max_state; } virtual int update() { return 0; } ; virtual void set_inc_dec_value(int value) { inc_dec_val = value; } virtual void set_inc_value(int value) { inc_val = value; } virtual void set_dec_value(int value) { dec_val = value; } virtual void set_down_adjust_control(bool value) { auto_down_adjust = value; } virtual int map_target_state(int target_valid, int target_state) { return target_state; } virtual void set_adaptive_target(struct adaptive_target &target) {}; void set_debounce_interval(int interval) { debounce_interval = interval; } void set_min_state(int _min_state) { min_state = _min_state; } void set_max_state(int _max_state) { max_state = _max_state; } bool in_min_state() { if ((min_state < max_state && get_curr_state() <= min_state) || (min_state > max_state && get_curr_state() >= min_state)) return true; return false; } bool in_max_state() { if ((min_state < max_state && get_curr_state() >= get_max_state()) || (min_state > max_state && get_curr_state() <= get_max_state())) return true; return false; } int cmp_current_state(int state) { if (get_curr_state() == state) return 0; if (min_state < max_state) { if (state > get_curr_state()) return 1; else return -1; } if (min_state > max_state) { if (state > get_curr_state()) return -1; else return 1; } return 0; } std::string get_cdev_type() { return type_str; } std::string get_cdev_alias() { return alias_str; } const std::string& get_base_path() { return cdev_sysfs.get_base_path(); } void set_cdev_type(std::string _type_str) { type_str = std::move(_type_str); } void set_cdev_alias(std::string _alias_str) { alias_str = std::move(_alias_str); } void set_pid_param(double kp, double ki, double kd) { pid_ctrl.kp = kp; pid_ctrl.ki = ki; pid_ctrl.kd = kd; thd_log_info("set_pid_param %d [%g.%g,%g]\n", index, kp, ki, kd); } void enable_pid() { thd_log_info("PID control enabled %d\n", index); pid_enable = true; } void thd_cdev_set_write_prefix(std::string prefix) { write_prefix = std::move(prefix); } void cdev_dump() { if (inc_val || dec_val){ thd_log_info("%d: %s, C:%d MN: %d MX:%d Inc ST:%d Dec ST:%d pt:%s rd_bk %d\n", index, type_str.c_str(), curr_state, min_state, max_state, inc_val, dec_val, get_base_path().c_str(), read_back); } else { thd_log_info("%d: %s, C:%d MN: %d MX:%d ST:%d pt:%s rd_bk %d\n", index, type_str.c_str(), curr_state, min_state, max_state, inc_dec_val, get_base_path().c_str(), read_back); } } }; #endif thermal_daemon-2.5.12/src/thd_cdev_backlight.cpp000066400000000000000000000070761521301715300216530ustar00rootroot00000000000000/* * thd_cdev_backlight.cpp: thermal backlight cooling implementation * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_cdev_backlight.h" static constexpr int MAX_BACKLIGHT_DEV = 4; static constexpr const char *backlight_devices[MAX_BACKLIGHT_DEV] = { "/sys/class/backlight/intel_backlight/", "/sys/class/backlight/acpi_video0/", "/sys/class/leds/lcd-backlight/", "/sys/class/backlight/lcd-backlight/" }; cthd_cdev_backlight::cthd_cdev_backlight(unsigned int _index, int _cpu_index) : cthd_cdev(_index, backlight_devices[0]), ref_backlight_state(0), min_back_light(0) { int active_device = 0; do { cdev_sysfs.update_path(backlight_devices[active_device]); if (update() == THD_SUCCESS) break; active_device++; } while (active_device < MAX_BACKLIGHT_DEV); } int cthd_cdev_backlight::update() { int ret; if (cdev_sysfs.exists()) { max_state = 0; // Initialize before read ret = cdev_sysfs.read("max_brightness", &max_state); if (ret < 0) return ret; } if (max_state <= 0) return THD_ERROR; set_inc_dec_value(max_state * (float) 10 / 100); // Don't let backlight less than min_backlight_percent percent min_back_light = max_state * min_backlight_percent /100; return THD_SUCCESS; } int cthd_cdev_backlight::map_target_state(int target_valid, int target_state) { if (!target_valid) return target_state; if (target_state > max_state) return 0; return max_state - target_state; } void cthd_cdev_backlight::set_curr_state(int state, int arg) { int ret; int backlight_val; /* * When state > 0, then we need to reduce backlight. But we should start * from backlight which triggered thermal condition. That is here stored * in ref_backlight_state. * When the first state more than 0 is called, then this variable is * updated with the current backlight value. When the state == 0, * it is restored. */ if (state == 0) { if (ref_backlight_state) { thd_log_debug("LCD restore original %d\n", ref_backlight_state); ret = cdev_sysfs.write("brightness", ref_backlight_state); if (ret < 0) { thd_log_warn("Failed to write brightness\n"); return; } ref_backlight_state = 0; } curr_state = state; return; } if (ref_backlight_state == 0 && state == inc_dec_val) { // First time enter to throttle LCD after normal or state == 0 // Store the current backlight ret = cdev_sysfs.read("brightness", &ref_backlight_state); if (ret < 0) return; thd_log_debug("LCD ref state is %d\n", ref_backlight_state); } backlight_val = ref_backlight_state - state; if (backlight_val <= min_back_light) { thd_log_debug("LCD reached min state\n"); backlight_val = min_back_light; } ret = cdev_sysfs.write("brightness", backlight_val); if (ret < 0) { thd_log_warn("Failed to write brightness\n"); return; } curr_state = state; } thermal_daemon-2.5.12/src/thd_cdev_backlight.h000066400000000000000000000025511521301715300213110ustar00rootroot00000000000000/* * thd_cdev_backlight.h: thermal backlight cooling interface * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef SRC_THD_CDEV_BACKLIGHT_H_ #define SRC_THD_CDEV_BACKLIGHT_H_ #include "thd_cdev.h" class cthd_cdev_backlight: public cthd_cdev { private: int ref_backlight_state; int min_back_light; public: static constexpr int min_backlight_percent = 25; // max percent from max cthd_cdev_backlight(unsigned int _index, int _cpu_index); void set_curr_state(int state, int arg) override; int update() override; int map_target_state(int target_valid, int target_state) override; }; #endif /* SRC_THD_CDEV_BACKLIGHT_H_ */ thermal_daemon-2.5.12/src/thd_cdev_cpufreq.cpp000066400000000000000000000140571521301715300213650ustar00rootroot00000000000000/* * thd_cdev_pstates.cpp: thermal cooling class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ /* Control P states using cpufreq. Each step reduces to next lower frequency * */ #include "thd_cdev_cpufreq.h" #include "thd_engine.h" int cthd_cdev_cpufreq::init() { // Get number of CPUs if (cdev_sysfs.exists("present")) { std::string count_str; size_t p0 = 0, p1; cdev_sysfs.read("present", count_str); p1 = count_str.find_first_of('-', p0); if (p1 == std::string::npos) return THD_ERROR; std::string token1 = count_str.substr(p0, p1 - p0); if (token1.empty()) return THD_ERROR; std::istringstream iss1(token1); if (!(iss1 >> cpu_start_index) || !iss1.eof()) { thd_log_warn("Invalid CPU start index format\n"); return THD_ERROR; } if (cpu_start_index < 0 || cpu_start_index > 63) { thd_log_warn("CPU start index out of range: %d\n", cpu_start_index); return THD_ERROR; } if ((p1 + 1) >= count_str.size()) return THD_ERROR; std::string token2 = count_str.substr(p1 + 1); if (token2.empty()) return THD_ERROR; std::istringstream iss2(token2); if (!(iss2 >> cpu_end_index)) { thd_log_warn("Invalid CPU end index format\n"); return THD_ERROR; } iss2 >> std::ws; if (!iss2.eof()) { thd_log_warn("Invalid CPU end index trailing characters\n"); return THD_ERROR; } if ((cpu_end_index <= 0) || (cpu_end_index < cpu_start_index) || cpu_end_index > 63) return THD_ERROR; } else { return THD_ERROR; } thd_log_debug("pstate CPU present %d-%d\n", cpu_start_index, cpu_end_index); // Get list of available frequencies for each CPU // Assuming every core supports same sets of frequencies, so // just reading for cpu0 std::vector _cpufreqs; if (cdev_sysfs.exists("cpu0/cpufreq/scaling_available_frequencies")) { std::string p = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies"; std::ifstream f(p.c_str(), std::fstream::in); if (f.fail()) return -EINVAL; while (!f.eof()) { std::string token; f >> token; if (!f.bad()) { if (!token.empty()) _cpufreqs.push_back(std::move(token)); } } f.close(); } else return THD_ERROR; // Check scaling max frequency and min frequency // Remove frequencies above and below this in the freq list // The available list contains these frequencies even if they are not allowed int scaling_min_frequency = 0; int scaling_max_frequency = 0; for (int i = cpu_start_index; i <= cpu_end_index; ++i) { std::ostringstream str; int freq_int = 0; str << "cpu" << i << "/cpufreq/scaling_min_freq"; if (cdev_sysfs.exists(str.str())) { int ret = cdev_sysfs.read(str.str(), &freq_int); // Only use freq_int if read returned data (ret > 0) if (ret > 0 && (scaling_min_frequency == 0 || freq_int < scaling_min_frequency)) scaling_min_frequency = freq_int; } } for (int i = cpu_start_index; i <= cpu_end_index; ++i) { std::ostringstream str; int freq_int = 0; str << "cpu" << i << "/cpufreq/scaling_max_freq"; if (cdev_sysfs.exists(str.str())) { int ret = cdev_sysfs.read(str.str(), &freq_int); // Only use freq_int if read returned data (ret > 0) if (ret > 0 && (scaling_max_frequency == 0 || freq_int > scaling_max_frequency)) scaling_max_frequency = freq_int; } } thd_log_debug("cpu freq max %d min %d\n", scaling_max_frequency, scaling_min_frequency); for (unsigned int i = 0; i < _cpufreqs.size(); ++i) { thd_log_debug("cpu freq Add %d: %s\n", i, _cpufreqs[i].c_str()); int freq_int = 0; std::istringstream iss(_cpufreqs[i]); if (!(iss >> freq_int) || !iss.eof()) { thd_log_warn("Invalid frequency format: %s\n", _cpufreqs[i].c_str()); continue; } if (freq_int >= scaling_min_frequency && freq_int <= scaling_max_frequency) { add_frequency(freq_int); } } for (unsigned int i = 0; i < cpufreqs.size(); ++i) { thd_log_debug("cpu freq %d: %d\n", i, cpufreqs[i]); } if (cpufreqs.size()) max_state = cpufreqs.size() - 1; pstate_active_freq_index = 0; return THD_SUCCESS; } void cthd_cdev_cpufreq::add_frequency(unsigned int freq_int) { if (cpufreqs.empty() || cpufreqs.at(0) > (int) freq_int) cpufreqs.push_back(freq_int); else { std::vector::iterator it; it = cpufreqs.begin(); cpufreqs.insert(it, freq_int); } } void cthd_cdev_cpufreq::set_curr_state(int state, int arg) { if (state >=0 && state < (int) cpufreqs.size()) { thd_log_debug("cpu freq set_curr_stat %d: %d\n", state, cpufreqs[state]); if (cpu_index == -1) { for (int i = cpu_start_index; i <= cpu_end_index; ++i) { std::ostringstream str; str << "cpu" << i << "/cpufreq/scaling_max_freq"; if (cdev_sysfs.exists(str.str())) { std::ostringstream speed; speed << cpufreqs[state]; cdev_sysfs.write(str.str(), speed.str()); } pstate_active_freq_index = state; curr_state = state; } } else { if (thd_engine->apply_cpu_operation(cpu_index)) { std::ostringstream str; str << "cpu" << cpu_index << "/cpufreq/scaling_max_freq"; if (cdev_sysfs.exists(str.str())) { std::ostringstream speed; speed << cpufreqs[state]; cdev_sysfs.write(str.str(), speed.str()); } pstate_active_freq_index = state; curr_state = state; } } } } int cthd_cdev_cpufreq::get_max_state() { return cpufreqs.size() - 1; } int cthd_cdev_cpufreq::update() { return init(); } thermal_daemon-2.5.12/src/thd_cdev_cpufreq.h000066400000000000000000000031321521301715300210220ustar00rootroot00000000000000/* * thd_cdev_pstates.h: thermal cooling class interface * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_PSTATES_H_ #define THD_CDEV_PSTATES_H_ #include #include #include "thd_cdev.h" class cthd_cdev_cpufreq: public cthd_cdev { private: int cpu_start_index; int cpu_end_index; std::vector cpufreqs; int pstate_active_freq_index; std::string last_governor; int cpu_index; void add_frequency(unsigned int freq_int); public: cthd_cdev_cpufreq(unsigned int _index, int _cpu_index) : cthd_cdev(_index, "/sys/devices/system/cpu/"), cpu_start_index(0), cpu_end_index( 0), pstate_active_freq_index(0), last_governor( ""), cpu_index(_cpu_index) { } int init() override; void set_curr_state(int state, int arg) override; int get_max_state() override; int update() override; }; #endif /* THD_CDEV_PSTATES_H_ */ thermal_daemon-2.5.12/src/thd_cdev_gen_sysfs.cpp000066400000000000000000000037441521301715300217210ustar00rootroot00000000000000/* * cthd_sysfs_gen_sysfs.cpp: thermal cooling class interface * for non thermal cdev sysfs * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_cdev_gen_sysfs.h" #include "thd_util.h" int cthd_gen_sysfs_cdev::update() { if (cdev_sysfs.exists()) { std::string base_path = cdev_sysfs.get_base_path(); if (!starts_with(base_path, "/sys")) { thd_log_debug( "cthd_gen_sysfs_cdev::update: Invalid sysfs path or allowed path %s\n", base_path.c_str()); return THD_ERROR; } curr_state = 0; // Initialize before read int ret = cdev_sysfs.read("", &curr_state); if (ret < 0) return ret; min_state = max_state = curr_state; } else { thd_log_info( "cthd_gen_sysfs_cdev::update: sysfs path does not exist %s\n", cdev_sysfs.get_base_path().c_str()); return THD_ERROR; } return THD_SUCCESS; } void cthd_gen_sysfs_cdev::set_curr_state(int state, int arg) { std::ostringstream state_str; if (write_prefix.length()) state_str << write_prefix; state_str << state; thd_log_debug("set cdev state index %d state %d %s\n", index, state, state_str.str().c_str()); cdev_sysfs.write("", state_str.str()); curr_state = state; } void cthd_gen_sysfs_cdev::set_curr_state_raw(int state, int arg) { set_curr_state(state, arg); } thermal_daemon-2.5.12/src/thd_cdev_gen_sysfs.h000066400000000000000000000024601521301715300213600ustar00rootroot00000000000000/* * cthd_sysfs_gen_sysfs.h: thermal cooling class interface * for non thermal cdev sysfs * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_GEN_SYSFS_H_ #define THD_CDEV_GEN_SYSFS_H_ #include "thd_cdev.h" class cthd_gen_sysfs_cdev: public cthd_cdev { protected: public: cthd_gen_sysfs_cdev(unsigned int _index, std::string control_path) : cthd_cdev(_index, std::move(control_path)) { } void set_curr_state(int state, int arg) override; void set_curr_state_raw(int state, int arg) override; int update() override; }; #endif /* THD_CDEV_GEN_SYSFS_H_ */ thermal_daemon-2.5.12/src/thd_cdev_intel_pstate_driver.cpp000066400000000000000000000074671521301715300237750ustar00rootroot00000000000000/* * thd_sysfs_intel_pstate_driver.cpp: thermal cooling class implementation * using Intel p state driver * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_cdev_intel_pstate_driver.h" /* * This implementation allows controlling get max state and * set current state of using Intel P state driver * P state drives uses a percent count. 100% means full * performance, setting anything lower limits performance. * Each lower state reduces performance by unit value. * unit value is calculated using number of possible p states * . Each step reduces bye one p state. * Contents of "/sys/devices/system/cpu/intel_pstate/" max_perf_pct, min_perf_pct, no_turbo */ void cthd_intel_p_state_cdev::set_curr_state(int state, int arg) { std::ostringstream tc_state_dev; int new_state; tc_state_dev << "/max_perf_pct"; if (cdev_sysfs.exists(tc_state_dev.str())) { std::ostringstream state_str; if (state == 0) new_state = 100; else { new_state = 100 - (state + min_compensation) * unit_value; } state_str << new_state; thd_log_debug("set cdev state index %d state %d percent %d\n", index, state, new_state); if (new_state <= turbo_disable_percent) set_turbo_disable_status(true); else set_turbo_disable_status(false); if (cdev_sysfs.write(tc_state_dev.str(), state_str.str()) < 0) curr_state = (state == 0) ? 0 : max_state; else curr_state = state; } else curr_state = (state == 0) ? 0 : max_state; } void cthd_intel_p_state_cdev::set_turbo_disable_status(bool enable) { std::ostringstream tc_state_dev; if (enable == turbo_status) { return; } tc_state_dev << "/no_turbo"; if (enable) { cdev_sysfs.write(tc_state_dev.str(), "1"); thd_log_info("turbo disabled\n"); } else { cdev_sysfs.write(tc_state_dev.str(), "0"); thd_log_info("turbo enabled\n"); } turbo_status = enable; } int cthd_intel_p_state_cdev::get_max_state() { return max_state; } int cthd_intel_p_state_cdev::map_target_state(int target_valid, int target_state) { if (!target_valid) return target_state; if (target_state > 100) return 0; return (100 - target_state) / unit_value; } int cthd_intel_p_state_cdev::update() { std::ostringstream tc_state_dev; std::ostringstream status_attr; status_attr << "/status"; if (cdev_sysfs.exists(status_attr.str())) { std::string status_str; int ret; ret = cdev_sysfs.read(status_attr.str(), status_str); if (ret >= 0 && status_str != "active") { thd_log_info("intel pstate is not in active mode\n"); } } tc_state_dev << "/max_perf_pct"; if (cdev_sysfs.exists(tc_state_dev.str())) { curr_state = 0; // Initialize before read int ret = cdev_sysfs.read(tc_state_dev.str(), &curr_state); if (ret < 0) return ret; } else { return THD_ERROR; } thd_log_info("Use Default pstate drv settings\n"); max_state = default_max_state; min_compensation = 0; unit_value = 100.0 / max_state; curr_state = 0; thd_log_debug( "cooling dev index:%d, curr_state:%d, max_state:%d, unit:%f, min_com:%d, type:%s\n", index, curr_state, max_state, unit_value, min_compensation, type_str.c_str()); return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_cdev_intel_pstate_driver.h000066400000000000000000000033051521301715300234250ustar00rootroot00000000000000/* * thd_sysfs_intel_pstate_driver.h: thermal cooling class interface * using Intel p state driver * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_INTEL_PSATATE_DRIVER_H_ #define THD_CDEV_INTEL_PSATATE_DRIVER_H_ #include "thd_cdev.h" class cthd_intel_p_state_cdev: public cthd_cdev { private: float unit_value; int min_compensation; bool turbo_status; void set_turbo_disable_status(bool enable); public: static constexpr int intel_pstate_limit_ratio = 2; static constexpr int default_max_state = 10; static constexpr int turbo_disable_percent = 70; cthd_intel_p_state_cdev(unsigned int _index) : cthd_cdev(_index, "/sys/devices/system/cpu/intel_pstate/"), unit_value( 1), min_compensation(0), turbo_status(false) { } ; void set_curr_state(int state, int arg) override; int get_max_state() override; int update() override; int map_target_state(int target_valid, int target_state) override; }; #endif /* THD_CDEV_INTEL_PSATATE_DRIVER_H_ */ thermal_daemon-2.5.12/src/thd_cdev_order_parser.cpp000066400000000000000000000060321521301715300224010ustar00rootroot00000000000000/* * thd_cdev_order_parser.cpp: Specify cdev order * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_cdev_order_parser.h" #include "thd_sys_fs.h" #include "thd_util.h" static constexpr int thd_xml_parse_options = XML_PARSE_NONET | XML_PARSE_NOERROR | XML_PARSE_NOWARNING; cthd_cdev_order_parse::cthd_cdev_order_parse() : doc(nullptr), root_element(nullptr) { std::string name = TDCONFDIR; filename = name + "/" "thermal-cpu-cdev-order.xml"; } int cthd_cdev_order_parse::parser_init() { int fd = open_validated_xml_file(filename); if (fd < 0) return THD_ERROR; doc = xmlReadFd(fd, filename.c_str(), nullptr, thd_xml_parse_options); close(fd); if (doc == nullptr) { thd_log_msg("error: could not parse file %s\n", filename.c_str()); return THD_ERROR; } if (doc->intSubset != nullptr || doc->extSubset != nullptr) { thd_log_warn("Config file %s must not contain a DTD\n", filename.c_str()); xmlFreeDoc(doc); doc = nullptr; return THD_ERROR; } root_element = xmlDocGetRootElement(doc); if (root_element == nullptr) { thd_log_warn("error: could not get root element\n"); return THD_ERROR; } return THD_SUCCESS; } int cthd_cdev_order_parse::start_parse() { parse(root_element, doc); return THD_SUCCESS; } void cthd_cdev_order_parse::parser_deinit() { xmlFreeDoc(doc); } int cthd_cdev_order_parse::parse_new_cdev(xmlNode * a_node, xmlDoc *doc) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { tmp_value = (char *) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { thd_log_info("node type: Element, name: %s value: %s\n", cur_node->name, tmp_value); cdev_order_list.emplace_back(tmp_value); xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_cdev_order_parse::parse(xmlNode * a_node, xmlDoc *doc) { xmlNode *cur_node = nullptr; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { if (!thd_strcasecmp_n((const char*) cur_node->name, "CoolingDeviceOrder")) { parse_new_cdev(cur_node->children, doc); } } } return THD_SUCCESS; } int cthd_cdev_order_parse::get_order_list(std::vector &list) { list = cdev_order_list; return 0; } thermal_daemon-2.5.12/src/thd_cdev_order_parser.h000066400000000000000000000026221521301715300220470ustar00rootroot00000000000000/* * thd_cdev_order_parser.h: Specify cdev order * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_ORDER_PARSE_H #define THD_CDEV_ORDER_PARSE_H #include #include #include #include #include "thermald.h" class cthd_cdev_order_parse { private: xmlDoc *doc; xmlNode *root_element; std::string filename; std::vector cdev_order_list; int parse(xmlNode * a_node, xmlDoc *doc); int parse_new_cdev(xmlNode * a_node, xmlDoc *doc); public: cthd_cdev_order_parse(); int parser_init(); void parser_deinit(); int start_parse(); int get_order_list(std::vector &list); }; #endif thermal_daemon-2.5.12/src/thd_cdev_rapl.cpp000066400000000000000000000412521521301715300206530ustar00rootroot00000000000000/* * cthd_cdev_rapl.cpp: thermal cooling class implementation * using RAPL * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_cdev_rapl.h" #include "thd_engine.h" /* This uses Intel RAPL driver to cool the system. RAPL driver show * mas thermal spec power in max_state. Each state can compensate * rapl_power_dec_percent, from the max state. * */ void cthd_sysfs_cdev_rapl::set_curr_state(int state, int control) { int new_state = state, ret; if (bios_locked) { if (state <= inc_dec_val) curr_state = min_state; else curr_state = max_state; return; } // If request to set a state which less than max_state i.e. lowest rapl power limit // then limit to the max_state. if (state < max_state) new_state = max_state; // If the state is more or equal to min_state, means that more than the // max rapl power limit, restore the power limit to min_state or // whatever the power on limit. Also make the rapl limit enforcement // to disabled, also restore power on time window. if (new_state >= min_state) { if (power_on_constraint_0_pwr) new_state = power_on_constraint_0_pwr; else new_state = min_state; curr_state = min_state; // If disabled during power on, disable if (!power_on_enable_status) rapl_update_enable_status(0); rapl_update_time_window(power_on_constraint_0_time_window); constrained = false; } else if (control) { if (!constrained) { // If it is the first time to activate this device, set the enabled flag // and set the time window. if (pl0_min_window) rapl_update_time_window(pl0_min_window); else rapl_update_time_window(def_rapl_time_window); // Set enable flag only if it was disabled if (!power_on_enable_status) rapl_update_enable_status(1); constrained = true; } } thd_log_info("set cdev state index %d state %d wr:%d\n", index, state, new_state); ret = rapl_update_pl1(new_state); if (ret < 0) { curr_state = (state == 0) ? 0 : max_state; if (ret == -ENODATA) { thd_log_info("powercap RAPL is BIOS locked, cannot update\n"); bios_locked = true; } } curr_state = new_state; } void cthd_sysfs_cdev_rapl::set_curr_state_raw(int state, int arg) { set_curr_state(state, arg); } // Return the last state or power set during set_curr_state int cthd_sysfs_cdev_rapl::get_curr_state() { return curr_state; } // Return the current power, using this the controller can choose the next state int cthd_sysfs_cdev_rapl::get_curr_state(bool read_again) { if (dynamic_phy_max_enable) { int pl1, power; thd_engine->rapl_power_meter.rapl_start_measure_power(); power = thd_engine->rapl_power_meter.rapl_action_get_power(PACKAGE); pl1 = rapl_read_pl1(); if (pl1 != THD_ERROR && pl1 < power) return pl1; return power; } return curr_state; } int cthd_sysfs_cdev_rapl::get_max_state() { return max_state; } int cthd_sysfs_cdev_rapl::rapl_sysfs_valid() { std::ostringstream temp_str; int found_long_term = 0; int i; // The primary control is powercap rapl long_term control // If absent we can't use rapl cooling device for (i = 0; i < rapl_no_time_windows; ++i) { temp_str.str(std::string()); temp_str << "constraint_" << i << "_name"; if (cdev_sysfs.exists(temp_str.str())) { std::string type_str; cdev_sysfs.read(temp_str.str(), type_str); if (type_str == "long_term") { constraint_index = i; found_long_term = 1; } if (type_str == "short_term") { pl2_index = i; } } } if (!found_long_term) { thd_log_info("powercap RAPL no long term time window\n"); return THD_ERROR; } temp_str.str(std::string()); temp_str << "constraint_" << constraint_index << "_power_limit_uw"; if (!cdev_sysfs.exists(temp_str.str())) { thd_log_debug("powercap RAPL no power limit uw %s\n", temp_str.str().c_str()); return THD_ERROR; } temp_str.str(std::string()); temp_str << "constraint_" << constraint_index << "_time_window_us"; if (!cdev_sysfs.exists(temp_str.str())) { thd_log_info("powercap RAPL no time_window_us %s\n", temp_str.str().c_str()); return THD_ERROR; } return 0; } int cthd_sysfs_cdev_rapl::rapl_read_pl1_max() { std::ostringstream temp_power_str; int current_pl1_max; temp_power_str << "constraint_" << constraint_index << "_max_power_uw"; current_pl1_max = 0; // Initialize before read if (cdev_sysfs.read(temp_power_str.str(), ¤t_pl1_max) > 0) { return current_pl1_max; } return THD_ERROR; } int cthd_sysfs_cdev_rapl::rapl_read_pl1() { std::ostringstream temp_power_str; int current_pl1; temp_power_str << "constraint_" << constraint_index << "_power_limit_uw"; current_pl1 = 0; // Initialize before read if (cdev_sysfs.read(temp_power_str.str(), ¤t_pl1) > 0) { return current_pl1; } return THD_ERROR; } int cthd_sysfs_cdev_rapl::rapl_update_pl1(int pl1) { std::ostringstream temp_power_str; int ret; temp_power_str << "constraint_" << constraint_index << "_power_limit_uw"; ret = cdev_sysfs.write(temp_power_str.str(), pl1); if (ret <= 0) { thd_log_info( "pkg_power: powercap RAPL max power limit failed to write %d\n", pl1); return ret; } return THD_SUCCESS; } int cthd_sysfs_cdev_rapl::rapl_read_pl2() { std::ostringstream temp_power_str; int current_pl2; temp_power_str << "constraint_" << pl2_index << "_power_limit_uw"; if (cdev_sysfs.read(temp_power_str.str(), ¤t_pl2) > 0) { return current_pl2; } return THD_ERROR; } int cthd_sysfs_cdev_rapl::rapl_update_pl2(int pl2) { std::ostringstream temp_power_str; int ret; if (pl2_index == -1) { thd_log_warn("Asked to set PL2 but couldn't find a PL2 device\n"); return THD_ERROR; } temp_power_str << "constraint_" << pl2_index << "_power_limit_uw"; ret = cdev_sysfs.write(temp_power_str.str(), pl2); if (ret <= 0) { thd_log_info( "pkg_power: powercap RAPL max power limit failed to write PL2 %d\n", pl2); return ret; } return THD_SUCCESS; } int cthd_sysfs_cdev_rapl::rapl_read_time_window() { std::ostringstream temp_time_str; int tm_window; temp_time_str << "constraint_" << constraint_index << "_time_window_us"; tm_window = 0; // Initialize before read if (cdev_sysfs.read(temp_time_str.str(), &tm_window) > 0) { return tm_window; } return THD_ERROR; } int cthd_sysfs_cdev_rapl::rapl_update_time_window(int time_window) { std::ostringstream temp_time_str; temp_time_str << "constraint_" << constraint_index << "_time_window_us"; if (cdev_sysfs.write(temp_time_str.str(), time_window) <= 0) { thd_log_info( "pkg_power: powercap RAPL time window failed to write %d\n", time_window); return THD_ERROR; } return THD_SUCCESS; } int cthd_sysfs_cdev_rapl::rapl_update_pl2_time_window(int time_window) { std::ostringstream temp_time_str; temp_time_str << "constraint_" << pl2_index << "_time_window_us"; if (cdev_sysfs.write(temp_time_str.str(), time_window) <= 0) { thd_log_info( "pkg_power: powercap RAPL time window failed to write %d\n", time_window); return THD_ERROR; } return THD_SUCCESS; } int cthd_sysfs_cdev_rapl::rapl_update_enable_status(int enable) { std::ostringstream temp_str; temp_str << "enabled"; if (cdev_sysfs.write(temp_str.str(), enable) <= 0) { thd_log_info( "pkg_power: powercap RAPL enable failed to write %d\n", enable); return THD_ERROR; } return THD_SUCCESS; } int cthd_sysfs_cdev_rapl::rapl_read_enable_status() { std::ostringstream temp_str; int enable; temp_str << "enabled"; enable = 0; // Initialize before read if (cdev_sysfs.read(temp_str.str(), &enable) > 0) { return enable; } return THD_ERROR; } void cthd_sysfs_cdev_rapl::set_tcc(int tcc) { csys_fs sysfs("/sys/bus/pci/devices/0000:00:04.0/"); if (!sysfs.exists("tcc_offset_degree_celsius")) return; if (sysfs.write("tcc_offset_degree_celsius", tcc) == -1) thd_log_debug("TCC write failed\n"); } void cthd_sysfs_cdev_rapl::set_adaptive_target(struct adaptive_target &target) { int argument = std::stoi(target.argument, nullptr); if (target.code == "PL1MAX") { int pl1_rapl; min_state = pl0_max_pwr = argument * 1000; pl1_rapl = rapl_read_pl1(); if (curr_state > pl1_rapl) set_curr_state(pl1_rapl, 1); if (curr_state > min_state) set_curr_state(min_state, 1); } else if (target.code == "PL1MIN") { max_state = pl0_min_pwr = argument * 1000; if (curr_state < max_state) set_curr_state(max_state, 1); } else if (target.code == "PL1STEP") { pl0_step_pwr = argument * 1000; set_inc_value(-pl0_step_pwr * 2); set_dec_value(-pl0_step_pwr); } else if (target.code == "PL1TimeWindow") { pl0_min_window = argument * 1000; } else if (target.code == "PL1PowerLimit") { rapl_update_pl1(argument * 1000); //set_curr_state(argument * 1000, 1); } else if (target.code == "PL2PowerLimit") { rapl_update_pl2(argument * 1000); } else if (target.code == "TccOffset") { set_tcc(argument); } } int cthd_sysfs_cdev_rapl::update() { int constraint_phy_max; bool ppcc = false; std::string domain_name; if (rapl_sysfs_valid()) return THD_ERROR; register_for_restoration(); ppcc = read_ppcc_power_limits(); if (ppcc) { // This is a DPTF compatible platform, which defined // maximum and minimum power limits. We can trust this to be something sane. phy_max = pl0_max_pwr; // We want to be aggressive controlling temperature but lazy during // removing of controls set_inc_value(-pl0_step_pwr * 2); set_dec_value(-pl0_step_pwr); min_state = pl0_max_pwr; max_state = pl0_min_pwr; rapl_update_pl1(pl0_max_pwr); if (pl0_max_window > pl0_min_window) rapl_update_time_window(pl0_max_window); // To be efficient to control from the current power instead of PPCC max. thd_engine->rapl_power_meter.rapl_start_measure_power(); dynamic_phy_max_enable = true; //set_debounce_interval(1); // Some system has PL2 limit as 0, then try to set PL2 limit also if (!rapl_read_pl2()) { thd_log_info("PL2 power limit is 0, will conditionally enable\n"); if (pl1_max_pwr) { thd_log_info("PL2 limits are updated to %d %d\n", pl1_max_pwr, pl1_max_window); rapl_update_pl2(pl1_max_pwr); rapl_update_pl2_time_window(pl1_max_window); rapl_update_enable_status(1); } } else { // By default enable the rapl device to enforce any power limits rapl_update_enable_status(1); } } else { // This is not a DPTF platform // Read the max power from the powercap rapl sysfs // If not present, we can't use rapl to cool. phy_max = rapl_read_pl1_max(); // Check if there is any sane max power limit set if (phy_max < 0 || phy_max > rapl_max_sane_phy_max) { int ret = cdev_sysfs.read("name", domain_name); if (!ret) thd_log_info("%s:powercap RAPL invalid max power limit range\n", domain_name.c_str()); thd_log_info("Calculate dynamically phy_max\n"); power_on_constraint_0_pwr = rapl_read_pl1(); thd_log_debug("power_on_constraint_0_pwr %d\n", power_on_constraint_0_pwr); power_on_constraint_0_time_window = rapl_read_time_window(); thd_log_debug("power_on_constraint_0_time_window %d\n", power_on_constraint_0_time_window); phy_max = max_state = 0; curr_state = min_state = rapl_max_sane_phy_max; thd_engine->rapl_power_meter.rapl_start_measure_power(); set_inc_dec_value(-rapl_min_default_step); dynamic_phy_max_enable = true; return THD_SUCCESS; } constraint_phy_max = rapl_read_pl1(); // If the constraint_0_max_power_uw < constraint_0_power_limit_uw // Use constraint_0_power_limit_uw as the phy_max and min_state if (constraint_phy_max > phy_max) { thd_log_info( "Default constraint power limit is more than max power %d:%d\n", constraint_phy_max, phy_max); phy_max = constraint_phy_max; } thd_log_info("powercap RAPL max power limit range %d\n", phy_max); set_inc_dec_value(-phy_max * (float) rapl_power_dec_percent / 100); min_state = phy_max; max_state = min_state - (float) min_state * rapl_low_limit_percent / 100; } power_on_constraint_0_time_window = rapl_read_time_window(); power_on_enable_status = rapl_read_enable_status(); thd_log_debug("power_on_enable_status: %d\n", power_on_enable_status); thd_log_debug("power_on_constraint_0_time_window: %d\n", power_on_constraint_0_time_window); thd_log_debug("RAPL max limit %d increment: %d\n", max_state, inc_dec_val); set_pid_param(-1000, 100, 10); curr_state = min_state; return THD_SUCCESS; } bool cthd_sysfs_cdev_rapl::read_ppcc_power_limits() { csys_fs sys_fs; ppcc_t *ppcc; ppcc = thd_engine->get_ppcc_param(device_name); if (ppcc) { int def_max_power; thd_log_info("Reading PPCC from the thermal-conf.xml\n"); pl0_max_pwr = ppcc->power_limit_max * 1000; pl0_min_pwr = ppcc->power_limit_min * 1000; pl0_min_window = ppcc->time_wind_min * 1000; pl0_max_window = ppcc->time_wind_max * 1000; pl0_step_pwr = ppcc->step_size * 1000; pl1_valid = ppcc->limit_1_valid; if (pl1_valid) { pl1_max_pwr = ppcc->power_limit_1_max * 1000; pl1_min_pwr = ppcc->power_limit_1_min * 1000; pl1_min_window = ppcc->time_wind_1_min * 1000; pl1_max_window = ppcc->time_wind_1_max * 1000; pl1_step_pwr = ppcc->step_1_size * 1000; } if (pl0_max_pwr <= pl0_min_pwr) { thd_log_info("Invalid limits: ppcc limits max:%d min:%d min_win:%d step:%d\n", pl0_max_pwr, pl0_min_pwr, pl0_min_window, pl0_step_pwr); return false; } thd_log_info("ppcc limits max:%d min:%d min_win:%d step:%d\n", pl0_max_pwr, pl0_min_pwr, pl0_min_window, pl0_step_pwr); int policy_matched; policy_matched = thd_engine->search_idsp("63BE270F-1C11-48FD-A6F7-3AF253FF3E2D"); if (policy_matched != THD_SUCCESS) policy_matched = thd_engine->search_idsp("9E04115A-AE87-4D1C-9500-0F3E340BFE75"); if (policy_matched == THD_SUCCESS) { thd_log_info("IDSP policy matched, so trusting PPCC limits\n"); return true; } def_max_power = rapl_read_pl1_max(); if (def_max_power > pl0_max_pwr) thd_log_warn("ppcc limits is less than def PL1 max power :%d check thermal-conf.xml.auto\n", def_max_power); return true; } std::string domain_name; // Since this base class is also used by DRAM rapl, avoid reading PPCC as // there are no power limits defined by DPTF based systems for any other // domain other than package-0 cdev_sysfs.read("name", domain_name); if (domain_name != "package-0") return false; if (sys_fs.exists("/sys/bus/pci/devices/0000:00:04.0/power_limits/")) sys_fs.update_path("/sys/bus/pci/devices/0000:00:04.0/power_limits/"); else if (sys_fs.exists("/sys/bus/pci/devices/0000:00:0b.0/power_limits/")) sys_fs.update_path("/sys/bus/pci/devices/0000:00:0b.0/power_limits/"); else if (sys_fs.exists( "/sys/bus/platform/devices/INT3401:00/power_limits/")) sys_fs.update_path( "/sys/bus/platform/devices/INT3401:00/power_limits/"); else return false; if (sys_fs.exists("power_limit_0_max_uw")) { if (sys_fs.read("power_limit_0_max_uw", &pl0_max_pwr) <= 0) return false; } if (sys_fs.exists("power_limit_0_min_uw")) { if (sys_fs.read("power_limit_0_min_uw", &pl0_min_pwr) <= 0) return false; } if (sys_fs.exists("power_limit_0_tmin_us")) { if (sys_fs.read("power_limit_0_tmin_us", &pl0_min_window) <= 0) return false; } if (sys_fs.exists("power_limit_0_tmax_us")) { if (sys_fs.read("power_limit_0_tmax_us", &pl0_max_window) <= 0) return false; } if (sys_fs.exists("power_limit_0_step_uw")) { if (sys_fs.read("power_limit_0_step_uw", &pl0_step_pwr) <= 0) return false; } if (pl0_max_pwr && pl0_min_pwr && pl0_min_window && pl0_step_pwr && pl0_max_window) { int def_max_power; if (pl0_max_pwr <= pl0_min_pwr) { thd_log_info("Invalid limits: ppcc limits max:%d min:%d min_win:%d step:%d\n", pl0_max_pwr, pl0_min_pwr, pl0_min_window, pl0_step_pwr); return false; } thd_log_info("ppcc limits max:%d min:%d min_win:%d step:%d\n", pl0_max_pwr, pl0_min_pwr, pl0_min_window, pl0_step_pwr); def_max_power = rapl_read_pl1_max(); if (def_max_power > pl0_max_pwr) { thd_log_info("ppcc limits is less than def PL1 max power :%d, so ignore\n", def_max_power); return false; } return true; } return false; } void cthd_sysfs_cdev_rapl::thd_cdev_set_min_state_param(int arg) { min_state = curr_state = arg; } thermal_daemon-2.5.12/src/thd_cdev_rapl.h000066400000000000000000000070541521301715300203220ustar00rootroot00000000000000/* * cthd_cdev_rapl.h: thermal cooling class interface * using RAPL * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_RAPL_H_ #define THD_CDEV_RAPL_H_ #include "thd_cdev.h" #include "thd_sys_fs.h" class cthd_sysfs_cdev_rapl: public cthd_cdev { protected: int phy_max; int package_id; int constraint_index; int pl2_index; bool dynamic_phy_max_enable; int pl0_max_pwr; int pl0_min_pwr; int pl0_min_window; int pl0_max_window; int pl0_step_pwr; int pl1_max_pwr; int pl1_min_pwr; int pl1_min_window; int pl1_max_window; int pl1_step_pwr; int pl1_valid; bool bios_locked; bool constrained; int power_on_constraint_0_pwr; int power_on_constraint_0_time_window; int power_on_enable_status; std::string device_name; virtual bool read_ppcc_power_limits(); private: int rapl_sysfs_valid(); int rapl_read_pl1(); int rapl_read_pl1_max(); int rapl_update_pl1(int pl1); int rapl_read_pl2(); int rapl_update_pl2(int pl2); int rapl_read_time_window(); int rapl_update_time_window(int time_window); int rapl_update_pl2_time_window(int time_window); int rapl_read_enable_status(); void register_for_restoration(); public: static constexpr int rapl_no_time_windows = 6; static constexpr long def_rapl_time_window = 1000000; // micro seconds static constexpr int rapl_min_default_step = 500000; //0.5W static constexpr int rapl_max_sane_phy_max = 1000000000; // Sane upper bound in uW (1000W) static constexpr int rapl_low_limit_percent = 50; static constexpr int rapl_power_dec_percent = 5; cthd_sysfs_cdev_rapl(unsigned int _index, int package) : cthd_sysfs_cdev_rapl(_index, package, "/sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/") { } cthd_sysfs_cdev_rapl(unsigned int _index, int package, std::string contol_path) : cthd_cdev(_index, std::move(contol_path)), phy_max(0), package_id( package), constraint_index( 0), pl2_index( -1), dynamic_phy_max_enable(false), pl0_max_pwr( 0), pl0_min_pwr( 0), pl0_min_window(0), pl0_max_window(0), pl0_step_pwr(0), bios_locked( false), constrained( false), power_on_constraint_0_pwr(0), power_on_constraint_0_time_window( 0), power_on_enable_status(0), device_name("TCPU.D0") { pl1_max_pwr = 0; pl1_min_pwr = 0; pl1_min_window = 0; pl1_max_window = 0; pl1_step_pwr = 0; pl1_valid = 0; } void set_curr_state(int state, int arg) override; int get_curr_state() override; int get_curr_state(bool read_again) override; int get_max_state() override; int update() override; void set_curr_state_raw(int state, int arg) override; void set_tcc(int tcc); void set_adaptive_target(struct adaptive_target &target) override; void thd_cdev_set_min_state_param(int arg) override; int get_phy_max_state() override { return phy_max; } int rapl_update_enable_status(int enable); }; #endif /* THD_CDEV_RAPL_H_ */ thermal_daemon-2.5.12/src/thd_cdev_rapl_dram.cpp000066400000000000000000000034061521301715300216550ustar00rootroot00000000000000/* * cthd_cdev_rapl_dram.cpp: thermal cooling class implementation * using RAPL DRAM * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include #include "thd_cdev_rapl_dram.h" #include "thd_engine.h" int cthd_sysfs_cdev_rapl_dram::update() { DIR *dir; struct dirent *entry; std::string base = "/sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/"; bool found = false; std::string path_name; dir = opendir(base.c_str()); if (!dir) return THD_ERROR; while ((entry = readdir(dir)) != nullptr) { std::string temp_str; temp_str = base + entry->d_name + "/" + "name"; csys_fs name_sysfs(std::move(temp_str)); if (!name_sysfs.exists()) { continue; } std::string name; if (name_sysfs.read("", name) < 0) { continue; } thd_log_info("name = %s\n", name.c_str()); if (name == "dram") { found = true; path_name = base + entry->d_name + "/"; break; } } closedir(dir); if (!found) return THD_ERROR; cdev_sysfs.update_path(std::move(path_name)); return cthd_sysfs_cdev_rapl::update(); } thermal_daemon-2.5.12/src/thd_cdev_rapl_dram.h000066400000000000000000000023411521301715300213170ustar00rootroot00000000000000/* * cthd_cdev_rapl_dram.h: thermal cooling class interface * using RAPL DRAM * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_RAPL_DRAM_H_ #define THD_CDEV_RAPL_DRAM_H_ #include "thd_cdev_rapl.h" class cthd_sysfs_cdev_rapl_dram: public cthd_sysfs_cdev_rapl { private: public: cthd_sysfs_cdev_rapl_dram(unsigned int _index, int _package) : cthd_sysfs_cdev_rapl(_index, _package) { device_name = "TMEM.D0"; } int update() override; }; #endif /* THD_CDEV_RAPL_DRAM_H_ */ thermal_daemon-2.5.12/src/thd_cdev_rapl_restore.cpp000066400000000000000000000100231521301715300224060ustar00rootroot00000000000000/* * cthd_cdev_rapl_restore.cpp: RAPL power limit restoration on exit * using RAPL * Copyright (C) 2026 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_cdev_rapl.h" #include "thermald.h" #include #include #include // Global registry of RAPL devices that need restoration on exit namespace { struct rapl_restore_info { std::string sysfs_path; int constraint_index; int power_limit; int time_window; int enable_status; }; std::vector restore_registry; std::mutex registry_mutex; bool atexit_registered = false; // Called by atexit() - restores all RAPL power limits void restore_rapl_limits() { std::vector registry_copy; { std::lock_guard lock(registry_mutex); registry_copy = restore_registry; } thd_log_info("Restoring %zu RAPL power limits on exit\n", registry_copy.size()); for (const auto& info : registry_copy) { csys_fs sysfs(info.sysfs_path); // Restore PL1 power limit std::ostringstream power_limit_path; power_limit_path << "constraint_" << info.constraint_index << "_power_limit_uw"; if (sysfs.exists(power_limit_path.str())) { sysfs.write(power_limit_path.str(), info.power_limit); thd_log_info(" Restored PL1=%d uW for %s\n", info.power_limit, info.sysfs_path.c_str()); } // Restore time window std::ostringstream time_window_path; time_window_path << "constraint_" << info.constraint_index << "_time_window_us"; if (sysfs.exists(time_window_path.str())) { sysfs.write(time_window_path.str(), info.time_window); thd_log_info(" Restored PL1 time window=%d for %s\n", info.time_window, info.sysfs_path.c_str()); } // Restore enable status if (sysfs.exists("enabled")) { sysfs.write("enabled", info.enable_status); thd_log_info(" Restored PL1 enabled=%d for %s\n", info.enable_status, info.sysfs_path.c_str()); } } } } // Register a RAPL device for restoration on exit void cthd_sysfs_cdev_rapl::register_for_restoration() { std::lock_guard lock(registry_mutex); // Register atexit handler on first call if (!atexit_registered) { if (std::atexit(restore_rapl_limits) != 0) { thd_log_warn("Failed to register RAPL power limit restoration handler\n"); return; } atexit_registered = true; thd_log_info("Registered RAPL power limit restoration handler\n"); } const std::string sysfs_path = cdev_sysfs.get_base_path(); for (const auto &existing : restore_registry) { if (existing.sysfs_path == sysfs_path && existing.constraint_index == constraint_index) { return; } } const int power_limit = rapl_read_pl1(); const int time_window = rapl_read_time_window(); const int enable_status = rapl_read_enable_status(); if (power_limit < 0 || time_window < 0 || enable_status < 0) { thd_log_warn( "Failed to read initial RAPL state for %s, skipping restoration registration\n", sysfs_path.c_str()); return; } rapl_restore_info info; info.sysfs_path = sysfs_path; info.constraint_index = constraint_index; info.power_limit = power_limit; info.time_window = time_window; info.enable_status = enable_status; restore_registry.push_back(info); thd_log_info("Registered RAPL %s: PL1=%d uW, window=%d us, enable=%d\n", sysfs_path.c_str(), info.power_limit, info.time_window, info.enable_status); } thermal_daemon-2.5.12/src/thd_cdev_therm_sys_fs.cpp000066400000000000000000000065321521301715300224240ustar00rootroot00000000000000/* * cthd_sysfs_cdev.cpp: thermal cooling class implementation * for thermal sysfs * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_cdev_therm_sys_fs.h" #include "thd_engine.h" /* This uses ACPI style thermal sysfs interface to set states. * It expects, max_state. curr_state in thermal sysfs and uses * these sysfs-files to control. * */ int cthd_sysfs_cdev::update() { std::ostringstream tc_state_dev; tc_state_dev << "cooling_device" << index << "/cur_state"; curr_state = 0; // Initialize before read if (cdev_sysfs.exists(tc_state_dev.str())) { int ret = cdev_sysfs.read(tc_state_dev.str(), &curr_state); if (ret < 0) return ret; } std::ostringstream tc_max_state_dev; tc_max_state_dev << "cooling_device" << index << "/max_state"; max_state = 0; // Initialize before read if (cdev_sysfs.exists(tc_max_state_dev.str())) { int ret = cdev_sysfs.read(tc_max_state_dev.str(), &max_state); if (ret < 0) return ret; } std::ostringstream tc_type_dev; tc_type_dev << "cooling_device" << index << "/type"; if (cdev_sysfs.exists(tc_type_dev.str())) { cdev_sysfs.read(tc_type_dev.str(), type_str); if (type_str.size()) { // They essentially change same ACPI object, so reading their // state from sysfs after a change to any processor will cause // double compensation if (type_str == "Processor") read_back = false; } } thd_log_debug("cooling dev %d:%d:%d:%s\n", index, curr_state, max_state, type_str.c_str()); return THD_SUCCESS; } int cthd_sysfs_cdev::get_max_state() { std::ostringstream tc_state_dev; tc_state_dev << "cooling_device" << index << "/max_state"; max_state = 0; // Initialize before read if (cdev_sysfs.exists(tc_state_dev.str())) { int ret = cdev_sysfs.read(tc_state_dev.str(), &max_state); if (ret < 0) return ret; } return max_state; } void cthd_sysfs_cdev::set_curr_state(int state, int arg) { std::ostringstream tc_state_dev; tc_state_dev << "cooling_device" << index << "/cur_state"; if (cdev_sysfs.exists(tc_state_dev.str())) { std::ostringstream state_str; state_str << state; thd_log_debug("set cdev state index %d state %d\n", index, state); cdev_sysfs.write(tc_state_dev.str(), state_str.str()); curr_state = state; } else curr_state = 0; } int cthd_sysfs_cdev::get_curr_state() { if (!read_back) { return curr_state; } std::ostringstream tc_state_dev; tc_state_dev << "cooling_device" << index << "/cur_state"; curr_state = 0; // Initialize before read if (cdev_sysfs.exists(tc_state_dev.str())) { int ret = cdev_sysfs.read(tc_state_dev.str(), &curr_state); if (ret < 0) return ret; } return curr_state; } thermal_daemon-2.5.12/src/thd_cdev_therm_sys_fs.h000066400000000000000000000024561521301715300220720ustar00rootroot00000000000000/* * cthd_sysfs_cdev.cpp: thermal cooling class interface * for thermal sysfs * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_THERM_SYS_FS_H_ #define THD_CDEV_THERM_SYS_FS_H_ #include "thd_cdev.h" class cthd_sysfs_cdev: public cthd_cdev { protected: public: cthd_sysfs_cdev(unsigned int _index, std::string control_path) : cthd_cdev(_index, std::move(control_path)) { } void set_curr_state(int state, int arg) override; int get_curr_state() override; int get_max_state() override; int update() override; }; #endif /* THD_CDEV_THERM_SYS_FS_H_ */ thermal_daemon-2.5.12/src/thd_common.h000066400000000000000000000016611521301715300176510ustar00rootroot00000000000000/* thd_msr.cpp: thermal engine common definitions * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_COMMON_H_ #define THD_COMMON_H_ #include "thermald.h" #endif /* THD_COMMON_H_ */ thermal_daemon-2.5.12/src/thd_cpu_default_binding.cpp000066400000000000000000000222551521301715300227030ustar00rootroot00000000000000/* * thd_default_binding.cpp: Default binding of thermal zones * implementation file * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * Author Name * */ /* * It will search for unbounded zones (There is valid passive trips and temp * reading is available but there is no cooling driver attached to it via XML * config or default hardcoded config. * In this case if CPU is causing this temp to go up then it will use RAPL and * power clamp to cool. If it fails for three times then this zone is added * to black list, so that this method will never be tried again, * The way this file implements using existing mechanism via a two pseudo * cooling devices "gates" called start and exit. The start gate checks the * current cpu load, if yes then it opens gate by changing it to max state. * Similarly if end gate state is reached means that the RAPL and power clamp * failed to control temperature of this zone. */ #include #include "thd_zone_therm_sys_fs.h" #include "thd_cpu_default_binding.h" class cthd_gating_cdev: public cthd_cdev { private: class cthd_cpu_default_binding *def_bind_ref; cpu_zone_binding_t *bind_zone; bool start; public: static constexpr int max_state = 0x01; cthd_gating_cdev(int _id, cthd_cpu_default_binding *_def_bind_ref, cpu_zone_binding_t *_bind_zone, bool _start) : cthd_cdev(_id, ""), def_bind_ref(_def_bind_ref), bind_zone( _bind_zone), start(_start) { } void set_curr_state(int state, int arg); int get_max_state(); int update(); int get_curr_state(); }; int cthd_gating_cdev::get_curr_state() { return curr_state; } void cthd_gating_cdev::set_curr_state(int state, int arg) { if (!start && state) { if (!bind_zone->zone) return; thd_log_info("CPU def binding exit for %s\n", bind_zone->zone->get_zone_type().c_str()); bind_zone->zone->zone_reset(); cpu_zone_stat_t stats; if (def_bind_ref->read_zone_stat(bind_zone->zone->get_zone_type(), &stats) == THD_SUCCESS) { ++stats.failures; def_bind_ref->update_zone_stat(bind_zone->zone->get_zone_type(), stats.failures); if (stats.failures > 3) { // This zone can't be controlled by this binding thd_log_info("CPU def binding is set to inactive for %s\n", bind_zone->zone->get_zone_type().c_str()); bind_zone->zone->set_zone_inactive(); } } else { def_bind_ref->update_zone_stat(bind_zone->zone->get_zone_type(), 0); } } else if (start && state) { if (def_bind_ref->check_cpu_load()) { thd_log_info("Turn on the gate\n"); curr_state = max_state; } else { thd_log_info("Not CPU specific increase\n"); curr_state = 0; } } else { curr_state = 0; } thd_log_info( "cthd_gating_cdev::set_curr_state[start:%d state:%d. curr_state:%d\n", start, state, curr_state); } int cthd_gating_cdev::get_max_state() { return max_state; } int cthd_gating_cdev::update() { thd_log_info("cthd_gating_cdev::update\n"); return 0; } int cthd_cpu_default_binding::read_zone_stat(std::string zone_name, cpu_zone_stat_t *stat) { std::ifstream filein; std::ostringstream filename; filename << TDRUNDIR << "/" << "cpu_def_zone_bind.out"; filein.open(filename.str().c_str(), std::ios::in | std::ios::binary); if (!filein) return THD_ERROR; while (filein) { filein.read((char *) stat, sizeof(*stat)); thd_log_info("read_zone_stat name:%s f:%d\n", stat->zone_name, stat->failures); if (filein && zone_name == stat->zone_name) return THD_SUCCESS; } filein.close(); return THD_ERROR; } void cthd_cpu_default_binding::update_zone_stat(std::string zone_name, int fail_cnt) { std::ifstream filein; std::streampos current = 0; cpu_zone_stat_t obj; bool found = false; std::ostringstream filename; filename << TDRUNDIR << "/" << "cpu_def_zone_bind.out"; filein.open(filename.str().c_str(), std::ios::in | std::ios::binary); if (filein.is_open()) { while (!filein.eof()) { current = filein.tellg(); filein.read((char *) &obj, sizeof(obj)); if (filein && zone_name == obj.zone_name) { found = true; break; } } } filein.close(); std::fstream fileout(filename.str().c_str(), std::ios::out | std::ios::in | std::ios::binary); if (!fileout.is_open()) { std::ofstream file; file.open(filename.str().c_str()); if (file.is_open()) { file.close(); fileout.open(filename.str().c_str(), std::ios::in | std::ios::out | std::ios::binary); } else { thd_log_info("Can't create cpu_def_zone_bind.out\n"); } } if (found) { fileout.seekp(current); } else fileout.seekp(0, std::ios::end); strncpy(obj.zone_name, zone_name.c_str(), sizeof(obj.zone_name) - 1); obj.zone_name[sizeof(obj.zone_name) - 1] = '\0'; obj.failures = fail_cnt; fileout.write((char *) &obj, sizeof(obj)); fileout.close(); } bool cthd_cpu_default_binding::check_cpu_load() { unsigned int max_power = 0; unsigned int min_power = 0; unsigned int power; power = thd_engine->rapl_power_meter.rapl_action_get_power(PACKAGE, &max_power, &min_power); if (cpu_package_max_power != 0) max_power = cpu_package_max_power; thd_log_info("cthd_gating_cdev power :%u %u %u\n", power, min_power, max_power); if ((max_power - min_power) < def_starting_power_differential) { return false; } if (power > (max_power * 60 / 100)) { thd_log_info("Significant cpu load\n"); return true; } return false; } bool cthd_cpu_default_binding::blacklist_match(std::string name) { int i = 0; const char *blacklist_zones[] = { "cpu", "acpitz", "Surface", "pkg-temp-0", "x86_pkg_temp", "soc_dts0", "soc_dts1", "B0D4", "B0DB", "" }; while (blacklist_zones[i][0] != '\0') { if (name == blacklist_zones[i]) return true; ++i; } cpu_zone_stat_t stats; if (read_zone_stat(name, &stats) == THD_SUCCESS) { if (stats.failures > 3) { // This zone can't be controlled by this binding thd_log_info(" zone %s in blacklist\n", name.c_str()); return true; } } return false; } void cthd_cpu_default_binding::do_default_binding( std::vector &cdevs) { int count = 0; int id = 0x1000; cthd_cdev *cdev_rapl; cthd_cdev *cdev_powerclamp; cdev_rapl = thd_engine->search_cdev("rapl_controller"); cdev_powerclamp = thd_engine->search_cdev("intel_powerclamp"); if (!cdev_rapl && !cdev_powerclamp) { thd_log_info( "cthd_cpu_default_binding::do_default_binding: No relevant cpu cdevs\n"); return; } for (unsigned int i = 0; i < thd_engine->get_zone_count(); ++i) { cthd_zone *zone = thd_engine->get_zone(i); if (!zone) { continue; } if (blacklist_match(zone->get_zone_type())) { continue; } if (!zone->zone_cdev_binded()) { std::unique_ptr cdev_binding_info(new cpu_zone_binding_t); cdev_binding_info->zone_name = zone->get_zone_type(); cdev_binding_info->zone = zone; cdev_binding_info->cdev_gate_entry.reset(new cthd_gating_cdev(id++, this, cdev_binding_info.get(), true)); if (cdev_binding_info->cdev_gate_entry) { cdev_binding_info->cdev_gate_entry->set_cdev_type( cdev_binding_info->zone_name + "_" + "cpu_gate_entry"); } else { continue; } cdev_binding_info->cdev_gate_exit.reset(new cthd_gating_cdev(id++, this, cdev_binding_info.get(), false)); if (cdev_binding_info->cdev_gate_exit) { cdev_binding_info->cdev_gate_exit->set_cdev_type( cdev_binding_info->zone_name + "_" + "cpu_gate_exit"); } else { thd_log_info("do_default_binding failed\n"); return; } thd_log_info("unbound zone %s\n", zone->get_zone_type().c_str()); int status = zone->bind_cooling_device(PASSIVE, 0, cdev_binding_info->cdev_gate_entry.get(), 0, def_gating_cdev_sampling_period); if (status == THD_ERROR) { thd_log_info("unbound zone: Bind attempt failed\n"); continue; } if (cdev_rapl) { zone->bind_cooling_device(PASSIVE, 0, cdev_rapl, 20); } if (cdev_powerclamp) { zone->bind_cooling_device(PASSIVE, 0, cdev_powerclamp, 20); } status = zone->bind_cooling_device(PASSIVE, 0, cdev_binding_info->cdev_gate_exit.get(), 0, def_gating_cdev_sampling_period); if (status == THD_ERROR) { thd_log_info("unbound zone: Bind attempt failed\n"); continue; } thd_log_info("unbound zone %s\n", zone->get_zone_type().c_str()); count++; zone->set_zone_active(); cdev_list.push_back(std::move(cdev_binding_info)); } } if (count) { thd_engine->rapl_power_meter.rapl_start_measure_power(); cpu_package_max_power = thd_engine->rapl_power_meter.rapl_action_get_max_power(PACKAGE); thd_log_info("do_default_binding max power CPU package :%u\n", cpu_package_max_power); } } thermal_daemon-2.5.12/src/thd_cpu_default_binding.h000066400000000000000000000036231521301715300223460ustar00rootroot00000000000000/* * thd_default_binding.h: Default binding of thermal zones * interface file * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_DEFAULT_BINDING_H_ #define THD_DEFAULT_BINDING_H_ #include #include "thd_engine.h" #include "thd_cdev.h" typedef struct { char zone_name[50 + 1]; int failures; } cpu_zone_stat_t; typedef struct { std::string zone_name; std::unique_ptr cdev_gate_entry; std::unique_ptr cdev_gate_exit; cthd_zone *zone; } cpu_zone_binding_t; class cthd_cpu_default_binding { private: int thd_read_default_thermal_zones(); unsigned int cpu_package_max_power; bool blacklist_match(std::string name); public: static constexpr int def_gating_cdev_sampling_period = 30; static constexpr unsigned int def_starting_power_differential = 4000000; std::vector> cdev_list; cthd_cpu_default_binding() : cpu_package_max_power(0) { } void do_default_binding(std::vector &cdevs); bool check_cpu_load(); int read_zone_stat(std::string zone_name, cpu_zone_stat_t *stat); void update_zone_stat(std::string zone_name, int fail_cnt); }; #endif /* THD_DEFAULT_BINDING_H_ */ thermal_daemon-2.5.12/src/thd_dbus_interface.cpp000066400000000000000000001061411521301715300216700ustar00rootroot00000000000000/* * thd_dbus_interface.cpp: Thermal Daemon dbus interface * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thermald.h" #include "thd_preference.h" #include "thd_engine.h" #include "thd_engine_default.h" #include "thd_sensor.h" #include "thd_zone.h" #include "thd_trip_point.h" #include "thd_util.h" #include #include #include #include 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) gboolean thd_dbus_interface_terminate(PrefObject *obj, GError **error); gboolean thd_dbus_interface_reinit(PrefObject *obj, GError **error); gboolean thd_dbus_interface_set_current_preference(PrefObject *obj, gchar *pref, GError **error); gboolean thd_dbus_interface_get_current_preference(PrefObject *obj, gchar **pref_out, GError **error); gboolean thd_dbus_interface_set_user_max_temperature(PrefObject *obj, gchar *zone_name, unsigned temperature, GError **error); gboolean thd_dbus_interface_set_user_passive_temperature(PrefObject *obj, gchar *zone_name, unsigned int temperature, GError **error); gboolean thd_dbus_interface_add_sensor(PrefObject *obj, gchar *sensor, gchar *path, GError **error); gboolean thd_dbus_interface_get_sensor_information(PrefObject *obj, gint index, gchar **sensor_out, gchar **path, gint *temp, GError **error); gboolean thd_dbus_interface_add_zone_passive(PrefObject *obj, gchar *zone_name, gint trip_temp, gchar *sensor_name, gchar *cdev_name, GError **error); gboolean thd_dbus_interface_set_zone_status(PrefObject *obj, gchar *zone_name, int status, GError **error); gboolean thd_dbus_interface_get_zone_status(PrefObject *obj, gchar *zone_name, int *status, GError **error); gboolean thd_dbus_interface_delete_zone(PrefObject *obj, gchar *zone_name, GError **error); gboolean thd_dbus_interface_add_virtual_sensor(PrefObject *obj, gchar *name, gchar *dep_sensor, double slope, double intercept, GError **error); gboolean thd_dbus_interface_add_cooling_device(PrefObject *obj, gchar *cdev_name, gchar *path, gint min_state, gint max_state, gint step, GError **error); gboolean thd_dbus_interface_update_cooling_device(PrefObject *obj, gchar *cdev_name, gchar *path, gint min_state, gint max_state, gint step, GError **error); gboolean thd_dbus_interface_get_sensor_count(PrefObject *obj, int *status, GError **error); gboolean thd_dbus_interface_get_sensor_temperature(PrefObject *obj, int index, unsigned int *temperature, GError **error); gboolean thd_dbus_interface_get_zone_count(PrefObject *obj, int *status, GError **error); gboolean thd_dbus_interface_get_zone_information(PrefObject *obj, gint index, gchar **zone_out, gint *sensor_count, gint *trip_count, gint *bound, GError **error); gboolean thd_dbus_interface_get_zone_sensor_at_index(PrefObject *obj, gint zone_index, gint sensor_index, gchar **sensor_out, GError **error); gboolean thd_dbus_interface_get_zone_trip_at_index(PrefObject *obj, gint zone_index, gint trip_index, int *temp, int *trip_type, int *sensor_id, int *cdev_size, GArray **cdev_ids, GError **error); gboolean thd_dbus_interface_get_cdev_count(PrefObject *obj, int *status, GError **error); gboolean thd_dbus_interface_get_cdev_information(PrefObject *obj, gint index, gchar **cdev_out, gint *min_state, gint *max_state, gint *curr_state, GError **error); // To be implemented gboolean thd_dbus_interface_add_trip_point(PrefObject *obj, gchar *name, GError **error) { return FALSE; } gboolean thd_dbus_interface_delete_trip_point(PrefObject *obj, gchar *name, GError **error) { return FALSE; } gboolean thd_dbus_interface_disable_cooling_device(PrefObject *obj, gchar *name, GError **error) { return FALSE; } // This is a generated file, which expects the above prototypes #ifndef GDBUS #include "thd_dbus_interface.h" #endif // DBUS Related functions // Dbus object initialization static void pref_object_init(PrefObject *obj) { g_assert(obj != nullptr); } // Dbus object class initialization static void pref_object_class_init(PrefObjectClass *_class) { g_assert(_class != nullptr); #ifndef GDBUS dbus_g_object_type_install_info(PREF_TYPE_OBJECT, &dbus_glib_thd_dbus_interface_object_info); #endif } // Callback function called to inform a sent value via dbus gboolean thd_dbus_interface_set_current_preference(PrefObject *obj, gchar *pref, GError **error) { int ret; thd_log_debug("thd_dbus_interface_set_current_preference %s\n", (char*) pref); g_assert(obj != nullptr); cthd_preference thd_pref; ret = thd_pref.set_preference((char*) pref); thd_engine->send_message(PREF_CHANGED, 0, nullptr); return ret; } // Callback function called to get value via dbus gboolean thd_dbus_interface_get_current_preference(PrefObject *obj, gchar **pref_out, GError **error) { thd_log_debug("thd_dbus_interface_get_current_preference\n"); g_assert(obj != nullptr); gchar *value_out; char *pref_str; pref_str = g_new(char, MAX_DBUS_REPLY_STR_LEN); if (!pref_str) return FALSE; cthd_preference thd_pref; value_out = (gchar*) thd_pref.get_preference_cstr(); if (!value_out) { g_free(pref_str); return FALSE; } strncpy(pref_str, value_out, MAX_DBUS_REPLY_STR_LEN); pref_str[MAX_DBUS_REPLY_STR_LEN - 1] = '\0'; free(value_out); thd_log_debug("thd_dbus_interface_get_current_preference out :%s\n", pref_str); *pref_out = pref_str; return TRUE; } gboolean (*thd_dbus_exit_callback)(void); gboolean thd_dbus_interface_terminate(PrefObject *obj, GError **error) { thd_engine->thd_engine_terminate(); if (thd_dbus_exit_callback) thd_dbus_exit_callback(); return TRUE; } gboolean thd_dbus_interface_reinit(PrefObject *obj, GError **error) { bool exclusive_control = false; if (thd_engine->get_control_mode() == EXCLUSIVE) exclusive_control = true; std::string config_file = thd_engine->get_config_file(); const char *conf_file = nullptr; if (!config_file.empty()) conf_file = config_file.c_str(); thd_engine->thd_engine_terminate(); sleep(1); thd_engine.reset(); sleep(2); if (thd_engine_create_default_engine(true, exclusive_control, conf_file) != THD_SUCCESS) { return FALSE; } return TRUE; } gboolean thd_dbus_interface_set_user_max_temperature(PrefObject *obj, gchar *zone_name, unsigned int temperature, GError **error) { if (zone_name == nullptr || !is_valid_thermal_object_name(zone_name)) return FALSE; thd_log_debug("thd_dbus_interface_set_user_set_point %s:%d\n", zone_name, temperature); g_assert(obj != nullptr); int ret = thd_engine->user_set_max_temp(zone_name, temperature); if (ret == THD_SUCCESS) thd_engine->send_message(PREF_CHANGED, 0, nullptr); else return FALSE; return TRUE; } gboolean thd_dbus_interface_set_user_passive_temperature(PrefObject *obj, gchar *zone_name, unsigned int temperature, GError **error) { if (zone_name == nullptr || !is_valid_thermal_object_name(zone_name)) return FALSE; thd_log_debug("thd_dbus_interface_set_user_passive_temperature %s:%u\n", zone_name, temperature); g_assert(obj != nullptr); int ret = thd_engine->user_set_psv_temp(zone_name, temperature); if (ret == THD_SUCCESS) thd_engine->send_message(PREF_CHANGED, 0, nullptr); else return FALSE; return TRUE; } gboolean thd_dbus_interface_add_sensor(PrefObject *obj, gchar *sensor, gchar *path, GError **error) { int ret; g_assert(obj != nullptr); thd_log_debug("thd_dbus_interface_add_sensor %s:%s\n", (char*) sensor, (char *) path); ret = thd_engine->user_add_sensor(sensor, path); if (ret == THD_SUCCESS) return TRUE; else return FALSE; } // Adjust parameters for the following gboolean thd_dbus_interface_add_virtual_sensor(PrefObject *obj, gchar *name, gchar *dep_sensor, double slope, double intercept, GError **error) { int ret; g_assert(obj != nullptr); if (name == nullptr || !is_valid_thermal_object_name(name)) return FALSE; if (!is_valid_finite_value(slope, -1000.0, 1000.0) || !is_valid_finite_value(intercept, -1000.0, 1000.0)) return FALSE; thd_log_debug("thd_dbus_interface_add_sensor %s:%s\n", (char*) name, (char *) dep_sensor); ret = thd_engine->user_add_virtual_sensor(name, dep_sensor, slope, intercept); if (ret == THD_SUCCESS) return TRUE; else return FALSE; } gboolean thd_dbus_interface_get_sensor_information(PrefObject *obj, gint index, gchar **sensor_out, gchar **path, gint *temp, GError **error) { char *sensor_str; char *path_str; thd_log_debug("thd_dbus_interface_get_sensor_information %d\n", index); cthd_sensor *sensor = thd_engine->user_get_sensor(index); if (!sensor) return FALSE; sensor_str = g_new(char, MAX_DBUS_REPLY_STR_LEN + 1); if (!sensor_str) return FALSE; path_str = g_new(char, MAX_DBUS_REPLY_STR_LEN + 1); if (!path_str) { g_free(sensor_str); return FALSE; } strncpy(sensor_str, sensor->get_sensor_type().c_str(), MAX_DBUS_REPLY_STR_LEN); sensor_str[MAX_DBUS_REPLY_STR_LEN] = '\0'; strncpy(path_str, sensor->get_sensor_path().c_str(), MAX_DBUS_REPLY_STR_LEN); path_str[MAX_DBUS_REPLY_STR_LEN] = '\0'; *temp = (gint) sensor->read_temperature(); *sensor_out = sensor_str; *path = path_str; return TRUE; } gboolean thd_dbus_interface_get_sensor_count(PrefObject *obj, int *count, GError **error) { *count = thd_engine->get_sensor_count(); return TRUE; } gboolean thd_dbus_interface_get_zone_count(PrefObject *obj, int *count, GError **error) { *count = thd_engine->get_zone_count(); return TRUE; } gboolean thd_dbus_interface_get_zone_information(PrefObject *obj, gint index, gchar **zone_out, gint *sensor_count, gint *trip_count, gint *bound, GError **error) { char *zone_str; thd_log_debug("thd_dbus_interface_get_zone_information %d\n", index); cthd_zone *zone = thd_engine->user_get_zone(index); if (!zone) return FALSE; zone_str = g_new(char, MAX_DBUS_REPLY_STR_LEN + 1); if (!zone_str) return FALSE; strncpy(zone_str, zone->get_zone_type().c_str(), MAX_DBUS_REPLY_STR_LEN); zone_str[MAX_DBUS_REPLY_STR_LEN] = '\0'; *zone_out = zone_str; *sensor_count = zone->get_sensor_count(); *trip_count = zone->get_trip_count(); *bound = (gint) zone->zone_active_status(); return TRUE; } gboolean thd_dbus_interface_get_zone_sensor_at_index(PrefObject *obj, gint zone_index, gint sensor_index, gchar **sensor_out, GError **error) { char *sensor_str; thd_log_debug("thd_dbus_interface_get_zone_sensor_at_index %d\n", zone_index); cthd_zone *zone = thd_engine->user_get_zone(zone_index); if (!zone) return FALSE; cthd_sensor *sensor = zone->get_sensor_at_index(sensor_index); if (!sensor) return FALSE; sensor_str = g_new(char, MAX_DBUS_REPLY_STR_LEN + 1); if (!sensor_str) return FALSE; strncpy(sensor_str, sensor->get_sensor_type().c_str(), MAX_DBUS_REPLY_STR_LEN); sensor_str[MAX_DBUS_REPLY_STR_LEN] = '\0'; *sensor_out = sensor_str; return TRUE; } gboolean thd_dbus_interface_get_zone_trip_at_index(PrefObject *obj, gint zone_index, gint trip_index, int *temp, int *trip_type, int *sensor_id, int *cdev_size, GArray **cdev_ids, GError **error) { thd_log_debug("thd_dbus_interface_get_zone_sensor_at_index %d\n", zone_index); cthd_zone *zone = thd_engine->user_get_zone(zone_index); if (!zone) return FALSE; cthd_trip_point *trip = zone->get_trip_at_index(trip_index); if (!trip) return FALSE; *temp = trip->get_trip_temp(); *trip_type = trip->get_trip_type(); *sensor_id = trip->get_sensor_id(); *cdev_size = trip->get_cdev_count(); GArray *garray; garray = g_array_new(FALSE, FALSE, sizeof(gint)); for (int i = 0; i < *cdev_size; i++) { trip_pt_cdev_t cdev_trip; int index; cdev_trip = trip->get_cdev_at_index(i); index = cdev_trip.cdev->thd_cdev_get_index(); g_array_prepend_val(garray, index); } *cdev_ids = garray; return TRUE; } gboolean thd_dbus_interface_get_cdev_count(PrefObject *obj, int *count, GError **error) { *count = thd_engine->get_cdev_count(); return TRUE; } gboolean thd_dbus_interface_get_cdev_information(PrefObject *obj, gint index, gchar **cdev_out, gint *min_state, gint *max_state, gint *curr_state, GError **error) { char *cdev_str; thd_log_debug("thd_dbus_interface_get_cdev_information %d\n", index); cthd_cdev *cdev = thd_engine->user_get_cdev(index); if (!cdev) { thd_log_debug("cthd_dbus_interface_get_cdev_information: Invalid cdev index %d\n", index); return FALSE; } cdev_str = g_new(char, MAX_DBUS_REPLY_STR_LEN + 1); if (!cdev_str) return FALSE; strncpy(cdev_str, cdev->get_cdev_type().c_str(), MAX_DBUS_REPLY_STR_LEN); cdev_str[MAX_DBUS_REPLY_STR_LEN] = '\0'; *cdev_out = cdev_str; *min_state = cdev->get_min_state(); *max_state = cdev->get_max_state(); *curr_state = cdev->get_curr_state(); return TRUE; } gboolean thd_dbus_interface_add_zone_passive(PrefObject *obj, gchar *zone_name, gint trip_temp, gchar *sensor_name, gchar *cdev_name, GError **error) { int ret; g_assert(obj != nullptr); if (zone_name == nullptr || !is_valid_thermal_object_name(zone_name)) return FALSE; thd_log_debug("thd_dbus_interface_add_zone_passive %s\n", (char*) zone_name); ret = thd_engine->user_add_zone(zone_name, trip_temp, sensor_name, cdev_name); if (ret == THD_SUCCESS) return TRUE; else return FALSE; } gboolean thd_dbus_interface_set_zone_status(PrefObject *obj, gchar *zone_name, int status, GError **error) { int ret; g_assert(obj != nullptr); thd_log_debug("thd_dbus_interface_set_zone_status %s\n", (char*) zone_name); ret = thd_engine->user_set_zone_status(zone_name, status); if (ret == THD_SUCCESS) return TRUE; else return FALSE; } gboolean thd_dbus_interface_get_zone_status(PrefObject *obj, gchar *zone_name, int *status, GError **error) { int ret; g_assert(obj != nullptr); thd_log_debug("thd_dbus_interface_set_zone_status %s\n", (char*) zone_name); ret = thd_engine->user_get_zone_status(zone_name, status); if (ret == THD_SUCCESS) return TRUE; else return FALSE; } gboolean thd_dbus_interface_delete_zone(PrefObject *obj, gchar *zone_name, GError **error) { int ret; g_assert(obj != nullptr); thd_log_debug("thd_dbus_interface_delete_zone %s\n", (char*) zone_name); ret = thd_engine->user_delete_zone(zone_name); if (ret == THD_SUCCESS) return TRUE; else return FALSE; } gboolean thd_dbus_interface_add_cooling_device(PrefObject *obj, gchar *cdev_name, gchar *path, gint min_state, gint max_state, gint step, GError **error) { int ret; g_assert(obj != nullptr); thd_log_debug("thd_dbus_interface_add_cooling_device %s\n", (char*) cdev_name); // Using a device in /etc is a security issue if ((strlen(path) >= strlen("/etc")) && !strncmp(path, "/etc", strlen("/etc"))) return FALSE; ret = thd_engine->user_add_cdev(cdev_name, path, min_state, max_state, step); if (ret == THD_SUCCESS) return TRUE; else return FALSE; } gboolean thd_dbus_interface_update_cooling_device(PrefObject *obj, gchar *cdev_name, gchar *path, gint min_state, gint max_state, gint step, GError **error) { g_assert(obj != nullptr); // Using a device in /etc is a security issue if ((strlen(path) >= strlen("/etc")) && !strncmp(path, "/etc", strlen("/etc"))) return FALSE; return thd_dbus_interface_add_cooling_device(obj, cdev_name, path, min_state, max_state, step, error); } gboolean thd_dbus_interface_get_sensor_temperature(PrefObject *obj, int index, unsigned int *temperature, GError **error) { int ret; ret = thd_engine->get_sensor_temperature(index, temperature); if (ret == THD_SUCCESS) return TRUE; else return FALSE; } #ifdef GDBUS #pragma GCC diagnostic push static GDBusInterfaceVTable interface_vtable; extern gint own_id; static GDBusNodeInfo * thd_dbus_load_introspection(const gchar *filename, GError **error) { g_autoptr(GBytes) data = nullptr; g_autofree gchar *path = nullptr; path = g_build_filename("/org/freedesktop/thermald", filename, nullptr); data = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, error); if (data == nullptr) return nullptr; return g_dbus_node_info_new_for_xml((gchar *)g_bytes_get_data(data, nullptr), error); } static void thd_dbus_handle_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { PrefObject *obj = PREF_OBJECT(user_data); g_autoptr(GError) error = nullptr; auto return_dbus_error = [&](const gchar *failed_method) { if (error != nullptr) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "%s failed", failed_method); }; thd_log_debug("Dbus method called %s %s.\n", interface_name, method_name); if (thd_engine->check_feature(DBUS_CONTROL) == 0) { thd_log_info("Dbus control support is disabled by config file\n"); GError *error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "DBUS control support is disabled by config file"); g_dbus_method_invocation_return_gerror(invocation, error); g_error_free(error); return; } if (g_strcmp0(method_name, "AddCoolingDevice") == 0) { g_autofree gchar *cdev_name = nullptr; g_autofree gchar *path = nullptr; gint min_state; gint max_state; gint step; g_variant_get(parameters, "(ssiii)", &cdev_name, &path, &min_state, &max_state, &step); thd_dbus_interface_add_cooling_device(obj, cdev_name, path, min_state, max_state, step, &error); if (error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "AddSensor") == 0) { g_autofree gchar *sensor_name = nullptr; g_autofree gchar *path = nullptr; g_variant_get(parameters, "(ss)", &sensor_name, &path); thd_dbus_interface_add_sensor(obj, sensor_name, path, &error); if (error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "AddTripPoint") == 0) { g_autofree gchar *zone_name = nullptr; guint trip_point_temp; g_autofree gchar *trip_point_sensor = nullptr; g_autofree gchar *trip_point_cdev = nullptr; g_variant_get(parameters, "(suss)", &zone_name, &trip_point_temp, &trip_point_sensor, &trip_point_cdev); thd_dbus_interface_add_trip_point(obj, zone_name, &error); if(error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "AddVirtualSensor") == 0) { g_autofree gchar *sensor_name = nullptr; g_autofree gchar *dep_sensor = nullptr; gdouble slope; gdouble intercept; g_variant_get(parameters, "(ssdd)", &sensor_name, &dep_sensor, &slope, &intercept); thd_dbus_interface_add_virtual_sensor(obj, sensor_name, dep_sensor, slope, intercept, &error); if (error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "AddZonePassive") == 0) { g_autofree gchar *zone_name = nullptr; gint trip_point_temp; g_autofree gchar *trip_point_sensor = nullptr; g_autofree gchar *trip_point_cdev = nullptr; g_variant_get(parameters, "(suss)", &zone_name, &trip_point_temp, &trip_point_sensor, &trip_point_cdev); thd_dbus_interface_add_zone_passive(obj, zone_name, trip_point_temp, trip_point_sensor, trip_point_cdev, &error); if (error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "DeleteTripPoint") == 0) { g_autofree gchar *zone_name = nullptr; guint trip_point_temp; g_variant_get(parameters, "(su)", &zone_name, &trip_point_temp); thd_dbus_interface_delete_trip_point(obj, zone_name, &error); if (error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "DeleteZone") == 0) { g_autofree gchar *zone_name = nullptr; g_variant_get(parameters, "(s)", &zone_name); thd_dbus_interface_delete_zone(obj, zone_name, &error); if (error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "DisableCoolingDevice") == 0) { g_autofree gchar *cdev_name = nullptr; g_variant_get(parameters, "(s)", &cdev_name); thd_dbus_interface_disable_cooling_device(obj, cdev_name, &error); if (error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "GetCdevCount") == 0) { gint count; thd_dbus_interface_get_cdev_count(obj, &count, &error); if (error) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value (invocation, g_variant_new("(u)", count)); return; } if (g_strcmp0(method_name, "GetCdevInformation") == 0) { gint ret; gint index; g_autofree gchar *cdev_out = nullptr; gint min_state; gint max_state; gint curr_state; g_variant_get(parameters, "(u)", &index); ret = thd_dbus_interface_get_cdev_information(obj, index, &cdev_out, &min_state, &max_state, &curr_state, &error); if (error || !ret) { return_dbus_error("GetCdevInformation"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(siii)", cdev_out, min_state, max_state, curr_state)); return; } if (g_strcmp0(method_name, "GetCurrentPreference") == 0) { gboolean ret; g_autofree gchar *cur_pref = nullptr; ret = thd_dbus_interface_get_current_preference(obj, &cur_pref, &error); if (error || !ret) { return_dbus_error("GetCurrentPreference"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", cur_pref)); return; } if (g_strcmp0(method_name, "GetSensorCount") == 0) { gboolean ret; gint count; ret = thd_dbus_interface_get_sensor_count(obj, &count, &error); if (error || !ret) { return_dbus_error("GetSensorCount"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", count)); return; } if (g_strcmp0(method_name, "GetSensorInformation") == 0) { gboolean ret; gint index; g_autofree gchar *sensor_out = nullptr; g_autofree gchar *path = nullptr; gint temp; g_variant_get(parameters, "(u)", &index); ret = thd_dbus_interface_get_sensor_information(obj, index, &sensor_out, &path, &temp, &error); if (error || !ret) { return_dbus_error("GetSensorInformation"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(ssi)", sensor_out, path, temp)); return; } if (g_strcmp0(method_name, "GetSensorTemperature") == 0) { gboolean ret; gint index; guint temperature; g_variant_get(parameters, "(u)", &index); ret = thd_dbus_interface_get_sensor_temperature(obj, index, &temperature, &error); if (error || !ret) { return_dbus_error("GetSensorTemperature"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", temperature)); return; } if (g_strcmp0(method_name, "GetZoneCount") == 0) { gboolean ret; gint count; ret = thd_dbus_interface_get_zone_count(obj, &count, &error); if (error || !ret) { return_dbus_error("GetZoneCount"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", count)); return; } if (g_strcmp0(method_name, "GetZoneInformation") == 0) { gboolean ret; gint index; g_autofree gchar *zone_out = nullptr; gint sensor_count; gint trip_count; gint bound; g_variant_get(parameters, "(u)", &index); ret = thd_dbus_interface_get_zone_information(obj, index, &zone_out, &sensor_count, &trip_count, &bound, &error); if (error || !ret) { return_dbus_error("GetZoneInformation"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(siii)", zone_out, sensor_count, trip_count, bound)); return; } if (g_strcmp0(method_name, "GetZoneSensorAtIndex") == 0) { gboolean ret; gint zone_index; gint sensor_index; g_autofree gchar *sensor_out = nullptr; g_variant_get(parameters, "(uu)", &zone_index, &sensor_index); ret = thd_dbus_interface_get_zone_sensor_at_index(obj, zone_index, sensor_index, &sensor_out, &error); if (error || !ret) { return_dbus_error("GetZoneSensorAtIndex"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", sensor_out)); return; } if (g_strcmp0(method_name, "GetZoneStatus") == 0) { gboolean ret; g_autofree gchar *zone_name = nullptr; gint status; g_variant_get(parameters, "(s)", &zone_name); ret = thd_dbus_interface_get_zone_status(obj, zone_name, &status, &error); if (error || !ret) { return_dbus_error("GetZoneStatus"); return; } g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", status)); return; } if (g_strcmp0(method_name, "GetZoneTripAtIndex") == 0) { gboolean ret; gint zone_index; gint trip_index; gint temp; gint trip_type; gint sensor_id; gint cdev_size; g_autoptr(GArray) cdev_ids = nullptr; g_autoptr(GVariantBuilder) builder = nullptr; GVariant **tmp; GVariant *array = nullptr; g_variant_get(parameters, "(uu)", &zone_index, &trip_index); ret = thd_dbus_interface_get_zone_trip_at_index(obj, zone_index, trip_index, &temp, &trip_type, &sensor_id, &cdev_size, &cdev_ids, &error); if (error || !ret) { return_dbus_error("GetZoneTripAtIndex"); return; } builder = g_variant_builder_new(G_VARIANT_TYPE("(iiiiai)")); g_variant_builder_add_value(builder, g_variant_new_int32(temp)); g_variant_builder_add_value(builder, g_variant_new_int32(trip_type)); g_variant_builder_add_value(builder, g_variant_new_int32(sensor_id)); g_variant_builder_add_value(builder, g_variant_new_int32(cdev_size)); tmp = (GVariant **) g_malloc0(cdev_size * sizeof(GVariant *)); for (int i = 0; i < cdev_size; i++) { tmp[i] = g_variant_new_int32(g_array_index(cdev_ids, gint, i)); } array = g_variant_new_array(G_VARIANT_TYPE_INT32, tmp, cdev_size); g_variant_builder_add_value(builder, array); g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(builder)); g_free(tmp); return; } if (g_strcmp0(method_name, "Reinit") == 0) { thd_dbus_interface_reinit(obj, &error); g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "SetCurrentPreference") == 0) { gboolean ret; g_autofree gchar *pref = nullptr; g_variant_get(parameters, "(s)", &pref); ret = thd_dbus_interface_set_current_preference(obj, pref, &error); if (!ret) { return_dbus_error("SetCurrentPreference"); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "SetUserMaxTemperature") == 0) { gboolean ret; g_autofree gchar *zone_name = nullptr; guint user_set_point_in_milli_degree_celsius; g_variant_get(parameters, "(su)", &zone_name, &user_set_point_in_milli_degree_celsius); ret = thd_dbus_interface_set_user_max_temperature(obj, zone_name, user_set_point_in_milli_degree_celsius, &error); if (!ret) { return_dbus_error("SetUserMaxTemperature"); return; } return; } if (g_strcmp0(method_name, "SetUserPassiveTemperature") == 0) { gboolean ret; g_autofree gchar *zone_name = nullptr; guint user_set_point_in_milli_degree_celsius; g_variant_get(parameters, "(su)", &zone_name, &user_set_point_in_milli_degree_celsius); ret = thd_dbus_interface_set_user_passive_temperature(obj, zone_name, user_set_point_in_milli_degree_celsius, &error); if (!ret) { return_dbus_error("SetUserPassiveTemperature"); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "SetZoneStatus") == 0) { gboolean ret; g_autofree gchar *zone_name = nullptr; gint status; g_variant_get(parameters, "(si)", &zone_name, &status); ret = thd_dbus_interface_set_zone_status(obj, zone_name, status, &error); if (!ret) { return_dbus_error("SetZoneStatus"); return; } g_dbus_method_invocation_return_value(invocation, nullptr); return; } if (g_strcmp0(method_name, "Terminate") == 0) { g_dbus_method_invocation_return_value(invocation, nullptr); thd_dbus_interface_terminate(obj, &error); return; } if (g_strcmp0(method_name, "UpdateCoolingDevice") == 0) { gboolean ret; g_autofree gchar *cdev_name = nullptr; g_autofree gchar *path = nullptr; gint min_state; gint max_state; gint step; g_variant_get(parameters, "(ssiii)", &cdev_name, &path, &min_state, &max_state, &step); ret = thd_dbus_interface_update_cooling_device(obj, cdev_name, path, min_state, max_state, step, &error); if (!ret) { return_dbus_error("UpdateCoolingDevice"); return; } g_dbus_method_invocation_return_value(invocation, nullptr); 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 * thd_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 nullptr; } static gboolean thd_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 thd_dbus_on_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { guint registration_id; GDBusProxy *proxy_id = nullptr; GError *error = nullptr; GDBusNodeInfo *introspection_data = nullptr; if (user_data == nullptr) { thd_log_error("user_data is nullptr\n"); return; } introspection_data = thd_dbus_load_introspection("src/thd_dbus_interface.xml", &error); if (introspection_data == nullptr || error != nullptr) { thd_log_error("Couldn't create introspection data: %s:\n", error->message); return; } registration_id = g_dbus_connection_register_object(connection, "/org/freedesktop/thermald", introspection_data->interfaces[0], &interface_vtable, user_data, nullptr, &error); proxy_id = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, nullptr, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", nullptr, &error); g_assert(registration_id > 0); g_assert(proxy_id != nullptr); } static void thd_dbus_on_name_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { } static void thd_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 thd_dbus_server_init(gboolean (*exit_handler)(void)) { PrefObject *value_obj; value_obj = PREF_OBJECT(g_object_new(PREF_TYPE_OBJECT, nullptr)); if (value_obj == nullptr) { thd_log_error("Failed to create one Value instance:\n"); return THD_FATAL_ERROR; } thd_dbus_exit_callback = exit_handler; interface_vtable.method_call = thd_dbus_handle_method_call; interface_vtable.get_property = thd_dbus_handle_get_property; interface_vtable.set_property = thd_dbus_handle_set_property; own_id = g_bus_own_name(G_BUS_TYPE_SYSTEM, "org.freedesktop.thermald", G_BUS_NAME_OWNER_FLAGS_REPLACE, thd_dbus_on_bus_acquired, thd_dbus_on_name_acquired, thd_dbus_on_name_lost, g_object_ref(value_obj), nullptr); return THD_SUCCESS; } #pragma GCC diagnostic pop #else // Setup dbus server int thd_dbus_server_init(gboolean (*exit_handler)(void)) { DBusGConnection *bus; DBusGProxy *bus_proxy; GError *error = nullptr; guint result; PrefObject *value_obj; thd_dbus_exit_callback = exit_handler; bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); if (error != nullptr) { thd_log_error("Couldn't connect to session bus: %s:\n", error->message); return THD_FATAL_ERROR; } // Get a bus proxy instance bus_proxy = dbus_g_proxy_new_for_name(bus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); if (bus_proxy == nullptr) { thd_log_error("Failed to get a proxy for D-Bus:\n"); return THD_FATAL_ERROR; } thd_log_debug("Registering the well-known name (%s)\n", THD_SERVICE_NAME); // register the well-known name if (!dbus_g_proxy_call(bus_proxy, "RequestName", &error, G_TYPE_STRING, THD_SERVICE_NAME, G_TYPE_UINT, 0, G_TYPE_INVALID, G_TYPE_UINT, &result, G_TYPE_INVALID)) { thd_log_error("D-Bus.RequestName RPC failed: %s\n", error->message); return THD_FATAL_ERROR; } thd_log_debug("RequestName returned %d.\n", result); if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { thd_log_error("Failed to get the primary well-known name:\n"); return THD_FATAL_ERROR; } value_obj = (PrefObject*) g_object_new(PREF_TYPE_OBJECT, nullptr); if (value_obj == nullptr) { thd_log_error("Failed to create one Value instance:\n"); return THD_FATAL_ERROR; } thd_log_debug("Registering it on the D-Bus.\n"); dbus_g_connection_register_g_object(bus, THD_SERVICE_OBJECT_PATH, G_OBJECT(value_obj)); return THD_SUCCESS; } #endif thermal_daemon-2.5.12/src/thd_dbus_interface.xml000066400000000000000000000135741521301715300217150ustar00rootroot00000000000000 thermal_daemon-2.5.12/src/thd_engine.cpp000066400000000000000000001060431521301715300201610ustar00rootroot00000000000000/* * thd_engine.cpp: thermal engine class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ /* This class acts as the parent class of all thermal engines. Main functions are: * - Initialization * - Read cooling devices and thermal zones(sensors), which can be overridden in child * - Starts a poll loop, All the thermal processing happens in this thread's context * - Message processing loop * - If either a poll interval is expired or notified via netlink, it schedules a * a change notification on the associated cthd_zone to read and process. */ #include #include #include #include #include #include #include #include #include #include "thd_engine.h" #include "thd_cdev_therm_sys_fs.h" #include "thd_zone_therm_sys_fs.h" #include "thd_zone_dynamic.h" #include "thd_cdev_gen_sysfs.h" #include "thd_util.h" #include "thd_int3400.h" #include "thd_platform.h" #include "thd_platform_intel.h" #include "thd_platform_arm.h" static void *cthd_engine_thread(void *arg); static bool canonicalize_sysfs_path(const std::string &path, std::string &canonical_path) { if (path.empty()) return false; char resolved_path[PATH_MAX]; if (realpath(path.c_str(), resolved_path) == nullptr) return false; canonical_path.assign(resolved_path); if (!starts_with(canonical_path, "/sys/")) return false; return true; } cthd_engine::cthd_engine(std::string _uuid) : current_cdev_index(0), current_zone_index(0), current_sensor_index(0), parse_thermal_zone_success( false), parse_thermal_cdev_success(false), uuid(std::move(_uuid)), parser_disabled( false), adaptive_mode(false), poll_timeout_msec(-1), wakeup_fd( -1), uevent_fd(-1), control_mode(COMPLEMENTRY), write_pipe_fd( 0), preference(0), status(true), thz_last_uevent_time(0), thz_last_temp_ind_time( 0), thz_last_update_event_time(0), terminate(false), has_invariant_tsc(0), has_aperf(0), proc_list_matched(false), poll_interval_sec(0), poll_sensor_mask(0), fast_poll_sensor_mask(0), saved_poll_interval(0), poll_fd_cnt(0), rt_kernel(false), parser_init_done(false) { thd_engine = pthread_t(); thd_attr = pthread_attr_t(); memset(poll_fds, 0, sizeof(poll_fds)); memset(last_cpu_update, 0, sizeof(last_cpu_update)); } cthd_engine::~cthd_engine() { if (parser_init_done) parser.parser_deinit(); } void cthd_engine::thd_engine_thread() { unsigned int i; int n; time_t tm; int poll_timeout_sec = poll_timeout_msec / 1000; thd_log_info("thd_engine_thread begin\n"); for (;;) { if (terminate) break; n = poll(poll_fds, poll_fd_cnt, poll_timeout_msec); thd_log_debug("poll exit %d polls_fd event %d %d\n", n, poll_fds[0].revents, poll_fds[1].revents); if (n < 0) { thd_log_warn("Write to pipe failed\n"); continue; } time(&tm); rapl_power_meter.rapl_measure_power(); if (n == 0 || (tm - thz_last_temp_ind_time) >= poll_timeout_sec) { if (!status) { thd_log_msg("Thermal Daemon is disabled\n"); continue; } thd_engine_lock(); // Polling mode enabled. Trigger a temp change message for (i = 0; i < zones.size(); ++i) { cthd_zone *zone = zones[i].get(); zone->zone_temperature_notification(0, 0); } thd_engine_unlock(); thz_last_temp_ind_time = tm; } if (uevent_fd >= 0 && (poll_fds[uevent_fd].revents & POLLIN)) { // Kobj uevent if (kobj_uevent.check_for_event()) { time_t tm; time(&tm); thd_log_debug("kobj uevent for thermal\n"); if ((tm - thz_last_uevent_time) >= thz_notify_debounce_interval) { thd_engine_lock(); for (i = 0; i < zones.size(); ++i) { cthd_zone *zone = zones[i].get(); zone->zone_temperature_notification(0, 0); } thd_engine_unlock(); } else { thd_log_debug("IGNORE THZ kevent\n"); } thz_last_uevent_time = tm; } } if (wakeup_fd >= 0 && (poll_fds[wakeup_fd].revents & POLLIN)) { message_capsul_t msg; thd_log_debug("wakeup fd event\n"); int result = read(poll_fds[wakeup_fd].fd, &msg, sizeof(message_capsul_t)); if (result < 0) { thd_log_warn("read on wakeup fd failed\n"); poll_fds[wakeup_fd].revents = 0; continue; } if (proc_message(&msg) < 0) { thd_log_debug("Terminating thread..\n"); } } if ((tm - thz_last_update_event_time) >= thd_poll_interval) { thd_engine_lock(); update_engine_state(); thd_engine_unlock(); thz_last_update_event_time = tm; } workarounds(); } thd_log_debug("thd_engine_thread_end\n"); } bool cthd_engine::set_preference(const int pref) { return true; } #define POWER_FLOOR_ENABLE_ATTRIBUTE "/sys/bus/pci/devices/0000:00:04.0/power_limits/power_floor_enable" #define POWER_FLOOR_STATUS_ATTRIBUTE "/sys/bus/pci/devices/0000:00:04.0/power_limits/power_floor_status" void cthd_engine::enable_power_floor_event() { int fd; /* Enable feature via sysfs knob */ fd = open(POWER_FLOOR_ENABLE_ATTRIBUTE, O_RDWR); if (fd < 0) { thd_log_debug("Unable to open power floor status file\n"); return; } if (write(fd, "1\n", 2) < 0) { thd_log_debug("Unable to enable power floor notifications\n"); close(fd); return; } close(fd); fd = open(POWER_FLOOR_STATUS_ATTRIBUTE, O_RDONLY); if (fd < 0) { thd_log_debug("Unable to open power floor status file\n"); return; } close(fd); cthd_cdev *cdev; cdev = search_cdev("intel_powerclamp"); if (!cdev) { thd_log_debug("Power clamp driver not found!\n"); return; } std::unique_ptr sensor(new cthd_sensor( current_sensor_index, POWER_FLOOR_STATUS_ATTRIBUTE, "power_floor", SENSOR_TYPE_RAW)); if (sensor->sensor_update() != THD_SUCCESS) { return; } std::unique_ptr zone(new cthd_zone_dynamic(current_zone_index, "power_floor", 1, PASSIVE, "power_floor", "intel_powerclamp")); if (!zone) { return; } sensors.push_back(std::move(sensor)); ++current_sensor_index; if (zone->zone_update() != THD_SUCCESS) { // sensor will be deleted when all elements of sensors are deleted from sensors[] return; } /* Add if present */ cthd_cdev *cdev_pci = match_cdev("PCIe_Port_Link_Speed"); if (cdev_pci) { thd_log_debug("PCIe port link cooling device present\n"); cthd_trip_point *trip = zone->get_trip_at_index(0); if (trip) trip->thd_trip_point_add_cdev(*cdev_pci, cthd_trip_point::default_influence); } zone->set_zone_active(); zone->zone_dump(); zones.push_back(std::move(zone)); ++current_zone_index; } int cthd_engine::check_acpi_platform_profile() { // Check PM profile and fail to start for non mobile platforms csys_fs pm_profile_fs("/sys/firmware/acpi/pm_profile"); if (pm_profile_fs.exists()) { std::string pm_profile; pm_profile_fs.read("", pm_profile); thd_log_info("PM profile is %s\n", pm_profile.c_str()); if (pm_profile != "2" && pm_profile != "8") { thd_log_error("Non mobile platform, exiting..\n"); return THD_FATAL_ERROR; } } else { thd_log_info("PM profile is not available, skipping check\n"); } return THD_SUCCESS; } void cthd_engine::thd_parse_features() { features_parser.parser_init(); if (features_parser.start_parse() == THD_SUCCESS) { thd_log_debug("Parsed features from XML config file\n"); int ret = features_parser.start_parse(); if (ret == THD_SUCCESS) { thd_log_debug("Features parsed successfully from XML config file\n"); } else { thd_log_debug("Failed to parse features from XML config file\n"); } features_parser.parser_deinit(); } else { thd_log_debug("No features parsed from XML config file\n"); } } int cthd_engine::thd_engine_init(bool ignore_cpuid_check, bool adaptive) { int ret; if (check_acpi_platform_profile() != THD_SUCCESS) { return THD_FATAL_ERROR; } adaptive_mode = adaptive; if (ignore_cpuid_check) { thd_log_debug("Ignore CPU ID check for MSRs\n"); proc_list_matched = true; } else { check_cpu_id(); if (!proc_list_matched) { if ((parser_init() == THD_SUCCESS) && parser.platform_matched()) { thd_log_msg("Unsupported cpu model, using thermal-conf.xml only\n"); } else { thd_log_msg("Unsupported cpu model, use thermal-conf.xml file or run with --ignore-cpuid-check\n"); return THD_ERROR; } } } ret = read_thermal_sensors(); if (ret != THD_SUCCESS) { thd_log_error("Thermal sysfs Error in reading sensors\n"); // This is a fatal error and daemon will exit return THD_FATAL_ERROR; } ret = read_cooling_devices(); if (ret != THD_SUCCESS) { thd_log_error("Thermal sysfs Error in reading cooling devs\n"); // This is a fatal error and daemon will exit return THD_FATAL_ERROR; } ret = read_thermal_zones(); if (ret != THD_SUCCESS) { thd_log_error("No thermal sensors found\n"); // This is a fatal error and daemon will exit return THD_FATAL_ERROR; } if (power_floor_enable) enable_power_floor_event(); return THD_SUCCESS; } int cthd_engine::thd_engine_start() { int ret; int wake_fds[2]; check_for_rt_kernel(); // Pipe is used for communication between two processes ret = pipe(wake_fds); if (ret) { thd_log_error("Thermal sysfs: pipe creation failed %d:\n", ret); return THD_FATAL_ERROR; } if (fcntl(wake_fds[0], F_SETFL, O_NONBLOCK) < 0) { thd_log_error("Cannot set non-blocking on pipe: %s\n", strerror(errno)); return THD_FATAL_ERROR; } if (fcntl(wake_fds[1], F_SETFL, O_NONBLOCK) < 0) { thd_log_error("Cannot set non-blocking on pipe: %s\n", strerror(errno)); return THD_FATAL_ERROR; } write_pipe_fd = wake_fds[1]; memset(poll_fds, 0, sizeof(poll_fds)); wakeup_fd = poll_fd_cnt; poll_fds[wakeup_fd].fd = wake_fds[0]; poll_fds[wakeup_fd].events = POLLIN; poll_fds[wakeup_fd].revents = 0; poll_fd_cnt++; poll_timeout_msec = -1; if (poll_interval_sec) { thd_log_msg("Polling mode is enabled: %d\n", poll_interval_sec); poll_timeout_msec = poll_interval_sec * 1000; } if (!parser_init() && parser.platform_matched()) { parser.set_default_preference(); int poll_secs = parser.get_polling_interval(); if (poll_secs) { thd_log_info("Poll interval is defined in XML config %d seconds\n", poll_secs); poll_interval_sec = poll_secs; poll_timeout_msec = poll_secs * 1000; } } // Check if polling is disabled and sensors don't support // async mode, in that enable force polling if (!poll_interval_sec) { unsigned int i; for (i = 0; i < zones.size(); ++i) { cthd_zone *zone = zones[i].get(); if (!zone->zone_active_status()) continue; if (!zone->check_sensor_async_status()) { thd_log_msg( "Polling will be enabled as some sensors are not capable to notify asynchronously\n"); poll_timeout_msec = def_poll_interval; break; } } if (i == zones.size()) { thd_log_info("Proceed without polling mode!\n"); } if (check_feature(KOBJECT_UEVENT_SUPPORT) == 0) { thd_log_info("Kobject uevent support is disabled by config file\n"); goto skip_kobj; } uevent_fd = poll_fd_cnt; poll_fds[uevent_fd].fd = kobj_uevent.kobj_uevent_open(); if (poll_fds[uevent_fd].fd < 0) { thd_log_warn("Invalid kobj_uevent handle\n"); uevent_fd = -1; goto skip_kobj; } thd_log_info("FD = %d\n", poll_fds[uevent_fd].fd); kobj_uevent.register_dev_path( (char *) "/devices/virtual/thermal/thermal_zone"); poll_fds[uevent_fd].events = POLLIN; poll_fds[uevent_fd].revents = 0; poll_fd_cnt++; } skip_kobj: #ifndef DISABLE_PTHREAD // Create thread pthread_attr_init(&thd_attr); pthread_attr_setdetachstate(&thd_attr, PTHREAD_CREATE_DETACHED); ret = pthread_create(&thd_engine, &thd_attr, cthd_engine_thread, (void*) this); #else { pid_t childpid; if((childpid = fork()) == - 1) { perror("fork"); exit(EXIT_FAILURE); } if(childpid == 0) { /* Child process closes up input side of pipe */ close(wake_fds[1]); cthd_engine_thread((void*)this); } else { /* Parent process closes up output side of pipe */ close(wake_fds[0]); } } #endif thd_pref.refresh(); preference = thd_pref.get_preference(); thd_log_info("Current user preference is %d\n", preference); if (control_mode == EXCLUSIVE) { thd_log_info("Control is taken over from kernel\n"); takeover_thermal_control(); } return ret; } int cthd_engine::thd_engine_stop() { return THD_SUCCESS; } static void *cthd_engine_thread(void *arg) { cthd_engine *obj = (cthd_engine*) arg; obj->thd_engine_thread(); return nullptr; } void cthd_engine::send_message(message_name_t msg_id, int size, unsigned char *msg) { message_capsul_t msg_cap; memset(&msg_cap, 0, sizeof(message_capsul_t)); msg_cap.msg_id = msg_id; msg_cap.msg_size = (size > MAX_MSG_SIZE) ? MAX_MSG_SIZE : size; if (msg) memcpy(msg_cap.msg, msg, msg_cap.msg_size); int result = write(write_pipe_fd, &msg_cap, sizeof(message_capsul_t)); if (result < 0) thd_log_warn("Write to pipe failed\n"); } void cthd_engine::process_pref_change() { int new_pref; thd_pref.refresh(); new_pref = thd_pref.get_preference(); if (new_pref == PREF_DISABLED) { status = false; return; } status = true; if (preference != new_pref) { thd_log_msg("Preference changed\n"); } preference = new_pref; for (unsigned int i = 0; i < zones.size(); ++i) { cthd_zone *zone = zones[i].get(); zone->update_zone_preference(); } if (control_mode == EXCLUSIVE) { thd_log_info("Control is taken over from kernel\n"); takeover_thermal_control(); } } void cthd_engine::thd_engine_terminate() { send_message(TERMINATE, 0, nullptr); sleep(1); process_terminate(); } int cthd_engine::thd_engine_set_user_max_temp(const char *zone_type, const char *user_set_point) { std::string str(user_set_point); cthd_zone *zone; thd_log_debug("thd_engine_set_user_set_point %s\n", user_set_point); std::locale loc; if (std::isdigit(str[0], loc) == 0) { thd_log_warn("thd_engine_set_user_set_point Invalid set point\n"); return THD_ERROR; } zone = get_zone(zone_type); if (!zone) { thd_log_warn("thd_engine_set_user_set_point Invalid zone\n"); return THD_ERROR; } return zone->update_max_temperature(atoi(user_set_point)); } int cthd_engine::thd_engine_set_user_psv_temp(const char *zone_type, const char *user_set_point) { std::string str(user_set_point); cthd_zone *zone; thd_log_debug("thd_engine_set_user_psv_temp %s\n", user_set_point); std::locale loc; if (std::isdigit(str[0], loc) == 0) { thd_log_warn("thd_engine_set_user_psv_temp Invalid set point\n"); return THD_ERROR; } zone = get_zone(zone_type); if (!zone) { thd_log_warn("thd_engine_set_user_psv_temp Invalid zone\n"); return THD_ERROR; } return zone->update_psv_temperature(atoi(user_set_point)); } void cthd_engine::thermal_zone_change(message_capsul_t *msg) { thermal_zone_notify_t *pmsg = (thermal_zone_notify_t*) msg->msg; for (unsigned i = 0; i < zones.size(); ++i) { cthd_zone *zone = zones[i].get(); if (zone->zone_active_status()) zone->zone_temperature_notification(pmsg->type, pmsg->data); else { thd_log_debug("zone is not active\n"); } } } void cthd_engine::poll_enable_disable(bool status, message_capsul_t *msg) { unsigned int *sensor_id = (unsigned int*) msg->msg; if (status) { poll_sensor_mask |= (1 << (*sensor_id)); poll_timeout_msec = def_poll_interval; thd_log_debug("thd_engine polling enabled via %u\n", *sensor_id); } else { poll_sensor_mask &= ~(1 << (*sensor_id)); if (!poll_sensor_mask) { poll_timeout_msec = -1; thd_log_debug("thd_engine polling last disabled via %u\n", *sensor_id); } } } void cthd_engine::fast_poll_enable_disable(bool status, message_capsul_t *msg) { unsigned int *sensor_id = (unsigned int*) msg->msg; if (status) { fast_poll_sensor_mask |= (1 << (*sensor_id)); if (!saved_poll_interval) saved_poll_interval = poll_timeout_msec; poll_timeout_msec = 1000; thd_log_debug("thd_engine fast polling enabled via %u\n", *sensor_id); } else { fast_poll_sensor_mask &= ~(1 << (*sensor_id)); if (!fast_poll_sensor_mask) { if (saved_poll_interval) poll_timeout_msec = saved_poll_interval; thd_log_debug("thd_engine polling last disabled via %u\n", *sensor_id); } } } int cthd_engine::proc_message(message_capsul_t *msg) { int ret = 0; thd_log_debug("Received message %d\n", msg->msg_id); switch (msg->msg_id) { case WAKEUP: break; case TERMINATE: thd_log_msg("Terminating ...\n"); ret = -1; terminate = true; break; case PREF_CHANGED: process_pref_change(); break; case THERMAL_ZONE_NOTIFY: if (!status) { thd_log_msg("Thermal Daemon is disabled\n"); break; } thermal_zone_change(msg); break; case RELOAD_ZONES: thd_engine_reload_zones(); break; case POLL_ENABLE: if (!poll_interval_sec) { poll_enable_disable(true, msg); } break; case POLL_DISABLE: if (!poll_interval_sec) { poll_enable_disable(false, msg); } break; case FAST_POLL_ENABLE: fast_poll_enable_disable(true, msg); break; case FAST_POLL_DISABLE: fast_poll_enable_disable(false, msg); break; default: break; } return ret; } cthd_cdev *cthd_engine::thd_get_cdev_at_index(int index) { for (int i = 0; i < (int) cdevs.size(); ++i) { if (cdevs[i]->thd_cdev_get_index() == index) return cdevs[i].get(); } return nullptr; } void cthd_engine::takeover_thermal_control() { csys_fs sysfs("/sys/class/thermal/"); DIR *dir; struct dirent *entry; const std::string base_path = "/sys/class/thermal/"; cthd_INT3400 int3400(uuid); thd_log_info("Taking over thermal control\n"); int3400.set_default_uuid(); if ((dir = opendir(base_path.c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (!strncmp(entry->d_name, "thermal_zone", strlen("thermal_zone"))) { // Safe integer parsing for zone number const char *num_str = entry->d_name + strlen("thermal_zone"); int i; if (parse_int_value(std::string(num_str), &i, 0, INT_MAX) != 0) { continue; // Invalid zone number, skip } std::ostringstream policy; std::string curr_policy; std::ostringstream type; std::string thermal_type; std::ostringstream mode; policy << "thermal_zone" << i << "/policy"; if (sysfs.exists(policy.str().c_str())) { int ret; ret = sysfs.read(policy.str(), curr_policy); if (ret >= 0) { zone_preferences.push_back(std::move(curr_policy)); sysfs.write(policy.str(), "user_space"); } } type << "thermal_zone" << i << "/type"; if (sysfs.exists(type.str().c_str())) { sysfs.read(type.str(), thermal_type); thd_log_info("Thermal zone of type %s\n", thermal_type.c_str()); if (thermal_type == "INT3400") { mode << "thermal_zone" << i << "/mode"; sysfs.write(mode.str(), "enabled"); } } } } closedir(dir); } } void cthd_engine::giveup_thermal_control() { if (control_mode != EXCLUSIVE) return; if (zone_preferences.size() == 0) return; thd_log_info("Giving up thermal control\n"); csys_fs sysfs("/sys/class/thermal/"); DIR *dir; struct dirent *entry; const std::string base_path = "/sys/class/thermal/"; int cnt = 0; if ((dir = opendir(base_path.c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (!strncmp(entry->d_name, "thermal_zone", strlen("thermal_zone"))) { // Safe integer parsing for zone number const char *num_str = entry->d_name + strlen("thermal_zone"); int i; if (parse_int_value(std::string(num_str), &i, 0, INT_MAX) != 0) { continue; // Invalid zone number, skip } std::ostringstream policy; std::ostringstream type; std::string thermal_type; std::ostringstream mode; policy << "thermal_zone" << i << "/policy"; if (sysfs.exists(policy.str().c_str())) { sysfs.write(policy.str(), zone_preferences[cnt++]); } type << "thermal_zone" << i << "/type"; if (sysfs.exists(type.str().c_str())) { sysfs.read(type.str(), thermal_type); if (thermal_type == "INT3400") { mode << "thermal_zone" << i << "/mode"; sysfs.write(mode.str(), "disabled"); } } } } closedir(dir); } } void cthd_engine::process_terminate() { thd_log_msg("terminating on user request ..\n"); giveup_thermal_control(); } void cthd_engine::thd_engine_poll_enable(int sensor_id) { send_message(POLL_ENABLE, (int) sizeof(sensor_id), (unsigned char*) &sensor_id); } void cthd_engine::thd_engine_poll_disable(int sensor_id) { send_message(POLL_DISABLE, (int) sizeof(sensor_id), (unsigned char*) &sensor_id); } void cthd_engine::thd_engine_fast_poll_enable(int sensor_id) { send_message(FAST_POLL_ENABLE, (int) sizeof(sensor_id), (unsigned char*) &sensor_id); } void cthd_engine::thd_engine_fast_poll_disable(int sensor_id) { send_message(FAST_POLL_DISABLE, (int) sizeof(sensor_id), (unsigned char*) &sensor_id); } void cthd_engine::thd_engine_reload_zones() { thd_log_msg(" Reloading zones\n"); zones.clear(); int ret = read_thermal_zones(); if (ret != THD_SUCCESS) { thd_log_error("No thermal sensors found\n"); // This is a fatal error and daemon will exit return; } } int cthd_engine::check_cpu_id() { // Create platform instance using factory method std::unique_ptr platform = cthd_platform::create_platform(); if (!platform) { thd_log_error("Failed to create platform instance\n"); proc_list_matched = false; return THD_ERROR; } // Dump platform information platform->dump_platform_info(); // Call platform-specific CPU ID check int ret = platform->check_cpu_id(proc_list_matched); return ret; } void cthd_engine::thd_read_default_thermal_sensors() { DIR *dir; struct dirent *entry; const std::string base_path = "/sys/class/thermal/"; int max_index = 0; if ((dir = opendir("/sys/class/thermal/thermal_zone1/")) == nullptr) { thd_log_info("Waiting for thermal sysfs to be ready\n"); sleep(2); } else { closedir(dir); } thd_log_debug("thd_read_default_thermal_sensors\n"); if ((dir = opendir(base_path.c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (!strncmp(entry->d_name, "thermal_zone", strlen("thermal_zone"))) { int i; i = atoi(entry->d_name + strlen("thermal_zone")); if (i > max_index) max_index = i; std::unique_ptr sensor(new cthd_sensor(i, base_path + entry->d_name + "/", "")); if (sensor->sensor_update() != THD_SUCCESS) { continue; } sensors.push_back(std::move(sensor)); } } closedir(dir); } if (sensors.size()) current_sensor_index = max_index + 1; thd_log_info("thd_read_default_thermal_sensors loaded %zu sensors\n", sensors.size()); } void cthd_engine::thd_read_default_thermal_zones() { DIR *dir; struct dirent *entry; const std::string base_path = "/sys/class/thermal/"; int max_index = 0; thd_log_debug("thd_read_default_thermal_zones\n"); if ((dir = opendir(base_path.c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (!strncmp(entry->d_name, "thermal_zone", strlen("thermal_zone"))) { int i; i = atoi(entry->d_name + strlen("thermal_zone")); if (i > max_index) max_index = i; std::unique_ptr zone(new cthd_sysfs_zone(i, "/sys/class/thermal/thermal_zone")); if (zone->zone_update() != THD_SUCCESS) { continue; } if (control_mode == EXCLUSIVE) zone->set_zone_active(); zones.push_back(std::move(zone)); } } closedir(dir); } if (zones.size()) current_zone_index = max_index + 1; thd_log_info("thd_read_default_thermal_zones loaded %zu zones\n", zones.size()); } void cthd_engine::thd_read_default_cooling_devices() { DIR *dir; struct dirent *entry; const std::string base_path = "/sys/class/thermal/"; int max_index = 0; thd_log_debug("thd_read_default_cooling devices\n"); if ((dir = opendir(base_path.c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (!strncmp(entry->d_name, "cooling_device", strlen("cooling_device"))) { int i; i = atoi(entry->d_name + strlen("cooling_device")); if (i > max_index) max_index = i; std::unique_ptr cdev(new cthd_sysfs_cdev(i, "/sys/class/thermal/")); if (cdev->update() != THD_SUCCESS) { continue; } cdevs.push_back(std::move(cdev)); } } closedir(dir); } if (cdevs.size()) current_cdev_index = max_index + 1; thd_log_info("thd_read_default_cooling devices loaded %zu cdevs\n", cdevs.size()); } ppcc_t* cthd_engine::get_ppcc_param(const std::string& name) { return parser.get_ppcc_param(name); } cthd_zone* cthd_engine::search_zone(const std::string& name) { cthd_zone *zone; for (unsigned int i = 0; i < zones.size(); ++i) { zone = zones[i].get(); if (!zone) continue; if (zone->get_zone_type() == name) return zone; if (!name.compare(0, 4, "pch_")) { if (!zone->get_zone_type().compare(0, 4, "pch_")) { thd_log_info("Matching partial zone %s %s\n", name.c_str(), zone->get_zone_type().c_str()); return zone; } } } return nullptr; } cthd_cdev* cthd_engine::search_cdev(const std::string& name) { cthd_cdev *cdev; for (unsigned int i = 0; i < cdevs.size(); ++i) { cdev = cdevs[i].get(); if (!cdev) continue; if (cdev->get_cdev_type() == name) return cdev; if (cdev->get_cdev_alias() == name) return cdev; } return nullptr; } // Partial match instead of full match cthd_cdev* cthd_engine::match_cdev(const std::string& name) { cthd_cdev *cdev; for (unsigned int i = 0; i < cdevs.size(); ++i) { cdev = cdevs[i].get(); if (!cdev) continue; std::string cdev_name = cdev->get_cdev_type(); size_t match = cdev_name.find(name); if (match != std::string::npos) { return cdev; } } return nullptr; } cthd_sensor* cthd_engine::search_sensor(const std::string& name) { cthd_sensor *sensor; for (unsigned int i = 0; i < sensors.size(); ++i) { sensor = sensors[i].get(); if (!sensor) continue; if (sensor->get_sensor_type() == name) return sensor; if (!name.compare(0, 4, "pch_")) { if (!sensor->get_sensor_type().compare(0, 4, "pch_")) { thd_log_info("Matching partial %s %s\n", name.c_str(), sensor->get_sensor_type().c_str()); return sensor; } } } return nullptr; } cthd_sensor* cthd_engine::get_sensor(int index) { if (index >= 0 && index < (int) sensors.size()) return sensors[index].get(); else return nullptr; } int cthd_engine::get_sensor_temperature(int index, unsigned int *temperature) { if (index >= 0 && index < (int) sensors.size()) { *temperature = sensors[index]->read_temperature(); return THD_SUCCESS; } else return THD_ERROR; } cthd_zone* cthd_engine::get_zone(int index) { if (index == -1) return nullptr; if (index >= 0 && index < (int) zones.size()) return zones[index].get(); else return nullptr; } cthd_zone* cthd_engine::get_zone(const std::string& type) { cthd_zone *zone; for (unsigned int i = 0; i < zones.size(); ++i) { zone = zones[i].get(); if (zone->get_zone_type() == type) return zone; } return nullptr; } // Code copied from // https://web.archive.org/web/20130822155153/https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO#Runtime_detection_of_an_RT-PREEMPT_Kernel void cthd_engine::check_for_rt_kernel() { struct utsname _uname; char *crit1 = nullptr; int crit2 = 0; FILE *fd; uname(&_uname); crit1 = strcasestr(_uname.version, "PREEMPT RT"); if ((fd = fopen("/sys/kernel/realtime", "r")) != nullptr) { int flag; crit2 = ((fscanf(fd, "%d", &flag) == 1) && (flag == 1)); fclose(fd); } if (crit1 && crit2) rt_kernel = true; else rt_kernel = false; thd_log_info("Running on a %s kernel\n", rt_kernel ? "PREEMPT RT" : "vanilla"); } int cthd_engine::user_add_sensor(std::string name, const std::string& path) { std::string canonical_path; if (!canonicalize_sysfs_path(path, canonical_path)) { thd_log_debug("Invalid sensor path %s\n", path.c_str()); return THD_ERROR; } std::lock_guard guard(thd_engine_mutex); for (unsigned int i = 0; i < sensors.size(); ++i) { if (sensors[i]->get_sensor_type() == name) { cthd_sensor *sensor = sensors[i].get(); sensor->update_path(std::move(canonical_path)); return THD_SUCCESS; } } std::unique_ptr sensor(new cthd_sensor(current_sensor_index, std::move(canonical_path), std::move(name), SENSOR_TYPE_RAW)); if (sensor->sensor_update() != THD_SUCCESS) { return THD_ERROR; } sensors.push_back(std::move(sensor)); ++current_sensor_index; send_message(WAKEUP, 0, nullptr); return THD_SUCCESS; } int cthd_engine::user_add_virtual_sensor(std::string name, std::string dep_sensor, double slope, double intercept) { cthd_sensor *sensor; int ret; if (!is_valid_thermal_object_name(name)) { thd_log_warn("Invalid virtual sensor name %s\n", name.c_str()); return THD_ERROR; } if (!is_valid_finite_value(slope, -1000.0, 1000.0) || !is_valid_finite_value(intercept, -1000.0, 1000.0)) { thd_log_warn("Invalid virtual sensor parameters for %s\n", name.c_str()); return THD_ERROR; } std::lock_guard guard(thd_engine_mutex); for (unsigned int i = 0; i < sensors.size(); ++i) { if (sensors[i]->get_sensor_type() == name) { sensor = sensors[i].get(); if (sensor->is_virtual()) { cthd_sensor_virtual *virt_sensor = (cthd_sensor_virtual *) sensor; ret = virt_sensor->sensor_update_param(dep_sensor, slope, intercept); } else { return THD_ERROR; } return ret; } } std::unique_ptr virt_sensor(new cthd_sensor_virtual( current_sensor_index, std::move(name), dep_sensor, slope, intercept)); if (virt_sensor->sensor_update() != THD_SUCCESS) { return THD_ERROR; } sensors.push_back(std::move(virt_sensor)); ++current_sensor_index; send_message(WAKEUP, 0, nullptr); return THD_SUCCESS; } cthd_sensor *cthd_engine::user_get_sensor(unsigned int index) { if (index < sensors.size()) return sensors[index].get(); else return nullptr; } cthd_zone *cthd_engine::user_get_zone(unsigned int index) { if (index < zones.size()) return zones[index].get(); else return nullptr; } cthd_cdev *cthd_engine::user_get_cdev(unsigned int index) { if (index < cdevs.size()) return cdevs[index].get(); else return nullptr; } int cthd_engine::user_set_psv_temp(const std::string& name, unsigned int temp) { cthd_zone *zone; int ret; if (!is_valid_thermal_object_name(name)) return THD_ERROR; std::lock_guard guard(thd_engine_mutex); zone = get_zone(name); if (!zone) { thd_log_warn("user_set_psv_temp\n"); return THD_ERROR; } thd_log_info("Setting psv %u\n", temp); ret = zone->update_psv_temperature(temp); return ret; } int cthd_engine::user_set_max_temp(const std::string& name, unsigned int temp) { cthd_zone *zone; int ret; if (!is_valid_thermal_object_name(name)) return THD_ERROR; std::lock_guard guard(thd_engine_mutex); zone = get_zone(name); if (!zone) { thd_log_warn("user_set_max_temp\n"); return THD_ERROR; } thd_log_info("Setting max %u\n", temp); ret = zone->update_max_temperature(temp); return ret; } int cthd_engine::user_add_zone(std::string zone_name, unsigned int trip_temp, std::string sensor_name, std::string cdev_name) { if (!is_valid_thermal_object_name(zone_name)) { thd_log_warn("Invalid zone name %s\n", zone_name.c_str()); return THD_ERROR; } std::lock_guard guard(thd_engine_mutex); int ret = THD_SUCCESS; std::unique_ptr zone(new cthd_zone_dynamic(current_zone_index, std::move(zone_name), trip_temp, PASSIVE, std::move(sensor_name), std::move(cdev_name))); if (!zone) { return THD_ERROR; } if (zone->zone_update() == THD_SUCCESS) { zone->set_zone_active(); zones.push_back(std::move(zone)); ++current_zone_index; } else { return THD_ERROR; } for (unsigned int i = 0; i < zones.size(); ++i) { zones[i]->zone_dump(); } return ret; } int cthd_engine::user_set_zone_status(const std::string& name, int status) { cthd_zone *zone; if (!is_valid_thermal_object_name(name)) return THD_ERROR; std::lock_guard guard(thd_engine_mutex); zone = get_zone(name); if (!zone) { return THD_ERROR; } thd_log_info("Zone Set status %d\n", status); if (status) zone->set_zone_active(); else zone->set_zone_inactive(); return THD_SUCCESS; } int cthd_engine::user_get_zone_status(const std::string& name, int *status) { cthd_zone *zone; if (!is_valid_thermal_object_name(name)) return THD_ERROR; std::lock_guard guard(thd_engine_mutex); zone = get_zone(name); if (!zone) { return THD_ERROR; } if (zone->zone_active_status()) *status = 1; else *status = 0; return THD_SUCCESS; } int cthd_engine::user_delete_zone(const std::string& name) { if (!is_valid_thermal_object_name(name)) return THD_ERROR; std::lock_guard guard(thd_engine_mutex); for (unsigned int i = 0; i < zones.size(); ++i) { if (zones[i]->get_zone_type() == name) { zones.erase(zones.begin() + i); break; } } for (unsigned int i = 0; i < zones.size(); ++i) { zones[i]->zone_dump(); } return THD_SUCCESS; } int cthd_engine::user_add_cdev(std::string cdev_name, const std::string& cdev_path, int min_state, int max_state, int step) { cthd_cdev *cdev; std::string canonical_cdev_path; if (!canonicalize_sysfs_path(cdev_path, canonical_cdev_path)) { thd_log_debug("Invalid cdev path %s\n", cdev_path.c_str()); return THD_ERROR; } std::lock_guard guard(thd_engine_mutex); // Check if there is existing cdev with this name and path cdev = search_cdev(cdev_name); if (!cdev) { std::unique_ptr cdev_sysfs(new cthd_gen_sysfs_cdev(current_cdev_index, std::move(canonical_cdev_path))); if (!cdev_sysfs) { return THD_ERROR; } cdev_sysfs->set_cdev_type(std::move(cdev_name)); if (cdev_sysfs->update() != THD_SUCCESS) { return THD_ERROR; } cdevs.push_back(std::move(cdev_sysfs)); cdev = cdevs.back().get(); ++current_cdev_index; } cdev->set_min_state(min_state); cdev->set_max_state(max_state); cdev->set_inc_dec_value(step); for (unsigned int i = 0; i < cdevs.size(); ++i) { cdevs[i]->cdev_dump(); } return THD_SUCCESS; } int cthd_engine::parser_init() { if (check_feature(XML_THERMAL_CONFIG) == 0) { thd_log_info("Parser is disabled by config file\n"); parser_disabled = true; } if (parser_disabled) return THD_ERROR; if (parser_init_done) return THD_SUCCESS; if (parser.parser_init(get_config_file()) == THD_SUCCESS) { if (parser.start_parse() == THD_SUCCESS) { parser.dump_thermal_conf(); parser_init_done = true; return THD_SUCCESS; } } return THD_ERROR; } void cthd_engine::parser_deinit() { if (parser_init_done) { parser.parser_deinit(); parser_init_done = false; } } int cthd_engine::debug_mode_on(void) { static constexpr char debug_mode[] = TDRUNDIR "/debug_mode"; struct stat s; if (stat(debug_mode, &s)) return 0; return 1; } thermal_daemon-2.5.12/src/thd_engine.h000066400000000000000000000176331521301715300176340ustar00rootroot00000000000000/* * thd_engine.h: thermal engine class interface * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_ENGINE_H_ #define THD_ENGINE_H_ #include #include #include #include #include #include "thd_common.h" #include "thd_sys_fs.h" #include "thd_preference.h" #include "thd_sensor.h" #include "thd_sensor_virtual.h" #include "thd_zone.h" #include "thd_cdev.h" #include "thd_parse.h" #include "thd_kobj_uevent.h" #include "thd_rapl_power_meter.h" #include "thd_features_parse.h" #define MAX_MSG_SIZE 512 #define THD_NUM_OF_POLL_FDS 10 typedef enum : uint8_t { WAKEUP, TERMINATE, PREF_CHANGED, THERMAL_ZONE_NOTIFY, RELOAD_ZONES, POLL_ENABLE, POLL_DISABLE, FAST_POLL_ENABLE, FAST_POLL_DISABLE, } message_name_t; // This defines whether the thermal control is entirely done by // this daemon or it just complements, what is done in kernel typedef enum : uint8_t { COMPLEMENTRY, EXCLUSIVE, } control_mode_t; typedef struct { message_name_t msg_id; int msg_size; unsigned long msg[MAX_MSG_SIZE]; } message_capsul_t; class cthd_engine { protected: std::vector> zones; std::vector> sensors; std::vector> cdevs; int current_cdev_index; int current_zone_index; int current_sensor_index; bool parse_thermal_zone_success; bool parse_thermal_cdev_success; std::string uuid; bool parser_disabled; bool adaptive_mode; private: int poll_timeout_msec; int wakeup_fd; int uevent_fd; control_mode_t control_mode; int write_pipe_fd; int preference; bool status; time_t thz_last_uevent_time; time_t thz_last_temp_ind_time; time_t thz_last_update_event_time; bool terminate; int has_invariant_tsc; int has_aperf; bool proc_list_matched; int poll_interval_sec; cthd_preference thd_pref; unsigned int poll_sensor_mask; unsigned int fast_poll_sensor_mask; int saved_poll_interval; std::string config_file; pthread_t thd_engine; pthread_attr_t thd_attr; std::mutex thd_engine_mutex; std::vector zone_preferences; static constexpr int thz_notify_debounce_interval = 3; struct pollfd poll_fds[THD_NUM_OF_POLL_FDS]; int poll_fd_cnt; bool rt_kernel; cthd_kobj_uevent kobj_uevent; bool parser_init_done; int proc_message(message_capsul_t *msg); void process_pref_change(); void thermal_zone_change(message_capsul_t *msg); void process_terminate(); void check_for_rt_kernel(); public: static constexpr int max_thermal_zones = 10; static constexpr int max_cool_devs = 50; static constexpr int def_poll_interval = 4000; static constexpr int soft_cdev_start_index = 100; cthd_parse parser; cthd_features_parse features_parser; cthd_rapl_power_meter rapl_power_meter; cthd_engine(std::string _uuid); virtual ~cthd_engine(); void set_control_mode(control_mode_t mode) { control_mode = mode; } control_mode_t get_control_mode() { return control_mode; } void thd_engine_thread(); virtual int thd_engine_init(bool ignore_cpuid_check, bool adaptive = false); virtual int thd_engine_start(); void thd_parse_features(); int thd_engine_stop(); int check_cpu_id(); bool set_preference(const int pref); void thd_engine_terminate(); void thd_engine_calibrate(); int thd_engine_set_user_max_temp(const char *zone_type, const char *user_set_point); int thd_engine_set_user_psv_temp(const char *zone_type, const char *user_set_point); void poll_enable_disable(bool status, message_capsul_t *msg); void fast_poll_enable_disable(bool status, message_capsul_t *msg); cthd_cdev *thd_get_cdev_at_index(int index); void send_message(message_name_t msg_id, int size, unsigned char *msg); void takeover_thermal_control(); void giveup_thermal_control(); void thd_engine_poll_enable(int sensor_id); void thd_engine_poll_disable(int sensor_id); void thd_engine_fast_poll_enable(int sensor_id); void thd_engine_fast_poll_disable(int sensor_id); void thd_read_default_thermal_sensors(); void thd_read_default_thermal_zones(); void thd_read_default_cooling_devices(); virtual void update_engine_state() {}; virtual int read_thermal_sensors() { return 0; } ; virtual int read_thermal_zones() { return 0; } ; virtual int read_cooling_devices() { return 0; } ; int use_custom_zones() { return parse_thermal_zone_success; } int use_custom_cdevs() { return parse_thermal_cdev_success; } static constexpr int max_cpu_count = 64; time_t last_cpu_update[max_cpu_count]; virtual bool apply_cpu_operation(int cpu) { return false; } int get_poll_timeout_ms() { return poll_timeout_msec; } int get_poll_timeout_sec() { return poll_timeout_msec / 1000; } void thd_engine_reload_zones(); bool processor_id_match() { return proc_list_matched; } int get_poll_interval() { return poll_interval_sec; } void set_poll_interval(int val) { poll_interval_sec = val; } int get_preference() { return preference; } void set_config_file(std::string conf_file) { config_file = std::move(conf_file); } std::string get_config_file() { return config_file; } virtual ppcc_t *get_ppcc_param(const std::string& name); virtual int search_idsp(const std::string& name) { return THD_ERROR; } cthd_zone *search_zone(const std::string& name); cthd_cdev *search_cdev(const std::string& name); cthd_cdev *match_cdev(const std::string& name); cthd_sensor *search_sensor(const std::string& name); cthd_sensor *get_sensor(int index); cthd_zone *get_zone(int index); cthd_zone *get_zone(const std::string& type); int get_sensor_temperature(int index, unsigned int *temperature); unsigned int get_sensor_count() { return sensors.size(); } unsigned int get_zone_count() { return zones.size(); } unsigned int get_cdev_count() { return cdevs.size(); } void add_zone(std::unique_ptr zone) { zones.push_back(std::move(zone)); } bool rt_kernel_status() { return rt_kernel; } virtual void workarounds() { } void thd_engine_lock() { thd_engine_mutex.lock(); } void thd_engine_unlock() { thd_engine_mutex.unlock(); } // User/External messages int user_add_sensor(std::string name, const std::string& path); cthd_sensor *user_get_sensor(unsigned int index); cthd_zone *user_get_zone(unsigned int index); int user_add_virtual_sensor(std::string name, std::string dep_sensor, double slope, double intercept); int user_set_psv_temp(const std::string& name, unsigned int temp); int user_set_max_temp(const std::string& name, unsigned int temp); int user_add_zone(std::string zone_name, unsigned int trip_temp, std::string sensor_name, std::string cdev_name); int user_set_zone_status(const std::string& name, int status); int user_get_zone_status(const std::string& name, int *status); int user_delete_zone(const std::string& name); int user_add_cdev(std::string cdev_name, const std::string& cdev_path, int min_state, int max_state, int step); cthd_cdev *user_get_cdev(unsigned int index); void enable_power_floor_event(); int parser_init(); void parser_deinit(); int debug_mode_on(void); int check_acpi_platform_profile(); int check_feature(thermald_feature_names_t feature) { if (feature >= MAX_FEATURE) { return THD_ERROR; } return features_parser.feature_list[feature]; } }; #endif /* THD_ENGINE_H_ */ thermal_daemon-2.5.12/src/thd_engine_adaptive.cpp000066400000000000000000000605351521301715300220430ustar00rootroot00000000000000/* * cthd_engine_adaptive.cpp: Adaptive thermal engine * * Copyright (C) 2013 Intel Corporation. All rights reserved. * Copyright 2020 Google LLC * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name Matthew Garrett * */ #include #include #include #include #include #include #include #include #include #include #include "thd_engine_adaptive.h" #include "thd_sensor_rapl_power.h" #include "thd_zone_dynamic.h" int cthd_engine_adaptive::install_passive(struct psv *psv) { std::string psv_zone; size_t pos = psv->target.find_last_of('.'); if (pos == std::string::npos) psv_zone = psv->target; else psv_zone = psv->target.substr(pos + 1); while (!psv_zone.empty() && psv_zone.back() == '_') { psv_zone.pop_back(); } cthd_zone *zone = search_zone(psv_zone); if (!zone) { if (!psv_zone.compare(0, 4, "B0D4")) { psv_zone = "TCPU"; zone = search_zone(psv_zone); } if (!zone) { if (!psv_zone.compare(0, 4, "TCPU")) { psv_zone = "B0D4"; zone = search_zone(psv_zone); } if (!zone) { thd_log_warn("Unable to find a zone for %s\n", psv_zone.c_str()); return THD_ERROR; } } } std::string psv_cdev; pos = psv->source.find_last_of('.'); if (pos == std::string::npos) psv_cdev = psv->source; else psv_cdev = psv->source.substr(pos + 1); while (!psv_cdev.empty() && psv_cdev.back() == '_') { psv_cdev.pop_back(); } cthd_cdev *cdev = search_cdev(psv_cdev); if (!cdev) { if (!psv_cdev.compare(0, 4, "TCPU")) { psv_cdev = "B0D4"; cdev = search_cdev(psv_cdev); } if (!cdev) { thd_log_warn("Unable to find a cooling device for %s\n", psv_cdev.c_str()); return THD_ERROR; } } cthd_sensor *sensor = search_sensor(psv_zone); if (!sensor) { thd_log_warn("Unable to find a sensor for %s\n", psv_zone.c_str()); return THD_ERROR; } int temp = DECI_KELVIN_TO_CELSIUS(psv->temp) * 1000; int target_state = 0; if (psv->limit.length()) { if (!strncasecmp(psv->limit.c_str(), "MAX", 3)) { target_state = TRIP_PT_INVALID_TARGET_STATE; } else if (!strncasecmp(psv->limit.c_str(), "MIN", 3)) { target_state = 0; } else { std::istringstream buffer(psv->limit); buffer >> target_state; target_state *= 1000; } } cthd_trip_point trip_pt(zone->get_trip_count(), PASSIVE, temp, 0, zone->get_zone_index(), sensor->get_index(), SEQUENTIAL); trip_pt.thd_trip_point_add_cdev(*cdev, cthd_trip_point::default_influence, psv->sample_period / 10, target_state ? 1 : 0, target_state, nullptr, 0, 0, 0); zone->add_trip(trip_pt, 1); zone->zone_cdev_set_binded(); zone->set_zone_active(); return 0; } void cthd_engine_adaptive::set_trip(const std::string& target, const std::string& argument) { std::string psv_zone; float float_temp = stof(argument, nullptr); int temp = (int) (float_temp * 1000); size_t pos = target.find_last_of('.'); if (pos == std::string::npos) psv_zone = target; else psv_zone = target.substr(pos + 1); while (!psv_zone.empty() && psv_zone.back() == '_') { psv_zone.pop_back(); } cthd_zone *zone = search_zone(psv_zone); if (!zone) { thd_log_warn("Unable to find a zone for %s\n", psv_zone.c_str()); return; } int index = 0; cthd_trip_point *trip = zone->get_trip_at_index(index); while (trip != nullptr) { if (trip->get_trip_type() == PASSIVE) { trip->update_trip_temp(temp); return; } index++; trip = zone->get_trip_at_index(index); } thd_log_warn("Unable to find a passive trippoint for %s\n", target.c_str()); } void cthd_engine_adaptive::psvt_consolidate() { /* Once all tables are installed, we need to consolidate since * thermald has different implementation. * If there is only entry of type MAX, then simply use thermald default at temperature + 1 * If there is a next trip after MAX for a target, then choose a temperature limit in the middle */ for (unsigned int i = 0; i < zones.size(); ++i) { cthd_zone *zone = zones[i].get(); unsigned int count = zone->get_trip_count(); // Special case for handling a single trip which has a defined // target state. Add another trip + 1C so that control is applied // till max state // Count is 2, because there is a default poll trip if (count == 2) { cthd_trip_point *trip = zone->get_trip_at_index(0); int target_state; thd_log_info("Single trip with a target state\n"); if (trip->is_target_valid(target_state) == THD_SUCCESS) { cthd_trip_point trip_pt(1, PASSIVE, trip->get_trip_temp() + 1000, 0, zone->get_zone_index(), trip->get_sensor_id(), SEQUENTIAL); trip_pt.thd_trip_point_add_cdev(*trip->get_first_cdev(), cthd_trip_point::default_influence); zone->add_trip(trip_pt, 1); continue; } } for (unsigned int j = 0; j < count; ++j) { cthd_trip_point *trip = zone->get_trip_at_index(j); int target_state; thd_log_debug("check trip zone:%d:%d\n", i, j); if (trip->is_target_valid(target_state) == THD_SUCCESS) { if (target_state == TRIP_PT_INVALID_TARGET_STATE) { if (j == count - 1) { // This is the last "MAX" trip // So make the target state invalid and temperature + 1 C trip->set_first_target_invalid(); trip->update_trip_temp(trip->get_trip_temp() + 1000); } else { // This is not the last trip. So something after this // if the next one has the same source and target cthd_trip_point *next_trip = zone->get_trip_at_index( j + 1); // Sinc this is not the last trip in this zone, we don't check // exception, next trip will be valid cthd_cdev *cdev = next_trip->get_first_cdev(); if (!cdev) { // Something wrong make the current target invalid trip->set_first_target_invalid(); trip->update_trip_temp( trip->get_trip_temp() + 1000); continue; } int next_target_state; if (trip->get_sensor_id() == next_trip->get_sensor_id() && trip->get_first_cdev() == next_trip->get_first_cdev() && next_trip->is_target_valid(next_target_state) == THD_SUCCESS) { // Same source and target and the target state of next is not of type MAX int state = cdev->get_min_state(); target_state = (state + next_target_state) / 2; trip->set_first_target(target_state); trip->update_trip_temp( (next_trip->get_trip_temp() + trip->get_trip_temp()) / 2); } else { // It has different source and target so // So make the target state invalid and temperature + 1 C trip->set_first_target_invalid(); trip->update_trip_temp( trip->get_trip_temp() + 1000); } } } } } } } #define DEFAULT_SAMPLE_TIME_SEC 5 int cthd_engine_adaptive::install_itmt(struct itmt_entry *itmt_entry) { std::string itmt_zone; size_t pos = itmt_entry->target.find_last_of('.'); if (pos == std::string::npos) itmt_zone = itmt_entry->target; else itmt_zone = itmt_entry->target.substr(pos + 1); while (!itmt_zone.empty() && itmt_zone.back() == '_') { itmt_zone.pop_back(); } cthd_zone *zone = search_zone(itmt_zone); if (!zone) { if (!itmt_zone.compare(0, 4, "B0D4")) { itmt_zone = "TCPU"; zone = search_zone(itmt_zone); } if (!zone) { if (!itmt_zone.compare(0, 4, "TCPU")) { itmt_zone = "B0D4"; zone = search_zone(itmt_zone); } if (!zone) { thd_log_warn("Unable to find a zone for %s\n", itmt_zone.c_str()); return THD_ERROR; } } } cthd_cdev *cdev = search_cdev("rapl_controller_mmio"); if (!cdev) { return THD_ERROR; } cthd_sensor *sensor = search_sensor(itmt_zone); if (!sensor) { thd_log_warn("Unable to find a sensor for %s\n", itmt_zone.c_str()); return THD_ERROR; } int temp = (itmt_entry->trip_point - 2732) * 100; int _min_state = 0, _max_state = 0; if (itmt_entry->pl1_max.length()) { if (!strncasecmp(itmt_entry->pl1_max.c_str(), "MAX", 3)) { _max_state = TRIP_PT_INVALID_TARGET_STATE; } else if (!strncasecmp(itmt_entry->pl1_max.c_str(), "MIN", 3)) { _max_state = 0; } else { std::istringstream buffer(itmt_entry->pl1_max); buffer >> _max_state; _max_state *= 1000; } } if (itmt_entry->pl1_min.length()) { if (!strncasecmp(itmt_entry->pl1_min.c_str(), "MAX", 3)) { _min_state = TRIP_PT_INVALID_TARGET_STATE; } else if (!strncasecmp(itmt_entry->pl1_min.c_str(), "MIN", 3)) { _min_state = 0; } else { std::istringstream buffer(itmt_entry->pl1_min); buffer >> _min_state; _min_state *= 1000; } } cthd_trip_point trip_pt(zone->get_trip_count(), PASSIVE, temp, itmt_hyst, zone->get_zone_index(), sensor->get_index(), SEQUENTIAL); /* * Why the min = max and max=min in the below * thd_trip_point_add_cdev? * * Thermald min_state is where no cooling is active * Thermald max_state is where max cooling is applied * * If you check one ITMT table entry: * target:\_SB_.PC00.LPCB.ECDV.CHRG trip_temp:45 pl1_min:28000 pl1.max:MAX * * This means that when exceeding 45 set the PL1 to 28W, * Below 45 PL1 is set to maximum (full power) * * That means that untrottled case is PL1_MAX * Throttled case is PL1_MIN * Which is opposite of the argument order in the * thd_trip_point_add_cdev() */ trip_pt.thd_trip_point_add_cdev(*cdev, cthd_trip_point::default_influence, DEFAULT_SAMPLE_TIME_SEC, 0, 0, nullptr, 1, _max_state, _min_state); zone->add_trip(trip_pt, 1); zone->zone_cdev_set_binded(); zone->set_zone_active(); return 0; } int cthd_engine_adaptive::set_itmt_target(struct adaptive_target &target) { struct itmt *itmt; thd_log_info("set_int3400 ITMT target %s\n", target.argument.c_str()); itmt = gddv.find_itmt(target.argument); if (!itmt) { return THD_ERROR; } if (!int3400_installed) { for (unsigned int i = 0; i < zones.size(); ++i) { cthd_zone *_zone = zones[i].get(); // This is only for debug to plot power, so keep if (debug_mode_on()) { if (_zone->get_zone_type() == "rapl_pkg_power" || _zone->get_zone_type() == "power_floor") continue; } _zone->zone_reset(1); _zone->trip_delete_all(); if (_zone->zone_active_status()) _zone->set_zone_inactive(); } } for (int i = 0; i < (int) itmt->itmt_entries.size(); i++) { install_itmt(&itmt->itmt_entries[i]); } return THD_SUCCESS; } void cthd_engine_adaptive::set_int3400_target(struct adaptive_target &target) { if (target.code == "ITMT" || target.code == "ITMT3") { if (set_itmt_target(target) == THD_SUCCESS) { int3400_installed = 1; } } if (target.code == "PSVT") { struct psvt *psvt; thd_log_info("set_int3400 target %s\n", target.argument.c_str()); psvt = gddv.find_psvt(target.argument); if (psvt) { if (!int3400_installed) { for (unsigned int i = 0; i < zones.size(); ++i) { cthd_zone *_zone = zones[i].get(); // This is only for debug to plot power, so keep if (_zone->get_zone_type() == "rapl_pkg_power" || _zone->get_zone_type() == "power_floor") continue; _zone->zone_reset(1); _zone->trip_delete_all(); if (_zone->zone_active_status()) _zone->set_zone_inactive(); } } for (int i = 0; i < (int) psvt->psvs.size(); i++) { install_passive(&psvt->psvs[i]); } int3400_installed = 1; } } if (int3400_installed) psvt_consolidate(); thd_log_info("\n\n ZONE DUMP BEGIN\n"); int new_zone_count = 0; for (unsigned int i = 0; i < zones.size(); ++i) { zones[i]->zone_dump(); if (zones[i]->zone_active_status()) ++new_zone_count; } thd_log_info("\n\n ZONE DUMP END\n"); if (!new_zone_count) { thd_log_warn("Adaptive policy couldn't create any zones\n"); thd_log_warn("Possibly some sensors in the PSVT are missing\n"); thd_log_warn("Restart in non adaptive mode via systemd\n"); char *resolved_rundir = realpath(TDRUNDIR, nullptr); if (!resolved_rundir) { thd_log_warn("Failed to resolve runtime dir %s: %s\n", TDRUNDIR, strerror(errno)); } else { int dir_fd = open(resolved_rundir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (dir_fd < 0) { thd_log_warn("Failed to open runtime dir %s: %s\n", resolved_rundir, strerror(errno)); } else { struct stat st; if (fstat(dir_fd, &st) < 0) { thd_log_warn("Failed to stat runtime dir %s: %s\n", resolved_rundir, strerror(errno)); } else if (!S_ISDIR(st.st_mode)) { thd_log_warn("Runtime path %s is not a directory\n", resolved_rundir); } else if (st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH))) { thd_log_warn("Insecure runtime dir permissions on %s (uid=%u mode=%o)\n", resolved_rundir, st.st_uid, st.st_mode & 0777); } else { int fd = openat(dir_fd, "ignore_adaptive", O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, 0600); if (fd < 0) { thd_log_warn("Failed to create %s/ignore_adaptive securely: %s\n", resolved_rundir, strerror(errno)); } else { close(fd); } } close(dir_fd); } free(resolved_rundir); } exit(EXIT_FAILURE); } if (target.code == "PSV") { set_trip(target.participant, target.argument); } } void cthd_engine_adaptive::install_passive_default() { thd_log_info("IETM_D0 processed\n"); for (unsigned int i = 0; i < zones.size(); ++i) { cthd_zone *_zone = zones[i].get(); _zone->zone_reset(1); _zone->trip_delete_all(); if (_zone->zone_active_status()) _zone->set_zone_inactive(); } struct psvt *psvt = gddv.find_def_psvt(); if (!psvt) return; std::vector psvs = psvt->psvs; thd_log_info("Name :%s\n", psvt->name.c_str()); for (unsigned int j = 0; j < psvs.size(); ++j) { install_passive(&psvs[j]); } psvt_consolidate(); thd_log_info("\n\n ZONE DUMP BEGIN\n"); for (unsigned int i = 0; i < zones.size(); ++i) { zones[i]->zone_dump(); } thd_log_info("\n\n ZONE DUMP END\n"); } void cthd_engine_adaptive::execute_target(struct adaptive_target &target) { cthd_cdev *cdev; std::string name; int argument; thd_log_info("Target Name:%s\n", target.name.c_str()); size_t pos = target.participant.find_last_of('.'); if (pos == std::string::npos) name = target.participant; else name = target.participant.substr(pos + 1); cdev = search_cdev(name); if (!cdev) { if (!name.compare(0, 4, "TCPU")) { name = "B0D4"; cdev = search_cdev(name); } } thd_log_info("looking for cdev %s\n", name.c_str()); if (!cdev) { thd_log_info("cdev %s not found\n", name.c_str()); if (target.participant == int3400_path) { set_int3400_target(target); return; } } if (target.code == "PSVT") { thd_log_info("PSVT...\n"); set_int3400_target(target); return; } try { argument = std::stoi(target.argument, nullptr); } catch (...) { thd_log_info("Invalid target target:%s %s\n", target.code.c_str(), target.argument.c_str()); return; } thd_log_info("target:%s %d\n", target.code.c_str(), argument); if (cdev) cdev->set_adaptive_target(target); } void cthd_engine_adaptive::exec_fallback_target(int target) { thd_log_debug("exec_fallback_target %d\n", target); int3400_installed = 0; for (int i = 0; i < (int) gddv.targets.size(); i++) { if (gddv.targets[i].target_id != (uint64_t) target) continue; execute_target(gddv.targets[i]); } } // Called every polling interval void cthd_engine_adaptive::update_engine_state() { int target = -1; // When This means that gddv doesn't have any conditions // no need to do any processing if (passive_def_only) return; if (fallback_id < 0) target = gddv.evaluate_conditions(); if (current_matched_target == target) { thd_log_debug("No change in target\n"); return; } current_matched_target = target; // No target matched // It is possible that the target which matched last time didn't match // because of conditions have changed. // So in that case we have to install the default target. // Return only when there is a fallback ID was identified during // start after installing that target if (target == -1) { if (fallback_id >= 0 && !policy_active) { exec_fallback_target(gddv.targets[fallback_id].target_id); policy_active = 1; return; } } int3400_installed = 0; if (target > 0) { for (int i = 0; i < (int) gddv.targets.size(); i++) { if (gddv.targets[i].target_id != (uint64_t) target) continue; execute_target(gddv.targets[i]); } policy_active = 1; } if (!int3400_installed) { thd_log_info("Adaptive target doesn't have PSVT or ITMT target\n"); install_passive_default(); } } int cthd_engine_adaptive::set_int3400_base_path() { const char *base_path = "/sys/bus/platform/drivers/int3400 thermal"; csys_fs sysfs (""); if (sysfs.exists (base_path)) { DIR *dir; struct dirent *entry; if ((dir = opendir (base_path)) != nullptr) { while ((entry = readdir (dir)) != nullptr) { if (!strncmp (entry->d_name, "INT", strlen ("INT"))) { int3400_base_path = "/sys/bus/platform/devices/"; int3400_base_path += entry->d_name; int3400_base_path += "/"; thd_log_debug ("Discovered int3400 path:%s\n", int3400_base_path.c_str ()); closedir(dir); return THD_SUCCESS; } } closedir(dir); } } return THD_ERROR; } int cthd_engine_adaptive::thd_engine_init(bool ignore_cpuid_check, bool adaptive) { csys_fs sysfs(""); size_t size; int res; if (check_acpi_platform_profile() != THD_SUCCESS) { return THD_FATAL_ERROR; } thd_parse_features(); parser_disabled = true; force_mmio_rapl = true; if (!ignore_cpuid_check) { check_cpu_id(); if (!processor_id_match()) { thd_log_msg("Unsupported cpu model or platform\n"); thd_log_msg("Try option --ignore-cpuid-check to disable this compatibility test\n"); exit(EXIT_SUCCESS); } } std::ostringstream filename; filename << TDRUNDIR << "/" << "ignore_adaptive"; csys_fs _sysfs(filename.str().c_str()); if (_sysfs.exists()) { return THD_ERROR; } if (set_int3400_base_path() != THD_SUCCESS) return THD_ERROR; if (sysfs.read(int3400_base_path + "firmware_node/path", int3400_path) < 0) { thd_log_debug("Unable to locate INT3400 firmware path\n"); return THD_ERROR; } size = sysfs.size(int3400_base_path + "data_vault"); if (size == 0) { thd_log_debug("Unable to open GDDV data vault\n"); return THD_ERROR; } std::unique_ptr buf(new char[size]); if (!buf) { thd_log_error("Unable to allocate memory for GDDV"); return THD_FATAL_ERROR; } if (sysfs.read(int3400_base_path + "data_vault", buf.get(), size) < int(size)) { thd_log_debug("Unable to read GDDV data vault\n"); return THD_FATAL_ERROR; } res = gddv.gddv_init(int3400_base_path); if (res != THD_SUCCESS) { return res; } if (!gddv.conditions.size()) { thd_log_info("No adaptive conditions present\n"); struct psvt *psvt = gddv.find_def_psvt(); if (psvt) { thd_log_info("IETM.D0 found\n"); passive_def_only = 1; } else { return THD_SUCCESS; } } thd_ignore_default_control = 1; /* Read the sensors/zones */ res = cthd_engine::thd_engine_init(ignore_cpuid_check, adaptive); if (res != THD_SUCCESS) return res; if (gddv.vscts.size()) { thd_log_info("Found virtual sensor [%s]\n", gddv.vscts_name.c_str()); std::string dev_name; size_t pos = gddv.vscts_name.find_last_of('.'); if (pos == std::string::npos) { dev_name = gddv.vscts_name; } else { dev_name = gddv.vscts_name; dev_name.resize(pos); } if (dev_name.empty()) { thd_log_info("Can't parse virtual sensor name\n"); return THD_SUCCESS; } std::string dummy = ""; std::unique_ptr virt_sensor(new cthd_sensor_virtual( current_sensor_index, dev_name, dummy, 0, 0)); for (unsigned int i = 0; i < gddv.vspts.size(); ++i) { struct polling_table_entry entry; entry.virtual_temp = gddv.vspts[i].virtual_temp; entry.sample_period = gddv.vspts[i].sample_period; virt_sensor->update_polling_table(entry); } // virt_sensor->enable_periodic_timer(); for (unsigned int i = 0; i < gddv.vscts.size(); ++i) { std::string target_name; int power_sensor = 0; if (gddv.vscts[i].coeff_type == 1) { target_name = "rapl_pkg_power"; if (!search_sensor(target_name)){ std::unique_ptr rapl_power(new cthd_sensor_rapl_power(current_sensor_index)); if (rapl_power->sensor_update() == THD_SUCCESS) { sensors.push_back(std::move(rapl_power)); ++current_sensor_index; } else { thd_log_info("Can't add power as virtual sensor\n"); return THD_SUCCESS; } } power_sensor = 1; } else { size_t pos = gddv.vscts[i].target.find_last_of('.'); if (pos == std::string::npos) target_name = gddv.vscts[i].target; else target_name = gddv.vscts[i].target.substr(pos + 1); } double operation; if (gddv.vscts[i].operation) operation = -1.0; else operation = 1.0; virt_sensor->add_target(target_name, ((double) gddv.vscts[i].coeff) / 1000 * operation, ((double) gddv.vscts[i].alpha) / 1000, power_sensor); } if (virt_sensor->sensor_update() != THD_SUCCESS) { thd_log_info("Can't add virtual sensor\n"); return THD_SUCCESS; } sensors.push_back(std::move(virt_sensor)); ++current_sensor_index; std::unique_ptr zone(new cthd_zone_dynamic(current_zone_index, dev_name, 0xffffffff, PASSIVE, dev_name, "intel_powerclamp")); if (!zone) { thd_log_info("Can't add virtual sensor\n"); return THD_SUCCESS; } if (zone->zone_update() != THD_SUCCESS) { // sensor will be deleted when all elements of sensors are deleted from sensors[] thd_log_info("Can't add virtual sensor\n"); return THD_SUCCESS; } zone->set_zone_active(); zone->zone_dump(); zones.push_back(std::move(zone)); ++current_zone_index; } return THD_SUCCESS; } int cthd_engine_adaptive::thd_engine_start() { if (passive_def_only) { // This means there are no conditions present // This doesn't mean that there are conditions present but none matched. install_passive_default(); return cthd_engine::thd_engine_start(); } if (gddv.verify_conditions()) { // This means that gddv has conditions which thermald can't support // Doesn't mean that the conditions supported by thermald matches // but can't satisfy any conditions as they don't match. // That can be only found during execution of conditions. thd_log_info( "Some conditions are not supported, so check if any condition set can be matched\n"); int target = gddv.evaluate_conditions(); if (target == -1) { thd_log_info("Also unable to evaluate any conditions\n"); thd_log_info( "Falling back to use configuration with the highest power\n"); int i = gddv.find_agressive_target(); thd_log_info("target:%d\n", i); if (i >= 0) { thd_log_info("fallback id:%d\n", i); fallback_id = i; } else { struct psvt *psvt = gddv.find_def_psvt(); if (psvt) { thd_log_info("IETM.D0 found\n"); install_passive_default(); } else { gddv.gddv_free(); return THD_ERROR; } } } } set_control_mode(EXCLUSIVE); // Check if any conditions can be satisfied at this time // If not just install the default passive IETM.D0 table if (gddv.evaluate_conditions() == -1) install_passive_default(); thd_log_info("adaptive engine reached end\n"); return cthd_engine::thd_engine_start(); } int thd_engine_create_adaptive_engine(bool ignore_cpuid_check, bool test_mode) { thd_engine.reset(new cthd_engine_adaptive()); thd_engine->set_poll_interval(thd_poll_interval); // Initialize thermald objects if (thd_engine->thd_engine_init(ignore_cpuid_check, true) != THD_SUCCESS) { thd_log_info("THD engine init failed\n"); return THD_ERROR; } if (thd_engine->thd_engine_start() != THD_SUCCESS) { thd_log_info("THD engine start failed\n"); if (test_mode) { thd_log_warn("This platform doesn't support adaptive mode\n"); thd_log_warn( "It is possible that manufacturer doesn't support DPTF tables or\n"); thd_log_warn( "didn't provide tables, which can be parsed in open source.\n"); exit(0); } return THD_ERROR; } return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_engine_adaptive.h000066400000000000000000000056431521301715300215070ustar00rootroot00000000000000/* * cthd_engine_adaptive.cpp: Adaptive thermal engine * * Copyright (C) 2013 Intel Corporation. All rights reserved. * Copyright 2020 Google LLC * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name Matthew Garrett * */ #ifndef THD_ENGINE_ADAPTIVE_H_ #define THD_ENGINE_ADAPTIVE_H_ #ifndef ANDROID #include #include #endif #include "thd_engine_default.h" #include "thd_cpu_default_binding.h" #include "thd_adaptive_types.h" class cthd_engine_adaptive: public cthd_engine_default { protected: int policy_active; int fallback_id; std::string int3400_path; std::string int3400_base_path; int passive_def_only; int passive_def_processed; int power_slider; int int3400_installed; int current_matched_target; int set_itmt_target(struct adaptive_target &target); int install_passive(struct psv *psv); int install_itmt(struct itmt_entry *itmt_entry); void psvt_consolidate(); void set_trip(const std::string& device, const std::string& argument); void set_int3400_target(struct adaptive_target &target); void exec_fallback_target(int target); void execute_target(struct adaptive_target &target); void install_passive_default(); int set_int3400_base_path(); public: static const int itmt_hyst = 2000; #ifndef ANDROID cthd_engine_adaptive() : cthd_engine_default("63BE270F-1C11-48FD-A6F7-3AF253FF3E2D"), policy_active( 0), fallback_id(-1), int3400_path(""), int3400_base_path( ""), passive_def_only(0), passive_def_processed(0), power_slider(75), int3400_installed(0), current_matched_target(-1) { } #else cthd_engine_adaptive() : cthd_engine_default("63BE270F-1C11-48FD-A6F7-3AF253FF3E2D"), policy_active( 0), fallback_id(-1), int3400_base_path(""), passive_def_only( 0), passive_def_processed(0) { } #endif ~cthd_engine_adaptive() override { } ppcc_t* get_ppcc_param(const std::string& name) override { return gddv.get_ppcc_param(name); } int search_idsp(const std::string& name) override { return gddv.search_idsp(name); } int thd_engine_init(bool ignore_cpuid_check, bool adaptive) override; int thd_engine_start() override; void update_engine_state() override; void update_power_slider(); }; int thd_engine_create_adaptive_engine(bool ignore_cpuid_check, bool test_mode); #endif /* THD_ENGINE_ADAPTIVE_H_ */ thermal_daemon-2.5.12/src/thd_engine_default.cpp000066400000000000000000000657141521301715300216760ustar00rootroot00000000000000/* * cthd_engine_defualt.cpp: Default thermal engine * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include #include #include #include #include #include "thd_engine_default.h" #include "thd_zone_cpu.h" #include "thd_zone_generic.h" #include "thd_cdev_gen_sysfs.h" #include "thd_cdev_cpufreq.h" #include "thd_cdev_rapl.h" #include "thd_cdev_intel_pstate_driver.h" #include "thd_cdev_rapl_dram.h" #include "thd_sensor_virtual.h" #include "thd_cdev_backlight.h" #include "thd_int3400.h" #include "thd_sensor_rapl_power.h" #include "thd_zone_rapl_power.h" #include "thd_platform.h" #include "thd_platform_intel.h" #include "thd_util.h" // Default CPU cooling devices, which are not part of thermal sysfs // Since non trivial initialization is not supported, we init all fields even if they are not needed /* Some security scan handler can't parse, the following block and generate unnecessary errors. * hiding good ones. So init in old style compatible to C++ static cooling_dev_t cpu_def_cooling_devices[] = { { .status = true, .mask = CDEV_DEF_BIT_UNIT_VAL | CDEV_DEF_BIT_READ_BACK | CDEV_DEF_BIT_MIN_STATE | CDEV_DEF_BIT_STEP, .index = 0, .unit_val = ABSOULUTE_VALUE, .min_state = 0, .max_state = 0, .inc_dec_step = 5, .read_back = false, .auto_down_control = false, .type_string = "intel_powerclamp", .path_str = "", .debounce_interval = 4, .pid_enable = false, .pid = {0.0, 0.0, 0.0}}, }; */ static const cooling_dev_t cpu_def_cooling_devices[] = { { true, CDEV_DEF_BIT_UNIT_VAL | CDEV_DEF_BIT_READ_BACK | CDEV_DEF_BIT_MIN_STATE | CDEV_DEF_BIT_STEP, 0, ABSOULUTE_VALUE, 0, 0, 5, false, false, "intel_powerclamp", "", 4, false, { 0.0, 0.0, 0.0 },"" }, { true, CDEV_DEF_BIT_UNIT_VAL | CDEV_DEF_BIT_READ_BACK | CDEV_DEF_BIT_MIN_STATE | CDEV_DEF_BIT_STEP, 0, ABSOULUTE_VALUE, 0, 100, 5, false, false, "LCD", "", 4, false, { 0.0, 0.0, 0.0 },"" } }; cthd_engine_default::~cthd_engine_default() { } int cthd_engine_default::read_thermal_sensors() { int index; DIR *dir; struct dirent *entry; cthd_sensor *sensor; const std::string base_path[] = { "/sys/devices/platform/", "/sys/class/hwmon/" }; int i; thd_read_default_thermal_sensors(); index = current_sensor_index; sensor = search_sensor("pkg-temp-0"); if (sensor) { // Force this to support async sensor->set_async_capable(true); } sensor = search_sensor("x86_pkg_temp"); if (sensor) { // Force this to support async sensor->set_async_capable(true); } sensor = search_sensor("soc_dts0"); if (sensor) { // Force this to support async sensor->set_async_capable(true); } // Default CPU temperature zone // Find path to read DTS temperature for (i = 0; i < 2; ++i) { if ((dir = opendir(base_path[i].c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (!strncmp(entry->d_name, "coretemp.", strlen("coretemp.")) || !strncmp(entry->d_name, "hwmon", strlen("hwmon"))) { // Check name std::string name_path = base_path[i] + entry->d_name + "/name"; csys_fs name_sysfs(name_path); if (!name_sysfs.exists()) { thd_log_info("dts %s doesn't exist\n", name_path.c_str()); continue; } std::string name; if (name_sysfs.read("", name) < 0) { thd_log_info("dts name read failed for %s\n", name_path.c_str()); continue; } if (name != "coretemp") continue; std::string temp_dir_path = base_path[i] + entry->d_name + "/"; DIR *temp_dir = nullptr; struct dirent *temp_dir_entry = nullptr; int len_temp_dir_entry = 0; int len_input = strlen("_input"); if ((temp_dir = opendir(temp_dir_path.c_str())) != nullptr) { while ((temp_dir_entry = readdir(temp_dir)) != nullptr) { len_temp_dir_entry = strlen(temp_dir_entry->d_name); if ((len_temp_dir_entry >= len_input && !thd_strcmp_n( temp_dir_entry->d_name + len_temp_dir_entry - len_input, "_input")) && (!strncmp(temp_dir_entry->d_name, "temp", strlen("temp")))) { std::unique_ptr sensor(new cthd_sensor(index, temp_dir_path + temp_dir_entry->d_name, "hwmon", SENSOR_TYPE_RAW)); if (sensor->sensor_update() != THD_SUCCESS) { closedir(temp_dir); closedir(dir); return THD_ERROR; } sensors.push_back(std::move(sensor)); ++index; } } closedir(temp_dir); } } } closedir(dir); } if (index != current_sensor_index) break; } if (index == current_sensor_index) { // No coretemp sysfs exist, try hwmon thd_log_warn("Thermal DTS: No coretemp sysfs found\n"); } if (debug_mode_on()) { // Only used for debug power using ThermalMonitor std::unique_ptr rapl_power(new cthd_sensor_rapl_power(index)); if (rapl_power->sensor_update() == THD_SUCCESS) { sensors.push_back(std::move(rapl_power)); ++index; } } current_sensor_index = index; // Add from XML sensor config if (!parser_init() && parser.platform_matched()) { for (int i = 0; i < parser.sensor_count(); ++i) { thermal_sensor_t *sensor_config = parser.get_sensor_dev_index(i); if (!sensor_config) continue; cthd_sensor *sensor = search_sensor(sensor_config->name); if (sensor) { if (sensor_config->mask & SENSOR_DEF_BIT_PATH) sensor->update_path(sensor_config->path); if (sensor_config->mask & SENSOR_DEF_BIT_ASYNC_CAPABLE) sensor->set_async_capable(sensor_config->async_capable); } else { std::unique_ptr sensor_new; if (sensor_config->virtual_sensor) { std::unique_ptr sensor_virt; std::string dummy = ""; if (!sensor_config->link_sensors.empty()) { sensor_virt.reset(new cthd_sensor_virtual(index, sensor_config->name, dummy, 0, 0)); for (unsigned int j = 0; j < sensor_config->link_sensors.size(); ++j) { std::string target_name = sensor_config->link_sensors[j].name; if (sensor_config->link_sensors[j].power_sensor) { std::string name = "rapl_pkg_power"; if (!search_sensor(name)) { std::unique_ptr rapl_power(new cthd_sensor_rapl_power(index)); if (rapl_power->sensor_update() == THD_SUCCESS) { sensors.push_back(std::move(rapl_power)); ++index; } } } if (sensor_virt->add_target(target_name, sensor_config->link_sensors[j].coeff, sensor_config->link_sensors[j].offset, sensor_config->link_sensors[j].power_sensor) != THD_SUCCESS) { sensor_virt.reset(); break; } } } else { sensor_virt.reset(new cthd_sensor_virtual(index, sensor_config->name, sensor_config->sensor_link.name, sensor_config->sensor_link.multiplier, sensor_config->sensor_link.offset)); } if (!sensor_virt) continue; for (unsigned int j = 0; j < sensor_config->polling_table.size(); ++j) { struct polling_table_entry entry; entry.virtual_temp = sensor_config->polling_table[j].virtual_temp; entry.sample_period = sensor_config->polling_table[j].sample_period; sensor_virt->update_polling_table(entry); } if (sensor_virt->sensor_update() != THD_SUCCESS) { continue; } if (sensor_config->polling_table.empty()) thd_log_info("No polling interval is defined\n"); sensor_new = std::move(sensor_virt); } else { if (!starts_with(sensor_config->path, "/sys/")) { thd_log_debug( "Invalid sysfs path or allowed path %s\n", sensor_config->path.c_str()); continue; } sensor_new.reset(new cthd_sensor(index, sensor_config->path, sensor_config->name, SENSOR_TYPE_RAW)); if (sensor_new->sensor_update() != THD_SUCCESS) { continue; } } if (sensor_new) { sensors.push_back(std::move(sensor_new)); ++index; } } } } current_sensor_index = index; for (unsigned int i = 0; i < sensors.size(); ++i) { sensors[i]->sensor_dump(); } return THD_SUCCESS; } bool cthd_engine_default::add_int340x_processor_dev(void) { if (thd_ignore_default_control) return false; /* Specialized processor thermal device names */ cthd_zone *processor_thermal = nullptr, *acpi_thermal = nullptr; cthd_INT3400 int3400(uuid); unsigned int passive, new_passive = 0, critical = 0; if (int3400.match_supported_uuid() == THD_SUCCESS) { processor_thermal = search_zone("B0D4"); } if (!processor_thermal) processor_thermal = search_zone("B0DB"); if (!processor_thermal) processor_thermal = search_zone("TCPU"); if (processor_thermal) { /* Check If there is a valid passive trip */ for (unsigned int i = 0; i < processor_thermal->get_trip_count(); ++i) { cthd_trip_point *trip = processor_thermal->get_trip_at_index(i); if (trip && trip->get_trip_type() == PASSIVE && (passive = trip->get_trip_temp()) && passive > processor_thermal_min_passive) { /* Need to honor ACPI _CRT, otherwise the system could be shut down by Linux kernel */ acpi_thermal = search_zone("acpitz"); if (acpi_thermal) { for (unsigned int i = 0; i < acpi_thermal->get_trip_count(); ++i) { cthd_trip_point *crit = acpi_thermal->get_trip_at_index(i); if (crit && crit->get_trip_type() == CRITICAL) { critical = crit->get_trip_temp(); break; } } } if (critical && passive + 5 * 1000 >= critical) { new_passive = critical - 15 * 1000; trip->thd_trip_update_set_point(new_passive); } thd_log_info("Processor thermal device is present\n"); thd_log_info("It will act as CPU thermal zone !!\n"); thd_log_info("Processor thermal device passive Trip is %u\n", trip->get_trip_temp()); processor_thermal->set_zone_active(); cthd_cdev *cdev; cdev = search_cdev("rapl_controller"); if (cdev) { processor_thermal->bind_cooling_device(PASSIVE, 0, cdev, cthd_trip_point::default_influence); } cdev = search_cdev("intel_pstate"); if (cdev) { processor_thermal->bind_cooling_device(PASSIVE, 0, cdev, cthd_trip_point::default_influence); } cdev = search_cdev("intel_powerclamp"); if (cdev) { processor_thermal->bind_cooling_device(PASSIVE, 0, cdev, cthd_trip_point::default_influence); } cdev = search_cdev("Processor"); if (cdev) { processor_thermal->bind_cooling_device(PASSIVE, 0, cdev, cthd_trip_point::default_influence); } return true; } } } return false; } void cthd_engine_default::disable_cpu_zone(thermal_zone_t *zone_config) { if (parser.thermal_conf_auto()) { cthd_zone *cpu_zone = search_zone("cpu"); if (cpu_zone) cpu_zone->set_zone_inactive(); return; } cthd_zone *zone = search_zone(zone_config->type); if (!zone) return; if (!zone->zone_active_status()) return; for (unsigned int k = 0; k < zone_config->trip_pts.size(); ++k) { trip_point_t &trip_pt_config = zone_config->trip_pts[k]; cthd_sensor *sensor = search_sensor(trip_pt_config.sensor_type); if (sensor && sensor->get_sensor_type() == "B0D4") { thd_log_info( "B0D4 is defined in thermal-config so deactivating default cpu\n"); cthd_zone *cpu_zone = search_zone("cpu"); if (cpu_zone) cpu_zone->set_zone_inactive(); } } } int cthd_engine_default::read_thermal_zones() { int index; DIR *dir; struct dirent *entry; const std::string base_path[] = { "/sys/devices/platform/", "/sys/class/hwmon/" }; int i; thd_read_default_thermal_zones(); index = current_zone_index; bool valid_int340x = add_int340x_processor_dev(); if (!thd_ignore_default_control && !valid_int340x && !search_zone("cpu")) { bool cpu_zone_created = false; thd_log_info("zone cpu will be created\n"); // Default CPU temperature zone // Find path to read DTS temperature for (i = 0; i < 2; ++i) { if ((dir = opendir(base_path[i].c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (!strncmp(entry->d_name, "coretemp.", strlen("coretemp.")) || !strncmp(entry->d_name, "hwmon", strlen("hwmon"))) { std::string name_path = base_path[i] + entry->d_name + "/name"; csys_fs name_sysfs(name_path); if (!name_sysfs.exists()) { thd_log_info("dts zone %s doesn't exist\n", name_path.c_str()); continue; } std::string name; if (name_sysfs.read("", name) < 0) { thd_log_info("dts zone name read failed for %s\n", name_path.c_str()); continue; } thd_log_info("%s->%s\n", name_path.c_str(), name.c_str()); if (name != "coretemp") continue; std::unique_ptr zone(new cthd_zone_cpu(index, base_path[i] + entry->d_name + "/", atoi(entry->d_name + strlen("coretemp.")))); if (zone->zone_update() == THD_SUCCESS) { zone->set_zone_active(); zones.push_back(std::move(zone)); cpu_zone_created = true; ++index; } } } closedir(dir); } if (cpu_zone_created) break; } if (!cpu_zone_created) { thd_log_error( "Thermal DTS or hwmon: No Zones present Need to configure manually\n"); } } current_zone_index = index; // Add from XML thermal zone if (!parser_init() && parser.platform_matched()) { for (int i = 0; i < parser.zone_count(); ++i) { bool activate; thermal_zone_t *zone_config = parser.get_zone_dev_index(i); if (!zone_config) continue; thd_log_debug("Look for Zone [%s]\n", zone_config->type.c_str()); cthd_zone *zone = search_zone(zone_config->type); if (zone) { activate = false; thd_log_info("Zone already present %s\n", zone_config->type.c_str()); for (unsigned int k = 0; k < zone_config->trip_pts.size(); ++k) { trip_point_t &trip_pt_config = zone_config->trip_pts[k]; thd_log_debug( "Trip %d, temperature %d, Look for Search sensor %s\n", k, trip_pt_config.temperature, trip_pt_config.sensor_type.c_str()); cthd_sensor *sensor = search_sensor( trip_pt_config.sensor_type); if (!sensor) { thd_log_error("XML zone: invalid sensor type [%s]\n", trip_pt_config.sensor_type.c_str()); // This will update the trip temperature for the matching // trip type if (trip_pt_config.temperature) { cthd_trip_point trip_pt(zone->get_trip_count(), trip_pt_config.trip_pt_type, trip_pt_config.temperature, trip_pt_config.hyst, zone->get_zone_index(), -1, trip_pt_config.control_type); zone->update_trip_temp(trip_pt); } continue; } zone->bind_sensor(sensor); if (trip_pt_config.temperature) { cthd_trip_point trip_pt(zone->get_trip_count(), trip_pt_config.trip_pt_type, trip_pt_config.temperature, trip_pt_config.hyst, zone->get_zone_index(), sensor->get_index(), trip_pt_config.control_type); if (trip_pt_config.dependency.dependency) { trip_pt.set_dependency(trip_pt_config.dependency.cdev, trip_pt_config.dependency.state); } // bind cdev for (unsigned int j = 0; j < trip_pt_config.cdev_trips.size(); ++j) { cthd_cdev *cdev = search_cdev( trip_pt_config.cdev_trips[j].type); if (cdev) { trip_pt.thd_trip_point_add_cdev(*cdev, trip_pt_config.cdev_trips[j].influence, trip_pt_config.cdev_trips[j].sampling_period, trip_pt_config.cdev_trips[j].target_state_valid, trip_pt_config.cdev_trips[j].target_state, &trip_pt_config.cdev_trips[j].pid_param, trip_pt_config.cdev_trips[j].min_max_valid, trip_pt_config.cdev_trips[j].target_min_state, trip_pt_config.cdev_trips[j].target_max_state); zone->zone_cdev_set_binded(); activate = true; } } zone->add_trip(trip_pt); } else { thd_log_debug("Trip temp == 0 is in zone %s\n", zone_config->type.c_str()); // Try to find some existing non zero trips and associate the cdevs // This is the way from an XML config a generic cooling device // can be bound. For example from ACPI thermal relationships tables for (unsigned int j = 0; j < trip_pt_config.cdev_trips.size(); ++j) { cthd_cdev *cdev = search_cdev( trip_pt_config.cdev_trips[j].type); if (!cdev) { thd_log_info("cdev for type %s not found\n", trip_pt_config.cdev_trips[j].type.c_str()); } if (cdev) { if (zone->bind_cooling_device( trip_pt_config.trip_pt_type, 0, cdev, trip_pt_config.cdev_trips[j].influence, trip_pt_config.cdev_trips[j].sampling_period, trip_pt_config.cdev_trips[j].target_state_valid, trip_pt_config.cdev_trips[j].target_state, trip_pt_config.cdev_trips[j].min_max_valid, trip_pt_config.cdev_trips[j].target_min_state, trip_pt_config.cdev_trips[j].target_max_state) == THD_SUCCESS) { thd_log_debug( "bind %s to trip to sensor %s\n", cdev->get_cdev_type().c_str(), sensor->get_sensor_type().c_str()); activate = true; } else { thd_log_debug( "bind_cooling_device failed for cdev %s trip %s\n", cdev->get_cdev_type().c_str(), sensor->get_sensor_type().c_str()); } } } } } if (activate) { thd_log_debug("Activate zone %s\n", zone->get_zone_type().c_str()); zone->set_zone_active(); } } else { std::unique_ptr zone(new cthd_zone_generic(index, i, zone_config->type)); if (zone->zone_update() == THD_SUCCESS) { zone->set_zone_active(); ++index; zones.push_back(std::move(zone)); } } disable_cpu_zone(zone_config); } } current_zone_index = index; if (debug_mode_on()) { // Only used for debug power using ThermalMonitor std::unique_ptr rapl_power(new cthd_zone_rapl_power(index)); if (rapl_power->zone_update() == THD_SUCCESS) { rapl_power->set_zone_active(); zones.push_back(std::move(rapl_power)); ++index; } current_zone_index = index; } if (!zones.size()) { thd_log_info("No Thermal Zones found\n"); return THD_FATAL_ERROR; } #ifdef AUTO_DETECT_RELATIONSHIP def_binding.do_default_binding(cdevs); #endif thd_log_info("\n\n ZONE DUMP BEGIN\n"); for (unsigned int i = 0; i < zones.size(); ++i) { zones[i]->zone_dump(); } thd_log_info("\n\n ZONE DUMP END\n"); return THD_SUCCESS; } int cthd_engine_default::add_replace_cdev(const cooling_dev_t *config) { cthd_cdev *cdev; bool cdev_present = false; bool percent_unit = false; // Check if there is existing cdev with this name and path cdev = search_cdev(config->type_string); if (cdev) { cdev_present = true; // Also check for path, some device like FAN has multiple paths for same type_str std::string base_path = cdev->get_base_path(); if (config->path_str.size() && config->path_str != base_path) { cdev_present = false; } } if (!cdev_present) { // create new std::unique_ptr tmp(new cthd_gen_sysfs_cdev(current_cdev_index, config->path_str)); if (!tmp) return THD_ERROR; tmp->set_cdev_type(config->type_string); if (tmp->update() != THD_SUCCESS) { return THD_ERROR; } cdevs.push_back(std::move(tmp)); cdev = cdevs.back().get(); ++current_cdev_index; } if (config->mask & CDEV_DEF_BIT_UNIT_VAL) { if (config->unit_val == RELATIVE_PERCENTAGES) percent_unit = true; } if (config->mask & CDEV_DEF_BIT_AUTO_DOWN) cdev->set_down_adjust_control(config->auto_down_control); if (config->mask & CDEV_DEF_BIT_STEP) { if (percent_unit) cdev->set_inc_dec_value( cdev->get_curr_state() * config->inc_dec_step / 100); else cdev->set_inc_dec_value(config->inc_dec_step); } if (config->mask & CDEV_DEF_BIT_MIN_STATE) { if (percent_unit) cdev->thd_cdev_set_min_state_param( cdev->get_curr_state() * config->min_state / 100); else cdev->thd_cdev_set_min_state_param(config->min_state); } if (config->mask & CDEV_DEF_BIT_MAX_STATE) { if (percent_unit) cdev->thd_cdev_set_max_state_param( cdev->get_curr_state() * config->max_state / 100); else cdev->thd_cdev_set_max_state_param(config->max_state); } if (config->mask & CDEV_DEF_BIT_READ_BACK) cdev->thd_cdev_set_read_back_param(config->read_back); if (config->mask & CDEV_DEF_BIT_DEBOUNCE_VAL) cdev->set_debounce_interval(config->debounce_interval); if (config->mask & CDEV_DEF_BIT_PID_PARAMS) { cdev->enable_pid(); cdev->set_pid_param(config->pid.Kp, config->pid.Ki, config->pid.Kd); } if (config->mask & CDEV_DEF_BIT_WRITE_PREFIX) cdev->thd_cdev_set_write_prefix(config->write_prefix); return THD_SUCCESS; } int cthd_engine_default::read_cooling_devices() { int size; int i; // Read first all the default cooling devices added by kernel thd_read_default_cooling_devices(); // Add RAPL cooling device std::unique_ptr rapl_dev(new cthd_sysfs_cdev_rapl( current_cdev_index, 0)); if (!rapl_dev) return THD_ERROR; cthd_sysfs_cdev_rapl *rapl_dev_borrow = nullptr; rapl_dev->set_cdev_type("rapl_controller"); rapl_dev->set_cdev_alias("B0D4"); if (rapl_dev->update() == THD_SUCCESS) { rapl_dev_borrow = rapl_dev.get(); cdevs.push_back(std::move(rapl_dev)); ++current_cdev_index; } else { rapl_dev.reset(); } // Add RAPL mmio cooling device if (!disable_active_power && (parser.thermal_matched_platform_index() >= 0 || force_mmio_rapl)) { std::unique_ptr rapl_mmio_dev( new cthd_sysfs_cdev_rapl( current_cdev_index, 0, "/sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/")); rapl_mmio_dev->set_cdev_type("rapl_controller_mmio"); if (rapl_mmio_dev->update() == THD_SUCCESS) { // Prefer MMIO access over MSR access for B0D4 if (rapl_dev_borrow) { struct adaptive_target target = {}; rapl_dev_borrow->set_cdev_alias(""); if (adaptive_mode) { thd_log_info("Disable rapl-msr interface and use rapl-mmio\n"); target.code = "PL1MAX"; target.argument = "200000"; rapl_dev_borrow->set_adaptive_target(target); } } rapl_mmio_dev->set_cdev_alias("B0D4"); cdevs.push_back(std::move(rapl_mmio_dev)); ++current_cdev_index; } } // Add Intel P state driver as cdev std::unique_ptr pstate_dev(new cthd_intel_p_state_cdev( current_cdev_index)); pstate_dev->set_cdev_type("intel_pstate"); if (pstate_dev->update() == THD_SUCCESS) { cdevs.push_back(std::move(pstate_dev)); ++current_cdev_index; } // Add statically defined cooling devices size = sizeof(cpu_def_cooling_devices) / sizeof(cooling_dev_t); for (i = 0; i < size; ++i) { add_replace_cdev(&cpu_def_cooling_devices[i]); } std::unique_ptr cpu_freq_dev(new cthd_cdev_cpufreq(current_cdev_index, -1)); cpu_freq_dev->set_cdev_type("cpufreq"); if (cpu_freq_dev->update() == THD_SUCCESS) { cdevs.push_back(std::move(cpu_freq_dev)); ++current_cdev_index; } std::unique_ptr rapl_dram_dev(new cthd_sysfs_cdev_rapl_dram( current_cdev_index, 0)); rapl_dram_dev->set_cdev_type("rapl_controller_dram"); if (rapl_dram_dev->update() == THD_SUCCESS) { cdevs.push_back(std::move(rapl_dram_dev)); ++current_cdev_index; } cthd_cdev *cdev = search_cdev("LCD"); if (!cdev) { std::unique_ptr backlight_dev(new cthd_cdev_backlight( current_cdev_index, 0)); backlight_dev->set_cdev_type("LCD"); if (backlight_dev->update() == THD_SUCCESS) { cdevs.push_back(std::move(backlight_dev)); ++current_cdev_index; } } // Add from XML cooling device config if (!parser_init() && parser.platform_matched()) { for (int i = 0; i < parser.cdev_count(); ++i) { cooling_dev_t *cdev_config = parser.get_cool_dev_index(i); if (!cdev_config) continue; add_replace_cdev(cdev_config); } } // Dump all cooling devices for (unsigned i = 0; i < cdevs.size(); ++i) { cdevs[i]->cdev_dump(); } return THD_SUCCESS; } // Thermal engine std::unique_ptr thd_engine; int thd_engine_create_default_engine(bool ignore_cpuid_check, bool exclusive_control, const char *conf_file) { int res; thd_engine.reset(new cthd_engine_default()); if (!thd_engine) return THD_ERROR; if (exclusive_control) thd_engine->set_control_mode(EXCLUSIVE); thd_engine->thd_parse_features(); // Initialize thermald objects thd_engine->set_poll_interval(thd_poll_interval); if (conf_file) thd_engine->set_config_file(conf_file); res = thd_engine->thd_engine_init(ignore_cpuid_check); if (res != THD_SUCCESS) { if (res == THD_FATAL_ERROR) thd_log_error("THD engine init failed\n"); else thd_log_msg("THD engine init failed\n"); return res; } res = thd_engine->thd_engine_start(); if (res != THD_SUCCESS) { if (res == THD_FATAL_ERROR) thd_log_error("THD engine start failed\n"); else thd_log_msg("THD engine start failed\n"); } return res; } void cthd_engine_default::workarounds() { if (!workaround_enabled) return; // Every 30 seconds repeat if (!disable_active_power && !workaround_interval) { // Create platform instance and call workaround std::unique_ptr platform = cthd_platform::create_platform(); if (platform) { platform->workaround_rapl_mmio_power(); } workaround_tcc_offset(); workaround_interval = 7; } else { --workaround_interval; } } void cthd_engine_default::workaround_tcc_offset(void) { #ifndef ANDROID csys_fs sys_fs; int tcc; if (tcc_offset_checked && tcc_offset_low) return; if (parser.thermal_matched_platform_index() < 0) { tcc_offset_checked = 1; tcc_offset_low = 1; return; } if (sys_fs.exists("/sys/bus/pci/devices/0000:00:04.0/tcc_offset_degree_celsius")) { if (sys_fs.read("/sys/bus/pci/devices/0000:00:04.0/tcc_offset_degree_celsius", &tcc) <= 0) { tcc_offset_checked = 1; tcc_offset_low = 1; return; } if (tcc > 10) { int ret; ret = sys_fs.write("/sys/bus/pci/devices/0000:00:04.0/tcc_offset_degree_celsius", 5); if (ret < 0) tcc_offset_low = 1; // probably locked so retryA tcc_offset_checked = 1; } else { if (!tcc_offset_checked) tcc_offset_low = 1; tcc_offset_checked = 1; } } else { thd_log_info("Kernel update is required to update TCC\n"); tcc_offset_checked = 1; tcc_offset_low = 1; } #endif } thermal_daemon-2.5.12/src/thd_engine_default.h000066400000000000000000000046031521301715300213310ustar00rootroot00000000000000/* * cthd_engine_defualt.cpp: Default thermal engine * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_ENGINE_DEFAULT_H_ #define THD_ENGINE_DEFAULT_H_ #include "thd_engine.h" #include "thd_cpu_default_binding.h" #include "thd_gddv.h" class cthd_engine_default: public cthd_engine { private: int add_replace_cdev(const cooling_dev_t *config); bool add_int340x_processor_dev(void); void disable_cpu_zone(thermal_zone_t *zone_config); void workaround_rapl_mmio_power(void); void workaround_tcc_offset(void); //cthd_cpu_default_binding def_binding; int workaround_interval; #ifndef ANDROID int tcc_offset_checked; int tcc_offset_low; #endif protected: bool force_mmio_rapl; public: static constexpr int power_clamp_reduction_percent = 5; static constexpr int processor_thermal_min_passive = 90000; cthd_gddv gddv; #ifndef ANDROID cthd_engine_default() : cthd_engine_default("42A441D6-AE6A-462b-A84B-4A8CE79027D3") { } cthd_engine_default(std::string _uuid) : cthd_engine(std::move(_uuid)), workaround_interval(0), tcc_offset_checked(0), tcc_offset_low(0), force_mmio_rapl(false) { } #else cthd_engine_default() : cthd_engine_default("42A441D6-AE6A-462b-A84B-4A8CE79027D3") { } cthd_engine_default(std::string _uuid) : cthd_engine(_uuid), workaround_interval(0), force_mmio_rapl(false) { } #endif ~cthd_engine_default() override; int read_thermal_zones() override; int read_cooling_devices() override; int read_thermal_sensors() override; void workarounds() override; }; int thd_engine_create_default_engine(bool ignore_cpuid_check, bool exclusive_control, const char *config_file); #endif /* THD_ENGINE_DEFAULT_H_ */ thermal_daemon-2.5.12/src/thd_features_parse.cpp000066400000000000000000000111271521301715300217220ustar00rootroot00000000000000/* * thd_feature_parse.cpp: Specify cdev order * * Copyright (C) 2026 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include #include "thd_features_parse.h" #include "thd_sys_fs.h" #include "thd_util.h" #include #include static constexpr int thd_xml_parse_options = XML_PARSE_NONET | XML_PARSE_NOERROR | XML_PARSE_NOWARNING; char *cthd_features_parse::char_trim(char *str) { int i; if (!str) return nullptr; if (str[0] == '\0') return str; while (isspace(*str)) str++; for (i = strlen(str) - 1; (isspace(str[i])); i--) ; str[i + 1] = '\0'; return str; } cthd_features_parse::cthd_features_parse() : doc(nullptr), root_element(nullptr) { std::string name = TDCONFDIR; filename = name + "/" "thermald-features.xml"; } int cthd_features_parse::parser_init() { // Init all features as supported feature_list.assign(MAX_FEATURE, 1); int fd = open_validated_xml_file(filename); if (fd < 0) return THD_ERROR; doc = xmlReadFd(fd, filename.c_str(), nullptr, thd_xml_parse_options); close(fd); if (doc == nullptr) { thd_log_msg("error: could not parse file %s\n", filename.c_str()); return THD_ERROR; } if (doc->intSubset != nullptr || doc->extSubset != nullptr) { thd_log_warn("Config file %s must not contain a DTD\n", filename.c_str()); xmlFreeDoc(doc); doc = nullptr; return THD_ERROR; } root_element = xmlDocGetRootElement(doc); if (root_element == nullptr) { thd_log_warn("error: could not get root element\n"); xmlFreeDoc(doc); doc = nullptr; return THD_ERROR; } return THD_SUCCESS; } void cthd_features_parse::set_feature_value(xmlNode *cur_node, xmlDoc *doc, thermald_feature_names_t feature) { char *tmp_value; if (feature >= MAX_FEATURE) return; tmp_value = (char *) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { thd_log_debug("node type: Element, name: %s value: \"%s\" \n", (const char *)cur_node->name, tmp_value); errno = 0; char *endptr = nullptr; int value = strtol(char_trim(tmp_value), &endptr, 10); if (endptr != tmp_value && *endptr == '\0' && errno == 0) { feature_list[feature] = static_cast(value); } else { thd_log_warn("Invalid feature value for %s: %s\n", (const char *)cur_node->name, tmp_value); } xmlFree(tmp_value); } } int cthd_features_parse::parse_features(xmlNode * a_node, xmlDoc *doc) { xmlNode *cur_node = nullptr; bool parsed_any = false; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { if (!thd_strcasecmp_n((const char*) cur_node->name, "DbusControl")) { set_feature_value(cur_node, doc, DBUS_CONTROL); parsed_any = true; continue; } if (!thd_strcasecmp_n((const char*) cur_node->name, "XMLThermalConfig")) { set_feature_value(cur_node, doc, XML_THERMAL_CONFIG); parsed_any = true; continue; } if (!thd_strcasecmp_n((const char*) cur_node->name, "DataVaultFromFileSystem")) { set_feature_value(cur_node, doc, DATA_VAULT_FS); parsed_any = true; continue; } if (!thd_strcasecmp_n((const char*) cur_node->name, "KobjectUeventSupport")) { set_feature_value(cur_node, doc, KOBJECT_UEVENT_SUPPORT); parsed_any = true; continue; } } } return parsed_any ? THD_SUCCESS : THD_ERROR; } int cthd_features_parse::parse(xmlNode * a_node, xmlDoc *doc) { xmlNode *cur_node = nullptr; bool parsed_any = false; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { if (!thd_strcasecmp_n((const char*) cur_node->name, "ThermaldFeatures")) { if (parse_features(cur_node->children, doc) == THD_SUCCESS) parsed_any = true; } } } return parsed_any ? THD_SUCCESS : THD_ERROR; } int cthd_features_parse::start_parse() { return parse(root_element, doc); } void cthd_features_parse::parser_deinit() { xmlFreeDoc(doc); doc = nullptr; root_element = nullptr; } thermal_daemon-2.5.12/src/thd_features_parse.h000066400000000000000000000031401521301715300213630ustar00rootroot00000000000000/* * thd_features_parse.h: Parse features * * Copyright (C) 2026 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_FEATURES_PARSE_H #define THD_FEATURES_PARSE_H #include #include #include #include #include #include "thermald.h" typedef enum : uint8_t { DBUS_CONTROL, XML_THERMAL_CONFIG, DATA_VAULT_FS, KOBJECT_UEVENT_SUPPORT, MAX_FEATURE, } thermald_feature_names_t; class cthd_features_parse { private: xmlDoc *doc; xmlNode *root_element; std::string filename; int parse_features(xmlNode *a_node, xmlDoc *doc); int parse(xmlNode *a_node, xmlDoc *doc); void set_feature_value(xmlNode *cur_node, xmlDoc *doc, thermald_feature_names_t feature); public: std::vector feature_list; char *char_trim(char *str); cthd_features_parse(); int parser_init(); void parser_deinit(); int start_parse(); }; #endif thermal_daemon-2.5.12/src/thd_gddv.cpp000066400000000000000000001633461521301715300176510ustar00rootroot00000000000000/* * cthd_gddv.cpp: Adaptive thermal engine * * Copyright (C) 2013 Intel Corporation. All rights reserved. * Copyright 2020 Google LLC * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name Matthew Garrett * */ #include #include #include #include #include #include "thd_lzma_dec.h" #include #include #include #include #include "thd_gddv.h" #include "thd_util.h" /* From esif_lilb_datavault.h */ #define ESIFDV_NAME_LEN 32 // Max DataVault Name (Cache Name) Length (not including nullptr) #define ESIFDV_DESC_LEN 64 // Max DataVault Description Length (not including nullptr) #define SHA256_HASH_BYTES 32 struct header { uint16_t signature; uint16_t headersize; uint32_t version; union { /* Added in V1 */ struct { uint32_t flags; } v1; /* Added in V2 */ struct { uint32_t flags; char segmentid[ESIFDV_NAME_LEN]; char comment[ESIFDV_DESC_LEN]; uint8_t payload_hash[SHA256_HASH_BYTES]; uint32_t payload_size; uint32_t payload_class; } v2; }; } __attribute__ ((packed)); class _gddv_exception: public std::exception { const char* what() const throw () override { return "GDDV parsing failed"; } } const gddv_exception; // TODO: why singleton void cthd_gddv::destroy_dynamic_sources() { #ifndef ANDROID if (upower_client) g_clear_object(&upower_client); if (power_profiles_daemon) g_clear_object(&power_profiles_daemon); if (tablet_dev) { close(libevdev_get_fd(tablet_dev)); libevdev_free(tablet_dev); if (lid_dev == tablet_dev) lid_dev = nullptr; tablet_dev = nullptr; } if (lid_dev) { close(libevdev_get_fd(lid_dev)); libevdev_free(lid_dev); lid_dev = nullptr; } #endif } cthd_gddv::~cthd_gddv() { } int cthd_gddv::get_type(char *object, int *offset) { if (!object || !offset || *offset < 0 || *offset + (int) sizeof(uint32_t) > gddv_cur_buf_len) { thd_log_warn("GDDV: get_type out of bounds (off=%d len=%d)\n", offset ? *offset : -1, gddv_cur_buf_len); throw gddv_exception; } return *(uint32_t*) (object + *offset); } uint64_t cthd_gddv::get_uint64(char *object, int *offset) { uint64_t value; if (!object || !offset || *offset < 0 || *offset + (int) sizeof(uint32_t) > gddv_cur_buf_len) { thd_log_warn("GDDV: get_uint64 type read out of bounds (off=%d len=%d)\n", offset ? *offset : -1, gddv_cur_buf_len); throw gddv_exception; } int type = *(uint32_t*) (object + *offset); if (type != 4) { thd_log_warn("Found object of type %d, expecting 4\n", type); throw gddv_exception; } *offset += 4; if (*offset + (int) sizeof(uint64_t) > gddv_cur_buf_len) { thd_log_warn("GDDV: get_uint64 value read out of bounds (off=%d len=%d)\n", *offset, gddv_cur_buf_len); throw gddv_exception; } value = *(uint64_t*) (object + *offset); *offset += 8; return value; } char* cthd_gddv::get_string(char *object, int *offset) { uint64_t length; char *value; if (!object || !offset || *offset < 0 || *offset + (int) sizeof(uint32_t) > gddv_cur_buf_len) { thd_log_warn("GDDV: get_string type read out of bounds (off=%d len=%d)\n", offset ? *offset : -1, gddv_cur_buf_len); throw gddv_exception; } int type = *(uint32_t*) (object + *offset); if (type != 8) { thd_log_warn("Found object of type %d, expecting 8\n", type); throw gddv_exception; } *offset += 4; if (*offset + (int) sizeof(uint64_t) > gddv_cur_buf_len) { thd_log_warn("GDDV: get_string length read out of bounds (off=%d len=%d)\n", *offset, gddv_cur_buf_len); throw gddv_exception; } length = *(uint64_t*) (object + *offset); *offset += 8; /* Reject lengths that would overflow the int offset arithmetic * or read past the buffer. */ if (length > (uint64_t) INT_MAX || *offset + (int) length > gddv_cur_buf_len) { thd_log_warn("GDDV: get_string value out of bounds (off=%d len=%d need=%llu)\n", *offset, gddv_cur_buf_len, (unsigned long long) length); throw gddv_exception; } value = &object[*offset]; *offset += length; return value; } /* * Bounded variant that returns a std::string constructed from the * exact field length, so callers cannot read past the declared length * even if the on-disk string is not NUL-terminated. */ char* cthd_gddv::get_string_obj(char *object, int *offset) { int saved = *offset; char *p = get_string(object, offset); /* get_string consumes 4 (type) + 8 (length) + length bytes. */ int consumed = *offset - saved; int length = consumed - 12; if (length < 0) return nullptr; return p; } int cthd_gddv::merge_custom(struct custom_condition *custom, struct condition *condition) { condition->device = custom->participant; condition->condition = (enum adaptive_condition) custom->type; return 0; } int cthd_gddv::merge_appc() { for (int i = 0; i < (int) custom_conditions.size(); i++) { for (int j = 0; j < (int) conditions.size(); j++) { for (int k = 0; k < (int) conditions[j].size(); k++) { if (custom_conditions[i].condition == conditions[j][k].condition) { merge_custom(&custom_conditions[i], &conditions[j][k]); } } } } return 0; } int cthd_gddv::parse_appc(char *appc, int len) { int offset = 0; uint64_t version; gddv_cur_buf_len = len; if (len < 1 || appc[0] != 4) { thd_log_info("Found malformed APPC table, ignoring\n"); return 0; } version = get_uint64(appc, &offset); if (version != 1) { // Invalid APPC tables aren't fatal thd_log_info("Found unsupported or malformed APPC version %d\n", (int) version); return 0; } while (offset < len) { struct custom_condition condition; condition.condition = (enum adaptive_condition) get_uint64(appc, &offset); condition.name = get_string_obj(appc, &offset); condition.participant = get_string_obj(appc, &offset); condition.domain = get_uint64(appc, &offset); condition.type = get_uint64(appc, &offset); custom_conditions.push_back(std::move(condition)); } return 0; } int cthd_gddv::parse_apat(char *apat, int len) { int offset = 0; gddv_cur_buf_len = len; uint64_t version = get_uint64(apat, &offset); if (version != 2) { thd_log_warn("Found unsupported APAT version %d\n", (int) version); throw gddv_exception; } while (offset < len) { struct adaptive_target target; target.target_id = get_uint64(apat, &offset); target.name = get_string_obj(apat, &offset); target.participant = get_string_obj(apat, &offset); target.domain = get_uint64(apat, &offset); target.code = get_string_obj(apat, &offset); target.argument = get_string_obj(apat, &offset); targets.push_back(std::move(target)); } return 0; } void cthd_gddv::dump_apat() { thd_log_info("..apat dump begin..\n"); for (unsigned int i = 0; i < targets.size(); ++i) { thd_log_info( "target_id:%" PRIu64 " name:%s participant:%s domain:%d code:%s argument:%s\n", targets[i].target_id, targets[i].name.c_str(), targets[i].participant.c_str(), (int)targets[i].domain, targets[i].code.c_str(), targets[i].argument.c_str()); } thd_log_info("apat dump end\n"); } #define MAX_APCT_COUNT 128 int cthd_gddv::parse_apct(char *apct, int len) { int i; int offset = 0; gddv_cur_buf_len = len; uint64_t version = get_uint64(apct, &offset); if (version == 1) { while (offset < len) { std::vector condition_set; uint64_t target = get_uint64(apct, &offset); if (int(target) == -1) { thd_log_warn("Invalid APCT target\n"); throw gddv_exception; } for (i = 0; i < 10; i++) { struct condition condition; condition.condition = adaptive_condition(0); condition.device = ""; condition.comparison = adaptive_comparison(0); condition.argument = 0; condition.operation = adaptive_operation(0); condition.time_comparison = adaptive_comparison(0); condition.time = 0; condition.target = 0; condition.state = 0; condition.state_entry_time = 0; condition.target = target; condition.ignore_condition = 0; if (offset >= len) { thd_log_warn("Read off end of buffer in APCT parsing\n"); throw gddv_exception; } condition.condition = adaptive_condition( get_uint64(apct, &offset)); condition.comparison = adaptive_comparison( get_uint64(apct, &offset)); condition.argument = get_uint64(apct, &offset); if (i < 9) { condition.operation = adaptive_operation( get_uint64(apct, &offset)); if (condition.operation == FOR) { offset += 12; condition.time_comparison = adaptive_comparison( get_uint64(apct, &offset)); condition.time = get_uint64(apct, &offset); offset += 12; i++; } } condition_set.push_back(std::move(condition)); } conditions.push_back(std::move(condition_set)); } } else if (version == 2) { while (offset < len) { std::vector condition_set; uint64_t target = get_uint64(apct, &offset); if (int(target) == -1) { thd_log_warn("Invalid APCT target"); throw gddv_exception; } uint64_t count = get_uint64(apct, &offset); // Validate count to prevent excessive allocation or DoS if (count > MAX_APCT_COUNT) { thd_log_warn("APCT v2 count %llu exceeds maximum\n", (unsigned long long)count); return THD_ERROR; } for (i = 0; i < int(count); i++) { struct condition condition = {}; condition.condition = adaptive_condition(0); condition.device = ""; condition.comparison = adaptive_comparison(0); condition.argument = 0; condition.operation = adaptive_operation(0); condition.time_comparison = adaptive_comparison(0); condition.time = 0; condition.target = 0; condition.state = 0; condition.state_entry_time = 0; condition.target = target; condition.ignore_condition = 0; if (offset >= len) { thd_log_warn("Read off end of buffer in parsing APCT\n"); throw gddv_exception; } condition.condition = adaptive_condition( get_uint64(apct, &offset)); condition.device = get_string_obj(apct, &offset); offset += 12; condition.comparison = adaptive_comparison( get_uint64(apct, &offset)); condition.argument = get_uint64(apct, &offset); if (i < int(count - 1)) { condition.operation = adaptive_operation( get_uint64(apct, &offset)); if (condition.operation == FOR) { offset += 12; get_string(apct, &offset); offset += 12; condition.time_comparison = adaptive_comparison( get_uint64(apct, &offset)); condition.time = get_uint64(apct, &offset); offset += 12; i++; } } condition_set.push_back(std::move(condition)); } conditions.push_back(std::move(condition_set)); } } else { thd_log_warn("Unsupported APCT version %d\n", (int) version); throw gddv_exception; } return 0; } static const char * const condition_names[] = { "Invalid", "Default", "Orientation", "Proximity", "Motion", "Dock", "Workload", "Cooling_mode", "Power_source", "Aggregate_power_percentage", "Lid_state", "Platform_type", "Platform_SKU", "Utilisation", "TDP", "Duty_cycle", "Power", "Temperature", "Display_orientation", "Oem0", "Oem1", "Oem2", "Oem3", "Oem4", "Oem5", "PMAX", "PSRC", "ARTG", "CTYP", "PROP", "Unk1", "Unk2", "Battery_state", "Battery_rate", "Battery_remaining", "Battery_voltage", "PBSS", "Battery_cycles", "Battery_last_full", "Power_personality", "Battery_design_capacity", "Screen_state", "AVOL", "ACUR", "AP01", "AP02", "AP10", "Time", "Temperature_without_hysteresis", "Mixed_reality", "User_presence", "RBHF", "VBNL", "CMPP", "Battery_percentage", "Battery_count", "Power_slider", "OS_Type", }; static const char * const comp_strs[] = { "INVALID", "ADAPTIVE_EQUAL", "ADAPTIVE_LESSER_OR_EQUAL", "ADAPTIVE_GREATER_OR_EQUAL", "ADAPTIVE_NOT_EQUAL" }; #define ARRAY_SIZE(array) \ (sizeof(array) / sizeof(array[0])) void cthd_gddv::dump_apct() { thd_log_info("..apct dump begin..\n"); for (unsigned int i = 0; i < conditions.size(); ++i) { std::vector condition_set; thd_log_info("condition_set %u\n", i); condition_set = conditions[i]; for (unsigned int j = 0; j < condition_set.size(); ++j) { std::string cond_name, comp_str, op_str; if (condition_set[j].condition < ARRAY_SIZE(condition_names)) { cond_name = condition_names[condition_set[j].condition]; } else if (condition_set[j].condition >= 0x1000 && condition_set[j].condition < 0x10000) { std::ostringstream msg; msg << "Oem" << (condition_set[j].condition - 0x1000 + 6); cond_name = msg.str(); } else if (condition_set[j].condition == OS_type) { cond_name = "OS_Type"; } else { std::ostringstream msg; msg << "UNKNOWN" << "( " << condition_set[j].condition << " )"; cond_name = msg.str(); } if (condition_set[j].comparison < ARRAY_SIZE(comp_strs)) { comp_str = comp_strs[condition_set[j].comparison]; } if (condition_set[j].operation == 1) { op_str = "AND"; } else if (condition_set[j].operation == 2) { op_str = "FOR"; } else { op_str = "INVALID"; } thd_log_info( "\ttarget:%d device:%s condition:%s comparison:%s argument:%d" " operation:%s time_comparison:%d time:%jd" " stare:%d state_entry_time:%jd\n", condition_set[j].target, condition_set[j].device.c_str(), cond_name.c_str(), comp_str.c_str(), condition_set[j].argument, op_str.c_str(), condition_set[j].time_comparison, (intmax_t)condition_set[j].time, condition_set[j].state, (intmax_t)condition_set[j].state_entry_time); } } thd_log_info("..apct dump end..\n"); } ppcc_t* cthd_gddv::get_ppcc_param(const std::string& name) { if (name != "TCPU.D0") return nullptr; for (int i = 0; i < (int) ppccs.size(); i++) { if (ppccs[i].name == name) return &ppccs[i]; } return nullptr; } int cthd_gddv::parse_ppcc(char *name, char *buf, int len) { ppcc_t ppcc; /* The first block reads up to offset 84 (76 + sizeof(uint64_t)). */ if (len < 84) { thd_log_warn("PPCC table too small (%d), ignoring\n", len); return 0; } ppcc.name = name; ppcc.power_limit_min = *(uint64_t*) (buf + 28); ppcc.power_limit_max = *(uint64_t*) (buf + 40); ppcc.time_wind_min = *(uint64_t*) (buf + 52); ppcc.time_wind_max = *(uint64_t*) (buf + 64); ppcc.step_size = *(uint64_t*) (buf + 76); ppcc.valid = 1; if (len < 156) return 0; thd_log_info("Processing ppcc limit 2, length %d\n", len); int start = 76 + 12; ppcc.power_limit_1_min = *(uint64_t*) (buf + start + 12); ppcc.power_limit_1_max = *(uint64_t*) (buf + start + 24); ppcc.time_wind_1_min = *(uint64_t*) (buf + start + 36); ppcc.time_wind_1_max = *(uint64_t*) (buf + start + 48); ppcc.step_1_size = *(uint64_t*) (buf + start + 60); if (ppcc.power_limit_1_max && ppcc.power_limit_1_min && ppcc.time_wind_1_min && ppcc.time_wind_1_max && ppcc.step_1_size) ppcc.limit_1_valid = 1; else ppcc.limit_1_valid = 0; ppccs.push_back(std::move(ppcc)); return 0; } void cthd_gddv::dump_ppcc() { thd_log_info("..ppcc dump begin..\n"); for (unsigned int i = 0; i < ppccs.size(); ++i) { thd_log_info( "Name:%s Limit:0 power_limit_max:%d power_limit_min:%d step_size:%d time_win_max:%d time_win_min:%d\n", ppccs[i].name.c_str(), ppccs[i].power_limit_max, ppccs[i].power_limit_min, ppccs[i].step_size, ppccs[i].time_wind_max, ppccs[i].time_wind_min); thd_log_info( "Name:%s Limit:1 power_limit_max:%d power_limit_min:%d step_size:%d time_win_max:%d time_win_min:%d\n", ppccs[i].name.c_str(), ppccs[i].power_limit_1_max, ppccs[i].power_limit_1_min, ppccs[i].step_1_size, ppccs[i].time_wind_1_max, ppccs[i].time_wind_1_min); } thd_log_info("ppcc dump end\n"); } int cthd_gddv::parse_psvt(char *name, char *buf, int len) { int offset = 0; gddv_cur_buf_len = len; int version = get_uint64(buf, &offset); struct psvt psvt; if (version > 2) { thd_log_warn("Found unsupported PSVT version %d\n", (int) version); throw gddv_exception; } if (name == nullptr) psvt.name = "Default"; else psvt.name = name; while (offset < len) { struct psv psv; psv.source = get_string_obj(buf, &offset); psv.target = get_string_obj(buf, &offset); psv.priority = get_uint64(buf, &offset); psv.sample_period = get_uint64(buf, &offset); psv.temp = get_uint64(buf, &offset); psv.domain = get_uint64(buf, &offset); psv.control_knob = get_uint64(buf, &offset); if (get_type(buf, &offset) == 8) { psv.limit = get_string_obj(buf, &offset); } else { uint64_t tmp = get_uint64(buf, &offset); psv.limit = std::to_string(tmp); } psv.step_size = get_uint64(buf, &offset); psv.limit_coeff = get_uint64(buf, &offset); psv.unlimit_coeff = get_uint64(buf, &offset); offset += 12; psvt.psvs.push_back(std::move(psv)); } psvts.push_back(std::move(psvt)); return 0; } void cthd_gddv::dump_psvt() { thd_log_info("..psvt dump begin..\n"); for (unsigned int i = 0; i < psvts.size(); ++i) { std::vector psvs = psvts[i].psvs; thd_log_info("Name :%s\n", psvts[i].name.c_str()); for (unsigned int j = 0; j < psvs.size(); ++j) { thd_log_info( "\t source:%s target:%s priority:%d sample_period:%d temp:%d domain:%d control_knob:%d psv.limit:%s\n", psvs[j].source.c_str(), psvs[j].target.c_str(), psvs[j].priority, psvs[j].sample_period, DECI_KELVIN_TO_CELSIUS(psvs[j].temp), psvs[j].domain, psvs[j].control_knob, psvs[j].limit.c_str()); } } thd_log_info("psvt dump end\n"); } struct psvt* cthd_gddv::find_def_psvt() { for (unsigned int i = 0; i < psvts.size(); ++i) { if (psvts[i].name == "generic_os") { thd_log_info("Found generic_os table\n"); return &psvts[i]; } } for (unsigned int i = 0; i < psvts.size(); ++i) { if (psvts[i].name == "IETM.D0") { return &psvts[i]; } } return nullptr; } struct __attribute__((packed)) itmt3_header { uint32_t reserved; uint64_t revision; }; struct __attribute__((packed)) itmt3 { char target[64]; uint64_t temp; uint64_t reserved_1; char pl1_min[64]; char pl1_max[64]; char reserved_2[88]; }; int cthd_gddv::parse_itmt3(char *name, char *buf, unsigned int len) { unsigned int offset = 0; struct itmt itmt; gddv_cur_buf_len = (int) len; if (name == nullptr) itmt.name = "Default"; else itmt.name = name; itmt.version = 3; if (len < sizeof(struct itmt3_header)) return THD_ERROR; offset += sizeof(struct itmt3_header); if (len - offset < sizeof(struct itmt3)) return THD_ERROR; while (offset < len) { struct itmt_entry itmt_entry; struct itmt3 *entry = (struct itmt3 *)&buf[offset]; if (len - offset < sizeof(struct itmt3)) return THD_ERROR; itmt_entry.target = entry->target; itmt_entry.trip_point = entry->temp; itmt_entry.pl1_min = entry->pl1_min; itmt_entry.pl1_max = entry->pl1_max; offset += sizeof(struct itmt3); itmt.itmt_entries.push_back(std::move(itmt_entry)); } itmts.push_back(std::move(itmt)); return 0; } int cthd_gddv::parse_vspt(char *name, char *buf, int len) { int offset = 0; gddv_cur_buf_len = len; int version = get_uint64(buf, &offset); if (version > 1) { thd_log_warn("Found unsupported VSPT version %d\n", (int) version); return THD_ERROR; } while (offset < len) { struct vspt_entry vspt; vspt.virtual_temp = get_uint64(buf, &offset); vspt.virtual_temp = DECI_KELVIN_TO_CELSIUS(vspt.virtual_temp), vspt.sample_period = get_uint64(buf, &offset) / 10; vspts.push_back(vspt); } return THD_SUCCESS; } int cthd_gddv:: parse_vsct(char *name, char *buf, int len) { if (name == nullptr) vscts_name = "vsct"; else vscts_name = name; thd_log_debug(" vsct name %s\n", vscts_name.c_str()); int offset = 0; gddv_cur_buf_len = len; int version = get_uint64(buf, &offset); if (version > 1) { thd_log_warn("Found unsupported VSCT version %d\n", (int) version); return THD_ERROR; } while (offset < len) { struct vsct_entry vsct; vsct.target = get_string_obj(buf, &offset); vsct.domain_type = get_uint64(buf, &offset); vsct.coeff_type = get_uint64(buf, &offset); vsct.coeff = get_uint64(buf, &offset); vsct.operation = get_uint64(buf, &offset); vsct.alpha = get_uint64(buf, &offset); vsct.trigger_point = get_uint64(buf, &offset); vscts.push_back(std::move(vsct)); } return THD_SUCCESS; } void cthd_gddv::dump_vsct() { thd_log_info("..vsct dump begin [%s]\n", vscts_name.c_str()); for (unsigned int i = 0; i < vscts.size(); ++i) { struct vsct_entry vsct = vscts[i]; thd_log_info( "\t target:%s domain_type:%d coeff_type:%d coeff:%d operation:%d alpha:%d trigger_point:%d\n", vsct.target.c_str(), vsct.domain_type, vsct.coeff_type, vsct.coeff, vsct.operation, vsct.alpha, vsct.trigger_point ); } thd_log_info("vsct dump end\n"); } void cthd_gddv::dump_vspt() { thd_log_info("..vspt dump begin\n"); for (unsigned int i = 0; i < vspts.size(); ++i) { struct vspt_entry vspt = vspts[i]; thd_log_info( "\t sample_temp:%d polling period:%d\n", vspt.virtual_temp, vspt.sample_period); } thd_log_info("vspt dump end\n"); } int cthd_gddv::parse_itmt(char *name, char *buf, int len) { int offset = 0; gddv_cur_buf_len = len; int version = get_uint64(buf, &offset); struct itmt itmt; thd_log_debug(" ITMT version %d %s\n", (int) version, name); if (version > 2) { thd_log_info("Unsupported ITMT version\n"); return THD_ERROR; } if (name == nullptr) itmt.name = "Default"; else itmt.name = name; itmt.version = 1; while (offset < len) { struct itmt_entry itmt_entry; itmt_entry.target = get_string_obj(buf, &offset); itmt_entry.trip_point = get_uint64(buf, &offset); itmt_entry.pl1_min = get_string_obj(buf, &offset); itmt_entry.pl1_max = get_string_obj(buf, &offset); itmt_entry.unused = get_string_obj(buf, &offset); if (version == 2) { // Ref DPTF/Sources/Manager/DataManager.cpp DataManager::loadItmtTableObject() std::string dummy_str; unsigned long long dummy1,dummy2, dummy3; // There are three additional fields dummy1 = get_uint64(buf, &offset); dummy_str = get_string_obj(buf, &offset); dummy2 = get_uint64(buf, &offset); dummy3 = get_uint64(buf, &offset); thd_log_debug("ignore dummy_str:%s %llu %llu %llu\n", dummy_str.c_str(), dummy1, dummy2, dummy3); } else { offset += 12; } itmt.itmt_entries.push_back(std::move(itmt_entry)); } itmts.push_back(std::move(itmt)); return 0; } void cthd_gddv::dump_itmt() { thd_log_info("..itmt dump begin..\n"); for (unsigned int i = 0; i < itmts.size(); ++i) { std::vector itmt = itmts[i].itmt_entries; thd_log_info("Name :%s\n", itmts[i].name.c_str()); thd_log_info("version :%d\n", itmts[i].version); for (unsigned int j = 0; j < itmt.size(); ++j) { thd_log_info("\t target:%s trip_temp:%d pl1_min:%s pl1.max:%s\n", itmt[j].target.c_str(), DECI_KELVIN_TO_CELSIUS(itmt[j].trip_point), itmt[j].pl1_min.c_str(), itmt[j].pl1_max.c_str()); } } thd_log_info("itmt dump end\n"); } void cthd_gddv::parse_idsp(char *name, char *start, int length) { int len, i = 0; unsigned char *str = (unsigned char*) start; if (length < 0) return; while (i < length) { char idsp[128]; std::string idsp_str; // The minimum length for an IDSP should be at least 28 // including headers and values if ((length - i) < 28) return; if (*str != 7) break; str += 4; // Get to Length field i += 4; len = *(int*) str; /* Reject negative or absurd lengths from untrusted input */ if (len < 16 || len > (length - i - 8)) { thd_log_warn("IDSP entry has invalid length %d (remaining %d)\n", len, length - i - 8); return; } str += 8; // Get to actual contents i += 8; snprintf(idsp, sizeof(idsp), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", str[3], str[2], str[1], str[0], str[5], str[4], str[7], str[6], str[8], str[9], str[10], str[11], str[12], str[13], str[14], str[15]); idsp_str = idsp; std::transform(idsp_str.begin(), idsp_str.end(), idsp_str.begin(), ::toupper); idsps.push_back(std::move(idsp_str)); str += len; i += len; } } void cthd_gddv::dump_idsps() { thd_log_info("..idsp dump begin..\n"); for (unsigned int i = 0; i < idsps.size(); ++i) { thd_log_info("idsp :%s\n", idsps[i].c_str()); } thd_log_info("idsp dump end\n"); } int cthd_gddv::search_idsp(const std::string& name) { for (unsigned int i = 0; i < idsps.size(); ++i) { if (!idsps[i].compare(0, 36, name)) return THD_SUCCESS; } return THD_ERROR; } void cthd_gddv::parse_trip_point(char *name, char *type, char *val, int len) { struct trippoint trip; if (len < (int) sizeof(int)) { thd_log_warn("trip point payload too small (%d)\n", len); return; } trip.name = name; trip.type_str = type; if (!trip.type_str.compare(0, 2, "_c")) trip.type = CRITICAL; else if (!trip.type_str.compare(0, 2, "_p")) trip.type = PASSIVE; else if (!trip.type_str.compare(0, 2, "_h")) trip.type = HOT; else if (!trip.type_str.compare(0, 2, "_a")) trip.type = ACTIVE; else trip.type = INVALID_TRIP_TYPE; trip.temp = DECI_KELVIN_TO_CELSIUS(*(int *)val); trippoints.push_back(std::move(trip)); } void cthd_gddv::dump_trips() { thd_log_info("..trippoint dump begin..\n"); for (unsigned int i = 0; i < trippoints.size(); ++i) { thd_log_info("name:%s type_str:%s type:%d temp:%d\n", trippoints[i].name.c_str(), trippoints[i].type_str.c_str(), trippoints[i].type, trippoints[i].temp); } thd_log_info("trippoint dump end\n"); } int cthd_gddv::get_trip_temp(const std::string& name, trip_point_type_t type) { std::string search_name = name + ".D0"; for (unsigned int i = 0; i < trippoints.size(); ++i) { if (trippoints[i].name == search_name && trippoints[i].type == type) return trippoints[i].temp; } return THD_ERROR; } int cthd_gddv::parse_trt(char *buf, int len) { int offset = 0; gddv_cur_buf_len = len; thd_log_debug("TRT len:%d\n", len); if (len > 0) { thd_log_info( "_TRT not implemented. Report this for implementation with the thermald log using --loglevel=debug\n"); } while (offset < len) { struct trt_entry entry; entry.source = get_string_obj(buf, &offset); entry.dest = get_string_obj(buf, &offset); entry.priority = get_uint64(buf, &offset); entry.sample_rate = get_uint64(buf, &offset); entry.resd0 = get_uint64(buf, &offset); entry.resd1 = get_uint64(buf, &offset); entry.resd2 = get_uint64(buf, &offset); entry.resd3 = get_uint64(buf, &offset); thd_log_info("trt source:%s dest:%s prio:%d sample_rate:%d\n", entry.source.c_str(), entry.dest.c_str(), entry.priority, entry.sample_rate); } return THD_SUCCESS; } // From Common/esif_sdk_iface_esif.h: #define ESIF_SERVICE_CONFIG_COMPRESSED 0x40000000/* Payload is Compressed */ // From Common/esif_sdk.h #define ESIFHDR_VERSION(major, minor, revision) ((uint32_t)((((major) & 0xFF) << 24) | (((minor) & 0xFF) << 16) | ((revision) & 0xFFFF))) #define ESIFHDR_GET_MAJOR(version) ((uint32_t)(((version) >> 24) & 0xFF)) #define ESIFHDR_GET_MINOR(version) ((uint32_t)(((version) >> 16) & 0xFF)) #define ESIFHDR_GET_REVISION(version) ((uint32_t)((version) & 0xFFFF)) //From ESIF/Products/ESIF_LIB/Sources/esif_lib_datavault.c #define ESIFDV_HEADER_SIGNATURE 0x1FE5 #define ESIFDV_ITEM_KEYS_REV0_SIGNATURE 0xA0D8 #define MAX_DATA_VAULT_SIZE (512 * 1024) #define MAX_GDDV_SEGMENTS 30 int cthd_gddv::handle_compressed_gddv(char *buf, int size) { if (!buf || !size || size <= (int)sizeof(struct header)) return THD_ERROR; struct header *header = (struct header*) buf; if (header->headersize > size) return THD_ERROR; uint64_t output_size; int res; size_t destlen=0; res = lzma_decompress(nullptr, &destlen, (const unsigned char*) (buf + header->headersize), size - header->headersize); if (res) return THD_ERROR; if (!destlen || destlen > MAX_DATA_VAULT_SIZE) { thd_log_warn("Invalid or unsupported data vault size\n"); return THD_ERROR; } output_size = header->headersize + destlen; std::unique_ptr decompressed(new unsigned char[output_size]); if (!decompressed) { thd_log_warn("Failed to allocate buffer for decompressed output\n"); return THD_ERROR; } thd_log_debug("output size =%" PRIu64 "\n", output_size); res=lzma_decompress((unsigned char*)(decompressed.get() + header->headersize), &destlen, (const unsigned char*) (buf + header->headersize), size - header->headersize); if (res) return THD_ERROR; /* Copy and update header. * This will contain one or more nested repositories usually. */ memcpy (decompressed.get(), buf, header->headersize); header = (struct header*) decompressed.get(); header->v2.flags &= ~ESIF_SERVICE_CONFIG_COMPRESSED; header->v2.payload_size = destlen; res = parse_gddv((char*)decompressed.get(), output_size, nullptr, 0); return res; } int cthd_gddv::parse_gddv_key(char *buf, int size, int *end_offset) { int offset = 0; uint32_t keyflags; uint32_t keylength; uint32_t valtype; uint32_t vallength; char *str; char *name = nullptr; char *type = nullptr; char *point = nullptr; char *ns = nullptr; if (size < 0 || (size_t) size < sizeof(keyflags) + sizeof(keylength)) { thd_log_warn("GDDV key truncated (size=%d)\n", size); throw gddv_exception; } memcpy(&keyflags, buf + offset, sizeof(keyflags)); offset += sizeof(keyflags); memcpy(&keylength, buf + offset, sizeof(keylength)); offset += sizeof(keylength); /* Validate keylength against remaining buffer; reserve one byte for * a NUL terminator so subsequent strtok() cannot walk off the end * if the on-disk key is not NUL-terminated. */ if (keylength == 0 || keylength >= (uint32_t) INT_MAX || offset + (int) keylength > size) { thd_log_warn("GDDV keylength %u rejected (offset=%d size=%d)\n", keylength, offset, size); throw gddv_exception; } std::unique_ptr key(new char[(size_t) keylength + 1]); if (!key) { thd_log_warn("Mem alloc failed for key\n"); throw gddv_exception; } memcpy(key.get(), buf + offset, keylength); key[keylength] = '\0'; offset += keylength; if (offset + (int) (sizeof(valtype) + sizeof(vallength)) > size) { thd_log_warn("GDDV value header truncated (offset=%d size=%d)\n", offset, size); throw gddv_exception; } memcpy(&valtype, buf + offset, sizeof(valtype)); offset += sizeof(valtype); memcpy(&vallength, buf + offset, sizeof(vallength)); offset += sizeof(vallength); if (vallength >= (uint32_t) INT_MAX || offset + (int) vallength > size) { thd_log_warn("GDDV vallength %u rejected (offset=%d size=%d)\n", vallength, offset, size); throw gddv_exception; } std::unique_ptr val(new char[(size_t) vallength + 1]); if (!val) { thd_log_warn("Mem alloc failed for val\n"); throw gddv_exception; } memcpy(val.get(), buf + offset, vallength); val[vallength] = '\0'; offset += vallength; if (end_offset) *end_offset = offset; str = strtok(key.get(), "/"); if (!str) { thd_log_debug("Ignoring key %s\n", key.get()); /* Ignore */ return THD_SUCCESS; } if (thd_strcmp_n(str, "participants") == 0) { name = strtok(nullptr, "/"); type = strtok(nullptr, "/"); point = strtok(nullptr, "/"); } else if (thd_strcmp_n(str, "shared") == 0) { ns = strtok(nullptr, "/"); type = strtok(nullptr, "/"); if (thd_strcmp_n(ns, "tables") == 0) { point = strtok(nullptr, "/"); } } if (name && type && thd_strcmp_n(type, "ppcc") == 0) { parse_ppcc(name, val.get(), vallength); } if (type && thd_strcmp_n(type, "psvt") == 0) { if (point == nullptr) parse_psvt(name, val.get(), vallength); else parse_psvt(point, val.get(), vallength); } if (type && thd_strcmp_n(type, "appc") == 0) { parse_appc(val.get(), vallength); } if (type && thd_strcmp_n(type, "apct") == 0) { parse_apct(val.get(), vallength); } if (type && thd_strcmp_n(type, "apat") == 0) { parse_apat(val.get(), vallength); } if (type && strncmp(type, "itmt3", strlen("itmt3")) == 0) { thd_log_debug("Found ITMT 3\n"); if (point == nullptr) parse_itmt3(name, val.get(), vallength); else parse_itmt3(point, val.get(), vallength); } if (type && thd_strcmp_n(type, "itmt") == 0) { if (point == nullptr) parse_itmt(name, val.get(), vallength); else parse_itmt(point, val.get(), vallength); } if (name && type && thd_strcmp_n(type, "idsp") == 0) { parse_idsp(name, val.get(), vallength); } if (name && type && point && thd_strcmp_n(type, "trippoint") == 0) { parse_trip_point(name, point, val.get(), vallength); } if (type && thd_strcmp_n(type, "_trt") == 0) { parse_trt(val.get(), vallength); } if (type && thd_strcmp_n(type, "vsct") == 0) { if (point == nullptr) parse_vsct(name, val.get(), vallength); else parse_vsct(point, val.get(), vallength); } if (type && thd_strcmp_n(type, "vspt") == 0) { if (point == nullptr) parse_vspt(name, val.get(), vallength); else parse_vspt(point, val.get(), vallength); } return THD_SUCCESS; } int cthd_gddv::parse_gddv(char *buf, int size, int *end_offset, int depth) { int offset = 0; struct header *header; if (depth > MAX_GDDV_SEGMENTS) { thd_log_warn("max segments %d exceeded maximum %d\n", depth, MAX_GDDV_SEGMENTS); return THD_ERROR; } if (size < (int) sizeof(struct header)) return THD_ERROR; header = (struct header*) buf; if (header->signature != ESIFDV_HEADER_SIGNATURE) { thd_log_warn("Unexpected GDDV signature 0x%x\n", header->signature); throw gddv_exception; } if (ESIFHDR_GET_MAJOR(header->version) != 1 && ESIFHDR_GET_MAJOR(header->version) != 2) return THD_ERROR; offset = header->headersize; thd_log_debug("header version[%d] size[%d] header_size[%d] flags[%08X]\n", ESIFHDR_GET_MAJOR(header->version), size, header->headersize, header->v1.flags); if (ESIFHDR_GET_MAJOR(header->version) == 2) { char name[ESIFDV_NAME_LEN + 1] = { 0 }; char comment[ESIFDV_DESC_LEN + 1] = { 0 }; if (header->v2.flags & ESIF_SERVICE_CONFIG_COMPRESSED) { thd_log_debug("Uncompress GDDV payload\n"); return handle_compressed_gddv(buf, size); } strncpy(name, header->v2.segmentid, sizeof(name) - 1); strncpy(comment, header->v2.comment, sizeof(comment) - 1); name[sizeof(name) - 1] = '\0'; comment[sizeof(comment) - 1] = '\0'; thd_log_debug("DV name: %s\n", name); thd_log_debug("DV comment: %s\n", comment); thd_log_debug("Got payload of size %d (data length: %d)\n", size, header->v2.payload_size); //size = header->v2.payload_size; } while ((offset + header->headersize) < size) { int res; int end_offset = 0; if (ESIFHDR_GET_MAJOR(header->version) == 2) { unsigned short signature; signature = *(unsigned short *) (buf + offset); if (signature == ESIFDV_ITEM_KEYS_REV0_SIGNATURE) { offset += sizeof(unsigned short); res = parse_gddv_key(buf + offset, size - offset, &end_offset); if (res != THD_SUCCESS) return res; offset += end_offset; } else if (signature == ESIFDV_HEADER_SIGNATURE) { thd_log_info("Got subobject in buf %p at %d\n", buf, offset); res = parse_gddv(buf + offset, size - offset, &end_offset, depth + 1); if (res != THD_SUCCESS) return res; /* Parse recursively */ offset += end_offset; thd_log_info("Subobject ended at %d of %d\n", offset, size); } else { thd_log_info("No known signature found 0x%04X\n", *(unsigned short *) (buf + offset)); return THD_ERROR; } } else { res = parse_gddv_key(buf + offset, size - offset, &end_offset); if (res != THD_SUCCESS) return res; offset += end_offset; } } if (end_offset) *end_offset = offset; return 0; } int cthd_gddv::verify_condition(const struct condition& condition) { const char *cond_name; if (condition.condition >= Oem0 && condition.condition <= Oem5) return 0; if (condition.condition >= adaptive_condition(0x1000) && condition.condition < adaptive_condition(0x10000)) return 0; if (condition.condition == Default) return 0; if (condition.condition == Temperature || condition.condition == Temperature_without_hysteresis || condition.condition == (adaptive_condition) 0) { return 0; } #ifndef ANDROID if (condition.condition == Lid_state && lid_dev != nullptr) return 0; if (condition.condition == Power_source && upower_client != nullptr) return 0; #endif if (condition.condition == Workload) return 0; if (condition.condition == Platform_type) return 0; if (condition.condition == Power_slider) return 0; if (condition.condition == OS_type) return 0; if ( condition.condition >= ARRAY_SIZE(condition_names)) cond_name = "UNKNOWN"; else cond_name = condition_names[condition.condition]; thd_log_info("Unsupported condition %" PRIu64 " (%s)\n", condition.condition, cond_name); return THD_ERROR; } int cthd_gddv::verify_conditions() { int result = 0; for (int i = 0; i < (int) conditions.size(); i++) { for (int j = 0; j < (int) conditions[i].size(); j++) { if (verify_condition(conditions[i][j])) result = THD_ERROR; } } if (result != 0) thd_log_info("Unsupported conditions are present\n"); return result; } int cthd_gddv::compare_condition(const struct condition& condition, int value) { if (thd_engine && thd_engine->debug_mode_on()) { if (condition.condition < ARRAY_SIZE(condition_names)) { std::string cond_name, comp_str; cond_name = condition_names[condition.condition]; if (condition.comparison < ARRAY_SIZE(comp_strs)) { comp_str = comp_strs[condition.comparison]; thd_log_debug( "compare condition [%s] comparison [%s] value [%d]\n", cond_name.c_str(), comp_str.c_str(), value); } else { thd_log_debug( "compare condition [%s] comparison [%" PRIu64 "] value [%d]\n", cond_name.c_str(), condition.comparison, value); } } else { thd_log_debug("compare condition %" PRIu64 " value %d\n", condition.comparison, value); } } switch (condition.comparison) { case ADAPTIVE_EQUAL: if (value == condition.argument) return THD_SUCCESS; else return THD_ERROR; break; case ADAPTIVE_LESSER_OR_EQUAL: if (value <= condition.argument) return THD_SUCCESS; else return THD_ERROR; break; case ADAPTIVE_GREATER_OR_EQUAL: if (value >= condition.argument) return THD_SUCCESS; else return THD_ERROR; break; case ADAPTIVE_NOT_EQUAL: if (value != condition.argument) return THD_SUCCESS; else return THD_ERROR; break; default: return THD_ERROR; } } int cthd_gddv::compare_time(const struct condition& condition) { int elapsed = time(nullptr) - condition.state_entry_time; switch (condition.time_comparison) { case ADAPTIVE_EQUAL: if (elapsed == condition.time) return THD_SUCCESS; else return THD_ERROR; break; case ADAPTIVE_LESSER_OR_EQUAL: if (elapsed <= condition.time) return THD_SUCCESS; else return THD_ERROR; break; case ADAPTIVE_GREATER_OR_EQUAL: if (elapsed >= condition.time) return THD_SUCCESS; else return THD_ERROR; break; case ADAPTIVE_NOT_EQUAL: if (elapsed != condition.time) return THD_SUCCESS; else return THD_ERROR; break; default: return THD_ERROR; } } int cthd_gddv::evaluate_oem_condition(const struct condition& condition) { csys_fs sysfs(int3400_base_path); int oem_condition = -1; if (condition.condition >= Oem0 && condition.condition <= Oem5) oem_condition = (int) condition.condition - Oem0; else if (condition.condition >= (adaptive_condition) 0x1000 && condition.condition < (adaptive_condition) 0x10000) oem_condition = (int) condition.condition - 0x1000 + 6; if (oem_condition != -1) { std::string filename = "odvp" + std::to_string(oem_condition); int value; if (sysfs.read(filename, &value) < 0) { thd_log_error("Unable to read %s\n", filename.c_str()); return THD_ERROR; } return compare_condition(condition, value); } return THD_ERROR; } int cthd_gddv::evaluate_temperature_condition( struct condition condition) { std::string sensor_name; if (condition.ignore_condition) return THD_ERROR; size_t pos = condition.device.find_last_of('.'); if (pos == std::string::npos) sensor_name = condition.device; else sensor_name = condition.device.substr(pos + 1); cthd_sensor *sensor = thd_engine->search_sensor(sensor_name); if (!sensor) { thd_log_info("Unable to find a sensor for %s\n", condition.device.c_str()); condition.ignore_condition = 1; return THD_ERROR; } int value = sensor->read_temperature(); // Conditions are specified in decikelvin, temperatures are in // millicelsius. value = value / 100 + 2732; return compare_condition(condition, value); } #ifdef ANDROID int cthd_gddv::evaluate_lid_condition(const struct condition& condition) { int value = 1; return compare_condition(condition, value); } #else int cthd_gddv::evaluate_lid_condition(const struct condition& condition) { int value = 0; if (lid_dev) { struct input_event ev; while (libevdev_has_event_pending(lid_dev)) libevdev_next_event(lid_dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); int lid_closed = libevdev_get_event_value(lid_dev, EV_SW, SW_LID); value = !lid_closed; } return compare_condition(condition, value); } #endif int cthd_gddv::evaluate_workload_condition( const struct condition& condition) { // We don't have a good way to assert workload at the moment, so just // default to bursty return compare_condition(condition, 3); } #ifdef ANDROID /* * Platform Type * Clamshell(1) * Tablet(2) * Other/Invalid(0) * */ int cthd_gddv::evaluate_platform_type_condition( const struct condition& condition) { int value = 2;//Tablet return compare_condition(condition, value); } #else int cthd_gddv::evaluate_platform_type_condition( const struct condition& condition) { int value = 1; if (tablet_dev) { struct input_event ev; while (libevdev_has_event_pending(tablet_dev)) libevdev_next_event(tablet_dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); int tablet = libevdev_get_event_value(tablet_dev, EV_SW, SW_TABLET_MODE); if (tablet) value = 2; } return compare_condition(condition, value); } #endif int cthd_gddv::evaluate_power_slider_condition( const struct condition& condition) { return compare_condition(condition, power_slider); } #ifdef ANDROID /* *Power Source AC(0) DC(1) Short Term DC(2) * */ int cthd_gddv::evaluate_ac_condition(const struct condition& condition) { csys_fs cdev_sysfs("/sys/class/power_supply/AC/online"); int status = 0; int value = 0; thd_log_debug("evaluate evaluate_ac_condition %" PRIu64 "\n", condition.condition); if (cdev_sysfs.exists("")) { int ret = cdev_sysfs.read("", &status); if (ret < 0) return ret; thd_log_debug("evaluate found battery sys status=%d\n",status); } if (status!=1) { value = 1; } thd_log_debug("evaluate found battery sys value=%d\n",value); return compare_condition(condition, value); } #else int cthd_gddv::evaluate_ac_condition(const struct condition& condition) { int value = 0; bool on_battery = up_client_get_on_battery(upower_client); if (on_battery) value = 1; return compare_condition(condition, value); } #endif int cthd_gddv::evaluate_os_type_condition(const struct condition& condition) { /* Match Linux, which is 3 */ return compare_condition(condition, 3); } int cthd_gddv::evaluate_condition(struct condition& condition) { int ret = THD_ERROR; if (condition.condition == Default) return THD_SUCCESS; thd_log_debug("evaluate condition.condition %" PRIu64 "\n", condition.condition); if (condition.condition == OS_type) { ret = evaluate_os_type_condition(condition); } if ((condition.condition >= Oem0 && condition.condition <= Oem5) || (condition.condition >= (adaptive_condition) 0x1000 && condition.condition < (adaptive_condition) 0x10000)) ret = evaluate_oem_condition(condition); if (condition.condition == Temperature || condition.condition == Temperature_without_hysteresis || condition.condition == (adaptive_condition) 0) { ret = evaluate_temperature_condition(condition); } if (condition.condition == Lid_state) { ret = evaluate_lid_condition(condition); } if (condition.condition == Power_source) { ret = evaluate_ac_condition(condition); } if (condition.condition == Workload) { ret = evaluate_workload_condition(condition); } if (condition.condition == Platform_type) { ret = evaluate_platform_type_condition(condition); } if (condition.condition == Power_slider) { ret = evaluate_power_slider_condition(condition); } if (condition.condition == Motion) { thd_log_debug("Match motion == 0 :%d\n", condition.argument); if (condition.argument == 0) ret = THD_SUCCESS; } if (!ret) { // After a target is matched, periodically when the condition set // is periodically checked, because of time component it will fail // first and then match. This will result in switch of target // back and forth, so don't check time for the current target if (condition.target == current_target_matched) return ret; if (condition.time) { thd_log_debug("time condition matched %ld \n", condition.state_entry_time); if (condition.state_entry_time == 0) { condition.state_entry_time = time(nullptr); return THD_ERROR; } else { ret = compare_time(condition); thd_log_debug("compare time output %d\n", ret); if (!ret) condition.state_entry_time = 0; } } } else { condition.state_entry_time = 0; } return ret; } int cthd_gddv::evaluate_condition_set(std::vector& condition_set) { for (int i = 0; i < (int) condition_set.size(); i++) { thd_log_debug("evaluate condition.condition at index %d\n", i); if (evaluate_condition(condition_set[i]) != 0) return THD_ERROR; } return THD_SUCCESS; } int cthd_gddv::evaluate_conditions() { int target = -1; for (int i = 0; i < (int) conditions.size(); i++) { thd_log_debug("evaluate condition set %d\n", i); if (evaluate_condition_set(conditions[i]) == THD_SUCCESS) { target = conditions[i][0].target; thd_log_debug("Condition Set matched:%d target:%d\n", i, target); current_target_matched = target; break; } } return target; } struct psvt* cthd_gddv::find_psvt(const std::string& name) { for (int i = 0; i < (int) psvts.size(); i++) { if (!thd_strcasecmp_n(psvts[i].name.c_str(), name.c_str())) { return &psvts[i]; } } return nullptr; } struct itmt* cthd_gddv::find_itmt(const std::string& name) { for (int i = 0; i < (int) itmts.size(); i++) { int matched; if (itmts[i].version == 3) { matched = thd_engine->search_idsp("4215267F-F429-4776-9D84-D6C5992848A4"); if (matched != THD_SUCCESS) { thd_log_info("Found version == 3; but no IDSP support\n"); continue; } } else if (itmts[i].version < 3) { matched = thd_engine->search_idsp("6BD40D2D-98AA-A44B-1A92-D22BDE3117F1"); if (matched != THD_SUCCESS) { thd_log_info("Found version < 3; but no IDSP support\n"); continue; } } if (!thd_strcasecmp_n(itmts[i].name.c_str(), name.c_str())) { return &itmts[i]; } } return nullptr; } #define MAX_PL_LIMIT 500000 // in mW int cthd_gddv::find_agressive_target() { int max_pl1_max = 0; int max_target_id = -1; for (int i = 0; i < (int) targets.size(); i++) { if (targets[i].code != "PL1MAX" && targets[i].code != "PL1PowerLimit") continue; // Use utility function for validated parsing // Value will be multiplied by 1000 in set_adaptive_target() int argument; if (parse_int_value(targets[i].argument, &argument, 0, MAX_PL_LIMIT) != 0) { thd_log_warn("Invalid power limit for target:%s value:%s\n", targets[i].code.c_str(), targets[i].argument.c_str()); continue; } thd_log_info("target:%s %d\n", targets[i].code.c_str(), argument); if (max_pl1_max < argument) { max_pl1_max = argument; max_target_id = i; } } return max_target_id; } #ifndef ANDROID void cthd_gddv::update_power_slider() { g_autoptr(GVariant) active_profile_v = nullptr; active_profile_v = g_dbus_proxy_get_cached_property (power_profiles_daemon, "ActiveProfile"); if (active_profile_v && g_variant_is_of_type (active_profile_v, G_VARIANT_TYPE_STRING)) { const char *active_profile = g_variant_get_string (active_profile_v, nullptr); if (thd_strcmp_n(active_profile, "power-saver") == 0) power_slider = 25; /* battery saver */ else if (thd_strcmp_n(active_profile, "balanced") == 0) power_slider = 75; /* better performance */ else if (thd_strcmp_n(active_profile, "performance") == 0) power_slider = 100; /* best performance */ else power_slider = 75; } else { power_slider = 75; } thd_log_info("Power slider is now set to %d\n", power_slider); } static void power_profiles_changed_cb(cthd_gddv *gddv) { gddv->update_power_slider(); } #endif #ifndef ANDROID static int is_event_device(const struct dirent *dir) { return strncmp("event", dir->d_name, 5) == 0; } void cthd_gddv::setup_input_devices() { struct dirent **namelist = nullptr; int i, ndev, ret; ndev = scandir("/dev/input", &namelist, is_event_device, versionsort); if (ndev == -1) { thd_log_info("Didn't find input devices\n"); return; } for (i = 0; i < ndev; i++) { struct libevdev *dev = nullptr; char fname[267]; int fd = -1; snprintf(fname, sizeof(fname), "/dev/input/%s", namelist[i]->d_name); free(namelist[i]); fd = open(fname, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (fd < 0) continue; ret = libevdev_new_from_fd(fd, &dev); if (ret) { close(fd); continue; } if (!tablet_dev && libevdev_has_event_code(dev, EV_SW, SW_TABLET_MODE)) tablet_dev = dev; if (!lid_dev && libevdev_has_event_code(dev, EV_SW, SW_LID)) lid_dev = dev; if (lid_dev != dev && tablet_dev != dev) { libevdev_free(dev); close(fd); } } free(namelist); } #endif // From // https://elixir.bootlin.com/linux/v6.19.8/source/tools/pcmcia/crc32hash.c unsigned int cthd_gddv::crc32(char const *p, unsigned int len) { int i; unsigned int crc = 0; while (len--) { crc ^= *p++; for (i = 0; i < 8; i++) crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0); } return crc; } int cthd_gddv::format_dv_filename(std::stringstream& file_name) { std::string sys_vendor; std::ifstream product_sys_vendor("/sys/class/dmi/id/sys_vendor"); if (!product_sys_vendor || !getline(product_sys_vendor, sys_vendor)) { thd_log_info("Can't read sys_vendor\n"); return THD_ERROR; } std::string product_name; std::ifstream product_product_name("/sys/class/dmi/id/product_name"); if (!product_product_name || !getline(product_product_name, product_name)) { thd_log_info("Can't read product_name\n"); return THD_ERROR; } std::string product_family; std::ifstream product_product_family("/sys/class/dmi/id/product_family"); if (!product_product_family || !getline(product_product_family, product_family)) { thd_log_info("Can't read product_family\n"); return THD_ERROR; } std::string product_sku; std::ifstream product_product_sku("/sys/class/dmi/id/product_sku"); if (!product_product_sku || !getline(product_product_sku, product_sku)) { thd_log_info("Can't read product_sku\n"); return THD_ERROR; } //format: // dtt_data_vault_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin file_name << "/lib/firmware/intel/dtt/dtt_data_vault_" << crc32(sys_vendor.c_str(), sys_vendor.length()) << "_" << crc32(product_family.c_str(), product_family.length()) << "_" << crc32(product_name.c_str(), product_name.length()) << "_" << crc32(product_sku.c_str(), product_sku.length()) << ".bin"; thd_log_info("Look for File name:%s\n", file_name.str().c_str()); return THD_SUCCESS; } // Load a data_vault file from file system. // Two formats are supported: // data_vault.hex // from "od -x" output from sysfs folder for gddv_dump // data_vault.bin // Binary as is. // This is for test only and hence conditionally compiled // This file is stored at TDCONFDIR #define MAX_GDDV_FILE_SIZE (4 * 1024) std::unique_ptr cthd_gddv::gddv_load(size_t *size) { *size = 0; if (thd_engine->check_feature(DATA_VAULT_FS) == 0) { thd_log_debug("Data vault filesystem loading is not allowed by config\n"); return {}; } std::stringstream file_name_str; std::unique_ptr data_buffer(new char[MAX_GDDV_FILE_SIZE]); if (!data_buffer) { thd_log_error("Unable to allocate memory for GDDV file load"); return {}; } #ifdef GDDV_LOAD_FROM_FILE std::string dir_name = TDCONFDIR; ssize_t line_size; char *line_buffer = nullptr; size_t line_buffer_size = 0; size_t data_buffer_index = 0; FILE *fp; file_name_str << dir_name << "/data_vault.hex"; fp = fopen(file_name_str.str().c_str(), "r"); if (!fp) { *size = 0; return {}; } thd_log_debug("Found data_vault %s\n", file_name_str.str().c_str()); line_size = getline(&line_buffer, &line_buffer_size, fp); while (line_size >= 0) { char s[2] = " "; char *token; /* get the first token */ token = strtok(line_buffer, s); if (!token) { break; } while (token != NULL) { if (data_buffer_index >= MAX_GDDV_FILE_SIZE) break; token = strtok(NULL, s); if (token) { int byte; sscanf(token, "%x", &byte); data_buffer[data_buffer_index++] = byte & 0xff; data_buffer[data_buffer_index++] = (byte & 0xff00) >> 8; } } line_size = getline(&line_buffer, &line_buffer_size, fp); } free(line_buffer); fclose(fp); *size = data_buffer_index; return data_buffer; #endif if (format_dv_filename(file_name_str) == THD_ERROR) return {}; int fd = open(file_name_str.str().c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd < 0) { if (errno == ELOOP) { thd_log_warn("Config file %s is a symlink\n", file_name_str.str().c_str()); } else { thd_log_info("Could not open file %s: %s\n", file_name_str.str().c_str(), strerror(errno)); } return {}; } struct stat file_stat; if (fstat(fd, &file_stat) == -1) { thd_log_warn("Could not stat opened file %s: %s\n", file_name_str.str().c_str(), strerror(errno)); close(fd); return {}; } // Verify file is owned by root and not writable by group/others if (file_stat.st_uid != 0) { thd_log_info("Config file %s is not owned by root\n", file_name_str.str().c_str()); close(fd); return {}; } if (file_stat.st_mode & (S_IWGRP | S_IWOTH)) { thd_log_info("Config file %s is group, other writable\n", file_name_str.str().c_str()); close(fd); return {}; } // Verify it's a regular file (not device, FIFO, etc.) if (!S_ISREG(file_stat.st_mode)) { thd_log_warn("Config file %s is not a regular file\n", file_name_str.str().c_str()); close(fd); return {}; } size_t _size = file_stat.st_size; if (_size == 0 || _size > MAX_GDDV_FILE_SIZE) { thd_log_debug("GDDV data vault has invalid size: %zu\n", _size); close(fd); return {}; } thd_log_debug("Found data vault file %s of size %zu\n", file_name_str.str().c_str(), _size); // Read from the already-opened and validated file descriptor // Loop to handle partial reads (POSIX read() may return short reads) size_t total_read = 0; while (total_read < _size) { ssize_t n = ::read(fd, data_buffer.get() + total_read, _size - total_read); if (n < 0) { if (errno == EINTR) { continue; // Interrupted by signal, retry } thd_log_warn("Read error loading GDDV: %s\n", strerror(errno)); close(fd); return {}; } if (n == 0) { // EOF reached before reading full file thd_log_warn("Unexpected EOF loading GDDV, read %zu of %zu bytes\n", total_read, _size); close(fd); return {}; } total_read += n; } close(fd); *size = _size; return data_buffer; } int cthd_gddv::gddv_init(std::string& base_path) { csys_fs sysfs(""); size_t size; int3400_base_path = base_path; std::unique_ptr buf = gddv_load(&size); if (size > 0) { thd_log_info("Loading data vault from a file\n"); goto skip_load; } if (sysfs.read(int3400_base_path + "firmware_node/path", int3400_path) < 0) { thd_log_debug("Unable to locate INT3400 firmware path\n"); return THD_ERROR; } size = sysfs.size(int3400_base_path + "data_vault"); if (size == 0) { thd_log_debug("Unable to open GDDV data vault\n"); return THD_ERROR; } buf.reset(new char[size]); if (!buf) { thd_log_error("Unable to allocate memory for GDDV"); return THD_FATAL_ERROR; } if (sysfs.read(int3400_base_path + "data_vault", buf.get(), size) < int(size)) { thd_log_debug("Unable to read GDDV data vault\n"); return THD_FATAL_ERROR; } skip_load: try { if (parse_gddv(buf.get(), size, nullptr, 0)) { thd_log_debug("Unable to parse GDDV"); return THD_FATAL_ERROR; } merge_appc(); dump_ppcc(); dump_psvt(); dump_itmt(); dump_apat(); dump_apct(); dump_idsps(); dump_trips(); dump_vsct(); dump_vspt(); } catch (std::exception &e) { thd_log_warn("%s\n", e.what()); return THD_FATAL_ERROR; } #ifndef ANDROID setup_input_devices(); upower_client = up_client_new(); if (!upower_client) { thd_log_info("Unable to connect to upower\n"); /* But continue to work */ } g_autoptr(GDBusConnection) bus = nullptr; bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, nullptr, nullptr); if (bus) { power_profiles_daemon = g_dbus_proxy_new_sync (bus, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, nullptr, "net.hadess.PowerProfiles", "/net/hadess/PowerProfiles", "net.hadess.PowerProfiles", nullptr, nullptr); if (power_profiles_daemon) { g_signal_connect_swapped (power_profiles_daemon, "g-properties-changed", (GCallback) power_profiles_changed_cb, this); power_profiles_changed_cb(this); } else { thd_log_info("Could not setup DBus watch for power-profiles-daemon"); } } #endif return THD_SUCCESS; } void cthd_gddv::gddv_free(void) { destroy_dynamic_sources(); } thermal_daemon-2.5.12/src/thd_gddv.h000066400000000000000000000172721521301715300173120ustar00rootroot00000000000000/* * cthd_engine_adaptive.cpp: Adaptive thermal engine * * Copyright (C) 2013 Intel Corporation. All rights reserved. * Copyright 2020 Google LLC * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name Matthew Garrett * */ #ifndef THD_GDDV_H_ #define THD_GDDV_H_ #ifndef ANDROID #include #include #endif #include #include "thd_engine.h" #include "thd_trt_art_reader.h" #define DECI_KELVIN_TO_CELSIUS(t) ({ \ int _t = (t); \ ((_t-2732 >= 0) ? (_t-2732+5)/10 : (_t-2732-5)/10); \ }) enum adaptive_condition : uint32_t { // NOLINT(performance-enum-size) Default = 0x01, Orientation, Proximity, Motion, Dock, Workload, Cooling_mode, Power_source, Aggregate_power_percentage, Lid_state, Platform_type, Platform_SKU, Utilisation, TDP, Duty_cycle, Power, Temperature, Display_orientation, Oem0, Oem1, Oem2, Oem3, Oem4, Oem5, PMAX, PSRC, ARTG, CTYP, PROP, Unk1, Unk2, Battery_state, Battery_rate, Battery_remaining, Battery_voltage, PBSS, Battery_cycles, Battery_last_full, Power_personality, Battery_design_capacity, Screen_state, AVOL, ACUR, AP01, AP02, AP10, Time, Temperature_without_hysteresis, Mixed_reality, User_presence, RBHF, VBNL, CMPP, Battery_percentage, Battery_count, Power_slider, OS_type = 86 }; enum adaptive_comparison : uint8_t { ADAPTIVE_EQUAL = 0x01, ADAPTIVE_LESSER_OR_EQUAL, ADAPTIVE_GREATER_OR_EQUAL, ADAPTIVE_NOT_EQUAL, }; enum adaptive_operation : uint8_t { AND = 0x01, FOR }; struct psv { std::string name; std::string source; std::string target; int priority; int sample_period; int temp; int domain; int control_knob; std::string limit; int step_size; int limit_coeff; int unlimit_coeff; }; struct condition { uint64_t condition; std::string device; uint64_t comparison; int argument; enum adaptive_operation operation; enum adaptive_comparison time_comparison; time_t time; int target; int state; time_t state_entry_time; int ignore_condition; }; struct custom_condition { enum adaptive_condition condition; std::string name; std::string participant; int domain; int type; }; struct psvt { std::string name; std::vector psvs; }; struct itmt_entry { std::string target; int trip_point; std::string pl1_min; std::string pl1_max; std::string unused; }; struct trt_entry { std::string source; std::string dest; int priority; int sample_rate; int resd0; int resd1; int resd2; int resd3; }; struct itmt { int version; std::string name; std::vector itmt_entries; }; struct trippoint { std::string name; std::string type_str; trip_point_type_t type; int temp; }; struct vsct_entry { std::string target; int domain_type; int coeff_type; int coeff; int operation; int alpha; int trigger_point; }; struct vspt_entry { int virtual_temp; int sample_period; }; class cthd_gddv { private: std::vector ppccs; std::vector custom_conditions; std::vector rel_list; std::vector psvts; std::vector itmts; std::vector idsps; std::vector trippoints; std::string int3400_path; #ifndef ANDROID UpClient *upower_client; GDBusProxy *power_profiles_daemon; struct libevdev *tablet_dev; struct libevdev *lid_dev; #endif std::string int3400_base_path; int power_slider; int current_target_matched; /* * Length of the buffer currently being parsed by get_type/ * get_uint64/get_string. Each parse_* helper assigns this from its * own `len` parameter on entry so that bounds checks in the readers * cannot walk off the end of attacker-controlled buffers. */ int gddv_cur_buf_len; void destroy_dynamic_sources(); int get_type(char *object, int *offset); uint64_t get_uint64(char *object, int *offset); char* get_string(char *object, int *offset); char* get_string_obj(char *object, int *offset); unsigned int crc32(char const *p, unsigned int len); int format_dv_filename(std::stringstream& filename); int merge_custom(struct custom_condition *custom, struct condition *condition); int merge_appc(void); int parse_appc(char *appc, int len); int parse_apat(char *apat, int len); int parse_apct(char *apct, int len); int parse_ppcc(char *name, char *ppcc, int len); int parse_psvt(char *name, char *psvt, int len); int parse_itmt(char *name, char *itmt, int len); int parse_itmt3(char *name, char *itmt, unsigned int len); int parse_vsct(char *name, char *psvt, int len); int parse_vspt(char *name, char *psvt, int len); int parse_trt(char *trt, int len); void parse_idsp(char *name, char *idsp, int len); void parse_trip_point(char *name, char *type, char *val, int len); int handle_compressed_gddv(char *buf, int size); int parse_gddv_key(char *buf, int size, int *end_offset); int parse_gddv(char *buf, int size, int *end_offset, int depth = 0); int verify_condition(const struct condition& condition); int compare_condition(const struct condition& condition, int value); int compare_time(const struct condition& condition); int evaluate_oem_condition(const struct condition& condition); int evaluate_temperature_condition(struct condition condition); int evaluate_ac_condition(const struct condition& condition); int evaluate_lid_condition(const struct condition& condition); int evaluate_workload_condition(const struct condition& condition); int evaluate_platform_type_condition(const struct condition& condition); int evaluate_power_slider_condition(const struct condition& condition); int evaluate_os_type_condition(const struct condition& condition); int evaluate_condition(struct condition& condition); int evaluate_condition_set(std::vector& condition_set); void exec_fallback_target(int target); void dump_apat(); void dump_apct(); void dump_ppcc(); void dump_psvt(); void dump_itmt(); void dump_idsps(); void dump_trips(); void dump_vsct(); void dump_vspt(); #ifndef ANDROID void setup_input_devices(); #endif int get_trip_temp(const std::string& name, trip_point_type_t type); public: #ifndef ANDROID cthd_gddv() : upower_client( nullptr), power_profiles_daemon(nullptr), tablet_dev(nullptr), lid_dev(nullptr), int3400_base_path(""), power_slider(75), current_target_matched(-1), gddv_cur_buf_len(0) { } #else cthd_gddv() : int3400_base_path(""), current_condition_set(0xffff) { } #endif ~cthd_gddv(); std::vector> conditions; std::vector targets; std::string vscts_name; std::vector vscts; std::vector vspts; ppcc_t* get_ppcc_param(const std::string& name); int gddv_init(std::string& base_path); std::unique_ptr gddv_load(size_t *size); void gddv_free(void); int verify_conditions(); int evaluate_conditions(); void update_power_slider(); int find_agressive_target(); struct psvt* find_psvt(const std::string& name); struct itmt* find_itmt(const std::string& name); struct psvt* find_def_psvt(); int search_idsp(const std::string& name); }; #endif /* THD_GDDV_H_ */ thermal_daemon-2.5.12/src/thd_int3400.cpp000066400000000000000000000062011521301715300200100ustar00rootroot00000000000000/* * thd_int3400.cpp: Load and check INT3400 uuids for match * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_int3400.h" #include "thd_cdev_gen_sysfs.h" #include #include #include #include cthd_INT3400::cthd_INT3400(std::string _uuid) : uuid(std::move(_uuid)), base_path("") { csys_fs cdev_sysfs(""); const char *int3400_base_path = "/sys/bus/platform/drivers/int3400 thermal"; csys_fs sysfs (""); if (sysfs.exists (int3400_base_path)) { DIR *dir; struct dirent *entry; if ((dir = opendir (int3400_base_path)) != nullptr) { while ((entry = readdir (dir)) != nullptr) { if (!strncmp (entry->d_name, "INT", strlen ("INT"))) { base_path = "/sys/bus/platform/devices/"; base_path += entry->d_name; base_path += "/"; thd_log_info("INT3400 Base path is %s\n", base_path.c_str()); closedir(dir); return; } } closedir(dir); } } } int cthd_INT3400::match_supported_uuid() { if (base_path == "") return THD_ERROR; std::string filename = base_path + "uuids/" + "available_uuids"; std::ifstream ifs(filename.c_str(), std::ifstream::in); if (ifs.good()) { std::string line; while (std::getline(ifs, line)) { thd_log_debug("uuid: %s\n", line.c_str()); if (line == uuid) return THD_SUCCESS; } ifs.close(); } return THD_ERROR; } void cthd_INT3400::set_default_uuid(void) { if (set_policy_osc() == THD_SUCCESS) return; if (base_path == "") return; std::string filename = base_path + "uuids/" + "current_uuid"; std::ofstream ofs(filename.c_str(), std::ofstream::out); if (ofs.good()) { thd_log_info("Set Default UUID: %s\n", uuid.c_str()); ofs << uuid; } } int cthd_INT3400::set_policy_osc(void) { if (base_path == "") return THD_ERROR; std::string filename = base_path + "uuids/" + "available_uuids"; std::ifstream ifs(filename.c_str(), std::ifstream::in); if (ifs.good()) { std::string line; if (std::getline(ifs, line)) { thd_log_debug("available uuids: %s\n", line.c_str()); if (line == "UNKNOWN") { std::string _filename = base_path + "uuids/" + "current_uuid"; std::ofstream ofs(_filename.c_str(), std::ofstream::out); if (ofs.good()) { std::string _uuid = "42A441D6-AE6A-462b-A84B-4A8CE79027D3"; thd_log_info("Set Default UUID: %s\n", _uuid.c_str()); ofs << _uuid; } return THD_SUCCESS; } } ifs.close(); } return THD_ERROR; } thermal_daemon-2.5.12/src/thd_int3400.h000066400000000000000000000023201521301715300174530ustar00rootroot00000000000000/* * thd_int3400.h: Load and check INT3400 uuids for match * * Copyright (C) 2018 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef SRC_THD_INT3400_UUID_H_ #define SRC_THD_INT3400_UUID_H_ #include "thd_common.h" #include #include class cthd_INT3400 { private: std::string uuid; std::string base_path; int set_policy_osc(void); public: cthd_INT3400(std::string _uuid); int match_supported_uuid(void); void set_default_uuid(void); }; #endif /* SRC_THD_INT3400_UUID_H_ */ thermal_daemon-2.5.12/src/thd_kobj_uevent.cpp000066400000000000000000000045311521301715300212260ustar00rootroot00000000000000/* * thd_kobj_uevent.cpp: Get notification from kobj uevent * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_kobj_uevent.h" #include "thd_common.h" int cthd_kobj_uevent::kobj_uevent_open() { memset(&nls, 0, sizeof(struct sockaddr_nl)); nls.nl_family = AF_NETLINK; nls.nl_pid = getpid(); nls.nl_groups = -1; fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if (fd < 0) return fd; if (bind(fd, (struct sockaddr*) &nls, sizeof(struct sockaddr_nl))) { thd_log_warn("kob_uevent bin failed\n"); close(fd); return -1; } return fd; } void cthd_kobj_uevent::kobj_uevent_close() { close(fd); } bool cthd_kobj_uevent::check_for_event() { ssize_t i = 0; ssize_t len; const char *dev_path = "DEVPATH="; unsigned int dev_path_len = strlen(dev_path); char buffer[max_buffer_size]; len = recv(fd, buffer, sizeof(buffer) - 1, MSG_DONTWAIT); if (len <= 0) return false; buffer[len] = '\0'; while (i < len) { // Use strnlen with len - i + 1 to include the appended '\0' at buffer[len] // This ensures the last field is processed even if it reaches position len-1 size_t field_len = strnlen(buffer + i, (size_t)(len - i + 1)); if (i + field_len > (size_t)len) { break; // Malformed uevent data } if (field_len > dev_path_len && !strncmp(buffer + i, dev_path, dev_path_len)) { if (!strncmp(buffer + i + dev_path_len, device_path, strlen(device_path))) { return true; } } i += field_len + 1; } return false; } void cthd_kobj_uevent::register_dev_path(char *path) { strncpy(device_path, path, max_buffer_size); device_path[max_buffer_size - 1] = '\0'; } thermal_daemon-2.5.12/src/thd_kobj_uevent.h000066400000000000000000000027211521301715300206720ustar00rootroot00000000000000/* * thd_kobj_uevent.h: Get notification from kobj uevent * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_KOBJ_UEVENT_H_ #define THD_KOBJ_UEVENT_H_ #include #include #include #include #include #include #include #include #include class cthd_kobj_uevent { private: static constexpr int max_buffer_size = 512; struct sockaddr_nl nls; int fd; char device_path[max_buffer_size]; public: cthd_kobj_uevent() { fd = 0; memset(&nls, 0, sizeof(nls)); device_path[0] = '\0'; } int kobj_uevent_open(); void kobj_uevent_close(); void register_dev_path(char *path); bool check_for_event(); } ; #endif thermal_daemon-2.5.12/src/thd_lzma_dec.cpp000066400000000000000000000111741521301715300204720ustar00rootroot00000000000000// From ESIF/Products/ESIF_CMP/Sources esif_cmp.c // Remove white spaces #include #include //#include #include #include #include "LzmaDec.h" #include "thd_common.h" // Duplicate ESIF_CMP/Sources/Alloc.c MyAlloc void *MyAlloc(size_t size) { if (size == 0) return nullptr; #ifdef _SZ_ALLOC_DEBUG { void *p = malloc(size); // PRINT_ALLOC("Alloc ", g_allocCount, size, p); return p; } #else return malloc(size); #endif } void MyFree(void *address) { //PRINT_FREE("Free ", g_allocCount, address); free(address); } #define UNUSED_VAR(x) (void)x; static void *SzAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p); return MyAlloc(size); } static void SzFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p); MyFree(address); } const ISzAlloc g_Alloc = { SzAlloc, SzFree }; // Standard LZMA File Header #define LZMA_PROPS_SIZE 5 // [XX YY YY YY YY] Where XX = Encoded -lc -lp -pb options, YY = Encoded -d option #pragma pack(push, 1) struct LzmaHeader { unsigned char properties[LZMA_PROPS_SIZE];// encoded LZMA_PROPS options unsigned long long original_size; // original uncompressed data size }; #pragma pack(pop) #define ENCODED_SIGNATURE {'\x5D','\x00'} /* Hardcoded LZMA Compression Property Values (and their LZMA_SDK v18.01 lzma.exe command line equivalents) * Items marked with "##" should never be changed since they affect the 5-byte LZMA Properties Header Signature * The following parameters correspond to to the ESIF_COMPRESS_SIGNATURE defined in esif_sdk_iface_compress.h, * which always maps to [5D 00 XX XX XX] for -lc3 -lp0 -pb2 and -d12 to -d27 lzma.exe options. */ #define LZMA_PROPS_LEVEL 9 // Compression Level [-a1 = 9] #define LZMA_PROPS_DICTSIZE (1 << 24) // Dictionary Size [-d24] ## #define LZMA_PROPS_LITCTXBITS 3 // Literal Context Bits [-lc3] ## #define LZMA_PROPS_LITPOSBITS 0 // Literal Pos Bits [-lp0] ## #define LZMA_PROPS_NUMPOSBITS 2 // Number of Pos Bits [-pb2] ## #define LZMA_PROPS_FASTBYTES 128 // Number of Fast Bytes [-fb128] #define LZMA_PROPS_THREADS 1 // Number of Threads [-mt1] #define LZMA_PADDING_MINSIZE 256 // Minimum Padding Bytes for Compression Buffer #define LZMA_PADDING_PERCENT 0.05 // Percent Padding Bytes for Compression Buffer (0.0-1.0) #define LZMA_MAX_COMPRESSED_SIZE (((size_t)(-1) >> 1) - 1) int lzma_decompress( unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen ) { int rc = THD_ERROR; struct LzmaHeader *header = nullptr; // nullptr dest = Return Required Buffer Size if (dest == nullptr && destLen && src && srcLen > sizeof(*header)) { header = (struct LzmaHeader *)src; unsigned char encoded_signature[] = ENCODED_SIGNATURE; // Compute Original Decompressed Size if valid Header Properties if ((memcmp(header->properties, encoded_signature, sizeof(encoded_signature)) == 0) && (header->original_size > 0 && header->original_size != (unsigned long long)(-1))) { *destLen = (size_t)header->original_size; rc = 0; } else { rc = THD_ERROR; } } else if (dest && destLen && src && srcLen > sizeof(*header)) { header = (struct LzmaHeader *)src; size_t lzmaSrcLen = srcLen - sizeof(*header); ELzmaStatus status; rc = LzmaDecode( dest, destLen, src + sizeof(*header), &lzmaSrcLen, src, sizeof(*header), LZMA_FINISH_ANY, &status, &g_Alloc); // Validate Data not Truncated since LzmaDecode returns OK if destLen too small if (*destLen < header->original_size) { rc = THD_ERROR; } // Bounds Check if (*destLen > LZMA_MAX_COMPRESSED_SIZE) { *destLen = LZMA_MAX_COMPRESSED_SIZE; rc = THD_ERROR; } } return rc; } thermal_daemon-2.5.12/src/thd_lzma_dec.h000066400000000000000000000003411521301715300201310ustar00rootroot00000000000000#include #include #include #include int lzma_decompress( unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen ); thermal_daemon-2.5.12/src/thd_parse.cpp000066400000000000000000001255461521301715300200370ustar00rootroot00000000000000/* * thd_engine.cpp: thermal engine class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ /* Parser to parse thermal configuration file. This uses libxml2 API. * */ #include "thd_parse.h" #include #include #include #include "thd_sys_fs.h" #include "thd_trt_art_reader.h" #include "thd_util.h" #define DEBUG_PARSER_PRINT(x,...) static constexpr int thd_xml_parse_options = XML_PARSE_NONET | XML_PARSE_NOERROR | XML_PARSE_NOWARNING; void cthd_parse::string_trim(std::string &str) { std::string chars = "\n \t\r"; for (unsigned int i = 0; i < chars.length(); ++i) { str.erase(std::remove(str.begin(), str.end(), chars[i]), str.end()); } } char *cthd_parse::char_trim(char *str) { int i; if (!str) return nullptr; if (str[0] == '\0') return str; while (isspace(*str)) str++; for (i = strlen(str) - 1; (isspace(str[i])); i--) ; str[i + 1] = '\0'; return str; } // Wrapper methods for XML parsing - delegate to utility functions int cthd_parse::parse_int_value(const char *str, int *result, int min_val, int max_val) { if (!str || !result) { return THD_ERROR; } return ::parse_int_value(std::string(str), result, min_val, max_val) == 0 ? THD_SUCCESS : THD_ERROR; } int cthd_parse::parse_double_value(const char *str, double *result, double min_val, double max_val) { if (!str || !result) { return THD_ERROR; } return ::parse_double_value(std::string(str), result, min_val, max_val) == 0 ? THD_SUCCESS : THD_ERROR; } cthd_parse::cthd_parse() : matched_thermal_info_index(-1), doc(nullptr), root_element(nullptr), auto_config( 0) { std::string name_conf = TDCONFDIR; std::string name_run = TDRUNDIR; #ifdef ANDROID filename = name_run + "/" + "thermal-conf.xml"; #else filename = name_conf + "/" + "thermal-conf.xml"; #endif filename_auto = name_run + "/" + "thermal-conf.xml.auto"; filename_auto_conf = name_conf + "/" + "thermal-conf.xml.auto"; } int cthd_parse::parser_init(const std::string& config_file) { cthd_acpi_rel rel; const char *xml_config_file; int ret; if (config_file.empty()) { std::ifstream conf_auto(filename_auto_conf.c_str()); if (conf_auto.is_open()) { thd_log_msg("Using generated %s\n", filename_auto_conf.c_str()); xml_config_file = filename_auto_conf.c_str(); auto_config = 1; } else { ret = rel.generate_conf(filename_auto); if (!ret) { thd_log_msg("Using generated %s\n", filename_auto.c_str()); xml_config_file = filename_auto.c_str(); auto_config = 1; } else { xml_config_file = filename.c_str(); } } } else { xml_config_file = config_file.c_str(); } /* We have not tested existence yet in this case. */ if (!auto_config) { std::ifstream conf(xml_config_file); if (!conf.is_open()) { thd_log_msg("Config file %s does not exist\n", xml_config_file); return THD_ERROR; } } int fd = open_validated_xml_file(xml_config_file); if (fd < 0) { if (errno == ELOOP) { thd_log_warn("Config file %s is a symlink\n", xml_config_file); } else { thd_log_info("Could not open file %s: %s\n", xml_config_file, strerror(errno)); } return THD_ERROR; } thd_log_msg("Using config file %s\n", xml_config_file); // Read file using already-opened and validated file descriptor doc = xmlReadFd(fd, xml_config_file, nullptr, thd_xml_parse_options); close(fd); if (doc == nullptr) { thd_log_warn("error: could not parse file %s\n", xml_config_file); return THD_ERROR; } if (doc->intSubset != nullptr || doc->extSubset != nullptr) { thd_log_warn("Config file %s must not contain a DTD\n", xml_config_file); xmlFreeDoc(doc); doc = nullptr; return THD_ERROR; } root_element = xmlDocGetRootElement(doc); if (root_element == nullptr) { thd_log_warn("error: could not get root element\n"); xmlFreeDoc(doc); return THD_ERROR; } return THD_SUCCESS; } int cthd_parse::parse_dependency_values(xmlNode * a_node, xmlDoc *doc, trip_cdev_depend_t *dependency) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char*) cur_node->name, "CoolingDeviceType")) { dependency->cdev.assign(char_trim(tmp_value)); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "CoolingDeviceState")) { dependency->state.assign(char_trim(tmp_value)); } dependency->dependency = 1; xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_parse::parse_new_trip_cdev(xmlNode * a_node, xmlDoc *doc, trip_cdev_t *trip_cdev) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char *) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char*) cur_node->name, "type")) { trip_cdev->type.assign((const char*) tmp_value); string_trim(trip_cdev->type); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "influence")) { int val; if (parse_int_value(tmp_value, &val, 0, 100) == THD_SUCCESS) { trip_cdev->influence = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "SamplingPeriod")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { trip_cdev->sampling_period = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "TargetState")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { trip_cdev->target_state = val; trip_cdev->target_state_valid = 1; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "TargetMinState")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { trip_cdev->target_min_state = val; trip_cdev->min_max_valid = 1; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "TargetMaxState")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { trip_cdev->target_max_state = val; trip_cdev->min_max_valid = 1; } } else if(!thd_strcasecmp_n((const char*) cur_node->name, "PidControl")) { pid_control_t pid_params; parse_pid_values(cur_node->children, doc, &pid_params); trip_cdev->pid_param.kp = pid_params.Kp; trip_cdev->pid_param.ki = pid_params.Ki; trip_cdev->pid_param.kd = pid_params.Kd; trip_cdev->pid_param.valid = 1; } xmlFree(tmp_value); } } } return THD_SUCCESS; } #define MIN_TEMP_LIMIT 0 #define MAX_TEMP_LIMIT 200000 int cthd_parse::parse_new_trip_point(xmlNode * a_node, xmlDoc *doc, trip_point_t *trip_pt) { xmlNode *cur_node = nullptr; char *tmp_value; trip_cdev_t trip_cdev; trip_pt->temperature = 0; trip_pt->trip_pt_type = ACTIVE; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char *) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (!tmp_value) { continue; } if (!thd_strcasecmp_n((const char*) cur_node->name, "Temperature")) { int val; if (parse_int_value(tmp_value, &val, MIN_TEMP_LIMIT, MAX_TEMP_LIMIT) == THD_SUCCESS) { trip_pt->temperature = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Power")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { trip_pt->temperature = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Hyst")) { int val; if (parse_int_value(tmp_value, &val, 0, 50000) == THD_SUCCESS) { trip_pt->hyst = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "CoolingDevice")) { trip_cdev.influence = 0; trip_cdev.sampling_period = 0; trip_cdev.target_state_valid = 0; trip_cdev.target_state = 0; trip_cdev.min_max_valid = 0; trip_cdev.target_min_state = 0; trip_cdev.target_max_state = 0; trip_cdev.type.clear(); trip_cdev.pid_param.valid = 0; trip_cdev.pid_param.kp = 0.0; trip_cdev.pid_param.ki = 0.0; trip_cdev.pid_param.kd = 0.0; parse_new_trip_cdev(cur_node->children, doc, &trip_cdev); trip_pt->cdev_trips.push_back(trip_cdev); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "SensorType")) { trip_pt->sensor_type.assign(tmp_value); string_trim(trip_pt->sensor_type); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "type")) { char *type_val = char_trim(tmp_value); if (type_val && !thd_strcasecmp_n(type_val, "active")) trip_pt->trip_pt_type = ACTIVE; else if (type_val && !thd_strcasecmp_n(type_val, "passive")) trip_pt->trip_pt_type = PASSIVE; else if (type_val && !thd_strcasecmp_n(type_val, "critical")) trip_pt->trip_pt_type = CRITICAL; else if (type_val && !thd_strcasecmp_n(type_val, "hot")) trip_pt->trip_pt_type = HOT; else if (type_val && !thd_strcasecmp_n(type_val, "max")) trip_pt->trip_pt_type = MAX; } else if (!thd_strcasecmp_n((const char*) cur_node->name, "ControlType")) { char *ctrl_val = char_trim(tmp_value); if (ctrl_val && !thd_strcasecmp_n(ctrl_val, "SEQUENTIAL")) trip_pt->control_type = SEQUENTIAL; else trip_pt->control_type = PARALLEL; } else if (!thd_strcasecmp_n((const char*) cur_node->name, "DependsOn")) { parse_dependency_values(cur_node->children, doc, &trip_pt->dependency); } xmlFree(tmp_value); } } return THD_SUCCESS; } int cthd_parse::parse_trip_points(xmlNode * a_node, xmlDoc *doc, thermal_zone_t *info_ptr) { xmlNode *cur_node = nullptr; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); if (!thd_strcasecmp_n((const char*) cur_node->name, "TripPoint")) { trip_point_t trip_pt; trip_pt.hyst = trip_pt.temperature = 0; trip_pt.trip_pt_type = PASSIVE; trip_pt.control_type = PARALLEL; trip_pt.influence = 100; trip_pt.sensor_type.clear(); trip_pt.dependency.dependency = 0; if (parse_new_trip_point(cur_node->children, doc, &trip_pt) == THD_SUCCESS) info_ptr->trip_pts.push_back(std::move(trip_pt)); } } } return THD_SUCCESS; } int cthd_parse::parse_pid_values(xmlNode * a_node, xmlDoc *doc, pid_control_t *pid_ptr) { xmlNode *cur_node = nullptr; char *tmp_value; pid_ptr->Kp = 0.0005; pid_ptr->Ki = 0.0001; pid_ptr->Kd = 0.0001; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char*) cur_node->name, "Kp")) { double val; if (parse_double_value(tmp_value, &val, 0.0, 100.0) == THD_SUCCESS) { pid_ptr->Kp = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Kd")) { double val; if (parse_double_value(tmp_value, &val, 0.0, 100.0) == THD_SUCCESS) { pid_ptr->Kd = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Ki")) { double val; if (parse_double_value(tmp_value, &val, 0.0, 100.0) == THD_SUCCESS) { pid_ptr->Ki = val; } } xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_parse::parse_new_zone(xmlNode * a_node, xmlDoc *doc, thermal_zone_t *info_ptr) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (!thd_strcasecmp_n((const char*) cur_node->name, "TripPoints")) { parse_trip_points(cur_node->children, doc, info_ptr); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Type")) { info_ptr->type.assign((const char*) tmp_value); string_trim(info_ptr->type); } if (tmp_value) xmlFree(tmp_value); } } return THD_SUCCESS; } int cthd_parse::parse_new_cooling_dev(xmlNode * a_node, xmlDoc *doc, cooling_dev_t *cdev) { xmlNode *cur_node = nullptr; char *tmp_value; cdev->max_state = cdev->min_state = 0; cdev->mask = 0; cdev->inc_dec_step = 1; cdev->read_back = true; cdev->auto_down_control = false; cdev->status = false; cdev->pid_enable = false; cdev->unit_val = ABSOULUTE_VALUE; cdev->debounce_interval = 0; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char *) cur_node->name, "Index")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { cdev->index = val; } } else if (!thd_strcasecmp_n((const char *) cur_node->name, "Type")) { cdev->type_string.assign((const char*) tmp_value); string_trim(cdev->type_string); } else if (!thd_strcasecmp_n((const char *) cur_node->name, "Path")) { cdev->mask |= CDEV_DEF_BIT_PATH; cdev->path_str.assign((const char*) tmp_value); string_trim(cdev->path_str); } else if (!thd_strcasecmp_n((const char *) cur_node->name, "MinState")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { cdev->min_state = val; cdev->mask |= CDEV_DEF_BIT_MIN_STATE; } } else if (!thd_strcasecmp_n((const char *) cur_node->name, "MaxState")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { cdev->max_state = val; cdev->mask |= CDEV_DEF_BIT_MAX_STATE; } } else if (!thd_strcasecmp_n((const char *) cur_node->name, "IncDecStep")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { cdev->inc_dec_step = val; cdev->mask |= CDEV_DEF_BIT_STEP; } } else if (!thd_strcasecmp_n((const char *) cur_node->name, "ReadBack")) { int val; if (parse_int_value(tmp_value, &val, 0, 1) == THD_SUCCESS) { cdev->read_back = val; cdev->mask |= CDEV_DEF_BIT_READ_BACK; } } else if (!thd_strcasecmp_n((const char *) cur_node->name, "DebouncePeriod")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { cdev->debounce_interval = val; cdev->mask |= CDEV_DEF_BIT_DEBOUNCE_VAL; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "PidControl")) { if (parse_pid_values(cur_node->children, doc, &cdev->pid) != THD_SUCCESS) { cdev->pid_enable = false; } else { cdev->mask |= CDEV_DEF_BIT_PID_PARAMS; cdev->pid_enable = true; } } else if (!thd_strcasecmp_n((const char *) cur_node->name, "AutoOffMode")) { int val; if (parse_int_value(tmp_value, &val, 0, 1) == THD_SUCCESS) { cdev->auto_down_control = (val != 0); cdev->mask |= CDEV_DEF_BIT_AUTO_DOWN; } } else if (!thd_strcasecmp_n((const char *) cur_node->name, "WritePrefix")){ cdev->mask |= CDEV_DEF_BIT_WRITE_PREFIX; cdev->write_prefix.assign((const char*) tmp_value); } xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_parse::parse_cooling_devs(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr) { xmlNode *cur_node = nullptr; cooling_dev_t cdev; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); if (!thd_strcasecmp_n((const char*) cur_node->name, "CoolingDevice")) { cdev.index = cdev.max_state = cdev.min_state = 0; cdev.inc_dec_step = 1; cdev.auto_down_control = false; cdev.path_str.clear(); cdev.type_string.clear(); parse_new_cooling_dev(cur_node->children, doc, &cdev); info_ptr->cooling_devs.push_back(cdev); } } } return THD_SUCCESS; } int cthd_parse::parse_thermal_zones(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr) { xmlNode *cur_node = nullptr; thermal_zone_t zone; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); if (!thd_strcasecmp_n((const char*) cur_node->name, "ThermalZone")) { zone.trip_pts.clear(); parse_new_zone(cur_node->children, doc, &zone); info_ptr->zones.push_back(zone); } } } return THD_SUCCESS; } int cthd_parse::parse_new_sensor_link(xmlNode * a_node, xmlDoc *doc, thermal_sensor_link_t *info_ptr) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char*) cur_node->name, "SensorType")) { info_ptr->name.assign(tmp_value); string_trim(info_ptr->name); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Multiplier")) { double val; if (parse_double_value(tmp_value, &val, -1000.0, 1000.0) == THD_SUCCESS) { info_ptr->multiplier = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Offset")) { double val; if (parse_double_value(tmp_value, &val, -1000.0, 1000.0) == THD_SUCCESS) { info_ptr->offset = val; } } xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_parse::parse_new_virtual_sensor_target(xmlNode * a_node, xmlDoc *doc, thermal_sensor_target_t *info_ptr) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char*) cur_node->name, "SensorType")) { info_ptr->name.assign(tmp_value); string_trim(info_ptr->name); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Coeff")) { double val; if (parse_double_value(tmp_value, &val, -1000.0, 1000.0) == THD_SUCCESS) { info_ptr->coeff = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Offset")) { double val; if (parse_double_value(tmp_value, &val, -1000.0, 1000.0) == THD_SUCCESS) { info_ptr->offset = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "PowerSensor")) { int val; if (parse_int_value(tmp_value, &val, 0, 1) == THD_SUCCESS) { info_ptr->power_sensor = val; } } xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_parse::parse_new_virtual_sensor_polling(xmlNode * a_node, xmlDoc *doc, thermal_sensor_polling_t *info_ptr) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char*) cur_node->name, "VirtualTemp")) { int val; if (parse_int_value(tmp_value, &val, 0, MAX_TEMP_LIMIT) == THD_SUCCESS) { info_ptr->virtual_temp = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "SamplePeriod")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { info_ptr->sample_period = val; } } xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_parse::parse_new_sensor(xmlNode * a_node, xmlDoc *doc, thermal_sensor_t *info_ptr) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char*) cur_node->name, "Type")) { info_ptr->name.assign(tmp_value); string_trim(info_ptr->name); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Path")) { info_ptr->mask |= SENSOR_DEF_BIT_PATH; info_ptr->path.assign(tmp_value); string_trim(info_ptr->path); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "AsyncCapable")) { int val; if (parse_int_value(tmp_value, &val, 0, 1) == THD_SUCCESS) { info_ptr->async_capable = val; info_ptr->mask |= SENSOR_DEF_BIT_ASYNC_CAPABLE; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Virtual")) { int val; if (parse_int_value(tmp_value, &val, 0, 1) == THD_SUCCESS) { info_ptr->virtual_sensor = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "SensorLink")) { parse_new_sensor_link(cur_node->children, doc, &info_ptr->sensor_link); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "LinkSensor")) { thermal_sensor_target_t link_sensor; link_sensor.name.clear(); link_sensor.coeff = 0.0; link_sensor.offset = 0.0; link_sensor.power_sensor = 0; parse_new_virtual_sensor_target(cur_node->children, doc, &link_sensor); info_ptr->link_sensors.push_back(std::move(link_sensor)); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "PollingTableEntry")) { thermal_sensor_polling_t polling_entry; polling_entry.virtual_temp = 0; polling_entry.sample_period = 0; parse_new_virtual_sensor_polling(cur_node->children, doc, &polling_entry); info_ptr->polling_table.push_back(polling_entry); } xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_parse::parse_thermal_sensors(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr) { xmlNode *cur_node = nullptr; thermal_sensor_t sensor; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); if (!thd_strcasecmp_n((const char*) cur_node->name, "ThermalSensor")) { sensor.name.clear(); sensor.path.clear(); sensor.async_capable = false; sensor.mask = 0; sensor.virtual_sensor = false; sensor.sensor_link.name.clear(); sensor.sensor_link.multiplier = 0.0; sensor.sensor_link.offset = 0.0; sensor.link_sensors.clear(); sensor.polling_table.clear(); parse_new_sensor(cur_node->children, doc, &sensor); info_ptr->sensors.push_back(sensor); } } } return THD_SUCCESS; } int cthd_parse::parse_new_platform_info(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr) { xmlNode *cur_node = nullptr; char *tmp_value; info_ptr->default_preference = PREF_ENERGY_CONSERVE; info_ptr->polling_interval = 0; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (!tmp_value) { continue; } if (!thd_strcasecmp_n((const char*) cur_node->name, "uuid")) { info_ptr->uuid.assign((const char*) tmp_value); string_trim(info_ptr->uuid); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "ProductName")) { info_ptr->product_name.assign((const char*) tmp_value); string_trim(info_ptr->product_name); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "ProductSku")) { info_ptr->product_sku.assign((const char*) tmp_value); string_trim(info_ptr->product_sku); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Name")) { info_ptr->name.assign((const char*) tmp_value); string_trim(info_ptr->name); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "Preference")) { char *pref_val = char_trim(tmp_value); if (pref_val && !thd_strcasecmp_n(pref_val, "PERFORMANCE")) info_ptr->default_preference = PREF_PERFORMANCE; else info_ptr->default_preference = PREF_ENERGY_CONSERVE; } else if (!thd_strcasecmp_n((const char*) cur_node->name, "ThermalZones")) { parse_thermal_zones(cur_node->children, doc, info_ptr); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "ThermalSensors")) { parse_thermal_sensors(cur_node->children, doc, info_ptr); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "CoolingDevices")) { parse_cooling_devs(cur_node->children, doc, info_ptr); } else if (!thd_strcasecmp_n((const char*) cur_node->name, "PollingInterval")) { int val; if (parse_int_value(tmp_value, &val, 0, INT_MAX) == THD_SUCCESS) { info_ptr->polling_interval = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "PPCC")) { parse_ppcc(cur_node->children, doc, &info_ptr->ppcc); info_ptr->ppcc.valid = 1; } xmlFree(tmp_value); } } return THD_SUCCESS; } int cthd_parse::parse_new_platform(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr) { xmlNode *cur_node = nullptr; unsigned char *tmp_value; thermal_info_t info = {}; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (unsigned char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (!thd_strcasecmp_n((const char*) cur_node->name, "Platform")) { info.cooling_devs.clear(); info.zones.clear(); info.polling_interval = 0; info.ppcc.valid = 0; info.ppcc.limit_1_valid = 0; parse_new_platform_info(cur_node->children, doc, &info); thermal_info_list.push_back(info); } if (tmp_value) xmlFree(tmp_value); } } return THD_SUCCESS; } #define MAX_POWER_LIMIT 5000000 #define MAX_TIME_WINDOW 5000000 int cthd_parse::parse_ppcc(xmlNode * a_node, xmlDoc *doc, ppcc_t *ppcc) { xmlNode *cur_node = nullptr; char *tmp_value; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); tmp_value = (char*) xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1); if (tmp_value) { if (!thd_strcasecmp_n((const char*) cur_node->name, "PowerLimitMinimum")) { int val; if (parse_int_value(tmp_value, &val, 0, MAX_POWER_LIMIT) == THD_SUCCESS) { ppcc->power_limit_min = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "PowerLimitMaximum")) { int val; if (parse_int_value(tmp_value, &val, 0, MAX_POWER_LIMIT) == THD_SUCCESS) { ppcc->power_limit_max = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "TimeWindowMinimum")) { int val; if (parse_int_value(tmp_value, &val, 0, MAX_TIME_WINDOW) == THD_SUCCESS) { ppcc->time_wind_min = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "TimeWindowMaximum")) { int val; if (parse_int_value(tmp_value, &val, 0, MAX_TIME_WINDOW) == THD_SUCCESS) { ppcc->time_wind_max = val; } } else if (!thd_strcasecmp_n((const char*) cur_node->name, "StepSize")) { int val; if (parse_int_value(tmp_value, &val, 0, MAX_POWER_LIMIT) == THD_SUCCESS) { ppcc->step_size = val; } } xmlFree(tmp_value); } } } return THD_SUCCESS; } int cthd_parse::parse_new_thermal_conf(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr) { xmlNode *cur_node = nullptr; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); if (!thd_strcasecmp_n((const char*) cur_node->name, "ThermalConfiguration")) { parse_new_platform(cur_node->children, doc, info_ptr); } } } return THD_SUCCESS; } int cthd_parse::parse(xmlNode * a_node, xmlDoc *doc) { xmlNode *cur_node = nullptr; thermal_info_t info; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { DEBUG_PARSER_PRINT("node type: Element, name: %s value: %s\n", cur_node->name, xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1)); if (!thd_strcasecmp_n((const char*) cur_node->name, "ThermalConfiguration")) { parse_new_platform(cur_node->children, doc, &info); } } } return THD_SUCCESS; } int cthd_parse::start_parse() { parse(root_element, doc); return THD_SUCCESS; } void cthd_parse::parser_deinit() { for (unsigned int i = 0; i < thermal_info_list.size(); ++i) { thermal_info_list[i].sensors.clear(); for (unsigned int j = 0; j < thermal_info_list[i].zones.size(); ++j) { thermal_info_list[i].zones[j].trip_pts.clear(); } thermal_info_list[i].zones.clear(); thermal_info_list[i].cooling_devs.clear(); } xmlFreeDoc(doc); } void cthd_parse::dump_thermal_conf() { thd_log_info(" Dumping parsed XML Data\n"); for (unsigned int i = 0; i < thermal_info_list.size(); ++i) { thd_log_info(" *** Index %u ***\n", i); thd_log_info("Name: %s\n", thermal_info_list[i].name.c_str()); thd_log_info("Product Name: %s\n", thermal_info_list[i].product_name.c_str()); thd_log_info("Product SKU: %s\n", thermal_info_list[i].product_sku.c_str()); thd_log_info("UUID: %s\n", thermal_info_list[i].uuid.c_str()); thd_log_info("type: %d\n", thermal_info_list[i].default_preference); thd_log_info("Polling Interval: %d seconds\n", thermal_info_list[i].polling_interval); if (thermal_info_list[i].ppcc.valid) { thd_log_info("PPCC: max_power_limit %d\n", thermal_info_list[i].ppcc.power_limit_max); thd_log_info("PPCC: min_power_limit %d\n", thermal_info_list[i].ppcc.power_limit_min); thd_log_info("PPCC: time_window_max %d\n", thermal_info_list[i].ppcc.time_wind_max); thd_log_info("PPCC: time_window_min %d\n", thermal_info_list[i].ppcc.time_wind_min); thd_log_info("PPCC: step_size %d\n", thermal_info_list[i].ppcc.step_size); } for (unsigned int j = 0; j < thermal_info_list[i].sensors.size(); ++j) { thd_log_info("\tSensor %u\n", j); thd_log_info("\t Name: %s\n", thermal_info_list[i].sensors[j].name.c_str()); thd_log_info("\t Path: %s\n", thermal_info_list[i].sensors[j].path.c_str()); thd_log_info("\t Async Capable: %d\n", thermal_info_list[i].sensors[j].async_capable); thd_log_info("\t Virtual: %d\n", thermal_info_list[i].sensors[j].virtual_sensor); if (thermal_info_list[i].sensors[j].virtual_sensor) { if (!thermal_info_list[i].sensors[j].sensor_link.name.empty()) { thd_log_info("\t\t Link type: %s\n", thermal_info_list[i].sensors[j].sensor_link.name.c_str()); thd_log_info("\t\t Link mult: %f\n", thermal_info_list[i].sensors[j].sensor_link.multiplier); thd_log_info("\t\t Link offset: %f\n", thermal_info_list[i].sensors[j].sensor_link.offset); } for (unsigned int k = 0; k < thermal_info_list[i].sensors[j].link_sensors.size(); ++k) { thd_log_info("\t\t Target sensor: %s coeff:%f offset:%f power:%d\n", thermal_info_list[i].sensors[j].link_sensors[k].name.c_str(), thermal_info_list[i].sensors[j].link_sensors[k].coeff, thermal_info_list[i].sensors[j].link_sensors[k].offset, thermal_info_list[i].sensors[j].link_sensors[k].power_sensor); } for (unsigned int k = 0; k < thermal_info_list[i].sensors[j].polling_table.size(); ++k) { thd_log_info("\t\t Polling entry: temp:%d period:%d\n", thermal_info_list[i].sensors[j].polling_table[k].virtual_temp, thermal_info_list[i].sensors[j].polling_table[k].sample_period); } } } for (unsigned int j = 0; j < thermal_info_list[i].zones.size(); ++j) { thd_log_info("\tZone %u\n", j); thd_log_info("\t Name: %s\n", thermal_info_list[i].zones[j].type.c_str()); for (unsigned int k = 0; k < thermal_info_list[i].zones[j].trip_pts.size(); ++k) { thd_log_info("\t\t Trip Point %u\n", k); thd_log_info("\t\t temp/power %d\n", thermal_info_list[i].zones[j].trip_pts[k].temperature); thd_log_info("\t\t trip type %d\n", thermal_info_list[i].zones[j].trip_pts[k].trip_pt_type); thd_log_info("\t\t hyst id %d\n", thermal_info_list[i].zones[j].trip_pts[k].hyst); thd_log_info("\t\t sensor type %s\n", thermal_info_list[i].zones[j].trip_pts[k].sensor_type.c_str()); if (thermal_info_list[i].zones[j].trip_pts[k].dependency.dependency) thd_log_info("\t\t Dependency on %s:%s\n", thermal_info_list[i].zones[j].trip_pts[k].dependency.cdev.c_str(), thermal_info_list[i].zones[j].trip_pts[k].dependency.state.c_str()); for (unsigned int l = 0; l < thermal_info_list[i].zones[j].trip_pts[k].cdev_trips.size(); ++l) { thd_log_info("\t\t cdev index %u\n", l); thd_log_info("\t\t\t type %s\n", thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].type.c_str()); thd_log_info("\t\t\t influence %d\n", thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].influence); thd_log_info("\t\t\t SamplingPeriod %d\n", thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].sampling_period); if (thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].target_state_valid) thd_log_info("\t\t\t TargetState %d\n", thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].target_state); if (thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].min_max_valid) thd_log_info("\t\t\t TargetMinState %d TargetMaxState %d\n", thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].target_min_state, thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].target_max_state); if (thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].pid_param.valid) thd_log_info("\t\t\t PID values %f:%f:%f\n", thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].pid_param.kp, thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].pid_param.ki, thermal_info_list[i].zones[j].trip_pts[k].cdev_trips[l].pid_param.kd); } } } for (unsigned int l = 0; l < thermal_info_list[i].cooling_devs.size(); ++l) { thd_log_info("\tCooling Dev %u\n", l); thd_log_info("\t\tType: %s\n", thermal_info_list[i].cooling_devs[l].type_string.c_str()); thd_log_info("\t\tPath: %s\n", thermal_info_list[i].cooling_devs[l].path_str.c_str()); thd_log_info("\t\tMin: %d\n", thermal_info_list[i].cooling_devs[l].min_state); thd_log_info("\t\tMax: %d\n", thermal_info_list[i].cooling_devs[l].max_state); thd_log_info("\t\tStep: %d\n", thermal_info_list[i].cooling_devs[l].inc_dec_step); thd_log_info("\t\tReadBack: %d\n", thermal_info_list[i].cooling_devs[l].read_back); thd_log_info("\t\tDebouncePeriod: %d\n", thermal_info_list[i].cooling_devs[l].debounce_interval); thd_log_info("\t\tAutoDownControl: %d\n", thermal_info_list[i].cooling_devs[l].auto_down_control); if (thermal_info_list[i].cooling_devs[l].pid_enable) { thd_log_info("\t PID: Kp %f\n", thermal_info_list[i].cooling_devs[l].pid.Kp); thd_log_info("\t PID: Ki %f\n", thermal_info_list[i].cooling_devs[l].pid.Ki); thd_log_info("\t PID: Kd %f\n", thermal_info_list[i].cooling_devs[l].pid.Kd); } } } } int cthd_parse::get_polling_interval() { return thermal_info_list[matched_thermal_info_index].polling_interval; } bool cthd_parse::match_product_sku(int index) { std::string line; std::ifstream product_sku("/sys/class/dmi/id/product_sku"); if (product_sku.is_open() && getline(product_sku, line)) { if (!thermal_info_list[index].product_sku.size()) return true; string_trim(line); thd_log_debug("config product sku [%s] match with [%s]\n", thermal_info_list[index].product_sku.c_str(), line.c_str()); if (thermal_info_list[index].product_sku == "*") { thd_log_info("Product Sku matched [wildcard]\n"); return true; } if (line == thermal_info_list[index].product_sku) { thd_log_info("Product Sku matched\n"); return true; } } else { return true; } return false; } bool cthd_parse::platform_matched() { std::string line; std::ifstream product_uuid("/sys/class/dmi/id/product_uuid"); if (product_uuid.is_open() && getline(product_uuid, line)) { for (unsigned int i = 0; i < thermal_info_list.size(); ++i) { if (!thermal_info_list[i].uuid.size()) continue; string_trim(line); thd_log_debug("config product uuid [%s] match with [%s]\n", thermal_info_list[i].uuid.c_str(), line.c_str()); if (thermal_info_list[i].uuid == "*") { matched_thermal_info_index = i; thd_log_info("UUID matched [wildcard]\n"); return true; } if (line == thermal_info_list[i].uuid) { matched_thermal_info_index = i; thd_log_info("UUID matched\n"); return true; } } } std::ifstream product_name("/sys/class/dmi/id/product_name"); if (product_name.is_open() && getline(product_name, line)) { for (unsigned int i = 0; i < thermal_info_list.size(); ++i) { if (!thermal_info_list[i].product_name.size()) continue; string_trim(line); thd_log_debug("config product name [%s] match with [%s]\n", thermal_info_list[i].product_name.c_str(), line.c_str()); if (thermal_info_list[i].product_name == "*") { matched_thermal_info_index = i; thd_log_info("Product Name matched [wildcard]\n"); return true; } if (line == thermal_info_list[i].product_name) { if (!match_product_sku(i)) continue; matched_thermal_info_index = i; thd_log_info("Product Name matched\n"); return true; } } } for (unsigned int i = 0; i < thermal_info_list.size(); ++i) { if (!thermal_info_list[i].uuid.size()) continue; if (!thermal_info_list[i].product_name.compare(0, 1, "*")) { matched_thermal_info_index = i; thd_log_info("Product Name matched\n"); return true; } } return false; } int cthd_parse::trip_count(unsigned int zone_index) { if (zone_index < thermal_info_list[matched_thermal_info_index].zones.size()) { return thermal_info_list[matched_thermal_info_index].zones[zone_index].trip_pts.size(); } else return -1; } trip_point_t* cthd_parse::get_trip_point(unsigned int zone_index, unsigned int trip_index) { if (zone_index < thermal_info_list[matched_thermal_info_index].zones.size()) { if (trip_index < thermal_info_list[matched_thermal_info_index].zones[zone_index].trip_pts.size()) return &thermal_info_list[matched_thermal_info_index].zones[zone_index].trip_pts[trip_index]; return nullptr; } else return nullptr; } cooling_dev_t* cthd_parse::get_cool_dev_index(unsigned int cdev_index) { if (cdev_index < thermal_info_list[matched_thermal_info_index].cooling_devs.size()) return &thermal_info_list[matched_thermal_info_index].cooling_devs[cdev_index]; else return nullptr; } thermal_sensor_t* cthd_parse::get_sensor_dev_index(unsigned int sensor_index) { if (sensor_index < thermal_info_list[matched_thermal_info_index].sensors.size()) return &thermal_info_list[matched_thermal_info_index].sensors[sensor_index]; else return nullptr; } thermal_zone_t *cthd_parse::get_zone_dev_index(unsigned int zone_index) { if (zone_index < thermal_info_list[matched_thermal_info_index].zones.size()) return &thermal_info_list[matched_thermal_info_index].zones[zone_index]; else return nullptr; } ppcc_t *cthd_parse::get_ppcc_param(const std::string& name) { if (name != "TCPU.D0") return nullptr; if (matched_thermal_info_index >= 0 && thermal_info_list[matched_thermal_info_index].ppcc.valid) return &thermal_info_list[matched_thermal_info_index].ppcc; return nullptr; } bool cthd_parse::pid_status(int cdev_index) { return thermal_info_list[matched_thermal_info_index].cooling_devs[cdev_index].pid_enable; } bool cthd_parse::get_pid_values(int cdev_index, int *Kp, int *Ki, int *Kd) { if (thermal_info_list[matched_thermal_info_index].cooling_devs[cdev_index].pid_enable) { *Kp = thermal_info_list[matched_thermal_info_index].cooling_devs[cdev_index].pid.Kp; *Kd = thermal_info_list[matched_thermal_info_index].cooling_devs[cdev_index].pid.Kd; *Ki = thermal_info_list[matched_thermal_info_index].cooling_devs[cdev_index].pid.Ki; return true; } return false; } int cthd_parse::set_default_preference() { cthd_preference thd_pref; int ret; if (thermal_info_list[matched_thermal_info_index].default_preference == PREF_PERFORMANCE) ret = thd_pref.set_preference("PERFORMANCE"); else ret = thd_pref.set_preference("ENERGY_CONSERVE"); return ret; } thermal_daemon-2.5.12/src/thd_parse.h000066400000000000000000000162471521301715300175010ustar00rootroot00000000000000/* * thd_engine.cpp: thermal engine class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_PARSE_H #define THD_PARSE_H #include #include #include #include #include "thermald.h" #include "thd_trip_point.h" #define CDEV_DEF_BIT_MIN_STATE 0x0001 #define CDEV_DEF_BIT_MAX_STATE 0x0002 #define CDEV_DEF_BIT_STEP 0x0004 #define CDEV_DEF_BIT_READ_BACK 0x0008 #define CDEV_DEF_BIT_AUTO_DOWN 0x0010 #define CDEV_DEF_BIT_PATH 0x0020 #define CDEV_DEF_BIT_STATUS 0x0040 #define CDEV_DEF_BIT_UNIT_VAL 0x0080 #define CDEV_DEF_BIT_DEBOUNCE_VAL 0x0100 #define CDEV_DEF_BIT_PID_PARAMS 0x0200 #define CDEV_DEF_BIT_WRITE_PREFIX 0x0400 #define SENSOR_DEF_BIT_PATH 0x0001 #define SENSOR_DEF_BIT_ASYNC_CAPABLE 0x0002 typedef struct { double Kp; double Ki; double Kd; } pid_control_t; typedef struct { std::string name; double multiplier; double offset; } thermal_sensor_link_t; typedef struct { std::string name; double coeff; double offset; int power_sensor; } thermal_sensor_target_t; typedef struct { int virtual_temp; int sample_period; } thermal_sensor_polling_t; typedef struct { unsigned int mask; std::string name; std::string path; bool async_capable; bool virtual_sensor; thermal_sensor_link_t sensor_link; std::vector link_sensors; std::vector polling_table; } thermal_sensor_t; typedef struct { int dependency; std::string cdev; std::string state; }trip_cdev_depend_t; typedef struct { std::string type; int influence; int sampling_period; int target_state_valid; int target_state; pid_param_t pid_param; int min_max_valid; int target_min_state; int target_max_state; } trip_cdev_t; typedef struct { int temperature; int hyst; trip_point_type_t trip_pt_type; trip_control_type_t control_type; int influence; std::string sensor_type; trip_cdev_depend_t dependency; std::vector cdev_trips; } trip_point_t; typedef struct { std::string type; std::vector trip_pts; } thermal_zone_t; typedef enum : uint8_t { ABSOULUTE_VALUE, RELATIVE_PERCENTAGES } unit_value_t; typedef struct { bool status; unsigned int mask; // Fields which are present in config int index; unit_value_t unit_val; int min_state; int max_state; int inc_dec_step; bool read_back; // For some device read back current state is not possible bool auto_down_control; std::string type_string; std::string path_str; int debounce_interval; bool pid_enable; pid_control_t pid; std::string write_prefix; } cooling_dev_t; typedef struct { std::string name; int valid; int power_limit_min; int power_limit_max; int time_wind_min; int time_wind_max; int step_size; int limit_1_valid; int power_limit_1_min; int power_limit_1_max; int time_wind_1_min; int time_wind_1_max; int step_1_size; } ppcc_t; typedef struct { std::string name; std::string uuid; std::string product_name; std::string product_sku; int default_preference; int polling_interval; ppcc_t ppcc; std::vector sensors; std::vector zones; std::vector cooling_devs; } thermal_info_t; class cthd_parse { private: std::string filename; std::string filename_auto; std::string filename_auto_conf; std::vector thermal_info_list; int matched_thermal_info_index; xmlDoc *doc; xmlNode *root_element; int auto_config; int parse(xmlNode * a_node, xmlDoc *doc); int parse_pid_values(xmlNode * a_node, xmlDoc *doc, pid_control_t *pid_ptr); int parse_dependency_values(xmlNode * a_node, xmlDoc *doc, trip_cdev_depend_t *dependency); int parse_new_trip_cdev(xmlNode * a_node, xmlDoc *doc, trip_cdev_t *trip_cdev); int parse_new_thermal_conf(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info); int parse_new_platform_info(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info); int parse_new_zone(xmlNode * a_node, xmlDoc *doc, thermal_zone_t *info_ptr); int parse_new_cooling_dev(xmlNode * a_node, xmlDoc *doc, cooling_dev_t *info_ptr); int parse_new_trip_point(xmlNode * a_node, xmlDoc *doc, trip_point_t *trip_pt); int parse_thermal_zones(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr); int parse_new_sensor(xmlNode * a_node, xmlDoc *doc, thermal_sensor_t *info_ptr); int parse_new_sensor_link(xmlNode * a_node, xmlDoc *doc, thermal_sensor_link_t *info_ptr); int parse_new_virtual_sensor_target(xmlNode * a_node, xmlDoc *doc, thermal_sensor_target_t *info_ptr); int parse_new_virtual_sensor_polling(xmlNode * a_node, xmlDoc *doc, thermal_sensor_polling_t *info_ptr); int parse_thermal_sensors(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr); int parse_cooling_devs(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info_ptr); int parse_trip_points(xmlNode * a_node, xmlDoc *doc, thermal_zone_t *info_ptr); int parse_new_platform(xmlNode * a_node, xmlDoc *doc, thermal_info_t *info); int parse_ppcc(xmlNode * a_node, xmlDoc *doc, ppcc_t *ppcc); void string_trim(std::string &str); char *char_trim(char *trim); int parse_int_value(const char *str, int *result, int min_val, int max_val); int parse_double_value(const char *str, double *result, double min_val, double max_val); bool match_product_sku(int index); public: cthd_parse(); int parser_init(const std::string& config_file); void parser_deinit(); int start_parse(); void dump_thermal_conf(); bool platform_matched(); int get_polling_interval(); ppcc_t *get_ppcc_param(const std::string& name); int zone_count() { return thermal_info_list[matched_thermal_info_index].zones.size(); } int cdev_count() { return thermal_info_list[matched_thermal_info_index].cooling_devs.size(); } int sensor_count() { return thermal_info_list[matched_thermal_info_index].sensors.size(); } int thermal_conf_auto() { return auto_config; } int thermal_matched_platform_index() { return matched_thermal_info_index; } int set_default_preference(); int trip_count(unsigned int zone_index); bool pid_status(int cdev_index); bool get_pid_values(int cdev_index, int *Kp, int *Ki, int *Kd); trip_point_t *get_trip_point(unsigned int zone_index, unsigned int trip_index); cooling_dev_t *get_cool_dev_index(unsigned int cdev_index); thermal_sensor_t *get_sensor_dev_index(unsigned int sensor_index); thermal_zone_t *get_zone_dev_index(unsigned int zone_index); // std::string get_sensor_path(int zone_index) { // return thermal_info_list[matched_thermal_info_index].zones[zone_index].path; // } }; #endif thermal_daemon-2.5.12/src/thd_pid.cpp000066400000000000000000000037741521301715300174770ustar00rootroot00000000000000/* * thd_pid.cpp: pid implementation * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_pid.h" cthd_pid::cthd_pid() { kp = 0.0005; ki = kd = 0.0001; last_time = 0; err_sum = 0.0; last_err = 0.0; target_temp = 0; } int cthd_pid::pid_output(unsigned int curr_temp, int initial_value) { double output; double d_err = 0; int error = curr_temp - target_temp; time_t now; time(&now); if (last_time == 0) { last_time = now; /* Initialize integrative component (err_sum) so that current * output is the initial_value. * d_err must be assumed to be zero for this */ if (ki) err_sum = (initial_value - kp * error) / ki; else err_sum = 0; } time_t timeChange = (now - last_time); thd_log_debug("pid_output error %d %g:%g\n", error, kp, kp * error); err_sum += (error * timeChange); if (timeChange) d_err = (error - last_err) / timeChange; else d_err = 0.0; /*Compute PID Output*/ output = kp * error + ki * err_sum + kd * d_err; thd_log_debug("pid %d:%d:%d:%d\n", (int) output, (int) (kp * error), (int) (ki * err_sum), (int) (kd * d_err)); /*Remember some variables for next time*/ last_err = error; last_time = now; thd_log_debug("pid_output %d:%d %g:%d\n", curr_temp, target_temp, output, (int) output); return (int) output; } thermal_daemon-2.5.12/src/thd_pid.h000066400000000000000000000027321521301715300171350ustar00rootroot00000000000000/* * thd_pid.h: pid interface * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thermald.h" #include typedef struct { int valid; double kp; double ki; double kd; }pid_param_t; class cthd_pid { private: double err_sum, last_err; time_t last_time; unsigned int target_temp; public: cthd_pid(); double kp, ki, kd; cthd_pid(const cthd_pid& x) = default; ~cthd_pid() { } cthd_pid& operator=(const cthd_pid& x) = default; void set_pid_param(double _kp, double _ki, double _kd) { kp = _kp; ki = _ki; kd = _kd; } int pid_output(unsigned int curr_temp, int initial_value = 0); void set_target_temp(unsigned int temp) { target_temp = temp; } void reset() { err_sum = last_err = last_time = 0; } }; thermal_daemon-2.5.12/src/thd_platform.cpp000066400000000000000000000102361521301715300205360ustar00rootroot00000000000000/* * thd_platform.cpp: Platform detection and abstraction layer implementation * * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * Author Name */ #include "thd_platform.h" #include "thd_platform_intel.h" #include "thd_platform_arm.h" #include "thd_common.h" #include #include #include "thd_util.h" cthd_platform::cthd_platform() : detected_platform(PLATFORM_UNKNOWN), machine_type("") { } cthd_platform::~cthd_platform() { } void cthd_platform::detect_platform() { struct utsname sysinfo; if (uname(&sysinfo) != 0) { thd_log_error("Failed to get system information\n"); detected_platform = PLATFORM_UNKNOWN; return; } machine_type = std::string(sysinfo.machine); thd_log_info("Detected machine architecture: %s\n", machine_type.c_str()); if (thd_strcmp_n(sysinfo.machine, "x86_64") == 0) { detected_platform = PLATFORM_INTEL_X86; } else if (thd_strcmp_n(sysinfo.machine, "aarch64") == 0) { detected_platform = PLATFORM_ARM64; } else if (thd_strcmp_n(sysinfo.machine, "arm") == 0) { detected_platform = PLATFORM_ARM32; } else { detected_platform = PLATFORM_OTHER; } } platform_type_t cthd_platform::get_platform() { return detected_platform; } std::string cthd_platform::get_machine_type() { return machine_type; } int cthd_platform::check_cpu_id(bool &proc_list_matched) { // Base implementation - to be overridden by derived classes proc_list_matched = false; return THD_SUCCESS; } void cthd_platform::workaround_rapl_mmio_power() { // Base implementation - to be overridden by derived classes // No workaround needed for generic platform } void cthd_platform::dump_platform_info() { platform_type_t platform = get_platform(); thd_log_info("=== Platform Information ===\n"); thd_log_info("Machine Type: %s\n", get_machine_type().c_str()); switch (platform) { case PLATFORM_INTEL_X86: thd_log_info("Platform: Intel x86/x86_64\n"); break; case PLATFORM_ARM64: thd_log_info("Platform: ARM64 (aarch64)\n"); break; case PLATFORM_ARM32: thd_log_info("Platform: ARM32\n"); break; case PLATFORM_OTHER: thd_log_info("Platform: Other (%s)\n", get_machine_type().c_str()); break; case PLATFORM_UNKNOWN: default: thd_log_info("Platform: Unknown\n"); break; } thd_log_info("============================\n"); } std::unique_ptr cthd_platform::create_platform() { // Detect platform architecture using uname struct utsname sysinfo; if (uname(&sysinfo) != 0) { thd_log_error("Failed to get system information\n"); return std::unique_ptr(new cthd_platform()); } // Create appropriate platform instance based on architecture if (thd_strcmp_n(sysinfo.machine, "x86_64") == 0) { thd_log_info("Creating Intel platform instance\n"); return std::unique_ptr(new intel_platform()); } else if (thd_strcmp_n(sysinfo.machine, "aarch64") == 0 || thd_strcmp_n(sysinfo.machine, "arm") == 0) { thd_log_info("Creating ARM platform instance\n"); return std::unique_ptr(new arm_platform()); } else { thd_log_info("Creating generic platform instance for %s\n", sysinfo.machine); return std::unique_ptr(new cthd_platform()); } } thermal_daemon-2.5.12/src/thd_platform.h000066400000000000000000000034111521301715300202000ustar00rootroot00000000000000/* * thd_platform.h: Platform detection and abstraction layer * * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * Author Name */ #ifndef THD_PLATFORM_H_ #define THD_PLATFORM_H_ #include #include #include #include typedef enum : uint8_t { PLATFORM_UNKNOWN = 0, PLATFORM_INTEL_X86, PLATFORM_ARM64, PLATFORM_ARM32, PLATFORM_OTHER } platform_type_t; class cthd_platform { protected: platform_type_t detected_platform; std::string machine_type; public: cthd_platform(); virtual ~cthd_platform(); // Virtual methods to be overridden by derived classes virtual void detect_platform(); virtual int check_cpu_id(bool &proc_list_matched); virtual void workaround_rapl_mmio_power(); virtual void dump_platform_info(); // Common methods platform_type_t get_platform(); std::string get_machine_type(); // Factory method to create appropriate platform instance static std::unique_ptr create_platform(); }; #endif /* THD_PLATFORM_H_ */ thermal_daemon-2.5.12/src/thd_platform_arm.cpp000066400000000000000000000027221521301715300213760ustar00rootroot00000000000000/* * thd_platform_arm.cpp: ARM platform-specific functionality implementation * * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * Author Name */ #include "thd_platform_arm.h" #include "thd_common.h" arm_platform::arm_platform() : cthd_platform() { // ARM platform specific initialization detect_platform(); } arm_platform::~arm_platform() { } void arm_platform::detect_platform() { // Call base class detection first cthd_platform::detect_platform(); // ARM-specific platform detection can be added here if needed thd_log_info("ARM platform detected\n"); } int arm_platform::check_cpu_id(bool &proc_list_matched) { // For ARM, we assume the platform is supported proc_list_matched = true; return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_platform_arm.h000066400000000000000000000023161521301715300210420ustar00rootroot00000000000000/* * thd_platform_arm.h: ARM platform-specific functionality * * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * Author Name */ #ifndef THD_PLATFORM_ARM_H_ #define THD_PLATFORM_ARM_H_ #include "thd_platform.h" class arm_platform : public cthd_platform { public: arm_platform(); ~arm_platform() override; // Override virtual methods from base class void detect_platform() override; int check_cpu_id(bool &proc_list_matched) override; }; #endif /* THD_PLATFORM_ARM_H_ */ thermal_daemon-2.5.12/src/thd_platform_intel.cpp000066400000000000000000000143371521301715300217370ustar00rootroot00000000000000/* * thd_platform_intel.cpp: Intel platform-specific functionality implementation * * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * */ #include "thd_platform_intel.h" #include "thd_common.h" #include "thd_engine.h" #include #include #include #include #ifndef ANDROID #ifdef __x86_64__ #include #include #endif #include #endif #define BIT_ULL(nr) (1ULL << (nr)) #ifndef ANDROID #ifdef __x86_64__ typedef struct { unsigned int family; unsigned int model; unsigned int adaptive_only; } supported_ids_t; static supported_ids_t intel_id_table[] = { { 6, 0x2a, 0 }, // Sandybridge { 6, 0x3a, 0 }, // IvyBridge { 6, 0x3c, 0 }, // Haswell { 6, 0x45, 0 }, // Haswell ULT { 6, 0x46, 0 }, // Haswell ULT { 6, 0x3d, 0 }, // Broadwell { 6, 0x47, 0 }, // Broadwell-GT3E { 6, 0x37, 0 }, // Valleyview BYT { 6, 0x4c, 0 }, // Brasewell { 6, 0x4e, 0 }, // skylake { 6, 0x5e, 0 }, // skylake { 6, 0x5c, 0 }, // Broxton { 6, 0x7a, 0 }, // Gemini Lake { 6, 0x8e, 0 }, // kabylake { 6, 0x9e, 0 }, // kabylake { 6, 0x66, 0 }, // Cannonlake { 6, 0x7e, 0 }, // Icelake { 6, 0x8c, 0 }, // Tigerlake_L { 6, 0x8d, 0 }, // Tigerlake { 6, 0xa5, 0 }, // Cometlake { 6, 0xa6, 0 }, // Cometlake_L { 6, 0xa7, 0 }, // Rocketlake { 6, 0x9c, 0 }, // Jasper Lake { 6, 0x97, 0 }, // Alderlake { 6, 0x9a, 0 }, // Alderlake { 6, 0xb7, 0 }, // Raptorlake { 6, 0xba, 0 }, // Raptorlake { 6, 0xbe, 0 }, // Alderlake N { 6, 0xbf, 0 }, // Raptorlake S { 6, 0xaa, 0 }, // Meteor Lake L { 6, 0xbd, 1 }, // Lunar Lake M { 6, 0xc6, 1 }, // Arrow Lake { 6, 0xc5, 1 }, // Arrow Lake H { 6, 0xb5, 1 }, // Arrow Lake U { 6, 0xcc, 1 }, // Panther Lake L { 15, 0x01, 1 }, // Nova Lake S { 15, 0x03, 1 }, // Nova Lake U/P/H/Hx { 0, 0, 0 } // Last Invalid entry }; static constexpr const char *blocklist_paths[] { /* Some Lenovo machines have in-firmware thermal management, * avoid having two entities trying to manage things. * We may want to change this to dytc_perfmode once that is * widely available. */ "/sys/devices/platform/thinkpad_acpi/dytc_lapmode", }; #endif // __x86_64__ #endif intel_platform::intel_platform() : cthd_platform() { // Intel platform specific initialization detect_platform(); } intel_platform::~intel_platform() { } void intel_platform::detect_platform() { // Call base class detection first cthd_platform::detect_platform(); thd_log_info("Intel platform detected\n"); } int intel_platform::check_cpu_id(bool &proc_list_matched) { #ifndef ANDROID #ifdef __x86_64__ unsigned int ebx, ecx, edx, max_level; unsigned int fms, family, model, stepping; unsigned int genuine_intel = 0; int i = 0; bool valid = false; proc_list_matched = false; ebx = ecx = edx = 0; __cpuid(0, max_level, ebx, ecx, edx); if (ebx == 0x756e6547 && edx == 0x49656e69 && ecx == 0x6c65746e) genuine_intel = 1; if (genuine_intel == 0) { // Simply return without further capability check return THD_SUCCESS; } __cpuid(1, fms, ebx, ecx, edx); family = (fms >> 8) & 0xf; model = (fms >> 4) & 0xf; stepping = fms & 0xf; if (family == 6 || family == 0xf) model += ((fms >> 16) & 0xf) << 4; thd_log_msg( "%u CPUID levels; family:model:stepping 0x%x:%x:%x (%u:%u:%u)\n", max_level, family, model, stepping, family, model, stepping); while (intel_id_table[i].family) { if (intel_id_table[i].family == family && intel_id_table[i].model == model) { proc_list_matched = true; valid = true; if (intel_id_table[i].adaptive_only && !adaptive_perf_enable) { proc_list_matched = false; thd_log_warn("CPU %u:%u is supported only in adaptive performance mode. Please enable --adaptive to use this platform.\n", family, model); } break; } i++; } if (!valid) { thd_log_msg(" Need Linux PowerCap sysfs\n"); } for (const char *path : blocklist_paths) { struct stat s; if (!stat(path, &s)) { proc_list_matched = false; thd_log_warn("[%s] present: Thermald can't run on this platform\n", path); break; } } #else thd_log_info("Non-x86_64 platform detected in Intel check - skipping CPUID\n"); #endif // __x86_64__ #endif // ANDROID return THD_SUCCESS; } void intel_platform::workaround_rapl_mmio_power(void) { // First check if workaround is enabled and needed extern bool workaround_enabled; if (!workaround_enabled) return; // Check if RAPL MMIO controller is already being used extern std::unique_ptr thd_engine; if (thd_engine) { cthd_cdev *cdev = thd_engine->search_cdev("rapl_controller_mmio"); if (cdev) { /* RAPL MMIO is enabled and getting used. No need to disable */ return; } else { csys_fs _sysfs("/sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/"); if (_sysfs.exists()) { std::stringstream temp_str; temp_str << "enabled"; if (_sysfs.write(temp_str.str(), 0) > 0) return; thd_log_debug("Failed to write to RAPL MMIO\n"); } } } // Direct MMIO access via /dev/mem has been removed. // The workaround now relies on the sysfs-based RAPL MMIO path above. } thermal_daemon-2.5.12/src/thd_platform_intel.h000066400000000000000000000023361521301715300214000ustar00rootroot00000000000000/* * thd_platform_intel.h: Intel platform-specific functionality * * Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * */ #ifndef THD_PLATFORM_INTEL_H_ #define THD_PLATFORM_INTEL_H_ #include "thd_platform.h" class intel_platform : public cthd_platform { public: intel_platform(); ~intel_platform() override; // Override virtual methods from base class void detect_platform() override; int check_cpu_id(bool &proc_list_matched) override; void workaround_rapl_mmio_power() override; }; #endif /* THD_PLATFORM_INTEL_H_ */ thermal_daemon-2.5.12/src/thd_preference.cpp000066400000000000000000000113231521301715300210260ustar00rootroot00000000000000/* * thd_preference.cpp: Thermal preference class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_preference.h" #include #include #include #include cthd_preference::cthd_preference() : preference(PREF_ENERGY_CONSERVE), old_preference(0) { std::ostringstream filename; filename << TDRUNDIR << "/" << "thd_preference.conf"; std::ifstream ifs(filename.str().c_str(), std::ifstream::in); if (!ifs.good()) { preference = PREF_ENERGY_CONSERVE; } else { ifs >> preference; } ifs.close(); } std::string cthd_preference::int_pref_to_string(int pref) { std::string perf_str; switch (preference) { case PREF_PERFORMANCE: perf_str = "PERFORMANCE"; break; case PREF_ENERGY_CONSERVE: perf_str = "ENERGY_CONSERVE"; break; case PREF_DISABLED: perf_str = "DISABLE"; break; default: perf_str = "INVALID"; break; } return perf_str; } int cthd_preference::string_pref_to_int(std::string &pref_str) { int pref; if (pref_str == "PERFORMANCE") pref = PREF_PERFORMANCE; else if (pref_str == "ENERGY_CONSERVE") pref = PREF_ENERGY_CONSERVE; else if (pref_str == "DISABLE") pref = PREF_DISABLED; else pref = -1; return pref; } std::string cthd_preference::get_preference_str() { return int_pref_to_string(preference); } const char *cthd_preference::get_preference_cstr() { return strdup(int_pref_to_string(preference).c_str()); } int cthd_preference::get_preference() { return preference; } void cthd_preference::refresh() { std::ostringstream filename; filename << TDRUNDIR << "/" << "thd_preference.conf"; std::ifstream ifs(filename.str().c_str(), std::ifstream::in); if (!ifs.good()) { preference = PREF_ENERGY_CONSERVE; } else { ifs >> preference; } ifs.close(); } bool cthd_preference::set_preference(const char *pref_str) { std::string str(pref_str); int pref = string_pref_to_int(str); /* Reject unknown preference values rather than silently coercing * them, so a misbehaving caller cannot blindly switch the daemon * into PERFORMANCE mode. */ if (pref < 0) return false; std::ostringstream filename; filename << TDRUNDIR << "/" << "thd_preference.conf"; /* Open with O_NOFOLLOW so that a symlink planted in TDRUNDIR cannot * redirect the write to an arbitrary file. O_TRUNC ensures we don't * leave stale bytes from a longer prior value. */ int fd = ::open(filename.str().c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0600); if (fd < 0) return false; std::ostringstream pref_buf; pref_buf << pref; const std::string &pref_data = pref_buf.str(); ssize_t w = ::write(fd, pref_data.c_str(), pref_data.size()); ::close(fd); if (w != (ssize_t) pref_data.size()) return false; // Save the old preference old_preference = preference; std::ostringstream filename_save; filename_save << TDRUNDIR << "/" << "thd_preference.conf.save"; int fd_save = ::open(filename_save.str().c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0600); if (fd_save < 0) return false; std::ostringstream save_buf; save_buf << old_preference; const std::string &save_data = save_buf.str(); ssize_t w_save = ::write(fd_save, save_data.c_str(), save_data.size()); ::close(fd_save); if (w_save != (ssize_t) save_data.size()) return false; std::ifstream ifs(filename.str().c_str(), std::ifstream::in); if (!ifs.good()) { preference = PREF_PERFORMANCE; } else { //ifs.read(reinterpret_cast < char * > (&preference), sizeof(preference)); ifs >> preference; } ifs.close(); thd_log_debug("old_preference %d new preference %d\n", old_preference, preference); return true; } int cthd_preference::get_old_preference() { std::ostringstream filename; filename << TDRUNDIR << "/" << "thd_preference.conf.save"; std::ifstream ifs(filename.str().c_str(), std::ifstream::in); if (!ifs.good()) { old_preference = PREF_PERFORMANCE; } else { //ifs.read(reinterpret_cast < char * > (&preference), sizeof(preference)); ifs >> old_preference; } ifs.close(); return old_preference; } thermal_daemon-2.5.12/src/thd_preference.h000066400000000000000000000027471521301715300205050ustar00rootroot00000000000000/* * thd_preference.h: Thermal preference class interface file * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_PREFERENCE_H #define THD_PREFERENCE_H #include "thd_common.h" #include #include #include #include #include #include enum : uint8_t { PREF_ENERGY_CONSERVE, PREF_PERFORMANCE, PREF_DISABLED }; class cthd_preference { private: int preference; int old_preference; int string_pref_to_int(std::string &pref_str); std::string int_pref_to_string(int pref); public: cthd_preference(); bool set_preference(const char *pref); std::string get_preference_str(); const char *get_preference_cstr(); int get_preference(); int get_old_preference(); void refresh(); }; #endif thermal_daemon-2.5.12/src/thd_rapl_power_meter.cpp000066400000000000000000000214721521301715300222640ustar00rootroot00000000000000/* * thd_rapl_power_meter.cpp: thermal cooling class implementation * using RAPL * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_rapl_power_meter.h" #include #include #include #include "thd_util.h" static void *rapl_periodic_callback(void *data) { cthd_rapl_power_meter *rapl_cl = (cthd_rapl_power_meter*) data; for (;;) { if (!rapl_cl->rapl_energy_loop()) break; sleep(rapl_cl->rapl_callback_timeout); } return nullptr; } cthd_rapl_power_meter::cthd_rapl_power_meter(unsigned int mask) : rapl_present(true), rapl_sysfs("/sys/class/powercap/intel-rapl/"), domain_list( 0), last_time(0), poll_thread(0), measure_mask(mask), enable_measurement( false) { thd_attr = pthread_attr_t(); if (rapl_sysfs.exists()) { thd_log_debug("RAPL sysfs present\n"); rapl_present = true; last_time = time(nullptr); rapl_read_domains(rapl_sysfs.get_base_path().c_str()); } else { thd_log_warn("NO RAPL sysfs present\n"); rapl_present = false; } } void cthd_rapl_power_meter::rapl_read_domains(const char *dir_name) { int count = 0; csys_fs sys_fs; if (rapl_present) { DIR *dir; struct dirent *dir_entry; thd_log_debug("RAPL base path %s\n", dir_name); if ((dir = opendir(dir_name)) != nullptr) { while ((dir_entry = readdir(dir)) != nullptr) { std::string buffer; std::ostringstream path; int status; rapl_domain_t domain; domain.half_way = 0; domain.energy_counter = 0; domain.energy_cumulative_counter = 0; domain.max_energy_range = 0; domain.max_energy_range_threshold = 0; domain.power = 0; domain.max_power = 0; domain.min_power = 0; domain.type = INVALID; if (!thd_strcmp_n(dir_entry->d_name, ".") || !thd_strcmp_n(dir_entry->d_name, "..")) continue; thd_log_debug("RAPL domain dir %s\n", dir_entry->d_name); path << dir_name << dir_entry->d_name << "/" << "name"; if (!sys_fs.exists(path.str())) { thd_log_debug(" %s doesn't exist\n", path.str().c_str()); continue; } status = sys_fs.read(path.str(), buffer); if (status < 0) continue; thd_log_debug("name %s\n", buffer.c_str()); if (fnmatch("package-*", buffer.c_str(), 0) == 0) { domain.type = PACKAGE; std::ostringstream path; path << dir_name << dir_entry->d_name << "/"; rapl_read_domains(path.str().c_str()); } else if (buffer == "core") { domain.type = CORE; } else if (buffer == "uncore") { domain.type = UNCORE; } else if (buffer == "dram") { domain.type = DRAM; } if (measure_mask & domain.type) { domain.name = std::move(buffer); domain.path = std::string(dir_name) + std::string(dir_entry->d_name); domain_list.push_back(std::move(domain)); ++count; } } closedir(dir); } else { thd_log_debug("opendir failed %s :%s\n", strerror(errno), rapl_sysfs.get_base_path().c_str()); } } thd_log_info("RAPL domain count %d\n", count); } void cthd_rapl_power_meter::rapl_enable_periodic_timer() { pthread_attr_init(&thd_attr); pthread_attr_setdetachstate(&thd_attr, PTHREAD_CREATE_DETACHED); pthread_create(&poll_thread, &thd_attr, rapl_periodic_callback, (void*) this); } bool cthd_rapl_power_meter::rapl_energy_loop() { csys_fs sys_fs; int status; unsigned long long counter; unsigned long long diff; time_t curr_time; if (!enable_measurement) return false; curr_time = time(nullptr); if ((curr_time - last_time) <= 0) return true; for (unsigned int i = 0; i < domain_list.size(); ++i) { unsigned long energy_uj; std::string path; if (!domain_list[i].max_energy_range) { std::string _path; unsigned long _value; _path = domain_list[i].path + "/" + "max_energy_range_uj"; status = sys_fs.read(_path, &_value); if (status >= 0) domain_list[i].max_energy_range = _value / 1000; domain_list[i].max_energy_range_threshold = domain_list[i].max_energy_range / 2; } path = domain_list[i].path + "/" + "energy_uj"; status = sys_fs.read(path, &energy_uj); if (status >= 0) { counter = domain_list[i].energy_counter; domain_list[i].energy_counter = energy_uj / 1000; // To milli Js diff = 0; if (domain_list[i].half_way && domain_list[i].energy_counter < domain_list[i].max_energy_range_threshold) { // wrap around domain_list[i].energy_cumulative_counter += domain_list[i].max_energy_range; diff = domain_list[i].max_energy_range - counter; counter = 0; domain_list[i].half_way = 0; } else if (domain_list[i].energy_counter > domain_list[i].max_energy_range_threshold) domain_list[i].half_way = 1; if (counter) domain_list[i].power = (domain_list[i].energy_counter - counter + diff) / (curr_time - last_time); if (domain_list[i].power > domain_list[i].max_power) domain_list[i].max_power = domain_list[i].power; if (domain_list[i].min_power == 0) domain_list[i].min_power = domain_list[i].power; else if (domain_list[i].power < domain_list[i].min_power) domain_list[i].min_power = domain_list[i].power; thd_log_debug(" energy %d:%lld:%lld mj: %u mw\n", domain_list[i].type, domain_list[i].energy_cumulative_counter, domain_list[i].energy_counter + domain_list[i].energy_cumulative_counter, domain_list[i].power); } } last_time = curr_time; return true; } unsigned long long cthd_rapl_power_meter::rapl_action_get_energy( domain_type type) { unsigned long long value = 0; for (unsigned int i = 0; i < domain_list.size(); ++i) { if (type == domain_list[i].type) { value = (domain_list[i].energy_counter + domain_list[i].energy_cumulative_counter) * 1000; if (!value) { rapl_energy_loop(); value = (domain_list[i].energy_counter + domain_list[i].energy_cumulative_counter) * 1000; } break; } } return value; } unsigned int cthd_rapl_power_meter::rapl_action_get_power(domain_type type) { unsigned int value = 0; if (!rapl_present) return 0; for (unsigned int i = 0; i < domain_list.size(); ++i) { if (type == domain_list[i].type) { value = domain_list[i].power * 1000; if (!value) { rapl_energy_loop(); sleep(1); rapl_energy_loop(); value = domain_list[i].power * 1000; } break; } } return value; } unsigned int cthd_rapl_power_meter::rapl_action_get_max_power( domain_type type) { unsigned int value = 0; if (!rapl_present) return 0; for (unsigned int i = 0; i < domain_list.size(); ++i) { if (type == domain_list[i].type) { int status; std::string _path; csys_fs sys_fs; int max_power; unsigned int const_0_val = 0, const_1_val = 0; const_0_val = 0; const_1_val = 0; _path = domain_list[i].path + "/" + "constraint_0_max_power_uw"; status = sys_fs.read(_path, &max_power); if (status >= 0) { if (max_power > 0) const_0_val = max_power; } _path = domain_list[i].path + "/" + "constraint_1_max_power_uw"; status = sys_fs.read(_path, &max_power); if (status >= 0) { if (max_power > 0) const_1_val = max_power; } value = const_1_val > const_0_val ? const_1_val : const_0_val; if (value) return value; } } return value; } unsigned int cthd_rapl_power_meter::rapl_action_get_last_power(domain_type type) { unsigned int value = 0; if (!rapl_present) return 0; for (unsigned int i = 0; i < domain_list.size(); ++i) { if (type == domain_list[i].type) { value = domain_list[i].power * 1000; break; } } return value; } unsigned int cthd_rapl_power_meter::rapl_action_get_power(domain_type type, unsigned int *max_power, unsigned int *min_power) { unsigned int value = 0; if (!rapl_present) return 0; for (unsigned int i = 0; i < domain_list.size(); ++i) { if (type == domain_list[i].type) { value = domain_list[i].power * 1000; if (!value) { rapl_energy_loop(); sleep(1); rapl_energy_loop(); value = domain_list[i].power * 1000; } *max_power = domain_list[i].max_power * 1000; *min_power = domain_list[i].min_power * 1000; break; } } return value; } void cthd_rapl_power_meter::rapl_measure_power() { if (rapl_present && enable_measurement) rapl_energy_loop(); } thermal_daemon-2.5.12/src/thd_rapl_power_meter.h000066400000000000000000000050511521301715300217240ustar00rootroot00000000000000/* * thd_rapl_power_meter.h: thermal cooling class interface * using RAPL * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_CDEV_RAPL_POWER_METER_H_ #define THD_CDEV_RAPL_POWER_METER_H_ #include "thd_common.h" #include "thd_sys_fs.h" #include #include typedef enum : uint8_t { INVALID = 0, PACKAGE = 0x01, DRAM = 0x02, CORE = 0x04, UNCORE = 0x08 } domain_type; typedef struct { domain_type type; std::string name; std::string path; // Store in milli-units to have a bigger range unsigned long long max_energy_range; unsigned long long max_energy_range_threshold; int half_way; unsigned long long energy_cumulative_counter; unsigned long long energy_counter; unsigned int power; unsigned int max_power; unsigned int min_power; } rapl_domain_t; class cthd_rapl_power_meter { private: bool rapl_present; csys_fs rapl_sysfs; std::vector domain_list; time_t last_time; pthread_t poll_thread; pthread_attr_t thd_attr; unsigned int measure_mask; bool enable_measurement; public: static constexpr int rapl_callback_timeout = 10; //seconds cthd_rapl_power_meter(unsigned int mask = PACKAGE | DRAM); void rapl_read_domains(const char *base_path); void rapl_enable_periodic_timer(); bool rapl_energy_loop(); void rapl_measure_power(); void rapl_start_measure_power() { enable_measurement = true; } void rapl_stop_measure_power() { enable_measurement = false; } // return in micro units to be compatible with kernel ABI unsigned long long rapl_action_get_energy(domain_type type); unsigned int rapl_action_get_last_power(domain_type type); unsigned int rapl_action_get_power(domain_type type); unsigned int rapl_action_get_power(domain_type type, unsigned int *max_power, unsigned int *min_power); unsigned int rapl_action_get_max_power(domain_type type); }; #endif thermal_daemon-2.5.12/src/thd_sensor.cpp000066400000000000000000000070171521301715300202260ustar00rootroot00000000000000/* * thd_sensor.h: thermal sensor class implementation * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_sensor.h" #include "thd_engine.h" cthd_sensor::cthd_sensor(int _index, std::string control_path, std::string _type_str, int _type) : index(_index), type(_type), sensor_sysfs(std::move(control_path)), sensor_active( false), type_str(std::move(_type_str)), async_capable(false), virtual_sensor( false), thresholds(0), scale(1) { } int cthd_sensor::sensor_update() { if (type == SENSOR_TYPE_THERMAL_SYSFS) { if (sensor_sysfs.exists("type")) { sensor_sysfs.read("type", type_str); thd_log_info("sensor_update: type %s\n", type_str.c_str()); } else return THD_ERROR; if (sensor_sysfs.exists("temp")) { return THD_SUCCESS; } else { thd_log_msg("sensor id %d: No temp sysfs for reading temp\n", index); return THD_ERROR; } } if (type == SENSOR_TYPE_RAW) { if (sensor_sysfs.exists("")) { return THD_SUCCESS; } else { thd_log_msg("sensor id %d %s: No temp sysfs for reading raw temp\n", index, sensor_sysfs.get_base_path().c_str()); return THD_ERROR; } } return THD_SUCCESS; } unsigned int cthd_sensor::read_temperature() { csys_fs sysfs; int temp, ret; thd_log_debug("read_temperature sensor ID %d\n", index); temp = 0; // Initialize before read if (type == SENSOR_TYPE_THERMAL_SYSFS) ret = sensor_sysfs.read("temp", &temp); else ret = sensor_sysfs.read("", &temp); if (ret < 0 || temp < 0) temp = 0; thd_log_debug("Sensor %s :temp %u\n", type_str.c_str(), temp); return (unsigned int)temp / scale; } void cthd_sensor::enable_uevent() { csys_fs cdev_sysfs("/sys/class/thermal/"); std::ostringstream policy_sysfs; policy_sysfs << "thermal_zone" << index << "/policy"; if (cdev_sysfs.exists(policy_sysfs.str().c_str())) { cdev_sysfs.write(policy_sysfs.str(), "user_space"); } } int cthd_sensor::set_threshold(int index, int temp) { if (type != SENSOR_TYPE_THERMAL_SYSFS) return THD_ERROR; std::ostringstream tcdev; std::ostringstream thres; int status = 0; if (thd_engine->get_poll_interval()) return THD_SUCCESS; if (!async_capable) { return THD_ERROR; } tcdev << "trip_point_" << index << "_temp"; thres << temp; if (sensor_sysfs.exists(tcdev.str().c_str())) { status = sensor_sysfs.write(tcdev.str(), thres.str()); } thd_log_debug("cthd_sensor::set_threshold: status %d\n", status); if (status > 0) { enable_uevent(); return THD_SUCCESS; } else return THD_ERROR; } void cthd_sensor::sensor_poll_trip(bool status) { if (status) thd_engine->thd_engine_poll_enable(index); else thd_engine->thd_engine_poll_disable(index); } void cthd_sensor::sensor_fast_poll(bool status) { if (status) thd_engine->thd_engine_fast_poll_enable(index); else thd_engine->thd_engine_fast_poll_disable(index); } thermal_daemon-2.5.12/src/thd_sensor.h000066400000000000000000000050721521301715300176720ustar00rootroot00000000000000/* * thd_sensor.h: thermal sensor class interface * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_SENSOR_H_ #define THD_SENSOR_H_ #include #include "thd_common.h" #include "thd_sys_fs.h" #include "thd_util.h" #define SENSOR_TYPE_THERMAL_SYSFS 0 #define SENSOR_TYPE_RAW 1 class cthd_sensor { protected: int index; int type; csys_fs sensor_sysfs; bool sensor_active; std::string type_str; bool async_capable; bool virtual_sensor; private: std::vector thresholds; int scale; void enable_uevent(); public: cthd_sensor(int _index, std::string control_path, std::string _type_str, int _type = SENSOR_TYPE_THERMAL_SYSFS); virtual ~cthd_sensor() { } int sensor_update(); virtual const std::string& get_sensor_type() { return type_str; } virtual const std::string& get_sensor_path() { return sensor_sysfs.get_base_path(); } virtual unsigned int read_temperature(); int get_index() { return index; } int set_threshold(int index, int temp); ; void update_path(std::string str) { if (!starts_with(str, "/sys/")) { thd_log_debug("Invalid path %s\n", str.c_str()); return; } sensor_sysfs.update_path(std::move(str)); } void set_async_capable(bool capable) { async_capable = capable; } bool check_async_capable() { return async_capable; } void set_scale(int _scale) { scale = _scale; } virtual void sensor_dump() { thd_log_info("sensor index:%d %s %s Async:%d\n", index, type_str.c_str(), sensor_sysfs.get_base_path().c_str(), async_capable); } // Even if sensors are capable of async, it is possible that it is not reliable enough // at critical monitoring point. Sensors can be forced to go to poll mode at that temp void sensor_poll_trip(bool status); void sensor_fast_poll(bool status); bool is_virtual() { return virtual_sensor; } }; #endif /* THD_SENSOR_H_ */ thermal_daemon-2.5.12/src/thd_sensor_rapl_power.cpp000066400000000000000000000026451521301715300224620ustar00rootroot00000000000000/* * thd_sensor_rapl_power.cpp: Power Sensor for RAPL * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include #include "thd_sensor_rapl_power.h" #include "thd_engine.h" cthd_sensor_rapl_power::cthd_sensor_rapl_power(int index) : cthd_sensor(index, "", "rapl_pkg_power", SENSOR_TYPE_RAW) { update_path("/sys/class/powercap/intel-rapl"); } unsigned int cthd_sensor_rapl_power::read_temperature() { thd_engine->rapl_power_meter.rapl_start_measure_power(); unsigned int pkg_power = thd_engine->rapl_power_meter.rapl_action_get_last_power(PACKAGE); pkg_power = (pkg_power / 1000); thd_log_debug("Sensor %s :power %u\n", type_str.c_str(), pkg_power); return pkg_power; } thermal_daemon-2.5.12/src/thd_sensor_rapl_power.h000066400000000000000000000021151521301715300221170ustar00rootroot00000000000000/* * thd_sensor_rapl_power.h: Power Sensor for rapl * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_SENSOR_RAPL_POWER_H #define THD_SENSOR_RAPL_POWER_H #include "thd_sensor.h" class cthd_sensor_rapl_power: public cthd_sensor { private: public: cthd_sensor_rapl_power(int index); unsigned int read_temperature() override; }; #endif thermal_daemon-2.5.12/src/thd_sensor_virtual.cpp000066400000000000000000000167231521301715300220000ustar00rootroot00000000000000/* * thd_sensor_virtual.h: thermal sensor virtual class implementation * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_sensor_virtual.h" #include "thd_engine.h" #include #include cthd_sensor_virtual::cthd_sensor_virtual(int _index, std::string _type_str, std::string& _link_type_str, double _multiplier, double _offset) : cthd_sensor(_index, "none", std::move(_type_str)), multiplier(_multiplier), offset(_offset), poll_thread(0), polling(false), poll_thread_started(false), stop_polling(false), last_temp(0), polling_period(def_polling_period) { if (!_link_type_str.empty()) { std::unique_ptr link_sensor(new link_sensor_t()); cthd_sensor *sensor = thd_engine->search_sensor(_link_type_str); link_sensor->sensor = sensor; link_sensor->offset = 0; link_sensor->coeff = 0; link_sensor->prev_avg = 0.0; link_sensors.push_back(std::move(link_sensor)); } virtual_sensor = true; } cthd_sensor_virtual::~cthd_sensor_virtual() { disable_periodic_timer(); // unique_ptr elements auto-delete, no manual cleanup needed link_sensors.clear(); } int cthd_sensor_virtual::add_target(std::string& _link_type_str, double coeff, double offset, int power_sensor) { cthd_sensor *sensor = thd_engine->search_sensor(_link_type_str); // If there is no sensor, this is treated as constant std::unique_ptr link_sensor(new link_sensor_t()); link_sensor->sensor = sensor; link_sensor->offset = offset; link_sensor->coeff = coeff; link_sensor->prev_avg = 0.0; link_sensor->power_sensor = power_sensor; link_sensors.push_back(std::move(link_sensor)); return THD_SUCCESS; } int cthd_sensor_virtual::sensor_update() { thd_log_debug("Add virtual sensor %s\n", type_str.c_str()); for (unsigned int i = 0; i < link_sensors.size(); ++i) { link_sensor_t *link_sensor = link_sensors[i].get(); if (link_sensor->sensor) { thd_log_debug("Add target sensor %s, %g %g\n", link_sensor->sensor->get_sensor_type().c_str(), link_sensor->coeff, link_sensor->offset); } else { thd_log_debug("Add constant, %g %g\n", link_sensor->coeff, link_sensor->offset); } } thd_log_info("Add virtual sensor success\n"); return THD_SUCCESS; } unsigned int cthd_sensor_virtual::read_temperature() { if (polling.load()) return static_cast(last_temp.load()); return _read_temperature(); } unsigned int cthd_sensor_virtual::_read_temperature() { double virt_temp = 0.0; int temp = 0; if (link_sensors.empty()) { thd_log_warn("Virtual sensor %s has no linked sensors\n", type_str.c_str()); return 0; } link_sensor_t *link_sensor = link_sensors[0].get(); if (link_sensor->sensor && !link_sensor->coeff && !link_sensor->offset) { temp = link_sensor->sensor->read_temperature(); temp = temp * multiplier + offset; thd_log_debug("cthd_sensor_virtual::read_temperature %d\n", temp); unsigned int clamped_temp = temp < 0 ? 0u : static_cast(temp); last_temp.store(clamped_temp); return clamped_temp; } for (unsigned int i = 0; i < link_sensors.size(); ++i) { link_sensor_t *link_sensor = link_sensors[i].get(); std::string link_sensor_name; if (link_sensor->sensor) { link_sensor_name = link_sensor->sensor->get_sensor_type(); temp = link_sensor->sensor->read_temperature(); } else { temp = 1000; link_sensor_name = "constant"; } double avg; double current_virt_temp; if (!link_sensor->power_sensor) temp = temp / 1000; //convert to degree C if (!link_sensor->prev_avg) link_sensor->prev_avg = (double)temp; avg = link_sensor->offset * (double) temp + ((1.0 - link_sensor->offset) * link_sensor->prev_avg); current_virt_temp = link_sensor->coeff * avg; link_sensor->prev_avg = avg; virt_temp += current_virt_temp; thd_log_debug("%s-> temp[%d] avg[%g] coeff[%g], result[%g] \n", link_sensor_name.c_str(), temp, avg, current_virt_temp, virt_temp); } thd_log_debug("virt temp:%g\n", virt_temp); temp = static_cast(std::round(virt_temp)); int temp_mc = temp * 1000; if (temp_mc < 0) temp_mc = 0; // Keep cache and return value in the same unit (mC). last_temp.store(temp_mc); return static_cast(temp_mc); } int cthd_sensor_virtual::sensor_update_param(const std::string& new_dep_sensor, double slope, double intercept) { cthd_sensor *sensor = thd_engine->search_sensor(new_dep_sensor); if (sensor) { std::unique_ptr link_sensor(new link_sensor_t()); link_sensor->sensor = sensor; link_sensor->offset = 0; link_sensor->coeff = 0; link_sensor->prev_avg = 0.0; link_sensors.push_back(std::move(link_sensor)); } else return THD_ERROR; multiplier = slope; offset = intercept; return THD_SUCCESS; } void cthd_sensor_virtual::update_polling_table(struct polling_table_entry& entry) { polling_table.push_back(entry); } static void *periodic_callback(void *data) { cthd_sensor_virtual *sensor = (cthd_sensor_virtual *) data; for (;;) { if (sensor->stop_polling.load()) { thd_log_info("periodic callback exit\n"); break; } sensor->_read_temperature(); int period = 0; for (size_t i = sensor->polling_table.size(); i > 0; --i) { struct polling_table_entry entry = sensor->polling_table[i - 1]; if (sensor->last_temp.load() >= entry.virtual_temp) { period = entry.sample_period; break; } } if (period) { sensor->polling_period.store(period); thd_log_debug("Update Sample period is set to %d seconds\n", period); } int sleep_period = sensor->polling_period.load(); if (sleep_period <= 0) sleep_period = cthd_sensor_virtual::def_polling_period; sleep(sleep_period); } return nullptr; } void cthd_sensor_virtual::enable_periodic_timer() { int period = 0; int invalid_poll = 100; thd_log_info("Virtual sensor periodic timer enabled\n"); if (polling_table.size() >= 1) { if (polling_table[0].sample_period < invalid_poll) period = polling_table[0].sample_period; } if (period) { polling_period.store(period); thd_log_info("Sample period is set to %d seconds\n", polling_period.load()); } if (polling.load()) return; stop_polling.store(false); pthread_attr_init(&thd_attr); pthread_attr_setdetachstate(&thd_attr, PTHREAD_CREATE_JOINABLE); if (pthread_create(&poll_thread, &thd_attr, periodic_callback, (void*) this) != 0) { thd_log_warn("Virtual sensor periodic timer create failed\n"); pthread_attr_destroy(&thd_attr); return; } poll_thread_started = true; polling.store(true); } void cthd_sensor_virtual::disable_periodic_timer() { if (!polling.load()) return; thd_log_info("Virtual sensor periodic timer disabled\n"); stop_polling.store(true); if (poll_thread_started) { pthread_join(poll_thread, nullptr); poll_thread_started = false; pthread_attr_destroy(&thd_attr); } polling.store(false); } thermal_daemon-2.5.12/src/thd_sensor_virtual.h000066400000000000000000000053521521301715300214410ustar00rootroot00000000000000/* * thd_sensor_virtual.h: thermal sensor virtual class interface * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_SENSOR_VIRTUAL_H_ #define THD_SENSOR_VIRTUAL_H_ #include "thd_sensor.h" #include #include #include typedef struct _link_sensor_t { cthd_sensor *sensor; double coeff; double offset; double prev_avg; int power_sensor; } link_sensor_t; struct polling_table_entry { int virtual_temp; int sample_period; }; class cthd_sensor_virtual: public cthd_sensor { private: std::vector > link_sensors; double multiplier; double offset; pthread_t poll_thread; pthread_attr_t thd_attr; std::atomic polling; bool poll_thread_started; public: std::atomic stop_polling; std::atomic last_temp; static const int def_polling_period = 5; //seconds std::atomic polling_period; std::vector polling_table; cthd_sensor_virtual(int _index, std::string _type_str, std::string& _link_type_str, double multiplier, double offset); ~cthd_sensor_virtual() override; int add_target(std::string& _link_type_str, double coeff, double offset, int power_sensor); int sensor_update(); unsigned int _read_temperature(); unsigned int read_temperature() override; void sensor_dump() override { thd_log_info("Sensor:%s \n", type_str.c_str()); for (unsigned int i = 0; i < link_sensors.size(); ++i) { link_sensor_t *link_sensor = link_sensors[i].get(); if (link_sensor->sensor) { thd_log_info("\tAdd target sensor %s, %g %g\n", link_sensor->sensor->get_sensor_type().c_str(), link_sensor->coeff, link_sensor->offset); } else { thd_log_info("\tAdd target CONSTANT, %g %g\n", link_sensor->coeff, link_sensor->offset); } } } int sensor_update_param(const std::string& new_dep_sensor, double slope, double intercept); void enable_periodic_timer(); void disable_periodic_timer(); void update_polling_table(struct polling_table_entry& entry); }; #endif /* THD_SENSOR_VIRTUAL_H_ */ thermal_daemon-2.5.12/src/thd_sys_fs.cpp000066400000000000000000000162421521301715300202230ustar00rootroot00000000000000/* * thd_sys_fs.cpp: sysfs class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_sys_fs.h" #include "thd_common.h" #include "thd_util.h" #include #include #include #include namespace { int open_validated_sysfs_write_fd(const std::string &path) { char resolved_path[PATH_MAX]; if (realpath(path.c_str(), resolved_path) == nullptr) return -errno; if (!starts_with(resolved_path, "/sys/")) return -EPERM; int fd = ::open(resolved_path, O_WRONLY | O_NOFOLLOW | O_CLOEXEC); if (fd < 0) return -errno; return fd; } } csys_fs::~csys_fs() { for (auto &entry : fd_cache) close(entry.second); } int csys_fs::get_cached_fd(const std::string &full_path) { auto it = fd_cache.find(full_path); if (it != fd_cache.end()) return it->second; int fd = ::open(full_path.c_str(), O_RDONLY | O_NOFOLLOW); if (fd >= 0) fd_cache[full_path] = fd; return fd; } int csys_fs::check_non_symbolic_path(const std::string& path) { struct stat stat; if (lstat(path.c_str(), &stat) == -1) { return -errno; } if (S_ISLNK(stat.st_mode)) return THD_ERROR; return THD_SUCCESS; } int csys_fs::write(const std::string &path, const std::string &buf) { std::string p = base_path + path; int fd = open_validated_sysfs_write_fd(p); if (fd < 0) { thd_log_info("sysfs write failed %s\n", path.c_str()); return fd; } int ret = ::write(fd, buf.c_str(), buf.size()); if (ret < 0) { ret = -errno; thd_log_info("sysfs write failed %s\n", path.c_str()); } close(fd); return ret; } int csys_fs::write(const std::string &path, unsigned int position, unsigned long long data) { std::string p = base_path + path; int fd = open_validated_sysfs_write_fd(p); if (fd < 0) { thd_log_info("sysfs write failed %s\n", path.c_str()); return fd; } if (::lseek(fd, position, SEEK_CUR) == -1) { thd_log_info("sysfs write failed %s\n", path.c_str()); close(fd); return -errno; } int ret = ::write(fd, &data, sizeof(data)); if (ret < 0) thd_log_info("sysfs write failed %s\n", path.c_str()); close(fd); return ret; } int csys_fs::write(const std::string &path, unsigned int data) { std::ostringstream os; os << data; return csys_fs::write(path, os.str()); } int csys_fs::read(const std::string &path, char *buf, int len) { if (!buf) return -EINVAL; std::string p = base_path + path; int fd = get_cached_fd(p); size_t curr_len = len; off_t offset = 0; if (fd < 0) { thd_log_info("sysfs read failed %s\n", p.c_str()); return -errno; } while (curr_len > 0) { ssize_t ret = ::pread(fd, buf, curr_len, offset); if (ret <= 0 || ret > len || ret >= INT_MAX) { thd_log_info("sysfs read failed %s\n", p.c_str()); return -1; } buf += (int) ret; offset += ret; curr_len -= ret; } return len; } int csys_fs::read(const std::string &path, unsigned int position, char *buf, int len) { std::string p = base_path + path; int fd = get_cached_fd(p); if (fd < 0) { thd_log_info("sysfs read failed %s\n", p.c_str()); return -errno; } int ret = ::pread(fd, buf, len, position); if (ret < 0) thd_log_info("sysfs read failed %s\n", p.c_str()); return ret; } int csys_fs::read(const std::string &path, int *ptr_val) { std::string p = base_path + path; char str[16]; int fd = get_cached_fd(p); if (fd < 0) { thd_log_info("sysfs open failed %s\n", p.c_str()); return -errno; } int ret = ::pread(fd, str, sizeof(str) - 1, 0); if (ret > 0) { str[ret] = '\0'; if (parse_int_value(std::string(str), ptr_val, INT_MIN, INT_MAX) != 0) { thd_log_error("sysfs parse failed for path: %s\n", p.c_str()); return -EINVAL; } } else thd_log_info("sysfs read failed %s\n", p.c_str()); return ret; } int csys_fs::read(const std::string &path, unsigned long *ptr_val) { std::string p = base_path + path; char str[32]; int fd = get_cached_fd(p); if (fd < 0) { thd_log_info("sysfs read failed %s\n", p.c_str()); return -errno; } int ret = ::pread(fd, str, sizeof(str) - 1, 0); if (ret > 0) { str[ret] = '\0'; *ptr_val = atol(str); } else thd_log_info("sysfs read failed %s\n", p.c_str()); return ret; } int csys_fs::read(const std::string &path, std::string &buf) { std::string p = base_path + path; int ret = 0; #ifndef ANDROID try { #endif int ret = check_non_symbolic_path(p); if (ret == THD_ERROR) { thd_log_info("sysfs read failed [path is symbolic link] %s\n", p.c_str()); return -EINVAL; } std::ifstream f(p.c_str(), std::fstream::in); if (f.fail()) { thd_log_info("sysfs read failed %s\n", p.c_str()); return -EINVAL; } f >> buf; if (f.bad()) { f.close(); thd_log_info("sysfs read failed %s\n", p.c_str()); return -EIO; } f.close(); #ifndef ANDROID } catch (...) { thd_log_info("csys_fs::read exception %s\n", p.c_str()); ret = -EIO; } #endif return ret; } bool csys_fs::exists(const std::string &path) { struct stat s; return (bool) (stat((base_path + path).c_str(), &s) == 0); } size_t csys_fs::size(const std::string &path) { struct stat s; if (stat((base_path + path).c_str(), &s) == 0) return s.st_size; return 0; } int csys_fs::create(int flags, mode_t mode) { mode_t _mode = mode | O_NOFOLLOW; thd_log_debug("create :%s\n", base_path.c_str()); if (base_path.empty() || base_path.front() != '/') { thd_log_debug("Invalid base_path:%s\n", base_path.c_str()); return -1; } std::istringstream path(base_path.c_str()); std::vector < std::string > tokens; std::string token; while (std::getline(path, token, '/')) { tokens.push_back(token); } std::ostringstream _path; for (const auto &word : tokens) { if (word.empty()) continue; _path << "/" << word; int ret = check_non_symbolic_path(_path.str()); if (ret == THD_ERROR) { thd_log_info("path :%s is symbolic link\n", _path.str().c_str()); return -1; } } int fd = ::open(base_path.c_str(), flags, _mode); if (fd < 0) { thd_log_info("sysfs create failed %s\n", base_path.c_str()); return -errno; } close(fd); return 0; } bool csys_fs::exists() { return csys_fs::exists(""); } mode_t csys_fs::get_mode(const std::string &path) { struct stat s; if (stat((base_path + path).c_str(), &s) == 0) return s.st_mode; else return 0; } int csys_fs::read_symbolic_link_value(const std::string &path, char *buf, int len) { std::string p = base_path + path; int ret = ::readlink(p.c_str(), buf, len); if (ret < 0) { *buf = '\0'; thd_log_info("read_symbolic_link %s\n", path.c_str()); return -errno; } buf[ret] = '\0'; return 0; } thermal_daemon-2.5.12/src/thd_sys_fs.h000066400000000000000000000051261521301715300176670ustar00rootroot00000000000000/* * thd_sys_fs.h: sysfs class interface * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_SYS_FS_H_ #define THD_SYS_FS_H_ #include #include #include #include #include #include #include #include #include #include class csys_fs { private: std::string base_path; std::unordered_map fd_cache; int get_cached_fd(const std::string &full_path); public: csys_fs() : base_path("") { } csys_fs(std::string path) : base_path(std::move(path)) { } ~csys_fs(); csys_fs(const csys_fs &) = delete; csys_fs &operator=(const csys_fs &) = delete; csys_fs(csys_fs &&) = delete; csys_fs &operator=(csys_fs &&) = delete; /* write data to base path (dir) + provided path */ int write(const std::string &path, const std::string &buf); int write(const std::string &path, unsigned int data); int write(const std::string &path, unsigned int position, unsigned long long data); /* read data from base path (dir) + provided path */ int read(const std::string &path, char *buf, int len); int read(const std::string &path, std::string &buf); int read(const std::string &path, int *ptr_val); int read(const std::string &path, unsigned long *ptr_val); int read(const std::string &path, unsigned int position, char *buf, int len); const std::string& get_base_path() { return base_path; } int read_symbolic_link_value(const std::string &path, char *buf, int len); bool exists(const std::string &path); bool exists(); size_t size(const std::string &path); int create(int flags = (O_CREAT | O_WRONLY | O_TRUNC), mode_t mode = 0600); mode_t get_mode(const std::string &path); int check_non_symbolic_path(const std::string& path); void update_path(std::string path) { base_path = std::move(path); } }; #endif /* THD_SYS_FS_H_ */ thermal_daemon-2.5.12/src/thd_trip_point.cpp000066400000000000000000000246041521301715300211050ustar00rootroot00000000000000/* * thd_trip_point.cpp: thermal zone class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include #include #include #include #include "thd_trip_point.h" #include "thd_engine.h" cthd_trip_point::cthd_trip_point(int _index, trip_point_type_t _type, unsigned int _temp, unsigned int _hyst, int _zone_id, int _sensor_id, trip_control_type_t _control_type) : index(_index), type(_type), temp(_temp), hyst(_hyst), control_type( _control_type), zone_id(_zone_id), sensor_id(_sensor_id), trip_on( false), poll_on(false), depend_cdev(nullptr), depend_cdev_state(0), depend_cdev_state_rel( EQUAL), crit_trip_count(0) { thd_log_debug("Add trip pt %d:%d:0x%x:%d:%d\n", type, zone_id, sensor_id, temp, hyst); } void cthd_trip_point::set_dependency(const std::string& cdev, const std::string& state_str) { cthd_cdev *cdev_ptr; cdev_ptr = thd_engine->search_cdev(cdev); if (cdev_ptr) { int match; int state_index = 0; depend_cdev = cdev_ptr; match = state_str.compare(0, 2, "=="); if (!match) { depend_cdev_state_rel = EQUAL; state_index = 2; } match = state_str.compare(0, 1, ">"); if (!match) { depend_cdev_state_rel = GREATER; state_index = 1; } match = state_str.compare(0, 1, "<"); if (!match) { state_index = 1; depend_cdev_state_rel = LESSER; } match = state_str.compare(0, 2, "<="); if (!match) { depend_cdev_state_rel = LESSER_OR_EQUAL; state_index = 2; } match = state_str.compare(0, 2, ">="); if (!match) { depend_cdev_state_rel = GREATER_OR_EQUAL; state_index = 2; } depend_cdev_state = atoi(state_str.substr(state_index).c_str()); } } bool cthd_trip_point::thd_trip_point_check(int id, unsigned int read_temp, int pref, bool *reset) { int on = -1; int off = -1; bool apply = false; *reset = false; if (type == INVALID_TRIP_TYPE) return false; if (depend_cdev && read_temp >= temp) { int _state = depend_cdev->get_curr_state(); int valid = 0; switch (depend_cdev_state_rel) { case EQUAL: if (_state == depend_cdev_state) valid = 1; break; case GREATER: if (_state > depend_cdev_state) valid = 1; break; case LESSER: if (_state < depend_cdev_state) valid = 1; break; case LESSER_OR_EQUAL: if (_state <= depend_cdev_state) valid = 1; break; case GREATER_OR_EQUAL: if (_state >= depend_cdev_state) valid = 1; break; default: break; } if (!valid) { thd_log_info("constraint failed %s:%d:%d:%d\n", depend_cdev->get_cdev_type().c_str(), _state, depend_cdev_state_rel, depend_cdev_state); return false; } } if (sensor_id != DEFAULT_SENSOR_ID && sensor_id != id) return false; if (read_temp == 0) { thd_log_debug("TEMP == 0 pref: %d\n", pref); } if (type == CRITICAL) { if (!ignore_critical && read_temp >= temp) { thd_log_warn("critical temp reached\n"); if (crit_trip_count < consecutive_critical_events) { ++crit_trip_count; return true; } crit_trip_count = 0; sync(); #ifdef ANDROID int ret; ret = property_set("sys.powerctl", "shutdown,thermal"); if (ret != 0) thd_log_warn("power off failed ret=%d err=%s\n", ret, strerror(errno)); else thd_log_warn("power off initiated\n"); #else thd_log_warn("power off initiated\n"); reboot(RB_POWER_OFF); #endif return true; } crit_trip_count = 0; } if (type == HOT) { if (!ignore_critical && read_temp >= temp) { thd_log_warn("Hot temp reached\n"); if (crit_trip_count < consecutive_critical_events) { ++crit_trip_count; return true; } crit_trip_count = 0; thd_log_warn("Hot temp reached\n"); csys_fs power("/sys/power/"); power.write("state", "mem"); return true; } crit_trip_count = 0; } if (type == POLLING && sensor_id != DEFAULT_SENSOR_ID) { cthd_sensor *sensor = thd_engine->get_sensor(sensor_id); if (sensor) { if (!poll_on && read_temp >= temp) { thd_log_debug("polling trip reached, on\n"); sensor->sensor_poll_trip(true); poll_on = true; sensor->sensor_fast_poll(true); if (sensor->check_async_capable()) sensor->set_threshold(0, temp); } else if (poll_on && read_temp < temp) { sensor->sensor_poll_trip(false); thd_log_debug("Dropped below poll threshold\n"); *reset = true; poll_on = false; sensor->sensor_fast_poll(false); if (sensor->check_async_capable()) sensor->set_threshold(0, temp); } } return true; } thd_log_debug("pref %d type %d temp %d trip %d\n", pref, type, read_temp, temp); switch (pref) { case PREF_DISABLED: return false; break; case PREF_PERFORMANCE: if (type == ACTIVE || type == MAX) { apply = true; thd_log_debug("Active Trip point applicable\n"); } break; case PREF_ENERGY_CONSERVE: if (type == PASSIVE || type == MAX) { apply = true; thd_log_debug("Passive Trip point applicable\n"); } break; default: break; } if (apply) { if (read_temp >= temp) { thd_log_debug("Trip point applicable > %d:%d\n", index, temp); on = 1; trip_on = true; } else if ((trip_on && (read_temp + hyst) < temp) || (!trip_on && read_temp < temp)) { thd_log_debug("Trip point applicable < %d:%d\n", index, temp); off = 1; trip_on = false; } } else return false; if (on != 1 && off != 1) return true; int i, ret; thd_log_debug("cdev size for this trippoint %lu\n", (unsigned long) cdevs.size()); if (on > 0) { for (unsigned i = 0; i < cdevs.size(); ++i) { cthd_cdev *cdev = cdevs[i].cdev; if (cdevs[i].sampling_priod) { time_t tm; time(&tm); if ((tm - cdevs[i].last_op_time) < cdevs[i].sampling_priod) { thd_log_debug("Too early to act zone:%d index %d tm %jd\n", zone_id, cdev->thd_cdev_get_index(), (intmax_t)tm - cdevs[i].last_op_time); break; } cdevs[i].last_op_time = tm; } thd_log_debug("cdev at index %d:%s\n", cdev->thd_cdev_get_index(), cdev->get_cdev_type().c_str()); /* * When the cdev is already in max state, we skip this cdev. */ if (cdev->in_max_state()) { thd_log_debug("Need to switch to next cdev target %d\n", cdev->map_target_state(cdevs[i].target_state_valid, cdevs[i].target_state)); // No scope of control with this cdev continue; } if (cdevs[i].target_state == TRIP_PT_INVALID_TARGET_STATE) cdevs[i].target_state = cdev->get_min_state(); ret = cdev->thd_cdev_set_state(temp, temp, read_temp, (type == MAX), 1, zone_id, index, cdevs[i].target_state_valid, cdev->map_target_state(cdevs[i].target_state_valid, cdevs[i].target_state), &cdevs[i].pid_param, cdevs[i].pid, false, cdevs[i].min_max_valid, cdevs[i].min_state, cdevs[i].max_state); if (control_type == SEQUENTIAL && ret == THD_SUCCESS) { // Only one cdev activation break; } } } if (off > 0) { for (i = cdevs.size() - 1; i >= 0; --i) { cthd_cdev *cdev = cdevs[i].cdev; thd_log_debug("cdev at index %d:%s\n", cdev->thd_cdev_get_index(), cdev->get_cdev_type().c_str()); if (cdev->in_min_state()) { thd_log_debug("Need to switch to next cdev\n"); // No scope of control with this cdev continue; } if (cdevs[i].target_state == TRIP_PT_INVALID_TARGET_STATE) cdevs[i].target_state = cdev->get_min_state(); cdev->thd_cdev_set_state(temp, temp, read_temp, (type == MAX), 0, zone_id, index, cdevs[i].target_state_valid, cdev->map_target_state(cdevs[i].target_state_valid, cdevs[i].target_state), &cdevs[i].pid_param, cdevs[i].pid, false, cdevs[i].min_max_valid, cdevs[i].min_state, cdevs[i].max_state); if (control_type == SEQUENTIAL) { // Only one cdev activation break; } } } return true; } void cthd_trip_point::thd_trip_point_add_cdev(cthd_cdev &cdev, int influence, int sampling_period, int target_state_valid, int target_state, pid_param_t *pid_param, int min_max_valid, int min_state, int max_state) { trip_pt_cdev_t thd_cdev = {}; thd_cdev.cdev = &cdev; thd_cdev.influence = influence; thd_cdev.sampling_priod = sampling_period; thd_cdev.last_op_time = 0; thd_cdev.target_state_valid = target_state_valid; thd_cdev.target_state = target_state; thd_cdev.min_max_valid = min_max_valid; thd_log_info("min:%d max:%d\n", min_state, max_state); if (min_max_valid) { thd_cdev.min_state = min_state; thd_cdev.max_state = max_state; } if (pid_param && pid_param->valid) { thd_log_info("pid valid %f:%f:%f\n", pid_param->kp, pid_param->ki, pid_param->kd); memcpy(&thd_cdev.pid_param, pid_param, sizeof(pid_param_t)); thd_cdev.pid.set_pid_param(pid_param->kp, pid_param->ki, pid_param->kd); } else { memset(&thd_cdev.pid_param, 0, sizeof(pid_param_t)); } trip_cdev_add(thd_cdev); } int cthd_trip_point::thd_trip_point_add_cdev_index(int _index, int influence) { cthd_cdev *cdev = thd_engine->thd_get_cdev_at_index(_index); if (cdev) { trip_pt_cdev_t thd_cdev = {}; thd_cdev.cdev = cdev; thd_cdev.influence = influence; thd_cdev.sampling_priod = 0; thd_cdev.last_op_time = 0; thd_cdev.target_state_valid = 0; trip_cdev_add(thd_cdev); return THD_SUCCESS; } else { thd_log_warn("thd_trip_point_add_cdev_index not present %d\n", _index); return THD_ERROR; } } void cthd_trip_point::thd_trip_cdev_state_reset(int force) { thd_log_debug("thd_trip_cdev_state_reset\n"); for (int i = cdevs.size() - 1; i >= 0; --i) { cthd_cdev *cdev = cdevs[i].cdev; thd_log_debug("thd_trip_cdev_state_reset index %d:%s\n", cdev->thd_cdev_get_index(), cdev->get_cdev_type().c_str()); if (!force && cdev->in_min_state()) { thd_log_debug("Need to switch to next cdev\n"); // No scope of control with this cdev continue; } cdev->thd_cdev_set_min_state(zone_id, index); } } thermal_daemon-2.5.12/src/thd_trip_point.h000066400000000000000000000166671521301715300205640ustar00rootroot00000000000000/* * thd_trip_point.h: thermal zone trip points class interface * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_TRIP_POINT_H #define THD_TRIP_POINT_H #include "thd_common.h" #include "thd_sys_fs.h" #include "thd_preference.h" #include "thd_cdev.h" #define __STDC_LIMIT_MACROS #include #include #include #include // std::sort #include typedef enum : uint8_t { CRITICAL, HOT, MAX, PASSIVE, ACTIVE, POLLING, INVALID_TRIP_TYPE } trip_point_type_t; typedef enum : uint8_t { PARALLEL, // All associated cdevs are activated together SEQUENTIAL // one after other once the previous cdev reaches its max state } trip_control_type_t; #define TRIP_PT_INVALID_TARGET_STATE INT32_MAX typedef enum : uint8_t { EQUAL, GREATER, LESSER, LESSER_OR_EQUAL, GREATER_OR_EQUAL } trip_point_cdev_depend_rel_t; class trip_pt_cdev_t{ public: cthd_cdev *cdev; int influence; int sampling_priod; time_t last_op_time; int target_state_valid; int target_state; pid_param_t pid_param; cthd_pid pid; int min_max_valid; int min_state; int max_state; trip_pt_cdev_t() { cdev = nullptr; influence = 0; sampling_priod = 0; last_op_time = 0; target_state_valid = 0; target_state = 0; pid_param.valid = 0; pid_param.kp = 0; pid_param.ki = 0; pid_param.kd = 0; min_max_valid = 0; min_state = 0; max_state = 0; } trip_pt_cdev_t(const trip_pt_cdev_t& x) = default; ~trip_pt_cdev_t() { } trip_pt_cdev_t& operator=(const trip_pt_cdev_t& x) = default; bool operator > (const trip_pt_cdev_t& other) const { return influence > other.influence; } bool operator < (const trip_pt_cdev_t& other) const { return influence < other.influence; } } ; #define DEFAULT_SENSOR_ID 0xFFFF static bool trip_cdev_sort(trip_pt_cdev_t &cdev1, trip_pt_cdev_t &cdev2) { return (cdev1.influence > cdev2.influence); } class cthd_trip_point { private: int index; trip_point_type_t type; unsigned int temp; unsigned int hyst; std::vector cdevs; trip_control_type_t control_type; int zone_id; int sensor_id; bool trip_on; bool poll_on; cthd_cdev *depend_cdev; int depend_cdev_state; trip_point_cdev_depend_rel_t depend_cdev_state_rel; int crit_trip_count; bool check_duplicate(cthd_cdev *cdev, int *index) { for (unsigned int i = 0; i < cdevs.size(); ++i) { if (cdevs[i].cdev->get_cdev_type() == cdev->get_cdev_type()) { *index = i; return true; } } return false; } public: static constexpr int default_influence = 0; static constexpr int consecutive_critical_events = 4; cthd_trip_point(int _index, trip_point_type_t _type, unsigned int _temp, unsigned int _hyst, int _zone_id, int _sensor_id, trip_control_type_t _control_type = PARALLEL); bool thd_trip_point_check(int id, unsigned int read_temp, int pref, bool *reset); void thd_trip_point_add_cdev(cthd_cdev &cdev, int influence, int sampling_period = 0, int target_state_valid = 0, int target_state = TRIP_PT_INVALID_TARGET_STATE, pid_param_t *pid_param = nullptr, int min_max_valid = 0, int min_state = 0, int max_state = 0); void delete_cdevs() { cdevs.clear(); } void thd_trip_cdev_state_reset(int force = 0); int thd_trip_point_value() { return temp; } void thd_trip_update_set_point(unsigned int new_value) { temp = new_value; } int thd_trip_point_add_cdev_index(int _index, int influence); void thd_trip_point_set_control_type(trip_control_type_t type) { control_type = type; } trip_point_type_t get_trip_type() { return type; } unsigned int get_trip_temp() const { return temp; } unsigned int get_trip_hyst() { return hyst; } void update_trip_temp(unsigned int _temp) { temp = _temp; } void update_trip_type(trip_point_type_t _type) { type = _type; } void update_trip_hyst(unsigned int _temp) { hyst = _temp; } int get_sensor_id() { return sensor_id; } unsigned int get_cdev_count() { return cdevs.size(); } int is_target_valid(int &target_state) { target_state = 0; for (unsigned int i = 0; i < cdevs.size(); ++i) { trip_pt_cdev_t &cdev = cdevs[i]; if (cdev.target_state_valid) { thd_log_debug("matched %d\n", cdev.target_state); target_state = cdev.target_state; return THD_SUCCESS; } } return THD_ERROR; } int set_first_target_invalid() { if (cdevs.size()) { trip_pt_cdev_t &cdev = cdevs[0]; cdev.target_state_valid = 0; return THD_SUCCESS; } return THD_ERROR; } int set_first_target(int state) { if (cdevs.size()) { trip_pt_cdev_t &cdev = cdevs[0]; cdev.target_state_valid = 1; cdev.target_state = state; return THD_SUCCESS; } return THD_ERROR; } cthd_cdev* get_first_cdev() { if (!cdevs.size()) return nullptr; return cdevs[0].cdev; } void set_dependency(const std::string& cdev, const std::string& state_str); #ifndef ANDROID trip_pt_cdev_t &get_cdev_at_index(unsigned int index) { if (index < cdevs.size()) return cdevs[index]; else throw std::invalid_argument("index"); } #endif void trip_cdev_add(trip_pt_cdev_t &trip_cdev) { int index; if (check_duplicate(trip_cdev.cdev, &index)) { cdevs[index].influence = trip_cdev.influence; } else cdevs.push_back(trip_cdev); std::sort(cdevs.begin(), cdevs.end(), trip_cdev_sort); } void trip_dump() { std::string _type_str; if (type == CRITICAL) _type_str = "critical"; else if (type == MAX) _type_str = "max"; else if (type == PASSIVE) _type_str = "passive"; else if (type == ACTIVE) _type_str = "active"; else if (type == POLLING) _type_str = "polling"; else if (type == HOT) _type_str = "hot"; else _type_str = "invalid"; thd_log_info( "index %d: type:%s temp:%u hyst:%u zone id:%d sensor id:%d control_type:%d cdev size:%lu\n", index, _type_str.c_str(), temp, hyst, zone_id, sensor_id, control_type, (unsigned long) cdevs.size()); if (depend_cdev) { thd_log_info("Depends on cdev %s:%d:%d\n", depend_cdev->get_cdev_type().c_str(), depend_cdev_state_rel, depend_cdev_state); } for (unsigned int i = 0; i < cdevs.size(); ++i) { thd_log_info("cdev[%u] %s, Sampling period: %d\n", i, cdevs[i].cdev->get_cdev_type().c_str(), cdevs[i].sampling_priod); if (cdevs[i].target_state_valid) thd_log_info("\t target_state:%d\n", cdevs[i].target_state); else thd_log_info("\t target_state:not defined\n"); thd_log_info("min_max %d\n", cdevs[i].min_max_valid); if (cdevs[i].pid_param.valid) thd_log_info("\t pid: kp=%g ki=%g kd=%g\n", cdevs[i].pid_param.kp, cdevs[i].pid_param.ki, cdevs[i].pid_param.kd); if (cdevs[i].min_max_valid) { thd_log_info("\t min_state:%d\n", cdevs[i].min_state); thd_log_info("\t max_state:%d\n", cdevs[i].max_state); } } } }; #endif thermal_daemon-2.5.12/src/thd_trt_art_reader.cpp000066400000000000000000000503211521301715300217120ustar00rootroot00000000000000/* * thd_trt_art_reader.cpp: Create configuration using ACPI * _ART and _TRT tables * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include #include #include "thd_common.h" #include "thd_sys_fs.h" #include "thd_trt_art_reader.h" #include "acpi_thermal_rel_ioct.h" #include "thd_int3400.h" using namespace std; #define PRINT_ERROR(...) thd_log_info(__VA_ARGS__) #define PRINT_DEBUG(...) thd_log_debug(__VA_ARGS__) typedef struct { const char *source; const char *sub_string; } sub_string_t; const sub_string_t source_substitue_strings[] = { { "TPCH", "pch_wildcat_point" }, { nullptr, nullptr } }; const sub_string_t target_substitue_strings[] = { { "B0D4", "rapl_controller" }, { "DPLY", "LCD" }, { "DISP", "LCD" }, { "TMEM", "rapl_controller_dram" }, { "TCPU", "rapl_controller" }, { "B0DB", "rapl_controller" }, { nullptr, nullptr } }; const sub_string_t sensor_substitue_strings[] = { { "TPCH", "pch_wildcat_point" }, { nullptr, nullptr } }; typedef enum : uint8_t { TARGET_DEV, SOURCE_DEV, SENSOR_DEV } sub_type_t; /* * The _TRT and _ART table may refer to entry, for which we have * we need to tie to some control device, which is not enumerated * as a thermal cooling device. In this case, we substitute them * to an inbuilt standard name. */ static void associate_device(sub_type_t type, string &name) { DIR *dir; struct dirent *entry; std::string base_path = "/sys/bus/platform/devices/"; if ((dir = opendir(base_path.c_str())) != nullptr) { while ((entry = readdir(dir)) != nullptr) { if (!strncmp(entry->d_name, "INT340", strlen("INT340"))) { char buf[256]; int ret; std::string name_path = base_path + entry->d_name + "/firmware_node"; ret = readlink(name_path.c_str(), buf, sizeof(buf) - 1); if (ret > 0) { buf[ret] = '\0'; name_path.clear(); name_path = base_path + entry->d_name + "/" + std::string(buf) + "/"; csys_fs acpi_sysfs(name_path); std::string uid; if (acpi_sysfs.exists("uid")) { ret = acpi_sysfs.read("uid", uid); if (ret < 0) continue; } else if (acpi_sysfs.exists("path")) { ret = acpi_sysfs.read("path", uid); if (ret < 0) continue; size_t pos = uid.find_last_of('.'); if (pos != std::string::npos) { uid = uid.substr(pos + 1); } } else continue; if (name == uid) { if (name_path.find("INT3406") != std::string::npos) { name = "DISP"; } else if (name_path.find("INT3402") != std::string::npos) { name = "TMEM"; } else if (name_path.find("INT3401") != std::string::npos) { name = "TCPU"; } closedir(dir); return; } } } } closedir(dir); } } static void subtitute_string(sub_type_t type, string &name) { int i = 0; const sub_string_t *list; if (type == TARGET_DEV) { associate_device(type, name); list = target_substitue_strings; } else if (type == SOURCE_DEV) list = source_substitue_strings; else list = sensor_substitue_strings; while (list[i].source) { if (name == list[i].source) { name = list[i].sub_string; break; } i++; } } cthd_acpi_rel::cthd_acpi_rel() : rel_cdev("/dev/acpi_thermal_rel"), xml_hdr("\n"), conf_begin( "\n"), conf_end( "\n"), conf_file(), trt_count( 0), art_count(0), psvt_count(0) { } int cthd_acpi_rel::process_psvt(const std::string& file_name) { cthd_INT3400 int3400 ("9E04115A-AE87-4D1C-9500-0F3E340BFE75"); std::string prefix; int ret; PRINT_DEBUG("Trying presence of PSVT\n"); if (int3400.match_supported_uuid () != THD_SUCCESS) { thd_log_info ("Passive 2 UUID is not present, hence ignore PSVT, as it may have junk!!\n"); return THD_ERROR; } ret = read_psvt (); if (ret) return ret; dump_psvt (); conf_file.open (file_name.c_str ()); if (!conf_file.is_open ()) { PRINT_ERROR("failed to open output file [%s]\n", file_name.c_str ()); return THD_ERROR; } conf_file << xml_hdr; conf_file << conf_begin; prefix = indentation = "\t"; conf_file << indentation << "" << "\n"; create_platform_conf (); create_platform_pref (0); prefix = indentation; conf_file << prefix << "" << "\n"; // Read PSVT parse_target_devices (); indentation += "\t"; for (unsigned long i = 0; i < rel_list.size (); ++i) { PRINT_DEBUG("Add target target, i::%lu.\n", i); if (rel_list[i].target_device == "") { PRINT_ERROR("Empty target, skipping ..\n"); continue; } conf_file << prefix << "\t" << "" << "\n"; subtitute_string (SOURCE_DEV, rel_list[i].target_device); conf_file << prefix << "\t\t" << "" << rel_list[i].target_device << "" << "\n"; conf_file << prefix << "\t\t" << "" << "\n"; add_psvt_trip_point (rel_list[i]); conf_file << prefix << "\t\t" << "" << "\n"; conf_file << prefix << "\t" << "" << "\n"; // } conf_file << prefix << "" << "\n"; conf_file << prefix << "" << "\n"; conf_file << conf_end; conf_file.close (); return THD_SUCCESS; } int cthd_acpi_rel::generate_conf(const std::string& file_name) { int trt_status; string prefix; int art_status; int ret = 0; cthd_INT3400 int3400("42A441D6-AE6A-462b-A84B-4A8CE79027D3"); std::ifstream conf_file_check(file_name.c_str()); if (conf_file_check.is_open()) { PRINT_ERROR(" File Exists: file_name %s, so no regenerate\n", file_name.c_str()); conf_file_check.close(); return 0; } conf_file_check.close(); art_status = read_art(); trt_status = read_trt(); if (trt_status < 0 && art_status < 0) { PRINT_ERROR("TRT/ART read failed\n"); return process_psvt(file_name); } if (int3400.match_supported_uuid() != THD_SUCCESS) { thd_log_info("Passive 1 UUID is not present, hence ignore _TRT, as it may have junk!!\n"); thd_log_info("Try Passive 2\n"); return process_psvt(file_name); } conf_file.open(file_name.c_str()); if (!conf_file.is_open()) { PRINT_ERROR("failed to open output file [%s]\n", file_name.c_str()); ret = -1; goto cleanup; } conf_file << xml_hdr; conf_file << conf_begin; prefix = indentation = "\t"; conf_file << indentation << "" << "\n"; create_platform_conf(); create_platform_pref(0); create_thermal_zones(); conf_file << prefix << "" << "\n"; conf_file << conf_end; conf_file.close(); cleanup: if (trt_status > 0) trt_data.reset(); if (art_status > 0) art_data.reset(); return ret; } int cthd_acpi_rel::read_psvt() { unsigned long length, count; int fd, ret; fd = open (rel_cdev.c_str (), O_RDWR); if (fd < 0) { PRINT_ERROR("failed to open %s\n", rel_cdev.c_str ()); return -1; } ret = ioctl (fd, ACPI_THERMAL_GET_PSVT_COUNT, &count); if (ret < 0) { PRINT_ERROR(" failed to GET COUNT on %s\n", rel_cdev.c_str ()); close (fd); return -1; } PRINT_DEBUG("PSVT count %lu ...\n", count); ret = ioctl (fd, ACPI_THERMAL_GET_PSVT_LEN, &length); if (ret < 0 || !length) { PRINT_ERROR(" failed to GET LEN on %s\n", rel_cdev.c_str ()); close (fd); return -1; } PRINT_DEBUG("PSVT length %lu ...\n", length); psvt_data.reset(new unsigned char[length]); if (!psvt_data) { PRINT_ERROR("cannot allocate buffer %lu to read PSVT\n", length); close (fd); return -1; } ret = ioctl (fd, ACPI_THERMAL_GET_PSVT, psvt_data.get()); if (ret < 0) { PRINT_ERROR(" failed to GET PSVT on %s\n", rel_cdev.c_str ()); close (fd); return -1; } psvt_count = count; close (fd); return 0; } int cthd_acpi_rel::read_art() { int fd; int ret; unsigned long count, length; fd = open(rel_cdev.c_str(), O_RDWR); if (fd < 0) { PRINT_ERROR("failed to open %s\n", rel_cdev.c_str()); return -1; } ret = ioctl(fd, ACPI_THERMAL_GET_ART_COUNT, &count); if (ret < 0) { PRINT_ERROR(" failed to GET COUNT on %s\n", rel_cdev.c_str()); close(fd); return -1; } PRINT_DEBUG("ART count %lu ...\n", count); ret = ioctl(fd, ACPI_THERMAL_GET_ART_LEN, &length); if (ret < 0 || !length) { PRINT_ERROR(" failed to GET LEN on %s\n", rel_cdev.c_str()); close(fd); return -1; } PRINT_DEBUG("ART length %lu ...\n", length); art_data.reset(new unsigned char[length]); if (!art_data) { PRINT_ERROR("cannot allocate buffer %lu to read ART\n", length); close(fd); return -1; } ret = ioctl(fd, ACPI_THERMAL_GET_ART, art_data.get()); if (ret < 0) { PRINT_ERROR(" failed to GET ART on %s\n", rel_cdev.c_str()); close(fd); return -1; } art_count = count; dump_art(); close(fd); return 0; } int cthd_acpi_rel::read_trt() { int fd; int ret; unsigned long count, length; fd = open(rel_cdev.c_str(), O_RDWR); if (fd < 0) { PRINT_ERROR("failed to open %s\n", rel_cdev.c_str()); return -1; } ret = ioctl(fd, ACPI_THERMAL_GET_TRT_COUNT, &count); if (ret < 0) { PRINT_ERROR(" failed to GET COUNT on %s\n", rel_cdev.c_str()); close(fd); return -1; } PRINT_DEBUG("TRT count %lu ...\n", count); ret = ioctl(fd, ACPI_THERMAL_GET_TRT_LEN, &length); if (ret < 0 || !length) { PRINT_ERROR(" failed to GET LEN on %s\n", rel_cdev.c_str()); close(fd); return -1; } trt_data.reset(new unsigned char[length]); if (!trt_data) { PRINT_ERROR("cannot allocate buffer %lu to read TRT\n", length); close(fd); return -1; } ret = ioctl(fd, ACPI_THERMAL_GET_TRT, trt_data.get()); if (ret < 0) { PRINT_ERROR(" failed to GET TRT on %s\n", rel_cdev.c_str()); close(fd); return -1; } trt_count = count; dump_trt(); close(fd); return 0; } #define ACPI_TYPE_STRING 0x02 #define DECI_KELVIN_TO_CELSIUS(t) ({ \ int _t = (t); \ ((_t-2732 >= 0) ? (_t-2732+5)/10 : (_t-2732-5)/10); \ }) void cthd_acpi_rel::add_psvt_trip_point(rel_object_t &rel_obj) { if (!rel_obj.psvt_objects.size ()) return; PRINT_DEBUG("add_passive_trip_point\n"); string prefix = indentation + "\t"; for (unsigned int j = 0; j < rel_obj.psvt_objects.size (); ++j) { union psvt_object *object = (union psvt_object*) rel_obj.psvt_objects[j]; string device_name = object->acpi_psvt_entry.source_device; int limit_value = -1; conf_file << prefix << "\n"; conf_file << prefix << "\t" << "" << object->acpi_psvt_entry.target_device << "\n"; PRINT_DEBUG("object->acpi_psvt_entry.control_knob_type:%llu\n", object->acpi_psvt_entry.control_knob_type); if (object->acpi_psvt_entry.control_knob_type == ACPI_TYPE_STRING) { if (!strncasecmp (object->acpi_psvt_entry.limit.string, "MAX", 3)) conf_file << prefix << "\t" << "" << (DECI_KELVIN_TO_CELSIUS(object->acpi_psvt_entry.passive_temp) + 1) * 1000 << "\n"; if (!strncasecmp (object->acpi_psvt_entry.limit.string, "MIN", 3)) conf_file << prefix << "\t" << "" << (DECI_KELVIN_TO_CELSIUS(object->acpi_psvt_entry.passive_temp) * 1000) << "\n"; } else { limit_value = object->acpi_psvt_entry.limit.integer; conf_file << prefix << "\t" << "" << (DECI_KELVIN_TO_CELSIUS(object->acpi_psvt_entry.passive_temp) * 1000) << "\n"; } conf_file << prefix << "\t" << "" << "Passive" << "\n"; conf_file << prefix << "\t" << "\n"; subtitute_string (TARGET_DEV, device_name); conf_file << prefix << "\t\t" << "" << device_name << "\n"; if (object->acpi_psvt_entry.sample_period) conf_file << prefix << "\t\t" << "" << (object->acpi_psvt_entry.sample_period / 10) << "\n"; if (limit_value > 0) conf_file << prefix << "\t\t" << "" << (limit_value * 1000) << "\n"; conf_file << prefix << "\t" << "\n"; conf_file << prefix << "\n"; } } void cthd_acpi_rel::add_passive_trip_point(rel_object_t &rel_obj) { if (!rel_obj.trt_objects.size()) return; string prefix = indentation + "\t"; conf_file << prefix << "\n"; subtitute_string(SENSOR_DEV, rel_obj.target_sensor); conf_file << prefix << "\t" << "" << rel_obj.target_sensor << "\n"; conf_file << prefix << "\t" << "" << "*" << "\n"; conf_file << prefix << "\t" << "" << "passive" "\n"; conf_file << prefix << "\t" << "" << "SEQUENTIAL" << "\n"; for (unsigned int j = 0; j < rel_obj.trt_objects.size(); ++j) { union trt_object *object = (union trt_object *) rel_obj.trt_objects[j]; string device_name = object->acpi_trt_entry.source_device; conf_file << prefix << "\t" << "\n"; subtitute_string(TARGET_DEV, device_name); conf_file << prefix << "\t\t" << "" << device_name << "\n"; conf_file << prefix << "\t\t" << "" << object->acpi_trt_entry.influence << "\n"; conf_file << prefix << "\t\t" << "" << object->acpi_trt_entry.sample_period * 100 / 1000 << "\n"; conf_file << prefix << "\t" << "\n"; } conf_file << prefix << "\n"; } void cthd_acpi_rel::add_active_trip_point(rel_object_t &rel_obj) { if (!rel_obj.art_objects.size()) return; string prefix = indentation + "\t"; conf_file << prefix << "\n"; subtitute_string(SENSOR_DEV, rel_obj.target_sensor); conf_file << prefix << "\t" << "" << rel_obj.target_sensor << "\n"; conf_file << prefix << "\t" << "" << "*" << "\n"; conf_file << prefix << "\t" << "" << "active" "\n"; conf_file << prefix << "\t" << "" << "SEQUENTIAL" << "\n"; for (unsigned int j = 0; j < rel_obj.art_objects.size(); ++j) { union art_object *object = (union art_object *) rel_obj.art_objects[j]; string device_name = object->acpi_art_entry.source_device; conf_file << prefix << "\t" << "\n"; subtitute_string(TARGET_DEV, device_name); conf_file << prefix << "\t\t" << "" << device_name << "\n"; conf_file << prefix << "\t\t" << "" << object->acpi_art_entry.weight << "\n"; conf_file << prefix << "\t" << "\n"; } conf_file << prefix << "\n"; } void cthd_acpi_rel::create_thermal_zone(const string& type) { unsigned int i; indentation += "\t"; string prefix = indentation; indentation += '\t'; parse_target_devices(); for (i = 0; i < rel_list.size(); ++i) { if (rel_list[i].target_device == "") { PRINT_ERROR("Empty target, skipping ..\n"); continue; } conf_file << prefix << "" << "\n"; subtitute_string(SOURCE_DEV, rel_list[i].target_device); conf_file << prefix << "\t" << "" << rel_list[i].target_device << "" << "\n"; conf_file << prefix << "\t" << "" << "\n"; indentation += "\t"; add_passive_trip_point(rel_list[i]); add_active_trip_point(rel_list[i]); conf_file << prefix << "\t" << "" << "\n"; conf_file << prefix << "" << "\n"; } } void cthd_acpi_rel::parse_target_devices() { union trt_object *trt = (union trt_object *) trt_data.get(); union art_object *art = (union art_object *) art_data.get(); union psvt_object *psvt = (union psvt_object *) psvt_data.get(); unsigned int i; for (i = 0; i < trt_count; i++) { rel_object_t rel_obj(trt[i].acpi_trt_entry.target_device); vector::iterator find_iter; find_iter = find_if(rel_list.begin(), rel_list.end(), object_finder(trt[i].acpi_trt_entry.target_device)); if (find_iter == rel_list.end()) { rel_obj.trt_objects.push_back(&trt[i]); rel_list.push_back(std::move(rel_obj)); } else find_iter->trt_objects.push_back(&trt[i]); } for (i = 0; i < art_count; i++) { rel_object_t rel_obj(art[i].acpi_art_entry.target_device); vector::iterator find_iter; find_iter = find_if(rel_list.begin(), rel_list.end(), object_finder(art[i].acpi_art_entry.target_device)); if (find_iter == rel_list.end()) { rel_obj.art_objects.push_back(&art[i]); rel_list.push_back(std::move(rel_obj)); } else find_iter->art_objects.push_back(&art[i]); } PRINT_DEBUG("parse target devices\n"); for (i = 0; i < psvt_count; i++) { rel_object_t rel_obj(psvt[i].acpi_psvt_entry.target_device, psvt[i].acpi_psvt_entry.passive_temp, psvt[i].acpi_psvt_entry.step_size); PRINT_DEBUG("parse target devices index :%d\n", i); vector::iterator find_iter; find_iter = find_if(rel_list.begin(), rel_list.end(), object_finder(psvt[i].acpi_psvt_entry.target_device)); if (find_iter == rel_list.end()) { rel_obj.psvt_objects.push_back(&psvt[i]); rel_list.push_back(std::move(rel_obj)); } else find_iter->psvt_objects.push_back(&psvt[i]); } } void cthd_acpi_rel::create_thermal_zones() { string prefix = indentation; conf_file << prefix << "" << "\n"; // Read ... create_thermal_zone("test"); // conf_file << prefix << "" << "\n"; } void cthd_acpi_rel::create_platform_conf() { // string line; string prefix; indentation += "\t"; prefix = indentation; conf_file << prefix << "" << "_TRT export" << "" << "\n"; ifstream product_name("/sys/class/dmi/id/product_name"); conf_file << indentation << ""; #if 0 if (product_name.is_open() && getline(product_name, line)) { #else char buffer[256]; if (product_name.is_open() && product_name.getline(buffer, sizeof(buffer))) { string line(buffer); #endif conf_file << line; } else conf_file << "*" << "\n"; conf_file << prefix << "" << "\n"; } #if LOG_DEBUG_INFO == 1 void cthd_acpi_rel::dump_trt() { union trt_object *trt = (union trt_object *) trt_data.get(); unsigned int i; for (i = 0; i < trt_count; i++) { PRINT_DEBUG("TRT %d: SRC %s:\t", i, trt[i].acpi_trt_entry.source_device); PRINT_DEBUG("TRT %d: TGT %s:\t", i, trt[i].acpi_trt_entry.target_device); PRINT_DEBUG("TRT %d: INF %llu:\t", i, trt[i].acpi_trt_entry.influence); PRINT_DEBUG("TRT %d: SMPL %llu:\n", i, trt[i].acpi_trt_entry.sample_period); } } void cthd_acpi_rel::dump_psvt() { union psvt_object *trt = (union psvt_object*) psvt_data.get(); unsigned int i; for (i = 0; i < psvt_count; i++) { PRINT_DEBUG("PSVT %d\n", i); PRINT_DEBUG("PSVT %d: SRC %s:\n", i, trt[i].acpi_psvt_entry.source_device); PRINT_DEBUG("TRT %d: TGT %s:\n", i, trt[i].acpi_psvt_entry.target_device); PRINT_DEBUG("TRT %d: SMPL %llu:\n", i, trt[i].acpi_psvt_entry.sample_period); PRINT_DEBUG("TRT %d: temp %d:\n", i, DECI_KELVIN_TO_CELSIUS(trt[i].acpi_psvt_entry.passive_temp)); PRINT_DEBUG("TRT %d: step %llu:\n", i, trt[i].acpi_psvt_entry.step_size); if (trt[i].acpi_psvt_entry.control_knob_type == ACPI_TYPE_STRING) PRINT_DEBUG("control limit:%s\n", trt[i].acpi_psvt_entry.limit.string); else PRINT_DEBUG("control_limit: %llu\n", trt[i].acpi_psvt_entry.limit.integer); } } void cthd_acpi_rel::dump_art() { union art_object *art = (union art_object *) art_data.get(); unsigned int i; for (i = 0; i < art_count; i++) { PRINT_DEBUG("ART %d: SRC %s:\t", i, art[i].acpi_art_entry.source_device); PRINT_DEBUG("ART %d: TGT %s:\t", i, art[i].acpi_art_entry.target_device); PRINT_DEBUG("ART %d: WT %llu:\n", i, art[i].acpi_art_entry.weight); } } #else void cthd_acpi_rel::dump_trt() { } void cthd_acpi_rel::dump_art() { } #endif void cthd_acpi_rel::create_platform_pref(int perf) { if (perf) conf_file << indentation << "PERFORMANCE" << "\n"; else conf_file << indentation << "QUIET" << "\n"; } thermal_daemon-2.5.12/src/thd_trt_art_reader.h000066400000000000000000000054051521301715300213620ustar00rootroot00000000000000/* * thd_trt_art_reader.h: Interface for configuration using ACPI * _ART and _TRT tables * Copyright (C) 2014 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_TRT_ART_READER_H_ #define THD_TRT_ART_READER_H_ #include #include #include #include #include #include #include #include #include #include #include struct rel_object_t { std::string target_device; std::string target_sensor; std::vector trt_objects; std::vector art_objects; std::vector psvt_objects; int temperature; int step; rel_object_t(std::string name, int _temperature = 0, int _step = 0) { target_device = name; target_sensor = std::move(name); temperature = _temperature; step = _step; } }; struct object_finder { object_finder(char *key) : obj_key(key) { } bool operator()(const rel_object_t& o) const { return obj_key == o.target_device; } const std::string obj_key; }; class cthd_acpi_rel { private: std::string rel_cdev; std::string xml_hdr; std::string conf_begin; std::string conf_end; std::string output_file_name; std::ofstream conf_file; std::vector rel_list; std::unique_ptr trt_data; unsigned int trt_count; std::unique_ptr art_data; unsigned int art_count; std::unique_ptr psvt_data; unsigned int psvt_count; int read_trt(); void dump_trt(); void create_platform_conf(); void create_platform_pref(int perf); void create_thermal_zones(); void create_thermal_zone(const std::string& type); void add_passive_trip_point(rel_object_t &rel_obj); void add_psvt_trip_point(rel_object_t &rel_obj); void add_active_trip_point(rel_object_t &rel_obj); void parse_target_devices(); int read_art(); void dump_art(); int read_psvt(); int process_psvt(const std::string& file_name); void dump_psvt(); public: std::string indentation; cthd_acpi_rel(); int generate_conf(const std::string& file_name); }; #endif /* THD_TRT_ART_READER_H_ */ thermal_daemon-2.5.12/src/thd_util.cpp000066400000000000000000000113711521301715300176700ustar00rootroot00000000000000/* * thd_util.cpp: Common utility functions * * Copyright (C) 2026 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name Andrew Gaul * */ #include "thd_util.h" #include "thermald.h" #include #include #include #include #include #include #include #include bool starts_with(const std::string& s, const char *prefix) { size_t len = strlen(prefix); return s.size() >= len && s.compare(0, len, prefix) == 0; } static const size_t THD_MAX_STR_CMP_LEN = 4096; size_t thd_cmp_len(const char *param1, const char *param2) { size_t param1_len; size_t param2_len; if (!param1) param1 = ""; if (!param2) param2 = ""; param1_len = strnlen(param1, THD_MAX_STR_CMP_LEN); param2_len = strnlen(param2, THD_MAX_STR_CMP_LEN); return (param1_len > param2_len) ? param1_len : param2_len; } int thd_strcmp_n(const char *param1, const char *param2) { if (!param1) param1 = ""; if (!param2) param2 = ""; return strncmp(param1, param2, thd_cmp_len(param1, param2)); } int thd_strcasecmp_n(const char *param1, const char *param2) { if (!param1) param1 = ""; if (!param2) param2 = ""; return strncasecmp(param1, param2, thd_cmp_len(param1, param2)); } // Safe integer parsing with validation int parse_int_value(const std::string &str, int *result, int min_val, int max_val) { if (str.empty() || !result) { return -1; } char *endptr; errno = 0; long val = strtol(str.c_str(), &endptr, 10); // Check for conversion errors if (errno == ERANGE || endptr == str.c_str()) { thd_log_warn("Invalid integer value: '%s'\n", str.c_str()); return -1; } // Allow trailing whitespace but not other garbage while (*endptr == ' ' || *endptr == '\t' || *endptr == '\n') { endptr++; } if (*endptr != '\0') { thd_log_warn("Invalid integer format: '%s'\n", str.c_str()); return -1; } // Check range if (val < min_val || val > max_val) { thd_log_warn("Integer value %ld out of range [%d, %d]: '%s'\n", val, min_val, max_val, str.c_str()); return -1; } *result = (int)val; return 0; } // Safe double parsing with validation int parse_double_value(const std::string &str, double *result, double min_val, double max_val) { if (str.empty() || !result) { return -1; } char *endptr; errno = 0; double val = strtod(str.c_str(), &endptr); // Check for conversion errors if (errno == ERANGE || endptr == str.c_str()) { thd_log_warn("Invalid floating point value: '%s'\n", str.c_str()); return -1; } // Allow trailing whitespace but not other garbage while (*endptr == ' ' || *endptr == '\t' || *endptr == '\n') { endptr++; } if (*endptr != '\0') { thd_log_warn("Invalid floating point format: '%s'\n", str.c_str()); return -1; } // Reject NaN/Inf explicitly: range comparisons do not catch NaN. if (!std::isfinite(val)) { thd_log_warn("Invalid non-finite floating point value: '%s'\n", str.c_str()); return -1; } // Check range if (val < min_val || val > max_val) { thd_log_warn("Floating point value %f out of range [%f, %f]: '%s'\n", val, min_val, max_val, str.c_str()); return -1; } *result = val; return 0; } bool is_valid_thermal_object_name(const std::string &name) { if (name.empty() || name.size() > 128) return false; if (name == "." || name == "..") return false; for (unsigned char ch : name) { if (!std::isalnum(ch) && ch != '_' && ch != '-' && ch != '.' && ch != 0x20) return false; } return true; } bool is_valid_finite_value(double value, double min_val, double max_val) { return std::isfinite(value) && value >= min_val && value <= max_val; } int open_validated_xml_file(const std::string &filename, bool require_root_owner) { int fd = open(filename.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC); if (fd < 0) return -1; struct stat file_stat; if (fstat(fd, &file_stat) == -1) { close(fd); return -1; } if (!S_ISREG(file_stat.st_mode)) { close(fd); errno = EPERM; return -1; } if (require_root_owner && (file_stat.st_uid != 0 || (file_stat.st_mode & (S_IWGRP | S_IWOTH)))) { close(fd); errno = EPERM; return -1; } return fd; } thermal_daemon-2.5.12/src/thd_util.h000066400000000000000000000034541521301715300173400ustar00rootroot00000000000000/* * thd_util.h: Common utility functions * * Copyright (C) 2026 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name Andrew Gaul * */ #ifndef THD_UTIL_H_ #define THD_UTIL_H_ #include #include #include // Replacement for C++20 std::string::starts_with bool starts_with(const std::string& s, const char *prefix)__attribute__((unused)); size_t thd_cmp_len(const char *param1, const char *param2); int thd_strcmp_n(const char *param1, const char *param2); int thd_strcasecmp_n(const char *param1, const char *param2); // Safe integer parsing with validation // Returns 0 on success, -1 on error int parse_int_value(const std::string &str, int *result, int min_val, int max_val); // Safe double parsing with validation // Returns 0 on success, -1 on error int parse_double_value(const std::string &str, double *result, double min_val, double max_val); bool is_valid_thermal_object_name(const std::string &name); bool is_valid_finite_value(double value, double min_val, double max_val); int open_validated_xml_file(const std::string &filename, bool require_root_owner = true); #endif /* THD_UTIL_H_ */ thermal_daemon-2.5.12/src/thd_zone.cpp000066400000000000000000000227221521301715300176700ustar00rootroot00000000000000/* * thd_zone.cpp: thermal zone class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ /* This class implements parent thermal zone/sensor. It is included * in a thermal engine. During initialization, it establishes a * relationship between cooling devices and trip points (where * some action needs to be taken). * When it gets a notification for a change, it reads the temperature * from sensors and uses cthd_trip point to schedule action on the event * if required. */ #include "thd_zone.h" #include "thd_engine.h" #include "thd_util.h" cthd_zone::cthd_zone(int _index, std::string control_path, sensor_relate_t rel) : index(_index), zone_sysfs(std::move(control_path)), zone_temp(0), zone_active( false), zone_cdev_binded_status(false), type_str(), sensor_rel( rel) { thd_log_debug("Added zone index:%d\n", index); } cthd_zone::~cthd_zone() { for (unsigned int i = 0; i < trip_points.size(); ++i) { trip_points[i].delete_cdevs(); } trip_points.clear(); sensors.clear(); } void cthd_zone::thermal_zone_temp_change(int id, unsigned int temp, int pref) { int i, count; bool reset = false; count = trip_points.size(); for (i = 0; i < count; ++i) { cthd_trip_point &trip_point = trip_points[i]; trip_point.thd_trip_point_check(id, temp, pref, &reset); // Force all cooling devices to min state if (reset) { zone_reset(0); break; } } } void cthd_zone::update_zone_preference() { if (!zone_active) return; thd_log_debug("update_zone_preference\n"); for (unsigned int i = 0; i < sensors.size(); ++i) { cthd_sensor *sensor; sensor = sensors[i]; zone_temp = sensor->read_temperature(); thermal_zone_temp_change(sensor->get_index(), 0, thd_engine->get_preference()); } for (unsigned int i = 0; i < sensors.size(); ++i) { cthd_sensor *sensor; sensor = sensors[i]; zone_temp = sensor->read_temperature(); thermal_zone_temp_change(sensor->get_index(), zone_temp, thd_engine->get_preference()); } } int cthd_zone::read_user_set_psv_temp() { std::ostringstream filename; int temp = -1; if (!is_valid_thermal_object_name(type_str)) return -1; filename << TDRUNDIR << "/" << "thd_user_psv_temp." << type_str << "." << "conf"; std::ifstream ifs(filename.str().c_str(), std::ifstream::in); if (ifs.good()) { ifs >> temp; thd_log_info("read_user_set_psv_temp %d\n", temp); if (temp < 1000) temp = -1; } ifs.close(); return temp; } static inline bool trip_sort(const cthd_trip_point& trip1, const cthd_trip_point& trip2) { return (trip1.get_trip_temp() < trip2.get_trip_temp()); } void cthd_zone::sort_and_update_poll_trip() { thd_log_debug("sort_and_update_poll_trip: trip_points_size =%zu\n", trip_points.size()); for (unsigned int i = 0; i < trip_points.size(); ++i) { if (trip_points[i].get_trip_type() == POLLING) { thd_log_debug("polling trip already present\n"); trip_points.erase(trip_points.begin() + i); break; } } if (trip_points.size()) { unsigned int polling_trip = 0; std::sort(trip_points.begin(), trip_points.end(), trip_sort); if (trip_points.size()) polling_trip = trip_points[0].get_trip_temp(); unsigned int poll_offset = polling_trip * def_async_trip_offset_pct / 100; if (poll_offset < def_async_trip_offset) poll_offset = def_async_trip_offset; polling_trip -= poll_offset; for (unsigned int i = 0; i < sensors.size(); ++i) { cthd_sensor *sensor; sensor = sensors[i]; sensor->set_threshold(0, polling_trip); // If the poll trip is already present then simply update // the trip, instead of creating a new one. cthd_trip_point trip_pt_polling(trip_points.size(), POLLING, polling_trip, 0, index, sensor->get_index()); trip_pt_polling.thd_trip_point_set_control_type(PARALLEL); trip_points.push_back(std::move(trip_pt_polling)); } } } int cthd_zone::zone_update() { int ret; if (zone_bind_sensors() != THD_SUCCESS) { thd_log_warn("Zone update failed: unable to bind\n"); return THD_ERROR; } ret = read_trip_points(); if (ret != THD_SUCCESS) return THD_ERROR; int usr_psv_temp = read_user_set_psv_temp(); if (usr_psv_temp > 0) { cthd_trip_point trip_pt_passive(0, PASSIVE, usr_psv_temp, 0, index, DEFAULT_SENSOR_ID); update_highest_trip_temp(trip_pt_passive); } ret = read_cdev_trip_points(); if (ret != THD_SUCCESS) { thd_log_info("No cdev trip points loaded for zone index %d\n", index); // Don't bail out as they may be attached by thermal relation tables } sort_and_update_poll_trip(); return THD_SUCCESS; } void cthd_zone::read_zone_temp() { if (zone_active) { unsigned int temp; zone_temp = 0; for (unsigned int i = 0; i < sensors.size(); ++i) { cthd_sensor *sensor; sensor = sensors[i]; temp = sensor->read_temperature(); if (zone_temp < temp) zone_temp = temp; if (sensor_rel == SENSOR_INDEPENDENT) thermal_zone_temp_change(sensor->get_index(), temp, thd_engine->get_preference()); } if (sensor_rel == SENSORS_CORELATED && zone_temp) thermal_zone_temp_change(sensors[0]->get_index(), zone_temp, thd_engine->get_preference()); } } void cthd_zone::zone_temperature_notification(int type, int data) { read_zone_temp(); } void cthd_zone::zone_reset(int force) { int i, count; if (zone_active) { count = trip_points.size(); for (i = count - 1; i >= 0; --i) { cthd_trip_point &trip_point = trip_points[i]; trip_point.thd_trip_cdev_state_reset(force); } } } int cthd_zone::bind_cooling_device(trip_point_type_t type, unsigned int trip_temp, cthd_cdev *cdev, int influence, int sampling_period, int target_state_valid, int target_state, int min_max_valid, int min_state, int max_state) { int i, count; bool added = false; // trip_temp = 0 is a special case, where it will add to first matched type count = trip_points.size(); for (i = 0; i < count; ++i) { cthd_trip_point &trip_point = trip_points[i]; if ((trip_point.get_trip_type() == type) && (trip_point.get_trip_temp() > 0) && (trip_temp == 0 || trip_point.get_trip_temp() == trip_temp)) { trip_point.thd_trip_point_add_cdev(*cdev, influence, sampling_period, target_state_valid, target_state, nullptr, min_max_valid, min_state, max_state); added = true; zone_cdev_set_binded(); break; } } #if 0 // Check again, if we really need this logic if (!added && trip_temp) { // Create a new trip point and add only if trip_temp is valid cthd_trip_point trip_pt(count, type, trip_temp, 0, index, DEFAULT_SENSOR_ID); trip_points.push_back(trip_pt); added = true; } #endif if (added) return THD_SUCCESS; else return THD_ERROR; } int cthd_zone::update_max_temperature(int max_temp) { std::ostringstream filename; std::ostringstream temp_str; if (!is_valid_thermal_object_name(type_str)) return THD_ERROR; filename << TDRUNDIR << "/" << "thd_user_set_max." << type_str << "." << "conf"; std::ofstream fout(filename.str().c_str()); if (!fout.good()) { return THD_ERROR; } temp_str << max_temp; fout << temp_str.str(); fout.close(); return THD_SUCCESS; } int cthd_zone::update_psv_temperature(int psv_temp) { std::ostringstream filename; std::ostringstream temp_str; if (!is_valid_thermal_object_name(type_str)) return THD_ERROR; filename << TDRUNDIR << "/" << "thd_user_psv_temp." << type_str << "." << "conf"; std::ofstream fout(filename.str().c_str()); if (!fout.good()) { return THD_ERROR; } temp_str << psv_temp; fout << temp_str.str(); fout.close(); return THD_SUCCESS; } void cthd_zone::add_trip(cthd_trip_point &trip, int force) { if (force) { trip_points.push_back(trip); sort_and_update_poll_trip(); return; } bool add = true; for (unsigned int j = 0; j < trip_points.size(); ++j) { if (trip_points[j].get_trip_type() == trip.get_trip_type()) { thd_log_debug("updating existing trip temp\n"); trip_points[j] = trip; add = false; break; } } if (add) trip_points.push_back(trip); sort_and_update_poll_trip(); } void cthd_zone::update_highest_trip_temp(cthd_trip_point &trip) { if (trip_points.size()) { thd_log_info("trip_points.size():%zu\n", trip_points.size()); for (int j = trip_points.size() - 1; j >= 0; --j) { if (trip_points[j].get_trip_type() == trip.get_trip_type()) { thd_log_info("updating existing trip temp\n"); trip_points[j].update_trip_temp(trip.get_trip_temp()); trip_points[j].update_trip_hyst(trip.get_trip_hyst()); break; } } sort_and_update_poll_trip(); } } void cthd_zone::update_trip_temp(cthd_trip_point &trip) { for (unsigned int j = 0; j < trip_points.size(); ++j) { if (trip_points[j].get_trip_type() == trip.get_trip_type()) { thd_log_debug("updating existing trip temp\n"); trip_points[j].update_trip_temp(trip.get_trip_temp()); trip_points[j].update_trip_hyst(trip.get_trip_hyst()); break; } } sort_and_update_poll_trip(); } thermal_daemon-2.5.12/src/thd_zone.h000066400000000000000000000146471521301715300173440ustar00rootroot00000000000000/* * thd_zone.h: thermal zone class interface * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_ZONE_H #define THD_ZONE_H #include #include "thd_common.h" #include "thd_sys_fs.h" #include "thd_preference.h" #include "thd_cdev.h" #include "thd_trip_point.h" #include "thd_sensor.h" #include "thd_sensor_virtual.h" typedef struct { int zone; int type; unsigned int data; } thermal_zone_notify_t; // If the zone has multiple sensors, there are two possibilities // Either the are independent, means that each has own set of trip points // Or related. In this case one trip point. Here we take max of the sensor reading // and then apply trip typedef enum : uint8_t { SENSOR_INDEPENDENT, SENSORS_CORELATED } sensor_relate_t; class cthd_zone { protected: int index; std::vector trip_points; std::string temperature_sysfs_path; csys_fs zone_sysfs; unsigned int zone_temp; bool zone_active; bool zone_cdev_binded_status; std::string type_str; std::vector sensors; sensor_relate_t sensor_rel; virtual int zone_bind_sensors() = 0; void thermal_zone_temp_change(int id, unsigned int temp, int pref); private: void sort_and_update_poll_trip(); public: static constexpr unsigned int def_async_trip_offset = 5000; static constexpr unsigned int def_async_trip_offset_pct = 10; cthd_zone(int _index, std::string control_path, sensor_relate_t rel = SENSOR_INDEPENDENT); virtual ~cthd_zone(); void zone_temperature_notification(int type, int data); int zone_update(); virtual void update_zone_preference(); void zone_reset(int force = 0); virtual int read_trip_points() = 0; virtual int read_cdev_trip_points() = 0; virtual void read_zone_temp(); int get_zone_index() { return index; } void add_trip(cthd_trip_point &trip, int force = 0); void update_trip_temp(cthd_trip_point &trip); void update_highest_trip_temp(cthd_trip_point &trip); void set_zone_active() { for (unsigned int i = 0; i < sensors.size(); ++i) { cthd_sensor *sensor = sensors[i]; if (sensor->is_virtual()) { cthd_sensor_virtual *_sensor = (cthd_sensor_virtual *) sensor; _sensor->enable_periodic_timer(); break; } } zone_active = true; } ; void set_zone_inactive() { for (unsigned int i = 0; i < sensors.size(); ++i) { cthd_sensor *sensor = sensors[i]; if (sensor->is_virtual()) { cthd_sensor_virtual *_sensor = (cthd_sensor_virtual *) sensor; _sensor->disable_periodic_timer(); break; } } zone_active = false; } bool zone_active_status() { return zone_active; } bool zone_cdev_binded() { return zone_cdev_binded_status; } void zone_cdev_set_binded() { thd_log_debug("zone %s bounded\n", type_str.c_str()); zone_cdev_binded_status = true; } std::string get_zone_type() { return type_str; } std::string get_zone_path() { return zone_sysfs.get_base_path(); } void set_zone_type(std::string type) { type_str = std::move(type); } void bind_sensor(cthd_sensor *sensor) { for (unsigned int i = 0; i < sensors.size(); ++i) { if (sensors[i] == sensor) return; } sensors.push_back(sensor); } // Even if one sensor, it is using doesn't // provide async control, return false bool check_sensor_async_status() { for (unsigned int i = 0; i < sensors.size(); ++i) { cthd_sensor *sensor = sensors[i]; if (!sensor->check_async_capable()) { return false; } } return true; } unsigned int get_trip_count() { return trip_points.size(); } int update_max_temperature(int max_temp); int update_psv_temperature(int psv_temp); int read_user_set_psv_temp(); int bind_cooling_device(trip_point_type_t type, unsigned int trip_temp, cthd_cdev *cdev, int influence, int sampling_period = 0, int target_state_valid = 0, int target_state = 0, int min_max_valid = 0, int min_state = 0, int max_state = 0); int get_sensor_count() { return sensors.size(); } cthd_sensor *get_sensor_at_index(unsigned int index) { if (index < sensors.size()) return sensors[index]; else return nullptr; } cthd_trip_point *get_trip_at_index(unsigned int index) { if (index < trip_points.size()) return &trip_points[index]; else return nullptr; } #ifdef ANDROID void trip_delete_all() { if (!trip_points.size()) return; for (unsigned int i = 0; i < trip_points.size(); ++i) { trip_point_type_t trip_type = trip_points[i].get_trip_type(); if (trip_type==HOT|| trip_type==CRITICAL || trip_type==MAX ) { thd_log_info("keep cdev trip_point %d temp=%d\n", i, trip_points[i].get_trip_temp()); } else trip_points[i].delete_cdevs(); } std::vector::iterator it; for (it=trip_points.begin(); it!=trip_points.end(); ) { trip_point_type_t trip_type = it->get_trip_type(); if (trip_type==HOT|| trip_type==CRITICAL || trip_type==MAX ) { thd_log_info("keep trip_point temp=%d\n", it->get_trip_temp()); ++it; } else { thd_log_info("remove trip_point temp=%d\n", it->get_trip_temp()); it=trip_points.erase(it); } } } #else void trip_delete_all() { if (!trip_points.size()) return; for (unsigned int i = 0; i < trip_points.size(); ++i) { trip_points[i].delete_cdevs(); } trip_points.clear(); } #endif void zone_dump() { if (!zone_active) return; thd_log_info("\n"); thd_log_info("Zone %d: %s, Active:%d Bind:%d Sensor_cnt:%lu\n", index, type_str.c_str(), zone_active, zone_cdev_binded_status, (unsigned long) sensors.size()); thd_log_info("..sensors..\n"); for (unsigned int i = 0; i < sensors.size(); ++i) { sensors[i]->sensor_dump(); } thd_log_info("..trips..\n"); for (unsigned int i = 0; i < trip_points.size(); ++i) { trip_points[i].trip_dump(); } thd_log_info("\n"); } ; }; #endif thermal_daemon-2.5.12/src/thd_zone_cpu.cpp000066400000000000000000000166231521301715300205420ustar00rootroot00000000000000/* * thd_zone_dts.cpp: thermal engine DTS class implementation * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * * This implementation allows using core temperature interface. */ /* Implementation of DTS sensor Zone. This * - Identifies DTS sensors, and use maximum reported temperature to control * - Prioritize the cooling device order * - Sets one control point starting from target temperature (max) not critical. * Very first time it reaches this temperature cthd_model, calculates a set point when * cooling device needs to be activated. * */ #include #include "thd_zone_cpu.h" #include "thd_engine_default.h" #include "thd_cdev_order_parser.h" const char * const def_cooling_devices[] = { "rapl_controller", "intel_pstate", "intel_powerclamp", "cpufreq", "Processor", nullptr }; cthd_zone_cpu::cthd_zone_cpu(int index, std::string path, int package_id) : cthd_zone(index, path, SENSORS_CORELATED), dts_sysfs(std::move(path)), critical_temp( 0), max_temp(0), psv_temp(0), trip_point_cnt(0), sensor_mask(0), phy_package_id( package_id), pkg_thres_th_zone(-1), pkg_temp_poll_enable(false) { type_str = "cpu"; thd_log_debug("zone dts syfs: %s, package id %d\n", dts_sysfs.get_base_path().c_str(), package_id); } int cthd_zone_cpu::init() { int temp = 0; bool found = false; max_temp = 0; critical_temp = 0; // Calculate the temperature trip points using the settings in coretemp for (int i = 0; i < max_dts_sensors; ++i) { std::ostringstream temp_crit_str; std::ostringstream temp_max_str; temp_crit_str << "temp" << i << "_crit"; temp_max_str << "temp" << i << "_max"; if (dts_sysfs.exists(temp_crit_str.str())) { temp = 0; // Initialize before read int ret = dts_sysfs.read(temp_crit_str.str(), &temp); if (ret < 0) return ret; if (critical_temp == 0 || temp < critical_temp) critical_temp = temp; } if (dts_sysfs.exists(temp_max_str.str())) { // Set which index is present sensor_mask = sensor_mask | (1 << i); temp = 0; // Initialize before read int ret = dts_sysfs.read(temp_max_str.str(), &temp); if (ret < 0) return ret; if (max_temp == 0 || temp < max_temp) max_temp = temp; found = true; } } if (!found) { thd_log_error("DTS temperature path not found\n"); return THD_ERROR; } if (critical_temp == 0) critical_temp = def_critical_temp; if (max_temp == 0) { max_temp = critical_temp - def_offset_from_critical; thd_log_info("Force max temp to %d\n", max_temp); } if ((critical_temp - max_temp) < def_offset_from_critical) { max_temp = critical_temp - def_offset_from_critical; thd_log_info("Buggy max temp: to close to critical %d\n", max_temp); } // max_temperature is where the Fan would have been activated fully // psv_temp is set more so that in the case if Fan is not able to // control temperature, the passive temperature will be acted on psv_temp = max_temp + ((critical_temp - max_temp) / 2); thd_log_info("Core temp DTS :critical %d, max %d, psv %d\n", critical_temp, max_temp, psv_temp); return THD_SUCCESS; } int cthd_zone_cpu::load_cdev_xml(cthd_trip_point &trip_pt, std::vector &list) { cthd_cdev *cdev; for (unsigned int i = 0; i < list.size(); ++i) { thd_log_debug("- %s\n", list[i].c_str()); cdev = thd_engine->search_cdev(list[i]); if (cdev) { trip_pt.thd_trip_point_add_cdev(*cdev, cthd_trip_point::default_influence); } } return THD_SUCCESS; } int cthd_zone_cpu::parse_cdev_order() { cthd_cdev_order_parse parser; std::vector order_list; int ret = THD_ERROR; if ((ret = parser.parser_init()) == THD_SUCCESS) { if ((ret = parser.start_parse()) == THD_SUCCESS) { ret = parser.get_order_list(order_list); if (ret == THD_SUCCESS) { cthd_trip_point trip_pt_passive(trip_point_cnt, PASSIVE, psv_temp, def_hystersis, index, DEFAULT_SENSOR_ID); trip_pt_passive.thd_trip_point_set_control_type(SEQUENTIAL); load_cdev_xml(trip_pt_passive, order_list); trip_points.push_back(std::move(trip_pt_passive)); trip_point_cnt++; } } parser.parser_deinit(); return ret; } return ret; } int cthd_zone_cpu::read_trip_points() { int ret; cthd_cdev *cdev; int i; ret = parse_cdev_order(); if (ret == THD_SUCCESS) { thd_log_info("CDEVS order specified in thermal-cpu-cdev-order.xml\n"); return THD_SUCCESS; } cthd_trip_point trip_pt_passive(trip_point_cnt, PASSIVE, psv_temp, def_hystersis, index, DEFAULT_SENSOR_ID); trip_pt_passive.thd_trip_point_set_control_type(SEQUENTIAL); i = 0; while (def_cooling_devices[i]) { cdev = thd_engine->search_cdev(def_cooling_devices[i]); if (cdev) { trip_pt_passive.thd_trip_point_add_cdev(*cdev, cthd_trip_point::default_influence); } ++i; } trip_points.push_back(std::move(trip_pt_passive)); trip_point_cnt++; // Add active trip point at the end cthd_trip_point trip_pt_active(trip_point_cnt, ACTIVE, max_temp, def_hystersis, index, DEFAULT_SENSOR_ID); trip_pt_active.thd_trip_point_set_control_type(SEQUENTIAL); cdev = thd_engine->search_cdev("Fan"); if (cdev) { trip_pt_active.thd_trip_point_add_cdev(*cdev, cthd_trip_point::default_influence); trip_points.push_back(std::move(trip_pt_active)); trip_point_cnt++; } return THD_SUCCESS; } int cthd_zone_cpu::zone_bind_sensors() { cthd_sensor *sensor; int status = THD_ERROR; bool async_sensor = false; if (init() != THD_SUCCESS) return THD_ERROR; if (!thd_engine->rt_kernel_status()) { sensor = thd_engine->search_sensor("pkg-temp-0"); if (sensor) { bind_sensor(sensor); async_sensor = true; } } if (!thd_engine->rt_kernel_status()) { sensor = thd_engine->search_sensor("x86_pkg_temp"); if (sensor) { bind_sensor(sensor); async_sensor = true; } } sensor = thd_engine->search_sensor("soc_dts0"); if (sensor) { bind_sensor(sensor); async_sensor = true; } if (async_sensor) { sensor = thd_engine->search_sensor("hwmon"); if (sensor) { sensor->set_async_capable(true); } return THD_SUCCESS; } // No package temp sensor fallback to core temp int cnt = 0; unsigned int mask = 0x1; do { if (sensor_mask & mask) { std::ostringstream temp_input_str; temp_input_str << "temp" << cnt << "_input"; cthd_sensor *sensor; sensor = thd_engine->search_sensor(temp_input_str.str()); if (sensor) { bind_sensor(sensor); status = THD_SUCCESS; } } mask = (mask << 1); cnt++; } while (mask != 0); if (status != THD_SUCCESS) { thd_log_info("Trying to bind hwmon sensor\n"); sensor = thd_engine->search_sensor("hwmon"); if (sensor) { bind_sensor(sensor); status = THD_SUCCESS; thd_log_info("Bind hwmon sensor\n"); } } return status; } int cthd_zone_cpu::read_cdev_trip_points() { return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_zone_cpu.h000066400000000000000000000034261521301715300202040ustar00rootroot00000000000000/* * thd_zone_dts.h: thermal engine DTS class interface * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * * This interface allows overriding per zone read data from sysfs for * buggy BIOS. */ #ifndef THD_ZONE_DTS_H #define THD_ZONE_DTS_H #include "thd_zone.h" #include class cthd_zone_cpu: public cthd_zone { protected: csys_fs dts_sysfs; int critical_temp; int max_temp; int psv_temp; int trip_point_cnt; unsigned int sensor_mask; int phy_package_id; std::vector sensor_sysfs; int init(); int parse_cdev_order(); int pkg_thres_th_zone; bool pkg_temp_poll_enable; public: static constexpr int max_dts_sensors = 16; static constexpr int def_hystersis = 0; static constexpr int def_offset_from_critical = 10000; static constexpr int def_critical_temp = 100000; cthd_zone_cpu(int count, std::string path, int package_id); int load_cdev_xml(cthd_trip_point &trip_pt, std::vector &list); int read_trip_points() override; int read_cdev_trip_points() override; int zone_bind_sensors() override; }; #endif thermal_daemon-2.5.12/src/thd_zone_dynamic.cpp000066400000000000000000000042051521301715300213700ustar00rootroot00000000000000/* * thd_zone_generic.cpp: zone implementation for xml conf * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_zone_dynamic.h" #include "thd_engine.h" cthd_zone_dynamic::cthd_zone_dynamic(int index, std::string _name, unsigned int _trip_temp, trip_point_type_t _trip_type, std::string _sensor, std::string _cdev) : cthd_zone(index, ""), name(std::move(_name)), trip_temp(_trip_temp), trip_type(_trip_type), sensor_name( std::move(_sensor)), cdev_name(std::move(_cdev)) { type_str = name; } int cthd_zone_dynamic::read_trip_points() { cthd_sensor *sensor = thd_engine->search_sensor(sensor_name); if (!sensor) { thd_log_warn("dynamic sensor: invalid sensor type\n"); return THD_ERROR; } thd_log_info("XX index = %d\n", index); cthd_trip_point trip_pt(0, trip_type, trip_temp, 0, index, sensor->get_index()); cthd_cdev *cdev = thd_engine->search_cdev(cdev_name); if (cdev) { trip_pt.thd_trip_point_add_cdev(*cdev, cthd_trip_point::default_influence); zone_cdev_set_binded(); } else return THD_ERROR; trip_points.push_back(std::move(trip_pt)); return THD_SUCCESS; } int cthd_zone_dynamic::read_cdev_trip_points() { return 0; } int cthd_zone_dynamic::zone_bind_sensors() { cthd_sensor *sensor = thd_engine->search_sensor(sensor_name); if (!sensor) { thd_log_warn("dynamic sensor: invalid sensor type\n"); return THD_ERROR; } bind_sensor(sensor); return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_zone_dynamic.h000066400000000000000000000026161521301715300210410ustar00rootroot00000000000000/* * thd_zone_generic.cpp: zone implementation for xml conf * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_ZONE_DYNAMIC_H_ #define THD_ZONE_DYNAMIC_H_ #include "thd_zone.h" class cthd_zone_dynamic: public cthd_zone { private: std::string name; unsigned int trip_temp; trip_point_type_t trip_type; std::string sensor_name; std::string cdev_name; public: cthd_zone_dynamic(int index, std::string _name, unsigned int _trip_temp, trip_point_type_t _trip_type, std::string _sensor, std::string _cdev); int read_trip_points() override; int read_cdev_trip_points() override; int zone_bind_sensors() override; }; #endif /* THD_ZONE_DYNAMIC_H_ */ thermal_daemon-2.5.12/src/thd_zone_generic.cpp000066400000000000000000000067241521301715300213700ustar00rootroot00000000000000/* * thd_zone_generic.cpp: zone implementation for xml conf * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_zone_generic.h" #include "thd_engine.h" cthd_zone_generic::cthd_zone_generic(int index, int _config_index, std::string type) : cthd_zone(index, ""), config_index(_config_index) { type_str = std::move(type); } int cthd_zone_generic::read_trip_points() { thermal_zone_t *zone_config = thd_engine->parser.get_zone_dev_index( config_index); int trip_point_cnt = 0; if (!zone_config) return THD_ERROR; for (unsigned int i = 0; i < zone_config->trip_pts.size(); ++i) { trip_point_t &trip_pt_config = zone_config->trip_pts[i]; if (!trip_pt_config.temperature) continue; cthd_sensor *sensor = thd_engine->search_sensor( trip_pt_config.sensor_type); if (!sensor) { thd_log_error("XML zone: invalid sensor type\n"); continue; } sensor_list.push_back(sensor); cthd_trip_point trip_pt(trip_point_cnt, trip_pt_config.trip_pt_type, trip_pt_config.temperature, trip_pt_config.hyst, index, sensor->get_index(), trip_pt_config.control_type); if (trip_pt_config.dependency.dependency) { trip_pt.set_dependency(trip_pt_config.dependency.cdev, trip_pt_config.dependency.state); } // bind cdev for (unsigned int j = 0; j < trip_pt_config.cdev_trips.size(); ++j) { cthd_cdev *cdev = thd_engine->search_cdev( trip_pt_config.cdev_trips[j].type); if (cdev) { trip_pt.thd_trip_point_add_cdev(*cdev, trip_pt_config.cdev_trips[j].influence, trip_pt_config.cdev_trips[j].sampling_period, trip_pt_config.cdev_trips[j].target_state_valid, trip_pt_config.cdev_trips[j].target_state, nullptr, trip_pt_config.cdev_trips[j].min_max_valid, trip_pt_config.cdev_trips[j].target_min_state, trip_pt_config.cdev_trips[j].target_max_state); zone_cdev_set_binded(); } } trip_points.push_back(std::move(trip_pt)); ++trip_point_cnt; } if (!trip_points.size()) { thd_log_info( " cthd_zone_generic::read_trip_points fail: No valid trips\n"); return THD_ERROR; } return 0; } int cthd_zone_generic::read_cdev_trip_points() { return 0; } int cthd_zone_generic::zone_bind_sensors() { cthd_sensor *sensor; thermal_zone_t *zone_config = thd_engine->parser.get_zone_dev_index( config_index); if (!zone_config) return THD_ERROR; sensor = nullptr; for (unsigned int i = 0; i < zone_config->trip_pts.size(); ++i) { trip_point_t &trip_pt_config = zone_config->trip_pts[i]; sensor = thd_engine->search_sensor(trip_pt_config.sensor_type); if (!sensor) { thd_log_error("XML zone: invalid sensor type %s\n", trip_pt_config.sensor_type.c_str()); continue; } bind_sensor(sensor); } if (!sensor) return THD_ERROR; return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_zone_generic.h000066400000000000000000000023671521301715300210340ustar00rootroot00000000000000/* * thd_zone_generic.h: zone interface for xml conf * * Copyright (C) 2013 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_ZONE_GENERIC_H_ #define THD_ZONE_GENERIC_H_ #include "thd_zone.h" class cthd_zone_generic: public cthd_zone { private: int config_index; std::vector sensor_list; public: cthd_zone_generic(int index, int _config_index, std::string type); int read_trip_points() override; int read_cdev_trip_points() override; int zone_bind_sensors() override; }; #endif /* THD_ZONE_GENERIC_H_ */ thermal_daemon-2.5.12/src/thd_zone_rapl_power.cpp000066400000000000000000000036311521301715300221200ustar00rootroot00000000000000/* * thd_zone_rapl_power.cpp: thermal zone for rapl power * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_engine_default.h" #include "thd_sys_fs.h" #include "thd_zone_rapl_power.h" cthd_zone_rapl_power::cthd_zone_rapl_power(int index) : cthd_zone(index, "") { type_str = "rapl_pkg_power"; } int cthd_zone_rapl_power::zone_bind_sensors() { cthd_sensor *sensor; sensor = thd_engine->search_sensor("rapl_pkg_power"); if (sensor) { bind_sensor(sensor); return THD_SUCCESS; } return THD_ERROR; } int cthd_zone_rapl_power::read_trip_points() { cthd_cdev *cdev_cpu; cdev_cpu = thd_engine->search_cdev("rapl_controller"); if (!cdev_cpu) { thd_log_info("rapl_controller, failed\n"); return THD_ERROR; } cthd_trip_point trip_pt_passive(0, PASSIVE, 100000, 0, index, DEFAULT_SENSOR_ID, PARALLEL); trip_pt_passive.thd_trip_point_add_cdev(*cdev_cpu, cthd_trip_point::default_influence, thd_engine->get_poll_interval(), 0, 0, nullptr); trip_points.push_back(std::move(trip_pt_passive)); thd_log_debug("cthd_zone_rapl_power::read_trip_points OK\n"); return THD_SUCCESS; } int cthd_zone_rapl_power::read_cdev_trip_points() { return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_zone_rapl_power.h000066400000000000000000000021401521301715300215570ustar00rootroot00000000000000/* * thd_zone_rapl_power.h * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_ZONE_RAPL_POWER_H #define THD_ZONE_RAPL_POWER_H #include "thd_zone.h" class cthd_zone_rapl_power: public cthd_zone { public: cthd_zone_rapl_power(int index); int read_trip_points() override; int zone_bind_sensors() override; int read_cdev_trip_points() override; }; #endif thermal_daemon-2.5.12/src/thd_zone_therm_sys_fs.cpp000066400000000000000000000147361521301715300224630ustar00rootroot00000000000000/* * thd_zone_therm_sys_fs.cpp: thermal zone class implementation * for thermal sysfs * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #include "thd_zone_therm_sys_fs.h" #include "thd_engine.h" #include "thd_util.h" #include #include cthd_sysfs_zone::cthd_sysfs_zone(int count, std::string path) : cthd_zone(count, std::move(path)), trip_point_cnt(0) { std::ostringstream tc_type_dev; tc_type_dev << index << "/type"; thd_log_debug("Thermal Zone look for %s\n", tc_type_dev.str().c_str()); if (zone_sysfs.exists(tc_type_dev.str())) { zone_sysfs.read(tc_type_dev.str(), type_str); } thd_log_debug("Thermal Zone %d:%s\n", index, type_str.c_str()); } cthd_sysfs_zone::~cthd_sysfs_zone() { std::ostringstream trip_sysfs; trip_sysfs << index << "/" << "trip_point_"; for (unsigned int i = 0; i < initial_trip_values.size(); ++i) { std::ostringstream temp_stream; temp_stream << trip_sysfs.str() << i << "_temp"; if (initial_trip_values[i] >= 0 && zone_sysfs.exists(temp_stream.str())) { if (zone_sysfs.write(temp_stream.str(), initial_trip_values[i]) == -1) thd_log_debug("Tzone_sysfs.write failed for %d\n", initial_trip_values[i]); } } } int cthd_sysfs_zone::zone_bind_sensors() { cthd_sensor *sensor; sensor = thd_engine->search_sensor(type_str); if (sensor) { bind_sensor(sensor); } else return THD_ERROR; return THD_SUCCESS; } int cthd_sysfs_zone::read_trip_points() { // Gather all trip points std::ostringstream trip_sysfs; trip_sysfs << index << "/" << "trip_point_"; for (int i = 0; i < max_trip_points; ++i) { std::ostringstream type_stream; std::ostringstream temp_stream; std::ostringstream hist_stream; std::string _type_str; trip_point_type_t trip_type; int temp = 0, hyst = 1; mode_t mode = 0; cthd_sensor *sensor; bool wr_mode = false; type_stream << trip_sysfs.str() << i << "_type"; if (zone_sysfs.exists(type_stream.str())) { zone_sysfs.read(type_stream.str(), _type_str); thd_log_debug("read_trip_points %s:%s\n", type_stream.str().c_str(), _type_str.c_str()); } temp_stream << trip_sysfs.str() << i << "_temp"; if (zone_sysfs.exists(temp_stream.str())) { mode = zone_sysfs.get_mode(temp_stream.str()); temp = 0; // Initialize before read int ret = zone_sysfs.read(temp_stream.str(), &temp); if (ret < 0) return ret; thd_log_debug("read_trip_points %s:%d\n", temp_stream.str().c_str(), temp); } hist_stream << trip_sysfs.str() << i << "_hyst"; if (zone_sysfs.exists(hist_stream.str())) { hyst = 0; // Initialize before read int ret = zone_sysfs.read(hist_stream.str(), &hyst); if (ret < 0) return ret; if (hyst < 1000 || hyst > 5000) hyst = 1000; thd_log_debug("read_trip_points %s:%d\n", hist_stream.str().c_str(), hyst); } if (_type_str == "critical") trip_type = CRITICAL; else if (_type_str == "hot") trip_type = MAX; else if (_type_str == "active") trip_type = ACTIVE; else if (_type_str == "passive") trip_type = PASSIVE; else trip_type = INVALID_TRIP_TYPE; sensor = thd_engine->search_sensor(type_str); if (sensor && (mode & S_IWUSR)) { sensor->set_async_capable(true); wr_mode = true; initial_trip_values.push_back(temp); } else initial_trip_values.push_back(-1); if (sensor && temp > 0 && trip_type != INVALID_TRIP_TYPE && !wr_mode) { cthd_trip_point trip_pt(trip_point_cnt, trip_type, temp, hyst, index, sensor->get_index()); trip_pt.thd_trip_point_set_control_type(SEQUENTIAL); trip_points.push_back(std::move(trip_pt)); ++trip_point_cnt; } } thd_log_debug("read_trip_points Added from sysfs %d trips\n", trip_point_cnt); if (!trip_point_cnt) { cthd_sensor *sensor; sensor = thd_engine->search_sensor(type_str); if (!sensor) return THD_ERROR; cthd_trip_point trip_pt(0, PASSIVE, INT32_MAX, 0, index, sensor->get_index()); trip_pt.thd_trip_point_set_control_type(SEQUENTIAL); trip_points.push_back(std::move(trip_pt)); ++trip_point_cnt; thd_log_debug("Added one default trip\n"); } return THD_SUCCESS; } int cthd_sysfs_zone::read_cdev_trip_points() { thd_log_debug(" >> read_cdev_trip_points for\n"); // Gather all Cdevs // Gather all trip points std::ostringstream cdev_sysfs; cdev_sysfs << index << "/" << "cdev"; for (int i = 0; i < max_cool_devs; ++i) { std::ostringstream trip_pt_stream, cdev_stream; int trip_cnt = -1; char buf[51], *ptr; trip_pt_stream << cdev_sysfs.str() << i << "_trip_point"; trip_cnt = 0; // Initialize before read if (zone_sysfs.exists(trip_pt_stream.str())) { int ret = zone_sysfs.read(trip_pt_stream.str(), &trip_cnt); if (ret < 0) return ret; } else continue; thd_log_debug("cdev trip point: %s contains %d\n", trip_pt_stream.str().c_str(), trip_cnt); cdev_stream << cdev_sysfs.str() << i; if (zone_sysfs.exists(cdev_stream.str())) { thd_log_debug("cdev%d present\n", i); int ret = zone_sysfs.read_symbolic_link_value(cdev_stream.str(), buf, sizeof(buf) - 1); if (ret == 0) { ptr = strstr(buf, "cooling_device"); if (ptr) { ptr += strlen("cooling_device"); thd_log_debug("symbolic name %s:%s\n", buf, ptr); if (trip_cnt >= 0 && trip_cnt < trip_point_cnt) { // Safe integer parsing for cooling device ID int cdev_id; if (parse_int_value(std::string(ptr), &cdev_id, 0, INT_MAX) != 0) { thd_log_debug("Invalid cooling device ID\n"); continue; } trip_points[trip_cnt].thd_trip_point_add_cdev_index( cdev_id, cthd_trip_point::default_influence); zone_cdev_set_binded(); } else { thd_log_debug("Invalid trip_cnt\n"); } } } } } thd_log_debug( "cthd_sysfs_zone::read_cdev_trip_points: ZONE bound to CDEV status %d\n", zone_cdev_binded_status); return THD_SUCCESS; } thermal_daemon-2.5.12/src/thd_zone_therm_sys_fs.h000066400000000000000000000025741521301715300221250ustar00rootroot00000000000000/* * thd_zone_therm_sys_fs.h: thermal zone class interface * for thermal sysfs * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_ZONE_THERM_SYS_FS_H_ #define THD_ZONE_THERM_SYS_FS_H_ #include "thd_zone.h" class cthd_sysfs_zone: public cthd_zone { private: int trip_point_cnt; std::vector initial_trip_values; public: static constexpr int max_trip_points = 50; static constexpr int max_cool_devs = 50; cthd_sysfs_zone(int count, std::string path); ~cthd_sysfs_zone() override; int read_trip_points() override; int read_cdev_trip_points() override; int zone_bind_sensors() override; }; #endif /* THD_ZONE_THERM_SYS_FS_H_ */ thermal_daemon-2.5.12/src/thermald.h000066400000000000000000000062221521301715300173200ustar00rootroot00000000000000/* * thermald.h: Thermal Daemon common header file * * Copyright (C) 2012 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name * */ #ifndef THD_THERMALD_H #define THD_THERMALD_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ANDROID #define LOG_NDEBUG 1 #undef LOG_TAG #define LOG_TAG "THERMALD" #include #include #include #define thd_log_fatal ALOGE #define thd_log_error ALOGE #define thd_log_warn ALOGW #define thd_log_msg ALOGI #if LOG_DEBUG_INFO == 1 #define thd_log_info ALOGI #define thd_log_debug ALOGD #else #define thd_log_info(...) #define thd_log_debug(...) #endif #else #include "config.h" // Keeping the logging flag enabled for non-android cases #define LOG_DEBUG_INFO 1 #define LOCKF_SUPPORT #ifdef GLIB_SUPPORT #include #include #include extern gboolean log_debug; // Log macros #define thd_log_fatal g_error // Print error and terminate #define thd_log_error g_critical #define thd_log_warn g_warning #define thd_log_msg g_message #define thd_log_debug(...) \ do { \ if (G_UNLIKELY (!g_log_writer_default_would_drop (G_LOG_LEVEL_DEBUG, G_LOG_DOMAIN)) || log_debug) \ g_debug(__VA_ARGS__); \ } while (0) #define thd_log_info(...) g_log(nullptr, G_LOG_LEVEL_INFO, __VA_ARGS__) #else static int dummy_printf(const char *__restrict __format, ...) { return 0; } #define thd_log_fatal printf #define thd_log_error printf #define thd_log_warn printf #define thd_log_msg printf #define thd_log_debug dummy_printf #define thd_log_info printf #endif #endif // Common return value defines #define THD_SUCCESS 0 #define THD_ERROR -1 #define THD_FATAL_ERROR -2 // Dbus related /* Well-known name for this service. */ #define THD_SERVICE_NAME "org.freedesktop.thermald" #define THD_SERVICE_OBJECT_PATH "/org/freedesktop/thermald" #define THD_SERVICE_INTERFACE "org.freedesktop.thermald" class cthd_engine; class cthd_engine_therm_sysfs; extern std::unique_ptr thd_engine; extern int thd_poll_interval; extern bool thd_ignore_default_control; extern bool workaround_enabled; extern bool disable_active_power; extern bool ignore_critical; extern bool power_floor_enable; extern bool adaptive_perf_enable; #endif thermal_daemon-2.5.12/test/000077500000000000000000000000001521301715300155355ustar00rootroot00000000000000thermal_daemon-2.5.12/test/Makefile000066400000000000000000000002571521301715300172010ustar00rootroot00000000000000obj-m += thermald_test_kern_module.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean thermal_daemon-2.5.12/test/cpufreq.sh000077500000000000000000000053001521301715300175370ustar00rootroot00000000000000#!/bin/bash CONF_FILE="/etc/thermald/thermal-conf.xml.auto" echo "Executing test : Test cpufreq cooling" cp cpufreq.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:x86_pkg_temp | sed 's/\/type.*//') cpuinfo_min_freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq) cpuinfo_max_freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq) echo "cpuinfo_min_freq:" $cpuinfo_min_freq echo "cpuinfo_max_freq:" $cpuinfo_max_freq cat ${THD0_ZONE}/temp sleep 2 echo "Forcing to throttle" echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 10 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -eq $cpuinfo_min_freq ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -ne $cpuinfo_min_freq ]; then echo "cpufreq: Step 0: Test failed" exit 1 else echo "cpufreq: Step 0: Test passed" fi echo "Removing throttle slowly stepwise" echo 69000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -eq $cpuinfo_max_freq ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -ne $cpuinfo_max_freq ]; then echo "cpufreq: Step 1: Test failed" exit 1 else echo "cpufreq: Step 1: Test passed" fi echo "Forcing throttle again " echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 10 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -eq $cpuinfo_min_freq ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -ne $cpuinfo_min_freq ]; then echo "cpufreq: Step 0: Test failed" exit 1 else echo "cpufreq: Step 0: Test passed" fi echo "Removing throttle in one step" echo 0 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -eq $cpuinfo_max_freq ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -ne $cpuinfo_max_freq ]; then echo "cpufreq: Step 2: Test failed" exit 1 else echo "cpufreq: Step 2: Test passed" fi thermal_daemon-2.5.12/test/cpufreq.xml000066400000000000000000000014151521301715300177250ustar00rootroot00000000000000 Example Laptop * QUIET _cpu x86_pkg_temp 70000 passive SEQUENTIAL 1 cpufreq 100 1 thermal_daemon-2.5.12/test/default.sh000077500000000000000000000112721521301715300175230ustar00rootroot00000000000000#!/bin/bash CONF_FILE="/etc/thermald/thermal-conf.xml.auto" echo "Executing test : default CPU cooling" cp default.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:x86_pkg_temp | sed 's/\/type.*//') #rapl_max_power=$(cat /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/constraint_0_max_power_uw) #rapl_min_power=$(expr $rapl_max_power / 2) rapl_max_power=25000000 rapl_min_power=10000000 echo "rapl_min_power:" $rapl_min_power echo "rapl_max_power:" $rapl_max_power min_perf_pct=50 max_perf_pct=100 echo "min_perf_pct:" $min_perf_pct echo "max_perf_pct:" $max_perf_pct CDEV_POWERCLAMP=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:intel_powerclamp | sed 's/\/type.*//') CDEV_PROCESSOR=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:Processor | head -1 | sed 's/\/type.*//') cat ${THD0_ZONE}/temp sleep 2 for i in {0..1} do echo "Forcing to throttle for step" $i echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 20 ]; do curr_power_limit=$(cat /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/constraint_0_power_limit_uw) echo "current state " ${curr_power_limit} if [ $curr_power_limit -le $rapl_min_power ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_power_limit -gt $rapl_min_power ]; then echo "intel_rapl: Step 0: Test failed" exit 1 else echo "intel_rapl: Step 0: Test passed" fi COUNTER=0 while [ $COUNTER -lt 20 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -le $min_perf_pct ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -gt $min_perf_pct ]; then echo "intel_pstate: Step 0: Test failed" exit 1 else echo "intel_pstate: Step 0: Test passed" fi max_state=$(cat ${CDEV_POWERCLAMP}/max_state) max_state=$(expr $max_state / 2 - 1 ) echo $max_state COUNTER=0 while [ $COUNTER -lt 20 ]; do cur_state=$(cat ${CDEV_POWERCLAMP}/cur_state) echo "current state " ${cur_state} if [ $cur_state -ge $max_state ]; then echo "Reached max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -lt $max_state ]; then echo "powerclamp: Step 0: Test failed" exit 1 else echo "powerclamp: Step 0: Test passed" fi max_state_processor=3 echo $max_state_processor COUNTER=0 while [ $COUNTER -lt 20 ]; do cur_state=$(cat ${CDEV_PROCESSOR}/cur_state) echo "current state " ${cur_state} if [ $cur_state -ge $max_state_processor ]; then echo "Reached max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -lt $max_state_processor ]; then echo "processor: Step 0: Test failed" exit 1 else echo "processor: Step 0: Test passed" fi if [ $i -eq 0 ]; then echo "Removing throttle slowly stepwise" echo 69000 > ${THD0_ZONE}/emul_temp else echo "Removing throttle in one shot" echo 0 > ${THD0_ZONE}/emul_temp fi COUNTER=0 while [ $COUNTER -lt 20 ]; do cur_state=$(cat ${CDEV_PROCESSOR}/cur_state) echo "current state " ${cur_state} if [ $cur_state -le 0 ]; then echo "Reached min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -gt 0 ]; then echo "processor: Step 0: Test failed" exit 1 else echo "processor: Step 0: Test passed" fi COUNTER=0 while [ $COUNTER -lt 20 ]; do cur_state=$(cat ${CDEV_POWERCLAMP}/cur_state) echo "current state " ${cur_state} if [ $cur_state -le 0 ]; then echo "Reached min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -gt 0 ]; then echo "powerclamp: Step 0: Test failed" exit 1 else echo "powerclamp: Step 0: Test passed" fi COUNTER=0 while [ $COUNTER -lt 20 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -eq $max_perf_pct ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -ne $max_perf_pct ]; then echo "intel_pstate: Step 2: Test failed" exit 1 else echo "intel_pstate: Step 2: Test passed" fi COUNTER=0 while [ $COUNTER -lt 20 ]; do curr_power_limit=$(cat /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/constraint_0_power_limit_uw) echo "current state " ${curr_power_limit} if [ $curr_power_limit -ge $rapl_max_power ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_power_limit -lt $rapl_max_power ]; then echo "intel_rapl: Step 1: Test failed" exit 1 else echo "intel_rapl: Step 1: Test passed" fi done thermal_daemon-2.5.12/test/default.xml000066400000000000000000000031151521301715300177030ustar00rootroot00000000000000 Example Laptop * QUIET 0 10000 25000 28000 28000 250 _cpu x86_pkg_temp 70000 passive SEQUENTIAL 1 rapl_controller_mmio 100 2 intel_pstate 90 3 intel_powerclamp 80 4 Processor 70 thermal_daemon-2.5.12/test/exec_config_tests.sh000077500000000000000000000173331521301715300215760ustar00rootroot00000000000000#!/bin/bash echo "Make sure that intel_pstate is not started with --adaptive option" make insmod thermald_test_kern_module.ko CONF_FILE="/etc/thermald/thermal-conf.xml.auto" #Test 1: Simple association: one zone to one cooling device # check if cdev state reach max when temp >= 40C and when # temp < 40C state reach to 0. echo "Executing test 1: Simple zone to cdev association" cp test1.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:thd_test_0 | sed 's/\/type.*//') THD0_CDEV=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep thd_cdev_0 | sed 's/\/type.*//') echo "Current temperature for thd_test_0 temp to" cat ${THD0_ZONE}/temp sleep 2 echo 50000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD0_CDEV}/cur_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 10 ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 10 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi cat ${THD0_CDEV}/cur_state echo 10000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD0_CDEV}/cur_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 0 ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 0 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi # TEST 2 echo "Executing test 2: Check if influence field is respected, in picking up cdev" cp test2.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:thd_test_0 | sed 's/\/type.*//') THD0_CDEV0=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep thd_cdev_0 | sed 's/\/type.*//') THD0_CDEV1=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep thd_cdev_1 | sed 's/\/type.*//') echo "Current temperature for thd_test_0 temp to" cat ${THD0_ZONE}/temp sleep 2 echo 50000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp # check the highest priority cdev picked up first COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD0_CDEV1}/cur_state) echo "current state for thd_cdev_1" ${curr_state} if [ $curr_state -eq 10 ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 10 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi # pick up the next COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD0_CDEV0}/cur_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 10 ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 10 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi echo 10000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD0_CDEV0}/cur_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 0 ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 0 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD0_CDEV1}/cur_state) echo "current state for thd_cdev_1" ${curr_state} if [ $curr_state -eq 0 ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 0 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi # Test 3 echo "Executing test 3: Check if sample field is respected" echo "currently it is a visual test only" echo "It will show Too early to act messages, gap between two ops is 15+ sec as per connfig here " cp test3.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:thd_test_0 | sed 's/\/type.*//') THD0_CDEV=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep thd_cdev_0 | sed 's/\/type.*//') echo "Current temperature for thd_test_0 temp to" cat ${THD0_ZONE}/temp sleep 2 echo 50000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 20 ]; do curr_state=$(cat ${THD0_CDEV}/cur_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 10 ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 10 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi cat ${THD0_CDEV}/cur_state echo 10000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD0_CDEV}/cur_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 0 ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 0 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi # Test 4 echo "Executing test 4: one cdev in multiple zones, one zone crossed passive, make sure that the other zone doesn't deactivate an activated cdev " cp test4.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:thd_test_0 | sed 's/\/type.*//') THD0_CDEV=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep thd_cdev_0 | sed 's/\/type.*//') echo "Current temperature for thd_test_0 temp to" cat ${THD0_ZONE}/temp sleep 2 echo 50000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 20 ]; do curr_state=$(cat ${THD0_CDEV}/cur_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 10 ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 10 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi cat ${THD0_CDEV}/cur_state echo 10000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD0_CDEV}/cur_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 0 ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 0 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi # Test 5 echo "Executing test 5: Test case where sensor/zone/cdev are not" echo " in thermal sysfs and still able to control" cp test5.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD5_ZONE=/sys/kernel/thermald_test THD5_CDEV=/sys/kernel/thermald_test echo "Current temperature" cat ${THD5_ZONE}/sensor_temp sleep 2 echo 50000 > ${THD5_ZONE}/sensor_temp echo "Emulate temp to" cat ${THD5_ZONE}/sensor_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD5_CDEV}/control_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 10 ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 10 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi cat ${THD5_CDEV}/control_state echo 10000 > ${THD5_ZONE}/sensor_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do curr_state=$(cat ${THD5_CDEV}/control_state) echo "current state for thd_cdev_0" ${curr_state} if [ $curr_state -eq 0 ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_state -ne 0 ]; then echo "Test Failed" exit 1 else echo "Test passed" fi thermal_daemon-2.5.12/test/gddv_tests/000077500000000000000000000000001521301715300177035ustar00rootroot00000000000000thermal_daemon-2.5.12/test/gddv_tests/gddv_json_example.json000066400000000000000000000035511521301715300242720ustar00rootroot00000000000000{ "PSVT":[ { "name":"IETM.D0", "table":[ { "source":"\\_SB_.PC00.B0D4", "target":"\\_SB_.PC00.TCPU", "priority":"10", "sample_period":"100", "temperature":"80", "domain":"9", "control_knob":"65536", "limit":"10000", "step_size":"500", "limit_coeff":"500", "unlimit_coeff":"500" }, { "source":"\\_SB_.PC00.B0D4", "target":"\\_SB_.PC00.TCPU", "priority":"10", "sample_period":"100", "temperature":"90", "domain":"9", "control_knob":"65536", "limit":"MIN", "step_size":"500", "limit_coeff":"500", "unlimit_coeff":"500" } ] }, { "name":"laptop", "table":[ { "source":"\\_SB_.PC00.TCPU", "target":"\\_SB_.PC00.LPCB.ECDV.SEN2", "priority":"10", "sample_period":"10", "temperature":"50", "domain":"9", "control_knob":"65536", "limit":"11000", "step_size":"500", "limit_coeff":"500", "unlimit_coeff":"500" }, { "source":"\\_SB_.PC00.TCPU", "target":"\\_SB_.PC00.LPCB.ECDV.SEN2", "priority":"10", "sample_period":"10", "temperature":"60", "domain":"9", "control_knob":"65536", "limit":"10000", "step_size":"500", "limit_coeff":"500", "unlimit_coeff":"500" } ] } ] } thermal_daemon-2.5.12/test/gddv_tests/gddv_json_test_0.json000066400000000000000000000010051521301715300240250ustar00rootroot00000000000000{ "PSVT":[ { "name":"IETM.D0", "table":[ { "source":"gddv_test_source_0", "target":"gddv_test_target_0", "priority":"10", "sample_period":"10", "temperature":"80", "domain":"9", "control_knob":"65536", "limit":"MAX", "step_size":"1", "limit_coeff":"1", "unlimit_coeff":"1" } ] } ] } thermal_daemon-2.5.12/test/gddv_tests/gddv_json_test_1.json000066400000000000000000000010071521301715300240300ustar00rootroot00000000000000{ "PSVT":[ { "name":"IETM.D0", "table":[ { "source":"gddv_test_source_0", "target":"gddv_test_target_0", "priority":"10", "sample_period":"10", "temperature":"80", "domain":"9", "control_knob":"65536", "limit":"30000", "step_size":"1", "limit_coeff":"1", "unlimit_coeff":"1" } ] } ] } thermal_daemon-2.5.12/test/gddv_tests/gddv_json_test_2.json000066400000000000000000000016611521301715300240370ustar00rootroot00000000000000{ "PSVT":[ { "name":"IETM.D0", "table":[ { "source":"gddv_test_source_0", "target":"gddv_test_target_0", "priority":"10", "sample_period":"10", "temperature":"80", "domain":"9", "control_knob":"65536", "limit":"30000", "step_size":"1", "limit_coeff":"1", "unlimit_coeff":"1" }, { "source":"gddv_test_source_0", "target":"gddv_test_target_0", "priority":"10", "sample_period":"10", "temperature":"90", "domain":"9", "control_knob":"65536", "limit":"50000", "step_size":"1", "limit_coeff":"1", "unlimit_coeff":"1" } ] } ] } thermal_daemon-2.5.12/test/gddv_tests/gddv_test_gen.py000066400000000000000000000106611521301715300230750ustar00rootroot00000000000000# Read gddv_test.json and generate gddv file import json import sys data = b"" file_name = "" def create_uintu64(value): global data header = b"\x04\x00\x00\x00" data = data + header + int(value).to_bytes(8, "little") def create_string(value): global data header = b"\x08\x00\x00\x00" byte_array = value.encode() byte_array = byte_array + b'\x00' length = len(value) + 1 length = length.to_bytes(8, "little") data = data + header + length + byte_array def create_key(key, val_array): global data key_len = len(key) + 1 key_flags = b"\x00\x00\x00\x00" key_length = int(key_len).to_bytes(4, "little") key_array = key.encode() key_array = key_array + b"\x00" val_type = b"\x00\x00\x00\x00" val_length = len(val_array).to_bytes(4, "little") data = data + key_flags + key_length + key_array + val_type + val_length + val_array def create_header(): global data data = b"\xE5\x1F\x0C\x00\x00\x00\x00\x01\x00\x00\x00\x00" def create_psvt(binary_file): global data data = b"" with open(file_name, "r") as gddv_test_file: gddv_data = json.load(gddv_test_file) print(json.dumps(gddv_data, indent=4)) psvts = {} for key, value in gddv_data.items(): if key == "PSVT": for index in range(len(gddv_data["PSVT"])): psvts[index] = gddv_data["PSVT"][index] print("index", index) print(" name", psvts[index]["name"]) key_name = "/shared/tables/psvt/" + psvts[index]["name"] table = psvts[index]["table"] create_uintu64("2") for i in range(len(table)): print("table_index", i) # create_uintu64("2") print(" source:", table[i]["source"]) create_string(table[i]["source"]) print(" target:", table[i]["target"]) create_string(table[i]["target"]) print(" priority:", table[i]["priority"]) create_uintu64(table[i]["priority"]) print(" sample_peri od:", table[i]["sample_period"]) create_uintu64(table[i]["sample_period"]) print(" temperature:", table[i]["temperature"]) temp = int(table[i]["temperature"]) temp = temp * 10 + 2727 create_uintu64(str(temp)) print(" domain:", table[i]["domain"]) create_uintu64(table[i]["domain"]) print(" control_knob:", table[i]["control_knob"]) create_uintu64(table[i]["control_knob"]) if table[i]["limit"] == "MIN" or table[i]["limit"] == "MAX": create_string(table[i]["limit"]) else: create_uintu64(table[i]["limit"]) print(" step_size:", table[i]["step_size"]) create_uintu64(table[i]["step_size"]) print(" limit_coeff:", table[i]["limit_coeff"]) create_uintu64(table[i]["limit_coeff"]) print(" unlimit_coeff:", table[i]["unlimit_coeff"]) create_uintu64(table[i]["unlimit_coeff"]) data = ( data + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ) print("(", data, ")") print("[", data, "]") psvt_entry = data data = b"" create_key(key_name, psvt_entry) binary_file.write(data) data = b"" def parse_psvt_json(binary_file): print(binary_file) create_psvt(binary_file) def create_gddv(): print("create gddv") with open("gddv.bin", "wb") as binary_file: create_header() binary_file.write(data) parse_psvt_json(binary_file) # main entry point # Get the config file name from command line print("Number of arguments:", len(sys.argv), "arguments.") print("Argument List:", str(sys.argv)) if len(sys.argv) < 2: print("Error: Need the Json config file as argument") sys.exit() file_name = str(sys.argv[1]) create_gddv() thermal_daemon-2.5.12/test/intel_pstate.sh000077500000000000000000000051041521301715300205670ustar00rootroot00000000000000#!/bin/bash CONF_FILE="/etc/thermald/thermal-conf.xml.auto" echo "Executing test : Test intel_pstate cooling" cp intel_pstate.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:x86_pkg_temp | sed 's/\/type.*//') min_perf_pct=50 max_perf_pct=100 echo "min_perf_pct:" $min_perf_pct echo "max_perf_pct:" $max_perf_pct cat ${THD0_ZONE}/temp sleep 2 echo "Forcing to throttle" echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 10 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -le $min_perf_pct ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -gt $min_perf_pct ]; then echo "intel_pstate: Step 0: Test failed" exit 1 else echo "intel_pstate: Step 0: Test passed" fi echo "Removing throttle slowly stepwise" echo 69000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -eq $max_perf_pct ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -ne $max_perf_pct ]; then echo "intel_state: Step 1: Test failed" exit 1 else echo "intel_psatte: Step 1: Test passed" fi echo "Forcing throttle again " echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 10 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -le $min_perf_pct ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -gt $min_perf_pct ]; then echo "intel_pstate: Step 0: Test failed" exit 1 else echo "intel_pstate: Step 0: Test passed" fi echo "Removing throttle in one step" echo 0 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do scaling_max_freq=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct) echo "current state " ${scaling_max_freq} if [ $scaling_max_freq -eq $max_perf_pct ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $scaling_max_freq -ne $max_perf_pct ]; then echo "intel_pstate: Step 2: Test failed" exit 1 else echo "intel_pstate: Step 2: Test passed" fi thermal_daemon-2.5.12/test/intel_pstate.xml000066400000000000000000000014221521301715300207510ustar00rootroot00000000000000 Example Laptop * QUIET _cpu x86_pkg_temp 70000 passive SEQUENTIAL 1 intel_pstate 100 1 thermal_daemon-2.5.12/test/intel_rapl.xml000066400000000000000000000022051521301715300204070ustar00rootroot00000000000000 Example Laptop * QUIET 0 10000 25000 28000 28000 250 _cpu x86_pkg_temp 70000 passive SEQUENTIAL 1 rapl_controller_mmio 100 1 thermal_daemon-2.5.12/test/powerclamp.sh000077500000000000000000000045461521301715300202560ustar00rootroot00000000000000#!/bin/bash CONF_FILE="/etc/thermald/thermal-conf.xml.auto" echo "Executing test : Test powerclamp cooling" cp powerclamp.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:x86_pkg_temp | sed 's/\/type.*//') CDEV=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:intel_powerclamp | sed 's/\/type.*//') cat ${THD0_ZONE}/temp sleep 2 echo "Forcing to throttle" echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp max_state=49 #$(cat ${CDEV}/max_state) max_state=$(expr $max_state / 2) echo $max_state COUNTER=0 while [ $COUNTER -lt 10 ]; do cur_state=$(cat ${CDEV}/cur_state) echo "current state " ${cur_state} if [ $cur_state -ge $max_state ]; then echo "Reached max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -lt $max_state ]; then echo "powerclamp: Step 0: Test failed" exit 1 else echo "powerclamp: Step 0: Test passed" fi echo "Removing throttle slowly stepwise" echo 69000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do cur_state=$(cat ${CDEV}/cur_state) echo "current state " ${cur_state} if [ $cur_state -le 0 ]; then echo "Reached min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -gt 0 ]; then echo "powerclamp: Step 0: Test failed" exit 1 else echo "powerclamp: Step 0: Test passed" fi echo "Forcing throttle again " echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 10 ]; do cur_state=$(cat ${CDEV}/cur_state) echo "current state " ${cur_state} if [ $cur_state -ge $max_state ]; then echo "Reached max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -lt $max_state ]; then echo "powerclamp: Step 1: Test failed" exit 1 else echo "powerclamp: Step 1: Test passed" fi echo "Removing throttle in one step" echo 0 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do cur_state=$(cat ${CDEV}/cur_state) echo "current state " ${cur_state} if [ $cur_state -eq -1 ]; then echo "Reached min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -gt 0 ]; then echo "powerclamp: Step 2: Test failed" exit 1 else echo "powerclamp: Step 2: Test passed" fi thermal_daemon-2.5.12/test/powerclamp.xml000066400000000000000000000014261521301715300204330ustar00rootroot00000000000000 Example Laptop * QUIET _cpu x86_pkg_temp 70000 passive SEQUENTIAL 1 intel_powerclamp 100 1 thermal_daemon-2.5.12/test/processor.sh000077500000000000000000000044541521301715300201220ustar00rootroot00000000000000#!/bin/bash CONF_FILE="/etc/thermald/thermal-conf.xml.auto" echo "Executing test : Test acpi processor cooling" cp processor_acpi.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:x86_pkg_temp | sed 's/\/type.*//') CDEV=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:Processor | head -1 | sed 's/\/type.*//') cat ${THD0_ZONE}/temp sleep 2 echo "Forcing to throttle" echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp max_state=3 echo $max_state COUNTER=0 while [ $COUNTER -lt 10 ]; do cur_state=$(cat ${CDEV}/cur_state) echo "current state " ${cur_state} if [ $cur_state -ge $max_state ]; then echo "Reached max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -lt $max_state ]; then echo "processor: Step 0: Test failed" exit 1 else echo "processor: Step 0: Test passed" fi echo "Removing throttle slowly stepwise" echo 69000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do cur_state=$(cat ${CDEV}/cur_state) echo "current state " ${cur_state} if [ $cur_state -le 0 ]; then echo "Reached min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -gt 0 ]; then echo "processor: Step 0: Test failed" exit 1 else echo "processor: Step 0: Test passed" fi echo "Forcing throttle again " echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 10 ]; do cur_state=$(cat ${CDEV}/cur_state) echo "current state " ${cur_state} if [ $cur_state -ge $max_state ]; then echo "Reached max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -lt $max_state ]; then echo "processor: Step 1: Test failed" exit 1 else echo "processor: Step 1: Test passed" fi echo "Removing throttle in one step" echo 0 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 10 ]; do cur_state=$(cat ${CDEV}/cur_state) echo "current state " ${cur_state} if [ $cur_state -le 0 ]; then echo "Reached min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $cur_state -gt 0 ]; then echo "processor: Step 2: Test failed" exit 1 else echo "processor: Step 2: Test passed" fi thermal_daemon-2.5.12/test/processor_acpi.xml000066400000000000000000000014171521301715300212750ustar00rootroot00000000000000 Example Laptop * QUIET _cpu x86_pkg_temp 70000 passive SEQUENTIAL 1 Processor 100 1 thermal_daemon-2.5.12/test/rapl.sh000077500000000000000000000055611521301715300170410ustar00rootroot00000000000000#!/bin/bash CONF_FILE="/etc/thermald/thermal-conf.xml.auto" echo "Executing test : Test intel_rapl cooling" cp intel_rapl.xml $CONF_FILE dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit sleep 5 THD0_ZONE=$(grep -r . /sys/class/thermal/* 2>/tmp/err.txt | grep type:x86_pkg_temp | sed 's/\/type.*//') #rapl_max_power=$(cat /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/constraint_0_max_power_uw) #rapl_min_power=$(expr $rapl_max_power / 2 ) rapl_max_power=25000000 rapl_min_power=10000000 echo "rapl_min_power:" $rapl_min_power echo "rapl_max_power:" $rapl_max_power cat ${THD0_ZONE}/temp sleep 2 echo "Forcing to throttle" echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 20 ]; do curr_power_limit=$(cat /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/constraint_0_power_limit_uw) echo "current state " ${curr_power_limit} if [ $curr_power_limit -le $rapl_min_power ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_power_limit -gt $rapl_min_power ]; then echo "intel_rapl: Step 0: Test failed" exit 1 else echo "intel_rapl: Step 0: Test passed" fi echo "Removing throttle slowly stepwise" echo 69000 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 20 ]; do curr_power_limit=$(cat /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/constraint_0_power_limit_uw) echo "current state " ${curr_power_limit} if [ $curr_power_limit -ge $rapl_max_power ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_power_limit -lt $rapl_max_power ]; then echo "intel_rapl: Step 1: Test failed" exit 1 else echo "intel_rapl: Step 1: Test passed" fi echo "Forcing throttle again " echo 70000 > ${THD0_ZONE}/emul_temp echo "Emulate temp to" cat ${THD0_ZONE}/temp COUNTER=0 while [ $COUNTER -lt 20 ]; do curr_power_limit=$(cat /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/constraint_0_power_limit_uw) echo "current state " ${curr_power_limit} if [ $curr_power_limit -le $rapl_min_power ]; then echo "Reached Min State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_power_limit -gt $rapl_min_power ]; then echo "intel_rapl: Step 0: Test failed" exit 1 else echo "intel_rapl: Step 0: Test passed" fi echo "Removing throttle in one step" echo 0 > ${THD0_ZONE}/emul_temp COUNTER=0 while [ $COUNTER -lt 20 ]; do curr_power_limit=$(cat /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/constraint_0_power_limit_uw) echo "current state " ${curr_power_limit} if [ $curr_power_limit -ge $rapl_max_power ]; then echo "Reached Max State" break fi sleep 5 let COUNTER=COUNTER+1 done if [ $curr_power_limit -lt $rapl_max_power ]; then echo "intel_rapl: Step 2: Test failed" exit 1 else echo "intel_rapl: Step 2: Test passed" fi thermal_daemon-2.5.12/test/readme_test.txt000066400000000000000000000005711521301715300205750ustar00rootroot00000000000000Tests are executed using a loopback Linux driver. Build this as a kernel module by copying thermald_test_kern_module.c to drivers/thermal in Linux kernel source. Add obj-m += thermald_test_kern_module.o in drivers/thermal/Makefile In addition enable in kernel .config CONFIG_THERMAL_EMULATION=y Once kernel driver is loaded using insmod/modprobe execute exec_config_tests.sh thermal_daemon-2.5.12/test/run_all_tests.sh000077500000000000000000000003431521301715300207520ustar00rootroot00000000000000#!/bin/bash echo "Make sure that thermald is not started with --adaptive option" source rapl.sh source intel_pstate.sh source powerclamp.sh source processor.sh #source cpufreq.sh source default.sh source exec_config_tests.sh thermal_daemon-2.5.12/test/test1.xml000066400000000000000000000020331521301715300173150ustar00rootroot00000000000000 THD_TEST_0 * QUIET thd_test_0 thd_test_0 * passive thd_cdev_0 thermal_daemon-2.5.12/test/test2.xml000066400000000000000000000025231521301715300173220ustar00rootroot00000000000000 THD_TEST_0 * QUIET thd_test_0 thd_test_0 * passive thd_cdev_0 80 thd_cdev_1 100 thermal_daemon-2.5.12/test/test3.xml000066400000000000000000000021331521301715300173200ustar00rootroot00000000000000 THD_TEST_0 * QUIET thd_test_0 thd_test_0 * passive thd_cdev_0 10 thermal_daemon-2.5.12/test/test4.xml000066400000000000000000000034631521301715300173300ustar00rootroot00000000000000 THD_TEST_0 * QUIET thd_test_0 thd_test_0 * passive thd_cdev_0 thd_test_1 thd_test_1 * passive thd_cdev_0 thermal_daemon-2.5.12/test/test5.xml000066400000000000000000000032431521301715300173250ustar00rootroot00000000000000 sysfs_zone_test * QUIET sysfs_sensor_0 /sys/kernel/thermald_test/sensor_temp 0 sysfs_thermal_zone_0 sysfs_sensor_0 40000 passive sysfs_cdev_0 sysfs_cdev_0 /sys/kernel/thermald_test/control_state 0 1 10 thermal_daemon-2.5.12/test/test6.xml000066400000000000000000000046251521301715300173330ustar00rootroot00000000000000 THD_TEST_0 * QUIET thd_test_ex_0 thd_test_0 40000 passive thd_cdev_0 3 thd_test_0 50000 passive thd_cdev_0 6 thd_test_0 60000 passive thd_cdev_0 thermal_daemon-2.5.12/test/test7.xml000066400000000000000000000056621521301715300173360ustar00rootroot00000000000000 THD_TEST_0 * QUIET thd_test_ex_0 thd_test_0 40000 passive thd_cdev_0_cap_3 thd_test_0 50000 passive thd_cdev_0_cap_6 thd_test_0 60000 passive thd_cdev_0_cap_9 /sys/class/thermal/cooling_device7/cur_state thd_cdev_0_cap_3 0 1 3 /sys/class/thermal/cooling_device7/cur_state thd_cdev_0_cap_6 4 1 6 /sys/class/thermal/cooling_device7/cur_state thd_cdev_0_cap_9 9 1 10 thermal_daemon-2.5.12/test/test_cases.txt000066400000000000000000000631461521301715300204450ustar00rootroot00000000000000Setup - Intel 8th gen system - Use "pch_skylake" to emulate temperature. It can be any sensor which can emulate a temperature. - One window: while true; do cat /sys/class/powercap/intel-rapl/intel-rapl\:0/constraint_0_power_limit_uw;sleep 1; done - Other window: sudo turbostat --show PkgWatt 1. Single step File Name: thermal_conf.xml.1 thermald command line $ sudo ./thermald --no-daemon --loglevel=info --ignore-default-control --config-file test/thermal_conf.xml.1 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET 0 6000 25000 28000 28000 250 auto_zone_1 pch_skylake 52000 Passive B0D4 5 1.1 No load: Current power is less than max like in this case 6W 1.1.1: Temp == 52C and then Temp == 51C Current power PkgWatt 2.76 2.76 #echo 52000 > emul_temp 25000000 25000000 25000000 25000000 6000000 6000000 6000000 6000000 6000000 6000000 6000000 #echo 51000 > emul_temp 6000000 6000000 6000000 6000000 6000000 6000000 6000000 6000000 6250000 6500000 6750000 7000000 7250000 7500000 7750000 8000000 8250000 .. .. 25000000 1.1.2 Temp == 52C and then drop below polling temp; #echo 52000 > emul_temp #echo 40000 > emul_temp 000000 25000000 6000000 6000000 6000000 6000000 6000000 6000000 6000000 6000000 6000000 6000000 6000000 6000000 25000000 25000000 Instead of stepwise the, power is set to 25W in one shot. 1.2 Full load 1.2.1 Go up to min state PkgWatt 14.96 14.96 PkgWatt 14.96 14.96 #echo 52000 > emul_temp 25000000 25000000 14375000 14375000 14375000 14375000 14375000 13500000 13500000 13500000 13500000 13500000 12500000 12500000 12500000 12500000 12500000 10500000 10500000 10500000 10500000 10500000 6500000 6500000 6500000 6500000 6500000 6000000 6000000 6000000 6000000 # echo 51000 > emul_temp 6000000 6250000 6500000 6750000 7000000 7250000 7500000 7750000 8000000 8250000 8500000 8750000 9000000 9250000 9500000 9750000 10000000 10250000 10500000 10750000 11000000 11250000 11500000 11750000 12000000 12250000 12500000 12750000 13000000 13250000 13500000 13750000 14000000 14250000 14500000 14750000 15000000 15250000 15500000 15750000 16000000 16250000 16500000 16750000 17000000 17250000 17500000 18000000 18250000 18500000 18750000 19000000 19250000 19500000 19750000 20000000 20250000 20500000 20750000 21000000 21250000 21500000 21750000 22000000 22250000 22500000 22750000 23000000 23250000 23500000 23750000 24000000 24250000 24500000 24750000 25000000 25000000 1.2.2 Not up to min The trip recovers before. 25000000 24500000 24500000 24500000 24500000 24500000 13500000 13500000 13500000 13750000 14000000 14250000 14500000 14750000 15000000 15250000 15500000 15750000 16000000 16250000 16500000 16750000 17000000 17250000 17500000 17750000 18000000 18250000 18500000 18750000 19000000 19250000 19500000 19750000 20000000 20250000 20500000 20750000 21000000 21250000 21500000 21750000 22000000 22250000 22500000 22750000 23000000 23250000 23500000 23750000 24000000 24250000 24500000 24750000 25000000 25000000 25000000 2. Single target state for max command line: $ sudo ./thermald --no-daemon --loglevel=info --ignore-default-control --config-file test/thermal_conf.xml.2 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET 0 6000 25000 28000 28000 250 auto_zone_1 pch_skylake 51000 Passive B0D4 0 2147483647 pch_skylake 52000 Passive B0D4 5 ~ Same as the above tests, but at 38C, the full power must be set to 25W. #echo 52000 > emul_temp 25000000 25000000 25000000 25000000 24375000 24375000 24375000 23875000 23875000 23875000 22875000 22875000 22875000 20875000 20875000 20875000 16875000 Wait for a few seconds # echo 51000 > emul_temp 25000000 25000000 25000000 25000000 25000000 25000000 25000000 25000000 # echo 50000 > emul_temp 25000000 25000000 Since already in min state, it will not change power but should not print anything in logs with info log level as there is nothing in the queue. 3. Multi step control command line: $ sudo ./thermald --no-daemon --loglevel=info --ignore-default-control --config-file test/thermal_conf.xml.3 - Run some busy workload to reach TDP - Configuration Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET 0 6000 15000 28000 28000 250 auto_zone_1 pch_skylake 38000 Passive B0D4 0 2147483647 pch_skylake 43000 Passive B0D4 5 13000000 pch_skylake 47000 Passive B0D4 5 12000000 pch_skylake 52000 Passive B0D4 5 Test cases Take one Step to 43C # echo 43000 > emul_temp Set :Power_limit: 13000000 Bring back to max target state #echo 38000 > emul_temp Set :Power_limit: 15000000 Take one Step to 43C # echo 43000 > emul_temp Set :Power_limit: 13000000 Set temp < max target state temperature #echo 35000 > emul_temp Set :Power_limit: 15000000 Take step to 43C # echo 43000 > emul_temp Set :Power_limit: 13000000 Take step to 47C # echo 47000 > emul_temp Set :Power_limit: 12000000 Bring back to max target state #echo 38000 > emul_temp Set :Power_limit: 15000000 Directly take two step to 47C # echo 47000 > emul_temp Set :Power_limit: 12000000 Take step to 52C where the exponenential step control is used #echo 52000 > emul_temp 5000000 14500000 14500000 14500000 14500000 14500000 13375000 13375000 13375000 13375000 13375000 12375000 12375000 12375000 12375000 12375000 10375000 10375000 10375000 10375000 10375000 10500000 10750000 11000000 11250000 11500000 11750000 Bring back the temp 1C lower. It will jump back to last clameped temp by 47C. Which is 12W #echo 52000 > emul_temp Set :Power_limit: 12000000 4. Control via powerclamp Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET auto_zone_1 pch_skylake 52000 Passive intel_powerclamp 5 - Run busy workload - In one window run turbostat sudo turbostat --show Core,CPU,Busy%,Bzy_MHz -i 1 echo 52000 > emul_temp Core CPU Busy% Bzy_MHz - - 98.78 3161 0 0 98.78 3161 0 4 98.78 3161 1 1 98.78 3161 1 5 98.78 3161 2 2 98.78 3161 2 6 98.78 3161 3 3 98.78 3161 3 7 98.78 3161 Core CPU Busy% Bzy_MHz - - 99.72 2196 0 0 99.72 2196 0 4 99.72 2196 1 1 99.72 2196 1 5 99.72 2196 2 2 99.72 2196 2 6 99.72 2196 3 3 99.72 2196 3 7 99.72 2196 Core CPU Busy% Bzy_MHz - - 97.40 2113 0 0 97.25 2112 0 4 97.83 2113 1 1 97.26 2112 1 5 97.86 2113 2 2 96.66 2113 2 6 97.86 2113 3 3 97.26 2113 3 7 97.26 2113 Core CPU Busy% Bzy_MHz - - 94.77 2156 0 0 94.71 2156 0 4 94.65 2156 1 1 94.75 2156 1 5 94.78 2156 2 2 94.75 2156 2 6 94.79 2156 3 3 94.82 2156 3 7 94.87 2156 Core CPU Busy% Bzy_MHz - - 94.21 2128 0 0 94.22 2128 0 4 94.21 2128 1 1 94.21 2128 1 5 94.20 2128 2 2 94.24 2128 2 6 94.19 2128 3 3 94.21 2128 3 7 94.22 2128 Core CPU Busy% Bzy_MHz - - 94.75 2192 0 0 94.74 2192 0 4 94.71 2192 1 1 94.73 2192 1 5 94.78 2192 2 2 94.77 2192 2 6 94.80 2192 3 3 94.74 2192 3 7 94.75 2192 Core CPU Busy% Bzy_MHz - - 94.75 2189 0 0 94.75 2189 0 4 94.70 2189 1 1 94.76 2189 1 5 94.74 2189 2 2 94.79 2189 2 6 94.77 2189 3 3 94.75 2188 3 7 94.74 2188 Core CPU Busy% Bzy_MHz - - 87.87 2209 0 0 87.93 2209 0 4 87.92 2209 1 1 87.90 2209 1 5 87.63 2209 2 2 87.83 2209 2 6 87.99 2208 3 3 87.84 2208 3 7 87.91 2208 Core CPU Busy% Bzy_MHz - - 84.41 2298 0 0 84.43 2297 0 4 84.42 2297 1 1 84.47 2298 1 5 84.42 2298 2 2 84.40 2298 2 6 84.30 2299 3 3 84.37 2299 3 7 84.51 2299 Core CPU Busy% Bzy_MHz - - 84.36 2300 0 0 84.51 2300 0 4 84.28 2300 1 1 84.27 2300 1 5 84.27 2300 2 2 84.32 2300 2 6 84.31 2300 3 3 84.46 2300 3 7 84.46 2300 Core CPU Busy% Bzy_MHz - - 84.11 2307 0 0 84.13 2307 0 4 84.14 2307 1 1 84.14 2307 1 5 84.02 2306 2 2 84.08 2307 2 6 84.19 2306 3 3 84.10 2307 3 7 84.09 2306 Core CPU Busy% Bzy_MHz - - 83.97 2334 0 0 84.01 2334 0 4 83.97 2334 1 1 83.91 2334 1 5 83.97 2334 2 2 84.06 2334 2 6 83.94 2334 3 3 83.90 2334 3 7 84.00 2334 Core CPU Busy% Bzy_MHz - - 77.10 2320 0 0 77.10 2320 0 4 77.06 2320 1 1 77.17 2320 1 5 77.06 2320 2 2 77.13 2320 2 6 77.01 2320 3 3 77.20 2320 3 7 77.08 2320 Core CPU Busy% Bzy_MHz - - 73.13 2300 0 0 73.21 2300 0 4 73.10 2300 1 1 73.28 2300 1 5 73.04 2300 2 2 73.04 2300 2 6 73.12 2300 3 3 73.05 2300 3 7 73.15 2300 Core CPU Busy% Bzy_MHz - - 73.47 2299 0 0 73.55 2299 0 4 73.47 2299 1 1 73.55 2299 1 5 73.19 2299 2 2 73.60 2299 2 6 73.58 2299 3 3 73.37 2299 3 7 73.46 2299 Core CPU Busy% Bzy_MHz - - 73.70 2183 0 0 73.65 2183 0 4 73.65 2183 1 1 73.81 2183 1 5 73.66 2183 2 2 73.82 2183 2 6 73.85 2183 3 3 73.48 2183 3 7 73.67 2183 Core CPU Busy% Bzy_MHz - - 73.12 2100 0 0 73.17 2100 0 4 73.16 2100 1 1 73.16 2100 1 5 73.00 2100 2 2 73.24 2100 2 6 73.06 2100 3 3 73.07 2100 3 7 73.08 2100 Core CPU Busy% Bzy_MHz - - 59.52 1685 0 0 59.81 1686 0 4 59.55 1686 1 1 59.48 1684 1 5 59.57 1686 2 2 59.27 1686 2 6 59.37 1686 3 3 59.51 1684 3 7 59.55 1684 Core CPU Busy% Bzy_MHz - - 52.00 800 0 0 52.10 800 0 4 51.92 800 1 1 51.99 800 1 5 52.02 800 2 2 52.09 800 2 6 51.85 800 3 3 52.13 800 3 7 51.91 800 Core CPU Busy% Bzy_MHz - - 53.40 800 0 0 53.58 800 0 4 52.98 800 1 1 53.92 800 1 5 53.05 800 2 2 53.51 800 2 6 53.31 800 3 3 53.48 800 3 7 53.41 800 Core CPU Busy% Bzy_MHz - - 52.69 800 0 0 52.84 800 0 4 52.71 800 1 1 52.51 800 1 5 52.56 800 2 2 52.66 800 2 6 52.67 800 3 3 52.82 800 3 7 52.72 800 Core CPU Busy% Bzy_MHz - - 52.39 800 0 0 52.26 800 0 4 52.19 800 1 1 52.88 800 1 5 52.57 800 2 2 52.10 800 2 6 52.32 800 3 3 51.92 800 3 7 52.92 800 Core CPU Busy% Bzy_MHz - - 49.97 800 0 0 50.79 800 0 4 50.30 800 1 1 50.19 800 1 5 50.00 800 2 2 50.13 800 2 6 50.07 800 3 3 50.36 800 3 7 47.90 800 #echo 51000 > emul_temp CCore CPU Busy% Bzy_MHz - - 99.38 1908 0 0 98.64 1909 0 4 99.49 1908 1 1 99.49 1908 1 5 99.49 1908 2 2 99.49 1908 2 6 99.49 1908 3 3 99.49 1908 3 7 99.49 1908 5. Control via intel_pstate Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET auto_zone_1 pch_skylake 52000 Passive intel_pstate 5 echo 52000 > emul_temp #while true; do cat /sys/devices/system/cpu/intel_pstate/max_perf_pct; cat /sys/devices/system/cpu/intel_pstate/no_turbo; sleep 1; done 100 0 90 0 90 0 90 0 90 0 90 0 70 1 70 1 70 1 70 1 70 1 50 1 50 1 50 1 50 1 50 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 10 1 #echo 51000 > emul_temp 10 1 20 1 30 1 40 1 50 1 60 1 70 1 80 0 90 0 100 0 100 6. Default with no XML config ..sensors.. [INFO]sensor index:5 x86_pkg_temp /sys/class/thermal/thermal_zone5/ Async:1 [INFO]..trips.. [INFO]index 0: type:passive temp:95000 hyst:0 zone id:7 sensor id:65535 control_type:1 cdev size:4 [INFO]cdev[0] rapl_controller, Sampling period: 0 [INFO] target_state:not defined [INFO]cdev[1] intel_pstate, Sampling period: 0 [INFO] target_state:not defined [INFO]cdev[2] intel_powerclamp, Sampling period: 0 [INFO] target_state:not defined [INFO]cdev[3] Processor, Sampling period: 0 [INFO] target_state:not defined [INFO]index 1: type:polling temp:85500 hyst:0 zone id:7 sensor id:5 control_type:0 cdev size:0 Open one window and monitor for each command. Run a busy workload. # while true; do cat /sys/class/powercap/intel-rapl/intel-rapl\:0/constraint_0_power_limit_uw;sleep 1; done On this platform max power is 15W # while true; do cat /sys/devices/system/cpu/intel_pstate/max_perf_pct; cat /sys/devices/system/cpu/intel_pstate/no_turbo; sleep 1; done In powerclamp and processor cooling device folder # while true; do cat cur_state ; sleep 1; done; Monitor by emulating 95C to x86_pkg_temp #echo 95000 > emul_temp First rapl power limit transition from 15W to 10W, then pstate percent drop from 100 to 10 and then power clamp curr_state from -1 to ~40 followed by the processor state. When temperature trip is recovered via #echo 94000 > emul_temp The device gets activated in the reverse fashion stepwise. Also try by directly emulating less than 20% drop. For example: echo 65000 > emul_temp This dectivates all the devices in one shot without going stepwise. 7. Multiple zones with the same target Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET 0 6000 15000 28000 28000 250 auto_zone_1 pch_skylake 38000 Passive B0D4 0 2147483647 pch_skylake 43000 Passive B0D4 5 13000000 pch_skylake 47000 Passive B0D4 5 12000000 pch_skylake 52000 Passive B0D4 5 auto_zone_2 x86_pkg_temp 38000 Passive B0D4 0 2147483647 x86_pkg_temp 43000 Passive B0D4 5 13000000 x86_pkg_temp 47000 Passive B0D4 5 12000000 x86_pkg_temp 52000 Passive B0D4 5 Emulate temperature in both x86_pkg_temp and pch_skylake. -Set 43C to pch_skylake Resulting power 13000000 -Set 47C to x86_pkg_temp Resulting power 12000000 -Set 38C to pch_skylake Resulting power 12000000 - Set 38C to x86_pkg_temp Resulting power 25000 8. intel_powerclamp control Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET auto_zone_1 pch_skylake 52000 Passive intel_powerclamp 5 Run some busy workload emulate temp to 52C monitir the powerclamp cooling device state in the sysfs. it should start from -1 4 3 3 3 4 11 10 11 10 12 .. then emulate to 51C the curr_state slowly brought back to -1 Repeat by emulating to 30C after 52C instead of 51C the curr_state in set to -1 in one shot. 10. intel_pstate Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET auto_zone_1 pch_skylake 52000 Passive intel_pstate 5 ~ Like previous test repeat with emulating 52C, 51C and 30C. Activation: Dump of max_perf_pct and no_turbo 100 0 -- 100 0 -- 90 0 -- 90 0 -- 90 0 -- 90 0 -- 90 0 -- 70 1 -- 70 1 -- 70 1 -- 70 1 -- 70 1 -- 50 1 -- 50 1 -- 50 1 -- 50 1 -- 50 1 -- 10 1 -- Deactivation 10 1 -- 20 1 -- 30 1 -- 40 1 -- 50 1 -- 60 1 -- 70 1 -- 80 0 -- 90 0 -- 100 0 11. Processor cooling device Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET auto_zone_1 pch_skylake 52000 Passive Processor 5 Use the command while true; do for i in {0..7}; do cat cooling_device$i/cur_state; sleep 1; done; echo "--";done Same as previous step once temperature is emulated to 52C, monitor output of the above command. It starts with 0. Also measure the power in one window. Run a busy workload. The power should drop as the state increments. When deactivated the state prints should return to 0. thermal_daemon-2.5.12/test/test_data_vault.bin000066400000000000000000000040441521301715300214140ustar00rootroot00000000000000@gddvTest data vaultb>)[+\9.XRo2REPO]Zrm$G=$oEf"g 9%R u8BAM/nS)缆+mNJ{V-5ľr*2M8=𬊂k6~d1.4 VWpup% Jb%:4s kR{=™a+E`2>sjc:آc1L^XsJOSRZ_:24/a&E.u1xsdRjLJ#`@Aa'r)oPeEv7\2T$Sr'.[ˌD7Ιo9a0wբNp12'8Oew*͊ð>*@ DƭtR386@]F:N|'REgUS&MJ03P54Н+T?H9}#[qYwt.f% j@diF*xǢ/gDVH).#k@! _4 2OYr8fvS/->m.|yJJ>-VxyXӎX&:?g=E;FIX>= MU9MGq"9TTk(uEQJl}J?ߢɑr? غ+>l:@rƔ;s?]x ѹ)'0Q?GS"YU~nh/ X/{> g.rD1.M~ܵ}~Lw'i+/IUL"vQu.^&'ku8`گxj)-AM5êu{EXG|SdO᠆ 7H#Pf%m W[TRU紓-8=D:tn&b_՘g"Km|WR7,^+Y$hk|%]Pct#WFV7"TQx}]TO>d6̦_VArɄ vrAܩ! ǤS-WR`5 Hϛ΍nf!At3Xʩ#KssxBC෸DVo50י]@^cfZD\D{@\}wɹmxǀҰu՝]ܛ|Eψ:Pw0Ta `B( ~pF= /DNmJoeo>,9>X59vpi {)xd+lfvT ލ!?Fv8! rkZmithermal_daemon-2.5.12/test/thermal-debug-dump-fedora.sh000077500000000000000000000036011521301715300230150ustar00rootroot00000000000000#!/bin/bash echo "Installing Packages" dnf install -y automake autoconf-archive gcc gcc-c++ \ glib-devel dbus-glib-devel libxml2-devel \ gtk-doc upower-devel libevdev-devel kernel-tools \ stress-ng acpidump status=$? if [ $status -eq 0 ]; then echo "Packages installed successfully" else echo "Package install failed" exit 1 fi Day=`date +%d` Hour=`date +%H` Minute=`date +%M` Second=`date +%S` folder_name=$Day$Hour$Minute$Second echo $folder_name mkdir $folder_name grep . /sys/class/powercap/intel-rapl/intel-rapl\:0/* > $folder_name/powercap_msr.txt grep . /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/* > $folder_name/powercap_mmio.txt grep . /sys/devices/system/cpu/cpu0/cpufreq/* > $folder_name/cpufreq.txt acpidump -o $folder_name/acpi_dump.out mkdir debug cd debug echo "Downloading latest thermald" git clone https://github.com/intel/thermal_daemon.git status=$? if [ $status -eq 0 ]; then echo "git clone successful" else echo "git clone failed" exit 1 fi cd thermal_daemon echo "Building thermald" ./autogen.sh prefix=/ make -j8 status=$? if [ $status -eq 0 ]; then echo "build successful" else echo "build failed" exit 1 fi systemctl stop thermald cd ../../$folder_name echo "Starting thermald" ../debug/thermal_daemon/thermald --no-daemon --loglevel=debug --adaptive --ignore-cpuid-check > thermald_log.txt& sleep 30 grep . /sys/class/powercap/intel-rapl/intel-rapl\:0/* > powercap_msr_after.txt grep . /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/* > powercap_mmio_after.txt echo "Executing stress-ng" stress-ng --cpu -1 --io 4 --vm 2 --vm-bytes 128M --fork 4 --timeout 300s& turbostat --show Core,CPU,Busy%,Bzy_MHz,TSC_MHz -o turbostat.out& sleep 305 pkill thermald pkill turbostat systemctl start thermald cd .. echo -n "Creating archive:" echo $folder_name.tar.gz tar cvfz $folder_name.tar.gz $folder_name echo -n "Attach archive to debug:" echo $folder_name.tar.gz thermal_daemon-2.5.12/test/thermal-debug-dump-ubuntu.sh000077500000000000000000000036231521301715300231030ustar00rootroot00000000000000#!/bin/bash echo "Installing Packages" apt install -y git autoconf-archive g++ libglib2.0-dev libdbus-1-dev \ libdbus-glib-1-dev libxml2-dev gtk-doc-tools libupower-glib-dev \ libevdev-dev stress-ng linux-tools-common acpidump status=$? if [ $status -eq 0 ]; then echo "Packages installed successfully" else echo "Package install failed" exit 1 fi Day=`date +%d` Hour=`date +%H` Minute=`date +%M` Second=`date +%S` folder_name=$Day$Hour$Minute$Second echo $folder_name mkdir $folder_name grep . /sys/class/powercap/intel-rapl/intel-rapl\:0/* > $folder_name/powercap_msr.txt grep . /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/* > $folder_name/powercap_mmio.txt grep . /sys/devices/system/cpu/cpu0/cpufreq/* > $folder_name/cpufreq.txt acpidump -o $folder_name/acpi_dump.out mkdir debug cd debug echo "Downloading latest thermald" git clone https://github.com/intel/thermal_daemon.git status=$? if [ $status -eq 0 ]; then echo "git clone successful" else echo "git clone failed" exit 1 fi cd thermal_daemon echo "Building thermald" ./autogen.sh prefix=/ make -j8 status=$? if [ $status -eq 0 ]; then echo "build successful" else echo "build failed" exit 1 fi systemctl stop thermald cd ../../$folder_name echo "Starting thermald" ../debug/thermal_daemon/thermald --no-daemon --loglevel=debug --adaptive --ignore-cpuid-check > thermald_log.txt& sleep 30 grep . /sys/class/powercap/intel-rapl/intel-rapl\:0/* > powercap_msr_after.txt grep . /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/* > powercap_mmio_after.txt echo "Executing stress-ng" stress-ng --cpu -1 --io 4 --vm 2 --vm-bytes 128M --fork 4 --timeout 300s& turbostat --show Core,CPU,Busy%,Bzy_MHz,TSC_MHz -o turbostat.out& sleep 305 pkill thermald pkill turbostat systemctl start thermald cd .. echo -n "Creating archive:" echo $folder_name.tar.gz tar cvfz $folder_name.tar.gz $folder_name echo -n "Attach archive to debug:" echo $folder_name.tar.gz thermal_daemon-2.5.12/test/thermal-dptf-tables-test-fedora.sh000077500000000000000000000025501521301715300241500ustar00rootroot00000000000000#!/bin/bash echo "This test will exit by printing error if there are no DPTF tables found" echo "Installing Packages" dnf install -y automake autoconf-archive gcc gcc-c++ \ glib-devel dbus-glib-devel libxml2-devel \ gtk-doc upower-devel libevdev-devel status=$? if [ $status -eq 0 ]; then echo "Packages installed successfully" else echo "Package install failed" exit 1 fi Day=`date +%d` Hour=`date +%H` Minute=`date +%M` Second=`date +%S` folder_name=$Day$Hour$Minute$Second echo $folder_name mkdir $folder_name grep . /sys/class/powercap/intel-rapl/intel-rapl\:0/* > $folder_name/powercap_msr.txt grep . /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/* > $folder_name/powercap_mmio.txt grep . /sys/devices/system/cpu/cpu0/cpufreq/* > $folder_name/cpufreq.txt acpidump -o $folder_name/acpi_dump.out mkdir debug cd debug echo "Downloading latest thermald" git clone https://github.com/intel/thermal_daemon.git status=$? if [ $status -eq 0 ]; then echo "git clone successful" else echo "git clone failed" exit 1 fi cd thermal_daemon echo "Building thermald" ./autogen.sh prefix=/ make -j8 status=$? if [ $status -eq 0 ]; then echo "build successful" else echo "build failed" exit 1 fi systemctl stop thermald cd ../../$folder_name echo "Starting thermald" ../debug/thermal_daemon/thermald --no-daemon --loglevel=debug --adaptive --ignore-cpuid-check --test-mode thermal_daemon-2.5.12/test/thermal-dptf-tables-test-ubuntu.sh000077500000000000000000000025651521301715300242400ustar00rootroot00000000000000#!/bin/bash echo "This test will exit by printing error if there are no DPTF tables found" echo "Installing Packages" apt install -y git autoconf-archive g++ libglib2.0-dev libdbus-1-dev \ libdbus-glib-1-dev libxml2-dev gtk-doc-tools libupower-glib-dev \ libevdev-dev status=$? if [ $status -eq 0 ]; then echo "Packages installed successfully" else echo "Package install failed" exit 1 fi Day=`date +%d` Hour=`date +%H` Minute=`date +%M` Second=`date +%S` folder_name=$Day$Hour$Minute$Second echo $folder_name mkdir $folder_name grep . /sys/class/powercap/intel-rapl/intel-rapl\:0/* > $folder_name/powercap_msr.txt grep . /sys/class/powercap/intel-rapl-mmio/intel-rapl-mmio\:0/* > $folder_name/powercap_mmio.txt grep . /sys/devices/system/cpu/cpu0/cpufreq/* > $folder_name/cpufreq.txt acpidump -o $folder_name/acpi_dump.out mkdir debug cd debug echo "Downloading latest thermald" git clone https://github.com/intel/thermal_daemon.git status=$? if [ $status -eq 0 ]; then echo "git clone successful" else echo "git clone failed" exit 1 fi cd thermal_daemon echo "Building thermald" ./autogen.sh prefix=/ make -j8 status=$? if [ $status -eq 0 ]; then echo "build successful" else echo "build failed" exit 1 fi systemctl stop thermald cd ../../$folder_name echo "Starting thermald" ../debug/thermal_daemon/thermald --no-daemon --loglevel=debug --adaptive --ignore-cpuid-check --test-mode thermal_daemon-2.5.12/test/thermal_conf.xml.1000066400000000000000000000026321521301715300210620ustar00rootroot00000000000000 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET 0 6000 25000 28000 28000 250 auto_zone_1 pch_skylake 52000 Passive B0D4 5 thermal_daemon-2.5.12/test/thermal_conf.xml.2000066400000000000000000000040421521301715300210600ustar00rootroot00000000000000 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET 0 6000 25000 28000 28000 250 auto_zone_1 pch_skylake 51000 Passive B0D4 0 2147483647 pch_skylake 52000 Passive B0D4 5 thermal_daemon-2.5.12/test/thermal_conf.xml.3000066400000000000000000000035031521301715300210620ustar00rootroot00000000000000 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET 0 6000 15000 28000 28000 250 auto_zone_1 pch_skylake 38000 Passive B0D4 0 2147483647 pch_skylake 43000 Passive B0D4 5 13000000 pch_skylake 47000 Passive B0D4 5 12000000 pch_skylake 52000 Passive B0D4 5 thermal_daemon-2.5.12/test/thermal_conf.xml.4000066400000000000000000000060011521301715300210570ustar00rootroot00000000000000 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET 0 6000 15000 28000 28000 250 auto_zone_1 pch_skylake 38000 Passive B0D4 0 2147483647 pch_skylake 43000 Passive B0D4 5 13000000 pch_skylake 47000 Passive B0D4 5 12000000 pch_skylake 52000 Passive B0D4 5 auto_zone_2 x86_pkg_temp 38000 Passive B0D4 0 2147483647 x86_pkg_temp 43000 Passive B0D4 5 13000000 x86_pkg_temp 47000 Passive B0D4 5 12000000 x86_pkg_temp 52000 Passive B0D4 5 thermal_daemon-2.5.12/test/thermal_conf.xml.5000066400000000000000000000020661521301715300210670ustar00rootroot00000000000000 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET auto_zone_1 pch_skylake 52000 Passive intel_powerclamp 5 thermal_daemon-2.5.12/test/thermal_conf.xml.6000066400000000000000000000020621521301715300210640ustar00rootroot00000000000000 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET auto_zone_1 pch_skylake 52000 Passive intel_pstate 5 thermal_daemon-2.5.12/test/thermal_conf.xml.7000066400000000000000000000020571521301715300210710ustar00rootroot00000000000000 Auto generated HP Spectre x360 Convertible 15-ch0xx QUIET auto_zone_1 pch_skylake 52000 Passive Processor 5 thermal_daemon-2.5.12/test/thermal_conf.xml.itmt000066400000000000000000000046411521301715300217010ustar00rootroot00000000000000 Example Laptop * QUIET 0 5000 40000 28000 28000 500 SEN3 SEN3 50000 passive 1 rapl_controller_mmio 100 4 35000000 10000000 SEN4 SEN4 40000 passive 1 rapl_controller_mmio 100 4 30000000 15000000 SEN5 SEN5 59000 passive 1 rapl_controller_mmio 100 4 40000000 18000000 thermal_daemon-2.5.12/test/thermal_conf.xml.virtual_sensor000066400000000000000000000042061521301715300240000ustar00rootroot00000000000000 Example Laptop * QUIET 0 10000 25000 28000 28000 250 virtual_sensor 1 CONSTANT -3.591 1.0 0 SEN4 -0.165 0.076 0 SEN5 1.140 0.09 0 30000 2 40000 1 _cpu virtual_sensor 50000 passive SEQUENTIAL 1 rapl_controller_mmio 100 1 thermal_daemon-2.5.12/test/thermal_config_dump.py000066400000000000000000000066301521301715300221220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # script to dump thermal config from the running thermald via dbus # The caller must be in "power" group import dbus import os def dump_thermal_config(): if os.path.exists(file_name): os.remove(file_name) f_handle = open(file_name, 'a') system_bus = dbus.SystemBus() thd = system_bus.get_object('org.freedesktop.thermald', '/org/freedesktop/thermald') thd_intf = dbus.Interface(thd, dbus_interface='org.freedesktop.thermald') zone_count = thd_intf.GetZoneCount() string_buffer = 'zones count:' + str(zone_count) + '\n' f_handle.write(string_buffer) sensor_count = thd_intf.GetSensorCount() string_buffer = 'sensors count:' + str(sensor_count) + '\n' f_handle.write(string_buffer) f_handle.write('\n') for i in range(zone_count): zone = thd_intf.GetZoneInformation(i) string_buffer = 'zone id' + ': ' + str(i) + ''' ''' f_handle.write(string_buffer) string_buffer = '\tzone name' + ': ' + zone[0] + '\n' f_handle.write(string_buffer) string_buffer = '\tSensor Count' + ': ' + str(zone[1]) + '\n' f_handle.write(string_buffer) for j in range(zone[1]): sensor = thd_intf.GetZoneSensorAtIndex(i, j) string_buffer = '\t\t sensor: ' + sensor + '\n' f_handle.write(string_buffer) string_buffer = '\tTrip Count' + ': ' + str(zone[2]) + '\n' f_handle.write(string_buffer) for j in range(zone[2]): string_buffer = '\tTrip id' + ': ' + str(j) + '\n' f_handle.write(string_buffer) trip = thd_intf.GetZoneTripAtIndex(i, j) string_buffer = '\t\t\t trip temp' + ': ' + str(trip[0]) \ + '\n' f_handle.write(string_buffer) string_buffer = '\t\t\t trip type' + ': ' + str(trip[1]) \ + '\n' f_handle.write(string_buffer) string_buffer = '\t\t\t sensor id' + ': ' + str(trip[2]) \ + '\n' f_handle.write(string_buffer) string_buffer = ''' ''' f_handle.write(string_buffer) cdev_count = thd_intf.GetCdevCount() string_buffer = 'cdev count:' + str(cdev_count) + '\n' f_handle.write(string_buffer) f_handle.write('\n') for i in range(cdev_count): string_buffer = 'cdev id' + ': ' + str(i) + '\n' f_handle.write(string_buffer) cdev = thd_intf.GetCdevInformation(i) string_buffer = '\t\t type' + ': ' + str(cdev[0]) + '\n' f_handle.write(string_buffer) string_buffer = '\t\t min_state' + ': ' + str(cdev[1]) + '\n' f_handle.write(string_buffer) string_buffer = '\t\t max_state' + ': ' + str(cdev[2]) + '\n' f_handle.write(string_buffer) string_buffer = '\t\t current_state' + ': ' + str(cdev[3]) \ + '\n' f_handle.write(string_buffer) f_handle.close() def remove_space(string): return string.replace(' ', '') if __name__ == '__main__': product_name = '/sys/class/dmi/id/product_name' try: file_name = open(product_name, 'r').read() except: print ('Error opening ', file_name) sys.exit(2) file_name = '/tmp/' + file_name.strip() file_name = remove_space(file_name) + '.config' print ('File Name to dump config:', file_name) dump_thermal_config() thermal_daemon-2.5.12/test/thermal_temp_dump.py000066400000000000000000000043451521301715300216230ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # script to dump temperature samples every 10 seconds # The caller must be in "power" group to call the script import dbus import os import time def dump_temperature(): if os.path.exists(file_name): os.remove(file_name) f_handle = open(file_name, 'a') system_bus = dbus.SystemBus() thd = system_bus.get_object('org.freedesktop.thermald', '/org/freedesktop/thermald') thd_intf = dbus.Interface(thd, dbus_interface='org.freedesktop.thermald') sensor_count = thd_intf.GetSensorCount() string_buffer = 'sensor count:' + str(sensor_count) + '\n' f_handle.write(string_buffer) f_handle.write('\n') string_buffer = '' for i in range(sensor_count): sensor_info = thd_intf.GetSensorInformation(i) string_buffer = string_buffer + sensor_info[0] + ',' f_handle.write(string_buffer) f_handle.write('\n') string_buffer = '' f_lock = open('/tmp/thermal_temp_dump.lock', 'a') f_lock.close() while True: if os.path.exists('/tmp/thermal_temp_dump.lock') == False: break string_buffer = '' for i in range(sensor_count): sensor = thd_intf.GetSensorTemperature(i) string_buffer = string_buffer + str(sensor / 1000) + ',' f_handle.write(string_buffer) f_handle.write('\n') time.sleep(10) f_handle.close() def remove_spaces(string): return string.replace(' ', '') if __name__ == '__main__': product_name = '/sys/class/dmi/id/product_name' try: file_name = open(product_name, 'r').read() except: print ('Error opening ', file_name) sys.exit(2) file_name = '/tmp/' + file_name.strip() file_name = remove_spaces(file_name) + '.csv' print ('File Name to dump config:', file_name) print ('Before starting this script, do the following steps') print ('sudo touch /var/run/thermald/debug_mode') print ('sudo systemctl restart thermald') print ('This will also dump power data from rapl as samples') print ('This script will loop forever till user deletes file') print ('/tmp/thermal_temp_dump.lock') dump_temperature() thermal_daemon-2.5.12/test/thermald_debug_output_graph.py000066400000000000000000000164311521301715300236630ustar00rootroot00000000000000# This script will plot temperature and trip graph from the thermald debug output # Run thermald with loglevel=debug # For example: # # systemctl stop thermald # thermald --no-daemon --loglevel=debug --adaptive --ignore-cpuid-check # Run your workload, and when done copy the output dumped on the screen # to a file. For example if the output file is # This output file is input to this script "thermald_log.txt" # Then use: # python thermald_debug_output_graph.py thermald_log.txt # # Prerequisites: # Python version 3.x or higher # gnuplot 5.0 or higher # python3-gnuplot 1.8 or higher # (Most of the distributions have these required packages. They may be called # gnuplot-py, python-gnuplot or python3-gnuplot, gnuplot-nox, ... ) # In case Gnuplot is not present, the script will use pygnuplot. To do so, install # pygnuplot using pip install pygnuplot. In case can't install with pip, download # the pygnuplot.py file from https://pypi.org/project/py-gnuplot # To install use "python3 setup.py install". It may ask for pandas, install it # using "pip install pandas" or from distro install python3-pandas. import sys import re use_pygnuplot = False try: import Gnuplot except ImportError: from pygnuplot import gnuplot use_pygnuplot = True sensors = [] temperature = [[]] temperature_count = [] max_range = 0 zones = [] trips = [[]] # Parse file for temperature def plot_temperature(): output_png = "temperature.png" if use_pygnuplot: g_plot = gnuplot.Gnuplot() else: g_plot = Gnuplot.Gnuplot() g_plot('set ylabel "Degree C"') g_plot('set xlabel "Samples"') g_plot("set yrange [0:100]") g_plot("set key autotitle columnhead") g_plot('set title "Temperature plot"') g_plot("set term png size 1200, 600") g_plot('set output "' + output_png + '"') print("Plotting using gnuplot, the output file name is : ", output_png) # g_plot('plot "' + output_png + '" using 1:(column(n)) w lines title columnhead(n), for [n=2:50] "trips.csv" using 1:(column(n)) w dots title # columnhead(n)') g_plot( 'plot for [n=2:12] "temperature.csv" u 1:(column(n)) w lines title columnhead(n), for [n=2:50] "trips.csv" u 1:(column(n)) w dots title columnhead(n)' ) def parse_trips(log_file): print("parsing trips from", log_file) log_fd = open(log_file, "r") data = log_fd.readlines() match_index = 0 line_index = 0 for line in data: x = re.search("ZONE DUMP BEGIN", line) if x: match_index = line_index line_index = line_index + 1 for x in range(match_index, line_index): s = re.search("Zone.*:", data[x]) if s: tokens = data[x].split() zone = tokens[2].split(",") zones.append(zone[0]) y = re.search("ZONE DUMP END", data[x]) if y: break index = 0 trips.append([]) # 3 for point to 3rd line starting from "ZONE DUMP BEGIN" for x in range(match_index + 3, line_index): s = re.search("passive temp:*", data[x]) if s: tokens = data[x].split() trip = tokens[3].split(":") str1 = str(int(trip[1]) / 1000) trips[index].append(str1) z = re.search("Zone.*:", data[x]) if z: index = index + 1 trips.append([]) y = re.search("ZONE DUMP END", data[x]) if y: break max_index = index f_handle = open("trips.csv", "w") string_buffer = "index, " for x in range(len(zones)): id = 0 for y in range(len(trips[x])): string_buffer += zones[x] string_buffer += "_trip_0" string_buffer += str(id) if x != (len(zones) - 1) and y != (len(trips) - 1): string_buffer += ", " id = id + 1 string_buffer += "\n" f_handle.write(string_buffer) string_buffer = "0," for i in range(max_range): string_buffer = str(i) string_buffer += ", " for x in range(len(zones)): for y in range(len(trips[x])): string_buffer += trips[x][y] if x != (len(zones) - 1) and y != (len(trips) - 1): string_buffer += ", " string_buffer += "\n" f_handle.write(string_buffer) f_handle.close() f_handle.close() def parse_temperature(log_file): # Parse file for temperature global max_range print("parsing temperature from", log_file) sensors.append("PL1") temperature_count.append(0) temp = [] temperature.append(temp) log_fd = open(log_file, "r") data = log_fd.readlines() next_line = 0 for line in data: next_line = next_line + 1 x = re.search(".*wr:", line) if x: tokens = line.split() tokens = tokens[7].split(":") str1 = tokens[1] val = int(str1) val = val / 1000000 if (val > 100): continue; str1 = str(val) temperature[0].append(str1) temperature_count[0] = temperature_count[0] + 1 if max_range < temperature_count[0]: max_range = temperature_count[0] x = re.search("Sensor.*:temp", line) if not x: x = re.search("Sensor.*:power", line) if x: if "compare" in data[next_line]: continue tokens = line.split() skip = 0 i = 0 for sensor in sensors: if sensor == tokens[1]: skip = 1 break i = i + 1 if skip == 0: sensors.append(tokens[1]) temperature_count.append(0) temp = [] temperature.append(temp) str1 = str(int(tokens[3]) / 1000) temperature[i].append(str1) temperature_count[i] = temperature_count[i] + 1 if max_range < temperature_count[i]: max_range = temperature_count[i] f_handle = open("temperature.csv", "w") last_val= [] string_buffer = "index, " for x in range(len(sensors)): last_val.append(0) if x == len(sensors) - 1: string_buffer += sensors[x] else: string_buffer += sensors[x] + ", " string_buffer += "\n" f_handle.write(string_buffer) index = 0 for y in range(max_range): string_buffer = str(index) + ", " index = index + 1 for x in range(len(sensors)): if y < temperature_count[x]: last_val[x] = temperature[x][y] if x == len(sensors) - 1: string_buffer += temperature[x][y] else: string_buffer += temperature[x][y] + ", " else: string_buffer += str(last_val[x]) + ", " string_buffer += "\n" f_handle.write(string_buffer) f_handle.close() log_fd.close() # Main entry point # Get the debug file name from command line print("Number of arguments:", len(sys.argv), "arguments.") print("Argument List:", str(sys.argv)) if len(sys.argv) < 2: print("Error: Need the logfile as argument") sys.exit() # Parse temperature parse_temperature(str(sys.argv[1])) parse_trips(str(sys.argv[1])) plot_temperature() thermal_daemon-2.5.12/test/thermald_test_kern_module.c000066400000000000000000000143471521301715300231350ustar00rootroot00000000000000/* * Test module to test thermald * * Copyright (C) 2015, Intel Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ /* * To build add * obj-m += thermald_test_kern_module.o in drivers/thermal/Makefile */ #include #include #include #include #include #include #include static char *zone_name = "none"; module_param(zone_name, charp, 0644); MODULE_PARM_DESC(zone_name, "Add a test zone name"); #define SENSOR_COUNT 12 #define CDEV_COUNT 12 struct thermald_sensor { struct thermal_zone_device *tzone; struct thermal_trip *trips; int num_trips; }; struct thermald_cdev { struct thermal_cooling_device *cdev; unsigned long max_state; unsigned long curr_state; }; static struct thermald_sensor *sensors[SENSOR_COUNT]; static struct thermald_cdev *cdevs[CDEV_COUNT]; static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp) { *temp = 10000; return 0; } static struct thermal_zone_device_ops tzone_ops = { .get_temp = sys_get_curr_temp, }; static int init_trips(struct thermald_sensor *sensor) { struct thermal_trip *trips; int i, num_trips = 2; trips = kzalloc(sizeof(*trips) * num_trips, GFP_KERNEL); if (!trips) return -ENOMEM; for (i = 0; i < num_trips; ++i) { trips[i].temperature = 40000; trips[i].type = THERMAL_TRIP_PASSIVE; } sensor->trips = trips; sensor->num_trips = num_trips; return 0; } static struct thermald_sensor *create_test_tzone(int id, char *_name) { struct thermald_sensor *sensor; char name[20]; sensor = kzalloc(sizeof(*sensor), GFP_KERNEL); if (!sensor) return NULL; init_trips(sensor); if (!_name) snprintf(name, sizeof(name), "thd_test_%d", id); else strncpy(name, _name, sizeof(name) -1); sensor->tzone = thermal_zone_device_register_with_trips(name, sensor->trips, sensor->num_trips, sensor, &tzone_ops, NULL, 0, 0); if (IS_ERR(sensor->tzone)) { kfree(sensor); return NULL; } return sensor; } static void destroy_test_tzone(struct thermald_sensor *sensor) { if (!sensor) return; if (sensor->num_trips) kfree(sensor->trips); thermal_zone_device_unregister(sensor->tzone); kfree(sensor); } static int get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { struct thermald_cdev *pcdev = cdev->devdata; *state = pcdev->max_state; return 0; } static int get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { struct thermald_cdev *pcdev = cdev->devdata; *state = pcdev->curr_state; return 0; } static int set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { struct thermald_cdev *pcdev = cdev->devdata; pcdev->curr_state = state; return 0; } static const struct thermal_cooling_device_ops cdev_ops = { .get_max_state = get_max_state, .get_cur_state = get_cur_state, .set_cur_state = set_cur_state, }; static struct thermald_cdev *create_test_cdev(int id, char *_name, int max_state) { struct thermald_cdev *cdev; char name[20]; cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); if (!cdev) return NULL; if (!_name) snprintf(name, sizeof(name), "thd_cdev_%d", id); else strncpy(name, _name, sizeof(name) -1); cdev->max_state = max_state; cdev->cdev = thermal_cooling_device_register(name, cdev, &cdev_ops); if (IS_ERR(cdev->cdev)) { kfree(cdev); return NULL; } return cdev; } static void destroy_test_cdev(struct thermald_cdev *cdev) { if (!cdev) return; thermal_cooling_device_unregister(cdev->cdev); kfree(cdev); } static int sensor_temp; static ssize_t sensor_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", sensor_temp); } static ssize_t sensor_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { sscanf(buf, "%d", &sensor_temp); return count; } static int control_state; static ssize_t control_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", control_state); } static ssize_t control_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { sscanf(buf, "%d", &control_state); return count; } static struct kobj_attribute sensor_attribute = __ATTR(sensor_temp, 0644, sensor_show, sensor_store); static struct kobj_attribute control_attribute = __ATTR(control_state, 0644, control_show, control_store); static struct attribute *thermald_attrs[] = { &sensor_attribute.attr, &control_attribute.attr, NULL, }; static struct attribute_group attr_group = { .attrs = thermald_attrs, }; static struct kobject *thermal_control_kobj; static struct thermald_sensor *test_sensor; static int __init thermald_init(void) { int i; int ret; if (strcmp(zone_name, "none")) { pr_info("create a zone:%s\n", zone_name); test_sensor = create_test_tzone(0, zone_name); if (test_sensor) return 0; return -EINVAL; } thermal_control_kobj = kobject_create_and_add("thermald_test", kernel_kobj); if (!thermal_control_kobj) return -ENOMEM; ret = sysfs_create_group(thermal_control_kobj, &attr_group); if (ret) { kobject_put(thermal_control_kobj); return ret; } for (i = 0; i < SENSOR_COUNT - 2; ++i) { sensors[i] = create_test_tzone(i, NULL); } for (i = 0; i < CDEV_COUNT - 2; ++i) { cdevs[i] = create_test_cdev(i, NULL, 10); } sensors[SENSOR_COUNT - 1] = create_test_tzone(SENSOR_COUNT - 1, "gddv_test_target_0"); sensors[SENSOR_COUNT - 2] = create_test_tzone(SENSOR_COUNT - 2, "gddv_test_target_1"); cdevs[CDEV_COUNT - 1] = create_test_cdev(CDEV_COUNT - 1, "gddv_test_source_0", 100000000); cdevs[CDEV_COUNT - 2] = create_test_cdev(CDEV_COUNT - 2, "gddv_test_source_1", 100000000); return 0; } static void __exit thermald_exit(void) { int i; if (test_sensor) { destroy_test_tzone(test_sensor); return; } for (i = 0; i < SENSOR_COUNT; ++i) { destroy_test_tzone(sensors[i]); } for (i = 0; i < CDEV_COUNT; ++i) { destroy_test_cdev(cdevs[i]); } kobject_put(thermal_control_kobj); } module_init(thermald_init) module_exit(thermald_exit) MODULE_LICENSE("GPL v2"); thermal_daemon-2.5.12/test/using_test_data_vault.txt000066400000000000000000000004441521301715300226700ustar00rootroot00000000000000This is for test on pre-production systems. 1. Insert module to create a test zone SEN2 sudo insmod ./thermald_test_kern_module.ko zone_name=SEN2 2. rename test_data_vault.bin to /lib/firmware/intel/dtt/dtt_data_vault_...bin 3. Use emul_temp to simulate temperature in the zone for SEN2 thermal_daemon-2.5.12/thermal_daemon_usage.txt000066400000000000000000000000551521301715300214620ustar00rootroot00000000000000Thermal Daemon Usage: Use: man thermald thermal_daemon-2.5.12/thermald-resource.gresource.xml000066400000000000000000000003121521301715300227160ustar00rootroot00000000000000 src/thd_dbus_interface.xml thermal_daemon-2.5.12/tools/000077500000000000000000000000001521301715300157165ustar00rootroot00000000000000thermal_daemon-2.5.12/tools/format_dv_name.c000066400000000000000000000061051521301715300210450ustar00rootroot00000000000000/* * thd_engine.cpp: thermal engine class implementation * * Copyright (C) 2026 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 or later as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * * Author Name */ #include #include #include #include #define DMI_ID_PATH "/sys/class/dmi/id" int read_dmi_attribute(const char *attr, char *out, size_t out_size) { FILE *fp; char path[PATH_MAX]; if (!attr || !out || out_size == 0) { errno = EINVAL; return -1; } if (snprintf(path, sizeof(path), "%s/%s", DMI_ID_PATH, attr) >= (int)sizeof(path)) { errno = ENAMETOOLONG; return -1; } fp = fopen(path, "r"); if (!fp) { return -1; } if (!fgets(out, (int)out_size, fp)) { int saved = errno ? errno : EIO; fclose(fp); errno = saved; return -1; } fclose(fp); out[strcspn(out, "\n")] = '\0'; return 0; } // From // https://elixir.bootlin.com/linux/v6.19.8/source/tools/pcmcia/crc32hash.c static unsigned int crc32(unsigned char const *p, unsigned int len) { int i; unsigned int crc = 0; while (len--) { crc ^= *p++; for (i = 0; i < 8; i++) crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0); } return crc; } int main(void) { char sys_vendor[256]; char product_name[256]; char product_sku[256]; char product_family[256]; char file_name[256]; if (read_dmi_attribute("sys_vendor", sys_vendor, sizeof(sys_vendor)) == 0) { printf("SYS_VENDOR : %s\n", sys_vendor); } else { perror("Failed to read SYS_VENDOR"); } if (read_dmi_attribute("product_name", product_name, sizeof(product_name)) == 0) { printf("PRODUCT_NAME : %s\n", product_name); } else { perror("Failed to read PRODUCT_NAME"); } if (read_dmi_attribute("product_family", product_family, sizeof(product_family)) == 0) { printf("PRODUCT_FAMILY : %s\n", product_family); } else { perror("Failed to read PRODUCT_FAMILY"); } if (read_dmi_attribute("product_sku", product_sku, sizeof(product_sku)) == 0) { printf("PRODUCT_SKU : %s\n", product_sku); } else { perror("Failed to read PRODUCT_SKU"); } //fomrmat: // dtt_data_vault_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin snprintf(file_name, sizeof(file_name), "dtt_data_vault_%u_%u_%u_%u.bin", crc32(sys_vendor, strlen(sys_vendor)), crc32(product_family, strlen(product_family)), crc32(product_name, strlen(product_name)), crc32(product_sku, strlen(product_sku)) ); printf("File name:%s\n", file_name); return 0; } thermal_daemon-2.5.12/tools/thermald_set_pref.sh000077500000000000000000000113601521301715300217450ustar00rootroot00000000000000#!/bin/bash echo "****thermald preference****" echo "0 : DEFAULT" echo "1 : PERFORMANCE" echo "2 : ENERGY_CONSERVE" echo "3 : DISABLED" echo "4 : CALIBRATE" echo "5 : SET USER DEFINED CPU MAX temp" echo "6 : SET USER DEFINED CPU PASSIVE temp" echo "7 : TERMINATE" echo "8 : REINIT" echo "A : Add sensor test" echo "B : Get Sensor Information" echo "C : Add zone test" echo "D : Set zone test" echo "E : Get zone test" echo "F : Delete zone test" echo "G : Add cdev test" echo "H : Get Sensor Count" echo "I : Get Zone Count" echo "J : Get Zone Information" echo "K : Get Zone Sensor Information" echo "L : Get Zone Trip Information" echo "M : Get cdev count" echo "N : Get cdev Information" arg="0" if [[ "$1" != "" ]]; then if [[ "$2" != "" ]]; then arg=$2 fi opt_no=$1 echo "Taking command line argument as choice!" else echo -n " Enter thermald preference [1..6]: " read opt_no fi case $opt_no in 0) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.SetCurrentPreference string:"FALLBACK" ;; 1) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.SetCurrentPreference string:"PERFORMANCE" ;; 2) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.SetCurrentPreference string:"ENERGY_CONSERVE" ;; 3) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.SetCurrentPreference string:"DISABLE" ;; 4) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Calibrate ;; 5) echo -n " Enter valid max temp in mill degree celsius " read max_temp dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.SetUserMaxTemperature string:cpu uint32:$max_temp ;; 6) echo -n " Enter valid passive temp in mill degree celsius " read psv_temp dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.SetUserPassiveTemperature string:cpu uint32:$psv_temp ;; 7) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Terminate ;; 8) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.Reinit ;; A) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.AddSensor string:"TEST_ADD_SENSOR" string:"/sys/class/thermal/thermal_zone0/temp" ;; B) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetSensorInformation uint32:0 ;; C) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.AddZonePassive string:"TEST_ADD_ZONE" uint32:90000 string:"TEST_ADD_SENSOR" string:"intel_pstate" ;; D) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.SetZoneStatus string:"TEST_ADD_ZONE" int32:0 ;; E) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetZoneStatus string:"TEST_ADD_ZONE" ;; F) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.DeleteZone string:"TEST_ADD_ZONE" ;; G) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.AddCoolingDevice string:"TEST_CDEV" string:"/sys/class/thermal/cooling_device0/cur_state" int32:0 int32:1 int32:1 ;; H) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetSensorCount ;; I) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetZoneCount ;; J) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetZoneInformation uint32:0 ;; K) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetZoneSensorAtIndex uint32:0 uint32:$arg ;; L) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetZoneTripAtIndex uint32:0 uint32:$arg ;; M) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetCdevCount ;; N) dbus-send --system --dest=org.freedesktop.thermald --print-reply /org/freedesktop/thermald org.freedesktop.thermald.GetCdevInformation uint32:$arg ;; *) echo "Invalid option" esac