From 66603c49bbe05979a1e2320e83a747e48fbb50c4 Mon Sep 17 00:00:00 2001 From: bong-water-water-bong Date: Sat, 24 Oct 2026 04:47:12 -0300 Subject: [PATCH 1/3] os: fans and power profile inside the image, on the boards we know MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The appliance had no fan or power control at all: on this board class the firmware's own fan curve is conservative, and without a driver for the board's embedded controller the image shows no fan readings and cannot set the EC power mode or the SMU limits. This brings all of it to the boards we know and writes nothing at all anywhere else. - os/mini/board.sh (installed as /usr/sbin/1bit-board): the EC fan curve, a predictive boost loop (0.5 s poll on package power, GPU busy, Tctl and its rise; level 4 on load, back to the curve after 20 s quiet, level 5 only above 82 C) and the power profile (EC power mode first, then the SMU limits through ryzenadj when the image carries it, then the fabric/SoC clock pins), with overrides read from /etc/1bit/power. Driver only: no debug-register access, ever. - os/mini/mkimage.sh: builds ec_su_axb35 for the image kernel from the source this repository ships (os/licenses/ec_su_axb35, GPL-2.0, verbatim upstream commit f62c2c22), installs the module plus its source and licence into the image, installs board.sh and the /etc/1bit/power template, and ships ryzenadj (LGPL-3.0, licence included) when the build machine has it. - os/mini/init.sh: on a board we know, load the EC driver, apply the curve and the power profile and start the boost loop; report fans, power profile and the two temperatures in the boot banner and in the 1bit.check self-test. On any other board, one line saying the firmware curve stays. - os/mini/release-check.sh: a fans row - PASS with the fan rpms at level 4, SKIP when this board has no EC we drive, FAIL when the driver loaded but a fan still reads 0 rpm at level 4 for 5 s (the fault mode we have seen on real hardware). - os/LICENSES.md: the board driver (GPL-2.0, source shipped) and the SMU tool (LGPL-3.0). Image boot on hardware pending: the module build and the VM boot test run once the build lane opens (EC_MODULE=skip builds an image without the driver). No behaviour change on machines without this board. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: bong-water-water-bong --- os/LICENSES.md | 13 + os/licenses/ec_su_axb35/LICENSE | 339 +++++++++ os/licenses/ec_su_axb35/SOURCE | 11 + os/licenses/ec_su_axb35/src/.clang-format | 119 ++++ os/licenses/ec_su_axb35/src/.clangd | 3 + os/licenses/ec_su_axb35/src/.gitignore | 56 ++ os/licenses/ec_su_axb35/src/Kbuild | 2 + os/licenses/ec_su_axb35/src/LICENSE | 339 +++++++++ os/licenses/ec_su_axb35/src/Makefile | 45 ++ os/licenses/ec_su_axb35/src/README.md | 51 ++ .../ec_su_axb35/src/contrib/netdata/README.md | 7 + .../src/contrib/netdata/axb35.chart.sh | 111 +++ .../src/contrib/ubuntu/01_setup_dkms_mok.sh | 72 ++ .../ubuntu/02_install_ec_axb35_dkms.sh | 120 ++++ os/licenses/ec_su_axb35/src/hwmon/Makefile | 10 + os/licenses/ec_su_axb35/src/hwmon/README.md | 11 + .../ec_su_axb35/src/hwmon/ec-su_axb35-hwmon.c | 200 ++++++ .../src/python-gui/ec-fan-control.desktop | 8 + .../src/python-gui/ec-su_axb35-linux-gui.py | 669 ++++++++++++++++++ os/licenses/ec_su_axb35/src/scripts/info.sh | 24 + .../ec_su_axb35/src/scripts/su_axb35_monitor | 571 +++++++++++++++ .../src/scripts/test_fan_mode_fixed.sh | 54 ++ os/licenses/ec_su_axb35/src/src/ec_su_axb35.c | 660 +++++++++++++++++ os/mini/board.sh | 233 ++++++ os/mini/init.sh | 27 + os/mini/mkimage.sh | 44 ++ os/mini/power.conf.example | 22 + os/mini/release-check.sh | 28 + 28 files changed, 3849 insertions(+) create mode 100644 os/licenses/ec_su_axb35/LICENSE create mode 100644 os/licenses/ec_su_axb35/SOURCE create mode 100644 os/licenses/ec_su_axb35/src/.clang-format create mode 100644 os/licenses/ec_su_axb35/src/.clangd create mode 100644 os/licenses/ec_su_axb35/src/.gitignore create mode 100644 os/licenses/ec_su_axb35/src/Kbuild create mode 100644 os/licenses/ec_su_axb35/src/LICENSE create mode 100644 os/licenses/ec_su_axb35/src/Makefile create mode 100644 os/licenses/ec_su_axb35/src/README.md create mode 100644 os/licenses/ec_su_axb35/src/contrib/netdata/README.md create mode 100755 os/licenses/ec_su_axb35/src/contrib/netdata/axb35.chart.sh create mode 100755 os/licenses/ec_su_axb35/src/contrib/ubuntu/01_setup_dkms_mok.sh create mode 100755 os/licenses/ec_su_axb35/src/contrib/ubuntu/02_install_ec_axb35_dkms.sh create mode 100644 os/licenses/ec_su_axb35/src/hwmon/Makefile create mode 100644 os/licenses/ec_su_axb35/src/hwmon/README.md create mode 100644 os/licenses/ec_su_axb35/src/hwmon/ec-su_axb35-hwmon.c create mode 100644 os/licenses/ec_su_axb35/src/python-gui/ec-fan-control.desktop create mode 100644 os/licenses/ec_su_axb35/src/python-gui/ec-su_axb35-linux-gui.py create mode 100755 os/licenses/ec_su_axb35/src/scripts/info.sh create mode 100755 os/licenses/ec_su_axb35/src/scripts/su_axb35_monitor create mode 100755 os/licenses/ec_su_axb35/src/scripts/test_fan_mode_fixed.sh create mode 100644 os/licenses/ec_su_axb35/src/src/ec_su_axb35.c create mode 100755 os/mini/board.sh create mode 100644 os/mini/power.conf.example diff --git a/os/LICENSES.md b/os/LICENSES.md index 19959432..595dcfaf 100644 --- a/os/LICENSES.md +++ b/os/LICENSES.md @@ -86,6 +86,17 @@ says `Ship: yes`. | Lemonade (lemond, the lemonade CLI, the web app) | third_party/lemonade pin | Apache-2.0, Advanced Micro Devices, Inc.; web app bundle licenses in `renderer.bundle.js.LICENSE.txt` | https://github.com/1bit-MONSTER/lemonade | | ROCr HSA runtime (TheRock, for HRX) | the build's ONEBIT_HRX_LIBHSA | University of Illinois/NCSA | https://github.com/ROCm/ROCR-Runtime, https://github.com/ROCm/TheRock | +## The board driver and the SMU tool + +The appliance controls its fans and its power profile on the boards it knows: an out-of-tree driver for +the board's embedded controller, and (when the build machine has it) the tool that sets the SMU power +limits. Both are shipped with their licences, and the driver also with its source. + +| Component | Release | License | Source | +|---|---|---|---| +| `ec_su_axb35` (fan curve, EC temperature, EC power mode on the AXB35-02 board) | commit `f62c2c228959a08683273a26ef3afd8991e69f6d`, built for the image kernel by `os/mini/mkimage.sh` | GPL-2.0-only | `os/licenses/ec_su_axb35/` in this repository; the same source is installed in the image at `/usr/share/licenses/1bit-os/ec_su_axb35/src`; upstream https://github.com/cmetz/ec-su_axb35-linux | +| `ryzenadj` (sets the SMU power limits; `/opt/1bit/ryzenadj` in the image) | built on the build machine, copied unmodified | LGPL-3.0, Copyright Jiaxun Yang and the RyzenAdj contributors | https://github.com/FlyGoat/RyzenAdj; the LGPL-3.0 text ships as `/usr/share/licenses/1bit-os/ryzenadj-LGPL-3` | + ## System software from Ubuntu mkimage.sh copies these from the build machine (Ubuntu 26.04), unmodified, and records the exact @@ -123,4 +134,6 @@ source available; the MPL requires the same for its covered files. For each: | eSpeak NG in kokoro's `koko`, and Kokoros | https://crates.io/api/v1/crates/espeak-rs-sys/0.1.9/download (eSpeak NG as built into koko); https://github.com/lemonade-sdk/Kokoros/tree/b17 | | TheRock sysdeps in llamacpp-hrx (elfutils, libnl, numactl, libmnl) | the upstream tarballs TheRock builds, https://github.com/ROCm/TheRock/tree/main/third-party/sysdeps/linux (elfutils 0.195, libnl 3.12.0, numactl 2.0.19, libmnl 1.0.5 at the time of this review) | | libquadmath, libgfortran in moonshine | GCC, https://gcc.gnu.org | +| `ec_su_axb35` (the board driver in the image) | the source as shipped in `os/licenses/ec_su_axb35/src`, upstream https://github.com/cmetz/ec-su_axb35-linux at commit `f62c2c228959a08683273a26ef3afd8991e69f6d` | +| `ryzenadj` | https://github.com/FlyGoat/RyzenAdj at the release the build machine carries (LGPL-3.0) | | certifi, tqdm in moonshine (MPL-2.0) | the covered files are on the image as shipped (Python source and cacert.pem); https://pypi.org/project/certifi/, https://pypi.org/project/tqdm/ | diff --git a/os/licenses/ec_su_axb35/LICENSE b/os/licenses/ec_su_axb35/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/os/licenses/ec_su_axb35/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 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. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/os/licenses/ec_su_axb35/SOURCE b/os/licenses/ec_su_axb35/SOURCE new file mode 100644 index 00000000..e8686074 --- /dev/null +++ b/os/licenses/ec_su_axb35/SOURCE @@ -0,0 +1,11 @@ +Board driver: ec_su_axb35 (Sixunited AXB35-02 embedded controller: fans, EC temperature, EC power mode) +Ship: yes +Project: ec-su_axb35-linux, https://github.com/cmetz/ec-su_axb35-linux +Release: commit f62c2c228959a08683273a26ef3afd8991e69f6d ("Reset all fans to AUTO mode before unloading module (#31)") +License: GPL-2.0 (LICENSE, shipped here); the module source in src/ is that commit, verbatim, and it is +also installed into the image under /usr/share/licenses/1bit-os/ec_su_axb35/src, so the binary in the +image always travels with the source it was built from. +Build: out of tree against the image kernel, by os/mini/mkimage.sh: + make -C KERNEL_BUILD=/lib/modules//build modules +Why it ships: the kernel has no driver for this EC, so without it the appliance has no fan control and no +EC power mode at all - the fans would sit on the firmware curve. diff --git a/os/licenses/ec_su_axb35/src/.clang-format b/os/licenses/ec_su_axb35/src/.clang-format new file mode 100644 index 00000000..01572da8 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/.clang-format @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 11. +# +# For more information, see: +# +# Documentation/dev-tools/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 4 +UseTab: Never +... diff --git a/os/licenses/ec_su_axb35/src/.clangd b/os/licenses/ec_su_axb35/src/.clangd new file mode 100644 index 00000000..99df3955 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/.clangd @@ -0,0 +1,3 @@ +CompileFlags: + Add: -Wno-unknown-warning-option + Remove: [-m*, -f*] \ No newline at end of file diff --git a/os/licenses/ec_su_axb35/src/.gitignore b/os/licenses/ec_su_axb35/src/.gitignore new file mode 100644 index 00000000..c69f359f --- /dev/null +++ b/os/licenses/ec_su_axb35/src/.gitignore @@ -0,0 +1,56 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf +.direnv +.cache +compile_commands.json +.envrc diff --git a/os/licenses/ec_su_axb35/src/Kbuild b/os/licenses/ec_su_axb35/src/Kbuild new file mode 100644 index 00000000..83b275aa --- /dev/null +++ b/os/licenses/ec_su_axb35/src/Kbuild @@ -0,0 +1,2 @@ +obj-m += ec_su_axb35.o +ec_su_axb35-y := src/ec_su_axb35.o diff --git a/os/licenses/ec_su_axb35/src/LICENSE b/os/licenses/ec_su_axb35/src/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/os/licenses/ec_su_axb35/src/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 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. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/os/licenses/ec_su_axb35/src/Makefile b/os/licenses/ec_su_axb35/src/Makefile new file mode 100644 index 00000000..da7e717c --- /dev/null +++ b/os/licenses/ec_su_axb35/src/Makefile @@ -0,0 +1,45 @@ +# Makefile for ec_su_axb35 + +KERNEL_BUILD ?= /lib/modules/$(shell uname -r)/build +MODULE_NAME := ec_su_axb35.ko +MODULE_INSTALLED_PATH := $(shell modinfo -n $(basename $(MODULE_NAME)) 2>/dev/null) +PWD := $(CURDIR) + +.PHONY: default +default: modules + +.PHONY: modules +modules: + $(MAKE) -C $(KERNEL_BUILD) M=$(PWD) modules + +.PHONY: modules_install +modules_install: modules + $(MAKE) -C $(KERNEL_BUILD) M=$(PWD) modules_install + +.PHONY: clean +clean: + $(MAKE) -C $(KERNEL_BUILD) M=$(PWD) clean + rm -rf $(BUILD_DIR) + +.PHONY: install +install: modules_install + depmod + @echo "Installing su_axb35_monitor script to /usr/local/bin/" + install -m 0755 scripts/su_axb35_monitor /usr/local/bin/ + +.PHONY: uninstall +uninstall: + @if [ -n "$(MODULE_INSTALLED_PATH)" ]; then \ + echo "Removing $(MODULE_INSTALLED_PATH)"; \ + rm -v "$(MODULE_INSTALLED_PATH)"; \ + depmod; \ + else \ + echo "Module not found."; \ + fi + @if [ -f "/usr/local/bin/su_axb35_monitor" ]; then \ + echo "Removing /usr/local/bin/su_axb35_monitor"; \ + rm -v "/usr/local/bin/su_axb35_monitor"; \ + else \ + echo "Monitor script not found."; \ + fi + diff --git a/os/licenses/ec_su_axb35/src/README.md b/os/licenses/ec_su_axb35/src/README.md new file mode 100644 index 00000000..e2d8589a --- /dev/null +++ b/os/licenses/ec_su_axb35/src/README.md @@ -0,0 +1,51 @@ +# ec-su_axb35-linux +Linux driver for the embedded controller on the Sixunited AXB35-02 board. + +Vendors using that board: + - GMKtec EVO-X2 + - Bosgame M5 + - FEVM FA-EX9 + - Peladn YO1 + - NIMO AI MiniPC + - Corsair AI Workstation 300 + +An update to date list can be found [here](https://strixhalo-homelab.d7.wtf/Hardware/Boards/Sixunited-AXB35) + +For more details, please have a look at the desevens wiki: https://strixhalo-homelab.d7.wtf/Guides/Power-Mode-and-Fan-Control + +# Build instructions +``` +$ make +$ sudo make install +$ sudo insmod ec_su_axb35 +``` + +# Devices +``` +# Fan devices +/sys/class/ec_su_axb35/fan1/ - CPU fan 1 +/sys/class/ec_su_axb35/fan2/ - CPU fan 2 +/sys/class/ec_su_axb35/fan3/ - System fan +/sys/class/ec_su_axb35/fanX/rpm (RO) - current speed in rpm +/sys/class/ec_su_axb35/fanX/mode (RW) - [auto, fixed, curve] +/sys/class/ec_su_axb35/fanX/level (RW) - [0-5] (0=0%, 1=20%, ..., 5=100%) +/sys/class/ec_su_axb35/fanX/rampup_curve (RW) - 5 values (°C thresholds for level 1-5) +/sys/class/ec_su_axb35/fanX/rampdown_curve (RW) - 5 values (°C thresholds for level 1-5) + +# Temperature device +/sys/class/ec_su_axb35/temp1/ - CPU temperature in °C +/sys/class/ec_su_axb35/temp1/temp (RO) - current +/sys/class/ec_su_axb35/temp1/min (RO) - min temp measured since dirver load +/sys/class/ec_su_axb35/temp1/max (RO) - amx temp measured since driver load + +# APU device +/sys/class/ec_su_axb35/apu/power_mode (RW) - [quiet, balanced, performance] +``` + +# Python GUI app (needs root to write to /sys/class/ec_su_axb35/*) +to test: +python ./ec-su_axb35-linux-gui.py + +to install: +sudo install -m 755 ec-su_axb35-linux-gui.py /usr/local/bin/ec-su_axb35-linux-gui +cp ec-fan-control.desktop ~/.local/share/applications/ diff --git a/os/licenses/ec_su_axb35/src/contrib/netdata/README.md b/os/licenses/ec_su_axb35/src/contrib/netdata/README.md new file mode 100644 index 00000000..2b2e48c0 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/contrib/netdata/README.md @@ -0,0 +1,7 @@ +# Netdata Plugin +This plugin adds all key metrics from the module to your dashboard. + +### Installation & Usage +1. Copy `axb35.chart.sh` to `/usr/libexec/netdata/charts.d`. +2. Restart your Netdata service (`systemctl restart netdata`). +3. New section called `axb35` should appear on your dashboard. \ No newline at end of file diff --git a/os/licenses/ec_su_axb35/src/contrib/netdata/axb35.chart.sh b/os/licenses/ec_su_axb35/src/contrib/netdata/axb35.chart.sh new file mode 100755 index 00000000..132e8e8d --- /dev/null +++ b/os/licenses/ec_su_axb35/src/contrib/netdata/axb35.chart.sh @@ -0,0 +1,111 @@ +# params +axb35_update_every=1 +axb35_priority=1 +axb35_path=/sys/class/ec_su_axb35 +declare -a axb35_vars + +# main metrics function +axb35_get() { + if [ ! -d $axb35_path ]; then + return 1 + fi + + # read all values from the module + mapfile -t axb35_vars < <( + cat \ + $axb35_path/fan{1..3}/rpm \ + $axb35_path/fan{1..3}/mode \ + $axb35_path/fan{1..3}/level \ + $axb35_path/temp1/{temp,min,max} \ + $axb35_path/apu/power_mode + ) + + # convert fan modes to numeric values + for i in {3..5}; do + case "${axb35_vars[i]}" in + "fixed") axb35_vars[i]=3 ;; + "curve") axb35_vars[i]=2 ;; + "auto") + axb35_vars[i]=1 + axb35_vars[(($i+3))]=6 + ;; + *) axb35_vars[i]=0 ;; # unknown mode + esac + done + + # convert power mode to numeric value + case "${axb35_vars[12]}" in + "performance") axb35_vars[12]=3 ;; + "balanced") axb35_vars[12]=2 ;; + "quiet") axb35_vars[12]=1 ;; + *) axb35_vars[12]=0 ;; # unknown mode + esac + + return 0 +} + +# called once to find out if this chart should be enabled or not +axb35_check() { + [ ! -d $axb35_path ] && error "can't find AXB35 class in $axb35_path" && return 1 + return 0 +} + +# called once to create the charts +axb35_create() { + cat << EOF +CHART axb35.cputemp 'axb351' "CPU Temp" "Degrees" "CPU Temperature" '' line $((axb35_priority)) $axb35_update_every '' '' 'axb35' +DIMENSION cputemp '' absolute 1 1 +DIMENSION cputempmin '' absolute 1 1 +DIMENSION cputempmax '' absolute 1 1 +CHART axb35.fanrpm 'axb352' "Fan RPMs" "rpm" "Fan RPMs" '' stacked $((axb35_priority+1)) $axb35_update_every '' '' 'axb35' +DIMENSION fan1rpm '' absolute 1 1 +DIMENSION fan2rpm '' absolute 1 1 +DIMENSION fan3rpm '' absolute 1 1 +CHART axb35.fanmode 'axb353' "Fan Modes (1 = Auto, 2 = Curve, 3 = Manual)" "Mode" "Fan Modes" '' line $((axb35_priority+2)) $axb35_update_every '' '' 'axb35' +DIMENSION fan1mode '' absolute 1 1 +DIMENSION fan2mode '' absolute 1 1 +DIMENSION fan3mode '' absolute 1 1 +CHART axb35.fanlevel 'axb354' "Fan Power Levels (6 = Auto)" "Level" "Fan Power Levels" '' line $((axb35_priority+3)) $axb35_update_every '' '' 'axb35' +DIMENSION fan1level '' absolute 1 1 +DIMENSION fan2level '' absolute 1 1 +DIMENSION fan3level '' absolute 1 1 +CHART axb35.powermode 'axb355' "Power Mode (1 = Quiet, 2 = Balanced, 3 = Performance)" "Mode" "Power Modes" '' line $((axb35_priority+4)) $axb35_update_every '' '' 'axb35' +DIMENSION powermode '' absolute 1 1 +EOF + + return 0 +} + +# called continuously to collect the values +axb35_update() { + axb35_get || return 1 + + cat << VALUESEOF +BEGIN axb35.fanrpm $1 +SET fan1rpm = ${axb35_vars[0]} +SET fan2rpm = ${axb35_vars[1]} +SET fan3rpm = ${axb35_vars[2]} +END +BEGIN axb35.fanmode $1 +SET fan1mode = ${axb35_vars[3]} +SET fan2mode = ${axb35_vars[4]} +SET fan3mode = ${axb35_vars[5]} +END +BEGIN axb35.fanlevel $1 +SET fan1level = ${axb35_vars[6]} +SET fan2level = ${axb35_vars[7]} +SET fan3level = ${axb35_vars[8]} +END +BEGIN axb35.cputemp $1 +SET cputemp = ${axb35_vars[9]} +SET cputempmin = ${axb35_vars[10]} +SET cputempmax = ${axb35_vars[11]} +END +BEGIN axb35.powermode $1 +SET powermode = ${axb35_vars[12]} +END + +VALUESEOF + + return 0 +} diff --git a/os/licenses/ec_su_axb35/src/contrib/ubuntu/01_setup_dkms_mok.sh b/os/licenses/ec_su_axb35/src/contrib/ubuntu/01_setup_dkms_mok.sh new file mode 100755 index 00000000..3ca82873 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/contrib/ubuntu/01_setup_dkms_mok.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# 01_setup_dkms_mok.sh +# Sets up DKMS and enrolls a MOK key for secure boot module signing. +# Run this first, then reboot and complete MOK enrollment before running script 2. +# Tested under Ubuntu 26.04 + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } + +MOK_KEY="/var/lib/shim-signed/mok/MOK.priv" +MOK_CERT="/var/lib/shim-signed/mok/MOK.der" + +[[ $EUID -ne 0 ]] && error "Run as root (sudo $0)" + +# Check secure boot state +if ! mokutil --sb-state 2>/dev/null | grep -q "SecureBoot enabled"; then + warn "Secure boot does not appear to be enabled." + read -rp "Continue anyway? [y/N] " confirm + [[ "$confirm" =~ ^[Yy]$ ]] || exit 0 +fi + +# Check if MOK already exists +if [[ -f "$MOK_KEY" && -f "$MOK_CERT" ]]; then + info "MOK key pair already exists at ${MOK_KEY} and ${MOK_CERT}" + info "Skipping key generation." +else + info "Installing dkms and openssl..." + apt-get install -y dkms openssl + + info "Generating DKMS MOK key pair..." + dkms generate_mok + info "MOK key pair created at ${MOK_KEY} and ${MOK_CERT}" +fi + +# Enroll the key +info "Enrolling MOK public key..." +echo "" +warn "You will be prompted to set a password for MOK enrollment." +warn "Password MUST be 5 characters or fewer (UEFI keyboard limitation)." +warn "You will need this password at the next boot screen." +echo "" + +mokutil --import "$MOK_CERT" + +info "MOK enrollment request submitted." +echo "" +echo -e "${YELLOW}========================================${NC}" +echo -e "${YELLOW} REBOOT REQUIRED${NC}" +echo -e "${YELLOW}========================================${NC}" +echo "" +echo "At the blue MOK Manager screen:" +echo " 1. Press any key" +echo " 2. Select 'Enroll MOK'" +echo " 3. Select 'Continue'" +echo " 4. Select 'Yes'" +echo " 5. Enter the password you just set" +echo " 6. Select 'Reboot'" +echo "" +echo "After reboot, run: sudo bash contrib/ubuntu/02_install_ec_axb35_dkms.sh" +echo "" +read -rp "Reboot now? [y/N] " doreboot +if [[ "$doreboot" =~ ^[Yy]$ ]]; then + systemctl reboot +fi diff --git a/os/licenses/ec_su_axb35/src/contrib/ubuntu/02_install_ec_axb35_dkms.sh b/os/licenses/ec_su_axb35/src/contrib/ubuntu/02_install_ec_axb35_dkms.sh new file mode 100755 index 00000000..0c5d0975 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/contrib/ubuntu/02_install_ec_axb35_dkms.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# 02_install_ec_axb35_dkms.sh +# Installs the ec_su_axb35 kernel module via DKMS with MOK signing. +# Must be run from the contrib/ubuntu/ directory inside the cloned ec-su_axb35-linux repo. +# Requires 01_setup_dkms_mok.sh to have been run and MOK enrolled at boot. +# Tested under Ubuntu 26.04 + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } + +MOK_KEY="/var/lib/shim-signed/mok/MOK.priv" +MOK_CERT="/var/lib/shim-signed/mok/MOK.der" + +[[ $EUID -ne 0 ]] && error "Run as root (sudo $0)" + +# Resolve repo root from script location +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && cd .. && pwd)" + +# Sanity check we're in the right repo +[[ -f "${REPO_ROOT}/Makefile" && -f "${REPO_ROOT}/Kbuild" ]] \ + || error "Could not find Makefile/Kbuild at ${REPO_ROOT}. Is this script in the repo's scripts/ dir?" + +MODULE_NAME="ec_su_axb35" +MODULE_VERSION="1.0" +DKMS_SRC="/usr/src/${MODULE_NAME}-${MODULE_VERSION}" + +# Verify MOK keys exist +[[ -f "$MOK_KEY" && -f "$MOK_CERT" ]] \ + || error "MOK keys not found at ${MOK_KEY} / ${MOK_CERT}. Run 01_setup_dkms_mok.sh first." + +# Verify MOK is enrolled +if mokutil --sb-state 2>/dev/null | grep -q "SecureBoot enabled"; then + mokutil --list-enrolled 2>/dev/null | grep "Secure Boot Module Signature key" + if !mokutil --test-key "$MOK_CERT" 2> /dev/null | grep -q "is already enrolled"; then + warn "MOK key doesn't appear to be enrolled yet." + warn "Did you complete the MOK enrollment at the boot screen?" + read -rp "Continue anyway? [y/N] " confirm + [[ "$confirm" =~ ^[Yy]$ ]] || exit 0 + else + info "MOK key confirmed enrolled." + fi +fi + +# Install build deps +info "Installing build dependencies..." +apt-get install -y dkms build-essential "linux-headers-$(uname -r)" + +# Remove existing DKMS entry + source if present +if dkms status "${MODULE_NAME}" 2>/dev/null | grep -q "${MODULE_NAME}"; then + warn "Removing existing DKMS entry for ${MODULE_NAME}..." + dkms remove "${MODULE_NAME}/${MODULE_VERSION}" --all 2>/dev/null || true +fi +[[ -d "$DKMS_SRC" ]] && rm -rf "$DKMS_SRC" + +# Copy repo root into DKMS source tree +info "Copying repo source to ${DKMS_SRC}..." +cp -r "${REPO_ROOT}" "${DKMS_SRC}" + +# Write dkms.conf into the DKMS source tree +info "Writing dkms.conf..." +cat > "${DKMS_SRC}/dkms.conf" << EOF +PACKAGE_NAME="${MODULE_NAME}" +PACKAGE_VERSION="${MODULE_VERSION}" +BUILT_MODULE_NAME[0]="${MODULE_NAME}" +DEST_MODULE_LOCATION[0]="/updates" +AUTOINSTALL="yes" +POST_BUILD="sign-file sha256 ${MOK_KEY} ${MOK_CERT} \${dkms_tree}/\${PACKAGE_NAME}/\${PACKAGE_VERSION}/\${kernelver}/\${arch}/module/\${BUILT_MODULE_NAME[0]}.ko" +EOF + +# DKMS add / build / install +info "Adding module to DKMS..." +dkms add "${MODULE_NAME}/${MODULE_VERSION}" + +info "Building module..." +dkms build "${MODULE_NAME}/${MODULE_VERSION}" + +info "Installing module..." +dkms install "${MODULE_NAME}/${MODULE_VERSION}" + +info "DKMS status:" +dkms status "${MODULE_NAME}" + +# Load module +info "Loading module..." +modprobe "${MODULE_NAME}" + +if dmesg | grep -q "Sixunited AXB35-02 EC driver loaded"; then + info "Module loaded successfully." +else + warn "Module loaded but expected dmesg message not found. Check: dmesg | grep -i axb35" +fi + +# Auto-load on boot +if grep -q "^${MODULE_NAME}$" /etc/modules 2>/dev/null; then + info "${MODULE_NAME} already in /etc/modules, skipping." +else + info "Adding ${MODULE_NAME} to /etc/modules..." + echo "${MODULE_NAME}" >> /etc/modules +fi + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN} Done!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo "Read power mode: cat /sys/class/ec_su_axb35/apu/power_mode" +echo "Set power mode: echo performance > /sys/class/ec_su_axb35/apu/power_mode" +echo " echo balanced > /sys/class/ec_su_axb35/apu/power_mode" +echo " echo quiet > /sys/class/ec_su_axb35/apu/power_mode" +echo "Live monitor: su_axb35_monitor" +echo "" diff --git a/os/licenses/ec_su_axb35/src/hwmon/Makefile b/os/licenses/ec_su_axb35/src/hwmon/Makefile new file mode 100644 index 00000000..ad5fa4e8 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/hwmon/Makefile @@ -0,0 +1,10 @@ +obj-m += su_axb35_hwmon.o + +KDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean diff --git a/os/licenses/ec_su_axb35/src/hwmon/README.md b/os/licenses/ec_su_axb35/src/hwmon/README.md new file mode 100644 index 00000000..73834291 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/hwmon/README.md @@ -0,0 +1,11 @@ +# ec-su_axb35-hwmon +Linux model ko file to show fan and temp in sensors + +Vendors using that board: + - GMKTec EVO-X2 + +# Build instructions +``` +$ make +$ sudo insmod ec-su_axb35-hwmon +$ sensors diff --git a/os/licenses/ec_su_axb35/src/hwmon/ec-su_axb35-hwmon.c b/os/licenses/ec_su_axb35/src/hwmon/ec-su_axb35-hwmon.c new file mode 100644 index 00000000..a62cd5cc --- /dev/null +++ b/os/licenses/ec_su_axb35/src/hwmon/ec-su_axb35-hwmon.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static int read_fan_rpm_from_sysfs(int fan_num) +{ + char path[128]; + char buffer[32]; + struct file *filp; + loff_t pos = 0; + ssize_t bytes_read; + int rpm = 0; + + snprintf(path, sizeof(path), "/sys/class/ec_su_axb35/fan%d/rpm", fan_num + 1); + + filp = filp_open(path, O_RDONLY, 0); + if (IS_ERR(filp)) + return PTR_ERR(filp); + + bytes_read = kernel_read(filp, buffer, sizeof(buffer) - 1, &pos); + if (bytes_read > 0) { + buffer[bytes_read] = '\0'; + kstrtoint(buffer, 10, &rpm); + } + + filp_close(filp, NULL); + return rpm; +} + +static int read_temperature_from_sysfs(void) +{ + char path[] = "/sys/class/ec_su_axb35/temp1/temp"; + char buffer[32]; + struct file *filp; + loff_t pos = 0; + ssize_t bytes_read; + int temp = 0; + + filp = filp_open(path, O_RDONLY, 0); + if (IS_ERR(filp)) + return PTR_ERR(filp); + + bytes_read = kernel_read(filp, buffer, sizeof(buffer) - 1, &pos); + if (bytes_read > 0) { + buffer[bytes_read] = '\0'; + kstrtoint(buffer, 10, &temp); + } + + filp_close(filp, NULL); + return temp; +} + +static umode_t suaxb35_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) + return 0444; + break; + case hwmon_fan: + if (attr == hwmon_fan_input) + return 0444; + break; + default: + break; + } + return 0; +} + +static int suaxb35_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) { + *val = read_temperature_from_sysfs() * 1000; // Convert to millidegrees + return 0; + } + break; + case hwmon_fan: + if (attr == hwmon_fan_input && channel < 3) { + *val = read_fan_rpm_from_sysfs(channel); + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops suaxb35_hwmon_ops = { + .is_visible = suaxb35_is_visible, + .read = suaxb35_read, +}; + +static const u32 temp_config[] = { + HWMON_T_INPUT, + 0 +}; + +static const u32 fan_config[] = { + HWMON_F_INPUT, + HWMON_F_INPUT, + HWMON_F_INPUT, + 0 +}; + +static const struct hwmon_channel_info temp_info = { + .type = hwmon_temp, + .config = temp_config, +}; + +static const struct hwmon_channel_info fan_info = { + .type = hwmon_fan, + .config = fan_config, +}; + +static const struct hwmon_channel_info *suaxb35_info[] = { + &temp_info, + &fan_info, + NULL +}; + +static const struct hwmon_chip_info suaxb35_chip_info = { + .ops = &suaxb35_hwmon_ops, + .info = suaxb35_info, +}; + +static struct class *hwmon_class; +static struct device *parent_device; +static struct device *hwmon_dev; + +static int __init su_axb35_hwmon_init(void) +{ + int ret; + + // Check if source files exist + struct file *test_file = filp_open("/sys/class/ec_su_axb35/fan1/rpm", O_RDONLY, 0); + if (IS_ERR(test_file)) { + pr_err("ec_su_axb35 sysfs interface not found\n"); + return PTR_ERR(test_file); + } + filp_close(test_file, NULL); + + hwmon_class = class_create("su_axb35_hwmon"); + if (IS_ERR(hwmon_class)) { + ret = PTR_ERR(hwmon_class); + goto err; + } + + parent_device = device_create(hwmon_class, NULL, MKDEV(0, 0), NULL, "su_axb35_device"); + if (IS_ERR(parent_device)) { + ret = PTR_ERR(parent_device); + goto err_device; + } + + hwmon_dev = hwmon_device_register_with_info(parent_device, "su_axb35", + NULL, &suaxb35_chip_info, NULL); + if (IS_ERR(hwmon_dev)) { + ret = PTR_ERR(hwmon_dev); + pr_err("Failed to register hwmon device: %d\n", ret); + goto err_hwmon; + } + + pr_info("su_axb35 hwmon driver loaded - reading from existing ec_su_axb35 interface\n"); + return 0; + +err_hwmon: + device_destroy(hwmon_class, MKDEV(0, 0)); +err_device: + class_destroy(hwmon_class); +err: + return ret; +} + +static void __exit su_axb35_hwmon_exit(void) +{ + if (!IS_ERR_OR_NULL(hwmon_dev)) { + hwmon_device_unregister(hwmon_dev); + } + if (!IS_ERR_OR_NULL(parent_device)) { + device_destroy(hwmon_class, MKDEV(0, 0)); + } + if (!IS_ERR_OR_NULL(hwmon_class)) { + class_destroy(hwmon_class); + } + pr_info("su_axb35 hwmon driver unloaded\n"); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("mike"); +MODULE_DESCRIPTION("SU-AXB35 Hardware Monitoring Driver - Bridge to existing interface"); + +module_init(su_axb35_hwmon_init); +module_exit(su_axb35_hwmon_exit); diff --git a/os/licenses/ec_su_axb35/src/python-gui/ec-fan-control.desktop b/os/licenses/ec_su_axb35/src/python-gui/ec-fan-control.desktop new file mode 100644 index 00000000..aa06794d --- /dev/null +++ b/os/licenses/ec_su_axb35/src/python-gui/ec-fan-control.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=EC Fan Control +Comment=Fan and APU control for ec_su_axb35 +Exec=/usr/local/bin/ec-su_axb35-linux-gui +Icon=fan +Terminal=false +Categories=System;Settings; \ No newline at end of file diff --git a/os/licenses/ec_su_axb35/src/python-gui/ec-su_axb35-linux-gui.py b/os/licenses/ec_su_axb35/src/python-gui/ec-su_axb35-linux-gui.py new file mode 100644 index 00000000..a620b647 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/python-gui/ec-su_axb35-linux-gui.py @@ -0,0 +1,669 @@ +#!/usr/bin/env python3 +""" +Fan Control GUI for ec_su_axb35 driver +Requires appropriate permissions to read/write /sys/class/ec_su_axb35/ +""" +import os +import sys +import subprocess + +def ensure_root(): + if os.geteuid() == 0: + return # already root + + # Prevent infinite relaunch loop + if os.environ.get("EC_FAN_CONTROL_ROOT") == "1": + sys.exit("Failed to gain root privileges") + + env = os.environ.copy() + env["EC_FAN_CONTROL_ROOT"] = "1" + + cmd = [ + "pkexec", + "env", + f"DISPLAY={env.get('DISPLAY', '')}", + f"XAUTHORITY={env.get('XAUTHORITY', '')}", + sys.executable, + os.path.abspath(__file__) + ] + + subprocess.run(cmd, env=env) + sys.exit(0) + +ensure_root() + +import argparse +import json +import tkinter as tk +from tkinter import ttk, messagebox, filedialog +import threading +import queue +import os + +CONFIG_PATH = "/etc/ec-fan-control.json" +BASE_PATH = "/sys/class/ec_su_axb35" + +class FanControlGUI: + def __init__(self, root): + self.root = root + self.root.title("Fan Control - ec_su_axb35") + self.root.geometry("900x800") + + self.config_path = CONFIG_PATH + self.base_path = BASE_PATH + self.update_interval = 1.0 + self.running = True + self.stop_event = threading.Event() + self.ui_queue = queue.Queue() + self.ui_after_id = None + self.mode_check_timer = None + self.fan_mode_check_timers = {} + self.curve_write_delay = 0.4 # seconds + # (fan, curve) -> generation counter + self.curve_write_gen = {} + # (fan, curve) -> threading.Timer + self.curve_write_timers = {} + + # Create GUI + self.create_widgets() + + # Only the main thread may interact with Tk. Worker threads put UI + # callbacks on this queue, which is drained from the Tk event loop. + self.ui_after_id = self.root.after(50, self.process_ui_queue) + + # Start monitoring thread + self.monitor_thread = threading.Thread(target=self.monitor_loop, daemon=True) + self.monitor_thread.start() + + # Initial mode read + self.read_all_modes() + + def queue_ui(self, callback): + """Queue a Tk callback for execution by the main thread.""" + if self.running: + self.ui_queue.put(callback) + + def process_ui_queue(self): + """Execute callbacks queued by worker threads.""" + if not self.running: + return + + while True: + try: + callback = self.ui_queue.get_nowait() + except queue.Empty: + break + callback() + + self.ui_after_id = self.root.after(50, self.process_ui_queue) + + def read_sysfs(self, path): + """Read value from sysfs file""" + try: + with open(path, 'r') as f: + return f.read().strip() + except Exception as e: + print(f"Error reading {path}: {e}") + return None + + def write_sysfs(self, path, value): + """Write value to sysfs file""" + try: + with open(path, 'w') as f: + f.write(str(value)) + return True + except Exception as e: + self.queue_ui(lambda error=e: messagebox.showerror( + "Error", f"Failed to write to {path}: {error}" + )) + return False + + def create_widgets(self): + # Top frame - Temperature and Fan RPMs + top_frame = ttk.LabelFrame(self.root, text="System Status", padding=10) + top_frame.pack(fill=tk.X, padx=10, pady=5) + + # Temperature + ttk.Label(top_frame, text="CPU Temperature:").grid(row=0, column=0, sticky=tk.W, padx=5) + self.temp_label = ttk.Label(top_frame, text="--°C", font=('Arial', 12, 'bold')) + self.temp_label.grid(row=0, column=1, sticky=tk.W, padx=5) + + # Fan RPMs + ttk.Label(top_frame, text="Fan 1 RPM:").grid(row=0, column=2, sticky=tk.W, padx=5) + self.fan1_rpm_label = ttk.Label(top_frame, text="----", font=('Arial', 12, 'bold')) + self.fan1_rpm_label.grid(row=0, column=3, sticky=tk.W, padx=5) + + ttk.Label(top_frame, text="Fan 2 RPM:").grid(row=1, column=0, sticky=tk.W, padx=5) + self.fan2_rpm_label = ttk.Label(top_frame, text="----", font=('Arial', 12, 'bold')) + self.fan2_rpm_label.grid(row=1, column=1, sticky=tk.W, padx=5) + + ttk.Label(top_frame, text="Fan 3 RPM:").grid(row=1, column=2, sticky=tk.W, padx=5) + self.fan3_rpm_label = ttk.Label(top_frame, text="----", font=('Arial', 12, 'bold')) + self.fan3_rpm_label.grid(row=1, column=3, sticky=tk.W, padx=5) + + # Update interval selector + ttk.Label(top_frame, text="Update Interval:").grid(row=0, column=4, sticky=tk.W, padx=5) + self.interval_var = tk.StringVar(value="1") + interval_combo = ttk.Combobox(top_frame, textvariable=self.interval_var, + values=["0.5", "1", "2", "5"], width=5, state='readonly') + interval_combo.grid(row=0, column=5, sticky=tk.W, padx=5) + interval_combo.bind('<>', self.on_interval_change) + ttk.Label(top_frame, text="sec").grid(row=0, column=6, sticky=tk.W) + + # APU Power Mode frame + apu_frame = ttk.LabelFrame(self.root, text="APU Power Mode", padding=10) + apu_frame.pack(fill=tk.X, padx=10, pady=5) + + ttk.Label(apu_frame, text="Power Mode:").grid(row=0, column=0, sticky=tk.W, padx=5) + self.apu_mode_var = tk.StringVar() + self.apu_mode_combo = ttk.Combobox(apu_frame, textvariable=self.apu_mode_var, + values=["quiet", "balanced", "performance"], + width=15, state='readonly') + self.apu_mode_combo.grid(row=0, column=1, sticky=tk.W, padx=5) + self.apu_mode_combo.bind('<>', self.on_apu_mode_change) + + # Fan control blocks + fans_frame = ttk.Frame(self.root) + fans_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) + + self.fan_controls = {} + for i, fan_num in enumerate([1, 2, 3], 1): + fan_name = ["CPU Fan 1", "CPU Fan 2", "System Fan"][i-1] + self.create_fan_control(fans_frame, fan_num, fan_name, i-1) + + # Config control block + config_frame = ttk.Frame(self.root) + config_frame.pack(fill=tk.X, padx=10, pady=5) + + ttk.Button( + config_frame, + text="Save Config", + command=self.save_config + ).pack(side=tk.LEFT, padx=5) + + ttk.Button( + config_frame, + text="Load Config", + command=self.load_config + ).pack(side=tk.LEFT, padx=5) + + def create_fan_control(self, parent, fan_num, fan_name, column): + """Create control block for a single fan""" + frame = ttk.LabelFrame(parent, text=fan_name, padding=10) + frame.grid(row=0, column=column, sticky=tk.NSEW, padx=5) + parent.columnconfigure(column, weight=1) + parent.rowconfigure(0, weight=1) + + # RPM display + ttk.Label(frame, text="Current RPM:").pack() + rpm_label = ttk.Label(frame, text="----", font=('Arial', 14, 'bold')) + rpm_label.pack() + + # Mode selector + ttk.Label(frame, text="Fan Mode:", font=('Arial', 10, 'bold')).pack(pady=(10, 0)) + mode_var = tk.StringVar() + mode_combo = ttk.Combobox(frame, textvariable=mode_var, + values=["auto", "fixed", "curve"], + width=12, state='readonly') + mode_combo.pack(pady=5) + mode_combo.bind('<>', + lambda e, fn=fan_num: self.on_fan_mode_change(fn)) + + # Level selector (for fixed mode) + level_frame = ttk.Frame(frame) + ttk.Label(level_frame, text="Level:").pack(side=tk.LEFT) + level_var = tk.StringVar() + level_combo = ttk.Combobox(level_frame, textvariable=level_var, + values=["1", "2", "3", "4", "5"], + width=5, state='readonly') + level_combo.pack(side=tk.LEFT, padx=5) + level_combo.bind('<>', + lambda e, fn=fan_num: self.on_level_change(fn)) + + # Curve controls + curve_frame = ttk.LabelFrame(frame, text="Fan Curves", padding=5) + + # Ramp up curve + rampup_frame = ttk.Frame(curve_frame) + rampup_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) + ttk.Label(rampup_frame, text="Ramp Up (°C)").pack() + + rampup_sliders = [] + rampup_labels = [] + slider_frame = ttk.Frame(rampup_frame) + slider_frame.pack(fill=tk.BOTH, expand=True) + + for i in range(5): + col_frame = ttk.Frame(slider_frame) + col_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2) + + value_label = ttk.Label(col_frame, text="--", font=('Arial', 9)) + value_label.pack() + + slider = tk.Scale(col_frame, from_=100, to=30, orient=tk.VERTICAL, + length=150, showvalue=0, + command=lambda v, fn=fan_num, idx=i, curve='rampup': + self.on_curve_change(fn, curve, idx, v)) + slider.pack() + + label = ttk.Label(col_frame, text=f"L{i+1}") + label.pack() + + rampup_sliders.append(slider) + rampup_labels.append(value_label) + + # Ramp down curve + rampdown_frame = ttk.Frame(curve_frame) + rampdown_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) + ttk.Label(rampdown_frame, text="Ramp Down (°C)").pack() + + rampdown_sliders = [] + rampdown_labels = [] + slider_frame = ttk.Frame(rampdown_frame) + slider_frame.pack(fill=tk.BOTH, expand=True) + + for i in range(5): + col_frame = ttk.Frame(slider_frame) + col_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2) + + value_label = ttk.Label(col_frame, text="--", font=('Arial', 9)) + value_label.pack() + + slider = tk.Scale(col_frame, from_=100, to=30, orient=tk.VERTICAL, + length=150, showvalue=0, + command=lambda v, fn=fan_num, idx=i, curve='rampdown': + self.on_curve_change(fn, curve, idx, v)) + slider.pack() + + label = ttk.Label(col_frame, text=f"L{i+1}") + label.pack() + + rampdown_sliders.append(slider) + rampdown_labels.append(value_label) + + # Store references + self.fan_controls[fan_num] = { + 'rpm_label': rpm_label, + 'mode_var': mode_var, + 'mode_combo': mode_combo, + 'level_var': level_var, + 'level_combo': level_combo, + 'level_frame': level_frame, + 'curve_frame': curve_frame, + 'rampup_sliders': rampup_sliders, + 'rampup_labels': rampup_labels, + 'rampup_values': [None] * 5, + 'rampdown_sliders': rampdown_sliders, + 'rampdown_labels': rampdown_labels, + 'rampdown_values': [None] * 5, + } + _ = self.read_curve(fan_num, "rampup") + _ = self.read_curve(fan_num, "rampdown") + + # Initially hide level and curve controls + level_frame.pack_forget() + curve_frame.pack_forget() + + def on_interval_change(self, event): + """Handle update interval change""" + self.update_interval = float(self.interval_var.get()) + + def on_apu_mode_change(self, event): + """Handle APU power mode change""" + mode = self.apu_mode_var.get() + path = f"{self.base_path}/apu/power_mode" + if self.write_sysfs(path, mode): + # Schedule mode verification after 10 seconds + if self.mode_check_timer: + self.mode_check_timer.cancel() + self.mode_check_timer = threading.Timer(10.0, self.read_apu_mode) + self.mode_check_timer.daemon = True + self.mode_check_timer.start() + + def on_fan_mode_change(self, fan_num): + """Handle fan mode change""" + mode = self.fan_controls[fan_num]['mode_var'].get() + path = f"{self.base_path}/fan{fan_num}/mode" + + if self.write_sysfs(path, mode): + # Update UI based on mode + self.update_fan_mode_ui(fan_num, mode) + + # If switching to curve mode, read current curve values + if mode == "curve": + self.read_fan_curves(fan_num) + + # Schedule mode verification after 10 seconds + old_timer = self.fan_mode_check_timers.get(fan_num) + if old_timer: + old_timer.cancel() + timer = threading.Timer(10.0, self.read_fan_mode, args=(fan_num,)) + timer.daemon = True + self.fan_mode_check_timers[fan_num] = timer + timer.start() + + def update_fan_mode_ui(self, fan_num, mode): + """Show/hide controls based on fan mode""" + controls = self.fan_controls[fan_num] + + # Hide everything first + controls['level_frame'].pack_forget() + controls['curve_frame'].pack_forget() + + # Show relevant controls based on mode + if mode == "fixed": + controls['level_frame'].pack(pady=5) + elif mode == "curve": + controls['curve_frame'].pack(fill=tk.BOTH, expand=True, pady=5) + + def on_level_change(self, fan_num): + """Handle fan level change""" + level = self.fan_controls[fan_num]['level_var'].get() + path = f"{self.base_path}/fan{fan_num}/level" + self.write_sysfs(path, level) + + def schedule_curve_write(self, fan_num, curve_type, values): + key = (fan_num, curve_type) + + # Increment generation + gen = self.curve_write_gen.get(key, 0) + 1 + self.curve_write_gen[key] = gen + + # Cancel old timer if it exists + old_timer = self.curve_write_timers.get(key) + if old_timer: + old_timer.cancel() + + def do_write(expected_gen): + # Discard if superseded + if self.curve_write_gen.get(key) != expected_gen: + return + + curve_str = ",".join(map(str, values)) + path = f"{self.base_path}/fan{fan_num}/{curve_type}_curve" + + self.write_sysfs(path, curve_str) + + timer = threading.Timer( + self.curve_write_delay, + lambda: do_write(gen) + ) + timer.daemon = True + + self.curve_write_timers[key] = timer + timer.start() + + def on_curve_change(self, fan_num, curve_type, index, value): + """Handle curve slider change with cascading constraints""" + controls = self.fan_controls[fan_num] + sliders = controls[f'{curve_type}_sliders'] + labels = controls[f'{curve_type}_labels'] + values = controls[f'{curve_type}_values'] + other_curve = 'rampdown' if curve_type == 'rampup' else 'rampup' + other_sliders = controls[f'{other_curve}_sliders'] + other_labels = controls[f'{other_curve}_labels'] + other_values = controls[f'{other_curve}_values'] + new_value = int(float(value)) + other_modified = False + + # Apply cascading constraints + # If we're moving a slider, ensure all subsequent sliders are >= this value + if new_value != values[index]: + values[index] = new_value + + # Enforce minimum constraint: each level must be >= previous level + for i in range(index + 1, 5): + if values[i] < values[index]: + values[i] = values[index] + # Temporarily block the callback to avoid recursion + sliders[i].config(command='') + sliders[i].set(values[i]) + sliders[i].config(command=lambda v, fn=fan_num, idx=i, curve=curve_type: + self.on_curve_change(fn, curve, idx, v)) + + # Also check backwards: if previous levels are > current, adjust them + for i in range(index - 1, -1, -1): + if values[i] > values[index]: + values[i] = values[index] + # Temporarily block the callback to avoid recursion + sliders[i].config(command='') + sliders[i].set(values[i]) + sliders[i].config(command=lambda v, fn=fan_num, idx=i, curve=curve_type: + self.on_curve_change(fn, curve, idx, v)) + + # Enforce rampup/rampdown constraint: rampdown <= rampup for each level + if curve_type == 'rampup': + for i in range(5): + if other_values[i] > values[i]: + other_values[i] = values[i] + other_sliders[i].config(command='') + other_sliders[i].set(other_values[i]) + other_sliders[i].config(command=lambda v, fn=fan_num, idx=i, curve=other_curve: + self.on_curve_change(fn, curve, idx, v)) + other_modified = True + else: # curve_type == 'rampdown' + for i in range(5): + if other_values[i] < values[i]: + other_values[i] = values[i] + other_sliders[i].config(command='') + other_sliders[i].set(other_values[i]) + other_sliders[i].config(command=lambda v, fn=fan_num, idx=i, curve=other_curve: + self.on_curve_change(fn, curve, idx, v)) + other_modified = True + + # If other was modified, enforce cascading constraint on it + if other_modified: + for i in range(4): + if other_values[i+1] < other_values[i]: + other_values[i+1] = other_values[i] + other_sliders[i+1].config(command='') + other_sliders[i+1].set(other_values[i+1]) + other_sliders[i+1].config(command=lambda v, fn=fan_num, idx=i+1, curve=other_curve: + self.on_curve_change(fn, curve, idx, v)) + + # Update all labels + for i, val in enumerate(values): + labels[i].config(text=f"{val}°C") + for i, val in enumerate(other_values): + other_labels[i].config(text=f"{val}°C") + + # Schedule Write all values to sysfs + self.schedule_curve_write(fan_num, curve_type, values) + controls[f'{curve_type}_values'] = values + + # Also write the other curve if we modified it + if other_modified: + self.schedule_curve_write(fan_num, other_curve, other_values) + controls[f'{other_curve}_values'] = other_values + + def read_curve(self, fan_num, curve_type): + """Read a fan curve (rampup_curve or rampdown_curve)""" + path = f"{self.base_path}/fan{fan_num}/{curve_type}_curve" + data = self.read_sysfs(path) + if data: + try: + values = [int(x) for x in data.replace(',', ' ').split()] + self.fan_controls[fan_num][f'{curve_type}_values'] = values + return values + except: + return None + return None + + def read_fan_curves(self, fan_num): + """Read and update curve sliders for a fan""" + rampup = self.read_curve(fan_num, "rampup") + rampdown = self.read_curve(fan_num, "rampdown") + + controls = self.fan_controls[fan_num] + + if rampup and len(rampup) == 5: + for i, value in enumerate(rampup): + controls['rampup_sliders'][i].set(value) + controls['rampup_labels'][i].config(text=f"{value}°C") + + if rampdown and len(rampdown) == 5: + for i, value in enumerate(rampdown): + controls['rampdown_sliders'][i].set(value) + controls['rampdown_labels'][i].config(text=f"{value}°C") + + def read_fan_mode(self, fan_num): + """Read current fan mode""" + path = f"{self.base_path}/fan{fan_num}/mode" + mode = self.read_sysfs(path) + if mode: + # Extract current mode from [auto, fixed, curve] format + if '[' in mode: + for m in ['auto', 'fixed', 'curve']: + if f'[{m}]' in mode: + mode = m + break + self.queue_ui(lambda m=mode: self.fan_controls[fan_num]['mode_var'].set(m)) + self.queue_ui(lambda m=mode: self.update_fan_mode_ui(fan_num, m)) + # If mode is curve, read the current curve values + if mode == 'curve': + self.queue_ui(lambda: self.read_fan_curves(fan_num)) + + def read_all_modes(self): + """Read all fan modes and APU mode""" + for fan_num in [1, 2, 3]: + self.read_fan_mode(fan_num) + self.read_apu_mode() + + def read_apu_mode(self): + """Read current APU power mode""" + path = f"{self.base_path}/apu/power_mode" + mode = self.read_sysfs(path) + if mode: + # Extract current mode from [quiet, balanced, performance] format + if '[' in mode: + for m in ['quiet', 'balanced', 'performance']: + if f'[{m}]' in mode: + mode = m + break + self.queue_ui(lambda m=mode: self.apu_mode_var.set(m)) + + def monitor_loop(self): + """Background thread to monitor temperature and RPM""" + while self.running: + try: + # Read temperature + temp_path = f"{self.base_path}/temp1/temp" + temp = self.read_sysfs(temp_path) + if temp: + self.queue_ui(lambda t=temp: self.temp_label.config(text=f"{t}°C")) + + # Read fan RPMs + for fan_num in [1, 2, 3]: + rpm_path = f"{self.base_path}/fan{fan_num}/rpm" + rpm = self.read_sysfs(rpm_path) + if rpm: + if fan_num == 1: + self.queue_ui(lambda r=rpm: self.fan1_rpm_label.config(text=r)) + elif fan_num == 2: + self.queue_ui(lambda r=rpm: self.fan2_rpm_label.config(text=r)) + elif fan_num == 3: + self.queue_ui(lambda r=rpm: self.fan3_rpm_label.config(text=r)) + + # Update fan control block RPM + self.queue_ui(lambda fn=fan_num, r=rpm: + self.fan_controls[fn]['rpm_label'].config(text=r)) + + except Exception as e: + print(f"Monitor error: {e}") + + self.stop_event.wait(self.update_interval) + + def save_config(self): + data = { + "apu_mode": self.apu_mode_var.get(), + "fans": {} + } + + for fan_num, c in self.fan_controls.items(): + data["fans"][str(fan_num)] = { + "mode": c["mode_var"].get(), + "level": c["level_var"].get(), + "rampup_curve": c["rampup_values"][:], + "rampdown_curve": c["rampdown_values"][:], + } + + try: + with open(self.config_path, "w") as f: + json.dump(data, f, indent=2) + messagebox.showinfo("Saved", "Configuration saved successfully.") + except Exception as e: + messagebox.showerror("Error", f"Failed to save config:\n{e}") + + def load_config(self): + try: + with open(self.config_path) as f: + data = json.load(f) + except Exception as e: + messagebox.showerror("Error", f"Failed to load config:\n{e}") + return + + # APU mode + apu = data.get("apu_mode") + if apu: + self.apu_mode_var.set(apu) + self.write_sysfs(f"{self.base_path}/apu/power_mode", apu) + + for fan_num_str, cfg in data.get("fans", {}).items(): + fan_num = int(fan_num_str) + controls = self.fan_controls.get(fan_num) + if not controls: + continue + + mode = cfg.get("mode") + if mode: + controls["mode_var"].set(mode) + self.write_sysfs(f"{self.base_path}/fan{fan_num}/mode", mode) + self.update_fan_mode_ui(fan_num, mode) + + level = cfg.get("level") + if level and mode == "fixed": + controls["level_var"].set(level) + self.write_sysfs(f"{self.base_path}/fan{fan_num}/level", level) + + # Curves + if mode == "curve": + ru = cfg.get("rampup_curve") + rd = cfg.get("rampdown_curve") + + if ru and len(ru) == 5: + controls["rampup_values"][:] = ru + for i, s in enumerate(controls["rampup_sliders"]): + s.set(ru[i]) + self.schedule_curve_write(fan_num, "rampup", ru) + + if rd and len(rd) == 5: + controls["rampdown_values"][:] = rd + for i, s in enumerate(controls["rampdown_sliders"]): + s.set(rd[i]) + self.schedule_curve_write(fan_num, "rampdown", rd) + messagebox.showinfo("Loaded", "Configuration loaded successfully.") + + def on_closing(self): + """Handle window close""" + self.running = False + self.stop_event.set() + if self.mode_check_timer: + self.mode_check_timer.cancel() + for timer in self.fan_mode_check_timers.values(): + timer.cancel() + for timer in self.curve_write_timers.values(): + timer.cancel() + if self.ui_after_id is not None: + self.root.after_cancel(self.ui_after_id) + self.ui_after_id = None + self.monitor_thread.join(timeout=1.0) + self.root.destroy() + +def main(): + root = tk.Tk() + app = FanControlGUI(root) + root.protocol("WM_DELETE_WINDOW", app.on_closing) + root.mainloop() + +if __name__ == "__main__": + main() diff --git a/os/licenses/ec_su_axb35/src/scripts/info.sh b/os/licenses/ec_su_axb35/src/scripts/info.sh new file mode 100755 index 00000000..b7857d00 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/scripts/info.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + + +printf "+--------------------------------------------------------------+\n" +# APU Power Mode +apu_mode=$(cat /sys/class/ec_su_axb35/apu/power_mode) +# Temperature +temp=$(cat /sys/class/ec_su_axb35/temp1/temp) +printf "| %-26s | %-31s |\n" "CPU-Temp: ${temp} C" "Power mode: $apu_mode" + + +# Fan Table +printf "+-----+-------+-------+------+----------------+----------------+\n" +printf "| %-3s | %-5s | %-5s | %-4s | %-14s | %-14s |\n" "FAN" "MODE" "LEVEL" "RPM" "RAMPUP" "RAMPDOWN" +for i in 1 2 3; do + mode=$(cat /sys/class/ec_su_axb35/fan${i}/mode) + level=$(cat /sys/class/ec_su_axb35/fan${i}/level) + rpm=$(cat /sys/class/ec_su_axb35/fan${i}/rpm) + rampup=$(cat /sys/class/ec_su_axb35/fan${i}/rampup_curve) + rampdown=$(cat /sys/class/ec_su_axb35/fan${i}/rampdown_curve) + printf "| %-3d | %-5s | %-5d | %4d | %14s | %14s |\n" $i "$mode" "$level" "$rpm" "$rampup" "$rampdown" + +done +printf "+-----+-------+-------+------+----------------+----------------+\n" diff --git a/os/licenses/ec_su_axb35/src/scripts/su_axb35_monitor b/os/licenses/ec_su_axb35/src/scripts/su_axb35_monitor new file mode 100755 index 00000000..13ee0217 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/scripts/su_axb35_monitor @@ -0,0 +1,571 @@ +#!/usr/bin/env bash + +# mandatory bash version check +if [[ "${BASH_VERSION%%.*}" -lt 4 ]]; then + echo "Error: This script requires Bash version 4 or higher" + echo "Current version: $BASH_VERSION" + exit 1 +fi + +axb35_name=ec_su_axb35 +axb35_path=/sys/class/$axb35_name +axb35_delay=1 +raw_mode=0 +dev_mode=0 +declare -a axb35_vars + +# usage info +usage() { + cat << EOF +Usage: $(basename "$0") [OPTIONS] + +OPTIONS: + -i SECONDS Update interval (0.3-10 seconds, default: 1) + -j Output current values in JSON and exit + -r Display raw values without additional processing + -h, --help Display this usage information + + +EOF + exit 0 +} + +# colors and formatting +RESET="\033[0m" +BOLD="\033[1m" +GREEN="\033[32m" +YELLOW="\033[33m" +BLUE="\033[34m" +CYAN="\033[36m" +RED="\033[31m" +MAGENTA="\033[35m" + +# magic numbers to navigate the array +fan1rpm=0 fan2rpm=1 fan3rpm=2 +fan1mode=3 fan2mode=4 fan3mode=5 +fan1level=6 fan2level=7 fan3level=8 +fan1ru=9 fan2ru=10 fan3ru=11 +fan1rd=12 fan2rd=13 fan3rd=14 +temp=15 tempmin=16 tempmax=17 +powermode=18 + +# function to get all needed values from the module +get_current_state() { + mapfile -t axb35_vars < <( + cat \ + $axb35_path/fan{1..3}/rpm \ + $axb35_path/fan{1..3}/mode \ + $axb35_path/fan{1..3}/level \ + $axb35_path/fan{1..3}/rampup_curve \ + $axb35_path/fan{1..3}/rampdown_curve \ + $axb35_path/temp1/{temp,min,max} \ + $axb35_path/apu/power_mode + ) +} + +# function to process received values +process_current_state() { + local modes=($fan1mode $fan2mode $fan3mode) + local rus=($fan1ru $fan2ru $fan3ru) + local rds=($fan1rd $fan2rd $fan3rd) + local levels=($fan1level $fan2level $fan3level) + + # for fans in auto or fixed mode, ramp-up and ramp-down curves are not applicable + # for fans in auto mode, fan level is not applicable + for i in {0..2}; do + if [[ "${axb35_vars[${modes[$i]}]}" == "auto" || "${axb35_vars[${modes[$i]}]}" == "fixed" ]]; then + axb35_vars[${rus[$i]}]='' + axb35_vars[${rds[$i]}]='' + fi + [[ "${axb35_vars[${modes[$i]}]}" == "auto" ]] && axb35_vars[${levels[$i]}]='' + done + + # check if any conditional field changed to/from empty compared to previous state + needs_redraw=0 + local conditional_indices=($fan1level $fan1ru $fan1rd $fan2level $fan2ru $fan2rd $fan3level $fan3ru $fan3rd) + for idx in "${conditional_indices[@]}"; do + local cur_empty=0 prev_empty=0 + [[ -z "${axb35_vars[$idx]}" ]] && cur_empty=1 + [[ -z "${axb35_vars_old[$idx]}" ]] && prev_empty=1 + if [[ $cur_empty -ne $prev_empty ]]; then + needs_redraw=1 + break + fi + done + axb35_vars_old=("${axb35_vars[@]}") +} + +# check if module is loaded and path exists (skipped in dev mode) +check_module() { + if ! lsmod | grep -q "$axb35_name" || [ ! -d "$axb35_path" ]; then + echo "Error: Module '$axb35_name' is not loaded or not accessible" + exit 2 + fi +} + +# dev mode state generator - cycles through various states to exercise the UI +_dev_tick=0 +get_dev_state() { + local tick=$(( _dev_tick % 40 )) + (( _dev_tick++ )) + + # cycle through phases: + # 0-9: all fans auto, low temp, quiet power + # 10-19: fan1 fixed, fan2 curve, fan3 auto, mid temp, balanced power + # 20-29: all fans curve, high temp, performance power + # 30-39: fan1 curve, fan2 auto, fan3 fixed, cooling down, balanced power + + local f1rpm f2rpm f3rpm + local f1mode f2mode f3mode + local f1level f2level f3level + local f1ru f2ru f3ru + local f1rd f2rd f3rd + local cur_temp pwr_mode + + if (( tick < 10 )); then + # phase 0: all fans auto, cool, quiet + f1rpm=$(( 800 + tick * 20 )) + f2rpm=$(( 750 + tick * 15 )) + f3rpm=$(( 600 + tick * 10 )) + f1mode=auto; f2mode=auto; f3mode=auto + f1level=''; f2level=''; f3level='' + f1ru=''; f2ru=''; f3ru='' + f1rd=''; f2rd=''; f3rd='' + cur_temp=$(( 38 + tick )) + pwr_mode=quiet + elif (( tick < 20 )); then + # phase 1: fan1 fixed, fan2 curve, fan3 auto, warming, balanced + local t=$(( tick - 10 )) + f1rpm=$(( 1800 + t * 80 )) + f2rpm=$(( 1200 + t * 60 )) + f3rpm=$(( 900 + t * 40 )) + f1mode=fixed; f2mode=curve; f3mode=auto + f1level=$(( 1 + t / 3 )); f2level=''; f3level='' + f1ru=''; f2ru="60,70,83,95,97"; f3ru='' + f1rd=''; f2rd="40,50,80,94,96"; f3rd='' + cur_temp=$(( 50 + t * 2 )) + pwr_mode=balanced + elif (( tick < 30 )); then + # phase 2: all fans curve, hot, performance + local t=$(( tick - 20 )) + f1rpm=$(( 3000 + t * 100 )) + f2rpm=$(( 2800 + t * 90 )) + f3rpm=$(( 2200 + t * 70 )) + f1mode=curve; f2mode=curve; f3mode=curve + f1level=''; f2level=''; f3level='' + f1ru="60,70,83,95,97"; f2ru="60,70,83,95,97"; f3ru="20,60,83,95,97" + f1rd="40,50,80,94,96"; f2rd="40,50,80,94,96"; f3rd="0,50,80,94,96" + cur_temp=$(( 72 + t )) + pwr_mode=performance + else + # phase 3: fan1 curve, fan2 auto, fan3 fixed, cooling, balanced + local t=$(( tick - 30 )) + f1rpm=$(( 3900 - t * 120 )) + f2rpm=$(( 3600 - t * 100 )) + f3rpm=$(( 2900 - t * 80 )) + f1mode=curve; f2mode=auto; f3mode=fixed + f1level=''; f2level=''; f3level=$(( 5 - t / 2 )) + f1ru="60,70,83,95,97"; f2ru=''; f3ru='' + f1rd="40,50,80,94,96"; f2rd=''; f3rd='' + cur_temp=$(( 80 - t * 2 )) + pwr_mode=balanced + fi + + axb35_vars=( + "$f1rpm" "$f2rpm" "$f3rpm" + "$f1mode" "$f2mode" "$f3mode" + "$f1level" "$f2level" "$f3level" + "$f1ru" "$f2ru" "$f3ru" + "$f1rd" "$f2rd" "$f3rd" + "$cur_temp" "35" "82" + "$pwr_mode" + ) +} + +# command line arguments processing +while getopts ":i:jrdh-:" opt; do + case $opt in + i) + if [[ $(echo "$OPTARG > 0.2 && $OPTARG <= 10" | bc) -eq 1 ]]; then + axb35_delay=$OPTARG + else + echo "Error: Interval must be between 0.3 and 10 seconds" + exit 1 + fi + ;; + j) + get_current_state + cat << EOF +{ + "fan1": { + "rpm": ${axb35_vars[$fan1rpm]}, + "mode": "${axb35_vars[$fan1mode]}", + "level": ${axb35_vars[$fan1level]}, + "rampup_curve": "${axb35_vars[$fan1ru]}", + "rampdown_curve": "${axb35_vars[$fan1rd]}" + }, + "fan2": { + "rpm": ${axb35_vars[$fan2rpm]}, + "mode": "${axb35_vars[$fan2mode]}", + "level": ${axb35_vars[$fan2level]}, + "rampup_curve": "${axb35_vars[$fan2ru]}", + "rampdown_curve": "${axb35_vars[$fan2rd]}" + }, + "fan3": { + "rpm": ${axb35_vars[$fan3rpm]}, + "mode": "${axb35_vars[$fan3mode]}", + "level": ${axb35_vars[$fan3level]}, + "rampup_curve": "${axb35_vars[$fan3ru]}", + "rampdown_curve": "${axb35_vars[$fan3rd]}" + }, + "temperature": { + "current": ${axb35_vars[$temp]}, + "min": ${axb35_vars[$tempmin]}, + "max": ${axb35_vars[$tempmax]} + }, + "power_mode": "${axb35_vars[$powermode]}" +} +EOF + exit 0 + ;; + r) + raw_mode=1 + ;; + d) + dev_mode=1 + ;; + h) + usage + ;; + -) + case "${OPTARG}" in + help) + usage + ;; + *) + echo "Invalid option: --${OPTARG}" >&2 + usage + ;; + esac + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + usage + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + usage + ;; + esac +done + +# module check runs after argument parsing so -d can suppress it +[[ $dev_mode -eq 0 ]] && check_module + +# function to format the RPM value with color +format_rpm() { + local rpm=$1 + if (( rpm < 1200 )); then + echo -e "${GREEN}${rpm}${RESET} RPM" + elif (( rpm < 3000 )); then + echo -e "${YELLOW}${rpm}${RESET} RPM" + else + echo -e "${RED}${rpm}${RESET} RPM" + fi +} + +# function to format the temperature with color +format_temp() { + local temp=$1 + if (( temp < 50 )); then + echo -e "${GREEN}${temp}${RESET} °C" + elif (( temp < 70 )); then + echo -e "${YELLOW}${temp}${RESET} °C" + else + echo -e "${RED}${temp}${RESET} °C" + fi +} + +# function to format the mode with color +format_mode() { + local mode=$1 + case "$mode" in + "auto") echo -e "[${GREEN}${mode}${RESET}]" ;; + "fixed") echo -e "[${YELLOW}${mode}${RESET}]" ;; + "curve") echo -e "[${CYAN}${mode}${RESET}]" ;; + *) echo -e "[${mode}]" ;; + esac +} + +# function to format the power mode with color +format_power_mode() { + local mode=$1 + case "$mode" in + "quiet") echo -e "${GREEN}${mode}${RESET}" ;; + "balanced") echo -e "${YELLOW}${mode}${RESET}" ;; + "performance") echo -e "${RED}${mode}${RESET}" ;; + *) echo -e "${mode}" ;; + esac +} + +# function to draw the interface +draw_interface() { + local value_offset=20 + local term_rows=$(tput lines) + local term_cols=$(tput cols) + + tput cup 0 0 + tput ed + + # terminal dimensions and update interval in top right corner + local interval_ms=$( printf "%.0f" $(echo "${axb35_delay} * 1000" | bc -l) ) + local dim_text="[i:${interval_ms}ms | t:${term_cols}x${term_rows}]" + local dim_pos=$((term_cols - ${#dim_text})) + tput cup 0 $dim_pos + echo -n -e "${CYAN}${dim_text}${RESET}" + + # title + tput cup 0 0 + echo -e "${BOLD}${MAGENTA}=== AXB35 System Monitor ===${RESET}" + echo "" + + # calculate layout based on terminal size + # each block is 6 rows high (1 title + 4 data + 1 empty) + # each column is 40 chars wide (static) + local block_height=6 + local column_width=40 + local max_columns=$((term_cols / column_width)) + [[ $max_columns -lt 1 ]] && max_columns=1 + + local usable_rows=$((term_rows - 4)) + local blocks_per_column=$((usable_rows / block_height)) + [[ $blocks_per_column -lt 1 ]] && blocks_per_column=1 + + # store the positions of each value for later updates + declare -gA positions + + # Block 1: APU Fan 1 + local col=0 + local row=2 + + if [[ $((row + block_height)) -gt $((usable_rows + 2)) ]]; then + col=$((col + 1)) + row=2 + fi + + if [[ $col -lt $max_columns ]]; then + local col_offset=$((col * column_width)) + tput cup $row $col_offset + echo -e "${BOLD}APU Fan 1${RESET}" + positions[fan1mode_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + echo -n " Speed: " + positions[fan1rpm_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan1level]}" ]] && echo -n " Level: " + positions[fan1level_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan1ru]}" ]] && echo -n " Ramp Up Curve: " + positions[fan1ru_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan1rd]}" ]] && echo -n " Ramp Down Curve: " + positions[fan1rd_pos]="$row $((col_offset + value_offset))" + + row=$((row + 2)) # skip one line + fi + + # Block 2: APU Fan 2 + if [[ $((row + block_height)) -gt $((usable_rows + 2)) ]]; then + col=$((col + 1)) + row=2 + fi + + if [[ $col -lt $max_columns ]]; then + local col_offset=$((col * column_width)) + tput cup $row $col_offset + echo -e "${BOLD}APU Fan 2${RESET}" + positions[fan2mode_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + echo -n " Speed: " + positions[fan2rpm_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan2level]}" ]] && echo -n " Level: " + positions[fan2level_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan2ru]}" ]] && echo -n " Ramp Up Curve: " + positions[fan2ru_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan2rd]}" ]] && echo -n " Ramp Down Curve: " + positions[fan2rd_pos]="$row $((col_offset + value_offset))" + + row=$((row + 2)) # skip one line + fi + + # Block 3: System Fan + if [[ $((row + block_height)) -gt $((usable_rows + 2)) ]]; then + col=$((col + 1)) + row=2 + fi + + if [[ $col -lt $max_columns ]]; then + local col_offset=$((col * column_width)) + tput cup $row $col_offset + echo -e "${BOLD}System Fan${RESET}" + positions[fan3mode_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + echo -n " Speed: " + positions[fan3rpm_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan3level]}" ]] && echo -n " Level: " + positions[fan3level_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan3ru]}" ]] && echo -n " Ramp Up Curve: " + positions[fan3ru_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + [[ -n "${axb35_vars[$fan3rd]}" ]] && echo -n " Ramp Down Curve: " + positions[fan3rd_pos]="$row $((col_offset + value_offset))" + + row=$((row + 2)) # skip one line + fi + + # Block 4: Temperature & Power + if [[ $((row + block_height)) -gt $((usable_rows + 2)) ]]; then + col=$((col + 1)) + row=2 + fi + + if [[ $col -lt $max_columns ]]; then + local col_offset=$((col * column_width)) + tput cup $row $col_offset + echo -e "${BOLD}Temperature & Power${RESET}" + + row=$((row + 1)) + tput cup $row $col_offset + echo -n " Current: " + positions[temp_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + echo -n " Min: " + positions[tempmin_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + echo -n " Max: " + positions[tempmax_pos]="$row $((col_offset + value_offset))" + + row=$((row + 1)) + tput cup $row $col_offset + echo -n " Power Mode: " + positions[powermode_pos]="$row $((col_offset + value_offset))" + + row=$((row + 2)) # skip one line + fi + + # prompt + local prompt_row=$((term_rows - 2)) + tput cup $prompt_row 0 + echo -e "${BOLD}${BLUE}Press Ctrl+C to exit${RESET}" +} + +# function to draw the values in their positions +draw_values() { + for pos in "${!positions[@]}"; do + read -r row col <<< "${positions[$pos]}" + tput cup "$row" "$col" + + local value_str='' + + # extract the variable name without _pos suffix + var_name=${pos%_pos} + + # special formatting for different types of values + case "$var_name" in + fan1rpm|fan2rpm|fan3rpm) + value_str=$(format_rpm "${axb35_vars[${!var_name}]}") + ;; + temp|tempmin|tempmax) + value_str=$(format_temp "${axb35_vars[${!var_name}]}") + ;; + fan1mode|fan2mode|fan3mode) + value_str=$(format_mode "${axb35_vars[${!var_name}]}") + ;; + powermode) + value_str=$(format_power_mode "${axb35_vars[${!var_name}]}") + ;; + *) + value_str="${axb35_vars[${!var_name}]}" + ;; + esac + + # calculate required padding + local clean_str=${value_str//$'\e'[\[0-9;]*m/} + local visible_length=${#clean_str} + local padding=$((15 - visible_length)) + printf "%s%${padding}s" "$value_str" "" + done +} + +# function to handle terminal resize +handle_resize() { + draw_interface + draw_values +} + +# function to handle cleanup on exit +cleanup() { + tput cnorm + tput sgr0 + clear + echo "Monitor terminated." + exit 0 +} + +# set up signal handlers +trap cleanup SIGINT SIGTERM +trap handle_resize SIGWINCH + +# hide cursor +tput civis + +# previous state array for change detection in process_current_state +declare -a axb35_vars_old + +# main loop +handle_resize +while true; do + if [[ $dev_mode -eq 1 ]]; then + get_dev_state + else + get_current_state + fi + [[ $raw_mode -eq 0 ]] && process_current_state + [[ $needs_redraw -eq 1 ]] && draw_interface + + draw_values + sleep $axb35_delay +done diff --git a/os/licenses/ec_su_axb35/src/scripts/test_fan_mode_fixed.sh b/os/licenses/ec_su_axb35/src/scripts/test_fan_mode_fixed.sh new file mode 100755 index 00000000..2519c534 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/scripts/test_fan_mode_fixed.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +class_path=/sys/class/ec_su_axb35 +declare -a fan1_rpms fan2_rpms fan3_rpms +declare -a original_modes original_levels + +echo "Saving original fan settings..." +for i in {1..3}; do + original_modes[$i]=$(cat $class_path/fan$i/mode) + original_levels[$i]=$(cat $class_path/fan$i/level) + echo "Fan $i: mode=${original_modes[$i]}, level=${original_levels[$i]}" +done +echo "------------------------" + +echo "Setting fans to fixed mode..." +echo fixed | tee $class_path/fan{1..3}/mode > /dev/null + +# Test each fan rpm level from 0 to 5 +for level in {0..5}; do + echo "Setting fans to level $level..." + echo "$level" | tee $class_path/fan{1..3}/level > /dev/null + + echo "Waiting 5 seconds for fans to adjust..." + sleep 5 + + fan1_rpms[$level]=$(cat $class_path/fan1/rpm) + fan2_rpms[$level]=$(cat $class_path/fan2/rpm) + fan3_rpms[$level]=$(cat $class_path/fan3/rpm) + + echo "Fan 1 rpm: ${fan1_rpms[$level]}" + echo "Fan 2 rpm: ${fan2_rpms[$level]}" + echo "Fan 3 rpm: ${fan3_rpms[$level]}" + echo "------------------------" +done + +echo "Restoring original fan settings..." +for i in {1..3}; do + echo "${original_modes[$i]}" > $class_path/fan$i/mode + echo "${original_levels[$i]}" > $class_path/fan$i/level + echo "Fan $i restored to: mode=${original_modes[$i]}, level=${original_levels[$i]}" +done + +# Generate report +echo " +=========================================== +| Level | Fan 1 | Fan 2 | Fan 3 | +===========================================" + +for level in {0..5}; do + printf "| %d | %4s | %4s | %4s |\n" $level "${fan1_rpms[$level]}" "${fan2_rpms[$level]}" "${fan3_rpms[$level]}" + echo "-------------------------------------------" +done + +echo "Fan rpm test completed." diff --git a/os/licenses/ec_su_axb35/src/src/ec_su_axb35.c b/os/licenses/ec_su_axb35/src/src/ec_su_axb35.c new file mode 100644 index 00000000..bb4c7858 --- /dev/null +++ b/os/licenses/ec_su_axb35/src/src/ec_su_axb35.c @@ -0,0 +1,660 @@ +// ec_su_axb35.c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int ec_read(u8 addr, u8 *val); +extern int ec_write(u8 addr, u8 val); + +enum fan_mode { AUTO, FIXED, CURVE }; + +struct ec_fan { + const char *name; + u8 speed_reg_high; + u8 speed_reg_low; + u8 mode_reg; + u8 rampup_curve[6]; + u8 rampdown_curve[6]; + enum fan_mode mode; + struct device *dev; +}; + +struct ec_temp { + const char *name; + u8 reg; + u8 temp_min; + u8 temp_max; + struct device *dev; +}; + +struct ec_apu { + const char *name; + u8 power_mode_reg; + struct device *dev; +}; + +static struct class *ec_class; + +static struct ec_fan ec_fans[] = { + { .name = "fan1", + .speed_reg_high = 0x35, + .speed_reg_low = 0x36, + .mode_reg = 0x21, + .rampup_curve = { 0, 60, 70, 83, 95, 97 }, + .rampdown_curve = { 0, 40, 50, 80, 94, 96 } }, + { .name = "fan2", + .speed_reg_high = 0x37, + .speed_reg_low = 0x38, + .mode_reg = 0x23, + .rampup_curve = { 0, 60, 70, 83, 95, 97 }, + .rampdown_curve = { 0, 40, 50, 80, 94, 96 } }, + { .name = "fan3", + .speed_reg_high = 0x28, + .speed_reg_low = 0x29, + .mode_reg = 0x25, + .rampup_curve = { 0, 20, 60, 83, 95, 97 }, + .rampdown_curve = { 0, 0, 50, 80, 94, 96 } }, +}; + +static struct ec_temp ec_temp = { + .name = "temp1", + .reg = 0x70, +}; + +static struct ec_apu ec_apu = { + .name = "apu", + .power_mode_reg = 0x31, +}; + +static ssize_t fan_rpm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + u8 hi; + // TODO: handle error + ec_read(fan->speed_reg_high, &hi); + u8 lo; + // TODO: handle error + ec_read(fan->speed_reg_low, &lo); + u16 rpm = (hi << 8) | lo; + // wired fan3 behavior, displaying 8000 before turning to 0 + if (strcmp(fan->name, "fan3") == 0 && rpm == 8000) + rpm = 0; + return sprintf(buf, "%u\n", rpm); +} + +static struct device_attribute dev_attr_fan_rpm = + __ATTR(rpm, 0444, fan_rpm_show, NULL); + +static void update_fan_mode(struct ec_fan *fan) +{ + u8 val; + // TODO: handle error + ec_read(fan->mode_reg, &val); + + switch (val) { + case 0x10: + case 0x20: + case 0x30: + fan->mode = AUTO; + break; + case 0x11: + case 0x21: + case 0x31: + // curve and fixed use the value in the EC register + // so fixed is only allowed if it was already know as + // FIXED to the driver + if (fan->mode != FIXED) { + fan->mode = CURVE; + } else { + fan->mode = FIXED; + } + break; + } +} + +static ssize_t fan_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + + update_fan_mode(fan); + + const char *mode = "unknown"; + switch (fan->mode) { + case AUTO: + mode = "auto"; + break; + case FIXED: + mode = "fixed"; + break; + case CURVE: + mode = "curve"; + break; + } + + return sprintf(buf, "%s\n", mode); +} + +static u8 read_fan_level(struct ec_fan *fan) +{ + u8 val; + // TODO: handle error + ec_read(fan->mode_reg + 1, &val); + + switch (val & 0xF) { + case 0x2: // 20% + return 1; + case 0x3: // 40% + return 2; + case 0x4: // 60% + return 3; + case 0x5: // 80% + return 4; + case 0x6: // 100% + return 5; + case 0x7: + default: // off + return 0; + } +} + +static ssize_t fan_level_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", read_fan_level(fan)); +} + +static void write_fan_level(struct ec_fan *fan, u8 level) +{ + u8 val; + + switch (fan->mode_reg) { + case 0x21: + val = 0x10; + break; + case 0x23: + val = 0x20; + break; + case 0x25: + val = 0x30; + break; + } + + switch (level) { + case 0: + val += 0x7; + break; + case 1: + val += 0x2; + break; + case 2: + val += 0x3; + break; + case 3: + val += 0x4; + break; + case 4: + val += 0x5; + break; + case 5: + default: + val += 0x6; + } + + // TODO: handle error + ec_write(fan->mode_reg + 1, val); +} + +static ssize_t fan_level_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + u8 val; + + if (kstrtou8(buf, 10, &val)) + return -EINVAL; + + write_fan_level(fan, val); + + return count; +} + +static struct device_attribute dev_attr_fan_level = + __ATTR(level, 0644, fan_level_show, fan_level_store); + +static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + u8 val; + + if (sysfs_streq(buf, "auto")) { + fan->mode = AUTO; + } else if (sysfs_streq(buf, "fixed")) { + fan->mode = FIXED; + } else if (sysfs_streq(buf, "curve")) { + fan->mode = CURVE; + } else { + return -EINVAL; + } + + switch (fan->mode) { + case AUTO: + fan->mode = AUTO; + switch (fan->mode_reg) { + case 0x21: + val = 0x10; + break; + case 0x23: + val = 0x20; + break; + case 0x25: + val = 0x30; + break; + default: + return -EINVAL; + } + break; + case FIXED: + case CURVE: + switch (fan->mode_reg) { + case 0x21: + val = 0x11; + break; + case 0x23: + val = 0x21; + break; + case 0x25: + val = 0x31; + break; + default: + return -EINVAL; + } + break; + } + + // TODO: handle error + ec_write(fan->mode_reg, val); + + // When switching to CURVE mode, set initial fan level based on current temperature + // to prevent RPM burst from inappropriate starting level + if (fan->mode == CURVE) { + u8 temp; + // TODO: handle error + if (ec_read(ec_temp.reg, &temp) == 0) { + u8 initial_level = 0; + int i; + + // Find the appropriate level based on current temperature + // Use rampup curve for initial positioning to be more responsive + for (i = 5; i > 0; i--) { + if (temp >= fan->rampup_curve[i]) { + initial_level = i; + break; + } + } + + write_fan_level(fan, initial_level); + } + } + + return count; +} + +static struct device_attribute dev_attr_fan_mode = + __ATTR(mode, 0644, fan_mode_show, fan_mode_store); + +static ssize_t fan_curve_show(u8 *curve, char *buf) +{ + int i; + char *p = buf; + + for (i = 1; i < 6; i++) { + p += sprintf(p, "%d", curve[i]); + if (i < 5) { + *p++ = ','; + } + } + *p++ = '\n'; + *p = '\0'; + + return p - buf; +} + +static ssize_t fan_curve_store(u8 *curve, const char *buf, size_t count) +{ + char *str = kstrndup(buf, count, GFP_KERNEL); + char *token; + int values[5]; + int i = 0; + int ret = 0; + + if (!str) + return -ENOMEM; + + token = strsep(&str, ","); + while (token && i < 5) { + if (kstrtoint(token, 10, &values[i]) < 0) { + ret = -EINVAL; + break; + } + if (values[i] < 0 || values[i] > 100) { + ret = -EINVAL; + break; + } + i++; + token = strsep(&str, ","); + } + + if (i != 5) + ret = -EINVAL; + + if (ret == 0) { + for (i = 0; i < 5; i++) { + curve[i + 1] = values[i]; + } + } + + kfree(str); + return ret ? ret : count; +} + +static ssize_t fan_rampup_curve_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + return fan_curve_show(fan->rampup_curve, buf); +} + +static ssize_t fan_rampup_curve_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + return fan_curve_store(fan->rampup_curve, buf, count); +} + +static struct device_attribute dev_attr_fan_rampup_curve = + __ATTR(rampup_curve, 0644, fan_rampup_curve_show, fan_rampup_curve_store); + +static ssize_t fan_rampdown_curve_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + return fan_curve_show(fan->rampdown_curve, buf); +} + +static ssize_t fan_rampdown_curve_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_fan *fan = dev_get_drvdata(dev); + return fan_curve_store(fan->rampdown_curve, buf, count); +} + +static struct device_attribute dev_attr_fan_rampdown_curve = __ATTR( + rampdown_curve, 0644, fan_rampdown_curve_show, fan_rampdown_curve_store); + +static ssize_t temp_current_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_temp *temp = dev_get_drvdata(dev); + u8 val; + // TODO: handle error + ec_read(temp->reg, &val); + return sprintf(buf, "%u\n", val); +} + +static struct device_attribute dev_attr_temp_cur = + __ATTR(temp, 0444, temp_current_show, NULL); + +static ssize_t temp_min_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ec_temp *temp = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", temp->temp_min); +} + +static struct device_attribute dev_attr_temp_min = + __ATTR(min, 0444, temp_min_show, NULL); + +static ssize_t temp_max_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ec_temp *temp = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", temp->temp_max); +} + +static struct device_attribute dev_attr_temp_max = + __ATTR(max, 0444, temp_max_show, NULL); + +static ssize_t apu_power_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_apu *apu = dev_get_drvdata(dev); + u8 val; + // TODO: handle error + ec_read(apu->power_mode_reg, &val); + + const char *mode = "unknown"; + switch (val) { + case 0x00: + mode = "balanced"; + break; + case 0x01: + mode = "performance"; + break; + case 0x02: + mode = "quiet"; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t apu_power_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ec_apu *apu = dev_get_drvdata(dev); + u8 val; + if (sysfs_streq(buf, "balanced")) { + val = 0x00; + } else if (sysfs_streq(buf, "performance")) { + val = 0x01; + } else if (sysfs_streq(buf, "quiet")) { + val = 0x02; + } else { + return -EINVAL; + } + + ec_write(apu->power_mode_reg, val); + return count; +} + +static struct device_attribute dev_attr_apu_power_mode = + __ATTR(power_mode, 0644, apu_power_mode_show, apu_power_mode_store); + +static struct delayed_work ec_update_work; + +static void ec_update_worker(struct work_struct *work) +{ + u8 temp; + // TODO: handle error + ec_read(ec_temp.reg, &temp); + int i; + + // Update min/max + if (ec_temp.temp_min == 0 || temp < ec_temp.temp_min) + ec_temp.temp_min = temp; + + if (temp > ec_temp.temp_max) + ec_temp.temp_max = temp; + + // update fan level if curve mode is active + for (i = 0; i < ARRAY_SIZE(ec_fans); i++) { + struct ec_fan *fan = &ec_fans[i]; + + if (fan->mode == CURVE) { + u8 level = read_fan_level(fan); + if (level < 5 && temp >= fan->rampup_curve[level + 1]) { + write_fan_level(fan, level + 1); + } else if (level > 0 && temp <= fan->rampdown_curve[level]) + write_fan_level(fan, level - 1); + } + } + + // Requeue the work + schedule_delayed_work(&ec_update_work, + msecs_to_jiffies(1000)); // every 1 sec +} + +static dev_t ec_su_axb35_dev; +static struct class *ec_class; + +static int __init ec_su_axb35_init(void) +{ + int i; + int ret; + + ret = alloc_chrdev_region(&ec_su_axb35_dev, 0, ARRAY_SIZE(ec_fans) + 2, + "ec_su_axb35"); + if (ret < 0) { + pr_err("ec_su_axb35: Failed to allocation major number\n"); + return ret; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + ec_class = class_create("ec_su_axb35"); +#else + ec_class = class_create(THIS_MODULE, "ec_su_axb35"); +#endif + + if (IS_ERR(ec_class)) { + unregister_chrdev_region(ec_su_axb35_dev, ARRAY_SIZE(ec_fans) + 2); + return PTR_ERR(ec_class); + } + + for (i = 0; i < ARRAY_SIZE(ec_fans); i++) { + struct ec_fan *fan = &ec_fans[i]; + + fan->dev = device_create( + ec_class, NULL, MKDEV(MAJOR(ec_su_axb35_dev), i), fan, fan->name); + if (IS_ERR(fan->dev)) + continue; + + dev_set_drvdata(fan->dev, fan); + device_create_file(fan->dev, &dev_attr_fan_rpm); + device_create_file(fan->dev, &dev_attr_fan_mode); + device_create_file(fan->dev, &dev_attr_fan_level); + device_create_file(fan->dev, &dev_attr_fan_rampup_curve); + device_create_file(fan->dev, &dev_attr_fan_rampdown_curve); + update_fan_mode(fan); + } + + ec_temp.dev = device_create( + ec_class, NULL, MKDEV(MAJOR(ec_su_axb35_dev), ARRAY_SIZE(ec_fans)), + &ec_temp, ec_temp.name); + if (!IS_ERR(ec_temp.dev)) { + dev_set_drvdata(ec_temp.dev, &ec_temp); + device_create_file(ec_temp.dev, &dev_attr_temp_cur); + device_create_file(ec_temp.dev, &dev_attr_temp_min); + device_create_file(ec_temp.dev, &dev_attr_temp_max); + } + + ec_apu.dev = device_create( + ec_class, NULL, MKDEV(MAJOR(ec_su_axb35_dev), ARRAY_SIZE(ec_fans) + 1), + &ec_apu, ec_apu.name); + if (!IS_ERR(ec_apu.dev)) { + dev_set_drvdata(ec_apu.dev, &ec_apu); + device_create_file(ec_apu.dev, &dev_attr_apu_power_mode); + } + + INIT_DELAYED_WORK(&ec_update_work, ec_update_worker); + schedule_delayed_work(&ec_update_work, msecs_to_jiffies(1000)); + + pr_info("ec_su_axb35: Sixunited AXB35-02 EC driver loaded\n"); + return 0; +} + +static void __exit ec_su_axb35_exit(void) +{ + int i; + + /* Reset all fans to AUTO mode before unloading */ + for (i = 0; i < ARRAY_SIZE(ec_fans); i++) { + struct ec_fan *fan = &ec_fans[i]; + u8 val; + + fan->mode = AUTO; + + switch (fan->mode_reg) { + case 0x21: // Fan 1 + val = 0x10; + break; + case 0x23: // Fan 2 + val = 0x20; + break; + case 0x25: // Fan 3 + val = 0x30; + break; + default: + continue; + } + ec_write(fan->mode_reg, val); + pr_info("ec_su_axb35: %s reset to AUTO mode\n", fan->name); + } + + for (i = 0; i < ARRAY_SIZE(ec_fans); i++) { + if (!IS_ERR(ec_fans[i].dev)) { + device_remove_file(ec_fans[i].dev, &dev_attr_fan_rpm); + device_remove_file(ec_fans[i].dev, &dev_attr_fan_mode); + device_remove_file(ec_fans[i].dev, &dev_attr_fan_level); + device_remove_file(ec_fans[i].dev, &dev_attr_fan_rampup_curve); + device_remove_file(ec_fans[i].dev, &dev_attr_fan_rampdown_curve); + device_destroy(ec_class, MKDEV(MAJOR(ec_su_axb35_dev), i)); + } + } + + if (!IS_ERR(ec_temp.dev)) { + device_remove_file(ec_temp.dev, &dev_attr_temp_cur); + device_remove_file(ec_temp.dev, &dev_attr_temp_min); + device_remove_file(ec_temp.dev, &dev_attr_temp_max); + device_destroy(ec_class, + MKDEV(MAJOR(ec_su_axb35_dev), ARRAY_SIZE(ec_fans))); + } + + if (!IS_ERR(ec_apu.dev)) { + device_remove_file(ec_apu.dev, &dev_attr_apu_power_mode); + device_destroy(ec_class, + MKDEV(MAJOR(ec_su_axb35_dev), ARRAY_SIZE(ec_fans) + 1)); + } + + cancel_delayed_work_sync(&ec_update_work); + + class_destroy(ec_class); + unregister_chrdev_region(ec_su_axb35_dev, ARRAY_SIZE(ec_fans) + 2); + pr_info("ec_su_axb35: Module unloaded\n"); +} + +module_init(ec_su_axb35_init); +module_exit(ec_su_axb35_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("loom@mopper.de"); +MODULE_DESCRIPTION("Sixunited AXB35-02 Embedded Controller driver"); diff --git a/os/mini/board.sh b/os/mini/board.sh new file mode 100755 index 00000000..142415c0 --- /dev/null +++ b/os/mini/board.sh @@ -0,0 +1,233 @@ +#!/bin/sh +# Copyright 2026 bong-water-water-bong +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# +# 1bit-board: fans and power profile on the boards this image knows. +# +# The firmware's own fan curve is conservative, so on a known board this sets the EC's fan curve plus a +# predictive boost (level 4 on load, back to the curve when quiet) and applies the power profile. All of it +# goes through the EC driver (ec_su_axb35), never through debug registers. On a board it does not know it +# writes nothing at all and says so: the firmware curve stays. +# +# Usage (root): 1bit-board known | curve | boost | status | power | power-status +# known exit 0 when this board's EC is one we drive (DMI match), 1 otherwise +# curve write the EC fan curve (mode=curve + ramp up/down) to fan1/fan2 +# boost the 0.5 s predictive-boost loop (init starts it in the background) +# status one line: fans: controlled (fan1 N rpm, fan2 N rpm) / not controlled (reason) +# power apply the power profile (EC power mode, SMU limits if the tool is present, clock pins) +# power-status one line: power: , / not applied (reason) +set -u + +EC=${EC_DIR:-/sys/class/ec_su_axb35} +POWER_CONF=${POWER_CONF:-/etc/1bit/power} +UP=${FAN_UP:-45,54,62,70,76} # level 1..5 engage at these EC temperatures (C); level 5 from 82 C +DOWN=${FAN_DOWN:-41,50,58,66,72} # release points: 4 C hysteresis, so the curve does not hunt +FANS=${FAN_NAMES:-"fan1 fan2"} # fan3 (system fan) is left alone: it reads 0 rpm on this board +BOOST_LEVEL=${BOOST_LEVEL:-4} # 80 %: level 5 is never the boost level +BOOST_HOT5_C=${BOOST_HOT5_C:-82} # above this Tctl the boost uses level 5 (100 %) and stays there to 76 C +BOOST_W=${BOOST_W:-35} # package watts that count as load +BOOST_BUSY=${BOOST_BUSY:-40} # GPU busy % that counts as load +BOOST_HOT_C=${BOOST_HOT_C:-70} # Tctl that keeps the boost on regardless of power +BOOST_SLOPE=${BOOST_SLOPE:-3} # Tctl rise per poll that counts as load +BOOST_HOLD=${BOOST_HOLD:-20} # quiet seconds before releasing to the curve +POWER_MODE=${POWER_MODE:-performance} # EC power mode: quiet | balanced | performance +STAPM_W=${STAPM_W:-90} # 90 W sustained, the profile the development box runs +SLOW_W=${SLOW_W:-90} +FAST_W=${FAST_W:-105} +TCTL_C=${TCTL_C:-95} +FCLK=${FCLK:-7} # amdgpu pp_dpm_fclk index, "auto" to leave it alone +SOCCLK=${SOCCLK:-6} # amdgpu pp_dpm_socclk index +SKIP_POWER=${SKIP_POWER:-0} +RYZENADJ=${RYZENADJ:-} + +say() { printf '%s\n' "$*"; } +die() { printf '1bit-board: %s\n' "$*" >&2; exit 1; } +# BusyBox sleep may not do fractions; a one-second poll still beats the firmware curve. +nap() { sleep 0.5 2>/dev/null || sleep 1; } +hwmon() { for h in /sys/class/hwmon/*; do [ "$(cat "$h/name" 2>/dev/null)" = "$1" ] && echo "$h"; done; } + +dmi() { cat "/sys/class/dmi/id/$1" 2>/dev/null | tr '[:upper:]' '[:lower:]'; } +known_board() { + # Sixunited AXB35-02, sold as BOSGAME M5 "BeyondMax Series": the one board whose EC we drive today. + case "$(dmi product_name) $(dmi board_name)" in + *beyondmax* | *axb35*) return 0 ;; + *) return 1 ;; + esac +} + +ec_ready() { # the driver loaded and the interface we need is there + [ -d "$EC" ] && [ -r "$EC/temp1/temp" ] || return 1 + for f in $FANS; do [ -w "$EC/$f/mode" ] || return 1; done + return 0 +} + +curve() { + ec_ready || die "no EC driver at $EC (nothing written)" + known_board || die "not a known board (nothing written)" + for f in $FANS; do + printf '%s\n' "$UP" > "$EC/$f/rampup_curve" || die "cannot write $EC/$f/rampup_curve" + printf '%s\n' "$DOWN" > "$EC/$f/rampdown_curve" || die "cannot write $EC/$f/rampdown_curve" + printf '%s\n' curve > "$EC/$f/mode" || die "cannot write $EC/$f/mode" + done + say "fans: curve $UP / $DOWN on $FANS (the EC applies it on its own sensor)" +} + +set_fans() { # set_fans fixed|curve [level] + for f in $FANS; do + printf '%s\n' "$1" > "$EC/$f/mode" 2>/dev/null + [ "${2:-}" ] && printf '%s\n' "$2" > "$EC/$f/level" 2>/dev/null + done +} + +boost() { + ec_ready || die "no EC driver at $EC (boost not started)" + known_board || die "not a known board (boost not started)" + K=$(hwmon k10temp); G=$(hwmon amdgpu); B=$(ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | head -n 1) + [ -n "$K" ] && [ -n "$G" ] || die "no k10temp/amdgpu hwmon (boost not started)" + set_fans curve + on=0; quiet=$(date +%s); last=$(( $(cat "$K/temp1_input" 2>/dev/null || echo 0) / 1000 )); lvl=$BOOST_LEVEL + trap 'set_fans curve; exit 0' TERM INT + say "fans: boost loop up (level $BOOST_LEVEL on load, $BOOST_HOLD s hold, ${BOOST_HOT5_C} C -> level 5)" + while :; do + tc=$(( $(cat "$K/temp1_input" 2>/dev/null || echo 0) / 1000 )) + w=$(( $(cat "$G/power1_input" 2>/dev/null || echo 0) / 1000000 )) + busy=$(cat "$B" 2>/dev/null || echo 0) + now=$(date +%s); load=0 + if [ "$w" -ge "$BOOST_W" ] || [ "$busy" -ge "$BOOST_BUSY" ] || [ "$tc" -ge "$BOOST_HOT_C" ] \ + || [ $(( tc - last )) -ge "$BOOST_SLOPE" ]; then load=1; fi + last=$tc + want=$BOOST_LEVEL + if [ "$tc" -ge "$BOOST_HOT5_C" ]; then want=5 + elif [ "$lvl" = 5 ] && [ "$tc" -gt $(( BOOST_HOT5_C - 6 )) ]; then want=5; fi + if [ "$load" = 1 ]; then + quiet=$now + if [ "$on" = 0 ]; then lvl=$want; set_fans fixed "$lvl"; on=1 + say "fans: boost ${w}W busy=${busy}% Tctl=${tc}C level $lvl" + elif [ "$want" != "$lvl" ]; then lvl=$want; set_fans fixed "$lvl"; say "fans: level $lvl at Tctl=${tc}C" + fi + # something else (the curve, a manual test) may have moved the mode: re-assert it + [ "$(cat "$EC/fan1/mode" 2>/dev/null)" = fixed ] && \ + [ "$(cat "$EC/fan1/level" 2>/dev/null)" = "$lvl" ] || set_fans fixed "$lvl" + elif [ "$on" = 1 ] && [ $(( now - quiet )) -ge "$BOOST_HOLD" ]; then + set_fans curve; on=0; say "fans: back to the curve (${w}W Tctl=${tc}C)" + fi + nap + done +} + +status() { # one line, for the banner, the health output and the release check + if ! known_board; then + say "fans: not controlled (this board is not one we know; the firmware curve stays)" + return 0 + fi + if ! ec_ready; then + say "fans: not controlled (no EC driver: the firmware curve stays)" + return 1 + fi + line="" + for f in $FANS; do + rpm=$(cat "$EC/$f/rpm" 2>/dev/null || echo 0) + line="$line $f $rpm rpm" + done + # informational: at low temperatures the EC legitimately leaves the fans stopped (level 0), so a + # 0 rpm reading here is not a fault. The release check forces level 4 and then expects rpm > 0. + say "fans: controlled ($(printf '%s' "$line" | sed 's/^ //'))" + return 0 +} + +ryzenadj_bin() { + [ -n "$RYZENADJ" ] && { [ -x "$RYZENADJ" ] && echo "$RYZENADJ"; return 0; } + for p in /opt/1bit/ryzenadj /usr/local/bin/ryzenadj /usr/bin/ryzenadj; do + [ -x "$p" ] && { echo "$p"; return 0; } + done + command -v ryzenadj 2>/dev/null && return 0 + return 1 +} + +power_conf() { # read KEY=value overrides from $POWER_CONF without executing it + [ -r "$POWER_CONF" ] || return 0 + while IFS= read -r line; do + case "$line" in ''|'#'*) continue ;; esac + key=${line%%=*}; val=${line#*=} + key=$(printf '%s' "$key" | tr -d ' \t') + val=$(printf '%s' "$val" | sed 's/[[:space:]]*#.*$//; s/^[[:space:]]*//; s/[[:space:]]*$//') + case "$key" in + POWER_MODE|STAPM_W|SLOW_W|FAST_W|TCTL_C|FCLK|SOCCLK|SKIP_POWER|RYZENADJ|FAN_UP|FAN_DOWN) + eval "CONF_$key=\$val" ;; + esac + done < "$POWER_CONF" + for k in POWER_MODE STAPM_W SLOW_W FAST_W TCTL_C FCLK SOCCLK SKIP_POWER RYZENADJ; do + eval "v=\${CONF_$k:-}"; [ -n "$v" ] && eval "$k=\$v" + done + return 0 +} + +power() { + power_conf + [ "$SKIP_POWER" = 1 ] && { say "power: not applied (SKIP_POWER=1 in $POWER_CONF)"; return 0; } + notes="" + if [ -w "$EC/apu/power_mode" ]; then + # the EC power mode resets the SMU limits, so it goes first - same order as the development box + printf '%s\n' "$POWER_MODE" > "$EC/apu/power_mode" 2>/dev/null && notes="$notes mode=$POWER_MODE" \ + || notes="$notes mode=write-failed" + sleep 2 + else + notes="$notes mode=no-ec" + fi + if [ -n "$RYZENADJ" ] && [ ! -x "$RYZENADJ" ]; then + notes="$notes cap=failed(RYZENADJ=$RYZENADJ is not executable)" + elif RJ=$(ryzenadj_bin); then + if "$RJ" --stapm-limit=$(( STAPM_W * 1000 )) --slow-limit=$(( SLOW_W * 1000 )) \ + --fast-limit=$(( FAST_W * 1000 )) --tctl-temp="$TCTL_C" >/dev/null 2>&1; then + notes="$notes cap=${STAPM_W}/${SLOW_W}/${FAST_W}W tctl=$TCTL_C" + else + notes="$notes cap=failed($RJ)" + fi + else + notes="$notes cap=not-applied(no ryzenadj; the EC profile alone is in force)" + fi + G=$(ls -d /sys/class/drm/card*/device 2>/dev/null | head -n 1) + if [ -n "$G" ] && [ -w "$G/power_dpm_force_performance_level" ]; then + if [ "$FCLK" = auto ]; then + printf 'auto\n' > "$G/power_dpm_force_performance_level" 2>/dev/null && notes="$notes clocks=auto" + else + printf 'manual\n' > "$G/power_dpm_force_performance_level" 2>/dev/null + printf '%s\n' "$FCLK" > "$G/pp_dpm_fclk" 2>/dev/null + printf '%s\n' "$SOCCLK" > "$G/pp_dpm_socclk" 2>/dev/null + notes="$notes fclk=$FCLK socclk=$SOCCLK" + fi + fi + say "power:$notes" +} + +power_status() { + mode=$(cat "$EC/apu/power_mode" 2>/dev/null || echo none) + if RJ=$(ryzenadj_bin) && info=$("$RJ" --info 2>/dev/null); then + lim=$(printf '%s\n' "$info" | sed -n 's/^STAPM LIMIT *| *\([0-9.]*\).*/\1/p' | head -n 1) + say "power: $mode mode, STAPM ${lim:-?} W" + else + say "power: $mode mode, no SMU limits readable (ryzenadj absent or not permitted: the EC profile alone is in force)" + fi +} + +case "${1:-}" in + known) known_board && exit 0 || exit 1 ;; + curve) curve ;; + boost) boost ;; + status) status ;; + power) power ;; + power-status) power_status ;; + *) say "usage: 1bit-board known|curve|boost|status|power|power-status" >&2; exit 2 ;; +esac diff --git a/os/mini/init.sh b/os/mini/init.sh index 2e8e06fc..257909b9 100644 --- a/os/mini/init.sh +++ b/os/mini/init.sh @@ -199,6 +199,17 @@ else say "[gpu] hwmon: $(for h in /sys/class/hwmon/hwmon*; do cat $h/name 2>/dev/null; done | tr '\n' ' ')" fi + +# fans and power: the EC driver exists only for the boards we know (os/mini/board.sh). On any other +# board nothing is written at all and the firmware's own fan curve stays. +if [ -x /usr/sbin/1bit-board ] && /usr/sbin/1bit-board known 2>/dev/null; then + modprobe -q ec_su_axb35 + say "[fans] curve: $(/usr/sbin/1bit-board curve 2>&1 | head -n 1)" + say "[power] $(/usr/sbin/1bit-board power 2>&1 | head -n 1)" + setsid /usr/sbin/1bit-board boost > /tmp/fans.log 2>&1 & +else + say "[fans] not controlled: this board is not one we know, the firmware curve stays" +fi # HRX: TheRock's HSA runtime (1bit serve also finds it at its compiled-in path), and the Loom # kernel cache on the stick, so kernels compiled on the first boot are reused after a reboot [ -s /etc/1bit/libhsa ] && export IREE_HAL_AMDGPU_LIBHSA_PATH="$(cat /etc/1bit/libhsa)" @@ -295,6 +306,16 @@ else touch /etc/1bit/live 2>/dev/null fi echo "GPU: $(ls /dev/dri/renderD* /dev/kfd 2>/dev/null | tr '\n' ' ') NPU: $(ls /dev/accel/accel* 2>/dev/null | tr '\n' ' ')" + +# health: fans, power profile and the two temperatures this board reports +if [ -x /usr/sbin/1bit-board ]; then + say "[fans] $(/usr/sbin/1bit-board status 2>/dev/null)" + say "[power] $(/usr/sbin/1bit-board power-status 2>/dev/null)" +fi +TJ=$(for h in /sys/class/hwmon/hwmon*; do [ "$(cat "$h/name" 2>/dev/null)" = k10temp ] && echo "$h"; done | head -n 1) +if [ -n "$TJ" ]; then + say "temps: Tctl $(( $(cat "$TJ/temp1_input" 2>/dev/null || echo 0) / 1000 ))C EC $(cat /sys/class/ec_su_axb35/temp1/temp 2>/dev/null || echo n/a)C" +fi echo "IP: $(ip -4 -o addr show scope global | awk '{print $2, $4}' | tr '\n' ' ')" echo; echo "Lemonade's web app and API: http://$lhost:$lport (from another machine: ssh -L $lport:localhost:$lport root@)" echo "Prove the engine answers from this boot: 1bit-selftest (console or SSH; logs to /data/selftest.log)" @@ -327,6 +348,12 @@ if grep -q "1bit.check" /proc/cmdline; then if find / -xdev \( -name 'libxrt*' -o -name 'libopenvino*' -o -name 'libonnxruntime*' \) 2>/dev/null | grep -q .; then echo "CHECK runtime deps: FAILED (an external accelerator runtime is in the image)"; ok=0 else echo "CHECK runtime deps: self-contained"; fi + if [ -x /usr/sbin/1bit-board ] && /usr/sbin/1bit-board known 2>/dev/null; then + if /usr/sbin/1bit-board status > /dev/null 2>&1; then echo "CHECK fans: controlled" + else echo "CHECK fans: FAILED"; ok=0; fi + else + echo "CHECK fans: not controlled (no EC driver on this board)" + fi [ -e /lib/firmware/amdnpu/17f0_11 ] && echo "CHECK NPU firmware: present" || { echo "CHECK NPU firmware: FAILED"; ok=0; } dropbear -V 2>&1 | grep -q Dropbear && echo "CHECK dropbear: runs" || { echo "CHECK dropbear: FAILED"; ok=0; } echo "CHECK modules: $(find /lib/modules -name '*.ko*' | wc -l) kernel modules, amdgpu $(modinfo -F version amdgpu >/dev/null 2>&1 && echo found || echo MISSING)" diff --git a/os/mini/mkimage.sh b/os/mini/mkimage.sh index d0f82299..84453180 100755 --- a/os/mini/mkimage.sh +++ b/os/mini/mkimage.sh @@ -123,6 +123,28 @@ for m in $mods; do modprobe -S "$kver" --show-depends "$m" 2>/dev/null | awk '$1 == "insmod" {print $2}' done | sort -u | while read -r ko; do mkdir -p "$root$(dirname "$ko")"; cp "$ko" "$root$ko"; done cp /lib/modules/"$kver"/modules.{order,builtin,builtin.modinfo} "$root/lib/modules/$kver/" 2>/dev/null || true + +echo "== board driver (ec_su_axb35, GPL-2.0, source shipped in os/licenses/ec_su_axb35)" +# The fans and the EC power mode are driven by the board's embedded controller and the kernel has no +# driver for it, so it is built here, out of tree, against the image kernel - from the source this +# repository ships next to it (GPL-2.0: the binary in the image always travels with its source). +ecsrc=$licenses/ec_su_axb35/src +if [ "${EC_MODULE:-build}" = skip ]; then + echo " skipped (EC_MODULE=skip): this image will leave the fans on the firmware curve" +elif [ ! -d "$ecsrc" ]; then + echo " os/licenses/ec_su_axb35/src is missing: the fans would stay on the firmware curve"; exit 1 +else + ecbuild=$(mktemp -d) + cp -a "$ecsrc"/. "$ecbuild"/ + if make -s -C "$ecbuild" KERNEL_BUILD=/lib/modules/"$kver"/build modules > "$out/logs/ec_su_axb35-build.log" 2>&1; then + mkdir -p "$root/lib/modules/$kver/updates" + cp "$ecbuild/ec_su_axb35.ko" "$root/lib/modules/$kver/updates/" + echo " ec_su_axb35.ko for $kver: $(stat -c %s "$root/lib/modules/$kver/updates/ec_su_axb35.ko") bytes" + else + echo " build FAILED (see $out/logs/ec_su_axb35-build.log); set EC_MODULE=skip to ship without it"; exit 1 + fi + rm -rf "$ecbuild" +fi # The NPU needs the PRIVATE XDNA driver build, not the stock module in /lib/modules: with the stock one # the engine's NPU route resets the machine. Same vermagic, so it is a straight drop-in. xdna=${AMDXDNA_KO:-$HOME/wt/perfopt-7.3-obj/drivers/accel/amdxdna/amdxdna.ko} @@ -259,6 +281,24 @@ cp /usr/sbin/mke2fs "$root/bin/mke2fs"; chmod +x "$root/bin/mke2fs" copy_libs /usr/sbin/mke2fs cp /etc/mke2fs.conf "$root/etc/mke2fs.conf" cp "$here/release-check.sh" "$root/bin/1bit-release-check"; chmod +x "$root/bin/1bit-release-check" +install -m 0755 "$here/board.sh" "$root/usr/sbin/1bit-board" +mkdir -p "$root/etc/1bit" +install -m 0644 "$here/power.conf.example" "$root/etc/1bit/power" +# The SMU limits (the 90 W sustained cap) are set through ryzenadj, which is LGPL-3.0: ship the tool and +# its licence. A builder without it still works - the EC power mode alone applies and the image says so. +RJ=${RYZENADJ:-$(command -v ryzenadj 2>/dev/null || echo /usr/local/bin/ryzenadj)} +if [ -x "$RJ" ]; then + mkdir -p "$root/opt/1bit" + cp -L "$RJ" "$root/opt/1bit/ryzenadj"; chmod 0755 "$root/opt/1bit/ryzenadj" + for lib in $(ldd "$RJ" 2>/dev/null | awk '/=> \//{print $3}'); do + case "$lib" in */libc.so*|*/libm.so*|*/ld-linux*) continue ;; esac + if [ ! -e "$root$lib" ]; then mkdir -p "$root$(dirname "$lib")"; cp -L "$lib" "$root$lib"; fi + done + cp /usr/share/common-licenses/LGPL-3 "$doc/ryzenadj-LGPL-3" 2>/dev/null || true + echo " ryzenadj shipped as /opt/1bit/ryzenadj (LGPL-3.0, licence in /usr/share/licenses/1bit-os)" +else + echo " no ryzenadj on the builder: the image applies the EC power mode only (90 W cap not applied)" +fi cp "$here/udhcpc.sh" "$root/etc/udhcpc.script"; chmod +x "$root/etc/udhcpc.script" echo "1bit-os" > "$root/etc/hostname" printf 'root:x:0:0:root:/root:/bin/sh\n' > "$root/etc/passwd" @@ -274,6 +314,10 @@ cp "$here/../LICENSES.md" "$src/LICENSE" "$src/NOTICE" "$src/LICENSE-BINARY" "$s cp "$src/third_party/lemonade/LICENSE" "$doc/lemonade-LICENSE" || { echo "no third_party/lemonade/LICENSE (git submodule update --init third_party/lemonade)"; exit 1; } cp "$licenses/llamacpp-hrx/ROCR-Runtime-LICENSE" "$doc/ROCR-Runtime-LICENSE" # TheRock's libhsa for HRX +# the board driver is GPL-2.0: its source and licence travel in the image, next to the binary +mkdir -p "$doc/ec_su_axb35" +cp -a "$licenses/ec_su_axb35/LICENSE" "$licenses/ec_su_axb35/SOURCE" "$doc/ec_su_axb35/" 2>/dev/null || true +if [ -d "$licenses/ec_su_axb35/src" ]; then cp -a "$licenses/ec_su_axb35/src" "$doc/ec_su_axb35/src"; fi cp -rL /usr/share/common-licenses "$root/usr/share/" # the Ubuntu package behind each file in the image, found at its path or its merged-/usr alias (cd "$root" && find . -type f | cut -c2-) > "$out/image-files.txt" diff --git a/os/mini/power.conf.example b/os/mini/power.conf.example new file mode 100644 index 00000000..9a3d1e42 --- /dev/null +++ b/os/mini/power.conf.example @@ -0,0 +1,22 @@ +# /etc/1bit/power — power profile overrides for 1bit OS. +# +# At boot the image applies: the EC power mode, then the SMU limits (STAPM / slow / fast, plus the +# die temperature limit) through ryzenadj when the image carries it, then the fabric/SoC clock pins. +# Order matters: an EC power-mode write resets the SMU limits, so the mode goes first. +# +# Uncomment and edit any line; the file is read as KEY=value, nothing in it is executed. +# Defaults are the profile the 1bit development box runs (performance mode, 90 W sustained). +# +#POWER_MODE=performance # quiet | balanced | performance (the EC's own power mode) +#STAPM_W=90 # sustained package power limit, watts +#SLOW_W=90 # slow (average) limit, watts +#FAST_W=105 # fast (burst) limit, watts +#TCTL_C=95 # die temperature limit, degrees C +#FCLK=7 # amdgpu pp_dpm_fclk index, or auto to let the driver decide +#SOCCLK=6 # amdgpu pp_dpm_socclk index +#RYZENADJ=/opt/1bit/ryzenadj # the SMU tool; empty = search /opt/1bit, /usr/local/bin, /usr/bin +#SKIP_POWER=1 # do not touch power at all (the firmware profile stays) +# +# Fans (the curve the EC applies on its own sensor; levels 1..5 = 20/40/60/80/100 %): +#FAN_UP=45,54,62,70,76 +#FAN_DOWN=41,50,58,66,72 diff --git a/os/mini/release-check.sh b/os/mini/release-check.sh index 6cd68ef6..bd70fe17 100755 --- a/os/mini/release-check.sh +++ b/os/mini/release-check.sh @@ -90,6 +90,34 @@ else skip npu "no NPU in this VM: /dev/accel and /lib/firmware/amdnpu only appear on the machine" fi +# ── fans (EC fan curve; only a board we know has one we drive) ────────────────────────────────── +if [ ! -x /usr/sbin/1bit-board ]; then + skip fans "no /usr/sbin/1bit-board in this image: the fans stay on the firmware curve" +elif ! /usr/sbin/1bit-board known; then + skip fans "this board has no EC we drive: the firmware fan curve stays" +elif [ ! -d /sys/class/ec_su_axb35 ]; then + skip fans "ec_su_axb35 did not load: the firmware fan curve stays" +else + # level 4 (the boost level) for 5 s, then every CPU fan must report rpm > 0. A fan that reads 0 rpm + # at 80 % is exactly the fault this row exists for - it is how the system fan failed on 2026-10-04. + for f in fan1 fan2; do + printf 'fixed\n' > "/sys/class/ec_su_axb35/$f/mode" 2>/dev/null + printf '4\n' > "/sys/class/ec_su_axb35/$f/level" 2>/dev/null + done + sleep 5 + RPMS=; BAD=0 + for f in fan1 fan2; do + r=$(cat "/sys/class/ec_su_axb35/$f/rpm" 2>/dev/null || echo 0) + RPMS="$RPMS$f $r rpm, " + [ "$r" -gt 0 ] 2>/dev/null || BAD=1 + done + for f in fan1 fan2; do printf 'curve\n' > "/sys/class/ec_su_axb35/$f/mode" 2>/dev/null; done + RPMS=$(printf '%s' "$RPMS" | sed 's/, $//') + if [ "$BAD" = 0 ]; then pass fans "controlled ($RPMS at level 4)" + else fail fans "the driver loaded but a fan reads 0 rpm at level 4 ($RPMS): the curve and the boost cannot cool this board" + fi +fi + # ── Lemonade, with and without the key ────────────────────────────────────────────────────────── if [ -z "$KEY" ]; then fail lemonade "no LEMONADE_API_KEY in /data/lemonade/lemond.conf: a network-facing server without a key" From 8538f82852b82f3d8f2922359161403fef223ea4 Mon Sep 17 00:00:00 2001 From: bong-water-water-bong Date: Sat, 24 Oct 2026 04:50:57 -0300 Subject: [PATCH 2/3] os: pin ryzenadj, sign the board driver, and fall back to the EC profile when the cap cannot be written MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things the review asked for, plus the two Secure Boot traps that would have bitten the image: - **ryzenadj is pinned, not conditional.** `os/licenses/ryzenadj` carries LGPL-3.0, a SOURCE naming tag v0.19.0 (commit ea71739b) with its build recipe, and that tag's source verbatim. `mkimage.sh` now builds it from that source (cmake; needs cmake, a C compiler and libpci-dev) instead of copying whatever the build machine happens to have, installs `/opt/1bit/ryzenadj`, and puts the source in the image next to the binary. The 90 W cap is deterministic. - **The board driver is signed with the module-signing MOK.** An out-of-tree module has to be signed by a key the kernel trusts; it must be the MOK, because the "Secure Boot Kernel" cert carries no module-signing OID (verified: amdxdna.ko and ec_su_axb35.ko on the development box are both signed by "strixhalo Secure Boot MOK"). `mkimage.sh` signs with MOK.priv/MOK.der through the kernel's sign-file, overridable with MODULE_SIGN_KEY/MODULE_SIGN_CERT, and warns loudly when it cannot. - **The SMU cap has a defined fallback.** If the limits cannot be written - lockdown refuses /dev/mem, a kernel without it, or no tool - `1bit-board power` falls back to the EC's own `balanced` profile (85 W sustained, 120 W burst) instead of leaving the board in `performance` with no cap, and the boot banner says which cap is in force (`power: … cap=90/90/105W …` or `… fallback=balanced …`). `FALLBACK_MODE` is in `/etc/1bit/power`. - `/etc/1bit/power` now documents that its defaults are the development box's profile (performance, 90/90/105 W, level-5 trip at 82 C unchanged) and marks the fan trip points as the first thing to revisit on a hotter unit. - `tools/copyright.py` knows `*.example` (a '#'-comment file; the template ships as /etc/1bit/power). Image boot on hardware and the VM test still pending (build lane). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: bong-water-water-bong --- os/LICENSES.md | 4 +- os/licenses/ryzenadj/LICENSE | 165 + os/licenses/ryzenadj/SOURCE | 12 + .../ryzenadj/src/.github/workflows/build.yml | 96 + .../src/.github/workflows/release.yml | 191 ++ os/licenses/ryzenadj/src/.gitignore | 350 ++ os/licenses/ryzenadj/src/CMakeLists.txt | 79 + os/licenses/ryzenadj/src/LICENSE | 165 + os/licenses/ryzenadj/src/README.md | 210 ++ os/licenses/ryzenadj/src/argparse.c | 435 +++ os/licenses/ryzenadj/src/argparse.h | 133 + .../ryzenadj/src/examples/pmtable-example.py | 59 + os/licenses/ryzenadj/src/examples/readjust.py | 70 + os/licenses/ryzenadj/src/lib/api.c | 2825 +++++++++++++++++ os/licenses/ryzenadj/src/lib/cpuid.c | 131 + .../ryzenadj/src/lib/linux/osdep_linux.c | 94 + .../ryzenadj/src/lib/linux/osdep_linux_mem.c | 98 + .../ryzenadj/src/lib/linux/osdep_linux_mem.h | 15 + .../lib/linux/osdep_linux_smu_kernel_module.c | 122 + .../lib/linux/osdep_linux_smu_kernel_module.h | 13 + os/licenses/ryzenadj/src/lib/nb_smu_ops.c | 174 + os/licenses/ryzenadj/src/lib/nb_smu_ops.h | 82 + os/licenses/ryzenadj/src/lib/ryzenadj.h | 180 ++ os/licenses/ryzenadj/src/lib/ryzenadj_priv.h | 27 + os/licenses/ryzenadj/src/lib/win32/OlsApi.h | 580 ++++ os/licenses/ryzenadj/src/lib/win32/OlsDef.h | 63 + .../ryzenadj/src/lib/win32/osdep_win32.cpp | 132 + os/licenses/ryzenadj/src/main.c | 346 ++ .../win32/RyzenAdjServiceTask.xml.template | 43 + .../ryzenadj/src/win32/WinRing0x64.dll | Bin 0 -> 62976 bytes .../ryzenadj/src/win32/WinRing0x64.exp | Bin 0 -> 8115 bytes .../ryzenadj/src/win32/WinRing0x64.lib | Bin 0 -> 15124 bytes .../ryzenadj/src/win32/WinRing0x64.sys | Bin 0 -> 14544 bytes os/licenses/ryzenadj/src/win32/demo.bat | 2 + os/licenses/ryzenadj/src/win32/inpoutx64.dll | Bin 0 -> 98304 bytes os/licenses/ryzenadj/src/win32/inpoutx64.lib | Bin 0 -> 5192 bytes .../ryzenadj/src/win32/installServiceTask.bat | 39 + .../ryzenadj/src/win32/readjustService.ps1 | 464 +++ .../src/win32/uninstallServiceTask.bat | 23 + os/mini/board.sh | 25 +- os/mini/mkimage.sh | 38 +- os/mini/power.conf.example | 38 +- tools/copyright.py | 2 +- 43 files changed, 7501 insertions(+), 24 deletions(-) create mode 100644 os/licenses/ryzenadj/LICENSE create mode 100644 os/licenses/ryzenadj/SOURCE create mode 100644 os/licenses/ryzenadj/src/.github/workflows/build.yml create mode 100644 os/licenses/ryzenadj/src/.github/workflows/release.yml create mode 100644 os/licenses/ryzenadj/src/.gitignore create mode 100644 os/licenses/ryzenadj/src/CMakeLists.txt create mode 100644 os/licenses/ryzenadj/src/LICENSE create mode 100644 os/licenses/ryzenadj/src/README.md create mode 100644 os/licenses/ryzenadj/src/argparse.c create mode 100644 os/licenses/ryzenadj/src/argparse.h create mode 100644 os/licenses/ryzenadj/src/examples/pmtable-example.py create mode 100644 os/licenses/ryzenadj/src/examples/readjust.py create mode 100644 os/licenses/ryzenadj/src/lib/api.c create mode 100644 os/licenses/ryzenadj/src/lib/cpuid.c create mode 100644 os/licenses/ryzenadj/src/lib/linux/osdep_linux.c create mode 100644 os/licenses/ryzenadj/src/lib/linux/osdep_linux_mem.c create mode 100644 os/licenses/ryzenadj/src/lib/linux/osdep_linux_mem.h create mode 100644 os/licenses/ryzenadj/src/lib/linux/osdep_linux_smu_kernel_module.c create mode 100644 os/licenses/ryzenadj/src/lib/linux/osdep_linux_smu_kernel_module.h create mode 100644 os/licenses/ryzenadj/src/lib/nb_smu_ops.c create mode 100644 os/licenses/ryzenadj/src/lib/nb_smu_ops.h create mode 100644 os/licenses/ryzenadj/src/lib/ryzenadj.h create mode 100644 os/licenses/ryzenadj/src/lib/ryzenadj_priv.h create mode 100644 os/licenses/ryzenadj/src/lib/win32/OlsApi.h create mode 100644 os/licenses/ryzenadj/src/lib/win32/OlsDef.h create mode 100644 os/licenses/ryzenadj/src/lib/win32/osdep_win32.cpp create mode 100644 os/licenses/ryzenadj/src/main.c create mode 100644 os/licenses/ryzenadj/src/win32/RyzenAdjServiceTask.xml.template create mode 100644 os/licenses/ryzenadj/src/win32/WinRing0x64.dll create mode 100644 os/licenses/ryzenadj/src/win32/WinRing0x64.exp create mode 100644 os/licenses/ryzenadj/src/win32/WinRing0x64.lib create mode 100644 os/licenses/ryzenadj/src/win32/WinRing0x64.sys create mode 100644 os/licenses/ryzenadj/src/win32/demo.bat create mode 100644 os/licenses/ryzenadj/src/win32/inpoutx64.dll create mode 100644 os/licenses/ryzenadj/src/win32/inpoutx64.lib create mode 100644 os/licenses/ryzenadj/src/win32/installServiceTask.bat create mode 100644 os/licenses/ryzenadj/src/win32/readjustService.ps1 create mode 100644 os/licenses/ryzenadj/src/win32/uninstallServiceTask.bat diff --git a/os/LICENSES.md b/os/LICENSES.md index 595dcfaf..eab89162 100644 --- a/os/LICENSES.md +++ b/os/LICENSES.md @@ -95,7 +95,7 @@ limits. Both are shipped with their licences, and the driver also with its sourc | Component | Release | License | Source | |---|---|---|---| | `ec_su_axb35` (fan curve, EC temperature, EC power mode on the AXB35-02 board) | commit `f62c2c228959a08683273a26ef3afd8991e69f6d`, built for the image kernel by `os/mini/mkimage.sh` | GPL-2.0-only | `os/licenses/ec_su_axb35/` in this repository; the same source is installed in the image at `/usr/share/licenses/1bit-os/ec_su_axb35/src`; upstream https://github.com/cmetz/ec-su_axb35-linux | -| `ryzenadj` (sets the SMU power limits; `/opt/1bit/ryzenadj` in the image) | built on the build machine, copied unmodified | LGPL-3.0, Copyright Jiaxun Yang and the RyzenAdj contributors | https://github.com/FlyGoat/RyzenAdj; the LGPL-3.0 text ships as `/usr/share/licenses/1bit-os/ryzenadj-LGPL-3` | +| `ryzenadj` (sets the SMU power limits; `/opt/1bit/ryzenadj` in the image) | **pinned v0.19.0**, commit `ea71739b4a3e1a0a624dfd1c9c268a31cb2a4182`, built by `os/mini/mkimage.sh` from the source in `os/licenses/ryzenadj` | LGPL-3.0, Copyright Jiaxun Yang and the RyzenAdj contributors | `os/licenses/ryzenadj/` in this repository (LICENSE, SOURCE, and the tag's source verbatim); the same source is installed in the image at `/usr/share/licenses/1bit-os/ryzenadj/src`; upstream https://github.com/FlyGoat/RyzenAdj | ## System software from Ubuntu @@ -135,5 +135,5 @@ source available; the MPL requires the same for its covered files. For each: | TheRock sysdeps in llamacpp-hrx (elfutils, libnl, numactl, libmnl) | the upstream tarballs TheRock builds, https://github.com/ROCm/TheRock/tree/main/third-party/sysdeps/linux (elfutils 0.195, libnl 3.12.0, numactl 2.0.19, libmnl 1.0.5 at the time of this review) | | libquadmath, libgfortran in moonshine | GCC, https://gcc.gnu.org | | `ec_su_axb35` (the board driver in the image) | the source as shipped in `os/licenses/ec_su_axb35/src`, upstream https://github.com/cmetz/ec-su_axb35-linux at commit `f62c2c228959a08683273a26ef3afd8991e69f6d` | -| `ryzenadj` | https://github.com/FlyGoat/RyzenAdj at the release the build machine carries (LGPL-3.0) | +| `ryzenadj` | the source as shipped in `os/licenses/ryzenadj/src`, upstream https://github.com/FlyGoat/RyzenAdj at tag v0.19.0 (commit `ea71739b4a3e1a0a624dfd1c9c268a31cb2a4182`), built by `os/mini/mkimage.sh` | | certifi, tqdm in moonshine (MPL-2.0) | the covered files are on the image as shipped (Python source and cacert.pem); https://pypi.org/project/certifi/, https://pypi.org/project/tqdm/ | diff --git a/os/licenses/ryzenadj/LICENSE b/os/licenses/ryzenadj/LICENSE new file mode 100644 index 00000000..0a041280 --- /dev/null +++ b/os/licenses/ryzenadj/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + 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 that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU 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 as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/os/licenses/ryzenadj/SOURCE b/os/licenses/ryzenadj/SOURCE new file mode 100644 index 00000000..f617296b --- /dev/null +++ b/os/licenses/ryzenadj/SOURCE @@ -0,0 +1,12 @@ +SMU power-limit tool: ryzenadj (sets STAPM / slow / fast PPT and the die temperature limit) +Ship: yes +Project: RyzenAdj, https://github.com/FlyGoat/RyzenAdj +Release: v0.19.0, commit ea71739b4a3e1a0a624dfd1c9c268a31cb2a4182 (2026-05-13, "Bump version to 0.19.0") +License: LGPL-3.0 (LICENSE, shipped here); the source in src/ is that tag, verbatim, and it is installed +into the image under /usr/share/licenses/1bit-os/ryzenadj/src. +Build: from the source in this directory, by os/mini/mkimage.sh (deterministic - the image never depends on + what the build machine happens to have): + cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j4 + needs cmake, gcc, pkg-config and libpci-dev on the build machine; links libpci, libudev, libz, libc. +Why it ships: the EC power mode alone gives the firmware's own profile; the 90 W sustained cap this image +ships needs the SMU limits written with this tool (through /dev/mem; see os/LICENSES.md and TOSFANS-STATE). diff --git a/os/licenses/ryzenadj/src/.github/workflows/build.yml b/os/licenses/ryzenadj/src/.github/workflows/build.yml new file mode 100644 index 00000000..494fb0e2 --- /dev/null +++ b/os/licenses/ryzenadj/src/.github/workflows/build.yml @@ -0,0 +1,96 @@ +name: Build + +on: + push: + pull_request: + +jobs: + Windows: + runs-on: windows-latest + + steps: + # Checks-out + - uses: actions/checkout@v4 + + - uses: ilammy/msvc-dev-cmd@v1 + + #don't use run-cmake for windows because only one build should add warnings to pull request + - name: Build Debug + run: | + mkdir debug + cd debug + cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Debug .. + nmake + + - name: Upload ryzenadj debug + uses: actions/upload-artifact@v4 + with: + name: ryzenadj-win64-debug + path: | + debug/ryzenadj.exe + debug/libryzenadj.dll + +# - name: Test Scripts +# shell: pwsh +# run: | +# cd release +# $expectedOutput = "Not AMD processor, must be kidding" +# $cmdDemoOutput = '' | cmd.exe /c .\demo.bat +# if ($cmdDemoOutput -notcontains $expectedOutput){ +# Write-Error "$cmdDemoOutput" +# exit 1 +# } +# $psServiceOutput = Powershell .\readjustService.ps1 -noGUI +# if ($psServiceOutput -ne $expectedOutput){ +# Write-Error "$psServiceOutput" +# exit 1 +# } +# $pythonOutput = python pmtable-example.py +# if ($pythonOutput -notcontains $expectedOutput){ +# Write-Error "$pythonOutput" +# exit 1 +# } +# $pythonOutput = python readjust.py +# if ($pythonOutput -notcontains $expectedOutput){ +# Write-Error "$pythonOutput" +# exit 1 +# } +# exit 0 + + Linux: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install libpci-dev + + - name: run-cmake #with support for inline error reporting + uses: lukka/run-cmake@v3.3 + + - name: Test make like readme + run: | + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + make + +# - name: Test Scripts +# shell: bash +# run: | +# cd examples +# export LD_LIBRARY_PATH="${{ github.workspace }}/build/" +# expectedOutput="Not AMD processor, must be kidding" +# pythonOutput=$(python pmtable-example.py 2>&1) || true +# if [[ ! "$pythonOutput" =~ "$expectedOutput" ]]; then +# echo "pmtable-example.py output: $pythonOutput" +# exit 1 +# fi +# pythonOutput=$(python readjust.py 2>&1) || true +# if [[ ! "$pythonOutput" =~ "$expectedOutput" ]]; then +# echo "readjust.py output: $pythonOutput" +# exit 1 +# fi +# exit 0 diff --git a/os/licenses/ryzenadj/src/.github/workflows/release.yml b/os/licenses/ryzenadj/src/.github/workflows/release.yml new file mode 100644 index 00000000..ed0e8a3c --- /dev/null +++ b/os/licenses/ryzenadj/src/.github/workflows/release.yml @@ -0,0 +1,191 @@ +name: Release + +on: + push: + workflow_dispatch: + release: + types: [created] + +permissions: + contents: write + +env: + RELEASE_TAG: ${{ github.event.release.tag_name || github.ref_name }} + +jobs: + manylinux_2_28: + name: manylinux_2_28 + runs-on: ubuntu-latest + container: quay.io/pypa/manylinux_2_28_x86_64 + + steps: + - name: Install build dependencies + run: | + yum install -y epel-release + yum install -y cmake3 git make pciutils-devel pkgconfig tar gzip + + - uses: actions/checkout@v4 + + - name: Build RyzenAdj + run: | + cmake3 -S . -B build \ + -DCMAKE_BUILD_TYPE=Release + cmake3 --build build --parallel + ldd build/ryzenadj + if ! ldd build/ryzenadj | grep -q libpci; then + echo "ryzenadj must link against shared libpci in the manylinux_2_28 package" + exit 1 + fi + + - name: Package + run: | + package="ryzenadj-manylinux_2_28-x86_64" + mkdir -p "dist/${package}" + cp build/ryzenadj "dist/${package}/" + cp build/libryzenadj.so "dist/${package}/" + cp lib/ryzenadj.h "dist/${package}/" + cp LICENSE README.md "dist/${package}/" + cp -r examples "dist/${package}/" + tar -czf "dist/${package}.tar.gz" -C dist "${package}" + + - uses: actions/upload-artifact@v4 + with: + name: release-manylinux_2_28 + path: dist/*.tar.gz + + musl-static: + name: musl-static + runs-on: ubuntu-latest + container: alpine:3.20 + + steps: + - name: Install build dependencies + run: | + apk add --no-cache build-base cmake curl file git linux-headers pkgconf tar + + - uses: actions/checkout@v4 + + - name: Build static libpci + env: + PCIUTILS_VERSION: 3.13.0 + run: | + curl -fsSLO "https://mj.ucw.cz/download/linux/pci/pciutils-${PCIUTILS_VERSION}.tar.gz" + tar -xf "pciutils-${PCIUTILS_VERSION}.tar.gz" + make -C "pciutils-${PCIUTILS_VERSION}" -j"$(nproc)" \ + PREFIX=/opt/pciutils-static \ + SHARED=no \ + ZLIB=no \ + DNS=no \ + HWDB=no \ + PCI_HAVE_PM_LINUX_SYSFS=1 \ + PCI_HAVE_PM_LINUX_PROC=1 \ + LIBKMOD=no + make -C "pciutils-${PCIUTILS_VERSION}" install-lib \ + PREFIX=/opt/pciutils-static \ + SHARED=no \ + ZLIB=no \ + DNS=no \ + HWDB=no \ + PCI_HAVE_PM_LINUX_SYSFS=1 \ + PCI_HAVE_PM_LINUX_PROC=1 \ + LIBKMOD=no + + - name: Build RyzenAdj + env: + PKG_CONFIG_PATH: /opt/pciutils-static/lib/pkgconfig + run: | + cmake -S . -B build-musl \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DPREFER_STATIC_LINKING=ON + cmake --build build-musl --parallel + + file build-musl/ryzenadj + if ! file build-musl/ryzenadj | grep -q "statically linked"; then + echo "ryzenadj must be a static executable in the musl-static package" + exit 1 + fi + + - name: Package + run: | + package="ryzenadj-linux-musl-static-x86_64" + mkdir -p "dist-musl/${package}" + cp build-musl/ryzenadj "dist-musl/${package}/" + cp build-musl/libryzenadj.a "dist-musl/${package}/" + cp lib/ryzenadj.h "dist-musl/${package}/" + cp LICENSE README.md "dist-musl/${package}/" + cp -r examples "dist-musl/${package}/" + tar -czf "dist-musl/${package}.tar.gz" -C dist-musl "${package}" + + - uses: actions/upload-artifact@v4 + with: + name: release-musl-static + path: dist-musl/*.tar.gz + + windows: + name: windows + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - uses: ilammy/msvc-dev-cmd@v1 + + - name: Build RyzenAdj + run: | + mkdir build + cd build + cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release .. + nmake + + - name: Prepare release folder + run: | + mkdir release + copy build/ryzenadj.exe release/ + copy build/libryzenadj.dll release/ + copy win32/* release/ + copy examples/* release/ + + - name: Package + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path dist + Remove-Item "release/*.lib", "release/*.exp" -ErrorAction SilentlyContinue + Compress-Archive -Path release/* -DestinationPath "dist/ryzenadj-win64.zip" + + New-Item -ItemType Directory -Force -Path lib-release + Copy-Item build/libryzenadj.dll, build/libryzenadj.lib, lib/ryzenadj.h -Destination lib-release + Copy-Item win32/inpoutx64.dll, win32/WinRing0x64.dll, win32/WinRing0x64.sys -Destination lib-release + Compress-Archive -Path lib-release/* -DestinationPath "dist/libryzenadj-win64.zip" + + - uses: actions/upload-artifact@v4 + with: + name: ryzenadj-win64 + path: dist/*.zip + + publish: + name: publish + runs-on: ubuntu-latest + needs: + - manylinux_2_28 + - musl-static + - windows + + steps: + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Upload release build run artifact + uses: actions/upload-artifact@v4 + with: + name: release-builds + path: artifacts/* + + - name: Upload release assets + if: github.event_name == 'release' + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload "${RELEASE_TAG}" artifacts/* --clobber diff --git a/os/licenses/ryzenadj/src/.gitignore b/os/licenses/ryzenadj/src/.gitignore new file mode 100644 index 00000000..edbd6728 --- /dev/null +++ b/os/licenses/ryzenadj/src/.gitignore @@ -0,0 +1,350 @@ +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +build/ + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ +# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true +**/wwwroot/lib/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- Backup*.rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# End of https://www.gitignore.io/api/visualstudio diff --git a/os/licenses/ryzenadj/src/CMakeLists.txt b/os/licenses/ryzenadj/src/CMakeLists.txt new file mode 100644 index 00000000..06d9d9af --- /dev/null +++ b/os/licenses/ryzenadj/src/CMakeLists.txt @@ -0,0 +1,79 @@ +#cmake version +CMAKE_MINIMUM_REQUIRED(VERSION 3.10) + +#define project name +PROJECT(ryzenadj) + +include(GNUInstallDirs) + +set(CMAKE_C_VISIBILITY_PRESET hidden) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + +option(BUILD_SHARED_LIBS "Build using shared libraries" ON) +option(PREFER_STATIC_LINKING "Prefer static linking for the ryzenadj executable" OFF) + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +#Enable LTO +include(CheckIPOSupported) +check_ipo_supported(RESULT supported OUTPUT error) +if( supported ) + message(STATUS "IPO / LTO enabled") + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() + +AUX_SOURCE_DIRECTORY(./ SRC_DIR) + +if(WIN32) +set(OS_SOURCE lib/win32/osdep_win32.cpp) +set(OS_LINK_LIBRARY WinRing0x64) +set(OS_LINK_DIR ./win32) +LINK_DIRECTORIES(${OS_LINK_DIR}) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") +set(OS_SOURCE lib/linux/osdep_linux.c lib/linux/osdep_linux_mem.c lib/linux/osdep_linux_smu_kernel_module.c) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(LIBPCI REQUIRED libpci) + +add_library(ryzenadj_libpci INTERFACE) +add_library(RyzenAdj::Libpci ALIAS ryzenadj_libpci) +target_include_directories(ryzenadj_libpci INTERFACE ${LIBPCI_INCLUDE_DIRS}) +target_compile_options(ryzenadj_libpci INTERFACE ${LIBPCI_CFLAGS_OTHER}) +target_link_libraries(ryzenadj_libpci INTERFACE ${LIBPCI_LDFLAGS}) + +add_library(ryzenadj_libpci_static INTERFACE) +add_library(RyzenAdj::LibpciStatic ALIAS ryzenadj_libpci_static) +target_include_directories(ryzenadj_libpci_static INTERFACE ${LIBPCI_STATIC_INCLUDE_DIRS}) +target_compile_options(ryzenadj_libpci_static INTERFACE ${LIBPCI_STATIC_CFLAGS_OTHER}) +target_link_libraries(ryzenadj_libpci_static INTERFACE ${LIBPCI_STATIC_LDFLAGS}) + +set(OS_LINK_LIBRARY RyzenAdj::Libpci) +set(OS_STATIC_LINK_LIBRARY RyzenAdj::LibpciStatic) +else() +message(FATAL_ERROR "Unsupported operating system: ${CMAKE_SYSTEM_NAME}") +endif() + +set(COMMON_SOURCES lib/nb_smu_ops.c lib/api.c lib/cpuid.c) +add_definitions(-D_LIBRYZENADJ_INTERNAL) + +ADD_EXECUTABLE(${PROJECT_NAME} ${OS_SOURCE} ${COMMON_SOURCES} argparse.c main.c) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND (PREFER_STATIC_LINKING OR NOT BUILD_SHARED_LIBS)) + if(PREFER_STATIC_LINKING) + set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY LINK_FLAGS " -static") + endif() + target_link_libraries(${PROJECT_NAME} ${OS_STATIC_LINK_LIBRARY}) +else() + target_link_libraries(${PROJECT_NAME} ${OS_LINK_LIBRARY}) +endif() +#SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C) +ADD_LIBRARY (libryzenadj ${OS_SOURCE} ${COMMON_SOURCES}) +set_target_properties(libryzenadj PROPERTIES PREFIX "") +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT BUILD_SHARED_LIBS) + target_link_libraries(libryzenadj ${OS_STATIC_LINK_LIBRARY}) +else() + target_link_libraries(libryzenadj ${OS_LINK_LIBRARY}) +endif() +#SET_TARGET_PROPERTIES(libryzenadj PROPERTIES LINKER_LANGUAGE C) +install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/os/licenses/ryzenadj/src/LICENSE b/os/licenses/ryzenadj/src/LICENSE new file mode 100644 index 00000000..0a041280 --- /dev/null +++ b/os/licenses/ryzenadj/src/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + 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 that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU 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 as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/os/licenses/ryzenadj/src/README.md b/os/licenses/ryzenadj/src/README.md new file mode 100644 index 00000000..f64a3ba0 --- /dev/null +++ b/os/licenses/ryzenadj/src/README.md @@ -0,0 +1,210 @@ +# RyzenAdj +Adjust power management settings for Ryzen Mobile Processors. + +[![Build Status](https://travis-ci.org/FlyGoat/RyzenAdj.svg?branch=master)](https://travis-ci.org/FlyGoat/RyzenAdj) + +Based on: [FlyGoat/ryzen_nb_smu](https://github.com/flygoat/ryzen_nb_smu) + +RyzenAdjUI_WPF by "JustSkill" is no longer maintained, for GUI please see [Universal x86 Tuning Utility](https://github.com/JamesCJ60/Universal-x86-Tuning-Utility) or [ryzen-controller-team/ryzen-controller](https://gitlab.com/ryzen-controller-team/ryzen-controller/). + +## Usage +The command line interface is identical on both Windows and Unix-Like OS. + +You should run it with Administrator on Windows or root on Linux. + +You can write a shell script or bat to do it automatically. + +``` +$./ryzenadj -h +Usage: ryzenadj [options] + + Ryzen Power Management adjust tool. + + -h, --help show this help message and exit + +Options + -i, --info Show information and most important power metrics after adjustment + --dump-table Show whole power metric table before and after adjustment + +Settings + -a, --stapm-limit= Sustained Power Limit - STAPM LIMIT (mW) + -b, --fast-limit= Actual Power Limit - PPT LIMIT FAST (mW) + -c, --slow-limit= Average Power Limit - PPT LIMIT SLOW (mW) + -d, --slow-time= Slow PPT Constant Time (s) + -e, --stapm-time= STAPM constant time (s) + -f, --tctl-temp= Tctl Temperature Limit (degree C) + -g, --vrm-current= VRM Current Limit - TDC LIMIT VDD (mA) + -j, --vrmsoc-current= VRM SoC Current Limit - TDC LIMIT SoC (mA) + -k, --vrmmax-current= VRM Maximum Current Limit - EDC LIMIT VDD (mA) + -l, --vrmsocmax-current= VRM SoC Maximum Current Limit - EDC LIMIT SoC (mA) + -m, --psi0-current= PSI0 VDD Current Limit (mA) + -n, --psi0soc-current= PSI0 SoC Current Limit (mA) + -o, --max-socclk-frequency= Maximum SoC Clock Frequency (MHz) + -p, --min-socclk-frequency= Minimum SoC Clock Frequency (MHz) + -q, --max-fclk-frequency= Maximum Transmission (CPU-GPU) Frequency (MHz) + -r, --min-fclk-frequency= Minimum Transmission (CPU-GPU) Frequency (MHz) + -s, --max-vcn= Maximum Video Core Next (VCE - Video Coding Engine) (MHz) + -t, --min-vcn= Minimum Video Core Next (VCE - Video Coding Engine) (MHz) + -u, --max-lclk= Maximum Data Launch Clock (MHz) + -v, --min-lclk= Minimum Data Launch Clock (MHz) + -w, --max-gfxclk= Maximum GFX Clock (MHz) + -x, --min-gfxclk= Minimum GFX Clock (MHz) + -y, --prochot-deassertion-ramp= Ramp Time After Prochot is Deasserted: limit power based on value, higher values does apply tighter limits after prochot is over + --apu-skin-temp= APU Skin Temperature Limit - STT LIMIT APU (degree C) + --dgpu-skin-temp= dGPU Skin Temperature Limit - STT LIMIT dGPU (degree C) + --apu-slow-limit= APU PPT Slow Power limit for A+A dGPU platform - PPT LIMIT APU (mW) + --skin-temp-limit= Skin Temperature Power Limit (mW) + --power-saving Hidden options to improve power efficiency (is set when AC unplugged): behavior depends on CPU generation, Device and Manufacture + --max-performance Hidden options to improve performance (is set when AC plugged in): behavior depends on CPU generation, Device and Manufacture +``` + +### Demo +If I'm going to set all the Power Limit to 45W, and Tctl to 90 °C, +then the command line should be: + + ./ryzenadj --stapm-limit=45000 --fast-limit=45000 --slow-limit=45000 --tctl-temp=90 + +### Documentation +- [Supported Models](https://github.com/FlyGoat/RyzenAdj/wiki/Supported-Models) +- [Renoir Tuning Guide](https://github.com/FlyGoat/RyzenAdj/wiki/Renoir-Tuning-Guide) +- [Options](https://github.com/FlyGoat/RyzenAdj/wiki/Options) +- [FAQ](https://github.com/FlyGoat/RyzenAdj/wiki/FAQ) + +## Installation + +You don't need to install RyzenAdj because it does not need configuration, everything is set via arguments +However, some settings could get overwritten by power management features of your device, and you need to regularly set your values again. + +We did provide some examples for automation. And these require configuration during installation. + +### Linux Installation + +Because it is very easy to build the latest version of RyzenAdj on Linux, we don't provide precompiled packages for distributions. +Just follow the build instructions below and you are ready to use it. + +### Windows Installation + +Before you start installing anything, it is highly recommended getting familiar with RyzenAdj to find out what can be done on your device. +Use the CLI `ryzenadj.exe` to test the support of your device and to benchmark the effects of each setting. +If your values don't stay persistent you may want to consider installing our example script for automation. + +1. Prepare your favorite RyzenAdj arguments +1. Copy the content of your RyzenAdj folder to the final destination +1. Put your configuration into `readjustService.ps1` and test it as administrator until everything works as expected +1. Install `readjustService.ps1` as Task for Windows Task Scheduler by running `installServiceTask.bat` + +Deinstallation of the Task can be done via `uninstallServiceTask.bat` + +Over Windows Task Scheduler you can check if it is running. It is called `RyzenAdj` below `AMD` folder. +Or just run + + SCHTASKS /query /TN "AMD\RyzenAdj" + +## Build + +### Build Requirements + +Building this tool requires C & C++ compilers as well as **cmake**. + +### Linux + +RyzenAdj needs elevated access to the NB config space. This can be achieved by using either one of +these two methods: + +* Using libpci and exposing `/dev/mem` +* Using the ryzen\_smu kernel module + +RyzenAdj will try ryzen\_smu first, and then fallback to /dev/mem, if no compatible smu driver is found. +The minimum supported version of ryzen_smu is 0.1.7 +If no backend is available, RyzenAdj will fail initialization. + +_**Please note that `/dev/mem` access may be restricted, for security reasons, in your kernel config**_ + +Please make sure that you have libpci dependency before compiling. + +On Debian-based distros this is covered by installing **pcilib-dev** package: + + sudo apt install build-essential cmake libpci-dev + +On Fedora: + + sudo dnf install cmake gcc-c++ pciutils-devel + +On Arch: + + sudo pacman -S base-devel pciutils cmake + + +On OpenSUSE Tumbleweed: + + sudo zypper in cmake gcc14-c++ pciutils-devel + +You may need to add the `iomem=relaxed` param to your kernel params on Tumbleweed, or [you may run into errors at runtime](https://github.com/FlyGoat/RyzenAdj/issues/241). + +If your Distribution is not supported, try finding the packages or use [Distrobox](https://github.com/89luca89/distrobox) or [Toolbox](https://docs.fedoraproject.org/en-US/fedora-silverblue/toolbox/) instead. + +The simplest way to build it: + + git clone https://github.com/FlyGoat/RyzenAdj.git + cd RyzenAdj + rm -r win32 + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + make + if [ -d ~/.local/bin ]; then ln -s $(readlink -f ryzenadj) ~/.local/bin/ryzenadj && echo "symlinked to ~/.local/bin/ryzenadj"; fi + if [ -d ~/.bin ]; then ln -s $(readlink -f ryzenadj) ~/.bin/ryzenadj && echo "symlinked to ~/.bin/ryzenadj"; fi + +#### Ryzen\_smu + +To let RyzenAdj use ryzen\_smu module, you have to install it first, it is not part of the linux kernel. + +On Fedora: + +```sh +sudo dnf install cmake gcc gcc-c++ dkms openssl +``` + +Clone and install ryzen\_smu: + +```sh +git clone https://github.com/amkillam/ryzen_smu # Active fork of the original module +(cd ryzen_smu/ && sudo make dkms-install) +``` + +If you are using secure boot, you have to enroll the UEFI keys which dkms has generated on its first +run. These have to be added to your machines UEFI key database. This can be done with following +command, which will ask you to set a password. This password is only needed _one single time_ later +in the MOK manager. + +```sh +sudo mokutil --import /var/lib/dkms/mok.pub +``` + +Restart your system. This will boot into the MOK manager. Choose `Enroll MOK`, enter your password +and then reboot. +[Here](https://github.com/dell/dkms/blob/f7f526c145ecc01fb4ac4eab3009b1879b14ced4/README.md#secure-boot) +are some screenshots describing the process. + +The module is now loaded and visible via dmesg. It will show a message about the kernel being +tainted, but this just means it loaded a (potentially proprietary) binary blob. + +Build and install RyzenAdj: + +```sh +git clone https://github.com/FlyGoat/RyzenAdj +cd RyzenAdj +cmake -B build -DCMAKE_BUILD_TYPE=Release +make -C build -j"$(nproc)" +sudo cp -v build/ryzenadj /usr/local/bin/ +``` + +### Windows + +It can be built by Visual Studio + MSVC automatically, or Clang + Nmake in command line. +However, as for now, MingW-gcc can't be used to compile for some reason. + +Required dll is included in ./win32 of source tree. Please put the dll +library and sys driver in the same folder with ryzenadj.exe. + +We don't recommend you to build by yourself on Windows since the environment configuration +is very complicated. If you would like to use ryzenadj functions in your program, see libryzenadj. diff --git a/os/licenses/ryzenadj/src/argparse.c b/os/licenses/ryzenadj/src/argparse.c new file mode 100644 index 00000000..ee39f939 --- /dev/null +++ b/os/licenses/ryzenadj/src/argparse.c @@ -0,0 +1,435 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#include +#include +#include +#include +#include +#include "argparse.h" + +#define OPT_UNSET 1 +#define OPT_LONG (1 << 1) + +static const char * +prefix_skip(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) ? NULL : str + len; +} + +static int +prefix_cmp(const char *str, const char *prefix) +{ + for (;; str++, prefix++) + if (!*prefix) { + return 0; + } else if (*str != *prefix) { + return (unsigned char)*prefix - (unsigned char)*str; + } +} + +static void +argparse_error(struct argparse *self, const struct argparse_option *opt, + const char *reason, int flags) +{ + (void)self; + if (flags & OPT_LONG) { + fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason); + } else { + fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason); + } + exit(1); +} + +static int +argparse_getvalue(struct argparse *self, const struct argparse_option *opt, + int flags) +{ + const char *s = NULL; + char buf[256]; + buf[0] = 0; + char *pbuf = buf; + + if (!opt->value) + goto skipped; + switch (opt->type) { + case ARGPARSE_OPT_BOOLEAN: + if (flags & OPT_UNSET) { + *(int *)opt->value = *(int *)opt->value - 1; + } else { + *(int *)opt->value = *(int *)opt->value + 1; + } + if (*(int *)opt->value < 0) { + *(int *)opt->value = 0; + } + break; + case ARGPARSE_OPT_BIT: + if (flags & OPT_UNSET) { + *(int *)opt->value &= ~opt->data; + } else { + *(int *)opt->value |= opt->data; + } + break; + case ARGPARSE_OPT_STRING: + if (self->optvalue) { + *(const char **)opt->value = self->optvalue; + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(const char **)opt->value = *++self->argv; + } else { + argparse_error(self, opt, "requires a value", flags); + } + break; + case ARGPARSE_OPT_INTEGER: + errno = 0; + if (self->optvalue) { + *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno){ + #ifdef _WIN32 + strerror_s(buf, sizeof(buf), errno); + #else + strerror_r(errno, buf, sizeof(buf)); + #endif + argparse_error(self, opt, pbuf, flags); + } + if (s[0] != '\0') + argparse_error(self, opt, "expects an integer value", flags); + break; + case ARGPARSE_OPT_U32: + errno = 0; + if (self->optvalue) { + *(uint32_t *)opt->value = strtoul(self->optvalue, (char **)&s, 0); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(uint32_t *)opt->value = strtoul(*++self->argv, (char **)&s, 0); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno){ + #ifdef _WIN32 + strerror_s(buf, sizeof(buf), errno); + #else + strerror_r(errno, buf, sizeof(buf)); + #endif + argparse_error(self, opt, pbuf, flags); + } + if (s[0] != '\0') + argparse_error(self, opt, "expects an unsigned 32-bit integer value", flags); + break; + case ARGPARSE_OPT_FLOAT: + errno = 0; + if (self->optvalue) { + *(float *)opt->value = strtof(self->optvalue, (char **)&s); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(float *)opt->value = strtof(*++self->argv, (char **)&s); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno){ + #ifdef _WIN32 + strerror_s(buf, sizeof(buf), errno); + #else + strerror_r(errno, buf, sizeof(buf)); + #endif + argparse_error(self, opt, pbuf, flags); + } + if (s[0] != '\0') + argparse_error(self, opt, "expects a numerical value", flags); + break; + default: + assert(0); + } + +skipped: + if (opt->callback) { + return opt->callback(self, opt); + } + + return 0; +} + +static void +argparse_options_check(const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + switch (options->type) { + case ARGPARSE_OPT_END: + case ARGPARSE_OPT_BOOLEAN: + case ARGPARSE_OPT_BIT: + case ARGPARSE_OPT_INTEGER: + case ARGPARSE_OPT_U32: + case ARGPARSE_OPT_FLOAT: + case ARGPARSE_OPT_STRING: + case ARGPARSE_OPT_GROUP: + continue; + default: + fprintf(stderr, "wrong option type: %d", options->type); + break; + } + } +} + +static int +argparse_short_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + if (options->short_name == *self->optvalue) { + self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL; + return argparse_getvalue(self, options, 0); + } + } + return -2; +} + +static int +argparse_long_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + const char *rest; + int opt_flags = 0; + if (!options->long_name) + continue; + + rest = prefix_skip(self->argv[0] + 2, options->long_name); + if (!rest) { + // negation disabled? + if (options->flags & OPT_NONEG) { + continue; + } + // only OPT_BOOLEAN/OPT_BIT supports negation + if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != + ARGPARSE_OPT_BIT) { + continue; + } + + if (prefix_cmp(self->argv[0] + 2, "no-")) { + continue; + } + rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name); + if (!rest) + continue; + opt_flags |= OPT_UNSET; + } + if (*rest) { + if (*rest != '=') + continue; + self->optvalue = rest + 1; + } + return argparse_getvalue(self, options, opt_flags | OPT_LONG); + } + return -2; +} + +int +argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags) +{ + memset(self, 0, sizeof(*self)); + self->options = options; + self->usages = usages; + self->flags = flags; + self->description = NULL; + self->epilog = NULL; + return 0; +} + +void +argparse_describe(struct argparse *self, const char *description, + const char *epilog) +{ + self->description = description; + self->epilog = epilog; +} + +int +argparse_parse(struct argparse *self, int argc, const char **argv) +{ + self->argc = argc - 1; + self->argv = argv + 1; + self->out = argv; + + argparse_options_check(self->options); + if(!self->argc) { + argparse_usage(self); + exit(1); + } + + for (; self->argc; self->argc--, self->argv++) { + const char *arg = self->argv[0]; + if (arg[0] != '-' || !arg[1]) { + if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) { + goto end; + } + if (self->flags & ARGPARSE_NON_OPTION_IS_INVALID) { + goto unknown; + } + // if it's not option or is a single char '-', copy verbatim + self->out[self->cpidx++] = self->argv[0]; + continue; + } + // short option + if (arg[1] != '-') { + self->optvalue = arg + 1; + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + while (self->optvalue) { + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + } + continue; + } + // if '--' presents + if (!arg[2]) { + self->argc--; + self->argv++; + break; + } + // long option + switch (argparse_long_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + continue; + +unknown: + fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]); + argparse_usage(self); + exit(1); + } + +end: + memmove(self->out + self->cpidx, self->argv, + self->argc * sizeof(*self->out)); + self->out[self->cpidx + self->argc] = NULL; + + return self->cpidx + self->argc; +} + +void +argparse_usage(struct argparse *self) +{ + if (self->usages) { + fprintf(stdout, "Usage: %s\n", *self->usages++); + while (*self->usages && **self->usages) + fprintf(stdout, " or: %s\n", *self->usages++); + } else { + fprintf(stdout, "Usage:\n"); + } + + // print description + if (self->description) + fprintf(stdout, "%s\n", self->description); + + fputc('\n', stdout); + + const struct argparse_option *options; + + // figure out best width + size_t usage_opts_width = 0; + size_t len; + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + len = 0; + if ((options)->short_name) { + len += 2; + } + if ((options)->short_name && (options)->long_name) { + len += 2; // separator ", " + } + if ((options)->long_name) { + len += strlen((options)->long_name) + 2; + } + if (options->type == ARGPARSE_OPT_INTEGER) { + len += strlen("="); + } + if (options->type == ARGPARSE_OPT_U32) { + len += strlen("="); + } + if (options->type == ARGPARSE_OPT_FLOAT) { + len += strlen("="); + } else if (options->type == ARGPARSE_OPT_STRING) { + len += strlen("="); + } + len = (len + 3) - ((len + 3) & 3); + if (usage_opts_width < len) { + usage_opts_width = len; + } + } + usage_opts_width += 4; // 4 spaces prefix + + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + size_t pos = 0; + size_t pad = 0; + if (options->type == ARGPARSE_OPT_GROUP) { + fputc('\n', stdout); + fprintf(stdout, "%s", options->help); + fputc('\n', stdout); + continue; + } + pos = fprintf(stdout, " "); + if (options->short_name) { + pos += fprintf(stdout, "-%c", options->short_name); + } + if (options->long_name && options->short_name) { + pos += fprintf(stdout, ", "); + } + if (options->long_name) { + pos += fprintf(stdout, "--%s", options->long_name); + } + if (options->type == ARGPARSE_OPT_INTEGER) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_U32) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_FLOAT) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_STRING) { + pos += fprintf(stdout, "="); + } + if (pos <= usage_opts_width) { + pad = usage_opts_width - pos; + } else { + fputc('\n', stdout); + pad = usage_opts_width; + } + fprintf(stdout, "%*s%s\n", (int)pad + 2, "", options->help); + } + + // print epilog + if (self->epilog) + fprintf(stdout, "%s\n", self->epilog); +} + +int +argparse_help_cb(struct argparse *self, const struct argparse_option *option) +{ + (void)option; + argparse_usage(self); + exit(0); +} diff --git a/os/licenses/ryzenadj/src/argparse.h b/os/licenses/ryzenadj/src/argparse.h new file mode 100644 index 00000000..c516be66 --- /dev/null +++ b/os/licenses/ryzenadj/src/argparse.h @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#ifndef ARGPARSE_H +#define ARGPARSE_H + +/* For c++ compatibility */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct argparse; +struct argparse_option; + +typedef int argparse_callback (struct argparse *self, + const struct argparse_option *option); + +enum argparse_flag { + ARGPARSE_STOP_AT_NON_OPTION = 1, + ARGPARSE_NON_OPTION_IS_INVALID = 2, +}; + +enum argparse_option_type { + /* special */ + ARGPARSE_OPT_END, + ARGPARSE_OPT_GROUP, + /* options with no arguments */ + ARGPARSE_OPT_BOOLEAN, + ARGPARSE_OPT_BIT, + /* options with arguments (optional or required) */ + ARGPARSE_OPT_INTEGER, + ARGPARSE_OPT_U32, + ARGPARSE_OPT_FLOAT, + ARGPARSE_OPT_STRING, +}; + +enum argparse_option_flags { + OPT_NONEG = 1, /* disable negation */ +}; + +/** + * argparse option + * + * `type`: + * holds the type of the option, you must have an ARGPARSE_OPT_END last in your + * array. + * + * `short_name`: + * the character to use as a short option name, '\0' if none. + * + * `long_name`: + * the long option name, without the leading dash, NULL if none. + * + * `value`: + * stores pointer to the value to be filled. + * + * `help`: + * the short help message associated to what the option does. + * Must never be NULL (except for ARGPARSE_OPT_END). + * + * `callback`: + * function is called when corresponding argument is parsed. + * + * `data`: + * associated data. Callbacks can use it like they want. + * + * `flags`: + * option flags. + */ +struct argparse_option { + enum argparse_option_type type; + const char short_name; + const char *long_name; + void *value; + const char *help; + argparse_callback *callback; + intptr_t data; + int flags; +}; + +/** + * argpparse + */ +struct argparse { + // user supplied + const struct argparse_option *options; + const char *const *usages; + int flags; + const char *description; // a description after usage + const char *epilog; // a description at the end + // internal context + int argc; + const char **argv; + const char **out; + int cpidx; + const char *optvalue; // current option value +}; + +// built-in callbacks +int argparse_help_cb(struct argparse *self, + const struct argparse_option *option); + +// built-in option macros +#define OPT_END() { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 } +#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ } +#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ } +#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ } +#define OPT_U32(...) { ARGPARSE_OPT_U32, __VA_ARGS__ } +#define OPT_FLOAT(...) { ARGPARSE_OPT_FLOAT, __VA_ARGS__ } +#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ } +#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 } +#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \ + "show this help message and exit", \ + argparse_help_cb, 0, OPT_NONEG) + +int argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags); +void argparse_describe(struct argparse *self, const char *description, + const char *epilog); +int argparse_parse(struct argparse *self, int argc, const char **argv); +void argparse_usage(struct argparse *self); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/os/licenses/ryzenadj/src/examples/pmtable-example.py b/os/licenses/ryzenadj/src/examples/pmtable-example.py new file mode 100644 index 00000000..c2ab4106 --- /dev/null +++ b/os/licenses/ryzenadj/src/examples/pmtable-example.py @@ -0,0 +1,59 @@ +import os, sys, time +from ctypes import * +from shutil import copyfile + +lib_path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(lib_path) + +if sys.platform == 'win32' or sys.platform == 'cygwin': + try: + os.add_dll_directory(lib_path) + except AttributeError: + pass #not needed for old python version + + winring0_driver_file_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), 'WinRing0x64.sys') + if not os.path.isfile(winring0_driver_file_path): + copyfile(os.path.join(lib_path, 'WinRing0x64.sys'), winring0_driver_file_path) + + lib = cdll.LoadLibrary('libryzenadj') +else: + lib = cdll.LoadLibrary('libryzenadj.so') + +# define ctype mappings for types which can not be mapped automatically +lib.init_ryzenadj.restype = c_void_p +lib.get_table_ver.argtypes = [c_void_p] +lib.get_table_size.argtypes = [c_void_p] +lib.get_table_values.restype = POINTER(c_float) +lib.get_table_values.argtypes = [c_void_p] +lib.refresh_table.argtypes = [c_void_p] + +ry = lib.init_ryzenadj() + +if not ry: + sys.exit("RyzenAdj could not get initialized") + +print("pmtable version: {:x}".format(lib.get_table_ver(ry))) + +input("Press any key to show all pmtable values...") + +pmtable_size = lib.get_table_size(ry) // 4 +pmtable = lib.get_table_values(ry) + +while True: + lib.refresh_table(ry) + columns, lines = os.get_terminal_size() + table_columns = columns // 16 # 16 chars per table entry + os.system('cls' if sys.platform == 'win32' else 'clear') + table_rows = 0 + for index in (range(pmtable_size)): + sys.stdout.write("{:3d}:{:8.2f}\t".format(index, pmtable[index])) + if index % table_columns == table_columns - 1: + sys.stdout.write('\n') + table_rows += 1 + if table_rows >= lines - 1: + sys.stdout.write('{:d} More entries ...'.format(pmtable_size - 1 - index)) + break + + if index % table_columns != table_columns - 1: sys.stdout.write('\n') + sys.stdout.flush() + time.sleep(1) diff --git a/os/licenses/ryzenadj/src/examples/readjust.py b/os/licenses/ryzenadj/src/examples/readjust.py new file mode 100644 index 00000000..74afb15a --- /dev/null +++ b/os/licenses/ryzenadj/src/examples/readjust.py @@ -0,0 +1,70 @@ +import os, sys, time +from ctypes import * +from shutil import copyfile + +lib_path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(lib_path) + +if sys.platform == 'win32' or sys.platform == 'cygwin': + try: + os.add_dll_directory(lib_path) + except AttributeError: + pass #not needed for old python version + + winring0_driver_file_path = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), 'WinRing0x64.sys') + if not os.path.isfile(winring0_driver_file_path): + copyfile(os.path.join(lib_path, 'WinRing0x64.sys'), winring0_driver_file_path) + + lib = cdll.LoadLibrary('libryzenadj') +else: + lib = cdll.LoadLibrary('libryzenadj.so') + +# define ctype mappings for types which can not be mapped automatically +lib.init_ryzenadj.restype = c_void_p +lib.refresh_table.argtypes = [c_void_p] +lib.get_fast_limit.restype = c_float +lib.get_fast_limit.argtypes = [c_void_p] + +ry = lib.init_ryzenadj() + +if not ry: + sys.exit("RyzenAdj could not get initialized") + +error_messages = { + -1: "{:s} is not supported on this family\n", + -3: "{:s} is not supported on this SMU\n", + -4: "{:s} is rejected by SMU\n" +} + +def adjust(field, value): + function_name = "set_" + field + adjust_func = lib.__getattr__(function_name) + adjust_func.argtypes = [c_void_p, c_ulong] + res = adjust_func(ry, value) + if res: + error = error_messages.get(res, "{:s} did fail with {:d}\n") + sys.stderr.write(error.format(function_name, res)); + +def enable(field): + function_name = "set_" + field + adjust_func = lib.__getattr__(function_name) + adjust_func.argtypes = [c_void_p] + res = adjust_func(ry) + if res: + error = error_messages.get(res, "{:s} did fail with {:d}\n") + sys.stderr.write(error.format(function_name, res)); + +print("Monitor if fast limit is not 35W") +while True: + lib.refresh_table(ry) + limit = round(lib.get_fast_limit(ry)) + if limit != 35: + print("reapply limits, because old limit was {:d}".format(limit)) + adjust("fast_limit", 35000) + adjust("slow_limit", 22000) + adjust("slow_time", 30) + adjust("tctl_temp", 97) + adjust("apu_skin_temp_limit", 50) + adjust("vrmmax_current", 100000) + enable("max_performance") + time.sleep(3) diff --git a/os/licenses/ryzenadj/src/lib/api.c b/os/licenses/ryzenadj/src/lib/api.c new file mode 100644 index 00000000..f4007edd --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/api.c @@ -0,0 +1,2825 @@ +// SPDX-License-Identifier: LGPL +/* Copyright (C) 2018-2019 Jiaxun Yang */ +/* RyzenAdj API */ +#include +#include + +#include "ryzenadj.h" +#include "math.h" + +#ifndef _WIN32 +#include +#define Sleep(x) usleep((x)*1000) +#endif + + +EXP ryzen_access CALL init_ryzenadj() { + const enum ryzen_family family = cpuid_get_family(); + ryzen_access ry; + + if (family == FAM_UNKNOWN) + return NULL; + + ry = (ryzen_access)malloc(sizeof(*ry)); + + if (!ry){ + printf("Out of memory\n"); + return NULL; + } + + memset(ry, 0, sizeof(*ry)); + + ry->family = family; + //init version and power metric table only on demand to avoid unnecessary SMU writes + ry->bios_if_ver = 0; + ry->table_values = NULL; + + ry->os_access = init_os_access_obj(); + if(!ry->os_access){ + printf("Unable to get os_access Obj, check permission\n"); + return NULL; + } + + ry->mp1_smu = get_smu(ry->os_access, TYPE_MP1); + if(!ry->mp1_smu){ + printf("Unable to get MP1 SMU Obj\n"); + goto err_exit; + } + + ry->psmu = get_smu(ry->os_access, TYPE_PSMU); + if(!ry->psmu){ + printf("Unable to get RSMU Obj\n"); + goto err_exit; + } + + return ry; + +err_exit: + cleanup_ryzenadj(ry); + return NULL; +} + +EXP void CALL cleanup_ryzenadj(ryzen_access ry) { + if (ry == NULL) + return; + + free(ry->mp1_smu); + free(ry->psmu); + free_os_access_obj(ry->os_access); + free(ry->table_values); + free(ry); +} + +EXP enum ryzen_family get_cpu_family(ryzen_access ry) +{ + return ry->family; +} + +EXP int get_bios_if_ver(ryzen_access ry) +{ + if(ry->bios_if_ver) + return ry->bios_if_ver; + + smu_service_args_t args = {0, 0, 0, 0, 0, 0}; + smu_service_req(ry->mp1_smu, 0x3, &args); + ry->bios_if_ver = args.arg0; + return ry->bios_if_ver; +} + +#define _return_translated_smu_error(SMU_RESP) \ +do { \ + if (SMU_RESP == REP_MSG_UnknownCmd) { \ + printf("%s is unsupported\n", __func__); \ + return ADJ_ERR_SMU_UNSUPPORTED; \ + } else if (SMU_RESP == REP_MSG_CmdRejectedPrereq){ \ + printf("%s was rejected\n", __func__); \ + return ADJ_ERR_SMU_REJECTED; \ + } else if (SMU_RESP == REP_MSG_CmdRejectedBusy) { \ + printf("%s was rejected - busy\n", __func__); \ + return ADJ_ERR_SMU_REJECTED; \ + } else if (SMU_RESP == REP_MSG_Failed) { \ + printf("%s failed\n", __func__); \ + return ADJ_ERR_SMU_REJECTED; \ + } else { \ + printf("%s failed with unknown response %x\n", __func__, SMU_RESP); \ + return ADJ_ERR_SMU_REJECTED; \ + } \ +} while (0); + +static int request_table_ver_and_size(ryzen_access ry) { + unsigned int get_table_ver_msg; + int resp; + + switch (ry->family) { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + get_table_ver_msg = 0xC; + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_REMBRANDT: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + get_table_ver_msg = 0x6; + break; + default: + printf("request_table_ver_and_size is not supported on this family\n"); + return ADJ_ERR_FAM_UNSUPPORTED; + } + + smu_service_args_t args = {0, 0, 0, 0, 0, 0}; + resp = smu_service_req(ry->psmu, get_table_ver_msg, &args); + ry->table_ver = args.arg0; + + switch (ry->table_ver) { + case 0x1E0001: ry->table_size = 0x568; break; + case 0x1E0002: ry->table_size = 0x580; break; + case 0x1E0003: ry->table_size = 0x578; break; + case 0x1E0004: + case 0x1E0005: + case 0x1E000A: + case 0x1E0101: ry->table_size = 0x608; break; + case 0x370000: ry->table_size = 0x794; break; + case 0x370001: ry->table_size = 0x884; break; + case 0x370002: ry->table_size = 0x88C; break; + case 0x370003: + case 0x370004: ry->table_size = 0x8AC; break; + case 0x370005: ry->table_size = 0x8C8; break; + case 0x3F0000: ry->table_size = 0x7AC; break; + case 0x400001: ry->table_size = 0x910; break; + case 0x400002: ry->table_size = 0x928; break; + case 0x400003: ry->table_size = 0x94C; break; + case 0x400004: + case 0x400005: ry->table_size = 0x944; break; + case 0x450004: ry->table_size = 0xAA4; break; + case 0x450005: ry->table_size = 0xAB0; break; + case 0x4C0003: ry->table_size = 0xB18; break; + case 0x4C0004: ry->table_size = 0xB1C; break; + case 0x4C0005: ry->table_size = 0xAF8; break; + case 0x4C0006: ry->table_size = 0xAFC; break; + case 0x4C0008: ry->table_size = 0xAF0; break; + case 0x4C0007: + case 0x4C0009: ry->table_size = 0xB00; break; + case 0x5D0008: + case 0x5D0009: + case 0x5D000B: ry->table_size = 0xD54; break; + case 0x64020c: ry->table_size = 0xE50; break; + + // use a larger size then the largest known table to be able to test real table size of unknown tables + default: ry->table_size = 0x1000; break; + } + + if (resp != REP_MSG_OK) { + _return_translated_smu_error(resp); + } + if(!ry->table_ver){ + printf("request_table_ver_and_size did not return anything\n"); + return ADJ_ERR_SMU_UNSUPPORTED; + } + return 0; +} + +static int request_table_addr(ryzen_access ry) +{ + unsigned int get_table_addr_msg; + int resp; + smu_service_args_t args = {0, 0, 0, 0, 0, 0}; + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + args.arg0 = 3; + get_table_addr_msg = 0xB; + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_REMBRANDT: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + get_table_addr_msg = 0x66; + break; + default: + printf("request_table_addr is not supported on this family\n"); + return ADJ_ERR_FAM_UNSUPPORTED; + } + + resp = smu_service_req(ry->psmu, get_table_addr_msg, &args); + + switch (ry->family) + { + case FAM_REMBRANDT: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + ry->table_addr = (uint64_t) args.arg1 << 32 | args.arg0; + break; + default: + ry->table_addr = args.arg0; + } + + if(resp != REP_MSG_OK){ + _return_translated_smu_error(resp); + } + if(!ry->table_addr){ + printf("request_table_addr did not return anything\n"); + return ADJ_ERR_SMU_UNSUPPORTED; + } + return 0; +} + +static int request_transfer_table(ryzen_access ry) +{ + int resp; + unsigned int transfer_table_msg; + smu_service_args_t args = {0, 0, 0, 0, 0, 0}; + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + args.arg0 = 3; + transfer_table_msg = 0x3D; + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_REMBRANDT: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + transfer_table_msg = 0x65; + break; + default: + printf("request_transfer_table is not supported on this family\n"); + return ADJ_ERR_FAM_UNSUPPORTED; + } + + resp = smu_service_req(ry->psmu, transfer_table_msg, &args); + if (resp == REP_MSG_CmdRejectedPrereq) { + //2nd try is needed for 2 usecase: if SMU got interrupted or first call after boot on Zen2 + //we need to wait because if we don't wait 2nd call will fail, too: similar to Raven and Picasso issue but with real reject instead of 0 data response + //but because we don't have to check any physical memory values, don't waste CPU cycles and use sleep instead + Sleep(10); + resp = smu_service_req(ry->psmu, transfer_table_msg, &args); + if(resp == REP_MSG_CmdRejectedPrereq){ + printf("request_transfer_table was rejected twice\n"); + Sleep(100); + resp = smu_service_req(ry->psmu, transfer_table_msg, &args); + } + } + if(resp != REP_MSG_OK){ + _return_translated_smu_error(resp); + } + return 0; +} + +EXP int CALL init_table(ryzen_access ry) +{ + DBG("init_table\n"); + int errorcode = 0; + + errorcode = request_table_ver_and_size(ry); + if(errorcode){ + return errorcode; + } + + errorcode = request_table_addr(ry); + if(errorcode){ + return errorcode; + } + + //init memory object because it is prerequiremt to woring with physical memory address + if (init_mem_obj(ry->os_access, ry->table_addr) < 0) { + printf("Unable to get memory access\n"); + return ADJ_ERR_MEMORY_ACCESS; + } + + //when using the ryzen_smu kernel module, its reported PM table size is authoritative + //because it reads the real value from hardware; prefer it over the SMU-derived size, + //especially for unknown table versions that fall back to the 0x1000 sentinel +#ifndef _WIN32 + if (is_using_smu_driver() && + ry->os_access->access.kmod.pm_table_size != ry->table_size) { + DBG("PM table size: using kmod value (%zu) over SMU-derived value (%zu)\n", + ry->os_access->access.kmod.pm_table_size, ry->table_size); + ry->table_size = ry->os_access->access.kmod.pm_table_size; + } +#endif + + //hold copy of table value in memory for our single value getters + ry->table_values = calloc(ry->table_size / 4, 4); + + errorcode = refresh_table(ry); + if(errorcode) + { + return errorcode; + } + + if(!ry->table_values[0]){ + //Raven and Picasso don't get table refresh on the very first transfer call after boot, but respond with OK + //if we detact 0 data, do an initial 2nd call after a small delay + //transfer, transfer, wait, wait longer; don't work + //transfer, wait, wait longer; don't work + //transfer, wait, transfer; does work + DBG("empty table detected, try again\n"); + Sleep(10); + return refresh_table(ry); + } + + return 0; +} + +#define _lazy_init_table(RETURN_VAR) \ +do { \ + if(!ry->table_values) { \ + DBG("warning: %s was called before init_table\n", __func__); \ + errorcode = init_table(ry); \ + if(errorcode) return RETURN_VAR; \ + } \ +} while (0); + +EXP uint32_t CALL get_table_ver(ryzen_access ry) +{ + int errorcode; + _lazy_init_table(0); + + return ry->table_ver; +} + +EXP size_t CALL get_table_size(ryzen_access ry) +{ + int errorcode; + _lazy_init_table(0); + + return ry->table_size; +} + +EXP float* CALL get_table_values(ryzen_access ry) +{ + return ry->table_values; +} + +EXP int CALL refresh_table(ryzen_access ry) +{ + int errorcode = 0; + _lazy_init_table(errorcode); + + //only execute request table if we don't use SMU driver + if(!is_using_smu_driver()){ + //if other tools call tables transfer, we may already find new data inside the memory and can avoid calling transfer table twice + //avoiding transfer table twice is important because SMU tend to reject transfer table calls if you repeat them too fast + //transfer table rejection happens even if we did correctly wait for response register change + //if multiple tools retry transfer table in a loop, both will get rejections, avoid this issue by checking if we need to transfer table + //refresh table if this is the first call (table is empty) or if the first 6 table values in memory doesn't have new values (compare result = 0) + if(ry->table_values[0] == 0 || compare_pm_table(ry->table_values, 6 * 4) == 0){ + errorcode = request_transfer_table(ry); + } + } + + if(errorcode){ + return errorcode; + } + + if(copy_pm_table(ry->os_access, ry->table_values, ry->table_size)){ + printf("refresh_table failed\n"); + return ADJ_ERR_MEMORY_ACCESS; + } + + return 0; +} + +#define _do_adjust(OPT) \ +do { \ + smu_service_args_t args = {0, 0, 0, 0, 0, 0}; \ + int resp; \ + args.arg0 = value; \ + resp = smu_service_req(ry->mp1_smu, OPT, &args); \ + if (resp == REP_MSG_OK) { \ + err = 0; \ + } else if (resp == REP_MSG_UnknownCmd) { \ + err = ADJ_ERR_SMU_UNSUPPORTED; \ + } else { \ + err = ADJ_ERR_SMU_REJECTED; \ + } \ +} while (0); + + +#define _do_adjust_psmu(OPT) \ +do { \ + smu_service_args_t args = {0, 0, 0, 0, 0, 0}; \ + int resp; \ + args.arg0 = value; \ + resp = smu_service_req(ry->psmu, OPT, &args); \ + if (resp == REP_MSG_OK) { \ + err = 0; \ + } else if (resp == REP_MSG_UnknownCmd) { \ + err = ADJ_ERR_SMU_UNSUPPORTED; \ + } else { \ + err = ADJ_ERR_SMU_REJECTED; \ + } \ +} while (0); + +#define _read_float_value(OFFSET) \ +do { \ + if(!ry->table_values) \ + return NAN; \ + return ry->table_values[(OFFSET) / 4]; \ +} while (0); + + +EXP int CALL set_stapm_limit(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x05, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x1a); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x14); + if (err) { + printf("%s: Retry with PSMU\n", __func__); + _do_adjust_psmu(0x31); + } + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust(0x4f); + break; + default: + break; + } + return err; +} + +EXP int CALL set_fast_limit(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x06, val]) */ + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x1b); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x15); + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust(0x3e); + break; + default: + break; + } + return err; +} + +EXP int CALL set_slow_limit(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x07, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x1c); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x16); + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust(0x5f); + break; + default: + break; + } + return err; +} + +EXP int CALL set_slow_time(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x08, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x1d); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x17); + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust(0x60); + break; + default: + break; + } + return err; +} + +EXP int CALL set_stapm_time(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x01, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x1e); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x18); + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust(0x4e); + default: + break; + } + return err; +} + +EXP int CALL set_tctl_temp(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x03, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x1f); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x19); + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust(0x3f); + break; + default: + break; + } + return err; +} + +EXP int CALL set_vrm_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x0b, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x20); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x1a); + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust(0x3c); + break; + default: + break; + } + return err; +} + +EXP int CALL set_vrmsoc_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x0e, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x21); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x1b); + default: + break; + } + return err; +} + +EXP int CALL set_vrmgfx_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_VANGOGH: + _do_adjust(0x1c); + default: + break; + } + return err; +} + +EXP int CALL set_vrmcvip_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_VANGOGH: + _do_adjust(0x1d); + default: + break; + } + return err; +} + +EXP int CALL set_vrmmax_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x0c, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x22); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x1c); + break; + case FAM_VANGOGH: + _do_adjust(0x1e); + default: + break; + } + return err; +} + +EXP int CALL set_vrmgfxmax_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_VANGOGH: + _do_adjust(0x1f); + default: + break; + } + return err; +} + +EXP int CALL set_vrmsocmax_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x11, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x23); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x1d); + default: + break; + } + return err; +} + +EXP int CALL set_psi0_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x24); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + _do_adjust(0x1e); + default: + break; + } + return err; +} + +EXP int CALL set_psi3cpu_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_VANGOGH: + _do_adjust(0x20); + default: + break; + } + return err; +} + +EXP int CALL set_psi0soc_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x25); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + _do_adjust(0x1f); + default: + break; + } + return err; +} + +EXP int CALL set_psi3gfx_current(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_VANGOGH: + _do_adjust(0x21); + default: + break; + } + return err; +} + +EXP int CALL set_max_gfxclk_freq(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + case FAM_LUCIENNE: + _do_adjust(0x46); + break; + default: + break; + } + return err; +} + +EXP int CALL set_min_gfxclk_freq(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + case FAM_LUCIENNE: + _do_adjust(0x47); + break; + default: + break; + } + return err; +} + +EXP int CALL set_max_socclk_freq(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x48); + break; + default: + break; + } + return err; +} + +EXP int CALL set_min_socclk_freq(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x49); + break; + default: + break; + } + return err; +} + +EXP int CALL set_max_fclk_freq(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x4A); + break; + default: + break; + } + return err; +} + +EXP int CALL set_min_fclk_freq(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x4B); + break; + default: + break; + } + return err; +} + +EXP int CALL set_max_vcn(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x4C); + break; + default: + break; + } + return err; +} + +EXP int CALL set_min_vcn(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x4D); + break; + default: + break; + } + return err; +} + +EXP int CALL set_max_lclk(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x4E); + break; + default: + break; + } + return err; +} + +EXP int CALL set_min_lclk(ryzen_access ry, uint32_t value){ + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x4F); + break; + default: + break; + } + return err; +} + +EXP int CALL set_prochot_deassertion_ramp(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x09, val]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x26); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + _do_adjust(0x20); + break; + case FAM_VANGOGH: + _do_adjust(0x22); + break; + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x1f); + default: + break; + } + return err; +} + +EXP int CALL set_apu_skin_temp_limit(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x22, val]) */ + + value *= 256; + switch (ry->family) + { + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + _do_adjust(0x38); + break; + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + _do_adjust(0x33); + break; + case FAM_STRIXHALO: + // not implemented on StrixHalo, seems to be controlled only via tctl-temp + default: + break; + } + return err; +} + +EXP int CALL set_dgpu_skin_temp_limit(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x23, val]) */ + + value *= 256; + switch (ry->family) + { + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + _do_adjust(0x39); + break; + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_STRIXPOINT: + _do_adjust(0x34); + break; + case FAM_STRIXHALO: + // not implemented on StrixHalo, seems to be controlled only via tctl-temp + default: + break; + } + return err; +} + +EXP int CALL set_apu_slow_limit(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x13, val]) */ + + switch (ry->family) + { + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + _do_adjust(0x21); + break; + case FAM_REMBRANDT: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x23); + break; + default: + break; + } + return err; +} + +EXP int CALL set_skin_temp_power_limit(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + /* \_SB.ALIB (0x0c, [size, 0x2e, val]) */ + + switch (ry->family) + { + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + _do_adjust(0x53); + break; + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x4a); + break; + default: + break; + } + return err; +} + +EXP int CALL set_gfx_clk(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: /* Added to debug on KRK, STX, & STXH */ + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust_psmu(0x89); + break; + default: + break; + } + return err; +} + +EXP int CALL set_power_saving(ryzen_access ry) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + uint32_t value = 0; + + /* \_SB.ALIB (0x01, [size, 0x1]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x19); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x12); + break; + default: + break; + } + return err; +} + +EXP int CALL set_max_performance(ryzen_access ry) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + uint32_t value = 0; + + /* \_SB.ALIB (0x01, [size, 0x0]) */ + + switch (ry->family) + { + case FAM_RAVEN: + case FAM_PICASSO: + case FAM_DALI: + _do_adjust(0x18); + break; + case FAM_RENOIR: + case FAM_LUCIENNE: + case FAM_CEZANNE: + case FAM_VANGOGH: + case FAM_REMBRANDT: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x11); + break; + default: + break; + } + return err; +} + +EXP int CALL set_oc_clk(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_LUCIENNE: + case FAM_RENOIR: + case FAM_CEZANNE: + case FAM_REMBRANDT: + _do_adjust(0x31); + if (err) { + printf("%s: Retry with PSMU\n", __func__); + _do_adjust_psmu(0x19); + } + break; + default: + break; + } + return err; +} + +EXP int CALL set_per_core_oc_clk(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_LUCIENNE: + case FAM_RENOIR: + case FAM_CEZANNE: + case FAM_REMBRANDT: + _do_adjust(0x32); + if (err) { + printf("%s: Retry with PSMU\n", __func__); + _do_adjust_psmu(0x1a); + } + break; + default: + break; + } + return err; +} + +EXP int CALL set_oc_volt(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_LUCIENNE: + case FAM_RENOIR: + case FAM_CEZANNE: + _do_adjust(0x33); + if (err) { + printf("%s: Retry with PSMU\n", __func__); + _do_adjust_psmu(0x1b); + } + break; + default: + break; + } + return err; +} + +EXP int CALL set_disable_oc(ryzen_access ry) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + uint32_t value = 0x0; + + switch (ry->family) + { + case FAM_LUCIENNE: + case FAM_RENOIR: + case FAM_CEZANNE: + _do_adjust(0x30); + if (err) { + printf("%s: Retry with PSMU\n", __func__); + _do_adjust_psmu(0x1d); + } + break; + case FAM_REMBRANDT: + _do_adjust_psmu(0x18); + break; + default: + break; + } + return err; +} + +EXP int CALL set_enable_oc(ryzen_access ry) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + uint32_t value = 0x0; + + switch (ry->family) + { + case FAM_LUCIENNE: + case FAM_RENOIR: + case FAM_CEZANNE: + _do_adjust(0x2F); + break; + case FAM_REMBRANDT: + _do_adjust_psmu(0x17); + break; + default: + break; + } + return err; +} + +EXP int CALL set_coall(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_CEZANNE: + case FAM_RENOIR: + case FAM_LUCIENNE: + _do_adjust(0x55); + break; + case FAM_REMBRANDT: + case FAM_VANGOGH: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x4C); + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust_psmu(0x7); + break; + default: + break; + } + return err; +} + +EXP int CALL set_coper(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_CEZANNE: + case FAM_RENOIR: + case FAM_LUCIENNE: + _do_adjust(0x54); + break; + case FAM_REMBRANDT: + case FAM_PHOENIX: + case FAM_VANGOGH: + case FAM_HAWKPOINT: + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + _do_adjust(0x4b); + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + _do_adjust_psmu(0x6); + break; + default: + break; + } + return err; +} + +EXP int CALL set_cogfx(ryzen_access ry, uint32_t value) { + int err = ADJ_ERR_FAM_UNSUPPORTED; + + switch (ry->family) + { + case FAM_CEZANNE: + case FAM_RENOIR: + case FAM_LUCIENNE: + _do_adjust(0x64); + break; + case FAM_REMBRANDT: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + case FAM_VANGOGH: + _do_adjust_psmu(0xB7); + break; + case FAM_STRIXHALO: + // 0xB7 is rejected on this architecture + default: + break; + } + return err; +} + +//PM Table section, offset of first lines are stable across multiple PM Table versions +EXP float CALL get_stapm_limit(ryzen_access ry){_read_float_value(0x0);} +EXP float CALL get_stapm_value(ryzen_access ry){_read_float_value(0x4);} +EXP float CALL get_fast_limit(ryzen_access ry){_read_float_value(0x8);} +EXP float CALL get_fast_value(ryzen_access ry){_read_float_value(0xC);} +EXP float CALL get_slow_limit(ryzen_access ry){_read_float_value(0x10);} +EXP float CALL get_slow_value(ryzen_access ry){_read_float_value(0x14);} + +//custom section, offsets are depending on table version +EXP float CALL get_apu_slow_limit(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x003F0000: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + case 0x005D0008: // Strix Point - looks correct from dumping table, defaults to 45W + case 0x005D0009: + case 0x005D000B: + case 0x0064020c: // StrixHalo - looks correct from dumping table, defaults to 70W + case 0x00650005: // Krackan Point + _read_float_value(0x18); + default: + break; + } + return NAN; +} +EXP float CALL get_apu_slow_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x003F0000: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0009: + case 0x005D0008: // Strix Point - untested, always 0? + case 0x005D0009: + case 0x005D000B: + case 0x0064020c: // StrixHalo - untested! + case 0x00650005: // Krackan Point + _read_float_value(0x1C); + default: + break; + } + return NAN; +} +EXP float CALL get_vrm_current(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x18); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x20); + case 0x005D0008: // Strix Point - tested, defaults to 70, max 70 + case 0x005D0009: + case 0x005D000B: + case 0x00650005: // Krackan Point + _read_float_value(0x30); + default: + break; + } + return NAN; +} +EXP float CALL get_vrm_current_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x1C); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x24); + case 0x005D0008: // Strix Point - looks correct from dumping table + case 0x005D0009: + case 0x005D000B: + case 0x00650005: // Krackan Point + _read_float_value(0x34); + default: + break; + } + return NAN; +} +EXP float CALL get_vrmsoc_current(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x20); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x28); + case 0x005D0008: // Strix Point - tested, defaults to 30, max 30 + case 0x005D0009: + case 0x005D000B: + case 0x00650005: // Krackan Point + _read_float_value(0x38); + default: + break; + } + return NAN; +} +EXP float CALL get_vrmsoc_current_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x24); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x2C); + case 0x005D0008: // Strix Point - looks correct from dumping table + case 0x005D0009: + case 0x005D000B: + case 0x00650005: // Krackan Point + _read_float_value(0x3C); + default: + break; + } + return NAN; +} +EXP float CALL get_vrmmax_current(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x28); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + // Strix Point - this is vrm_current; 0x1C-0x2C are all 0 + // Same for Krackan Point + _read_float_value(0x30); + case 0x005D0008: + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x30); + default: + break; + } + return NAN; +} +EXP float CALL get_vrmmax_current_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x2C); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x34); + case 0x005D0008: + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x34); + default: + break; + } + return NAN; +} +EXP float CALL get_vrmsocmax_current(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x34); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + // Strix Point - this is vrmsoc_current; 0x1C-0x2C are all 0 + // Same for Krackan Point + _read_float_value(0x38); + case 0x005D0008: + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x38); + default: + break; + } + return NAN; +} +EXP float CALL get_vrmsocmax_current_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x38); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x3C); + case 0x005D0008: + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x3C); + default: + break; + } + return NAN; +} +EXP float CALL get_tctl_temp(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + case 0x0064020c: // StrixHalo tested + _read_float_value(0x58); //use core1 because core0 is not reported on dual core cpus + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x003F0000: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + case 0x005D0008: // Strix Point - untested, defaults to 100 + case 0x005D0009: + case 0x005D000B: + case 0x00650005: // Krackan Point + _read_float_value(0x40); + default: + break; + } + return NAN; +} +EXP float CALL get_tctl_temp_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + case 0x0064020c: // StrixHalo tested + _read_float_value(0x5C); //use core1 because core0 is not reported on dual core cpus + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x003F0000: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + /* + * Strix Point - tested + * 0x44: Zen5 clst, taskset + stress-ng + * 0x4C: Zen5c clst, ditto + * 0x54: gfx, llama-bench/FurMark, gpu_metrics_v3_0 + * 0x5C: soc, all of above + memtester + * Corresponding limits default to 100 (untested) + */ + case 0x005D0008: + case 0x005D0009: + case 0x005D000B: + case 0x00650005: // Krackan Point + _read_float_value(0x44); + default: + break; + } + return NAN; +} +EXP float CALL get_apu_skin_temp_limit(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x003F0000: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + case 0x005D0008: // Strix Point - untested + case 0x005D0009: + case 0x005D000B: + case 0x0064020c: // StrixHalo tested + _read_float_value(0x58); + break; + default: + break; + } + return NAN; +} +EXP float CALL get_apu_skin_temp_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x003F0000: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + case 0x005D0008: // Strix Point - this is gpu_metrics_v3_0.temperature_soc, !=gpu_metrics_v3_0.temperature_skin + case 0x005D0009: + case 0x005D000B: + case 0x0064020c: // StrixHalo tested + _read_float_value(0x5C); + default: + break; + } + return NAN; +} +EXP float CALL get_dgpu_skin_temp_limit(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + case 0x0064020c: // StrixHalo tested + _read_float_value(0x60); + case 0x005D0008: // Strix Point - tested + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x68); + default: + break; + } + return NAN; +} +EXP float CALL get_dgpu_skin_temp_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x00450004: + case 0x00450005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + case 0x0064020c: + _read_float_value(0x64); + case 0x005D0008: // Strix Point - calculated from corresponding limit + 0x4, 0 on my device due to no dGPU + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x6C); + default: + break; + } + return NAN; +} + +EXP float CALL get_psi0_current(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x40); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x78); + default: + break; + } + return NAN; +} + +EXP float CALL get_psi0soc_current(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x48); + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x80); + default: + break; + } + return NAN; +} + +EXP float CALL get_cclk_setpoint(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x98); //use core1 because core0 is not reported on dual core cpus; + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + _read_float_value(0xFC); + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + _read_float_value(0x100); + case 0x005D0008: // Strix Point - first entry of 12-core boost target array, tested + case 0x005D0009: + case 0x005D000B: + _read_float_value(0xD0); + default: + break; + } + return NAN; +} + +EXP float CALL get_cclk_busy_value(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0001: + case 0x001E0002: + case 0x001E0003: + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x9C); //use core1 because core0 is not reported on dual core cpus + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + _read_float_value(0x100); + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + _read_float_value(0x104); + case 0x005D0008: // Strix Point - aggregate busy clock, tested + case 0x005D0009: + case 0x005D000B: + _read_float_value(0xCC); + default: + break; + } + return NAN; +} + +EXP float CALL get_stapm_time(ryzen_access ry) +{ + switch (ry->table_ver) + { + case 0x001E0002: + _read_float_value(0x564); + case 0x001E0003: + _read_float_value(0x55C); + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x5E0); + case 0x00370000: + _read_float_value(0x768); + case 0x00370001: + _read_float_value(0x858); + case 0x00370002: + _read_float_value(0x860); + case 0x00370003: + case 0x00370004: + _read_float_value(0x880); + case 0x00370005: + _read_float_value(0x89C); + case 0x00400001: + _read_float_value(0x8E4); + case 0x00400002: + _read_float_value(0x8FC); + case 0x00400003: + _read_float_value(0x920); + case 0x00400004: + case 0x00400005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x918); + case 0x005D0008: // Strix Point - calculated from slow time (0x9C0 - 0x4), always 1? + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x9BC); + case 0x00650005: // Krackan Point, might be incorrect + _read_float_value(0x90C); + default: + break; + } + return NAN; +} + +EXP float CALL get_slow_time(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x001E0002: + _read_float_value(0x568); + case 0x001E0003: + _read_float_value(0x560); + case 0x001E0004: + case 0x001E0005: + case 0x001E000A: + case 0x001E0101: + _read_float_value(0x5E4); + case 0x00370000: + _read_float_value(0x76C); + case 0x00370001: + _read_float_value(0x85C); + case 0x00370002: + _read_float_value(0x864); + case 0x00370003: + case 0x00370004: + _read_float_value(0x884); + case 0x00370005: + _read_float_value(0x8A0); + case 0x00400001: + _read_float_value(0x8E8); + case 0x00400002: + _read_float_value(0x900); + case 0x00400003: + _read_float_value(0x924); + case 0x00400004: + case 0x00400005: + case 0x004C0006: + case 0x004C0007: + case 0x004C0008: + case 0x004C0009: + _read_float_value(0x91C); + case 0x005D0008: // Strix Point - tested, defaults to 5 (low-power/balanced) or 15 (performance), max 30 + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x9C0); + case 0x00650005: // Krackan Point, might be incorrect + _read_float_value(0x910); + default: + break; + } + return NAN; +} + +EXP float CALL get_core_power(ryzen_access ry, uint32_t core) { + if (core > 15) + return NAN; + + uint32_t baseOffset; + // kevin 0x104 might be power, 16 entries + switch (ry->table_ver) { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + baseOffset = 0x300; + break; + case 0x00370005: + baseOffset = 0x31C; + break; + case 0x003F0000: { // Van Gogh + if (core >= 4) + return NAN; + + baseOffset = 0x238; + } + break; + case 0x00400001: + baseOffset = 0x304; + break; + case 0x00400004: + case 0x00400005: + baseOffset = 0x320; + break; + case 0x005D0008: // Strix Point - manufacturer-disabled cores are 0W (12 cores in total) + case 0x005D0009: + case 0x005D000B: + baseOffset = 0x9D8; + break; + case 0x0064020c: // Strix Halo + baseOffset = 0xB90; + break; + default: + return NAN; + } + + _read_float_value(baseOffset + (core * 4)); +} + +EXP float CALL get_core_volt(ryzen_access ry, uint32_t core) { + if (core > 15) + return NAN; + + uint32_t baseOffset; + // kevinh 0x1cc - 17 entries? + switch (ry->table_ver) { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + baseOffset = 0x320; + break; + case 0x00370005: + baseOffset = 0x33C; + break; + case 0x003F0000: { // Van Gogh + if (core >= 4) + return NAN; + + baseOffset = 0x248; + } + break; + case 0x00400004: + case 0x00400005: + baseOffset = 0x340; + break; + case 0x005D0008: // Strix Point - manufacturer-disabled cores are 0V + case 0x005D0009: + case 0x005D000B: + baseOffset = 0xA08; + break; + case 0x0064020c: // Strix Halo + baseOffset = 0xBD0; + break; + default: + return NAN; + } + + _read_float_value(baseOffset + (core * 4)); +} + +EXP float CALL get_core_temp(ryzen_access ry, uint32_t core) { + if (core > 15) + return NAN; + + uint32_t baseOffset; + + switch (ry->table_ver) { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + baseOffset = 0x340; + break; + case 0x00370005: + baseOffset = 0x35C; + break; + case 0x003F0000: { // Van Gogh + if (core >= 4) + return NAN; + + baseOffset = 0x258; + } + break; + case 0x00400004: + case 0x00400005: + baseOffset = 0x360; + break; + case 0x005D0008: // Strix Point - manufacturer-disabled cores also have temp collected + case 0x005D0009: + case 0x005D000B: + baseOffset = 0xA38; + break; + case 0x0064020c: // Strix Halo + baseOffset = 0xC10; + break; + default: + return NAN; + } + + _read_float_value(baseOffset + (core * 4)); +} + +EXP float CALL get_core_clk(ryzen_access ry, uint32_t core) { + if (core > 15) + return NAN; + + uint32_t baseOffset; + + switch (ry->table_ver) { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + baseOffset = 0x3A0; + break; + case 0x00370005: + baseOffset = 0x3BC; + break; + case 0x003F0000: { // Van Gogh + if (core >= 4) + return NAN; + + baseOffset = 0x288; + } + break; + case 0x00400004: + case 0x00400005: + baseOffset = 0x3c0; + break; + case 0x005D0008: // Strix Point - manufacturer-disabled cores are 0GHz + case 0x005D0009: + case 0x005D000B: + baseOffset = 0xA68; + break; + case 0x0064020c: + baseOffset = 0xc50; + break; + default: + return NAN; + } + + _read_float_value(baseOffset + (core * 4)); +} + +EXP float CALL get_l3_clk(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x568); + case 0x00370005: + _read_float_value(0x584); + case 0x003F0000: //Van Gogh + _read_float_value(0x35C); //860 + case 0x00400004: + case 0x00400005: + _read_float_value(0x614); //1556 + default: + break; + } + return NAN; +} + +EXP float CALL get_l3_logic(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x540); + case 0x00370005: + _read_float_value(0x55C); + case 0x003F0000: //Van Gogh + _read_float_value(0x348); //840 + case 0x00400004: + case 0x00400005: + _read_float_value(0x600); //1536 + default: + break; + } + return NAN; +} + +EXP float CALL get_l3_vddm(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x548); + case 0x00370005: + _read_float_value(0x564); + case 0x003F0000: //Van Gogh + _read_float_value(0x34C); //844 + case 0x00400004: + case 0x00400005: + _read_float_value(0x604); //1540 + default: + break; + } + return NAN; +} + +EXP float CALL get_l3_temp(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x550); + case 0x00370005: + _read_float_value(0x56C); + case 0x003F0000: //Van Gogh + _read_float_value(0x350); //848 + case 0x00400004: + case 0x00400005: + _read_float_value(0x608); //1544 + default: + break; + } + return NAN; +} + +EXP float CALL get_gfx_clk(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x5B4); + case 0x00370005: + _read_float_value(0x5D0); + case 0x00400001: + _read_float_value(0x60C); //1548 + case 0x00400002: + _read_float_value(0x624); //1572 + case 0x00400003: + _read_float_value(0x644); //1604 + case 0x00400004: + case 0x00400005: + _read_float_value(0x648); //1608 + case 0x003F0000: //Van Gogh + _read_float_value(0x388); //904 + /* + * Strix Point - tested, RAPL + gpu_metrics_v3_0 + llama-bench/FurMark/memtester + switching platform_profile + * 0x4B4: gfx power, as FurMark consumes more than llama-bench when pkg power is constant + * memtester won't increase this, so this is not uncore power + * 0x4B8: volt, 4BC: temp, 4C{0,4}: gfx-related clk (MHz), 4C8: unknown clk + */ + case 0x005D0008: + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x4C0); // 4C0 and 4C4 are always close to each other, but 4C0 seems more correct + case 0x0064020c: // Strix Halo + _read_float_value(0x558); + default: + break; + } + return NAN; +} + +EXP float CALL get_gfx_volt(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x5A8); + case 0x00370005: + _read_float_value(0x5C4); + case 0x00400001: + _read_float_value(0x600); //1536 + case 0x00400002: + _read_float_value(0x618); //1560 + case 0x00400003: + _read_float_value(0x638); //1592 + case 0x00400004: + case 0x00400005: + _read_float_value(0x63C); //1596 + case 0x003F0000: //Van Gogh + _read_float_value(0x37C); //896 + case 0x005D0008: // Strix Point + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x4B8); + case 0x0064020c: // Strix Halo + _read_float_value(0x54C); + default: + break; + } + return NAN; +} + +EXP float CALL get_gfx_temp(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x5AC); + case 0x00370005: + _read_float_value(0x5C8); + case 0x00400001: + _read_float_value(0x604); //1540 + case 0x00400002: + _read_float_value(0x61C); //1564 + case 0x00400003: + _read_float_value(0x63C); //1596 + case 0x00400004: + case 0x00400005: + _read_float_value(0x640); //1600 + case 0x003F0000: //Van Gogh + _read_float_value(0x380); //896 + case 0x005D0008: // Strix Point + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x4C8); + case 0x0064020c: // Strix Halo + _read_float_value(0x550); + default: + break; + } + return NAN; +} + +EXP float CALL get_fclk(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x5CC); + case 0x00370005: + _read_float_value(0x5E8); + case 0x003F0000: //Van Gogh + _read_float_value(0x3C5); //956 + case 0x00400004: + case 0x00400005: + _read_float_value(0x664); //1636 + /* + * Strix Point - tested (AI 365, LPDDR5-7500), switching platform_profile + * 0x4E0-0x548 seems to be target clocks (avg between two reads) + * 0x550-0x5B4 seems to be corresponding sampled clocks (avg between two reads) + * 0x550-0x568 maybe 0 if platform_profile is low-power + * 0x4E0/54C: fabric_clk = gpu_metrics_v3_0.average_fclk_frequency + * 0x4E4/550: controller_clk? = DRM_IOCTL_AMDGPU_INFO GFX_MCLK/gpu_metrics_v3_0.average_uclk_frequency + * 0x4E8/554: phy_clk? = controller_clk * 2 + * 0x4EC/558: transfer rate (MT/s) = phy_clk * 2 (low-power) or 4 (balanced/performance) + * 0x4F0/55C: = gpu_metrics_v3_0.average_vclk_frequency + * 0x4F8/564: = gpu_metrics_v3_0.average_socclk_frequency + * 0x50C/578: = gpu_metrics_v3_0.average_mpipu_frequency + * 0x510/57C: = gpu_metrics_v3_0.average_ipuclk_frequency + * Possible 0x4E0 values: + * 400, 1050, 1200, 1400, 1600, 1960 (declared at 0x240-0x254) + * 400, 1050, 1200, 1400, 1500, 1600, 1800, 1960 (amdgpu sysfs pp_dpm_fclk) + * Possible 0x4E4 values: + * 400, 800, 937.5, 937.5, 937.5, 937.5 (declared at 0x258-0x26C) + * 400, 800, 937.5 (amdgpu sysfs pp_dpm_mclk) + * Possible 0x4E8 values: + * 800, 1600, 1875, 1875, 1875, 1875 (declared at 0x270-0x284) + * Possible 0x4EC values: + * 1600, 6400, 7500, 7500, 7500, 7500 (declared at 0x288-0x29C) + * Possible 0x4F0 values: + * 800, 800, 1605, 1766, 1962, 2208, 2523, 2944 (amdgpu sysfs pp_dpm_vclk) + * Possible 0x4F8 values: + * 600, 736, 883, 981, 1104, 1261, 1261, 1472 (amdgpu sysfs pp_dpm_socclk) + * See also: https://semiengineering.com/advantages-of-lpddr5-a-new-clocking-scheme/ + */ + case 0x005D0008: + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x4E0); + default: + break; + } + return NAN; +} + +EXP float CALL get_mem_clk(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x5D4); + case 0x00370005: + _read_float_value(0x5F0); + case 0x003F0000: //Van Gogh + _read_float_value(0x3C4); //964 + case 0x00400004: + case 0x00400005: + _read_float_value(0x66c); //1644 + case 0x005D0008: // Strix Point - see above + case 0x005D0009: + case 0x005D000B: + _read_float_value(0x4EC); // FIXME: which one? + default: + break; + } + return NAN; +} + +EXP float CALL get_soc_volt(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x198); + case 0x00370005: + _read_float_value(0x198); + case 0x003F0000: //Van Gogh + _read_float_value(0x1A0); //416 + case 0x00400004: + case 0x00400005: + _read_float_value(0x19c); //412 + + default: + break; + } + return NAN; +} + +EXP float CALL get_soc_power(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + _read_float_value(0x1A0); + case 0x00370005: + _read_float_value(0x1A0); + case 0x003F0000: //Van Gogh + _read_float_value(0x1A8); //424 + case 0x00400004: + case 0x00400005: + _read_float_value(0x1a4); //420 + default: + break; + } + return NAN; +} + +EXP float CALL get_socket_power(ryzen_access ry) { + switch (ry->table_ver) + { + case 0x00370000: + case 0x00370001: + case 0x00370002: + case 0x00370003: + case 0x00370004: + case 0x00370005: + _read_float_value(0x98); + case 0x00400001: + case 0x00400002: + case 0x00400003: + case 0x00400004: + case 0x00400005: + _read_float_value(0x98); //152 + case 0x003F0000: //Van Gogh + _read_float_value(0xA8); //168 + /* + * Strix Point - tested, RAPL + memtester/llama-bench + switching platform_profile + * 0xA4: = RAPL pkg power - gfx power, should this be SoC power??? + * 0xD0: = RAPL pkg power + */ + case 0x005D0008: + case 0x005D0009: + case 0x005D000B: + _read_float_value(0xD0); + default: + break; + } + return NAN; +} diff --git a/os/licenses/ryzenadj/src/lib/cpuid.c b/os/licenses/ryzenadj/src/lib/cpuid.c new file mode 100644 index 00000000..505fc9ef --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/cpuid.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: LGPL +/* Copyright (C) 2020 Jiaxun Yang */ +/* RyzenAdj CPU ID stuff */ + +#include "ryzenadj.h" + +#include + +#if defined(__GNUC__) +#include +#elif defined (_WIN32) +#include +#endif + +#define CPUID_VENDOR_AMD "AuthenticAMD" + +static void getcpuid(unsigned int CPUInfo[4], unsigned int InfoType) +{ +#if defined(__GNUC__) + __cpuid(InfoType, CPUInfo[0],CPUInfo[1],CPUInfo[2],CPUInfo[3]); +#elif defined(_WIN32) + __cpuid((int*)(void*)CPUInfo, (int)InfoType); +#endif +} + +static enum ryzen_family cpuid_family = WAIT_FOR_LOAD; + +static enum ryzen_family cpuid_load_family() +{ + uint32_t regs[4]; + int family, model; + char vendor[4 * 4]; + + getcpuid(regs, 0); + + /* Hack Alert! Put into str buffer */ + *(uint32_t *) &vendor[0] = regs[1]; + *(uint32_t *) &vendor[4] = regs[3]; + *(uint32_t *) &vendor[8] = regs[2]; + + if (strncmp(vendor, CPUID_VENDOR_AMD, strlen(CPUID_VENDOR_AMD))) { + printf("Not AMD processor, must be kidding\n"); + return FAM_UNKNOWN; + } + + getcpuid(regs, 1); + + family = ((regs[0] >> 8) & 0xf) + ((regs[0] >> 20) & 0xff); + model = ((regs[0] >> 4) & 0xf) | ((regs[0] >> 12) & 0xf0); + + switch (family) { + case 0x17: /* Zen, Zen+, Zen2 */ + switch (model) { + case 17: + return FAM_RAVEN; + case 24: + return FAM_PICASSO; + case 32: + return FAM_DALI; + case 96: + return FAM_RENOIR; + case 104: + return FAM_LUCIENNE; + case 144: + case 145: + return FAM_VANGOGH; + case 160: + return FAM_MENDOCINO; + default: + printf("Fam%xh: unsupported model %d\n", family, model); + break; + }; + break; + + case 0x19: /* Zen3, Zen4 */ + switch (model) { + case 80: + return FAM_CEZANNE; + case 64: + case 68: + return FAM_REMBRANDT; + case 97: + return FAM_DRAGONRANGE; + case 116: + case 120: + return FAM_PHOENIX; + case 117: + return FAM_HAWKPOINT; + default: + printf("Fam%xh: unsupported model %d\n", family, model); + break; + }; + break; + case 0x1A: /* Zen5, Zen6 */ + switch (model) { + case 32: + case 36: + return FAM_STRIXPOINT; + case 68: + return FAM_FIRERANGE; + case 96: + return FAM_KRACKANPOINT; + case 112: + return FAM_STRIXHALO; + default: + printf("Fam%xh: unsupported model %d\n", family, model); + break; + } + + default: + printf("Unsupported family: %xh\n", family); + break; + } + + printf("Only Ryzen Mobile Series are supported\n"); + return FAM_UNKNOWN; +} + +/* + * The function cpuid_load_family() processes a lot of + * information. This information will be used several + * times by acquiring the same data. Avoid this wasted + * computation by entering cpuid_get_family(). This function + * guarantees the load is done only 1 time. + */ +enum ryzen_family cpuid_get_family() { + if (cpuid_family == WAIT_FOR_LOAD) + cpuid_family = cpuid_load_family(); + + return cpuid_family; +} diff --git a/os/licenses/ryzenadj/src/lib/linux/osdep_linux.c b/os/licenses/ryzenadj/src/lib/linux/osdep_linux.c new file mode 100644 index 00000000..ee1d1b61 --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/linux/osdep_linux.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: LGPL +/* Copyright (C) 2018-2019 Jiaxun Yang */ +#include + +#include "osdep_linux_mem.h" +#include "osdep_linux_smu_kernel_module.h" + +bool is_smu = false; + +static bool is_ryzen_smu_driver_compatible() { + FILE *drv_ver = fopen("/sys/kernel/ryzen_smu_drv/drv_version", "r"); + int major, minor, patch, ret; + + if (drv_ver == NULL) { + DBG("failed to open drv_version"); + return false; + } + + ret = fscanf(drv_ver, "%d.%d.%d", &major, &minor, &patch); + if (ret == EOF || ret < 3) { + DBG("failed to parse ryzen_smu version string\n"); + fclose(drv_ver); + return false; + } + + if (major != 0 || minor != 1 || patch < 7) { + fclose(drv_ver); + return false; + } + + fclose(drv_ver); + return true; +} + +os_access_obj_t *init_os_access_obj() { + struct stat stats; + + if (lstat("/sys/kernel/ryzen_smu_drv", &stats) == 0 && is_ryzen_smu_driver_compatible()) { + fprintf(stderr, "detected compatible ryzen_smu kernel module\n"); + is_smu = true; + return init_os_access_obj_kmod(); + } + + fprintf(stderr, "no compatible ryzen_smu kernel module found, fallback to /dev/mem\n"); + return init_os_access_obj_mem(); +} + +int init_mem_obj(os_access_obj_t *os_access, const uintptr_t physAddr) { + if (is_smu) + return init_mem_obj_kmod(os_access, physAddr); + + return init_mem_obj_mem(os_access, physAddr); +} + +void free_os_access_obj(os_access_obj_t *obj) { + if (is_smu) + free_os_access_obj_kmod(obj); + else + free_os_access_obj_mem(obj); + + is_smu = false; +} + +uint32_t smn_reg_read(const os_access_obj_t *obj, const uint32_t addr) { + if (is_smu) + return smn_reg_read_kmod(obj, addr); + + return smn_reg_read_mem(obj, addr); +} + +void smn_reg_write(const os_access_obj_t *obj, const uint32_t addr, const uint32_t data) { + if (is_smu) + smn_reg_write_kmod(obj, addr, data); + else + smn_reg_write_mem(obj, addr, data); +} + +int copy_pm_table(const os_access_obj_t *obj, void *buffer, const size_t size) { + if (is_smu) + return copy_pm_table_kmod(obj, buffer, size); + + return copy_pm_table_mem(obj, buffer, size); +} + +int compare_pm_table(const void *buffer, const size_t size) { + if (is_smu) + return compare_pm_table_kmod(buffer, size); + + return compare_pm_table_mem(buffer, size); +} + +bool is_using_smu_driver() { + return is_smu; +} diff --git a/os/licenses/ryzenadj/src/lib/linux/osdep_linux_mem.c b/os/licenses/ryzenadj/src/lib/linux/osdep_linux_mem.c new file mode 100644 index 00000000..5bf2db18 --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/linux/osdep_linux_mem.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: LGPL +/* Copyright (C) 2018-2019 Jiaxun Yang */ +/* Access PCI Config Space - libpci */ +#include +#include +#include +#include +#include +#include + +#include "osdep_linux_mem.h" + +void *phy_map = MAP_FAILED; + +os_access_obj_t *init_os_access_obj_mem() { + os_access_obj_t *obj = malloc(sizeof(os_access_obj_t)); + + if (obj == NULL) + return NULL; + + memset(obj, 0, sizeof(os_access_obj_t)); + + obj->access.mem.pci_acc = pci_alloc(); + if (!obj->access.mem.pci_acc) { + fprintf(stderr, "pci_alloc failed\n"); + goto err_exit; + } + + pci_init(obj->access.mem.pci_acc); + + obj->access.mem.pci_dev = pci_get_dev(obj->access.mem.pci_acc, 0, 0, 0, 0); + if (!obj->access.mem.pci_dev) { + fprintf(stderr, "Unable to get pci device\n"); + pci_cleanup(obj->access.mem.pci_acc); + goto err_exit; + } + + pci_fill_info(obj->access.mem.pci_dev, PCI_FILL_IDENT | PCI_FILL_BASES | PCI_FILL_CLASS); + return obj; + +err_exit: + free(obj); + return NULL; +} + +int init_mem_obj_mem([[maybe_unused]] os_access_obj_t *os_access, const uintptr_t physAddr) { + const int dev_mem_fd = open("/dev/mem", O_RDONLY); + + // It is too complicated to check PAT, CONFIG_NONPROMISC_DEVMEM, CONFIG_STRICT_DEVMEM or other dependencies, just try to open /dev/mem + if (dev_mem_fd > 0) { + phy_map = mmap(NULL, 0x1000, PROT_READ, MAP_SHARED, dev_mem_fd, (long)physAddr); + close(dev_mem_fd); + } + + return phy_map == MAP_FAILED ? -1 : 0; +} + +void free_os_access_obj_mem(os_access_obj_t *obj) { + if (obj == NULL) + return; + + if (obj->access.mem.pci_dev) + pci_free_dev(obj->access.mem.pci_dev); + + if (obj->access.mem.pci_acc) + pci_cleanup(obj->access.mem.pci_acc); + + if (phy_map != MAP_FAILED) { + munmap(phy_map, 0x1000); + phy_map = MAP_FAILED; + } + + free(obj); +} + +uint32_t smn_reg_read_mem(const os_access_obj_t *obj, const uint32_t addr) { + pci_write_long(obj->access.mem.pci_dev, NB_PCI_REG_ADDR_ADDR, addr & (~0x3)); + return pci_read_long(obj->access.mem.pci_dev, NB_PCI_REG_DATA_ADDR); +} + +void smn_reg_write_mem(const os_access_obj_t *obj, const uint32_t addr, const uint32_t data) { + pci_write_long(obj->access.mem.pci_dev, NB_PCI_REG_ADDR_ADDR, addr); + pci_write_long(obj->access.mem.pci_dev, NB_PCI_REG_DATA_ADDR, data); +} + +int copy_pm_table_mem([[maybe_unused]] const os_access_obj_t *obj, void *buffer, const size_t size) { + if (phy_map != MAP_FAILED) { + memcpy(buffer, phy_map, size); + return 0; + } + + DBG("failed to get pm_table from /dev/mem\n"); + return -1; +} + +int compare_pm_table_mem(const void *buffer, const size_t size) { + return memcmp(buffer, phy_map, size); +} diff --git a/os/licenses/ryzenadj/src/lib/linux/osdep_linux_mem.h b/os/licenses/ryzenadj/src/lib/linux/osdep_linux_mem.h new file mode 100644 index 00000000..baba82b3 --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/linux/osdep_linux_mem.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL +/* Copyright (C) 2018-2019 Jiaxun Yang */ +/* Access PCI Config Space - libpci */ +#pragma once + +#include "../nb_smu_ops.h" + +os_access_obj_t *init_os_access_obj_mem(); +int init_mem_obj_mem(os_access_obj_t *os_access, uintptr_t physAddr); +int copy_pm_table_mem(const os_access_obj_t *obj, void *buffer, size_t size); +int compare_pm_table_mem(const void *buffer, size_t size); +void free_os_access_obj_mem(os_access_obj_t *obj); + +uint32_t smn_reg_read_mem(const os_access_obj_t *obj, uint32_t addr); +void smn_reg_write_mem(const os_access_obj_t *obj, uint32_t addr, uint32_t data); diff --git a/os/licenses/ryzenadj/src/lib/linux/osdep_linux_smu_kernel_module.c b/os/licenses/ryzenadj/src/lib/linux/osdep_linux_smu_kernel_module.c new file mode 100644 index 00000000..41ef6209 --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/linux/osdep_linux_smu_kernel_module.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: LGPL +/* Backend which uses the sysfs interface provided by the ryzen_smu kernel module. */ +#include +#include +#include +#include +#include + +#include "osdep_linux_smu_kernel_module.h" + +static uint32_t get_pm_table_size() { + const int fd = open("/sys/kernel/ryzen_smu_drv/pm_table_size", O_RDONLY); + uint32_t table_sz = 0; + + if (fd == -1) + return -1; + + if (read(fd, &table_sz, sizeof(table_sz)) == -1) { + DBG("failed to retrieve PM table size: %s\n", strerror(errno)); + close(fd); + return -1; + } + + close(fd); + return table_sz; +} + +os_access_obj_t *init_os_access_obj_kmod() { + os_access_obj_t *obj = malloc(sizeof(os_access_obj_t)); + + if (obj == NULL) + return NULL; + + memset(obj, 0, sizeof(os_access_obj_t)); + + obj->access.kmod.pm_table_size = get_pm_table_size(); + if (obj->access.kmod.pm_table_size == -1) + goto err_exit; + + obj->access.kmod.smn_fd = open("/sys/kernel/ryzen_smu_drv/smn", O_RDWR); + if (obj->access.kmod.smn_fd == -1) { + DBG("failed to open smn fd: %s\n", strerror(errno)); + goto err_exit; + } + + obj->access.kmod.pm_table_fd = open("/sys/kernel/ryzen_smu_drv/pm_table", O_RDONLY); + if (obj->access.kmod.pm_table_fd == -1) { + DBG("failed to open pm_table fd: %s\n", strerror(errno)); + close(obj->access.kmod.smn_fd); + goto err_exit; + } + + return obj; + +err_exit: + free(obj); + return NULL; +} + +int init_mem_obj_kmod([[maybe_unused]] os_access_obj_t *os_access, [[maybe_unused]] const uintptr_t physAddr) { + return 0; +} + +void free_os_access_obj_kmod(os_access_obj_t *obj) { + close(obj->access.kmod.smn_fd); + close(obj->access.kmod.pm_table_fd); + free(obj); +} + +uint32_t smn_reg_read_kmod(const os_access_obj_t *obj, const uint32_t addr) { + uint32_t result = 0; + + lseek(obj->access.kmod.smn_fd, 0, SEEK_SET); + + if (write(obj->access.kmod.smn_fd, &addr, sizeof(addr)) == -1) { + DBG("%s: write error: %s\n", __func__, strerror(errno)); + return 0; + } + + lseek(obj->access.kmod.smn_fd, 0, SEEK_SET); + + if (read(obj->access.kmod.smn_fd, &result, sizeof(result)) == -1) { + DBG("%s: read error: %s\n", __func__, strerror(errno)); + return 0; + } + + return result; +} + +void smn_reg_write_kmod(const os_access_obj_t *obj, const uint32_t addr, const uint32_t data) { + const uint32_t write_buffer[2] = { addr, data }; + + lseek(obj->access.kmod.smn_fd, 0, SEEK_SET); + + if (write(obj->access.kmod.smn_fd, &write_buffer, sizeof(write_buffer)) == -1) + DBG("%s: error: %s\n", __func__, strerror(errno)); +} + +int copy_pm_table_kmod(const os_access_obj_t *obj, void *buffer, const size_t size) { + if (obj->access.kmod.pm_table_size < size) { + DBG("PM table size too small: ryzenadj (%zd) | ryzen_smu (%zd)\n", size, obj->access.kmod.pm_table_size); + return -1; + } + + if (obj->access.kmod.pm_table_size != size) { + DBG("PM table size mismatch (reading prefix): ryzenadj (%zd) | ryzen_smu (%zd)\n", size, obj->access.kmod.pm_table_size); + } + + lseek(obj->access.kmod.pm_table_fd, 0, SEEK_SET); + + if (read(obj->access.kmod.pm_table_fd, buffer, size) == -1) { + DBG("%s: error: %s\n", __func__, strerror(errno)); + return -1; + } + + return 0; +} + +int compare_pm_table_kmod([[maybe_unused]] const void *buffer, [[maybe_unused]] size_t size) { + DBG("internal error: compare_pm_table() should never be called if ryzen_smu is loaded\n"); + return -1; +} diff --git a/os/licenses/ryzenadj/src/lib/linux/osdep_linux_smu_kernel_module.h b/os/licenses/ryzenadj/src/lib/linux/osdep_linux_smu_kernel_module.h new file mode 100644 index 00000000..ef32d08a --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/linux/osdep_linux_smu_kernel_module.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LGPL +#pragma once + +#include "../nb_smu_ops.h" + +os_access_obj_t *init_os_access_obj_kmod(); +int init_mem_obj_kmod(os_access_obj_t *os_access, uintptr_t physAddr); +int copy_pm_table_kmod(const os_access_obj_t *obj, void *buffer, size_t size); +int compare_pm_table_kmod(const void *buffer, size_t size); +void free_os_access_obj_kmod(os_access_obj_t *obj); + +uint32_t smn_reg_read_kmod(const os_access_obj_t *obj, uint32_t addr); +void smn_reg_write_kmod(const os_access_obj_t *obj, uint32_t addr, uint32_t data); diff --git a/os/licenses/ryzenadj/src/lib/nb_smu_ops.c b/os/licenses/ryzenadj/src/lib/nb_smu_ops.c new file mode 100644 index 00000000..ff7c8f9f --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/nb_smu_ops.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: LGPL +/* Copyright (C) 2018-2019 Jiaxun Yang */ +/* Ryzen NB SMU Service Request Operations */ +#include + +#include "ryzenadj.h" + +#define MP1_C2PMSG_MESSAGE_ADDR_1 0x3B10528 +#define MP1_C2PMSG_RESPONSE_ADDR_1 0x3B10564 +#define MP1_C2PMSG_ARG_BASE_1 0x3B10998 + +/* For Vangogh and Rembrandt */ +#define MP1_C2PMSG_MESSAGE_ADDR_2 0x3B10528 +#define MP1_C2PMSG_RESPONSE_ADDR_2 0x3B10578 +#define MP1_C2PMSG_ARG_BASE_2 0x3B10998 + +/* For Strix Point */ +#define MP1_C2PMSG_MESSAGE_ADDR_3 0x3b10928 +#define MP1_C2PMSG_RESPONSE_ADDR_3 0x3b10978 +#define MP1_C2PMSG_ARG_BASE_3 0x3b10998 + +#define PSMU_C2PMSG_MESSAGE_ADDR_1 0x3B10a20 +#define PSMU_C2PMSG_RESPONSE_ADDR_1 0x3B10a80 +#define PSMU_C2PMSG_ARG_BASE_1 0x3B10a88 + +/* For Dragon and Fire Range */ +#define MP1_C2PMSG_MESSAGE_ADDR_4 0x3B10530 +#define MP1_C2PMSG_RESPONSE_ADDR_4 0x3B1057C +#define MP1_C2PMSG_ARG_BASE_4 0x3B109C4 + +#define PSMU_C2PMSG_MESSAGE_ADDR_2 0x03B10524 +#define PSMU_C2PMSG_RESPONSE_ADDR_2 0x03B10570 +#define PSMU_C2PMSG_ARG_BASE_2 0x03B10A40 + +/* +* All the SMU have the same TestMessage as for now +* Correct me if they don't +*/ +#define SMU_TEST_MSG 0x1 + +static uint32_t c2pmsg_argX_addr(const uint32_t base, const uint32_t offt) { + return base + 4 * offt; +} + +uint32_t smu_service_req(smu_t smu, const uint32_t id, smu_service_args_t *args) { + uint32_t response = 0x0; + DBG("SMU_SERVICE REQ_ID:0x%x\n", id); + DBG("SMU_SERVICE REQ: arg0: 0x%x, arg1:0x%x, arg2:0x%x, arg3:0x%x, arg4: 0x%x, arg5: 0x%x\n", \ + args->arg0, args->arg1, args->arg2, args->arg3, args->arg4, args->arg5); + + /* Clear the response */ + smn_reg_write(smu->os_access, smu->rep, 0x0); + /* Pass arguments */ + smn_reg_write(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 0), args->arg0); + smn_reg_write(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 1), args->arg1); + smn_reg_write(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 2), args->arg2); + smn_reg_write(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 3), args->arg3); + smn_reg_write(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 4), args->arg4); + smn_reg_write(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 5), args->arg5); + /* Send message ID */ + smn_reg_write(smu->os_access, smu->msg, id); + /* Wait until response changed */ + while(response == 0x0) { + response = smn_reg_read(smu->os_access, smu->rep); + } + /* Read back arguments */ + args->arg0 = smn_reg_read(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 0)); + args->arg1 = smn_reg_read(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 1)); + args->arg2 = smn_reg_read(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 2)); + args->arg3 = smn_reg_read(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 3)); + args->arg4 = smn_reg_read(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 4)); + args->arg5 = smn_reg_read(smu->os_access, c2pmsg_argX_addr(smu->arg_base, 5)); + + DBG("SMU_SERVICE REP: REP: 0x%x, arg0: 0x%x, arg1:0x%x, arg2:0x%x, arg3:0x%x, arg4: 0x%x, arg5: 0x%x\n", \ + response, args->arg0, args->arg1, args->arg2, args->arg3, args->arg4, args->arg5); + + return response; +} + +static int smu_service_test(smu_t smu) +{ + uint32_t response = 0x0; + + /* Clear the response */ + smn_reg_write(smu->os_access, smu->rep, 0x0); + /* Test message with unique argument */ + smn_reg_write(smu->os_access, smu->arg_base, 0x47); + if(smn_reg_read(smu->os_access, smu->arg_base) != 0x47){ + printf("PCI Bus is not writeable, check secure boot\n"); + return 0; + } + + /* Send message ID */ + smn_reg_write(smu->os_access, smu->msg, SMU_TEST_MSG); + /* Wait until response changed */ + while(response == 0x0) { + response = smn_reg_read(smu->os_access, smu->rep); + } + + return response == REP_MSG_OK; +} + +smu_t get_smu(os_access_obj_t *obj, const int smu_type) { + smu_t smu = malloc(sizeof(*smu)); + + if (smu == NULL) + return NULL; + + smu->os_access = obj; + + /* Fill SMU information */ + switch(smu_type){ + case TYPE_MP1: + switch (cpuid_get_family()) { + case FAM_REMBRANDT: + case FAM_VANGOGH: + case FAM_MENDOCINO: + case FAM_PHOENIX: + case FAM_HAWKPOINT: + smu->msg = MP1_C2PMSG_MESSAGE_ADDR_2; + smu->rep = MP1_C2PMSG_RESPONSE_ADDR_2; + smu->arg_base = MP1_C2PMSG_ARG_BASE_2; + break; + case FAM_KRACKANPOINT: + case FAM_STRIXPOINT: + case FAM_STRIXHALO: + smu->msg = MP1_C2PMSG_MESSAGE_ADDR_3; + smu->rep = MP1_C2PMSG_RESPONSE_ADDR_3; + smu->arg_base = MP1_C2PMSG_ARG_BASE_3; + break; + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + smu->msg = MP1_C2PMSG_MESSAGE_ADDR_4; + smu->rep = MP1_C2PMSG_RESPONSE_ADDR_4; + smu->arg_base = MP1_C2PMSG_ARG_BASE_4; + break; + default: + smu->msg = MP1_C2PMSG_MESSAGE_ADDR_1; + smu->rep = MP1_C2PMSG_RESPONSE_ADDR_1; + smu->arg_base = MP1_C2PMSG_ARG_BASE_1; + break; + } + break; + case TYPE_PSMU: + switch (cpuid_get_family()) { + case FAM_DRAGONRANGE: + case FAM_FIRERANGE: + smu->msg = PSMU_C2PMSG_MESSAGE_ADDR_2; + smu->rep = PSMU_C2PMSG_RESPONSE_ADDR_2; + smu->arg_base = PSMU_C2PMSG_ARG_BASE_2; + break; + default: + smu->msg = PSMU_C2PMSG_MESSAGE_ADDR_1; + smu->rep = PSMU_C2PMSG_RESPONSE_ADDR_1; + smu->arg_base = PSMU_C2PMSG_ARG_BASE_1; + break; + } + break; + default: + DBG("Failed to get SMU, unknown SMU_TYPE: %i\n", smu_type); + goto err; + break; + } + + if(smu_service_test(smu)){ + return smu; + } else { + DBG("Failed to get SMU, SMU_TYPE: %i\n", smu_type); + goto err; + } +err: + free(smu); + return NULL; +} diff --git a/os/licenses/ryzenadj/src/lib/nb_smu_ops.h b/os/licenses/ryzenadj/src/lib/nb_smu_ops.h new file mode 100644 index 00000000..dfb2d48d --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/nb_smu_ops.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL */ +/* Copyright (C) 2018-2019 Jiaxun Yang */ +/* Ryzen NB SMU Service Request Operations */ + +#pragma once + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif +#include +#include +#include + +#ifdef NDEBUG +#define DBG(...) +#else +#define DBG(...) fprintf(stderr, __VA_ARGS__) +#endif + +enum SMU_TYPE{ + TYPE_MP1, + TYPE_PSMU, + TYPE_COUNT, +}; + +#define NB_PCI_REG_ADDR_ADDR 0xB8 +#define NB_PCI_REG_DATA_ADDR 0xBC + +#define REP_MSG_OK 0x1 +#define REP_MSG_Failed 0xFF +#define REP_MSG_UnknownCmd 0xFE +#define REP_MSG_CmdRejectedPrereq 0xFD +#define REP_MSG_CmdRejectedBusy 0xFC + +typedef struct _smu_service_args_t { + uint32_t arg0; + uint32_t arg1; + uint32_t arg2; + uint32_t arg3; + uint32_t arg4; + uint32_t arg5; +} smu_service_args_t; + +typedef struct { +#ifdef _WIN32 + uint32_t pci_address; + HINSTANCE inpoutDll; +#else + union { + struct { + struct pci_access *pci_acc; + struct pci_dev *pci_dev; + } mem; + struct { + int smn_fd; + int pm_table_fd; + size_t pm_table_size; + } kmod; + } access; +#endif +} os_access_obj_t; + +typedef struct _smu_t { + os_access_obj_t *os_access; + uint32_t msg; + uint32_t rep; + uint32_t arg_base; +} *smu_t; + +os_access_obj_t *init_os_access_obj(); +int init_mem_obj(os_access_obj_t *os_access, uintptr_t physAddr); +int copy_pm_table(const os_access_obj_t *obj, void *buffer, size_t size); +int compare_pm_table(const void *buffer, size_t size); +void free_os_access_obj(os_access_obj_t *obj); + +uint32_t smn_reg_read(const os_access_obj_t *obj, uint32_t addr); +void smn_reg_write(const os_access_obj_t *obj, uint32_t addr, uint32_t data); +bool is_using_smu_driver(); + +smu_t get_smu(os_access_obj_t *obj, int smu_type); +uint32_t smu_service_req(smu_t smu, uint32_t id, smu_service_args_t *args); diff --git a/os/licenses/ryzenadj/src/lib/ryzenadj.h b/os/licenses/ryzenadj/src/lib/ryzenadj.h new file mode 100644 index 00000000..56be8939 --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/ryzenadj.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: LGPL */ +/* Copyright (C) 2019 Jiaxun Yang */ +/* RyzenAdj API */ + +#ifndef RYZENADJ_H +#define RYZENADJ_H +#ifdef __cplusplus +extern "C" { +#endif + +#define RYZENADJ_REVISION_VER 0 +#define RYZENADJ_MAJOR_VER 19 +#define RYZENADJ_MINIOR_VER 0 + +enum ryzen_family { + WAIT_FOR_LOAD = -2, + FAM_UNKNOWN = -1, + FAM_RAVEN = 0, + FAM_PICASSO, + FAM_RENOIR, + FAM_CEZANNE, + FAM_DALI, + FAM_LUCIENNE, + FAM_VANGOGH, + FAM_REMBRANDT, + FAM_MENDOCINO, + FAM_PHOENIX, + FAM_HAWKPOINT, + FAM_DRAGONRANGE, + FAM_KRACKANPOINT, + FAM_STRIXPOINT, + FAM_STRIXHALO, + FAM_FIRERANGE, + FAM_END +}; + +#ifdef _LIBRYZENADJ_INTERNAL +#include "ryzenadj_priv.h" + +#ifdef _WIN32 +#define EXP __declspec(dllexport) +#define CALL __stdcall +#else +#define EXP __attribute__((visibility("default"))) +#define CALL +#endif + +#else + +#ifdef _WIN32 +#define EXP __declspec(dllimport) +#define CALL __stdcall +#else +#define EXP +#define CALL +#endif +struct _ryzen_access; + +#endif + +#define ADJ_ERR_FAM_UNSUPPORTED -1 +#define ADJ_ERR_SMU_TIMEOUT -2 +#define ADJ_ERR_SMU_UNSUPPORTED -3 +#define ADJ_ERR_SMU_REJECTED -4 +#define ADJ_ERR_MEMORY_ACCESS -5 + +typedef struct _ryzen_access *ryzen_access; + +EXP ryzen_access CALL init_ryzenadj(); + +EXP void CALL cleanup_ryzenadj(ryzen_access ry); + +EXP enum ryzen_family get_cpu_family(ryzen_access ry); +EXP int get_bios_if_ver(ryzen_access ry); + +EXP int CALL init_table(ryzen_access ry); +EXP uint32_t CALL get_table_ver(ryzen_access ry); +EXP size_t CALL get_table_size(ryzen_access ry); +EXP float* CALL get_table_values(ryzen_access ry); +EXP int CALL refresh_table(ryzen_access ry); + +EXP int CALL set_stapm_limit(ryzen_access, uint32_t value); +EXP int CALL set_fast_limit(ryzen_access, uint32_t value); +EXP int CALL set_slow_limit(ryzen_access, uint32_t value); +EXP int CALL set_slow_time(ryzen_access, uint32_t value); +EXP int CALL set_stapm_time(ryzen_access, uint32_t value); +EXP int CALL set_tctl_temp(ryzen_access, uint32_t value); +EXP int CALL set_vrm_current(ryzen_access, uint32_t value); +EXP int CALL set_vrmsoc_current(ryzen_access, uint32_t value); +EXP int CALL set_vrmgfx_current(ryzen_access, uint32_t value); +EXP int CALL set_vrmcvip_current(ryzen_access, uint32_t value); +EXP int CALL set_vrmmax_current(ryzen_access, uint32_t value); +EXP int CALL set_vrmgfxmax_current(ryzen_access, uint32_t value); +EXP int CALL set_vrmsocmax_current(ryzen_access, uint32_t value); +EXP int CALL set_psi0_current(ryzen_access, uint32_t value); +EXP int CALL set_psi3cpu_current(ryzen_access, uint32_t value); +EXP int CALL set_psi0soc_current(ryzen_access, uint32_t value); +EXP int CALL set_psi3gfx_current(ryzen_access, uint32_t value); +EXP int CALL set_max_gfxclk_freq(ryzen_access, uint32_t value); +EXP int CALL set_min_gfxclk_freq(ryzen_access, uint32_t value); +EXP int CALL set_max_socclk_freq(ryzen_access, uint32_t value); +EXP int CALL set_min_socclk_freq(ryzen_access, uint32_t value); +EXP int CALL set_max_fclk_freq(ryzen_access, uint32_t value); +EXP int CALL set_min_fclk_freq(ryzen_access, uint32_t value); +EXP int CALL set_max_vcn(ryzen_access, uint32_t value); +EXP int CALL set_min_vcn(ryzen_access, uint32_t value); +EXP int CALL set_max_lclk(ryzen_access, uint32_t value); +EXP int CALL set_min_lclk(ryzen_access, uint32_t value); +EXP int CALL set_prochot_deassertion_ramp(ryzen_access ry, uint32_t value); +EXP int CALL set_apu_skin_temp_limit(ryzen_access, uint32_t value); +EXP int CALL set_dgpu_skin_temp_limit(ryzen_access, uint32_t value); +EXP int CALL set_apu_slow_limit(ryzen_access, uint32_t value); +EXP int CALL set_skin_temp_power_limit(ryzen_access ry, uint32_t value); +EXP int CALL set_gfx_clk(ryzen_access ry, uint32_t value); +EXP int CALL set_oc_clk(ryzen_access ry, uint32_t value); +EXP int CALL set_per_core_oc_clk(ryzen_access ry, uint32_t value); +EXP int CALL set_oc_volt(ryzen_access ry, uint32_t value); +EXP int CALL set_disable_oc(ryzen_access ry); +EXP int CALL set_enable_oc(ryzen_access ry); +EXP int CALL set_power_saving(ryzen_access ry); +EXP int CALL set_max_performance(ryzen_access ry); +EXP int CALL set_coall(ryzen_access ry, uint32_t value); +EXP int CALL set_coper(ryzen_access ry, uint32_t value); +EXP int CALL set_cogfx(ryzen_access ry, uint32_t value); + +EXP float CALL get_stapm_limit(ryzen_access ry); +EXP float CALL get_stapm_value(ryzen_access ry); +EXP float CALL get_fast_limit(ryzen_access ry); +EXP float CALL get_fast_value(ryzen_access ry); +EXP float CALL get_slow_limit(ryzen_access ry); +EXP float CALL get_slow_value(ryzen_access ry); +EXP float CALL get_apu_slow_limit(ryzen_access ry); +EXP float CALL get_apu_slow_value(ryzen_access ry); +EXP float CALL get_vrm_current(ryzen_access ry); +EXP float CALL get_vrm_current_value(ryzen_access ry); +EXP float CALL get_vrmsoc_current(ryzen_access ry); +EXP float CALL get_vrmsoc_current_value(ryzen_access ry); +EXP float CALL get_vrmmax_current(ryzen_access ry); +EXP float CALL get_vrmmax_current_value(ryzen_access ry); +EXP float CALL get_vrmsocmax_current(ryzen_access ry); +EXP float CALL get_vrmsocmax_current_value(ryzen_access ry); +EXP float CALL get_tctl_temp(ryzen_access ry); +EXP float CALL get_tctl_temp_value(ryzen_access ry); +EXP float CALL get_apu_skin_temp_limit(ryzen_access ry); +EXP float CALL get_apu_skin_temp_value(ryzen_access ry); +EXP float CALL get_dgpu_skin_temp_limit(ryzen_access ry); +EXP float CALL get_dgpu_skin_temp_value(ryzen_access ry); +EXP float CALL get_psi0_current(ryzen_access ry); +EXP float CALL get_psi0soc_current(ryzen_access ry); +EXP float CALL get_stapm_time(ryzen_access ry); +EXP float CALL get_slow_time(ryzen_access ry); +EXP float CALL get_cclk_setpoint(ryzen_access ry); +EXP float CALL get_cclk_busy_value(ryzen_access ry); + +EXP float CALL get_core_clk(ryzen_access ry, uint32_t value); +EXP float CALL get_core_volt(ryzen_access ry, uint32_t value); +EXP float CALL get_core_power(ryzen_access ry, uint32_t value); +EXP float CALL get_core_temp(ryzen_access ry, uint32_t value); + +EXP float CALL get_l3_clk(ryzen_access ry); +EXP float CALL get_l3_logic(ryzen_access ry); +EXP float CALL get_l3_vddm(ryzen_access ry); +EXP float CALL get_l3_temp(ryzen_access ry); + +EXP float CALL get_gfx_clk(ryzen_access ry); +EXP float CALL get_gfx_temp(ryzen_access ry); +EXP float CALL get_gfx_volt(ryzen_access ry); + +EXP float CALL get_mem_clk(ryzen_access ry); +EXP float CALL get_fclk(ryzen_access ry); + +EXP float CALL get_soc_power(ryzen_access ry); +EXP float CALL get_soc_volt(ryzen_access ry); + +EXP float CALL get_socket_power(ryzen_access ry); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/os/licenses/ryzenadj/src/lib/ryzenadj_priv.h b/os/licenses/ryzenadj/src/lib/ryzenadj_priv.h new file mode 100644 index 00000000..3e74de6b --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/ryzenadj_priv.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL */ +/* Copyright (C) 2020 Jiaxun Yang */ +/* RyzenAdj Private stuff */ +/* Do not include this file! */ + +#ifndef RYZENADJ_PRIV_H +#define RYZENADJ_PRIV_H + +#include + +#include "nb_smu_ops.h" + +struct _ryzen_access { + os_access_obj_t *os_access; + smu_t mp1_smu; + smu_t psmu; + enum ryzen_family family; + int bios_if_ver; + uintptr_t table_addr; + uint32_t table_ver; + size_t table_size; + float *table_values; +}; + +enum ryzen_family cpuid_get_family(); + +#endif diff --git a/os/licenses/ryzenadj/src/lib/win32/OlsApi.h b/os/licenses/ryzenadj/src/lib/win32/OlsApi.h new file mode 100644 index 00000000..9f11e6aa --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/win32/OlsApi.h @@ -0,0 +1,580 @@ +//----------------------------------------------------------------------------- +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : http://openlibsys.org/ +// License : The modified BSD license +// +// Copyright 2007-2009 OpenLibSys.org. All rights reserved. +//----------------------------------------------------------------------------- +// for WinRing0 1.3.x + +#pragma once + +/****************************************************************************** +** +** DLL Information +** +******************************************************************************/ + +//----------------------------------------------------------------------------- +// GetDllStatus +//----------------------------------------------------------------------------- +DWORD // DLL Status, defined OLS_DLL_**** +WINAPI GetDllStatus(); + +//----------------------------------------------------------------------------- +// GetDllVersion +//----------------------------------------------------------------------------- +DWORD // DLL Version, defined OLS_VERSION +WINAPI GetDllVersion( + PBYTE major, // major version + PBYTE minor, // minor version + PBYTE revision, // revision + PBYTE release // release/build +); + +//----------------------------------------------------------------------------- +// GetDriverVersion +//----------------------------------------------------------------------------- +DWORD // Device Driver Version, defined OLS_DRIVER_VERSION +WINAPI GetDriverVersion( + PBYTE major, // major version + PBYTE minor, // minor version + PBYTE revision, // revision + PBYTE release // release/build +); + +//----------------------------------------------------------------------------- +// GetDriverType +//----------------------------------------------------------------------------- +DWORD // Device Driver Type, defined OLS_DRIVER_TYPE_**** +WINAPI GetDriverType(); + +//----------------------------------------------------------------------------- +// InitializeOls +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI InitializeOls(); + +//----------------------------------------------------------------------------- +// DeinitializeOls +//----------------------------------------------------------------------------- +VOID WINAPI DeinitializeOls(); + +/****************************************************************************** +** +** CPU +** +******************************************************************************/ + +//----------------------------------------------------------------------------- +// IsCpuid +//----------------------------------------------------------------------------- +BOOL // TRUE: support CPUID instruction, FALSE: not support CPUID instruction +WINAPI IsCpuid(); + +//----------------------------------------------------------------------------- +// IsMsr +//----------------------------------------------------------------------------- +BOOL // TRUE: support MSR(Model-Specific Register), FALSE: not support MSR +WINAPI IsMsr(); + +//----------------------------------------------------------------------------- +// IsTsc +//----------------------------------------------------------------------------- +BOOL // TRUE: support TSC(Time Stamp Counter), FALSE: not support TSC +WINAPI IsTsc(); + +//----------------------------------------------------------------------------- +// Rdmsr +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI Rdmsr( + DWORD index, // MSR index + PDWORD eax, // bit 0-31 + PDWORD edx // bit 32-63 +); + +//----------------------------------------------------------------------------- +// RdmsrTx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI RdmsrTx( + DWORD index, // MSR index + PDWORD eax, // bit 0-31 + PDWORD edx, // bit 32-63 + DWORD_PTR threadAffinityMask +); + +//----------------------------------------------------------------------------- +// RdmsrPx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI RdmsrPx( + DWORD index, // MSR index + PDWORD eax, // bit 0-31 + PDWORD edx, // bit 32-63 + DWORD_PTR processAffinityMask +); + +//----------------------------------------------------------------------------- +// Wrmsr +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI Wrmsr( + DWORD index, // MSR index + DWORD eax, // bit 0-31 + DWORD edx // bit 32-63 +); + +//----------------------------------------------------------------------------- +// WrmsrTx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI WrmsrTx( + DWORD index, // MSR index + DWORD eax, // bit 0-31 + DWORD edx, // bit 32-63 + DWORD_PTR threadAffinityMask +); + +//----------------------------------------------------------------------------- +// WrmsrPx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI WrmsrPx( + DWORD index, // MSR index + DWORD eax, // bit 0-31 + DWORD edx, // bit 32-63 + DWORD_PTR processAffinityMask +); + +//----------------------------------------------------------------------------- +// Rdpmc +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI Rdpmc( + DWORD index, // PMC index + PDWORD eax, // bit 0-31 + PDWORD edx // bit 32-63 +); + +//----------------------------------------------------------------------------- +// RdmsrTx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI RdpmcTx( + DWORD index, // PMC index + PDWORD eax, // bit 0-31 + PDWORD edx, // bit 32-63 + DWORD_PTR threadAffinityMask +); + +//----------------------------------------------------------------------------- +// RdmsrPx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI RdpmcPx( + DWORD index, // PMC index + PDWORD eax, // bit 0-31 + PDWORD edx, // bit 32-63 + DWORD_PTR processAffinityMask +); + +//----------------------------------------------------------------------------- +// Cpuid +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI Cpuid( + DWORD index, // CPUID index + PDWORD eax, + PDWORD ebx, + PDWORD ecx, + PDWORD edx +); + +//----------------------------------------------------------------------------- +// CpuidTx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI CpuidTx( + DWORD index, // CPUID index + PDWORD eax, + PDWORD ebx, + PDWORD ecx, + PDWORD edx, + DWORD_PTR threadAffinityMask +); + +//----------------------------------------------------------------------------- +// CpuidPx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI CpuidPx( + DWORD index, // CPUID index + PDWORD eax, + PDWORD ebx, + PDWORD ecx, + PDWORD edx, + DWORD_PTR processAffinityMask +); + +//----------------------------------------------------------------------------- +// Rdtsc +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI Rdtsc( + PDWORD eax, // bit 0-31 + PDWORD edx // bit 32-63 +); + +//----------------------------------------------------------------------------- +// RdmsrTx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI RdtscTx( + PDWORD eax, // bit 0-31 + PDWORD edx, // bit 32-63 + DWORD_PTR threadAffinityMask +); + +//----------------------------------------------------------------------------- +// RdmsrPx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI RdtscPx( + PDWORD eax, // bit 0-31 + PDWORD edx, // bit 32-63 + DWORD_PTR processAffinityMask +); + +//----------------------------------------------------------------------------- +// Hlt +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI Hlt(); + +//----------------------------------------------------------------------------- +// HltTx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI HltTx( + DWORD_PTR threadAffinityMask +); + +//----------------------------------------------------------------------------- +// HltPx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI HltTx( + DWORD_PTR processAffinityMask +); + +/****************************************************************************** +** +** I/O +** +******************************************************************************/ + +//----------------------------------------------------------------------------- +// ReadIoPortByte +//----------------------------------------------------------------------------- +BYTE // Read Value +WINAPI ReadIoPortByte( + WORD port // I/O port address +); + +//----------------------------------------------------------------------------- +// ReadIoPortWord +//----------------------------------------------------------------------------- +WORD // Read Value +WINAPI ReadIoPortWord( + WORD port // I/O port address +); + +//----------------------------------------------------------------------------- +// ReadIoPortDword +//----------------------------------------------------------------------------- +DWORD // Read Value +WINAPI ReadIoPortDword( + WORD port // I/O port address +); + +//----------------------------------------------------------------------------- +// ReadIoPortByteEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI ReadIoPortByteEx( + WORD port, // I/O port address + PBYTE value // Read Value +); +//----------------------------------------------------------------------------- +// ReadIoPortWordEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI ReadIoPortWordEx( + WORD port, // I/O port address + PWORD value // Read Value +); +//----------------------------------------------------------------------------- +// ReadIoPortDwordEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI ReadIoPortDwordEx( + WORD port, // I/O port address + PDWORD value // Read Value +); + +//----------------------------------------------------------------------------- +// WriteIoPortByte +//----------------------------------------------------------------------------- +VOID +WINAPI WriteIoPortByte( + WORD port, // I/O port address + BYTE value // Write Value +); + +//----------------------------------------------------------------------------- +// WriteIoPortDword +//----------------------------------------------------------------------------- +VOID +WINAPI WriteIoPortDword( + WORD port, // I/O port address + DWORD value // Write Value +); + + +//----------------------------------------------------------------------------- +// WriteIoPortWord +//----------------------------------------------------------------------------- +VOID +WINAPI WriteIoPortWord( + WORD port, // I/O port address + WORD value // Write Value +); + +//----------------------------------------------------------------------------- +// WriteIoPortByteEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI WriteIoPortByteEx( + WORD port, // I/O port address + BYTE value // Write Value +); + +//----------------------------------------------------------------------------- +// WriteIoPortWordEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI WriteIoPortWordEx( + WORD port, // I/O port address + WORD value // Write Value +); + + +//----------------------------------------------------------------------------- +// WriteIoPortDwordEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI WriteIoPortDwordEx( + WORD port, // I/O port address + DWORD value // Write Value +); + +/****************************************************************************** +** +** PCI +** +******************************************************************************/ +// pciAddress +// 0- 2: Function Number +// 3- 7: Device Number +// 8-15: PCI Bus Number +// 16-31: Reserved +// 0xFFFFFFFF : Error + +//----------------------------------------------------------------------------- +// SetPciMaxBusNo +//----------------------------------------------------------------------------- +VOID +WINAPI SetPciMaxBusIndex( + BYTE max // Max PCI Bus to Scan +); + +//----------------------------------------------------------------------------- +// ReadPciConfigByte +//----------------------------------------------------------------------------- +BYTE // Read Value +WINAPI ReadPciConfigByte( + DWORD pciAddress, // PCI Device Address + BYTE regAddress // Configuration Address 0-255 +); + +//----------------------------------------------------------------------------- +// ReadPciConfigWord +//----------------------------------------------------------------------------- +WORD // Read Value +WINAPI ReadPciConfigWord( + DWORD pciAddress, // PCI Device Address + BYTE regAddress // Configuration Address 0-255 +); + +//----------------------------------------------------------------------------- +// ReadPciConfigDword +//----------------------------------------------------------------------------- +DWORD // Read Value +WINAPI ReadPciConfigDword( + DWORD pciAddress, // PCI Device Address + BYTE regAddress // Configuration Address 0-255 +); + +//----------------------------------------------------------------------------- +// ReadPciConfigByteEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI ReadPciConfigByteEx( + DWORD pciAddress, // PCI Device Address + DWORD regAddress, // Configuration Address 0-whatever + PBYTE value // Read Value +); + +//----------------------------------------------------------------------------- +// ReadPciConfigWordEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI ReadPciConfigWordEx( + DWORD pciAddress, // PCI Device Address + DWORD regAddress, // Configuration Address 0-whatever + PWORD value // Read Value +); + +//----------------------------------------------------------------------------- +// ReadPciConfigDwordEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI ReadPciConfigDwordEx( + DWORD pciAddress, // PCI Device Address + DWORD regAddress, // Configuration Address 0-whatever + PDWORD value // Read Value +); + +//----------------------------------------------------------------------------- +// WritePciConfigByte +//----------------------------------------------------------------------------- +VOID +WINAPI WritePciConfigByte( + DWORD pciAddress, // PCI Device Address + BYTE regAddress, // Configuration Address 0-255 + BYTE value // Write Value +); + +//----------------------------------------------------------------------------- +// WritePciConfigWord +//----------------------------------------------------------------------------- +VOID +WINAPI WritePciConfigWord( + DWORD pciAddress, // PCI Device Address + BYTE regAddress, // Configuration Address 0-255 + WORD value // Write Value +); + +//----------------------------------------------------------------------------- +// WritePciConfigDword +//----------------------------------------------------------------------------- +VOID +WINAPI WritePciConfigDword( + DWORD pciAddress, // PCI Device Address + BYTE regAddress, // Configuration Address 0-255 + DWORD value // Write Value +); + +//----------------------------------------------------------------------------- +// WritePciConfigByteEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI WritePciConfigByteEx( + DWORD pciAddress, // PCI Device Address + DWORD regAddress, // Configuration Address 0-whatever + BYTE value // Write Value +); + +//----------------------------------------------------------------------------- +// WritePciConfigWordEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI WritePciConfigWordEx( + DWORD pciAddress, // PCI Device Address + DWORD regAddress, // Configuration Address 0-whatever + WORD value // Write Value +); + +//----------------------------------------------------------------------------- +// WritePciConfigDwordEx +//----------------------------------------------------------------------------- +BOOL // TRUE: success, FALSE: failure +WINAPI WritePciConfigDwordEx( + DWORD pciAddress, // PCI Device Address + DWORD regAddress, // Configuration Address 0-whatever + DWORD value // Write Value +); + +//----------------------------------------------------------------------------- +// FindPciDeviceById +//----------------------------------------------------------------------------- +DWORD // pciAddress, 0xFFFFFFFF: failure +WINAPI FindPciDeviceById( + WORD vendorId, // Vendor ID + WORD deviceId, // Device ID + BYTE index // Index +); + +//----------------------------------------------------------------------------- +// FindPciDeviceByClass +//----------------------------------------------------------------------------- +DWORD // pciAddress, 0xFFFFFFFF: failure +WINAPI FindPciDeviceByClass( + BYTE baseClass, // Base Class + BYTE subClass, // Sub Class + BYTE programIf, // Program Interface + BYTE index // Index +); + +/****************************************************************************** +** +** Memory (Special API) +** +******************************************************************************/ + +#ifdef _PHYSICAL_MEMORY_SUPPORT +//----------------------------------------------------------------------------- +// ReadDmiMemory +//----------------------------------------------------------------------------- +DWORD // Read size(byte), 0: failure +WINAPI ReadDmiMemory( + PBYTE buffer, // Buffer + DWORD count, // Count + DWORD unitSize // Unit Size (BYTE, WORD, DWORD) +); + +//----------------------------------------------------------------------------- +// ReadPhysicalMemory +//----------------------------------------------------------------------------- +DWORD // Read size(byte), 0: failure +WINAPI ReadPhysicalMemory( + DWORD_PTR address, // Physical Memory Address + PBYTE buffer, // Buffer + DWORD count, // Count + DWORD unitSize // Unit Size (BYTE, WORD, DWORD) +); + +//----------------------------------------------------------------------------- +// WritePhysicalMemory +//----------------------------------------------------------------------------- +DWORD // Write size(byte), 0: failure +WINAPI WritePhysicalMemory( + DWORD_PTR address, // Physical Memory Address + PBYTE buffer, // Buffer + DWORD count, // Count + DWORD unitSize // Unit Size (BYTE, WORD, DWORD) +); +#endif \ No newline at end of file diff --git a/os/licenses/ryzenadj/src/lib/win32/OlsDef.h b/os/licenses/ryzenadj/src/lib/win32/OlsDef.h new file mode 100644 index 00000000..f6f57028 --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/win32/OlsDef.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Author : hiyohiyo +// Mail : hiyohiyo@crystalmark.info +// Web : http://openlibsys.org/ +// License : The modified BSD license +// +// Copyright 2007 OpenLibSys.org. All rights reserved. +//----------------------------------------------------------------------------- + +#pragma once + +//----------------------------------------------------------------------------- +// +// DLL Status Code +// +//----------------------------------------------------------------------------- + +#define OLS_DLL_NO_ERROR 0 +#define OLS_DLL_UNSUPPORTED_PLATFORM 1 +#define OLS_DLL_DRIVER_NOT_LOADED 2 +#define OLS_DLL_DRIVER_NOT_FOUND 3 +#define OLS_DLL_DRIVER_UNLOADED 4 +#define OLS_DLL_DRIVER_NOT_LOADED_ON_NETWORK 5 +#define OLS_DLL_UNKNOWN_ERROR 9 + +//----------------------------------------------------------------------------- +// +// Driver Type +// +//----------------------------------------------------------------------------- + +#define OLS_DRIVER_TYPE_UNKNOWN 0 +#define OLS_DRIVER_TYPE_WIN_9X 1 +#define OLS_DRIVER_TYPE_WIN_NT 2 +#define OLS_DRIVER_TYPE_WIN_NT4 3 // Obsolete +#define OLS_DRIVER_TYPE_WIN_NT_X64 4 +#define OLS_DRIVER_TYPE_WIN_NT_IA64 5 // Reserved + +//----------------------------------------------------------------------------- +// +// PCI Error Code +// +//----------------------------------------------------------------------------- + +#define OLS_ERROR_PCI_BUS_NOT_EXIST (0xE0000001L) +#define OLS_ERROR_PCI_NO_DEVICE (0xE0000002L) +#define OLS_ERROR_PCI_WRITE_CONFIG (0xE0000003L) +#define OLS_ERROR_PCI_READ_CONFIG (0xE0000004L) + +//----------------------------------------------------------------------------- +// +// Support Macros +// +//----------------------------------------------------------------------------- + +// Bus Number, Device Number and Function Number to PCI Device Address +#define PciBusDevFunc(Bus, Dev, Func) ((Bus&0xFF)<<8) | ((Dev&0x1F)<<3) | (Func&7) +// PCI Device Address to Bus Number +#define PciGetBus(address) ((address>>8) & 0xFF) +// PCI Device Address to Device Number +#define PciGetDev(address) ((address>>3) & 0x1F) +// PCI Device Address to Function Number +#define PciGetFunc(address) (address&7) diff --git a/os/licenses/ryzenadj/src/lib/win32/osdep_win32.cpp b/os/licenses/ryzenadj/src/lib/win32/osdep_win32.cpp new file mode 100644 index 00000000..ae6317db --- /dev/null +++ b/os/licenses/ryzenadj/src/lib/win32/osdep_win32.cpp @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: LGPL +/* Copyright (C) 2018-2019 Jiaxun Yang */ +/* Access PCI Config Space - winring0 */ +extern "C" { +#include "../nb_smu_ops.h" +} +#include + +#include "OlsApi.h" +#include "OlsDef.h" + +typedef BOOL (__stdcall *lpIsInpOutDriverOpen)(void); +typedef PBYTE (__stdcall *lpMapPhysToLin)(uintptr_t pbPhysAddr, size_t dwPhysSize, HANDLE *pPhysicalMemoryHandle); +typedef BOOL (__stdcall *lpUnmapPhysicalMemory)(HANDLE PhysicalMemoryHandle, uintptr_t pbLinAddr); +typedef BOOL (__stdcall *lpGetPhysLong)(uintptr_t pbPhysAddr, uint32_t *pdwPhysVal); +lpIsInpOutDriverOpen gfpIsInpOutDriverOpen; +lpGetPhysLong gfpGetPhysLong; +lpMapPhysToLin gfpMapPhysToLin; +lpUnmapPhysicalMemory gfpUnmapPhysicalMemory; +uint32_t *pdwLinAddr; +HANDLE physicalMemoryHandle; + +#ifdef __cplusplus +extern "C" { +#endif + +os_access_obj_t *init_os_access_obj() { + InitializeOls(); + + const DWORD dllStatus = GetDllStatus(); + + switch (dllStatus) { + case OLS_DLL_NO_ERROR: { + os_access_obj_t *obj = static_cast(std::malloc(sizeof(os_access_obj_t))); + + if (obj == NULL) + return NULL; + + memset(obj, 0, sizeof(os_access_obj_t)); + return obj; + } + case OLS_DLL_UNSUPPORTED_PLATFORM: + DBG("WinRing0 Err: Unsupported Platform\n"); + break; + case OLS_DLL_DRIVER_NOT_LOADED: + DBG("WinRing0 Err: Driver not loaded\n"); + break; + case OLS_DLL_DRIVER_NOT_FOUND: + DBG("WinRing0 Err: Driver not found\n"); + break; + case OLS_DLL_DRIVER_UNLOADED: + DBG("WinRing0 Err: Driver unloaded\n"); + break; + case OLS_DLL_DRIVER_NOT_LOADED_ON_NETWORK: + DBG("WinRing0 Err: Driver not loaded on network\n"); + break; + case OLS_DLL_UNKNOWN_ERROR: + DBG("WinRing0 Err: unknown error\n"); + break; + default: + DBG("WinRing0 Err: unknown status code %lu\n", dllStatus); + break; + } + + return NULL; +} + +void free_os_access_obj(os_access_obj_t *obj) { + if (obj == NULL) + return; + + DeinitializeOls(); + gfpUnmapPhysicalMemory(physicalMemoryHandle, *pdwLinAddr); + FreeLibrary(obj->inpoutDll); + free(obj); +} + +uint32_t smn_reg_read(const os_access_obj_t *obj, uint32_t addr) { + WritePciConfigDword(obj->pci_address, NB_PCI_REG_ADDR_ADDR, addr & (~0x3)); + return ReadPciConfigDword(obj->pci_address, NB_PCI_REG_DATA_ADDR); +} + +void smn_reg_write(const os_access_obj_t *obj, uint32_t addr, uint32_t data) { + WritePciConfigDword(obj->pci_address, NB_PCI_REG_ADDR_ADDR, addr); + WritePciConfigDword(obj->pci_address, NB_PCI_REG_DATA_ADDR, data); +} + +int init_mem_obj(os_access_obj_t *os_access, uintptr_t physAddr) { + HINSTANCE hInpOutDll = LoadLibrary ("inpoutx64.DLL"); + + if (hInpOutDll == NULL) { + DBG("Could not load inpoutx64.DLL\n"); + return -1; + } + + gfpIsInpOutDriverOpen = (lpIsInpOutDriverOpen)GetProcAddress(hInpOutDll, "IsInpOutDriverOpen"); + gfpGetPhysLong = (lpGetPhysLong)GetProcAddress(hInpOutDll, "GetPhysLong"); + gfpMapPhysToLin = (lpMapPhysToLin)GetProcAddress(hInpOutDll, "MapPhysToLin"); + gfpUnmapPhysicalMemory = (lpUnmapPhysicalMemory)GetProcAddress(hInpOutDll, "UnmapPhysicalMemory"); + + if (!gfpIsInpOutDriverOpen()) { + DBG("Could not open inpoutx64 driver\n"); + return -1; + } + + pdwLinAddr = (uint32_t*)gfpMapPhysToLin(physAddr, 0x1000, &physicalMemoryHandle); + if (pdwLinAddr == NULL) { + DBG("failed to map memory\n"); + return -1; + } + + os_access->inpoutDll = hInpOutDll; + + return 0; +} + +int copy_pm_table(const os_access_obj_t *obj, void *buffer, const size_t size) { + memcpy(buffer, pdwLinAddr, size); + return 0; +} + +int compare_pm_table(const void *buffer, const size_t size) { + return memcmp(buffer, pdwLinAddr, size); +} + +bool is_using_smu_driver() { + return false; +} + +#ifdef __cplusplus +} +#endif diff --git a/os/licenses/ryzenadj/src/main.c b/os/licenses/ryzenadj/src/main.c new file mode 100644 index 00000000..fed00193 --- /dev/null +++ b/os/licenses/ryzenadj/src/main.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2018-2019 Jiaxun Yang */ +/* Ryzen NB SMU Service Request Tool */ + +#include +#include + +#include "lib/ryzenadj.h" +#include "argparse.h" + +#define STRINGIFY2(X) #X +#define STRINGIFY(X) STRINGIFY2(X) + +#define _do_adjust(ARG) \ +do { \ + /* ignore max unsigned integer values */ \ + if (ARG != -1) { \ + int adjerr = set_##ARG(ry, ARG); \ + if (!adjerr){ \ + any_adjust_applied = 1; \ + printf("Successfully set " STRINGIFY(ARG) " to %u\n", ARG); \ + } else if (adjerr == ADJ_ERR_FAM_UNSUPPORTED) { \ + printf("set_" STRINGIFY(ARG) " is not supported on this family\n"); \ + err = -1; \ + } else if (adjerr == ADJ_ERR_SMU_UNSUPPORTED) { \ + printf("set_" STRINGIFY(ARG) " is not supported on this SMU\n"); \ + err = -1; \ + } else if (adjerr == ADJ_ERR_SMU_REJECTED) { \ + printf("set_" STRINGIFY(ARG) " is rejected by SMU\n"); \ + err = -1; \ + } else { \ + printf("Failed to set" STRINGIFY(ARG) " \n"); \ + err = -1; \ + } \ + } \ +} while(0); + +#define _do_enable(ARG) \ +do { \ + if (ARG) { \ + int adjerr = set_##ARG(ry); \ + if (!adjerr){ \ + any_adjust_applied = 1; \ + printf("Successfully enable " STRINGIFY(ARG) "\n"); \ + } else if (adjerr == ADJ_ERR_FAM_UNSUPPORTED) { \ + printf("set_" STRINGIFY(ARG) " is not supported on this family\n"); \ + err = -1; \ + } else if (adjerr == ADJ_ERR_SMU_UNSUPPORTED) { \ + printf("set_" STRINGIFY(ARG) " is not supported on this SMU\n"); \ + err = -1; \ + } else if (adjerr == ADJ_ERR_SMU_REJECTED) { \ + printf("set_" STRINGIFY(ARG) " is rejected by SMU\n"); \ + err = -1; \ + } else { \ + printf("Failed to set" STRINGIFY(ARG) " \n"); \ + err = -1; \ + } \ + } \ +} while(0); + +static const char *const usage[] = { + "ryzenadj [options]", + NULL, +}; + +static const char *family_name(enum ryzen_family fam) +{ + switch (fam) { + case FAM_RAVEN: return "Raven"; + case FAM_PICASSO: return "Picasso"; + case FAM_RENOIR: return "Renoir"; + case FAM_CEZANNE: return "Cezanne"; + case FAM_DALI: return "Dali"; + case FAM_LUCIENNE: return "Lucienne"; + case FAM_VANGOGH: return "Vangogh"; + case FAM_REMBRANDT: return "Rembrandt"; + case FAM_PHOENIX: return "Phoenix Point"; + case FAM_HAWKPOINT: return "Hawk Point"; + case FAM_DRAGONRANGE: return "Dragon Range"; + case FAM_KRACKANPOINT: return "Krackan Point"; + case FAM_STRIXPOINT: return "Strix Point"; + case FAM_STRIXHALO: return "Strix Halo"; + case FAM_FIRERANGE: return "Fire Range"; + default: break; + } + + return "Unknown"; +} + +static void show_info_header(ryzen_access ry) +{ + printf("CPU Family: %s\n", family_name(get_cpu_family(ry))); + printf("SMU BIOS Interface Version: %d\n", get_bios_if_ver(ry)); + printf("Version: v" STRINGIFY(RYZENADJ_REVISION_VER) "." STRINGIFY(RYZENADJ_MAJOR_VER) "." STRINGIFY(RYZENADJ_MINIOR_VER) " \n"); +} + +static void show_info_table(ryzen_access ry) +{ + printf("PM Table Version: %x\n", get_table_ver(ry)); + + //get refresh table after adjust + int errorcode = refresh_table(ry); + if(errorcode){ + printf("Unable to refresh power metric table: %d\n", errorcode); + return; + } + + //print table in github markdown + printf("| Name | Value | Parameter |\n"); + printf("|---------------------|-----------|--------------------|\n"); + char tableFormat[] = "| %-19s | %9.3lf | %-18s |\n"; + printf(tableFormat, "STAPM LIMIT", get_stapm_limit(ry), "stapm-limit"); + printf(tableFormat, "STAPM VALUE", get_stapm_value(ry), ""); + printf(tableFormat, "PPT LIMIT FAST", get_fast_limit(ry), "fast-limit"); + printf(tableFormat, "PPT VALUE FAST", get_fast_value(ry), ""); + printf(tableFormat, "PPT LIMIT SLOW", get_slow_limit(ry), "slow-limit"); + printf(tableFormat, "PPT VALUE SLOW", get_slow_value(ry), ""); + printf(tableFormat, "StapmTimeConst", get_stapm_time(ry), "stapm-time"); + printf(tableFormat, "SlowPPTTimeConst", get_slow_time(ry), "slow-time"); + printf(tableFormat, "PPT LIMIT APU", get_apu_slow_limit(ry), "apu-slow-limit"); + printf(tableFormat, "PPT VALUE APU", get_apu_slow_value(ry), ""); + printf(tableFormat, "TDC LIMIT VDD", get_vrm_current(ry), "vrm-current"); + printf(tableFormat, "TDC VALUE VDD", get_vrm_current_value(ry), ""); + printf(tableFormat, "TDC LIMIT SOC", get_vrmsoc_current(ry), "vrmsoc-current"); + printf(tableFormat, "TDC VALUE SOC", get_vrmsoc_current_value(ry), ""); + printf(tableFormat, "EDC LIMIT VDD", get_vrmmax_current(ry), "vrmmax-current"); + printf(tableFormat, "EDC VALUE VDD", get_vrmmax_current_value(ry), ""); + printf(tableFormat, "EDC LIMIT SOC", get_vrmsocmax_current(ry), "vrmsocmax-current"); + printf(tableFormat, "EDC VALUE SOC", get_vrmsocmax_current_value(ry), ""); + printf(tableFormat, "THM LIMIT CORE", get_tctl_temp(ry), "tctl-temp"); + printf(tableFormat, "THM VALUE CORE", get_tctl_temp_value(ry), ""); + printf(tableFormat, "STT LIMIT APU", get_apu_skin_temp_limit(ry), "apu-skin-temp"); + printf(tableFormat, "STT VALUE APU", get_apu_skin_temp_value(ry), ""); + printf(tableFormat, "STT LIMIT dGPU", get_dgpu_skin_temp_limit(ry), "dgpu-skin-temp"); + printf(tableFormat, "STT VALUE dGPU", get_dgpu_skin_temp_value(ry), ""); + printf(tableFormat, "CCLK Boost SETPOINT", get_cclk_setpoint(ry), "power-saving /"); + printf(tableFormat, "CCLK BUSY VALUE", get_cclk_busy_value(ry), "max-performance"); +} + +static void show_table_dump(ryzen_access ry, int any_adjust_applied) +{ + size_t index, table_size; + uint32_t *table_data_copy; + float *current_table_values, *old_table_values; + + printf("PM Table Dump of Version: %x\n", get_table_ver(ry)); + table_size = get_table_size(ry); + + current_table_values = get_table_values(ry); + table_data_copy = malloc(table_size); + memcpy(table_data_copy, current_table_values, table_size); + + if(any_adjust_applied) + { + //copy old values before refresh + old_table_values = malloc(table_size); + memcpy(old_table_values, table_data_copy, table_size); + + int errorcode = refresh_table(ry); + if(errorcode){ + printf("Unable to refresh power metric table: %d\n", errorcode); + } + + //print table in github markdown + printf("| Offset | Data | Value | After Adjust |\n"); + printf("|--------|------------|-----------|--------------|\n"); + char tableFormat[] = "| 0x%04X | 0x%08X | %9.3lf | %12.3lf |\n"; + for(index = 0; index < table_size / 4; index++) + { + printf(tableFormat, index * 4, table_data_copy[index], old_table_values[index], current_table_values[index]); + } + + free(old_table_values); + } + else + { + //print table in github markdown + printf("| Offset | Data | Value |\n"); + printf("|--------|------------|-----------|\n"); + char tableFormat[] = "| 0x%04X | 0x%08X | %9.3lf |\n"; + for(index = 0; index < table_size / 4; index++) + { + printf(tableFormat, index * 4, table_data_copy[index], current_table_values[index]); + } + } + + free(table_data_copy); + //don't free current_table_values because this would deinitialize our table +} + + +int main(int argc, const char **argv) +{ + ryzen_access ry; + int err = 0; + + int info = 0, dump_table = 0, any_adjust_applied = 0; + int power_saving = 0, max_performance = 0, enable_oc = 0x0, disable_oc = 0x0; + //init unsigned types with max value because we treat max value as unset + uint32_t stapm_limit = -1, fast_limit = -1, slow_limit = -1, slow_time = -1, stapm_time = -1, tctl_temp = -1; + uint32_t vrm_current = -1, vrmsoc_current = -1, vrmmax_current = -1, vrmsocmax_current = -1, psi0_current = -1, psi0soc_current = -1; + uint32_t vrmgfx_current = -1, vrmcvip_current = -1, vrmgfxmax_current = -1, psi3cpu_current = -1, psi3gfx_current = -1; + uint32_t max_socclk_freq = -1, min_socclk_freq = -1, max_fclk_freq = -1, min_fclk_freq = -1, max_vcn = -1, min_vcn = -1, max_lclk = -1, min_lclk = -1; + uint32_t max_gfxclk_freq = -1, min_gfxclk_freq = -1, prochot_deassertion_ramp = -1, apu_skin_temp_limit = -1, dgpu_skin_temp_limit = -1, apu_slow_limit = -1; + uint32_t skin_temp_power_limit = -1; + uint32_t gfx_clk = -1, oc_clk = -1, oc_volt = -1, coall = -1, coper = -1, cogfx = -1; + + //create structure for parsing + struct argparse_option options[] = { + OPT_HELP(), + OPT_GROUP("Options"), + OPT_BOOLEAN('i', "info", &info, "Show information and most important power metrics after adjustment"), + OPT_BOOLEAN('\0', "dump-table", &dump_table, "Show whole power metric table before and after adjustment"), + OPT_GROUP("Settings"), + OPT_U32('a', "stapm-limit", &stapm_limit, "Sustained Power Limit - STAPM LIMIT (mW)"), + OPT_U32('b', "fast-limit", &fast_limit, "Actual Power Limit - PPT LIMIT FAST (mW)"), + OPT_U32('c', "slow-limit", &slow_limit, "Average Power Limit - PPT LIMIT SLOW (mW)"), + OPT_U32('d', "slow-time", &slow_time, "Slow PPT Constant Time (s)"), + OPT_U32('e', "stapm-time", &stapm_time, "STAPM constant time (s)"), + OPT_U32('f', "tctl-temp", &tctl_temp, "Tctl Temperature Limit (degree C)"), + OPT_U32('g', "vrm-current", &vrm_current, "VRM Current Limit - TDC LIMIT VDD (mA)"), + OPT_U32('j', "vrmsoc-current", &vrmsoc_current, "VRM SoC Current Limit - TDC LIMIT SoC (mA)"), + OPT_U32('\0', "vrmgfx-current", &vrmgfx_current, "VRM GFX Current Limit - TDC LIMIT GFX (mA)"), + OPT_U32('\0', "vrmcvip-current", &vrmcvip_current, "VRM CVIP Current Limit - TDC LIMIT CVIP (mA)"), + OPT_U32('k', "vrmmax-current", &vrmmax_current, "VRM Maximum Current Limit - EDC LIMIT VDD (mA)"), + OPT_U32('l', "vrmsocmax-current", &vrmsocmax_current, "VRM SoC Maximum Current Limit - EDC LIMIT SoC (mA)"), + OPT_U32('\0', "vrmgfxmax_current", &vrmgfxmax_current, "VRM GFX Maximum Current Limit - EDC LIMIT GFX (mA)"), + OPT_U32('m', "psi0-current", &psi0_current, "PSI0 VDD Current Limit (mA)"), + OPT_U32('\0', "psi3cpu_current", &psi3cpu_current, "PSI3 CPU Current Limit (mA)"), + OPT_U32('n', "psi0soc-current", &psi0soc_current, "PSI0 SoC Current Limit (mA)"), + OPT_U32('\0', "psi3gfx_current", &psi3gfx_current, "PSI3 GFX Current Limit (mA)"), + OPT_U32('o', "max-socclk-frequency", &max_socclk_freq, "Maximum SoC Clock Frequency (MHz)"), + OPT_U32('p', "min-socclk-frequency", &min_socclk_freq, "Minimum SoC Clock Frequency (MHz)"), + OPT_U32('q', "max-fclk-frequency", &max_fclk_freq, "Maximum Transmission (CPU-GPU) Frequency (MHz)"), + OPT_U32('r', "min-fclk-frequency", &min_fclk_freq, "Minimum Transmission (CPU-GPU) Frequency (MHz)"), + OPT_U32('s', "max-vcn", &max_vcn, "Maximum Video Core Next (VCE - Video Coding Engine) (MHz)"), + OPT_U32('t', "min-vcn", &min_vcn, "Minimum Video Core Next (VCE - Video Coding Engine) (MHz)"), + OPT_U32('u', "max-lclk", &max_lclk, "Maximum Data Launch Clock (MHz)"), + OPT_U32('v', "min-lclk", &min_lclk, "Minimum Data Launch Clock (MHz)"), + OPT_U32('w', "max-gfxclk", &max_gfxclk_freq, "Maximum GFX Clock (MHz)"), + OPT_U32('x', "min-gfxclk", &min_gfxclk_freq, "Minimum GFX Clock (MHz)"), + OPT_U32('y', "prochot-deassertion-ramp", &prochot_deassertion_ramp, "Ramp Time After Prochot is Deasserted: limit power based on value, higher values does apply tighter limits after prochot is over"), + OPT_U32('\0', "apu-skin-temp", &apu_skin_temp_limit, "APU Skin Temperature Limit - STT LIMIT APU (degree C)"), + OPT_U32('\0', "dgpu-skin-temp", &dgpu_skin_temp_limit, "dGPU Skin Temperature Limit - STT LIMIT dGPU (degree C)"), + OPT_U32('\0', "apu-slow-limit", &apu_slow_limit, "APU PPT Slow Power limit for A+A dGPU platform - PPT LIMIT APU (mW)"), + OPT_U32('\0', "skin-temp-limit", &skin_temp_power_limit, "Skin Temperature Power Limit (mW)"), + OPT_U32('\0', "gfx-clk", &gfx_clk, "Forced Clock Speed MHz (Renoir Only)"), + OPT_U32('\0', "oc-clk", &oc_clk, "Forced Core Clock Speed MHz (Renoir and up Only)"), + OPT_U32('\0', "oc-volt", &oc_volt, "Forced Core VID: Must follow this calculation (1.55 - [VID you want to set e.g. 1.25 for 1.25v]) / 0.00625 (Renoir and up Only)"), + OPT_BOOLEAN('\0', "enable-oc", &enable_oc, "Enable OC (Renoir and up Only)"), + OPT_BOOLEAN('\0', "disable-oc", &disable_oc, "Disable OC (Renoir and up Only)"), + OPT_U32('\0', "set-coall", &coall, "All core Curve Optimiser"), + OPT_U32('\0', "set-coper", &coper, "Per core Curve Optimiser"), + OPT_U32('\0', "set-cogfx", &cogfx, "iGPU Curve Optimiser"), + OPT_BOOLEAN('\0', "power-saving", &power_saving, "Hidden options to improve power efficiency (is set when AC unplugged): behavior depends on CPU generation, Device and Manufacture"), + OPT_BOOLEAN('\0', "max-performance", &max_performance, "Hidden options to improve performance (is set when AC plugged in): behavior depends on CPU generation, Device and Manufacture"), + OPT_GROUP("P-State Functions"), + OPT_END(), + }; + + + struct argparse argparse; + argparse_init(&argparse, options, usage, ARGPARSE_NON_OPTION_IS_INVALID); + argparse_describe(&argparse, "\n Ryzen Power Management adjust tool.", "\nWARNING: Use at your own risk!\nBy Jiaxun Yang , Under LGPL.\nVersion: v" STRINGIFY(RYZENADJ_REVISION_VER) "." STRINGIFY(RYZENADJ_MAJOR_VER) "." STRINGIFY(RYZENADJ_MINIOR_VER)); + argc = argparse_parse(&argparse, argc, argv); + + + //init RyzenAdj and validate that it was able to + ry = init_ryzenadj(); + if(!ry){ + printf("Unable to init ryzenadj\n"); + return -1; + } + + //shows info header before init_table + if (info) { + show_info_header(ry); + } + + if (info || dump_table) { + //init before adjustment to get the default values + err = init_table(ry); + if (err) { + printf("Unable to init power metric table: %d, this does not affect adjustments because it is only needed for monitoring.\n", err); + } + } + + //adjust all the arguments sent to RyzenAdj.exe + _do_adjust(stapm_limit); + _do_adjust(fast_limit); + _do_adjust(slow_limit); + _do_adjust(slow_time); + _do_adjust(stapm_time); + _do_adjust(tctl_temp); + _do_adjust(vrm_current); + _do_adjust(vrmsoc_current); + _do_adjust(vrmgfx_current); + _do_adjust(vrmcvip_current); + _do_adjust(vrmmax_current); + _do_adjust(vrmsocmax_current); + _do_adjust(vrmgfxmax_current); + _do_adjust(psi0_current); + _do_adjust(psi3cpu_current); + _do_adjust(psi0soc_current); + _do_adjust(psi3gfx_current); + _do_adjust(max_socclk_freq); + _do_adjust(min_socclk_freq); + _do_adjust(max_fclk_freq); + _do_adjust(min_fclk_freq); + _do_adjust(max_vcn); + _do_adjust(min_vcn); + _do_adjust(max_lclk); + _do_adjust(min_lclk); + _do_adjust(max_gfxclk_freq); + _do_adjust(min_gfxclk_freq); + _do_adjust(prochot_deassertion_ramp); + _do_adjust(apu_skin_temp_limit); + _do_adjust(dgpu_skin_temp_limit); + _do_adjust(apu_slow_limit); + _do_adjust(skin_temp_power_limit); + _do_adjust(gfx_clk); + _do_adjust(oc_clk); + _do_adjust(oc_volt); + _do_enable(power_saving); + _do_enable(max_performance); + _do_enable(enable_oc) + _do_enable(disable_oc); + _do_adjust(coall); + _do_adjust(coper); + _do_adjust(cogfx); + + if (!err) { + //call show table dump before anybody did call table refresh, because we want to copy the old values first + if (dump_table) { + show_table_dump(ry, any_adjust_applied); + } + //show power table after apply settings + if (info) { + show_info_table(ry); + } + } + + cleanup_ryzenadj(ry); + + return err; +} diff --git a/os/licenses/ryzenadj/src/win32/RyzenAdjServiceTask.xml.template b/os/licenses/ryzenadj/src/win32/RyzenAdjServiceTask.xml.template new file mode 100644 index 00000000..240a74f2 --- /dev/null +++ b/os/licenses/ryzenadj/src/win32/RyzenAdjServiceTask.xml.template @@ -0,0 +1,43 @@ + + + + + true + + + + + InteractiveToken + HighestAvailable + + + + IgnoreNew + false + false + true + false + false + + false + false + + true + true + false + false + false + PT0S + 7 + + PT60M + 10 + + + + + powershell + -WindowStyle hidden -ExecutionPolicy Bypass -file "###SCRIPTPATH###" + + + diff --git a/os/licenses/ryzenadj/src/win32/WinRing0x64.dll b/os/licenses/ryzenadj/src/win32/WinRing0x64.dll new file mode 100644 index 0000000000000000000000000000000000000000..4a48c7a1f6c9945906e9507fce4077facb69821a GIT binary patch literal 62976 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 W?#|t;;?My=KcH@brl5YZT=-uktk~rM literal 0 HcmV?d00001 diff --git a/os/licenses/ryzenadj/src/win32/WinRing0x64.lib b/os/licenses/ryzenadj/src/win32/WinRing0x64.lib new file mode 100644 index 0000000000000000000000000000000000000000..0f16977102c8346f0dc8304b903daf09288906d7 GIT binary patch literal 15124 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 literal 0 HcmV?d00001 diff --git a/os/licenses/ryzenadj/src/win32/demo.bat b/os/licenses/ryzenadj/src/win32/demo.bat new file mode 100644 index 00000000..fc41aa04 --- /dev/null +++ b/os/licenses/ryzenadj/src/win32/demo.bat @@ -0,0 +1,2 @@ +%~dp0\ryzenadj.exe --stapm-limit=40000 --fast-limit=45000 --slow-limit=45000 --tctl-temp=90 +pause diff --git a/os/licenses/ryzenadj/src/win32/inpoutx64.dll b/os/licenses/ryzenadj/src/win32/inpoutx64.dll new file mode 100644 index 0000000000000000000000000000000000000000..82c343fe10de5d96cdc86082ca99db9b0be2aebc GIT binary patch literal 98304 zcmeFadtg-6)$l!&WXKRQoS+OuG%{*XG(uCOH5gK7U{cS>L{YItMMa|&AJkHq5v&&y zPofzPgY?n%Rcl+dJ}s@b_F@t6mJlT2rhrwsXo=P8iQ@%tAs}$R-`ZzpGC}P3yx-p+ znmK3R*IIk+wbx#I?S1MmU*^hpxm*SO*|y8IlBfI&s^9{;D(z6;R`Pb-1MXCt{?C76_ux} zKK6^ei?9CKjqc3f!b^s^rxRXyiQ8Ss^HlFh_vI@6Cig@YpXa_)Jx92&QqPj~bGUk5 zd_%)*sq32{pIYZ~U3d2&*Yk_duFvGz<2o@gB+u(|{gs#vPHf4^JWJGjqpD2a^Bg)! ztVaU!ubWhe_r#rf=@_R_x-1zE4?8g>Znx`EiVp8{xh6_IGYQP)Io0jjGDITM|F*bY zB?^E{#Q*ZS#pU{Q8pI=&7$3WSVT^>g{=$!dD7Xs~C4X@t=Y#v{E?S{3ff8+do$Vou1bu5C3#y?bwb@>3@``czn3ql^3t8YKtbr$LJ~S9=4&hi(7IFRJSGZ1tP;rVnS+tZsTP zMmp(LYxP8Px$uqYchjixfm>^S7@&;(Zms1$UaH!{$@AR;M^IX;7GOfH;qplWL|C8- z(yoto9bl#bjAUM200a^hu>C&-jFz_eX}14T-l9p*$!lmk87-e0HoH~>hs$annvSvk zA@XP|q&;(q0!^>lWIZnFO=|`Aj#*W0xk-tfq)mEaMtSL_U@3zYEN?u-!@=?qVrct= zc4=uzc_YoPE)ZHIo-S`A%?fBLL;K+wtn(CbzLKNeP6^xpKQBn_zK@S`OO+-iR$otj zwm(cl)kk^@5bn{Mw-ZOUPO|Cd7TbTk6l-bInn976E*rZoZ+L8^dOsR7(H-A1P*TwEZj8ivx4jwyO1^ zmFK!N>K&@$9WpB1x5r1>{@KJDeV(|dXQx1HhRf&nJg43~XSE3mOUjo)|Eg_z!cAA5 zN?d5=r;;HN@@mg`0z}M8gi5p(o{_w^hK`aVzoCeA7Cq8W3a1{2NWVY;3fME{XS)1M zl^D~&4{aV+|7fWTfqu2^Z{`(xX)V8kDIAf&OQY@o3e7hp+AoD)RS>wH zQo6Z47DGHS>;=OA#*00b1ep;gE**Xm-X{*3q(6v5>>TAIZS;2<-A{T@}>b-(6{Jg;iG%Ph+e2*1`4`8a@KDpiTb? zzOQ|mi|^e!I;tND{Td%s@WM*>0zPO*SC=ZZ`~IGT z_8iXuv_(Ji{s1Uu8nv1<8!`JB-7z1yn=_dDjkPnc?l9Nr*)9iyskBi+8v>dxb7-sZ zfTS`jsdEAW3Rh{t><^-G^&_F=!5<{SoqKB zkJ%m4Vxy|hQ8pg|ZXJ9XT{F;$cgdq#fukS^C#M8f2Y`cUSYw93@b9Mu2JL4ZrfP9! zmJuBJX4K|Oi@Lcp=2c99@KJTGmQJ-g581YoJX>OKn~I}?iG1rK+qN@;f&30nXX^W# zm-@b;x`j@4v3G?xgG{`MQmf!dl?lH9f5{KfXXlDSVR|0^oQIAMx2Z2u5}MP~jyc}e;%QL$fAMmT#( zxll{Qz!i#)50|&fvuUl6^=de5N%=*PEK53PP2{pRgDBK`T}BjnrjiFbDS9PtFArFo ziO3|_{(ERmaYQQVaD&r-YPnl6;tW%k8RHNJAk&ro4C{VXCiexmt` zH3$xIIJFW^4P4z})Lu(kUfg5-;?oS*FiXWO}YevrcnPn<5-5 zM#1-C0@lBU8Zb{lI#*(B|Kh*Mkcodg8-2fup6^7Da;VxyoKV#xbZA;DO?5E5D-FvX zJCV6EzI`P%)BKY1OxB$ePG@z7PfjA5O*Tk0tEiZ^2 zWrn=bhET!Y?a}--dP7(Ar1nTd`vqpPD`HM6F?DYw|9r2uB9y;eiw)6Mv>Dxd^au3( zEs?6Ny1Bu+gqAX+*O|&ydr@RoX%v|kpk=ktp3V?ZlFXVaOABLdbq;ycl%F+g)-v_C zOt10n`^4pn57XP*OJ?nDx9xb|=N)?c`jVbUDK7HAT5nk&HP^>{dds$$w;mc-hFjg% zXJ}#0XcIL`#zg=SU7gDEW5v*7SB@a80BD@xiWTWbyIV)=h<{2&@j;>1VX4*2LfQ(l zw7Vllo4dKKHDt#IsXXsXo=WmqiwCsR={iv=&9r6}OFJq`5LL2p0KP|r{~Uq;i~;p# zYfQucbYU+14!t1R{{r5fK-5o{!{MDF_5KHVoes+BAG1AT^m*r9qOI5#S>;aui)c@6 zB|13oczy2<m5^qtnBdxM;A*48iSx+tI%sC(w zE3Y=^7wCw^pm|$yG;v+I9yS|Fn%=h;Nuy5;s3J)yWE{wg6=9CQC}K`G=rwjbfD05i_l~ zPm*?zr6uO4Gf$$deS&&#&Ewsmo|d+SXIeW0>g8lBRHk0as@7X8SgJ8Dk1{-$kk_4& z6X0u^bffh;7G7%@HDt)Z;Hon{r;wScZ|QEtHxh7yf2e}Hgy^`(YVrw{cWEs$Z&{P2 zo~2UH`cjuGIhmm>OCDo?UVOGSi}=;s^IR@P7weRfnf}q54VfBz<31urEJ|lSDUim- zNY`vs_1XSX;`GkRUZ`Ow@^gTfEXP8g4AjQ{{CFYg-ZPv*Bt783kQQp{A^L~v+!E>8 z($F*nKxM?-WA2ZdZ>ut(u1)Ztj{Tjw+In808dgCuk6=<6HGOwI<#N@xtdA9j?5&~@ z0`0J9sbDap>rnefsGqN_POtpm3uKn4iKC zPmwihcFOwrWoe|+VP+WN>K^H5dO*qtn2sDBf{6lw6^ zP8agmWlSjz7+vUDGf>o}o{rx3V64pgnaUw@^6NQd=mx8Ld)wWyV|v?567#)U^DoJ% zo0Ch7w-4!w{KS+}b8;yOM)xO1SKjYg3b_)KOXJ_NKKiyG+g7R9Y>y4Kb~~Y&gyKU` zV=g2tM&yUA4`8C7hg;XC+nVvF<-vnq=`!d#Mr6tFe)s zR$fUq^ChcuJdp3H`7$;*P0f@?!6sq|Sp;M`0OgJ*=}+GYhftC-vB>A94BI@N9 z5VQS%SuUYyd8Nqc2Z*xHLuJ;@=JMs_W2UY|khFC<^>5k%-U2$(x&$Al8C(WHm>}pk`m}811d(&EhNG)m7{Ci_L<3Cnj zDl3sc3$$hrYr-EKM2ed`)DrSD>+K@q8Qp=Xd4G8~nZo8qxEq6=Zf=gUUJKY;dSs9* zBY~Ai>wY(LT?2}P3+mkVYQ^NNy)Ij1yY&JRS)cdpL%l+$A@1}3~mGYFzK zncr)?v2Pn|5Z-NGWB<5$dy=7#jJ{zD2CW2KS4UHO7#Xbew$)xDjD173@2%<38`gx( za9aU&BNWD3KNQ7MfP^V0zt6kDju2Hc-{))%DF9Udvvcx~%*j9e>-nAe-`rv4r^^hc zjd#ExRX_v786E%iZw7NI8mGBN@F|T|DZLVW_6a_d6+VT}3O=dPNkb4?CAd5*+NjY7 zLOU2u4ejQPH3il$#nLO7B+7n5BO-#X=txW#M0f!M;Ud%RF~Xe3${;pLaH^Ho zXR5)h79%L0EPt3_SlM0{MkG89ty{rf&A znu(Fw(G?3xOVyO805 znSP&Z`_K6eOyD`4a*nd~Um=kqwtp1yP(b8|?e`EcY6p?Y6)$q~O(LJ|fA=v2i8Q4Y z>6Zy1Jq6gb+{li<&!@pk8?7fV7I1ynze692xLIWjon&mUUn>Q!tJh?Ts`?qZvVM(^vnG9&8LdZJudZdv>R+##p5+IW zp1MBaR?25~P1#@HXN;yJaBF7$JD@(J_6OAGifu4GSMZWkX%1>e?f8*&J>#krYM#Wq z_1()96kZ|OQ4*0n2SO&xj#qtMdo~ZitJyvOSY%jCH;2f8%(J&<$kIdfl~lG4h1|Mm zx+=R`Iw)EDsyrxp^pS{Z)IKo+P-3Gg`xtN5-zf{lHQC2lA4*Qm!`?zXjc4iZFi?8_ zMQDm0yz~d5X(7=#E9=Z|b7$D>QT=Q6Ug~l!Gio1WfTs8FJ3UJ!0FlD2VUD4=V&J+WQ3TEvZ*w(cy|3zAs@=YZ+3As9{c29uMmdqL$UQyc;WW0QWH}2*%4g zJ^x}n3%nt;0I&50_L-2m+4>M!wMmFFRfMPP9#n>|49&<+%fXEPZQL;vn}}2EGZYry zMFoezkk{QK@=_RBcoj|0-SZnK!WnPYS+vZ^f(q7YGvsxnwe51}d48{BxBP25{80M& zSH_s+TP=$-o_9$&=`)#xERnp(rbWAX$kG68ziuJ$3N2!J&WBTsRWoX=B2!-m3vSz zztzT^;E?S+;gOz)zL9LFIL0)7h5vs&fPOWH@js~%^JVy->I4oMgN%z`Wa594j+ed- zbvBoG0hNFlTGw12<7KkE?3EXgxvAT!WF;B)7@jK$x>uozWB-|o$gWR3S>8sdzU8C1 zhFbD5ICLY{~ z0C-Nxz%zaTJgrPJ%QB*Ye9SQfvh8pL(;z+Sqtb4{~IdBGb$!zxz zA&C;2A}`luM}0s4DVWDe_FVq!Pgel>zLuWxH!*#Y?T!$_)VnLL+5T1kN7%KBSa`H* zo9(YALSUH=|Czqy)7ia}^hZge-JF@XU%pv|m=>&y&leReKFRcTzmb)8*Hf<3vyli# z+O6+TnxvARPA6G^qqv$ISg9v{uPK&0mnXTU@cK}HN6v%%25d)}-!k-$OZJD;Qd|F;n*PM}FF~Re%JucTR zCyChTqxNv}*B0Y8TvN7z_>lRPcHY67gK;#q^oZ>@xd|gGeI%#7^rRybjj&lx8K^+C;S&WBGgJ5dLYH5q>GD_NL+0l|^hK!Vi}(xw86vg3 zr;ovVwfL#a#Q6yI3H=kEo2e`AQGF=x50#-+$Xijo*c2j&NMWkJf$aSZnB|=Q@de+E zp(5LVp;X-I8BRd{9nKq4B26FGH)?-Dwv7U!B80@!xJ(MM90=I{a;jRkdO8uw^Si7O zjQA)k7Fg1YC8OuBKnD1u3*D|&0rDh#1-$AWm$=;3Y@dHo6w4aXIP8&JxdzzuV~4~d5I>+{D`WuI88!?I5@=? zMG^&Xnw}uZMq41^Ih&VA(sZ!dG5?|*eHW0uuCF1~*ihYg7)I+RM_qza#4LC-7az|~ z_U>8Vx&cyKax-*Em4waOwZx_hfpH@*=I*){TU&ZNP)E#qawBf8<@Zo*2K&u}mb+uq zmV4txw*Qv@R^sb>dIO`POAPM8#e0nRM(cBWr$_S7+!Vq3swW0Xq1Nwt_2dO5s~^#r z+J4TwgfcP6_+GI)rFyiRwg3t`^7oQARI~f`=CJvRZtm61f9i8~VZ|@26v)kd<)^3d zdnh)zaf-LGXAxx~OA~9^c;PChcRN*Vz7jGHy0!b;jrK2W|GJ+_pLMH}5x6K823*_! z4ABJ6jwVhLhk?Gl_#i3_rUn~*`SAi?0eMOq$QIxfwr)+bZkLhHA{tp{qrwLy!h9 zkp=EpHkcImnBu^Mh`lm1u^lE~9HUOM)=yhiy?Sz7F|4DTqnSeSqfHvZLhR1WKVicB zV%F|pWBa!PgKieW1JfPGAj7=fx~W;8pkV8Xvvs2rqtL;%|qNLANrc>kVC&#{8%zjy(+ic^}Z=aIq3NigAA%LZhH= zmMufWk*P@6Rvg{DE%sk({-F_>0>1j}PmTL)rKN^_v7p}2gVBJ6ul1ymKxE^f*nD=t zb@Mei#p?a1SYKnq`|VXqa9QKuBe${6tLN`tOcU`(X?2veik9-2>{=$q3?UdzoZRWT znJyB6AgzmkI;Lrpl38RyZ>fA{z4>Wp z9c>A(^|A5_b+TP6VC{$%MiY55!ss2LM`O*2@!?f%*kpn$PSozF6FTs@=}v`=I+t5! zq9SbtBd*ikdY-MVsFGx6@YkL3JMsvbBuB8hH+Gzuc^(Zm9ty+;@jE#FGgaHV^K7*h zk#bk{I_)D;If&v)%WGPj7>E^29301wrNf>2q_Kt!p94H=rkFR9xC5z%d@u&NMZhns zTHjbRW3?a`AFBE{e`o#%qGG**S$Xq@`0-e{w8q=qC+cjvGufHjQ$u8@ffeTcZRVb> zJ!n58jdpq+=e2c(GXz<|3!`Tb(7>Y&$Q3V8T`_TR><1X@&(~h)vR)NUR%UAW;;1x@ z1Y+nUrTeL-XhcGf_z#lVE4d^xn@~+cx^-l&(|XT4h!qFrbWaM(X{J%^lfyYV3qs)! zmfo@QfQW`lqxLAA5oBN*Cjf7b&Jda>yB z3*%a+XC~322K5}^$MjrE5H~lig;5RFR%ENtn#WLj7E36RIDoWUB*b+5$&HS@S(s7d zz1g=9MKV_GX0KH8RLf7LzV$2oSl!B8NqDT1FSfs)1^}_airpa6=~v=YDIo8OUDjV# z3R0PrrR}kbjI4)M(fAP2xJOVfVt&Ee&h~!>a=N)rwNa9rJyvvJvOni+7%n&$kR4Af%sl_7uCzh6&<|k4SS^Bk3Y}!c>Gv+ zhJ9*P?cpurhT$9p8tVs)ZDj?Uqd%r+y|N#OWhYrXRUVk)0chEgtnpNn zUFj?NnJIsYQ$Fy2TVC07)gY67#u8iKEc7%d_vtm3cGvHO-6kK9cLP(I%ebuo zIfyRdGV@_TjPeY|%1Azhd)?lG6H=>eLftt()HJcYVg60s@v*qMzsEj+OKiZtw|KKE zgGz-Ns>G$T8LlJRoKg^S)O+*wNdC*#`3Sa%d0x=Gq@cc`xLmjiP363?k^I-|&9&mW zBKrome+>;<^<+k3!P+IE8gRL5A|fDl6P$B%#Qbp}Qqe0Y-ZBI9N|d}dr+lH;^vwIN zgXjG3=ry0t^C2=8ctgG6@`6D8+vX4iav|}tlT&OxOem@X;jGDT)=oN^^M5*Rx7h&gKC!q2F@h2ngW^mLeH@Hr-_IvpPRavn{6T9dwRv zwmt$SqYa+*T@uI7e&wInSthKCYkG zmJ-t0#y(H{ShZCVIuK1v@(QQtnxo0!jvp-|?XU#lyh`1CVmaXGiNDFiJiox$=ha$R z00E)@SRgcg$M74<-%!t1gn-MuPuf=2eLTwago8WMt1_WLJOY(KRj7Jc_9JBM zC$FfVVozw=r$&#b=N+aYjO9T`R+yfn*Fy0Z&vLiYF^pT4te5rzwAj0lTyZ48LKp&tW&{dy3w0b;dhtdj(?#~S z@AnvQkH(ee=Di|Y3byET)=zJ|&g(rWZV%NC(Q?M47%gPGEUnhc<|j(5-mt^?>JaX4 z)|+&Vov5~+;-w$*9yStO46#-j6*Xm_^>VD(cv#RfKiAt2d7{a)J|-@>_$%XmR;act zje8eItG&kC?)Z4?zcaO7)?ce^Vf5?#*6y7SmZ>-t+@@opkScn^)n0e(C>A;q^EDC@ zXl^JKsZvYJ)?}TTu3a}?vB`72kl*Y~oo*eQY4%^cvdA3P?Cf;2!U{4+S3358cH>wt zs@-9hi)Q^yOH-Y?wle1gD(guoDSI$YANF~qQBS0uhjXMvytbx1O^T=^cV4=;Wv zWV|~%YJSBubn}<=f4l98g5lKqIqyzC-z?rIip+FO1h*Gg2lMRU7CYEz=gFc0N6_zt z%$N3VS?M%@gvO(k_2r+7W-3D006|_$TT4J2@v@HXLyKB4%;1 zc7F%@Bf?KAJAi(Ot~@x{Y-`Q$5wQKQlT?RSW2`*PVv6xXRc~p{D@Y7uE{san7MZtX zew(VTz>5{9n7S&vaUY9x&v~?_o5Qz+wEH_l#_c6I+0;Owa7nZ^T7Hd)ieM}5LhP22 zqyWU5CZtYYSR2uxWL#e@4TXCyrVF$e)>}`P39zwmN7&V^=+N_DV(`1Qr7Hvmsn03R zh_x0w6~tbYbxI0X3YAJeKzh}>F`fFTb9gNnQqD1=5>(3%K34#SPoKF72|1R=nWj(| zq%PaPs8N7`B3koR=m|3Tr@|G}S>DMq0HaJXMrbIJ_BJZ^GN`hCOt#;?)1i16Xu{^_ z;fgQA`Mu!@*OiC0B|a*|Y8AUm)yi3@nLesM_?dv6t~JvJRvkqzg%e-~fRavoG+jIU z3NCDYVYjwqB||xkjvU3b6XGv5PlZx*ggF(9=EX;9&*al{vucYti-^~gbegF8IawxZ z&p<@$kvdigHDqy4&q{>?@$s|RwwVPSxI9|7XY$17N;?0W9ThKyYF^Ur>QoCO>C`*m zQx=%QG^{i@7#B_Yjs{aHF$HW}!$r|4Wie(@PqeBzL=4}AL zSkTRbT62l20MEFp_2?qAMBnXH*v&s(TY*KY(><{$K6G8aPxu97oFgyo?p90?cr9?K zqQK>vw^5jEZrJ3=UX()z6>X}^MWzJk>owB5b7hp;{+BUf!u4l3%TaRA)hl*{^I4B} zXiLrpNY)ie|MopDSDMCG!$m#Et1h}Tz{&JQGw%ZblJZ7~TD47eNVY%5=OW^p)#(;Q1cNo|Zx%N=NU&+YG%$|?tIet! z(WDUsvGU4_E_F2kRf`~a0&PsoJ)+kAqzb_4I1uI)xpgAvUjSmk?I{ z4}6Np8#am{NKPxB%a}fu?FH5I*?}EKqM5~P17Y^C{X2f5I( zNCI@|qDjngh>p6NFSWZ4GQd;cX8%xDbs`Q6hCF?MD9X6$5 zVHP*$y3~Bz|0Acg4CXN*!}i4e*6@F1W^-!-ui->>>bwwJt1}3sYMpg=(tp**BA(PJ zE?A>xpDjGF05mU|9llGRQ8s^wX=$@;JL zNY`!)rG1}(F|pFXLC`hSC8|oDZB3@QLi50 zyJljGzPId_2{1tEe6hXK3T z)LzZTOZ+Hn4WK&8u3D>9ceJcuct(wH$cG#=I}Lwq)N76R#a(PHr#0@3s=YYuS<2l5k4G{u?-cj6cbs>gHVm) z>)xg8^$E{4WU*#sDB(F{7k0?8$*C-MHzvx$l6Y=5(JzTZR*n)HC<3sALo#1Q(-E^P zR~{sWi{$Ylh*+hh+~PLpd1EKd|C{{hlRvZ?xr%@&bJN7K(Bdw&D({mO2=wTXrAO2( zW|xUgts(p#ig{R=#|L%hAq#9g$EbfzWke2Iu5ng{nY|Y^e%PF`_J&=q#@aWx(heRQ zH-rG%^obt}u#fHeeRt+C9t9kKL-Dyy`j)LEN6n6TL5Mp1IeFi{;7nO0t$gAS>fdR4 z#a4aq2lO5`*A04m&$-FyuoBCAy35rxrPS4^8os|P5iCzpy_&}urt6|H#LVaZv`CXwHn z3SpSm&>>ON2Or`bZv5YB)h~9{+y$<{i+%n^vClVs*r;T;=b67zzq2Bnt73Z02vUNj z0p}>=GRCy#nKUj}A3RdYZiyN&m$9YHhnaI42Urh~#k^EX1nSA!6kJeT+A1AFg4lZeJ5~`$*U!Iqa zrkcjVfpm1?I+ZM3O25gDGs+l!9shgkB3bs;mnouV`ZsyWualDkM&B_DhEg?xJ6+8v zW50?DBqw(tR&D79f-aGJ42yx-r;2Ow6Nlt7?oXUz0?&G-kU_b+7*HTMP2=(XN_L%bp$X%j5W~o-4CiMw2DA%dii` zhjFf}yr9z)N~fh>lA?v_qG;qoyqq2M0lq?xyv9BQ`Z}HP=chng#DI(Bh&hH5vZ|akif9deRhe7FyXz)zONL8Ga|f9B^lg*cvF#-s7zAmy9H_??{=d zkD^GiU^F>y6_YLp{>*=4TUa)MgPw9@o4S%xIDaWx%!5i+Ur?x3K;D9@E8z>iRcZ=maSbbyp z$x`;@*jd_&N!*1H8;8)#(^g>6ZeLIus_2R&!=+`w)##O@aDdk1j(;u-nqc*5an_WR zN3U80V$QlWLytz?_BP@NVzeP8DDVl}B~3k#BYt8Fjh=$obfd=?Lql`NO3~o}#AUve zn5;U19_V?Jm~{Ng0@gw~Nk7d=I%;6jEjdXA-T|eq$VnPDuvCyFSU~z;n*KJy3FDsJ z0Hi*qLTei?1wG|NiY|XVPqc`BqWs;E9}*(c{&oQ>)a$&4)SWod{4TX7 z3stkV)gIF`^kKdW3bD`Tg^T;zYPKvm36s&Nvi|urpS=bVh}h#*?wT#P^q^+tg{sHK z+al<4h+xDp`qpFgXwxS`+%(N%<;Euku~Op`U#wWpg^zk(h?qM1c_CP;_<12t%JaM; z#GK+O0)PH`riAe3f>~(R>TM8jN2xb2Z^PAF5pN^ZTQP5?>TM`*90J^ET?V1BZn4B_ zq&uVRGz3LpR?Fhb_Z&jcSgU7ORcU#a@n`F|S)2Pp#>=)<%(92=TC04&Jbxtuja6VT zA=bJzlMA%w2kDAc0Y;Y9_PXThW8t7Rw@L?@a=#gmuFy)A;alSv0J{>S*)GSxX0?4K zwd^5#%XpQ2sbp6U!XTmoICSm&3*A|>iR0QO)USNJI*HN~FF3gi-Mx2lmH8%sdiBXejLiL>J4WdZ z8|Z`!OJ;=<&HD6lxV5cuv+)o}z3t>}+Am}Ij$itvQvCbc%ZA1Uq6wp%Jx{MPi9 zh~+zu(;GH2#ZM5KC7J(x44FIJk}dTnqH_!)jmgU99v0JANK1@&d4fXjHM_U8uKh_UM?H%0^JU)CC7CqaN-%igDi2^xs4`LCKt@g0_GQAtsG?uJ7 zyUG~1Ze?XLiwRG!X^-8coA;DR67kmM zWlz*6%-It=Dt(f#NHF$TrIOvatxwb^bBb3mO{W~fFn|&mkl8WL-ZtA@1Ltd0m#cfW zlPlB7Z2mI9P>Py_Btqu`M@edO$lM=xh}s{{kjlxQ3{4NM6!4jk7oz@0C+K@(9Gp_n zk%cCVg+Pqx)^p?90x~nlx9H|wLW(^`qY%j5&o8pa{B!CRF>n}^63&HE!nsgNI2TH2 zoCD>yzslU0fwM$TEdysB^h?9Z%Dz9YAI_#6IBB6h3#uY%dVMT@Ydd3g(lx=TIV@VU zE%q(BI>+2#Ek+Bfr(#y`8>})0OvpG#?psQFrUFs4W((u|b+&gz7geT%0gLwZdA}Bx zk2tPmbm>C~Ku(Efbm^hi^(y^=HVBuhaL!Kr_i>_7CrydNqj55wWL-e>RO4g_9V-l= zPL%|#0)$2uf(*0p?3V|{)MNRi%-8f`XH+N4Y!G@8otE~7SZg||(cpM4ixbR5eQ~ZF z5#|(@?582(?$-Ztq{e!;KEYSeflhUQ{~@ngVT^qNXEPNwb2eSX5q#NAHTT8K_E=AUmaF@Z6y5*tY281pJ+q{IIaQ~| z%?bmZdgTndThzy(p2*X=X-vm|KwAM^O7e@pcQ9n!IouWh;j9p*d)F=B7U#U9(Pmw#X(96hTsGd7)${{Is6~NLxl+vP0&*S8YW&3@AkJdg6T*qdRZi{$x?a@j z*a_hYPE34`)SQ9kgFmkd|7<4Rbb4Q|pIrtLLfDx(+~U}SGJA5?cMr-K$i0D0jb}PZJOHs5 z%nY7^p>>-;G&|UOySPxi%|UMHG1dC17{VQ1>R(FgHD}!_{E}Tte*Qpg^;i+fRJSBG+e+3E0!^J@#;v`p;u zocIzj^3RcBE!(uEjf4)vQ%S^#xsO}S%+ItHJB}Sp!ER?t6mfl6rrUrEuiZnYMR*|S{(pJhfL)+f~%*IND{P~@$H@|<;TB_LZAY2S((Uk|LP zQ};p+P%Hi=QzMyTW6i}~JX?GhQUQ)il}tjqdB0NwMiL4*IrSP(`R^%kl6jXZ;5l9j z_^qeJk1t!m$=j>4Iwj*Xb;lmbqIySsvFBxf?!jNs!U6bSWBu3n_I2*#567FDku<0#4|>xOhA6%v?1;0U$X z5p8a#Zs%yuZmMgH*Ja401R03!UP56eyy=$bnQtPQ33Fyh*3*iTWbJ^6{gaKdm8Z#j zb#C4nUa~fSBx+7rex}x$?a{;%rDJH#my${58k7RpY8UhkF>Be29P5SbG0UMH zoD(ps3+rjE@?DisvaWmL^aaBr+6ymDTD+uW&uetS+gd6=WUgnU4Ttu&NJAU9Eqsq9 z>9CU5Dba2fgt%rh3|HQW{w_BKrPeY;we~WNqonj4 zERwcgW{X2YpEx166OtRdLfYz&3!UILCo#_nt(8#Y!PDXurnoS8W&4u(2#jWHiQhR{ zxqMZ^PJ#P~!}>WpS=<@onH9joK_#@I3ZF3?(hk_Jvg9H0&j#@PSe z8{-(taoHih*ol9Sc*w%7Dngd+P6(YzLK_HiLQQgO6ThS4;>R@J={{4{lBeRGoR5%m zVwFlfBb(T(If;fOUZ*wRCeIr%Z_4vWTJs!v#qz@5b^l zLn>#m^!!0eIjtU*+bVD~>1JAjf<3;_P5Zb%0gs9lLsQa2Qp4_LEyuZAS zQ77Vks^UUx$(M@MXQGEjQ9I{~@~tNyR@r*~ftAtmOY)L7Rnl6W%NkT+DU;2+oV@B( z9cSm#LnroK-JDKnSL`g{dT+@5QQC{H7o?U4;k#(Xnyq~dn5jKCNi#1Z9~k@$c@=k= zs;*4VwX0_OkFCj>H7wEOoZ3@GE)YE&cMbsJhGK`z&X5*nRov-KQ$v9rI|q;y7`hfN zyMz{6+5{eKpRtmPb+a&<$RVZnJht}t=oK6Ft@~7CTs^-m1!#Tlq1i5cR#H7$H z)c8_>5gZf&lI|ci?#$0_^@Uy(Q;HH6sVm74KN{ii!}!Ia7p0^VUl*>~f9K$^xgSHM zo*Eo#vVTL`o#R8v7q29j-tdZbE*mPU^P0;aAx*Br*>3Jl4GFy{SyeUSt%DNeYbFNn zJjlj?S8wQ* zd#qqEf=1+iD(fezBBTT4CYRtfDxI!L`sD-C8*9eJ5eBP2rb9T%C-Ut-@XzNU4nos} zuA!AkQjnxXYJrMW5ve&!{)JvF$)v8t%hv^(5AaA8uVmjb6RKPr<~r>ef+wg#4p*RPt}BioB`yHrdUI{a zeB&k0;zsI*=exzQVzRk_xD9ZZW8qqbD`!iWk- z&CiwfSM<3$?U|uU%!8W@v*mP%V`vQ-AF<1{F3)<9Rj%xr@W~e!{&SLp)9%f-gLzJ}F8u>f)uQaL4y8NObLy~k{=rPAQXqA9dfF_JX|t7$ zF6QdY@=s>#q05~)(>ugc`3utpWx_TN+VP3jrYFe3GVq*q6D8>;yj35m5~I>BoF*-> zC{#^2X+V1Km1w&{tCn{$mnl|ColC>cx^UU=oYuFbTYp)4PUoTyRrC4%oyHmHjn==! z`TvdYjAj4(~rB?G2u8!oR*9fQGtCn1Z(t}vUcT$!n?9j#YCYd_>5Zj{{Ss_xRcamA0WHd{5bEj-kZvv z&SXqdY$?8Zxe#*m>fuyq{Rk@3mpRAqm4B}FyWh%O>pztunX}Eo*0YYs=4Ja+7pexq z#j{ZAO8WB1w)!i2!W#Vz_5DEVb2Kh4dVIp=a_%HB_D#G6r?}{;Z_(H@*w?~IJ)e4y zlX@$*T7lD~sjq|Qr8=^GdKv8s9^hlW`^zJv^5*}Lx1l4GuRg!+LNmP9Yd!r9`JW^| z(A-4bOug*xNag2MN+UwY0ll+|vB3R8t1ck5rH#8sV&73WVpL-GZ`dfKnOipaE&;a6 zh&gQ&8btqv4AEqW6T}?My{%sA&h7L87A5iLb2|UTGgRltMmxcy35wKhr>BkD0yG!) zn4cyf9H(RGxcDJ?zg^sAI;!lZYGDcPq!QN^c+DO;@55A#$NV2T^>uvPseYsre3~G9 zb+J(^*B!d!kuzmk9PXZ{7a#N(P3adQiF*Um-dCkJ-|>8dGoR)braSods*jC8xGu>+h1o z;1dD|()Um%%Ps^$wZ|~7g<;O67~*e#w(Mz)G8S>B4JT5rA9@rb$YJiSSYM)vQ_!1A z9NO<<)EbJGxo+@ zPXiHS;sIBz0GIt+SB<5VH5G)!SPtK4R>R}6(NXLWaQZ6Ig{^z}S*D7`$E&`Qd2lC7 z4N7n{d874$Y>}MWU$Qr)MH0syNa38f(R!3b=;=$)w(Z|F1#Lt55kL{~356KJ!-%2m zu?j)ipYu&s1s|iJ;mZ7aYo&8bev7J1>)cPYZGeJWYYZ97=EcX(gt`}(|;aSBaJ~%molW!ky$70+CQ;0X12VJqbIR7QvYkypJFoFcCTFB>UHB!;y-E z(c}WOrbAxsuDghnJ+_MRvTM{GsaZGwQgx%1Ub9BmCT-zMKXL}!CwUeK7Scx%tSW&> z%|GUGu4A(`(~0J4<#sz?PoCK)s>l^61R(eB3oK>|s&=JDOV@o*MGoS^BCmChli^v( zkXNUyn|ARx@O%v)bhus=3n%Fe4NWQ-vFPhbKdr|3-JT`jQ4D8NLd?9I~=uzgjXns3> zO~K)4#M1}Wwd}&@*Hs!DZC+H&?)tnU_Biv`uzNm_bxb>LwvVl=**NcQjyw<8qZjz( zYPh8Ds0Hx)O-vvNr_p>aCt_}~mJ)=}G0hB?w_Bt4ZZW#u>I)iK6teiUzgo4vshTx? z8@Do<{MXOa8Dj8M^myZsLgLmf!Uk+Y7uZ`1>9OPt3v9yC)?Y%L4%LMcn3ooMv};3B&yUrqg+Fhmrj+WY1Ps7&Nnihgvitg6F0p zB(ymRs|F;9()E;+uzWzmND_YKB>bP;1naWjtAUid($btUB&@G4)%uY@XD)k04W|uU zUr^L4$5;!*jkujPN&2>eHF7Wa*p!88smiGN0)XQ|N>ntlLf*+8 zKRRr#_%rXFcgrt?!=+D^kVGy^jNtuhZpCg=$)OO_^Id}ZUEFHGp_dJ6(zyE(%3wC@ z@;Yv2m8Q>6rqrv}1qdAORx1ZU`R>iV`&jT$sn7hmG-YlK*{`KM>LFkk3D_RMjvpOO zC{QIc#&QK7^XiFK?XP5(7@^ujO_IF_=c@%)$HUa^T-bo6=IQcA^1!Y^=VmXtgko7> zG3fDkF@JOTo50`4Ciy&WPac2rzbBtRkEb9n&ERVMFk^gR+%Xq}6i954T`&~x%zWT` zkSWmFOjZ2*weU{|q%&S@|D8w?N2fS^Kn;}Nj;at;cb9O$iUpO77wWU#RZ>vRowvy{ zex|kUQAeXyDn<@rihcoMDCv&#DT=b&8K|jq7%)@N%WeOd$xsOaasoQ+Fk+C%+#@ZK z&b(Ygk%$!GlxbN|qz%$}ITNnTM=T(?vV^;ion zeX9libf>sY;a<2QlN0yA3Dl1d3P<#wViM`ET5cyb_IJyImV+>;dL+7t%;jwb%okl47&q)%o%j!cVttd{LGT~oU>LD~q@=%P@Ir&ZFt)-js} ztiHn>O)@7l#FGP`;Oeq=D-UfV_c(>n94xcT358|+{32>3-OkSdn0Ct`W_%{s0QQ@JDjBe8w)KSY`=X(7_oClAdKAe}E2u41tCgPXyh=dwtC z*Wn$c+v_uZq&kRwue_X2QXNEGLAH}J9W=GSgNz=p&{Pr5-QPilPG2m`Y2ts`KjET2 zp~M~X<}IXHPt=y2Xv3%DgA?~bb-@9^o4Xwwws*(Y0v4O zXu3V2Z=cW?f=7!W{j?(Kgoq_^-4eH5;LKaAi|ELx1DVr7ULuG6r~X+@|Ktp$ z^lP@qfd0enY&zCgwbfT)D!LB8f2Q8C*6S5WG`{jmT6%6&6V@_u>|q0NT6wAXMwCQ4 z+XJ5aj!Ai)if1j$W_DO16V8Irua78EB-i^iL$4a&(N8g4ZddLYXN}~2@pfbi3pAI+Z0wDLbefDdCo_W0j#ORlJIgU_0ee*T>2Y z*mEX|q^6$E0(E<^BiGYH){(E3_lpD`3^>-Myr#tyLb#JCbX)bwrCeuSuo7iM&U25p zPE@5OqxJrMnTpgMN@85%aQB%tzVR3t=bx^S^q;{y>QU~8?abjDdc%33h`phJ^GR~N zd!wpLRlAJW{z=w>G&|K=NBuV-J1}Oy&f~0q=&y{uMY(!97A>HVb$9y#E>arzKiF1- zi~9`cBBd#2?B6bRU#)RF-ztlrm7Frzx|nU2Wls25G{m#h_vrotI#;d75*XfLgwHN^ zJC>F&KVpvC6)TxyM*oeCHadZOY9Bp^Sxc?6GgBKD6ZI)jncviHxuU;8(E#e3Q_ml% zM^dwDn?RlP^gfwB7|$VaIUCPriMoS|mb@icMZQZ@4$CQhY$i@{Ct*-eAI6)?jTEqP za6;KtGM~+60*|A%%Z14CV(TFkFk%{<7&emDLwvP0B0e2#t1|PT({WP8_K&9>F@ios z6yIZ~Hcs7OLtf|Z)>@15Wjt(NkYW|!QU!Ht<*7318yQ?-o^ zjQ(48Y=4;)FnzC6iMq8pm<5E7v$mz7cz{eO6y>wnat+GSrm3<;amGE0bThB$==98^ zbpbo|<}5V6Y!)q>)YE!5T2@Bp5EQj-3?B9QiyQ`%rwi<=Y}_ zVNro(K_Hv;)i?(hHqmXzhcbGIOY&;gPjT7L=B*C(DlH{1eoDwtM?~`kD0hl`{;?j#+0v7$C|YSl zlN9DM#d%eYi`=f*I0cNUVzuTLQB!FoXgn(F7+0?igvz?+UXenlk>()5X%u>)-+A%y z0(@!7x%^2%7fOoi(5ZkE$|wj;y}~D_#w+%@`C*8c>r}c6Dif^ z$S3FeX>8ab^MHIv%5ClX0I~CIzR-ywx2&dY=W@=E{g>YGIfMF7Vl6c$?s3JYVj11j z8!9zo!>qnzW$0ldz}(JKtK9f%`0enHr%`f~4V{PE`yM(=LcTdpNc)`w=K zl)bq2^8AT;@hN-Ty%dQ}R(mF9O~q{`E%u#&&HDHS>FG*L21;zUbQA zcB^J|ALPVis!)B&^kwp?J&h@u8%QuwC%7_phO^?ksw2HVm9@`~IG-TL^4kMJU4Ww2 zsYiyVdH_771K<&Vmj4V7tMMr%EqpFP)}M(XoCT#pPQ0fl)GIo))eWUX=N}`uKKDFy z?y_DR#n)yrpeg`!m)T$Am&~1lxX)Ly2mML%LbvrhsJ_fphYsq@hv^p*PahL_-SWXxXQdYgGeP|bXA-mgr8m?EuhCah)>rOcg%z~JLl3|)k}J!t~6}=hu_Z>u#5Z2>*Ul%Jx;52 zZ2s?0Tv{S4>nZ9gbLT-c%PC)n4kkBJRoNQ<}Hr?3=Gy%k-kgQ1%B zcNB$_5uB##=cWdQ&GkG(eAQZO{xP)!uhu-97oym?{3&l?^N+1G8#dp;#UhTg7&p|; zE|~C3v`pucjFlp;>&3wHX?1*rPS;RHQAP#BbJ064}dL(S3Q^U8fT} zB^muriT)xh2KlF@??78Hp0jxxM?M$ii-pGM-eb)RK3Zue@*3+n#7Dk$`{yAn#BNUJ+POf+l*K}+54&lX_B9HML>7NTIKcdmmw0x5ntI!y``Myx*0fymCnG$|4v zrb)rq3^Sx)OA{;5yj=9cEN&nH`=z#jDV&Bb6LjzaCUCEoTQfH?$o<{0)xzQ7 z#2qE(--X|Bs2@b6Ik!MA6gw7!B|;`_TFN>vo)$Ih;5n^X{8gjL`nl#7^W`jL))nN3 z5jZ**+akRV%2BgzV0Zo>ppBY$EF@b)7sJ7;Eq$Mcxh$zWVy@Tj*-3yAlc@Se|vKI1F9s zO+#$fUu!y$`Qhy;-Sl0MhkB=cMR;3 zkA5x}rN3g zpl2cuO~j#zI80t2$ZQCt?f2$d`676uP&6?c&1PY#I?~3;$eEaVW?RA5g%G`mD{#Z+ z2U<%~Wt>}L{Y{o{Y4zCr^Gb&6^>ljC-?Q)QUVMVX}~j?!e#O^StG z)Jy!nwg_Yl*Qrr+Gc^T?>LQB!+z3?@Y9kafUn5j0eqss}0%LjqGfBJ#te@`_U5j%? zviw^phX_T+sv~pHl|RC3|EamAwT*PyRIVvb~6l%ql%u{ilIqLsm?@QpK ztk(a}3jc1 z(o!?UCATuk%(TU*Yc+}~nz`j|{@>@k?>h{rE%)C3_ukL17oK^Z^PGKq&VCLu$F<)K z1d_K=N~@~{X&i5@L#srk-{D~>^I|TDefR7+`v|*|giIZ(6tBBVs=J*EsDh!GSW!%@C?*vKb;G(#Js=&`-;#^Q z`&Zs5>-qS`mG?Rp&hJ%u&zcb+lz7x8+Mk5VtE;cParwMX_MlDbFcXQ&8)SlcR9MO5CF!(px-U5TV`&8Zuj>-$48dP4$Ds(NS%81Gv8N(|tRadH$@x1bCq1p(QPx{%^)m&(bU#aU_{#d1VU$12}FX;O&})N{;`5PoMO8s z5Sg}Gfe5#KEf7ZA8G$IURdd2h5wRT*Xi9k7c7di?*)|C@28Fo)LZB;&enOxry=-Ly zO(|)cC(tDIY({~uB06254--8>psR`23-oECBL%vK=rDn{5gjPdbwsxn=$k~V1)5}) zO(D=UgtXnnz+Y#zs)@cR(0)YM2(*UilLF0e4LK~(TH@~#=y0O92y`UT8w8r}{;;hP zXzGvHmJv;($bG_$8%+!itwvPn9mh=Fb|sN{0=)^a)}#DuyNDURhW;*`6+bW_zTIh z(yFqdc5@gO3}lw?YJ1TD3HRwW*2h5!%$4JXJDB>40_74@=0VB4h!*C_2a8v9nZC8X zA}WDLA?l!<*?by=Qn*3D5mwui=oj((0=NZXBI0syM&9nxxQennb4dOK)9K1%^Jt_q!Y*tPyGT z!kvbC^zFmVU8G)XLN+4Y$`*`?ESh-@*W>g!96)!GT#0BGI1>V+lVrLRnf9^0v6)Q6 zO)`oZB^bS4Na9p(=~=?BRFrdVVV2iV?8d^hqb20pbT5V?zTAQ>PuDHcw!!Gd)Gw(x z3ba`H_Mppfk6BJ$gA>F@n=u7s_QWiXeBSH$4OqB|#*RihfLw^c>F{bo88E8h1EJUB zDyHDt`JFVYD_hW-ztD;0@#B2ArY*#i25E?3ZSy#5F$rDLTZ+5wYLqBcO(K^@&^e*n z-MGBGH*G>Fuy{)y=cN%P$FN=m-FDFJX;H(lsK>ePq5^?cmCc0HH0`7+O%9o6hcqXjLeN|xE)-OR}?4gU{s8Zx?ZI~q*S|)mz>wuYNBpbxxl=+Z^iKE z2KmnxH8iyfZes6|sEGIT{Y#GFA;~MIuK2MiB96{#S8_~2Th~x|Yh{tw-&I;#Lm8)v zJsWzxc50OBwQDX{q-0wReTAhXer1*z>$@d2aL;}~C4=961O{TQhv^Kg^+V~<3tFBEuelid z*oL9E#zisqmkpF??h2Jowe*AtYBipa!R0}E>lNEo)OlQ(I-p%@A$3A+xX*xkJur_m z=~pN~6<*^)X%$OJ(=mMRNmW{lOv5OuH#D%AY4>eQ(*ra?bb>T6llvY<`Y><@Gi3a& zDOWk1xBFgeD`;;LwBL1T*DfUOk45bYuDwh+hga@;3YxY7Xlzg+8k`!zpu_gC8I!EL zxKX(!8h$1Sj@o^fh^`GG*AVb>*T29aa|f95k<6rtX4;UMa#u49V09n|NV*|Y7jaBg zis??K8_;_)_^Q$+P}p8Zy20c~ZlTWhG$|Kd$8>}l*Ys4GGwi1b^`J3xSQNi$iYh)< z05OooBFm3Rq$vAI7;(p8wMdx-12?Q3+15ic-7A*Xe7^lot1!8#c3$?ajlpk=TAWPw z`r85oE#jCt#MX+3p~@U+ucqi?Jk%cgYeoGQwi`k^&?pIe+Gw5u>mbmzorQ*UQ6hyJ zOAg25t?$NJPjQocD=9x z`z40$9pu^liutK~E*-gR5 zT+K#Tx+QTM+ANvbLs)h`coZA5{`L{&WEFTVDW6}5^15pTjLFSYbrs9_ObFiJpy4#- z7#>O4P8G)X;7lqI7)!xj+EamC)gi~VsHR$9ND$&^bQR)M3KZDDnIpP_xygwjg)=Y= z1>GrWIf?kA#YR>940C!&Io@!R8!;7;E<53UBuN8WF0=cl_#D?hd94lSyh!^JMnOrs-@ z9<-M&0O#NFh8J#A{x=|Iz~XM z672Luq{X+dx?BaX!Q%|qmWpp*b+u(U+`>5}n(}sv;e|Y2scPpV3TMAStT2srO?dOF zH&{Kv;*$I^P-8W=*D#&u(34Ag^??BuIW|eSY)`yW ziHp7pS{1XpR*$7X&`KCOy6;d<#acVYhJGJ$6xUqES$~Wv&q3pXscpPlUp`rBy;UBs zP?dU-rxj!U@NPTOm72;**esdvhgGO2L6Ke*ozp53wTnb;%Utxi?1y3*T2_XayRDyN zR_@WIQ0UWY9g}>R3bf5Gz*Jyw$8`R~_nQh-Acrtdgk;pCUp*?mKKD6OYei*oI>TlPGkM)>gr5}ME1)lVY|YcgMqu88SP zau^eIeURj|N@a~#zBU=Von58F$8iy4yWh}VG?K#Ki6>b?#<~Uv2N5Z0M zrqGi6E1l3A6_7l#ELW_6CeH@&32H(ZZGh8M`h`?$~!I_~4*+IY@+FqQ4up?q|Q`BWM~ zV65<9=Y!~5BT}W`Ap+7?96FvYc0^Kexh14r0li6wEn0i+N*ZR^DqrJWs1A{3y8;Sf zv+S7ws!O;TtQd$z@v&~LFW7}pf=#+$!&fdAs41n1Fs!qBa>KHact*tHs_E@ zy#3`{C-KlZs?a>#ug?$trqO-c*vant=_0fXrF=uT#iur?#4fjV2YWx**CSJ8=C(ZA zwrE%sV|v=wk6gh7e0#XqgJ|l?mpA7E)WhyO6%?M?#%Vd|4?IO#{Ut3^@4e|t_mQDD zVu0&9yii70lP&se=s|4MWM*Hq34_oX;u4rv8%k&!J0*cb!Ov&it37OMJ28XCBX}Qh zEr|Mt@i6iMjd|HB;D~l#VDqS|gerJ_!*~X~dpC@|ZSy5ZV;qk18syYI9?cZm$7u-t zZp788eQXNW+@GoaN9!kdxFEEC(~xdh$LMMEm;9*7k^=}TI19<7gY74G`|}^kr39EyWomhb$K-Au;^^3y$kM}V)iaw z`R{4`Itmmg>gTT@N!0psAKY4meqKC1vVyI?`kvMCs-Yhl^wzdrV3);5e1ylDql&&} zb@3HX)9&XeTqXprr4E~>1E@BW$7=LA;U1mm`@RKrNZnP)rwZR{D0m6G+zdpy>zZ^R zv4<&|e}|j@g!C(Xt0eP#1oOtb%~x>qe-+Gck<2d_%P#m{q6`R0z}+G#CLuY z_`^iC7xISh`3C|Pl@RCK2xv@lg?<67g9PUlXzX zXTe{!h&zcmM8uIIP89JB5f_U1DG|Re;`c@TwTSPC*uyUP6CmO+5swgYx`=0qc(I7r zhk*ppf=e1RhFC*olu9w*{#5ziCxVATl{;M% zmunntF>BIIS+fkLpr9a{Kcp|#WXdyXx};0$q&fL!Lr!SOq!?~8f0AH~Ijg5-<+7ms zIau|?-6^@oJc~IrL^LL<3;KnLjIp_?)3Oa3bDk#8Xvo!c$(Mql5qwU|OEwAFy)bR zI1g$tnUZMesXTF!V7+We|xy-_{@z5Qs~w+!v9?B|B(^do zGX)=G1;zq=N|<2oOsND?;*XU4OXw;S%7uud^GvY?S!PGPbMn*jOa?KQcCPRIENUBq&C&Cv!1|hWf;#PbPO!xVpLHMw_OdUd>eM z<}JK^d|S5i^KadzZM*gzG#xv24(QUgTVVGdJ$v;I>Z1(~2@UJpuYdS}ff0iSKQJUR zYG`y!?6BdwxDg}u@uNm3j7dyN#`Nce2PZx>Y4Vh*DXG)a(hV8YGqYyQ%+ATpGd^s} zH(O@SE|~Mk+=vFd zz7Xk!L`_I2L$Ec0g=K}X#gMF-nVCwZdx6n7+i1KiJx!Keb5@Q)W8n1!cLIqeGg&`6 zDM{dRR#M!k;Z8~xGdeyhZj>_zp5aUErwtAB@`%dSq#BLcS!tkU1QEPXreVzII^>*nNm%2G+D?m6P3>FIYAx?*@o17gC;F6*PNPW<~E#M z@0PF0w-}9iCg>Pab2J&K@Hr?1a06rx4?&O=FA86%mq%}nSg|M=7PuXs6sLy=2x5jQ zF9%*^1E#Wav&>nk*;$Wp=RK0id{%BcMc4qFnrW8l zP;2JoSxn9;5u}MTYhW}dFW;=m&YEd}>y!W{L%t>3tjWvJq@`wOlV?aN3RCa2yc}az zHl#vepR{aKcTI*RmseRKoT>GJ%FQ>MENNy$9{)TNG1=Misad%^?iBA3ig#|FS!2k} zvrNy_`nwM*U6zhY+1EevNly-zGcmUa}k2h~3YxlV8}&I=H=!Xa?N6V$UINidzw!*O}CKYd`IlbJg;vV5OTcv zHd5Q;kW}Ohi9Z7`-5AnjrJ3^b^D@kuaalaJ(LH-=q;$~>LXpzb19bu28#c&j%A0P2 zw98F}^tR;A%*~sf`vB^Rn3TNt(b2;L4I>#Fs3FVv;v~6ch&Dljp?oF@U4W?E^dw_e zZgd_TfH|rE;Alv>?LU7stnptU`b5HCW1V}cb?6!W(qAdN`>&z-B3Az=W54as*@a>p zSCjta?6>_%_BDSNfV=Wv!!rMWmjGFj<4@S(^XtD-4iYYw)&I$5zwJ-5f3GtA`*^=r z1IQ;I{7F8Ns-9MhdzyYd#)65)=6_pk>rJUyJuOi5(p>#lSX5k6x@hr|vht~^SATfoqmv(>`sDPPPe1$oi?d(WeD(F;&wcal zcee8vYA=3&>2lqbt3O=(@%oLMw{HJ*2d~Zl%LA1A4?up?0Oda&|NpfA|8n~O+5qwX z`h0-+pN_vyH!vq>Ak8=kUpl^Cns|H~BIe-Z8u9Uc($o70iiCK~YXIoOxEo_EJ|ySr zQF*f(s7GT&L)u9OBPK!wgHe|0mV9(lMp&}B<0H_ACCgDpQx=W-<5Nv(ncOwTh8d=r zEErIVN;V=jmn-2fgI^5pliLh#l6!z$*q=jgkvn9c?69O%v&EDS4Sd5)St7+)a;C+U z?@(bL#gLwBaAG8*n@ig9dAZzFl10R+W^#w@lN|^f>$#}j~YETIe@~0U?Fq}B*KayBPHM@(gaC^6hcBFy^w53JtQL15=n}b zr3C&BpG+ZRilLZiA&g})e}6f1m-S`|9T=kx-W}maA(nsM08GI08>xv7!W?@Hsg-i3 zG(UJZFC7z&PWJpexH7M>iOfqjj=5nXirlZnN;tWv@xk_Pz;@AnqCWs^aB}}H9^7}f zcR%-KZgS=pBG)#Krxafa`{S)t#XcciG*5P4zK4Q&gf(Lxue%v#AucQ=%#+)5_#^r! zgl7!mJBqoX3{ZSi5MLR$@5Ver+>An6x8ZtKlAl9Gzwd={!+Eo5qP`QRG+faMPecOF z*^s8~l&sx>Cd}8YWM1j+%r)7qfqtW~CN%L?!mJ1LO@|#k)8U)gysBVOFP0O~0o@qOCbg>^;c(~r4S2BY zY){rq!J36NVa-B3jIt&!tch$ZlWXB`t0t^f`cBp=c?WA1ww<+-y~)%_*9Q4I6lyX3 zJECXJkWZSPC`SOr1q#zWcoeQi=6D{ZtAsoz_YzTlJmt(&*4*K~xtnLlRZ{o$5>h;NN|b$wn3^+O#86au*a z&f#~aMfV(W@>Vc!gu^>T#p8lhW+~x}Jqln0xKnIB=uZI}Q7y$y$P)#t1i#Zj#@5t| zWhJa3Ps(R1>s2uKCE$RQS!bSPR_3E*J}6&4D0e<6uRbAaqpY~GctyssfmaLNI3?EMqN( zLB@Yi^#*nP0K$ec*nsb)U|!(6HsTBJdBHtTrZS;yI+q=1x}zu3tvTzMtVY^3V;xQH z>e|%!SGTJ2g1Dj$Xc!KmUMDms*%#(T zGOtEe?N!y(=#e7jn;m_muu-TF0Qz{kfToKrV=h6hRxtIo-AsAl2&;7B`*%_@wQLV- zQsZ9jYE-09X(9Pv<0{taMENiJJ9#|hIiLnWpHx>I@mDr@p-RPD=cGM+!5FW6B>@-)f=4Lm@ye19!j(XsSHWc#A5Aw_pb=)sRXix8( zE>iyT{toIr`Z2ypAK#GrHt_33$mhi3m}7Y96P&5GNqLV`bcW@k5sIc$-7RHXp;NQ zt&F`0NLkh>Jk+L5b1}*j;3rQT$U~GlU(_pKQ+?VjtU#L%s1mUxmnh#NA4MM?FzE;6 zTT2CNX-2;@-K_y%GB2d5l6fS$iTy`+F57FCqYtnGZ4bFYbxiELI`bWN(SN>InU=ZJ z_~q%lWS^tVNM(TP4*JNg&FBZfAF&@$Eu~e5)o4Zmg#h~eebEnmU6A8QCeeMdje87j zoaF8=^rfGC8g1$`NGkw+lx``YivcUHK}~f8<=vSl#FxujUO#GJ_9wtOG7tMixA+U{ zn!~>P-8|%jvpq*Xh+%ew{TE~^sx)(X7Td7^l>qvV!ru(g)uP(Jw^wWjvkz;N?u|0o zf_X5Es_lrV2_uV4cG$!*#4uSCfW>GLu!SbDd0u}UP~owd9oR6aX^Lf zrVmpjw_uS@ZpfO-S<|Zut$(mr-_B+=>wx}y8}un!g|;v?tMjb!s8&`<<4$TvC=6^P zHa7qUT^H0u2LsxYnunn|*LU!E7(5XMFNC25!k}*8hK$T=WEJx@H^=x)&Bq_=*C6+{Fjfk`d7eM!R->p!zfH1(;MTm(nDc@C|(FVCCNcJ6=*N<&Ndsqy=BpJfnG+rlDczGas!pAmEc%M+J z7wxt))(sE|pwD5pxf^Q^S>8OcVIS%Tm>aMI^$0*8A7>*Cs4N7Mx?9s&|7ahKti0-) z)-Zc7Yj3n8Dj>_WMD2VK$|Q1d#YV$M9F&NQd-TZ%vnGu-$;c2T8ekb0zJJ0~{Uf-Yl358Vob3dVw4*%`@3HveH(Pz-5 z=B8&GSh*aV`ivHHi~;*;lFYOVCkofIW7e2# zC3O_rPkss82xIvExjfUHC{|0{aRyU9wmZdQPIVorX;)fQdb)}4$SWdFd|tXGTZ|oI z;@E}8x2ruM#68JiqS%Q^z|zIgifq_=RW=%X4U?kdQ*$weYXT=uj`?Ae*xewX_bM^U^5%z)X=SWB*o!R{DS-W=A0{1A$QxHaQ# zO35U1x>P>0NKGX@d5+P*b_lvC3>G7K5{3N)F@|h|x$%AvwvbE;<&kWU!afp|lv`Mp zFc@O%_rD0WE=z1(~u;=7sEC$n@1cQn1;={Hwo&Z=2`ifjg&PtmprpPc-i{)7?TPV=)6IZCG$=Kl- zmCw@%6zfE3N-D>8&@S8sVM|qX0%QH0)JP(f_@}Xh6a}-SO>mOFAOIZw(u7Ae<5(ik1mH_BO!T-!x% z3%XPS5$-(=IWRMeI1sbgWGEC$)?TOykjxmPqNZ5#Gbu-gS~4;qL>u;G-dWl|o;U|& zwA%$wQHl@_wGprpP^m_r5U`~c<~;!^ei$bLwEo}$G_BFEYLBr4aAX_s0IV(66kzvT zz-@p^E@J_J0f5nfhXAPnGhhM03RnsF3jiZl=E@Y9HFe{UyvcEIy9Yw&#hNl<&p7so zb8#D!oq(;WiP%&}cx)P~18c;~&dZ(Nn3tak(;|~MW{qvvGo7s0GX?ud_Jqti`FipI z`^K>!JT70@KQ8Qlr-lTq2?_nehGv7P3z$`u2!>l^Z{Bs_;OLhsX0OUTZ6z4Su#_)#&@jHtAzQCkLwddxd zMK#0=BRh#AKS{(82HgDzfyr-LuO~lq02+W1fNIH1017L`gThk?pfDGSxEPr9F;&O) zOM%Iqm4NO5ni3%O^8gCp1_1f@R{-(f0#Nuq0FeGC0MhqEf>HVqP`a!L7SbPQi}^?C z;rvPbLJHg;@F72|C|uIl4r3gz~x&mqnxAb93tbrO+Tn*GK8mWvbFy<%zQuhsJ#fGEea5_h$&~rf&AwS@TldiY-CyuLw=o7Ct}&lZ`BcNEemdjRkh{%)boy+~ zEv(Zl5JI-$OQC;=TN&(RIJ%LI-_wU_Wc7C`tJ*Z|cQv;BpVp^z_@%U{>vZq%SAHR+ zul8#JZ~41`jU5H^)ndFQw`F|Id`k0456Wo>h+G|8LjY;Us6xGtOp!!q6 zywvqwBdTc(LSLq+ZXqaHq^R~5)oM{qV?6p!pAp={)CT{yh-yr4@UK8r(=-Qtk)oQ$ zR`mIa>h^+?-8?P0hh;JTtq|23Q9VIacNEnrp9tnViR!9Tg1SIdZxq$i1i>0n{fMYv zCaSp*0IyI~^GXD@QB;e@?fdYr_uBuX-ESP8#_GoY|6f&0^;ME1 z>qL3&NRMxYa(m|+0pAqyH4)c|_@anyBCZkfX%Sb8xJtxZM7%-7Yef8nh*yYsnTX3o zTq@#15ziBGfr!l_Hi|e~#F-*a5pjZuV?;bi#FG1AqPmxe14Qg6Vzr2wi0jS?>0=Y| zIT4>0ag~U-h`0$RDV)osB(H-U2`>;eqTEji73BH9iWiEX)Sgfn$WJLO=nn9Y+H3kG zEV5alQ7~I~MQG0xl9DB@e+nnW{A>1CbQaq=5mT6*19C4l$@B=oL;%U-)qrh)lYk!p z&5;;%OWr6zj+ijy4%r(Bpt-r(0L(2hZ1-Rv0j>hnFxv+Z56Bknf&J(A?@R%75W)d< z-=fV1UJbYjn$|ZKQfJ{iw3Wcpc^K!jFmxv70zeBhggwxC(10fF>x29Q?R;j%`AiF) zcX`$q^PMn5csHOBG~pwdfHi`y27a*>Flbt5xd=E7T3TJZvkweY5k=uIw*CZ zX&q)g;3jBVkMTxFMQKBLfS&@WLDSky1Uf7l(6mOg3D65Pt<~HHgoCEZ;o*Qt&^*y9M;0y+h_1h5_S3SfHhrV6z4c@sK!at=@jK4JG> zjC~H8a3J6?Xf5#H0O|{{59|S9=?7X3JQ$z>9SJ-S5D2;w_+x+;bPaI#KG?qoS_`}Y z5Cggpc%+swJ?I4BtYCx}G@UD14wwLX3vjCt#1XUxxDN0HXu@Ygv8@es4e*vQ#1V8A zuuEUu8wXkqJg*<(0-DZ>tOc9|T?yQ+Kk}m%;Q>AX@B>{9d@CH=j6u_R5Pv`~&~z4L z03ZN#B=AZ=CTQogAao|g2q*=g@LQw??R?&Y&VrD@-2y&gS3o6bHEgYXl8?Vt&d z9*Qypn(%Z$0cay|MGWdJ=oP?^#i9&@c0N-rglu@`20%M^8b#KqmluPDRLOKJkfvyDhoq;^9LmYtz0Pc_( z;Msrx@GF5w=fFMC3Bc06BhmB8VP5H`>$z_E)VM?urx{A|GIppC#=0qUy=GjNk7 z$Vbp>VDB=NNzkP5W#AL81{gsTE?S9jg06c4d9@1ugH8bscoJ<5Xf5y|z-clM zJa0A1C1~2?z7|kJeBc{vAOo)<%xh6@o<_R^+6Wx>48jgN68J$tIB4g+>a>sjUBEo> z3DchQY|zeo&z<+H)4uhwfL$;{I2*7DG~vGho&jA6yy{uh3!;G!0HfZqb} zGl5YR3xW72=nMO%Hr|6ThpDQ2@R<^MCpgi-9pc;W!Kd*)C6Dw;{t_QYSeYgjd$fX( zwt@z$S`PKVdUeWC8PmDet2Z8(VV`cj+Jy~eacmTepSXg#fKSfQNO-&GVWfzE2~9v` zn|0XPjBRaltmAf>{#ZwTZJ$xRNh>Wimu}^nhTHfoxpenf%;+RsY?p2b^78Ts_$7z` z)cgOI0uL|k|6bqP2iMfYgf$(ZzJ5-zM$56=ntmL+uL}58YbI+dbN47jnP=_-cTrPR7Akxmq(iFOejf@5{!|U;2Ww6MNCNgBGDa)8x@yK>N3>H zLd2aaHOIshKMlGsL8g3D8q|~~ls1JTmdp^2#L=Z7lYus)GjxA=TJi89&78kTm~&SD z^R(4I)_Tzz?^%-CmX)OXv5*sX-vkdC9^0L~PlV%5e!wDHL z*nK165z%(v7*O_nh22*N!Ww@E&o$VdLjoPyr(o|>DC+&6gxhyB*&Kv}!jp=9ck$58 zL1;|iHVR1s-WwF(D%VW1!#BNF0-2wXq$BjP}yTuPq zluzDzD0WvQr>e>)A1Lo&_YFkABKDWZ-c&7n7aB8^hRl8{d~z#u+Q9whsn%GVHNM6Q+hfo5+}q|V0%CdJtjomFItG*F@uw@| zJ>kviF$qcKv7QyN*Aj4be*f~ZpWA)IrO?e&TVrc_#-7IG87{Hc%4M}m3j82wTVrn` z!H?`y3)#qXah%=a2{(K-(6?KZ);iJX!6{TQcy^1q?N?&BLz(SqjZU%Zl;f=Vp5v_r z3Tve2P9II=GZFjB<2}nQH&siHB5_ou>+m~R0S)t>W5&Mg3}qSC=|k_u`Gsiutx5Ua%f6`O4*o4R*^llad8?IV?)LZ z*1jYy66~icVmE>F45!v4gkB#vZbm1VNEnx7_tnEl#KC#pNzKjp1_+#1hoZw5Lkb;= zHtusH6m}A-&?-^Xgds^N&h^E5OnLmB8O+|7L6F(|k~p(l?qs5?jK>$Wa8rwyP_2DeC{pa^!^#O){!*?3_jpMKEbwDLx8NdX%2%t{^+!k=n6ZdohCj(A{-UrwY zs05S&W&v>R21@~M{7HQPsun4LdB}T$#H5&{k>!(op4h!D`i+35i|u2lUZJ9I7&r;H zYQ@BioD|xBfHo*NJXAXgt=6Q>*>QAcBm=iB$CzeK%9}PL!(^B`yTCZLAiUqCtWf$9 zua^%prcZ-Qzy9rs!RwH3|GK$9)C3n{yeKZJ+CiZ)DgC11x?(zLm<)B6umzNQZJGbe` zRHAT8F|CxvUEU*~zBV-27~unH*}#cbsUP$i^qeCC5%p7@e3rb*z5$sNtw@EHO4J#+jAm%!$@T zC1QQi*&b)HvCgKFba0WehZ>j@n7SyvaES39W#a!oOUAqM==>Bm^jjEjYwy9Q@h}ZA zYS5QG>C(^^sZP3(R2|2X*i`(+;`~Gs#=WC4@}0`Zzr#S&_m1N7&%z7=Ng$d55bzMt z)c024kDM2WEubLpz(5BRy-ySGi%w_5fV0sLj6=^f1ARe^Nw^s;3u9U~01$>QGvdJ( zpd84syDblWMkD%m2nBruaneM_(^iOkF7J2Or&{B_K@h@2J#_l2kh(IahyT;LElPPKbScJ^ z&Ssdoo2S8@25C6|qCpt7_zMU1&*V%H>?MflCB&Ul>X-b}!}N57tpFv)g0KzcaUU&) zmtsm|4ynA6N+>zMlm-(p(naS5ZsZ}CEQp!8L78i8SA&$N^bzt>gZe{xD&?L=%sb41 zqi!^Ix1m`npD{;@xX?^gDqNC6ABWfowFPn;Zjsb8!9*_n$_92Wk4+R?$iTn#qj`X* z!oCQr4Rh)1)wgBe8GYyVeXZ}&zPI{j_FK~Lul-){7t()Z|NQ>T`fu)U3V%GD4Nwkf zJs@~M@_>v14-Y69uyDYO12zrVIpE*`Ys9k=>my!|cspWW#J@F&f0$!@E<`S8+Tzv~ zZcTbD)-Ar_g*$gDi(4-M>k+w37Ti?Z#Lcy5GZ%SlSEkK$^XTb@OGb+O%VmnniP{O; zP$!N`+rtWoitp$J4qTEI?=4bnSZVw&*$MNXlH9OHl>W7M073ir9f|R}+1Al`T%`-g3l4eMYXFxn$)JeUm5j zz3^ebDbwN;$Amq+Z*E4r^21;BI55NGdjB(P%qy=ywt368Sx;VhX+&D=@SvGZ9~(8Y z=dw!^Mp#d}pKIOgX7S4t&Kw*1L+>if*@{;VJ=*T6Y`t<-`?H^2p8l0-U);WDQwDwY z_3oDI(+aYD=Dylnj;xlgFFK$t+N%w7Q=(Y7y1L6`ia>2wZGfcK$`<=&n$5<6efs33 z@rEO2@<_eUF4c^-3!73yxF9L4KNm0SQ zL!46?MaiXD27&hO$io4JU!L;{*mt(|-=3F0|6*}(&l2CF{vO56dTp85Ds|gw*PqKi zyRvfS8{=9>zF7O%?(hLyEd9&_*Ni^bf9KlrqpOCt+p^fK6HKeQrJUNcEp8T4Mpuc0O&2&zTtcPSAAKLI4t_cXE{2r z;0=wEOj~4istFULf_=5#l*CGJ&++s!i78hTooX}$`)FGb+ugfqjKwr96&E99XB(va z@bGp^&P<(cHUziRwjpCpy?q3RCK@kkWo6KNK)BT+xP!Kxkbf;jZK}ZeQvDUHZ76DL zSbeo6WvEnb{}5SGEbHW)DpXyIWh%&4+#E$E>ZPNvZ+QQh<~5J`<(rmR>b8!$^5tID zp6RLYtWR(I>D#xChQ3m&EuXMp*=IAq==ZE@&xaQauFigOLEfM}kH6-%GxJ*Z6Gz`k z?EQ*v@Q-hPJb7vx`E$4X%xu5m#`>pTY<*b%)k1yZ-No)94@XyLtsd-n}>&VLdlkkcb(-nuYhUrgKJ z@;PgJKDnoOlk8Mm{jn`eC**#q?7n`=;iHdxf2TyseJ$16vG~XLT z`ga^{3@*H44PWwx&9<8Te0=PaqXJI6)NRxwo8L-}`k}`M-yJzK<%_p_E_z_wU$&k3 zYP@~-)^`{Dc--^Zy464T{3s%^O`Gs;H-?PR7Ay7u0>ye9wetCi0}8%89%sl1Z>P8H zYZPMbRHthEORZ>sZC@k^YG_b&HeUA9glZBj(=cw=B$%>t=*h7LHC7X4G2;e?Eb|=R zqK0Yv2jfK_`lv-EWnZak7H$2FArOT?Q}34bO{xhu*=t4_%(L@MGlL_v1B4)kB;x{| ze0mOy-X=>mYp8_eqfMGUdv?R7#biv?1g3S@h6M*{dy!=q?=BA4rHIlEBN}R^sk0EG z)rL_=;nynv?p>F5Ex24#Tho8^PcG{4-O3Zo=KtmPV+;K~_szUCG;!J4kJ{?IyIk0t z_Q1xKwfKPAy_*}04fPTBG*UwV6@6;_IJA7rguS?@Lw^{o9 zKe`n6QfqAwzY=JhJz~t2i^p!(C7;Zz`g@MY@-wH?E7D%?J*wOFMa_QB`Fr=ZUoRRy zulM4Gw(-kfoxU$LWTm&}&P$zrp32!*I&xN~;^T*VP40NR%&Y4^I`m%I^E=tt_^Ua6 z1B<^rGjr0Lr_Q`w^pB~>N_M9GrMlCdmm-E{z5eLHZM(KUtD17)La$fci9Bl&_pgV>>QacS6>fFNk@)yW<{KY3-t#GT&W~FInz4_)V%%iR|%Wj{MhV^nxLX%h3m5uN3XPuUAnW=)e{$@EMUV&!pNR2)# z2Y0!p2S1=4M4C;#`^IEV&objhxVRXNB;tB&>R(Vx!ZT{KQ%wd9zg~%Vz%;W$q@G+; zpQe}w#cHvUUTg~%y>z*km%lii+1ep+>XF{Mb+>=o6!}o|L!RZ`w&V=yRuzA9)e^plG?N$f3wSP$&W83n|5O0^_gY9>Z>-^LM{kMO(lO24* z-eiBj_8+|CIeedC(R0Zwr2?G3$JKJ8Qo$#=p_Vf9NrRz;d!aUxv^ppTU|864;5l(n>R! zv>hl-IxGCNEer20)*8!9g&IS*`l*CA3f`Y%=+ZT+%zP;J&hW6h$`@nr+&ngI>F91r z7stMKzUf~V=eb2c-({JlUH5b6cdlMByWq$)>t^4`8_Q##n!EJT$@@aD`doHTyK-_| zmmfwSJU(LO2SYc0vuD`fGkqp!Cao{ss_M`-Z)w*ZZKFHJ&((JGY47v&ZSC>|&xt3_ z?@K)MQ}?Cs%$$4Z+0z?h7nNO{{?p5vkIx?deBY{BuNJ$#HTckjX{T#r_r%-k-a4hcGdT13Lc^P;cb9*%p-JG%c|&zeFRa<}_){()_gMV>=?m%p z+k%3gEdG0D&zz&1Z_krm{cL&rn~Njd^sl(zzV^`4;WvV?V(`%BnzF?=s$VjEADTJt zp>IPwp3xS&x?m!tR*-)lsh%y|_>54SMv0|e=p-S~dBaebs<_hIV|;3UCVDGoNC`D> z&@cjXhfv6YY9g8Sp`-T~CP6P3ZF{KK2GUlcJ%CO!D<)iz6IK3mmaDk4Ao`}o&p#6~VJ^=$fK z=$SLkk6)5c=+NaxkBu>|FJE-&P_XdX$9Fu%UhX{q)TTx2(qa#py1jo*f6(&qi2;{K z53RF&>9OMAqnf-ms+*fSPc?S$x8ca*@Ba3b=DnmB7JWB(_xz`R4u}o?e3tL`s&x;i z>kUCgnX!MBL`-Lhm<~K^(B1LSqUgIR(DPy1er`>~*)?Bx1*LiiNvW);`LKLc=_k+Z zcVGPC;-A_s|7g#PzA}HgESH8rO{w~+s4pU0tW}YM&#Jg;U8q9w?hWs8e7$h)>DeED z`K-r~XScrBEa;`npJmO4w&bagT%m5?4D-@7Lrk;z(JZP#)t>f~N5_X2M&~yFv`KQp z%pXrY`S6uzwPVS@4vJA)y>>*Uu5#Go7*T?d6wHy{;iL(lKE|0@)D7!{ErRKITQ|Rt zqr6dZ;-!g75f_pTC-+>65l`8=e)ydJos(m8-|vey6y+&49`=tKU3Z^@h7W=E<0Vj)TU<88+IC~hkdJ@P*>UCO z8y7xVdA;B8f~sykd=F)wy7um!(W4GeAENFZ`P`NEM^bg){nh18aK2~Vu(F@WeHzz& z)#WCiuK#BD<&=wOk}Ds4Ch(o)%Nf24m*`7Bd$HN7=|N-Wh7EW#YTvTb%&wmY$eVmu z^r*+`-j^@^b;p_)RYett)Z4!LeoY@;Uecnt^n#}^PTsV->DaFOFYl~4{NW3ICN6w@ zW@_uur~S6Co2EV+aADS0eWGhGelf2_SU~xwfnzs+V;L3Pb9&mF8$Wp^-^1mDcP~6R zy<@6pvxxB!#8mW_H=3_6%f<) zO=i>JW|%;&SNm!^*IQHsV|+39sr3(*7C#!3clm*rPKOSDbmDuHc4&(pbF$+ZTvUP) zN9CwO{V%K-YFKZqR?#Yr5!3n7ZaZ4{%f&iUL!AQFkA*!SRM<80NMKGJ55KF+NeCe0K@g=|UC$cd|)w2n>acF3=9)UaWdR~{~Ve$wy{ zypIoDP}6lyFza%u{m1fr_0%QP_72#wc3#LrS=J|4r+hHtnYoYL zSv_=5=XEm-@2&0GRk9G-4UVNFJk$)w5$F|2ed z8%-SwcNvO?L&r5(C5$A3!?k#2npP<~)axpOTPW0X9y{4#!y8j_7OdTN#`WQWKi-`K zqb7Z*dEr>Ql2%(1nKjI#uhq}><@p&dbC(|9yu_pL_t(Gq+uFP-@2-Bo_xjai?MCf5U z`TnzKKiXXnCw8BL`c;>WyU%jGdG{HPckez8?6GZ{J zY>?AF={nYlHI-x60qtuR_wT3^K$9A)r~D%t8>(FKBLU|d@rxBw|Hmbh;&LgsOD06Z zp>}Y%_3Q*;-!x0X9t5%)DeT!oH5c|xgGFDV=6CL!hIwp-x$h_Ko8CbClPGKy??x-V z8mE2J^C%g>pfr%81d4x=2)eXyn$Fns4Hsmt1=n``L}K4GHj43m*eS#XgS+QLiux}S z?n(Qrhmku-BObPv^=63Du=@SgJnd4zq>ei6H+T9X|84uGogDo?Lh4E`28JHdS@-G6 zM}(J;(1xDSSr6NN2cyV@R_RI(1nw91L`uK9B-k+Otgdfskc;Ip0V$vlH34lsqpQ#d zXm#Z>aNZ$KMKUR)lu&OYb$OH;p{t6sUWv2Xz%SF6rw63ieJ^A4ldhsPU?W`6SHuJq?4*t4I_n3z^3s4Z zFb{#JCC%~;0fnS44Ojsb2_V)Q5m$2D)$aQ(99&doy{Rheiu$9oHq*gXV{GV=*w8BO zHhCOK$%)vPI}G1~eVw&5;3jbB34O(~06MCqtMGIo`xErm{kn2}Ktja`nXY_Nz=Rlh z%?$|dG>1ESYfL~w=n2)Er^!ZwEvr>41yd9PB=*f9l}PTq|{0wZ7t*qfzmobXD!}Nx=16VuG}1u5Mzy0#-gN`&kNA$thcQRYHK9Q<5WM_J74Ze zi?UuVx!G2=#EgjPE4+?gwA-UpZdk05X_7F$bUuLV%1zB+S8h^5KjZt1a&DWX;duqu z$6k5bm`t|cs<)kSS=h*a8++JcUBSKnUa(B!{rn;Pl-yRTig$vrMk`creI)sO`TWb^ zEjpnprNXMGI-ea-ptD}!bxmje$o42@t~Hsm3t6bADo8mRZhPSl?M=7V75GrC*66M8 z>a6eCrt-3o7p}9yo$}e*+W18$%>Bt>vPljO79E6E&v6<72pPNmjs)LA>WEF3T4anbQ>x(r2$oN0^(xt1-o68 za+xSng=mGiuq!U(6v?^b6#@wfRJ=wY{j9~bC#XCM=~1j99;ynZzhE~|FhzE?0!enm z1rlYlI8q=r)?yt|Nc~JvP)6E)hoL~}DxMJBLz?3_MJQPMib(+}lyQ-IlzDB`j!y88 zQuDooF#&-jy)YA z#tIHA^k`OkeyCdf3<^bD$xWqc+V#Ee=Kk0B%FLci6&`W+!(}pE$zIv@y>hcE!wOD` zO`d@^sARAF`d)?EOJFHDM-gv59Jlwpt0h4oT+BW?=u=LV>{Z6uk67yBN^0EW7gbr@ z&wkXj^OkP;yUiP4-a2MGacK8h!Z~o6Z{J zQAgaH@B_>`FmGB;!mkeJO{fo!=)&n0x}w`mjY9+5wrh||DnEsX$}h$vQdhLM5NZVD zqORyXnb;l)wchF}_@`IxZvq&j)LZo)YVynv$~ZOg{D_B2o9q#YU_}zYhWIw(3jwnq zCT<;ZZ(3fCE3%OfaYZ)?sY)J3AnOV}1K9bUINDMl-1?+2=f$}k<{6Yq8Ptz5sHx6= zM9L$cKf03ha;H4v`2(K9kw^E)pOW*a8QtHc_TR!I5liwhFr2MI~+h+^;gS?<@ab%N_KYN|>M=$zE>3fsX7YVNS zK+-3BP$tCLh*v|r)5NPrxOfgx_KiVC>V=RA8KeZW`LE?qgQ~<8jvQN|i|~@Q!c|0S z-QK0INze%dqjOc5jFgMek$+R&Ia;12}GKl{=KL88k=|CBP+QJr~mx^pcDv5 zZ0N9VjG#^tae;`7MZ8AD{X~2~#Gi}!nuy&*cejdajffliCa9#bIr;|Jz>0e;GyupM zI=|GR^G9K;`>u^HGrLc(c`IZ;#QyI+zm#h*PmPL?=@&LBH76av!n7DYcOo8Qa=hu+ z0&iBHz&o*BJz3WSeyoGpA8#|Z#vAHw@LqRYrjoT|9pQf+#<^bTM1{zc_22PPqW&NKq<2fCcTE1TC4(CE zECirHS&jt$0N@F_2KXD4b05&oWt+-9^`j%fC*0f@(?6g!z!{jY$pmc#ehOd){pa_; zl>(TBxl&`d$L)|Rgbo9J*Ek%P?4z%6Jq0b@b`84rg*RyM!{S1#;N?IM-hIAu zs^c`JbV4!J!D)Dw@qzoCuk7MDUpf4q=PO72!uiUiM&~P6-sgPfSjRcahGEh>_)_S- zalZ1OIZ^rFb-M9h<>pV`?@0j}jZ>H(s&_+MzkWV6B?b2^K`u1@Y=7YSP#ygHwez6? z+|2*o^P!2%A890=1Z;e2P&zlL!P*hll_7k8%Q?|OIOzUc&xs1|)}IvpUq2@r!{h$@ z&WScogNdvi((kvO6P5Dix1AKlnQ2TrHFmpku1jZv1-Jh@&x|&fBY&#@m=vJ7X!f5z zpZaKrF|#^!^jV&{>9I-g-a7yP+B^5qwyH3IXJxJF9xlSrNDE%8)qri6gm#p^mI=-v z70a|E>q6J0NzF6~p-H!El^W_}*@j)zt;Kii*lhD@wh#5q|alE8%Z@Q_nrLs^h5-gZcqsm;H3a zQPsHJth>k?+TLiZet3Om&xvPU=AwO0+nwcWXAEOI?z?-*sN-DXowrtP{amlo55D#( zf2HrNH+c5@AG=Z>;~l!5x(CfeQy-I^JKEb`J~jRJk%U_@Q`}@jwFWgof zxc|Ee{qk+~DJMR*FMV-&L(^WBWofi)@%q-0%Y&*d{=_bFBcV>$NF1#ZFINWPoz^Ll7u|`tV0Nu4%;vfJ0|<25#)G&dt@P>%**&atL7EKE>jk^7&7n zY0g0fjIKs{*Is?6?dlc9P>#;v{QT3j%u1=u&AroWU}o*`+5$64kLY$3)|1KgRm(b8ct#A?SV9S6wL`>0nIUpKPwqHCDGuWBBdPY8%{1PN@!g&gYklAg=*L zJ>m5FUSB5vD&OcDS1;HOWgiFAfgSGeO)%F^aBs_QavSt08y|9H&EEUej6R#4Vgp;so=@cHpHcWu{4ZhpjSz@qJd_T1onrxEOfwm9(L z%W=L2zVYzA72N97Z$4LiXUCc!=d)2x`}OBj) zZ>PiVvUn@6Nn|QPB2#Dax~ewo$~c$By{QYNub_?bf)lo}43v#!O-mOwtjN~Y)UsMJ zDRb&^JdP818mIAjtQ1rNcTjVXF2L@C{;P)7!$CBTCvhwiJ6szzMfqqb8joh8DxxMF z(GV>$5F=?JCelG1#6x)ECjk;9ArdBWk|0TvCK;j>)uKkM6^)`vbcnnd5JO^EjEe~| zDW=7YsC*L|y{eJjx&iR$>)aV-9Pu z7T00}HsTg+!X4OwJ($OS9Kb;w!oxU>W0;OI2_wv4g+NEC7C1p8XoXtAD5wso4?z2D LV>9qSnt`7HBkO?C literal 0 HcmV?d00001 diff --git a/os/licenses/ryzenadj/src/win32/inpoutx64.lib b/os/licenses/ryzenadj/src/win32/inpoutx64.lib new file mode 100644 index 0000000000000000000000000000000000000000..b3d0dbb749367861cb4dc106beb96c63c4f3c0b1 GIT binary patch literal 5192 zcmcIoL2nvI6#jx;$Hg`VJEur(X%)#y+PXFdQmd6&rw|(%Fv7T{J%mu80SN=bxN6ix zZ#h&shNqRWGdrA8aI_aC0)c!586LB2Z#M;m(}i9Mp|73Qt3{yHyDKM79N$GxcdfkWY#=(I; z5<6Uqh{FrnT5)O^IiU*CTvm$7_}#V{0A&g_ekxz<7h8``%e`FNobO_0H^X#5FZxwR zPWzBd9d}QJzIMIV$v01WgGXi(u_o3!(&j@lwbvZ7Z#gT1`>cUzERY@&siO(K$GW;T zPmeEmY4oe8(Jw&jKD+qYD&p^*YWku?ZGI`7d(;MydHrIe7JEdZ= zQXS1wwMbq^v#ngLFs~WcLCqbmoZ0Uu%XO4=%^!bB{zgD=!M7JwP8N}2c0z;=GIprF zuuPpz#g-e^xCq(i6zud^ua=(4`e>{75<4DwiH2tCDhw>N*-(7Dv zYQx%%m~6O4fBq)=*J8|2X<*}pC6~0=gg!uk>`6K18;HILPb;XbIlOlM zD_*}I??b|bmP~To24MQ!k{ok{!jdHuXBp}QTP88$F#i*NSr2OmzOrgu1n@1r-~MS& z8MBX!i6yqiGA3YV%%7%z?B(Q)X^1BKJ(e+BlF1qKhpbp7XUx0G1V6L;0r}y2f(}j{ zx$Vud$ceRfK^DYZPmY|&T1!|#GFO0H4U%WTkZUb2dAw5okrk_Cxpxs^KhfnAi3gNT z9p9u(%qZW=c*QmzGoF1)c6g=hrx3X@nKue$GVxYzbKG^?%I11m+i@$cv~hn%cIhhW zux)Rn=B}+8S86`cAeoqK`*X6(nCdv4$)Jm~onr0{qV8dx_YQ}38Fq1WysY))4+h0Z0b;f8(Te<%IKs7e~ z%95a|jq?^+;{Da*8^a|nCtM;NC9C||$9X4E4JtJsag}$zs5VkQRMF)Rer_C!&8cTL tAwO_w9(+jFd6&@dGncgHl1FP~iO+?uwNUL 2>NUL +if %errorlevel% NEQ 0 ( + echo Installation need be run as Administrator to install a Task + pause + exit /B 0 +) + +cd /D "%~dp0" +choice /C YN /M "Do you want to install Service based on directory %~dp0? It can not be changed after installation." +if %ERRORLEVEL% NEQ 1 exit /B 1 + +for %%f in (RyzenAdjServiceTask.xml.template readjustService.ps1 libryzenadj.dll WinRing0x64.dll WinRing0x64.sys inpoutx64.dll) do ( + if not exist %%f echo %%f is missing && goto failed +) + +echo Please configure RyzenAdjService by adding your preferred values in the top section of the powershell script. +timeout /t 2 > NUL +notepad "%~dp0\readjustService.ps1" + +powershell -Command "(gc '%~dp0RyzenAdjServiceTask.xml.template') -replace '###SCRIPTPATH###', '%~dp0readjustService.ps1' | Out-File -encoding ASCII '%~dp0RyzenAdjServiceTask.xml'" + +SCHTASKS /Create /TN "AMD\RyzenAdj" /XML "%~dp0RyzenAdjServiceTask.xml" /F || goto failed + +SCHTASKS /run /TN "AMD\RyzenAdj" || goto failed + +timeout /t 2 > NUL + +SCHTASKS /query /TN "AMD\RyzenAdj" || goto failed + +echo. +echo Installation successful +pause +exit /B 0 + +:FAILED +echo Installation failed +pause +exit /B 1 diff --git a/os/licenses/ryzenadj/src/win32/readjustService.ps1 b/os/licenses/ryzenadj/src/win32/readjustService.ps1 new file mode 100644 index 00000000..b4d44a42 --- /dev/null +++ b/os/licenses/ryzenadj/src/win32/readjustService.ps1 @@ -0,0 +1,464 @@ +<# +.SYNOPSIS + Automates ryzenAdj calls based on custom conditions +.DESCRIPTION + This script is designed to provide maximum flexibility to the user. For that reason it does not use parameters. + Instead of parameters, you need to populate the functions in the configuration section with your custom adjustments and additional custom code. +.NOTES + SPDX-License-Identifier: LGPL + Falco Schaffrath +#> + +Param([Parameter(Mandatory=$false)][switch]$noGUI) +$Error.Clear() +################################################################################ +#### Configuration Start +################################################################################ +# WARNING: Use at your own risk! + +$pathToRyzenAdjDlls = Split-Path -Parent $PSCommandPath #script path is DLL path, needs to be absolute path if you define something else + +$showErrorPopupsDuringInit = $true +# debug mode prints adjust success messages too instead of errorss only +$debugMode = $false +# if monitorField is set, this script does only adjust values if something did revert your monitored value. Clear monitorField String to disable monitoring +# This needs to be an value which actually gets overwritten by your device firmware/software if no changes get detected, your settings will not reapplied +$monitorField = "fast_limit" +# Does reapply adjustments if power slider did change position, check $Script:acSlider or $Script:dcSlider to apply slider specific values +$monitorPowerSlider = $true +# Does reapply adjustments if AC line status did change when AC power cable is plugged or unplugged +$monitorACLineStatus = $true +# HWiNFO needs to be restartet after this script did run the first time with this option +$updateHWINFOSensors = $false +# some Zen3 devices have a locked STAPM limit, this workaround resets the stapm timer to have unlimited stapm. Use max stapm_limit and stapm_time (usually 500) to trigger as less resets as possible +$resetSTAPMUsage = $false + +function doAdjust_ACmode { + $Script:repeatWaitTimeSeconds = 1 #only use values below 5s if you are using $monitorField + adjust "fast_limit" 46000 + adjust "slow_limit" 25000 + #adjust "slow_time" 30 + #adjust "tctl_temp" 93 + #adjust "apu_skin_temp_limit" 50 + #adjust "vrmmax_current" 100000 + #replace any_other_field with additional adjustments. Name is equal to RyzenAdj options but it uses _ instead of - + #adjust "any_other_field" 1234 + + #custom code, for example set fan control back to auto + #values (WriteRegister: 47, FanSpeedResetValue:128) extracted from similar devices at https://github.com/hirschmann/nbfc/blob/master/Configs/ + #Start-Process -NoNewWindow -Wait -filePath "C:\Program Files (x86)\NoteBook FanControl\ec-probe.exe" -ArgumentList("write", "47", "128") + + if($Script:acSlider -eq $Script:betterBattery){ + #put adjustments for energie saving slider position here: + enable "power_saving" #add 10s boost delay for usage on cable to reduce idle power consumption + } +} + +function doAdjust_BatteryMode { + $Script:repeatWaitTimeSeconds = 10 #do less reapplies and less HWiNFO updates to save power + adjust "fast_limit" 26000 + adjust "slow_limit" 10000 + #adjust "any_other_field" 1234 + + if($Script:dcSlider -eq $Script:betterBattery){ + #put adjustments for energie saving slider position here: for example disable fan to save power + #Start-Process -NoNewWindow -Wait -filePath "C:\Program Files (x86)\NoteBook FanControl\ec-probe.exe" -ArgumentList("write", "47", "0") + } + + if($Script:dcSlider -eq $Script:bestPerformance){ + #put adjustments for highest performance slider position here: + enable "max_performance" #removes 10s boost delay on battery + doAdjust_ACmode #set limits from cable mode on battery + } +} +################################################################################ +#### Configuration End +################################################################################ + +$env:PATH += ";$pathToRyzenAdjDlls" +$NL = $([System.Environment]::NewLine); + +if($noGUI){ $showErrorPopupsDuringInit = $false } + +$apiHeader = @' +[DllImport("libryzenadj.dll")] public static extern IntPtr init_ryzenadj(); +[DllImport("libryzenadj.dll")] public static extern int set_stapm_limit(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_fast_limit(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_slow_limit(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_slow_time(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_stapm_time(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_tctl_temp(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_vrm_current(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_vrmsoc_current(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_vrmmax_current(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_vrmsocmax_current(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_psi0_current(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_psi0soc_current(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_max_gfxclk_freq(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_min_gfxclk_freq(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_max_socclk_freq(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_min_socclk_freq(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_max_fclk_freq(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_min_fclk_freq(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_max_vcn(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_min_vcn(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_max_lclk(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_min_lclk(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_prochot_deassertion_ramp(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_apu_skin_temp_limit(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_dgpu_skin_temp_limit(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_apu_slow_limit(IntPtr ry, [In]uint value); +[DllImport("libryzenadj.dll")] public static extern int set_power_saving(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern int set_max_performance(IntPtr ry); + +[DllImport("libryzenadj.dll")] public static extern int refresh_table(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern IntPtr get_table_values(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_stapm_limit(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_stapm_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_stapm_time(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_fast_limit(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_fast_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_slow_limit(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_slow_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_apu_slow_limit(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_apu_slow_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_vrm_current(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_vrm_current_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_vrmsoc_current(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_vrmsoc_current_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_vrmmax_current(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_vrmmax_current_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_vrmsocmax_current(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_vrmsocmax_current_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_tctl_temp(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_tctl_temp_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_apu_skin_temp_limit(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_apu_skin_temp_value(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_dgpu_skin_temp_limit(IntPtr ry); +[DllImport("libryzenadj.dll")] public static extern float get_dgpu_skin_temp_value(IntPtr ry); + +[DllImport("kernel32.dll")] public static extern uint GetModuleFileName(IntPtr hModule, [Out]StringBuilder lpFilename, [In]int nSize); +[DllImport("kernel32.dll")] public static extern Boolean GetSystemPowerStatus(out SystemPowerStatus sps); +public struct SystemPowerStatus { + public Byte ACLineStatus; + public Byte BatteryFlag; + public Byte BatteryLifePercent; + public Byte Reserved1; + public Int32 BatteryLifeTime; + public Int32 BatteryFullLifeTime; +} + +public static String getExpectedWinRing0DriverFilepath(){ + StringBuilder fileName = new StringBuilder(255); + GetModuleFileName(IntPtr.Zero, fileName, fileName.Capacity); + return Path.GetDirectoryName(fileName.ToString()) + "\\WinRing0x64.sys"; +} + +public static String getDllImportErrors(){ + try { + Marshal.PrelinkAll(typeof(adj)); + } catch (Exception e) { + return e.Message; + } + return ""; +} +'@ + +if(-not ([System.Management.Automation.PSTypeName]'ryzen.adj').Type){ + Add-Type -MemberDefinition $apiHeader -Namespace 'ryzen' -Name 'adj' -UsingNamespace ('System.Text', 'System.IO') +} + +Add-Type -AssemblyName System.Windows.Forms +function showErrorMsg ([String] $msg){ + if($showErrorPopupsDuringInit){ + [void][System.Windows.Forms.MessageBox]::Show($msg, $PSCommandPath, + [System.Windows.Forms.MessageBoxButtons]::OK, + [System.Windows.Forms.MessageBoxIcon]::Error) + } +} + +$dllImportErrors = [ryzen.adj]::getDllImportErrors(); +if($dllImportErrors -or $Error){ + Write-Error $dllImportErrors + showErrorMsg "Problem with using libryzenadj.dll$NL$NL$($Error -join $NL)" + exit 1 +} + +$winring0DriverFilepath = [ryzen.adj]::getExpectedWinRing0DriverFilepath() +if(!(Test-Path $winring0DriverFilepath)) { Copy-Item -Path $pathToRyzenAdjDlls\WinRing0x64.sys -Destination $winring0DriverFilepath } + +$ry = [ryzen.adj]::init_ryzenadj() +if($ry -eq 0){ + $msg = "RyzenAdj could not get initialized.$($NL)Reason can be found inside Powershell$($NL)" + if($psISE) { $msg += "It is not possible to see the error reason inside ISE, you need to test it in PowerShell Console" } + showErrorMsg "$msg$NL$NL$($Error -join $NL)" + exit 1 +} + +function adjust ([String] $fieldName, [uInt32] $value) { + if($fieldName -eq $Script:monitorField) { + # Adjust target value to the unit of measurement of return value of RyzenAdj + $multiplier = if($value -gt 2000) { 0.001 } else { 1 } + $newTargetValue = [math]::round($value * $multiplier, 3, 0) + if($Script:monitorFieldAdjTarget -ne $newTargetValue){ + $Script:monitorFieldAdjTarget = $newTargetValue + Write-Host "set new monitoring target $fieldName to $newTargetValue" + } + } + $res = Invoke-Expression "[ryzen.adj]::set_$fieldName($ry, $value)" + switch ($res) { + 0 { + if($debugMode) { Write-Host "set $fieldName to $value" } + return + } + -1 { Write-Error "set_$fieldName is not supported on this family"} + -3 { Write-Error "set_$fieldName is not supported on this SMU"} + -4 { Write-Error "set_$fieldName is rejected by SMU"} + default { Write-Error "set_$fieldName did fail with $res"} + } +} + +function enable ([String] $fieldName) { + $res = Invoke-Expression "[ryzen.adj]::set_$fieldName($ry)" + switch ($res) { + 0 { + if($debugMode) { Write-Host "enable $fieldName"} + return + } + -1 { Write-Error "set_$fieldName is not supported on this family"} + -3 { Write-Error "set_$fieldName is not supported on this SMU"} + -4 { Write-Error "set_$fieldName is rejected by SMU"} + default { Write-Error "set_$fieldName did fail with $res"} + } +} + +function testMonitorField { + if($monitorField -and $Script:monitorFieldAdjTarget -eq 0){ + Write-Error ("You forgot to set $monitorField in your profile.$NL$NL" + + "If you ignore it, the script will apply values unnecessary often.$NL") + } +} + +function updateMonitorFieldAdjResult { + if($monitorField){ + [void][ryzen.adj]::refresh_table($ry) + $Script:monitorFieldAdjResult = [math]::round((getMonitorValue), 3, 0) + if($Script:monitorFieldAdjTarget -ne $Script:monitorFieldAdjResult){ + Write-Host ("Warning - $monitorField adjust result $Script:monitorFieldAdjResult does not match target value $Script:monitorFieldAdjTarget. Value $Script:monitorFieldAdjResult will be used for monitoring") + } + } +} + +function testConfiguration { + Write-Host "Test Adjustments" + if($Script:systemPowerStatus.ACLineStatus){ + doAdjust_BatteryMode + testMonitorField + $Script:monitorFieldAdjTarget = 0 + doAdjust_ACmode + } else { + doAdjust_ACmode + testMonitorField + $Script:monitorFieldAdjTarget = 0 + doAdjust_BatteryMode + } + testMonitorField + updateMonitorFieldAdjResult + + if($resetSTAPMUsage -and [ryzen.adj]::get_stapm_time($ry) -eq 1) { + Write-Error "resetSTAPMUsage function does only work on devices with active STAPM control and don't do anything on devices with enabled STT control." + } + + if($Error -and $showErrorPopupsDuringInit){ + $answer = [System.Windows.Forms.MessageBox]::Show("Your Adjustment configuration did not work.$NL$NL$($Error -join $NL)", $PSCommandPath, + [System.Windows.Forms.MessageBoxButtons]::AbortRetryIgnore, + [System.Windows.Forms.MessageBoxIcon]::Warning) + $Error.Clear() + if($answer -eq "Abort"){ exit 1 } + if($answer -eq "Retry"){ testConfiguration } + } +} + +function getMonitorValue { + if($monitorField){ + return Invoke-Expression "[ryzen.adj]::get_$monitorField($ry)" + } + return 0 +} + +function createOrDeleteHWINFOSensors { + if($updateHWINFOSensors){ + New-Item -Path HKCU:\Software\HWiNFO64\Sensors\Custom -Name RyzenAdj -Force > $null + 'Key,Name,Value + Power0,STAPM Limit + Power1,STAPM + Power2,PPT FAST Limit + Power3,PPT FAST + Power4,PPT SLOW Limit + Power5,PPT SLOW + Power6,APU SLOW Limit + Power7,APU SLOW + Current0,TDC VRM Limit + Current1,TDC VRM + Current2,TDC VRM SoC Limit + Current3,TDC VRM SoC + Current4,EDC VRM Max Limit + Current5,EDC VRM Max + Current6,EDC VRM SoC Max Limit + Current7,EDC VRM SoC Max + Temp0,TCTL Temp Limit + Temp1,TCTL Temp + Temp2,SST APU Skin Temp Limit + Temp3,SST APU Skin Temp + Temp4,SST dGPU Skin Temp Limit + Temp5,SST dGPU Skin Temp + Usage0,STAPM Limit Usage, ("STAPM" / "STAPM Limit") * 100 + Usage1,PPT FAST Limit Usage, ("PPT FAST" / "PPT FAST Limit") * 100 + Usage2,PPT SLOW Limit Usage, ("PPT SLOW" / "PPT SLOW Limit") * 100 + Usage3,APU SLOW Limit Usage, ("APU SLOW" / "APU SLOW Limit") * 100 + Usage4,TDC VRM Limit Usage, ("TDC VRM" / "TDC VRM Limit") * 100 + Usage5,TDC VRM SoC Limit Usage, ("TDC VRM SoC" / "TDC VRM SoC Limit") * 100 + Usage6,EDC VRM Max Limit Usage, ("EDC VRM Max" / "EDC VRM Max Limit") * 100 + Usage7,EDC VRM SoC Max Limit Usage, ("EDC VRM SoC Max" / "EDC VRM SoC Max Limit") * 100 + Usage8,TCTL Temp Limit Usage, ("TCTL Temp" / "TCTL Temp Limit") * 100 + Usage9,SST APU Skin Temp Limit Usage, ("SST APU Skin Temp" / "SST APU Skin Temp Limit") * 100 + Usage10,SST dGPU Skin Temp Limit Usage, ("SST dGPU Skin Temp" / "SST dGPU Skin Temp Limit") * 100' | ConvertFrom-Csv -outvariable hwinfo_keys > $null + $hwinfo_keys | ForEach-Object {New-item -Path HKCU:\Software\HWiNFO64\Sensors\Custom\RyzenAdj -Name $_.Key -Force} > $null + $hwinfo_keys | ForEach-Object {[Microsoft.Win32.Registry]::SetValue("HKEY_CURRENT_USER\Software\HWiNFO64\Sensors\Custom\RyzenAdj\" + $_.Key,"Name",$_.Name)} > $null + $hwinfo_keys | Where Value -ne $null | ForEach-Object {[Microsoft.Win32.Registry]::SetValue("HKEY_CURRENT_USER\Software\HWiNFO64\Sensors\Custom\RyzenAdj\" + $_.Key,"Value",$_.Value)} > $null + } else { + Remove-Item HKCU:\Software\HWiNFO64\Sensors\Custom\RyzenAdj -Recurse -ErrorAction:Ignore + } +} + +function setHWINFOValue ([String] $name, [float] $value) { + if(![float]::IsNaN($value)){ [Microsoft.Win32.Registry]::SetValue("HKEY_CURRENT_USER\Software\HWiNFO64\Sensors\Custom\RyzenAdj\" + $name,"Value",[String]$value) } +} + +function updateHWINFOSensors { + setHWINFOValue Power0 ([ryzen.adj]::get_stapm_limit($ry)) + setHWINFOValue Power1 ([ryzen.adj]::get_stapm_value($ry)) + setHWINFOValue Power2 ([ryzen.adj]::get_fast_limit($ry)) + setHWINFOValue Power3 ([ryzen.adj]::get_fast_value($ry)) + setHWINFOValue Power4 ([ryzen.adj]::get_slow_limit($ry)) + setHWINFOValue Power5 ([ryzen.adj]::get_slow_value($ry)) + setHWINFOValue Power6 ([ryzen.adj]::get_apu_slow_limit($ry)) + setHWINFOValue Power7 ([ryzen.adj]::get_apu_slow_value($ry)) + setHWINFOValue Current0 ([ryzen.adj]::get_vrm_current($ry)) + setHWINFOValue Current1 ([ryzen.adj]::get_vrm_current_value($ry)) + setHWINFOValue Current2 ([ryzen.adj]::get_vrmsoc_current($ry)) + setHWINFOValue Current3 ([ryzen.adj]::get_vrmsoc_current_value($ry)) + setHWINFOValue Current4 ([ryzen.adj]::get_vrmmax_current($ry)) + setHWINFOValue Current5 ([ryzen.adj]::get_vrmmax_current_value($ry)) + setHWINFOValue Current6 ([ryzen.adj]::get_vrmsocmax_current($ry)) + setHWINFOValue Current7 ([ryzen.adj]::get_vrmsocmax_current_value($ry)) + setHWINFOValue Temp0 ([ryzen.adj]::get_tctl_temp($ry)) + setHWINFOValue Temp1 ([ryzen.adj]::get_tctl_temp_value($ry)) + setHWINFOValue Temp2 ([ryzen.adj]::get_apu_skin_temp_limit($ry)) + setHWINFOValue Temp3 ([ryzen.adj]::get_apu_skin_temp_value($ry)) + setHWINFOValue Temp4 ([ryzen.adj]::get_dgpu_skin_temp_limit($ry)) + setHWINFOValue Temp5 ([ryzen.adj]::get_dgpu_skin_temp_value($ry)) + #setHWINFOValue Usage11 $pmTable[546] +} + +function resetSTAPMIfNeeded { + $stapm_limit = [ryzen.adj]::get_stapm_limit($ry) + $stapm_value = [ryzen.adj]::get_stapm_value($ry) + $stapm_hysteresis = 1 #Throttling starts around ~0.9W before limit + + if ($stapm_value -gt ($stapm_limit - $stapm_hysteresis)) { + $stapm_time = [ryzen.adj]::get_stapm_time($ry) + $reduced_stapm_limit = ($stapm_limit - 5) #reduce stapm by 5W + Write-Host "[STAPM_RESET] stapm_value ($stapm_value) nearing stapm_limit ($stapm_limit), resetting..." + [void][ryzen.adj]::set_stapm_limit($ry, ($reduced_stapm_limit) * 1000) + [void][ryzen.adj]::set_stapm_time($ry, 0) + [Threading.Thread]::Sleep(10) #10ms is usually enough time + [void][ryzen.adj]::set_stapm_time($ry, $stapm_time) + [void][ryzen.adj]::set_stapm_limit($ry, $stapm_limit * 1250) # add 25% STAPM limit in case we are at battery saving mode where applied values get reduced by 10% or 20% + } +} + +if(-not $Script:repeatWaitTimeSeconds) { $Script:repeatWaitTimeSeconds = 5 } +$Script:monitorFieldAdjResult = 0; #adjust result will be used for monitoring because SMU may only set 90% and 80% of your value +$Script:monitorFieldAdjTarget = 0; +$powerkey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SYSTEM\ControlSet001\Control\Power\User\PowerSchemes\") +$Script:betterBattery = "961cc777-2547-4f9d-8174-7d86181b8a7a" +$Script:betterPerformance = "00000000-0000-0000-0000-000000000000" +$Script:bestPerformance = "ded574b5-45a0-4f42-8737-46345c09c238" +$Script:acSlider = $powerkey.GetValue("ActiveOverlayACPowerScheme") +$Script:dcSlider = $powerkey.GetValue("ActiveOverlayDCPowerScheme") + +$systemPowerStatus = New-Object ryzen.adj+SystemPowerStatus +[void][ryzen.adj]::GetSystemPowerStatus([ref]$systemPowerStatus) +$Script:acLineStatus = $systemPowerStatus.ACLineStatus + +testConfiguration + +<# Example how to get 560 lines of ptable +$pmTable = [float[]]::new(560) +$tablePtr = [ryzen.adj]::get_table_values($ry); +[System.Runtime.InteropServices.Marshal]::Copy($tablePtr, $pmTable, 0, 560); +#> +createOrDeleteHWINFOSensors + +$mtxtArray = @() +if($monitorField){$mtxtArray += "$monitorField changes"} +if($monitorPowerSlider){$mtxtArray += "PowerSlider changes"} +if($monitorACLineStatus){$mtxtArray += "ACLineStatus changes"} +if($mtxtArray.Length){ + $processType = "Monitor " + ($mtxtArray -join " and ") +} else { + $processType = "Apply Settings" +} +Write-Host "$processType every $Script:repeatWaitTimeSeconds seconds..." +while($true) { + $doAdjust = !$monitorField -and !$monitorPowerSlider -and !$monitorACLineStatus + if($monitorField -or $updateHWINFOSensors -or $resetSTAPMUsage) { + [void][ryzen.adj]::refresh_table($ry) + #[System.Runtime.InteropServices.Marshal]::Copy($tablePtr, $pmTable, 0, 560); + } + + if($updateHWINFOSensors){ + updateHWINFOSensors + } + + if($monitorPowerSlider -and ($Script:acSlider -ne $powerkey.GetValue("ActiveOverlayACPowerScheme") -or $Script:dcSlider -ne $powerkey.GetValue("ActiveOverlayDCPowerScheme"))){ + Write-Host "Power Slider changed" + $Script:acSlider = $powerkey.GetValue("ActiveOverlayACPowerScheme") + $Script:dcSlider = $powerkey.GetValue("ActiveOverlayDCPowerScheme") + $doAdjust = $true + } + if($monitorField){ + $monitorValue = getMonitorValue + if($Script:monitorFieldAdjResult -ne [math]::round($monitorValue, 3, 0)){ + Write-Host "$monitorField value unexpectedly changed from $Script:monitorFieldAdjResult to $monitorValue" + $doAdjust = $true + } + } + if($monitorACLineStatus){ + [void][ryzen.adj]::GetSystemPowerStatus([ref]$systemPowerStatus) + if($Script:acLineStatus -ne $systemPowerStatus.ACLineStatus){ + Write-Host "AC Line Status changed" + $Script:acLineStatus = $systemPowerStatus.ACLineStatus + $doAdjust = $true + } + } + + if($resetSTAPMUsage){ + resetSTAPMIfNeeded + } + + if($doAdjust){ + [void][ryzen.adj]::GetSystemPowerStatus([ref]$systemPowerStatus) + $oldWait = $Script:repeatWaitTimeSeconds + if($systemPowerStatus.ACLineStatus){ + doAdjust_ACmode + } else { + doAdjust_BatteryMode + } + updateMonitorFieldAdjResult + if($oldWait -ne $Script:repeatWaitTimeSeconds ) { Write-Host "$processType every $Script:repeatWaitTimeSeconds seconds..." } + } + + sleep $Script:repeatWaitTimeSeconds +} diff --git a/os/licenses/ryzenadj/src/win32/uninstallServiceTask.bat b/os/licenses/ryzenadj/src/win32/uninstallServiceTask.bat new file mode 100644 index 00000000..83045b65 --- /dev/null +++ b/os/licenses/ryzenadj/src/win32/uninstallServiceTask.bat @@ -0,0 +1,23 @@ +@echo off +NET FILE 1>NUL 2>NUL +if %errorlevel% NEQ 0 ( + echo Deinstallation need to be run as Administrator to delete your Scheduled Task + pause + exit /B 0 +) + +reg query HKCU\Software\HWiNFO64\Sensors\Custom\RyzenAdj 2>NUL +if %errorlevel% EQU 0 reg delete HKCU\Software\HWiNFO64\Sensors\Custom\RyzenAdj + +SCHTASKS /query /TN "AMD\RyzenAdj" 2>NUL + +if %errorlevel% NEQ 0 ( + echo RyzenAdj Service Task is not installed + pause + exit /B 0 +) + +SCHTASKS /delete /TN "AMD\RyzenAdj" 2>NUL + +pause +exit /B 0 diff --git a/os/mini/board.sh b/os/mini/board.sh index 142415c0..01a01cdf 100755 --- a/os/mini/board.sh +++ b/os/mini/board.sh @@ -50,6 +50,7 @@ TCTL_C=${TCTL_C:-95} FCLK=${FCLK:-7} # amdgpu pp_dpm_fclk index, "auto" to leave it alone SOCCLK=${SOCCLK:-6} # amdgpu pp_dpm_socclk index SKIP_POWER=${SKIP_POWER:-0} +FALLBACK_MODE=${FALLBACK_MODE:-balanced} # EC mode when the SMU limits cannot be written RYZENADJ=${RYZENADJ:-} say() { printf '%s\n' "$*"; } @@ -168,9 +169,10 @@ power_conf() { # read KEY=value overrides from $POWER_CONF without executing i eval "CONF_$key=\$val" ;; esac done < "$POWER_CONF" - for k in POWER_MODE STAPM_W SLOW_W FAST_W TCTL_C FCLK SOCCLK SKIP_POWER RYZENADJ; do + for k in POWER_MODE STAPM_W SLOW_W FAST_W TCTL_C FCLK SOCCLK SKIP_POWER RYZENADJ FALLBACK_MODE; do eval "v=\${CONF_$k:-}"; [ -n "$v" ] && eval "$k=\$v" done + v=${CONF_FAN_HOT5_C:-}; [ -n "$v" ] && BOOST_HOT5_C=$v return 0 } @@ -186,17 +188,28 @@ power() { else notes="$notes mode=no-ec" fi + cap_ok=0 if [ -n "$RYZENADJ" ] && [ ! -x "$RYZENADJ" ]; then notes="$notes cap=failed(RYZENADJ=$RYZENADJ is not executable)" elif RJ=$(ryzenadj_bin); then if "$RJ" --stapm-limit=$(( STAPM_W * 1000 )) --slow-limit=$(( SLOW_W * 1000 )) \ --fast-limit=$(( FAST_W * 1000 )) --tctl-temp="$TCTL_C" >/dev/null 2>&1; then - notes="$notes cap=${STAPM_W}/${SLOW_W}/${FAST_W}W tctl=$TCTL_C" + notes="$notes cap=${STAPM_W}/${SLOW_W}/${FAST_W}W tctl=$TCTL_C"; cap_ok=1 else notes="$notes cap=failed($RJ)" fi else - notes="$notes cap=not-applied(no ryzenadj; the EC profile alone is in force)" + notes="$notes cap=not-applied(no ryzenadj)" + fi + if [ "$cap_ok" = 0 ]; then + # Secure Boot lockdown, no /dev/mem access or no tool: the SMU limits cannot be written, so fall back + # to the EC's own balanced profile (85 W sustained, 120 W burst). Never leave the board in performance + # mode (its ~120 W profile) with no cap at all. + if printf '%s\n' "$FALLBACK_MODE" > "$EC/apu/power_mode" 2>/dev/null; then + notes="$notes fallback=$FALLBACK_MODE (the EC profile alone is in force)" + else + notes="$notes fallback=unavailable" + fi fi G=$(ls -d /sys/class/drm/card*/device 2>/dev/null | head -n 1) if [ -n "$G" ] && [ -w "$G/power_dpm_force_performance_level" ]; then @@ -215,10 +228,10 @@ power() { power_status() { mode=$(cat "$EC/apu/power_mode" 2>/dev/null || echo none) if RJ=$(ryzenadj_bin) && info=$("$RJ" --info 2>/dev/null); then - lim=$(printf '%s\n' "$info" | sed -n 's/^STAPM LIMIT *| *\([0-9.]*\).*/\1/p' | head -n 1) - say "power: $mode mode, STAPM ${lim:-?} W" + lim=$(printf '%s\n' "$info" | sed -n 's/^STAPM LIMIT *| *\([0-9.]*\)\..*/\1/p' | head -n 1) + say "power: $mode mode, SMU cap ${lim:-?} W sustained" else - say "power: $mode mode, no SMU limits readable (ryzenadj absent or not permitted: the EC profile alone is in force)" + say "power: $mode mode, EC profile only (no SMU cap: ryzenadj absent or /dev/mem not writable)" fi } diff --git a/os/mini/mkimage.sh b/os/mini/mkimage.sh index 84453180..4e7e2fa6 100755 --- a/os/mini/mkimage.sh +++ b/os/mini/mkimage.sh @@ -140,6 +140,21 @@ else mkdir -p "$root/lib/modules/$kver/updates" cp "$ecbuild/ec_su_axb35.ko" "$root/lib/modules/$kver/updates/" echo " ec_su_axb35.ko for $kver: $(stat -c %s "$root/lib/modules/$kver/updates/ec_su_axb35.ko") bytes" + # Secure Boot: an out-of-tree module has to be signed by a key the kernel trusts, i.e. a MOK that is + # enrolled. It must be the MOK - the "Secure Boot Kernel" cert carries no module-signing OID, so it + # can sign an EFI binary but not a module (verified on the development box: amdxdna.ko and our own + # ec_su_axb35.ko are both signed by "strixhalo Secure Boot MOK"). + kdir=/lib/modules/"$kver"/build + modkey=${MODULE_SIGN_KEY:-/var/lib/shim-signed/mok/MOK.priv} + modcrt=${MODULE_SIGN_CERT:-/var/lib/shim-signed/mok/MOK.der} + if [ -x "$kdir/scripts/sign-file" ] && [ -r "$modkey" ] && [ -r "$modcrt" ]; then + "$kdir/scripts/sign-file" sha256 "$modkey" "$modcrt" "$root/lib/modules/$kver/updates/ec_su_axb35.ko" \ + && echo " signed with $(basename "$modcrt") (the module-signing MOK)" + else + echo " WARNING: ec_su_axb35.ko left UNSIGNED (need $modkey, $modcrt and $kdir/scripts/sign-file)" + echo " it loads where module signature checking is off, which is the case for our kernel:" + echo " CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT is not set, so lockdown stays 'none' even with SB on" + fi else echo " build FAILED (see $out/logs/ec_su_axb35-build.log); set EC_MODULE=skip to ship without it"; exit 1 fi @@ -286,19 +301,25 @@ mkdir -p "$root/etc/1bit" install -m 0644 "$here/power.conf.example" "$root/etc/1bit/power" # The SMU limits (the 90 W sustained cap) are set through ryzenadj, which is LGPL-3.0: ship the tool and # its licence. A builder without it still works - the EC power mode alone applies and the image says so. -RJ=${RYZENADJ:-$(command -v ryzenadj 2>/dev/null || echo /usr/local/bin/ryzenadj)} -if [ -x "$RJ" ]; then +echo "== SMU tool (ryzenadj v0.19.0, LGPL-3.0, source pinned in os/licenses/ryzenadj)" +# Deterministic: built from the source this repository ships, never from whatever the build machine has. +rjsrc=$licenses/ryzenadj/src +if [ ! -d "$rjsrc" ]; then echo " os/licenses/ryzenadj/src is missing: the SMU cap could not be applied"; exit 1; fi +rjbuild=$(mktemp -d) +if cp -a "$rjsrc"/. "$rjbuild"/ && ( cd "$rjbuild" \ + && cmake -B build -DCMAKE_BUILD_TYPE=Release . > "$out/logs/ryzenadj-build.log" 2>&1 \ + && cmake --build build -j4 >> "$out/logs/ryzenadj-build.log" 2>&1 ); then mkdir -p "$root/opt/1bit" - cp -L "$RJ" "$root/opt/1bit/ryzenadj"; chmod 0755 "$root/opt/1bit/ryzenadj" - for lib in $(ldd "$RJ" 2>/dev/null | awk '/=> \//{print $3}'); do + cp "$rjbuild/build/ryzenadj" "$root/opt/1bit/ryzenadj"; chmod 0755 "$root/opt/1bit/ryzenadj" + for lib in $(ldd "$root/opt/1bit/ryzenadj" 2>/dev/null | awk '/=> \//{print $3}'); do case "$lib" in */libc.so*|*/libm.so*|*/ld-linux*) continue ;; esac if [ ! -e "$root$lib" ]; then mkdir -p "$root$(dirname "$lib")"; cp -L "$lib" "$root$lib"; fi done - cp /usr/share/common-licenses/LGPL-3 "$doc/ryzenadj-LGPL-3" 2>/dev/null || true - echo " ryzenadj shipped as /opt/1bit/ryzenadj (LGPL-3.0, licence in /usr/share/licenses/1bit-os)" + echo " /opt/1bit/ryzenadj $(stat -c %s "$root/opt/1bit/ryzenadj") bytes ($(ldd "$root/opt/1bit/ryzenadj" 2>/dev/null | wc -l) libs linked)" else - echo " no ryzenadj on the builder: the image applies the EC power mode only (90 W cap not applied)" + echo " build FAILED (see $out/logs/ryzenadj-build.log); needs cmake, a C compiler and libpci-dev"; exit 1 fi +rm -rf "$rjbuild" cp "$here/udhcpc.sh" "$root/etc/udhcpc.script"; chmod +x "$root/etc/udhcpc.script" echo "1bit-os" > "$root/etc/hostname" printf 'root:x:0:0:root:/root:/bin/sh\n' > "$root/etc/passwd" @@ -318,6 +339,9 @@ cp "$licenses/llamacpp-hrx/ROCR-Runtime-LICENSE" "$doc/ROCR-Runtime-LICENSE" # mkdir -p "$doc/ec_su_axb35" cp -a "$licenses/ec_su_axb35/LICENSE" "$licenses/ec_su_axb35/SOURCE" "$doc/ec_su_axb35/" 2>/dev/null || true if [ -d "$licenses/ec_su_axb35/src" ]; then cp -a "$licenses/ec_su_axb35/src" "$doc/ec_su_axb35/src"; fi +mkdir -p "$doc/ryzenadj" +cp -a "$licenses/ryzenadj/LICENSE" "$licenses/ryzenadj/SOURCE" "$doc/ryzenadj/" 2>/dev/null || true +if [ -d "$licenses/ryzenadj/src" ]; then cp -a "$licenses/ryzenadj/src" "$doc/ryzenadj/src"; fi cp -rL /usr/share/common-licenses "$root/usr/share/" # the Ubuntu package behind each file in the image, found at its path or its merged-/usr alias (cd "$root" && find . -type f | cut -c2-) > "$out/image-files.txt" diff --git a/os/mini/power.conf.example b/os/mini/power.conf.example index 9a3d1e42..1469219f 100644 --- a/os/mini/power.conf.example +++ b/os/mini/power.conf.example @@ -1,22 +1,44 @@ -# /etc/1bit/power — power profile overrides for 1bit OS. +# Copyright 2026 bong-water-water-bong +# SPDX-License-Identifier: Apache-2.0 # -# At boot the image applies: the EC power mode, then the SMU limits (STAPM / slow / fast, plus the -# die temperature limit) through ryzenadj when the image carries it, then the fabric/SoC clock pins. -# Order matters: an EC power-mode write resets the SMU limits, so the mode goes first. +# 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 # -# Uncomment and edit any line; the file is read as KEY=value, nothing in it is executed. -# Defaults are the profile the 1bit development box runs (performance mode, 90 W sustained). +# 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. +# +# /etc/1bit/power — power profile and fan overrides for 1bit OS. +# +# The defaults below are the values in force with this file untouched: they are the profile the 1bit +# development box runs on the same board (performance mode, 90 W sustained), so a shipped appliance and the +# numbers on the leaderboard describe the same machine. Change a line to override it; the file is read as +# KEY=value and nothing in it is executed. +# +# Order at boot: the EC power mode first (a mode write resets the SMU limits), then the SMU limits, then the +# fabric/SoC clock pins. If the SMU limits cannot be written (no tool, or /dev/mem refused - see the Secure +# Boot note in TOSFANS-STATE), the EC falls back to FALLBACK_MODE and the boot banner says which cap is in force. # #POWER_MODE=performance # quiet | balanced | performance (the EC's own power mode) #STAPM_W=90 # sustained package power limit, watts #SLOW_W=90 # slow (average) limit, watts #FAST_W=105 # fast (burst) limit, watts #TCTL_C=95 # die temperature limit, degrees C -#FCLK=7 # amdgpu pp_dpm_fclk index, or auto to let the driver decide +#FALLBACK_MODE=balanced # EC mode used when the SMU limits cannot be written (85 W sustained, 120 W burst) +#FCLK=7 # amdgpu pp_dpm_fclk index, or auto to let the driver choose #SOCCLK=6 # amdgpu pp_dpm_socclk index #RYZENADJ=/opt/1bit/ryzenadj # the SMU tool; empty = search /opt/1bit, /usr/local/bin, /usr/bin #SKIP_POWER=1 # do not touch power at all (the firmware profile stays) # -# Fans (the curve the EC applies on its own sensor; levels 1..5 = 20/40/60/80/100 %): +# Fans: the EC applies this curve on its own sensor (levels 1..5 = 20/40/60/80/100 %). +# The boost raises the CPU fans to level 4 on load and back to the curve after 20 s quiet. #FAN_UP=45,54,62,70,76 #FAN_DOWN=41,50,58,66,72 +#FAN_HOT5_C=82 # Tctl that sends the boost to level 5 (100 %) and holds it down to 76 C. + # FIRST THING TO REVISIT on a hotter unit: this trip, and the curve points above, + # were tuned on the development box's board with a phase-change pad. diff --git a/tools/copyright.py b/tools/copyright.py index 5d24c56a..63646e0e 100644 --- a/tools/copyright.py +++ b/tools/copyright.py @@ -32,7 +32,7 @@ LINE_STYLES = [ (("*.cpp", "*.h", "*.inc", "*.hpp", "*.c", "*.cc", "*.rs", "*.hip"), "//"), (("*.py", "*.sh", "*.yml", "*.yaml", "*.cff", "*.cmake", "*.cmake.in", "CMakeLists.txt", ".gitmodules", - ".gitignore", ".gitattributes", ".clang-format", "*.toml", "*.config", "*.fragment", + ".gitignore", ".gitattributes", ".clang-format", "*.toml", "*.config", "*.fragment", "*.example", "*.service", "*.timer", "*.conf", "lemonade-hrx", "Dockerfile", "*.Dockerfile"), "#"), ] BLOCK_STYLES = [(("*.md", "*.html", "*.svg"), ("")), (("*.css", "*.js"), ("/*", "*/"))] From f49e7fe13148ffc7602a0f20a319d9bb876928f1 Mon Sep 17 00:00:00 2001 From: bong-water-water-bong Date: Sat, 24 Oct 2026 09:03:13 -0300 Subject: [PATCH 3/3] os: build the board driver with the kernel's own toolchain and sign it through the MOK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first real image build found three things the PR could not have known without building: - the image kernel is clang-built (`CONFIG_CC_IS_CLANG`), so `gcc` rejects its flags (`-mretpoline-external-thunk`); the module now builds with `LLVM=1` and the matching `/usr/lib/llvm-/bin` on PATH, because `LLVM=1` also wants `ld.lld`, `llvm-ar` and friends - a bare `clang` is not enough (`ld.lld: not found`); - the MOK private key is 0600 root, so an unprivileged build cannot read it: the module is signed through `sudo -n` when needed, and always from the scratch copy, never from the file already inside the image tree (a root-written module there broke the later chmod/cpio); - `$out/logs` is created before the build logs are opened. Also documents that MODULE_SIGN_KEY/MODULE_SIGN_CERT are deliberately not MOK_KEY/MOK_CERT: a module needs the MOK that carries the module-signing OID, the EFI image needs the kernel-signing key. Built and verified: image 2.3 GB (EFI 97 MB, initramfs 86 MB), `ec_su_axb35.ko` 33,846 bytes signed by `MOK.der` ("strixhalo Secure Boot MOK"), ryzenadj v0.19.0 built from the pinned source (68,184 bytes, 6 libs), `1bit-board` and `/etc/1bit/power` in the initramfs, both licence trees with their source. `vmtest-release.sh` in QEMU: RESULT PASS (16 checks, no failures) for the image with the driver and for the `EC_MODULE=skip` image, with `SKIP fans : this board has no EC we drive` in both. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: bong-water-water-bong --- os/mini/mkimage.sh | 62 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/os/mini/mkimage.sh b/os/mini/mkimage.sh index 4e7e2fa6..902d98fc 100755 --- a/os/mini/mkimage.sh +++ b/os/mini/mkimage.sh @@ -128,28 +128,58 @@ echo "== board driver (ec_su_axb35, GPL-2.0, source shipped in os/licenses/ec_su # The fans and the EC power mode are driven by the board's embedded controller and the kernel has no # driver for it, so it is built here, out of tree, against the image kernel - from the source this # repository ships next to it (GPL-2.0: the binary in the image always travels with its source). -ecsrc=$licenses/ec_su_axb35/src +lic=$(cd "$here/.." && pwd)/licenses # self-contained: $licenses is defined further down +ecsrc=$lic/ec_su_axb35/src if [ "${EC_MODULE:-build}" = skip ]; then echo " skipped (EC_MODULE=skip): this image will leave the fans on the firmware curve" elif [ ! -d "$ecsrc" ]; then echo " os/licenses/ec_su_axb35/src is missing: the fans would stay on the firmware curve"; exit 1 else + mkdir -p "$out/logs" ecbuild=$(mktemp -d) cp -a "$ecsrc"/. "$ecbuild"/ - if make -s -C "$ecbuild" KERNEL_BUILD=/lib/modules/"$kver"/build modules > "$out/logs/ec_su_axb35-build.log" 2>&1; then + # the image kernel is clang-built (CONFIG_CC_IS_CLANG): an out-of-tree module has to use the same + # toolchain, or the compiler rejects the kernel's own flags (e.g. -mretpoline-external-thunk) + kmake="" + if grep -q "^CONFIG_CC_IS_CLANG=y" /boot/config-"$kver" 2>/dev/null; then + # LLVM=1 wants the whole LLVM toolchain (ld.lld, llvm-ar, llvm-nm, ...), so the matching + # /usr/lib/llvm-/bin goes on PATH - a bare `clang` is not enough + kv=$(sed -n 's/^CONFIG_CLANG_VERSION=\([0-9]*\)/\1/p' /boot/config-"$kver" | head -n 1) + major=$(( ${kv:-99} / 10000 )) + for d in "/usr/lib/llvm-$major/bin" "$HOME/.cache/xdna-km/llvm$major/bin" /usr/lib/llvm-18/bin; do + [ -x "$d/ld.lld" ] && [ -x "$d/clang" ] && { PATH="$d:$PATH"; export PATH; kmake="LLVM=1"; break; } + done + [ -n "$kmake" ] || echo " no LLVM toolchain (ld.lld) on this builder: the kernel is clang-built, the module will not build" + fi + if make -s -C "$ecbuild" KERNEL_BUILD=/lib/modules/"$kver"/build $kmake modules > "$out/logs/ec_su_axb35-build.log" 2>&1; then mkdir -p "$root/lib/modules/$kver/updates" - cp "$ecbuild/ec_su_axb35.ko" "$root/lib/modules/$kver/updates/" - echo " ec_su_axb35.ko for $kver: $(stat -c %s "$root/lib/modules/$kver/updates/ec_su_axb35.ko") bytes" # Secure Boot: an out-of-tree module has to be signed by a key the kernel trusts, i.e. a MOK that is # enrolled. It must be the MOK - the "Secure Boot Kernel" cert carries no module-signing OID, so it # can sign an EFI binary but not a module (verified on the development box: amdxdna.ko and our own # ec_su_axb35.ko are both signed by "strixhalo Secure Boot MOK"). + # note: MODULE_SIGN_KEY/MODULE_SIGN_CERT sign the module, and they are not MOK_KEY/MOK_CERT, which sign + # the EFI image further down: a module needs the MOK with the module-signing OID, the image needs the + # kernel-signing key. Both certificates are enrolled on the development box. kdir=/lib/modules/"$kver"/build modkey=${MODULE_SIGN_KEY:-/var/lib/shim-signed/mok/MOK.priv} modcrt=${MODULE_SIGN_CERT:-/var/lib/shim-signed/mok/MOK.der} - if [ -x "$kdir/scripts/sign-file" ] && [ -r "$modkey" ] && [ -r "$modcrt" ]; then - "$kdir/scripts/sign-file" sha256 "$modkey" "$modcrt" "$root/lib/modules/$kver/updates/ec_su_axb35.ko" \ - && echo " signed with $(basename "$modcrt") (the module-signing MOK)" + # sign the scratch copy, never the file inside the image: sign-file needs the root-only MOK key, so it + # may run through sudo, and a root-written file inside the image tree breaks the later cpio/chmod + ko=$ecbuild/ec_su_axb35.ko; kosign=$ecbuild/ec_su_axb35.signed.ko + signed=0 + if [ -x "$kdir/scripts/sign-file" ] && [ -r "$modcrt" ]; then + if [ -r "$modkey" ]; then + "$kdir/scripts/sign-file" sha256 "$modkey" "$modcrt" "$ko" "$kosign" && signed=1 + elif sudo -n true 2> /dev/null; then + sudo -n "$kdir/scripts/sign-file" sha256 "$modkey" "$modcrt" "$ko" "$kosign" && signed=1 + fi + fi + [ "$signed" = 1 ] && cp "$kosign" "$root/lib/modules/$kver/updates/ec_su_axb35.ko" \ + || cp "$ko" "$root/lib/modules/$kver/updates/ec_su_axb35.ko" + chmod 0644 "$root/lib/modules/$kver/updates/ec_su_axb35.ko" + echo " ec_su_axb35.ko for $kver: $(stat -c %s "$root/lib/modules/$kver/updates/ec_su_axb35.ko") bytes" + if [ "$signed" = 1 ]; then + echo " signed with $(basename "$modcrt") (the module-signing MOK)" else echo " WARNING: ec_su_axb35.ko left UNSIGNED (need $modkey, $modcrt and $kdir/scripts/sign-file)" echo " it loads where module signature checking is off, which is the case for our kernel:" @@ -301,10 +331,11 @@ mkdir -p "$root/etc/1bit" install -m 0644 "$here/power.conf.example" "$root/etc/1bit/power" # The SMU limits (the 90 W sustained cap) are set through ryzenadj, which is LGPL-3.0: ship the tool and # its licence. A builder without it still works - the EC power mode alone applies and the image says so. -echo "== SMU tool (ryzenadj v0.19.0, LGPL-3.0, source pinned in os/licenses/ryzenadj)" +echo "== SMU tool (ryzenadj v0.19.0, LGPL-3.0, source pinned in os/licenses/ryzenadj, userspace build)" # Deterministic: built from the source this repository ships, never from whatever the build machine has. -rjsrc=$licenses/ryzenadj/src +rjsrc=$lic/ryzenadj/src if [ ! -d "$rjsrc" ]; then echo " os/licenses/ryzenadj/src is missing: the SMU cap could not be applied"; exit 1; fi +mkdir -p "$out/logs" rjbuild=$(mktemp -d) if cp -a "$rjsrc"/. "$rjbuild"/ && ( cd "$rjbuild" \ && cmake -B build -DCMAKE_BUILD_TYPE=Release . > "$out/logs/ryzenadj-build.log" 2>&1 \ @@ -335,13 +366,12 @@ cp "$here/../LICENSES.md" "$src/LICENSE" "$src/NOTICE" "$src/LICENSE-BINARY" "$s cp "$src/third_party/lemonade/LICENSE" "$doc/lemonade-LICENSE" || { echo "no third_party/lemonade/LICENSE (git submodule update --init third_party/lemonade)"; exit 1; } cp "$licenses/llamacpp-hrx/ROCR-Runtime-LICENSE" "$doc/ROCR-Runtime-LICENSE" # TheRock's libhsa for HRX -# the board driver is GPL-2.0: its source and licence travel in the image, next to the binary -mkdir -p "$doc/ec_su_axb35" -cp -a "$licenses/ec_su_axb35/LICENSE" "$licenses/ec_su_axb35/SOURCE" "$doc/ec_su_axb35/" 2>/dev/null || true -if [ -d "$licenses/ec_su_axb35/src" ]; then cp -a "$licenses/ec_su_axb35/src" "$doc/ec_su_axb35/src"; fi -mkdir -p "$doc/ryzenadj" -cp -a "$licenses/ryzenadj/LICENSE" "$licenses/ryzenadj/SOURCE" "$doc/ryzenadj/" 2>/dev/null || true -if [ -d "$licenses/ryzenadj/src" ]; then cp -a "$licenses/ryzenadj/src" "$doc/ryzenadj/src"; fi +# the board driver is GPL-2.0 and the SMU tool LGPL-3.0: source and licence travel in the image with them +mkdir -p "$doc/ec_su_axb35" "$doc/ryzenadj" +cp -a "$lic/ec_su_axb35/LICENSE" "$lic/ec_su_axb35/SOURCE" "$doc/ec_su_axb35/" 2>/dev/null || true +if [ -d "$lic/ec_su_axb35/src" ]; then cp -a "$lic/ec_su_axb35/src" "$doc/ec_su_axb35/src"; fi +cp -a "$lic/ryzenadj/LICENSE" "$lic/ryzenadj/SOURCE" "$doc/ryzenadj/" 2>/dev/null || true +if [ -d "$lic/ryzenadj/src" ]; then cp -a "$lic/ryzenadj/src" "$doc/ryzenadj/src"; fi cp -rL /usr/share/common-licenses "$root/usr/share/" # the Ubuntu package behind each file in the image, found at its path or its merged-/usr alias (cd "$root" && find . -type f | cut -c2-) > "$out/image-files.txt"