diff options
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; +} |