summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Lezcano <daniel.lezcano@linaro.org>2023-08-08 11:41:22 +0200
committerDaniel Lezcano <daniel.lezcano@linaro.org>2023-09-11 12:46:57 +0200
commit63808cf8d437ebb55c21c9cae0fd2fdf7b113196 (patch)
treec49d356f576608cec01e9bc424d28ae4616c141f
parentb0274055b83bbb2a78cbb39da47dcaaf7bd317e2 (diff)
dragonboards: Add thermal HALthermal-hal
This change brings the generic thermal HAL. These are coming from different git trees merged into this single change. - https://git.linaro.org/power/libthermal.git - https://git.linaro.org/power/android-power-thermal.git/ It creates device/linaro/dragonboard/shared with the following hierarchy: shared | `- etc | `- thermal | `- lib | `- bin The 'etc' contains the thermal configuration for db845c. The port to RB5 must create a similar configuration file. Assuming it will be very close to RB3, that should be trivial to create it. The status at this point is the thermal HAL is running fine on RB3 but the SE Linux policy is missing and it has to be launched manually. Initially a memory corruption was randomly preventing to start the service but now it is fixed. Any help to start the service at boot time is welcome. The thermal HAL is designed to be as generic as possible in order to be reused by any platforms in the future. The thermal monitoring daemon is expecting to get a SKIN temperature sensor. As the RB3/RB5 are evaluation platform there is no such a sensor on the board so arbitrarly the configuration says the SKIN sensor is the cluster0. But as this one has fast temperature transitions, a more convenient sensor may be used later (temperature speed ~1°C / sec) The in-kernel thermal framework provides more events than what Android supports like thermal zone creation or deletion. However in the future, those may be supported, so the thermal HAL has the stubs to be filled with the logic. The code is documented, anyone interested in using the generic thermal HAL can easily fetch the code and understand the purpose of each function. Nevertheless, the thermal HAL being designed to be generic, the changes should end up in the common trunk. Cc: Tom Gall <tom.gall@linaro.org> Cc: Sumit Semwal <sumit.semwal@linaro.org> Cc: Vincent Guittot <vincent.guittot@linaro.org> Cc: Joyce Qi <joyce.qi@linaro.org> Cc: Julianus Larson <julianus.larson@linaro.org> Change-Id: I12aa7719248b2feb36fce9488c560557c5323c89 Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r--db845c/device.mk4
-rw-r--r--device-common.mk4
-rw-r--r--shared/bin/thermal-manager/Android.bp75
-rw-r--r--shared/bin/thermal-manager/COPYING.txt456
-rw-r--r--shared/bin/thermal-manager/Makefile10
-rw-r--r--shared/bin/thermal-manager/src/Makefile30
-rw-r--r--shared/bin/thermal-manager/src/log.c77
-rw-r--r--shared/bin/thermal-manager/src/log.h31
-rw-r--r--shared/bin/thermal-manager/src/mainloop.c120
-rw-r--r--shared/bin/thermal-manager/src/mainloop.h15
-rw-r--r--shared/bin/thermal-manager/src/thermal-engine.c341
-rw-r--r--shared/bin/thermal-manager/src/thermal-tools.h10
-rw-r--r--shared/bin/thermal-manager/src/uptimeofday.c40
-rw-r--r--shared/bin/thermal-manager/src/uptimeofday.h12
-rw-r--r--shared/bin/thermal-manager/thermal-manager.xml5
-rw-r--r--shared/etc/thermal-db845c.json190
-rw-r--r--shared/lib/libthermal/Android.bp75
-rw-r--r--shared/lib/libthermal/COPYING.txt456
-rw-r--r--shared/lib/libthermal/Makefile10
-rw-r--r--shared/lib/libthermal/include/thermal.h142
-rw-r--r--shared/lib/libthermal/patches/array_size_macro.patch15
-rw-r--r--shared/lib/libthermal/patches/patches.list1
-rw-r--r--shared/lib/libthermal/src/Makefile20
-rw-r--r--shared/lib/libthermal/src/commands.c353
-rw-r--r--shared/lib/libthermal/src/events.c164
-rw-r--r--shared/lib/libthermal/src/sampling.c75
-rw-r--r--shared/lib/libthermal/src/thermal.c135
-rw-r--r--shared/lib/libthermal/src/thermal_nl.c215
-rw-r--r--shared/lib/libthermal/src/thermal_nl.h46
-rwxr-xr-xshared/lib/libthermal/sync.sh51
-rw-r--r--shared/lib/libthermal/tst/Makefile16
-rw-r--r--shared/lib/libthermal/tst/tst_thermal.c303
-rw-r--r--shared/sepolicy/hal_thermal.te9
-rw-r--r--shared/thermal/Android.bp77
-rw-r--r--shared/thermal/Config.cpp312
-rw-r--r--shared/thermal/Config.h99
-rw-r--r--shared/thermal/CpuInfo.cpp113
-rw-r--r--shared/thermal/CpuInfo.h45
-rw-r--r--shared/thermal/LibThermal.cpp104
-rw-r--r--shared/thermal/LibThermal.h87
-rw-r--r--shared/thermal/Thermal.cpp925
-rw-r--r--shared/thermal/Thermal.h137
-rw-r--r--shared/thermal/android.hardware.thermal@2.0-service.linaro-generic.rc6
-rw-r--r--shared/thermal/android.hardware.thermal@2.0-service.linaro-generic.xml13
-rw-r--r--shared/thermal/service.cpp121
-rw-r--r--shared/thermal/tstThermalHAL.cpp340
46 files changed, 5885 insertions, 0 deletions
diff --git a/db845c/device.mk b/db845c/device.mk
index 23f16a9..8f465ba 100644
--- a/db845c/device.mk
+++ b/db845c/device.mk
@@ -50,6 +50,10 @@ PRODUCT_COPY_FILES += \
device/linaro/dragonboard/shared/utils/set_hw.sh:$(TARGET_COPY_OUT_VENDOR)/bin/set_hw.sh \
device/linaro/dragonboard/shared/utils/set_udc.sh:$(TARGET_COPY_OUT_VENDOR)/bin/set_udc.sh
+# Install thermal configuration file
+PRODUCT_COPY_FILES += \
+ device/linaro/dragonboard/shared/etc/thermal-db845c.json:$(TARGET_COPY_OUT_VENDOR)/etc/thermal.json
+
# Install scripts to set Ethernet MAC address
PRODUCT_COPY_FILES += \
device/linaro/dragonboard/shared/utils/ethaddr/ethaddr.rc:/system/etc/init/ethaddr.rc \
diff --git a/device-common.mk b/device-common.mk
index d2d2036..e1902d3 100644
--- a/device-common.mk
+++ b/device-common.mk
@@ -76,3 +76,7 @@ PRODUCT_COPY_FILES += \
device/linaro/dragonboard/init.common.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/init.$(TARGET_HARDWARE).rc \
device/linaro/dragonboard/init.common.usb.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/init.$(TARGET_HARDWARE).usb.rc \
frameworks/base/data/keyboards/Generic.kl:$(TARGET_COPY_OUT_VENDOR)/usr/keylayout/$(TARGET_HARDWARE).kl
+
+# Product common HAL
+PRODUCT_PACKAGES += \
+ android.hardware.thermal@2.0-service.linaro-generic
diff --git a/shared/bin/thermal-manager/Android.bp b/shared/bin/thermal-manager/Android.bp
new file mode 100644
index 0000000..9c16e03
--- /dev/null
+++ b/shared/bin/thermal-manager/Android.bp
@@ -0,0 +1,75 @@
+// Copyright 2023 The Android Open Source Project
+
+package {
+ default_applicable_licenses: ["external_thermal_manager_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+ name: "external_thermal_manager_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-LGPL",
+ "SPDX-license-identifier-LGPL-2.1",
+ "SPDX-license-identifier-LGPL-3.0",
+ ],
+ license_text: [
+ "COPYING.txt",
+ ],
+}
+
+/// In the beryllium device.mk add the thermal-manager to PRODUCT_PACKAGES, like:
+/// https://github.com/aospm/android_device_generic_sdm845/blob/main/shared/device.mk#L185
+/// That should get it building, it will be installed in /vendor/bin/hw/
+/// You should adjust your manifest.xml to clone the thermal-manager repo to external/thermal-manager
+/// if you stick with "vendor: true" below
+
+/// pixel powerstats seems like it may be a somewhat analogous example:
+/// https://cs.android.com/android/platform/superproject/+/master:hardware/google/pixel/powerstats/Android.bp
+/// dmesgd uses cc_defaults which maybe is useful for commonizing cflags?
+/// https://cs.android.com/android/platform/superproject/+/master:system/dmesgd/Android.bp
+
+cc_binary {
+ name: "thermal-manager",
+ /// Same as above
+ vendor: true,
+ /// This seems to be expected for vendor HALs ??
+ relative_install_path: "hw",
+
+ /// the libthermal library defines these include
+ /// dirs already, so it shouldn't be needed here
+ // local_include_dirs: ["src/include"],
+
+ srcs: [
+ "src/log.c",
+ "src/mainloop.c",
+ "src/thermal-engine.c",
+ "src/uptimeofday.c",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wno-sign-compare",
+ ],
+
+ /// libbase, libutils, libcutils might be handy
+ shared_libs: ["libthermal"],
+
+ /// TODO: this probably requires dealing with SELinux policies and stuff
+ // init_rc: ["thermal-manager.rc"],
+}
diff --git a/shared/bin/thermal-manager/COPYING.txt b/shared/bin/thermal-manager/COPYING.txt
new file mode 100644
index 0000000..ca4f73b
--- /dev/null
+++ b/shared/bin/thermal-manager/COPYING.txt
@@ -0,0 +1,456 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, 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.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, 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 library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
+
+ 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 Library or any portion
+of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
+
+ If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be 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.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+ 9. 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 Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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 with
+this License.
+
+ 11. 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 Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library 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.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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 Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
diff --git a/shared/bin/thermal-manager/Makefile b/shared/bin/thermal-manager/Makefile
new file mode 100644
index 0000000..0c9da2e
--- /dev/null
+++ b/shared/bin/thermal-manager/Makefile
@@ -0,0 +1,10 @@
+TOPTARGETS := all clean install uninstall
+
+SUBDIRS := src
+
+$(TOPTARGETS): $(SUBDIRS)
+
+$(SUBDIRS):
+ $(MAKE) -C $@ $(MAKECMDGOALS)
+
+.PHONY: $(TOPTARGETS) $(SUBDIRS)
diff --git a/shared/bin/thermal-manager/src/Makefile b/shared/bin/thermal-manager/src/Makefile
new file mode 100644
index 0000000..9cf5087
--- /dev/null
+++ b/shared/bin/thermal-manager/src/Makefile
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: LGPL-2.1+
+CC=gcc
+CFLAGS+=-g -Wall -Wno-unused -I../include -fPIC -Wextra -O2
+LDFLAGS=-lthermal -L../lib
+OBJS=log.o mainloop.o thermal-engine.o uptimeofday.o
+PREFIX?=/usr
+BINDIR=${PREFIX}/bin
+TARGET=thermal-manager
+VERSION=0.0.1
+DEPS=thermal-tools.h
+
+default: $(TARGET)
+
+$(TARGET): $(OBJS)
+ $(CC) $(CFLAGS) -D VERSION=\"$(VERSION)\" -o $@ $^ $(LDFLAGS)
+
+%.o: %.c $(DEPS)
+ $(CROSS_COMPILE)$(CC) -c -o $@ $< $(CFLAGS)
+
+install: FORCE
+ install -d ${BINDIR}
+ install -m 0755 ${TARGET} ${BINDIR}
+
+uninstall:
+ $(RM) ${BINDIR}/${TARGET}
+
+FORCE:
+
+clean:
+ rm -f $(OBJS) $(TARGET) *~
diff --git a/shared/bin/thermal-manager/src/log.c b/shared/bin/thermal-manager/src/log.c
new file mode 100644
index 0000000..597d6e7
--- /dev/null
+++ b/shared/bin/thermal-manager/src/log.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include "log.h"
+
+static const char *__ident = "unknown";
+static int __options;
+
+static const char * const loglvl[] = {
+ [LOG_DEBUG] = "DEBUG",
+ [LOG_INFO] = "INFO",
+ [LOG_NOTICE] = "NOTICE",
+ [LOG_WARNING] = "WARN",
+ [LOG_ERR] = "ERROR",
+ [LOG_CRIT] = "CRITICAL",
+ [LOG_ALERT] = "ALERT",
+ [LOG_EMERG] = "EMERG",
+};
+
+int log_str2level(const char *lvl)
+{
+ int i;
+
+ for (i = 0; i < sizeof(loglvl) / sizeof(loglvl[LOG_DEBUG]); i++)
+ if (!strcmp(lvl, loglvl[i]))
+ return i;
+
+ return LOG_DEBUG;
+}
+
+extern void logit(int level, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+
+ if (__options & TO_SYSLOG)
+ vsyslog(level, format, args);
+
+ if (__options & TO_STDERR)
+ vfprintf(stderr, format, args);
+
+ if (__options & TO_STDOUT)
+ vfprintf(stdout, format, args);
+
+ va_end(args);
+}
+
+int log_init(int level, const char *ident, int options)
+{
+ if (!options)
+ return -1;
+
+ if (level > LOG_DEBUG)
+ return -1;
+
+ if (!ident)
+ return -1;
+
+ __ident = ident;
+ __options = options;
+
+ if (options & TO_SYSLOG) {
+ openlog(__ident, options | LOG_NDELAY, LOG_USER);
+ setlogmask(LOG_UPTO(level));
+ }
+
+ return 0;
+}
+
+void log_exit(void)
+{
+ closelog();
+}
diff --git a/shared/bin/thermal-manager/src/log.h b/shared/bin/thermal-manager/src/log.h
new file mode 100644
index 0000000..be8ab51
--- /dev/null
+++ b/shared/bin/thermal-manager/src/log.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
+#ifndef __THERMAL_TOOLS_LOG_H
+#define __THERMAL_TOOLS_LOG_H
+
+#include <syslog.h>
+
+#ifndef __maybe_unused
+#define __maybe_unused __attribute__((__unused__))
+#endif
+
+#define TO_SYSLOG 0x1
+#define TO_STDOUT 0x2
+#define TO_STDERR 0x4
+
+extern void logit(int level, const char *format, ...);
+
+#define DEBUG(fmt, ...) logit(LOG_DEBUG, "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define INFO(fmt, ...) logit(LOG_INFO, fmt, ##__VA_ARGS__)
+#define NOTICE(fmt, ...) logit(LOG_NOTICE, fmt, ##__VA_ARGS__)
+#define WARN(fmt, ...) logit(LOG_WARNING, fmt, ##__VA_ARGS__)
+#define ERROR(fmt, ...) logit(LOG_ERR, fmt, ##__VA_ARGS__)
+#define CRITICAL(fmt, ...) logit(LOG_CRIT, fmt, ##__VA_ARGS__)
+#define ALERT(fmt, ...) logit(LOG_ALERT, fmt, ##__VA_ARGS__)
+#define EMERG(fmt, ...) logit(LOG_EMERG, fmt, ##__VA_ARGS__)
+
+int log_init(int level, const char *ident, int options);
+int log_str2level(const char *lvl);
+void log_exit(void);
+
+#endif
diff --git a/shared/bin/thermal-manager/src/mainloop.c b/shared/bin/thermal-manager/src/mainloop.c
new file mode 100644
index 0000000..94cbbcb
--- /dev/null
+++ b/shared/bin/thermal-manager/src/mainloop.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/epoll.h>
+#include "mainloop.h"
+#include "log.h"
+
+static int epfd = -1;
+static unsigned short nrhandler;
+static sig_atomic_t exit_mainloop;
+
+struct mainloop_data {
+ mainloop_callback_t cb;
+ void *data;
+ int fd;
+};
+
+static struct mainloop_data **mds;
+
+#define MAX_EVENTS 10
+
+int mainloop(unsigned int timeout)
+{
+ int i, nfds;
+ struct epoll_event events[MAX_EVENTS];
+ struct mainloop_data *md;
+
+ if (epfd < 0)
+ return -1;
+
+ for (;;) {
+
+ nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
+
+ if (exit_mainloop || !nfds)
+ return 0;
+
+ if (nfds < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+
+ for (i = 0; i < nfds; i++) {
+ md = events[i].data.ptr;
+
+ if (md->cb(md->fd, md->data) > 0)
+ return 0;
+ }
+ }
+}
+
+int mainloop_add(int fd, mainloop_callback_t cb, void *data)
+{
+ struct epoll_event ev = {
+ .events = EPOLLIN,
+ };
+
+ struct mainloop_data *md;
+
+ if (fd >= nrhandler) {
+ mds = realloc(mds, sizeof(*mds) * (fd + 1));
+ if (!mds)
+ return -1;
+ nrhandler = fd + 1;
+ }
+
+ md = malloc(sizeof(*md));
+ if (!md)
+ return -1;
+
+ md->data = data;
+ md->cb = cb;
+ md->fd = fd;
+
+ mds[fd] = md;
+ ev.data.ptr = md;
+
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+ free(md);
+ return -1;
+ }
+
+ return 0;
+}
+
+int mainloop_del(int fd)
+{
+ if (fd >= nrhandler)
+ return -1;
+
+ if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) < 0)
+ return -1;
+
+ free(mds[fd]);
+
+ return 0;
+}
+
+int mainloop_init(void)
+{
+ epfd = epoll_create(2);
+ if (epfd < 0)
+ return -1;
+
+ return 0;
+}
+
+void mainloop_exit(void)
+{
+ exit_mainloop = 1;
+}
+
+void mainloop_fini(void)
+{
+ close(epfd);
+}
diff --git a/shared/bin/thermal-manager/src/mainloop.h b/shared/bin/thermal-manager/src/mainloop.h
new file mode 100644
index 0000000..89b61e8
--- /dev/null
+++ b/shared/bin/thermal-manager/src/mainloop.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
+#ifndef __THERMAL_TOOLS_MAINLOOP_H
+#define __THERMAL_TOOLS_MAINLOOP_H
+
+typedef int (*mainloop_callback_t)(int fd, void *data);
+
+extern int mainloop(unsigned int timeout);
+extern int mainloop_add(int fd, mainloop_callback_t cb, void *data);
+extern int mainloop_del(int fd);
+extern void mainloop_exit(void);
+extern int mainloop_init(void);
+extern void mainloop_fini(void);
+
+#endif
diff --git a/shared/bin/thermal-manager/src/thermal-engine.c b/shared/bin/thermal-manager/src/thermal-engine.c
new file mode 100644
index 0000000..9b1476a
--- /dev/null
+++ b/shared/bin/thermal-manager/src/thermal-engine.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Thermal monitoring tool based on the thermal netlink events.
+ *
+ * Copyright (C) 2022 Linaro Ltd.
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@kernel.org>
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <syslog.h>
+
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <thermal.h>
+#include "thermal-tools.h"
+
+struct options {
+ int loglevel;
+ int logopt;
+ int interactive;
+ int daemonize;
+};
+
+struct thermal_data {
+ struct thermal_zone *tz;
+ struct thermal_handler *th;
+};
+
+static int show_trip(struct thermal_trip *tt, __maybe_unused void *arg)
+{
+ INFO("trip id=%d, type=%d, temp=%d, hyst=%d\n",
+ tt->id, tt->type, tt->temp, tt->hyst);
+
+ return 0;
+}
+
+static int show_temp(struct thermal_zone *tz, __maybe_unused void *arg)
+{
+ thermal_cmd_get_temp(arg, tz);
+
+ INFO("temperature: %d\n", tz->temp);
+
+ return 0;
+}
+
+static int show_governor(struct thermal_zone *tz, __maybe_unused void *arg)
+{
+ thermal_cmd_get_governor(arg, tz);
+
+ INFO("governor: '%s'\n", tz->governor);
+
+ return 0;
+}
+
+static int show_tz(struct thermal_zone *tz, __maybe_unused void *arg)
+{
+ INFO("thermal zone '%s', id=%d\n", tz->name, tz->id);
+
+ for_each_thermal_trip(tz->trip, show_trip, NULL);
+
+ show_temp(tz, arg);
+
+ show_governor(tz, arg);
+
+ return 0;
+}
+
+static int tz_create(const char *name, int tz_id, __maybe_unused void *arg)
+{
+ INFO("Thermal zone '%s'/%d created\n", name, tz_id);
+
+ return 0;
+}
+
+static int tz_delete(int tz_id, __maybe_unused void *arg)
+{
+ INFO("Thermal zone %d deleted\n", tz_id);
+
+ return 0;
+}
+
+static int tz_disable(int tz_id, void *arg)
+{
+ struct thermal_data *td = arg;
+ struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
+
+ INFO("Thermal zone %d ('%s') disabled\n", tz_id, tz->name);
+
+ return 0;
+}
+
+static int tz_enable(int tz_id, void *arg)
+{
+ struct thermal_data *td = arg;
+ struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
+
+ INFO("Thermal zone %d ('%s') enabled\n", tz_id, tz->name);
+
+ return 0;
+}
+
+static int trip_high(int tz_id, int trip_id, int temp, void *arg)
+{
+ struct thermal_data *td = arg;
+ struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
+
+ INFO("Thermal zone %d ('%s'): trip point %d crossed way up with %d °C\n",
+ tz_id, tz->name, trip_id, temp);
+
+ return 0;
+}
+
+static int trip_low(int tz_id, int trip_id, int temp, void *arg)
+{
+ struct thermal_data *td = arg;
+ struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
+
+ INFO("Thermal zone %d ('%s'): trip point %d crossed way down with %d °C\n",
+ tz_id, tz->name, trip_id, temp);
+
+ return 0;
+}
+
+static int trip_add(int tz_id, int trip_id, int type, int temp, int hyst, __maybe_unused void *arg)
+{
+ INFO("Trip point added %d: id=%d, type=%d, temp=%d, hyst=%d\n",
+ tz_id, trip_id, type, temp, hyst);
+
+ return 0;
+}
+
+static int trip_delete(int tz_id, int trip_id, __maybe_unused void *arg)
+{
+ INFO("Trip point deleted %d: id=%d\n", tz_id, trip_id);
+
+ return 0;
+}
+
+static int trip_change(int tz_id, int trip_id, int type, int temp,
+ int hyst, __maybe_unused void *arg)
+{
+ struct thermal_data *td = arg;
+ struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
+
+ INFO("Trip point changed %d: id=%d, type=%d, temp=%d, hyst=%d\n",
+ tz_id, trip_id, type, temp, hyst);
+
+ tz->trip[trip_id].type = type;
+ tz->trip[trip_id].temp = temp;
+ tz->trip[trip_id].hyst = hyst;
+
+ return 0;
+}
+
+static int cdev_add(const char *name, int cdev_id, int max_state, __maybe_unused void *arg)
+{
+ INFO("Cooling device '%s'/%d (max state=%d) added\n", name, cdev_id, max_state);
+
+ return 0;
+}
+
+static int cdev_delete(int cdev_id, __maybe_unused void *arg)
+{
+ INFO("Cooling device %d deleted", cdev_id);
+
+ return 0;
+}
+
+static int cdev_update(int cdev_id, int cur_state, __maybe_unused void *arg)
+{
+ INFO("cdev:%d state:%d\n", cdev_id, cur_state);
+
+ return 0;
+}
+
+static int gov_change(int tz_id, const char *name, __maybe_unused void *arg)
+{
+ struct thermal_data *td = arg;
+ struct thermal_zone *tz = thermal_zone_find_by_id(td->tz, tz_id);
+
+ INFO("%s: governor changed %s -> %s\n", tz->name, tz->governor, name);
+
+ strcpy(tz->governor, name);
+
+ return 0;
+}
+
+static struct thermal_ops ops = {
+ .events.tz_create = tz_create,
+ .events.tz_delete = tz_delete,
+ .events.tz_disable = tz_disable,
+ .events.tz_enable = tz_enable,
+ .events.trip_high = trip_high,
+ .events.trip_low = trip_low,
+ .events.trip_add = trip_add,
+ .events.trip_delete = trip_delete,
+ .events.trip_change = trip_change,
+ .events.cdev_add = cdev_add,
+ .events.cdev_delete = cdev_delete,
+ .events.cdev_update = cdev_update,
+ .events.gov_change = gov_change
+};
+
+static int thermal_event(__maybe_unused int fd, __maybe_unused void *arg)
+{
+ struct thermal_data *td = arg;
+
+ return thermal_events_handle(td->th, td);
+}
+
+static void usage(const char *cmd)
+{
+ printf("%s : A thermal monitoring engine based on notifications\n", cmd);
+ printf("Usage: %s [options]\n", cmd);
+ printf("\t-h, --help\t\tthis help\n");
+ printf("\t-d, --daemonize\n");
+ printf("\t-l <level>, --loglevel <level>\tlog level: ");
+ printf("DEBUG, INFO, NOTICE, WARN, ERROR\n");
+ printf("\t-s, --syslog\t\toutput to syslog\n");
+ printf("\n");
+ exit(0);
+}
+
+static int options_init(int argc, char *argv[], struct options *options)
+{
+ int opt;
+
+ struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "daemonize", no_argument, NULL, 'd' },
+ { "syslog", no_argument, NULL, 's' },
+ { "loglevel", required_argument, NULL, 'l' },
+ { 0, 0, 0, 0 }
+ };
+
+ while (1) {
+
+ int optindex = 0;
+
+ opt = getopt_long(argc, argv, "l:dhs", long_options, &optindex);
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case 'l':
+ options->loglevel = log_str2level(optarg);
+ break;
+ case 'd':
+ options->daemonize = 1;
+ break;
+ case 's':
+ options->logopt = TO_SYSLOG;
+ break;
+ case 'h':
+ usage(basename(argv[0]));
+ break;
+ default: /* '?' */
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+enum {
+ THERMAL_ENGINE_SUCCESS = 0,
+ THERMAL_ENGINE_OPTION_ERROR,
+ THERMAL_ENGINE_DAEMON_ERROR,
+ THERMAL_ENGINE_LOG_ERROR,
+ THERMAL_ENGINE_THERMAL_ERROR,
+ THERMAL_ENGINE_MAINLOOP_ERROR,
+};
+
+int main(int argc, char *argv[])
+{
+ struct thermal_data td;
+ struct options options = {
+ .loglevel = LOG_INFO,
+ .logopt = TO_STDOUT,
+ };
+
+ if (options_init(argc, argv, &options)) {
+ ERROR("Usage: %s --help\n", argv[0]);
+ return THERMAL_ENGINE_OPTION_ERROR;
+ }
+
+ if (options.daemonize && daemon(0, 0)) {
+ ERROR("Failed to daemonize: %p\n");
+ return THERMAL_ENGINE_DAEMON_ERROR;
+ }
+
+ if (log_init(options.loglevel, basename(argv[0]), options.logopt)) {
+ ERROR("Failed to initialize logging facility\n");
+ return THERMAL_ENGINE_LOG_ERROR;
+ }
+
+ td.th = thermal_init(&ops);
+ if (!td.th) {
+ ERROR("Failed to initialize the thermal library\n");
+ return THERMAL_ENGINE_THERMAL_ERROR;
+ }
+
+ td.tz = thermal_zone_discover(td.th);
+ if (!td.tz) {
+ ERROR("No thermal zone available\n");
+ return THERMAL_ENGINE_THERMAL_ERROR;
+ }
+
+ for_each_thermal_zone(td.tz, show_tz, td.th);
+
+ if (mainloop_init()) {
+ ERROR("Failed to initialize the mainloop\n");
+ return THERMAL_ENGINE_MAINLOOP_ERROR;
+ }
+
+ if (mainloop_add(thermal_events_fd(td.th), thermal_event, &td)) {
+ ERROR("Failed to setup the mainloop\n");
+ return THERMAL_ENGINE_MAINLOOP_ERROR;
+ }
+
+ INFO("Waiting for thermal events ...\n");
+
+ if (mainloop(-1)) {
+ ERROR("Mainloop failed\n");
+ return THERMAL_ENGINE_MAINLOOP_ERROR;
+ }
+
+ return THERMAL_ENGINE_SUCCESS;
+}
diff --git a/shared/bin/thermal-manager/src/thermal-tools.h b/shared/bin/thermal-manager/src/thermal-tools.h
new file mode 100644
index 0000000..f43939a
--- /dev/null
+++ b/shared/bin/thermal-manager/src/thermal-tools.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
+#ifndef __THERMAL_TOOLS
+#define __THERMAL_TOOLS
+
+#include "log.h"
+#include "mainloop.h"
+#include "uptimeofday.h"
+
+#endif
diff --git a/shared/bin/thermal-manager/src/uptimeofday.c b/shared/bin/thermal-manager/src/uptimeofday.c
new file mode 100644
index 0000000..dacb029
--- /dev/null
+++ b/shared/bin/thermal-manager/src/uptimeofday.c
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
+#include <stdio.h>
+#include <sys/time.h>
+#include <linux/sysinfo.h>
+#include "thermal-tools.h"
+
+static unsigned long __offset;
+static struct timeval __tv;
+
+int uptimeofday_init(void)
+{
+ struct sysinfo info;
+
+ if (sysinfo(&info))
+ return -1;
+
+ gettimeofday(&__tv, NULL);
+
+ __offset = __tv.tv_sec - info.uptime;
+
+ return 0;
+}
+
+unsigned long getuptimeofday_ms(void)
+{
+ gettimeofday(&__tv, NULL);
+
+ return ((__tv.tv_sec - __offset) * 1000) + (__tv.tv_usec / 1000);
+}
+
+struct timespec msec_to_timespec(int msec)
+{
+ struct timespec tv = {
+ .tv_sec = (msec / 1000),
+ .tv_nsec = (msec % 1000) * 1000000,
+ };
+
+ return tv;
+}
diff --git a/shared/bin/thermal-manager/src/uptimeofday.h b/shared/bin/thermal-manager/src/uptimeofday.h
new file mode 100644
index 0000000..c0da5de
--- /dev/null
+++ b/shared/bin/thermal-manager/src/uptimeofday.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
+#ifndef __THERMAL_TOOLS_UPTIMEOFDAY_H
+#define __THERMAL_TOOLS_UPTIMEOFDAY_H
+#include <sys/sysinfo.h>
+#include <sys/time.h>
+
+int uptimeofday_init(void);
+unsigned long getuptimeofday_ms(void);
+struct timespec msec_to_timespec(int msec);
+
+#endif
diff --git a/shared/bin/thermal-manager/thermal-manager.xml b/shared/bin/thermal-manager/thermal-manager.xml
new file mode 100644
index 0000000..f2c34e6
--- /dev/null
+++ b/shared/bin/thermal-manager/thermal-manager.xml
@@ -0,0 +1,5 @@
+<manifest>
+ <!-- Repositories -->
+ <remote name="linaro" fetch="http://git.linaro.org/power"/>
+ <project path="external/thermal-manager" name="thermal-manager" revision="android" remote="linaro" groups="default"/>
+</manifest>
diff --git a/shared/etc/thermal-db845c.json b/shared/etc/thermal-db845c.json
new file mode 100644
index 0000000..4efa0b7
--- /dev/null
+++ b/shared/etc/thermal-db845c.json
@@ -0,0 +1,190 @@
+{
+ "Sensors":[
+ {
+ "Name":"cpu0-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ },
+ {
+ "Type": "Vr",
+ "None": 59
+ }
+ ]
+ },
+ {
+ "Name":"cpu1-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"cpu2-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"cpu3-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"cpu4-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"cpu5-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"cpu6-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"cpu7-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"cluster0-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"cluster1-thermal",
+ "Type":"CPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"gpu-top-thermal",
+ "Type":"GPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"gpu-bottom-thermal",
+ "Type":"GPU",
+ "Throttling":[
+ {
+ "Type": "Hot",
+ "Severe": 90,
+ "Shutdown": 125
+ }
+ ]
+ },
+ {
+ "Name":"pm8998-thermal",
+ "Type":"BATTERY"
+ },
+ {
+ "Name":"q6-modem-thermal",
+ "Type":"MODEM"
+ },
+ {
+ "Name":"q6-hvx-thermal",
+ "Type":"NPU"
+ },
+ {
+ "Name":"mem-thermal",
+ "Type":"UNKNOWN"
+ },
+ {
+ "Name":"wlan-thermal",
+ "Type":"UNKNOWN"
+ },
+ {
+ "Name":"camera-thermal",
+ "Type":"SKIN"
+ },
+ {
+ "Name":"video-thermal",
+ "Type":"UNKOWN"
+ },
+ {
+ "Name":"aoss0-thermal",
+ "Type":"UNKNOWN"
+ },
+ {
+ "Name":"aoss1-thermal",
+ "Type":"UNKNOWN"
+ }
+ ],
+ "CoolingDevices":[
+ {
+ "Name":"cpufreq-0",
+ "Type":"CPU"
+ },
+ {
+ "Name":"cpufreq-1",
+ "Type":"CPU"
+ },
+ {
+ "Name":"devfreq-5000000.gpu",
+ "Type":"GPU"
+ }
+ ]
+}
diff --git a/shared/lib/libthermal/Android.bp b/shared/lib/libthermal/Android.bp
new file mode 100644
index 0000000..f34b188
--- /dev/null
+++ b/shared/lib/libthermal/Android.bp
@@ -0,0 +1,75 @@
+// Copyright 2021 The Android Open Source Project
+
+package {
+ default_applicable_licenses: ["external_libthermal_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+ name: "external_libthermal_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-LGPL",
+ "SPDX-license-identifier-LGPL-2.1",
+ "SPDX-license-identifier-LGPL-3.0",
+ ],
+ license_text: [
+ "COPYING.txt",
+ ],
+}
+
+cc_library_headers {
+ name: "libthermal_headers",
+ export_include_dirs: ["include"],
+}
+
+cc_library_shared {
+ host_supported: true,
+ vendor_available: true,
+ product_available: true,
+ // vndk: {
+ // enabled: true,
+ // },
+
+ target: {
+ linux_bionic: {
+ enabled: true,
+ },
+ },
+
+ name: "libthermal",
+ srcs: [
+ "src/thermal.c",
+ "src/events.c",
+ "src/sampling.c",
+ "src/commands.c",
+ "src/thermal_nl.c",
+ ],
+
+ local_include_dirs: [
+ "include",
+ ],
+
+ export_include_dirs: ["include"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-sign-compare",
+ "-Wno-unused-parameter",
+ ],
+ shared_libs: ["libnl"],
+}
diff --git a/shared/lib/libthermal/COPYING.txt b/shared/lib/libthermal/COPYING.txt
new file mode 100644
index 0000000..ca4f73b
--- /dev/null
+++ b/shared/lib/libthermal/COPYING.txt
@@ -0,0 +1,456 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, 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.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, 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 library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
+
+ 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 Library or any portion
+of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
+
+ If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be 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.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+ 9. 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 Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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 with
+this License.
+
+ 11. 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 Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library 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.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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 Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
diff --git a/shared/lib/libthermal/Makefile b/shared/lib/libthermal/Makefile
new file mode 100644
index 0000000..30b7407
--- /dev/null
+++ b/shared/lib/libthermal/Makefile
@@ -0,0 +1,10 @@
+TOPTARGETS := all clean
+
+SUBDIRS := src tst
+
+$(TOPTARGETS): $(SUBDIRS)
+
+$(SUBDIRS):
+ $(MAKE) -C $@ $(MAKECMDGOALS)
+
+.PHONY: $(TOPTARGETS) $(SUBDIRS)
diff --git a/shared/lib/libthermal/include/thermal.h b/shared/lib/libthermal/include/thermal.h
new file mode 100644
index 0000000..1abc560
--- /dev/null
+++ b/shared/lib/libthermal/include/thermal.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
+#ifndef __LIBTHERMAL_H
+#define __LIBTHERMAL_H
+
+#include <linux/thermal.h>
+
+#ifndef LIBTHERMAL_API
+#define LIBTHERMAL_API __attribute__((visibility("default")))
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct thermal_sampling_ops {
+ int (*tz_temp)(int tz_id, int temp, void *arg);
+};
+
+struct thermal_events_ops {
+ int (*tz_create)(const char *name, int tz_id, void *arg);
+ int (*tz_delete)(int tz_id, void *arg);
+ int (*tz_enable)(int tz_id, void *arg);
+ int (*tz_disable)(int tz_id, void *arg);
+ int (*trip_high)(int tz_id, int trip_id, int temp, void *arg);
+ int (*trip_low)(int tz_id, int trip_id, int temp, void *arg);
+ int (*trip_add)(int tz_id, int trip_id, int type, int temp, int hyst, void *arg);
+ int (*trip_change)(int tz_id, int trip_id, int type, int temp, int hyst, void *arg);
+ int (*trip_delete)(int tz_id, int trip_id, void *arg);
+ int (*cdev_add)(const char *name, int cdev_id, int max_state, void *arg);
+ int (*cdev_delete)(int cdev_id, void *arg);
+ int (*cdev_update)(int cdev_id, int cur_state, void *arg);
+ int (*gov_change)(int tz_id, const char *gov_name, void *arg);
+};
+
+struct thermal_ops {
+ struct thermal_sampling_ops sampling;
+ struct thermal_events_ops events;
+};
+
+struct thermal_trip {
+ int id;
+ int type;
+ int temp;
+ int hyst;
+};
+
+struct thermal_zone {
+ int id;
+ int temp;
+ char name[THERMAL_NAME_LENGTH];
+ char governor[THERMAL_NAME_LENGTH];
+ struct thermal_trip *trip;
+};
+
+struct thermal_cdev {
+ int id;
+ char name[THERMAL_NAME_LENGTH];
+ int max_state;
+ int min_state;
+ int cur_state;
+};
+
+typedef enum {
+ THERMAL_ERROR = -1,
+ THERMAL_SUCCESS = 0,
+} thermal_error_t;
+
+struct thermal_handler;
+
+typedef int (*cb_tz_t)(struct thermal_zone *, void *);
+
+typedef int (*cb_tt_t)(struct thermal_trip *, void *);
+
+typedef int (*cb_tc_t)(struct thermal_cdev *, void *);
+
+LIBTHERMAL_API int for_each_thermal_zone(struct thermal_zone *tz, cb_tz_t cb, void *arg);
+
+LIBTHERMAL_API int for_each_thermal_trip(struct thermal_trip *tt, cb_tt_t cb, void *arg);
+
+LIBTHERMAL_API int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg);
+
+LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz,
+ const char *name);
+
+LIBTHERMAL_API struct thermal_zone *thermal_zone_find_by_id(struct thermal_zone *tz, int id);
+
+LIBTHERMAL_API struct thermal_zone *thermal_zone_discover(struct thermal_handler *th);
+
+LIBTHERMAL_API struct thermal_handler *thermal_init(struct thermal_ops *ops);
+
+LIBTHERMAL_API void thermal_exit(struct thermal_handler *th);
+
+/*
+ * Netlink thermal events
+ */
+LIBTHERMAL_API thermal_error_t thermal_events_exit(struct thermal_handler *th);
+
+LIBTHERMAL_API thermal_error_t thermal_events_init(struct thermal_handler *th);
+
+LIBTHERMAL_API thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg);
+
+LIBTHERMAL_API int thermal_events_fd(struct thermal_handler *th);
+
+/*
+ * Netlink thermal commands
+ */
+LIBTHERMAL_API thermal_error_t thermal_cmd_exit(struct thermal_handler *th);
+
+LIBTHERMAL_API thermal_error_t thermal_cmd_init(struct thermal_handler *th);
+
+LIBTHERMAL_API thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th,
+ struct thermal_zone **tz);
+
+LIBTHERMAL_API thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th,
+ struct thermal_cdev **tc);
+
+LIBTHERMAL_API thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th,
+ struct thermal_zone *tz);
+
+LIBTHERMAL_API thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th,
+ struct thermal_zone *tz);
+
+LIBTHERMAL_API thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th,
+ struct thermal_zone *tz);
+
+/*
+ * Netlink thermal samples
+ */
+LIBTHERMAL_API thermal_error_t thermal_sampling_exit(struct thermal_handler *th);
+
+LIBTHERMAL_API thermal_error_t thermal_sampling_init(struct thermal_handler *th);
+
+LIBTHERMAL_API thermal_error_t thermal_sampling_handle(struct thermal_handler *th, void *arg);
+
+LIBTHERMAL_API int thermal_sampling_fd(struct thermal_handler *th);
+
+#endif /* __LIBTHERMAL_H */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/shared/lib/libthermal/patches/array_size_macro.patch b/shared/lib/libthermal/patches/array_size_macro.patch
new file mode 100644
index 0000000..dfcc87a
--- /dev/null
+++ b/shared/lib/libthermal/patches/array_size_macro.patch
@@ -0,0 +1,15 @@
+diff --git a/src/commands.c b/src/commands.c
+index 73d4d4e8d6ec..f57db6d07615 100644
+--- a/src/commands.c
++++ b/src/commands.c
+@@ -9,4 +9,8 @@
+ #include <thermal.h>
+ #include "thermal_nl.h"
+
++#ifndef ARRAY_SIZE
++#define ARRAY_SIZE(__array) (sizeof(__array) / sizeof(__array[0]))
++#endif
++
+ static struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {
+ /* Thermal zone */
+ [THERMAL_GENL_ATTR_TZ] = { .type = NLA_NESTED },
diff --git a/shared/lib/libthermal/patches/patches.list b/shared/lib/libthermal/patches/patches.list
new file mode 100644
index 0000000..6f28588
--- /dev/null
+++ b/shared/lib/libthermal/patches/patches.list
@@ -0,0 +1 @@
+array_size_macro.patch
diff --git a/shared/lib/libthermal/src/Makefile b/shared/lib/libthermal/src/Makefile
new file mode 100644
index 0000000..a2d4d82
--- /dev/null
+++ b/shared/lib/libthermal/src/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: LGPL-2.1+
+CC=gcc
+CFLAGS+=-g -Wall -Wno-unused -I/usr/include/libnl3 -I../include -fPIC -Wextra -O2
+LDFLAGS=-lnl-genl-3 -lnl-3 -shared
+DEPS = include/libthermal.h
+OBJS = thermal.o events.o sampling.o commands.o thermal_nl.o
+LIB=libthermal.so
+
+BINS=$(C_BINS:.c=)
+
+default: libthermal.so
+
+%.o: %.c $(DEPS)
+ $(CROSS_COMPILE)$(CC) -c -o $@ $< $(CFLAGS)
+
+$(LIB): $(OBJS)
+ $(CROSS_COMPILE)$(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS)
+
+clean:
+ rm -f $(OBJS) $(LIB) *~
diff --git a/shared/lib/libthermal/src/commands.c b/shared/lib/libthermal/src/commands.c
new file mode 100644
index 0000000..f57db6d
--- /dev/null
+++ b/shared/lib/libthermal/src/commands.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <thermal.h>
+#include "thermal_nl.h"
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(__array) (sizeof(__array) / sizeof(__array[0]))
+#endif
+
+static struct nla_policy thermal_genl_policy[THERMAL_GENL_ATTR_MAX + 1] = {
+ /* Thermal zone */
+ [THERMAL_GENL_ATTR_TZ] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TEMP] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_TRIP_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_TEMP] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_TYPE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_TRIP_HYST] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_MODE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_CDEV_WEIGHT] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_TZ_NAME] = { .type = NLA_STRING },
+
+ /* Governor(s) */
+ [THERMAL_GENL_ATTR_TZ_GOV] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_TZ_GOV_NAME] = { .type = NLA_STRING },
+
+ /* Cooling devices */
+ [THERMAL_GENL_ATTR_CDEV] = { .type = NLA_NESTED },
+ [THERMAL_GENL_ATTR_CDEV_ID] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_CUR_STATE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_MAX_STATE] = { .type = NLA_U32 },
+ [THERMAL_GENL_ATTR_CDEV_NAME] = { .type = NLA_STRING },
+};
+
+static int parse_tz_get(struct genl_info *info, struct thermal_zone **tz)
+{
+ struct nlattr *attr;
+ struct thermal_zone *__tz = NULL;
+ size_t size = 0;
+ int rem;
+
+ nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ], rem) {
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_ID) {
+
+ size++;
+
+ __tz = realloc(__tz, sizeof(*__tz) * (size + 2));
+ if (!__tz)
+ return THERMAL_ERROR;
+
+ __tz[size - 1].id = nla_get_u32(attr);
+ }
+
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_NAME)
+ nla_strlcpy(__tz[size - 1].name, attr,
+ THERMAL_NAME_LENGTH);
+ }
+
+ if (__tz)
+ __tz[size].id = -1;
+
+ *tz = __tz;
+
+ return THERMAL_SUCCESS;
+}
+
+static int parse_cdev_get(struct genl_info *info, struct thermal_cdev **cdev)
+{
+ struct nlattr *attr;
+ struct thermal_cdev *__cdev = NULL;
+ size_t size = 0;
+ int rem;
+
+ nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_CDEV], rem) {
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_ID) {
+
+ size++;
+
+ __cdev = realloc(__cdev, sizeof(*__cdev) * (size + 2));
+ if (!__cdev)
+ return THERMAL_ERROR;
+
+ __cdev[size - 1].id = nla_get_u32(attr);
+ }
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_NAME) {
+ nla_strlcpy(__cdev[size - 1].name, attr,
+ THERMAL_NAME_LENGTH);
+ }
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_CUR_STATE)
+ __cdev[size - 1].cur_state = nla_get_u32(attr);
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_CDEV_MAX_STATE)
+ __cdev[size - 1].max_state = nla_get_u32(attr);
+ }
+
+ if (__cdev)
+ __cdev[size].id = -1;
+
+ *cdev = __cdev;
+
+ return THERMAL_SUCCESS;
+}
+
+static int parse_tz_get_trip(struct genl_info *info, struct thermal_zone *tz)
+{
+ struct nlattr *attr;
+ struct thermal_trip *__tt = NULL;
+ size_t size = 0;
+ int rem;
+
+ nla_for_each_nested(attr, info->attrs[THERMAL_GENL_ATTR_TZ_TRIP], rem) {
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_ID) {
+
+ size++;
+
+ __tt = realloc(__tt, sizeof(*__tt) * (size + 2));
+ if (!__tt)
+ return THERMAL_ERROR;
+
+ __tt[size - 1].id = nla_get_u32(attr);
+ }
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TYPE)
+ __tt[size - 1].type = nla_get_u32(attr);
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_TEMP)
+ __tt[size - 1].temp = nla_get_u32(attr);
+
+ if (nla_type(attr) == THERMAL_GENL_ATTR_TZ_TRIP_HYST)
+ __tt[size - 1].hyst = nla_get_u32(attr);
+ }
+
+ if (__tt)
+ __tt[size].id = -1;
+
+ tz->trip = __tt;
+
+ return THERMAL_SUCCESS;
+}
+
+static int parse_tz_get_temp(struct genl_info *info, struct thermal_zone *tz)
+{
+ int id = -1;
+
+ if (info->attrs[THERMAL_GENL_ATTR_TZ_ID])
+ id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+
+ if (tz->id != id)
+ return THERMAL_ERROR;
+
+ if (info->attrs[THERMAL_GENL_ATTR_TZ_TEMP])
+ tz->temp = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_TEMP]);
+
+ return THERMAL_SUCCESS;
+}
+
+static int parse_tz_get_gov(struct genl_info *info, struct thermal_zone *tz)
+{
+ int id = -1;
+
+ if (info->attrs[THERMAL_GENL_ATTR_TZ_ID])
+ id = nla_get_u32(info->attrs[THERMAL_GENL_ATTR_TZ_ID]);
+
+ if (tz->id != id)
+ return THERMAL_ERROR;
+
+ if (info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME]) {
+ nla_strlcpy(tz->governor,
+ info->attrs[THERMAL_GENL_ATTR_TZ_GOV_NAME],
+ THERMAL_NAME_LENGTH);
+ }
+
+ return THERMAL_SUCCESS;
+}
+
+static int handle_netlink(struct nl_cache_ops *unused,
+ struct genl_cmd *cmd,
+ struct genl_info *info, void *arg)
+{
+ int ret;
+
+ switch (cmd->c_id) {
+
+ case THERMAL_GENL_CMD_TZ_GET_ID:
+ ret = parse_tz_get(info, arg);
+ break;
+
+ case THERMAL_GENL_CMD_CDEV_GET:
+ ret = parse_cdev_get(info, arg);
+ break;
+
+ case THERMAL_GENL_CMD_TZ_GET_TEMP:
+ ret = parse_tz_get_temp(info, arg);
+ break;
+
+ case THERMAL_GENL_CMD_TZ_GET_TRIP:
+ ret = parse_tz_get_trip(info, arg);
+ break;
+
+ case THERMAL_GENL_CMD_TZ_GET_GOV:
+ ret = parse_tz_get_gov(info, arg);
+ break;
+
+ default:
+ return THERMAL_ERROR;
+ }
+
+ return ret;
+}
+
+static struct genl_cmd thermal_cmds[] = {
+ {
+ .c_id = THERMAL_GENL_CMD_TZ_GET_ID,
+ .c_name = (char *)"List thermal zones",
+ .c_msg_parser = handle_netlink,
+ .c_maxattr = THERMAL_GENL_ATTR_MAX,
+ .c_attr_policy = thermal_genl_policy,
+ },
+ {
+ .c_id = THERMAL_GENL_CMD_TZ_GET_GOV,
+ .c_name = (char *)"Get governor",
+ .c_msg_parser = handle_netlink,
+ .c_maxattr = THERMAL_GENL_ATTR_MAX,
+ .c_attr_policy = thermal_genl_policy,
+ },
+ {
+ .c_id = THERMAL_GENL_CMD_TZ_GET_TEMP,
+ .c_name = (char *)"Get thermal zone temperature",
+ .c_msg_parser = handle_netlink,
+ .c_maxattr = THERMAL_GENL_ATTR_MAX,
+ .c_attr_policy = thermal_genl_policy,
+ },
+ {
+ .c_id = THERMAL_GENL_CMD_TZ_GET_TRIP,
+ .c_name = (char *)"Get thermal zone trip points",
+ .c_msg_parser = handle_netlink,
+ .c_maxattr = THERMAL_GENL_ATTR_MAX,
+ .c_attr_policy = thermal_genl_policy,
+ },
+ {
+ .c_id = THERMAL_GENL_CMD_CDEV_GET,
+ .c_name = (char *)"Get cooling devices",
+ .c_msg_parser = handle_netlink,
+ .c_maxattr = THERMAL_GENL_ATTR_MAX,
+ .c_attr_policy = thermal_genl_policy,
+ },
+};
+
+static struct genl_ops thermal_cmd_ops = {
+ .o_name = (char *)"thermal",
+ .o_cmds = thermal_cmds,
+ .o_ncmds = ARRAY_SIZE(thermal_cmds),
+};
+
+static thermal_error_t thermal_genl_auto(struct thermal_handler *th, int id, int cmd,
+ int flags, void *arg)
+{
+ struct nl_msg *msg;
+ void *hdr;
+
+ msg = nlmsg_alloc();
+ if (!msg)
+ return THERMAL_ERROR;
+
+ hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, thermal_cmd_ops.o_id,
+ 0, flags, cmd, THERMAL_GENL_VERSION);
+ if (!hdr)
+ return THERMAL_ERROR;
+
+ if (id >= 0 && nla_put_u32(msg, THERMAL_GENL_ATTR_TZ_ID, id))
+ return THERMAL_ERROR;
+
+ if (nl_send_msg(th->sk_cmd, th->cb_cmd, msg, genl_handle_msg, arg))
+ return THERMAL_ERROR;
+
+ nlmsg_free(msg);
+
+ return THERMAL_SUCCESS;
+}
+
+thermal_error_t thermal_cmd_get_tz(struct thermal_handler *th, struct thermal_zone **tz)
+{
+ return thermal_genl_auto(th, -1, THERMAL_GENL_CMD_TZ_GET_ID,
+ NLM_F_DUMP | NLM_F_ACK, tz);
+}
+
+thermal_error_t thermal_cmd_get_cdev(struct thermal_handler *th, struct thermal_cdev **tc)
+{
+ return thermal_genl_auto(th, -1, THERMAL_GENL_CMD_CDEV_GET,
+ NLM_F_DUMP | NLM_F_ACK, tc);
+}
+
+thermal_error_t thermal_cmd_get_trip(struct thermal_handler *th, struct thermal_zone *tz)
+{
+ return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_TRIP,
+ 0, tz);
+}
+
+thermal_error_t thermal_cmd_get_governor(struct thermal_handler *th, struct thermal_zone *tz)
+{
+ return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_GOV, 0, tz);
+}
+
+thermal_error_t thermal_cmd_get_temp(struct thermal_handler *th, struct thermal_zone *tz)
+{
+ return thermal_genl_auto(th, tz->id, THERMAL_GENL_CMD_TZ_GET_TEMP, 0, tz);
+}
+
+thermal_error_t thermal_cmd_exit(struct thermal_handler *th)
+{
+ if (genl_unregister_family(&thermal_cmd_ops))
+ return THERMAL_ERROR;
+
+ nl_thermal_disconnect(th->sk_cmd, th->cb_cmd);
+
+ return THERMAL_SUCCESS;
+}
+
+thermal_error_t thermal_cmd_init(struct thermal_handler *th)
+{
+ int ret;
+ int family;
+
+ if (nl_thermal_connect(&th->sk_cmd, &th->cb_cmd))
+ return THERMAL_ERROR;
+
+ ret = genl_register_family(&thermal_cmd_ops);
+ if (ret)
+ return THERMAL_ERROR;
+
+ ret = genl_ops_resolve(th->sk_cmd, &thermal_cmd_ops);
+ if (ret)
+ return THERMAL_ERROR;
+
+ family = genl_ctrl_resolve(th->sk_cmd, "nlctrl");
+ if (family != GENL_ID_CTRL)
+ return THERMAL_ERROR;
+
+ return THERMAL_SUCCESS;
+}
diff --git a/shared/lib/libthermal/src/events.c b/shared/lib/libthermal/src/events.c
new file mode 100644
index 0000000..a7a55d1
--- /dev/null
+++ b/shared/lib/libthermal/src/events.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
+#include <linux/netlink.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+
+#include <thermal.h>
+#include "thermal_nl.h"
+
+/*
+ * Optimization: fill this array to tell which event we do want to pay
+ * attention to. That happens at init time with the ops
+ * structure. Each ops will enable the event and the general handler
+ * will be able to discard the event if there is not ops associated
+ * with it.
+ */
+static int enabled_ops[__THERMAL_GENL_EVENT_MAX];
+
+static int handle_thermal_event(struct nl_msg *n, void *arg)
+{
+ struct nlmsghdr *nlh = nlmsg_hdr(n);
+ struct genlmsghdr *genlhdr = genlmsg_hdr(nlh);
+ struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1];
+ struct thermal_handler_param *thp = arg;
+ struct thermal_events_ops *ops = &thp->th->ops->events;
+
+ genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL);
+
+ arg = thp->arg;
+
+ /*
+ * This is an event we don't care of, bail out.
+ */
+ if (!enabled_ops[genlhdr->cmd])
+ return THERMAL_SUCCESS;
+
+ switch (genlhdr->cmd) {
+
+ case THERMAL_GENL_EVENT_TZ_CREATE:
+ return ops->tz_create(nla_get_string(attrs[THERMAL_GENL_ATTR_TZ_NAME]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_DELETE:
+ return ops->tz_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_ENABLE:
+ return ops->tz_enable(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_DISABLE:
+ return ops->tz_disable(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_TRIP_CHANGE:
+ return ops->trip_change(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_TRIP_ADD:
+ return ops->trip_add(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_TRIP_DELETE:
+ return ops->trip_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_TRIP_UP:
+ return ops->trip_high(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_TRIP_DOWN:
+ return ops->trip_low(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg);
+
+ case THERMAL_GENL_EVENT_CDEV_ADD:
+ return ops->cdev_add(nla_get_string(attrs[THERMAL_GENL_ATTR_CDEV_NAME]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_MAX_STATE]), arg);
+
+ case THERMAL_GENL_EVENT_CDEV_DELETE:
+ return ops->cdev_delete(nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]), arg);
+
+ case THERMAL_GENL_EVENT_CDEV_STATE_UPDATE:
+ return ops->cdev_update(nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_CUR_STATE]), arg);
+
+ case THERMAL_GENL_EVENT_TZ_GOV_CHANGE:
+ return ops->gov_change(nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
+ nla_get_string(attrs[THERMAL_GENL_ATTR_GOV_NAME]), arg);
+ default:
+ return -1;
+ }
+}
+
+static void thermal_events_ops_init(struct thermal_events_ops *ops)
+{
+ enabled_ops[THERMAL_GENL_EVENT_TZ_CREATE] = !!ops->tz_create;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_DELETE] = !!ops->tz_delete;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_DISABLE] = !!ops->tz_disable;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_ENABLE] = !!ops->tz_enable;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_UP] = !!ops->trip_high;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_DOWN] = !!ops->trip_low;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_CHANGE] = !!ops->trip_change;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_ADD] = !!ops->trip_add;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_TRIP_DELETE] = !!ops->trip_delete;
+ enabled_ops[THERMAL_GENL_EVENT_CDEV_ADD] = !!ops->cdev_add;
+ enabled_ops[THERMAL_GENL_EVENT_CDEV_DELETE] = !!ops->cdev_delete;
+ enabled_ops[THERMAL_GENL_EVENT_CDEV_STATE_UPDATE] = !!ops->cdev_update;
+ enabled_ops[THERMAL_GENL_EVENT_TZ_GOV_CHANGE] = !!ops->gov_change;
+}
+
+thermal_error_t thermal_events_handle(struct thermal_handler *th, void *arg)
+{
+ struct thermal_handler_param thp = { .th = th, .arg = arg };
+
+ if (!th)
+ return THERMAL_ERROR;
+
+ if (nl_cb_set(th->cb_event, NL_CB_VALID, NL_CB_CUSTOM,
+ handle_thermal_event, &thp))
+ return THERMAL_ERROR;
+
+ return nl_recvmsgs(th->sk_event, th->cb_event);
+}
+
+int thermal_events_fd(struct thermal_handler *th)
+{
+ if (!th)
+ return -1;
+
+ return nl_socket_get_fd(th->sk_event);
+}
+
+thermal_error_t thermal_events_exit(struct thermal_handler *th)
+{
+ if (nl_unsubscribe_thermal(th->sk_event, th->cb_event,
+ THERMAL_GENL_EVENT_GROUP_NAME))
+ return THERMAL_ERROR;
+
+ nl_thermal_disconnect(th->sk_event, th->cb_event);
+
+ return THERMAL_SUCCESS;
+}
+
+thermal_error_t thermal_events_init(struct thermal_handler *th)
+{
+ thermal_events_ops_init(&th->ops->events);
+
+ if (nl_thermal_connect(&th->sk_event, &th->cb_event))
+ return THERMAL_ERROR;
+
+ if (nl_subscribe_thermal(th->sk_event, th->cb_event,
+ THERMAL_GENL_EVENT_GROUP_NAME))
+ return THERMAL_ERROR;
+
+ return THERMAL_SUCCESS;
+}
diff --git a/shared/lib/libthermal/src/sampling.c b/shared/lib/libthermal/src/sampling.c
new file mode 100644
index 0000000..7057742
--- /dev/null
+++ b/shared/lib/libthermal/src/sampling.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <thermal.h>
+#include "thermal_nl.h"
+
+static int handle_thermal_sample(struct nl_msg *n, void *arg)
+{
+ struct nlmsghdr *nlh = nlmsg_hdr(n);
+ struct genlmsghdr *genlhdr = genlmsg_hdr(nlh);
+ struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1];
+ struct thermal_handler_param *thp = arg;
+ struct thermal_handler *th = thp->th;
+
+ genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL);
+
+ switch (genlhdr->cmd) {
+
+ case THERMAL_GENL_SAMPLING_TEMP:
+ return th->ops->sampling.tz_temp(
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]),
+ nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]), arg);
+ default:
+ return THERMAL_ERROR;
+ }
+}
+
+thermal_error_t thermal_sampling_handle(struct thermal_handler *th, void *arg)
+{
+ struct thermal_handler_param thp = { .th = th, .arg = arg };
+
+ if (!th)
+ return THERMAL_ERROR;
+
+ if (nl_cb_set(th->cb_sampling, NL_CB_VALID, NL_CB_CUSTOM,
+ handle_thermal_sample, &thp))
+ return THERMAL_ERROR;
+
+ return nl_recvmsgs(th->sk_sampling, th->cb_sampling);
+}
+
+int thermal_sampling_fd(struct thermal_handler *th)
+{
+ if (!th)
+ return -1;
+
+ return nl_socket_get_fd(th->sk_sampling);
+}
+
+thermal_error_t thermal_sampling_exit(struct thermal_handler *th)
+{
+ if (nl_unsubscribe_thermal(th->sk_sampling, th->cb_sampling,
+ THERMAL_GENL_SAMPLING_GROUP_NAME))
+ return THERMAL_ERROR;
+
+ nl_thermal_disconnect(th->sk_sampling, th->cb_sampling);
+
+ return THERMAL_SUCCESS;
+}
+
+thermal_error_t thermal_sampling_init(struct thermal_handler *th)
+{
+ if (nl_thermal_connect(&th->sk_sampling, &th->cb_sampling))
+ return THERMAL_ERROR;
+
+ if (nl_subscribe_thermal(th->sk_sampling, th->cb_sampling,
+ THERMAL_GENL_SAMPLING_GROUP_NAME))
+ return THERMAL_ERROR;
+
+ return THERMAL_SUCCESS;
+}
diff --git a/shared/lib/libthermal/src/thermal.c b/shared/lib/libthermal/src/thermal.c
new file mode 100644
index 0000000..72a76dc
--- /dev/null
+++ b/shared/lib/libthermal/src/thermal.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
+#include <stdio.h>
+#include <thermal.h>
+
+#include "thermal_nl.h"
+
+int for_each_thermal_cdev(struct thermal_cdev *cdev, cb_tc_t cb, void *arg)
+{
+ int i, ret = 0;
+
+ if (!cdev)
+ return 0;
+
+ for (i = 0; cdev[i].id != -1; i++)
+ ret |= cb(&cdev[i], arg);
+
+ return ret;
+}
+
+int for_each_thermal_trip(struct thermal_trip *tt, cb_tt_t cb, void *arg)
+{
+ int i, ret = 0;
+
+ if (!tt)
+ return 0;
+
+ for (i = 0; tt[i].id != -1; i++)
+ ret |= cb(&tt[i], arg);
+
+ return ret;
+}
+
+int for_each_thermal_zone(struct thermal_zone *tz, cb_tz_t cb, void *arg)
+{
+ int i, ret = 0;
+
+ if (!tz)
+ return 0;
+
+ for (i = 0; tz[i].id != -1; i++)
+ ret |= cb(&tz[i], arg);
+
+ return ret;
+}
+
+struct thermal_zone *thermal_zone_find_by_name(struct thermal_zone *tz,
+ const char *name)
+{
+ int i;
+
+ if (!tz || !name)
+ return NULL;
+
+ for (i = 0; tz[i].id != -1; i++) {
+ if (!strcmp(tz[i].name, name))
+ return &tz[i];
+ }
+
+ return NULL;
+}
+
+struct thermal_zone *thermal_zone_find_by_id(struct thermal_zone *tz, int id)
+{
+ int i;
+
+ if (!tz || id < 0)
+ return NULL;
+
+ for (i = 0; tz[i].id != -1; i++) {
+ if (tz[i].id == id)
+ return &tz[i];
+ }
+
+ return NULL;
+}
+
+static int __thermal_zone_discover(struct thermal_zone *tz, void *th)
+{
+ if (thermal_cmd_get_trip(th, tz) < 0)
+ return -1;
+
+ if (thermal_cmd_get_governor(th, tz))
+ return -1;
+
+ return 0;
+}
+
+struct thermal_zone *thermal_zone_discover(struct thermal_handler *th)
+{
+ struct thermal_zone *tz;
+
+ if (thermal_cmd_get_tz(th, &tz) < 0)
+ return NULL;
+
+ if (for_each_thermal_zone(tz, __thermal_zone_discover, th))
+ return NULL;
+
+ return tz;
+}
+
+void thermal_exit(struct thermal_handler *th)
+{
+ thermal_cmd_exit(th);
+ thermal_events_exit(th);
+ thermal_sampling_exit(th);
+
+ free(th);
+}
+
+struct thermal_handler *thermal_init(struct thermal_ops *ops)
+{
+ struct thermal_handler *th;
+
+ th = malloc(sizeof(*th));
+ if (!th)
+ return NULL;
+ th->ops = ops;
+
+ if (thermal_events_init(th))
+ goto out_free;
+
+ if (thermal_sampling_init(th))
+ goto out_free;
+
+ if (thermal_cmd_init(th))
+ goto out_free;
+
+ return th;
+
+out_free:
+ free(th);
+
+ return NULL;
+}
diff --git a/shared/lib/libthermal/src/thermal_nl.c b/shared/lib/libthermal/src/thermal_nl.c
new file mode 100644
index 0000000..b05cf95
--- /dev/null
+++ b/shared/lib/libthermal/src/thermal_nl.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: LGPL-2.1+
+// Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <thermal.h>
+#include "thermal_nl.h"
+
+struct handler_args {
+ const char *group;
+ int id;
+};
+
+static __thread int err;
+static __thread int done;
+
+static int nl_seq_check_handler(struct nl_msg *msg, void *arg)
+{
+ return NL_OK;
+}
+
+static int nl_error_handler(struct sockaddr_nl *nla, struct nlmsgerr *nl_err,
+ void *arg)
+{
+ int *ret = arg;
+
+ if (ret)
+ *ret = nl_err->error;
+
+ return NL_STOP;
+}
+
+static int nl_finish_handler(struct nl_msg *msg, void *arg)
+{
+ int *ret = arg;
+
+ if (ret)
+ *ret = 1;
+
+ return NL_OK;
+}
+
+static int nl_ack_handler(struct nl_msg *msg, void *arg)
+{
+ int *ret = arg;
+
+ if (ret)
+ *ret = 1;
+
+ return NL_OK;
+}
+
+int nl_send_msg(struct nl_sock *sock, struct nl_cb *cb, struct nl_msg *msg,
+ int (*rx_handler)(struct nl_msg *, void *), void *data)
+{
+ if (!rx_handler)
+ return THERMAL_ERROR;
+
+ err = nl_send_auto_complete(sock, msg);
+ if (err < 0)
+ return err;
+
+ nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, rx_handler, data);
+
+ err = done = 0;
+
+ while (err == 0 && done == 0)
+ nl_recvmsgs(sock, cb);
+
+ return err;
+}
+
+static int nl_family_handler(struct nl_msg *msg, void *arg)
+{
+ struct handler_args *grp = arg;
+ struct nlattr *tb[CTRL_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *mcgrp;
+ int rem_mcgrp;
+
+ nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), NULL);
+
+ if (!tb[CTRL_ATTR_MCAST_GROUPS])
+ return THERMAL_ERROR;
+
+ nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
+
+ struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+ nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
+ nla_data(mcgrp), nla_len(mcgrp), NULL);
+
+ if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
+ !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
+ continue;
+
+ if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
+ grp->group,
+ nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
+ continue;
+
+ grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
+
+ break;
+ }
+
+ return THERMAL_SUCCESS;
+}
+
+static int nl_get_multicast_id(struct nl_sock *sock, struct nl_cb *cb,
+ const char *family, const char *group)
+{
+ struct nl_msg *msg;
+ int ret = 0, ctrlid;
+ struct handler_args grp = {
+ .group = group,
+ .id = -ENOENT,
+ };
+
+ msg = nlmsg_alloc();
+ if (!msg)
+ return THERMAL_ERROR;
+
+ ctrlid = genl_ctrl_resolve(sock, "nlctrl");
+
+ genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
+
+ nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, family);
+
+ ret = nl_send_msg(sock, cb, msg, nl_family_handler, &grp);
+ if (ret)
+ goto nla_put_failure;
+
+ ret = grp.id;
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb)
+{
+ struct nl_cb *cb;
+ struct nl_sock *sock;
+
+ cb = nl_cb_alloc(NL_CB_DEFAULT);
+ if (!cb)
+ return THERMAL_ERROR;
+
+ sock = nl_socket_alloc();
+ if (!sock)
+ goto out_cb_free;
+
+ if (genl_connect(sock))
+ goto out_socket_free;
+
+ if (nl_cb_err(cb, NL_CB_CUSTOM, nl_error_handler, &err) ||
+ nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, nl_finish_handler, &done) ||
+ nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, nl_ack_handler, &done) ||
+ nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nl_seq_check_handler, &done))
+ return THERMAL_ERROR;
+
+ *nl_sock = sock;
+ *nl_cb = cb;
+
+ return THERMAL_SUCCESS;
+
+out_socket_free:
+ nl_socket_free(sock);
+out_cb_free:
+ nl_cb_put(cb);
+ return THERMAL_ERROR;
+}
+
+void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb)
+{
+ nl_close(nl_sock);
+ nl_socket_free(nl_sock);
+ nl_cb_put(nl_cb);
+}
+
+int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb,
+ const char *group)
+{
+ int mcid;
+
+ mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME,
+ group);
+ if (mcid < 0)
+ return THERMAL_ERROR;
+
+ if (nl_socket_drop_membership(nl_sock, mcid))
+ return THERMAL_ERROR;
+
+ return THERMAL_SUCCESS;
+}
+
+int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb,
+ const char *group)
+{
+ int mcid;
+
+ mcid = nl_get_multicast_id(nl_sock, nl_cb, THERMAL_GENL_FAMILY_NAME,
+ group);
+ if (mcid < 0)
+ return THERMAL_ERROR;
+
+ if (nl_socket_add_membership(nl_sock, mcid))
+ return THERMAL_ERROR;
+
+ return THERMAL_SUCCESS;
+}
diff --git a/shared/lib/libthermal/src/thermal_nl.h b/shared/lib/libthermal/src/thermal_nl.h
new file mode 100644
index 0000000..ddf6356
--- /dev/null
+++ b/shared/lib/libthermal/src/thermal_nl.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/* Copyright (C) 2022, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org> */
+#ifndef __THERMAL_H
+#define __THERMAL_H
+
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/mngt.h>
+#include <netlink/genl/ctrl.h>
+
+struct thermal_handler {
+ int done;
+ int error;
+ struct thermal_ops *ops;
+ struct nl_msg *msg;
+ struct nl_sock *sk_event;
+ struct nl_sock *sk_sampling;
+ struct nl_sock *sk_cmd;
+ struct nl_cb *cb_cmd;
+ struct nl_cb *cb_event;
+ struct nl_cb *cb_sampling;
+};
+
+struct thermal_handler_param {
+ struct thermal_handler *th;
+ void *arg;
+};
+
+/*
+ * Low level netlink
+ */
+extern int nl_subscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb,
+ const char *group);
+
+extern int nl_unsubscribe_thermal(struct nl_sock *nl_sock, struct nl_cb *nl_cb,
+ const char *group);
+
+extern int nl_thermal_connect(struct nl_sock **nl_sock, struct nl_cb **nl_cb);
+
+extern void nl_thermal_disconnect(struct nl_sock *nl_sock, struct nl_cb *nl_cb);
+
+extern int nl_send_msg(struct nl_sock *sock, struct nl_cb *nl_cb, struct nl_msg *msg,
+ int (*rx_handler)(struct nl_msg *, void *),
+ void *data);
+
+#endif /* __THERMAL_H */
diff --git a/shared/lib/libthermal/sync.sh b/shared/lib/libthermal/sync.sh
new file mode 100755
index 0000000..2f1ea2f
--- /dev/null
+++ b/shared/lib/libthermal/sync.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+# set -ax
+GIT_LINUX_PATH=".linux"
+GIT_REPO=https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
+
+if [ ! -d $GIT_LINUX_PATH ]; then
+ echo " * Cloning $GIT_REPO"
+ git clone --depth 1 --sparse $GIT_REPO $GIT_LINUX_PATH
+fi
+
+pushd $GIT_LINUX_PATH > /dev/null
+
+# Update the official Linus' tree
+echo " * Updating $GIT_REPO"
+git pull
+
+GIT_LIB_THERMAL="tools/lib/thermal"
+GIT_THERMAL_LIB="tools/thermal/lib"
+GIT_SPARSE_SET="$GIT_LIB_THERMAL $GIT_THERMAL_LIB"
+
+echo " * Sparse checkout $GIT_SPARSE_SET"
+git sparse-checkout set $GIT_SPARSE_SET
+
+popd > /dev/null
+
+INCLUDE_PATH=src/include
+LIB_PATH=src/lib
+THERMAL_MANAGER_PATH=src/thermal-manager
+
+declare -A TARGETS
+
+TARGETS["include"]="$GIT_LINUX_PATH/$GIT_LIB_THERMAL/include/*.[ch]"
+TARGETS["src"]="$GIT_LINUX_PATH/$GIT_LIB_THERMAL/*.[ch] $GIT_LINUX_PATH/$GIT_LIB_THERMAL/Makefile"
+
+echo " * Updating source files"
+for TARGET in ${!TARGETS[@]}; do
+ for SRC in $(ls ${TARGETS[${TARGET}]}); do
+ diff -q $SRC $TARGET 2> /dev/null
+ if [ "$?" != "0" ]; then
+ echo " - Copying $SRC --> $TARGET"
+ cp $SRC $TARGET
+ fi
+ done
+done
+
+echo " * Patching source files"
+for PATCH in $(cat patches/patches.list); do
+ patch -p1 < patches/$PATCH
+done
+
+echo "Done"
diff --git a/shared/lib/libthermal/tst/Makefile b/shared/lib/libthermal/tst/Makefile
new file mode 100644
index 0000000..bea0a68
--- /dev/null
+++ b/shared/lib/libthermal/tst/Makefile
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: LGPL-2.1+
+CC=gcc
+DEPS = ../include/libthermal.h
+LIB=../src/libthermal.so
+C_BINS=tst_thermal.c
+CFLAGS=-Wall -Wno-unused
+
+BINS=$(C_BINS:.c=)
+
+tests: $(LIB) $(BINS)
+
+$(BINS): $(C_BINS)
+ $(CROSS_COMPILE)$(CC) $(CFLAGS) $< -o $@ -lthermal -L../src -Wl,-rpath=../src -I../include
+
+clean:
+ rm -f $(BINS) *~
diff --git a/shared/lib/libthermal/tst/tst_thermal.c b/shared/lib/libthermal/tst/tst_thermal.c
new file mode 100644
index 0000000..6206bd0
--- /dev/null
+++ b/shared/lib/libthermal/tst/tst_thermal.c
@@ -0,0 +1,303 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "thermal.h"
+
+#define MAX_EVENTS 10
+
+static int show_trip(struct thermal_trip *tt, void *arg)
+{
+ printf("trip id=%d, type=%d, temp=%d, hyst=%d\n",
+ tt->id, tt->type, tt->temp, tt->hyst);
+
+ return 0;
+}
+
+static int show_temp(struct thermal_zone *tz, void *arg)
+{
+ thermal_cmd_get_temp(arg, tz);
+
+ printf("temperature: %d\n", tz->temp);
+
+ return 0;
+}
+
+static int show_governor(struct thermal_zone *tz, void *arg)
+{
+ thermal_cmd_get_governor(arg, tz);
+
+ printf("governor: '%s'\n", tz->governor);
+
+ return 0;
+}
+
+static int show_tz(struct thermal_zone *tz, void *arg)
+{
+ printf("thermal zone '%s', id=%d\n", tz->name, tz->id);
+
+ for_each_thermal_trip(tz->trip, show_trip, NULL);
+
+ show_temp(tz, arg);
+
+ show_governor(tz, arg);
+
+ return 0;
+}
+
+static int tz_create(const char *name, int tz_id, void *arg)
+{
+ printf("Thermal zone '%s'/%d created\n", name, tz_id);
+
+ return 0;
+}
+
+static int tz_delete(int tz_id, void *arg)
+{
+ printf("Thermal zone %d deleted\n", tz_id);
+
+ return 0;
+}
+
+static int tz_disable(int tz_id, void *arg)
+{
+ printf("Thermal zone %d disabled\n", tz_id);
+
+ return 0;
+}
+
+static int tz_enable(int tz_id, void *arg)
+{
+ printf("Thermal zone %d enabled\n", tz_id);
+
+ return 0;
+}
+
+static int tz_temp(int tz_id, int temp, void *arg)
+{
+ printf("Thermal zone %d temperature: %d\n", tz_id, temp);
+
+ return 0;
+}
+
+static int trip_high(int tz_id, int trip_id, int temp, void *arg)
+{
+ printf("Thermal zone %d: trip point %d crossed way up with %d °C\n",
+ tz_id, trip_id, temp);
+
+ return 0;
+}
+
+static int trip_low(int tz_id, int trip_id, int temp, void *arg)
+{
+ printf("Thermal zone %d: trip point %d crossed way down with %d °C\n",
+ tz_id, trip_id, temp);
+
+ return 0;
+}
+
+static int trip_add(int tz_id, int trip_id, int type, int temp, int hyst, void *arg)
+{
+ printf("Trip point added %d: id=%d, type=%d, temp=%d, hyst=%d\n",
+ tz_id, trip_id, type, temp, hyst);
+
+ return 0;
+}
+
+static int trip_delete(int tz_id, int trip_id, void *arg)
+{
+ printf("Trip point deleted %d: id=%d\n", tz_id, trip_id);
+
+ return 0;
+}
+
+static int trip_change(int tz_id, int trip_id, int type, int temp, int hyst, void *arg)
+{
+ printf("Trip point changed %d: id=%d, type=%d, temp=%d, hyst=%d\n",
+ tz_id, trip_id, type, temp, hyst);
+
+ return 0;
+}
+
+static int cdev_add(const char *name, int cdev_id, int max_state, void *arg)
+{
+ printf("Cooling device '%s'/%d (max state=%d) added",
+ name, cdev_id, max_state);
+
+ return 0;
+}
+
+static int cdev_delete(int cdev_id, void *arg)
+{
+ printf("Cooling device %d deleted", cdev_id);
+
+ return 0;
+}
+
+static int cdev_update(int cdev_id, int cur_state, void *arg)
+{
+ printf("cdev:%d state:%d\n", cdev_id, cur_state);
+
+ return 0;
+}
+
+static int gov_change(int tz_id, const char *name, void *arg)
+{
+ printf("tz %d, governor=%s\n", tz_id, name);
+
+ return 0;
+}
+
+static struct thermal_ops ops = {
+ .sampling.tz_temp = tz_temp,
+ .events.tz_create = tz_create,
+ .events.tz_delete = tz_delete,
+ .events.tz_disable = tz_disable,
+ .events.tz_enable = tz_enable,
+ .events.trip_high = trip_high,
+ .events.trip_low = trip_low,
+ .events.trip_add = trip_add,
+ .events.trip_delete = trip_delete,
+ .events.trip_change = trip_change,
+ .events.cdev_add = cdev_add,
+ .events.cdev_delete = cdev_delete,
+ .events.cdev_update = cdev_update,
+ .events.gov_change = gov_change
+};
+
+static int stop = 0;
+
+static void sighandler(int sig)
+{
+ stop = 1;
+};
+
+int thermal_netlink_get_temp_bench(struct thermal_handler *th,
+ struct thermal_zone *tz)
+{
+ int nr_messages = 0;
+ int nr_secs = 5;
+ unsigned long long sum = 0;
+
+ printf("Benchmarking netlink... wait %d secs\n", nr_secs);
+
+ signal(SIGALRM, sighandler);
+ alarm(nr_secs);
+ while (!stop) {
+ thermal_cmd_get_temp(th, tz);
+ sum += tz->temp;
+ nr_messages++;
+ }
+
+ printf("Temperature reading %d msg/sec (%llu usec/msg), avg temp=%llu\n",
+ nr_messages / nr_secs, 1000000ULL / (nr_messages / nr_secs),
+ sum / nr_messages);
+
+ return 0;
+}
+
+int thermal_sysfs_get_temp_bench(struct thermal_zone *tz)
+{
+ int nr_messages = 0;
+ int nr_secs = 5;
+ unsigned long long sum = 0;
+ char path[PATH_MAX];
+ int fd;
+
+ snprintf(path, PATH_MAX,
+ "/sys/class/thermal/thermal_zone%d/temp", tz->id);
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ printf("Benchmarking sysfs... wait %d secs\n", nr_secs);
+
+ signal(SIGALRM, sighandler);
+ alarm(nr_secs);
+ stop = 0;
+ while (!stop) {
+ char buffer[128] = { 0 };
+
+ pread(fd, buffer, 127, 0);
+ sum += atoi(buffer);
+ nr_messages++;
+ }
+
+ if (!nr_messages) {
+ fprintf(stderr, "No message read\n");
+ return -1;
+ }
+
+ printf("Temperature reading %d msg/sec (%llu usec/msg), avg temp=%llu\n",
+ nr_messages / nr_secs, 1000000ULL / (nr_messages / nr_secs),
+ sum / nr_messages);
+
+ close(fd);
+
+ return 0;
+}
+
+int main(void)
+{
+ struct thermal_zone *tz;
+ struct thermal_handler *th;
+ struct epoll_event ev;
+ struct epoll_event events[MAX_EVENTS];
+ int epollfd;
+ int nfds;
+ int i;
+
+ th = thermal_init(&ops);
+ if (!th)
+ return -1;
+
+ tz = thermal_zone_discover(th);
+ if (!tz)
+ return -1;
+
+ thermal_netlink_get_temp_bench(th, tz);
+
+ thermal_sysfs_get_temp_bench(tz);
+
+ for_each_thermal_zone(tz, show_tz, th);
+
+ epollfd = epoll_create1(0);
+ if (epollfd < 0)
+ return -1;
+
+ ev.events = EPOLLIN;
+ ev.data.ptr = thermal_events_handle;
+
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, thermal_events_fd(th), &ev) == -1)
+ return -1;
+
+ ev.events = EPOLLIN;
+ ev.data.ptr = thermal_sampling_handle;
+
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, thermal_sampling_fd(th), &ev) == -1)
+ return -1;
+
+ while (1) {
+
+ nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
+ for (i = 0; i < nfds; i++) {
+ if (events[i].data.ptr == thermal_events_handle) {
+ thermal_events_handle(th, NULL);
+ } else if (events[i].data.ptr == thermal_sampling_handle) {
+ thermal_sampling_handle(th, NULL);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/shared/sepolicy/hal_thermal.te b/shared/sepolicy/hal_thermal.te
new file mode 100644
index 0000000..d4ebc04
--- /dev/null
+++ b/shared/sepolicy/hal_thermal.te
@@ -0,0 +1,9 @@
+# vendor.thermal-hal-2-0.linaro-generic service
+type hal_thermal_linaro, domain;
+type hal_thermal_linaro_exec, exec_type, vendor_file_type, file_type;
+
+init_daemon_domain(hal_thermal_linaro);
+
+allow hal_thermal_linaro self:netlink_generic_socket create_socket_perms_no_ioctl;
+allow hal_thermal_linaro hwservicemanager_prop:file { getattr map open read };
+allow hal_thermal_linaro hwservicemanager:binder { call };
diff --git a/shared/thermal/Android.bp b/shared/thermal/Android.bp
new file mode 100644
index 0000000..1330aa3
--- /dev/null
+++ b/shared/thermal/Android.bp
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2023 Linaro Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "android.hardware.thermal@2.0-service.linaro-generic",
+ defaults: ["hidl_defaults"],
+ relative_install_path: "hw",
+ vendor: true,
+ init_rc: ["android.hardware.thermal@2.0-service.linaro-generic.rc"],
+ vintf_fragments: ["android.hardware.thermal@2.0-service.linaro-generic.xml"],
+
+ srcs: [
+ "Thermal.cpp",
+ "Config.cpp",
+ "CpuInfo.cpp",
+ "LibThermal.cpp",
+ "service.cpp"
+ ],
+
+ shared_libs: [
+ "libthermal",
+ "libbase",
+ "libhidlbase",
+ "libutils",
+ "libjsoncpp",
+ "android.hardware.thermal@2.0",
+ "android.hardware.thermal@1.0",
+ ],
+
+ cflags: ["-fexceptions"],
+
+ strip: {
+ none: true,
+ },
+}
+
+cc_binary {
+ name: "tstThermalHAL",
+ defaults: ["hidl_defaults"],
+ relative_install_path: "hw",
+ vendor: true,
+ srcs: [
+ "tstThermalHAL.cpp",
+ ],
+
+ // FIXME, remove json deps (need code reorg)
+ shared_libs: [
+ "libthermal",
+ "libbase",
+ "libhidlbase",
+ "libutils",
+ "libjsoncpp",
+ "android.hardware.thermal@1.0",
+ "android.hardware.thermal@2.0",
+ ],
+}
diff --git a/shared/thermal/Config.cpp b/shared/thermal/Config.cpp
new file mode 100644
index 0000000..103b55c
--- /dev/null
+++ b/shared/thermal/Config.cpp
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Config.h"
+
+#include <cmath>
+#include <iostream>
+#include <fstream>
+
+#include <android-base/properties.h>
+#include <android-base/logging.h>
+
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace V2_0 {
+namespace implementation {
+
+template<class T> bool Config::typeToEnum(std::string &type, T &t)
+{
+ if (type.empty())
+ return false;;
+
+ for (const auto cdt : hidl_enum_range<T>()) {
+
+ const std::string upperType = toString(cdt);
+
+ /*
+ * Let's compare on uppercase only so we can be
+ * comfortable with the syntax used in the
+ * configuration file.
+ */
+ if (toUpper(type) != toUpper(upperType))
+ continue;
+
+ t = cdt;
+
+ return true;
+ }
+
+ return false;
+}
+
+std::string Config::toUpper(const std::string &str)
+{
+ std::string upper_str(str);
+
+ for (std::string::size_type i = 0; i < upper_str.length(); i++)
+ upper_str[i] = std::toupper(str[i], std::locale());
+
+ return upper_str;
+}
+
+bool Config::readHotColdThrottling(Json::Value &throttling,
+ float *tempThreshold)
+{
+ if (throttling.empty())
+ return false;
+
+ for (const std::string mn : throttling.getMemberNames()) {
+
+ for (const auto ts : hidl_enum_range<V2_0::ThrottlingSeverity>()) {
+
+ const std::string severity = toString(ts);
+
+ if (toUpper(severity) != toUpper(mn))
+ continue;
+
+ tempThreshold[(int)ts] = throttling[mn].asFloat();
+
+ LOG(DEBUG) << "Throttling[\"" << severity << "\"] = " <<
+ tempThreshold[(int)ts] << "°C";
+
+ }
+ }
+
+ return true;
+}
+
+bool Config::readColdThrottling(Json::Value &throttling,
+ TemperatureThreshold &tempThreshold)
+{
+ return readHotColdThrottling(throttling,
+ tempThreshold.coldThrottlingThresholds.data());
+}
+
+bool Config::readHotThrottling(Json::Value &throttling,
+ TemperatureThreshold &tempThreshold)
+{
+ return readHotColdThrottling(throttling,
+ tempThreshold.hotThrottlingThresholds.data());
+}
+
+bool Config::readVrThrottling(Json::Value &throttling,
+ TemperatureThreshold &tempThreshold)
+{
+ LOG(DEBUG) << "Reading Virtual Reality configuration";
+
+ if (throttling["None"].empty()) {
+ LOG(ERROR) << "Invalid temperature threshold";
+ return false;
+ }
+
+ tempThreshold.vrThrottlingThreshold = throttling["None"].asFloat();
+
+ LOG(DEBUG) << "Virtual Reality Throtlling: " <<
+ tempThreshold.vrThrottlingThreshold << "°C";
+
+ return true;
+}
+
+void Config::initThreshold(TemperatureThreshold &tempThreshold)
+{
+ for (int i = 0; i < tempThreshold.hotThrottlingThresholds.size(); i++)
+ tempThreshold.hotThrottlingThresholds[i] = NAN;
+
+ for (int i = 0; i < tempThreshold.coldThrottlingThresholds.size(); i++)
+ tempThreshold.coldThrottlingThresholds[i] = NAN;
+
+ tempThreshold.vrThrottlingThreshold = NAN;
+}
+
+bool Config::readThrottling(const std::string &name, Json::Value &throttling)
+{
+ TemperatureThreshold tempThreshold = {
+ .name = name,
+ };
+
+ initThreshold(tempThreshold);
+
+ if (throttling.empty()) {
+ LOG(DEBUG) << "No threshold specified for '" << name << "'";
+ return true;
+ }
+
+ for (Json::Value::ArrayIndex i = 0; i < throttling.size(); ++i) {
+
+ std::string type = throttling[i]["Type"].asString();
+
+ LOG(DEBUG) << "Getting '" << type << "' throttling configuration";
+
+ if (type == "Hot") {
+ if (!readHotThrottling(throttling[i], tempThreshold)) {
+ LOG(ERROR) << "Failed to read hot throttling entry";
+ return false;
+ }
+ } else if (type == "Cold") {
+ if (!readColdThrottling(throttling[i], tempThreshold)) {
+ LOG(ERROR) << "Failed to read cold throttling entry";
+ return false;
+ }
+ } else if (type == "Vr") {
+ if (!readVrThrottling(throttling[i], tempThreshold)) {
+ LOG(ERROR) << "Failed to read Virtual Reality throttling entry";
+ return false;
+ }
+ } else {
+ LOG(ERROR) << "Invalid Throttling type: " << type;
+ return false;
+ }
+
+ tempThreshold.name = name;
+ }
+
+ this->m_threshold.insert(std::pair<std::string,
+ TemperatureThreshold>(name, tempThreshold));
+
+ return true;
+}
+
+bool Config::readSensor(Json::Value &sensor)
+{
+ std::string name = sensor["Name"].asString();
+ std::string type = sensor["Type"].asString();
+
+ Temperature_1_0 temperature_1_0;
+ Temperature_2_0 temperature_2_0;
+
+ if (name.empty()) {
+ LOG(ERROR) << "Missing sensor name section";
+ return false;
+ }
+
+ temperature_1_0.name = name;
+ temperature_2_0.name = name;
+
+ if (typeToEnum(type, temperature_1_0.type))
+ m_temperature_1_0.push_back(temperature_1_0);
+
+ if (typeToEnum(type, temperature_2_0.type))
+ m_temperature_2_0.push_back(temperature_2_0);
+
+ /*
+ * The skin temperature sensors are special ones and are
+ * stored in a second list for quick access for monitoring
+ */
+ if (temperature_1_0.type == V1_0::TemperatureType::SKIN)
+ m_skin_sensors.push_back(name);
+
+ LOG(DEBUG) << "Sensor: '" << name << "' / type: " << type;
+
+ if (!readThrottling(name, sensor["Throttling"]))
+ return false;
+
+ return true;
+}
+
+bool Config::readCoolingDevice(Json::Value &coolingDevice)
+{
+ std::string name = coolingDevice["Name"].asString();
+ std::string type = coolingDevice["Type"].asString();
+
+ CoolingDevice_1_0 coolingDevice_1_0;
+ CoolingDevice_2_0 coolingDevice_2_0;
+
+ if (name.empty() || type.empty()) {
+ LOG(ERROR) << "Missing Cooling device name/type";
+ return false;
+ }
+
+ coolingDevice_1_0.name = name;
+ coolingDevice_2_0.name = name;
+
+ if (typeToEnum(type, coolingDevice_1_0.type))
+ m_cooling_device_1_0.push_back(coolingDevice_1_0);
+
+ if (typeToEnum(type, coolingDevice_2_0.type))
+ m_cooling_device_2_0.push_back(coolingDevice_2_0);
+
+ return true;
+}
+
+bool Config::parseFile(std::string path, Json::Value &root)
+{
+ Json::CharReaderBuilder builder;
+ std::string strerr;
+ std::ifstream ifs;
+
+ LOG(DEBUG) << "Reading configuration file: " << path;
+
+ ifs.open(path);
+ if (!ifs) {
+ LOG(ERROR) << "Failed to open: " << path;
+ return false;
+ }
+
+ if (!parseFromStream(builder, ifs, &root, &strerr)) {
+ LOG(ERROR) << "Failed to parse JSON config: " << strerr;
+ return false;
+ }
+
+ LOG(DEBUG) << "Configuration file parsed successfully";
+
+ ifs.close();
+
+ return true;
+}
+
+bool Config::read(std::string path)
+{
+ Json::Value root;
+ Json::Value sensors;
+ Json::Value coolingDevices;
+
+ if (!parseFile(path, root))
+ return false;
+
+ sensors = root["Sensors"];
+
+ for (Json::Value::ArrayIndex i = 0; i < sensors.size(); ++i) {
+
+ if (!readSensor(sensors[i]))
+ return false;
+ }
+
+ coolingDevices = root["CoolingDevices"];
+
+ for (Json::Value::ArrayIndex i = 0; i < coolingDevices.size(); ++i) {
+
+ if (!readCoolingDevice(coolingDevices[i]))
+ return false;
+ }
+
+ return true;
+}
+
+bool Config::init(void)
+{
+ std::string property("vendor.thermal.config");
+ std::string default_conf("thermal.json");
+ std::string path = "/vendor/etc/" + android::base::GetProperty(property, default_conf);
+
+ return read(path);
+}
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace thermal
+} // namespace hardware
+} // namespace android
diff --git a/shared/thermal/Config.h b/shared/thermal/Config.h
new file mode 100644
index 0000000..22a5fde
--- /dev/null
+++ b/shared/thermal/Config.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef THERMAL_CONFIG_PARSER_H__
+#define THERMAL_CONFIG_PARSER_H__
+
+#include <android/hardware/thermal/2.0/IThermal.h>
+
+#include <map>
+#include <string>
+
+#include <json/reader.h>
+#include <json/value.h>
+
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace V2_0 {
+namespace implementation {
+
+using Temperature_1_0 = ::android::hardware::thermal::V1_0::Temperature;
+using Temperature_2_0 = ::android::hardware::thermal::V2_0::Temperature;
+using CoolingDevice_1_0 = ::android::hardware::thermal::V1_0::CoolingDevice;
+using CoolingDevice_2_0 = ::android::hardware::thermal::V2_0::CoolingDevice;
+
+using ::android::hardware::thermal::V2_0::TemperatureThreshold;
+using ::android::hardware::thermal::V2_0::TemperatureType;
+
+class Config {
+private:
+ template<class T> bool typeToEnum(std::string &type, T &t);
+
+ std::string toUpper(const std::string &str);
+
+ bool read(std::string file);
+
+ void initThreshold(TemperatureThreshold &tempThreshold);
+
+ bool readHotColdThrottling(Json::Value &throttling, float *tempThreshold);
+
+ bool readHotThrottling(Json::Value &throttling,
+ TemperatureThreshold &tempThreshold);
+
+ bool readColdThrottling(Json::Value &throttling,
+ TemperatureThreshold &tempThreshold);
+
+ bool readVrThrottling(Json::Value &throttling,
+ TemperatureThreshold &tempThreshold);
+
+ bool readThrottling(const std::string &name,
+ Json::Value &throttling);
+
+ bool readCoolingDevice(Json::Value &coolingDevice);
+
+ bool readSensor(Json::Value &sensor);
+
+ bool parseFile(std::string path, Json::Value &root);
+
+public:
+ std::vector<Temperature_1_0> m_temperature_1_0;
+ std::vector<Temperature_2_0> m_temperature_2_0;
+
+ std::vector<CoolingDevice_1_0> m_cooling_device_1_0;
+ std::vector<CoolingDevice_2_0> m_cooling_device_2_0;
+
+ /*
+ * Each sensor can have associated a configuration for the
+ * threshold, the key is the name of the sensor.
+ */
+ std::map<const std::string, TemperatureThreshold> m_threshold;
+
+ /*
+ * Contains the list of the skin temperature sensors.
+ */
+ std::vector<const std::string> m_skin_sensors;
+
+ bool init(void);
+};
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace thermal
+} // namespace hardware
+} // namespace android
+
+#endif
diff --git a/shared/thermal/CpuInfo.cpp b/shared/thermal/CpuInfo.cpp
new file mode 100644
index 0000000..5cc6e0b
--- /dev/null
+++ b/shared/thermal/CpuInfo.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <unistd.h>
+
+#include <iostream>
+#include <iomanip>
+#include <fstream>
+#include <regex>
+
+#include <hidl/HidlTransportSupport.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "CpuInfo.h"
+
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace V2_0 {
+namespace implementation {
+
+#define CPU_USAGE "/proc/stat"
+
+bool CpuInfo::parseCpuUsagesFileAndAssignUsages(hidl_vec<CpuUsage> &cpuUsages)
+{
+ std::string line;
+ std::ifstream file;
+ std::regex regEx("^cpu[0-9](.+)");
+
+ file.open(CPU_USAGE);
+ if (!file) {
+ LOG(ERROR) << "Failed to open: " << CPU_USAGE;
+ return false;
+ }
+
+ while (std::getline(file, line)) {
+
+ uint64_t cpuNum, cpuUser, cpuNice, cpuSys, cpuIdle;
+ std::vector<std::string> fields;
+
+ if (!regex_match(line, regEx))
+ continue;
+
+ fields = android::base::Split(line, " ");
+
+ /*
+ * The /proc/stat file is an ABI and not supposed to
+ * change. We can trust its content and the
+ * format. There is no need to check if the CPU is
+ * online as it will be removed from the file if it is
+ * offline. Consequently, if we can read the usage for
+ * a particular CPU, that means it is in the list,
+ * thus online.
+ */
+ cpuNum = std::stoi(fields[0].substr(3));
+ cpuUser = std::stoi(fields[1]);
+ cpuNice = std::stoi(fields[2]);
+ cpuSys = std::stoi(fields[3]);
+ cpuIdle = std::stoi(fields[4]);
+
+ cpuUsages[cpuNum].name = fields[0];
+ cpuUsages[cpuNum].active = cpuUser + cpuNice + cpuSys;
+ cpuUsages[cpuNum].total = cpuUser + cpuNice + cpuSys + cpuIdle;
+ cpuUsages[cpuNum].isOnline = true;
+ }
+
+ file.close();
+
+ return true;
+}
+
+bool CpuInfo::CpuUsages(hidl_vec<CpuUsage> &cpuUsages)
+{
+ if (!m_NrCpus)
+ return false;
+
+ /*
+ * Set the vector size to the number of possible CPUs
+ */
+ cpuUsages.resize(m_NrCpus);
+
+ /*
+ * Parse the stat file
+ */
+ parseCpuUsagesFileAndAssignUsages(cpuUsages);
+
+ return true;
+}
+
+CpuInfo::CpuInfo()
+{
+ m_NrCpus = sysconf(_SC_NPROCESSORS_CONF);
+}
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace thermal
+} // namespace hardware
+} // namespace android
diff --git a/shared/thermal/CpuInfo.h b/shared/thermal/CpuInfo.h
new file mode 100644
index 0000000..77e46e3
--- /dev/null
+++ b/shared/thermal/CpuInfo.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_THERMAL_V2_0_CPUINFO_H
+#define ANDROID_HARDWARE_THERMAL_V2_0_CPUINFO_H
+
+#include <android/hardware/thermal/2.0/IThermal.h>
+
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace V2_0 {
+namespace implementation {
+
+using ::android::hardware::hidl_vec;
+using ::android::hardware::thermal::V1_0::CpuUsage;
+
+class CpuInfo {
+private:
+ std::size_t m_NrCpus;
+ bool parseCpuUsagesFileAndAssignUsages(hidl_vec<CpuUsage> &cpuUsages);
+public:
+ bool CpuUsages(hidl_vec<CpuUsage> &cpuUsages);
+ CpuInfo();
+};
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace thermal
+} // namespace hardware
+} // namespace android
+#endif
diff --git a/shared/thermal/LibThermal.cpp b/shared/thermal/LibThermal.cpp
new file mode 100644
index 0000000..4244a70
--- /dev/null
+++ b/shared/thermal/LibThermal.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include "LibThermal.h"
+
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace V2_0 {
+namespace implementation {
+
+struct thermal_zone *LibThermal::getThermalZone(int id)
+{
+ return thermal_zone_find_by_id(this->m_tz, id);
+}
+
+struct thermal_zone *LibThermal::getThermalZone(std::string name)
+{
+ return thermal_zone_find_by_name(this->m_tz, name.c_str());
+}
+
+int LibThermal::getThermalZonetemp(struct thermal_zone *tz)
+{
+ if (thermal_cmd_get_temp(this->m_th, tz)) {
+ LOG(ERROR) << "Failed to read '" << tz->name << "' temperature";
+ return INT_MAX;
+ }
+
+ return tz->temp;
+}
+
+int LibThermal::getThermalZonetemp(int id)
+{
+ struct thermal_zone *tz;
+
+ tz = getThermalZone(id);
+ if (!tz) {
+ LOG(ERROR) << "Thermal zone <id=" << id << "> not found";
+ return INT_MAX;
+ }
+
+ return getThermalZonetemp(tz);
+}
+
+int LibThermal::getThermalZonetemp(const std::string name)
+{
+ struct thermal_zone *tz;
+
+ tz = getThermalZone(name);
+ if (!tz) {
+ LOG(ERROR) << "Thermal zone <" << name << "> not found";
+ return INT_MAX;
+ }
+
+ LOG(DEBUG) << "Getting temperature for thermal zone "
+ << name << " id=" << tz->id;
+
+ return getThermalZonetemp(tz);
+}
+
+std::string LibThermal::getThermalZoneName(int id)
+{
+ struct thermal_zone *tz;
+
+ tz = getThermalZone(id);
+ if (!tz)
+ return std::string("");
+
+ return std::string(tz->name);
+}
+
+LibThermal::LibThermal(void)
+{
+ LOG(DEBUG) << "Initializing the thermal library";
+
+ m_th = thermal_init(&m_ops);
+ if (!m_th)
+ throw("Failed to initialize the thermal library");
+
+ m_tz = thermal_zone_discover(m_th);
+ if (!m_tz)
+ throw("Failed to discover the thermal zones");
+}
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace thermal
+} // namespace hardware
+} // namespace android
diff --git a/shared/thermal/LibThermal.h b/shared/thermal/LibThermal.h
new file mode 100644
index 0000000..530faa9
--- /dev/null
+++ b/shared/thermal/LibThermal.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <string>
+
+#include "thermal.h"
+
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace V2_0 {
+namespace implementation {
+
+class LibThermalCallbacks {
+public:
+ /*
+ * Callbacks called when a thermal zone event occurs
+ */
+ int (*thermalZoneCreate)(const char *, int, void *);
+ int (*thermalZoneDelete) (int, void *);
+ int (*thermalZoneEnable)(int, void *);
+ int (*thermalZoneDisable)(int, void *);
+
+ /*
+ * Callbacks called when a trip event occurs
+ */
+ int (*tripHigh)(int, int, int, void *);
+ int (*tripLow)(int, int, int, void *);
+ int (*tripAdd)(int, int, int, int, int, void *);
+ int (*tripChange)(int, int, int, int, int, void *);
+ int (*tripDelete)(int, int, void *);
+
+ /*
+ * Callbacks called when a cooling device event occurs
+ */
+ int (*cdevAdd)(const char *, int, int, void *);
+ int (*cdevDelete)(int, void *);
+ int (*cdevUpdate)(int, int, void *);
+
+ /*
+ * Callback called when a governor event occurs
+ */
+ int (*govChange)(int, const char *, void *);
+
+ /*
+ * Callback called when there is a temperature sampling
+ */
+ int (*thermalZoneTemp)(int, int, void *);
+};
+
+class LibThermal {
+
+private:
+ struct thermal_zone *getThermalZone(int id);
+ struct thermal_zone *getThermalZone(std::string name);
+
+protected:
+ struct thermal_ops m_ops;
+ struct thermal_handler *m_th;
+ struct thermal_zone *m_tz;
+
+ LibThermal();
+public:
+ int getThermalZonetemp(const std::string name);
+ int getThermalZonetemp(struct thermal_zone *tz);
+ int getThermalZonetemp(int id);
+
+ std::string getThermalZoneName(int id);
+};
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace thermal
+} // namespace hardware
+} // namespace android
diff --git a/shared/thermal/Thermal.cpp b/shared/thermal/Thermal.cpp
new file mode 100644
index 0000000..48681e8
--- /dev/null
+++ b/shared/thermal/Thermal.cpp
@@ -0,0 +1,925 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "android.hardware.thermal@2.0-service.linaro-generic"
+
+#include <cmath>
+#include <set>
+
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include "Thermal.h"
+
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace V2_0 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::interfacesEqual;
+using ::android::hardware::thermal::V1_0::ThermalStatus;
+using ::android::hardware::thermal::V1_0::ThermalStatusCode;
+
+// -----------------------------------------------------------------------------
+// Methods from ::android::hardware::thermal::V1_0::IThermal follow.
+// -----------------------------------------------------------------------------
+
+/**
+ * Retrieves temperatures in Celsius.
+ *
+ * @return status Status of the operation. If status code is FAILURE,
+ * the status.debugMessage must be populated with the human-readable
+ * error message.
+ * @return temperatures If status code is SUCCESS, it's filled with the
+ * current temperatures. The order of temperatures of built-in
+ * devices (such as CPUs, GPUs and etc.) in the list must be kept
+ * the same regardless the number of calls to this method even if
+ * they go offline, if these devices exist on boot. The method
+ * always returns and never removes such temperatures.
+ *
+ */
+Return<void> Thermal::getTemperatures(getTemperatures_cb _hidl_cb)
+{
+ ThermalStatus status = { . code = ThermalStatusCode::SUCCESS };
+
+ for (auto &p : this->m_config.m_temperature_1_0) {
+
+ std::string name = p.name;
+ int temperature;
+
+ temperature = this->getThermalZonetemp(name);
+ if (temperature == INT_MAX) {
+ LOG(ERROR) << "Failed to read \"" << name << "\" temperature";
+ continue;
+ }
+
+ p.currentValue = temperature / 1000;
+ }
+
+ _hidl_cb(status, m_config.m_temperature_1_0);
+
+ return Void();
+}
+
+/**
+ * Retrieves CPU usage information of each core: active and total times
+ * in ms since first boot.
+ *
+ * @return status Status of the operation. If status code is FAILURE,
+ * the status.debugMessage must be populated with the human-readable
+ * error message.
+ * @return cpuUsages If status code is SUCCESS, it's filled with the current
+ * CPU usages. The order and number of CPUs in the list must be kept
+ * the same regardless the number of calls to this method.
+ *
+ */
+Return<void> Thermal::getCpuUsages(getCpuUsages_cb _hidl_cb)
+{
+ ThermalStatus status = { .code = ThermalStatusCode::SUCCESS };
+ hidl_vec<CpuUsage> cpuUsages;
+
+ if (m_cpuInfo.CpuUsages(cpuUsages))
+ _hidl_cb(status, cpuUsages);
+
+ return Void();
+}
+
+/**
+ * Retrieves the cooling devices information.
+ *
+ * @return status Status of the operation. If status code is FAILURE,
+ * the status.debugMessage must be populated with the human-readable
+ * error message.
+ * @return devices If status code is SUCCESS, it's filled with the current
+ * cooling device information. The order of built-in cooling
+ * devices in the list must be kept the same regardless the number
+ * of calls to this method even if they go offline, if these devices
+ * exist on boot. The method always returns and never removes from
+ * the list such cooling devices.
+ *
+ */
+Return<void> Thermal::getCoolingDevices(getCoolingDevices_cb _hidl_cb)
+{
+ ThermalStatus status = { .code = ThermalStatusCode::SUCCESS };
+
+ if (this->m_config.m_cooling_device_1_0.empty()) {
+ status.code = ThermalStatusCode::FAILURE;
+ status.debugMessage = "No cooling devices";
+ }
+
+ _hidl_cb(status, this->m_config.m_cooling_device_1_0);
+
+ return Void();
+}
+
+// -----------------------------------------------------------------------------
+// Methods from ::android::hardware::thermal::V2_0::IThermal follow.
+// -----------------------------------------------------------------------------
+
+/**
+ * Retrieves static temperature thresholds in Celsius.
+ *
+ * @param filterType whether to filter the result for a given type.
+ * @param type the TemperatureType such as battery or skin.
+ *
+ * @return status Status of the operation. If status code is FAILURE,
+ * the status.debugMessage must be populated with a human-readable error message.
+ * @return temperatureThresholds If status code is SUCCESS, it's filled with the
+ * temperatures thresholds. The order of temperatures of built-in
+ * devices (such as CPUs, GPUs and etc.) in the list must be kept
+ * the same regardless of the number of calls to this method even if
+ * they go offline, if these devices exist on boot. The method
+ * always returns and never removes such temperatures. The thresholds
+ * are returned as static values and must not change across calls. The actual
+ * throttling state is determined in device thermal mitigation policy/agorithm
+ * which might not be simple thresholds so these values Thermal HAL provided
+ * may not be accurate to detemin the throttling status. To get accurate
+ * throttling status, use getCurrentTemperatures or registerThermalChangedCallback
+ * and listen to the callback.
+ *
+ */
+Return<void> Thermal::getTemperatureThresholds(bool filterType, TemperatureType type,
+ getTemperatureThresholds_cb _hidl_cb)
+{
+ ThermalStatus status = { .code = ThermalStatusCode::SUCCESS };
+ std::vector<TemperatureThreshold> thresholds;
+
+ for (auto const& p : this->m_config.m_threshold) {
+
+ if (filterType && (type != p.second.type))
+ continue;
+
+ thresholds.push_back(p.second);
+ }
+
+ if (thresholds.empty()) {
+ status.code = ThermalStatusCode::FAILURE;
+ status.debugMessage = "No threshold temperature matching the type \"" + toString(type) + "\"";
+ }
+
+ _hidl_cb(status, thresholds);
+
+ return Void();
+}
+
+/**
+ * Retrieves temperatures in Celsius.
+ *
+ * @param filterType whether to filter the result for a given type.
+ * @param type the TemperatureType such as battery or skin.
+ *
+ * @return status Status of the operation. If status code is FAILURE,
+ * the status.debugMessage must be populated with a human-readable
+ * error message.
+ *
+ * @return temperatures If status code is SUCCESS, it's filled with the
+ * current temperatures. The order of temperatures of built-in
+ * devices (such as CPUs, GPUs and etc.) in the list must be kept
+ * the same regardless of the number of calls to this method even if
+ * they go offline, if these devices exist on boot. The method
+ * always returns and never removes such temperatures.
+ *
+ */
+Return<void> Thermal::getCurrentTemperatures(bool filterType, TemperatureType type,
+ getCurrentTemperatures_cb _hidl_cb)
+{
+ ThermalStatus status = { .code = ThermalStatusCode::SUCCESS };
+ std::vector<Temperature_2_0> temperatures;
+
+ for (auto &p : this->m_config.m_temperature_2_0) {
+
+ std::string name = p.name;
+ int temperature;
+
+ if (filterType && (type != p.type))
+ continue;
+
+ temperature = this->getThermalZonetemp(name);
+ if (temperature == INT_MAX) {
+ LOG(ERROR) << "Failed to read \"" << name << "\" temperature";
+ continue;
+ }
+
+ p.value = temperature / 1000;
+ temperatures.push_back(p);
+ }
+
+ if (temperatures.empty()) {
+ status.code = ThermalStatusCode::FAILURE;
+ status.debugMessage = "No temperature matching the type \"" + toString(type) + "\"";
+ }
+
+ _hidl_cb(status, temperatures);
+
+ return Void();
+}
+
+/**
+ * Retrieves the cooling devices information.
+ *
+ * @param filterType whether to filter the result for a given type.
+ * @param type the CoolingDevice such as CPU/GPU.
+ *
+ * @return status Status of the operation. If status code is FAILURE,
+ * the status.debugMessage must be populated with the human-readable
+ * error message.
+ * @return devices If status code is SUCCESS, it's filled with the current
+ * cooling device information. The order of built-in cooling
+ * devices in the list must be kept the same regardless of the number
+ * of calls to this method even if they go offline, if these devices
+ * exist on boot. The method always returns and never removes from
+ * the list such cooling devices.
+ *
+ */
+Return<void> Thermal::getCurrentCoolingDevices(bool filterType, CoolingType type,
+ getCurrentCoolingDevices_cb _hidl_cb)
+{
+ ThermalStatus status = { .code = ThermalStatusCode::SUCCESS };
+
+ std::vector<CoolingDevice_2_0> cooling_devices;
+
+ for (auto const& p : this->m_config.m_cooling_device_2_0) {
+
+ if (filterType && (type != p.type))
+ continue;
+
+ cooling_devices.push_back(p);
+ }
+
+ if (cooling_devices.empty()) {
+ status.code = ThermalStatusCode::FAILURE;
+ status.debugMessage = "No cooling device matching the type \"" + toString(type) + "\"";
+ }
+
+ _hidl_cb(status, cooling_devices);
+
+ return Void();
+}
+
+/**
+ * Register an IThermalChangedCallback, used by the Thermal HAL
+ * to receive thermal events when thermal mitigation status changed.
+ * Multiple registrations with different IThermalChangedCallback must be allowed.
+ * Multiple registrations with same IThermalChangedCallback is not allowed, client
+ * should unregister the given IThermalChangedCallback first.
+ *
+ * @param callback the IThermalChangedCallback to use for receiving
+ * thermal events (nullptr callback will lead to failure with status code FAILURE).
+ * @param filterType if filter for given sensor type.
+ * @param type the type to be filtered.
+ *
+ * @return status Status of the operation. If status code is FAILURE,
+ * the status.debugMessage must be populated with a human-readable error message.
+ *
+ */
+Return<void> Thermal::registerThermalChangedCallback(const sp<IThermalChangedCallback> &callback,
+ bool filterType, TemperatureType type,
+ registerThermalChangedCallback_cb _hidl_cb)
+{
+ ThermalStatus status = { .code = ThermalStatusCode::SUCCESS };
+ std::lock_guard<std::mutex> cbLock(m_callback_mutex);
+
+ if (callback == nullptr) {
+ status.code = ThermalStatusCode::FAILURE;
+ status.debugMessage = "Invalid thermal changed callback (null)";
+ goto out;
+ }
+
+ if (std::any_of(m_callbacks.begin(),
+ m_callbacks.end(),
+ [&](const CallbackSetting& c) {
+ return interfacesEqual(c.callback, callback);
+ })) {
+ status.code = ThermalStatusCode::FAILURE;
+ status.debugMessage = "Same callback interface already registered";
+ goto out;
+ }
+
+ m_callbacks.emplace_back(callback, filterType, type);
+
+ LOG(DEBUG) << "A callback has been registered to ThermalHAL, isFilter: "
+ << filterType << " " << toString(type);
+out:
+ _hidl_cb(status);
+ return Void();
+}
+
+/**
+ * Unregister an IThermalChangedCallback, used by the Thermal HAL
+ * to receive thermal events when thermal mitigation status changed.
+ *
+ * @param callback the IThermalChangedCallback used for receiving
+ * thermal events (nullptr callback will lead to failure with status code FAILURE).
+ *
+ * @return status Status of the operation. If status code is FAILURE,
+ * the status.debugMessage must be populated with a human-readable error message.
+ *
+ */
+Return<void> Thermal::unregisterThermalChangedCallback(const sp<IThermalChangedCallback> &callback,
+ unregisterThermalChangedCallback_cb _hidl_cb)
+{
+ ThermalStatus status = { .code = ThermalStatusCode::SUCCESS };
+ std::lock_guard<std::mutex> cbLock(m_callback_mutex);
+ std::vector<CallbackSetting>::iterator it;
+
+ if (callback == nullptr) {
+ status.code = ThermalStatusCode::FAILURE;
+ status.debugMessage = "Invalid thermal changed callback (null)";
+ goto out;
+ }
+
+ it = std::remove_if(m_callbacks.begin(), m_callbacks.end(),
+ [&](const CallbackSetting& c) {
+ return interfacesEqual(c.callback, callback);
+ });
+
+ if (it == m_callbacks.end()) {
+ status.code = ThermalStatusCode::FAILURE;
+ status.debugMessage = "The callback was not registered before";
+ goto out;
+ }
+
+ LOG(DEBUG) << "A callback has been unregistered from ThermalHAL, isFilter: "
+ << (*it).is_filter_type << " Type: " << toString((*it).type);
+
+ m_callbacks.erase(it);
+out:
+ _hidl_cb(status);
+
+ return Void();
+}
+
+/**
+ * Notify all subscribers about a trip point crossed the way up or
+ * down from the kernel.
+ *
+ * Please note, the kernel has a bogus behavior regarding the trip
+ * point crossed. It can send multiple message for the same event. The
+ * consumer of these events should not assume a trip point crossed the
+ * way up event will be followed by a trip point crossed the way down
+ * event or the next trip point crossed the way up.
+ *
+ * This bogus behavior is present in kernel < v6.7
+ *
+ * @param temperature the temperature sensor where the event happen
+ *
+ */
+void Thermal::thermalChangedCallback(Temperature_2_0 &temperature)
+{
+ for (auto &c : m_callbacks) {
+ if (c.is_filter_type && c.type != temperature.type)
+ continue;
+
+ c.callback->notifyThrottling(temperature);
+ }
+}
+
+/**
+ * Compute the severity of the throttling given the current
+ * temperature for a specific sensor name
+ *
+ * The configuration file may describe for each temperature a severity
+ * level. The function will figure out in which throttling interval
+ * falls the temperature given a specified temperature name.
+ *
+ * @param name the temperature name to lookup in the configuration
+ *
+ * @param temperature a float giving the current temperature in Celsius
+ *
+ * @return ThrottlingSeverity the throttling severity which can be one
+ * NONE, LIGHT, MODERATE, SEVERE, CRITICAL, EMERGENCY, SHUTDOWN. If no
+ * throttling severity is configured for the specified temperature
+ * @name, NONE is returned
+ *
+ */
+ThrottlingSeverity Thermal::throttlingSeverity(const std::string &name, float temperature)
+{
+ TemperatureThreshold threshold;
+ ThrottlingSeverity severity = ThrottlingSeverity::NONE;
+
+ if (m_config.m_threshold.find(name) == m_config.m_threshold.end()) {
+ LOG(DEBUG) << "No threshold for " << name;
+ return severity;
+ }
+
+ threshold = m_config.m_threshold[name];
+
+ for (const auto ts : hidl_enum_range<V2_0::ThrottlingSeverity>()) {
+
+ if (std::isnan(threshold.hotThrottlingThresholds[(int)ts]))
+ continue;
+
+ if (temperature < threshold.hotThrottlingThresholds[(int)ts])
+ break;
+
+ severity = ts;
+ }
+
+ LOG(DEBUG) << "Throttle severity=" << toString(severity)
+ << " temp=" << threshold.hotThrottlingThresholds[(int)severity]
+ << " current temp=" << temperature;
+
+ return severity;
+}
+
+/**
+ * The thermalZoneCreate callback is called when a thermal zone is
+ * created. That happens very early at system init. Usually when no
+ * listener of the events are there. But it is possible to have
+ * thermal zone to be created dynamically when loading a thermal
+ * sensor module, in this case the event is emitted.
+ *
+ * The event is not handled by the thermal HAL.
+ *
+ * @param name a string containing the name of the thermal zone,
+ * please note on some systems this name is not unique
+ *
+ * @param tzid an integer as unique identifier for the thermal zone
+ *
+ * @param arg a private pointer passed when registering all the
+ * callback. It allows to pass private date from the library user to
+ * its own routine
+ *
+ * @return 0
+ *
+ */
+int Thermal::thermalZoneCreate(const char *name, int tz_id, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Thermal zone " << name << "/" << tz_id << " created";
+
+ return 0;
+}
+
+/**
+ * The thermalZoneDelete callback is called when a thermal zone is
+ * deleted. That happens when a sensor module is unloaded in this case
+ * the event is emitted from the kernel.
+ *
+ * The event is not handled by the thermal HAL.
+ *
+ * @param tzid an integer as unique identifier for the thermal zone
+ *
+ * @param arg a private pointer passed when registering all the
+ * callback. It allows to pass private date from the library user to
+ * its own routine
+ *
+ * @return 0
+ *
+ */
+int Thermal::thermalZoneDelete(int tz_id, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Thermal zone " << tz_id << " delete";
+
+ return 0;
+}
+
+/**
+ * The thermalZoneEnable callback is called when a thermal zone is
+ * enabled. This event happens when the thermal zone was disabled and
+ * then enabled back. The action can be done from kernel space under
+ * some circumstances like a suspend or for any other reason.
+ * The userspace can be responsible of enabling / disabling a thermal
+ * zone.
+ *
+ * The event is not handled by the thermal HAL.
+ *
+ * @param tzid an integer as unique identifier for the thermal zone
+ *
+ * @param arg a private pointer passed when registering all the
+ * callback. It allows to pass private date from the library user to
+ * its own routine
+ *
+ * @return 0
+ *
+ */
+int Thermal::thermalZoneEnable(int tz_id, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Thermal zone " << tz_id << " enabled";
+
+ return 0;
+}
+
+/**
+ * The thermalZoneEnable callback is called when a thermal zone is
+ * disabled. This event happens when the thermal zone was disabled and
+ * then enabled back. The action can be done from kernel space under
+ * some circumstances like a suspend or for any other reason.
+ * The userspace can be responsible of enabling / disabling a thermal
+ * zone.
+ *
+ * The event is not handled by the thermal HAL.
+ *
+ * @param tz_id an integer as unique identifier for the thermal zone
+ *
+ * @param arg a private pointer passed when registering all the
+ * callback. It allows to pass private date from the library user to
+ * its own routine
+ *
+ * @return 0
+ *
+ */
+int Thermal::thermalZoneDisable(int tz_id, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Thermal zone " << tz_id << " disabled";
+
+ return 0;
+}
+
+/**
+ * The tripCrossed callback is called when a trip point is crossed the
+ * way up or down. It will invoke the registered HAL
+ * ThermalChangedCallback after figuring out the throttling severity
+ * given the thermal zone id.
+ *
+ * @param tz_id an integer as unique identifier for the thermal zone
+ *
+ * @param trip_id an integer as unique identifier in the thermal zone
+ * namespace for the trip point
+ *
+ * @param temp an integer representing the current temperature in
+ * milliCelsius
+ *
+ * @param thermal a pointer to the current Thermal object
+ *
+ * @param up a boolean telling the way the trip point was crossed
+ *
+ * @return 0 on success, -1 in case of error
+ *
+ */
+int Thermal::tripCrossed(int tz_id, int trip_id, int temp, Thermal *thermal, bool up)
+{
+ std::string name;
+ float temperature = temp / 1000.0;
+
+ name = thermal->getThermalZoneName(tz_id);
+ if (name.empty()) {
+ LOG(ERROR) << "No thermal zone name matching id " << tz_id;
+ return -1;
+ }
+
+ for (auto &p : thermal->m_config.m_temperature_2_0) {
+
+ if (p.name != name)
+ continue;
+
+ p.throttlingStatus = throttlingSeverity(name, temperature);
+ p.value = temperature;
+
+ thermal->thermalChangedCallback(p);
+ }
+
+ LOG(DEBUG) << "Thermal zone " << tz_id
+ << " (" << name << ")"
+ << " trip crossed the way "
+ << (up ? "up" : "down") << " with trip_id=" << trip_id
+ << ",temp=" << temp;
+
+ return 0;
+}
+
+/**
+ * The tripHigh callback is called from the thermal library when a
+ * trip point is crossed the way up (temperature is above the limit)
+ *
+ * @param tz_id an integer as unique identifier for the thermal zone
+ *
+ * @param trip_id an integer as unique identifier in the thermal zone
+ * namespace for the trip point
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0 on success, !0 otherwise
+ *
+ */
+int Thermal::tripHigh(int tz_id, int trip_id, int temp, void *arg)
+{
+ Thermal *thermal = (typeof(thermal))arg;
+
+ return thermal->tripCrossed(tz_id, trip_id, temp, thermal, true);
+}
+
+/**
+ * The tripLow callback is called from the thermal library when a trip
+ * point is crossed the way down (temperature is below the limit)
+ *
+ * @param tz_id an integer as unique identifier for the thermal zone
+ *
+ * @param trip_id an integer as unique identifier in the thermal zone
+ * namespace for the trip point
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0 on success, !0 otherwise
+ *
+ */
+int Thermal::tripLow(int tz_id, int trip_id, int temp, void *arg)
+{
+ Thermal *thermal = (typeof(thermal))arg;
+
+ return thermal->tripCrossed(tz_id, trip_id, temp, thermal, false);
+}
+
+/**
+ * The tripAdd callback is called from the thermal library when a trip
+ * point is added. This event only happens on very specific platforms,
+ * usually ACPI based. It is not known Android platforms with such a
+ * behavior yet.
+ *
+ * This event is not supported by the thermal HAL
+ *
+ * @param tz_id an integer as unique identifier for the thermal zone
+ *
+ * @param trip_id an integer as unique identifier in the thermal zone
+ * namespace for the trip point
+ *
+ * @param type an integer giving the type of the trip point
+ *
+ * @param temp an integer representing in milliCelsius the temperature limit
+ *
+ * @param hyst an integer giving in milliCelsius the hysteresis value
+ * to trigger events
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0
+ *
+ */
+int Thermal::tripAdd(int tz_id, int trip_id, int type,
+ int temp, int hyst, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Trip " << trip_id << " from thermal zone "
+ << tz_id << " added: type="
+ << type << ", temp=" << temp << " ,hyst=" << hyst;
+
+ return 0;
+}
+
+/**
+ * The tripChange callback is called from the thermal library when a
+ * trip point temperature or a hysteresis is changed. That can happen
+ * in the case of writable trip point where the userspace changes in
+ * sysfs these values.
+ *
+ * This can happen often if a thermal manager is monitoring closely
+ * the temperature of specific zones with low temperature transitions.
+ *
+ * At this point, this is not supported by the thermal HAL but the
+ * trip crossed events will reflect the change of situation for the
+ * thermalChangedCallback consumers.
+ *
+ * @param tz_id an integer as unique identifier for the thermal zone
+ *
+ * @param trip_id an integer as unique identifier in the thermal zone
+ * namespace for the trip point
+ *
+ * @param type an integer giving the type of the trip point
+ *
+ * @param temp an integer representing in milliCelsius the temperature limit
+ *
+ * @param hyst an integer giving in milliCelsius the hysteresis value
+ * to trigger events
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0
+ *
+ */
+int Thermal::tripChange(int tz_id, int trip_id, int type,
+ int temp, int hyst, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Trip " << trip_id << " from thermal zone "
+ << tz_id << " changed: type="
+ << type << ", temp=" << temp << " ,hyst=" << hyst;
+
+ return 0;
+}
+
+/**
+ * The tripDelete callback is called from the thermal library when a
+ * trip point is deleted. This event only happens on very specific
+ * platforms, usually ACPI based. It is not known Android platforms
+ * with such a behavior yet.
+ *
+ * This event is not supported by the thermal HAL
+ *
+ * @param tz_id an integer as unique identifier for the thermal zone
+ *
+ * @param trip_id an integer as unique identifier in the thermal zone
+ * namespace for the trip point
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0
+ *
+ */
+int Thermal::tripDelete(int tz_id, int trip_id, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Trip " << trip_id << " from thermal zone "
+ << tz_id << " deleted";
+
+ return 0;
+}
+
+/**
+ * The cdevAdd callback is called from the thermal library when a
+ * cooling device is registered in the thermal framework.
+ *
+ * This event can happen when a specific driver is loaded and
+ * registers itself in the thermal framework as a cooling device.
+ *
+ * The event does not tell if the the cooling device is bound to a
+ * thermal zone.
+ *
+ * This event is not supported by the thermal HAL
+ *
+ * @param name the name of the cooling device
+ *
+ * @param cdev_id an integer as an unique identifier for the cooling
+ * device
+ *
+ * @param max_state an integer telling the maximum cooling device
+ * state, the minimum being implicitely zero
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0
+ *
+ */
+int Thermal::cdevAdd(const char *name, int cdev_id, int max_state,
+ __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Cooling device '" << name << "'/" << cdev_id
+ << " max state" << max_state << " created";
+
+ return 0;
+}
+
+/**
+ * The cdevDelete callback is called from the thermal library when a
+ * cooling device is unregistered from the thermal framework.
+ *
+ * This event can happen when a specific driver is unloaded and
+ * unregisters itself from the thermal framework.
+ *
+ * This event is not supported by the thermal HAL
+ *
+ * @param cdev_id an integer as an unique identifier for the cooling
+ * device
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0
+ *
+ */
+int Thermal::cdevDelete(int cdev_id, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Cooling device " << cdev_id << " deleted";
+
+ return 0;
+}
+
+/**
+ * The cdevUpdate callback is called from the thermal library during
+ * mitigation episode. It gives an update of the current level of
+ * cooling effect. The number of events in this case can be high.
+ *
+ * This event is not supported by the thermal HAL
+ *
+ * @param cdev_id an integer as an unique identifier for the cooling
+ * device
+ *
+ * @param state the current cooling device state
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0
+ *
+ */
+int Thermal::cdevUpdate(int cdev_id, int state, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Coolling device " << cdev_id << " state=" << state;
+
+ return 0;
+}
+
+/**
+ * The govChange callback is called from the thermal library if the
+ * mitigation policy is changed. This event results from an userspace
+ * action.
+ *
+ * This event is not supported by the thermal HAL
+ *
+ * @param tz_id an integer as an unique identifier for the thermal
+ * zone
+ *
+ * @param name a string containing the thermal zone policy name
+ *
+ * @param arg a pointer to a private data
+ *
+ * @return 0
+ *
+ */
+int Thermal::govChange(int tz_id, const char *name, __attribute__((unused))void *arg)
+{
+ LOG(DEBUG) << "Governor change '" << name << "' " << "for thermal zone " << tz_id;
+
+ return 0;
+}
+
+/**
+ * Process events when they are available on the thermal library. This
+ * function will be responsible of having the thermal library invoking
+ * the different callback registered in the m_ops.
+ *
+ * @return 1
+ */
+int Thermal::handleThermalEvents(void)
+{
+ int ret;
+
+ ret = thermal_events_handle(m_th, this);
+ if (ret)
+ LOG(DEBUG) << "Failed to handle thermal event ret=" << ret;
+
+ /*
+ * We don't want to have the file descriptor removed in case
+ * of an error, so let's pretend everything is all right even
+ * if we had an error before
+ */
+ return 1;
+}
+
+/**
+ * This function is wrapper to do the connection between the Looper
+ * API and the Thermal object. It keeps the separation for the
+ * internals of the objects.
+ *
+ * @param fd the file description where new data arrived (unused)
+ *
+ * @param events an incremental identifier (unused)
+ *
+ * @param data a private data pointer to be passed around (unused)
+ *
+ * @return 1
+ */
+int ThermalLooperCallback::handleEvent(__attribute__((unused))int fd,
+ __attribute__((unused))int events, void *data)
+{
+ Thermal *thermal = (typeof(thermal))data;
+
+ return thermal->handleThermalEvents();
+}
+
+Thermal::Thermal(Looper *looper)
+{
+ LOG(DEBUG) << "Initializing the configuration";
+
+ if (!this->m_config.init())
+ throw("ThermalHAL failed to initialize the configuration");
+
+ /*
+ * The Thermal class is inherited from the LibThermal. The
+ * constructor of the Libthermal will initialize itself. We
+ * can safely set the callback after the initialization of the
+ * LibThermal as those are needed when calling the
+ * 'thermal_events_handle()' function, so after setting and
+ * polling the file descriptor.
+ */
+ m_ops.events.tz_create = thermalZoneCreate;
+ m_ops.events.tz_delete = thermalZoneDelete;
+ m_ops.events.tz_disable = thermalZoneDisable;
+ m_ops.events.tz_enable = thermalZoneEnable;
+ m_ops.events.trip_high = tripHigh;
+ m_ops.events.trip_low = tripLow;
+ m_ops.events.trip_add = tripAdd;
+ m_ops.events.trip_delete = tripDelete;
+ m_ops.events.trip_change = tripChange;
+ m_ops.events.cdev_add = cdevAdd;
+ m_ops.events.cdev_delete = cdevDelete;
+ m_ops.events.cdev_update = cdevUpdate;
+ m_ops.events.gov_change = govChange;
+
+ if (!looper->addFd(thermal_events_fd(this->m_th), 0,
+ Looper::EVENT_INPUT, m_thermalLooperCallback, this))
+ throw("Failed to add thermal file descriptor to the mainloop");
+}
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace thermal
+} // namespace hardware
+} // namespace android
diff --git a/shared/thermal/Thermal.h b/shared/thermal/Thermal.h
new file mode 100644
index 0000000..b98a2c9
--- /dev/null
+++ b/shared/thermal/Thermal.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANDROID_HARDWARE_THERMAL_V2_0_THERMAL_H
+#define ANDROID_HARDWARE_THERMAL_V2_0_THERMAL_H
+
+#include <android/hardware/thermal/2.0/IThermal.h>
+#include <android/hardware/thermal/2.0/IThermalChangedCallback.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+
+#include <utils/Looper.h>
+
+#include "Config.h"
+#include "CpuInfo.h"
+#include "LibThermal.h"
+
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace V2_0 {
+namespace implementation {
+
+using ::android::sp;
+using ::android::hardware::hidl_array;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::thermal::V1_0::CpuUsage;
+using ::android::hardware::thermal::V2_0::CoolingType;
+using ::android::hardware::thermal::V2_0::IThermal;
+using CoolingDevice_1_0 = ::android::hardware::thermal::V1_0::CoolingDevice;
+using CoolingDevice_2_0 = ::android::hardware::thermal::V2_0::CoolingDevice;
+using TemperatureType_1_0 = ::android::hardware::thermal::V1_0::Temperature;
+using TemperatureType_2_0 = ::android::hardware::thermal::V2_0::Temperature;
+using ::android::hardware::thermal::V2_0::IThermalChangedCallback;
+using ::android::hardware::thermal::V2_0::TemperatureThreshold;
+using ::android::hardware::thermal::V2_0::TemperatureType;
+
+struct CallbackSetting {
+ CallbackSetting(sp<IThermalChangedCallback> callback, bool is_filter_type, TemperatureType type)
+ : callback(std::move(callback)), is_filter_type(is_filter_type), type(type) {}
+ sp<IThermalChangedCallback> callback;
+ bool is_filter_type;
+ TemperatureType type;
+};
+
+class ThermalLooperCallback : public LooperCallback {
+
+public:
+ int handleEvent(int fd, int events, void* data);
+};
+
+class Thermal : public LibThermal, public IThermal {
+ public:
+ // Methods from ::android::hardware::thermal::V1_0::IThermal follow.
+ Return<void> getTemperatures(getTemperatures_cb _hidl_cb) override;
+
+ Return<void> getCpuUsages(getCpuUsages_cb _hidl_cb) override;
+
+ Return<void> getCoolingDevices(getCoolingDevices_cb _hidl_cb) override;
+
+ // Methods from ::android::hardware::thermal::V2_0::IThermal follow.
+ Return<void> getCurrentTemperatures(bool filterType, TemperatureType type,
+ getCurrentTemperatures_cb _hidl_cb) override;
+
+ Return<void> getTemperatureThresholds(bool filterType, TemperatureType type,
+ getTemperatureThresholds_cb _hidl_cb) override;
+
+ Return<void> registerThermalChangedCallback(const sp<IThermalChangedCallback> &callback,
+ bool filterType, TemperatureType type,
+ registerThermalChangedCallback_cb _hidl_cb) override;
+
+ Return<void> unregisterThermalChangedCallback(const sp<IThermalChangedCallback> &callback,
+ unregisterThermalChangedCallback_cb _hidl_cb) override;
+
+ Return<void> getCurrentCoolingDevices(bool filterType, CoolingType type,
+ getCurrentCoolingDevices_cb _hidl_cb) override;
+
+ int handleThermalEvents(void);
+
+ Thermal(Looper *);
+
+private:
+ static int thermalZoneDelete(int, void *);
+ static int thermalZoneCreate(const char *, int, void *);
+ static int thermalZoneEnable(int tz_id, void *arg);
+ static int thermalZoneDisable(int tz_id, void *arg);
+ static int tripHigh(int tz_id, int trip_id, int temp, void *arg);
+ static int tripLow(int tz_id, int trip_id, int temp, void *arg);
+ static int tripAdd(int tz_id, int trip_id, int type,
+ int temp, int hyst, void *arg);
+
+ static int tripChange(int tz_id, int trip_id, int type,
+ int temp, int hyst, void *arg);
+ static int tripDelete(int tz_id, int trip_id, void *arg);
+ static int cdevAdd(const char *name, int cdev_id, int max_state, void *arg);
+ static int cdevDelete(int cdev_id, void *arg);
+ static int cdevUpdate(int cdev_id, int state, void *arg);
+ static int govChange(int tz_id, const char *name, void *arg);
+
+ sp<ThermalLooperCallback> m_thermalLooperCallback;
+
+ void thermalChangedCallback(Temperature_2_0 &temperature);
+
+ ThrottlingSeverity throttlingSeverity(const std::string &name, float temperature);
+
+ int tripCrossed(int tz_id, int trip_id, int temp, Thermal *thermal, bool up);
+
+ Config m_config;
+ CpuInfo m_cpuInfo;
+
+ std::mutex m_callback_mutex;
+ std::vector<CallbackSetting> m_callbacks;
+};
+
+} // namespace implementation
+} // namespace V2_0
+} // namespace thermal
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_THERMAL_V2_0_THERMAL_H
diff --git a/shared/thermal/android.hardware.thermal@2.0-service.linaro-generic.rc b/shared/thermal/android.hardware.thermal@2.0-service.linaro-generic.rc
new file mode 100644
index 0000000..3b33b4e
--- /dev/null
+++ b/shared/thermal/android.hardware.thermal@2.0-service.linaro-generic.rc
@@ -0,0 +1,6 @@
+service hal_thermal /vendor/bin/hw/android.hardware.thermal@2.0-service.linaro-generic
+ interface android.hardware.thermal@1.0::IThermal generic
+ interface android.hardware.thermal@2.0::IThermal generic
+ class hal
+ user system
+ group system
diff --git a/shared/thermal/android.hardware.thermal@2.0-service.linaro-generic.xml b/shared/thermal/android.hardware.thermal@2.0-service.linaro-generic.xml
new file mode 100644
index 0000000..152d8a9
--- /dev/null
+++ b/shared/thermal/android.hardware.thermal@2.0-service.linaro-generic.xml
@@ -0,0 +1,13 @@
+<!-- Copyright 2023 Linaro Ltd. -->
+<manifest version="1.0" type="device">
+ <hal format="hidl">
+ <name>android.hardware.thermal</name>
+ <transport>hwbinder</transport>
+ <version>1.0</version>
+ <version>2.0</version>
+ <interface>
+ <name>IThermal</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/shared/thermal/service.cpp b/shared/thermal/service.cpp
new file mode 100644
index 0000000..75136ab
--- /dev/null
+++ b/shared/thermal/service.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "android.hardware.thermal@2.0-service.linaro-generic"
+
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+#include "Thermal.h"
+
+using ::android::OK;
+using ::android::status_t;
+
+// libhwbinder:
+using ::android::hardware::setupTransportPolling;
+using ::android::hardware::handleTransportPoll;
+
+// libutils:
+using ::android::Looper;
+
+// Generated HIDL files:
+using ::android::hardware::thermal::V2_0::IThermal;
+using ::android::hardware::thermal::V2_0::implementation::Thermal;
+
+/*
+ * TODO : more accurate exit values
+ */
+typedef enum {
+ THERMAL_HAL_OK,
+ THERMAL_HAL_INTERNAL_ERROR,
+} thermal_hal_error_t;
+
+static int shutdown(thermal_hal_error_t exit_code)
+{
+ LOG(ERROR) << "Thermal Service is shutting down";
+ exit(exit_code);
+}
+
+static int transportCallback(int fd, __attribute__((unused)) int event,
+ __attribute__((unused)) void *data)
+{
+ handleTransportPoll(fd);
+ return 1;
+}
+
+static bool setupTransportCallback(Looper *looper)
+{
+ int fd;
+
+ fd = setupTransportPolling();
+ if (fd < 0)
+ return false;
+
+ if (!looper->addFd(fd, 0, Looper::EVENT_INPUT, transportCallback, NULL))
+ return false;
+
+ return true;
+}
+
+int main(int /* argc */, char** /* argv */)
+{
+
+ status_t status;
+ Thermal *service;
+ Looper *looper;
+
+ LOG(DEBUG) << "Thermal HAL Service generic 2.0 starting...";
+
+ looper = new Looper(false);
+ if (looper == nullptr) {
+ LOG(ERROR) << "Failed to create looper object";
+ return shutdown(THERMAL_HAL_INTERNAL_ERROR);
+ }
+
+ try {
+ service = new Thermal(looper);
+ if (service == nullptr) {
+ LOG(ERROR) << "Error creating an instance of ThermalHAL. Exiting...";
+ return shutdown(THERMAL_HAL_INTERNAL_ERROR);
+ }
+ }
+
+ catch(std::string error) {
+ LOG(ERROR) << "Exception when creating Thermal object error: " << error;
+ return shutdown(THERMAL_HAL_INTERNAL_ERROR);
+ }
+
+ //
+ // No threads for the transport layer. They are not needed for
+ // our purpose. Furthermore, adding threads will lead us to
+ // the locking hell and an unexpected behavior with the
+ // netlink thermal protocol.
+ //
+ if (!setupTransportCallback(looper)) {
+ LOG(ERROR) << "Failed to setup transport callback";
+ return shutdown(THERMAL_HAL_INTERNAL_ERROR);
+ }
+
+ status = service->registerAsService("default");
+ if (status != OK) {
+ LOG(ERROR) << "Could not register service for ThermalHAL (" << status << ")";
+ return shutdown(THERMAL_HAL_INTERNAL_ERROR);
+ }
+
+ LOG(INFO) << "Thermal Service started successfully.";
+
+ looper->pollAll(-1);
+
+ return shutdown(THERMAL_HAL_OK);
+}
diff --git a/shared/thermal/tstThermalHAL.cpp b/shared/thermal/tstThermalHAL.cpp
new file mode 100644
index 0000000..4fb73ed
--- /dev/null
+++ b/shared/thermal/tstThermalHAL.cpp
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "tstThermalHal"
+
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <sys/timerfd.h>
+#include <sys/types.h>
+
+#include<iostream>
+
+#include <android/hardware/thermal/2.0/IThermal.h>
+#include <android/hardware/thermal/2.0/IThermalChangedCallback.h>
+#include <android/hardware/thermal/2.0/types.h>
+#include <android-base/logging.h>
+#include <hidl/HidlTransportSupport.h>
+#include <hidl/ServiceManagement.h>
+
+#include "Thermal.h"
+
+#define SKIN_POLLING_SEC 2
+
+using ::android::sp;
+using ::android::Looper;
+using ::android::hardware::hidl_enum_range;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::thermal::V1_0::ThermalStatus;
+using ::android::hardware::thermal::V1_0::ThermalStatusCode;
+using ::android::hardware::thermal::V2_0::CoolingDevice;
+using ::android::hardware::thermal::V2_0::CoolingType;
+using ::android::hardware::thermal::V2_0::IThermal;
+using ::android::hardware::thermal::V2_0::IThermalChangedCallback;
+using ::android::hardware::thermal::V2_0::Temperature;
+using ::android::hardware::thermal::V2_0::TemperatureThreshold;
+using ::android::hardware::thermal::V2_0::TemperatureType;
+using ::android::hardware::thermal::V2_0::ThrottlingSeverity;
+
+using ::android::hardware::setupTransportPolling;
+using ::android::hardware::handleTransportPoll;
+
+class ThermalCallback : public IThermalChangedCallback {
+ public:
+ Return<void> notifyThrottling(const Temperature& temperature) override {
+ /*
+ * ThermalCallbackArgs args;
+ * args.temperature = temperature;
+ * NotifyFromCallback(kCallbackNameNotifyThrottling, args);
+ */
+ std::cout << "---------- Thermal throttling notification ----------" << std::endl;
+
+ std::cout << "name=" << temperature.name
+ << ", type=" << toString(temperature.type)
+ << ", value=" << temperature.value
+ << ", throttling severity=" << toString(temperature.throttlingStatus)
+ << std::endl;
+
+ return Void();
+ }
+};
+
+static int getTemperatureThresholds(sp<IThermal> thermal)
+{
+ hidl_vec<TemperatureThreshold> thermal_thresholds;
+ ThermalStatus thermal_status;
+ Return<void> ret;
+
+ ret = thermal->getTemperatureThresholds(false, TemperatureType::UNKNOWN,
+ [&](ThermalStatus status, hidl_vec<TemperatureThreshold> thresholds) {
+ thermal_status = status;
+ thermal_thresholds = thresholds;
+ });
+
+ std::cout << "---------- Thresholds ----------" << std::endl;
+
+ if (ret.isOk() && thermal_status.code == ThermalStatusCode::SUCCESS) {
+ for (auto threshold : thermal_thresholds) {
+ std::cout << "name=" << threshold.name
+ << ", type=" << toString(threshold.type)
+ << ", hot throttling=" << toString(threshold.hotThrottlingThresholds)
+ << ", cold throttling=" << toString(threshold.coldThrottlingThresholds)
+ << ", VR throttling=" << android::hardware::toString(threshold.vrThrottlingThreshold)
+ << std::endl;
+ }
+ }
+
+ return 0;
+}
+
+static int getCurrentTemperature(sp<IThermal> thermal)
+{
+ hidl_vec<Temperature> thermal_temperatures;
+ ThermalStatus thermal_status;
+ Return<void> ret;
+
+ ret = thermal->getCurrentTemperatures(false, TemperatureType::UNKNOWN,
+ [&](ThermalStatus status, hidl_vec<Temperature> temperatures) {
+ thermal_status = status;
+ thermal_temperatures = temperatures;
+ });
+
+ std::cout << "---------- Temperatures ----------" << std::endl;
+
+ if (ret.isOk() && thermal_status.code == ThermalStatusCode::SUCCESS) {
+ for (auto temperature : thermal_temperatures) {
+ std::cout << "name=" << temperature.name
+ << ", type=" << toString(temperature.type)
+ << ", value=" << temperature.value
+ << ", throttling severity=" << toString(temperature.throttlingStatus)
+ << std::endl;
+ }
+ }
+
+ return 0;
+}
+
+static int getCurrentCoolingDevices(sp<IThermal> thermal)
+{
+ hidl_vec<CoolingDevice> thermal_coolingdevices;
+ ThermalStatus thermal_status;
+ Return<void> ret;
+
+ ret = thermal->getCurrentCoolingDevices(false, CoolingType::CPU,
+ [&](ThermalStatus status, hidl_vec<CoolingDevice> coolingdevices) {
+ thermal_status = status;
+ thermal_coolingdevices = coolingdevices;
+ });
+
+ std::cout << "---------- Cooling types ----------" << std::endl;
+
+ if (ret.isOk() && thermal_status.code == ThermalStatusCode::SUCCESS) {
+ for (auto coolingdevice : thermal_coolingdevices) {
+ std::cout << "name=" << coolingdevice.name
+ << ", type=" << toString(coolingdevice.type)
+ << ", value=" << coolingdevice.value
+ << std::endl;
+ }
+ }
+
+ return 0;
+}
+
+static ThermalCallback *registerThermalChangedCallback(sp<IThermal> thermal)
+{
+ Return<void> ret;
+ ThermalStatus thermal_status;
+ ThermalCallback *thermalCallback;
+
+ std::cout << "Setting thermal change callback" << std::endl;
+
+ thermalCallback = new(ThermalCallback);
+ if (!thermalCallback)
+ return NULL;
+
+ ret = thermal->registerThermalChangedCallback(thermalCallback, false, TemperatureType::UNKNOWN,
+ [&](ThermalStatus status) { thermal_status = status; });
+ if (!ret.isOk() || (thermal_status.code != ThermalStatusCode::SUCCESS)) {
+ std::cout << "Failed to register thermal change callback" << std::endl;
+ return NULL;
+ }
+
+ return thermalCallback;
+}
+
+static int unregisterThermalChangedCallback(sp<IThermal> thermal, ThermalCallback *thermalCallback)
+{
+ Return<void> ret;
+ ThermalStatus thermal_status;
+
+ ret = thermal->unregisterThermalChangedCallback(thermalCallback,
+ [&](ThermalStatus status) { thermal_status = status; });
+ if (!ret.isOk() || (thermal_status.code != ThermalStatusCode::SUCCESS)) {
+ std::cout << "Failed to unregister thermal change callback" << std::endl;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int transportCallback(int fd, __attribute__((unused)) int event,
+ __attribute__((unused)) void *data)
+{
+ handleTransportPoll(fd);
+ return 1;
+}
+
+static int setupTransportCallback(Looper *looper)
+{
+ int fd;
+
+ fd = setupTransportPolling();
+ if (fd < 0)
+ return -1;
+
+ if (!looper->addFd(fd, 0, Looper::EVENT_INPUT, transportCallback, NULL))
+ return -1;
+
+ return 0;
+}
+
+int skinTemperatureCallback(int fd, __attribute__((unused)) int event, void *data)
+{
+ sp<IThermal> *thermal = (typeof(thermal))data;
+ hidl_vec<Temperature> thermal_temperatures;
+ ThermalStatus thermal_status;
+ Return<void> ret;
+ uint64_t expirations;
+
+ if (read(fd, &expirations, sizeof(expirations)) < 0) {
+ std::cout << "Failed to read signal data" << std::endl;
+ return 0;
+ }
+
+ ret = (*thermal)->getCurrentTemperatures(true, TemperatureType::SKIN,
+ [&](ThermalStatus status, hidl_vec<Temperature> temperatures) {
+ thermal_status = status;
+ thermal_temperatures = temperatures;
+ });
+
+ std::cout << "---------- Skin temperatures ----------" << std::endl;
+
+ if (ret.isOk() && thermal_status.code == ThermalStatusCode::SUCCESS) {
+ for (auto temperature : thermal_temperatures) {
+ std::cout << "name=" << temperature.name
+ << ", type=" << toString(temperature.type)
+ << ", value=" << temperature.value
+ << ", throttling severity=" << toString(temperature.throttlingStatus)
+ << std::endl;
+ }
+ }
+
+ return 1;
+}
+
+static int setupSkinTempMonitoring(Looper *looper, sp<IThermal> *thermal)
+{
+ struct itimerspec iti = {
+ .it_interval = { SKIN_POLLING_SEC, 0 },
+ .it_value = { SKIN_POLLING_SEC, 0 },
+ };
+
+ int timerfd;
+
+ timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
+ if (timerfd < 0) {
+ std::cout << "Failed to create timer" << std::endl;
+ return 1;
+ }
+
+ if (timerfd_settime(timerfd, 0, &iti, NULL) < 0) {
+ std::cout << "Failed to set timer time" << std::endl;
+ return 1;
+ }
+
+ if (!looper->addFd(timerfd, 0, Looper::EVENT_INPUT, skinTemperatureCallback, thermal)) {
+ std::cout << "Failed to add skin temperature callback to mainloop" << std::endl;
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int, char *[])
+{
+ ThermalCallback *thermalCallback;
+ class Looper *looper;
+
+ sp<IThermal> thermal;
+
+ looper = new Looper(false);
+ if (!looper)
+ return 1;
+
+ thermal = IThermal::getService("default");
+ if (!thermal) {
+ std::cout << "Failed to get thermal service" << std::endl;
+ return 1;
+ }
+
+ if (setupTransportCallback(looper)) {
+ std::cout << "Failed to setup transport callback" << std::endl;
+ return 1;
+ }
+
+ if (setupSkinTempMonitoring(looper, &thermal)) {
+ std::cout << "Failed to setup skin monitoring" << std::endl;
+ return 1;
+ }
+
+ if (getCurrentTemperature(thermal)) {
+ std::cout << "Failed to get temperatures" << std::endl;
+ return 1;
+ }
+
+ if (getTemperatureThresholds(thermal)) {
+ std::cout << "Failed to get thresholds" << std::endl;
+ return 1;
+ }
+
+ if (getCurrentCoolingDevices(thermal)) {
+ std::cout << "Failed to get cooling devices" << std::endl;
+ return 1;
+ }
+
+ thermalCallback = registerThermalChangedCallback(thermal);
+ if (!thermalCallback) {
+ std::cout << "Failed to register changed callback" << std::endl;
+ return 1;
+ }
+
+ std::cout << "Successfully registered thermal change callback" << std::endl;
+
+ std::cout << "Entering mainloop..." << std::endl;
+
+ looper->pollAll(-1);
+
+ std::cout << "Exiting mainloop..." << std::endl;
+
+ if (unregisterThermalChangedCallback(thermal, thermalCallback)) {
+ std::cout << "Failed to unregister changed callback" << std::endl;
+ return 1;
+ }
+ std::cout << "Successfully unregistered thermal change callback" << std::endl;
+
+ return 0;
+}