pax_global_header00006660000000000000000000000064151756557450014536gustar00rootroot0000000000000052 comment=5558e46461fea9b7dac3b1a2f66babdfe08a7f30 TLP-1.10.1/000077500000000000000000000000001517565574500122555ustar00rootroot00000000000000TLP-1.10.1/.editorconfig000066400000000000000000000002541517565574500147330ustar00rootroot00000000000000# https://editorconfig.org/ root = true [*] indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true [Makefile] indent_style = tabTLP-1.10.1/.github/000077500000000000000000000000001517565574500136155ustar00rootroot00000000000000TLP-1.10.1/.github/Bug_Reporting_Howto.md000066400000000000000000000025561517565574500200750ustar00rootroot00000000000000## How to submit a bug report ### Before you report a bug Make sure you have: * Followed the appropiate [Installation instructions](https://linrunner.de/tlp/installation) * Read the [Settings guide](https://linrunner.de/tlp/settings) * Carefully checked the [FAQ](https://linrunner.de/tlp/faq) * Checked [existing bug reports](https://github.com/linrunner/TLP/issues) * Tried to isolate the cause as described in [Troubleshooting](https://linrunner.de/tlp/support/troubleshooting.html) ### What not to report * **Outdated, missing or broken TLP packages. These are *not* provided by the project, but by the Linux distributions (exception: the Ubuntu PPA).** * Asking for help about installation, configuration and usage * Questions about your laptop's power consumption and how to optimize it * Deviations from powertop's recommendations * Hardware issues e.g. worn out or malfunctioning batteries Please use adequate Linux forums for help and support questions. ### Reporting a bug This project uses [GitHub issues](https://github.com/linrunner/TLP/issues) for bug reports and feature requests. When opening an issue, provide **all the information requested by the [template](https://github.com/linrunner/TLP/blob/master/.github/ISSUE_TEMPLATE/bug_report.md)**. Bug reports not providing the necessary information get flagged *incomplete* and may be closed without further notice. TLP-1.10.1/.github/CODE_OF_CONDUCT.md000066400000000000000000000062331517565574500164200ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org TLP-1.10.1/.github/CONTRIBUTING.md000066400000000000000000000003461517565574500160510ustar00rootroot00000000000000## How can I contribute to TLP? Contributing is not only about coding and pull requests. Volunteers helping with support and testing are always welcome! Please read [Contribute](https://linrunner.de/tlp/contribute) for details. TLP-1.10.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001517565574500160005ustar00rootroot00000000000000TLP-1.10.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000017261517565574500205000ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- [x] I've read and accepted the [Bug Reporting Howto](https://github.com/linrunner/TLP/blob/master/.github/Bug_Reporting_Howto.md) [x] I've provided all required `tlp-stat` outputs via [Gist](https://gist.github.com/) (see below) **Describe the bug** A clear and concise description of what the bug is. **Expected behavior** A clear and concise description of what you expected to happen. *"Works fine" is not enough to analyze the problem!* **To Reproduce** Steps to reproduce the unexpected behavior: 1. Does the problem occur on battery or AC or both? 2. Actions to reproduce the behaviour 3. Shell commands entered and their output *including error message(s)* 4. **Full output of `tlp-stat` via https://gist.github.com/ for *all* matching cases of 1** (not as file attachment, no screenshots) **Additional context** Add any other context about the problem here. TLP-1.10.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000013361517565574500215300ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: feature request assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I repeatedly ran into [...] **Describe the solution you'd like** A clear and concise description of: * Your use case(s) * What you want to happen **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context and references about the feature request here: * New kernel interfaces: please provide links to the description and usage examples * Sample shell code TLP-1.10.1/.gitignore000066400000000000000000000002141517565574500142420ustar00rootroot00000000000000conf.py debian *.geany .idea *.log *.patch PKGBUILD* pkg __pycache__/ *.py[cod] research* .ropeproject src tlp.install worktree* .zed *.zst TLP-1.10.1/.perlcriticrc000066400000000000000000000002331517565574500147410ustar00rootroot00000000000000[ValuesAndExpressions::ProhibitConstantPragma] severity = 1 [InputOutput::RequireBriefOpen] severity = 1 [Subroutines::RequireArgUnpacking] severity = 1 TLP-1.10.1/.shellcheckrc000066400000000000000000000000661517565574500147120ustar00rootroot00000000000000# shellcheck config for TLP shell=dash disable=SC3043 TLP-1.10.1/00-template.conf000066400000000000000000000002461517565574500151560ustar00rootroot00000000000000# 00-template.conf - Template for TLP drop-in customizations # See full explanation: https://linrunner.de/tlp/settings # # PARAMETER="value" # PARAMETER+="add value" TLP-1.10.1/AUTHORS000066400000000000000000000003041517565574500133220ustar00rootroot00000000000000Main author: Thomas Koch - Contributors: André Erdmann Pali Rohár https://github.com/linrunner/TLP/graphs/contributors TLP-1.10.1/COPYING000066400000000000000000000022201517565574500133040ustar00rootroot00000000000000Main Author: Thomas Koch Copyright: Copyright (c) 2026 Thomas Koch, André Erdmann, Pali Rohár See https://github.com/linrunner/TLP/ for additional contributors tlpctl is an adaptation of powerprofilesctl, which is part of power-profiles-daemon, written by Bastien Nociera and Mario Limonciello Copyright (c) 2020 Red Hat, Inc. (c) 2024 Advanced Micro Devices, Inc (c) 2024 Canonical Ltd - https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/blob/main/src/powerprofilesctl?ref_type=heads - https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/blob/main/docs/power-profiles-daemon-docs.xml?ref_type=heads Some code and descriptions were adapted from: - laptop-mode-tools Copyright (c) 2004 by Bart Samwel, Kiko Piris, Micha Feigin, Andrew Morton, Herve Eychenne, Dax Kelson, Jan Topinski - https://thinkwiki.org License: This software is licensed under the GPL v2 or later, see https://spdx.org/licenses/GPL-2.0-or-later.html tlpctl is licensed under the GPL v3, see https://spdx.org/licenses/GPL-3.0.html TLP-1.10.1/LICENSE000066400000000000000000000431001517565574500132600ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 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, see . 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 Moe Ghoul, 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. TLP-1.10.1/Makefile000066400000000000000000000306101517565574500137150ustar00rootroot00000000000000# Makefile for TLP # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later TLPVER := $(shell read _ver _dummy < ./VERSION; printf '%s' "$${_ver:-undef}") # Append Git commit ID to TLPVER for alpha and beta versions ifneq (,$(shell which git 2> /dev/null)) ifneq (,$(shell echo "$(TLPVER)" | grep -E 'alpha|beta')) COMMIT_ID := $(shell git rev-parse --short HEAD 2> /dev/null) ifneq (,$(COMMIT_ID)) TLPVER := $(TLPVER)_$(COMMIT_ID) endif endif endif # Evaluate parameters TLP_SBIN ?= /usr/sbin TLP_BIN ?= /usr/bin TLP_TLIB ?= /usr/share/tlp TLP_FLIB ?= /usr/share/tlp/func.d TLP_ULIB ?= /usr/lib/udev TLP_BATD ?= /usr/share/tlp/bat.d TLP_NMDSP ?= /usr/lib/NetworkManager/dispatcher.d TLP_CONFUSR ?= /etc/tlp.conf TLP_CONFDIR ?= /etc/tlp.d TLP_CONFDEF ?= /usr/share/tlp/defaults.conf TLP_CONFREN ?= /usr/share/tlp/rename.conf TLP_CONFDPR ?= /usr/share/tlp/deprecated.conf TLP_SYSD ?= /usr/lib/systemd/system TLP_SDSL ?= /usr/lib/systemd/system-sleep TLP_SYSV ?= /etc/init.d TLP_ELOD ?= /usr/lib/elogind/system-sleep TLP_POLKIT ?= /usr/share/polkit-1/actions TLP_DBCONF ?= /usr/share/dbus-1/system.d TLP_DBSVC ?= /usr/share/dbus-1/system-services TLP_SHCPL ?= /usr/share/bash-completion/completions TLP_ZSHCPL ?= /usr/share/zsh/site-functions TLP_FISHCPL ?= /usr/share/fish/vendor_completions.d TLP_MAN ?= /usr/share/man TLP_META ?= /usr/share/metainfo TLP_RUN ?= /run/tlp TLP_VAR ?= /var/lib/tlp # Catenate DESTDIR to paths _SBIN = $(DESTDIR)$(TLP_SBIN) _BIN = $(DESTDIR)$(TLP_BIN) _TLIB = $(DESTDIR)$(TLP_TLIB) _FLIB = $(DESTDIR)$(TLP_FLIB) _ULIB = $(DESTDIR)$(TLP_ULIB) _BATD = $(DESTDIR)$(TLP_BATD) _NMDSP = $(DESTDIR)$(TLP_NMDSP) _CONFUSR = $(DESTDIR)$(TLP_CONFUSR) _CONFDIR = $(DESTDIR)$(TLP_CONFDIR) _CONFDEF = $(DESTDIR)$(TLP_CONFDEF) _CONFREN = $(DESTDIR)$(TLP_CONFREN) _CONFDPR = $(DESTDIR)$(TLP_CONFDPR) _SYSD = $(DESTDIR)$(TLP_SYSD) _SDSL = $(DESTDIR)$(TLP_SDSL) _SYSV = $(DESTDIR)$(TLP_SYSV) _ELOD = $(DESTDIR)$(TLP_ELOD) _POLKIT = $(DESTDIR)$(TLP_POLKIT) _DBCONF = $(DESTDIR)$(TLP_DBCONF) _DBSVC = $(DESTDIR)$(TLP_DBSVC) _SHCPL = $(DESTDIR)$(TLP_SHCPL) _ZSHCPL = $(DESTDIR)$(TLP_ZSHCPL) _FISHCPL = $(DESTDIR)$(TLP_FISHCPL) _MAN = $(DESTDIR)$(TLP_MAN) _META = $(DESTDIR)$(TLP_META) _RUN = $(DESTDIR)$(TLP_RUN) _VAR = $(DESTDIR)$(TLP_VAR) SED = sed \ -e "s|@TLPVER@|$(TLPVER)|g" \ -e "s|@TLP_SBIN@|$(TLP_SBIN)|g" \ -e "s|@TLP_TLIB@|$(TLP_TLIB)|g" \ -e "s|@TLP_FLIB@|$(TLP_FLIB)|g" \ -e "s|@TLP_ULIB@|$(TLP_ULIB)|g" \ -e "s|@TLP_BATD@|$(TLP_BATD)|g" \ -e "s|@TLP_CONFUSR@|$(TLP_CONFUSR)|g" \ -e "s|@TLP_CONFDIR@|$(TLP_CONFDIR)|g" \ -e "s|@TLP_CONFDEF@|$(TLP_CONFDEF)|g" \ -e "s|@TLP_CONFREN@|$(TLP_CONFREN)|g" \ -e "s|@TLP_CONFDPR@|$(TLP_CONFDPR)|g" \ -e "s|@TLP_SDSL@|$(TLP_SDSL)|g" \ -e "s|@TLP_RUN@|$(TLP_RUN)|g" \ -e "s|@TLP_VAR@|$(TLP_VAR)|g" INFILES = \ tlp \ tlp.conf \ tlpctl \ tlp-func-base \ tlp-pd \ tlp-pd.service \ tlp-rdw-nm \ tlp-rdw.rules \ tlp-rdw-udev \ tlp-rdw \ tlp-rf \ tlp.rules \ tlp-readconfs \ tlp-run-on \ tlp.service \ tlp-stat \ tlp.upstart \ tlp-usb-udev MANFILES1 = \ bluetooth.1 \ nfc.1 \ run-on-ac.1 \ run-on-bat.1 \ wifi.1 \ wwan.1 MANFILES8 = \ tlp.8 \ tlp-stat.8 \ tlp.service.8 MANFILESRDW8 = \ tlp-rdw.8 MANFILESPD1 = \ tlpctl.1 MANFILESPD8 = \ tlp-pd.8 \ tlp-pd.service.8 SHFILES = \ tlp.in \ tlp-func-base.in \ func.d/* \ bat.d/* \ tlp-rdw.in \ tlp-rdw-nm.in \ tlp-rdw-udev.in \ tlp-rf.in \ tlp-run-on.in \ tlp-sleep \ tlp-sleep.elogind \ tlp-stat.in \ tlp-usb-udev.in \ UTSHFILES = \ unit-tests/test-func \ unit-tests/*.sh PLFILES = \ tlp-pcilist \ tlp-readconfs.in \ tlp-usblist PYFILES = \ tlpctl.in \ tlp-pd.in BATDRVFILES = $(foreach drv,$(wildcard bat.d/[0-9][0-9]-[a-z]*),$(drv)~) EXCLUDECHECKWIP = research* # Make targets all: $(INFILES) $(INFILES): %: %.in $(SED) $< > $@ clean: rm -f $(INFILES) rm -f bat.d/*~ rm -f *.log unit-tests/*.log install-tlp: all # Package tlp install -D -m 755 tlp $(_SBIN)/tlp install -D -m 755 tlp-rf $(_BIN)/bluetooth ln -sf bluetooth $(_BIN)/nfc ln -sf bluetooth $(_BIN)/wifi ln -sf bluetooth $(_BIN)/wwan install -m 755 tlp-run-on $(_BIN)/run-on-ac ln -sf run-on-ac $(_BIN)/run-on-bat install -m 755 tlp-stat $(_BIN)/ install -D -m 755 -t $(_TLIB)/func.d func.d/* install -m 755 tlp-func-base $(_TLIB)/ install -D -m 755 -t $(_TLIB)/bat.d bat.d/* install -m 755 tlp-pcilist $(_TLIB)/ install -m 755 tlp-readconfs $(_TLIB)/ install -m 755 tlp-usblist $(_TLIB)/ install -D -m 755 tlp-usb-udev $(_ULIB)/tlp-usb-udev install -D -m 644 tlp.rules $(_ULIB)/rules.d/85-tlp.rules [ -f $(_CONFUSR) ] || install -D -m 644 tlp.conf $(_CONFUSR) install -d $(_CONFDIR) install -D -m 644 README.d $(_CONFDIR)/README install -D -m 644 00-template.conf $(_CONFDIR)/00-template.conf install -D -m 644 defaults.conf $(_CONFDEF) install -D -m 644 rename.conf $(_CONFREN) install -D -m 644 deprecated.conf $(_CONFDPR) ifneq ($(TLP_NO_INIT),1) install -D -m 755 tlp.init $(_SYSV)/tlp endif ifneq ($(TLP_WITH_SYSTEMD),0) install -D -m 644 tlp.service $(_SYSD)/tlp.service install -D -m 755 tlp-sleep $(_SDSL)/tlp endif ifneq ($(TLP_WITH_ELOGIND),0) install -D -m 755 tlp-sleep.elogind $(_ELOD)/49-tlp-sleep endif ifneq ($(TLP_NO_BASHCOMP),1) install -D -m 644 completion/bash/tlp.bash_completion $(_SHCPL)/tlp ln -sf tlp $(_SHCPL)/tlp-stat ln -sf tlp $(_SHCPL)/bluetooth ln -sf tlp $(_SHCPL)/nfc ln -sf tlp $(_SHCPL)/wifi ln -sf tlp $(_SHCPL)/wwan ln -sf tlp $(_SHCPL)/run-on-ac ln -sf tlp $(_SHCPL)/run-on-bat endif ifneq ($(TLP_NO_ZSHCOMP),1) install -D -m 644 completion/zsh/_tlp $(_ZSHCPL)/_tlp install -D -m 644 completion/zsh/_tlp-radio-device $(_ZSHCPL)/_tlp-radio-device install -D -m 644 completion/zsh/_tlp-run-on $(_ZSHCPL)/_tlp-run-on install -D -m 644 completion/zsh/_tlp-stat $(_ZSHCPL)/_tlp-stat endif ifneq ($(TLP_NO_FISHCOMP),1) install -D -m 644 completion/fish/tlp.fish $(_FISHCPL)/tlp.fish install -D -m 644 completion/fish/tlp-stat.fish $(_FISHCPL)/tlp-stat.fish ln -sf tlp.fish $(_FISHCPL)/bluetooth.fish ln -sf tlp.fish $(_FISHCPL)/nfc.fish ln -sf tlp.fish $(_FISHCPL)/wifi.fish ln -sf tlp.fish $(_FISHCPL)/wwan.fish ln -sf tlp.fish $(_FISHCPL)/run-on-ac.fish ln -sf tlp.fish $(_FISHCPL)/run-on-bat.fish endif install -D -m 644 de.linrunner.tlp.metainfo.xml $(_META)/de.linrunner.tlp.metainfo.xml install -d -m 755 $(_VAR) install-rdw: all # Package tlp-rdw install -D -m 755 tlp-rdw $(_BIN)/tlp-rdw install -D -m 644 tlp-rdw.rules $(_ULIB)/rules.d/85-tlp-rdw.rules install -D -m 755 tlp-rdw-udev $(_ULIB)/tlp-rdw-udev install -D -m 755 tlp-rdw-nm $(_NMDSP)/99tlp-rdw-nm ifneq ($(TLP_NO_BASHCOMP),1) install -D -m 644 completion/bash/tlp-rdw.bash_completion $(_SHCPL)/tlp-rdw endif ifneq ($(TLP_NO_ZSHCOMP),1) install -D -m 644 completion/zsh/_tlp-rdw $(_ZSHCPL)/_tlp-rdw endif ifneq ($(TLP_NO_FISHCOMP),1) install -D -m 644 completion/fish/tlp-rdw.fish $(_FISHCPL)/tlp-rdw.fish endif install-pd: all # Package tlp-pd install -D -m 755 tlp-pd $(_SBIN)/tlp-pd install -D -m 755 tlpctl $(_BIN)/tlpctl install -D -m 644 tlp-pd.service $(_SYSD)/tlp-pd.service install -D -m 644 tlp-pd.policy $(_POLKIT)/tlp-pd.policy $(foreach BUS_NAME,org.freedesktop.UPower.PowerProfiles net.hadess.PowerProfiles, \ install -D -m 644 tlp-pd.dbus.conf $(_DBCONF)/$(BUS_NAME).conf; \ sed -e 's|@BUS_NAME@|$(BUS_NAME)|g' -i $(_DBCONF)/$(BUS_NAME).conf; \ install -D -m 644 tlp-pd.dbus.service $(_DBSVC)/$(BUS_NAME).service; \ sed -e 's|@BUS_NAME@|$(BUS_NAME)|g' -i $(_DBSVC)/$(BUS_NAME).service;) ifneq ($(TLP_NO_BASHCOMP),1) install -D -m 644 completion/bash/tlpctl.bash_completion $(_SHCPL)/tlpctl endif ifneq ($(TLP_NO_ZSHCOMP),1) install -D -m 644 completion/zsh/_tlpctl $(_ZSHCPL)/_tlpctl endif ifneq ($(TLP_NO_FISHCOMP),1) install -D -m 644 completion/fish/tlpctl.fish $(_FISHCPL)/tlpctl.fish endif install-man-tlp: # manpages install -d -m 755 $(_MAN)/man1 cd man && install -m 644 $(MANFILES1) $(_MAN)/man1/ install -d -m 755 $(_MAN)/man8 cd man && install -m 644 $(MANFILES8) $(_MAN)/man8/ install-man-rdw: # manpages install -d -m 755 $(_MAN)/man8 cd man-rdw && install -m 644 $(MANFILESRDW8) $(_MAN)/man8/ install-man-pd: # manpages install -d -m 755 $(_MAN)/man1 cd man-pd && install -m 644 $(MANFILESPD1) $(_MAN)/man1/ install -d -m 755 $(_MAN)/man8 cd man-pd && install -m 644 $(MANFILESPD8) $(_MAN)/man8/ install: install-tlp install-rdw install-pd install-man: install-man-tlp install-man-rdw install-man-pd uninstall-tlp: # Package tlp rm $(_SBIN)/tlp rm $(_BIN)/bluetooth rm $(_BIN)/nfc rm $(_BIN)/wifi rm $(_BIN)/wwan rm $(_BIN)/run-on-ac rm $(_BIN)/run-on-bat rm $(_BIN)/tlp-stat rm $(_CONFDIR)/README rm $(_CONFDIR)/00-template.conf rm -r $(_TLIB) rm $(_ULIB)/tlp-usb-udev rm $(_ULIB)/rules.d/85-tlp.rules rm -f $(_SYSV)/tlp rm -f $(_SYSD)/tlp.service rm -f $(_SDSL)/tlp-sleep rm -f $(_ELOD)/49-tlp-sleep rm -f $(_SHCPL)/tlp rm -f $(_SHCPL)/tlp-stat rm -f $(_SHCPL)/bluetooth rm -f $(_SHCPL)/nfc rm -f $(_SHCPL)/wifi rm -f $(_SHCPL)/wwan rm -f $(_SHCPL)/run-on-ac rm -f $(_SHCPL)/run-on-bat rm -f $(_ZSHCPL)/_tlp rm -f $(_ZSHCPL)/_tlp-radio-device rm -f $(_ZSHCPL)/_tlp-run-on rm -f $(_ZSHCPL)/_tlp-stat rm -f $(_FISHCPL)/tlp.fish rm -f $(_FISHCPL)/tlp-stat.fish rm -f $(_FISHCPL)/bluetooth.fish rm -f $(_FISHCPL)/nfc.fish rm -f $(_FISHCPL)/wifi.fish rm -f $(_FISHCPL)/wwan.fish rm -f $(_FISHCPL)/run-on-ac.fish rm -f $(_FISHCPL)/run-on-bat.fish rm -f $(_META)/de.linrunner.tlp.metainfo.xml rm -r $(_VAR) uninstall-rdw: # Package tlp-rdw rm $(_BIN)/tlp-rdw rm $(_ULIB)/rules.d/85-tlp-rdw.rules rm $(_ULIB)/tlp-rdw-udev rm $(_NMDSP)/99tlp-rdw-nm rm -f $(_SHCPL)/tlp-rdw rm -f $(_ZSHCPL)/_tlp-rdw rm -f $(_FISHCPL)/tlp-rdw.fish uninstall-pd: rm $(_SBIN)/tlp-pd rm $(_BIN)/tlpctl rm -f $(_SYSD)/tlp-pd.service rm -f $(_POLKIT)/tlp-pd.policy rm -f $(_DBCONF)/org.freedesktop.UPower.PowerProfiles.conf rm -f $(_DBSVC)/org.freedesktop.UPower.PowerProfiles.service rm -f $(_DBCONF)/net.hadess.PowerProfiles.conf rm -f $(_DBSVC)/net.hadess.PowerProfiles.service rm -f $(_SHCPL)/tlpctl rm -f $(_ZSHCPL)/_tlpctl rm -f $(_FISHCPL)/tlpctl.fish uninstall-man-tlp: # manpages cd $(_MAN)/man1 && rm -f $(MANFILES1) cd $(_MAN)/man8 && rm -f $(MANFILES8) uninstall-man-rdw: # manpages cd $(_MAN)/man8 && rm -f $(MANFILESRDW8) uninstall-man-pd: # manpages cd $(_MAN)/man1 && rm -f $(MANFILESPD1) cd $(_MAN)/man8 && rm -f $(MANFILESPD8) uninstall: uninstall-tlp uninstall-rdw uninstall-pd uninstall-man: uninstall-man-tlp uninstall-man-rdw uninstall-man-pd checkall: checkbatdrv checkbashisms shellcheck perlcritic checkdupconst checkman checkconf checkwip checkbashisms: @echo "*** checkbashisms ***************************************************************************" @{ checkbashisms $(SHFILES) 2>&1 | sed -e '/test with unary -a (should be -e)/{N;d;}'; } || true shellcheck: @echo "*** shellcheck ******************************************************************************" @shellcheck -s dash $(SHFILES) $(UTSHFILES) || true perlcritic: @echo "*** perlcritic ******************************************************************************" @perlcritic --severity 4 --verbose "%F: [%p] %m at line %l, column %c. (Severity: %s)\n" $(PLFILES) || true checkdupconst: @echo "*** checkdupconst ***************************************************************************" @{ sed -n -r -e 's,^.*readonly\s+([A-Za-z_][A-Za-z_0-9]*)=.*$$,\1,p' $(SHFILES) | sort | uniq -d; } || true checkman: @echo "*** checkman ********************************************************************************" @grep '.TH ' man/* man-pd/* man-rdw/* checkconf: @echo "*** checkconf *******************************************************************************" @grep -v '^\s*\(#\|$$\)' tlp.conf.in || true checkwip: @echo "*** checkwip ********************************************************************************" @grep -E -n --exclude=$(EXCLUDECHECKWIP) "### (DEBUG|DEVEL|FIXME|TODO|WIP)" $(SHFILES) $(UTSHFILES) $(PLFILES) $(PYFILES) || true bat.d/TEMPLATE~: bat.d/TEMPLATE @awk '/^batdrv_[a-z_]+ ()/ { print $$1; }' $< | grep -v 'batdrv_is' | sort > $@ bat.d/%~: bat.d/% @printf "*** checkbatdrv %-25s ***********************************************\n" "$<" @awk '/^batdrv_[a-z_]+ ()/ { print $$1; }' $< | grep -v -E 'batdrv_(is|has)' | sort > $@ @diff -U 1 -s bat.d/TEMPLATE~ $@ || true checkbatdrv: bat.d/TEMPLATE~ $(BATDRVFILES) rm -f bat.d/*~ TLP-1.10.1/README.d000066400000000000000000000005731517565574500133640ustar00rootroot00000000000000This directory is intended to contain drop-in customizations for TLP. See full explanation: https://linrunner.de/tlp/settings The naming scheme is 00-name.conf, the files are read in lexical (aphabetical) order. You may also use /etc/tlp.conf directly, which will override any settings in this directory. After making changes, run 'tlp start' to activate them without reboot. TLP-1.10.1/README.rst000066400000000000000000000044051517565574500137470ustar00rootroot00000000000000TLP - Optimize Linux Laptop Battery Life ======================================== TLP is a feature-rich Linux utility that saves laptop battery power without the need to delve into technical details. Rest easy after installation knowing that TLP's default settings are already optimized for battery life. However, you can also fully customize TLP to suit your unique needs. The settings are organized into three customisable profiles *performance*, *balanced* and *power-saver*. The profiles switch automatically, allowing you to adjust between savings and performance independently for AC and battery operation. The newly introduced TLP profiles daemon (**tlp-pd**) enables manual profile switching with a mouse click. Together with TLP as the backend, it provides a **complete replacement** for **power-profiles-daemon** by implementing the same D-Bus API used by major Linux desktop environments such as GNOME, KDE and Cinnamon for switching power profiles. In addition TLP can enable or disable Bluetooth, NFC, Wi-Fi and WWAN radio devices on boot and when connecting or removing the LAN cable. For ThinkPads and many other supported laptops it provides a unified approach to setting battery charge thresholds. Documentation ------------- Read the full documentation at the website ``_. For a summary of how TLP works and its features see `Introduction `_. Installation ------------ TLP packages are available for all major Linux distributions: `Installation `_. Settings -------- Refer to `Settings `_ and the `Optimizing Guide `_ to learn how to customize the configuration if desired. Support ------- Please visit your favorite Linux community for help and support questions. Make shure to check `Support `_ first. Bug reports ----------- Refer to the `Bug Reporting Howto `_. Contribute ---------- Contributing is not only about coding. Volunteers helping with support, testing and documentation are always welcome! See `Contributing `_. TLP-1.10.1/VERSION000066400000000000000000000000071517565574500133220ustar00rootroot000000000000001.10.1 TLP-1.10.1/bat.d/000077500000000000000000000000001517565574500132455ustar00rootroot00000000000000TLP-1.10.1/bat.d/05-thinkpad000066400000000000000000001360321517565574500152210ustar00rootroot00000000000000#!/bin/sh # 05-thinkpad - Battery Plugin for ThinkPads w/ thinkpad_acpi driver # providing thresholds and forced discharge, i.e. X220/T420 and newer. # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly SMAPIBATDIR=/sys/devices/platform/smapi readonly RE_TPSMAPI_ONLY='^(Edge( 13.*)?|G41|R[56][012][eip]?|R[45]00|SL[45]10|T23|T[346][0123][p]?|T[45][01]0[s]?|W[57][01][01]|X[346][012][s]?( Tablet)?|X1[02]0e|X[23]0[01][s]?( Tablet)?|Z6[01][mpt])$' readonly RE_TPSMAPI_AND_TPACPI='^(X1|X220[s]?( Tablet)?|T[45]20[s]?|W520)$' readonly RE_TP_NONE='^(L[45]20|L512|SL[345]00|X121e)$' readonly MOD_TPSMAPI="tp_smapi" supports_tpsmapi_only () { # rc: 0=ThinkPad supports tpsmapi only/1=false # prerequisite: check_thinkpad() printf '%s' "$_tpmodel" | grep -E -q "${RE_TPSMAPI_ONLY}" } supports_tpsmapi_and_tpacpi () { # rc: 0=ThinkPad supports tpsmapi, tpacpi-bat, natacpi/1=false # prerequisite: check_thinkpad() printf '%s' "$_tpmodel" | grep -E -q "${RE_TPSMAPI_AND_TPACPI}" } supports_no_tp_bat_funcs () { # rc: 0=ThinkPad doesn't support battery features/1=false # prerequisite: check_thinkpad() printf '%s' "$_tpmodel" | grep -E -q "${RE_TP_NONE}" } check_thinkpad () { # check for ThinkPad hardware and save model string # rc: 0=ThinkPad, 1=other hardware # retval: $_tpmodel local pv _tpmodel="" # sanitize DMI product_version string pv="$(read_dmi product_version | tr -C -d 'a-zA-Z0-9 ')" # check if kernel module thinkpad_acpi is loaded if [ -d "$TPACPID" ]; then # it's a ThinkPad, determine model if [ -n "$X_SIMULATE_MODEL" ]; then # simulate arbitrary model _tpmodel="$X_SIMULATE_MODEL" else # stock BIOS: strip "ThinkPad" from DMI product_version if printf '%s' "$pv" | grep -E -q 'Think[Pp]ad'; then # stock BIOS --> save model substring _tpmodel=$(printf '%s\n' "$pv" | sed -r 's/^Think[Pp]ad //') else # coreboot happens to use DMI product_name instead of model code _tpmodel="$(read_dmi product_name | tr -C -d 'a-zA-Z0-9 ')" fi fi else # not a ThinkPad echo_debug "bat" "check_thinkpad.not_a_thinkpad: model=$pv" return 1 fi echo_debug "bat" "check_thinkpad: tpmodel=$_tpmodel" return 0 } is_coreboot () { # check if coreboot BIOS # rc: 0=coreboot detected/1=not detected [ "$(read_dmi bios_vendor)" = "coreboot" ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for native kernel acpi (thresholds require Linux 4.19, discharge needs 5.17) # --> retval $_natacpi: # 0=thresholds and discharge/ # 1=thresholds only/ # 32=disabled/ # 128=no kernel support/ # 254=ThinkPad not supported # # 2. check for tp-smapi external kernel module # --> retval $_tpsmapi: # 1=readonly/ # 32=disabled/ # 64=tp_smapi module not loaded/ # 128=tp_smapi module not installed # # 3. determine method for # reading battery data --> retval $_bm_read, # none/natacpi/tpsmapi # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 4. determine sysfile basenames for natacpi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # force discharge --> retval $_bn_dischg; # # 5. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 6. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="thinkpad" _batdrv_kmod="thinkpad_acpi" # kernel module for natacpi if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if ThinkPad if ! check_thinkpad; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.not_a_thinkpad" return 1 elif supports_no_tp_bat_funcs; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.unsupported_model" return 1 fi fi # presume no features at all _natacpi=128 _tpsmapi=254 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="" _bn_stop="" _bn_dischg="charge_behaviour" _batteries="" _bt_def_start=96 _bt_def_stop=100 # --- iterate batteries and check for native kernel ACPI local bd bs local done=0 for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 continue fi if [ -f "$bd/charge_control_start_threshold" ] \ && [ -f "$bd/charge_control_end_threshold" ]; then # sysfiles for thresholds exist (kernel 5.9 and newer) _bn_start="charge_control_start_threshold" _bn_stop="charge_control_end_threshold" _natacpi=254 elif [ -f "$bd/charge_start_threshold" ] \ && [ -f "$bd/charge_stop_threshold" ]; then # sysfiles for thresholds exist (kernel 4.17 and newer) _bn_start="charge_start_threshold" _bn_stop="charge_stop_threshold" _natacpi=254 else # nothing detected _natacpi=128 continue fi if readable_sysf "$bd/$_bn_start" \ && readable_sysf "$bd/$_bn_stop"; then # start/stop thresholds are actually readable _natacpi=1 _bm_thresh="natacpi" if [ -f "$bd/$_bn_dischg" ] && grep -q "force-discharge" "$bd/$_bn_dischg" \ && [ -f "$bd/energy_now" ] && [ "$X_DISCHG_SIMULATE_NONE" != "1" ]; then # charge_behaviour exists and flags the force-discharge capability # and BIOS reports battery ratings in energy_* [mWh] # # known constellations *not* supporting 'tlp discharge|recalibrate' are: # 1. older coreboot does not support force-discharge (charge_behaviour = "[auto]") # 2. future coreboot will support force-discharge # (charge_behaviour = "[auto] inhibit-charge force-discharge"), # but unless coreboot has also been patched to properly report battery ratings # in energy_* [mWh], batdrv_discharge() cannot work # _natacpi=0 _bm_dischg="natacpi" fi fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # consider legacy ThinkPads with coreboot/natacpi if supports_tpsmapi_only && [ $_natacpi -ge 32 ]; then # no natacpi --> try 10-thinkpad-legacy next echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_natacpi: batteries=$_batteries; natacpi=$_natacpi; tpsmapi=$_tpsmapi" return 1 fi # probe tp-smapi external kernel module (relevant models only) if supports_tpsmapi_and_tpacpi; then load_modules $MOD_TPSMAPI if [ -d $SMAPIBATDIR ]; then # module loaded --> tp-smapi available if [ "$TPSMAPI_ENABLE" = "0" ]; then # tpsmapi disabled by configuration _tpsmapi=32 else # reading battery data via tpsmapi is preferred over natacpi # because it provides cycle count and more _tpsmapi=1 _bm_read="tpsmapi" fi elif $MODINFO $MOD_TPSMAPI > /dev/null 2>&1; then # module installed but not loaded _tpsmapi=64 else # module neither installed nor builtin _tpsmapi=128 fi fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; tpsmapi=$_tpsmapi" echo_debug "bat" "batdrv_init.${_batdrv_plugin}: read=$_bm_read; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg; bn_dischg=$_bn_dischg" return 0 } batdrv_select_battery () { # determine battery sysfiles and tpacpi-bat index # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries, $_bm_read, $_bm_dischg, $_bn_start, $_bn_stop, $_bn_dischg # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1; # $_bt_cfg_bat: config suffix = BAT0/BAT1; # $_bat_idx: 1/2; # $_bd_read: directory with battery data sysfiles; # $_bd_readsm: directory with battery data sysfiles (tp-smapi); # $_bf_start: sysfile for start threshold; # $_bf_stop: sysfile for stop threshold; # $_bf_dischg: sysfile for force discharge; # prerequisite: batdrv_init() # defaults _bat_idx=0 # no index _bat_str="" # no bat _bd_read="" # no directories _bd_readsm="" _bf_start="" _bf_stop="" _bf_dischg="" _bt_cfg_bat="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param local bs case "$bat" in DEF) # 1st battery is default bs="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then bs="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # determine battery name and index for main/aux distinction in tlp-stat -b case $bs in BAT0) _bat_str="$bs" # BAT0 is always assumed main battery _bat_idx=1 ;; BAT1) _bat_str="$bs" if [ -d "$ACPIBATDIR/BAT0" ]; then # BAT0 exists, so BAT1 is aux _bat_idx=2 else # BAT0 does not exist, so BAT1 is main _bat_idx=1 fi ;; esac # config suffix equals battery name _bt_cfg_bat="$_bat_str" # determine natacpi sysfiles if [ "$_bm_thresh" = "natacpi" ]; then _bf_start="$ACPIBATDIR/$bs/$_bn_start" _bf_stop="$ACPIBATDIR/$bs/$_bn_stop" fi if [ "$_bm_dischg" = "natacpi" ]; then _bf_dischg="$ACPIBATDIR/$bs/$_bn_dischg" fi case "$_bm_read" in natacpi) _bd_read="$ACPIBATDIR/$bs" ;; tpsmapi) _bd_read="$ACPIBATDIR/$bs" _bd_readsm="$SMAPIBATDIR/$bs" ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bat_idx=$_bat_idx; bd_read=$_bd_read; bd_readsm=$_bd_readsm; bf_start=$_bf_start; bf_stop=$_bf_stop; bf_dischg=$_bf_dischg" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop, $_bat_idx # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case "$1" in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; bat_idx=$_bat_idx; out=$out; rc=$rc" return 0 fi case $_bm_thresh in natacpi) # read threshold from sysfile case "$1" in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi # workaround: read threshold sysfile a second time to mitigate # the annoying firmware issue on ThinkPad A/E/L/S/X series # (refer to issue #369 and FAQ) if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi ;; *) # no threshold api rc=255 ;; esac # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; bat_idx=$_bat_idx; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold 0(disabled)..99/DEF(default) # $2: new stop threshold 1..100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bat_idx, $_bt_cfg_bat, $_bf_start, $_bf_stop, $_bt_def_start, $_bt_def_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start="${1:-}" local new_stop="${2:-}" local verb="${3:-0}" local old_start old_stop # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # start: check for 3 digits max, ensure min 0 / max 99 if ! is_uint "$new_start" 3 || \ ! is_within_bounds "$new_start" 0 99; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (0..99). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (0..99). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2 else cprintf "" "Error: start charge threshold (%s) for battery %s is not specified, invalid or out of range (0..99). Aborted.\n" "$new_start" "$_bat_str" 1>&2 fi ;; esac return 2 fi # stop: check for 3 digits max, ensure min 1 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 1 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (1..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (1..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s is not specified, invalid or out of range (1..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # check if start < stop if [ "$new_start" -ge "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} >= STOP_CHARGE_THRESH_${_bt_cfg_bat}. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s >= STOP_CHARGE_THRESH_%s. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold >= stop threshold for battery %s. Aborted.\n" "$_bat_str" 1>&2 fi ;; esac return 3 fi # read active threshold values if ! old_start=$(batdrv_read_threshold start 0) || \ ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Warning: could not read current charge threshold(s) for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current charge threshold(s) for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi if [ "$old_start" -ge "$old_stop" ]; then # invalid threshold reading, happens on ThinkPad E/L series old_start="none" old_stop="none" fi # determine write sequence because driver's intrinsic boundary conditions # must be met in all write stages: # - natacpi: start < stop (write fails if not met) # - tpacpi: nothing (maybe BIOS/ECP enforces something) local rc=0 steprc tseq if [ "$old_stop" != "none" ] && [ "$new_start" -ge "$old_stop" ]; then tseq="stop start" else tseq="start stop" fi # write new thresholds in determined sequence if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds for battery %s:\n" "$_bat_str" 1>&2 fi for step in $tseq; do local old_thresh new_thresh steprc case $step in start) old_thresh=$old_start new_thresh=$new_start ;; stop) old_thresh=$old_stop new_thresh=$new_stop ;; esac if [ "$old_thresh" != "$new_thresh" ]; then # new threshold differs from effective one --> write it case $step in start) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_start" ;; stop) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_stop" ;; esac steprc=$?; [ $steprc -ne 0 ] && [ $rc -eq 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then if [ "$step" = "start" ] && [ "$new_thresh" -eq 0 ]; then printf " %-5s = %3d (disabled)\n" "$step" "$new_thresh" 1>&2 else printf " %-5s = %3d\n" "$step" "$new_thresh" 1>&2 fi else cprintf "err" " %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing $step charge threshold for battery $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2 fi fi done # for step if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation which also covers ThinkPad special cases: # coreboot or EC firmware glitch (?), refer to issue #812 soc_calc "$1" } batdrv_chargeonce () { # charge battery to stop threshold once # use pre-determined method and sysfiles from global parms # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bat_idx, $_bf_start, $_bf_stop # rc: 0=ok/ # 2=charge level read error/ # 3=charge level too high/ # 4=threshold read error/ # 5=threshold write error/ # prerequisite: batdrv_init(), batdrv_select_battery() local soc cur_stop temp_start local rc=0 if ! cur_stop=$(batdrv_read_threshold stop 0); then cprintf "" "Error: reading stop charge threshold of battery %s failed. Aborted.\n" "$_bat_str" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).thresh_unknown: stop=$cur_stop; rc=4" return 4 fi # get current charge level (in %) if ! soc="$(batdrv_calc_soc)"; then cprintf "" "Error: cannot determine charge level of battery %s.\n" "$_bat_str" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).charge_level_unknown; rc=2" return 2 fi temp_start=$(( cur_stop - 1 )) if [ "$soc" -gt "$temp_start" ]; then cprintf "" "Error: the charge level of battery %s is %s%%. " "$_bat_str" "$soc" 1>&2 cprintf "err" "For this command to work, it must not be higher than %s%% (stop threshold - 1).\n" "$temp_start" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).charge_level_too_high: soc=$soc; stop=$cur_stop; rc=3" return 3 fi printf "Current charge level of battery %s is %s%%, we are about to charge to %s%%.\n" "$_bat_str" "$soc" "$cur_stop" 1>&2 printf "Setting temporary charge threshold for battery %s:\n" "$_bat_str" 1>&2 write_sysf "$temp_start" "$_bf_start" || rc=5 if [ $rc -eq 0 ]; then printf " start = %3d\n" "$temp_start" 1>&2 cprintf "notice" "Charging starts now, keep the charger connected.\n" 1>&2 printf "If charging does not start immediately, unplug the charger for a moment and reconnect it.\n" 1>&2 else cprintf "err" " start = %3d (Error: write failed)\n" "$temp_start" 1>&2 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str): soc=$soc; start=$temp_start; stop=$cur_stop; rc=$rc" return $rc } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat start_thresh stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi done return 0 } batdrv_read_force_discharge () { # read and print force-discharge state # $1: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_dischg, $_bat_str, $_bf_dischg, $_bat_idx # out: # - api: 0=off/1=on/"" on error # - tlp-stat: status text/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local rc=0 out="" case $_bm_dischg in natacpi) # read state from sysfile if out=$(read_sysf "$_bf_dischg"); then if [ "$1" != "1" ]; then # api output if echo "$out" | grep -q "\[force-discharge\]"; then out=1 else out=0 fi fi else # not readable/non-existent [ "$1" = "1" ] && out="(not available)" rc=4 fi ;; *) # no discharge api [ "$1" = "1" ] && out="(not available)" rc=255 ;; esac # "return" force-discharge if [ "$X_DISCHG_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else [ "$1" = "1" ] && printf "(not available)\n" rc=4 fi if [ "$rc" -gt 0 ]; then # log output in the error case only echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge($_bat_str): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; bat_idx=$_bat_idx; out=$out; rc=$rc" fi return $rc } batdrv_write_force_discharge () { # write force-discharge state # $1: 0=off/1=on # global params: $_batdrv_plugin, $_bat_str, $_bat_idx, $_bm_dischg, $_bf_dischg # rc: 0=done/5=write error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local rc=0 case $_bm_dischg in natacpi) # write force-discharge case "$1" in 0) write_sysf "auto" "$_bf_dischg" || rc=5 ;; 1) write_sysf "force-discharge" "$_bf_dischg" || rc=5 ;; esac ;; # natacpi *) # no discharge api rc=255 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge($_bat_str, $1): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; bat_idx=$_bat_idx; rc=$rc" return $rc } batdrv_cancel_force_discharge () { # trap: called from batdrv_discharge # global params: $_batdrv_plugin, $_bat_str # prerequisite: batdrv_discharge() batdrv_write_force_discharge 0 unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.cancelled($_bat_str)" printf " Cancelled.\n" do_exit 5 } batdrv_force_discharge_active () { # check if battery is in 'force-discharge' state # $1: init/run # global params: $_batdrv_plugin, $_bat_str, $_bm_read, $_bd_read, $_bat_target_en # rc: init: 0=running or timeout/1=waiting, run: 0=running/1=stopped # retval: $_bat_dischg_rc: 0=discharging/1=not discharging/2=AC detached/4=terminated/6=target soc reached/255=internal error, # $_bat_en, $_bat_fd, $_bat_pwr, $_bat_soc, $_bat_st, $_bat_volt # prerequisite: batdrv_init(), batdrv_select_battery() local task="$1" local label local rc # battery readings to be returned to caller as retvals (and for trace output) _bat_en="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')" _bat_soc="$(batdrv_calc_soc)" _bat_pwr="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/power_now")"' / 1000.0 );')" _bat_volt="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/voltage_now")"' / 1000.0 );')" _bat_st="$(read_sysf "$_bd_read/status")" _bat_fd="$(batdrv_read_force_discharge 0)" if [ "$_bat_en" -le "$_bat_target_en" ]; then # target soc reached _bat_dischg_rc=6 elif ! get_sys_power_supply; then # AC detached _bat_dischg_rc=2 elif [ "$_bat_fd" = "0" ]; then # force-discharge is off _bat_dischg_rc=4 else # quirky firmware (e.g. ThinkPad E-series) may keep force-discharge on --> check battery status too case "$_bat_st" in [Ds]ischarging) # battery is discharging --> running _bat_dischg_rc=0 ;; *) # battery is not discharging --> init: waiting, run: stopped _bat_dischg_rc=1 ;; esac fi # rc=0 when actively discharging, or when init detects an abnormal condition # --> terminate calling loop immediately if [ "$_bat_dischg_rc" -eq 0 ] || { [ "$task" = "init" ] && [ "$_bat_dischg_rc" -gt 1 ]; }; then rc=0 else rc=1 fi case "$_bat_dischg_rc" in 0) label="running" ;; 1) label="not_discharging" ;; 2) label="ac_detached" ;; 4) label="terminated" ;; 6) label="target_soc" ;; *) label="int_err" ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.${label}($_bat_str): fd=$_bat_fd; bst=$_bat_st; target=$_bat_target_en; soc=$_bat_soc; en=$_bat_en; pwr=$_bat_pwr; volt=$_bat_volt; drc=$_bat_dischg_rc; rc=$rc" return $rc } batdrv_discharge_safetylock () { # check safety lock - ThinkPads implement force-discharge sanely and do not need a safety lock # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged # ThinkPads implement force-discharge sanely and do not need a safety lock return 1 } batdrv_discharge () { # discharge battery # $1: target soc 0(default)..99 # global params: $_batdrv_plugin, $_bm_dischg, $_bat_str, $_bat_idx, $_bd_read, $_bf_dischg # rc: 0=done/2=AC detached/3=not emptied/5=cancelled by ^C/6=target soc reached/7=target soc out of bounds/8=force-discharge failed or timeout/9=charge level read error # prerequisite: batdrv_init(), batdrv_select_battery() local ef en label sleeps soc target_soc timeout wt if ! soc="$(batdrv_calc_soc)"; then _bat_dischg_rc=9 echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge($_bat_str).charge_level_unknown; rc=$_bat_dischg_rc" cecho "Error: cannot read charge level of battery ${_bat_str}." 1>&2 return $_bat_dischg_rc fi target_soc="${1:-0}" if is_uint "$target_soc"; then if ! is_within_bounds "$target_soc" 0 99; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_out_of_bounds($_bat_str): target=$target_soc" cprintf "" "Error: target charge level (%s) for battery %s is out of range (0..99).\n" "$target_soc" "$_bat_str" 1>&2 _bat_dischg_rc=7 return $_bat_dischg_rc fi fi ef="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_full")"' / 1000.0 );')" _bat_target_en="$(perl -e 'printf ("%d", '"$ef"' * '"$target_soc"' / 100.0)')" en="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')" if [ "$_bat_target_en" -ge "$en" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_below_soc($_bat_str): target=$target_soc; soc=$soc" cprintf "" "Error: target level (%s%%) is too high compared to the actual charge level (%s%%) of battery %s.\n" "$target_soc" "$soc" "$_bat_str" 1>&2 _bat_dischg_rc=7 return $_bat_dischg_rc fi # start discharge if ! batdrv_write_force_discharge 1; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.force_discharge_write_error($_bat_str)" cecho "Error: discharge $_bat_str failed -- check your hardware (battery, charger)." 1>&2 _bat_dischg_rc=8 return $_bat_dischg_rc fi trap batdrv_cancel_force_discharge INT # enable ^C hook # wait for start == while status not "discharging" -- 15.0 sec timeout, quit if force-discharge is reset printf "Initiating discharge of battery %s " "$_bat_str" 1>&2 sleep 0.1 timeout="${X_DISCHG_INIT_TIMEOUT:-15}" sleeps="${X_DISCHG_INIT_SLEEP:-1}" wt="$timeout" while ! batdrv_force_discharge_active "init" && [ "$wt" -gt 0 ] ; do sleep "$sleeps" printf "." 1>&2 wt=$((wt - 1)) done printf "\n" 1>&2 if [ "$_bat_dischg_rc" = "0" ]; then # discharge initiated successfully --> wait for force-discharge reset by ec or status != "discharging" while batdrv_force_discharge_active "run"; do clear printf "Currently discharging battery %s to %s%% (%s mWh):\n" "$_bat_str" "$target_soc" "$_bat_target_en" 1>&2 # show current battery state if [ -n "$_bat_volt" ]; then printf "voltage = %6s [mV]\n" "$_bat_volt" 1>&2 else printf "voltage = not available [mV]\n" 1>&2 fi printf "remaining energy = %6d [mWh]\n" "$_bat_en" 1>&2 if soc="$(batdrv_calc_soc "%6.1f")"; then printf "remaining percent = %6s [%%]\n" "$soc" 1>&2 else printf "remaining percent = n/a [%%]\n" 1>&2 fi if [ "$_bat_pwr" != "0" ]; then perl -e 'printf ("remaining time = %6d [min]\n", 60.0 * ('"$_bat_en"' - '"$_bat_target_en"') / '"$_bat_pwr"');' 1>&2 printf "power = %6s [mW]\n" "$_bat_pwr" 1>&2 else printf "remaining time = not discharging [min]\n" 1>&2 fi printf "status = %s\n" "$_bat_st" 1>&2 printf "force-discharge = %s\n" "$_bat_fd" 1>&2 echo "Press Ctrl+C to cancel." 1>&2 sleep 5 done # cancel force-discharge in case the EC has not already done so [ "$(batdrv_read_force_discharge 0)" = "1" ] && batdrv_write_force_discharge 0 # check exit cause label="completed" case "$_bat_dischg_rc" in 2) # system on battery, AC power removed label="ac_detached" cecho "Warning: battery $_bat_str discharge has stopped early -- AC/charger removed." 1>&2 ;; 1|4) # discharging stopped(1) or force-discharge reset (4) if [ "$_bat_soc" -gt "$target_soc" ]; then # system on AC: force-discharge ended prematurely with SOC > target label="not_completed" _bat_dischg_rc=3 cecho "Error: battery $_bat_str discharge was aborted early -- check your hardware (battery, charger)." 1>&2 else # battery discharge completed _bat_dischg_rc=0 cecho "Done: battery $_bat_str discharge completed." "success" 1>&2 fi ;; 6) # target_soc reached label="target_soc" _bat_dischg_rc=0 cecho "Done: battery $_bat_str discharge completed." "success" 1>&2 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.${label}($_bat_str): fd=$_bat_fd; bst=$_bat_st; target=$_bat_target_en; soc=$_bat_soc; en=$_bat_en; pwr=$_bat_pwr; volt=$_bat_volt; drc=$_bat_dischg_rc" wt="$X_DISCHG_TRACE_FOLLOWUP" if is_uint "$wt"; then # diagnostic: trace battery status after force-discharge stop printf "Additional battery trace data is being collected for %d second(s), hold on " "$wt" 1>&2 while [ "$wt" -gt 0 ]; do # battery readings to be returned to caller as retvals (and for trace output) _bat_en="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')" _bat_soc="$(batdrv_calc_soc)" _bat_pwr="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/power_now")"' / 1000.0 );')" _bat_volt="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/voltage_now")"' / 1000.0 );')" _bat_st="$(read_sysf "$_bd_read/status")" _bat_fd="$(batdrv_read_force_discharge 0)" echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.followup($_bat_str): fd=$_bat_fd; bst=$_bat_st; soc=$_bat_soc; en=$_bat_en; pwr=$_bat_pwr; volt=$_bat_volt; wt=$wt" printf "." 1>&2 sleep 1 wt=$((wt - 1)) done printf " ok.\n" 1>&2 fi else # force-discharge timeout _bat_dischg_rc=8 echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.timeout($_bat_str): fd=$_bat_fd; bst=$_bat_st; target=$_bat_target_en; soc=$_bat_soc; en=$_bat_en; pwr=$_bat_pwr; volt=$_bat_volt; drc=$_bat_dischg_rc" cecho "Error: discharge $_bat_str timeout after $(((timeout - wt) * sleeps)) second(s) -- check your hardware (battery, charger)." 1>&2 fi trap - INT # remove ^C hook return $_bat_dischg_rc } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_natacpi, $_tpsmapi, # $_bm_thresh, $_bm_dischg, $_bd_read, $_bd_readsm, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" local fs="" [ "$_bm_thresh" = "natacpi" ] && fs="charge thresholds" if [ "$_bm_dischg" = "natacpi" ]; then fs="${fs}${fs:+, }chargeonce, discharge, recalibrate" fi if [ -n "$fs" ]; then cprintf "success" "Supported features: %s\n" "$fs" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0|1) cprintf "success" "* natacpi (%s) = active (%s)\n" "$_batdrv_kmod" "$(print_bat_methods_per_driver "natacpi")" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (ThinkPad not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac # Legacy-ThinkPad battery API case $_tpsmapi in 1) cprintf "success" "* tp-smapi (tp_smapi) = readonly (%s)\n" "$(print_bat_methods_per_driver "tpsmapi")" ;; 32) cprintf "notice" "* tp-smapi (tp_smapi) = inactive (disabled by configuration)\n" ;; 64) cprintf "err" "* tp-smapi (tp_smapi) = inactive (kernel module 'tp_smapi' load error)\n" ;; 128) cprintf "notice" "* tp-smapi (tp_smapi) = inactive (kernel module 'tp_smapi' not installed)\n" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0/1: 0(off)..96(default)..99\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 1..100(default)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate detected batteries batdrv_select_battery "$bat" case $_bat_idx in 1) printf "+++ ThinkPad Battery Status: %s (Main / Internal)\n" "$bat" ;; 2) printf "+++ ThinkPad Battery Status: %s (Removable)\n" "$bat" ;; 0) printf "+++ ThinkPad Battery Status: %s\n" "$bat" ;; esac # --- show basic data case $_bm_read in natacpi) # use ACPI data print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" if is_coreboot && [ -f "$_bd_read/charge_full" ]; then # handle special case: coreboot (Skulls) provides ACPI mW(h) values incorrectly: # - using sysfiles for mA(h) # - one decimal place too little # refer to issue #657 printparm "%-59s = ##%6d## [mWh] *" "$_bd_read/charge_full_design" "" 00 printparm "%-59s = ##%6d## [mWh] *" "$_bd_read/charge_full" "" 00 printparm "%-59s = ##%6d## [mWh] *" "$_bd_read/charge_now" "" 00 printparm "%-59s = ##%6d## [mW] *" "$_bd_read/current_now" "" 00 # store values for charge / capacity calculation _bat_ed=$(read_sysval "$_bd_read/charge_full_design") _bat_ef=$(read_sysval "$_bd_read/charge_full") _bat_en=$(read_sysval "$_bd_read/charge_now") _bat_efsum=$((_bat_efsum + _bat_ef)) _bat_ensum=$((_bat_ensum + _bat_en)) else print_bat_energy fi print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" ;; # natacpi tpsmapi) # ThinkPad with active tp-smapi printparm "%-59s = ##%s##" "$_bd_readsm/manufacturer" printparm "%-59s = ##%s##" "$_bd_readsm/model" if [ "$verbose" = "1" ]; then printparm "%-59s = ##%s##" "$_bd_readsm/serial" "" "" 1 fi printparm "%-59s = ##%s##" "$_bd_readsm/manufacture_date" printparm "%-59s = ##%s##" "$_bd_readsm/first_use_date" printparm "%-59s = ##%6d##" "$_bd_readsm/cycle_count" if [ -f "$_bd_readsm/temperature" ]; then # shellcheck disable=SC2046 perl -e 'printf ("%-59s = %6d [°C]\n", "'"$_bd_readsm/temperature"'", '$(read_sysval "$_bd_readsm/temperature")' / 1000.0);' fi printparm "%-59s = ##%6d## [mWh]" "$_bd_readsm/design_capacity" printparm "%-59s = ##%6d## [mWh]" "$_bd_readsm/last_full_capacity" printparm "%-59s = ##%6d## [mWh]" "$_bd_readsm/remaining_capacity" if [ "$verbose" -eq 1 ]; then printparm "%-59s = ##%6d## [%%]" "$_bd_readsm/remaining_percent" fi printparm "%-59s = ##%6s## [min]" "$_bd_readsm/remaining_running_time_now" printparm "%-59s = ##%6s## [min]" "$_bd_readsm/remaining_charging_time" printparm "%-59s = ##%6d## [mW]" "$_bd_readsm/power_now" printparm "%-59s = ##%6d## [mW]" "$_bd_readsm/power_avg" print_bat_state "$_bd_readsm/state" printf "\n" if [ "$verbose" -eq 1 ]; then printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/design_voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/group0_voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/group1_voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/group2_voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/group3_voltage" printf "\n" fi # store values for charge / capacity calculation _bat_ed=$(read_sysval "$_bd_readsm/design_capacity") _bat_ef=$(read_sysval "$_bd_readsm/last_full_capacity") _bat_en=$(read_sysval "$_bd_readsm/remaining_capacity") _bat_efsum=$((_bat_efsum + _bat_ef)) _bat_ensum=$((_bat_ensum + _bat_en)) ;; # tp-smapi esac # $_bm_read # --- show battery features: thresholds, force-discharge local lf=0 if [ "$_bm_thresh" = "natacpi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start 1)" printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" lf=1 fi if [ "$_bm_dischg" = "natacpi" ]; then printf "%-59s = %s\n" "$_bf_dischg" "$(batdrv_read_force_discharge 1)" lf=1 fi [ "$lf" -gt 0 ] && printf "\n" # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total if is_coreboot && [ -f "$_bd_read/charge_full" ]; then printf "*) Converted coreboot charge readings may differ.\n\n" fi return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output ThinkPad specific recommendations # prerequisite: batdrv_init(), batdrv_select_battery() soc_gt_stop_recommendation if [ "$_natacpi" = "1" ] \ && { [ ! -f "$_bd_read/charge_behaviour" ]||[ "$X_DISCHG_SIMULATE_NONE" = "1" ]; }; then # kernel does not expose charge_behaviour printf "Install kernel 5.17 (or later) for battery recalibration support\n" fi if [ "$_tpsmapi" = "128" ]; then printf "Install tp-smapi kernel modules for extended battery status (e.g. the cycle count)\n" fi return 0 } TLP-1.10.1/bat.d/10-thinkpad-legacy000066400000000000000000001153001517565574500164520ustar00rootroot00000000000000#!/bin/sh # 10-thinkpad-legacy - Battery Plugin for ThinkPads using tp_smapi # for thresholds and forced discharge, # i.e. X201/T410 and older. # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat, 05-thinkpad # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $batdrv_kmod # # 1. check for tp_smapi external kernel module # --> retval $_tpsmapi # 0=supported/ # 32=disabled/ # 64=tp_smapi module not loaded/ # 128=tp_smapi module not installed/ # 254=ThinkPad not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi/tpacpi/tpsmapi # # 3. determine sysfile basenames for tpsmapi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # force discharge --> retval $_bn_dischg; # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="thinkpad-legacy" _batdrv_kmod="tp_smapi" # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! check_thinkpad; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.not_a_thinkpad" return 1 elif ! supports_tpsmapi_only; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.unsupported_model" return 1 fi fi # presume no features at all _tpsmapi=254 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="start_charge_thresh" _bn_stop="stop_charge_thresh" _bn_dischg="force_discharge" _batteries="" _bt_def_start=96 _bt_def_stop=100 local bs bd done=0 if [ "$TPSMAPI_ENABLE" = "0" ]; then # tp-smapi disabled by configuration _tpsmapi=32 else # probe tp_smapi external kernel module load_modules "$MOD_TPSMAPI" if [ -d "$SMAPIBATDIR" ]; then # module loaded --> tp-smapi available # enumerate tp_smapi batteries # note: tp_smapi battery names may diverge from ACPI names, # refer to https://github.com/linrunner/TLP/issues/714 for bd in "$SMAPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/installed")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip tp-smapi detection for 2nd battery [ $done -eq 1 ] && continue done=1 if readable_sysf "$bd/$_bn_start" \ && readable_sysf "$bd/$_bn_stop" \ && readable_sysf "$bd/$_bn_dischg"; then # tp-smapi sysfiles are actually readable _tpsmapi=0 _bm_read="tpsmapi" _bm_thresh="tpsmapi" _bm_dischg="tpsmapi" fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi fi fi if [ "$_tpsmapi" -ne 0 ]; then # tp-smapi unavailable or disabled # enumerate ACPI batteries so that at least tlp-stat -b can show their data _batteries="" for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # remaining tp-smapi inavailability cases if [ "$_tpsmapi" -eq 32 ]; then : # tp-smapi disabled elif $MODINFO "$MOD_TPSMAPI" > /dev/null 2>&1; then # module installed but not loaded _tpsmapi=64 else # module neither installed nor builtin _tpsmapi=128 fi fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; tpsmapi=$_tpsmapi" echo_debug "bat" "batdrv_init.${_batdrv_plugin}: read=$_bm_read; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg; bn_dischg=$_bn_dischg" return 0 } batdrv_select_battery () { # determine battery sysfiles and bat index # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries, $_bm_thresh, $_bm_dischg # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1; # $_bat_idx: 1/2; # $_bd_read: directory with battery data sysfiles; # $_bd_readsm: directory with battery data sysfiles (tp-smapi); # $_bf_start: sysfile for start threshold; # $_bf_stop: sysfile for stop threshold; # $_bf_dischg: sysfile for force discharge # prerequisite: batdrv_init() # defaults _bat_idx=0 # no index _bat_str="" # no bat _bd_read="" # no directories _bd_readsm="" _bf_start="" _bf_stop="" _bf_dischg="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param local bs case "$bat" in DEF) # 1st battery is default bs="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then bs="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # determine bat index for main/aux distinction _bat_str="$bs" case $bs in BAT0) _bat_idx=1 ;; BAT1) _bat_idx=2 ;; esac # config suffix equals battery name _bt_cfg_bat="$_bat_str" # determine tp-smapi sysfiles if [ "$_bm_thresh" = "tpsmapi" ]; then _bf_start="$SMAPIBATDIR/$bs/$_bn_start" _bf_stop="$SMAPIBATDIR/$bs/$_bn_stop" fi if [ "$_bm_dischg" = "tpsmapi" ]; then _bf_dischg="$SMAPIBATDIR/$bs/$_bn_dischg" fi case "$_bm_read" in natacpi) _bd_read="$ACPIBATDIR/$bs" _bd_readsm="" ;; tpsmapi) _bd_read="$ACPIBATDIR/$bs" _bd_readsm="$SMAPIBATDIR/$bs" ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bat_idx=$_bat_idx; bd_read=$_bd_read; bd_readsm=$_bd_readsm; bf_start=$_bf_start; bf_stop=$_bf_stop; bf_dischg=$_bf_dischg" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case "$1" in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi case $_bm_thresh in tpsmapi) # read threshold from sysfile case "$1" in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi ;; *) # no threshold api rc=255 ;; esac # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; bat_idx=$_bat_idx; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold 0(disabled)..99/DEF(default) # $2: new stop threshold 1..100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start="${1:-}" local new_stop="${2:-}" local verb="${3:-0}" local old_start old_stop # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # start: check for 3 digits max, ensure min 2 / max 96 if ! is_uint "$new_start" 3 || \ ! is_within_bounds "$new_start" 2 96; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (2..96). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (2..96). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2 else cprintf "" "Error: start charge threshold (%s) for battery %s is not specified, invalid or out of range (2..96). Aborted.\n" "$new_start" "$_bat_str" 1>&2 fi ;; esac return 2 fi # stop: check for 3 digits max, ensure min 6 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 6 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (6..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (6..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s is not specified, invalid or out of range (6..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # check minimum start - stop difference if [ $((new_start + 4)) -gt "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} > STOP_CHARGE_THRESH_${_bt_cfg_bat} - 4. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s > STOP_CHARGE_THRESH_%s - 4. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold > stop threshold - 4 for battery %s. Aborted.\n" "$_bat_str" 1>&2 fi ;; esac return 3 fi # read active threshold values if ! old_start=$(batdrv_read_threshold start 0) || \ ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current charge threshold(s) for $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current charge threshold(s) for %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # determine write sequence because driver's intrinsic boundary conditions # must be met in all write stages: # - tp-smapi: start <= stop - 4 (changes value for compliance) local rc=0 steprc tseq if [ "$new_start" -gt "$old_stop" ]; then tseq="stop start" else tseq="start stop" fi # write new thresholds in determined sequence if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds for battery %s:\n" "$_bat_str" 1>&2 fi for step in $tseq; do local old_thresh new_thresh steprc case $step in start) old_thresh=$old_start new_thresh=$new_start ;; stop) old_thresh=$old_stop new_thresh=$new_stop ;; esac if [ "$old_thresh" != "$new_thresh" ]; then # new threshold differs from effective one --> write it case $step in start) write_sysf "$new_thresh" "$_bf_start" ;; stop) write_sysf "$new_thresh" "$_bf_stop" ;; esac steprc=$?; [ $steprc -ne 0 ] && [ $rc -eq 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then printf " %-5s = %3d\n" "$step" "$new_thresh" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2 rc=5 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing $step charge threshold for battery $_bat_str failed." rc=5 fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2 fi fi done # for step if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read, $_bd_readsm # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error local ef en if [ -f "$_bd_read/energy_full" ]; then # vendor BIOS ef=$(read_sysval "$_bd_read/energy_full") en=$(read_sysval "$_bd_read/energy_now") elif [ -f "$_bd_readsm/last_full_capacity" ]; then # tp-smapi ef=$(read_sysval "$_bd_readsm/last_full_capacity") en=$(read_sysval "$_bd_readsm/remaining_capacity") elif [ -f "$_bd_read/charge_full" ]; then # coreboot(?) # refer to https://github.com/linrunner/TLP/issues/793#issuecomment-2727452395 ef=$(read_sysval "$_bd_read/charge_full") en=$(read_sysval "$_bd_read/charge_now") else ef=0 en=0 fi if [ "$ef" != "0" ]; then if [ -n "$1" ]; then perl -e 'printf ("'"$1"'", 100.0 * '"$en"' / '"$ef"')' else perl -e 'printf ("%d", int(100.0 * '"$en"' / '"$ef"' + 0.5))' fi return 0 else printf "255" return 1 fi } batdrv_chargeonce () { # charge battery to stop threshold once # use pre-determined method and sysfiles from global parms # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bf_start, $_bf_stop # rc: 0=ok/ # 2=charge level read error/ # 3=charge level too high/ # 4=threshold read error/ # 5=threshold write error/ # 6=stop threshold too low # prerequisite: batdrv_init(), batdrv_select_battery() local soc cur_stop temp_start local rc=0 if ! cur_stop=$(batdrv_read_threshold stop 0); then cprintf "" "Error: reading stop charge threshold of battery %s failed. Aborted.\n" "$_bat_str" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).thresh_unknown: stop=$cur_stop; rc=4" return 4 elif [ "$cur_stop" -lt 6 ]; then cprintf "Error: the stop charge threshold of battery %s is %s. " "$_bat_str" "$cur_stop" 1>&2 cprintf "err" "For this command to work, it must be 6 at least.\n" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).stop_thresh_too_low: stop=$cur_stop; rc=6" return 6 fi # get current charge level (in %) if ! soc="$(batdrv_calc_soc)"; then cprintf "" "Error: cannot determine charge level of battery %s.\n" "$_bat_str" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).charge_level_unknown; rc=2" return 2 fi temp_start=$(( cur_stop - 4 )) if [ "$soc" -gt "$temp_start" ]; then cprintf "" "Error: the %s charge level is %s%%. " "$_bat_str" "$soc" 1>&2 cprintf "err" "For this command to work, it must not be higher than %s%% (stop threshold - 4).\n" "$temp_start" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).charge_level_too_high: soc=$soc; start=$temp_start; stop=$cur_stop; rc=3" return 3 fi printf "Current charge level of battery %s is %s%%, we are about to charge to %s%%.\n" "$_bat_str" "$soc" "$cur_stop" 1>&2 printf "Setting temporary charge threshold for battery %s:" "$_bat_str" 1>&2 case $_bm_thresh in tpsmapi) write_sysf "$temp_start" "$_bf_start" || rc=5 ;; esac if [ $rc -eq 0 ]; then printf " start = %3d\n" "$temp_start" 1>&2 cprintf "notice" "Charging starts now, keep the charger connected.\n" 1>&2 else cprintf "err" " start = %3d (Error: write failed)\n" "$temp_start" 1>&2 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str): soc=$soc; start=$temp_start; stop=$cur_stop; rc=$rc" return $rc } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat start_thresh stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi done return 0 } batdrv_read_force_discharge () { # read and print force discharge state # $1: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_dischg, $_bat_str, $_bf_dischg, $_bat_idx # - api: 0=off/1=on/"" on error # - tlp-stat: 0=off/1=on/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local rc=0 out="" case $_bm_dischg in tpsmapi) # read force discharge from sysfile if ! out=$(read_sysf "$_bf_dischg"); then # not readable/non-existent [ "$1" = "1" ] && out="(not available)" rc=4 fi ;; *) # no discharge api [ "$1" = "1" ] && out="(not available)" rc=255 ;; esac # "return" force-discharge if [ "$X_DISCHG_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else [ "$1" = "1" ] && printf "(not available)\n" rc=4 fi if [ "$rc" -gt 0 ]; then # log output in the error case only echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge($_bat_str): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; out=$out; rc=$rc" fi return $rc } batdrv_write_force_discharge () { # write force discharge state # $1: 0=off/1=on # global params: $_batdrv_plugin, $_bat_str, $_bat_idx, $_bm_dischg, $_bf_dischg # rc: 0=done/5=write error # prerequisite: batdrv_init(), batdrv_select_battery() local rc=0 case $_bm_dischg in tpsmapi) # write force_discharge write_sysf "$1" "$_bf_dischg" || rc=5 ;; *) # no discharge api rc=255 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge($_bat_str, $1): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; bat_idx=$_bat_idx; rc=$rc" return $rc } batdrv_cancel_force_discharge () { # trap: called from batdrv_discharge # global params: $_batdrv_plugin, $_bat_str # prerequisite: batdrv_discharge() batdrv_write_force_discharge 0 unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.cancelled($_bat_str)" printf " Cancelled.\n" do_exit 5 } batdrv_force_discharge_active () { # check if battery is in 'force.discharge' state # $1: init/run # global params: $_batdrv_plugin, $_bat_str, $_bm_read, $_bd_read, $_bd_readsm, $_bat_target_en # rc: init: 0=running or timeout/1=waiting, run: 0=running/1=stopped # retval: $_bat_dischg_rc: 0=discharging/2=ac detached/4=terminated/6=target soc reached/255=internal error, # $_bat_en, $_bat_fd, $_bat_pwr, $_bat_soc, $_bat_st, $_bat_volt # prerequisite: batdrv_init(), batdrv_select_battery() local task="$1" local label local rc # battery readings to be returned to caller as retvals (and for trace output) _bat_en="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')" _bat_soc="$(batdrv_calc_soc)" _bat_pwr="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/power_now")"' / 1000.0 );')" _bat_volt="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/voltage_now")"' / 1000.0 );')" _bat_st="$(read_sysf "$_bd_readsm/state")" _bat_fd="$(batdrv_read_force_discharge 0)" if [ "$_bat_en" -le "$_bat_target_en" ]; then # target soc reached _bat_dischg_rc=6 elif ! get_sys_power_supply; then # AC detached _bat_dischg_rc=2 elif [ "$_bat_fd" = "0" ]; then # force-discharge is off _bat_dischg_rc=4 else # running _bat_dischg_rc=0 fi # rc=0 when actively discharging, or when init detects an abnormal condition # --> terminate calling loop immediately if [ "$_bat_dischg_rc" -eq 0 ] || { [ "$task" = "init" ] && [ "$_bat_dischg_rc" -gt 0 ]; }; then rc=0 else rc=1 fi case "$_bat_dischg_rc" in 0) label="running" ;; 2) label="ac_detached" ;; 4) label="terminated" ;; 6) label="target_soc" ;; *) label="int_err" ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.${label}($_bat_str): bm_read=$_bm_read; fd=$_bat_fd; bst=$_bat_st; target=$_bat_target_en; soc=$_bat_soc; en=$_bat_en; pwr=$_bat_pwr; volt=$_bat_volt; drc=$_bat_dischg_rc; rc=$rc" return $rc } batdrv_discharge_safetylock () { # check safety lock # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged # ThinkPads implement force-discharge sanely and do not need a safety lock return 1 } batdrv_discharge () { # discharge battery # $1: target soc 0(default)..99 # global params: $_batdrv_plugin, $_bm_dischg, $_bat_str, $_bd_read, $_bf_dischg # rc: 0=done/2=AC detached/3=not emptied/5=cancelled by ^C/6=target soc reached/7=target soc out of bounds/8=force-discharge failed or timeout/9=charge level read error # prerequisite: batdrv_init(), batdrv_select_battery() local label sleeps soc target_soc timeout wt if ! soc="$(batdrv_calc_soc)"; then _bat_dischg_rc=9 echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge($_bat_str).charge_level_unknown; rc=$_bat_dischg_rc" cecho "Error: cannot read charge level of battery ${_bat_str}." 1>&2 return $_bat_dischg_rc fi target_soc="${1:-0}" if is_uint "$target_soc"; then if ! is_within_bounds "$target_soc" 0 99; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_out_of_bounds($_bat_str): target=$target_soc" cprintf "" "Error: target charge level (%s) for battery %s is out of range (0..99).\n" "$target_soc" "$_bat_str" 1>&2 _bat_dischg_rc=7 return $_bat_dischg_rc fi fi ef="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_full")"' / 1000.0 );')" _bat_target_en="$(perl -e 'printf ("%d", '"$ef"' * '"$target_soc"' / 100.0)')" en="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')" if [ "$_bat_target_en" -ge "$en" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_below_soc($_bat_str): target=$target_soc; soc=$soc" cprintf "" "Error: target level (%s%%) is too high compared to the actual charge level (%s%%) of battery %s.\n" "$target_soc" "$soc" "$_bat_str" 1>&2 _bat_dischg_rc=7 return $_bat_dischg_rc fi # start discharge if ! batdrv_write_force_discharge 1; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.force_discharge_timeout($_bat_str)" cecho "Error: discharge $_bat_str failed -- check your hardware (battery, charger)." 1>&2 _bat_dischg_rc=8 return $_bat_dischg_rc fi trap batdrv_cancel_force_discharge INT # enable ^C hook # wait for start == while status not "discharging" -- 15.0 sec timeout printf "Initiating discharge of battery %s " "$_bat_str" 1>&2 timeout="${X_DISCHG_INIT_TIMEOUT:-15}" sleeps="${X_DISCHG_INIT_SLEEP:-1}" wt="$timeout" sleep "$sleeps" while ! batdrv_force_discharge_active "init" && [ "$wt" -gt 0 ] ; do sleep "$sleeps" printf "." 1>&2 wt=$((wt - 1)) done printf "\n" 1>&2 if [ "$_bat_dischg_rc" = "0" ]; then # discharge initiated successfully --> wait for force-discharge reset by ec while batdrv_force_discharge_active "run"; do clear printf "Currently discharging battery %s to %s%% (%s mWh):\n" "$_bat_str" "$target_soc" "$_bat_target_en" 1>&2 # show current battery state printf "voltage = %6s [mV]\n" "$_bat_volt" 1>&2 printf "remaining capacity = %6s [mWh]\n" "$_bat_en" 1>&2 if soc="$(batdrv_calc_soc "%6.1f")"; then printf "remaining percent = %6s [%%]\n" "$soc" 1>&2 else printf "remaining percent = n/a [%%]\n" 1>&2 fi if [ "$_bat_pwr" != "0" ]; then perl -e 'printf ("remaining time = %6d [min]\n", 60.0 * ('"$_bat_en"' - '"$_bat_target_en"') / '"$_bat_pwr"');' 1>&2 printf "power = %6s [mW]\n" "$_bat_pwr" 1>&2 else printf "remaining time = not discharging [min]\n" 1>&2 printf "power = %6s [mW]\n" "$_bat_pwr" 1>&2 fi printf "state = %s\n" "$_bat_st" 1>&2 printf "force discharge = %s\n" "$_bat_fd" 1>&2 printf "Press Ctrl+C to cancel.\n" 1>&2 sleep 5 done # cancel force-discharge in case the EC has not already done so [ "$(batdrv_read_force_discharge 0)" = "1" ] && batdrv_write_force_discharge 0 # check exit cause label="completed" case "$_bat_dischg_rc" in 2) # system on battery, AC power removed label="ac_detached" cecho "Warning: battery $_bat_str discharge has stopped early -- AC/charger removed." 1>&2 ;; 4) # force-discharge reset (4) if [ "$_bat_soc" -gt "$target_soc" ]; then # system on AC: force-discharge ended prematurely with SOC > target label="not_completed" _bat_dischg_rc=3 cecho "Error: battery $_bat_str discharge was aborted early -- check your hardware (battery, charger)." 1>&2 else # battery discharged completely _bat_dischg_rc=0 cecho "Done: battery $_bat_str discharge complete." "success" 1>&2 fi ;; 6) # target_soc reached label="target_soc" _bat_dischg_rc=0 cecho "Done: battery $_bat_str discharge complete." "success" 1>&2 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.${label}($_bat_str): fd=$_bat_fd; bst=$_bat_st; target=$_bat_target_en; soc=$_bat_soc; en=$_bat_en; pwr=$_bat_pwr; volt=$_bat_volt; drc=$_bat_dischg_rc" else # force-discharge timeout _bat_dischg_rc=8 echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.timeout($_bat_str): fd=$_bat_fd; st=$_bat_st; target=$_bat_target_en; soc=$_bat_soc; en=$_bat_en; pwr=$_bat_pwr; volt=$_bat_volt; drc=$_bat_dischg_rc" cecho "Error: discharge $_bat_str timeout after $(((timeout - wt + 1) * sleeps)) second(s) -- check your hardware (battery, charger)" 1>&2 fi trap - INT # remove ^C hook return $_bat_dischg_rc } batdrv_show_battery_data () { # output battery data # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_tpsmapi, $_bm_thresh, $_bd_read, $_bd_readsm, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" local fs="" [ "$_bm_thresh" = "tpsmapi" ] && fs="charge thresholds" if [ "$_bm_dischg" = "tpsmapi" ]; then [ -n "$fs" ] && fs="${fs}, " fs="${fs}chargeonce, discharge, recalibrate" fi if [ -n "$fs" ]; then cprintf "success" "Supported features: %s\n" "$fs" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # ThinkPad-specific battery API case $_tpsmapi in 0) cprintf "success" "* tp-smapi (%s) = active (%s)\n" "$_batdrv_kmod" "$(print_bat_methods_per_driver "tpsmapi")" ;; 32) cprintf "notice" "* tp-smapi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 64) cprintf "err" "* tp-smapi (%s) = inactive (kernel module '%s' load error)\n" "$_batdrv_kmod" "$_batdrv_kmod" ;; 128) cprintf "err" "* tp-smapi (%s) = inactive (kernel module '%s' not installed)\n" "$_batdrv_kmod" "$_batdrv_kmod" ;; 254) cprintf "warning" "* tp-smapi (%s) = inactive (ThinkPad not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* tp-smapi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "tpsmapi" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0/1: 2..96(default)\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 6..100(default)\n" fi printf "\n" local bat local bcnt=0 for bat in $_batteries; do # iterate detected batteries batdrv_select_battery "$bat" case $_bat_idx in 1) printf "+++ ThinkPad Battery Status: %s (Main / Internal)\n" "$bat" ;; 2) printf "+++ ThinkPad Battery Status: %s (Removable)\n" "$bat" ;; 0) printf "+++ ThinkPad Battery Status: %s\n" "$bat" ;; esac # --- show basic data case $_bm_read in natacpi) # no tp-smapi --> use ACPI data print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" ;; # natacpi tpsmapi) # tp-smapi active printparm "%-59s = ##%s##" "$_bd_readsm/manufacturer" printparm "%-59s = ##%s##" "$_bd_readsm/model" if [ "$verbose" = "1" ]; then printparm "%-59s = ##%s##" "$_bd_readsm/serial" "" "" 1 fi printparm "%-59s = ##%s##" "$_bd_readsm/manufacture_date" printparm "%-59s = ##%s##" "$_bd_readsm/first_use_date" printparm "%-59s = ##%6d##" "$_bd_readsm/cycle_count" if [ -f "$_bd_read/temperature" ]; then # shellcheck disable=SC2046 perl -e 'printf ("%-59s = %6d [°C]\n", "'"$_bd_readsm/temperature"'", '$(read_sysval "$_bd_readsm/temperature")' / 1000.0);' fi printparm "%-59s = ##%6d## [mWh]" "$_bd_readsm/design_capacity" printparm "%-59s = ##%6d## [mWh]" "$_bd_readsm/last_full_capacity" printparm "%-59s = ##%6d## [mWh]" "$_bd_readsm/remaining_capacity" if [ "$verbose" -eq 1 ]; then printparm "%-59s = ##%6d## [%%]" "$_bd_readsm/remaining_percent" fi printparm "%-59s = ##%6s## [min]" "$_bd_readsm/remaining_running_time_now" printparm "%-59s = ##%6s## [min]" "$_bd_readsm/remaining_charging_time" printparm "%-59s = ##%6d## [mW]" "$_bd_readsm/power_now" printparm "%-59s = ##%6d## [mW]" "$_bd_readsm/power_avg" print_bat_state "$_bd_readsm/state" printf "\n" if [ "$verbose" -eq 1 ]; then printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/design_voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/group0_voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/group1_voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/group2_voltage" printparm "%-59s = ##%6s## [mV]" "$_bd_readsm/group3_voltage" printf "\n" fi # store values for charge / capacity calculation below _bat_ed=$(read_sysval "$_bd_readsm/design_capacity") _bat_ef=$(read_sysval "$_bd_readsm/last_full_capacity") _bat_en=$(read_sysval "$_bd_readsm/remaining_capacity") _bat_efsum=$((_bat_efsum + _bat_ef)) _bat_ensum=$((_bat_ensum + _bat_en)) ;; # tp-smapi esac # $_bm_read # --- show battery features: thresholds, force-discharge local lf=0 if [ "$_bm_thresh" = "tpsmapi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start 1)" printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" lf=1 fi if [ "$_bm_dischg" = "tpsmapi" ]; then printf "%-59s = %6d\n" "$_bf_dischg" "$(batdrv_read_force_discharge 1)" lf=1 fi [ $lf -gt 0 ] && printf "\n" # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "tpsmapi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output Legacy ThinkPad specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation if [ "$_tpsmapi" = "128" ]; then printf "Install tp-smapi kernel modules for ThinkPad battery thresholds and recalibration\n" fi return 0 } TLP-1.10.1/bat.d/15-lenovo000066400000000000000000000375751517565574500147360ustar00rootroot00000000000000#!/bin/sh # 15-lenovo - Battery Plugin for Lenovo laptops: # 1. Non-ThinkPad series # 2. ThinkBook seriea # Requires the ideapad_laptop driver as of kernel 6.17 providing # /sys/class/power_supply/BAT[01]/charge_types: Standard [Long_Life] # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_LENOVO_MD=/sys/bus/platform/drivers/ideapad_acpi batdrv_is_ideapad () { # check if kernel module loaded # rc: 0=Ideapad, 1=other hardware [ -d $BATDRV_LENOVO_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod, $_batdrv_sim # # 1. check for charge_types kernel api # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # stop threshold --> retval $_bn_stop, # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="lenovo" _batdrv_kmod="ideapad_laptop" # kernel module for natacpi _batdrv_sim=0 # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" _batdrv_sim=1 else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_ideapad; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _batteries="" _bn_stop="" _bt_def_stop=0 # iterate batteries and check for charge_types kernel api local bd bs local done=0 for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 continue fi if readable_sysf "$bd/charge_types" || [ "$_batdrv_sim" = "1" ] ; then # charge_types sysfile is actually readable (or simulated) _natacpi=0 _bm_thresh="natacpi" _bn_stop="charge_types" else # charge_types sysfile not present --> no kernel support _natacpi=128 continue fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # consider legacy kernels < 6.17 if [ $_natacpi -eq 128 ]; then # kernel module detected but charge_types not present --> try 16-lenovo-legacy next echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_charge_types: batteries=$_batteries; natacpi=$_natacpi" return 1 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_stop=$_bn_stop" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries, $_bn_start, $_bn_stop, $_bn_dischg # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1/; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # $_bf_stop: sysfile for stop threshold; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_stop="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # config suffix equals battery name _bt_cfg_bat="$_bat_str" # determine natacpi sysfile _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_stop=$_bf_stop" return 0 } batdrv_read_threshold () { # read and print charge threshold (stop only) # $1: start/stop - unused dummy for plugin api compatibility # $2: 0=api/1=tlp-stat output - unused dummy for plugin api compatibility # global params: $_batdrv_plugin, $_bf_stop # out: threshold 0/1/"" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bct out="" rc=0 if [ "$_bm_thresh" = "natacpi" ]; then bct="$(get_listitem "${X_THRESH_SIMULATE_BCT:-$(read_sysf "$_bf_stop")}")" out="$(bat_bct2bool "$bct")" || rc=4 else # no threshold api rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else rc=4 fi if [ -n "$X_THRESH_SIMULATE_BCT" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold.simulate: bf_stop=$_bf_stop; bct=$bct; out=$out; rc=$rc" else echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold: bf_stop=$_bf_stop; bct=$bct; out=$out; rc=$rc" fi return $rc } batdrv_write_thresholds () { # write charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold -- unused dummy for plugin api compatibility # $2: new stop threshold 0/1/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bat_str, $_bt_cfg_bat, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold out of range or non-numeric/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_stop="${2:-}" local verb="${3:-0}" local old_stop local new_bct old_bct # insert defaults [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure 0 or 1 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 0 1; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop. bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified or invalid (must be 0 or 1). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified or invalid (must be 0 or 1). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: charge type (%s) for battery %s is not specified or invalid (must be 0 or 1). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # read active stop threshold value if ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current charge type for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current charge type for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi old_bct="$(bat_bool2bct "$old_stop")" # write new threshold if [ "$verb" = "2" ]; then printf "Setting temporary charge type for battery %s:\n" "$_bat_str" 1>&2 fi local rc=0 new_bct="$(bat_bool2bct "$new_stop")" if [ "$old_stop" != "$new_stop" ]; then # new threshold differs from effective one --> write it (if not in simulation) if [ "$X_THRESH_SIMULATE_WRITEERR" = "1" ] \ || { [ "$_batdrv_sim" = "0" ] && ! write_sysf "$new_bct" "$_bf_stop"; }; then rc=5 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_bct/$old_stop; new=$new_bct/$new_stop; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " charge type = %s\n" "$new_bct" 1>&2 else cprintf "err" " charge type = %s (Error: write failed)\n" "$new_bct" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing charge type failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_bct/$old_stop; new=$new_bct/$new_stop" if [ "$verb" = "2" ]; then printf " charge type = %s (no change)\n" "$new_bct" 1>&2 fi fi return $rc } batdrv_calc_soc () { # function not implemented as not required return 255 } batdrv_chargeonce () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured charge type from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "DEF" "$stop_thresh" 1 1 fi done return 0 } batdrv_read_force_discharge () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Lenovo laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_natacpi, $_bm_thresh, $_bd_read, $_bf_stop # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" = "natacpi" ]; then cprintf "success" "Supported features: charge threshold\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # vendor specific kernel api case $_natacpi in 0) cprintf "success" "* vendor (%s) = active (charge type)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* vendor (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* vendor (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* vendor (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* vendor (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then printf "Parameter value range:\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 0(Standard)..1(Long_Life) -- charge_types\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then if [ "$_batdrv_sim" = "1" ]; then printf "%-59s = %s\n" "$_bf_stop" "$X_THRESH_SIMULATE_BCT" else printparm "%-59s = ##%s##" "$_bf_stop" "not available" fi printf "\n" fi print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # function not implemented for Lenovo laptops # note: the actual discrete threshold value varies depending on the model and cannot be read in userspace return 1 } batdrv_recommendations () { # no recommendations for Lenovo laptops return 0 } TLP-1.10.1/bat.d/16-lenovo-legacy000066400000000000000000000347441517565574500161740ustar00rootroot00000000000000#!/bin/sh # 16-lenovo-legacy - Battery Plugin for Lenovo laptops: # 1. Non-ThinkPad series # 2. ThinkBook series # Requires the ideapad_laptop driver as of kernel 4.14 providing # /sys/bus/platform/drivers/ideapad_acpi/VPC2004:00/conservation_mode # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Plugin API functions readonly BATDRV_LENOVO_CMODE="${BATDRV_LENOVO_MD}/VPC2004:00/conservation_mode" batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $batdrv_kmod # # 1. check for vendor specific legacy kernel api # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 4. define conservation mode config, sysfile and default # config suffix (BAT0) --> retval $_bt_cfg_bat, # sysfile --> retval $_bf_stop, # default --> retval $_bt_def_stop; _batdrv_plugin="lenovo-legacy" _batdrv_kmod="ideapad_laptop" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_ideapad; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _batteries="" _bt_cfg_bat="BAT0" # all batteries share the BAT0 config parameter _bf_stop="" _bt_def_stop=0 # iterate batteries local bd bs for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" fi done # check for vendor specific kernel api if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 elif [ -f "$BATDRV_LENOVO_CMODE" ] && readable_sysf "$BATDRV_LENOVO_CMODE"; then # conservation_mode sysfile exists and is actually readable _natacpi=0 _bm_thresh="natacpi" _bf_stop="$BATDRV_LENOVO_CMODE" elif [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then # simulate api _natacpi=0 _bm_thresh="natacpi" _bf_stop="$BATDRV_LENOVO_CMODE" else # conservation_mode sysfile not present -> laptop not supported _natacpi=254 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bf_stop=$_bf_stop" return 0 } batdrv_select_battery () { # determine battery acpidir # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1; # $_bd_read: directory with battery data sysfiles; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bd_read="" # no directory local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read;" return 0 } batdrv_read_threshold () { # read and print charge threshold (stop only) # $1: start/stop - unused dummy for plugin api compatibility # $2: 0=api/1=tlp-stat output - unused dummy for plugin api compatibility # global params: $_batdrv_plugin, $_bf_stop # out: threshold 0/1/"" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local out="" rc=0 out="$X_THRESH_SIMULATE_STOP" if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold.simulate: bf_stop=$_bf_stop; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then out=$(read_sysf "$_bf_stop") || rc=4 else # no threshold api rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold: bf_stop=$_bf_stop; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold -- unused dummy for plugin api compatibility # $2: new stop threshold 0/1/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bat_str, $_bt_cfg_bat, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold out of range or non-numeric/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_stop="${2:-}" local verb="${3:-0}" local old_stop # insert defaults [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure 0 or 1 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 0 1; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop. bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": conservation mode not specified or invalid (must be 0 or 1). Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": conservation mode not specified or invalid (must be 0 or 1). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: conservation mode (%s) not specified or invalid (must be 0 or 1). Aborted.\n" "$new_stop" 1>&2 fi ;; esac return 2 fi # read active stop threshold value if ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current conservation mode. Skipped." ;; 2) cprintf "" "Error: could not read current conservation mode. Aborted.\n" 1>&2 ;; esac return 4 fi # write new threshold if [ "$verb" = "2" ]; then printf "Setting temporary charge threshold for all batteries:\n" 1>&2 fi local rc=0 if [ "$old_stop" != "$new_stop" ]; then # new threshold differs from effective one --> write it write_sysf "$new_stop" "$_bf_stop" || rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " conservation mode = %d\n" "$new_stop" 1>&2 else cprintf "err" " conservation mode = %d (Error: write failed)\n" "$new_stop" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing conservation mode failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop" if [ "$verb" = "2" ]; then printf " conservation mode = %d (no change)\n" "$new_stop" 1>&2 fi fi return $rc } batdrv_calc_soc () { # function not implemented as not required return 255 } batdrv_chargeonce () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured conservation mode from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local stop_thresh if batdrv_select_battery "DEF"; then eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "DEF" "$stop_thresh" 1 1 fi return 0 } batdrv_read_force_discharge () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Lenovo laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Lenovo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_natacpi, $_bm_thresh, $_bd_read, $_bf_stop # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" = "natacpi" ]; then cprintf "success" "Supported features: charge threshold\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # vendor specific kernel api case $_natacpi in 0) cprintf "success" "* vendor (%s) = active (charge threshold)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* vendor (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* vendor (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* vendor (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* vendor (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then local th sfx= printf "Parameter value range:\n" printf "* STOP_CHARGE_THRESH_BAT0: 0(off), 1(on) -- conservation_mode\n\n" if th=$(batdrv_read_threshold stop 1); then case $th in 0) sfx=" (off)" ;; 1) sfx=" (on)" ;; *) sfx=" (invalid)" ;; esac printf "%-59s = %d%s\n" "$_bf_stop" "$th" "$sfx" else printf "%-59s = %s\n" "$_bf_stop" "(not available)" fi fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # function not implemented for Lenovo laptops # note: the actual discrete threshold value varies depending on the model and cannot be read in userspace return 1 } batdrv_recommendations () { # no recommendations for Lenovo laptops return 0 } TLP-1.10.1/bat.d/20-huawei000066400000000000000000000412641517565574500147000ustar00rootroot00000000000000#!/bin/sh # 20-huawei - Battery Plugin for Huawei MateBooks w/ huawei_wmi driver # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_HUAWEI_MD=/sys/devices/platform/huawei-wmi batdrv_is_huawei () { # check if kernel module loaded # rc: 0=Huawei, 1=other hardware [ -d $BATDRV_HUAWEI_MD ] } # --- Plugin API functions readonly BATDRV_HUAWEI_THRESH="${BATDRV_HUAWEI_MD}/charge_control_thresholds" batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for native kernel acpi (Linux 5.4 or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 4. determine charge threshold config, sysfile and defaults # sysfile (start and stop threshold) --> retval $_bf_start_stop, # start default --> retval $_bt_def_start, # stop default --> retval $_bt_def_stop; _batdrv_plugin="huawei" _batdrv_kmod="huawei_wmi" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_huawei; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _batteries="" _bt_def_start=0 _bt_def_stop=100 # iterate batteries local bd bs for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" fi done # check for vendor specific kernel api if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 elif [ -f "$BATDRV_HUAWEI_THRESH" ] && readable_sysf "$BATDRV_HUAWEI_THRESH"; then # sysfile exists and is actually readable _natacpi=0 _bm_thresh="natacpi" _bf_start_stop="$BATDRV_HUAWEI_THRESH" elif [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then # simulate api _natacpi=0 _bm_thresh="natacpi" _bf_start_stop="$BATDRV_HUAWEI_THRESH" else # nothing detected _natacpi=254 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; start_stop=$_bf_start_stop;" return 0 } batdrv_select_battery () { # determine battery acpidir # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bd_read="" # no directory local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # all batteries share the BAT0 config parameters _bt_cfg_bat="BAT0" # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read;" return 0 } batdrv_read_threshold () { # read and print both charge thresholds # $1: start/stop - unused dummy for plugin api compatibility # $2: 0=api/1=tlp-stat output - unused dummy for plugin api compatibility # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start_stop # out: both thresholds 0..100 separated with a blank/"" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local rc=0 local out="$X_THRESH_SIMULATE_START $X_THRESH_SIMULATE_STOP" if [ "$out" != " " ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold.simulate: bm_thresh=$_bm_thresh; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then out=$(read_sysf "$_bf_start_stop") || rc=4 else rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold: bm_thresh=$_bm_thresh; bf_start_stop=$_bf_start_stop; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold 0(off)..100/DEF(default) # $2: new stop threshold 0(off)..100(off)/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bt_cfg_bat, $_bf_start_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start=${1:-} local new_stop=${2:-} local verb=${3:-0} # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: cfg=$_bt_cfg_bat" return 1 fi # start: check for 3 digits max, ensure min 0 / max 99 if ! is_uint "$new_start" 3 || \ ! is_within_bounds "$new_start" 0 99; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (0..99). Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (0..99). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2 else cprintf "" "Error: start charge threshold (%s) is not specified, invalid or out of range (0..99). Aborted.\n" "$new_start" 1>&2 fi ;; esac return 2 fi # stop: check for 3 digits max, ensure min 1 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 1 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (1..100). Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (1..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) is not specified, invalid or out of range (1..100). Aborted.\n" "$new_stop" 1>&2 fi ;; esac return 2 fi # check start <= stop if [ "$new_start" -gt "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} > STOP_CHARGE_THRESH_${_bt_cfg_bat}. Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s > STOP_CHARGE_THRESH_%s. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold > stop threshold. Aborted.\n" 1>&2 fi ;; esac return 3 fi # write new thresholds if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds:\n" 1>&2 fi local rc=0 local new_threshs="$new_start $new_stop" write_sysf "$new_threshs" "$_bf_start_stop" || rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4): cfg=$_bt_cfg_bat; new=$new_threshs; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " %s = %d, %s = %d\n" start "$new_start" stop "$new_stop" 1>&2 else cprintf "err" " %s = %d, %s = %d (Error: write failed)\n" start "$new_start" stop "$new_stop" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing charge thresholds failed." fi ;; esac if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented for Huawei MateBooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local start_thresh stop_thresh if batdrv_select_battery "DEF"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi return 0 } batdrv_read_force_discharge () { # function not implemented for Huawei MateBooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Huawei MateBooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Huawei MateBooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Huawei MateBooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Huawei MateBooks # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Huawei MateBooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_natacpi, $_bd_read, $_bf_start_stop # prerequisite: batdrv_init() local verbose="${1:-0}" local threshs printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" = "natacpi" ]; then cprintf "success" "Supported features: charge thresholds\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* vendor (%s) = active (charge thresholds)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* vendor (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* vendor (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* vendor (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* vendor (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0: 0(default)..99\n" printf "* STOP_CHARGE_THRESH_BAT0: 1..100(default)\n\n" if threshs=$(batdrv_read_threshold); then case "$threshs" in "0 0"|"0 100") printf "%-59s = %s [%%] (disabled)\n" "$_bf_start_stop" "$threshs" ;; *) printf "%-59s = %s [%%]\n" "$_bf_start_stop" "$threshs" ;; esac else printf "%-59s = %s\n" "$_bf_start_stop" "(not available)" fi fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold | awk '{print $2; }')" if is_uint "$stop" 3 && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output Huawei MateBook specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/25-msi000066400000000000000000000450231517565574500142100ustar00rootroot00000000000000#!/bin/sh # 55-msi - Battery Plugin for MSI laptops w/ msi_ec driver # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_MSI_MD=/sys/module/msi_ec batdrv_is_msi () { # check if vendor specific kernel module is loaded # rc: 0=ok, 1=other hardware [ -d $BATDRV_MSI_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for native kernel acpi (Linux 5.4 or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="msi" _batdrv_kmod="msi_ec" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_msi; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="" _bn_stop="" _batteries="" # vendor defaults _bt_def_start=90 _bt_def_stop=100 # iterate batteries and check for native kernel ACPI local bd bs local done=0 for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 continue fi if [ -f "$bd/charge_control_start_threshold" ] \ && [ -f "$bd/charge_control_end_threshold" ]; then # threshold sysfiles exist _bn_start="charge_control_start_threshold" _bn_stop="charge_control_end_threshold" _natacpi=254 else # nothing detected _natacpi=254 continue fi if readable_sysf "$bd/$_bn_start" \ && readable_sysf "$bd/$_bn_stop"; then # threshold sysfiles are actually readable _natacpi=0 _bm_thresh="natacpi" fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: BAT0/BAT1/DEF # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1; # $_bd_read: directory with battery data sysfiles; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bf_start: sysfile for stop threshold; # $_bf_stop: sysfile for stop threshold; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_start="" _bf_stop="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # config suffix equals battery name _bt_cfg_bat="$_bat_str" # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_start="$ACPIBATDIR/$_bat_str/$_bn_start" _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_start=$_bf_start; bf_stop=$_bf_stop" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case $1 in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then # read threshold from sysfile case $1 in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$2" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # note: the MSI hardware allows to choose only the stop threshold value, start is always = stop - 10 # $1: new start threshold 0..90/DEF(default) # $2: new stop threshold 10..100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start=${1:-} local new_stop=${2:-} local verb=${3:-0} local old_stop local nom_start # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure min 10 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 10 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (10..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (10..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s is not specified, invalid or out of range (10..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # hardware constraint: start = stop - 10 nom_start=$((new_stop - 10)) # start: silently ignore non-numeric or invalid threshold, substitute it with stop - 10 if ! is_uint "$new_start" 3 || [ "$new_start" -ne "$nom_start" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat; nom=$nom_start" new_start="$nom_start" fi # read active stop threshold value if ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current stop charge threshold for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current stop charge threshold for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # write new thresholds if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds for battery %s:\n" "$_bat_str" 1>&2 fi local rc=0 steprc if [ "$old_stop" != "$new_stop" ]; then # new stop threshold differs from effective one --> write it write_sysf "$new_stop" "$_bf_stop" steprc=$?; [ $steprc -ne 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).stop.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then printf " %-5s = %3d\n" "stop" "$new_stop" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "stop" "$new_stop" 1>&2 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing stop charge threshold for battery $_bat_str failed." fi ;; esac # write start threshold only when simulating MSI hardware if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then write_sysf "$new_start" "$_bf_start" fi if [ "$verb" = "2" ]; then printf " %-5s = %3d (due to hardware constraint)\n" "start" "$new_start" 1>&2 fi else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).stop.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "stop" "$new_stop" 1>&2 printf " %-5s = %3d (no change)\n" "start" "$new_start" 1>&2 fi fi if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented for MSI laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat start_thresh stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi done return 0 } batdrv_read_force_discharge () { # function not implemented for MSI laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for MSI laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for MSI laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for MSI laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Huawei MateBooks # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for MSI laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bm_thresh, $_bd_read, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" != "none" ]; then cprintf "success" "Supported features: charge thresholds\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge thresholds)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" != "none" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0/1: don't care (hardware enforces stop - 10)\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 10..100(default)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start 1)" printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" printf "\n" fi # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output MSI laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/30-samsung000066400000000000000000000363271517565574500151000ustar00rootroot00000000000000#!/bin/sh # samsung - Battery Plugin for Samsung laptops w/ samsung_laptop driver # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_SAMSUNG_MD=/sys/devices/platform/samsung batdrv_is_samsung () { # check if kernel module loaded # rc: 0=Samsung, 1=other hardware [ -d $BATDRV_SAMSUNG_MD ] } # --- Plugin API functions readonly BATDRV_SAMSUNG_BLE="${BATDRV_SAMSUNG_MD}/battery_life_extender" batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $batdrv_kmod # # 1. check for vendor specific kernel api # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 4. define battery life extender config, sysfile and default # config suffix (BAT0) --> retval $_bt_cfg_bat, # sysfile --> retval $_bf_stop, # default --> retval $_bt_def_stop; _batdrv_plugin="samsung" _batdrv_kmod="samsung_laptop" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_samsung; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _batteries="" _bt_cfg_bat="BAT0" # all batteries share the BAT0 config parameter _bf_stop="" _bt_def_stop=0 # iterate batteries local bd bs for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" fi done # check for vendor specific kernel api if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 elif [ -f "$BATDRV_SAMSUNG_BLE" ] && readable_sysf "$BATDRV_SAMSUNG_BLE"; then # sysfile exists and is actually readable _natacpi=0 _bm_thresh="natacpi" _bf_stop="$BATDRV_SAMSUNG_BLE" elif [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then # simulate api _natacpi=0 _bm_thresh="natacpi" _bf_stop="$BATDRV_SAMSUNG_BLE" else # nothing detected _natacpi=254 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bf_stop=$_bf_stop" return 0 } batdrv_select_battery () { # determine battery acpidir # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries # # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1; # $_bd_read: directory with battery data sysfiles; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bd_read="" # no directory local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; bd_read=$_bd_read;" return 0 } batdrv_read_threshold () { # read and print charge threshold (stop only) # $1: start/stop - unused dummy for plugin api compatibility # $2: 0=api/1=tlp-stat output - unused dummy for plugin api compatibility # global params: $_batdrv_plugin, $_bm_thresh, $_bf_stop # out: threshold 0/1/"" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local out="" rc=0 out="$X_THRESH_SIMULATE_STOP" if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold.simulate: bf_stop=$_bf_stop; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then out=$(read_sysf "$_bf_stop") || rc=4 else # no threshold api rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold: bf_stop=$_bf_stop; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold -- unused dummy for plugin api compatibility # $2: new stop threshold 0/1/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bat_str, $_bt_cfg_bat, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold out of range or non-numeric/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_stop=${2:-} local verb=${3:-0} local old_stop # insert defaults [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure 0 or 1 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 0 1; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": life extender not specified or invalid (must be 0 or 1). Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": life extender not specified or invalid (must be 0 or 1). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: life extender (%s) not specified or invalid (must be 0 or 1). Aborted.\n" "$new_stop" 1>&2 fi ;; esac return 2 fi # read active stop threshold value if ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current life extender. Skipped." ;; 2) cprintf "" "Error: could not read current life extender. Aborted.\n" 1>&2 ;; esac return 4 fi # write new threshold if [ "$verb" = "2" ]; then printf "Setting temporary charge threshold for all batteries:\n" 1>&2 fi local rc=0 if [ "$old_stop" != "$new_stop" ]; then # new threshold differs from effective one --> write it write_sysf "$new_stop" "$_bf_stop" || rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " life extender = %d\n" "$new_stop" 1>&2 else cprintf "err" " life extender = %d (Error: write failed)\n" "$new_stop" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing life extender failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop" if [ "$verb" = "2" ]; then printf " life extender = %d (no change)\n" "$new_stop" 1>&2 fi fi if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented for Samsung laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured battery life extender from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local stop_thresh if batdrv_select_battery "DEF"; then eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "DEF" "$stop_thresh" 1 1 fi return 0 } batdrv_read_force_discharge () { # function not implemented for Samsung laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Samsung laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Samsung laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Samsung laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Huawei MateBooks # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Samsung laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bm_thresh, $_natacpi, $_bd_read, $_bf_stop # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" = "natacpi" ]; then cprintf "success" "Supported features: charge threshold\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # vendor specific kernel api case $_natacpi in 0) cprintf "success" "* vendor (%s) = active (charge threshold)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* vendor (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* vendor (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* vendor (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* vendor (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then local th sfx= printf "Parameter value range:\n" printf "* STOP_CHARGE_THRESH_BAT0: 0(off), 1(on) -- battery life extender\n\n" if th=$(batdrv_read_threshold stop 1); then case $th in 0) sfx=" (100%)" ;; 1) sfx=" (80%)" ;; *) sfx=" (invalid)";; esac printf "%-59s = %d%s\n" "$_bf_stop" "$th" "$sfx" else printf "%-59s = %s\n" "$_bf_stop" "(not available)" fi fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # if life extender is enabled then check if battery charge level (SOC) is greater than 80% (fixed threshold) # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ "$stop" = "1" ] && [ "$soc" -gt 80 ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output Samsung laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/35-lg000066400000000000000000000412621517565574500140240ustar00rootroot00000000000000#!/bin/sh # lg - Battery Plugin for LG laptops # # Requires the lg_laptop module as of Linux 5.18 providing the following sysfs node: # * /sys/class/power_supply/{CMB[01],BAT[01]}/charge_control_end_threshold: 80, 100 # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_LG_MD=/sys/devices/platform/lg-laptop batdrv_is_lg () { # check if kernel module loaded # rc: 0=LG, 1=other hardware [ -d $BATDRV_LG_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $batdrv_kmod # # 1. check for vendor specific kernel api # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 4. define battery care limit config, sysfile and default # config suffix (BAT0) --> retval $_bt_cfg_bat, # sysfile --> retval $_bf_stop, # default --> retval $_bt_def_stop; _batdrv_plugin="lg" _batdrv_kmod="lg_laptop" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_lg; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _batteries="" _bt_cfg_bat="BAT0" # all batteries share the BAT0 config parameter _bf_stop="" _bt_def_stop=100 # iterate batteries local bd bs sp local done=0 for bd in "$ACPIBATDIR"/CMB[01] "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 elif [ -f "$bd/charge_control_end_threshold" ]; then # sysfile for threshold exists (kernel 5.18 and newer) if sp="$(read_sysf "$bd/charge_control_end_threshold")"; then # sysfile is actually readable if [ "$sp" != "0" ]; then # threshold is non-zero _natacpi=0 _bm_thresh="natacpi" _bn_stop="charge_control_end_threshold" else # zero means laptop not supported via the kernel driver _natacpi=254 fi fi elif [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then # simulate api _natacpi=0 _bm_thresh="natacpi" _bn_stop="charge_control_end_threshold" fi fi done # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bf_stop=$_bf_stop" return 0 } batdrv_select_battery () { # determine battery acpidir # $1: BAT0/BAT1/CMB0/CMB1/DEF # global params: $_batdrv_plugin, $_batteries, $_bm_thresh, $_bn_stop # # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1/CMB0/CMB1; # $_bf_stop: sysfile for stop threshold # $_bd_read: directory with battery data sysfiles; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bd_read="" # no directory local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # determine natacpi sysfile _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; bd_read=$_bd_read; bf_stop=$_bf_stop" return 0 } batdrv_read_threshold () { # read and print charge threshold (stop only) # $1: start/stop - unused dummy for plugin api compatibility # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_stop # out: threshold 80,100/"" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local out="" rc=0 out="$X_THRESH_SIMULATE_STOP" if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold.simulate: bf_stop=$_bf_stop; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then if ! out=$(read_sysf "$_bf_stop"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$2" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold: bf_stop=$_bf_stop; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold -- unused dummy for plugin api compatibility # $2: new stop threshold 80/100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bat_str, $_bt_cfg_bat, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold out of range or non-numeric/ # 4=threshold read error/ # 5=threshold write error/ # 6=threshold write discarded by kernel or firmware # prerequisite: batdrv_init(), batdrv_select_battery() local new_stop=${2:-} local verb=${3:-0} local old_stop # insert defaults [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure 80 or 100 if ! is_uint "$new_stop" 3 || ! wordinlist "$new_stop" "80 100"; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified or invalid (must be 80 or 100). Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified or invalid (must be 80 or 100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s not specified or invalid (must be 80 or 100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # read active stop threshold value if ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current stop charge threshold for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current stop charge threshold for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # write new threshold if [ "$verb" = "2" ]; then printf "Setting temporary charge threshold for battery %s:\n" "$_bat_str" 1>&2 fi local rc=0 if [ "$old_stop" != "$new_stop" ]; then # new threshold differs from effective one --> write it if write_sysf "$new_stop" "$_bf_stop"; then if [ "$(read_sysf "$_bf_stop")" != "$new_stop" ]; then # write discarded by kernel or firmware rc=6 fi else # write error rc=5 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " %-5s = %3d\n" "stop" "$new_stop" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "stop" "$new_stop" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing stop charge threshold for battery $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "stop" "$new_stop" 1>&2 fi fi if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented for LG laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat stop_thresh for bat in CMB0 CMB1 BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "DEF" "$stop_thresh" 1 1; rc=$? break # one battery only fi done return 0 } batdrv_read_force_discharge () { # function not implemented for LG laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for LG laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for LG laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for LG laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for LG laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for LG laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bm_thresh, $_natacpi, $_bd_read, $_bf_stop # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" = "natacpi" ]; then cprintf "success" "Supported features: charge threshold\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # vendor specific kernel api case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge threshold)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then printf "Parameter value range:\n" printf "* STOP_CHARGE_THRESH_BAT0: 80(on), 100(off)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" printf "\n" fi # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output LG laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/40-sony000066400000000000000000000364241517565574500144120ustar00rootroot00000000000000#!/bin/sh # sony - Battery Plugin for Sony laptops w/ sony_laptop driver # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_SONY_MD=/sys/devices/platform/sony-laptop batdrv_is_sony () { # check if kernel module loaded # rc: 0=Sony, 1=other hardware [ -d $BATDRV_SONY_MD ] } # --- Plugin API functions readonly BATDRV_SONY_BLE="${BATDRV_SONY_MD}/battery_care_limiter" batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for vendor specific kernel api # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 4. define battery care limiter config, sysfile and default # conig suffix (BAT0 --> retval $_bt_cfg_bat, # sysfile --> retval $_bf_stop, # default --> retval $_bt_def_stop; _batdrv_plugin="sony" _batdrv_kmod="sony_laptop" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_sony; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _batteries="" _bt_cfg_bat="BAT0" # all batteries share the BAT0 config parameter _bf_stop="" _bt_def_stop=100 # iterate batteries local bd bs for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" fi done # check for vendor specific kernel api if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 elif [ -f "$BATDRV_SONY_BLE" ] && readable_sysf "$BATDRV_SONY_BLE"; then # sysfile exists and is actually readable _natacpi=0 _bm_thresh="natacpi" _bf_stop="$BATDRV_SONY_BLE" elif [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then # simulate api _natacpi=0 _bm_thresh="natacpi" _bf_stop="$BATDRV_SONY_BLE" else # nothing detected _natacpi=254 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bf_stop=$_bf_stop" return 0 } batdrv_select_battery () { # determine battery acpidir # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries # # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1; # $_bd_read: directory with battery data sysfiles; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bd_read="" # no directory local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # determine battery directory _bd_read="$ACPIBATDIR/$_bat_str" echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; bd_read=$_bd_read;" return 0 } batdrv_read_threshold () { # read and print charge threshold (stop only) # $1: start/stop - unused dummy for plugin api compatibility # $2: 0=api/1=tlp-stat output - unused dummy for plugin api compatibility # global params: $_batdrv_plugin, $_bm_thresh, $_bf_stop # out: threshold 50/80/100/"" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local out="" rc=0 out="$X_THRESH_SIMULATE_STOP" if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold.simulate: bf_stop=$_bf_stop; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then out=$(read_sysf "$_bf_stop") || rc=4 else # no threshold api rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold: bf_stop=$_bf_stop; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold -- unused dummy for plugin api compatibility # $2: new stop threshold 50/80/100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bat_str, $_bt_cfg_bat, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold out of range or non-numeric/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_stop=${2:-} local verb=${3:-0} local old_stop local sony_stop # insert defaults [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure 50, 80 or 100 if ! is_uint "$new_stop" 3 || \ ! wordinlist "$new_stop" "50 80 100"; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": care limiter not specified or invalid (must be 50, 80 or 100). Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": care limiter not specified or invalid (must be 50, 80 or 100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: care limiter (%s) not specified or invalid (must be 50, 80 or 100). Aborted.\n" "$new_stop" 1>&2 fi ;; esac return 2 fi # convention to disable the stop threshold is 100 --> translate to 0 for Sony if [ "$new_stop" = "100" ]; then sony_stop="0" else sony_stop="$new_stop" fi # read active stop threshold value if ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current care limiter. Skipped." ;; 2) cprintf "" "Error: could not read current care limiter. Aborted.\n" 1>&2 ;; esac return 4 fi # write new threshold if [ "$verb" = "2" ]; then printf "Setting temporary charge threshold for all batteries:\n" 1>&2 fi local rc=0 if [ "$old_stop" != "$sony_stop" ]; then # new threshold differs from effective one --> write it write_sysf "$sony_stop" "$_bf_stop" || rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$sony_stop; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " care limiter = %3d\n" "$new_stop" 1>&2 else cprintf "err" " care limiter = %3d (Error: write failed)\n" "$new_stop" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing care limiter failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$sony_stop" if [ "$verb" = "2" ]; then printf " care limiter = %3d (no change)\n" "$new_stop" 1>&2 fi fi if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented for Sony laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured battery life extender from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local stop_thresh if batdrv_select_battery "DEF"; then eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "DEF" "$stop_thresh" 1 1 fi return 0 } batdrv_read_force_discharge () { # function not implemented for Sony laptops echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Sony laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Sony laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Sony laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Sony laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Sony laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bm_thresh, $_natacpi, $_bd_read, $_bf_stop # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" = "natacpi" ]; then cprintf "success" "Supported features: charge threshold\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # vendor specific kernel api case $_natacpi in 0) cprintf "success" "* vendor (%s) = active (charge threshold)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* vendor (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* vendor (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* vendor (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* vendor (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then local sp printf "Parameter value range:\n" printf "* STOP_CHARGE_THRESH_BAT0: 50, 80, 100(off) -- battery care limiter\n\n" if sp=$(batdrv_read_threshold stop 1); then if [ "$sp" = "0" ]; then printf "%-59s = %6d (100) [%%]\n" "$_bf_stop" "$sp" else printf "%-59s = %6d [%%]\n" "$_bf_stop" "$sp" fi else printf "%-59s = %s [%%]\n" "$_bf_stop" "(not available)" fi fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output Sony laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/45-system76000066400000000000000000000507141517565574500151260ustar00rootroot00000000000000#!/bin/sh # 45-system76 - Battery Plugin for System76 laptops # w/ open source EC firmware and system76_acpi driver # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_SYSTEM76_MD=/sys/module/system76_acpi batdrv_is_system76 () { # check if system76 specific kernel module is loaded # rc: 0=ok, 1=other hardware [ -d $BATDRV_SYSTEM76_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for native kernel acpi (Linux 5.4 or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="system76" _batdrv_kmod="system76_acpi" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_system76; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="" _bn_stop="" _batteries="" # vendor defaults _bt_def_start=90 _bt_def_stop=100 # iterate batteries and check for native kernel ACPI local bd bs local done=0 # System76 EC only supports one battery # shellcheck disable=SC2043 for bd in "$ACPIBATDIR"/BAT0; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 continue fi if [ -f "$bd/charge_control_start_threshold" ] \ && [ -f "$bd/charge_control_end_threshold" ]; then # threshold sysfiles exist _bn_start="charge_control_start_threshold" _bn_stop="charge_control_end_threshold" _natacpi=254 else # nothing detected _natacpi=254 continue fi if readable_sysf "$bd/$_bn_start" \ && readable_sysf "$bd/$_bn_stop"; then # threshold sysfiles are actually readable _natacpi=0 _bm_thresh="natacpi" fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: BAT0/DEF # # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0 # $_bt_cfg_bat: config suffix (BAT0); # $_bd_read: directory with battery data sysfiles; # $_bf_start: sysfile for stop threshold; # $_bf_stop: sysfile for stop threshold; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bd_read="" # no directory _bf_start="" _bf_stop="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$1" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # System76 EC only supports one battery _bt_cfg_bat="BAT0" # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_start="$ACPIBATDIR/$_bat_str/$_bn_start" _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_start=$_bf_start; bf_stop=$_bf_stop" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case $1 in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then # read threshold from sysfile case $1 in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$2" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold 0(disabled)..99/DEF(default) # $2: new stop threshold 1..100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start=${1:-} local new_stop=${2:-} local verb=${3:-0} local old_start old_stop # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # start: check for 3 digits max, ensure min 0 / max 99 if ! is_uint "$new_start" 3 || \ ! is_within_bounds "$new_start" 0 99; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (0..99). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (0..99). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2 else cprintf "" "Error: start charge threshold (%s) for battery %s is not specified, invalid or out of range (0..99). Aborted.\n" "$new_start" "$_bat_str" 1>&2 fi ;; esac return 2 fi # stop: check for 3 digits max, ensure min 1 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 1 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (1..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (1..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s is not specified, invalid or out of range (1..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # check start < stop if [ "$new_start" -ge "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} >= STOP_CHARGE_THRESH_${_bt_cfg_bat}. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s >= STOP_CHARGE_THRESH_%s. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold >= stop threshold for battery %s. Aborted.\n" "$_bat_str" 1>&2 fi ;; esac return 3 fi # read active threshold values if ! old_start=$(batdrv_read_threshold start 0) || \ ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current charge threshold(s) for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current charge threshold(s) for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # determine write sequence too meet boundary condition start < stop # disclaimer: the driver doesn't enforce it but we don't know about the # firmware and it's reasonable anyway local rc=0 steprc tseq if [ "$new_start" -ge "$old_stop" ]; then tseq="stop start" else tseq="start stop" fi # write new thresholds in determined sequence if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds for battery %s:\n" "$_bat_str" 1>&2 fi for step in $tseq; do local old_thresh new_thresh steprc case $step in start) old_thresh=$old_start new_thresh=$new_start ;; stop) old_thresh=$old_stop new_thresh=$new_stop ;; esac if [ "$old_thresh" != "$new_thresh" ]; then # new threshold differs from effective one --> write it case $step in start) write_sysf "$new_thresh" "$_bf_start" ;; stop) write_sysf "$new_thresh" "$_bf_stop" ;; esac steprc=$?; [ $steprc -ne 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then printf " %-5s = %3d\n" "$step" "$new_thresh" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing $step charge threshold for battery $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; old=$old_thresh; new=$new_thresh" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2 fi fi done # for step if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local start_thresh stop_thresh if batdrv_select_battery "DEF"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi return 0 } batdrv_read_force_discharge () { # function not implemented for System76 laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for System76 laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for System76 laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for System76 laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for System76 laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for System76 laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" != "none" ]; then cprintf "success" "Supported features: charge thresholds\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge thresholds)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0: 0(off)..90(default)..99\n" printf "* STOP_CHARGE_THRESH_BAT0: 1..100(default)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start 1)" printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" printf "\n" fi # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output System76 laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/50-toshiba000066400000000000000000000405301517565574500150450ustar00rootroot00000000000000#!/bin/sh # 50-toshiba - Battery Plugin for Toshiba (now Dynabook) laptops # w/ toshiba_acpi driver # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_TOSHIBA_MD=/sys/module/toshiba_acpi batdrv_is_toshiba () { # check if kernel module loaded # rc: 0=Toshiba, 1=other hardware [ -d $BATDRV_TOSHIBA_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for native kernel acpi (Linux 6.0 or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basename for natacpi # stop threshold --> retval $_bn_stop, # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="toshiba" _batdrv_kmod="toshiba_acpi" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_toshiba; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_stop="" _batteries="" _bt_def_stop=100 # iterate batteries and check for native kernel ACPI local bd bs local done=0 for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 elif [ -f "$bd/charge_control_end_threshold" ] && readable_sysf "$bd/charge_control_end_threshold"; then # sysfile for stop threshold exists and is actually readable _natacpi=0 _bm_thresh="natacpi" _bn_stop="charge_control_end_threshold" elif [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then # simulate api _natacpi=0 _bm_thresh="natacpi" _bn_stop="charge_control_end_threshold" else # nothing detected _natacpi=254 fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; stop=$_bn_stop;" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfile # $1: BAT0/BAT1/DEF # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BATC/BATT/BAT1; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # $_bf_stop: sysfile for stop threshold; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_stop="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # config suffix equals battery name _bt_cfg_bat="$_bat_str" # determine natacpi sysfile _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_stop=$_bf_stop" return 0 } batdrv_read_threshold () { # read and print charge threshold (stop only) # $1: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_stop # out: # - api: 80,100/"" on error # - tlp-stat: 80,100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local out rc=0 out="$X_THRESH_SIMULATE_STOP" if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1).simulate: bm_thresh=$_bm_thresh; bf_stop=$_bf_stop; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then if ! out=$(read_sysf "$_bf_stop"); then # not readable/non-existent if [ "$1" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$1" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$1" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1): bm_thresh=$_bm_thresh; bf_stop=$_bf_stop; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold -- unused dummy for plugin api compatibility # $2: new stop threshold 80,100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold out of range or non-numeric/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_stop=${2:-} local verb=${3:-0} local old_stop # insert defaults [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds if [ -n "$4" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure 80 or 100 if ! is_uint "$new_stop" 3 || ! wordinlist "$new_stop" "80 100"; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified or invalid (must be 80 or 100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified or invalid (must be 80 or 100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s is not specified or invalid (must be 80 or 100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # read active threshold value if ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current stop charge threshold for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current stop charge threshold for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # write new threshold if [ "$verb" = "2" ]; then printf "Setting temporary charge threshold for battery %s:\n" "$_bat_str" 1>&2 fi local rc=0 if [ "$old_stop" != "$new_stop" ]; then # new threshold differs from effective one --> write it write_sysf "$new_stop" "$_bf_stop" || rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " %-5s = %3d\n" "stop" "$new_stop" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "stop" "$new_stop" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing stop charge threshold for battery $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "stop" "$new_stop" 1>&2 fi fi if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented for Toshiba / Dynabook laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "DEF" "$stop_thresh" 1 1; rc=$? fi done return 0 } batdrv_read_force_discharge () { # function not implemented for Toshiba / Dynabook laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Toshiba / Dynabook laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Toshiba / Dynabook laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Toshiba / Dynabook laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Toshiba / Dynabook laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Toshiba / Dynabook laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_stop # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" = "natacpi" ]; then cprintf "success" "Supported features: charge threshold\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge threshold)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then printf "Parameter value range:\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 80(on), 100(off)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold 1)" printf "\n" fi # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output Toshiba / Dynabook laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/55-cros-ec000066400000000000000000001177151517565574500147660ustar00rootroot00000000000000#!/bin/sh # 55-cros-ec - Battery Plugin for laptops with ChromeOS EC: # 1. Framework 13/16 AMD or Intel # 2. Chromebooks modded with chrultrabook/coreboot custom UEFI firmware # # Requires the cros_charge-control module as of Kernel 6.12.8/6.13 providing the following sysfs nodes: # * /sys/class/power_supply/BAT[01]/charge_control_start_threshold: 0..100 # * /sys/class/power_supply/BAT[01]/charge_control_end_threshold: 0..100 # * /sys/class/power_supply/BAT[01]/charge_behaviour: auto|inhibit-charge|force-discharge # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_CROSCC_DL='/sys/bus/platform/devices/cros-charge-control.*.auto/driver' readonly BATDRV_CROSCC_MIN_KVER="6.12" batdrv_is_cros_ec () { # check if kernel module owns a device # rc: 0=laptop w/ ChromeOS EC and cros_charge-control driver active # 1=other hardware - or overriden by framework_laptop driver local drvl # shellcheck disable=SC2086 drvl="$(readlink $BATDRV_CROSCC_DL)" [ "${drvl##*/}" = "cros-charge-control" ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod, $_batdrv_sim # # 1. check for native kernel acpi (Linux 6.12.8/6.13 or higher required) # --> retval $_natacpi: # 0=start and stop threshold/ # 1=stop threshold only/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported (incompatible hard- or firmware) # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # discharge --> retval $_bn_discharge # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="cros-ec" _batdrv_kmod="cros_charge-control" # kernel module for natacpi _batdrv_sim=0 # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" _batdrv_sim=1 else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if driver loaded if ! batdrv_is_cros_ec; then # other hardware or overriden by framework_laptop driver -> try next plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="" _bn_stop="" _bn_dischg="" _batteries="" _bt_def_start=0 _bt_def_stop=100 # iterate batteries and check for native kernel ACPI local bd bs local done=0 for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 continue fi if readable_sysf "$bd/charge_control_start_threshold" \ && readable_sysf "$bd/charge_control_end_threshold" \ && readable_sysf "$bd/charge_behaviour" \ && [ "$X_BAT_CROSCC_SIMULATE_ECVER" != "2" ] \ && [ "$X_BAT_CROSCC_SIMULATE_ECVER" != "1" ]; then # all sysfiles exist and are actually readable if kernel_version_ge "$BATDRV_CROSCC_MIN_KVER"; then # patched kernel -> EC cmd v3 w/ start and stop threshold _bn_start="charge_control_start_threshold" _bn_stop="charge_control_end_threshold" _bm_thresh="natacpi" _bn_dischg="charge_behaviour" _bm_dischg="natacpi" _natacpi=0 else # unpatched kernel: # - No distinction can be made between EC cmd v2 and v3 # - start == stop cannot be set for v2 # -> insufficient kernel support _natacpi=128 fi elif [ "$X_BAT_CROSCC_SIMULATE_ECVER" = "2" ] \ || [ ! -f "$bd/charge_control_start_threshold" ] \ && readable_sysf "$bd/charge_control_end_threshold" \ && readable_sysf "$bd/charge_behaviour" \ && [ "$X_BAT_CROSCC_SIMULATE_ECVER" != "1" ]; then # patched kernel -> EC cmd v2 w/ stop threshold _bn_stop="charge_control_end_threshold" _bm_thresh="natacpi" _bn_dischg="charge_behaviour" _bm_dischg="natacpi" _natacpi=1 else # EC cmd v1 w/o thresholds i.e unsupported firmware _natacpi=254 fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg; bn_dischg=$_bn_dischg" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_natacpi, $_batteries, $_bn_start, $_bn_stop, $_bn_dischg # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1/; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # $_bf_start: sysfile for start threshold; # $_bf_stop: sysfile for stop threshold; # $_bf_status: sysfile for status read # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_start="" _bf_stop="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # config suffix equals battery name _bt_cfg_bat="$_bat_str" # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then [ "$_natacpi" = "0" ] && _bf_start="$ACPIBATDIR/$_bat_str/$_bn_start" _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi if [ "$_bm_dischg" = "natacpi" ]; then _bf_dischg="$ACPIBATDIR/$_bat_str/$_bn_dischg" _bf_status="$ACPIBATDIR/$_bat_str/status" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_start=$_bf_start; bf_stop=$_bf_stop; bf_dischg=$_bf_dischg" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_natapci, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 if [ "$1" = "start" ] && [ "$_natacpi" = "1" ]; then # EC cmd v2 w/o start [ "$2" = "1" ] && printf "(not available)" return 0 fi case "$1" in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then # read threshold from sysfile case "$1" in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent [ "$2" = "1" ] && out="(not available)" rc=4 fi else # no threshold api rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else [ "$2" = "1" ] && printf "(not available)" rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold 0(disabled)..99/DEF(disabled) # $2: new stop threshold 1..100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_natacpi, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start="${1:-}" local new_stop="${2:-}" local verb="${3:-0}" local old_start old_stop # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # start: check for 3 digits max, ensure min 0 / max 99 -- EC cmd v3 only if [ "$_natacpi" = "0" ] && \ { ! is_uint "$new_start" 3 || ! is_within_bounds "$new_start" 0 99; }; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (0..99). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (0..99). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2 else cprintf "" "Error: start charge threshold (%s) for %s is not specified, invalid or out of range (0..99). Aborted.\n" "$new_start" "$_bat_str" 1>&2 fi ;; esac return 2 fi # stop: check for 3 digits max, ensure min 1 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 1 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (1..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (1..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for %s is not specified, invalid or out of range (1..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # check start < stop -- EC cmd v3 only if [ "$_natacpi" = "0" ] && [ "$new_start" -ge "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} >= STOP_CHARGE_THRESH_$_bt_cfg_bat. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s >= STOP_CHARGE_THRESH_%s. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold >= stop threshold for %s. Aborted.\n" "$_bat_str" 1>&2 fi ;; esac return 3 fi # read active threshold values if ! old_start=$(batdrv_read_threshold start 0) || \ ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current charge threshold(s) for $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current charge threshold(s) for %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # determine write sequence too meet boundary condition start < stop # disclaimer: the driver doesn't enforce it but we don't know about the # firmware and it seems reasonable anyway local rc=0 steprc tseq if [ "$_natacpi" = "0" ]; then # EC cmd v3 if [ "$new_start" -ge "$old_stop" ]; then tseq="stop start" else tseq="start stop" fi else # EC cmd v2 tseq="stop" fi # write new thresholds in determined sequence if [ "$verb" = "2" ]; then printf "Setting temporary charge threshold(s) for battery %s:\n" "$_bat_str" 1>&2 fi for step in $tseq; do local old_thresh new_thresh steprc case $step in start) old_thresh=$old_start new_thresh=$new_start ;; stop) old_thresh=$old_stop new_thresh=$new_stop ;; esac if [ "$old_thresh" != "$new_thresh" ]; then # new threshold differs from effective one --> write it case $step in start) write_sysf "$new_thresh" "$_bf_start" ;; stop) write_sysf "$new_thresh" "$_bf_stop" ;; esac steprc=$?; [ $steprc -ne 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then printf " %-5s = %3d\n" "$step" "$new_thresh" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing $step charge threshold for $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2 fi fi done # for step echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # charge battery to stop threshold once # use pre-determined method and sysfiles from global parms # global params: $_batdrv_plugin, $_natacpi, $_bm_thresh, $_bat_str, $_bf_start, $_bf_stop # rc: 0=ok/ # 2=charge level read error/ # 3=charge level too high/ # 4=threshold read error/ # 5=threshold write error/ # 255=not implemented (EC cmd v2) # prerequisite: batdrv_init(), batdrv_select_battery() local soc cur_stop temp_start local rc=0 if [ "$_natacpi" = "1" ]; then # EC cmd v2 echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 fi if ! cur_stop=$(batdrv_read_threshold stop 0); then cprintf "" "Error: reading stop charge threshold of battery %s failed. Aborted.\n" "$_bat_str" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).thresh_unknown: stop=$cur_stop; rc=4" return 4 fi # get current charge level (in %) if ! soc="$(batdrv_calc_soc)"; then cprintf "" "Error: cannot determine charge level of battery %s.\n" "$_bat_str" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).charge_level_unknown; rc=2" return 2 fi temp_start=$(( cur_stop - 1 )) if [ "$soc" -gt "$temp_start" ]; then cprintf "" "Error: the charge level of battery %s is %s%%. " "$_bat_str" "$soc" 1>&2 cprintf "err" "For this command to work, it must not be higher than %s%% (stop threshold - 1).\n" "$temp_start" 1>&2 echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str).charge_level_too_high: soc=$soc; stop=$cur_stop; rc=3" return 3 fi printf "Current charge level of battery %s is %s%%, we are about to charge to %s%%.\n" "$_bat_str" "$soc" "$cur_stop" 1>&2 printf "Setting temporary charge threshold for battery %s:\n" "$_bat_str" 1>&2 write_sysf "$temp_start" "$_bf_start" || rc=5 if [ $rc -eq 0 ]; then printf " start = %3d\n" "$temp_start" 1>&2 cprintf "notice" "Charging starts now, keep the charger connected.\n" 1>&2 else cprintf "err" " start = %3d (Error: write failed)\n" "$temp_start" 1>&2 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce($_bat_str): soc=$soc; start=$temp_start; stop=$cur_stop; rc=$rc" return $rc } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # global params: $_bt_cfg_bat # prerequisite: batdrv_init() local bat start_thresh stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi done return 0 } batdrv_read_force_discharge () { # read and print force-discharge state # $1: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_dischg, $_bat_str, $_bf_dischg # out: # - api: 0=off/1=on/"" on error # - tlp-stat: status text/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local rc=0 out="" case $_bm_dischg in natacpi) # read state from sysfile if out=$(read_sysf "$_bf_dischg"); then if [ "$1" != "1" ]; then # api output if echo "$out" | grep -q "\[force-discharge\]"; then out=1 else out=0 fi fi else # not readable/non-existent [ "$1" = "1" ] && out="(not available)" rc=4 fi ;; *) # no discharge api [ "$1" = "1" ] && out="(not available)" rc=255 ;; esac # "return" force-discharge if [ "$X_DISCHG_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else [ "$1" = "1" ] && printf "(not available)\n" rc=4 fi if [ "$rc" -gt 0 ]; then # log output in the error case only echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge($_bat_str): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; out=$out; rc=$rc" fi return $rc } batdrv_write_force_discharge () { # write force discharge state # $1: 0=off/1=on # global params: $_batdrv_plugin, $_bat_str, $_bm_dischg, $_bf_dischg # rc: 0=done/5=write error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local rc=0 case $_bm_dischg in natacpi) # write force_discharge case "$1" in 0) write_sysf "auto" "$_bf_dischg" || rc=5 ;; 1) write_sysf "force-discharge" "$_bf_dischg" || rc=5 ;; esac ;; # natacpi *) # no discharge api rc=255 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge($_bat_str, $1): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; rc=$rc" return $rc } batdrv_cancel_force_discharge () { # trap: called from batdrv_discharge # global params: $_batdrv_plugin, $_bat_str # prerequisite: batdrv_discharge() batdrv_write_force_discharge 0 unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.cancelled($_bat_str)" printf " Cancelled.\n" do_exit 5 } batdrv_force_discharge_active () { # check battery soc and terminate 'force-discharge' in time *before* the battery is empty # DANGER: Framework EC firmware does *not* automatically terminate discharge, the laptop switches off hard instead # $1: init/run # global params: $_batdrv_plugin, $_batdrv_sim, $_bat_str, $_bm_read, $_bd_read, $_bat_target_ch # rc: init: 0=running or timeout/1=waiting, run: 0=running/1=stopped # retval: $_bat_dischg_rc: 0=discharging/1=not discharging/2=AC detached/4=terminated/6=target soc reached/255=internal error, # $_bat_cn, $_bat_fd, $_bat_cur, $_bat_limit, $_bat_soc, $_bat_st, $_bat_volt # prerequisite: batdrv_init(), batdrv_select_battery() local task="$1" local label local rc # battery readings to be returned to caller as retvals (and for trace output) case "$_batdrv_sim" in 0) # runs on native hardware _bat_cn="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/charge_now")"' / 1000.0 );')" _bat_soc="$(batdrv_calc_soc)" _bat_cur="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/current_now")"' / 1000.0 );')" _bat_volt="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/voltage_now")"' / 1000.0 );')" # Calculate minimum charge [mAh] where force-discharge must be terminated: # Momentary discharge current [mA] multiplied by the 5 sec i.e. 1h / 720 test interval, # plus a 50% (1.5x) safety margin _bat_limit=$(perl -e 'printf ("%d", 2.0 * '"$_bat_cur"' / 720.0 + '"$_bat_target_ch"' );') ;; 1) # simulation on ThinkPad _bat_cn="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')" _bat_soc="$(batdrv_calc_soc)" _bat_cur="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/power_now")"' / 1000.0 );')" _bat_volt="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/voltage_now")"' / 1000.0 );')" # Calculate minimum charge [mWh] where force-discharge must be terminated: # Momentary discharge power [mW] multiplied by the 5 sec i.e. 1h / 720 test interval, # plus a 50% (1.5x) safety margin _bat_limit=$(perl -e 'printf ("%d", 2.0 * '"$_bat_cur"' / 720.0 + '"$_bat_target_ch"');') ;; esac _bat_st="$(read_sysf "$_bd_read/status")" _bat_fd="$(batdrv_read_force_discharge 0)" if [ "$_bat_cn" -lt "$_bat_limit" ]; then # limit is reached -> cancel force-discharge immediately to prevent sudden poweroff batdrv_write_force_discharge 0 _bat_fd=0 _bat_dischg_rc=6 elif ! get_sys_power_supply; then # AC detached _bat_dischg_rc=2 elif [ "$_bat_fd" = "0" ]; then # force_discharge was reset (by some other process, not by the ec) _bat_dischg_rc=4 else # check battery status case "$_bat_st" in Discharging) # battery is discharging _bat_dischg_rc=0 ;; *) # battery is not discharging --> wait _bat_dischg_rc=1 ;; esac fi # rc=0 when actively discharging, or when init detects an abnormal condition --> terminate calling loop immediately if [ "$_bat_dischg_rc" -eq 0 ] || { [ "$task" = "init" ] && [ "$_bat_dischg_rc" -gt 1 ]; }; then rc=0 else rc=1 fi case "$_bat_dischg_rc" in 0) label="running" ;; 1) label="not_discharging" ;; 2) label="ac_detached" ;; 4) label="terminated" ;; 6) label="target_soc" ;; *) label="int_err" ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge.${label}($_bat_str): fd=$_bat_fd; bst=$_bat_st; target=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; cur=$_bat_cur; volt=$_bat_volt; dst=$_bat_dischg_rc; rc=$rc" return $rc } batdrv_discharge_safetylock () { # check safety lock # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged local mode mode="${1:-discharge}" if [ "$CROS_EC_UNBLOCK_DISCHARGE" != "1" ]; then # safety lock engaged echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.safetylock" cecho "Caution: ${mode} operation is blocked for safety reasons." "err" 1>&2 echo 1>&2 cecho "Your laptop's EC firmware will not stop until the battery is completely discharged." "err" 1>&2 cecho "This will cause the laptop to suddenly power off, which can lead to data loss and data corruption." "err" 1>&2 cecho "TLP will stop discharging *before* this happens, but it can't respond to unforeseen battery behavior." "err" 1>&2 echo 1>&2 cecho "Add CROS_EC_UNBLOCK_DISCHARGE=1 to your configuration to accept the risk and unblock ${mode}." "notice" 1>&2 cecho "Make sure all data is saved and backups are in place." "notice" 1>&2 echo 1>&2 return 0 else # safety lock disengaged by user configuration return 1 fi } batdrv_discharge () { # discharge battery # $1: target soc 0(default)..99 # global params: $_batdrv_plugin, $_batdrv_sim, $_bm_dischg, $_bat_str, $_bat_idx, $_bd_read, $_bf_dischg # rc: 0=done/2=AC detached/3=not emptied/4=already empty/5=cancelled by ^C/6=target soc reached/7=target soc out of bounds/8=force-discharge failed or timeout/9=charge level read error/16=blocked # prerequisite: batdrv_init(), batdrv_select_battery() local cf cn label sleeps soc target_soc timeout wt if ! soc="$(batdrv_calc_soc)"; then _bat_dischg_rc=9 echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge($_bat_str).charge_level_unknown; rc=$_bat_dischg_rc" cecho "Error: cannot read charge level of battery ${_bat_str}." 1>&2 return $_bat_dischg_rc fi target_soc="${1:-0}" if is_uint "$target_soc"; then if ! is_within_bounds "$target_soc" 0 99; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_out_of_bounds($_bat_str): target=$target_soc" cprintf "" "Error: target charge level (%s) for battery %s is out of range (0..99).\n" "$target_soc" "$_bat_str" 1>&2 _bat_dischg_rc=7 return $_bat_dischg_rc fi fi case "$_batdrv_sim" in 0) # runs on native hardware cf="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/charge_full")"' / 1000.0 );')" _bat_target_ch="$(perl -e 'printf ("%d", '"$cf"' * '"$target_soc"' / 100.0)')" cn="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/charge_now")"' / 1000.0 );')" ;; 1) # simulation on ThinkPad cf="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_full")"' / 1000.0 );')" _bat_target_ch="$(perl -e 'printf ("%d", '"$cf"' * '"$target_soc"' / 100.0)')" cn="$(perl -e 'printf ("%d", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0 );')" ;; esac if [ "$_bat_target_ch" -ge "$cn" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_below_soc($_bat_str): target=$target_soc; soc=$soc" cprintf "" "Error: target level (%s%%) is too high compared to the actual charge level (%s%%) of battery %s.\n" "$target_soc" "$soc" "$_bat_str" 1>&2 _bat_dischg_rc=7 return $_bat_dischg_rc fi # start discharge if ! batdrv_write_force_discharge 1; then echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.force_discharge_write_error($_bat_str)" cecho "Error: discharge $_bat_str failed -- check your hardware (battery, charger)." 1>&2 unlock_tlp tlp_discharge _bat_dischg_rc=8 return $_bat_dischg_rc fi trap batdrv_cancel_force_discharge INT TERM # enable ^C and error hook # wait for start == while status not "discharging" -- 15.0 sec timeout, quit if force-discharge is reset printf "Initiating discharge of battery %s " "$_bat_str" 1>&2 sleep 0.1 timeout="${X_DISCHG_INIT_TIMEOUT:-15}" sleeps="${X_DISCHG_INIT_SLEEP:-1}" wt="$timeout" while ! batdrv_force_discharge_active "init" && [ "$wt" -gt 0 ] ; do if [ "$_bat_dischg_rc" = "6" ]; then # limit already undercut - do not continue echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.target_soc($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc" echo 1>&2 cecho "Error: actual charge level of battery $_bat_str is too close to the target level." "err" 1>&2 _bat_dischg_rc=4 return $_bat_dischg_rc fi sleep "$sleeps" printf "." 1>&2 wt=$((wt - 1)) done printf "\n" 1>&2 if [ "$_bat_dischg_rc" = "0" ]; then # discharge initiated successfully --> wait for completion while batdrv_force_discharge_active "run"; do clear printf "Currently discharging battery %s to %s%% (%s mAh):\n" "$_bat_str" "$target_soc" "$_bat_target_ch" 1>&2 # show current battery state if [ -n "$_bat_volt" ]; then printf "voltage = %6s [mV]\n" "$_bat_volt" 1>&2 else printf "voltage = not available [mV]\n" 1>&2 fi case "$_batdrv_sim" in 0) # runs on native hardware printf "remaining charge = %6d [mAh]\n" "$_bat_cn" 1>&2 printf "safety cut-off = %6d [mAh]\n" "$_bat_limit" 1>&2 ;; 1) # simulation on ThinkPad printf "remaining charge = %6d [mWh]\n" "$_bat_cn" 1>&2 printf "safety cut-off = %6d [mWh]\n" "$_bat_limit" 1>&2 ;; esac if soc="$(batdrv_calc_soc "%6.1f")"; then printf "remaining percent = %6s [%%]\n" "$soc" 1>&2 else printf "remaining percent = n/a [%%]\n" 1>&2 fi if [ "$_bat_cur" != "0" ]; then perl -e 'printf ("remaining time = %6d [min]\n", 60.0 * ('"$_bat_cn"' - '"$_bat_target_ch"') / '"$_bat_cur"');' 1>&2 else printf "remaining time = not discharging [min]\n" 1>&2 fi case "$_batdrv_sim" in 0) printf "current = %6s [mA]\n" "$_bat_cur" 1>&2 ;; 1) printf "current = %6s [mW]\n" "$_bat_cur" 1>&2 ;; esac printf "status = %s\n" "$_bat_st" 1>&2 printf "force-discharge = %s\n" "$_bat_fd" 1>&2 cecho "Caution: make sure all data is saved and backups are in place." "err" 1>&2 echo "Press Ctrl+C to cancel." 1>&2 sleep 5 done # cancel force-discharge because EC cannot do it on its own batdrv_write_force_discharge 0 # check exit cause label="completed" case "$_bat_dischg_rc" in 2) # system on battery, AC power removed label="ac_detached" echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.ac_detached($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur; volt=$_bat_volt; rc=$_bat_dischg_rc" cecho "Warning: battery $_bat_str discharge has stopped early -- AC/charger removed." 1>&2 ;; 1|4) # discharging stopped(1) or force-discharge reset (3) before limit was reached if [ "$_bat_soc" -gt "$target_soc" ]; then # system on AC: force-discharge ended prematurely with SOC > target label="not_completed" _bat_dischg_rc=3 cecho "Error: battery $_bat_str discharge was aborted early -- check your hardware (battery, charger)." 1>&2 else # battery discharge completed _bat_dischg_rc=0 cecho "Done: battery $_bat_str discharge completed." "success" 1>&2 fi ;; 6) # target soc reached resp. limit undercut label="target_soc" _bat_dischg_rc=0 cecho "Done: battery $_bat_str discharge completed." "success" 1>&2 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.${label}($_bat_str): fd=$_bat_fd; bst=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur; volt=$_bat_volt; drc=$_bat_dischg_rc" else # force-discharge timeout _bat_dischg_rc=8 echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.timeout($_bat_str): fd=$_bat_fd; st=$_bat_st; limit=$_bat_limit; soc=$_bat_soc; cn=$_bat_cn; pwr=$_bat_cur; volt=$_bat_volt; drc=$_bat_dischg_rc" cecho "Error: discharge $_bat_str timeout after $(((timeout - wt) * sleeps)) second(s) -- check your hardware (battery, charger)." 1>&2 fi trap - INT TERM # remove ^C and error hook return $_bat_dischg_rc } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" != "none" ]; then case $_natacpi in 0) cecho "Supported features: charge thresholds, chargeonce, discharge, recalibrate" "success" ;; 1) cecho "Supported features: charge threshold, chargeonce, discharge, recalibrate" "success" ;; esac else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge thresholds, force-discharge) - EC cmd v3\n" "$_batdrv_kmod" ;; 1) cprintf "success" "* natacpi (%s) = active (charge threshold, force-discharge) - EC cmd v2\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "warning" "* natacpi (%s) = inactive (insufficient kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported) - EC cmd v1\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" != "none" ]; then printf "Parameter value ranges:\n" [ "$_natacpi" = "0" ] && printf "* START_CHARGE_THRESH_BAT0/1: 0(off)..99\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 1..100(default)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds, force_discharge local lf=0 if [ "$_bm_thresh" = "natacpi" ]; then [ "$_natacpi" = "1" ] || printf "%-59s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start "1")" printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop "1")" lf=1 fi if [ "$_bm_dischg" = "natacpi" ]; then printf "%-59s = %6s\n" "$_bf_dischg" "$(batdrv_read_force_discharge 1)" lf=1 fi [ $lf -gt 0 ] && printf "\n" # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/60-macbook000066400000000000000000000510711517565574500150320ustar00rootroot00000000000000#!/bin/sh # 60-macbook - Battery Plugin for Apple Silicon Macbooks w/ applesmc driver # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_APPLESMC_MD=/sys/class/power_supply/macsmc-battery batdrv_is_applesmc () { # check if vendor specific kernel module is loaded # rc: 0=ok, 1=other hardware [ -d $BATDRV_APPLESMC_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for native kernel acpi (Linux 5.4 or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # discharge --> retval $_bn_discharge # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="macbook" _batdrv_kmod="macsmc_power" # kernel module for natacpi (Asahi Linux) # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_applesmc; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 # shellcheck disable=SC2034 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="" _bn_stop="" _bn_dischg="" _batteries="" _bt_def_start=100 _bt_def_stop=100 # iterate batteries and check for native kernel ACPI # note: assume only one battery called "macsmc-battery" local bd bs local done=0 local bat_glob="macsmc-battery" if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then bat_glob='BAT[01]' fi for bd in "$ACPIBATDIR"/$bat_glob; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 continue fi if [ -f "$bd/charge_control_start_threshold" ] \ && [ -f "$bd/charge_control_end_threshold" ]; then # threshold sysfiles exist _bn_start="charge_control_start_threshold" _bn_stop="charge_control_end_threshold" _natacpi=254 else # nothing detected _natacpi=254 continue fi if readable_sysf "$bd/$_bn_start" \ && readable_sysf "$bd/$_bn_stop"; then # threshold sysfiles are actually readable _natacpi=0 _bm_thresh="natacpi" if readable_sysf "$bd/charge_behaviour"; then # sysfile for force-discharge exists and is actually readable _bm_dischg="natacpi" _bn_dischg="charge_behaviour" fi fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg; bn_dischg=$_bn_dischg" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: macsmc-battery/DEF # global params: $_batdrv_plugin, $_batteries, $_bn_start, $_bn_stop, $_bn_dischg # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: macsmc-battery; # $_bt_cfg_bat: config suffix (BAT0); # $_bd_read: directory with battery data sysfiles; # $_bf_start: sysfile for start threshold; # $_bf_stop: sysfile for stop threshold; # $_bf_dischg: sysfile for force-discharge; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_start="" _bf_stop="" _bf_dischg="" local bat="$1" # convert battery param to lowercase for backward compatibility # with versions earlier than 1.7, but not in simulation if [ -z "$X_BAT_PLUGIN_SIMULATE" ]; then bat="$(printf '%s' "$bat" | tr "[:upper:]" "[:lower:]")" fi # validate battery param case "$bat" in DEF|def) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # one battery only -> always use BAT0 config _bt_cfg_bat="BAT0" # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_start="$ACPIBATDIR/$_bat_str/$_bn_start" _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi if [ "$_bm_dischg" = "natacpi" ]; then _bf_dischg="$ACPIBATDIR/$_bat_str/$_bn_dischg" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_start=$_bf_start; bf_stop=$_bf_stop; bf_dischg=$_bf_dischg" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case $1 in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then # read threshold from sysfile case $1 in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$2" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # notes: # * Apple Silicon hardware with MacOS 13.0 or later firmware provides only two discrete threshold value pairs: # 100/100 or 75/80 # * The kernel driver discards writes to the start threshold; upon reading, it returns a discrete value that # matches the stop threshold # $1: new start threshold -- unused dummy for plugin api compatibility # $2: new stop threshold 80/100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop, $_bt_def_start, $_bt_def_stop, # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start=${1:-} local new_stop=${2:-} local verb=${3:-0} local old_stop # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure 80 or 100 if ! is_uint "$new_stop" 3 || ! wordinlist "$new_stop" "80 100"; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified or invalid (must be 80 or 100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified or invalid (must be 80 or 100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s is not specified or invalid (must be 80 or 100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # start threshold value depends on stop threshold (hardware constraint) case "$new_stop" in 80) new_start="75" ;; 100) new_start="100" ;; esac # read active stop threshold value if ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current stop charge threshold for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current stop charge threshold for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # write new stop threshold if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds for battery %s:\n" "$_bat_str" 1>&2 fi local rc=0 if [ "$old_stop" != "$new_stop" ]; then # new threshold differs from effective one --> write it write_sysf "$new_stop" "$_bf_stop" || rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).stop.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " %-5s = %3d\n" "stop" "$new_stop" 1>&2 printf " %-5s = %3d (due to hardware constraint)\n" "start" "$new_start" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "stop" "$new_stop" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing stop charge threshold for battery $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).stop.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_stop; new=$new_stop" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "stop" "$new_stop" 1>&2 printf " %-5s = %3d (no change)\n" "start" "$new_start" 1>&2 fi fi if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # global param: $_batdrv_plugin # prerequisite: batdrv_init() if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then if batdrv_select_battery BAT0; then batdrv_write_thresholds "$START_CHARGE_THRESH_BAT0" "$STOP_CHARGE_THRESH_BAT0" 1 1; rc=$? fi else if batdrv_select_battery macsmc-battery; then batdrv_write_thresholds "$START_CHARGE_THRESH_BAT0" "$STOP_CHARGE_THRESH_BAT0" 1 1; rc=$? fi fi return 0 } batdrv_read_force_discharge () { # read and print force-discharge state # $1: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_dischg, $_bat_str, $_bf_dischg # out: # - api: 0=off/1=on/"" on error # - tlp-stat: status text/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local rc=0 out="" if [ "$_bm_dischg" = "natacpi" ]; then # read state from sysfile if out=$(read_sysf "$_bf_dischg"); then if [ "$1" != "1" ]; then if echo "$out" | grep -q "\[force-discharge\]"; then out=1 else out=0 fi fi else # not readable/non-existent if [ "$1" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no discharge api if [ "$1" = "1" ]; then out="(not available)" fi rc=255 fi printf "%s" "$out" if [ "$rc" -gt 0 ]; then # log output in the error case only echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge($_bat_str): bm_dischg=$_bm_dischg; bf_dischg=$_bf_dischg; out=$out; rc=$rc" fi return $rc } batdrv_write_force_discharge () { # function not implemented for Macbooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Macbooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Macbooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Macbooks # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Macbooks # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_natacpi, $_bm_thresh, $_bd_read, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" != "none" ]; then cprintf "success" "Supported features: charge thresholds\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) printf "* natacpi (%s) = active (charge thresholds)\n" "$_batdrv_kmod" ;; 32) printf "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) printf "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) printf "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) printf "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" != "none" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0: don't care (hardware enforces 75, 100)\n" printf "* STOP_CHARGE_THRESH_BAT0: 80, 100(default)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds, force_discharge local lf=0 if [ "$_bm_thresh" = "natacpi" ]; then printf "%-69s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start 1)" printf "%-69s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" lf=1 fi if [ "$_bm_dischg" = "natacpi" ]; then printf "%-69s = %6s\n" "$_bf_dischg" "$(batdrv_read_force_discharge 1)" lf=1 fi [ $lf -gt 0 ] && printf "\n" # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output Macbook specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/65-dell000066400000000000000000000550641517565574500143520ustar00rootroot00000000000000#!/bin/sh # 65-dell - Battery Plugin for Dell laptops # # Requires the dell_laptop module as of Linux 6.12 providing the following sysfs nodes: # * /sys/class/power_supply/BAT[01]/charge_control_start_threshold: 50..95 # * /sys/class/power_supply/BAT[01]/charge_control_end_threshold: 55..100 # Condition: charge_control_start_threshold + 5 <= charge_control_end_threshold # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_DELL_MD=/sys/module/dell_laptop batdrv_is_dell () { # check if kernel module is loaded # rc: 0=ok, 1=other hardware [ -d $BATDRV_DELL_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod, $_batdrv_sim # # 1. check for native kernel acpi (Linux 6.12 or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # discharge --> retval $_bn_discharge # charge type (Dell specific) --> retval $_bn_chtype # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="dell" _batdrv_kmod="dell_laptop" # kernel module for natacpi _batdrv_sim=0 # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" _batdrv_sim=1 else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_dell; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="" _bn_stop="" _bn_dischg="" _bn_chtype="" _batteries="" _bt_def_start=95 _bt_def_stop=100 # iterate batteries and check for native kernel ACPI local bd bs local done=0 for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 continue fi if readable_sysf "$bd/charge_control_start_threshold" \ && readable_sysf "$bd/charge_control_end_threshold"; then # threshold sysfiles exist and are actually readable _bn_start="charge_control_start_threshold" _bn_stop="charge_control_end_threshold" _bn_chtype="charge_types" _bm_thresh="natacpi" _natacpi=0 else # laptop model not supported by the driver _natacpi=254 fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; bn_chtype=$_bn_chtype; dischg=$_bm_dischg; bn_dischg=$_bn_dischg" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries, $_bn_start, $_bn_stop, $_bn_dischg # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1/; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # $_bf_start: sysfile for start threshold; # $_bf_stop: sysfile for stop threshold; # $_bf_chtype: sysfile for charge type; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_start="" _bf_stop="" _bf_chtype="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac _bt_cfg_bat="$_bat_str" # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_start="$ACPIBATDIR/$_bat_str/$_bn_start" _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" _bf_chtype="$ACPIBATDIR/$_bat_str/$_bn_chtype" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_start=$_bf_start; bf_stop=$_bf_stop; bf_chtype=$_bf_chtype" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case $1 in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then # read threshold from sysfile case $1 in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$2" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold 50..95(default)/DEF # $2: new stop threshold 55..100(default)/DEF # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error/ # 6=charge type write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start="${1:-}" local new_stop="${2:-}" local verb="${3:-0}" local old_start old_stop old_chtype # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # start: check for 3 digits max, ensure min 50 / max 95 if ! is_uint "$new_start" 3 || \ ! is_within_bounds "$new_start" 50 95; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (50..95). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (50..95). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2 else cprintf "" "Error: start charge threshold (%s) for battery %s is not specified, invalid or out of range (50..95). Aborted.\n" "$new_start" "$_bat_str" 1>&2 fi ;; esac return 2 fi # stop: check for 3 digits max, ensure min 55 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 55 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (55..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (55..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s is not specified, invalid or out of range (55..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # check start + 5 <= stop if [ $((new_start + 5)) -gt "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} > STOP_CHARGE_THRESH_$_bt_cfg_bat - 5. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s > STOP_CHARGE_THRESH_%s - 5. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold > stop threshold - 5 for battery %s. Aborted.\n" "$_bat_str" 1>&2 fi ;; esac return 3 fi # read active threshold values if ! old_start=$(batdrv_read_threshold start 0) || \ ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current charge threshold(s) for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current charge threshold(s) for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # determine write sequence because driver's intrinsic boundary conditions must be met in all write stages # note: the driver changes values to meet start + 5 <= stop local rc=0 steprc tseq if [ "$new_start" -ge "$old_stop" ]; then tseq="stop start" else tseq="start stop" fi # write new thresholds in determined sequence if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds for battery %s:\n" "$_bat_str" 1>&2 fi # prerequisite: check charge type and change to 'Custom' if necessary if [ "$X_THRESH_SIMULATE_LOCKEDBIOS" = "1" ] || [ "$_batdrv_sim" = "0" ]; then old_chtype="$(read_sysf "$_bf_chtype" | sed -r 's/.*\[([A-Z][a-z]+)\].*/\1/')" if [ "$X_THRESH_SIMULATE_LOCKEDBIOS" = "1" ] || [ "$old_chtype" != "Custom" ] && ! write_sysf "Custom" "$_bf_chtype" ; then # changing to 'Custom' failed case $verb in 1) echo_message "Error: failed to set 'Custom' charge type for battery $_bat_str. Battery skipped." cecho "Remove the BIOS Admin password to allow the thresholds to be set." "notice" 1>&2 ;; 2) cprintf "" "Error: failed to set 'Custom' charge type for battery %s. Aborted.\n" "$_bat_str" 1>&2 cecho "Remove the BIOS Admin password to allow the thresholds to be set." "notice" 1>&2 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).chtype_error: bat=$_bat_str; cfg=$_bt_cfg_bat; old_chtype=$old_chtype; rc=6" return 6 fi fi for step in $tseq; do local old_thresh new_thresh steprc case $step in start) old_thresh=$old_start new_thresh=$new_start ;; stop) old_thresh=$old_stop new_thresh=$new_stop ;; esac if [ "$old_thresh" != "$new_thresh" ]; then # new threshold differs from effective one --> write it case $step in start) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_start" ;; stop) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_stop" ;; esac steprc=$?; [ $steprc -ne 0 ] && [ $rc -eq 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then printf " %-5s = %3d\n" "$step" "$new_thresh" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing $step charge threshold for battery $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2 fi fi done # for step if [ $rc -eq 5 ] && [ "$verb" -gt 0 ]; then cecho "Remove the BIOS Admin password to allow the thresholds to be set." "notice" 1>&2 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat start_thresh stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi done return 0 } batdrv_read_force_discharge () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Dell laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" != "none" ]; then cprintf "success" "Supported features: charge thresholds\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge thresholds)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" != "none" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0/1: 50..95(default)\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 55..100(default)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start 1)" printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" printparm "%-59s = ##%s##" "$_bd_read/charge_types" "not available" printf "\n" fi # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output Dell laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/66-wilco-ec000066400000000000000000000531161517565574500151310ustar00rootroot00000000000000#!/bin/sh # 66-wilco-ec - Battery Plugin for laptops with Wilco EC: # 1. Dell Chromebooks modded with custom UEFI firmware from MrChromebox.tech # 2. Possibly other laptops/Chromebooks # # Requires the wilco_charger module as of Linux 5.9 providing the following sysfs nodes: # * /sys/class/power_supply/wilco-charger/charge_control_start_threshold: 50..95 # * /sys/class/power_supply/wilco-charger/charge_control_end_threshold: 55..100 # Condition: charge_control_start_threshold + 5 <= charge_control_end_threshold # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_WILCOC_MD=/sys/module/wilco_charger batdrv_is_wilco () { # check if kernel module is loaded # rc: 0=ok, 1=other hardware [ -d $BATDRV_WILCOC_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod, $_batdrv_sim # # 1. check for native kernel acpi (Linux 6.12 or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # start threshold --> retval $_bf_start, # stop threshold --> retval $_bf_stop, # charge type --> retval $_bf_chtype # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="wilco-ec" _batdrv_kmod="wilco_charger" # kernel module for natacpi _batdrv_sim=0 # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" _batdrv_sim=1 else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_wilco; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bf_start="" _bf_stop="" _bf_chtype="" _batteries="" _bt_def_start=95 _bt_def_stop=100 # iterate batteries and check for native kernel ACPI local bd bs for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 else sysf="$ACPIBATDIR/wilco-charger" if readable_sysf "$sysf/charge_control_start_threshold" \ && readable_sysf "$sysf/charge_control_end_threshold"; then # threshold sysfiles exist and are actually readable _bf_start="$sysf/charge_control_start_threshold" _bf_stop="$sysf/charge_control_end_threshold" _bf_chtype="$sysf/charge_type" _bm_thresh="natacpi" _natacpi=0 else # laptop model not supported by the driver _natacpi=254 fi fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bf_start=$_bf_start; bf_stop=$_bf_stop; bf_chtype=$_bf_chtype; dischg=$_bm_dischg" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1/; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac _bt_cfg_bat="BAT0" _bd_read="$ACPIBATDIR/$_bat_str" echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case $1 in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then # read threshold from sysfile case $1 in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$2" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold 50..95(default)/DEF # $2: new stop threshold 55..100(default)/DEF # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error/ # 6=charge type write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start="${1:-}" local new_stop="${2:-}" local verb="${3:-0}" local old_start old_stop old_chtype # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # start: check for 3 digits max, ensure min 50 / max 95 if ! is_uint "$new_start" 3 || \ ! is_within_bounds "$new_start" 50 95; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (50..95). Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (50..95). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2 else cprintf "" "Error: start charge threshold (%s) is not specified, invalid or out of range (50..95). Aborted.\n" "$new_start" 1>&2 fi ;; esac return 2 fi # stop: check for 3 digits max, ensure min 55 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 55 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (55..100). Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (55..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) is not specified, invalid or out of range (55..100). Aborted.\n" "$new_stop" 1>&2 fi ;; esac return 2 fi # check start + 5 <= stop if [ $((new_start + 5)) -gt "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} > STOP_CHARGE_THRESH_$_bt_cfg_bat - 5. Skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s > STOP_CHARGE_THRESH_%s - 5. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold > stop threshold - 5. Aborted.\n" 1>&2 fi ;; esac return 3 fi # read active threshold values if ! old_start=$(batdrv_read_threshold start 0) || \ ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current charge threshold(s). Skipped." ;; 2) cprintf "" "Error: could not read current charge threshold(s). Aborted.\n" 1>&2 ;; esac return 4 fi # determine write sequence because driver's intrinsic boundary conditions must be met in all write stages # note: the driver changes values to meet start + 5 <= stop local rc=0 steprc tseq if [ "$new_start" -ge "$old_stop" ]; then tseq="stop start" else tseq="start stop" fi # write new thresholds in determined sequence if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds:\n" 1>&2 fi # prerequisite: check charge type and change to 'Custom' if necessary if [ "$_batdrv_sim" = "0" ]; then old_chtype="$(read_sysf "$_bf_chtype")" if [ "$old_chtype" != "Custom" ] && ! write_sysf "Custom" "$_bf_chtype" ; then # changing to 'Custom' failed case $verb in 1) echo_message "Error: failed to set 'Custom' charge type. Skipped." ;; 2) cprintf "" "Error: failed to set 'Custom' charge type. Aborted.\n" 1>&2 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).chtype_error: bat=$_bat_str; cfg=$_bt_cfg_bat; old_chtype=$old_chtype; rc=6" return 6 fi fi for step in $tseq; do local old_thresh new_thresh steprc case $step in start) old_thresh=$old_start new_thresh=$new_start ;; stop) old_thresh=$old_stop new_thresh=$new_stop ;; esac if [ "$old_thresh" != "$new_thresh" ]; then # new threshold differs from effective one --> write it case $step in start) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_start" ;; stop) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_stop" ;; esac steprc=$?; [ $steprc -ne 0 ] && [ $rc -eq 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then printf " %-5s = %3d\n" "$step" "$new_thresh" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing $step charge threshold failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2 fi fi done # for step echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error local ef en if [ -f "$_bd_read/energy_full" ]; then ef=$(read_sysval "$_bd_read/energy_full") en=$(read_sysval "$_bd_read/energy_now") elif [ -f "$_bd_read/charge_full" ]; then ef=$(read_sysval "$_bd_read/charge_full") en=$(read_sysval "$_bd_read/charge_now") else ef=0 en=0 fi if [ "$ef" != "0" ]; then if [ -n "$1" ]; then perl -e 'printf ("'"$1"'", 100.0 * '"$en"' / '"$ef"')' else perl -e 'printf ("%d", int(100.0 * '"$en"' / '"$ef"' + 0.5))' fi return 0 else printf "255" return 1 fi } batdrv_chargeonce () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local start_thresh stop_thresh if batdrv_select_battery "DEF"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi return 0 } batdrv_read_force_discharge () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for Dell laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Dell laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_start, $_bf_stop # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" != "none" ]; then cprintf "success" "Supported features: charge thresholds\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge thresholds)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" != "none" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0: 50..95(default)\n" printf "* STOP_CHARGE_THRESH_BAT0: 55..100(default)\n" fi printf "\n" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then printf "%-69s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start 1)" printf "%-69s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" printparm "%-59s = ##%s##" "$_bf_chtype" "not available" printf "\n" fi # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output Dell laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/70-tuxedo000066400000000000000000000575241517565574500147410ustar00rootroot00000000000000#!/bin/sh # 70-tuxedo - Battery Plugin for Tuxedo laptops (Clevo OEM chassis) # # Requires the out-of-tree clevo_acpi module from Tuxedo providing the # following sysfs nodes: # * /sys/class/power_supply/BAT[01]/charge_control_start_threshold: # 40, 50, 60, 70, 80, 95 # * /sys/class/power_supply/BAT[01]/charge_control_end_threshold: # 60, 70, 80, 90, 100 # Condition: charge_control_start_threshold < charge_control_end_threshold # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection batdrv_is_tuxedo () { # check if DMI system vendor is Tuxedo # rc: 0=ok, 1=other hardware [ "$(read_dmi sys_vendor)" = "TUXEDO" ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod, $_batdrv_sim # # 1. check for native kernel acpi # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 64=kernel module not loaded/ # 128=kernel module not installed/ # 254=model not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # discharge --> retval $_bn_discharge # charge type (Tuxedo specific) --> retval $_bn_chtype # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop, # start threshold allowed values set --> retval $_bt_set_start, # stop threshold allowed values set --> retval $_bt_set_stop; _batdrv_plugin="tuxedo" # kernel module for natacpi; in the simulation, a different one can be entered _batdrv_kmod="${X_BAT_PLUGIN_SIM_KMOD:-clevo_acpi}" _batdrv_sim=0 # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" _batdrv_sim=1 else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_tuxedo; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=254 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="" _bn_stop="" _bn_dischg="" _bn_chtype="" _batteries="" _bt_def_start=95 _bt_def_stop=100 _bt_set_start="" _bt_set_stop="" # check out-of-tree kernel module inavailability cases if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 elif $MODINFO "$_batdrv_kmod" > /dev/null 2>&1; then # module installed -> load it (also succeeds if builtin) if ! $MODPRO "$_batdrv_kmod" > /dev/null 2>&1; then # module load error _natacpi=64 fi else # module neither installed nor builtin _natacpi=128 fi # iterate batteries and check for native kernel ACPI local bd bs local done=0 for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$_natacpi" -lt "254" ]; then # natacpi kernel module not available --> skip actual detection continue fi if readable_sysf "$bd/charge_control_start_threshold" \ && readable_sysf "$bd/charge_control_end_threshold" ; then # threshold sysfiles exist and are actually readable _natacpi=0 _bn_start="charge_control_start_threshold" _bn_stop="charge_control_end_threshold" _bm_thresh="natacpi" _bn_chtype="charge_type" _bt_set_start="$(read_sysf "$bd/charge_control_start_available_thresholds")" _bt_set_start="${_bt_set_start:-40 50 60 70 80 95}" _bt_set_stop="$(read_sysf "$bd/charge_control_end_available_thresholds")" _bt_set_stop="${_bt_set_stop:-60 70 80 90 100}" fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg; bn_dischg=$_bn_dischg; bn_chtype=$_bn_chtype" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries, $_bn_start, $_bn_stop, $_bn_dischg # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1/; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # $_bf_start: sysfile for start threshold; # $_bf_stop: sysfile for stop threshold; # $_bf_chtype: sysfile for charge type; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_start="" _bf_stop="" _bf_chtype="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac _bt_cfg_bat="$_bat_str" # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_start="$ACPIBATDIR/$_bat_str/$_bn_start" _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" _bf_chtype="$ACPIBATDIR/$_bat_str/$_bn_chtype" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_start=$_bf_start; bf_stop=$_bf_stop" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case "$1" in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then # read threshold from sysfile case "$1" in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$2" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold: 40/50/60/70/80/95(default)/DEF # $2: new stop threshold: 60/70/80/90/100(default)/DEF # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop, $_bt_set_start, $_bt_set_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start="${1:-}" local new_stop="${2:-}" local verb="${3:-0}" local old_start old_stop pset # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # start: check for 3 digits max, ensure in $_bt_set_start if ! is_uint "$new_start" 3 || \ ! wordinlist "$new_start" "$_bt_set_start"; then # threshold out of range pset="$(echo "$_bt_set_start" | sed -r 's/ /, /g')" echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or not in {$pset}. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or not in {%s}. Aborted.\n" "$_bt_cfg_bat" "$new_start" "$pset" 1>&2 else cprintf "" "Error: start charge threshold (%s) for %s is not specified, invalid or not in {%s}. Aborted.\n" "$new_start" "$_bat_str" "$pset" 1>&2 fi ;; esac return 2 fi # stop: check for 3 digits max, ensure in $_bt_set_stop if ! is_uint "$new_stop" 3 || \ ! wordinlist "$new_stop" "$_bt_set_stop"; then # threshold out of range pset="$(echo "$_bt_set_stop" | sed -r 's/ /, /g')" echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or not in {$pset}. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or not in {%s}. Aborted.\n" "$_bt_cfg_bat" "$new_stop" "$pset" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for %s is not specified, invalid or not in {%s}. Aborted.\n" "$new_stop" "$_bat_str" "$pset" 1>&2 fi ;; esac return 2 fi # check start < stop if [ "$new_start" -ge "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} >= STOP_CHARGE_THRESH_$_bt_cfg_bat. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s >= STOP_CHARGE_THRESH_%s. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold >= stop threshold for %s. Aborted.\n" "$_bat_str" 1>&2 fi ;; esac return 3 fi # read active threshold values if ! old_start=$(batdrv_read_threshold start 0) || \ ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current charge threshold(s) for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current charge threshold(s) for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # determine write sequence to meet boundary condition start < stop # disclaimer: the driver doesn't enforce it but we don't know about the # firmware and it's reasonable anyway local rc=0 steprc tseq if [ "$new_start" -ge "$old_stop" ]; then tseq="stop start" else tseq="start stop" fi # write new thresholds in determined sequence if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds for battery %s:\n" "$_bat_str" 1>&2 fi # prerequisite: check charge type and change to 'Custom' if necessary if [ "$_batdrv_sim" = "0" ]; then old_chtype="$(read_sysf "$_bf_chtype")" if [ "$old_chtype" != "Custom" ] && ! write_sysf "Custom" "$_bf_chtype" ; then # changing to 'Custom' failed case $verb in 1) echo_message "Error: failed to set 'Custom' charge type for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: failed to set 'Custom' charge type for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).chtype_error: bat=$_bat_str; cfg=$_bt_cfg_bat; old_chtype=$old_chtype; rc=6" return 6 fi fi for step in $tseq; do local old_thresh new_thresh steprc case $step in start) old_thresh=$old_start new_thresh=$new_start ;; stop) old_thresh=$old_stop new_thresh=$new_stop ;; esac if [ "$old_thresh" != "$new_thresh" ]; then # new threshold differs from effective one --> write it case $step in start) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_start" ;; stop) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_stop" ;; esac steprc=$?; [ $steprc -ne 0 ] && [ $rc -eq 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then printf " %-5s = %3d\n" "$step" "$new_thresh" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing $step charge threshold for $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2 fi fi done # for step echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat start_thresh stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi done return 0 } batdrv_read_force_discharge () { # function not implemented for Tuxedo/Clevo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for Tuxedo/Clevo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for Tuxedo/Clevo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for Tuxedo/Clevo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for Tuxedo/Clevo laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" != "none" ]; then cprintf "success" "Supported features: charge thresholds\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge thresholds)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 64) cprintf "err" "* natacpi (%s) = inactive (kernel module '%s' load error)\n" "$_batdrv_kmod" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (kernel module '%s' not installed)\n" "$_batdrv_kmod" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (Tuxedo's non-Clevo models are unsupported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" != "none" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0/1: %s(default)\n" "$(echo "$_bt_set_start" | tr ' ' '/')" printf "* STOP_CHARGE_THRESH_BAT0/1: %s(default)\n" "$(echo "$_bt_set_stop" | tr ' ' '/')" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printparm "%-59s = ##%6d## [%%]" "$_bd_read/charge_control_start_threshold" "not available" printparm "%-59s = ##%6d## [%%]" "$_bd_read/charge_control_end_threshold" "not available" printparm "%-59s = ##%6s##" "$_bd_read/charge_type" "not available" else printf "%-59s = %s [%%]\n" "$_bd_read/charge_control_start_threshold" "(not available)" printf "%-59s = %s [%%]\n" "$_bd_read/charge_control_end_threshold" "(not available)" printf "%-59s = %s\n" "$_bd_read/charge_type" "(not available)" fi printf "\n" # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output tuxedo laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation if [ "$_natacpi" = "128" ]; then printf "Install tuxedo-drivers for battery thresholds\n" fi return 0 } TLP-1.10.1/bat.d/89-asus000066400000000000000000000403221517565574500144020ustar00rootroot00000000000000#!/bin/sh # 15-asus - Battery Plugin for ASUS laptops w/ asus_wmi driver # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_ASUS_MD=/sys/module/asus_wmi batdrv_is_asus () { # check if kernel module loaded # rc: 0=ASUS, 1=other hardware [ -d $BATDRV_ASUS_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for native kernel acpi (Linux 5.4 or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basename for natacpi # stop threshold --> retval $_bn_stop, # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="asus" _batdrv_kmod="asus_wmi" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_asus; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_stop="" _batteries="" _bt_def_stop=100 # iterate batteries and check for native kernel ACPI local bd bs local done=0 for bd in "$ACPIBATDIR"/BAT[01CT]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs="${bd##/*/}" _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 elif [ -f "$bd/charge_control_end_threshold" ] && readable_sysf "$bd/charge_control_end_threshold"; then # sysfile for stop threshold exists and is actually readable _natacpi=0 _bm_thresh="natacpi" _bn_stop="charge_control_end_threshold" elif [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then # simulate api _natacpi=0 _bm_thresh="natacpi" _bn_stop="charge_control_end_threshold" else # nothing detected _natacpi=254 fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; stop=$_bn_stop;" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfile # $1: BAT0/BATC/BATT/BAT1/DEF # global params: $_batdrv_plugin, $_batteries, $_bn_stop # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BATC/BATT/BAT1; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # $_bf_stop: sysfile for stop threshold; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_stop="" local bat="$1" # convert battery param to uppercase bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac case "$_bat_str" in BAT0|BATC|BATT) _bt_cfg_bat="BAT0" ;; BAT1) _bt_cfg_bat="BAT1" ;; *) _bt_cfg_bat="BAT0" ;; esac # determine natacpi sysfile _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_stop=$_bf_stop" return 0 } batdrv_read_threshold () { # read and print charge threshold (stop only) # $1: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local out rc=0 out="$X_THRESH_SIMULATE_STOP" if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1).simulate: bm_thresh=$_bm_thresh; bf_stop=$_bf_stop; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then if ! out=$(read_sysf "$_bf_stop"); then # not readable/non-existent if [ "$1" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$1" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$1" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1): bm_thresh=$_bm_thresh; bf_stop=$_bf_stop; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold -- unused dummy for plugin api compatibility # $2: new stop threshold 1..100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold out of range or non-numeric/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_stop=${2:-} local verb=${3:-0} # insert defaults [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds if [ -n "$4" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # stop: check for 3 digits max, ensure min 1 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 1 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (1..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (1..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for %s is not specified, invalid or out of range (1..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # give hint if threshold is not 40, 60, 80 or 100(off) if ! wordinlist "$new_stop" "40 60 80 100"; then case $verb in 1) if [ -n "$4" ]; then echo_message "Notice: some ASUS laptops silently ignore charge thresholds other than 40, 60 or 80. Please check if STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\" works as expected." fi ;; 2) if [ -n "$4" ]; then echo_message "Notice: some ASUS laptops silently ignore charge thresholds other than 40, 60 or 80. Please check if STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\" works as expected." else cprintf "" "Notice: some ASUS laptops silently ignore charge thresholds other than 40, 60 or 80. Please check if %s works as expected.\n" "$new_stop" 1>&2 fi ;; esac fi # write new threshold if [ "$verb" = "2" ]; then printf "Setting temporary charge threshold for %s:\n" "$_bat_str" 1>&2 fi local rc=0 write_sysf "$new_stop" "$_bf_stop" || rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($2, $3, $4).write: bat=$_bat_str; cfg=$_bt_cfg_bat; new=$new_stop; rc=$rc" case $verb in 2) if [ $rc -eq 0 ]; then printf " %-5s = %3d\n" "stop" "$new_stop" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "stop" "$new_stop" 1>&2 fi ;; 1) if [ $rc -gt 0 ]; then echo_message "Error: writing stop charge threshold for $_bat_str failed." fi ;; esac if [ "$rc" -eq 0 ] && [ "$verb" = "2" ]; then soc_gt_stop_notice fi return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented for ASUS laptops echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat stop_thresh for bat in BAT0 BATC BATT BAT1; do if batdrv_select_battery "$bat"; then eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "DEF" "$stop_thresh" 1 1; rc=$? fi done return 0 } batdrv_read_force_discharge () { # function not implemented for ASUS laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for ASUS laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for ASUS laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for ASUS laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for ASUS laptops # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for ASUS laptops # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_stop # prerequisite: batdrv_init() local verbose=${1:-0} printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" = "natacpi" ]; then cprintf "success" "Supported features: charge threshold\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge threshold)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac if [ "$_bm_thresh" = "natacpi" ]; then printf "Parameter value range:\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 1..100(default)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold 1)" printf "\n" fi # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output ASUS laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/bat.d/90-generic000066400000000000000000000127751517565574500150460ustar00rootroot00000000000000#!/bin/sh # 90-generic - Battery plugin catchall for laptops that either not provide # a kernel interface for battery care or TLP doesn't support it yet. # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat batdrv_init () { # detect hardware and initialize driver # rc: 0 (catchall) # retval: $_batdrv_plugin # # 1. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 2. designate battery care as unsupported # reading battery data --> retval $_bm_read = "none", # reading/writing charging thresholds --> retval $_bm_thresh = "none", # reading/writing force discharge --> retval $_bm_dischg = "none": _batdrv_plugin="generic" # iterate batteries local bs bd _batteries="" for bd in "$ACPIBATDIR"/*; do if [ "$(read_sysf "$bd/type")" = "Battery" ] \ && [ "$(read_sysf "$bd/present")" = "1" ]; then bs="${bd##/*/}" # ignore atypical power supplies and batteries printf '%s\n' "$bs" | grep -E -q "$RE_PS_IGNORE" && continue # record detected batteries and directories _batteries="${_batteries}${_batteries:+ }${bs}" fi done # shellcheck disable=SC2034 _bm_read="none" # shellcheck disable=SC2034 _bm_thresh="none" # shellcheck disable=SC2034 _bm_dischg="none" # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries" # catchall: always return 0 return 0 } batdrv_select_battery () { # determine battery acpidir # $1: battery # retval: $_bd_read: directory with battery data sysfiles local bat="$1" # note: do *not* convert battery name to uppercase _bd_read="$ACPIBATDIR/$bat" return 0 } batdrv_read_threshold () { # function not implemented for generic hardware echo_debug "bat" "batdrv.${_batdrv_plugin}.read_treshold.not_implemented" return 255 } batdrv_write_thresholds () { # function not implemented for generic hardware echo_debug "bat" "batdrv.${_batdrv_plugin}.write_tresholds.not_implemented" return 255 } batdrv_calc_soc () { # function not implemented as not required return 255 } batdrv_chargeonce () { # function not implemented for generic hardware echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # function not implemented for generic hardware echo_debug "bat" "batdrv.${_batdrv_plugin}.apply_configured_thresholds.not_implemented" return 255 } batdrv_read_force_discharge () { # function not implemented for generic hardware echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented for generic hardware echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented for generic hardware echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented for generic hardware echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented for generic hardware # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented for generic hardware # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery data # $1: 1=verbose # global param: $_batteries # rc: 0=ok/1=no batteries specified local verbose=${1:-0} printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" cprintf "warning" "Supported features: none available\n" printf "\n" local bat local bcnt=0 if [ -z "$_batteries" ]; then printf "+++ Battery Status\n" cprintf "warning" "No batteries detected.\n" printf "\n" return 1 fi for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" printparm "%-59s = ##%6d## [%%]" "$_bd_read/charge_control_start_threshold" "not available" printparm "%-59s = ##%6d## [%%]" "$_bd_read/charge_control_end_threshold" "not available" printparm "%-59s = ##%6s##" "$_bd_read/charge_behaviour" "not available" printparm "%-59s = ##%6s##" "$_bd_read/charge_types" "not available" printf "\n" # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # function not implemented for generic hardware return 1 } batdrv_recommendations () { # no recommendations for generic hardware return 0 } TLP-1.10.1/bat.d/TEMPLATE000066400000000000000000000525771517565574500144230ustar00rootroot00000000000000#!/bin/sh # Battery Plugin TEMPLATE for laptops providing standard sysfs nodes for # charge thresholds: # * /sys/class/power_supply/BAT0/charge_control_start_threshold # * /sys/class/power_supply/BAT0/charge_control_end_threshold # # IMPORTANT: customize at least all places marked with "TEMPLATE" # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat # --- Hardware Detection readonly BATDRV_TEMPLATE_MD=/sys/module/TEMPLATE_MODULE_DIR batdrv_is_TEMPLATE () { # check if vendor specific kernel module is loaded # rc: 0=ok, 1=other hardware [ -d $BATDRV_TEMPLATE_MD ] } # --- Plugin API functions batdrv_init () { # detect hardware and initialize driver # rc: 0=matching hardware detected/1=not detected/2=no batteries detected # retval: $_batdrv_plugin, $_batdrv_kmod # # 1. check for native kernel acpi (TEMPLATE: Linux m.n or higher required) # --> retval $_natacpi: # 0=thresholds/ # 32=disabled/ # 128=no kernel support/ # 254=laptop not supported # # 2. determine method for # reading battery data --> retval $_bm_read, # reading/writing charging thresholds --> retval $_bm_thresh, # reading/writing force discharge --> retval $_bm_dischg: # none/natacpi # # 3. define sysfile basenames for natacpi # start threshold --> retval $_bn_start, # stop threshold --> retval $_bn_stop, # discharge --> retval $_bn_discharge # # 4. determine present batteries # list of batteries (space separated) --> retval $_batteries; # # 5. define charge threshold defaults # start threshold --> retval $_bt_def_start, # stop threshold --> retval $_bt_def_stop; _batdrv_plugin="TEMPLATE" _batdrv_kmod="TEMPLATE_MOD" # kernel module for natacpi # check plugin simulation override and denylist if [ -n "$X_BAT_PLUGIN_SIMULATE" ]; then if [ "$X_BAT_PLUGIN_SIMULATE" = "$_batdrv_plugin" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate" else echo_debug "bat" "batdrv_init.${_batdrv_plugin}.simulate_skip" return 1 fi elif wordinlist "$_batdrv_plugin" "$X_BAT_PLUGIN_DENYLIST"; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.denylist" return 1 else # check if hardware matches if ! batdrv_is_TEMPLATE; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_match" return 1 fi fi # presume no features at all _natacpi=128 _bm_read="natacpi" _bm_thresh="none" _bm_dischg="none" _bn_start="" _bn_stop="" _bn_dischg="" _batteries="" # TEMPLATE: customize to vendor defaults _bt_def_start=96 _bt_def_stop=100 # iterate batteries and check for native kernel ACPI local bd bs local done=0 # TEMPLATE: customize battery names if necessary for bd in "$ACPIBATDIR"/BAT[01]; do if [ "$(read_sysf "$bd/present")" = "1" ]; then # record detected batteries and directories bs=${bd##/*/} _batteries="${_batteries}${_batteries:+ }${bs}" # skip natacpi detection for 2nd and subsequent batteries [ $done -eq 1 ] && continue done=1 if [ "$NATACPI_ENABLE" = "0" ]; then # natacpi disabled in configuration --> skip actual detection _natacpi=32 continue fi if [ -f "$bd/charge_control_start_threshold" ] \ && [ -f "$bd/charge_control_end_threshold" ]; then # threshold sysfiles exist _bn_start="charge_control_start_threshold" _bn_stop="charge_control_end_threshold" _natacpi=254 else # nothing detected _natacpi=254 continue fi if readable_sysf "$bd/$_bn_start" \ && readable_sysf "$bd/$_bn_stop"; then # threshold sysfiles are actually readable _natacpi=0 _bm_thresh="natacpi" fi fi done # quit if no battery detected, there is no point in activating the plugin if [ -z "$_batteries" ]; then echo_debug "bat" "batdrv_init.${_batdrv_plugin}.no_batteries" return 2 fi # shellcheck disable=SC2034 _batdrv_selected=$_batdrv_plugin echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries; natacpi=$_natacpi; thresh=$_bm_thresh; bn_start=$_bn_start; bn_stop=$_bn_stop; dischg=$_bm_dischg; bn_dischg=$_bn_dischg" return 0 } batdrv_select_battery () { # determine battery acpidir and sysfiles # $1: BAT0/BAT1/DEF # global params: $_batdrv_plugin, $_batteries, $_bn_start, $_bn_stop, $_bn_dischg # rc: 0=bat exists/1=bat non-existent # retval: $_bat_str: BAT0/BAT1/; # $_bt_cfg_bat: config suffix (BAT0/BAT1); # $_bd_read: directory with battery data sysfiles; # $_bf_start: sysfile for start threshold; # $_bf_stop: sysfile for stop threshold; # prerequisite: batdrv_init() # defaults _bat_str="" # no bat _bt_cfg_bat="" _bd_read="" # no directory _bf_start="" _bf_stop="" local bat="$1" # convert battery param to uppercase # TEMPLATE: omit the following line for lower case battery device names bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" # validate battery param case "$bat" in DEF) # 1st battery is default _bat_str="${_batteries%% *}" ;; *) if wordinlist "$bat" "$_batteries"; then _bat_str="$bat" else # battery not present --> quit echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1).not_present" return 1 fi ;; esac # TEMPLATE: assume config suffix matches battery name, i.e. works for BAT0/BAT1 only _bt_cfg_bat="$_bat_str" # determine natacpi sysfiles _bd_read="$ACPIBATDIR/$_bat_str" if [ "$_bm_thresh" = "natacpi" ]; then _bf_start="$ACPIBATDIR/$_bat_str/$_bn_start" _bf_stop="$ACPIBATDIR/$_bat_str/$_bn_stop" fi echo_debug "bat" "batdrv.${_batdrv_plugin}.select_battery($1): bat_str=$_bat_str; cfg=$_bt_cfg_bat; bd_read=$_bd_read; bf_start=$_bf_start; bf_stop=$_bf_stop" return 0 } batdrv_read_threshold () { # read and print charge threshold # $1: start/stop # $2: 0=api/1=tlp-stat output # global params: $_batdrv_plugin, $_bm_thresh, $_bf_start, $_bf_stop # out: # - api: 0..100/"" on error # - tlp-stat: 0..100/"(not available)" on error # rc: 0=ok/4=read error/255=no api # prerequisite: batdrv_init(), batdrv_select_battery() local bf out="" rc=0 case "$1" in start) out="$X_THRESH_SIMULATE_START" ;; stop) out="$X_THRESH_SIMULATE_STOP" ;; esac if [ -n "$out" ]; then printf "%s" "$out" echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2).simulate: bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return 0 fi if [ "$_bm_thresh" = "natacpi" ]; then # read threshold from sysfile case "$1" in start) bf=$_bf_start ;; stop) bf=$_bf_stop ;; esac if ! out=$(read_sysf "$bf"); then # not readable/non-existent if [ "$2" != "1" ]; then out="" else out="(not available)" fi rc=4 fi else # no threshold api if [ "$2" = "1" ]; then out="(not available)" fi rc=255 fi # "return" threshold if [ "$X_THRESH_SIMULATE_READERR" != "1" ]; then printf "%s" "$out" else if [ "$2" = "1" ]; then printf "(not available)\n" fi rc=4 fi echo_debug "bat" "batdrv.${_batdrv_plugin}.read_threshold($1, $2): bm_thresh=$_bm_thresh; bf=$bf; out=$out; rc=$rc" return $rc } batdrv_write_thresholds () { # write both charge thresholds for a battery # use pre-determined method and sysfiles from global parms # $1: new start threshold 0(disabled)..100/DEF(default) # $2: new stop threshold 0..100/DEF(default) # $3: 0=quiet/1=output parameter errors/2=output progress and errors # $4: non-empty string indicates thresholds stem from configuration # global params: $_batdrv_plugin, $_bm_thresh, $_bat_str, $_bt_cfg_bat, $_bf_start, $_bf_stop # rc: 0=ok/ # 1=not configured/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=threshold read error/ # 5=threshold write error # prerequisite: batdrv_init(), batdrv_select_battery() local new_start="${1:-}" local new_stop="${2:-}" local verb="${3:-0}" local old_start old_stop # insert defaults [ "$new_start" = "DEF" ] && new_start=$_bt_def_start [ "$new_stop" = "DEF" ] && new_stop=$_bt_def_stop # --- validate thresholds local rc if [ -n "$4" ] && [ -z "$new_start" ] && [ -z "$new_stop" ]; then # do nothing if unconfigured echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).not_configured: bat=$_bat_str; cfg=$_bt_cfg_bat" return 1 fi # TEMPLATE: customize boundaries to vendor specs # start: check for 3 digits max, ensure min 0 / max 100 if ! is_uint "$new_start" 3 || \ ! is_within_bounds "$new_start" 0 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_start: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at START_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_start}\": not specified, invalid or out of range (0..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at START_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (0..100). Aborted.\n" "$_bt_cfg_bat" "$new_start" 1>&2 else cprintf "" "Error: start charge threshold (%s) for battery %s is not specified, invalid or out of range (0..100). Aborted.\n" "$new_start" "$_bat_str" 1>&2 fi ;; esac return 2 fi # TEMPLATE: customize boundaries to vendor specs # stop: check for 3 digits max, ensure min 0 / max 100 if ! is_uint "$new_stop" 3 || \ ! is_within_bounds "$new_stop" 0 100; then # threshold out of range echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_stop: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration at STOP_CHARGE_THRESH_${_bt_cfg_bat}=\"${new_stop}\": not specified, invalid or out of range (0..100). Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration at STOP_CHARGE_THRESH_%s=\"%s\": not specified, invalid or out of range (0..100). Aborted.\n" "$_bt_cfg_bat" "$new_stop" 1>&2 else cprintf "" "Error: stop charge threshold (%s) for battery %s is not specified, invalid or out of range (0..100). Aborted.\n" "$new_stop" "$_bat_str" 1>&2 fi ;; esac return 2 fi # TEMPLATE: customize assertion to vendor specs # check start < stop if [ "$new_start" -ge "$new_stop" ]; then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).invalid_diff: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) if [ -n "$4" ]; then echo_message "Error in configuration: START_CHARGE_THRESH_${_bt_cfg_bat} >= STOP_CHARGE_THRESH_$_bt_cfg_bat. Battery skipped." fi ;; 2) if [ -n "$4" ]; then cprintf "" "Error in configuration: START_CHARGE_THRESH_%s >= STOP_CHARGE_THRESH_%s. Aborted.\n" "$_bt_cfg_bat" "$_bt_cfg_bat" 1>&2 else cprintf "" "Error: start threshold >= stop threshold for battery %s. Aborted.\n" "$_bat_str" 1>&2 fi ;; esac return 3 fi # read active threshold values if ! old_start=$(batdrv_read_threshold start 0) || \ ! old_stop=$(batdrv_read_threshold stop 0); then echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).read_error: bat=$_bat_str; cfg=$_bt_cfg_bat" case $verb in 1) echo_message "Error: could not read current charge threshold(s) for battery $_bat_str. Battery skipped." ;; 2) cprintf "" "Error: could not read current charge threshold(s) for battery %s. Aborted.\n" "$_bat_str" 1>&2 ;; esac return 4 fi # TEMPLATE: customize assertion to vendor specs # determine write sequence too meet boundary condition start < stop # disclaimer: the driver doesn't enforce it but we don't know about the # firmware and it's reasonable anyway local rc=0 steprc tseq if [ "$new_start" -ge "$old_stop" ]; then tseq="stop start" else tseq="start stop" fi # write new thresholds in determined sequence if [ "$verb" = "2" ]; then printf "Setting temporary charge thresholds for battery %s:\n" "$_bat_str" 1>&2 fi for step in $tseq; do local old_thresh new_thresh steprc case $step in start) old_thresh=$old_start new_thresh=$new_start ;; stop) old_thresh=$old_stop new_thresh=$new_stop ;; esac if [ "$old_thresh" != "$new_thresh" ]; then # new threshold differs from effective one --> write it case $step in start) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_start" ;; stop) [ "$X_THRESH_SIMULATE_WRITEERR" != "1" ] && write_sysf "$new_thresh" "$_bf_stop" ;; esac steprc=$?; [ $steprc -ne 0 ] && [ $rc -eq 0 ] && rc=5 echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.write: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh; steprc=$steprc" case $verb in 2) if [ $steprc -eq 0 ]; then printf " %-5s = %3d\n" "$step" "$new_thresh" 1>&2 else cprintf "err" " %-5s = %3d (Error: write failed)\n" "$step" "$new_thresh" 1>&2 fi ;; 1) if [ $steprc -gt 0 ]; then echo_message "Error: writing $step charge threshold for $_bat_str failed." fi ;; esac else echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).$step.no_change: bat=$_bat_str; cfg=$_bt_cfg_bat; old=$old_thresh; new=$new_thresh" if [ "$verb" = "2" ]; then printf " %-5s = %3d (no change)\n" "$step" "$new_thresh" 1>&2 fi fi done # for step echo_debug "bat" "batdrv.${_batdrv_plugin}.write_thresholds($1, $2, $3, $4).complete: bat=$_bat_str; cfg=$_bt_cfg_bat; rc=$rc" return $rc } # shellcheck disable=SC2120 batdrv_calc_soc () { # calc and print battery charge level (rounded) # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error # use generic implementation soc_calc "$1" } batdrv_chargeonce () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.charge_once.not_implemented" return 255 } batdrv_apply_configured_thresholds () { # apply configured stop thresholds from configuration to all batteries # - called for bg tasks tlp init [re]start/auto and tlp start # output parameter errors only # prerequisite: batdrv_init() local bat start_thresh stop_thresh for bat in BAT0 BAT1; do if batdrv_select_battery "$bat"; then eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" batdrv_write_thresholds "$start_thresh" "$stop_thresh" 1 1 fi done return 0 } batdrv_read_force_discharge () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" return 255 } batdrv_write_force_discharge () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" return 255 } batdrv_cancel_force_discharge () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" return 255 } batdrv_force_discharge_active () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" return 255 } batdrv_discharge_safetylock () { # check safety lock - force-discharge not implemented # $1: discharge/recalibrate # rc: 0=engaged/1=disengaged return 1 } batdrv_discharge () { # function not implemented # global param: $_batdrv_plugin # prerequisite: batdrv_init() # Important: release lock from caller unlock_tlp tlp_discharge echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" return 255 } batdrv_show_battery_data () { # output battery status # $1: 1=verbose # global params: $_batdrv_plugin, $_batteries, $_batdrv_kmod, $_bd_read, $_bf_start, $_bf_stop, $_bf_dischg # prerequisite: batdrv_init() local verbose="${1:-0}" printf "+++ Battery Care\n" printf "Plugin: %s\n" "$_batdrv_plugin" if [ "$_bm_thresh" != "none" ]; then cprintf "success" "Supported features: charge thresholds\n" else cprintf "warning" "Supported features: none available\n" fi printf "Driver usage:\n" # native kernel ACPI battery API case $_natacpi in 0) cprintf "success" "* natacpi (%s) = active (charge thresholds)\n" "$_batdrv_kmod" ;; 32) cprintf "notice" "* natacpi (%s) = inactive (disabled by configuration)\n" "$_batdrv_kmod" ;; 128) cprintf "err" "* natacpi (%s) = inactive (no kernel support)\n" "$_batdrv_kmod" ;; 254) cprintf "warning" "* natacpi (%s) = inactive (laptop not supported)\n" "$_batdrv_kmod" ;; *) cprintf "err" "* natacpi (%s) = unknown status\n" "$_batdrv_kmod" ;; esac # TEMPLATE: customize to vendor specs if [ "$_bm_thresh" != "none" ]; then printf "Parameter value ranges:\n" printf "* START_CHARGE_THRESH_BAT0/1: 0(off)..100\n" printf "* STOP_CHARGE_THRESH_BAT0/1: 0..100(default)\n" fi printf "\n" # -- show battery data local bat local bcnt=0 for bat in $_batteries; do # iterate batteries batdrv_select_battery "$bat" printf "+++ Battery Status: %s\n" "$bat" print_bat_make "$verbose" print_bat_cycle_count "$_bd_read/cycle_count" print_bat_energy print_bat_state "$_bd_read/status" printf "\n" print_bat_voltages "$verbose" # --- show battery features: thresholds if [ "$_bm_thresh" = "natacpi" ]; then printf "%-59s = %6s [%%]\n" "$_bf_start" "$(batdrv_read_threshold start 1)" printf "%-59s = %6s [%%]\n" "$_bf_stop" "$(batdrv_read_threshold stop 1)" printparm "%-59s = ##%6s##" "$_bd_read/charge_behaviour" "not available" printf "\n" fi # --- show charge level (SOC) and capacity print_bat_level bcnt=$((bcnt+1)) done # for bat [ $bcnt -gt 1 ] && print_bat_level_total return 0 } batdrv_check_soc_gt_stop () { # check if battery charge level (SOC) is greater than the stop threshold # rc: 0=greater/1=less or equal (or thresholds not supported) # global params: $_bm_thresh, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() local soc stop if [ "$_bm_thresh" = "natacpi" ] && soc="$(batdrv_calc_soc)"; then stop="$(batdrv_read_threshold stop 0)" if [ -n "$stop" ] && [ "$soc" -gt "$stop" ]; then return 0 fi fi return 1 } batdrv_recommendations () { # output TEMPLATE laptop specific recommendations # prerequisite: batdrv_init() soc_gt_stop_recommendation return 0 } TLP-1.10.1/changelog000066400000000000000000002030021517565574500141240ustar00rootroot00000000000000This is the user-oriented changelog. For information regarding packaging, please refer to: https://linrunner.de/tlp/developers/packaging.html +++ 1.10.1 --- 04.05.2026 +++ * Bugfix Release Battery Care: - ThinkPads: disable 'tlp discharge|recalibrate' if battery ratings are not reported in energy_* [mWh], since the workflow cannot handle this. A scenario that could arise with upcoming interim versions of Coreboot. - Lenovo laptops (non-ThinkPad series and ThinkBooks): fix the "could not read charge type" error triggered by a firmware update that introduces the "Fast" charge type (Issue #882). Operation: - Fix tlp.service start on Fedora +++ 1.10.0 --- 20.04.2026 +++ * Bugfixes since 1.10.0-beta.2 Disks: - tlp-stat -d: fix incomplete device list +++ 1.10.0-beta.2 --- 07.04.2026 +++ * Beta Release * Features since 1.10.0-beta.1 Battery Care: - Framework laptops: remove the obsolete framework plugin, as the required out-of-tree kernel module framework_laptop was abandoned in favor of the upstream driver cros_charge-control back in 2024. Unfortunately, the current state of setting charge thresholds on Framework hardware is as follows: - Recent changes to Framework's EC firmware have broken the cros_charge-control kernel module, which forms the base for TLP's alternative cros-ec plugin. - The only remaining viable option is currently to use 'framework_tool' (part of the framework-system library). However, this does not provide the standard sysfs attributes charge_control_start/end_threshold, which prevents TLP from setting charge thresholds. General: - TLP_DISABLE_DEFAULTS: exclude TLP_AUTO_SWITCH=2. Specify all excluded parameters in tlp.conf to make operating behaviour transparent. Graphics: - INTEL_GPU_POWER_PROFILE_ON_BAT: change default to power_saving PCIe Devices: - PCIE_ASPM_ON_SAV added for power-saver profile Profiles: - TLP_AUTO_SWITCH: substitute invalid config with 0 * Bugfixes since 1.10.0-beta.1 General: - Fix disabled bluetooth after suspend/resume cycle when paired with system76-driver (Issue #872) Graphics: - Fix INTEL_GPU_POWER_PROFILE_AC/BAT/SAV not working for GPUs with multiple GTs +++ 1.10.0-beta.1 --- 27.03.2026 +++ * Beta Release * Features Battery Care: - Lenovo laptops (non-ThinkPad series and ThinkBooks) use the new charge_types sysfs attribute as of kernel 6.17 (Issue #831). Support for the legacy interface in kernels < 6.17 has been split off into the additional plugin lenovo-legacy. - Introduce start and stop threshold for laptops with Wilco EC, such as Dell Chromebooks modded with chrultrabook/coreboot custom UEFI firmware (Issue #838). - LG Gram laptops: remove support for kernels < 5.18 (lg-legacy plugin). Note: the lg plugin for kernel 5.18 and newer remains included, upgrade your kernel. - ThinkPads: - Charge thresholds are not restored or changed by 'tlp start' or automatic operation while 'tlp discharge/recalibrate' is running - Streamline model detection - RESTORE_THRESHOLDS_ON_BAT: change default to enabled. If the charger is disconnected, for instance after a 'tlp fullcharge', then the configured charge thresholds will be automatically restored. - tlp-stat -b: on laptops that provide battery metrics in mA(h), calculate the equivalent in mW(h) and display it adjacently (Issue #820) - tlp-stat -b -v: show battery serial number General: - Remove backward compatibility with /etc/default/tlp - tlp-stat -s: - "Power profile" renamed to "TLP profile" to distinguish it from similarly sounding kernel tunables - OS Release: fix parsing of Gentoo's /etc/os-release with '' quoting - tlp-stat -T: show timestamps with microsecond precision - Use 'daemon' facility for logging and trace Graphics: - Introduce support for Intel Xe graphics (Issue #857): - INTEL_GPU_POWER_PROFILE_ON_AC/BAT/SAV: SLPC power profile - INTEL_GPU_MIN/MAX_FREQ_ON_AC/BAT/SAV: min/max frequency - RADEON_DPM_PERF_LEVEL_ON_SAV added for power-saver profile (Issue #850) - RADEON_DPM_STATE_ON_AC/BAT/SAV: *DEPRECATED* feature will be removed in TLP 1.11. Only affects the legacy driver radeon. - tlp-stat -g: show power_state for AMD and Nvidia GPUs - RUNTIME_PM_DRIVER_DENYLIST: add amdgpu, nvidia to default to facilitate hybrid GPU powerdown PCIe Devices: - tlp-stat -e: indicate when autosuspend is deactivated using RUNTIME_PM_ON_AC/BAT="" Profiles: - TLP_PROFILE_AC/BAT: customize automatic TLP profiles for AC and battery. For example, they can be changed to balanced on AC power and power-saver on battery power (Issue #844). - TLP_DEFAULT_MODE: renamed to TLP_PROFILE_DEFAULT - TLP_PERSISTENT_DEFAULT: *DEPRECATED* instead use TLP_AUTO_SWITCH=0 together with TLP_PROFILE_DEFAULT to lock TLP to a fixed profile - Reversed the order of TLP's profiles in the desktop UI to match power-profiles-daemon's order - Rewrite of tlp-pd to use only GLib and no longer python-dbus - Sandbox tlp-pd.service - tlp-pd property BatteryAware now reflects TLP_AUTO_SWITCH (0=false, 1/2=true) and becomes de facto readonly, i.e. it discards written values Processor: - tlp-stat -p: section "+++ Platform profile" renamed to "+++ Platform" Radio Devices: - RESTORE_DEVICE_STATE_ON_STARTUP: *DEPRECATED* feature will be removed in TLP 1.11 * Bugfixes Battery Care: - ThinkPads: - tlp discharge/recalibrate: catch reset during initialization, refactor process - Fix detection of T580 with Libreboot (Issue #862) Power Profiles: - tlpctl: recover --version +++ 1.9.1 --- 07.01.2026 +++ * Bugfix Release Power Profiles (tlp-pd) - Fix Polkit Authentication Bypass in Profiles Daemon in Version 1.9.0 (CVE-2025-67859) - https://security.opensuse.org/2026/01/07/tlp-polkit-authentication-bypass.html - Version 1.8.0 and older are not affected - Ensure that all processes that change the power profile or the daemon's log level are authenticated, either by a desktop user session or as root - Limit the number of stacked profile holds (tlpctl launch) to 16 - Make profile hold cookies unpredictable, to prevent unrelated users from releasing an active profile hold Battery: - Fix RESTORE_THRESHOLDS_ON_BAT: consider the change to battery power, not the profile. Ensure even after suspension. +++ 1.9.0 --- 01.12.2025 +++ * Features since 1.9.0-beta.1 Power Profiles: - tlp-stat --pd-diag: display active desktop session * Bugfixes since 1.9.0-beta.1 Disks: - Fix double processing of plugged USB disks - tlp-stat -d: fix anonymization of newish NVMe disk ids Operation: - run-on-ac/bat: fix power source detection (regression in 1.9.0-beta.1) +++ 1.9.0-beta.1 --- 05.11.2025 +++ * Beta Release * Features Power Profiles: - tlp-pd: TLP Profiles Daemon is a new addition to TLP. It implements the D-Bus interface, which lets desktop environments show a power profile switch. Together with TLP as the backend for applying these profiles, it replaces power-profiles-daemon. - Introduce a third configuration profile for maximum power savings and make tlp-pd possible. The following are now available: - performance: parameters ending in _AC are used when AC power is connected or when the new command 'tlp performance' is run. - balanced: parameters ending in _BAT are used when operating on battery power or when the new command 'tlp balanced' is run. - power-saver: parameters ending in _SAV are used when the new command 'tlp power-saver' is run. If there is no _SAV parameter available for a feature, the _BAT parameter will be used instead. - tlpctl: new command to switch between power profiles, launch applications with specific profiles and control tlp-pd. It offers a subset of powerprofilesctl's commands supplemented by TLP-specific shortcuts. - TLP_AUTO_SWITCH: controls the automatic switching of the power profile when the charger is connected or removed (even during a suspend). The Innovation (and new default) is a "smart" mode that can retain a manually selected profile when the power source changes. - Use the TLP_DEFAULT_MODE profile when automatic switching is disabled. - tlp-stat --pd-diag: show tlp-pd diagnostic - tlp: drop the commands 'false' and 'true', which have been superseded by 'ac' and 'bat' for ages Battery Care: - Tuxedo laptops supported by the clevo_acpi out-of-tree driver from tuxedo-drivers: start and stop threshold (Issue #803); only models with a Clevo ODM chassis are compatible, check the tlp-stat -b output to find out if yours is - tlp-stat -b: advertise chargeonce, discharge, recalibrate features individually General: - TLP_DISABLE_DEFAULTS: deactivate all intrinsic defaults so that only settings that have been explicitly activated are applied; helpful to use only selected features - tlp-stat -s: unified representation of tlp/tlp-rdw/tlp-pd's status - tlp-stat --trace-nm: show the journal entries for NetworkManager correlated with TLP's trace output, supplemented with device details for NetworkManager and rfkill - Response to invalid subcommands/options: output not only “Usage:” but also “Error:” Graphics: - New parameter for the power-saver profile: AMDGPU_ABM_LEVEL_ON_SAV - tlp-stat -g -v: show power state and clocks of AMD GPUs - RADEON_POWER_PROFILE_ON_AC/BAT removed (deprecated since 1.8.0) Processor: - PLATFORM_PROFILE_ON_AC/BAT/SAV: activated by default - New parameters for the power-saver profile: - CPU_DRIVER_OPMODE_ON_SAV - CPU_SCALING_GOVERNOR_ON_SAV - CPU_SCALING_MIN/MAX_FREQ_ON_SAV - CPU_MIN/MAX_PERF_ON_SAV - CPU_BOOST_ON_SAV - CPU_HWP_DYN_BOOST_ON_SAV - tlp-stat -p: add to display per core: - amd_pstate_lowest_nonlinear_freq, amd_pstate_max_freq - (-v) scaling_cur_freq (current core frequency) - (-v) amd_pstate_hw_prefcore, amd_pstate_prefcore_ranking - tlp-stat -t -v: show all individual temperature/fan sensors Radio Devices: - bluetooth/nfc/wifi/wwan: add command 'cycle', equivalent of 'toggle' twice - DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE: Fix the disabling of nfc (Issue #808) - tlp-stat -s: warn if RDW is configured but tlp-rdw is not installed - tlp-stat -r -v: show device details for NetworkManager and rfkill * Bugfixes Battery Care: - Chromebooks, Framework laptops: fix tlp discharge to a target percentage - ThinkPads: - Fix X201, X220 discharge (Issue #793) - Fix bash completion for tlp discharge on tp-smapi models - Detect coreboot more reliable via DMI bios_vendor - tlp-stat -b: - Fix X13s Gen 1 ARM data read out (Issue #807) - Do not wrongly recommend kernel 5.17 if coreboot is detected, as coreboot does not implement discharge anyway Radio Devices: - Ensure clean wifi/wwan switching by keeping the status of NetworkManager and TLP in sync - bluetooth/nfc/wifi/wwan: restore device status display +++ 1.8.0 --- 13.02.2025 +++ * Features since 1.8.0-beta.1 General: - tlp.8 manpage: explain battery name command parameters - tlp.conf: parameter qualifiers don't have to match battery names Battery Care: - bash completion: suggest stop threshold as target level for tlp discharge * Bugfixes since 1.8.0-beta.1 Battery Care: - Dell: point to BIOS Admin password as cause of a threshold write error (Issue #785) +++ 1.8.0-beta.1 --- 19.01.2025 +++ * Beta Release * Features Battery Care: - Chromebooks modded with chrultrabook/coreboot custom UEFI firmware running kernel 6.12.8 (or later): stop threshold, recalibration; latest Chromebooks with EC firmware v3 also support a start threshold (Issue #765) - Dell laptops with kernel 6.12 (or later): start and stop threshold (Issue #379) - Framework laptops with kernel 6.12.8 (or later): stop threshold, recalibration (Issue #765) - ThinkPads: - tlp discharge to a target percentage - Discharge code refactored, messages improved - tlp chargeonce: better explain the process in the output Diagnostic: - tlp-stat --psup: add readings Graphics: - AMDGPU_ABM_LEVEL_ON_AC/BAT: write only when a change is made to avoid unnecessary screen flickering - RADEON_POWER_PROFILE_ON_AC/BAT: *DEPRECATED* feature will be removed in TLP 1.9 Processor: - tlp-stat -p -v: show boost per core (amd-pstate needs kernel >= 6.11) * Bugfixes Operation: - Use AC mode for desktop PCs if no power source can be detected (Issue #768) Processor: - tlp-stat -t: discover all coretemp sensors Radio Devices: - RDW: if LAN gets disconnected in suspend, ensure wifi activation upon resume +++ 1.7.0 --- 27.09.2024 +++ * No changes since 1.7.0-beta.2 +++ 1.7.0-beta.2 --- 20.09.2024 +++ * Beta release * Features since 1.7.0-beta.1 Processor: - tlp-stat -p: identify CPU model on ARM systems * Bugfixes since 1.7.0-beta.1 Battery: - ThinkPads: - Fix premature abort of discharge/recalibrate for *20 models (Issue #759) - Fix ThinkPad X13s ARM power source detection (Issue #758) - tlp-stat -b: do not recommend kernel 5.17 for coreboot, as coreboot does not support discharge/recalibrate anyway. USB: - tlp-stat -u: filter out error messages +++ 1.7.0-beta.1 --- 04.09.2024 +++ * Beta release * Features Battery Care: - Apple Silicon Macbooks (M*) with MacOS 13.0 (or later) firmware and Asahi Linux kernel 6.6 (or later): start and stop threshold - MSI laptops supported by the msi_ec driver in kernel 6.3 (or later): start and stop threshold (Issue #707) - Lenovo laptops (non-ThinkPad series): tlp-stat -b: number 60% removed because the threshold varies by model (Issue #717) - LG Gram laptops: forward to lg-legacy for kernel <= 5.17 only - ThinkPads: - The obsolete tpacpi-bat driver along with the acpi_call dependency were removed. Current Linux distributions ship with kernel 5.17 or later, which enables battery care via the built-in thinkpad_acpi. - tlp-stat -b improvements for coreboot: - Fix charge readings (Issue #657) - Indicate when recalibration is not possible - tlp-stat -b: Highlight battery care status - tlp setcharge, tlp-stat -b: display a recommendation to use the laptop on battery power until the battery is discharged to the stop threshold Disks: - tlp-stat -d: replace disk serial number with asterisks (for privacy) General: - TLP_MSG_COLORS: highlight error, warning, notice and success messages in color. - Add option --version to all TLP commands - Add tlp-stat output options: -m|--mode: print current power mode -q|--quiet: omit version header and show less information in the processor category - Add Fish shell command completion - Improve busybox compatibility by using only portable options for flock Graphics: - AMDGPU_ABM_LEVEL_ON_AC/BAT: configure display panel power savings via Adaptive Backlight Modulation (ABM) from AMD Vega or newer GPUs; kernel 6.9 required Processor: - CPU_ENERGY_PERF_POLICY_ON_AC/BAT: drop backwards compatibility for EPB with kernels < 5.2; x86_energy_perf_policy is no longer required - CPU_HWP_DYN_BOOST_ON_AC/BAT: drop the AMD implementation; the corresponding kernel patch was discarded during the 2022 review, reincarnation seems unlikely - tlp-stat -t: - Show AMD CPU temperatures - Indicate unavailable fan speed Radio Devices: - DEVICES_TO_ENABLE/DISABLE_ON_SHUTDOWN removed USB: - USB_AUTOSUSPEND_DISABLE_ON_SHUTDOWN removed * Bugfixes Battery: - ThinkPads: - Fix Edge 11 battery enumeration (Issue #714) - Support ThinkPad W510 Disks: - Ensure power saving for all USB disks when plugged in - tlp diskid: filter duplicates General: - tlp-stat -s: rework manual mode detection (Issue #702) - tlp-stat --psup/udev: fix udev rule check Radio Devices: - Avoid error popups from NetworkManager when switching wifi/wwan USB: - Prevent USB devices from being unrecognized or malfunctioning on battery; adds xhci_hcd to RUNTIME_PM_DRIVER_DENYLIST defaults (Issues #436, #587) +++ 1.6.1 --- 18.09.2023 +++ * Bugfixes Battery: - LG Gram laptops: ensure lg-legacy is used for kernel <= 5.17 (Issue #708) General: - tlp-stat -s: report manual mode despite restrictive umask (Issue #702) +++ 1.6.0 --- 24.08.2023 +++ * Changes since 1.6.0-beta.1 Battery: - System76: default start threshold set to 90% (due to EC firmware change) Processor: - CPU_DRIVER_OPMODE_ON_AC/BAT: check if operation mode is actually supported by the current CPU driver +++ 1.6.0-beta.1 --- 20.07.2023 +++ * Beta release * Features Battery: - LG Gram laptops: - Use standard sysfs attribute 'charge_control_end_threshold' provided by kernel 5.18 and newer - Restore threshold after hibernate - System76 laptops with open source EC firmware: start and stop threshold - ThinkPads: model detection adapted for Libreboot (Issue #679) - Toshiba/Dynabook laptops: stop threshold 80/100% Configuration: - Allow comments (#) after parameters (Issue #598) - tlp-stat -c/--cdiff: append a notice to deprecated or removed parameters Disks: - SATA_LINKPWR_ON_AC/BAT: remove fallbacks for outdated kernels before 4.15 from defaults (still available via explicit configuration) General: - Allow coexistence with power-profiles-daemon: do not apply PLATFORM_PROFILE_ON_AC/BAT, CPU_ENERGY_PERF_POLICY_ON_AC/BAT and CPU_BOOST_ON_BAT/BAT when it is running - MEM_SLEEP_ON_AC/BAT: change system suspend mode - Add ZSH command completion - tlp-stat -s: - Silence warnings about unmasked systemd-rfkill.service/.socket when DEVICES_TO_ENABLE/DISABLE_ON_STARTUP is actually unconfigured - Show EC firmware version - Show system suspend mode - Show proper ThinkPad model string with Libreboot (Issue #679) PCI(e) devices: - RUNTIME_PM_ENABLE/DISABLE: apply even when RUNTIME_PM_ON_AC/BAT is disabled (Issue #614) Processor: - CPU_DRIVER_OPMODE_ON_AC/BAT: set CPU scaling driver operation mode (active, guided, passive); amd-pstate/intel_pstate driver required - CPU_ENERGY_PERF_POLICY_ON_AC/BAT: support AMD Zen 2 or newer CPUs; kernel 6.3 with amd-pstate driver in active mode required - CPU_HWP_DYN_BOOST_ON_AC/BAT: *EXPERIMENTAL* support AMD Zen 2 or newer CPUs; yet unreleased kernel 6.x and amd-pstate driver in active mode required - SCHED_POWERSAVE_ON_AC/BAT removed (unavailable since kernel 3.5) - tlp-stat -p: - Show amd-pstate operation mode, dynamic boost and performance attributes - Show min/max operating frequency the processor can run at (cpuinfo_min/max_freq) and limit imposed by the BIOS (bios_limit) - (-v) Show acpi_cppc performance and frequency attributes Radio Devices: - Support Thunderbolt docks to switch radio devices - DEVICES_TO_ENABLE/DISABLE_ON_SHUTDOWN: *DEPRECATED* feature will be removed in TLP 1.7 USB: - USB_AUTOSUSPEND_DISABLE_ON_SHUTDOWN: *DEPRECATED* feature will be removed in TLP 1.7 * Bugfixes Configuration: - Strip trailing blanks from unquoted parameter values Operation: - Deactivate AHCI_RUNTIME_PM and PCIE_ASPM before suspend to avoid resume freezes (Issue #593, #606, #698) Processor: - Set governor although not listed in scaling_available_governors +++ 1.5.0 --- 07.01.2022 +++ * Bugfixes - tlp-stat: drop PM_RUNTIME=y kernel config recommendation +++ 1.5.0-beta.1 --- 20.12.2021 +++ * Beta release * Features Battery: - Sony laptops: stop threshold 50/80/100% aka "battery care limiter" - ThinkPads: - Use new sysfs attribute 'charge_behaviour' for recalibration - Support T400 running coreboot via natacpi (Issue #601) - tlp-stat -b: display "cycle_count = 0" with the note "or not supported" Radio Devices: - Add support for switching NFC devices - Remove support for wireless-tools (iwconfig) * Bugfixes Battery: - ASUS laptops: apply stop threshold reliably on boot and after hibernate (Issue #589) Disks: - tlp-stat -d: display correct sysfs path for disk runtime pm: /sys/block//device/power/control (Issue #606) +++ 1.4.0 --- 24.09.2021 +++ * Features Audio: - SOUND_POWER_SAVE_ON_AC: change default to enabled (Issue #495) Battery: - "Battery Features" renamed to "Battery Care" - Introduce plugins to support Battery Care for non-ThinkPads: - ASUS laptops: stop threshold - Huawei MateBooks: start and stop threshold - LG Gram laptops: stop threshold at 80% aka "battery care limit" - Lenovo laptops: stop threshold at 60% aka "battery conservation mode" - Samsung laptops: stop threshold at 80% aka "battery life extender" - ThinkPads - Allow deactivation of the start threshold (START_CHARGE_THRESH_BATx=0) - Discharge malfunction: hint to check battery and charger - Use standard sysfs attributes for charge thresholds (Issue #513) - Flag ThinkPad L512 as "unsupported" - Validate charge threshold configuration, don't fail silently - tlp-stat -b - Battery Care: show plugin, supported features, driver usage and related kernel module(s) - Map battery state "Unknown" to "Idle" for clarity Configuration: - tlp-stat --cdiff: show configuration differing from defaults - PARAMETER+="add values": append values to a parameter already defined as intrinsic default or in a *previously* read file (Issue #457) - TLP_WARN_LEVEL: warn about invalid settings, configurable for background tasks and command line - Rename parameters (backwards compatible to legacy config files): SATA_LINKPWR_BLACKLIST -> SATA_LINKPWR_DENYLIST RUNTIME_PM_BLACKLIST -> RUNTIME_PM_DENYLIST RUNTIME_PM_DRIVER_BLACKLIST -> RUNTIME_PM_DRIVER_DENYLIST USB_BLACKLIST -> USB_DENYLIST USB_BLACKLIST_BTUSB -> USB_EXCLUDE_BTUSB USB_BLACKLIST_PHONE -> USB_EXCLUDE_PHONE USB_BLACKLIST_PRINTER -> USB_EXCLUDE_PRINTER USB_BLACKLIST_WWAN -> USB_EXCLUDE_WWAN USB_WHITELIST -> USB_ALLOWLIST Disks: - AHCI_RUNTIME_PM_ON_AC/BAT: - Works only on disks defined in DISK_DEVICES - Works on NVMe (new), SATA/ATA and plugged in USB (new) disks - Works on SATA ports - No longer experimental i.e. now enabled by default; the previously existing risk of system freezes (and data loss) with the multiqueue scheduler is now eliminated: - Kernel >= 4.19 itself locks unsafe disk runtime pm - TLP generally locks it for kernel < 4.19 - DISK_APM_CLASS_DENYLIST: exclude disk classes from APM, i.e. you may now activate it for USB and IEEE1394 drives (Issue #523) - DISK_APM_LEVEL_ON_AC/BAT, DISK_SPINDOWN_TIMEOUT_ON_AC, DISK_IOSCHED: now also work when plugging in USB disks; provided disk is contained in DISK_DEVICES and 'usb' is removed from DISK_APM_CLASS_DENYLIST - tlp-stat -d - Explain why AHCI_RUNTIME_PM is locked for a disk - Show disks attached to SATA links/ports - Show a disk's host (for SATA_LINKPWR_DENYLIST) - Show ALPM state in the sysfs directory of the AHCI host - Show IDs for all disks configured in DISK_DEVICES - Distinguish SATA from ATA(IDE) disks - Show NVMe disk temperature General: - power-profiles-daemon: issue error messages about conflicting service - tlp-stat -s: determine OS release without the lsb_release utility Graphics: - Add support for amdgpu (Issue #498) - Improve powerdown of unused GPUs with drivers amdgpu, nouveau, nvidia and without driver (Issues #488, #495, #498) - tlp-stat -g: - List all GPUs with at least the driver info - Show hybrid graphics switch state (switcheroo) - Intel GPU: - Show hardware min/max frequency instead of list of available frequencies (kernel change) - Show more informative RC6, FBC and PSR status where available (Issue #203) Operation Mode AC/BAT: - Speed up system shutdown/suspend by not applying AC settings anymore - TLP_PS_IGNORE: add USB; allow to ignore multiple power supply classes - tlp-stat -s: speed up power source detection - tlp-stat --psup/udev: check if udev rules for power source changes and connecting USB devices are active PCI(e) devices: - RUNTIME_PM_ENABLE/DISABLE: permanently enable/disable runtime PM for PCI(e) devices based on address (independent of the power source) - RUNTIME_PM_DENYLIST: remove amdgpu, nvidia, pcieport from defaults - tlp-stat -e -v: show device runtime_status Platform: - PLATFORM_PROFILE_ON_AC/BAT: select platform profile to control system operating characteristics around power/performance levels, thermal and fan speed - tlp-stat -p: show state of ThinkPad lapmode Processor: - CPU_HWP_DYN_BOOST_ON_AC/BAT: Intel CPU HWP dynamic boost (Issue #468) - Remove backwards compatibility of CPU_ENERGY_PERF_POLICY_ON_AC/BAT with ENERGY_PERF_POLICY_ON_AC/BAT to prevent performance issues caused by the value 'power' in legacy configurations - PHC_CONTROLS removed (obsolete) - tlp-stat -p: - cpu1..cpuN omitted for clarity, use -v to show all - Sort more than 10 CPU cores in proper numerical order - Show intel_pstate operation mode ("status") - Omit "EPB: unsupported" for AMD or non-intel_pstate CPUs USB: - USB_EXCLUDE_AUDIO: exclude audio devices from autosuspend (Issue #556) - tlp-stat -u -v: show device runtime_status * Bugfixes Battery Care: - tlp discharge/recalibrate: - Terminate properly when AC/charger is removed - Check support on ThinkPads because of Coreboot (Issue #547) - [Try to] mitigate false threshold readouts caused by a firmware issue on ThinkPad A/E/L/S/X series (Issue #369) Disks: - Issue #474: confine AHCI_RUNTIME_PM_ON_AC/BAT to SATA disks Graphics: - Issue #488: Idle temperature 20 °C higher on battery (Nvidia GPU) - Issue #495: SOUND_POWER_SAVE_ON_AC=0 prevents powerdown of Nvidia GPU - Issue #522: Intel GPU settings fail -> validate configuration Operation Mode AC/BAT: - Issue #573: power mode not updating when switching from AC to battery Processor: - Issue #570: no_turbo=1 decreases scaling_max_freq on ThinkPad X1 Gen9 * 1.4.0 Bugfixes since beta.2 Battery: - tlp start: catch missing threshold method Configuration: - Ignore trailing blanks on non-quoted parameter values - tlp-stat --cdiff: do not show user config lines matching the default Disks: - Confine plugin event handling to USB +++ 1.4.0-beta.2 --- 09.09.2021 +++ * Beta release * Bugfixes Battery: - Huawei: use legacy interface 'charge_control_thresholds' for reliability - LG: fix battery and battery_care_limit detection (Issue #568) - ThinkPad: fix enforcement via NATACPI_ENABLE, TPAPCI_ENABLE Operation Mode AC/BAT: - Issue #573: power mode not updating when switching from AC to battery Platform: - tlp-stat -p: show state of ThinkPad lapmode Processor: - Issue #570: no_turbo=1 decreases scaling_max_freq on ThinkPad X1 Gen9 - tlp-stat -p: omit "EPB: unsupported" for AMD or non-intel_pstate CPUs +++ 1.4.0-beta.1 --- 29.07.2021 +++ * Beta release * Features Audio: - SOUND_POWER_SAVE_ON_AC: change default to enabled (Issue #495) Battery: - "Battery Features" renamed to "Battery Care" - Introduce plugins to support Battery Care for non-ThinkPads: - ASUS laptops: stop threshold - Huawei MateBooks: start and stop threshold - LG Gram laptops: stop threshold at 80% aka "battery care limit" - Lenovo laptops: stop threshold at 60% aka "battery conservation mode" - Samsung laptops: stop threshold at 80% aka "battery life extender" - ThinkPads - Allow deactivation of the start threshold (START_CHARGE_THRESH_BATx=0) - Discharge malfunction: hint to check battery and charger - Use standard sysfs attributes for charge thresholds (Issue #513) - Flag ThinkPad L512 as "unsupported" - Validate charge threshold configuration, don't fail silently - tlp-stat -b - Battery Care: show plugin, supported features, driver usage and related kernel module(s) - Map battery state "Unknown" to "Idle" for clarity Configuration: - tlp-stat --cdiff: show configuration differing from defaults - PARAMETER+="add values": append values to a parameter already defined as intrinsic default or in a *previously* read file (Issue #457) - TLP_WARN_LEVEL: warn about invalid settings, configurable for background tasks and command line - Rename parameters (backwards compatible to legacy config files): SATA_LINKPWR_BLACKLIST -> SATA_LINKPWR_DENYLIST RUNTIME_PM_BLACKLIST -> RUNTIME_PM_DENYLIST RUNTIME_PM_DRIVER_BLACKLIST -> RUNTIME_PM_DRIVER_DENYLIST USB_BLACKLIST -> USB_DENYLIST USB_BLACKLIST_BTUSB -> USB_EXCLUDE_BTUSB USB_BLACKLIST_PHONE -> USB_EXCLUDE_PHONE USB_BLACKLIST_PRINTER -> USB_EXCLUDE_PRINTER USB_BLACKLIST_WWAN -> USB_EXCLUDE_WWAN USB_WHITELIST -> USB_ALLOWLIST Disks: - AHCI_RUNTIME_PM_ON_AC/BAT: - Works only on disks defined in DISK_DEVICES - Works on NVMe (new), SATA/ATA and plugged in USB (new) disks - Works on SATA ports - No longer experimental i.e. now enabled by default; the previously existing risk of system freezes (and data loss) with the multiqueue scheduler is now eliminated: - Kernel >= 4.19 itself locks unsafe disk runtime pm - TLP generally locks it for kernel < 4.19 - DISK_APM_CLASS_DENYLIST: exclude disk classes from APM, i.e. you may now activate it for USB and IEEE1394 drives (Issue #523) - DISK_APM_LEVEL_ON_AC/BAT, DISK_SPINDOWN_TIMEOUT_ON_AC, DISK_IOSCHED: now also work when plugging in USB disks; provided disk is contained in DISK_DEVICES and 'usb' is removed from DISK_APM_CLASS_DENYLIST - tlp-stat -d - Explain why AHCI_RUNTIME_PM is locked for a disk - Show disks attached to SATA links/ports - Show a disk's host (for SATA_LINKPWR_DENYLIST) - Show ALPM state in the sysfs directory of the AHCI host - Show IDs for all disks configured in DISK_DEVICES - Distinguish SATA from ATA(IDE) disks - Show NVMe disk temperature General: - power-profiles-daemon: issue error messages about conflicting service - tlp-stat -s: determine OS release without the lsb_release utility Graphics: - Add support for amdgpu (Issue #498) - Improve powerdown of unused GPUs with drivers amdgpu, nouveau, nvidia and without driver (Issues #488, #495, #498) - tlp-stat -g: - List all GPUs with at least the driver info - Show hybrid graphics switch state (switcheroo) - Intel GPU: - Show hardware min/max frequency instead of list of available frequencies (kernel change) - Show more informative RC6, FBC and PSR status where available (Issue #203) Operation Mode AC/BAT: - Speed up system shutdown/suspend by not applying AC settings anymore - TLP_PS_IGNORE: add USB; allow to ignore multiple power supply classes - tlp-stat -s: speed up power source detection - tlp-stat --psup/udev: check if udev rules for power source changes and connecting USB devices are active PCI(e) devices: - RUNTIME_PM_ENABLE/DISABLE: permanently enable/disable runtime PM for PCI(e) devices based on address (independent of the power source) - RUNTIME_PM_DENYLIST: remove amdgpu, nvidia, pcieport from defaults - tlp-stat -e -v: show device runtime_status Platform: - PLATFORM_PROFILE_ON_AC/BAT: select platform profile to control system operating characteristics around power/performance levels, thermal and fan speed Processor: - CPU_HWP_DYN_BOOST_ON_AC/BAT: Intel CPU HWP dynamic boost (Issue #468) - Remove backwards compatibility of CPU_ENERGY_PERF_POLICY_ON_AC/BAT with ENERGY_PERF_POLICY_ON_AC/BAT to prevent performance issues caused by the value 'power' in legacy configurations - PHC_CONTROLS removed (obsolete) - tlp-stat -p: - cpu1..cpuN omitted for clarity, use -v to show all - Sort more than 10 CPU cores in proper numerical order - Show intel_pstate operation mode ("status") USB: - USB_EXCLUDE_AUDIO: exclude audio devices from autosuspend (Issue #556) - tlp-stat -u -v: show device runtime_status * Bugfixes Battery Care: - tlp discharge/recalibrate: - Terminate properly when AC/charger is removed - Check support on ThinkPads because of Coreboot (Issue #547) - [Try to] mitigate false threshold readouts caused by a firmware issue on ThinkPad A/E/L/S/X series (Issue #369) Disks: - Issue #474: confine AHCI_RUNTIME_PM_ON_AC/BAT to SATA disks Graphics: - Issue #488: Idle temperature 20 °C higher on battery (Nvidia GPU) - Issue #495: SOUND_POWER_SAVE_ON_AC=0 prevents powerdown of Nvidia GPU - Issue #522: Intel GPU settings fail -> validate configuration +++ 1.3.1 --- 06.02.2020 +++ * Bugfixes Processor: - Issue #460: default CPU_ENERGY_PERF_POLICY_ON_BAT=power too aggressive +++ 1.3.0 --- 31.01.2020 +++ * Features New configuration scheme: - /etc/default/tlp is replaced by /etc/tlp.conf - Settings are read in the following order: 1. Intrinsic defaults 2. /etc/tlp.d/*.conf - Drop-in customization snippets 3. /etc/tlp.conf - User configuration In case of identical parameters, the last occurrence has precedence - Parse config files instead of sourcing --> no more shell expansion Battery Features, tlp-stat -b: - Charge thresholds: better checks for command line and configuration; clearer error messages - tlp discharge: error message "check your hardware" when battery wasn't completely discharged (Issue #438) - Distinguish between "no kernel support" for natacpi (< 4.17) and "laptop not supported" (>= 4.17) - Supplement battery status "Unknown" with "threshold may prevent charging" when thresholds are available only General: - systemd: replace tlp-sleep.service with /lib/systemd/system-sleep/tlp Operation Mode AC/BAT: - TLP_PS_IGNORE: power supply class to ignore when determining operation mode; workaround for laptops reporting incorrect AC or battery status (Issue #446) PCI(e) devices: - PCIE_ASPM_ON_AC/BAT: add method 'powersupersave' (Issue #425) Processor: - CPU_ENERGY_PERF_POLICY_ON_AC/BAT: backward compatible merge of settings for Intel energy vs. performance policies EPB (ENERGY_PERF_POLICY_ON_AC/BAT) and HWP.EPP (CPU_HWP_ON_AC/BAT); when HWP.EPP is available, EPB is not set; validate parameters; eliminate external tool x86_energy_perf_policy for kernel >= 5.2 tlp-stat: - Configuration: show file where the parameter comes from - System Info: - Show SELinux status - Show RDW as 'disabled' when TLP is disabled as a whole - Indicate persistent mode - Intel CPU: don't show EPB values when HWP.EPP is active (see above) - PCIe ASPM: show available policies - Undervolting: remove "PHC kernel not available" message * Bugfixes Battery Features: - Issue #415: ThinkPad X240 discharge BAT1 malfunction when BAT0 is not installed Disks: - Fix NVMe detection on Linux 4.15 tlp-stat: - Issue #430: ignore hid device batteries +++ 1.2.2 --- 04.05.2019 +++ * Bugfixes Battery Features, tlp-stat -b: - Show data for batteries named other than BAT0/BAT1 (non-ThinkPads) - Issue #395: ThinkPad X1C6 discharge malfunction - Separate checks for charge thresholds and recalibration - Intelligible recommendation for tp-smapi on ThinkPad X220/T420 et al. - Explain "Battery Features: Charge Thresholds and Recalibrate" Disks: - Fix type determination Operation Mode AC/BAT: - MacBookPro 5.3: workaround for false AC offline status Processor, tlp-stat -p: - Fix read of /sys/devices/system/cpu/cpufreq/boost - x86_energy_perf_policy: detect unsupported CPUs in newer versions Radio Devices: - Issue #404: make RESTORE_DEVICE_STATE_ON_STARTUP=1 persistent again +++ 1.2.1 --- 18.03.2019 +++ * Bugfix - Issue #391: unknown command "chargeonce" +++ 1.2 --- 11.03.2019 +++ * Features Disks: - Support for NVMe devices - Support for removable drives e.g. USB / IEE1394 devices - Improve support for multi queue I/O schedulers (blk-mq) General: - tlp bat/ac: keep manual power settings until tlp start (Issue #349) - Remove all pm-utils scripts (Issue #363) - tlp/tlp-stat: Temporarily overwrite configuration for one program invocation only: -- PARAM=value ... - Document intrinsic defaults in config file (Issue #353) - Code verified with ShellCheck Graphics: - INTEL_GPU_MIN_FREQ_ON_AC/BAT, INTEL_GPU_MAX_FREQ_ON_AC/BAT, INTEL_GPU_BOOST_FREQ_ON_AC/BAT: Intel GPU frequency limits Radio Devices: - tlp-rdw: new command to disable RDW actions temporarily (until reboot) - Support ThinkPad Pro Dock CS18 (17ef:306f) - USB_BLACKLIST_WWAN: disable by default - Retire compatibility with Network Manager 0.9.8 (Ubuntu 14.04 EOL) PCI(e) devices: - RUNTIME_PM_BLACKLIST: add mei_me, pcieport ThinkPad Battery: - New native kernel API for battery features "natacpi" (Issue #321); requires kernel 4.17; enabled by default - NATACPI_ENABLE, TPACPI_ENABLE, TPSMAPI_ENABLE: make all battery feature drivers switchable - tlp discharge/recalibrate: exclude multiple simultaneous invocations - Support ThinkPad 25, *80 (Coffee Lake) and all newer models tlp-stat: - Check systemd-rfkill.socket masked status - Disks: show all configured devices (consider default) - Intel GPU: show frequency limits and available frequencies - Rename "Suggestions" section to "Recommendations" - Remove invocation via 'tlp stat' USB: - Exclude scanners managed by libsane from autosuspend - Remove long deprecated level/autosuspend attributes * Bugfixes - Issue #193: do not try to start NetworkManager (systemd) - Issue #319: get_disk_dev logic is not compatible with NVMe devices - Issue #320: AC mode not detected with USB charger - Issue #331: Process '/usr/bin/tlp auto' failed with exit code 4 - Issue #332: zsh parse error in tlp diskid - Issue #343, #362, #375: circumvent broken AC/charger detection - Issue #344: keep ASPM default to enable deeper C-states on AC - Issue #356: fix writing sequence for start/stop charge thresholds - Issue #368: recognize Think*p*ad P50 - tlp-stat: filter HWP lines from x86_energy_perf_policy output +++ 1.1 --- 24.01.2018 +++ * Features Disks: - SATA_LINKPWR_ON_AC/BAT: try multiple values to support new recommended ALPM policy "med_power_with_dipm" in kernel 4.15 Processor: - Issue #297: ENERGY_PERF_POLICY_ON_AC/BAT: support changed values performance, balance-performance, default, balance-power, power ThinkPad Battery: - Support ThinkPad 13 1st & 2nd Gen, E130; new tpacpi-bat version - tlp-stat --psup: show ASLbase for tpacpi-bat (in device/path) - tlp discharge: show state of battery and force_discharge USB: - USB_BLACKLIST_PRINTER: exclude printers from autosuspend * Bugfixes - Issue #271: intercept link_power_management_policy write error - Issue #283: fix AC power detection for MacBook Pro 2017 - Issue #298: move runtime data from /var/run/tlp to /run/tlp - Issue #301: DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE="bluetooth" not working as expected - Issue #313: don't detect wireless input devices' batteries as power supply +++ 1.0 --- 25.05.2017 +++ * Features General: - TLP_PERSISTENT_DEFAULT: use TLP_DEFAULT_MODE regardless of the actual power source Device Bays: - BAY_POWEROFF_ON_AC: power off optical drive not only on bat (Issue #243) Graphics: - RUNTIME_PM_DRIVER_BLACKLIST: when unconfigured default to "amdgpu nouveau nvidia radeon"; driver default control=auto allows PRIME/Bumblebee to turn the dGPU off and prevents accidential power-on during boot as well - Exclude Nvidia dGPU from runtime power management even when no driver is associated (improve compatibility with Bumblebee) PCI(e) devices - RUNTIME_PM_ALL removed (default to RUNTIME_PM_ALL=1 internally) Processor: - CPU_HWP_ON_AC/BAT: HWP energy performance hints; needs kernel 4.10 and Intel Skylake CPU or newer, intel_pstate only ThinkPad Battery: - RESTORE_THRESHOLDS_ON_BAT: restore charge thresholds on battery - Detect ThinkPad *70 models (Kaby Lake) - Detect ThinkPad 13 (1st and 2nd Gen) as unsupported (Issue #227) - Change texts "ThinkPad (extended) battery functions" to "ThinkPad battery features" - tlp-stat: - Show "Charge total %" when more than one battery is present - Show battery temperature (tp-smapi only) - Show "unsupported" instead of "not installed" for tp-smapi incapable hardware USB: - USB_BLACKLIST_BTUSB: exclude bluetooth devices from autosuspend (Issue #239) - USB_BLACKLIST_PHONE: exclude phone devices from autosuspend (Issue #188) tlp-stat: - -g|--graphics: show graphics card data only - i915: explain enable_psr modes - --psup: show power supply diagnostic - SMART attribute G-Sense_Error_Rate (191) - -v: additional battery voltages - Workqueue power efficient status * Bugfixes - Issue #237: init start: apply powersave regardless of previous state - Issue #256: tlp-stat: intercept non-existing or invalid charge values - tlp-stat: show Intel graphics parameters for Ubuntu's i915_bpo module +++ 0.9 --- 18.08.2016 +++ * Features General: - Block concurrent invocation of tlp Disks: - AHCI runtime power management for host controllers and attached disks - SATA_LINKPWR_BLACKLIST: exclude SATA host devices from power management Radio Devices: - Finally remove deprecated DEVICES_TO_ENABLE_ON_RADIOSW code (works with Ubuntu 12.04/Kernel 3.2 only) - Change WIFI_PWR_ON_AC/BAT default config values to off/on for better usability (1/5 is still supported for backwards compatibility) Radio Device Wizard (tlp-rdw): - Add another ThinkPad OneLink Pro Dock USB LAN (17ef:304) (ThinkPad) Battery: - tlp-stat: - Distinguish incompatible hardware from load errors (Issue #160) - Display battery charge and capacity values in % - More selective suggestions for tp-smapi/acpi-call kernel modules tlp-stat: - Intel graphics: include i915 parameters enable_dc, enable_psr, modeset - --pev: monitor power supply udev events (Issue #172) - Processor: - Display available scaling governors - intel_pstate: display turbo_pct, num_pstates - Storage Devices: - Recognize blk-mq I/O scheduler - SMART attributes Power_Cycle_Count, Unused_Rsvd_Blk_Cnt_Tot, NAND_Writes_1GiB - Suggestions: add *60 models for tp-smapi/acpi-call - System Info: display boot mode UEFI / BIOS - TLP Status: - Display time and mode of tlp's last invocation - Issue warning when systemd-rfkill[@].service is not masked - Wireless: display type in interface list * Bugfixes - Issue #163: handle kernel with built-in acpi_call module properly - Issue #170: battery discharge does not terminate on ThinkPad E-series - Issue #172: TLP does not notice power source change - Issue #175: do not touch wifi on shutdown when unconfigured - Issue #176: optimize rdw locking heuristics - tlp-stat: fix ata error count (filter "DevExch" caused by bay inserts) - tp-smapi: do not load on unsupported ThinkPads - Fix bash completion +++ 0.8 --- 05.08.2015 +++ * Features General: - TLP_DEFAULT_MODE: change default operation mode to AC when no power supply can be detected (concerns some desktop and embedded hardware only) Radio Devices: - Resume: restore bluetooth state (derball2008) Radio Device Wizard (tlp-rdw): - Support ThinkPad OneLink Dock Basic/Pro for dock/undock events - Detect systemd "predictable network interface names" for WWAN ThinkPad Battery: - tpacpi-bat: new upstream version 2.2: get ASL path from /sys/class/power_supply/*/device/path; avoids unnecessary "AE_NOT_FOUND" kernel messages - tlp-stat: - Show "No batteries detected." - Explain battery status "Unknown" as "threshold effective" - Show battery cell group voltages (verbose mode, tp-smapi only) - Show acpi-call suggestion for ThinkPad *40, *50, X1 models USB: - Remove USB_DRIVER_BLACKLIST (because of issues #147, #149, see below) tlp-stat: - Show warnings for ata errors by default * Bugfixes - Issue #123: tlp-stat: detect kernel config with PM_RUNTIME=N - Issue #124: tlp recalibrate: fix exitcode check - Issue #133: USB autosuspend: write power/control if actually changing only - Ignore missing files in /proc/sys/fs/xfs (Gijs Hillenius) - Issues #147, #149: fix udev event processing for AC/BAT switching and USB hotplugging (required for udevd v221 and higher) +++ 0.7 --- 29.01.2015 +++ * Features Processor: - Limit max/min P-state to control power dissipation of the CPU (intel_pstate only) - Set performance versus energy savings policy (x86_energy_perf_policy) USB: - USB_DRIVER_BLACKLIST: exclude devices assigned to the listed drivers from USB autosuspend; default: usbhid - USB_BLACKLIST_WWAN: match internal driver list instead of USB vendor ids - USB_WHITELIST: include devices into USB autosuspend even if already excluded by the driver or WWAN blacklists Radio devices: - DEVICES_TO_ENABLE_ON_AC/DISABLE_ON_BAT/DISABLE_ON_BAT_NOT_IN_USE: enable/disable radio devices upon change of power source (excluding connected devices) ThinkPad Battery: - Issue #105: provide proper return codes for all battery functions - Issue #106: setcharge allowed on battery power - Do not activate new thresholds with force_discharge anymore (tp-smapi) - tpacpi-bat: support for ThinkPad E325 charge thresholds Misc: - RUNTIME_PM_DRIVER_BLACKLIST: exclude PCI(e) devices assigned to drivers; default: radeon, nouveau (solves issue #94) - Support Dell Latitude docks tlp-stat: - New options -d|--disk, -e|--pcie, -p|--processor, -s|--system, -u|--usb - Show driver and connection state for all enabled radio devices - Show driver name in Runtime PM PCI(e) device list - Show type and state of docks and device bays - Show type of init system - Check if TLP's systemd services are enabled (zenox) * Bugfixes Udev: - Run change of power source in a detached subshell to avoid blocking udev - Fix dock/undock event processing for ThinkPad Adv Mini Dock and Ultrabase - Make USB device event processing more robust - Run in a detached subshell to avoid blocking udev - Wait 2s for subdevices to populate for proper black/whitelist detection Misc: - Support for NM 0.9.10 (Debian Jessie) - Issue #109: tlp-stat: report "unknown" power source when no AC detected - Issue #98: do not change autosuspend_delay_ms, keep kernel default settings (garyp) - Exclude pseudo usb disks (Raphaël Halimi) * Misc - Add AUTHORS file +++ 0.6 --- 06.10.2014 +++ * Features - Set systemd service type to simple, allows tlp service to start asynchronously in the background (Timofey) - Remove DISABLE_TPACPIBAT from configuration - Remove DEVICES_TO_ENABLE_ON_RADIOSW from configuration because it's deprecated: works with Ubuntu 12.04/Kernel 3.2 only - Enable RUNTIME_PM_ALL by default - Do not touch kernel settings if param is empty or commented: DISK_IDLE_SECS_ON, MAX_LOST_WORK_SECS_ON, SCHED_POWERSAVE_ON, NMI_WATCHDOG, SATA_LINKPWR_ON, PCIE_ASPM_ON, RADEON_DPM_STATE_ON, RADEON_POWER_PROFILE_ON, WIFI_PWR_ON, SOUND_POWER_SAVE_ON, RUNTIME_PM_ON. - DISK_APM_LEVEL_ON, DISK_SPINDOWN_TIMEOUT_ON, DISK_IOSCHED: use _ or keep to skip the setting for a particular disk - tlp-stat - Consider changed sysfs paths for i915 enable_rc6/fbc as of kernel 3.15 (M@C) - Consider changed sysfs paths for hwmon coretemp/sensors as of kernel 3.15/3.16 - Report speed of all fans, not just the first one - Show warning for kernel sata errors (possibly) caused by SATA_LINKPWR_ON_BAT/AC=min/medium power - Retrieve trace output from systemd journal if present - Do not disable TLP when laptop-mode-tools is detected, just output a warning about possible conflicts * Bugfixes - Issue #74: Makefile: remove tlp-nop in uninstall-tlp target (beatinho, peterkenji94) - Issue #86: tlp-stat: don't suggest tp-smapi on non-ThinkPad hardware with thinkpad_acpi loaded (sayantandas) - tlp-stat: do not show /proc/acpi/ibm/fan on Lenovo non-ThinkPad models (Qasim) +++ 0.5 --- 24.03.2014 +++ * Features - tpacpi-bat: auto detection of all ThinkPad models (v2.1) - tlp-stat: include newer models in tpacpi-bat suggestions - tlp-rdw: support newer docks - Handle special case where BAT1 = main battery (Thinkpad Edge/L/S series) - Issue #61: sound power save depending on power source ac/bat - Issue #62: don't touch devices in RUNTIME_PM_BLACKLIST or excluded by RUNTIME_PM_ALL=0 * Bugfixes - run-on-ac/bat: check if command exists - Issue #59: do not write sata link power when not configured - Fix RESTORE_DEVICE_STATE_ON_STARTUP (fabio) - Restore bay power state upon resume only when on bat power and the setting is active (xudabit) - Use nmcli before rfkill to change radio state; re-enable wifi on shutdown when not explicitly configured (Ubuntu 14.04) * Packaging - Create symlinks instead of hardlinks for bluetooth/wifi/wwan, run-on-ac/bat - Makefile: new params TLP_* - tlp.init: remove requirement $all +++ 0.4.1 --- 02.01.2014 +++ * Bugfix version (_not_ for Ubuntu/Debian) * Features - tpacpi-bat: support ThinkPad E431 * Bugfixes - Bug #43: tlp-rdw not working with NM 0.9.9/Fedora 20 (wofritz) - Bug #44: run-on-ac|bat: remove dependency on pm-utils/on_ac_power +++ 0.4 --- 17.09.2013 +++ * Features - New radeon dynamic power management (dpm); needs Kernel >= 3.11 (Pali Rohár) - RUNTIME_PM_BLACKLIST: exclude listed pci(e) device addresses from Runtime PM (wofritz) - USB_BLACKLIST_WWAN: exclude wwan devices from usb autosuspend; works for ids 05c6:* 0bdb:* 1199:* only - Apply ac settings for faster shutdown +++ 0.3.11 --- 10.09.2013 +++ *** Testing version (for Arch Linux) *** * Bugfixes - Issue #42: - Remove dependency to on_ac_power (part of pm-utils in Arch Linux) - Fix udev rule to detect power source change ac - bat * Packaging - Pull request #40: systemd: start tlp.service after local-fs.target instead of graphical.target (cprussin) +++ 0.3.10 --- 17.08.2013 +++ *** Testing version (for Arch Linux) *** * Architecture - Issue #36: detect change of power source via udev instead of being called by pm-powersave - Handle suspend/resume w/o pm-utils in systemd environments: - Encapsulate suspend/resume tasks as a tlp subcommand - Add tlp-sleep.service to call tlp suspend/resume - Remove 48tlp-rdw.lock because it doesn't work as expected * Features - Issue #31: detect and use intel_pstate driver to control turbo mode (ValdikSS) - Disable wol for all ethernet devices i.e. non-eth0 (blafoo) - tpacpi-bat: - merge upstream support for ThinkPad T430u, Edge E335/E530 * Bugfixes - Issue #28: do not touch dirty_(background_)ratio anymore, i.e. revert setting to kernel defaults * Packaging - debian/control: remove ${shlibs:Depends} +++ 0.3.9 --- 02.05.2013 +++ * Features - tpacpi-bat: - merge upstream w/ support for ThinkPad (Edge) S430 - add support for ThinkPad L530 - tlp-stat: - Subtract offset 128 from threshold values on ThinkPad Edge S430 - Show /sys/class/power_supply/BATx/cycle_count = 0 as "(unsupported)" * Bugfixes - Issue #14: tlp recalibrate fails when /bin/sh -> bash (slyon) - Bug #42: X121e battery functions not working (Jlp) - Set more reasonable values for dirty_ratio/dirty_background_ratio - Reverse order of writing the thresholds upon system start to stop - start, to achieve a consistent tlp-stat output between tlp init/start and tlp setcharge on quirky Edge and L series. - tlp-stat: - Fix threshold output trailing empty line +++ 0.3.8.1-3 --- 07.04.2013 +++ * Packaging - Fix #41: postinst/postrm fails without acpid (Petit Carlin) - recommends: acpid - postinst/rm: ignore missing acpid +++ 0.3.8.1 --- 29.03.2013 *** * Packaging - Remove obsolete desktop autostart hook - New format for debian/copyright - Add dummy case construct to tlp.init to make lintian happy - Rename tlp-init.service to tlp.service - postinst/postrm: restart acpid for thinkpad-radiosw event - Move smartmontools to "recommends:" * Features - New options CPU_BOOST_ON_* for cpu turbo boost (Linux 3.7 or later) - New option DEVICES_TO_ENABLE_ON_RADIOSW to enable only selected radios when wireless radio switch is turned on (Ubuntu + ThinkPad only) - [EXPERIMENTAL] New option RUNTIME_PM_ALL to activate runtime pm for all PCI(e) devices - tpacpi-bat: new upstream version (25.03.2013, commit dd5a682) - add support for X121e, L430, E420s, S420 - tlp chargeonce: charge battery to upper threshold once - tlp discharge: show current power consumption - tlp-stat: - Nicer output, code refactored - Remove dmidecode – get DMI data from /sys/class/dmi/id/ - When ASPM policy is not writable, show "using bios prefs" - Show interpretation for i915 params - Show disk status - Show tp-smapi, tpacpi-bat availability and status - Show cpu model - Resolve all pci device classes (new subcommand tlp-pcilist) - Show suggestions to install missing kernel modules/tools - Use iw for wifi power save if available, iwconfig is considered deprecated - Remove obsolete tlp wifi subcommand - Remove 2s delay in applying settings upon change of the power source * Bugfixes - tlp-stat: - Exclude usb media from "Storage Devices" section - Fix display of data in /sys/class/power_supply/BAT?/ +++ 0.3.7.1 --- 17.08.2012 +++ * Bug fixes - #39: tlp-stat: /sys/devices/platform/coretemp.0/temp1_input does not exist (Laurent Bonnaud) +++ 0.3.7 --- 13.08.2012 +++ * Packaging - implement startup/shutdown code as a command: tlp init - systemd support: tlp-init.service - deb recommends: tlp-rdw; suggests: acpi-call, tp-smapi * Features - Battery charge thresholds for Sandy Bridge and later models (X220/T420/...) by means of tpacpi-bat - Use tpacpi-bat even when tp-smapi is not available; for Ivy Bridge models (X230/T430/...) - DEVICES_TO_ENABLE_ON_SHUTDOWN to prevent other operating systems from missing radios - DEVICES_TO_ENABLE_ON_STARTUP - tlp-stat: - show TRIM capabilty for SSDs - add SMART attributes (179, 241) - new cmdline options -r, -t, -T - show cpu temp, fan speed even if /proc/acpi/ibm/{thermal|fan} are not available - show tp_smapi/power_avg * Bug fixes - #34: system start hangs in Fedora 17 (DigitalFlow) - #35: shutdown results in reboot; new config param USB_AUTOSUSPEND_DISABLE_ON_SHUTDOWN (Thubo) - #38: wifi on/off not working with ipw2100/2200 (kristatos) +++ 0.3.6-2 --- 24.03.2012 +++ * Packaging: - Fix tlp.postinst for systems without upstart dir /etc/init/ +++ 0.3.6 --- 22.03.2014 +++ * Features - handle usb autosuspend via udev events - usb hid detection overhauled (based on subdev/bInterfaceClass) - Restrict runtime pm to a safe subclass of pci devices (from Ubuntu Precise's implementation of pm-utils) - Restore radio device state on system startup (from previous shutdown) - Radio device wizard: switch radios upon network connect/disconnect and dock/undock events (samba) - Set cpu scaling governor and mix/max frequencies (Alex Lochmann) - tlp-stat: add smart attributes for samsung ssd - tlp-stat: show settings * Packaging - postinst/postrm: - disable power.d/harddrive, pci_devices, readahead, usb_bluetooth (Package pm-utils, Ubuntu 12.04) - disable conflicting upstart jobs (Package rfkill, Ubuntu 12.04) - split package - tlp: power save functions - tlp-rdw: radio device wizard (depends on network manager) * Bug Fixes - tlp-usblist: cleanup code, add pragmas "strict" and "warnings" (dywisor) - Remove setting of ext3/4 fs commit timeout (see LP #900923) +++ 0.3.5 --- 19.12.2011 +++ * Features - tlp recalibrate = fullcharge + discharge - tlp-stat: show thinkpad fan speed, battery model, power_now, i915: powersave, lvds_downclock - tlp-stat: usb output refactored, new subcommand tlp-usblist - tlp-stat: show kernel cmdline - added non-rfkill device ipw2100 (kristatos) * Bug Fixes - #27: tlp-stat complains about missing /proc/acpi/ibm/thermal and start_charge_thresh on X220/T420(s) et al. (Esc) - Check if start_charge_thresh, stop_charge_thresh, force_discharge are writable - #28: further mitigate race with gdm when disabling radios in init script (blackbox) - #29: tlp-stat: remove smartctl garbage output (SirVival) - #30: suppress dmidecode error output (kristatos) - iterate over all sched_powersave instances - i915: rc6/fbc features removed - Start upowerd in init script - #32: show error message suggesting to uninstall latop-mode-tools if present (Kuzoku) +++ 0.3.4 --- 05.12.2014 +++ * Features - Intel graphics: rc6 power save mode, frame buffer compression +++ 0.3.3 --- 19.09.2011 +++ * Features - tlp-stat: show hdd temp SMART values (bassplayer) - enable/disable kernel NMI watchdog * Other changes - set_charge_thresholds(): check for undefined thresh values - set_extfs_commit(): skip bind mounts (Fedora sandbox) - zztlp: check param; show help text * Bug Fixes - #24: openSUSE 11.4/2.6.37: writing to autosuspend_delay_ms fails, fallback to autosuspend - #25: fix sched_mc_power_savings on bat - #26: tlp-stat complains about missing dmidecode (Sara) +++ 0.3.2-2 --- 11.07.2011 +++ * Bug Fix - #23: init.d script not linked/unlinked by install/purge (LePatron) +++ 0.3.2 --- 04.07.2011 +++ * Bug Fix - #22: runtime pm causes shutdown to fail, reboots instead (fabio) disabled by default +++ 0.3.1 --- 23.06.2011 +++ * Changes to ease porting to other distros - removed system utils absolute paths - added PATH debug output in tlp, tlp.init/tlp.upstart - manpages moved from debian/ to man/ * Features, other changes - runtime pm (ccyx) - set/disable hard disk spindown timeout (enrico65, hausmarke86) - use power/autosuspend_delay_ms (kernel >= 2.6.38) - tlp-stat: now runs with root privilege only, show intel ssd specific smart values, check for pcie aspm disabled by kernel - bluetooth/wifi/wwan: when using rfkill, check for root privilege or /dev/rfkill user-writable - tlp/bluetooth/wifi/wwan: bash completion * Bug Fixes - #18: tlp start (ac): incorrect ouptut "started in bat mode" fixed (yatpu) - #19: tlp-stat: incorrect wifi power mgmt detection for wl driver (DrPaulaner) - #20: handle disabled pcie aspm in kernel 2.6.39 gracefully (Schmitti, g3eB4Y) - #21: battery attributes /sys/class/power_supply/BAT?/charge_* not recognized (tanjapetri) +++ 0.3.0-2 --- 20.03.2011 +++ * Bug Fixes - DEVICES_TO_DISABLE_ON_STARTUP (Debian): startup code fixed; SysV-script depends on $syslog now *** 0.3.0 --- 18.03.2011 *** * Bug Fixes - Switch wwan off before suspend (workaround for kernel/network-manager quirk) * Features - Specify DISK_DEVICES with id's from /dev/disk/by-id (egalus) - tlp diskid: show disk id's - DISK_IOSCHED: set i/o scheduler (egalus) - PCIe ASPM - Do not set START_CHARGE_THRESH on tp_smapi-capable ThinkPad Edge - SCHED_POWERSAVE: cpu/sched_*_power_savings - Set radeon clock speed via /sys/class/drm/card*/device/power_profile * Packaging - Move startup code from upstart back to init.d - Move symlinking in /etc/pm/power.d/ to postinst/postrm - Move /usr/lib/tlp/ to /usr/lib/tlp-pm/ *** 0.2.8 --- 25.09.2010 *** * Features - USB_AUTOSUSPEND: exclude input devices (usbhid) w/o blacklist - tlp-stat: indicate drivers in usb device list - DISK_APM_LEVEL: support multiple devices (Stifflers_mom) - maverick: override pm-utils power.d/ scripts with own functionality *** 0.2.7 --- 11.09.2010 *** * Bug fixes - usb autosuspend/tlp-stat not showing all usb devices - #15: tlp-stat abort w/ ipw2200 (agape) - #16: PHC_CONTROL written to all cpus/cores (pumpe et al.) * Features - charge thresholds: new command tlp setcharge (crishu) - DEVICES_TO_DISABLE_ON_STARTUP: handle bluetooth in upstart job (previously via desktop login) - set usb autosuspend for wwan devices on ifup *** 0.2.6 --- 17.07.2010 *** * Bugfixes - tlp-stat: error checking get_ctrl_device, tlp-stat batinfo (mikar) - #14: delayed login window (greeter) w/ USB_AUTOSUSPEND=1 (steveurkel, fishmac, saubaer) * Features - tlp fullcharge - set_charge_thresholds on startup only, not on shutdown - ext3/ext4 fs commit depending on MAX_LOST_WORK_SECS - tlp-stat: check wifi power mgmt capability - tlp-stat: display wifi driver *** 0.2.5-2 --- 17.05.2010 *** * Bugfix/Package change - Conflicts: pm-utils-powersave-policy - powersave-policy-sata-link-power breaks pm-powersave w/ sata controllers in compatible mode an pata controllers (LP# 514881). - TLP implements same functionality as conflicting package anyway ... *** 0.2.5 --- 03.05.2010 *** * Bugfixes - #11: excessive boottime (+40s) w/ USB_SUSPEND=1 & USB_BLACKLIST="" - tlp-stat: display hard disk w/o apm as "none/disabled" * Features - bluetooth/wifi/wwan: toggle (#12, thatgui) - changed usb autosuspend default: on - wifi power management re-enabled on 2.6.32 w/ some adapters - trace feature, output to syslog/debug (TLP_DEBUG) - new variable BAY_DEVICE *** 0.2.4 --- 10.03.2010 *** * Bugfixes - #8: tlp-rf-func warnings on ThinkPad w/o bluetooth and wwan (woelffchen) - #9: bayoff: ultrabay power on again after resume (linrunner) -> script sleep.d/49bay added * Features - tlp: force battery discharge - run-on-ac/run-on-bat *** 0.2.3 --- 07.03.2010 *** * Bugfixes - #7: bayoff - media not unmounted, drives != sr0 not recognized (linrunner) *** 0.2.2 --- 04.03.2010 *** * Bugfixes - #3: cannot re-enable bluetooth after disabling (M@C) - #5: autoload tp_smapi (Starko) * Features - upstart integration - tlp-stat: error checking improved - poweroff ultrabay optical drive on battery - support for ipw2200 radio enable/disable (karlitos) *** 0.2.1 --- 31.01.2010 *** * Bugfixes - #1: pm-suspend/pm-hibernate hang w/o wwan device (Zaphod_42) - #2: error messages from set_sata_link_power() w/o sata-ahci or ide (quarf) * tlp-stat: more info *** 0.2.0 --- 30.01.2010 *** * Initial public release TLP-1.10.1/completion/000077500000000000000000000000001517565574500144265ustar00rootroot00000000000000TLP-1.10.1/completion/bash/000077500000000000000000000000001517565574500153435ustar00rootroot00000000000000TLP-1.10.1/completion/bash/tlp-rdw.bash_completion000066400000000000000000000006251517565574500220270ustar00rootroot00000000000000# bash completion for tlp-rdw # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later _tlp_rdw() { local cur prev words cword opts _init_completion || return opts="enable disable --version" if [ $cword -eq 1 ]; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi } && complete -F _tlp_rdw tlp-rdw TLP-1.10.1/completion/bash/tlp.bash_completion000066400000000000000000000051511517565574500212340ustar00rootroot00000000000000# bash completion for tlp, tlp-stat, bluetooth, nfc, wwan, wifi # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later _batteries() { # show list of batteries local bats b bats=$( { for b in /sys/class/power_supply/*; do if echo "$b" | grep -E -v -q "hid" \ && [ "$(cat $b/present 2> /dev/null)" = "1" ] \ && [ "$(cat $b/type)" = "Battery" ]; then echo "${b##/*/} " fi done } ) if [ -n "$bats" ]; then COMPREPLY=( $(compgen -W "${bats}" -- ${cur}) ) fi } _target_level() { local thresh if thresh="$(cat /sys/class/power_supply/${COMP_WORDS[2]}/charge_control_end_threshold 2> /dev/null)" \ || thresh="$(cat /sys/devices/platform/smapi/${COMP_WORDS[2]}/stop_charge_thresh 2> /dev/null)"; then COMPREPLY=( $(compgen -W "$thresh" -- ${cur}) ) fi } _tlp() { local cur prev words cword opts bats _init_completion || return opts="start performance ac balanced bat power-saver usb setcharge fullcharge chargeonce discharge recalibrate bayoff diskid --version" case $cword in 1) # subcmds only COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 ;; 2) case "${prev}" in fullcharge|chargeonce|discharge|recalibrate) _batteries return 0 ;; esac ;; 3) if [ "${COMP_WORDS[1]}" = "discharge" ]; then _target_level return 0 fi ;; 4) if [ "${COMP_WORDS[1]}" = "setcharge" ]; then _batteries return 0 fi ;; esac } && complete -F _tlp tlp _tlp_rf() { local cur prev words cword opts _init_completion || return opts="on off toggle cycle --version" if [ $cword -eq 1 ]; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi } && complete -F _tlp_rf bluetooth nfc wwan wifi _tlp_stat() { local cur prev words cword opts _init_completion || return opts="--battery --cdiff --config --disk --graphics --mode --pcie --pd-diag --pev --processor --psup --quiet --rfkill --system --temp --trace --trace-nm --udev --usb --verbose --warn" if [ $cword -eq 1 ]; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi } && complete -F _tlp_stat tlp-stat complete -F _command run-on-ac run-on-bat TLP-1.10.1/completion/bash/tlpctl.bash_completion000066400000000000000000000144171517565574500217440ustar00rootroot00000000000000# bash completion for tlpctl # AUTOMATICALLY GENERATED by `shtab` and EDITED _shtab_tlpctl_subparsers=('performance' 'balanced' 'power-saver' 'list' 'list-holds' 'get' 'set' 'launch' 'loglevel' 'version') _shtab_tlpctl_option_strings=('-h' '--help', '--version') _shtab_tlpctl_performance_option_strings=('-h' '--help') _shtab_tlpctl_balanced_option_strings=('-h' '--help') _shtab_tlpctl_power_saver_option_strings=('-h' '--help') _shtab_tlpctl_list_option_strings=('-h' '--help') _shtab_tlpctl_list_holds_option_strings=('-h' '--help') _shtab_tlpctl_get_option_strings=('-h' '--help') _shtab_tlpctl_set_option_strings=('-h' '--help') _shtab_tlpctl_launch_option_strings=('-h' '--help' '--profile' '-p' '--reason' '-r' '--appid' '-i') _shtab_tlpctl_loglevel_option_strings=('-h' '--help') _shtab_tlpctl_pos_0_choices=('performance' 'balanced' 'power-saver' 'list' 'list-holds' 'get' 'set' 'launch' 'loglevel' 'version') _shtab_tlpctl_set_pos_0_choices=('performance' 'balanced' 'power-saver') _shtab_tlpctl_loglevel_pos_0_choices=('info' 'debug') _shtab_tlpctl_pos_0_nargs=A... _shtab_tlpctl__h_nargs=0 _shtab_tlpctl___help_nargs=0 _shtab_tlpctl_performance__h_nargs=0 _shtab_tlpctl_performance___help_nargs=0 _shtab_tlpctl_balanced__h_nargs=0 _shtab_tlpctl_balanced___help_nargs=0 _shtab_tlpctl_power_saver__h_nargs=0 _shtab_tlpctl_power_saver___help_nargs=0 _shtab_tlpctl_list__h_nargs=0 _shtab_tlpctl_list___help_nargs=0 _shtab_tlpctl_list_holds__h_nargs=0 _shtab_tlpctl_list_holds___help_nargs=0 _shtab_tlpctl_get__h_nargs=0 _shtab_tlpctl_get___help_nargs=0 _shtab_tlpctl_set_pos_0_nargs=1 _shtab_tlpctl_set__h_nargs=0 _shtab_tlpctl_set___help_nargs=0 _shtab_tlpctl_launch_pos_0_nargs=* _shtab_tlpctl_launch__h_nargs=0 _shtab_tlpctl_launch___help_nargs=0 _shtab_tlpctl_loglevel_pos_0_nargs=1 _shtab_tlpctl_loglevel__h_nargs=0 _shtab_tlpctl_loglevel___help_nargs=0 _shtab_tlpctl_version__h_nargs=0 _shtab_tlpctl_version___help_nargs=0 # $1=COMP_WORDS[1] _shtab_compgen_files() { compgen -f -- $1 # files } # $1=COMP_WORDS[1] _shtab_compgen_dirs() { compgen -d -- $1 # recurse into subdirs } # $1=COMP_WORDS[1] _shtab_replace_nonword() { echo "${1//[^[:word:]]/_}" } # set default values (called for the initial parser & any subparsers) _set_parser_defaults() { local subparsers_var="${prefix}_subparsers[@]" sub_parsers=${!subparsers_var-} local current_option_strings_var="${prefix}_option_strings[@]" current_option_strings=${!current_option_strings_var} completed_positional_actions=0 _set_new_action "pos_${completed_positional_actions}" true } # $1=action identifier # $2=positional action (bool) # set all identifiers for an action's parameters _set_new_action() { current_action="${prefix}_$(_shtab_replace_nonword $1)" local current_action_compgen_var=${current_action}_COMPGEN current_action_compgen="${!current_action_compgen_var-}" local current_action_choices_var="${current_action}_choices[@]" current_action_choices="${!current_action_choices_var-}" local current_action_nargs_var="${current_action}_nargs" if [ -n "${!current_action_nargs_var-}" ]; then current_action_nargs="${!current_action_nargs_var}" else current_action_nargs=1 fi current_action_args_start_index=$(( $word_index + 1 - $pos_only )) current_action_is_positional=$2 } # Notes: # `COMPREPLY`: what will be rendered after completion is triggered # `completing_word`: currently typed word to generate completions for # `${!var}`: evaluates the content of `var` and expand its content as a variable # hello="world" # x="hello" # ${!x} -> ${hello} -> "world" _shtab_tlpctl() { local completing_word="${COMP_WORDS[COMP_CWORD]}" local previous_word="${COMP_WORDS[COMP_CWORD-1]}" local completed_positional_actions local current_action local current_action_args_start_index local current_action_choices local current_action_compgen local current_action_is_positional local current_action_nargs local current_option_strings local sub_parsers COMPREPLY=() local prefix=_shtab_tlpctl local word_index=0 local pos_only=0 # "--" delimeter not encountered yet _set_parser_defaults word_index=1 # determine what arguments are appropriate for the current state # of the arg parser while [ $word_index -ne $COMP_CWORD ]; do local this_word="${COMP_WORDS[$word_index]}" if [[ $pos_only = 1 || " $this_word " != " -- " ]]; then if [[ -n $sub_parsers && " ${sub_parsers[@]} " == *" ${this_word} "* ]]; then # valid subcommand: add it to the prefix & reset the current action prefix="${prefix}_$(_shtab_replace_nonword $this_word)" _set_parser_defaults fi if [[ " ${current_option_strings[@]} " == *" ${this_word} "* ]]; then # a new action should be acquired (due to recognised option string or # no more input expected from current action); # the next positional action can fill in here _set_new_action $this_word false fi if [[ "$current_action_nargs" != "*" ]] && \ [[ "$current_action_nargs" != "+" ]] && \ [[ "$current_action_nargs" != *"..." ]] && \ (( $word_index + 1 - $current_action_args_start_index - $pos_only >= \ $current_action_nargs )); then $current_action_is_positional && let "completed_positional_actions += 1" _set_new_action "pos_${completed_positional_actions}" true fi else pos_only=1 # "--" delimeter encountered fi let "word_index+=1" done # Generate the completions if [[ $pos_only = 0 && "${completing_word}" == -* ]]; then # optional argument started: use option strings COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") ) elif [[ "${previous_word}" == ">" || "${previous_word}" == ">>" || "${previous_word}" =~ ^[12]">" || "${previous_word}" =~ ^[12]">>" ]]; then # handle redirection operators COMPREPLY=( $(compgen -f -- "${completing_word}") ) else # use choices & compgen local IFS=$'\n' # items may contain spaces, so delimit using newline COMPREPLY=( $([ -n "${current_action_compgen}" ] \ && "${current_action_compgen}" "${completing_word}") ) unset IFS COMPREPLY+=( $(compgen -W "${current_action_choices[*]}" -- "${completing_word}") ) fi return 0 } complete -o filenames -F _shtab_tlpctl tlpctl TLP-1.10.1/completion/fish/000077500000000000000000000000001517565574500153575ustar00rootroot00000000000000TLP-1.10.1/completion/fish/tlp-rdw.fish000066400000000000000000000006551517565574500176310ustar00rootroot00000000000000# Fish shell completion for tlp-rdw set -l tlp_rdw_commands enable disable complete -c tlp-rdw -f complete -c tlp-rdw -n "not __fish_seen_subcommand_from $tlp_rdw_commands" -a enable -d 'Enable RDW actions' complete -c tlp-rdw -n "not __fish_seen_subcommand_from $tlp_rdw_commands" -a disable -d 'Disable RDW actions' complete -c tlp-rdw -n "not __fish_seen_subcommand_from $tlp_rdw_commands" -l version -d 'Print TLP version' TLP-1.10.1/completion/fish/tlp-stat.fish000066400000000000000000000031571517565574500200100ustar00rootroot00000000000000# Fish shell completion for tlp-stat complete -c tlp-stat -f complete -c tlp-stat -s b -l battery -d 'View battery data' complete -c tlp-stat -s c -l config -d 'View active configuration' complete -c tlp-stat -l cdiff -d 'View difference between defaults and user configuration' complete -c tlp-stat -s d -l disk -d 'View disk device information' complete -c tlp-stat -s e -l pcie -d 'View PCIe device information' complete -c tlp-stat -s g -l graphics -d 'View graphics card information' complete -c tlp-stat -s m -l mode -d 'View tlp status information' complete -c tlp-stat -s p -l processor -d 'View processor information' complete -c tlp-stat -s q -l quiet -d 'Show less information' complete -c tlp-stat -s r -l rfkill -d 'View radio device states' complete -c tlp-stat -s s -l system -d 'View system information and TLP status' complete -c tlp-stat -s t -l temp -d 'View temperatures and fan speed' complete -c tlp-stat -s u -l usb -d 'View USB device information' complete -c tlp-stat -s v -l verbose -d 'Show more information' complete -c tlp-stat -l version -d 'Print TLP version' complete -c tlp-stat -l pd-diag -d 'View tlp-pd diagnostics' complete -c tlp-stat -l pev -d 'Monitor power supply udev events' complete -c tlp-stat -s P -l psup -d 'View power supply diagnostics' complete -c tlp-stat -s T -l trace -d 'View trace output for tlp and tlp-pd' complete -c tlp-stat -l trace-nm -d 'View trace output correlated with NetworkManager journal' complete -c tlp-stat -l udev -d 'Check if udev rules for power source changes and connecting USB devices are active' complete -c tlp-stat -s w -l warn -d 'View warnings about SATA disks' TLP-1.10.1/completion/fish/tlp.fish000066400000000000000000000070571517565574500170420ustar00rootroot00000000000000# Fish shell completion for tlp, radio device command: bluetooth nfc wifi wwan, and run-on command: run-on-ac run-on-bat set -l tlp_commands start performance balanced power-saver ac bat usb bayoff setcharge fullcharge discharge recalibrate chargeonce diskid set -l tlp_rf_devices bluetooth nfc wifi wwan set -l tlp_rf_devices_commands on off toggle set -l runon_commands run-on-ac run-on-bat set -l current_command (status basename | path change-extension '') if test $current_command = "tlp" set -l bats for b in /sys/class/power_supply/* if not string match -q -r hid $b; and test -f $b/present; and test (cat $b/present) = "1"; and test (cat $b/type) = "Battery" set -a bats (path basename $b) end end complete -c tlp -f complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a start -d "Start tlp and apply appropriate profile" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a performance -d "Apply 'performance' profile" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a balanced -d "Apply 'balanced' profile" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a power-saver -d "Apply 'power-saver' profile" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a ac -d "Apply profile for AC power and enter manual mode" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a bat -d "Apply profile for battery power and enter manual mode" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a usb -d "Enable autosuspend for all USB devices except excluded" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a bayoff -d "Turn off optical drive in UltraBay/MediaBay" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a setcharge -d "Change charge thresholds temporarily" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a fullcharge -d "Charge battery to full capacity" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a chargeonce -d "Charge battery to the stop charge threshold once" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a discharge -d "Force a complete discharge of the battery" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a recalibrate -d "Perform a battery recalibration" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a diskid -d "Print disk ids for configured drives" complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -l version -d 'Print TLP version' complete -c tlp -n "__fish_seen_subcommand_from $tlp_commands[6..10] && not __fish_seen_subcommand_from $bats" -a "$bats" end if contains $current_command $tlp_rf_devices complete -c $current_command -f complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -a on -d 'Switch device on' complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -a off -d 'Switch device off' complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -a toggle -d 'Toggle device state' complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -a cycle -d 'Toggle device state twice' complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -l version -d 'Print TLP version' end if contains $current_command $runon_commands complete -c $current_command -xa "(__fish_complete_subcommand)" end TLP-1.10.1/completion/fish/tlpctl.fish000066400000000000000000000051131517565574500175340ustar00rootroot00000000000000# Fish completion for tlpctl # Disable file completion as it is not needed complete -c tlpctl -f # Helper function to check if a command has been given function __fish_tlpctl_needs_command set -l cmd (commandline -opc) set -e cmd[1] for i in $cmd switch $i case '-*' continue case '*' return 1 end end return 0 end # Helper function to check if using a specific subcommand function __fish_tlpctl_using_command set -l cmd (commandline -opc) if test (count $cmd) -gt 1 if test $argv[1] = $cmd[2] return 0 end end return 1 end # Main commands - TLP-specific shortcuts first complete -c tlpctl -n __fish_tlpctl_needs_command -a performance -d "Switch to 'performance' profile" complete -c tlpctl -n __fish_tlpctl_needs_command -a balanced -d "Switch to 'balanced' profile" complete -c tlpctl -n __fish_tlpctl_needs_command -a power-saver -d "Switch to 'power-saver' profile" # Standard commands complete -c tlpctl -n __fish_tlpctl_needs_command -a list -d "List available profiles" complete -c tlpctl -n __fish_tlpctl_needs_command -a list-holds -d "List current profile holds" complete -c tlpctl -n __fish_tlpctl_needs_command -a get -d "Print the currently active profile" complete -c tlpctl -n __fish_tlpctl_needs_command -a set -d "Set the active profile" complete -c tlpctl -n __fish_tlpctl_needs_command -a launch -d "Run a command using a specific profile (hold)" complete -c tlpctl -n __fish_tlpctl_needs_command -a loglevel -d "Set the loglevel of the tlp-pd daemon" complete -c tlpctl -n __fish_tlpctl_needs_command -a version -d "Print version information and exit" # Global options complete -c tlpctl -n __fish_tlpctl_needs_command -s h -l help -d "Show help message and exit" complete -c tlpctl -n __fish_tlpctl_needs_command -l version -d "Print version information and exit" # 'set' subcommand - complete with available TLP profiles complete -c tlpctl -n '__fish_tlpctl_using_command set' -a 'performance balanced power-saver' -d "Profile" # 'loglevel' subcommand - complete with log levels complete -c tlpctl -n '__fish_tlpctl_using_command loglevel' -a 'info debug' -d "Log level" # 'launch' subcommand options complete -c tlpctl -n '__fish_tlpctl_using_command launch' -s p -l profile -d "Profile to hold while running command" -a '(__fish_tlpctl_profiles)' complete -c tlpctl -n '__fish_tlpctl_using_command launch' -s r -l reason -d "Reason to be noted on the hold" complete -c tlpctl -n '__fish_tlpctl_using_command launch' -s i -l appid -d "AppID to be noted on the hold" TLP-1.10.1/completion/zsh/000077500000000000000000000000001517565574500152325ustar00rootroot00000000000000TLP-1.10.1/completion/zsh/_tlp000066400000000000000000000041131517565574500161120ustar00rootroot00000000000000#compdef tlp # zsh completion for tlp # Copyright (c) 2022 Arvid Norlander and others. # SPDX-License-Identifier: GPL-2.0-or-later # Helper to find installed batteries. Avoid a generic name since this is # going into the global scope. _tlp_batteries() { local -a bats for b in /sys/class/power_supply/*; do if [[ ! $b =~ hid \ && -f $b/present && "$(< $b/present)" == "1" \ && "$(< $b/type)" = "Battery" ]]; then bats+=("${b##/*/}[Battery ${b##/*/}]") fi done if [[ -n "$bats" ]]; then _values "battery" $bats else _message "battery (none found)" fi } # Extra helper hoop required when using _regex_words below local -a subcmd_batteries subcmd_batteries=(/$'[^\0]##\0'/ ':battery:battery:_tlp_batteries') local -a subcmd_setcharge subcmd_setcharge=( /$'[0-9]##\0'/ ':number:start charge threshold: ' /$'[0-9]##\0'/ ':number:stop charge threshold: ' /$'[^\0]##\0'/ ':battery:battery:_tlp_batteries' ) local -a reply local -a args args=( # Command word. Don't care what that is. /$'[^\0]#\0'/ ) _regex_words commands 'tlp command' \ "start:Start TLP and apply appropriate profile" \ "performance:Apply performance profile" \ "balanced:Apply balanced profile" \ "power-saver:Apply power-saver profile" \ "ac:Apply profile for AC power and enter manual mode" \ "bat:Apply profile for battery power and enter manual mode" \ "usb:Enable autosuspend for all USB devices except excluded" \ "bayoff:Turn off optical drive in UltraBay/MediaBay" \ "chargeonce:Charge battery to the stop charge threshold once:$subcmd_batteries" \ "discharge:Force a complete discharge of the battery:$subcmd_batteries" \ "setcharge:Change charge thresholds temporarily:$subcmd_setcharge" \ "fullcharge:Charge battery to full capacity:$subcmd_batteries" \ "recalibrate:Perform a battery recalibration:$subcmd_batteries" \ "diskid:Print disk ids for configured drives" \ "--version:Print TLP version" args+=("$reply[@]") _regex_arguments _tlp "$args[@]" _tlp "$@" TLP-1.10.1/completion/zsh/_tlp-radio-device000066400000000000000000000010721517565574500204440ustar00rootroot00000000000000#compdef bluetooth nfc wifi wwan # zsh completion for bluetooth, nfc, wifi, wwan # Copyright (c) 2022 Arvid Norlander and others. # SPDX-License-Identifier: GPL-2.0-or-later local -a args reply args=( # Command word. Don't care what that is. /$'[^\0]#\0'/ ) _regex_words commands "$service command" \ 'on:Switch device on' \ 'off:Switch device off' \ 'toggle:Toggle device state' \ 'cycle:Toggle device state twice' \ '--version:Print TLP version' args+=("$reply[@]") _regex_arguments _tlp-radio-device "$args[@]" _tlp-radio-device "$@" TLP-1.10.1/completion/zsh/_tlp-rdw000066400000000000000000000007051517565574500167070ustar00rootroot00000000000000#compdef tlp-rdw # zsh completion for tlp-rdw # Copyright (c) 2022 Arvid Norlander and others. # SPDX-License-Identifier: GPL-2.0-or-later local -a args reply args=( # Command word. Don't care what that is. /$'[^\0]#\0'/ ) _regex_words commands 'tlp-rdw command' \ 'disable:Disable RDW actions' \ 'enable:Enable RDW actions' \ '--version:Print TLP version' args+=("$reply[@]") _regex_arguments _tlp-rdw "$args[@]" _tlp-rdw "$@" TLP-1.10.1/completion/zsh/_tlp-run-on000066400000000000000000000004061517565574500173270ustar00rootroot00000000000000#compdef run-on-ac run-on-bat # zsh completion for run-on-ac, run-on-bat # Copyright (c) 2022 Arvid Norlander and others. # SPDX-License-Identifier: GPL-2.0-or-later # Command line is an arbitrary command, so just forward completion to _precommand _precommand TLP-1.10.1/completion/zsh/_tlp-stat000066400000000000000000000030231517565574500170620ustar00rootroot00000000000000#compdef tlp-stat # zsh completion for tlp-stat # Copyright (c) 2022 Arvid Norlander and others. # SPDX-License-Identifier: GPL-2.0-or-later local -a args args=( '(-b --battery)'{--battery,-b}'[View battery data]' '(-c --config)'{--config,-c}'[View active configuration]' '--cdiff[View difference between defaults and user configuration]' '(-d --disk)'{--disk,-d}'[View disk device information]' '(-e --pcie)'{--pcie,-e}'[View PCIe device information]' '(-g --graphics)'{--graphics,-g}'[View graphics card information]' '(-m --mode)'{--mode,-m}'[Print current power mode]' '(-p --processor)'{--processor,-p}'[View processor information]' '(-q --quiet)'{--quiet,-q}'[Show less information]' '(-r --rfkill)'{--rfkill,-r}'[View radio device states]' '(-s --system)'{--system,-s}'[View system information and TLP status]' '(-t --temp)'{--temp,-t}'[View temperatures and fan speed]' '(-u --usb)'{--usb,-u}'[View USB device information]' '(-v --verbose)'{--verbose,-v}'[Show more information]' '--version[Print TLP version]' '--pd-diag[View tlp-pd diagnostics]' '(-P --pev)'{--pev,-P}'[Monitor power supply udev events]' '--psup[View power supply diagnostics]' '(-T --trace)'{--trace,-T}'[View trace output for tlp and tlp-pd]' '--trace-nm[View trace output correlated with NetworkManager journal]' '--udev[Check if udev rules for power source changes and connecting USB devices are active]' '(-w --warn)'{--warn,-w}'[View warnings about SATA disks]' ) _arguments $args TLP-1.10.1/completion/zsh/_tlpctl000066400000000000000000000060171517565574500166220ustar00rootroot00000000000000#compdef tlpctl # zsh completion for tlp # GENERATED by `shtab` and EDITED _shtab_tlpctl_commands() { local _commands=( "performance:Switch to performance profile" "balanced:Switch to balanced profile" "power-saver:Switch to power-saver profile" "get:Print the currently active profile" "launch:Run a command using a specific profile (hold)" "list:List available profiles" "list-holds:List current profile holds (from launch command)" "loglevel:Set the loglevel of the tlp-pd daemon" "set:Set the active profile" "version:Print version info" ) _describe 'tlpctl commands' _commands } _shtab_tlpctl_options=( "(-h --help)"{-h,--help}"[show this help message and exit]" "--version[Print version info]" ) _shtab_tlpctl___version_options=( ) _shtab_tlpctl_balanced_options=( ) _shtab_tlpctl_get_options=( ) _shtab_tlpctl_launch_options=( {--profile,-p}"[Profile that is kept in hold while the command is running]:profile:" {--reason,-r}"[Reason to be noted on the hold]:reason:" {--appid,-i}"[AppID to be noted on the hold]:appid:" "(*)::Command to launch:" ) _shtab_tlpctl_list_options=( ) _shtab_tlpctl_list_holds_options=( ) _shtab_tlpctl_loglevel_options=( ":loglevel:(info debug)" ":loglevel:(info debug)" ) _shtab_tlpctl_performance_options=( ) _shtab_tlpctl_power_saver_options=( ) _shtab_tlpctl_set_options=( ":Profile to activate:(performance balanced power-saver)" ":Profile to activate:(performance balanced power-saver)" ) _shtab_tlpctl_version_options=( ) _shtab_tlpctl() { local context state line curcontext="$curcontext" one_or_more='(-)*' remainder='(*)' if ((${_shtab_tlpctl_options[(I)${(q)one_or_more}*]} + ${_shtab_tlpctl_options[(I)${(q)remainder}*]} == 0)); then # noqa: E501 _shtab_tlpctl_options+=(': :_shtab_tlpctl_commands' '*::: :->tlpctl') fi _arguments -C -s $_shtab_tlpctl_options case $state in tlpctl) words=($line[1] "${words[@]}") (( CURRENT += 1 )) curcontext="${curcontext%:*:*}:_shtab_tlpctl-$line[1]:" case $line[1] in --version) _arguments -C -s $_shtab_tlpctl___version_options ;; balanced) _arguments -C -s $_shtab_tlpctl_balanced_options ;; get) _arguments -C -s $_shtab_tlpctl_get_options ;; launch) _arguments -C -s $_shtab_tlpctl_launch_options ;; list) _arguments -C -s $_shtab_tlpctl_list_options ;; list-holds) _arguments -C -s $_shtab_tlpctl_list_holds_options ;; loglevel) _arguments -C -s $_shtab_tlpctl_loglevel_options ;; performance) _arguments -C -s $_shtab_tlpctl_performance_options ;; power-saver) _arguments -C -s $_shtab_tlpctl_power_saver_options ;; set) _arguments -C -s $_shtab_tlpctl_set_options ;; version) _arguments -C -s $_shtab_tlpctl_version_options ;; esac esac } typeset -A opt_args if [[ $zsh_eval_context[-1] == eval ]]; then # eval/source/. command, register function for later compdef _shtab_tlpctl -N tlpctl else # autoload from fpath, call function directly _shtab_tlpctl "$@" fi TLP-1.10.1/de.linrunner.tlp.metainfo.xml000066400000000000000000000044301517565574500200020ustar00rootroot00000000000000 de.linrunner.tlp MIT TLP GPL-2.0-or-later AND GPL-3.0 Optimize laptop battery life https://linrunner.de/tlp Thomas Koch

TLP is a feature-rich command-line utility, saving laptop battery power without the need to delve deeper into technical details.

TLP’s default settings are already optimized for battery life and implement Powertop’s recommendations out of the box. Moreover TLP is highly customizable to fulfil specific user requirements.

Settings are organized into the three customizable TLP profiles performance (AC), balanced (BAT) and power-saver (SAV), allowing to adjust between savings and performance independently for battery and AC operation.

The optional TLP Profiles Daemon (tlp-pd) implements the D-Bus interface, which lets desktop environments show a TLP profile switch. Together with TLP as the backend for applying the customizable profiles, it replaces power-profiles-daemon.

In addition TLP can enable or disable Bluetooth, NFC, Wi-Fi and WWAN devices at boot time, as well as automated switching when a LAN cable is connected or removed, or when the laptop is docked or undocked.

For ThinkPads and may other laptop brands it provides a unified way to configure charge thresholds and recalibrate the battery.

System dmi:*:ct8:* dmi:*:ct9:* dmi:*:ct10:* acpi:PNP0C0A:*
TLP-1.10.1/defaults.conf000066400000000000000000000036441517565574500147420ustar00rootroot00000000000000# /usr/share/tlp/defaults.conf - TLP intrinsic defaults # IMPORTANT: do not edit this file, put your settings in /etc/tlp.conf or # /etc/tlp.d/*.conf instead! # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later TLP_ENABLE=1 TLP_WARN_LEVEL=3 TLP_MSG_COLORS="91 93 1 92" TLP_AUTO_SWITCH=2 TLP_PROFILE_AC=PRF TLP_PROFILE_BAT=BAL DISK_IDLE_SECS_ON_AC=0 DISK_IDLE_SECS_ON_BAT=2 MAX_LOST_WORK_SECS_ON_AC=15 MAX_LOST_WORK_SECS_ON_BAT=60 CPU_ENERGY_PERF_POLICY_ON_AC=balance_performance CPU_ENERGY_PERF_POLICY_ON_BAT=balance_power CPU_ENERGY_PERF_POLICY_ON_SAV=power PLATFORM_PROFILE_ON_AC=performance PLATFORM_PROFILE_ON_BAT=balanced PLATFORM_PROFILE_ON_SAV=low-power NMI_WATCHDOG=0 DISK_DEVICES="nvme0n1 sda" DISK_APM_LEVEL_ON_AC="254 254" DISK_APM_LEVEL_ON_BAT="128 128" DISK_APM_CLASS_DENYLIST="usb ieee1394" DISK_IOSCHED="keep keep" SATA_LINKPWR_ON_AC="med_power_with_dipm" SATA_LINKPWR_ON_BAT="med_power_with_dipm" AHCI_RUNTIME_PM_ON_AC=on AHCI_RUNTIME_PM_ON_BAT=auto AHCI_RUNTIME_PM_TIMEOUT=15 PCIE_ASPM_ON_AC=default PCIE_ASPM_ON_BAT=default PCIE_ASPM_ON_SAV=default INTEL_GPU_POWER_PROFILE_ON_AC=base INTEL_GPU_POWER_PROFILE_ON_BAT=power_saving INTEL_GPU_POWER_PROFILE_ON_SAV=power_saving RADEON_DPM_PERF_LEVEL_ON_AC=auto RADEON_DPM_PERF_LEVEL_ON_BAT=auto RADEON_DPM_PERF_LEVEL_ON_SAV=low AMDGPU_ABM_LEVEL_ON_AC=0 AMDGPU_ABM_LEVEL_ON_BAT=1 AMDGPU_ABM_LEVEL_ON_SAV=3 WIFI_PWR_ON_AC=off WIFI_PWR_ON_BAT=on WOL_DISABLE=Y SOUND_POWER_SAVE_ON_AC=1 SOUND_POWER_SAVE_ON_BAT=1 SOUND_POWER_SAVE_CONTROLLER=Y BAY_POWEROFF_ON_AC=0 BAY_POWEROFF_ON_BAT=0 BAY_DEVICE="sr0" RUNTIME_PM_ON_AC=on RUNTIME_PM_ON_BAT=auto RUNTIME_PM_DRIVER_DENYLIST="amdgpu mei_me nouveau nvidia xhci_hcd" USB_AUTOSUSPEND=1 USB_EXCLUDE_AUDIO=1 USB_EXCLUDE_BTUSB=0 USB_EXCLUDE_PHONE=0 USB_EXCLUDE_PRINTER=1 USB_EXCLUDE_WWAN=0 RESTORE_DEVICE_STATE_ON_STARTUP=0 RESTORE_THRESHOLDS_ON_BAT=1 NATACPI_ENABLE=1 TPSMAPI_ENABLE=1 TLP-1.10.1/deprecated.conf000066400000000000000000000017271517565574500152330ustar00rootroot00000000000000TLP_PERSISTENT_DEFAULT # Parameter is deprecated: use TLP_AUTO_SWITCH=0 together with TLP_PROFILE_DEFAULT DEVICES_TO_DISABLE_ON_SHUTDOWN # Parameter was removed DEVICES_TO_ENABLE_ON_SHUTDOWN # Parameter was removed RADEON_DPM_STATE_ON_AC # Parameter is deprecated: feature (radeon driver only) will be removed with the next release RADEON_DPM_STATE_ON_BAT # Parameter is deprecated: feature (radeon driver only) will be removed with the next release RADEON_DPM_STATE_ON_SAV # Parameter is deprecated: feature (radeon driver only) will be removed with the next release RADEON_POWER_PROFILE_ON_AC # Parameter was removed RADEON_POWER_PROFILE_ON_BAT # Parameter was removed RESTORE_DEVICE_STATE_ON_STARTUP # Parameter is deprecated: feature will be removed with the next release SCHED_POWERSAVE_ON_AC # Parameter was removed: obsolete since kernel 3.5 SCHED_POWERSAVE_ON_BAT # Parameter was removed: obsolete since kernel 3.5 USB_AUTOSUSPEND_DISABLE_ON_SHUTDOWN # Parameter was removed TLP-1.10.1/func.d/000077500000000000000000000000001517565574500134325ustar00rootroot00000000000000TLP-1.10.1/func.d/05-tlp-func-pm000066400000000000000000000210401517565574500157360ustar00rootroot00000000000000#!/bin/sh # tlp-func-pm - Device Power Management Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base # ---------------------------------------------------------------------------- # Constants readonly ETHTOOL=ethtool readonly PCID=/sys/bus/pci/devices readonly PCIDRV=/sys/bus/pci/drivers readonly SLEEPMODE=/sys/power/mem_sleep # ---------------------------------------------------------------------------- # Functions # --- PCI(e) Devices set_runtime_pm () { # set runtime power management # $1: PP_PRF, PP_BAL, PP_SAV local address class control device driver drv_bl type local pci_bl_adr="" local pci_bl_drv="" local pci_enable_adr pci_disable_adr case "$1" in "$PP_PRF") control="${RUNTIME_PM_ON_AC:-}" ;; "$PP_BAL"|"$PP_SAV") control="${RUNTIME_PM_ON_BAT:-}" ;; esac # permanent addresses pci_enable_adr=${RUNTIME_PM_ENABLE:-} pci_disable_adr=${RUNTIME_PM_DISABLE:-} if [ -z "$control" ] && [ -z "$pci_enable_adr" ] && [ -z "$pci_disable_adr" ] ; then # quit if completely unconfigured echo_debug "pm" "set_runtime_pm($1).not_configured" return 0 fi case "$control" in auto|on|"") # valid control value or no operation ("") ;; *) # invalid control value echo_debug "pm" "set_runtime_pm($1).invalid: $control" return 0 ;; esac if [ -n "$control" ]; then # RUNTIME_PM_ON_AC/BAT is configured --> prepare denylists # driver specific denylist: # - undefined = use intrinsic default from /usr/share/tlp/defaults.conf # - empty = disable feature drv_bl="$RUNTIME_PM_DRIVER_DENYLIST" # pci address denylisting pci_bl_adr=${RUNTIME_PM_DENYLIST:-} # pci driver denylisting: corresponding pci addresses pci_bl_drv="" # cumulate pci addresses for devices with denylisted drivers for driver in $drv_bl; do # iterate list if [ -n "$driver" ] && [ -d "$PCIDRV/$driver" ]; then # driver is active --> iterate over assigned devices for device in "$PCIDRV/$driver/0000:"*; do # get short device address address=${device##/*/0000:} # add to list when not already contained if ! wordinlist "$address" "$pci_bl_drv"; then pci_bl_drv="$pci_bl_drv $address" fi done # for device fi # if driver done # for driver fi # iterate pci(e) devices for type in $PCID; do for device in "$type"/*; do if [ -f "$device/power/control" ]; then # get short device address, class address=${device##/*/0000:} class=$(read_sysf "$device/class") if wordinlist "$address" "$pci_enable_adr"; then # device should be permanently 'auto' (enabled) write_sysf "auto" "$device/power/control" echo_debug "pm" "set_runtime_pm($1).perm_auto: $device [$class]; rc=$?" elif wordinlist "$address" "$pci_disable_adr"; then # device should be permanently 'on' (disabled) write_sysf "on" "$device/power/control" echo_debug "pm" "set_runtime_pm($1).perm_on: $device [$class]; rc=$?" elif wordinlist "$address" "$pci_bl_adr"; then # device is in address denylist echo_debug "pm" "set_runtime_pm($1).deny_address: $device [$class]" elif wordinlist "$address" "$pci_bl_drv"; then # device is in driver denylist echo_debug "pm" "set_runtime_pm($1).deny_driver: $device [$class]" else case $control in auto|on) write_sysf "$control" "$device/power/control" echo_debug "pm" "set_runtime_pm($1).$control: $device [$class]; rc=$?" ;; "") # no operation i.e. apply RUNTIME_PM_ENABLE/DISABLE only echo_debug "pm" "set_runtime_pm($1).nop: $device [$class]" ;; esac fi # if denylist fi # if power/control done # for device done # for type return 0 } set_pcie_aspm () { # set pcie active state power management # $1: PP_PRF, PP_BAL, PP_SAV, PP_SUS local pwr="" case "$1" in "$PP_PRF") pwr="${PCIE_ASPM_ON_AC:-}" ;; "$PP_BAL") pwr="${PCIE_ASPM_ON_BAT:-}" ;; "$PP_SAV") pwr="${PCIE_ASPM_ON_SAV:-${PCIE_ASPM_ON_BAT:-}}" ;; "$PP_SUS") # reset on suspend only when configured if [ -n "${PCIE_ASPM_ON_AC:-}${PCIE_ASPM_ON_BAT:-}${PCIE_ASPM_ON_SAV:-}" ]; then pwr="default" fi ;; esac if [ -z "$pwr" ]; then # do nothing if unconfigured echo_debug "pm" "set_pcie_aspm($1).not_configured" return 0 fi if [ -f /sys/module/pcie_aspm/parameters/policy ]; then if write_sysf "$pwr" /sys/module/pcie_aspm/parameters/policy; then echo_debug "pm" "set_pcie_aspm($1): $pwr" else echo_debug "pm" "set_pcie_aspm($1).disabled_by_kernel" fi else echo_debug "pm" "set_pcie_aspm($1).not_available" fi return 0 } # -- Audio Devices set_sound_power_mode () { # set sound chip power modes # $1: PP_PRF, PP_BAL, PP_SAV local pwr cpwr case "$1" in "$PP_PRF") pwr="${SOUND_POWER_SAVE_ON_AC:-}" ;; "$PP_BAL"|"$PP_SAV") pwr="${SOUND_POWER_SAVE_ON_BAT:-}" ;; esac # when unconfigured consider legacy config param [ -z "$pwr" ] && pwr="${SOUND_POWER_SAVE:-}" if [ -z "$pwr" ]; then # do nothing if unconfigured echo_debug "pm" "set_sound_power_mode($1).not_configured" return 0 fi cpwr="$SOUND_POWER_SAVE_CONTROLLER" if [ -d /sys/module/snd_hda_intel ]; then write_sysf "$pwr" /sys/module/snd_hda_intel/parameters/power_save echo_debug "pm" "set_sound_power_mode($1).hda: $pwr; rc=$?" if [ "$pwr" = "0" ]; then write_sysf "N" /sys/module/snd_hda_intel/parameters/power_save_controller echo_debug "pm" "set_sound_power_mode($1).hda_controller: N controller=$cpwr; rc=$?" else write_sysf "$cpwr" /sys/module/snd_hda_intel/parameters/power_save_controller echo_debug "pm" "set_sound_power_mode($1).hda_controller: $cpwr; rc=$?" fi fi if [ -d /sys/module/snd_ac97_codec ]; then write_sysf "$pwr" /sys/module/snd_ac97_codec/parameters/power_save echo_debug "pm" "set_sound_power_mode($1).ac97: $pwr; rc=$?" fi return 0 } # --- LAN Devices get_ethifaces () { # get all eth devices -- retval: $_ethifaces local ei eic _ethifaces="" for eic in "$NETD"/*/device/class; do if [ "$(read_sysf "$eic")" = "0x020000" ] \ && [ ! -d "${eic%/class}/ieee80211" ]; then ei="${eic%/device/class}"; ei="${ei##*/}" _ethifaces="${_ethifaces}${_ethifaces:+ }${ei}" fi done return 0 } disable_wake_on_lan () { # disable WOL local ei if [ "$WOL_DISABLE" = "Y" ]; then get_ethifaces if [ -n "$_ethifaces" ]; then for ei in $_ethifaces; do $ETHTOOL -s "$ei" wol d > /dev/null 2>&1 echo_debug "pm" "disable_wake_on_lan: $ei; rc=$?" done else echo_debug "pm" "disable_wake_on_lan.no_ifaces" fi else echo_debug "pm" "disable_wake_on_lan.not_configured" fi return 0 } # --- Set suspend method set_mem_sleep () { # $1: PP_PRF, PP_BAL, PP_SAV local susp case "$1" in "$PP_PRF") susp="${MEM_SLEEP_ON_AC:-}" ;; "$PP_BAL"|"$PP_SAV") susp="${MEM_SLEEP_ON_BAT:-}" ;; esac if [ -z "$susp" ]; then # do nothing if unconfigured echo_debug "pm" "set_mem_sleep($1).not_configured" return 0 fi if [ -f $SLEEPMODE ]; then if write_sysf "$susp" $SLEEPMODE; then echo_debug "pm" "set_mem_sleep($1): $susp" else echo_debug "pm" "set_mem_sleep($1).rejected_by_kernel" fi else echo_debug "pm" "set_mem_sleep($1).not_available" fi return 0 } TLP-1.10.1/func.d/10-tlp-func-cpu000066400000000000000000000426351517565574500161220ustar00rootroot00000000000000#!/bin/sh # tlp-func-cpu - Processor Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base # ---------------------------------------------------------------------------- # Constants readonly CPUD=/sys/devices/system/cpu readonly CPU_BOOST_ALL_CTRL=${CPUD}/cpufreq/boost readonly INTEL_PSTATED=${CPUD}/intel_pstate readonly AMD_PSTATED=${CPUD}/amd_pstate readonly CPU_MIN_PERF_PCT=$INTEL_PSTATED/min_perf_pct readonly CPU_MAX_PERF_PCT=$INTEL_PSTATED/max_perf_pct readonly CPU_TURBO_PSTATE=$INTEL_PSTATED/no_turbo readonly INTEL_DYN_BOOST=$INTEL_PSTATED/hwp_dynamic_boost readonly CPU_INFO=/proc/cpuinfo readonly FWACPID=/sys/firmware/acpi # ---------------------------------------------------------------------------- # Functions # --- Scaling Driver and Governor check_intel_pstate () { # detect intel_pstate driver -- rc: 0=present/1=absent # note: intel_pstate requires Linux 3.9 or higher [ -d $INTEL_PSTATED ] } check_amd_pstate () { # detect amd_pstate driver -- rc: 0=present/1=absent # note: intel_pstate requires Linux 5.17 or higher [ -d $AMD_PSTATED ] } print_cpu_driver () { # print active CPU scaling driver read_sysf "${CPUD}/cpu0/cpufreq/scaling_driver" || printf "cpu" } set_cpu_driver_opmode () { # set CPU driver operation mode -- $1: PP_PRF, PP_BAL, PP_SAV local cfg opmode case "$1" in "$PP_PRF") opmode="${CPU_DRIVER_OPMODE_ON_AC:-}" cfg="CPU_DRIVER_OPMODE_ON_AC" ;; "$PP_BAL") opmode="${CPU_DRIVER_OPMODE_ON_BAT:-}" cfg="CPU_DRIVER_OPMODE_ON_BAT" ;; "$PP_SAV") opmode="${CPU_DRIVER_OPMODE_ON_SAV:-$CPU_DRIVER_OPMODE_ON_BAT}" cfg="CPU_DRIVER_OPMODE_ON_SAV" ;; esac if [ -z "$opmode" ]; then echo_debug "pm" "set_cpu_driver_opmode($1).not_configured" return 0 fi if check_intel_pstate; then if write_sysf "$opmode" "${INTEL_PSTATED}/status"; then echo_debug "pm" "set_cpu_driver_opmode($1).intel_pstate: $opmode; rc=$?" else # kernel rejected the configured opmode echo_debug "pm" "set_cpu_driver_opmode($1).intel_pstate: $opmode; rc=$?" echo_message "Error in configuration at ${cfg}=\"${opmode}\": this operation mode is not supported by the intel_pstate driver." return 1 fi elif check_amd_pstate; then if write_sysf "$opmode" "${AMD_PSTATED}/status"; then echo_debug "pm" "set_cpu_driver_opmode($1).amd_pstate: $opmode; rc=$?" else # kernel rejected the configured opmode echo_debug "pm" "set_cpu_driver_opmode($1).amd_pstate: $opmode; rc=$?" echo_message "Error in configuration at ${cfg}=\"${opmode}\": this operation mode is not supported by the amd-pstate driver." return 1 fi else echo_debug "pm" "set_cpu_driver_opmode($1).unsupported_driver" echo_message "Error in configuration at ${cfg}=\"${opmode}\": the $(print_cpu_driver) driver does not support operation modes." return 1 fi return 0 } set_cpu_scaling_governor () { # set CPU scaling governor -- $1: PP_PRF, PP_BAL, PP_SAV local cfg cpu ec gov case "$1" in "$PP_PRF") gov="${CPU_SCALING_GOVERNOR_ON_AC:-}" cfg="CPU_SCALING_GOVERNOR_ON_AC" ;; "$PP_BAL") gov="${CPU_SCALING_GOVERNOR_ON_BAT:-}" cfg="CPU_SCALING_GOVERNOR_ON_BAT" ;; "$PP_SAV") if [ -n "$CPU_SCALING_GOVERNOR_ON_SAV" ]; then gov="${CPU_SCALING_GOVERNOR_ON_SAV}" cfg="CPU_SCALING_GOVERNOR_ON_SAV" else gov="${CPU_SCALING_GOVERNOR_ON_BAT}" cfg="CPU_SCALING_GOVERNOR_ON_BAT" fi ;; esac if [ -n "$gov" ]; then # apply governor ec=0 for cpu in "${CPUD}"/cpu*/cpufreq/scaling_governor; do if [ -f "$cpu" ] && ! write_sysf "$gov" "$cpu"; then echo_debug "pm" "set_cpu_scaling_governor($1).write_error: $cpu $gov; rc=$?" ec=$((ec+1)) fi done if [ "$ec" -gt 0 ]; then # kernel rejected the configured governor echo_debug "pm" "set_cpu_scaling_governor($1).not_available: $gov; ec=$ec" echo_message "Error in configuration at ${cfg}=\"${gov}\": governor not available." return 1 fi echo_debug "pm" "set_cpu_scaling_governor($1): $gov; ec=$ec" else echo_debug "pm" "set_cpu_scaling_governor($1).not_configured" fi return 0 } set_cpu_scaling_min_max_freq () { # set CPU scaling limits -- $1: PP_PRF, PP_BAL, PP_SAV local minfreq maxfreq cpu ec local conf=0 case "$1" in "$PP_PRF") minfreq="${CPU_SCALING_MIN_FREQ_ON_AC:-}" maxfreq="${CPU_SCALING_MAX_FREQ_ON_AC:-}" ;; "$PP_BAL") minfreq="${CPU_SCALING_MIN_FREQ_ON_BAT:-}" maxfreq="${CPU_SCALING_MAX_FREQ_ON_BAT:-}" ;; "$PP_SAV") minfreq="${CPU_SCALING_MIN_FREQ_ON_SAV:-$CPU_SCALING_MIN_FREQ_ON_BAT}" maxfreq="${CPU_SCALING_MAX_FREQ_ON_SAV:-$CPU_SCALING_MAX_FREQ_ON_BAT}" ;; esac if [ -n "$minfreq" ] && [ "$minfreq" != "0" ]; then conf=1 ec=0 for cpu in "${CPUD}"/cpu*/cpufreq/scaling_min_freq; do if [ -f "$cpu" ] && ! write_sysf "$minfreq" "$cpu"; then echo_debug "pm" "set_cpu_scaling_min_max_freq($1).min.write_error: $cpu $minfreq; rc=$?" ec=$((ec+1)) fi done echo_debug "pm" "set_cpu_scaling_min_max_freq($1).min: $minfreq; ec=$ec" fi if [ -n "$maxfreq" ] && [ "$maxfreq" != "0" ]; then conf=1 ec=0 for cpu in "${CPUD}"/cpu*/cpufreq/scaling_max_freq; do if [ -f "$cpu" ] && ! write_sysf "$maxfreq" "$cpu"; then echo_debug "pm" "set_cpu_scaling_min_max_freq($1).max.write_error: $cpu $maxfreq; rc=$?" ec=$((ec+1)) fi done echo_debug "pm" "set_cpu_scaling_min_max_freq($1).max: $maxfreq; ec=$ec" fi [ $conf -eq 1 ] || echo_debug "pm" "set_cpu_scaling_min_max_freq($1).not_configured" return 0 } # --- Performance Policies and Boost supports_intel_cpu_epb () { # rc: 0=CPU supports EPB/1=false grep -E -q -m 1 '^flags.+epb' $CPU_INFO } supports_intel_cpu_epp () { # rc: 0=CPU supports HWP.EPP/1=false grep -E -q -m 1 '^flags.+hwp_epp' $CPU_INFO } supports_amd_cpu_epp () { # rc: 0=CPU supports amd_pstate EPP/1=false [ "$(read_sysf "${AMD_PSTATED}/status")" = "active" ] } set_cpu_perf_policy () { # set Intel/AMD CPU energy vs. performance policies # $1: PP_PRF, PP_BAL, PP_SAV # # depending on the CPU model the values # performance, balance_performance, default, balance_power, power # in CPU_ENERGY_PERF_POLICY_ON_AC/BAT are applied to: # --- intel_pstate # (a) energy-performance preference (EPP) in IA32_HWP_REQUEST MSR # (b) energy-performance bias (EPB) in IA32_ENERGY_PERF_BIAS MSR # EPP and EPB are mutually exclusive: when EPP is available, # Intel CPUs will not honor EPB # --- amd_pstate # CPPC firmware via the Energy Performance Preference register # # References: # [1] https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_pstate.html#energy-vs-performance-hints # [2] https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_epb.html # [3] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4beec1d7519691b4b6c6b764e75b4e694a09c5f7 # [4] http://manpages.ubuntu.com/manpages/kinetic/man8/x86_energy_perf_policy.8.html # [5] https://lore.kernel.org/lkml/20221208111852.386731-1-perry.yuan@amd.com/ local cnt cpuf ec perf pnum case "$1" in "$PP_PRF") perf="${CPU_ENERGY_PERF_POLICY_ON_AC:-}" ;; "$PP_BAL") perf="${CPU_ENERGY_PERF_POLICY_ON_BAT:-}" ;; "$PP_SAV") perf="${CPU_ENERGY_PERF_POLICY_ON_SAV:-$CPU_ENERGY_PERF_POLICY_ON_BAT}" ;; esac # translate alphanumeric values from EPB to EPP syntax case "$perf" in 'balance-performance') perf='balance_performance' ;; 'normal') perf='default' ;; 'balance-power') perf='balance_power' ;; 'powersave') perf='power' ;; esac if [ -z "$perf" ]; then echo_debug "pm" "set_cpu_perf_policy($1).not_configured" return 0 fi if { check_intel_pstate && supports_intel_cpu_epp; } || { check_amd_pstate && supports_amd_cpu_epp; }; then # validate EPP setting case "$perf" in performance|balance_performance|default|balance_power|power) # OK --> continue ;; [0-9]|[0-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) # HWP.EPP accepts 0 to 255 --> continue ;; *) # invalid setting echo_debug "pm" "set_cpu_perf_policy($1).epp.invalid: perf=$perf" return 0 ;; esac # don't apply EPP when power-profiles-daemon is running if check_ppd_running ; then echo_message "Warning: CPU_ENERGY_PERF_POLICY_ON_AC/BAT/SAV is not set because power-profiles-daemon is running." echo_debug "pm" "set_cpu_perf_policy($1).epp.nop_ppd_active" return 0 fi # apply EPP setting cnt=0 ec=0 for cpuf in "${CPUD}"/cpu*/cpufreq/energy_performance_preference; do if [ -f "$cpuf" ]; then cnt=$((cnt+1)) if ! write_sysf "$perf" "$cpuf"; then echo_debug "pm" "set_cpu_perf_policy($1).epp.write_error: $perf $cpuf; rc=$?" ec=$((ec+1)) fi fi done if [ $cnt -gt 0 ]; then echo_debug "pm" "set_cpu_perf_policy($1).epp: $perf; ec=$ec" # EPP active and applied, quit unless EPB is forced [ "$X_FORCE_EPB" = "1" ] || return 0 else echo_debug "pm" "set_cpu_perf_policy($1).epp.not_available" fi else echo_debug "pm" "set_cpu_perf_policy($1).epp.unsupported_cpu" fi if supports_intel_cpu_epb; then # translate Intel HWP.EPP alphanumeric to EPB numeric values for native kernel support; # validate numeric values case "$perf" in performance) pnum=0 ;; balance_performance) pnum=4 ;; default) pnum=6 ;; balance_power) pnum=8 ;; power) pnum=15 ;; 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15) pnum="$perf" ;; *) # invalid setting echo_debug "pm" "set_cpu_perf_policy($1).epb.invalid: perf=$perf" return 0 ;; esac # apply EPB setting using native kernel API (5.2 and later required) cnt=0 ec=0 for cpuf in "${CPUD}"/cpu*/power/energy_perf_bias; do if [ -f "$cpuf" ]; then cnt=$((cnt + 1)) if ! write_sysf "$pnum" "$cpuf"; then echo_debug "pm" "set_cpu_perf_policy($1).epb.write_error: $perf($pnum) $cpuf; rc=$?" ec=$((ec+1)) fi fi done if [ $cnt -gt 0 ]; then # native kernel API actually detected echo_debug "pm" "set_cpu_perf_policy($1).epb: $perf($pnum); ec=$ec" else # no native kernel support echo_debug "pm" "set_cpu_perf_policy($1).epb.not_available" fi else echo_debug "pm" "set_cpu_perf_policy($1).epb.unsupported_cpu" fi return 0 } set_intel_cpu_perf_pct () { # set Intel P-state performance limits # $1: PP_PRF, PP_BAL, PP_SAV local min max if ! check_intel_pstate; then echo_debug "pm" "set_intel_cpu_perf_pct($1).no_intel_pstate" return 0 fi case "$1" in "$PP_PRF") min="${CPU_MIN_PERF_ON_AC:-}" max="${CPU_MAX_PERF_ON_AC:-}" ;; "$PP_BAL") min="${CPU_MIN_PERF_ON_BAT:-}" max="${CPU_MAX_PERF_ON_BAT:-}" ;; "$PP_SAV") min="${CPU_MIN_PERF_ON_SAV:-}" max="${CPU_MAX_PERF_ON_SAV:-}" ;; esac if [ ! -f $CPU_MIN_PERF_PCT ]; then echo_debug "pm" "set_intel_cpu_perf_pct($1).min.not_supported" elif [ -n "$min" ]; then write_sysf "$min" $CPU_MIN_PERF_PCT echo_debug "pm" "set_intel_cpu_perf_pct($1).min: $min; rc=$?" else echo_debug "pm" "set_intel_cpu_perf_pct($1).min.not_configured" fi if [ ! -f $CPU_MAX_PERF_PCT ]; then echo_debug "pm" "set_intel_cpu_perf_pct($1).max.not_supported" elif [ -n "$max" ]; then write_sysf "$max" $CPU_MAX_PERF_PCT echo_debug "pm" "set_intel_cpu_perf_pct($1).max: $max; rc=$?" else echo_debug "pm" "set_intel_cpu_perf_pct($1).max.not_configured" fi return 0 } set_cpu_boost_all () { # $1: PP_PRF, PP_BAL, PP_SAV # set global CPU boost behavior # # Note: # * needs commit #615b7300717b9ad5c23d1f391843484fe30f6c12 # (linux-2.6 tree), "Add support for disabling dynamic overclocking", # => requires Linux 3.7 or later local val case "$1" in "$PP_PRF") val="${CPU_BOOST_ON_AC:-}" ;; "$PP_BAL") val="${CPU_BOOST_ON_BAT:-}" ;; "$PP_SAV") val="${CPU_BOOST_ON_SAV:-$CPU_BOOST_ON_BAT}" ;; esac if [ -z "$val" ]; then # do nothing if unconfigured echo_debug "pm" "set_cpu_boost_all($1).not_configured" return 0 fi if check_intel_pstate; then if check_ppd_running; then # do not apply no_turbo when power-profiles-daemon is running echo_message "Warning: CPU_BOOST_ON_BAT/BAT is not set because power-profiles-daemon is running." echo_debug "pm" "set_cpu_boost_all($1).intel_pstate.nop_ppd_active" return 0 fi # use intel_pstate sysfiles, invert value if write_sysf "$((val ^ 1))" $CPU_TURBO_PSTATE; then echo_debug "pm" "set_cpu_boost_all($1).intel_pstate: $val" else echo_debug "pm" "set_cpu_boost_all($1).intel_pstate.cpu_not_supported" fi elif [ -f $CPU_BOOST_ALL_CTRL ]; then # use acpi_cpufreq sysfiles # simple test for attribute "w" doesn't work, so actually write if write_sysf "$val" $CPU_BOOST_ALL_CTRL; then echo_debug "pm" "set_cpu_boost_all($1).acpi_cpufreq: $val" else echo_debug "pm" "set_cpu_boost_all($1).acpi_cpufreq.cpu_not_supported" fi else echo_debug "pm" "set_cpu_boost_all($1).not_available" fi return 0 } set_cpu_dyn_boost () { # set CPU dynamic boost feature # requires intel_state in HWP active mode # $1: PP_PRF, PP_BAL, PP_SAV, PP_SUS local val case "$1" in "$PP_PRF") val="${CPU_HWP_DYN_BOOST_ON_AC:-}" ;; "$PP_BAL") val="${CPU_HWP_DYN_BOOST_ON_BAT:-}" ;; "$PP_SAV") val="${CPU_HWP_DYN_BOOST_ON_SAV:-$CPU_HWP_DYN_BOOST_ON_BAT}" ;; esac if [ -z "$val" ]; then echo_debug "pm" "set_cpu_dyn_boost($1).not_configured" elif check_intel_pstate; then if [ -f "$INTEL_DYN_BOOST" ]; then write_sysf "$val" $INTEL_DYN_BOOST echo_debug "pm" "set_cpu_dyn_boost($1).intel_pstate: $val; rc=$?" else echo_debug "pm" "set_cpu_dyn_boost($1).intel_pstate.not_supported" fi else echo_debug "pm" "set_cpu_dyn_boost($1).no_driver" fi return 0 } # --- Misc set_nmi_watchdog () { # enable/disable nmi watchdog local nmiwd=${NMI_WATCHDOG:-} if [ -z "$nmiwd" ]; then # do nothing if unconfigured echo_debug "pm" "set_nmi_watchdog.not_configured" return 0 fi if [ -f /proc/sys/kernel/nmi_watchdog ]; then if write_sysf "$nmiwd" /proc/sys/kernel/nmi_watchdog; then echo_debug "pm" "set_nmi_watchdog: $nmiwd; rc=$?" else echo_debug "pm" "set_nmi_watchdog.disabled_by_kernel: $nmiwd" fi else echo_debug "pm" "set_nmi_watchdog.not_available" fi return 0 } # --- Platform set_platform_profile () { # set platform profile # $1: PP_PRF, PP_BAL, PP_SAV local pwr case "$1" in "$PP_PRF") pwr="${PLATFORM_PROFILE_ON_AC:-}" ;; "$PP_BAL") pwr="${PLATFORM_PROFILE_ON_BAT:-}" ;; "$PP_SAV") pwr="${PLATFORM_PROFILE_ON_SAV:-$PLATFORM_PROFILE_ON_BAT}" ;; esac if [ -z "$pwr" ]; then # do nothing if unconfigured echo_debug "pm" "set_platform_profile($1).not_configured" return 0 fi if [ -f $FWACPID/platform_profile ]; then if check_ppd_running; then # do not apply platform profile when power-profiles-daemon is running echo_message "Warning: PLATFORM_PROFILE_ON_AC/BAT is not set because power-profiles-daemon is running." echo_debug "pm" "set_platform_profile($1).nop_ppd_active" return 0 fi if write_sysf "$pwr" $FWACPID/platform_profile; then echo_debug "pm" "set_platform_profile($1): $pwr" else echo_debug "pm" "set_platform_profile($1).write_error" fi else echo_debug "pm" "set_platform_profile($1).not_available" fi return 0 } TLP-1.10.1/func.d/15-tlp-func-disk000066400000000000000000000541021517565574500162620ustar00rootroot00000000000000#!/bin/sh # tlp-func-disk - Storage Device and Filesystem Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base # ---------------------------------------------------------------------------- # Constants readonly AHCI_GLOB=$PCID'/*/ata*' readonly ALPM_GLOB=$AHCI_GLOB'/host*/scsi_host/host*' readonly DISK_NOP_WORDS="_ keep" # ---------------------------------------------------------------------------- # Functions # --- Device Helpers get_disk_dev () { # translate disk id to device (sdX) # and determine disk type and capabilities # $1: id or device basename # $2: target disk device (optional) # rc: 0=disk exists (optional: amd matches target) # 1=disk non-existent (optional: or does not match target) # retval: $_disk_dev: device basename - below /dev or /sys/block; # $_disk_id: id basename - below /dev/disk/by-id; # $_disk_type: nvme/ata/sata/usb/ieee1394; # $_disk_apm: 0=no apm/1=apm capable; # $_disk_mq: scheduler: 0=single queue/1=multi queue; # $_disk_runpm: runtime pm: 0=allowed/1=denied by kernel/2=denied by tlp/3=incapable local dev id idpath local target="$2" _disk_dev="" if [ -h "/dev/disk/by-id/$1" ]; then # $1 is disk id _disk_id=$1 _disk_dev=$(printf '%s' "$_disk_id" | sed -r 's/-part[1-9][0-9]*$//') _disk_dev=$(readlink "/dev/disk/by-id/$_disk_dev") _disk_dev=${_disk_dev##*/} else # $1 is disk dev _disk_dev=$1 _disk_id="" if [ -b "/dev/$_disk_dev" ]; then # disk exists, lookup id for idpath in /dev/disk/by-id/*; do id="${idpath##*/}" # filter partitions [ -n "${id%%*-part[1-9]*}" ] || continue case "$id" in ata-*|usb-*) dev=$(readlink "$idpath") dev=${dev##*/} if [ "$dev" = "$_disk_dev" ]; then _disk_id="$id" break fi ;; nvme-*) # filter 'nvme-eui.*' if [ -n "${id##nvme-eui.*}" ]; then dev=$(readlink "$idpath") dev=${dev##*/} if [ "$dev" = "$_disk_dev" ]; then _disk_id="$id" break fi fi ;; esac done fi fi if [ -b "/dev/$_disk_dev" ]; then # retrieve device attributes local bus dpath path udevadm_data local DEVPATH= local ID_PATH= local ID_BUS= local ID_ATA_FEATURE_SET_PM_ENABLED= if udevadm_data="$( $UDEVADM info -q property "/dev/$_disk_dev" 2>/dev/null | \ grep -E '^(DEVPATH|ID_BUS|ID_PATH|ID_ATA_FEATURE_SET_PM_ENABLED)=' )"; then eval "${udevadm_data}" fi # determine device type (bus) path="$ID_PATH" bus="$ID_BUS" case "$path" in pci-*-nvme-*) _disk_type="nvme" ;; pci-*-ata-*) _disk_type="ata" ;; pci-*-usb-*) _disk_type="usb" ;; pci-*-ieee1394-*) _disk_type="ieee1394" ;; *) case "$bus" in nvme) _disk_type="nvme" ;; ata) _disk_type="ata" ;; usb) _disk_type="usb" ;; ieee1394) _disk_type="ieee1394" ;; *) dpath="${DEVPATH##*/}" case $dpath in nvme*) _disk_type="nvme" ;; *) _disk_type="unknown" ;; esac ;; esac esac # distinguish sata from ata(ide) disks if [ "$_disk_type" = "ata" ]; then if glob_files "/link_power_management_policy" "/sys${DEVPATH%/target*}/scsi_host/host*" > /dev/null 2>&1; then _disk_type=sata fi fi # determine APM capability _disk_apm=0 if [ "$ID_ATA_FEATURE_SET_PM_ENABLED" = "1" ]; then _disk_apm=1 fi # determine if single- or multi-queue scheduler if [ -d "/sys/block/$_disk_dev/mq" ]; then _disk_mq="1" else _disk_mq="0" fi # determine if runtime pm is possible and allowed if [ -f "/sys/block/$_disk_dev/device/power/control" ]; then # disk has a runtime pm control sysfile case "$_disk_type" in nvme) # nvme disks do not have a readable autosuspend_delay_ms, # nevertheless runtime pm changes are safe _disk_runpm="0" ;; sata|ata|usb) if ! readable_sysf "/sys/block/$_disk_dev/device/power/autosuspend_delay_ms"; then # autosuspend_delay_ms is missing or not readable # --> kernel itself denies runtime pm for the disk _disk_runpm="1" else # kernel allows runtime pm for the disk # --> tlp decides if it is safe if [ "$_disk_mq" = "0" ] || kernel_version_ge 4.19; then # singlequeue scheduler is considered safe for all kernels, # multiqueue and kernel >= 4.19 too _disk_runpm="0" else # multiqueue scheduler and kernel < 4.19 is considered unsafe, # because system freezes and data loss may occur when enabling # runtime pm for a sata or ata disk. # only with kernel 4.19 a lock was introduced which prevents that mq # is forced via command line options: # https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v4.19&id=b233f127042dba991229e3882c6217c80492f6ef # distribution kernels < 4.19 may be patched too, but better safe than sorry. _disk_runpm="2" fi fi ;; *) # tlp shall not (yet) touch runtime pm for other disk types _disk_runpm="2" ;; esac else # disk is not runtime pm capable _disk_runpm="3" fi if [ -n "$target" ]; then # in target mode trace output only in case of match if [ "$target" = "$_disk_dev" ]; then echo_debug "disk" "get_disk_dev($1).target: dev=$_disk_dev; id=$_disk_id; type=$_disk_type; path=$path; bus=$bus; dpath=$dpath; apm=$_disk_apm; mq=$_disk_mq; runpm=$_disk_runpm" return 0 else # no match return 1 fi else echo_debug "disk" "get_disk_dev($1): dev=$_disk_dev; id=$_disk_id; type=$_disk_type; path=$path; bus=$bus; dpath=$dpath; apm=$_disk_apm; mq=$_disk_mq; runpm=$_disk_runpm" return 0 fi else _disk_type="none" echo_debug "disk" "get_disk_dev($1).missing" return 1 fi } show_disk_ids () { # show disk id's local dev local shown="" { # iterate SATA and NVMe disks for dev in $(glob_files '/*' /dev/disk/by-id/ | sed -rn 's/.*\/((ata|ieee1394|nvme|usb))/\1/p' | grep -E -v '(^nvme-eui|-part[1-9]+)'); do if [ -n "$dev" ] && get_disk_dev "$dev" && ! wordinlist "$_disk_dev" "$shown" ; then printf '%s: %s\n' "$_disk_dev" "$_disk_id" shown="$shown $_disk_dev" fi done } | sort return 0 } # --- Disk APM Features set_disk_apm_level () { # set disk apm level # $1: PP_PRF, PP_BAL, PP_SAV # $2: target disk device (optional) local pwrmode="$1" local target="$2" local dev log_message # quit when disabled if [ -z "$DISK_DEVICES" ]; then echo_debug "disk" "set_disk_apm_level($pwrmode).disabled" return 0 fi # set @argv := apmlist (blanks removed - relying on a sane $IFS) case "$1" in "$PP_PRF") # shellcheck disable=SC2086 set -- $DISK_APM_LEVEL_ON_AC ;; "$PP_BAL"|"$PP_SAV") # shellcheck disable=SC2086 set -- $DISK_APM_LEVEL_ON_BAT ;; *) set -- ;; esac # quit if empty apmlist if [ $# -eq 0 ]; then echo_debug "disk" "set_disk_apm_level($pwrmode).not_configured" return 0 fi if [ -z "$target" ]; then echo_debug "disk" "*** set_disk_apm_level($pwrmode).all" else echo_debug "disk" "*** set_disk_apm_level($pwrmode).target: $target" fi # pairwise iteration DISK_DEVICES[1,n], apmlist[1,m]; m > 0 # for j in [1,n]: disk_dev[j], apmlist[min(j,m)] # operation modes: # 1. work on all disks in $DISK_DEVICES # 2. work on $target only -- when called by udev event for dev in $DISK_DEVICES; do : "${1:?BUG: broken DISK_APM_LEVEL list handling}" if get_disk_dev "$dev" "$target"; then log_message="set_disk_apm_level($pwrmode): $_disk_dev [$_disk_id] $1" if wordinlist "$_disk_type" "$DISK_APM_CLASS_DENYLIST"; then echo_debug "disk" "${log_message}; class denylist" elif [ "$_disk_apm" = "0" ]; then echo_debug "disk" "${log_message}; not supported" elif wordinlist "$1" "$DISK_NOP_WORDS"; then echo_debug "disk" "${log_message}; keep as is" else $HDPARM -B "$1" "/dev/$_disk_dev" > /dev/null 2>&1 echo_debug "disk" "${log_message}; rc=$?" fi fi # quit the loop after reaching the target [ -n "$target" ] && [ "$target" = "$_disk_dev" ] && break # last entry in apmlist applies to all remaining disks [ $# -lt 2 ] || shift done return 0 } set_disk_spindown_timeout () { # set disk spindown timeout # $1: 0=ac mode, 1=battery mode # $2: target disk device (optional) local pwrmode="$1" local target="$2" local dev log_message # quit when disabled if [ -z "$DISK_DEVICES" ]; then echo_debug "disk" "set_disk_spindown_timeout($pwrmode).disabled" return 0 fi # set @argv := timeoutlist case "$pwrmode" in "$PP_PRF") # shellcheck disable=SC2086 set -- $DISK_SPINDOWN_TIMEOUT_ON_AC ;; "$PP_BAL"|"$PP_SAV") # shellcheck disable=SC2086 set -- $DISK_SPINDOWN_TIMEOUT_ON_BAT ;; *) set -- ;; esac # quit if empty timeoutlist if [ $# -eq 0 ]; then echo_debug "disk" "set_disk_spindown_timeout($pwrmode).not_configured" return 0 fi if [ -z "$target" ]; then echo_debug "disk" "*** set_disk_spindown_timeout($pwrmode).all" else echo_debug "disk" "*** set_disk_spindown_timeout($pwrmode).target: $target" fi # pairwise iteration DISK_DEVICES[1,n], timeoutlist[1,m]; m > 0 # for j in [1,n]: disk_dev[j], timeoutlist[min(j,m)] # operation modes: # 1. work on all disks in $DISK_DEVICES # 2. work on $target only -- when called by udev event for dev in $DISK_DEVICES; do : "${1:?BUG: broken DISK_SPINDOWN_TIMEOUT list handling}" if get_disk_dev "$dev" "$target"; then log_message="set_disk_spindown_timeout($pwrmode): $_disk_dev [$_disk_id] $1" if wordinlist "$1" "$DISK_NOP_WORDS"; then echo_debug "disk" "${log_message}; keep as is" else $HDPARM -S "$1" "/dev/$_disk_dev" > /dev/null 2>&1 echo_debug "disk" "${log_message}; rc=$?" fi fi # quit the loop after reaching the target [ -n "$target" ] && [ "$target" = "$_disk_dev" ] && break # last entry in timeoutlist applies to all remaining disks [ $# -lt 2 ] || shift done return 0 } spindown_disk () { # stop spindle motor -- $1: dev $HDPARM -y "/dev/$1" > /dev/null 2>&1 return 0 } set_disk_iosched () { # set disk io scheduler # $1: target disk device (optional) local target="$1" local dev log_message # quit when disabled if [ -z "$DISK_DEVICES" ]; then echo_debug "disk" "set_disk_iosched.disabled" return 0 fi # set @argv := schedlist # shellcheck disable=SC2086 set -- $DISK_IOSCHED # quit if empty schedlist if [ $# -eq 0 ]; then echo_debug "disk" "set_disk_iosched.not_configured" return 0 fi if [ -z "$target" ]; then echo_debug "disk" "*** set_disk_iosched.all" else echo_debug "disk" "*** set_disk_iosched.target: $target" fi # pairwise iteration DISK_DEVICES[1,n], schedlist[1,m]; m > 0 # for j in [1,min(n,m)] : disk_dev[j], schedlistj] # for j in [min(n,m)+1,n] : disk_dev[j], %keep # operation modes: # 1. work on all disks in $DISK_DEVICES # 2. work on $target only -- when called by udev event for dev in $DISK_DEVICES; do local sched schedctrl if get_disk_dev "$dev" "$target"; then # get sched from argv, use "keep" when list is too short sched=${1:-keep} schedctrl="/sys/block/$_disk_dev/queue/scheduler" log_message="set_disk_iosched: $_disk_dev [$_disk_id] $sched" if [ ! -f "$schedctrl" ]; then echo_debug "disk" "${log_message}; not supported" elif wordinlist "$sched" "$DISK_NOP_WORDS"; then echo_debug "disk" "${log_message}; keep as is" else write_sysf "$sched" "$schedctrl" echo_debug "disk" "${log_message}; rc=$?" fi fi # quit the loop after reaching the target [ -n "$target" ] && [ "$target" = "$_disk_dev" ] && break # using "keep" when argv is empty [ $# -eq 0 ] || shift done return 0 } # --- Power Saving set_sata_link_power () { # set ahci link power management # $1: 0=ac mode, 1=battery mode local pwrmode="$1" local host host_bl hostid linkpol pwr rc local pwrlist="" local ctrl_avail="0" case "$pwrmode" in "$PP_PRF") pwrlist="${SATA_LINKPWR_ON_AC:-}" ;; "$PP_BAL"|"$PP_SAV") pwrlist="${SATA_LINKPWR_ON_BAT:-}" ;; esac if [ -z "$pwrlist" ]; then # do nothing if unconfigured echo_debug "disk" "set_sata_link_power($pwrmode).not_configured" return 0 fi # ALPM denylist host_bl=${SATA_LINKPWR_DENYLIST:-} # copy configured values to args # shellcheck disable=SC2086 set -- $pwrlist # iterate SATA hosts for host in $ALPM_GLOB ; do linkpol="$host/link_power_management_policy" if [ -f "$linkpol" ]; then hostid=${host##*/} if wordinlist "$hostid" "$host_bl"; then # host denylisted --> skip echo_debug "disk" "set_sata_link_power($pwrmode).deny: $host" ctrl_avail="1" else # host not denylisted --> iterate all configured values for pwr in "$@"; do write_sysf "$pwr" "$linkpol"; rc=$? echo_debug "disk" "set_sata_link_power($pwrmode).$pwr: $host; rc=$rc" if [ $rc -eq 0 ]; then # write successful --> goto next host ctrl_avail="1" break else # write failed --> don't use this value for remaining hosts # and try next value shift fi done fi fi done [ "$ctrl_avail" = "0" ] && echo_debug "disk" "set_sata_link_power($pwrmode).not_available" return 0 } set_ahci_disk_runtime_pm () { # set runtime power management for ahci disks # $1: PP_PRF, PP_BAL, PP_SAV, PP_SUS # $2: target disk device (optional) local target="$2" local control dev timeout rc case "$1" in "$PP_PRF") control="${AHCI_RUNTIME_PM_ON_AC:-}" ;; "$PP_BAL"|"$PP_SAV") control="${AHCI_RUNTIME_PM_ON_BAT:-}" ;; "$PP_SUS") # reset on suspend only when configured if [ -n "${AHCI_RUNTIME_PM_ON_AC:-}${AHCI_RUNTIME_PM_ON_BAT:-}" ]; then control="on" fi ;; esac # calc timeout in millisecs timeout="$AHCI_RUNTIME_PM_TIMEOUT" [ -z "$timeout" ] || timeout=$((timeout * 1000)) # check values case "$control" in on|auto) ;; *) control="" ;; # invalid input --> unconfigured esac if [ -z "$control" ]; then # do nothing if unconfigured echo_debug "disk" "set_ahci_disk_runtime_pm($1).not_configured" return 0 fi # when timeout is unconfigured we're done here if [ -z "$timeout" ]; then echo_debug "disk" "set_ahci_disk_runtime_pm($1).timeout_not_configured" return 0 fi if [ -z "$target" ]; then echo_debug "disk" "*** set_ahci_disk_runtime_pm($1).all" else echo_debug "disk" "*** set_ahci_disk_runtime_pm($1).target: $target" fi # iterate DISK_DEVICES for dev in $DISK_DEVICES; do if get_disk_dev "$dev" "$target"; then case "$_disk_runpm" in 0) # runtime pm allowed for disk rc=0 # write timeout first to prevent lockups if ! write_sysf "$timeout" "/sys/block/$_disk_dev/device/power/autosuspend_delay_ms"; then # writing timeout failed rc=1 fi # proceed with activation if ! write_sysf "$control" "/sys/block/$_disk_dev/device/power/control"; then # activation failed rc=2 fi echo_debug "disk" "set_ahci_disk_runtime_pm($1).$control: disk=$_disk_dev timeout=$timeout; rc=$rc" ;; 1|2) # runtime pm denied for disk echo_debug "disk" "set_ahci_disk_runtime_pm($1).denied: disk=$_disk_dev; runpm=$_disk_runpm" ;; 3) # disk not runtime pm capable echo_debug "disk" "set_ahci_disk_runtime_pm($1).incapable: disk=$_disk_dev; runpm=$_disk_runpm" ;; esac fi # quit the loop after reaching the target [ -n "$target" ] && [ "$target" = "$_disk_dev" ] && break done return 0 } set_ahci_port_runtime_pm () { # set runtime power management for ahci ports # $1: 0=ac mode, 1=battery mode, 3=suspend mode local control device case "$1" in "$PP_PRF") control="${AHCI_RUNTIME_PM_ON_AC:-}" ;; "$PP_BAL"|"$PP_SAV") control="${AHCI_RUNTIME_PM_ON_BAT:-}" ;; "$PP_SUS") # reset on suspend only when configured if [ -n "${AHCI_RUNTIME_PM_ON_AC:-}${AHCI_RUNTIME_PM_ON_BAT:-}" ]; then control="on" fi ;; esac # check values case "$control" in on|auto) ;; *) control="" ;; # invalid input --> unconfigured esac if [ -z "$control" ]; then # do nothing if unconfigured echo_debug "disk" "set_ahci_port_runtime_pm($1).not_configured" return 0 fi # iterate ahci ports for device in $AHCI_GLOB; do if write_sysf "$control" "${device}/power/control"; then echo_debug "disk" "set_ahci_port_runtime_pm($1).$control: port=$device; rc=0" else echo_debug "disk" "set_ahci_port_runtime_pm($1).no-port" fi done return 0 } # --- Filesystem Parameters set_laptopmode () { # set kernel laptop mode # $1: PP_PRF, PP_BAL, PP_SAV local isec case "$1" in "$PP_PRF") isec="${DISK_IDLE_SECS_ON_AC:-}" ;; "$PP_BAL"|"$PP_SAV") isec="${DISK_IDLE_SECS_ON_BAT:-}" ;; esac # replace with empty string if non-numeric chars are contained isec=$(printf '%s' "$isec" | grep -E '^[0-9]+$') if [ -z "$isec" ]; then # do nothing if unconfigured or non numeric value echo_debug "disk" "set_laptopmode($1).not_configured" return 0 fi write_sysf "$isec" /proc/sys/vm/laptop_mode echo_debug "disk" "set_laptopmode($1): $isec; rc=$?" return 0 } set_dirty_parms () { # set filesystem buffer params # $1: PP_PRF, PP_BAL, PP_SAV local age cage df ec case "$1" in "$PP_PRF") age="${MAX_LOST_WORK_SECS_ON_AC:-0}" ;; "$PP_BAL"|"$PP_SAV") age="${MAX_LOST_WORK_SECS_ON_BAT:-0}" ;; esac # calc age in centisecs, non numeric values result in "0" cage=$((age * 100)) if [ "$cage" = "0" ]; then # do nothing if unconfigured or invalid age echo_debug "disk" "set_dirty_parms($1).not_configured" return 0 fi ec=0 for df in /proc/sys/vm/dirty_writeback_centisecs \ /proc/sys/vm/dirty_expire_centisecs \ /proc/sys/fs/xfs/age_buffer_centisecs \ /proc/sys/fs/xfs/xfssyncd_centisecs; do if [ -f "$df" ] && ! write_sysf "$cage" $df; then echo_debug "disk" "set_dirty_parms($1).write_error: $df $cage; rc=$?" ec=$((ec+1)) fi done # shellcheck disable=SC2043 for df in /proc/sys/fs/xfs/xfsbufd_centisecs; do if [ -f "$df" ] && ! write_sysf "3000" $df; then echo_debug "disk" "set_dirty_parms($1).write_error: $df 3000; rc=$?" ec=$((ec+1)) fi done echo_debug "disk" "set_dirty_parms($1): $cage; ec=$ec" return 0 } TLP-1.10.1/func.d/20-tlp-func-usb000066400000000000000000000247701517565574500161250ustar00rootroot00000000000000#!/bin/sh # tlp-func-usb - USB Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base # ---------------------------------------------------------------------------- # Constants readonly USBD=/sys/bus/usb/devices readonly USB_TIMEOUT_MS=2000 readonly USB_WWAN_VENDORS="0bdb 05c6 1199 2cb7" readonly USB_DONE=usb_done # ---------------------------------------------------------------------------- # Functions # --- USB Autosuspend usb_suspend_device () { # enable/disable usb autosuspend for a single device # except input, scanners and denylisted # $1: device syspath # $2: batch/udev # $3: auto=enable/on=disable local usbdev=$1 if [ -f "$usbdev/power/autosuspend_delay_ms" ]; then # device is autosuspendable local vendor usbid busdev dclass vendor="$(read_sysf "$usbdev/idVendor")" usbid="$vendor:$(read_sysf "$usbdev/idProduct")" busdev="Bus $(read_sysf "$usbdev/busnum") Dev $(read_sysf "$usbdev/devnum")" dclass="$(read_sysf "$usbdev/bDeviceClass")" local control="${3:-auto}" local caller="$2" local exc="" local chg=0 rc1=0 rc2=0 local drvlist="" # trace only: get drivers for all subdevices if [ "$X_USB_DRIVER_TRACE" = "1" ]; then local dl drvlist=$(for dl in "$usbdev"/*:*/driver; do readlink "$dl" | \ sed -r 's/.+\///'; done | sort -u | tr '\n' ' ') drvlist="(${drvlist% })" fi if wordinlist "$usbid" "$USB_ALLOWLIST"; then # device is in allowlist -- allowlist always wins control="auto" exc="_dev_allow" elif wordinlist "$usbid" "$USB_DENYLIST"; then # device is in denylist control="on" exc="_dev_deny" else local subdev # udev: wait for subdevices to populate [ "$caller" = "udev" ] && sleep 0.5 # check for hid subdevices for subdev in "$usbdev"/*:*; do if [ "$(read_sysf "$subdev/bInterfaceClass")" = "03" ]; then control="on" exc="_hid_deny" break fi done if [ -z "$exc" ]; then # check for bluetooth devices if [ "$USB_EXCLUDE_BTUSB" = "1" ] \ && [ "$dclass" = "e0" ] \ && [ "$(read_sysf "$usbdev/bDeviceSubClass")" = "01" ] \ && [ "$(read_sysf "$usbdev/bDeviceProtocol")" = "01" ]; then control="on" exc="_btusb_deny" fi fi # bluetooth if [ -z "$exc" ]; then # check for audio devices if [ "$USB_EXCLUDE_AUDIO" = "1" ]; then for subdev in "$usbdev"/*:*; do if [ "$(read_sysf "$subdev/bInterfaceClass")" = "01" ]; then control="on" exc="_audio_deny" break fi done fi fi # audio if [ -z "$exc" ]; then # check for scanners: # libsane_matched envvar is set by libsane's udev rules # shellcheck disable=SC2154 if [ "$libsane_matched" = "yes" ] || [ "$2" = "batch" ] \ && $UDEVADM info -q property "$usbdev" 2>/dev/null | grep -q 'libsane_matched=yes'; then # do not touch this device control="deny" exc="_libsane" fi fi if [ -z "$exc" ]; then # check for phone devices if [ "$USB_EXCLUDE_PHONE" = "1" ]; then if [ "$vendor" = "0fca" ]; then # RIM if [ "$dclass" = "ef" ]; then # RIM / BlackBerry control="on" exc="_phone_deny" elif [ "$dclass" = "00" ]; then for subdev in "$usbdev"/*:*; do if [ -d "$subdev" ]; then if [ "$(read_sysf "$subdev/interface")" = "BlackBerry" ]; then # Blackberry control="on" exc="_phone_deny" break fi fi done fi elif [ "$vendor" = "045e" ] && [ "$dclass" = "ef" ]; then # Windows Phone control="on" exc="_phone_deny" elif [ "$vendor" = "05ac" ] && [ "$(read_sysf "$usbdev/product")" = "iPhone" ]; then # iPhone control="on" exc="_phone_deny" elif [ "$dclass" = "00" ]; then # class defined at interface level, iterate subdevices for subdev in "$usbdev"/*:*; do if [ -d "$subdev" ]; then if [ "$(read_sysf "$subdev/interface")" = "MTP" ]; then # MTP: mostly Android control="on" exc="_phone_deny" break elif [ "$(read_sysf "$subdev/bInterfaceClass")" = "ff" ] \ && [ "$(read_sysf "$subdev/bInterfaceSubClass")" = "42" ] \ && [ "$(read_sysf "$subdev/bInterfaceProtocol")" = "01" ]; then # ADB: Android control="on" exc="_phone_deny" break elif [ "$(read_sysf "$subdev/bInterfaceClass")" = "06" ] \ && [ "$(read_sysf "$subdev/bInterfaceSubClass")" = "01" ] \ && [ "$(read_sysf "$subdev/bInterfaceProtocol")" = "01" ]; then # PTP: iPhone, Lumia et al. # caveat: may also be a camera control="on" exc="_phone_deny" break fi fi done fi # dclass 00 fi # exclude phone fi # phone if [ -z "$exc" ]; then # check for printers if [ "$USB_EXCLUDE_PRINTER" = "1" ]; then if [ "$dclass" = "00" ]; then # check for printer subdevices for subdev in "$usbdev"/*:*; do if [ "$(read_sysf "$subdev/bInterfaceClass")" = "07" ]; then control="on" exc="_printer_deny" break fi done fi fi fi # printer if [ -z "$exc" ]; then # check for wwan devices if [ "$USB_EXCLUDE_WWAN" = "1" ]; then if [ "$dclass" != "00" ]; then # check for cdc subdevices for subdev in "$usbdev"/*:*; do if [ "$(read_sysf "$subdev/bInterfaceClass")" = "0a" ]; then control="on" exc="_wwan_deny" break fi done fi if [ -z "$exc" ]; then # check for vendors if wordinlist "$vendor" "$USB_WWAN_VENDORS"; then control="on" exc="_wwan_deny" fi fi fi # exclude wwan fi # wwan fi # !device denylist if [ "$(read_sysf "$usbdev/power/control")" != "$control" ]; then # set control, write actual changes only case $control in auto|on) write_sysf "$control" "$usbdev/power/control"; rc1=$? chg=1 ;; deny) # do not touch denylisted device ;; esac fi if [ "$X_TLP_USB_SET_AUTOSUSPEND_DELAY" = "1" ]; then # set autosuspend delay write_sysf $USB_TIMEOUT_MS "$usbdev/power/autosuspend_delay_ms"; rc2=$? echo_debug "usb" "usb_suspend_device.$caller.$control$exc: $busdev ID $usbid $usbdev [$drvlist]; control: rc=$rc1; delay: rc=$rc2" elif [ $chg -eq 1 ]; then # default: change control but not autosuspend_delay, i.e. keep kernel default setting echo_debug "usb" "usb_suspend_device.$caller.$control$exc: $busdev ID $usbid $usbdev [$drvlist]; control: rc=$rc1" else # we didn't change anything actually echo_debug "usb" "usb_suspend_device.$caller.$control$exc.no_change: $busdev ID $usbid $usbdev [$drvlist]" fi fi # autosuspendable return 0 } set_usb_suspend () { # enable/disable usb autosuspend for all devices # $1: 0=silent/1=report result # $2: auto=enable/on=disable local usbdev if [ "$USB_AUTOSUSPEND" = "1" ]; then # autosuspend is configured --> iterate devices for usbdev in "$USBD"/*; do case "$usbdev" in *:*) ;; # colon in device name --> do nothing *) usb_suspend_device "$usbdev" "batch" "$2" ;; esac done [ "$1" = "1" ] && echo "USB autosuspend settings applied." echo_debug "usb" "set_usb_suspend.done" # set "startup completion" flag for tlp-usb-udev set_run_flag $USB_DONE else [ "$1" = "1" ] && cecho "Error: USB autosuspend is disabled. Set USB_AUTOSUSPEND=1 in ${CONF_USR}." 1>&2 echo_debug "usb" "set_usb_suspend.not_configured" fi return 0 } TLP-1.10.1/func.d/25-tlp-func-rf000066400000000000000000000116271517565574500157450ustar00rootroot00000000000000#!/bin/sh # tlp-func-rf - Radio Device Checks and PM Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base # ---------------------------------------------------------------------------- # Constants readonly IW=iw readonly BLUETOOTHD=/sys/class/bluetooth # ---------------------------------------------------------------------------- # Functions # --- Wifi Device Checks get_wifi_ifaces () { # get all wifi devices -- retval: $_wifaces local wi wiu _wifaces="" for wiu in "$NETD"/*/uevent; do if grep -q -s 'DEVTYPE=wlan' "$wiu" ; then wi="${wiu%/uevent}"; wi="${wi##*/}" _wifaces="${_wifaces}${_wifaces:+ }${wi}" fi done return 0 } get_wifi_driver () { # get driver associated with interface # $1: iface; retval: $_wifidrv local drvl _wifidrv="" if [ -d "$NETD/$1" ]; then drvl=$(readlink "$NETD/$1/device/driver") # shellcheck disable=SC2034 [ -n "$drvl" ] && _wifidrv=${drvl##*/} fi return 0 } wireless_in_use () { # check if wifi or wwan device is in use -- $1: iface if [ -f "$NETD/$1/carrier" ]; then if [ "$(read_sysf "$NETD/$1/carrier")" = "1" ]; then return 0 fi fi return 1 } any_wifi_in_use () { # check if any wifi device is in use local iface get_wifi_ifaces for iface in $_wifaces; do wireless_in_use "$iface" && return 0 done return 1 } # --- Wifi Power Management set_wifi_power_mode () { # set wifi power save mode -- $1: PP_PRF, PP_BAL, PP_SAV local pwr iface case "$1" in "$PP_PRF") pwr="${WIFI_PWR_ON_AC:-}" ;; "$PP_BAL"|"$PP_SAV") pwr="${WIFI_PWR_ON_BAT:-}" ;; esac # check values, translate obsolete syntax case "$pwr" in off|on) ;; 0|1|N) pwr="off" ;; 2|3|4|5|6|Y) pwr="on" ;; *) pwr="" ;; # invalid input --> unconfigured esac if [ -z "$pwr" ]; then # do nothing if unconfigured echo_debug "pm" "set_wifi_power_mode($1).not_configured" return 0 fi get_wifi_ifaces if [ -z "$_wifaces" ]; then echo_debug "pm" "set_wifi_power_mode($1).no_ifaces" return 0 fi for iface in $_wifaces; do if [ -n "$iface" ]; then if cmd_exists $IW; then $IW dev "$iface" set power_save "$pwr" > /dev/null 2>&1 echo_debug "pm" "set_wifi_power_mode($1, $iface).iw: $pwr; rc=$?" else # iw not iwconfig installed echo_debug "pm" "set_wifi_power_mode($1, $iface).no_iw" return 1 fi fi done return 0 } # --- WWAN Device Checks get_wwan_ifaces () { # get all wwan devices -- retval: $_wanifaces local wi wiu _wanifaces="" for wiu in "$NETD"/*/uevent; do if grep -q -s 'DEVTYPE=wwan' "$wiu" ; then wi="${wiu%/uevent}"; wi="${wi##*/}" _wanifaces="${_wanifaces}${_wanifaces:+ }${wi}" fi done return 0 } any_wwan_in_use () { # check if any wwan device is in use local iface get_wwan_ifaces for iface in $_wanifaces; do wireless_in_use "$iface" && return 0 done return 1 } get_wwan_driver () { # get driver associated with interface # $1: iface; retval: $_wwandrv local drvl _wwandrv="" if [ -d "$NETD/$1" ]; then drvl=$(readlink "$NETD/$1/device/driver") # shellcheck disable=SC2034 [ -n "$drvl" ] && _wwandrv=${drvl##*/} fi return 0 } # --- Bluetooth Device Checks get_bluetooth_ifaces () { # get all bluetooth interfaces -- retval: $_bifaces # enumerate symlinks only _bifaces="$(for i in "$BLUETOOTHD"/*; do [ -h "$i" ] && echo "${i##/*/}"; done | grep -v ':')" return 0 } get_bluetooth_driver () { # get driver associated with interface -- $1: iface; retval: $_btdrv local drvl # shellcheck disable=SC2034 _btdrv="" if [ -d "$BLUETOOTHD/$1" ]; then drvl=$(readlink "$BLUETOOTHD/$1/device/driver") # shellcheck disable=SC2034 [ -n "$drvl" ] && _btdrv=${drvl##*/} fi return 0 } bluetooth_in_use () { # check if bluetooth interface is in use -- $1: iface local uev # when devices are connected to an interface its sysdir is populated with # subdevices like : where the uevent file contains a line # "DEVTYPE=link" for uev in "$BLUETOOTHD/$1/$1":*/uevent; do grep -q -s 'DEVTYPE=link' "$uev" && return 0 done return 1 } any_bluetooth_in_use () { # check if any bluetooth interface is in use local i get_bluetooth_ifaces for i in $_bifaces; do bluetooth_in_use "$i" && return 0 done return 1 } # --- NFC Device Checks any_nfc_in_use () { # always consider nfc *not* in use return 1 } TLP-1.10.1/func.d/30-tlp-func-rf-sw000066400000000000000000000350241517565574500163650ustar00rootroot00000000000000#!/bin/sh # tlp-func-rf-sw - Radio Switch Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, tlp-func-rf # shellcheck disable=SC2034 # ---------------------------------------------------------------------------- # Constants readonly NMCLI=nmcli readonly RFKILL="rfkill" readonly RFKD="/dev/rfkill" readonly ALLDEV="bluetooth nfc wifi wwan" readonly RDW_NM_LOCK="rdw_nm" readonly RDW_DOCK_LOCK="rdw_dock" readonly RDW_NM_LOCKTIME=2 readonly RDW_KILL="rdw_kill" readonly RFSTATEFILE="$VARDIR/rfkill_saved" # ---------------------------------------------------------------------------- # Functions get_rf_dev_state () { # get radio device state # $1: rftype # rc: 0=ok/1=no device (or unknown type) # retval $_devs: 0=off/1=on/254=no device (or unknown type) # $_devc: sysfs rkill state local rfki rfks # preset: no device _devs=254 _devc="" # step 1: get control device for radio type case "$1" in wwan|bluetooth|nfc) for rfki in /sys/class/rfkill/rfkill* ; do if [ "$(read_sysf "$rfki/type")" = "$1" ]; then _devc="$rfki/state" fi done ;; wifi) for rfki in /sys/class/rfkill/rfkill* ; do if [ "$(read_sysf "$rfki/type")" = "wlan" ]; then _devc="$rfki/state" fi done ;; *) # unknown radio type -> quit cecho "Error: unknown device type $1" 1>&2 echo_debug "rf" "get_rf_dev_state($1).unknown_type" return 1 ;; esac if [ -z "$_devc" ]; then # no device of this radio type -> quit echo_debug "rf" "get_rf_dev_state($1).not_present" return 1 fi # step 2: get state for radio type if check_nm && wordinlist "$1" "wifi wwan"; then # read state from rfkill sysfs first ... rfks="$(read_sysf "$_devc")" # ... then get state from NM case "$($NMCLI radio "$1" 2> /dev/null)" in enabled) # assumption: the case NM(on), rfkill(off) does not occur _devs=1 ;; disabled) case "$rfks" in 0) _devs=0 ;; # rfkill soft blocked 1) _devs=0 ;; # NM=off and rfkill=unblocked i.e. states are *not* in sync # -> return off(soft blocked) to keep things going 2) _devs=2 ;; # rfkill hard blocked esac ;; *) _devs=3 # unknown state esac echo_debug "rf" "get_rf_dev_state.nmcli($1) = $_devs; devc=$_devc; rfkill=$rfks" else # get state from rfkill sysfs _devs="$(read_sysf "$_devc")" case "$_devs" in 0|1|2) ;; # 0=soft blocked(off)/1=unblocked(on)/2=hard blocked(off) *) _devs=3 ;; # unknown state esac echo_debug "rf" "get_rf_dev_state.sysfs($1) = $_devs; devc=$_devc" fi return 0 } err_no_root_priv () { # check root privilege cecho "Error: missing root privilege." 1>&2 echo_debug "rf" "$1.missing_root_privilege" return 0 } test_rfkill_perms () { # test if either root priv or rfkill device writable test_root || [ -w $RFKD ] } check_nm () { # test if NetworkManager is installed [ "$X_USE_NMCLI" != "0" ] && cmd_exists $NMCLI } invoke_nmcli () { # call nmcli to switch radio # $1: rftype, $2: on/off, $3: caller # rc: nmcli rc local rc check_nm || return 0 # return if NetworkManager not running $NMCLI radio "$1" "$2" > /dev/null 2>&1; rc=$? echo_debug "rf" "invoke_nmcli($1, $2).radio: rc=$rc" return $rc } device_switch () { # switch radio state # $1: rftype, $2: 1/on/0/off/toggle # $3: lock id, $4: lock duration # rc: 0=switched/1=invalid device or operation/ # 2=hard blocked/3=invalid state/4=no change # retval $_devc, $_devs: 0=off/1=on local curst devn newst nmrc echo_debug "rf" "device_switch($1, $2, $3, $4)" # get current device state if ! get_rf_dev_state "$1"; then # no device -> quit echo_debug "rf" "device_switch($1, $2).no_device: rc=1" return 1 fi curst="$_devs" # quit if invalid operation if ! wordinlist "$2" "on 1 off 0 toggle"; then echo_debug "rf" "device_switch($1, $2).invalid_op: rc=1" return 1 fi # quit if device state is hard blocked or invalid if [ "$_devs" -ge 2 ]; then case "$_devs" in 2) echo_debug "rf" "device_switch($1, $2).hard_blocked: rc=$_devs" ;; *) echo_debug "rf" "device_switch($1, $2).invalid_state: rc=$_devs" ;; esac return "$_devs" fi # determine desired device state case "$2" in 1|on) newst=1 ;; 0|off) newst=0 ;; toggle) newst=$((curst ^ 1)) ;; esac # compare current and desired device state if [ "$curst" = "$newst" ]; then # desired matches current state --> do nothing echo_debug "rf" "device_switch($1, $2).desired_state" return 0 else # desired does not match current state --> do switch # set timed lock if required [ -n "$3" ] && [ -n "$4" ] && [ "$1" != "bluetooth" ] && \ set_timed_lock "$3" "$4" if check_nm && wordinlist "$1" "wifi wwan"; then # switch device with NetworkManager case "$newst" in 1) invoke_nmcli "$1" on; nmrc=$? ;; 0) invoke_nmcli "$1" off; nmrc=$? ;; esac # record device state after nmcli get_rf_dev_state "$1" # interactive command only: check if failed due to missing privileges if [ -z "$3" ] && [ "$nmrc" = "1" ] && ! test_root; then err_no_root_priv "device_switch($1, $2).nmcli" fi elif cmd_exists $RFKILL ; then # switch device with rfkill if test_rfkill_perms ; then # use rfkill echo_debug "rf" "device_switch($1, $2).rfkill" case "$newst" in 1) $RFKILL unblock "$1" > /dev/null 2>&1 ;; 0) $RFKILL block "$1" > /dev/null 2>&1 ;; *) ;; esac # record device state after rfkill get_rf_dev_state "$1" else # missing permission to rfkill err_no_root_priv "device_switch($1, $2).rfkill" fi else # switch device with direct write # TODO: can't we remove that? if test_root ; then write_sysf "$newst" "$_devc" echo_debug "rf" "device_switch($1, $2).devc: rc=$?" # record device state after direct write get_rf_dev_state "$1" else err_no_root_priv "device_switch($1, $2).devc" fi fi fi # states did not match # quit if device state is hard blocked or invalid if [ "$_devs" -ge 2 ]; then case "$_devs" in 2) echo_debug "rf" "device_switch($1, $2).hard_blocked: rc=$_devs" ;; *) echo_debug "rf" "device_switch($1, $2).invalid_state: rc=$_devs" ;; esac return "$_devs" fi # compare old and new device state if [ "$curst" = "$_devs" ]; then # state did not change echo_debug "rf" "device_switch($1, $2).no_change: rc=4" return 4 else echo_debug "rf" "device_switch($1, $2).ok: rc=0" return 0 fi } echo_device_state () { # print radio state -- $1: rftype, $2: state # prerequisite: get_rf_dev_state() # case "$1" in bluetooth) devstr="bluetooth" ;; nfc) devstr="nfc " ;; wifi) devstr="wifi " ;; wwan) devstr="wwan " ;; *) devstr=$1 ;; esac case "$2" in 0) echo "$devstr = off (software)" ;; 1) echo "$devstr = on" ;; 2) echo "$devstr = off (hardware)" ;; 254) echo "$devstr = none (no device)" ;; *) echo "$devstr = invalid state" esac return 0 } # shellcheck disable=SC2120 save_device_states () { # save radio states # $1: list of rftypes # rc: 0=ok/1=create failed/2=write failed local dev local devlist="${1:-$ALLDEV}" # when arg empty -> use all local rc=0 # create empty state file if [ -d "$VARDIR" ] && { : > "$RFSTATEFILE"; } 2> /dev/null; then # iterate over all possible devices -> save state in file for dev in $devlist; do get_rf_dev_state "$dev" if [ "$_devs" = "0" ] || [ "$_devs" = "1" ]; then { printf '%s\n' "$dev $_devs" >> "$RFSTATEFILE"; } 2> /dev/null || rc=2 fi done else # create failed rc=1 fi echo_debug "rf" "save_device_states($devlist): $RFSTATEFILE; rc=$rc" return $rc } restore_device_states () { # restore radio states # rc: 0=ok/1=state file nonexistent local sline local rc=0 if [ -f "$RFSTATEFILE" ]; then # read state file # shellcheck disable=SC2162 while read -r sline; do # shellcheck disable=SC2086 set -- $sline # read dev, state into $1, $2 device_switch "$1" "$2" done < "$RFSTATEFILE" rm -f "$RFSTATEFILE" 2> /dev/null else # state file nonexistent rc=1 fi echo_debug "rf" "restore_device_states: $RFSTATEFILE; rc=$rc" return $rc } set_radio_device_states () { # set/initialize all radio states # $1: start/stop/PP_PRF=0/PP_BAL=1/PP_SAV=2 # called from init scripts or upon change of power source local dev devs2disable devs2enable restore local quiet=0 if wordinlist "$1" "start stop" && [ "${RESTORE_DEVICE_STATE_ON_STARTUP}" = "1" ]; then # save/restore mode on system startup/shutdown (DEPRECATED) echo_debug "rf" "set_radio_device_states($1).restore" case "$1" in start) if restore_device_states; then echo "Radio device states restored." else echo "No saved radio device states found." fi ;; stop) # shellcheck disable=SC2119 save_device_states echo "Radio device states saved." ;; esac else # disable/enable on system startup/shutdown or profile select case "$1" in start) # system startup devs2disable="$DEVICES_TO_DISABLE_ON_STARTUP" devs2enable="$DEVICES_TO_ENABLE_ON_STARTUP" ;; stop) # system shutdown devs2disable="" devs2enable="" if [ "$X_WIFI_ON_SHUTDOWN" != "0" ]; then # NM workaround: if # 1. disable wifi is configured somehow, and # 2. wifi is not explicitly configured for shutdown # then re-enable wifi on shutdown to prepare for startup if wordinlist "wifi" "$DEVICES_TO_DISABLE_ON_BAT $DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE $DEVICES_TO_DISABLE_ON_LAN_CONNECT $DEVICES_TO_DISABLE_ON_WIFI_CONNECT $DEVICES_TO_DISABLE_ON_WWAN_CONNECT" && \ ! wordinlist "wifi" "$devs2disable $devs2enable"; then devs2enable="wifi $devs2enable" fi fi ;; "$PP_PRF") # performance profile --> build enable list quiet=1 # do not display progress devs2enable="${DEVICES_TO_ENABLE_ON_AC:-}" devs2disable="" ;; "$PP_BAL"|"$PP_SAV") # balanced/power-saver profile --> build disable list quiet=1 # do not display progress devs2enable="" devs2disable="${DEVICES_TO_DISABLE_ON_BAT:-}" # check configured list for connected devices for dev in ${DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE:-}; do # if device is not connected and not in list yet --> add to disable list { case $dev in bluetooth) any_bluetooth_in_use ;; nfc) any_nfc_in_use ;; wifi) any_wifi_in_use ;; wwan) any_wwan_in_use ;; esac } || wordinlist "$dev" "$devs2disable" || devs2disable="$dev $devs2disable" done devs2disable="${devs2disable# }" ;; esac echo_debug "rf" "set_radio_device_states($1): enable=$devs2enable disable=$devs2disable" # disable configured radios if [ -n "$devs2disable" ]; then [ "$quiet" = "1" ] || printf "Disabling radios:" for dev in bluetooth nfc wifi wwan; do if wordinlist "$dev" "$devs2disable"; then [ "$quiet" = "1" ] || printf ' %s' "$dev" device_switch $dev off fi done [ "$quiet" = "1" ] || echo "." fi # enable configured radios if [ -n "$devs2enable" ]; then if [ "$1" = "radiosw" ]; then # radiosw mode: disable radios not listed for dev in bluetooth nfc wifi wwan; do if ! wordinlist "$dev" "$devs2enable"; then device_switch $dev off fi done else # start mode: enable listed radios [ "$quiet" = "1" ] || printf "Enabling radios:" for dev in bluetooth nfc wifi wwan; do if wordinlist "$dev" "$devs2enable"; then [ "$quiet" = "1" ] || printf ' %s' "$dev" device_switch $dev on fi done [ "$quiet" = "1" ] || echo "." fi fi # clean up: discard state file rm -f "$RFSTATEFILE" 2> /dev/null fi return 0 } TLP-1.10.1/func.d/35-tlp-func-batt000066400000000000000000000254221517565574500162670ustar00rootroot00000000000000#!/bin/sh # tlp-func-batt - Battery Feature Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 34-tlp-func-platform # ---------------------------------------------------------------------------- # Constants readonly BCT_ON=Long_Life readonly BCT_OFF=Standard # shellcheck disable=SC2034 readonly ACPIBATDIR=/sys/class/power_supply # ---------------------------------------------------------------------------- # Operational functions init_batteries_thresholds () { # apply thresholds from configuration to all batteries # optional depending on active plugin when specified in $1 # - called from bg tasks tlp init [re]start/auto and tlp start # $1: plugin list (space separated) # rc: 0=ok/ # 1=battery not present/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=read error/ # 5=write error/ # 6=threshold write discarded by kernel or firmware/ # 255=no thresh api local rc # select battery feature driver select_batdrv # shellcheck disable=SC2154 if [ "$_bm_thresh" = "none" ]; then # thresholds not available --> quit echo_debug "bat" "set_charge_thresholds.no_method" return 255 fi # apply thresholds # shellcheck disable=SC2154 if [ -z "$1" ]; then # Apply thresholds unconditionally batdrv_apply_configured_thresholds; rc=$? elif wordinlist "$_batdrv_plugin" "$1"; then # Specific hardware which resets the EC when resuming -> apply thresholds batdrv_apply_configured_thresholds; rc=$? fi return $rc } setcharge_battery () { # apply charge thresholds for a single battery # - called from cmdline tlp setcharge/fullcharge/recalibrate # $1: start charge threshold || battery # $2: stop charge threshold # $3: battery # rc: 0=ok/ # 1=battery not present/ # 2=threshold(s) out of range or non-numeric/ # 3=minimum start stop diff violated/ # 4=read error/ # 5=write error/ # 6=threshold write discarded by kernel or firmware/ # 255=no thresh api local bat rc start_thresh stop_thresh local use_cfg=0 # select battery feature driver select_batdrv # shellcheck disable=SC2154 if [ "$_bm_thresh" = "none" ]; then # thresholds not available --> quit cecho "Error: there is no hardware driver support for charge thresholds." 1>&2 echo_debug "bat" "setcharge_battery.no_method" return 255 fi # check params case $# in 0) # no args bat=DEF # use default(1st) battery use_cfg=1 # use configured values ;; 1) # assume $1 is battery bat=$1 use_cfg=1 # use configured values ;; 2) # assume $1,$2 are thresholds start_thresh=$1 stop_thresh=$2 bat=DEF # use default(1st) battery ;; 3|4) # assume $1,$2 are thresholds, $3 is battery start_thresh=$1 stop_thresh=$2 bat=${3:-DEF} ;; esac # check bat presence and/or get default(1st) battery if batdrv_select_battery "$bat"; then # battery present -> get configured values if requested if [ $use_cfg -eq 1 ]; then # shellcheck disable=SC2154 eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" # shellcheck disable=SC2154 eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" fi else # battery not present cecho "Error: battery $bat not present." 1>&2 echo_debug "bat" "setcharge_battery.not_present($bat)" return 1 fi # apply thresholds if [ $use_cfg -eq 1 ]; then # from configuration batdrv_write_thresholds "$start_thresh" "$stop_thresh" 2 1; rc=$? else # from command line batdrv_write_thresholds "$start_thresh" "$stop_thresh" 2; rc=$? fi return $rc } chargeonce_battery () { # charge battery to upper threshold once # $1: battery # rc: 0=ok/1=battery not present/255=no api local bat rc # select battery feature driver select_batdrv if [ "$_bm_thresh" = "none" ]; then # thresholds not available --> quit cecho "Error: there is no hardware driver support for charge thresholds." 1>&2 echo_debug "bat" "chargeonce_battery.no_method" return 255 fi # check params if [ -n "$1" ]; then # parameter(s) given, check $1 bat="${1:-DEF}" bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" else # no parameters given, use default(1st) battery bat=DEF fi # check bat presence and/or get default(1st) battery if ! batdrv_select_battery "$bat"; then # battery not present cecho "Error: battery $bat not present." 1>&2 # shellcheck disable=SC2154 echo_debug "bat" "chargeonce_battery.not_present($_bat_str)" return 1 fi # apply temporary start threshold batdrv_chargeonce; rc=$? if [ $rc -eq 255 ]; then cecho "Error: chargeonce not available for your hardware." 1>&2 echo_debug "bat" "chargeonce_battery.not_supported" return 255 fi return $rc } discharge_battery () { # discharge battery # $1: discharge/recalibrate # $2: battery # $2 or $3: target soc 0(default)..99 # rc: 0=ok/6=target soc out of bounds/7=target > actual soc/8=target soc reached/10=battery not present/11=fullcharge malfunction/12=no ac power/15=concurrent op running/16=safety lock/255=no api local bat mode rc target_soc # get params mode="${1:-discharge}" shift if ! check_ac_power "$mode"; then return 12 fi check_root # select battery care plugin select_batdrv # shellcheck disable=SC2154 if [ "$_bm_dischg" = "none" ]; then # no method available --> quit cecho "Error: battery $mode is not supported." 1>&2 echo_debug "bat" "discharge_battery.no_method" return 255 fi if batdrv_discharge_safetylock "$mode"; then return 16 fi if ! lock_tlp_nb tlp_discharge; then cecho "Error: another $mode operation is pending." 1>&2 echo_debug "bat" "discharge_battery.concurrent_op_running" return 15 fi # check params $1, $2 (after shift) case "$mode" in recalibrate) # $1 is battery (if existent) bat="${1:-DEF}" target_soc=0 ;; discharge) if [ -z "$1" ] && [ -z "$2" ]; then bat=DEF target_soc=0 elif [ -n "$1" ] && [ -z "$2" ]; then # $1 is target soc value or battery if is_uint "$1"; then bat=DEF target_soc="$1" else bat="$1" target_soc=0 fi else # $1 is battery, $2 is target soc value bat="$1" target_soc="$2" fi ;; esac bat=$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]") # check bat presence and/or get default(1st) battery if ! batdrv_select_battery "$bat"; then # battery not present cecho "Error: battery $bat not present." 1>&2 echo_debug "bat" "discharge_battery.not_present($bat)" unlock_tlp tlp_discharge return 10 fi # enable fullcharge if [ "$mode" = "recalibrate" ] && ! batdrv_write_thresholds DEF DEF 2 ""; then echo_debug "bat" "discharge_battery.fullcharge_malfunction" unlock_tlp tlp_discharge return 11 fi # execute discharge batdrv_discharge "$target_soc"; rc=$? if [ $rc -eq 0 ] && [ "$mode" = "recalibrate" ]; then cecho "Charging starts now. For a complete recalibration" "notice" 1>&2 cecho "keep AC connected until the battery is fully charged." "notice" 1>&2 fi unlock_tlp tlp_discharge return $rc } # ---------------------------------------------------------------------------- # Helper functions bat_bct2bool () { # convert battery charge type string to boolean # $1: charge type string case "$1" in "$BCT_ON") printf "1" ;; *) printf "0" ;; esac return 0 } bat_bool2bct () { # convert boolean to battery charge type string # $1: boolean case "$1" in 1) printf "%s" "$BCT_ON" ;; 0) printf "%s" "$BCT_OFF" ;; *) return 1 esac return 0 } soc_calc () { # calc and print battery charge level (rounded) # generic implementation # $1: format (optional) # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() # rc: 0=ok/1=charge level read error local ef en # shellcheck disable=SC2154 if [ -f "$_bd_read/energy_full" ]; then ef=$(read_sysval "$_bd_read/energy_full") en=$(read_sysval "$_bd_read/energy_now") elif [ -f "$_bd_read/charge_full" ]; then ef=$(read_sysval "$_bd_read/charge_full") en=$(read_sysval "$_bd_read/charge_now") else ef=0 en=0 fi if [ "$ef" != "0" ]; then if [ -n "$1" ]; then perl -e 'printf ("'"$1"'", 100.0 * '"$en"' / '"$ef"')' else perl -e 'printf ("%d", int(100.0 * '"$en"' / '"$ef"' + 0.5))' fi return 0 else printf "255" return 1 fi } soc_gt_stop_notice () { # output notice to discharge on battery power if SOC is above stop threshold # global params: $_batteries, $_bm_thresh, $_bd_read, $_bat_str # prerequisite: batdrv_init(), batdrv_select_battery() # disable SOC check in unit-tests [ "$X_SOC_CHECK" = "0" ] && return 0 # shellcheck disable=SC2154 if batdrv_check_soc_gt_stop; then echo_message "Notice: $_bat_str charge is above the stop threshold. Run your laptop"` `" on battery power until the battery is discharged to the stop threshold." fi return 0 } soc_gt_stop_recommendation () { # output recommendation to discharge on battery power if SOC is above stop threshold # global params: $_batteries, $_bm_thresh, $_bd_read # prerequisite: batdrv_init() local bat # disable SOC check in unit-tests [ "$X_SOC_CHECK" = "0" ] && return 0 # shellcheck disable=SC2154 for bat in $_batteries; do # iterate detected batteries batdrv_select_battery "$bat" if batdrv_check_soc_gt_stop; then printf "%s charge is above the stop threshold. Run your laptop on battery power"` `" until the battery is discharged to the stop threshold.\n" "$bat" fi done return 0 } TLP-1.10.1/func.d/40-tlp-func-bay000066400000000000000000000144541517565574500161070ustar00rootroot00000000000000#!/bin/sh # tlp-func-bay - Bay Functions # # Copyright (c) 2026 Thomas Koch and others. # This software is licensed under the GPL v2 or later. # SPDX-License-Identifier: GPL-2.0-or-later # ---------------------------------------------------------------------------- # Constants readonly DOCK_GLOB="/sys/devices/platform/dock.?" readonly BAYSTATEFILE=$RUNDIR/bay_saved # ---------------------------------------------------------------------------- # Functions # --- Drive Bay get_drivebay_device () { # Find generic dock interface for drive bay # rc: 0; retval: $dock # shellcheck disable=SC2086 dock=$(grep -l 'ata_bay' $DOCK_GLOB/type 2> /dev/null) dock=${dock%%/type} if [ ! -d "$dock" ]; then dock="" fi return 0 } check_is_docked() { # check if $dock is docked; # rc: 0 if docked, else 1 local dock_status dock_info_file # return 0 if any sysfs file indicates "docked" for dock_info_file in docked firmware_node/status; do if [ -f "$dock/$dock_info_file" ] && \ read -r dock_status < "$dock/$dock_info_file" 2>/dev/null; then # catch empty $dock_status (safety check, unlikely case) [ "${dock_status:-0}" != "0" ] && return 0 fi done # otherwise assume "not docked" return 1 } poweroff_drivebay () { # power off optical drive in drive bay # $1: PP_PRF, PP_BAL, PP_SAV # $2: 0=conditional+quiet mode, 1=force+verbose mode # Some code adapted from https://www.thinkwiki.org/wiki/How_to_hotswap_UltraBay_devices local pwr optdrv syspath case "$1" in "$PP_PRF") pwr="$BAY_POWEROFF_ON_AC" ;; "$PP_BAL"|"$PP_SAV") pwr="$BAY_POWEROFF_ON_BAT" ;; esac # Run only if forced or enabled if [ "$2" != "1" ]; then case "$pwr" in 1) # enabled --> proceed ;; 0) # disabled echo_debug "pm" "poweroff_drivebay($1).disabled" return 0 ;; *) # not configured or invalid parameter echo_debug "pm" "poweroff_drivebay($1).not_configured" return 0 ;; esac fi get_drivebay_device if [ -z "$dock" ] || [ ! -d "$dock" ]; then echo_debug "pm" "poweroff_drivebay($1).no_bay_device" [ "$2" = "1" ] && cecho "Error: cannot locate bay device." 1>&2 return 1 fi echo_debug "pm" "poweroff_drivebay($1): dock=$dock" # Check if bay is occupied if ! check_is_docked; then echo_debug "pm" "poweroff_drivebay($1).drive_already_off" [ "$2" = "1" ] && echo "No drive in bay (or power already off)." else # Check for optical drive optdrv="$BAY_DEVICE" if [ -z "$optdrv" ]; then echo_debug "pm" "poweroff_drivebay($1).opt_drive_not_configured" [ "$2" = "1" ] && cecho "Error: no optical drive configured (BAY_DEVICE=\"\")." 1>&2 return 1 elif [ ! -b "/dev/$optdrv" ]; then echo_debug "pm" "poweroff_drivebay($1).no_opt_drive: /dev/$optdrv" [ "$2" = "1" ] && echo "No optical drive in bay (/dev/$optdrv)." return 0 else echo_debug "pm" "poweroff_drivebay($1): optdrv=$optdrv" [ "$2" = "1" ] && printf "Powering off drive bay..." # Unmount media umount -l "$optdrv" > /dev/null 2>&1 # Sync drive sync sleep 1 # Power off drive $HDPARM -Y "$optdrv" > /dev/null 2>&1 sleep 5 # Unregister scsi device if syspath="$($UDEVADM info --query=path --name="$optdrv" 2> /dev/null)"; then syspath="/sys${syspath%/block/*}" if [ "$syspath" != "/sys" ]; then write_sysf "1" "$syspath/delete" echo_debug "pm" "poweroff_drivebay($1): syspath=$syspath; rc=$?" else echo_debug "pm" "poweroff_drivebay($1): got empty/invalid syspath for $optdrv" fi else echo_debug "pm" "poweroff_drivebay($1): failed to get syspath (udevadm returned $?)" fi # Turn power off write_sysf "1" "$dock/undock" echo_debug "pm" "poweroff_drivebay($1).bay_powered_off: rc=$?" [ "$2" = "1" ] && echo "done." fi fi return 0 } suspend_drivebay () { # Save power state of drive bay before suspend # $1: PP_PRF, PP_BAL, PP_SAV if { [ "$1" = "$PP_BAL" ] || [ "$1" = "$PP_SAV" ]; } && [ "$BAY_POWEROFF_ON_BAT" = "1" ] || \ [ "$1" = "$PP_PRF" ] && [ "$BAY_POWEROFF_ON_AC" = "1" ]; then # setting corresponding to mode is active -> save state get_drivebay_device if [ -n "$dock" ]; then create_rundir if ! check_is_docked; then write_sysf "off" "$BAYSTATEFILE" echo_debug "pm" "suspend_drivebay($1): bay=off; rc=$?" else write_sysf "on" "$BAYSTATEFILE" echo_debug "pm" "suspend_drivebay($1): bay=on; rc=$?" fi fi else # setting not active -> remove state file rm -f "$BAYSTATEFILE" 2> /dev/null fi return 0 } resume_drivebay () { # # $1: 0=ac mode, 1=battery mode local cnt rc if [ "$(read_sysf "$BAYSTATEFILE")" = "off" ]; then # saved state = off get_drivebay_device if [ -n "$dock" ]; then if check_is_docked; then # device active -> deactivate if [ -e "$dock/undock" ]; then cnt=5 rc=1 until [ $rc = 0 ] || [ $cnt = 0 ]; do cnt=$((cnt - 1)) { printf '%s\n' "1" > "$dock/undock"; } 2> /dev/null rc=$? [ $rc = 0 ] || sleep 0.5 done echo_debug "pm" "resume_drivebay.bay_off: rc=$rc" fi else echo_debug "pm" "resume_drivebay.already_off" fi fi else # No saved state or state != off --> apply settings poweroff_drivebay "$1" 0 fi rm -f "$BAYSTATEFILE" 2> /dev/null return 0 } TLP-1.10.1/func.d/45-tlp-func-gpu000066400000000000000000000431341517565574500161310ustar00rootroot00000000000000#!/bin/sh # tlp-func-gpu - Intel GPU Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base # ---------------------------------------------------------------------------- # Constants readonly BASE_MODD=/sys/module readonly BASE_DRMD=/sys/class/drm readonly BASE_DEBUGD=/sys/kernel/debug/dri # Intel i915 driver readonly IGPU_MIN_FREQ=gt_min_freq_mhz readonly IGPU_MAX_FREQ=gt_max_freq_mhz readonly IGPU_BOOST_FREQ=gt_boost_freq_mhz # shellcheck disable=SC2034 readonly IGPU_RPN_FREQ=gt_RPn_freq_mhz # shellcheck disable=SC2034 readonly IGPU_RP0_FREQ=gt_RP0_freq_mhz # Intel xe driver readonly IGPU_XE_MIN_FREQ=min_freq readonly IGPU_XE_MAX_FREQ=max_freq # shellcheck disable=SC2034 readonly IGPU_XE_RPN_FREQ=rpn_freq # shellcheck disable=SC2034 readonly IGPU_XE_RP0_FREQ=rp0_freq # ---------------------------------------------------------------------------- # Functions get_gpu_driver_parms () { # determine GPU driver and parameter locations # $1: drm card dir # retval: $_gpu_driver: kernel driver # $_gpu_freqs frequency control dir (list) - Intel only # $_gpu_parm: parameter sysdir - Intel only # $_gpu_dbg: debug parameter sysdir - Intel only local carddir="$1" local gtfd _gpu_driver="$(readlink "${carddir}/device/driver")" _gpu_driver="${_gpu_driver##*/}" _gpu_freqs="" _gpu_parm="" _gpu_dbg="" case "$_gpu_driver" in i915*) # Intel GPU _gpu_freqs="$carddir" _gpu_parm=${BASE_MODD}/${_gpu_driver}/parameters _gpu_dbg=${BASE_DEBUGD}/${carddir##"${BASE_DRMD}/card"} ;; xe) # Intel GPU: new driver (Tiger Lake integrated graphics and newer, or discrete graphics card) # iterate and concat all GT instances to cover multi-tile devices # see: https://docs.kernel.org/gpu/xe/xe_tile.html for gtfd in "$carddir"/device/tile*/gt*/freq*; do [ -d "$gtfd" ] || break _gpu_freqs="${_gpu_freqs}${_gpu_freqs:+ }$gtfd" done ;; esac # driver echo_debug "pm" "get_gpu_driver_parms: card=$carddir; driver=$_gpu_driver; freqs=$_gpu_freqs; parm=$_gpu_parm; dbg=$_gpu_dbg" return 0 } # --- Intel GPU set_intel_gpu_power_profile () { # set gpu frequency limits # $1: PP_PRF, PP_BAL, PP_SAV # rc: 0=ok/1=parameter error local profile ec gtfd gtnum rc local gdone=0 # 1=gpu present case "$1" in "$PP_PRF") profile="${INTEL_GPU_POWER_PROFILE_ON_AC:-}" ;; "$PP_BAL") profile="${INTEL_GPU_POWER_PROFILE_ON_BAT:-}" ;; "$PP_SAV") profile="${INTEL_GPU_POWER_PROFILE_ON_SAV:-$INTEL_GPU_POWER_PROFILE_ON_BAT}" ;; esac if [ -z "$profile" ]; then echo_debug "pm" "set_intel_gpu_power_profile($1).not_configured" return 0 fi for gpu in "${BASE_DRMD}"/card?; do [ -d "$gpu" ] || break get_gpu_driver_parms "$gpu" case "$_gpu_driver" in xe) # Intel XE GPU # iterate GT instances to cover multi-tile devices gtnum=0 ec=0 for gtfd in $_gpu_freqs; do if [ -f "$gtfd/power_profile" ]; then gtnum=$((gtnum + 1)) if ! write_sysf "$profile" "$gtfd/power_profile"; then echo_debug "pm" "set_intel_gpu_power_profile($1).write_error: gt=$gtfd profile=$profile; rc=$?" ec=$((ec + 1)) fi gdone=1 fi done echo_debug "pm" "set_intel_gpu_power_profile($1): gpu=$gpu profile=$profile; gtnum=$gtnum; ec=$ec" ;; esac done # card if [ $gdone -eq 0 ]; then echo_debug "pm" "set_intel_gpu_power_profile($1).no_gpu" fi return 0 } set_intel_gpu_min_max_boost_freq () { # set gpu frequency limits # $1: PP_PRF, PP_BAL, PP_SAV # rc: 0=ok/1=parameter error local new_min new_max new_boost local old_min old_max old_boost gpu_min gpu_max local gtfd gtnum suffix case "$1" in "$PP_PRF") new_min="${INTEL_GPU_MIN_FREQ_ON_AC:-}" new_max="${INTEL_GPU_MAX_FREQ_ON_AC:-}" new_boost="${INTEL_GPU_BOOST_FREQ_ON_AC:-}" suffix="AC" ;; "$PP_BAL") new_min="${INTEL_GPU_MIN_FREQ_ON_BAT:-}" new_max="${INTEL_GPU_MAX_FREQ_ON_BAT:-}" new_boost="${INTEL_GPU_BOOST_FREQ_ON_BAT:-}" suffix="BAT" ;; "$PP_SAV") new_min="${INTEL_GPU_MIN_FREQ_ON_SAV:-$INTEL_GPU_MIN_FREQ_ON_BAT}" new_max="${INTEL_GPU_MAX_FREQ_ON_SAV:-$INTEL_GPU_MAX_FREQ_ON_BAT}" new_boost="${INTEL_GPU_BOOST_FREQ_ON_SAV:-$INTEL_GPU_BOOST_FREQ_ON_BAT}" suffix="SAV" ;; esac if [ -z "$new_min" ] && [ -z "$new_max" ] && [ -z "$new_boost" ]; then echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).not_configured" return 0 fi for gpu in "${BASE_DRMD}"/card?; do [ -d "$gpu" ] || break get_gpu_driver_parms "$gpu" case "$_gpu_driver" in i915*) # Intel GPU # shellcheck disable=SC2034 if old_min=$(read_sysf "$_gpu_freqs/$IGPU_MIN_FREQ") \ && old_max=$(read_sysf "$_gpu_freqs/$IGPU_MAX_FREQ") \ && old_boost=$(read_sysf "$_gpu_freqs/$IGPU_BOOST_FREQ") \ && gpu_min=$(read_sysf "$_gpu_freqs/$IGPU_RPN_FREQ") \ && gpu_max=$(read_sysf "$_gpu_freqs/$IGPU_RP0_FREQ"); then # frequencies actually readable, check new ones against hardware limits and boundary conditions if ! is_uint "$new_min" 5 || [ "$new_min" -lt "$gpu_min" ] || [ "$new_min" -gt "$gpu_max" ]; then echo_message "Error in configuration at INTEL_GPU_MIN_FREQ_ON_${suffix}=\"${new_min}\": frequency invalid or out of range (see 'tlp-stat -g')." echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).invalid: gpu=$gpu min=$new_min gpu_min=$gpu_min hw_max=$gpu_max; rc=1" return 1 elif ! is_uint "$new_max" 5 || [ "$new_max" -lt "$gpu_min" ] || [ "$new_max" -gt "$gpu_max" ]; then echo_message "Error in configuration at INTEL_GPU_MAX_FREQ_ON_${suffix}=\"${new_max}\": frequency invalid or out of range (see 'tlp-stat -g')." echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).invalid: gpu=$gpu min=$new_min gpu_min=$gpu_min gpu_max=$gpu_max; rc=1" return 1 elif ! is_uint "$new_boost" 5 || [ "$new_boost" -lt "$gpu_min" ] || [ "$new_boost" -gt "$gpu_max" ]; then echo_message "Error in configuration at INTEL_GPU_BOOST_FREQ_ON_${suffix}=\"${new_boost}\": frequency invalid or out of range (see 'tlp-stat -g')." echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).invalid: gpu=$gpu boost=$new_boost gpu_min=$gpu_min gpu_max=$gpu_max; rc=1" return 1 elif [ "$new_min" -gt "$new_max" ]; then echo_message "Error in configuration: INTEL_GPU_MIN_FREQ_ON_${suffix} > INTEL_GPU_MAX_FREQ_ON_${suffix}." echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).min_gt_max: gpu=$gpu min=$new_min max=$new_max; rc=1" return 1 elif [ "$new_max" -gt "$new_boost" ]; then echo_message "Error in configuration: INTEL_GPU_MAX_FREQ_ON_${suffix} > INTEL_GPU_BOOST_FREQ_ON_${suffix}." echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).max_gt_boost: gpu=$gpu max=$new_max boost=$new_boost; rc=1" return 1 fi # all parameters valid --> write min, max in proper sequence if [ "$new_min" -gt "$old_max" ]; then write_sysf "$new_max" "$_gpu_freqs/$IGPU_MAX_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).max: gpu=$gpu freq=$new_max; rc=$?" write_sysf "$new_min" "$_gpu_freqs/$IGPU_MIN_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).min: gpu=$gpu freq=$new_min; rc=$?" else write_sysf "$new_min" "$_gpu_freqs/$IGPU_MIN_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).min: gpu=$gpu freq=$new_min; rc=$?" write_sysf "$new_max" "$_gpu_freqs/$IGPU_MAX_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).max: gpu=$gpu freq=$new_max; rc=$?" fi write_sysf "$new_boost" "$_gpu_freqs/$IGPU_BOOST_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).boost: gpu=$gpu freq=$new_boost; rc=$?" else echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).not_available: gpu=$gpu" fi ;; # i915 xe) # Intel XE GPU # iterate GT instances to cover multi-tile devices gtnum=0 for gtfd in $_gpu_freqs; do [ -d "$gtfd" ] || break gtnum=$((gtnum + 1)) # shellcheck disable=SC2034 if [ "$gtnum" -eq 1 ] \ && old_min=$(read_sysf "$gtfd/$IGPU_XE_MIN_FREQ") \ && old_max=$(read_sysf "$gtfd/$IGPU_XE_MAX_FREQ") \ && gpu_min=$(read_sysf "$gtfd/$IGPU_XE_RPN_FREQ") \ && gpu_max=$(read_sysf "$gtfd/$IGPU_XE_RP0_FREQ"); then # validate parameter on the first GT only if ! is_uint "$new_min" 5 || [ "$new_min" -lt "$gpu_min" ] || [ "$new_min" -gt "$gpu_max" ]; then echo_message "Error in configuration at INTEL_GPU_MIN_FREQ_ON_${suffix}=\"${new_min}\": frequency invalid or out of range (see 'tlp-stat -g')." echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).invalid: gpu=$gpu min=$new_min gpu_min=$gpu_min hw_max=$gpu_max; rc=1" return 1 elif ! is_uint "$new_max" 5 || [ "$new_max" -lt "$gpu_min" ] || [ "$new_max" -gt "$gpu_max" ]; then echo_message "Error in configuration at INTEL_GPU_MAX_FREQ_ON_${suffix}=\"${new_max}\": frequency invalid or out of range (see 'tlp-stat -g')." echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).invalid: gpu=$gpu min=$new_min gpu_min=$gpu_min gpu_max=$gpu_max; rc=1" return 1 elif [ "$new_min" -gt "$new_max" ]; then echo_message "Error in configuration: INTEL_GPU_MIN_FREQ_ON_${suffix} > INTEL_GPU_MAX_FREQ_ON_${suffix}." echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).min_gt_max: gpu=$gpu min=$new_min max=$new_max; rc=1" return 1 fi fi # validate # all parameters valid --> write min, max in proper sequence (for all GTs) if [ "$new_min" -gt "$old_max" ]; then write_sysf "$new_max" "$gtfd/$IGPU_XE_MAX_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).max: gpu=$gpu freq=$new_max; rc=$?" write_sysf "$new_min" "$gtfd/$IGPU_XE_MIN_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).min: gpu=$gpu freq=$new_min; rc=$?" else write_sysf "$new_min" "$gtfd/$IGPU_XE_MIN_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).min: gpu=$gpu freq=$new_min; rc=$?" write_sysf "$new_max" "$gtfd/$IGPU_XE_MAX_FREQ" echo_debug "pm" "set_intel_gpu_min_max_boost_freq($1).max: gpu=$gpu freq=$new_max; rc=$?" fi done ;; # xe esac done # card return 0 } # --- AMD Radeon GPU set_amdgpu_profile () { # set amdgpu/radeon power profile # $1: PP_PRF, PP_BAL, PP_SAV local gpu level pwr rc1 rc2 local sdone=0 # 1=gpu present for gpu in "${BASE_DRMD}"/card?; do [ -d "$gpu" ] || break get_gpu_driver_parms "$gpu" case "$_gpu_driver" in amdgpu) if [ -f "$gpu/device/power_dpm_force_performance_level" ]; then # Use amdgpu dynamic power management method (DPM) case "$1" in "$PP_PRF") level="${RADEON_DPM_PERF_LEVEL_ON_AC:-}" ;; "$PP_BAL") level="${RADEON_DPM_PERF_LEVEL_ON_BAT:-}" ;; "$PP_SAV") level="${RADEON_DPM_PERF_LEVEL_ON_SAV:-$RADEON_DPM_PERF_LEVEL_ON_BAT}" ;; esac if [ -z "$level" ]; then # do nothing if unconfigured echo_debug "pm" "set_amdgpu_profile($1).amdgpu.not_configured: gpu=$gpu" return 0 else write_sysf "$level" "$gpu/device/power_dpm_force_performance_level"; rc1=$? echo_debug "pm" "set_amdgpu_profile($1).amdgpu: gpu=$gpu level=${level}: rc=$rc1" fi sdone=1 fi ;; radeon) if [ -f "$gpu/device/power_dpm_force_performance_level" ] && [ -f "$gpu/device/power_dpm_state" ]; then # Use radeon dynamic power management method (DPM) case "$1" in "$PP_PRF") level="${RADEON_DPM_PERF_LEVEL_ON_AC:-}" pwr="${RADEON_DPM_STATE_ON_AC:-}" ;; "$PP_BAL") level="${RADEON_DPM_PERF_LEVEL_ON_BAT:-}" pwr="${RADEON_DPM_STATE_ON_BAT:-}" ;; "$PP_SAV") level="${RADEON_DPM_PERF_LEVEL_ON_SAV:-$RADEON_DPM_PERF_LEVEL_ON_BAT}" pwr="${RADEON_DPM_STATE_ON_SAV:-$RADEON_DPM_STATE_ON_BAT}" ;; esac if [ -z "$pwr" ] || [ -z "$level" ]; then # do nothing if (partially) unconfigured echo_debug "pm" "set_amdgpu_profile($1).radeon.not_configured: gpu=$gpu" return 0 else write_sysf "$level" "$gpu/device/power_dpm_force_performance_level"; rc1=$? write_sysf "$pwr" "$gpu/device/power_dpm_state"; rc2=$? echo_debug "pm" "set_amdgpu_profile($1).radeon: gpu=$gpu perf=${level}: rc=$rc1; state=${pwr}: rc=$rc2" fi sdone=1 fi ;; esac done if [ $sdone -eq 0 ]; then echo_debug "pm" "set_amdgpu_profile($1).no_gpu" fi return 0 } set_abm_level () { # set amdgpu adaptive backlight modulation (ABM) # $1: PP_PRF, PP_BAL, PP_SAV local card gpu level old_level pps rc local sdone=0 # 1=gpu present for gpu in "${BASE_DRMD}"/card?; do [ -d "$gpu" ] || break get_gpu_driver_parms "$gpu" case "$_gpu_driver" in amdgpu) card="${gpu##/*/}" for pps in "$gpu/${card}-eDP"*; do if [ -f "$pps/amdgpu/panel_power_savings" ]; then case "$1" in "$PP_PRF") level="${AMDGPU_ABM_LEVEL_ON_AC:-}" ;; "$PP_BAL") level="${AMDGPU_ABM_LEVEL_ON_BAT:-}" ;; "$PP_SAV") level="${AMDGPU_ABM_LEVEL_ON_SAV:-$AMDGPU_ABM_LEVEL_ON_BAT}" ;; esac if [ -z "$level" ]; then # do nothing if unconfigured echo_debug "pm" "set_abm_level($1).amdgpu.not_configured: gpu=$gpu" return 0 fi old_level="$(read_sysf "$pps/amdgpu/panel_power_savings")" if [ "$level" = "$old_level" ]; then # level does not change -> do not apply to prevent screen flicker echo_debug "pm" "set_abm_level($1).amdgpu.no_change: pps=$pps level=${level} old_level=${old_level}" return 0 elif check_ppd_running ; then # don't apply ABM when power-profiles-daemon is running echo_message "Warning: AMDGPU_ABM_LEVEL_ON_AC/BAT is not set because power-profiles-daemon is running." echo_debug "pm" "set_abm_level($1).amdgpu.nop_ppd_active" return 0 else write_sysf "$level" "$pps/amdgpu/panel_power_savings"; rc=$? echo_debug "pm" "set_abm_level($1).amdgpu: pps=$pps level=${level}: rc=$rc" fi sdone=1 fi done ;; esac done if [ $sdone -eq 0 ]; then echo_debug "pm" "set_abm_level($1).no_gpu_or_abm" fi return 0 } TLP-1.10.1/func.d/tlp-func-stat000066400000000000000000001105361517565574500160640ustar00rootroot00000000000000#!/bin/sh # tlp-func-stat - tlp-stat Helper Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Needs: tlp-func-base, 15-tlp-func-disk, 35-tlp-func-batt # ---------------------------------------------------------------------------- # Constants readonly INITCTL=initctl readonly SESTATUS=sestatus readonly SMARTCTL=smartctl readonly RE_ATA_ERROR='ata[0-9]+: SError: {.*CommWake }' # ---------------------------------------------------------------------------- # Functions # --- Service checks check_upstart () { # check if upstart is active init system (PID 1) # rc: 0=yes, 1=no cmd_exists $INITCTL && $INITCTL --version | grep -q upstart } check_openrc () { # check if openrc is the active init system (PID 1) # rc: 0=yes, 1=no [ -e /run/openrc/softlevel ] } check_rdw_installed () { # check if tlp-rdw is installed cmd_exists "$TLPRDW" } check_tlp_pd_running () { # check if power-profiles-daemon is running # rc: 0=yes, 1=no # shellcheck disable=SC2009 ps aux 2> /dev/null | grep -v "grep" | grep -q -E "/python.+$TLPPD" } check_ppd_service () { # check if power-profiles-daemon.service is active # rc: 0=yes, 1=no check_service_state "$PPD_SERVICE" active } check_tuned_ppd_running () { # check if power-profiles-daemon is running # rc: 0=yes, 1=no # shellcheck disable=SC2009 ps aux 2> /dev/null | grep -v "grep" | grep -q "$TUNED" } # --- Formatted Output printparm () { # formatted output of sysfile - general # $1: printf format string # $2: sysfile # $3: n/a message, "_"=no output # $4: cutoff i.e. string to be removed from the end # $5: 1=trim leading and trailing whitespace local format="$1" local sysf="$2" local namsg="$3" local cutoff="$4" local trim="${5:-0}" local val="" if val=$(read_sysf "$sysf"); then # sysfile read successful if [ "$trim" = "1" ]; then val="$(trim "$val")" fi if [ -n "$cutoff" ]; then val="${val%"$cutoff"}" fi fi if [ -z "$val" ]; then # replace empty value with n/a text if [ -n "$namsg" ]; then if [ "$namsg" != "_" ]; then # use specific n/a text format="$(echo "$format" | sed -r -e "s/##(.*)##/($namsg)/" -e "s/\[.*\]//")" else # _ = skip sysf="" fi else # empty n/a text, use default text format="$(echo "$format" | sed -r -e "s/##(.*)##/(not available)/" -e "s/\[.*\]//")" fi # output n/a text or skip # shellcheck disable=SC2059 [ -n "$sysf" ] && printf "$format\n" "$sysf" else # non empty value: strip delimiters from format str format="$(echo "$format" | sed -r "s/##(.*)##/\1/")" # shellcheck disable=SC2059 printf "$format\n" "$sysf" "$val" fi return 0 } printparm_epb () { # formatted output of sysfile - Intel EPB variant # $1: sysfile local val strval if val=$(read_sysf "$1"); then # sysfile exists and is actually readable, output content printf "%-54s = %2d " "$1" "$val" # Convert distinct values to strings strval=$(echo "$val" | sed -r 's/^0/performance/; s/^4/balance_performance/; s/^6/default/; s/^8/balance_power/; s/^15/power/; s/[0-9]+//') if [ -n "$strval" ]; then printf "(%s) [EPB]\n" "$strval" else printf " [EPB]\n" fi else # sysfile was not readable printf "%-54s = (not available) [EPB]\n" "$1" fi return 0 } printparm_ml () { # indented output of a multiline sysfile # $1: indent str # $2: sysfile # $3: n/a message, ""=no output local ind="$1" local sysf="$2" local namsg="$3" local sline if [ -f "$sysf" ]; then printf "%s:\n" "$sysf" # read and output sysfile line by line # shellcheck disable=SC2162 while read -r sline; do printf "%s%s\n" "$ind" "$sline" done < "$sysf" printf "\n" elif [ -n "$namsg" ]; then printf "%s (%s)\n\n" "$sysf" "$namsg" fi } print_sysf () { # formatted output of a sysfile # $1: format; $2: sysfile local val if val=$(read_sysf "$2"); then # sysfile readable # shellcheck disable=SC2059 printf "$1" "$val" else # sysfile not readable # shellcheck disable=SC2059 printf "$1" "(not available)" fi return 0 } print_sysf_trim () { # formatted output of a sysfile, trim leading and trailing # blanks -- $1: format; $2: sysfile local val if val=$(read_sysf "$2"); then # sysfile readable # shellcheck disable=SC2059 printf "$1" "$(printf "%s" "$val" | sed -r 's/^[[:blank:]]*//;s/[[:blank:]]*$//')" else # sysfile not readable # shellcheck disable=SC2059 printf "$1" "(not available)" fi return 0 } print_file_modtime_and_age () { # show a file's last modification time # and age in secs -- $1: file local mtime age if [ -f "$1" ]; then mtime=$(date +%X -r "$1") age=$(( $(date +%s) - $(date +%s -r "$1") )) printf '%s, %d sec(s) ago' "$mtime" "$age" else printf "unknown" fi } print_saved_profile () { # read and print saved profile read_saved_power_profile # shellcheck disable=SC2154 printf "%s" "$(pp2strx "$_pp_last")" # shellcheck disable=SC2154 if get_manual_mode > /dev/null; then printf " (manual)\n" elif [ "$_pp_def_mem" = "$_pp_last" ]; then printf " (default)\n" else printf "\n" fi return 0 } print_selinux () { # print SELinux status and mode if cmd_exists $SESTATUS; then $SESTATUS | awk -F '[ \t\n]+' '/SELinux status:/ { printf "SELinux status = %s", $3 } ; \ /Current mode:/ { printf " (%s)", $3 }' printf "\n" fi } # --- Storage Devices print_disk_model () { # print disk model -- $1: dev local model vendor model=$($HDPARM -I "/dev/$1" 2> /dev/null | grep 'Model Number' | \ cut -f2 -d: | sed -r 's/^ *//' ) if [ -z "$model" ]; then # hdparm -I not supported --> try udevadm approach vendor="$($UDEVADM info -q property "/dev/$1" 2>/dev/null | sed -n 's/^ID_VENDOR=//p')" model="$( $UDEVADM info -q property "/dev/$1" 2>/dev/null | sed -n 's/^ID_MODEL=//p' )" model=$(printf "%s %s" "$vendor" "$model" | sed -r 's/_/ /g; s/-//g; s/[[:space:]]+$//') fi printf '%s\n' "${model:-unknown}" return 0 } print_disk_firmware () { # print firmware version --- $1: dev local firmware firmware=$($HDPARM -I "/dev/$1" 2> /dev/null | grep 'Firmware Revision' | \ cut -f2 -d: | sed -r 's/^ *//' ) printf '%s\n' "${firmware:-unknown}" return 0 } get_disk_state () { # get disk power state -- $1: dev; retval: $_disk_state _disk_state=$($HDPARM -C "/dev/$1" 2> /dev/null | awk -F ':' '/drive state is/ { gsub(/ /,"",$2); print $2; }') [ -z "$_disk_state" ] && _disk_state="(not available)" return 0 } get_disk_apm_level () { # get disk apm level -- $1: dev; rc: apm local apm apm=$($HDPARM -I "/dev/$1" 2> /dev/null | grep 'Advanced power management level' | \ cut -f2 -d: | grep -E '^ *[0-9]+ *$') if [ -n "$apm" ]; then return "$apm" else return 0 fi } get_disk_trim_capability () { # check for trim capability # $1: dev; rc: 0=no, 1=yes, 254=no ssd device local trim if $HDPARM -I "/dev/$1" 2> /dev/null | grep -q 'Solid State Device'; then if $HDPARM -I "/dev/$1" 2> /dev/null | grep -q 'TRIM supported'; then trim=1 else trim=0 fi else trim=255 fi return $trim } check_ata_errors () { # check kernel log for ata errors # (possibly) caused by SATA_LINKPWR_ON_AC/BAT != max_performance # stdout: error count for lpw in $SATA_LINKPWR_ON_BAT $SATA_LINKPWR_ON_AC; do if wordinlist "$lpw" "min_power med_power_with_dipm medium_power"; then # config values != max_performance exist --> check kernel log # count matching error lines and quit dmesg | grep -E -c "${RE_ATA_ERROR}" 2> /dev/null return 0 fi done # no values in question configured echo "0" return 0 } get_ahci_host () { # get host associated with a disk # $1: device # retval: $_ahci_host # /sys/block/$device is a softlink to # ../devices/pci0000:00/0000:00:XY.Z/ataN/.../$device # which reveals the associated ahci host: 0000:00:XY.Z/ataN/hostM _ahci_host="$(readlink "/sys/block/$1" | sed -r 's/^\.\.\/devices\/pci[0-9:]+\/[0-9a-f:.]+\/ata[0-9]+\/(host[0-9]+).*$/\1/')" if [ -n "$_ahci_host" ]; then echo_debug "disk" "get_ahci_host($1): host=$_ahci_host" return 0 else echo_debug "disk" "get_ahci_host($1).none" return 1 fi } print_nvme_temp () { # print NVMe disk temperature from hwmon API # $1: device # # Reference: # - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=400b6a7b13a3fd71cff087139ce45dd1e5fff444 local sens ts # temp1_input is "Composite" for sens in $(glob_files '/hwmon*/temp1_input' "/sys/block/$1/device"); do if ts=$(read_sysval "$sens"); then perl -e 'printf (" Temp = %-2.0f °C\n", '"$ts"' / 1000.0);' break fi done return 0 } anonymize_disk_ids () { # replace disk serial number with asterisks # $*: space separated list of disk ids while [ $# -gt 0 ]; do echo "$1" | awk '{ s = $1 if (s ~ /_/) { i = length(s) while (substr(s, i, 1) != "_" && i > 0) { s = substr(s, 1, i-1) "*" substr(s, i+1) i-- } } else if (s ~ /^nvme-nvme\./) { for (i = 1; i <= length(s); i++) { if (i > 24 && substr(s, i, 1) != "-") { s = substr(s, 1, i-1) "*" substr(s, i+1) } } } printf("%s", s) }' shift [ $# -gt 0 ] && printf " " done } show_disk_data () { # formatted output of NVMe / SATA disk data # $1: disk device # translate disk name and check presence if ! get_disk_dev "$1"; then # no block device for disk name --> we're done # shellcheck disable=SC2154 printf "\n%s: not present.\n" "/dev/$_disk_dev" return 1 fi # --- show general data # shellcheck disable=SC2154 case "$_disk_type" in nvme) # NVMe disk printf "\n%s:\n" "/dev/$_disk_dev" printf " Type = NVMe\n" [ -n "$_disk_id" ] && printf " Disk ID = %s\n" "$(anonymize_disk_ids "$_disk_id")" print_sysf " Model = %s\n" "/sys/block/$_disk_dev/device/model" print_sysf " Firmware = %s\n" "/sys/block/$_disk_dev/device/firmware_rev" print_nvme_temp "$_disk_dev" ;; sata|ata|usb|ieee1394) # ATA/USB/IEEE1394 disk printf "\n%s:\n" "/dev/$_disk_dev" printf " Type = %s\n" "$(toupper "$_disk_type")" [ -n "$_disk_id" ] && printf " Disk ID = %s\n" "$(anonymize_disk_ids "$_disk_id")" # save spindle state get_disk_state "$_disk_dev" printf " Model = " print_disk_model "$_disk_dev" printf " Firmware = " print_disk_firmware "$_disk_dev" get_disk_apm_level "$_disk_dev"; local apm=$? printf " APM Level = " case $apm in 0|255) printf "none/disabled\n" ;; *) printf "%s" $apm if wordinlist "$_disk_type" "$DISK_TYPES_NO_APM_CHANGE"; then printf " (changes not supported)\n" else printf "\n" fi ;; esac printf " Status = %s\n" "$_disk_state" get_disk_trim_capability "$_disk_dev"; local trim=$? case $trim in 0) printf " TRIM = not supported\n" ;; 1) printf " TRIM = supported\n" ;; esac if [ "$_disk_type" = "sata" ] || [ "$_disk_type" = "ata" ]; then get_ahci_host "$_disk_dev" && printf " Host = %s\n" "$_ahci_host" fi # restore standby state [ "$_disk_state" = "standby" ] && spindown_disk "$_disk_dev" ;; *) printf "\n%s: Device type \"%s\" ignored.\n" "/dev/$_disk_dev" "$_disk_type" return 1 ;; esac if [ -f "/sys/block/$_disk_dev/queue/scheduler" ]; then # shellcheck disable=SC2154 if [ "$_disk_mq" = "1" ]; then print_sysf_trim " Scheduler = %s (multi queue)\n" "/sys/block/$_disk_dev/queue/scheduler" else print_sysf_trim " Scheduler = %s (single queue)\n" "/sys/block/$_disk_dev/queue/scheduler" fi fi # shellcheck disable=SC2154 if [ "$_disk_runpm" != "3" ]; then # disk has runtime pm capability echo # shellcheck disable=SC2154 case "$_disk_runpm" in 0) printf " Runtime PM:\n";; 1) printf " Runtime PM: locked by kernel\n";; 2) printf " Runtime PM: locked by TLP\n" ;; esac print_sysf " /sys/block/$_disk_dev/device/power/control = %s, " "/sys/block/$_disk_dev/device/power/control" print_sysf "autosuspend_delay_ms = %s\n" "/sys/block/$_disk_dev/device/power/autosuspend_delay_ms" fi # --- show SMART data # skip if smartctl not installed or disk not SMART capable cmd_exists "$SMARTCTL" && $SMARTCTL "/dev/$_disk_dev" > /dev/null 2>&1 || return 0 case "$_disk_type" in nvme) # NVMe disk printf "\n SMART info:\n" $SMARTCTL -A "/dev/$_disk_dev" | \ grep -E -e '^(Critical Warning|Temperature:|Available Spare)' \ -e '^(Percentage Used:|Data Units Written:|Power|Unsafe)' \ -e 'Integrity Errors' | \ sed 's/^/ /' ;; sata|ata|usb) printf "\n SMART info:\n" $SMARTCTL -A "/dev/$_disk_dev" | grep -v '<==' | \ awk -F ' ' '$2 ~ /Power_Cycle_Count|Start_Stop_Count|Load_Cycle_Count|Reallocated_Sector_Ct/ \ { printf " %3d %-25s = %8d \n", $1, $2, $10 } ; \ $2 ~ /Used_Rsvd_Blk_Cnt_Chip|Used_Rsvd_Blk_Cnt_Tot|Unused_Rsvd_Blk_Cnt_Tot/ \ { printf " %3d %-25s = %8d \n", $1, $2, $10 } ; \ $2 ~ /Power_On_Hours/ \ { printf " %3d %-25s = %8d %s\n", $1, $2, $10, "[h]" } ; \ $2 ~ /Temperature_Celsius/ \ { printf " %3d %-25s = %8d %s %s %s %s\n", $1, $2, $10, $11, $12, $13, "[°C]" } ; \ $2 ~ /Airflow_Temperature_Cel/ \ { printf " %3d %-25s = %8d %s\n", $1, $2, $10, "[°C]" } ; \ $2 ~ /G-Sense_Error_Rate/ \ { printf " %3d %-25s = %8d \n", $1, $2, $10 } ; \ $2 ~ /Host_Writes/ \ { printf " %3d %-25s = %8.3f %s\n", $1, $2, $10 / 32768.0, "[TB]" } ; \ $2 ~ /Total_LBAs_Written/ \ { printf " %3d %-25s = %8.3f %s\n", $1, $2, $10 / 2147483648.0, "[TB]" } ; \ $2 ~ /NAND_Writes_1GiB/ \ { printf " %3d %-25s = %8d %s\n", $1, $2, $10, "[GB]" } ; \ $2 ~ /Available_Reservd_Space|Media_Wearout_Indicator|Wear_Leveling_Count/ \ { printf " %3d %-25s = %8d %s\n", $1, $2, $4, "[%]" }' ;; *) # unknown disk type ;; esac return 0 } get_ahci_disk () { # get disk associated with an alpm or ahci port runtime pm sysfile # $1: sysfile # retval: $_ahci_disk local aport # cut sysfile path down to the ahci port /sys/bus/pci/devices/0000:00:XY.Z/ataN aport="$(echo "$1" | sed -r 's/^(\/sys\/bus\/pci\/devices\/[0-9a-f:.]+\/ata[0-9]+).*$/\1/')" # the directory /sys/bus/pci/devices/0000:00:XY.Z/ataN/host*/target*/*/block # lists the actual block device name pointing to /dev/sdX resp. /sys/block/sdX # shellcheck disable=SC2086 _ahci_disk="$(glob_dirs '/*' ${aport}/host*/target*/*/block 2> /dev/null | head -1)" _ahci_disk="${_ahci_disk##/*/}" if [ -n "$_ahci_disk" ]; then echo_debug "disk" "get_ahci_disk($1): port=$aport ahci_disk=$_ahci_disk" return 0 else echo_debug "disk" "get_ahci_disk($1).none" return 1 fi } printparm_ahci () { # print alpm or ahci port runtime pm sysfile # accompanied by the attached disk device # $1: sysfile local val if val=$(read_sysf "$1"); then # sysfile exists and is actually readable, output content printf "%-56s = %s " "$1" "$val" get_ahci_disk "$1" if [ -n "$_ahci_disk" ]; then printf " -- %s\n" "$_ahci_disk" else printf "\n" fi fi return 0 } # --- Graphics printparm_i915 () { # formatted output of i915 parameters interpreting special values # $1: sysfile local sysf="$1" val if val=$(read_sysf "$sysf"); then # sysfile exists and is actually readable, output content printf "%-44s = %2d " "$sysf" "$val" # explain content if [ "$val" = "-1" ]; then printf "(use per-chip default)\n" else printf "(" if [ "${sysf##/*/}" = "enable_psr" ]; then # enable_psr case $val in 0) printf "disabled" ;; 1) printf "enabled" ;; 2) printf "force link-standby mode" ;; 3) printf "force link-off mode" ;; *) printf "unknown" ;; esac else # other parms if [ $((val & 1)) -ne 0 ]; then printf "enabled" else printf "disabled" fi fi printf ")\n" fi else printf "%-44s = (not available)\n" "$sysf" fi return 0 } printparm_amdgpu_ml () { # output multiline clock readout from sysfile $1 as columns # $1: sysfile # rc: 0=file exists/1=file non-existent local sysf="$1" local out="" local line local rc=0 if [ ! -f "$sysf" ]; then # sysfile nonexistent out="(not available)" rc=1 else # parse sysfile line by line # shellcheck disable=SC2162 while read -r line; do if [ -n "$line" ]; then line=$(printf "%-13s" "$line") out="${out}${out:+ }${line}" fi done < "$sysf" fi if [ -n "$out" ]; then printf "%-43s = %s\n" "$sysf" "$out" fi return $rc } show_gpu_data () { # show GPU data for all drivers # $1: 1=verbose (default: 0) local verbose="${1:-0}" local card clk gpu gtnum lc odir sysout local hdr= for gpu in "${BASE_DRMD}"/card?; do [ -d "$gpu" ] || continue get_gpu_driver_parms "$gpu" # shellcheck disable=SC2154 case "$_gpu_driver" in i915*) # Intel GPU # power management data if [ "$hdr" != "i915" ]; then printf "+++ Intel Graphics\n" hdr="i915" fi printf "%-44s = %s\n" "$gpu/device/driver" "$_gpu_driver" # shellcheck disable=SC2154 printparm_i915 "$gpu/power/rc6_enable" # shellcheck disable=SC2154 if sysout=$(grep '^FBC ' "$_gpu_dbg/i915_fbc_status" 2> /dev/null); then printf "%-44s = %s\n" "$_gpu_dbg/i915_fbc_status" "$sysout" else printparm_i915 "$_gpu_parm/enable_fbc" fi if sysout=$(grep '^PSR mode:' "$_gpu_dbg/i915_edp_psr_status" 2> /dev/null); then printf "%-44s = %s\n" "$_gpu_dbg/i915_edp_psr_status" "$sysout" else printparm_i915 "$_gpu_parm/enable_psr" fi printf "\n" # frequency parameters if readable_sysf "$_gpu_freqs//$IGPU_MIN_FREQ"; then printparm "%-44s = ##%5d## [MHz]" "$_gpu_freqs/$IGPU_MIN_FREQ" printparm "%-44s = ##%5d## [MHz]" "$_gpu_freqs/$IGPU_MAX_FREQ" printparm "%-44s = ##%5d## [MHz]" "$_gpu_freqs/$IGPU_BOOST_FREQ" printparm "%-44s = ##%5d## [MHz] (GPU min)" "$_gpu_freqs/$IGPU_RPN_FREQ" printparm "%-44s = ##%5d## [MHz] (GPU max)" "$_gpu_freqs/$IGPU_RP0_FREQ" printf "\n" fi ;; xe) # Intel GPU: new driver (Tiger Lake integrated graphics and newer, or discrete graphics card) if [ "$hdr" != "xe" ]; then printf "+++ Intel Graphics\n" hdr="xe" fi printf "%-60s = %s\n" "$gpu/device/driver" "$_gpu_driver" printparm "%-60s = ##%s##" "$gpu/device/power_state" # iterate GT instances to cover multi-tile devices gtnum="$(echo "$_gpu_freqs" | wc -w)" lc=0 for gtfd in $_gpu_freqs; do printparm "%-60s = ##%s##" "$gtfd/power_profile" printparm "%-60s = ##%6d## [MHz]" "$gtfd/$IGPU_XE_MIN_FREQ" printparm "%-60s = ##%6d## [MHz]" "$gtfd/$IGPU_XE_MAX_FREQ" if [ "$verbose" = "1" ]; then printparm "%-60s = ##%6d## [MHz]" "$gtfd/cur_freq" printparm "%-60s = ##%6d## [MHz]" "$gtfd/act_freq" fi printparm "%-60s = ##%6d## [MHz] (GPU min)" "$gtfd/$IGPU_XE_RPN_FREQ" printparm "%-60s = ##%6d## [MHz] (GPU efficient)" "$gtfd/rpe_freq" printparm "%-60s = ##%6d## [MHz] (GPU max)" "$gtfd/$IGPU_XE_RP0_FREQ" if [ "$verbose" = "1" ]; then printparm "%-60s = ##%6d##" "$gtfd/rpa_freq" fi lc=$((lc + 1)) [ "$verbose" = "1" ] || break # print only first instance if non-verbose done if [ "$lc" -lt "$gtnum" ]; then printf "Remaining GTs omitted for clarity, use -v to show all.\n" fi [ "$lc" -gt 0 ] && printf "\n" ;; amdgpu) # AMD GPU if [ "$hdr" != "amdgpu" ]; then printf "+++ AMD Radeon Graphics\n" hdr="amdgpu" fi printf "%-65s = %s\n" "$gpu/device/driver" "$_gpu_driver" printparm "%-65s = ##%s##" "$gpu/device/power_state" lc=0 if [ -f "$gpu/device/power_dpm_force_performance_level" ]; then printparm "%-65s = ##%s##" "$gpu/device/power_dpm_force_performance_level" lc=1 fi card="${gpu##/*/}" for odir in "$gpu/${card}-eDP"*; do printparm "%-65s = ##%s##" "$odir/amdgpu/panel_power_savings" "not available" lc=1 done if [ "$lc" -eq "1" ]; then printf "\n" fi if [ "$verbose" = "1" ]; then lc=0 printparm "%-43s = ##%s##" "$gpu/device/power_state" "not available" for clk in pp_dpm_dcefclk pp_dpm_fclk pp_dpm_mclk pp_dpm_pcie pp_dpm_sclk pp_dpm_socclk; do printparm_amdgpu_ml "$gpu/device/${clk}" && lc=1 done if [ "$lc" -eq "1" ]; then printf "\n" fi fi ;; radeon) # AMD GPU: legacy driver if [ "$hdr" != "radeon" ]; then printf "+++ AMD Radeon Graphics\n" hdr="radeon" fi printf "%-65s = %s\n" "$gpu/device/driver" "$_gpu_driver" printparm "%-65s = ##%s##" "$gpu/device/power_state" if [ -f "$gpu/device/power_dpm_force_performance_level" ]; then # AMD hardware printparm "%-65s = ##%s##" "$gpu/device/power_dpm_force_performance_level" printparm "%-65s = ##%s##" "$gpu/device/power_dpm_state" printf "\n" elif [ -f "$gpu/device/power_method" ]; then # legacy ATI hardware printparm "%-65s = ##%s##" "$gpu/device/power_method" printparm "%-65s = ##%s##" "$gpu/device/power_profile" printf "\n" fi ;; nouveau|nvidia) # Nvidia GPU: open and proprietary driver if [ "$hdr" != "$_gpu_driver" ]; then printf "+++ Nvidia Graphics\n" hdr="$_gpu_driver" fi printf "%-44s = %s\n" "$gpu/device/driver" "$_gpu_driver" printparm "%-44s = ##%s##" "$gpu/device/power_state" ;; *) # Other GPU driver if [ "$hdr" != "$_gpu_driver" ]; then printf "+++ Other Graphics\n" hdr="$_gpu_driver" fi printf "%-44s = %s\n" "$gpu/device/driver" "$_gpu_driver" printparm "%-44s = ##%s##" "$gpu/device/power_state" ;; esac done return 0 } # --- Battery print_bat_methods_per_driver () { # show features provided by a Thinkpad battery plugin # $1: driver = natacpi, tpacpi, tpsmapi local bm m mlist="" for bm in _bm_read _bm_thresh _bm_dischg; do if [ "$(eval echo \$$bm)" = "$1" ]; then # method matches driver m="" case $bm in _bm_read) [ "$1" = "tpsmapi" ] && m="status" ;; _bm_thresh) m="charge thresholds" ;; _bm_dischg) m="force-discharge" ;; esac if [ -n "$m" ]; then # concat method to output mlist="${mlist}${mlist:+, }${m}" fi fi done if [ -n "$mlist" ]; then printf "%s\n" "$mlist" else printf "(none)\n" fi return 0 } print_bat_make () { # print battery manufacturer and mode name # $1: verbose # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() local verbose="${1:-0}" # shellcheck disable=SC2154 printparm "%-59s = ##%s##" "$_bd_read/manufacturer" printparm "%-59s = ##%s##" "$_bd_read/model_name" if [ "$verbose" = "1" ]; then printparm "%-59s = ##%s##" "$_bd_read/serial_number" "" "" 1 fi return 0 } print_bat_state () { # print battery charging state with # an explanation when a threshold inhibits charging # $1: sysfile # global params: $_bm_thresh, $_syspwr local sysf val # check if bat state sysfile exists if [ -f "$1" ]; then sysf=$1 else # sysfile non-existent printf "%-59s = (not available)\n" "$1" return 0 fi if val=$(read_sysf "$sysf"); then # sysfile was readable, output state # map "Unknown" to "Idle" for clarity (and avoid user questions) [ "$val" = "Unknown" ] && val="Idle" printf "%-59s = %s\n" "$sysf" "$val" else # sysfile was not readable printf "%-59s = (not available)\n" "$sysf" fi return 0 } print_bat_cycle_count () { # print battery cycle count, explain special case of 0 # $1: cycle count sysfile local cc cc="$(read_sysf "$1")" case "$cc" in 0) printf "%-59s = %6d (or not supported)\n" "$1" "$cc" ;; "") printf "%-59s = (not supported)\n" "$1" ;; *) printf "%-59s = %6d\n" "$1" "$cc" ;; esac return 0 } print_bat_energy () { # print and sum up battery energy or charge data # global param: $_bd_read # global vars/retvals: # $_bat_ed - design capacity (mWh or mAh) # $_bat_ef - last fully charged level (mWh or mAh) # $_bat_en - current charge level (mWh or mAh) # $_bat_efsum - accumulated fully charged level # $_bat_ensum - accumulated current charge level # prerequisite: batdrv_init(), batdrv_select_battery() local ed ef en pn pwr volt # initialize accumulation _bat_efsum="${_bat_efsum:-0}" _bat_ensum="${_bat_ensum:-0}" # shellcheck disable=SC2154 if [ -f "$_bd_read/energy_full" ]; then # battery exposes mW(h) Units # store data for battery charge level / capacity calculation _bat_ed="$(perl -e 'printf "%.0f", '"$(read_sysval "$_bd_read/energy_full_design")"' / 1000.0')" _bat_ef="$(perl -e 'printf "%.0f", '"$(read_sysval "$_bd_read/energy_full")"' / 1000.0')" _bat_en="$(perl -e 'printf "%.0f", '"$(read_sysval "$_bd_read/energy_now")"' / 1000.0')" pwr="$(perl -e 'printf "%.0f", '"$(read_sysval "$_bd_read/power_now")"' / 1000.0')" # accumulate data for total charge level calculation _bat_efsum=$((_bat_efsum + _bat_ef)) _bat_ensum=$((_bat_ensum + _bat_en)) printf "%-59s = %6d [mWh]\n" "$_bd_read/energy_full_design" "$_bat_ed" printf "%-59s = %6d [mWh]\n" "$_bd_read/energy_full" "$_bat_ef" printf "%-59s = %6d [mWh]\n" "$_bd_read/energy_now" "$_bat_en" printf "%-59s = %6d [mW]\n" "$_bd_read/power_now" "$pwr" elif [ -f "$_bd_read/charge_full" ]; then # battery exposes mA(h) Units # store data for battery charge level / capacity calculation _bat_ed="$(perl -e 'printf "%.0f", '"$(read_sysval "$_bd_read/charge_full_design")"' / 1000.0')" _bat_ef="$(perl -e 'printf "%.0f", '"$(read_sysval "$_bd_read/charge_full")"' / 1000.0')" _bat_en="$(perl -e 'printf "%.0f", '"$(read_sysval "$_bd_read/charge_now")"' / 1000.0')" # accumulate data for total charge level calculation _bat_efsum=$((_bat_efsum + _bat_ef)) _bat_ensum=$((_bat_ensum + _bat_en)) pwr="$(perl -e 'printf "%.0f", '"$(read_sysval "$_bd_read/current_now")"' / 1000.0')" volt="$(read_sysval "$_bd_read/voltage_now")" if [ "$volt" -gt 0 ]; then # voltage is valid -> display mA(h) and mW(h) units side by side # convert mA(h) to mW(h) units volt="$(perl -e 'printf "%.3f", '"$volt"' / 1000000.0')" ed="$(perl -e 'printf "%.0f\n", '"$_bat_ed"' * '"$volt"'')" ef="$(perl -e 'printf "%.0f\n", '"$_bat_ef"' * '"$volt"'')" en="$(perl -e 'printf "%.0f\n", '"$_bat_en"' * '"$volt"'')" pn="$(perl -e 'printf "%.0f\n", '"$pwr"' * '"$volt"'')" printf "%-59s = %6d [mAh] (%6d mWh)\n" "$_bd_read/charge_full_design" "$_bat_ed" "$ed" printf "%-59s = %6d [mAh] (%6d mWh)\n" "$_bd_read/charge_full" "$_bat_ef" "$ef" printf "%-59s = %6d [mAh] (%6d mWh)\n" "$_bd_read/charge_now" "$_bat_en" "$en" printf "%-59s = %6d [mA] (%6d mW)\n" "$_bd_read/current_now" "$pwr" "$pn" else # voltage is not available or invalid -> display mA(h) units only printf "%-59s = %6d [mAh]\n" "$_bd_read/charge_full_design" "$_bat_ed" printf "%-59s = %6d [mAh]\n" "$_bd_read/charge_full" "$_bat_ef" printf "%-59s = %6d [mAh]\n" "$_bd_read/charge_now" "$_bat_en" printf "%-59s = %6d [mA]\n" "$_bd_read/current_now" "$pwr" fi else _bat_ed=0 _bat_ef=0 _bat_en=0 fi return 0 } print_bat_level () { # print battery charge level (SOC) and capacity in percent # global params: $_bat_ed, $_bat_ef, $_bat_en local lf=0 if [ "$_bat_ef" -ne 0 ]; then perl -e 'printf ("%-59s = %6.1f [%%]\n", "Charge", 100.0 * '"$_bat_en"' / '"$_bat_ef"');' lf=1 fi if [ "$_bat_ed" -ne 0 ]; then perl -e 'printf ("%-59s = %6.1f [%%]\n", "Capacity", 100.0 * '"$_bat_ef"' / '"$_bat_ed"');' lf=1 fi [ "$lf" -gt 0 ] && printf "\n" return 0 } print_bat_level_total () { # print total charge level (SOC) in percent (accumulated over all batteries) # global vars: $_bat_efsum, $_bat_ensum if [ "$_bat_efsum" -ne 0 ]; then # more than one battery detected --> show charge total perl -e 'printf ("%-59s = %6.1f [%%]\n", "+++ Charge total", 100.0 * '"$_bat_ensum"' / '"$_bat_efsum"');' printf "\n" fi return 0 } print_bat_voltages () { # print battery voltages # $1: verbose # global param: $_bd_read # prerequisite: batdrv_init(), batdrv_select_battery() local verbose="${1:-0}" if [ "$verbose" = "1" ]; then printparm "%-59s = ##%6s## [mV]" "$_bd_read/voltage_min_design" "" 000 printparm "%-59s = ##%6s## [mV]" "$_bd_read/voltage_now" "" 000 printf "\n" fi return 0 } # --- Radio Devices print_nm_rfkill_states () { # print radio devices states as seen by NetworkManager and rfkill # shellcheck disable=SC2086 if cmd_exists $NMCLI; then echo "NetworkManager details:" $NMCLI radio echo fi # shellcheck disable=SC2086 if cmd_exists $RFKILL; then echo "rfkill details:" $RFKILL echo fi return 0 } # -- Desktop Environment print_desktop_session () { # print DE, version, wayland/x11 local session stype version session="${XDG_SESSION_DESKTOP:-unknown}" stype="$XDG_SESSION_TYPE" case "$session" in gnome) version="$(gnome-shell --version 2> /dev/null)" ;; ubuntu) version="Ubuntu/$(gnome-shell --version 2> /dev/null)" ;; plasma) version="$(plasmashell --version 2> /dev/null)" ;; cinnamon) version="$(cinnamon --version 2> /dev/null)" ;; lxqt) version="$(lxqt-session --version 2> /dev/null | grep "session")" ;; esac if [ "$session" = "unknown" ]; then if [ "$stype" = "tty" ]; then printf "Desktop session = none (tty)\n" else printf "Desktop session = unknown (%s)\n" "$stype" fi return 1 fi [ -n "$version" ] && session="$version" if [ -n "$stype" ]; then printf "Desktop session = %s (%s)\n" "$session" "$stype" else printf "Desktop session = %s\n" "$session" fi return 0 } # -- udev diagnostic check_udev_rule_ps () { # check if udev rule for power source changes draws if [ -n "$_psdev" ]; then $UDEVADM_TEST -a change "$_psdev" 2>&1 | grep -E '^Reading rules file: .*tlp.rules$' if $UDEVADM_TEST -a change "$_psdev" 2> /dev/null | grep -E '(^run:|RUN{program} :) .*tlp auto'; then printf "OK.\n\n" else cprintf "err" "Fatal Error: TLP's udev rule for power source changes (85-tlp.rules) is not active -- possible package bug.\n\n" fi fi } check_udev_rule_usb () { # check if udev rule for connecting USB device draws local ud ud="$(glob_dirs '/usb[1-9]' /sys/bus/usb/devices 2> /dev/null | head -1)" if [ -n "$ud" ]; then $UDEVADM_TEST -a add "$ud" 2>&1 | grep -E '^Reading rules file: .*tlp.rules$' if $UDEVADM_TEST -a add "$ud" 2> /dev/null | grep -E '(^run:|RUN{program} :) .*tlp-usb-udev usb'; then printf "OK.\n\n" else cprintf "err" "Fatal Error: TLP's udev rule for connecting USB devices (85-tlp.rules) is not active -- possible package bug.\n\n" fi fi } TLP-1.10.1/man-pd/000077500000000000000000000000001517565574500134315ustar00rootroot00000000000000TLP-1.10.1/man-pd/tlp-pd.8000066400000000000000000000011771517565574500147300ustar00rootroot00000000000000.TH tlp-pd 8 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME . tlp-pd - TLP Profiles Daemon . .SH SYNOPSIS .B tlp-pd \fB[\fIoptions\fR] . .SH DESCRIPTION tlp-pd implements the D-Bus interface org.freedesktop.UPower.PowerProfile which lets desktop environments show a profile switch. TLP is used as the backend to apply these profiles. .SH OPTIONS . .TP .B -D, --debug Write debug information to the system log. You can view it using \fBtlp-stat --trace\fR. . .SH SEE ALSO .BR tlp (8), tlp-pd.service (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man-pd/tlp-pd.service.8000066400000000000000000000010261517565574500163600ustar00rootroot00000000000000.TH tlp-pd.service 8 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME . tlp-pd.service - starts the TLP Profiles Daemon (tlp-pd) . .SH SYNOPSIS .B tlp-pd\&.service . .SH DESCRIPTION tlp-pd implements the D-Bus interface org.freedesktop.UPower.PowerProfile which lets desktop environments show a profile switch. TLP is used as the backend to apply these profiles. . .SH SEE ALSO .BR tlp (8), tlp-pd (8) . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man-pd/tlpctl.1000066400000000000000000000102021517565574500150100ustar00rootroot00000000000000.TH tlpctl 1 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME tlpctl \- control TLP profiles . .SH SYNOPSIS .B tlpctl [\fB\-h\fR] .I command [\fIoptions\fR] [\fIargs\fR] . .SH DESCRIPTION .B tlpctl is a command-line utility to control TLP profiles. It allows users to switch between profiles, list available profiles and launch applications with specific profiles. It offers a subset of powerprofilesctl's commands supplemented by TLP-specific shortcuts. .PP .B tlpctl communicates with the TLP Profiles Daemon (tlp-pd) which implements the org.freedesktop.UPower.PowerProfiles D-Bus interface similar to power-profiles-daemon. . .SH COMMANDS .SS Profile Shortcuts .TP .B performance Switch to the performance profile (shortcut for \fBtlpctl set performance\fR). .TP .B balanced Switch to the balanced profile (shortcut for \fBtlpctl set balanced\fR). .TP .B power-saver Switch to the power-saver profile (shortcut for \fBtlpctl set power-saver\fR). . .SS Profile Management .TP .B list List available TLP profiles. The active profile is marked with an asterisk (*). .TP .B get Print the currently active TLP profile. .TP .BI "set " profile Set the active TLP profile. Valid profiles are: .BR performance ", " balanced " and " power-saver "." This will release all active profile holds. .TP . .BI "launch " "command " "[\fIoptions\fR]" Run a command and request a specific TLP profile for it ("profile hold"). .RS .TP .BR \-p ", " \-\-profile " " \fIprofile\fR Profile to hold while running the command (default: performance). .TP .BR \-r ", " \-\-reason " " \fIreason\fR Reason for the profile hold (default: second and remaining words of the \fIcommand\fR). .TP .BR \-i ", " \-\-appid " " \fIappid\fR Application identifier for the hold (default: first word of \fIcommand\fR). .TP Holds are automatically released, returning to the user's selected profile, when: .IP \(bu 3 The holding command exits .IP \(bu 3 The user manually changes the profile (with \fBtlpctl set\fR) .IP \(bu 3 The application explicitly releases the hold .TP Multiple holds can be active simultaneously. .RE .TP .B list-holds List current TLP profile holds (from launch command), showing the profile name, application ID, and reason for each hold. . .SS Diagnostics and Debugging .TP .BI "loglevel " \fIlevel\fR Set the loglevel of the TLP Profiles Daemon (tlp-pd). Valid levels are: .BR info " and " debug "." . .SS Information .TP .BR version ", " --version Display version information for both the tlpctl client and the TLP Profiles Daemon. . .SH OPTIONS .TP .BR \-h ", " \-\-help Show a help message and exit. Can be used with any command to show command-specific help. . .SH AUTHORIZATION .PP An active, unlocked session is required to perform actions. Changing the \fBloglevel\fR requires root privilege. . .SH POWER PROFILES .TP .B performance Optimizes the system for maximum performance at the cost of higher power consumption. Recommended for demanding workloads. TLP default when running on AC power. .TP .B balanced Provides a balance between performance and power consumption. Suitable for general-purpose use. TLP default when running on battery power. .TP .B power-saver Optimizes the system for maximum battery life at the cost of reduced performance. Recommended for maximizing battery runtime. . .SH EXAMPLES .TP List available profiles: .EX $ tlpctl list .EE .TP Switch to the power-saver profile: .EX $ tlpctl power-saver .EE .TP Get the currently active TLP profile: .EX $ tlpctl get .EE .TP Launch a game with the performance profile: .EX $ tlpctl launch --profile performance --reason "Gaming" steam .EE .TP Launch a command with default performance hold: .EX $ tlpctl launch make -j8 .EE .TP List current TLP profile holds: .EX $ tlpctl list-holds .EE . .SH EXIT STATUS .TP .B 0 Success .TP .B 1 General error (Invalid arguments, tlp-pd refuses the operation, D-Bus communication failure) .TP .B Other When using \fBlaunch\fR, the exit status of the launched command is returned. . .SH SEE ALSO .BR tlp (8), .BR tlp-stat (8), .BR tlp-pd (8). .PP TLP documentation: . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man-rdw/000077500000000000000000000000001517565574500136225ustar00rootroot00000000000000TLP-1.10.1/man-rdw/tlp-rdw.8000066400000000000000000000011251517565574500153030ustar00rootroot00000000000000.TH tlp-rdw 8 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME tlp-rdw - disable Radio Device Wizard temporarily (until reboot). . .SH SYNOPSIS .B tlp-rdw \fR[\fIcommand\fR] . .SH COMMANDS . .TP .B disable Disable RDW actions. . .TP .B enable Enable RDW actions. . .TP Show RDW state. . .TP .B --version Print TLP version. . .SH AUTHORIZATION .PP The \fBtlp-rdw\fR command requires root privileges for enabling/disabling actions. . .SH SEE ALSO .BR tlp (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/000077500000000000000000000000001517565574500130305ustar00rootroot00000000000000TLP-1.10.1/man/bluetooth.1000066400000000000000000000011601517565574500151150ustar00rootroot00000000000000.TH bluetooth 1 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME bluetooth - enable/disable internal bluetooth device . .SH SYNOPSIS .B bluetooth \fR[\fIcommand\fR] . .SH COMMANDS . .TP .B on Switch device on. . .TP .B off Switch device off. . .TP .B toggle Toggle device state. . .TP .B cycle Toggle the device twice (to return to the original state). . .TP Show device state. . .TP .B --version Print TLP version. . .SH SEE ALSO .BR nfc (1), .BR wifi (1), .BR wwan (1), .BR tlp (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/nfc.1000066400000000000000000000011361517565574500136610ustar00rootroot00000000000000.TH nfc 1 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME nfc - enable/disable internal NFC device . .SH SYNOPSIS .B nfc \fR[\fIcommand\fR] . .SH COMMANDS . .TP .B on Switch device on. . .TP .B off Switch device off. . .TP .B toggle Toggle device state. . .TP .B cycle Toggle the device twice (to return to the original state). . .TP Show device state. . .TP .B --version Print TLP version. . .SH SEE ALSO .BR bluetooth (1), .BR wifi (1), .BR wwan (1), .BR tlp (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/run-on-ac.1000066400000000000000000000005351517565574500147140ustar00rootroot00000000000000.TH run-on-ac 1 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME run-on-ac - run command when on ac power . .SH SYNOPSIS .B run-on-ac \fR\fIcommand\fR \fR[\fIarg(s)]\fR . .SH SEE ALSO .BR run-on-bat (1), .BR tlp (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/run-on-bat.1000066400000000000000000000005441517565574500150770ustar00rootroot00000000000000.TH run-on-bat 1 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME run-on-bat - run command when on battery power . .SH SYNOPSIS .B run-on-bat \fR\fIcommand\fR \fR[\fIarg(s)]\fR . .SH SEE ALSO .BR run-on-ac (1), .BR tlp (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/tlp-stat.8000066400000000000000000000071321517565574500146740ustar00rootroot00000000000000.TH tlp-stat 8 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME tlp-stat - view power saving status . .SH SYNOPSIS .B tlp-stat \fB[\fIoptions\fR] [\fB--\fR \fICONFIG_PARAM\fR\fB=\fIvalue\fR "..."] . .SH DESCRIPTION View configuration, system information, kernel power saving tunables and battery data. Invocation without options shows all information categories. . .SH OPTIONS . .SS System Operation .TP .B -s, --system View system information and TLP status. . .TP .B -m, --mode Print currently active TLP profile. . .SS Configuration .TP .B -c, --config View active configuration. . .TP .B --cdiff View the difference between defaults and user configuration. . .TP .B -- \fR\fICONFIG_PARAM\fR\fB=\fIvalue\fR "..." Append configuration parameters to a command. These temporarily override the system configuration during execution of that command only and are not kept afterwards. Disclaimer: this feature exists for the sole purpose of test automation during TLP's development. It is provided as is and there is no support whatsoever. . .SS Power Saving .TP .B -d, --disk View disk device tunables. . .TP .B -e, --pcie View PCIe device tunables. Add \fB-v\fR to see device runtime status. . .TP .B -g, --graphics View graphics card tunables. Add \fB-v\fR to see power state and clocks of AMD GPUs. . .TP .B -p, --processor View processor tunables. For clarity the standard output shows only cpu0. Add \fB-v\fR to see all cpus. Add \fB-q\fR to see cpu driver state only. . .TP .B -r, --rfkill View radio device states and tunables. Add \fB-v\fR to see NetworkManager and rfkill details. . .TP .B -u, --usb View USB device tunables. Add \fB-v\fR to see device runtime status. . .SS Battery Care .TP .B -b, --battery View battery data and battery care features supported by your hardware. Add \fB-v\fR to see battery voltages (if available). . .SS Information .TP .B -q, --quiet Omit version header and show less information in the processor category. . .TP .B -t, --temp View temperatures and fan speed. Add \fB-v\fR to see all individual sensors. . .TP .B -v, --verbose Show more information in the battery, graphics, PCIe, processor, temperature, wireless and USB categories. . .TP .B --version Print TLP version. . .SS Diagnostics and Debugging .TP .B --pd-diag View tlp-pd diagnostics. . .TP .B -P, --pev Monitor power supply udev events. . .TP .B --psup View power supply diagnostics. . .TP .B -T, --trace View trace output for tlp and tlp-pd. . .TP .B --trace-nm View trace output correlated with NetworkManager journal. . .TP .B --udev Check if udev rules for power source changes and connecting USB devices are active. . .TP .B -w, --warn View warnings about SATA disks. . .SH AUTHORIZATION .PP Many (but not all) \fBtlp-stat\fR commands require root privileges. . .SH FILES .I /etc/tlp.conf .RS System-wide user configuration file, uncomment parameters here to override default settings and customization files below. .PP .RE .I /etc/tlp.d/*.conf .RS System-wide drop-in customization files, overriding defaults below. .PP .RE .I /usr/share/tlp/defaults.conf .RS Intrinsic default settings. DO NOT EDIT this file, instead use one of the above alternatives. .PP .RE .I /run/tlp/run.conf .RS Effective settings consolidated from all above files. DO NOT CHANGE this file, it is for reference only and regenerated on every invocation of TLP. .PP .RE . .SH SEE ALSO .BR tlp (8), .BR tlpctl (8). .IP \(bu 3 TLP documentation: .IP \(bu 3 Battery care specifics: . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/tlp.8000066400000000000000000000135601517565574500137250ustar00rootroot00000000000000.TH tlp 8 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME tlp - apply laptop power saving settings . .SH SYNOPSIS .B tlp \fIcommand\fR [\fIparameters\fR] [\fB--\fR \fICONFIG_PARAM\fR\fB=\fIvalue\fR "..."] . .SH DESCRIPTION \fBtlp\fR applies power saving settings manually and controls battery care features. . .SH COMMANDS . .SS Profile Management .TP .B start Start \fBtlp\fR and apply the appropriate TLP profile. Also use to apply a changed configuration or to leave manual mode. . .TP .B performance Apply the performance profile. Parameters ending in _AC are used. . .TP .B balanced Apply the balanced profile. Parameters ending in _BAT are used. . .TP .B power-saver Apply the power-saver profile. Parameters ending in _SAV are used. If there is no _SAV parameter available for a feature, the _BAT parameter will be used instead. . .TP .B ac Apply profile for AC power and enter manual mode. Manual mode means that changes to the power source will be ignored until the next reboot or \fBtlp start\fR is run to resume automatic mode. . .TP .B bat Apply profile for battery power and enter manual mode. Manual mode means that changes to the power source will be ignored until the next reboot or \fBtlp start\fR is run to resume automatic mode. . .SS Battery Care .TP .B setcharge\fR [\fIstart_threshold stop_threshold\fR] [\fIbattery\fR] Change battery charge thresholds temporarily. If your hardware supports only a stop threshold, set the start value to 0. Configured charge thresholds will be restored at the next boot or by using \fBtlp setcharge\fR again but without the threshold arguments. . .TP .B fullcharge\fR [\fIbattery\fR] Charge battery to full capacity. This is done by applying vendor presets to the charge thresholds temporarily. Configured charge thresholds will be restored at the next boot or by using \fBtlp setcharge\fR without the threshold arguments. . .TP .B chargeonce\fR [\fIbattery\fR] Charge battery to the stop charge threshold once. This is done by temporarily lifting the start charge threshold. The configured start charge threshold will be restored at the next boot or by using \fBtlp setcharge\fR without the threshold arguments. . .TP .B discharge\fR [\fIbattery\fR] [\fItarget_charge_level\fR] Force a complete or partial discharge of the battery while on AC power. . .TP .B recalibrate\fR [\fIbattery\fR] Perform a battery recalibration while on AC power: completely discharge the battery and recharge to 100%. The latter is done by temporarily applying vendor presets to the thresholds. Configured thresholds will be restored at the next boot or by using \fBtlp setcharge\fR. . .SS Device Power Management .TP .B usb Enable autosuspend for all USB devices except those excluded by default or via configuration. . .TP .B bayoff Turn off optical drive in UltraBay/MediaBay. The drive may be re-enabled by pulling the eject lever or pushing the media eject button on newer models. . .SS Information .TP .B diskid Print disk ids for configured drives. . .TP .B --version Print TLP version. . .SS Configuration .TP .B --\fR \fICONFIG_PARAM\fR\fB="\fIvalue\fR" "..." Append configuration parameters to a command. These temporarily override the system configuration during execution of that command only and are not kept afterwards. Disclaimer: this feature exists for the sole purpose of test automation during TLP's development. It is provided as is and there is no support whatsoever. . .SH AUTHORIZATION .PP Almost all \fBtlp\fR commands require root privileges. To change the TLP profile without root, you may use \fBtplctl\fR. . .SH BATTERY CARE .PP Availability of the above battery care commands and the possible charge threshold values always depends on laptop vendor or brand, Linux kernel version and TLP version. Check for actual availability, threshold ranges and battery names with \fBtlp-stat -b\fR. Follow the link in the \fBSEE ALSO\fR section for details. .PP For laptops with two batteries, the secondary battery must be specified as a command parameter in order to select it. In many cases the main battery will be \fBBAT0\fR, the secondary battery \fBBAT1\fR. When in doubt, the output of \fBtlp-stat -b\fR, which lists all batteries, can help. . .SH POWER PROFILES .TP .B performance Optimizes the system for maximum performance at the cost of higher power consumption. Recommended for demanding workloads. TLP default when running on AC power. .TP .B balanced Provides a balance between performance and power consumption. Suitable for general-purpose use. TLP default when running on battery power. .TP .B power-saver Optimizes the system for maximum battery life at the cost of reduced performance. Recommended for maximizing battery runtime. . .SH EXAMPLES .TP Change thresholds of the main battery to 70 / 90% temporarily: .EX tlp setcharge 70 90 .EE .TP Charge the secondary battery to full capacity: .EX tlp fullcharge BAT1 .EE .TP Recalibrate the main battery: .EX tlp recalibrate .EE . .SH FILES .I /etc/tlp.conf .RS System-wide user configuration file, uncomment parameters here to override default settings and customization files below. .PP .RE .I /etc/tlp.d/*.conf .RS System-wide drop-in customization files, overriding defaults below. .PP .RE .I /usr/share/tlp/defaults.conf .RS Intrinsic default settings. DO NOT EDIT this file, instead use one of the above alternatives. .PP .RE .I /run/tlp/run.conf .RS Effective settings consolidated from all above files. DO NOT CHANGE this file, it is for reference only and regenerated on every invocation of TLP. .PP .RE . .SH EXIT STATUS On success, 0 is returned, a non-zero failure code otherwise. . .SH SEE ALSO .BR tlpctl (1), .BR tlp-stat (8), .BR bluetooth (1), .BR nfc (1), .BR wifi (1), .BR wwan (1). .IP \(bu 3 TLP documentation: .IP \(bu 3 Battery care specifics: . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/tlp.service.8000066400000000000000000000015051517565574500153600ustar00rootroot00000000000000.TH tlp.service 8 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME . tlp.service - Initialize power saving at boot and cleanup upon shutdown . .SH SYNOPSIS .B tlp\&.service . .SH DESCRIPTION tlp.service executes the following tasks: .IP " 1." 4 System boot-up: switch or restore radio states, apply power saving settings and charge thresholds. .IP " 2." 4 System shutdown: save radio states and cleanup. . .SH FILES .I /lib/systemd/system-sleep/tlp .RS Applies power saving settings upon system suspend and resume. .SH SEE ALSO .BR tlp (8). . .SH NOTES Do \fInot\fR employ tlp.service to refresh power saving settings after a configuration change. Use \fBtlp start\fR instead. . .SH SEE ALSO .BR tlp (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/wifi.1000066400000000000000000000011431517565574500140470ustar00rootroot00000000000000.TH wifi 1 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME wifi - enable/disable internal Wi-Fi device . .SH SYNOPSIS .B wifi \fR[\fIcommand\fR] . .SH COMMANDS . .TP .B on Switch device on. . .TP .B off Switch device off. . .TP .B toggle Toggle device state. . .TP .B cycle Toggle the device twice (to return to the original state). . .TP Show device state. . .TP .B --version Print TLP version. . .SH SEE ALSO .BR bluetooth (1), .BR nfc (1), .BR wwan (1), .BR tlp (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/man/wwan.1000066400000000000000000000011531517565574500140660ustar00rootroot00000000000000.TH wwan 1 2026-05-04 "TLP 1.10.1" "Power Management" . .SH NAME wwan - enable/disable internal WWAN (3G/4G/5G) device . .SH SYNOPSIS .B wwan \fR[\fIcommand\fR] . .SH COMMANDS . .TP .B on Switch device on. . .TP .B off Switch device off. . .TP .B toggle Toggle device state. . .TP .B cycle Toggle the device twice (to return to the original state). . .TP Show device state. . .TP .B --version Print TLP version. . SH SEE ALSO .BR bluetooth (1), .BR nfc (1), .BR wifi (1), .BR tlp (8). . .SH BUGS Report bugs to: . . .SH AUTHOR (c) 2026 Thomas Koch TLP-1.10.1/pyrightconfig.json000066400000000000000000000004331517565574500160240ustar00rootroot00000000000000{ "include": [], "exclude": ["worktree*"], "defineConstant": { "DEBUG": true }, "typeCheckingMode": "standard", "reportAttributeAccessIssue": "none", "reportUnusedImport": true, "reportUnusedClass": true, "reportUnusedFunction": true } TLP-1.10.1/rename.conf000066400000000000000000000011701517565574500143720ustar00rootroot00000000000000CPU_HWP_ON_AC CPU_ENERGY_PERF_POLICY_ON_AC CPU_HWP_ON_BAT CPU_ENERGY_PERF_POLICY_ON_BAT SATA_LINKPWR_BLACKLIST SATA_LINKPWR_DENYLIST RUNTIME_PM_BLACKLIST RUNTIME_PM_DENYLIST RUNTIME_PM_DRIVER_BLACKLIST RUNTIME_PM_DRIVER_DENYLIST TLP_DEFAULT_MODE TLP_PROFILE_DEFAULT USB_BLACKLIST USB_DENYLIST USB_BLACKLIST_BTUSB USB_EXCLUDE_BTUSB USB_BLACKLIST_PHONE USB_EXCLUDE_PHONE USB_BLACKLIST_PRINTER USB_EXCLUDE_PRINTER USB_BLACKLIST_WWAN USB_EXCLUDE_WWAN USB_WHITELIST USB_ALLOWLIST TLP-1.10.1/tlp-func-base.in000066400000000000000000001336501517565574500152550ustar00rootroot00000000000000#!/bin/sh # tlp - Base Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # shellcheck disable=SC2034 # ---------------------------------------------------------------------------- # Constants readonly TLPVER="@TLPVER@" readonly RUNDIR=@TLP_RUN@ readonly VARDIR=@TLP_VAR@ readonly CONF_DEF=@TLP_CONFDEF@ readonly CONF_DIR=@TLP_CONFDIR@ readonly CONF_USR=@TLP_CONFUSR@ readonly CONF_RUN="$RUNDIR/run.conf" readonly FLOCK=flock readonly GDBUS=gdbus readonly HDPARM=hdparm readonly LAPMODE=laptop_mode readonly LOGGER=logger readonly MKTEMP=mktemp readonly MODPRO=modprobe readonly MODINFO=modinfo readonly PPD=power-profiles-daemon readonly READCONFS=@TLP_TLIB@/tlp-readconfs readonly SYSTEMCTL=systemctl readonly TPACPIBAT=@TPACPIBAT@ readonly TUNEDPPD="tuned-ppd" readonly UDEVADM=udevadm readonly UDEVADM_TEST="udevadm test" readonly TLPRDW=tlp-rdw readonly TLPPD=tlp-pd readonly TLPPD_BUS_NAME="org.freedesktop.UPower.PowerProfiles" readonly TLPPD_INTERFACE_NAME="$TLPPD_BUS_NAME" readonly TLPPD_OBJECT_PATH="/org/freedesktop/UPower/PowerProfiles" readonly PROPERTY_INTERFACE_NAME="org.freedesktop.DBus.Properties" readonly LOCKFILE=$RUNDIR/lock readonly LOCKTIMEOUT=2 readonly PWRRUNFILE=$RUNDIR/last_pwr readonly MANUALMODEFILE=$RUNDIR/manual_mode readonly DMID=/sys/devices/virtual/dmi/id/ readonly NETD=/sys/class/net readonly TPACPID=/sys/devices/platform/thinkpad_acpi readonly RE_PARAM='^[A-Z_]+[0-9]*=[][0-9a-zA-Z _.:-]*$' # power supplies: ignore MacBook Pro 2017 sbs-charger, ThinkPad X13s ARM qcom-battmgr-ac, hid devices, game controllers readonly RE_PS_IGNORE='sbs-charger|qcom-battmgr-ac|hidpp_battery|hid-|controller-battery-|controller_battery_' readonly DEBUG_TAGS_ALL="arg bat cfg disk lock nm path pm ps rf run sysfs udev usb" readonly TLP_SERVICE="tlp.service" readonly TLPPD_SERVICE="tlp-pd.service" readonly PPD_SERVICE="power-profiles-daemon.service" readonly RFKILL_SERVICES="systemd-rfkill.service systemd-rfkill.socket" # power supplies readonly PS_AC=0 readonly PS_BAT=1 readonly PS_UNKNOWN=128 # TLP profiles readonly PP_PRF=0 readonly PP_BAL=1 readonly PP_SAV=2 readonly PP_SUS=3 readonly PP_NONE=255 readonly PP_USER="0 1 2" # ---------------------------------------------------------------------------- # Control _nodebug=0 # ---------------------------------------------------------------------------- # Functions # -- Exit do_exit () { # cleanup and exit -- $1: rc # remove temporary runconf [ -z "$_conf_tmp" ] || rm -f -- "$_conf_tmp" exit "$1" } # --- Messages echo_debug () { # write trace message to syslog if tag matches -- $1: tag; $2: msg; [ "$_nodebug" = "1" ] && return 0 if wordinlist "$1" "$TLP_DEBUG"; then $LOGGER -p daemon.debug -t "tlp" --id=$$ -- "$2" > /dev/null 2>&1 fi } cprintf_init () { # preset ANSI sequences for colorized message output # retval: $_cprintf_color_err # $_cprintf_color_warn # $_cprintf_color_note # $_cprintf_color_succ # $_cprintf_color_dbg # $_cprintf_color_rst # proceed only when external printf command exists if [ -n "$TLP_MSG_COLORS" ]; then # shellcheck disable=SC2086 set -- $TLP_MSG_COLORS # note: dash internal printf does *not* support '\x1b' notation for ESC is_uint "$1" 3 && _cprintf_color_err="\033[1;${1}m" is_uint "$2" 3 && _cprintf_color_warn="\033[1;${2}m" is_uint "$3" 3 && _cprintf_color_note="\033[1;${3}m" is_uint "$4" 3 && _cprintf_color_succ="\033[1;${4}m" is_uint "$5" 3 && _cprintf_color_dbg="\033[1;${5}m" _cprintf_color_rst="\033[0m" fi } cprintf () { # printf colorized message to stdout # color is selected by class from $1 or, if the argument is left blank, by the message prefix # $1: message class: err/warning/notice/debug # $2: printf format string == message # $3..n: printf arguments # prerequisite: cprintf_init() local class color fmt class="$1"; shift fmt="$1"; shift if [ -z "$class" ]; then # explicit class not specified -> cut out message prefix preceding ": " case "${fmt}" in Error*) class="err" ;; Warning*) class="warning" ;; Notice*) class="notice" ;; Debug*) class="debug" ;; esac fi case "$class" in err) color="$_cprintf_color_err" ;; warning) color="$_cprintf_color_warn" ;; notice) color="$_cprintf_color_note" ;; success) color="$_cprintf_color_succ" ;; debug) color="$_cprintf_color_dbg" ;; *) color="" ;; esac if [ -n "$color" ] && [ -t 1 ]; then # color is specd and output is a terminal (not a pipe) # shellcheck disable=SC2059 printf "${color}${fmt}${_cprintf_color_rst}" "$@" else # shellcheck disable=SC2059 printf "$fmt" "$@" fi return 0 } cecho () { # echo colorized message to stdout, terminated by LF # color is selected by the message prefix # $1: message string # $2: message class: err/warning/notice/debug # prerequisite: cprintf_init() cprintf "$2" "$1\n" } echo_message () { # output message according to TLP_MSG_LEVEL # $1: message # $2: message class/log level for syslog: : err/warning/notice/debug/info local msg local class msg="$1" class="$2" # shellcheck disable=SC2154 if [ "$_bgtask" = "1" ]; then # called from background task --> use syslog if [ -z "$class" ]; then # explicit class not specified -> cut out message prefix preceding ": " case "${msg%: *}" in Error) class="err" ;; Warning) class="warning" ;; Notice) class="notice" ;; Debug) class="debug" ;; *) class="info" ;; esac fi if [ -n "$msg" ]; then case "$TLP_WARN_LEVEL" in 1|3) $LOGGER -p "daemon.$class" -t "tlp" --id=$$ -- "$msg" > /dev/null 2>&1 ;; esac fi else # called from command line task --> use stderr case "$TLP_WARN_LEVEL" in 2|3) # shellcheck disable=SC2059 cecho "$msg" "$class" 1>&2 ;; esac fi } print_version () { echo "TLP version $TLPVER" } # --- Strings trim () { # remove leading and trailing whitespace -- $1: string printf "%s" "$1" | sed -r -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' } tolower () { # print string in lowercase -- $1: string printf "%s" "$1" | tr "[:upper:]" "[:lower:]" } toupper () { # print string in uppercase -- $1: string printf "%s" "$1" | tr "[:lower:]" "[:upper:]" } wordinlist () { # test if word in list # $1: word, $2: whitespace-separated list of words local word if [ -n "${1-}" ]; then for word in ${2-}; do [ "${word}" != "${1}" ] || return 0 # exact match done fi return 1 # no match } get_listitem () { # print the active list item, which is the one enclosed in [] # $1: list printf "%s" "$(echo "$1" | sed -r 's/.*\[([A-Za-z0-9_]+)\].*/\1/')" } # --- Sysfiles read_sysf () { # read and print contents of a sysfile # return 1 and print default if read fails # $1: sysfile # $2: default # rc: 0=ok/1=error if cat "$1" 2> /dev/null; then return 0 else printf "%s" "$2" return 1 fi } readable_sysf () { # check if sysfile is actually readable # $1: file # rc: 0=readable/1=read error cat "$1" > /dev/null 2>&1 } read_sysval () { # read and print contents of a sysfile # print '0' if file is non-existent, read fails or content is non-numeric # $1: sysfile # rc: 0=ok/1=error printf "%d" "$(read_sysf "$1")" 2> /dev/null } write_sysf () { # write string to a sysfile # $1: string # $2: sysfile # rc: 0=ok/1=error { printf '%s\n' "$1" > "$2"; } 2> /dev/null } # --- Globbing glob_files () { # @stdout glob_files ( glob_pattern, dir[, dir...] ) # # Nested loop that applies a glob expression to several directories # (or path prefixes) and prints matching file paths (including symlinks) # to stdout. # # NOTE: for x in $(glob_files 'a*' dirpath ); do ...; done # globs twice: # (a) once in the "for file_iter" loop in glob_files() # (b) another time when x gets word expanded in the "for x" loop # crafted filenames (e.g. a file named '*') will break this function, # as such it should be only be used with 'sort-of trustworthy' directories # (sysfs, proc). [ -n "${1-}" ] || return 64 local glob_pattern file_iter local rc=1 glob_pattern="${1}" while shift && [ $# -gt 0 ]; do for file_iter in ${1}${glob_pattern}; do if [ -f "${file_iter}" ] || [ -L "${file_iter}" ]; then printf '%s\n' "${file_iter}" rc=0 fi done done return $rc } glob_dirs () { # @stdout glob_dirs ( glob_pattern, dir[, dir...] ) # # Nested loop that applies a glob expression to several directories # (or path prefixes) and prints matching directory paths to stdout. # # NOTE: globs twice, see glob_files(). [ -n "${1-}" ] || return 64 local glob_pattern dir_iter local rc=1 glob_pattern="${1}" while shift && [ $# -gt 0 ]; do for dir_iter in ${1}${glob_pattern}; do if [ -d "${dir_iter}" ]; then printf '%s\n' "${dir_iter}" rc=0 fi done done return $rc } # --- Checks cmd_exists () { # test if command exists -- $1: command command -v "$1" > /dev/null 2>&1 } test_root () { # test root privilege -- rc: 0=root, 1=not root [ "$(id -u)" = "0" ] } check_root () { # show error message and quit when root privilege missing if ! test_root; then cecho "Error: missing root privilege." 1>&2 do_exit 1 fi } check_tlp_enabled () { # check if TLP is enabled in config file # $1: 1=verbose (default: 0) # rc: 0=disabled/1=enabled if [ "$TLP_ENABLE" = "1" ]; then return 0 else [ "${1:-0}" = "1" ] && cecho "Error: TLP power save is disabled. Set TLP_ENABLE=1 in ${CONF_USR}." 1>&2 return 1 fi } check_tlp_pd_installed () { cmd_exists "$TLPPD" } check_systemd () { # check if systemd is the active init system (PID 1) and systemctl is installed # rc: 0=yes, 1=no [ -d /run/systemd/system ] && cmd_exists $SYSTEMCTL } check_service_state () { # check service state # $1: service # $2: state match: active/enabled/masked # rc: 0=yes, 1=no case "$2" in active) $SYSTEMCTL is-active "$1" > /dev/null 2>&1 ;; enabled) $SYSTEMCTL is-enabled "$1" > /dev/null 2>&1 ;; masked) $SYSTEMCTL is-enabled "$1" 2> /dev/null | grep -q 'masked' ;; esac } check_services_activation_status () { # issue messages for # - TLP service(s) not enabled # - conflicting services enabled # rc: 0=no messages/messages issued local rc=0 if check_systemd; then if ! check_service_state "$TLP_SERVICE" enabled > /dev/null 2>&1; then echo_message "Error: TLP's power saving will not apply on boot because $TLP_SERVICE is not enabled "` `"--> Run 'systemctl enable $TLP_SERVICE' to ensure the full functionality of TLP." "err" echo_message "" rc=1 fi if check_tlp_pd_installed && ! check_service_state "$TLPPD_SERVICE" enabled > /dev/null 2>&1; then if ! check_service_state "$TLPPD_SERVICE" active; then echo_message "Error: You can't switch TLP profiles by mouse click because $TLPPD_SERVICE is not enabled "` `"--> Run 'systemctl enable --now $TLPPD_SERVICE' to ensure the full functionality of TLP." "err" else echo_message "Error: After the next restart, you won't be able to switch TLP profiles by mouse click because "` `"$TLPPD_SERVICE is not enabled --> Run 'systemctl enable $TLPPD_SERVICE' to ensure the "` `"full functionality of TLP." "err" fi echo_message "" rc=1 fi for su in $RFKILL_SERVICES; do if ! check_service_state "$su" masked 2> /dev/null; then if [ "$RESTORE_DEVICE_STATE_ON_STARTUP" = "1" ]; then echo_message "Warning: TLP's radio device switching on boot may not work as expected because "` `"RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and $su is not masked "` `"--> Run 'systemctl mask $su' to ensure the full functionality of TLP." "err" echo_message "" elif [ -n "$DEVICES_TO_DISABLE_ON_STARTUP" ] || [ -n "$DEVICES_TO_ENABLE_ON_STARTUP" ]; then echo_message "Warning: TLP's radio device switching on boot may not work as expected because "` `"DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP "` `"is configured and $su is not masked "` `"--> Run 'systemctl mask $su' to ensure the full functionality of TLP." "err" echo_message "" fi rc=1 fi done fi return $rc } check_ppd_running () { # check if power-profiles-daemon is running # rc: 0=yes, 1=no # shellcheck disable=SC2009 ps aux 2> /dev/null | grep -v "grep" | grep -q "/$PPD" } # --- Type and value checking is_uint () { # check for unsigned integer -- $1: string; $2: max digits printf "%s" "$1" | grep -E -q "^[0-9]{1,$2}$" 2> /dev/null } is_within_bounds () { # check condition min <= value <= max # $1: value; $2: min; $3: max (all unsigned int) # rc: 0=within/1=below/2=above/255=invalid # # value, min or max undefined/non-numeric means that this branch of the # condition is fulfilled is_uint "$1" || return 255 if is_uint "$2"; then [ "$1" -ge "$2" ] || return 1 fi if is_uint "$3"; then [ "$1" -le "$3" ] || return 2 fi return 0 } # --- Locking and Semaphores set_run_flag () { # set flag -- $1: flag name # rc: 0=success/1,2=failed local rc create_rundir touch "$RUNDIR/$1"; rc=$? echo_debug "lock" "set_run_flag.touch: $1; rc=$rc" return $rc } reset_run_flag () { # reset flag -- $1: flag name if rm "$RUNDIR/$1" 2> /dev/null 1>&2 ; then echo_debug "lock" "reset_run_flag($1).remove" else echo_debug "lock" "reset_run_flag($1).not_found" fi return 0 } check_run_flag () { # check flag -- $1: flag name # rc: 0=flag set/1=flag not set local rc [ -f "$RUNDIR/$1" ]; rc=$? echo_debug "lock" "check_run_flag($1): rc=$rc" return $rc } lock_tlp () { # get exclusive lock: blocking with timeout # $1: lock id (default: tlp) # rc: 0=success/1=failed create_rundir # open file for writing and attach fd 9 # when successful lock fd 9 exclusive and blocking # wait $LOCKTIMEOUT secs to obtain the lock if { exec 9> "${LOCKFILE}_${1:-tlp}" ; } 2> /dev/null && timeout $LOCKTIMEOUT $FLOCK -x 9 ; then echo_debug "lock" "lock_tlp($1).success" return 0 else echo_debug "lock" "lock_tlp($1).failed" return 1 fi } lock_tlp_nb () { # get exclusive lock: non-blocking # $1: lock id (default: tlp) # rc: 0=success/1=failed create_rundir # open file for writing and attach fd 9 # when successful lock fd 9 exclusive and non-blocking if { exec 9> "${LOCKFILE}_${1:-tlp}" ; } 2> /dev/null && $FLOCK -x -n 9 ; then echo_debug "lock" "lock_tlp_nb($1).success" return 0 else echo_debug "lock" "lock_tlp_nb($1).failed" return 1 fi } unlock_tlp () { # free exclusive lock # $1: lock id (default: tlp) # defer unlock for $X_DEFER_UNLOCK seconds -- debugging only [ -n "$X_DEFER_UNLOCK" ] && sleep "$X_DEFER_UNLOCK" # free fd 9 and scrap lockfile { exec 9>&- ; } 2> /dev/null rm -f "${LOCKFILE}_${1:-tlp}" echo_debug "lock" "unlock_tlp($1)" return 0 } lockpeek_tlp () { # check for pending lock (by looking for the lockfile) # $1: lock id (default: tlp) if [ -f "${LOCKFILE}_${1:-tlp}" ]; then echo_debug "lock" "lockpeek_tlp($1).locked" return 0 else echo_debug "lock" "lockpeek_tlp($1).not_locked" return 1 fi } echo_tlp_locked () { # print "locked" message cecho "Error: TLP is locked by another operation." 1>&2 return 0 } set_timed_lock () { # create timestamp n seconds in the future # $1: lock id, $2: lock duration [s] local lock rc time lock="${1}_timed_lock_$(date +%s -d "+${2} seconds")" set_run_flag "$lock"; rc=$? echo_debug "lock" "set_timed_lock($1, $2): $lock; rc=$rc" # cleanup obsolete locks time=$(date +%s) for lockfile in "$RUNDIR/${1}_timed_lock_"*; do if [ -f "$lockfile" ]; then locktime="${lockfile#"${RUNDIR}/${1}_timed_lock_"}" if [ "$time" -ge "$locktime" ]; then rm -f "$lockfile" echo_debug "lock" "set_timed_lock($1, $2).remove_obsolete: ${lockfile#"${RUNDIR}/"}" fi fi done return $rc } check_timed_lock () { # check if active timestamp exists # $1: lock id; rc: 0=locked/1=not locked local lockfile locktime time time=$(date +%s) for lockfile in "$RUNDIR/${1}_timed_lock_"*; do if [ -f "$lockfile" ]; then locktime=${lockfile#"${RUNDIR}/${1}_timed_lock_"} if [ "$time" -lt $(( locktime - 120 )) ]; then # timestamp is more than 120 secs in the future, # something weird has happened -> remove it rm -f "$lockfile" echo_debug "lock" "check_timed_lock($1).remove_invalid: ${lockfile#"${RUNDIR}/"}" elif [ "$time" -lt "$locktime" ]; then # timestamp in the future -> we're locked echo_debug "lock" "check_timed_lock($1).locked: $time, $locktime" return 0 else # obsolete timestamp -> remove it rm -f "$lockfile" echo_debug "lock" "check_timed_lock($1).remove_obsolete: ${lockfile#"${RUNDIR}/"}" fi fi done echo_debug "lock" "check_timed_lock($1).not_locked: $time" return 1 } # --- Environment print_shell () { # determine the shell executing this script readlink -n "/proc/$$/exe" } add_sbin2path () { # check if /sbin /usr/sbin in $PATH, otherwise add them # retval: $PATH, $_oldpath, $_addpath local sp _oldpath="$PATH" _addpath="" for sp in /usr/sbin /sbin; do if [ -d $sp ] && [ ! -h $sp ]; then # dir exists and is not a symlink case ":$PATH:" in *":$sp:"*) # $sp already in $PATH ;; *) # $sp not in $PATH, add it _addpath="$_addpath:$sp" ;; esac fi done if [ -n "$_addpath" ]; then export PATH="${PATH}${_addpath}" fi return 0 } # --- Directories and Files create_rundir () { # make sure $RUNDIR exists [ -d $RUNDIR ] || mkdir -p $RUNDIR 2> /dev/null 1>&2 } chmod_readable4all () { # make file world readable -- $1: file chmod -f o+r "$1" } # -- Battery Plugins select_batdrv () { # source battery feature drivers and # activate the one that matches the hardware # do not execute twice # shellcheck disable=SC2154 [ -z "$_batdrv_selected" ] || return 0 # iterate until a matching driver is found for batdrv in @TLP_BATD@/[0-9][0-9]-[a-z]*; do # shellcheck disable=SC1090 . "$batdrv" || exit 70 # end iteration when a matching driver is found batdrv_init && break done return 0 } # --- Configuration read_config () { # read all config files and write temporary runconf file # $1: 1=no trace # $2..n: arguments of the caller for examination if in-cmd-config TLP_DISABLE_DEFAULTS=1 is set # rc: 0=ok/5=tlp.conf missing/6=defaults.conf missing/7=file creation error # retval: config parameters; # _conf_tmp: runconf local rc=0 local in_cmd_cfg=0 local no_trace="$1" local tmpdir local xargs="" # check if caller holds in-cmd-config TLP_DISABLE_DEFAULTS=1 shift while [ $# -gt 0 ]; do if [ "$in_cmd_cfg" = "1" ] && [ "$1" = "TLP_DISABLE_DEFAULTS=1" ] ; then xargs="--skipdefs" elif [ "$1" = "--" ]; then in_cmd_cfg=1 fi shift # next argument done # while arguments if test_root; then tmpdir=$RUNDIR create_rundir else tmpdir=${TMPDIR:-/tmp} fi if _conf_tmp=$($MKTEMP -p "$tmpdir" "tlp-run.conf_tmpXXXXXX"); then # external perl script: merge all config files if [ "$no_trace" = "1" ]; then if [ -z "$xargs" ]; then xargs="--notrace" else xargs="$xargs --notrace" fi fi # shellcheck disable=SC2086 $READCONFS --outfile "$_conf_tmp" $xargs; rc=$? # shellcheck disable=SC1090 [ $rc -eq 0 ] && . "$_conf_tmp" else rc=7 fi if [ $rc -ne 0 ]; then case $rc in 5) cecho "Error: cannot read user configuration from $CONF_USR." 1>&2 ;; 6) cecho "Error: cannot read default configuration from $CONF_DEF." 1>&2 ;; 7) cecho "Error: cannot write runtime configuration to $_conf_tmp." 1>&2 ;; esac fi return 0 } parse_args4config () { # parse command-line arguments: everything after the # delimiter '--' is interpreted as a config parameter # retval: config parameters local argd="" cfgd="" dflag=0 param value # iterate arguments while [ $# -gt 0 ]; do if [ $dflag -eq 1 ]; then # delimiter was passed --> sanitize and parse argument: # quotes stripped by the shell calling tlp # format is PARAMETER=value # PARAMETER allows 'A'..'Z' and '_' only, may end in a number (_BAT0) # value allows 'A'..'Z', 'a'..'z', '0'..'9', ' ', '-', '_', '.', ':' # value may be an empty string if printf "%s" "$1" | grep -E -q "$RE_PARAM"; then param="${1%%=*}" value="${1#*=}" if [ -n "$param" ]; then eval "$param='$value'" 2> /dev/null cfgd="$cfgd $param=""$value""" fi fi elif [ "$1" = "--" ]; then # delimiter reached --> begin interpretation dflag=1 else argd="$argd $1" fi shift # next argument done # while arguments echo_debug "arg" "parse_args4config: ${0##/*/}$argd --$cfgd" return 0 } save_runconf () { # copy temporary to final runconf create_rundir if cp --preserve=timestamps "$_conf_tmp" $CONF_RUN > /dev/null 2>&1; then chmod 664 "$_conf_tmp" $CONF_RUN > /dev/null 2>&1 echo_debug "run" "save_runconf.ok: $_conf_tmp -> $CONF_RUN" else echo_debug "run" "save_runconf.failed: $_conf_tmp -> $CONF_RUN" fi } # --- Kernel kernel_version_ge () { # check if running kernel version >= $1: minimum version [ "$1" = "$(printf "%s\n%s\n" "$1" "$(uname -r)" | sort -V | head -n 1)" ] } load_modules () { # load kernel module(s) -- $*: modules local mod # verify module loading is allowed (else explicitly disabled) # and possible (else implicitly disabled) [ "${TLP_LOAD_MODULES:-y}" = "y" ] && [ -e /proc/modules ] || return 0 # load modules, ignore any errors # shellcheck disable=SC2048 for mod in $*; do $MODPRO "$mod" > /dev/null 2>&1 done return 0 } # --- DMI and Hardware read_dmi () { # read DMI data # $1: dmi id # stdout: dmi string # rc: 0=ok/1=nonexistent local out out="$(read_sysf "${DMID}/$1" | \ grep -E -v -i 'not available|to be filled|DMI table is broken')" printf '%s' "$out" if [ -n "$out" ]; then return 0 else return 1 fi } is_laptop () { # check if machine is a laptop # rc: 0=laptop/1=other/desktop case "$(read_dmi "chassis_type")" in 8|9|10|11) return 0 ;; *) return 1 ;; esac } # --- Power Source get_sys_power_supply () { # determine active power supply # $1: tlp command # rc: PS_AC=0/PS_BAT=1/PS_UNKNOWN=128 # retval: $_syspwr == rc # $_psdev: 1st power supply (sysfs path) found (for udev rule check) # # examine all power supply devices in lexical order, typically this is: # AC, ADPx (AC chargers) -> BATx, CMBx (batteries) -> ucsi* (USB). # names in $RE_PS_IGNORE are ignored. # # the ranking of power source classes for the determination of the active # power supply is as follows: # 1. AC chargers # 2. USB chargers (also power banks) # 3. Batteries # $TLP_PS_IGNORE may be used to ignore one or more power source classes local bs ps_ignore psrc psrc_name local ac0seen= local tlp_cmd="$1" local wait= _psdev="" _syspwr="$X_SIMULATE_PS" if [ -n "$_syspwr" ]; then # simulate power supply echo_debug "ps" "get_sys_power_supply.simulate: syspwr=$_syspwr" return "$_syspwr" fi ps_ignore=$(toupper "$TLP_PS_IGNORE") for psrc in /sys/class/power_supply/*; do # -f $psrc/type not necessary - read_sysf() handles this psrc_name="${psrc##*/}" # ignore atypical power supplies and batteries printf '%s\n' "$psrc_name" | grep -E -q "$RE_PS_IGNORE" && continue case "$(read_sysf "$psrc/type")" in Mains) # AC detected _psdev="${_psdev:-$psrc}" # if configured, skip device to ignore incorrect AC status if wordinlist "AC" "$ps_ignore"; then echo_debug "ps" "get_sys_power_supply(${psrc_name}).ac_ignored: syspwr=$_syspwr" continue fi # check AC status if [ "$(read_sysf "$psrc/online")" = "1" ]; then # AC online --> end iteration _syspwr=$PS_AC echo_debug "ps" "get_sys_power_supply(${psrc_name}).ac_online: syspwr=$_syspwr" break else # AC offine --> end iteration _syspwr=$PS_BAT echo_debug "ps" "get_sys_power_supply(${psrc_name}).ac_offline: syspwr=$_syspwr" break fi ;; USB) # USB PS detected _psdev="${_psdev:-$psrc}" # if configured, skip device to ignore incorrect USB status if wordinlist "USB" "$ps_ignore"; then echo_debug "ps" "get_sys_power_supply(${psrc_name}).usb_ignored: syspwr=$_syspwr" continue fi # check USB PS status if [ "$(read_sysf "$psrc/online")" = "1" ]; then # USB online --> end iteration _syspwr=$PS_AC echo_debug "ps" "get_sys_power_supply(${psrc_name}).usb_online: syspwr=$_syspwr" break else # USB PS offline could mean battery, but multiple connectors may exist # --> remember and continue looking ac0seen=1 echo_debug "ps" "get_sys_power_supply(${psrc_name}).remember_usb_offline" fi ;; Battery) # battery detected _psdev="${_psdev:-$psrc}" # if configured, skip device to ignore incorrect battery status if wordinlist "BAT" "$ps_ignore"; then echo_debug "ps" "get_sys_power_supply(${psrc_name}).bat_ignored: syspwr=$_syspwr" continue fi # check battery status bs="$(read_sysf "$psrc/status")" if [ "$bs" != "Discharging" ] && [ "$tlp_cmd" = "auto" ] && [ -z "$wait" ]; then # if command is 'tlp auto', not "Discharging" might be caused by lagging battery status updates # --> recheck every 0.1 secs for 1.5 secs (or user value in deciseconds) max # use delay loop only once wait="$X_PS_WAIT_DS" is_uint "$wait" 2 || wait=15 echo_debug "ps" "get_sys_power_supply(${psrc_name}).bat_not_discharging_recheck: bs=$bs; syspwr=$_syspwr; wait=$wait" while [ "$wait" -gt 0 ]; do sleep 0.1 wait=$((wait - 1)) bs="$(read_sysf "$psrc/status")" [ "$bs" = "Discharging" ] && break done fi case "$bs" in Discharging) if ! lockpeek_tlp tlp_discharge; then # battery status "Discharging" means battery mode ... _syspwr=$PS_BAT echo_debug "ps" "get_sys_power_supply(${psrc_name}).bat_discharging: syspwr=$_syspwr; wait=$wait" else # ... unless forced discharge is in progress, which means AC _syspwr=$PS_AC echo_debug "ps" "get_sys_power_supply(${psrc_name}).forced_discharge: syspwr=$_syspwr; wait=$wait" fi break # --> end iteration ;; *) # assume AC mode for everything else, e.g. "Charging", "Full", "Not charging", "Unknown" # --> continue looking because there may be multiple batteries _syspwr=$PS_AC echo_debug "ps" "get_sys_power_supply(${psrc_name}).bat_not_discharging: bs=$bs; syspwr=$_syspwr; wait=$wait" ;; esac ;; *) # unknown power source type --> ignore ;; esac done if [ -z "$_syspwr" ]; then # _syspwr result yet undecided if [ "$ac0seen" = "1" ]; then # AC offline remembered --> battery mode _syspwr=$PS_BAT echo_debug "ps" "get_sys_power_supply(${ac0seen##/*/}).ac_offline_remembered: syspwr=$_syspwr" else # we have seen neither a AC nor a battery power source --> unknown mode _syspwr=$PS_UNKNOWN echo_debug "ps" "get_sys_power_supply.none_found: syspwr=$_syspwr" fi fi return "$_syspwr" } check_ac_power () { # check if ac power connected -- $1: function if ! get_sys_power_supply ; then echo_debug "bat" "check_ac_power($1).no_ac_power" cecho "Error: $1 is possible on AC power only." 1>&2 return 1 fi return 0 } # --- TLP Profiles id2pp () { # convert power profile id to code # $1: id: PRF/AC/BAL/BAT/SAV case "$(toupper "$1")" in PRF|AC) printf "%s" "$PP_PRF" ;; BAL|BAT) printf "%s" "$PP_BAL" ;; SAV) printf "%s" "$PP_SAV" ;; *) printf "%s" "$PP_NONE" ;; esac } set_manual_mode () { # set manual profile # $1: profile code: PP_PRF=0/PP_BAL=1/PP_SAV=2 # rc: 0=ok/1=error if ! wordinlist "$1" "$PP_USER"; then echo_debug "ps" "set_manual_mode($1).invalid" return 1 fi create_rundir if write_sysf "$1" $MANUALMODEFILE; then echo_debug "ps" "set_manual_mode($1).ok" return 0 else echo_debug "ps" "set_manual_mode($1).write_error" return 1 fi } clear_manual_mode () { # remove manual profile rm -f $MANUALMODEFILE 2> /dev/null echo_debug "ps" "clear_manual_mode" return 0 } get_manual_mode () { # print saved manual profile code: PP_PRF=0/PP_BAL=1/PP_SAV=2/PP_NONE=255 # rc: 0=active/1=inactive local rc=1 local man_pp="$PP_NONE" if [ -f "$MANUALMODEFILE" ]; then # read mode file if man_pp="$(read_sysf "$MANUALMODEFILE")"; then if wordinlist "$man_pp" "$PP_USER"; then printf "%s" "$man_pp" rc=0 fi else # cannot read mode file - possible cause: # tlp-stat -s is running without root privilege and the file is not world readable # because tlp ac/bat is run with sudo when creating the file and thereby a # restrictive umask applies; see https://github.com/linrunner/TLP/issues/702 # -> manual mode is active but operation mode cannot be determined printf "%s" "$PP_NONE" rc=0 fi fi return $rc } pp2str () { # convert profile code to string # $1: profile: PP_PRF=0/PP_BAL=1/PP_SAV=2 case "$1" in "$PP_PRF") printf "performance" ;; "$PP_BAL") printf "balanced" ;; "$PP_SAV") printf "power-saver" ;; *) printf "unknown" ;; esac } pp2strx () { # convert profile code to string plus config suffix # $1: profile: PP_PRF=0/PP_BAL=1/PP_SAV=2 pp2str "$1" case "$1" in "$PP_PRF") printf "/AC" ;; "$PP_BAL") printf "/BAT" ;; "$PP_SAV") printf "/SAV" ;; esac } read_saved_power_profile () { # get saved profile # retval: $_pp_last (PP_PRF=0, PP_BAL=1, PP_SAV=2) # $_ps_last (PS_AC=0, PS_BAT=1, PS_UNKNOWN=128) # read last saved profile and power source from statefile if [ ! -f $PWRRUNFILE ]; then # file non-existent _pp_last="$PP_NONE" _ps_last="$PS_UNKNOWN" else read -r _pp_last _ps_last < $PWRRUNFILE 2> /dev/null fi # intercept invalid saved states case "$_pp_last" in "$PP_PRF"|"$PP_BAL"|"$PP_SAV") ;; "") # nothing echo_debug "ps" "read_saved_power_profile.no_pp" _pp_last="$PP_NONE" ;; *) # invalid echo_debug "ps" "read_saved_power_profile.invalid_pp: saved=$_pp_last" _pp_last="$PP_NONE" ;; esac case "$_ps_last" in "$PS_AC"|"$PS_BAT"|"$PS_UNKNOWN") # valid ;; "") # nothing echo_debug "ps" "read_saved_power_profile.no_ps" _ps_last="$PS_UNKNOWN" ;; *) # invalid echo_debug "ps" "read_saved_power_profile.invalid_ps: saved=$_ps_last" _ps_last="$PS_UNKNOWN" ;; esac return 0 } assigned_profile () { # print profile code assigned to the given or current power source # global param: $_syspwr # $1: power source: PS_AC=0/PS_BAT=1/PS_UNKNOWN=128 # $2: 1=quiet (no debug log) # rc: PP_PRF=0/PP_BAL=1/PP_SAV=2 # retval: $_pp_def_mem: PP_PRF=0/PP_BAL=1/PP_SAV=2 local ps="${1:-$_syspwr}" local quiet="${2:-0}" local ap case "$ps" in "$PS_AC") ap="$(id2pp "$TLP_PROFILE_AC")" if [ "$ap" = "$PP_NONE" ]; then # borked config --> substitute default or performance profile ap="$(id2pp "${TLP_PROFILE_DEFAULT:-PRF}")" fi ;; "$PS_BAT") ap="$(id2pp "$TLP_PROFILE_BAT")" if [ "$ap" = "$PP_NONE" ]; then # borked config --> substitute default or balanced profile ap="$(id2pp "${TLP_PROFILE_DEFAULT:-BAL}")" fi ;; *) # unknown power source --> substitute default profile ap="$(id2pp "$TLP_PROFILE_DEFAULT")" if [ "$ap" = "$PP_NONE" ]; then # no default profile --> check machine type if is_laptop; then # laptop: use battery profile ap="$(id2pp "$TLP_PROFILE_BAT")" # borked config --> substitute balanced profile [ "$ap" = "$PP_NONE" ] && ap="$PP_BAL" else # desktop/other: use AC profile ap="$(id2pp "$TLP_PROFILE_AC")" # borked config --> substitute performance profile [ "$ap" = "$PP_NONE" ] && ap="$PP_PRF" fi else _pp_def_mem="$ap" fi ;; esac printf "%s" "$ap" [ "$quiet" = "0" ] && echo_debug "ps" "assigned_profile($1): ps=$ps; def=$_pp_def_mem; ap=$ap" return "$ap" } get_next_power_profile () { # determine next profile: either # - depending on the tlp command, # - actual power source or # - default/persistence setting # $1: tlp command # retval: $_pp_next, $_pp_last, $_pp_def_mem: PP_PRF=0/PP_BAL=1/PP_SAV=2 # rc: same as $_pp_next # like get_sys_power_supply(), plus: # - maps unknown power source to TLP_PROFILE_DEFAULT # - if TLP_PROFILE_DEFAULT is unconfigured and power source is unknown, # returns PP_BAL for laptop / PP_PRF for desktop/other # - considers manual mode if not 'tlp start' # determine current power supply get_sys_power_supply "$tlp_cmd" # determine next profile based on command read_saved_power_profile local tlp_cmd="$1" local neutral=0 # shellcheck disable=SC2155 local def_pp="$(id2pp "$TLP_PROFILE_DEFAULT")" local man_pp="" local pers=0 _pp_next="$PP_NONE" _pp_def_mem="$PP_NONE" if [ "$TLP_PERSISTENT_DEFAULT" = "1" ]; then # persistent profile pers=1 _pp_next="$def_pp" _pp_def_mem="$def_pp" else # non-persistent mode case "$tlp_cmd" in init|start) case "$TLP_AUTO_SWITCH" in 0) # switching disabled --> default profile _pp_next="$def_pp" _pp_def_mem="$def_pp" ;; 1|2) # auto and smart switching --> assigned profile _pp_next="$(assigned_profile)" ;; *) # borked config --> default profile _pp_next="$def_pp" _pp_def_mem="$def_pp" ;; esac clear_manual_mode ;; auto|resume) if man_pp="$(get_manual_mode)"; then # manual profile _pp_next="$man_pp" else case "$TLP_AUTO_SWITCH" in 0) # switching disabled --> keep profile _pp_next=$_pp_last ;; 1) # auto switching if [ "$_syspwr" = "$PS_UNKNOWN" ]; then # power source unknown --> default profile _pp_next="$(assigned_profile)" elif [ "$_syspwr" != "$_ps_last" ]; then # power source has changed --> assigned profile _pp_next="$(assigned_profile)" else # power source has not changed --> keep profile # note: ignores meaningless power_supply events triggered by unplugging USB-C (disk) devices _pp_next=$_pp_last fi ;; 2) # smart switching if [ "$_syspwr" = "$PS_UNKNOWN" ]; then # power source unknown --> default profile _pp_next="$(assigned_profile)" elif [ "$_syspwr" != "$_ps_last" ]; then # power source has changed case "$_syspwr" in "$PS_AC") if [ "$_pp_last" = "$(assigned_profile "$PS_BAT" 1)" ]; then # last profile matches the battery default --> assigned AC profile _pp_next="$(assigned_profile "$PS_AC")" else # last profile does not match the battery default --> keep profile _pp_next="$_pp_last" fi ;; "$PS_BAT") if [ "$_pp_last" = "$(assigned_profile "$PS_AC" 1)" ]; then # last profile matches the AC default --> assigned battery profile _pp_next="$(assigned_profile "$PS_BAT")" else # last profile does not match the AC default --> keep profile _pp_next="$_pp_last" fi ;; esac else # power source has not changed --> keep profile # note: ignores meaningless power_supply events triggered by unplugging USB-C (disk) devices _pp_next=$_pp_last fi ;; *) # borked config --> keep profile _pp_next=$_pp_last ;; esac # TLP_AUTO_SWITCH fi # not manual mode ;; bat) _pp_next="$(assigned_profile "$PS_BAT")" set_manual_mode "$_pp_next" man_pp="$_pp_next" ;; ac) _pp_next="$(assigned_profile "$PS_AC")" set_manual_mode "$_pp_next" man_pp="$_pp_next" ;; power-saver) _pp_next="$PP_SAV" clear_manual_mode man_pp="" ;; balanced) _pp_next="$PP_BAL" clear_manual_mode man_pp="" ;; performance) _pp_next="$PP_PRF" clear_manual_mode man_pp="" ;; *) # all other tlp commands and *tlp-usb-udev disk* that do not touch the power profile neutral=1 if man_pp="$(get_manual_mode)"; then # manual profile _pp_next="$man_pp" else # use previous profile # note: this can even be $PP_NONE if 'tlp init start' has not yet been run, i.e. no last_pwr exists _pp_next=$_pp_last fi ;; esac # $tlp_cmd fi # non-persistent profile if [ "$neutral" != "1" ] && [ "$_pp_next" = "$PP_NONE" ]; then # catch all: non-neutral command and profile still undefined # -> try assigned profile as last resort _pp_next="$(assigned_profile)" fi # still undefined if [ "$_pp_next" != "$_pp_last" ]; then echo_debug "ps" "get_next_power_profile($tlp_cmd).change: autosw=$TLP_AUTO_SWITCH; def=$def_pp; man=$man_pp; pers=$pers; ps_last=$_ps_last; pp_last=$_pp_last; pp_next=$_pp_next" else echo_debug "ps" "get_next_power_profile($tlp_cmd).keep: autosw=$TLP_AUTO_SWITCH; def=$def_pp; man=$man_pp; pers=$pers; ps_last=$_ps_last; pp_last=$_pp_last; pp_next=$_pp_next" fi return "$_pp_next" } notify_profile_daemon () { # Notify tlp-pd of a profile change via D-Bus # $1: profile id local autosw case "$TLP_AUTO_SWITCH" in 1|2) autosw="true" ;; *) autosw="false" ;; esac if [ -n "$1" ]; then if cmd_exists "$GDBUS"; then $GDBUS call -y -d $TLPPD_BUS_NAME -o $TLPPD_OBJECT_PATH -m "${TLPPD_INTERFACE_NAME}.SyncProfile" "$1" "$autosw" 2> /dev/null 1>&2 echo_debug "run" "notify_profile_daemon.gdbus.call.SyncProfile($1): rc=$?" else echo_debug "run" "notify_profile_daemon($1).gdbus_missing" fi else echo_debug "run" "notify_profile_daemon($1).no_profile" fi } has_power_profile_changed () { # compare $_pp_next to $_pp_last, save $_pp_next when different # rc: 0=different, 1=equal # global params: $_pp_last, $_pp_next, $_syspwr # save new state to # - record power source for smart switching decision # - record time of last run create_rundir write_sysf "$_pp_next $_syspwr" $PWRRUNFILE if [ -z "$_pp_last" ] || [ "$_pp_next" != "$_pp_last" ]; then # profile changes (including the edge case that previous profile is unknown) echo_debug "ps" "has_power_profile_changed.yes: last=$_pp_last; next=$_pp_next; syspwr=$_syspwr" return 0 else # no profile change echo_debug "ps" "has_power_profile_changed.no: next=$_pp_next; syspwr=$_syspwr" return 1 fi } clear_saved_power_profile () { # remove last saved profile rm -f $PWRRUNFILE 2> /dev/null return 0 } echo_started_profile () { # announce profile and auto/manual mode after start # $1: profile: PP_PRF=0/PP_BAL=1/PP_SAV=2 # $2: tlp command printf "TLP started using profile %s" "$(pp2strx "$1")" if get_manual_mode > /dev/null; then printf " (manual).\n" elif [ "$_pp_def_mem" = "$1" ]; then printf " (default).\n" elif [ "$2" = "start" ]; then printf " (auto).\n" else printf ".\n" fi return 0 } TLP-1.10.1/tlp-pcilist000066400000000000000000000051471517565574500144530ustar00rootroot00000000000000#!/usr/bin/perl # tlp-pcilist - list pci devices with runtime pm mode and device class # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Cmdline options # --verbose: show Runtime PM device status package tlp_pcilist; use strict; use warnings; # --- Modules use Getopt::Long; # --- Global vars my $verbose = 0; # --- Subroutines # Read content from a sysfile # $_[0]: input file # return: content / empty string if nonexistent or not readable sub catsysf { my $fname = "$_[0]"; my $sysval = ""; if (open my $sysf, "<", $fname) { chomp ($sysval = <$sysf>); close $sysf; } return $sysval; } # Read device driver from DEVICE/uevent # $_[0]: (sub)device base path # return: driver / empty string if uevent nonexistent or not readable sub getdriver { my $dpath = "$_[0]"; my $driver = ""; if ( open (my $sysf, "<", $dpath . "/uevent") ) { # read file line by line while (<$sysf>) { # match line content and return DRIVER= value if ( s/^DRIVER=(.*)/$1/ ) { chomp ($driver = $_); last; # break loop } } close ($sysf); } return $driver } # --- MAIN # parse arguments GetOptions ('verbose' => \$verbose); # Output device list with Runtime PM mode, status and device class foreach (`lspci -m`) { # parse lspci output: get short PCI(e) id and long description of device my ($dev, $classdesc) = /(\S+) \"(.+?)\"/; # join device path my $devp = "/sys/bus/pci/devices/0000:$dev"; # control file for Runtime PM my $devc = "$devp/power/control"; # status file for Runtime PM my $devs = "$devp/power/runtime_status"; # get device class my $class = catsysf ("$devp/class"); # get device driver my $driver = getdriver ("$devp") || "no driver"; if (-f $devc) { # control file exists # get device mode my $pmode = catsysf ("$devc"); if ( $verbose ) { # get device status my $pstatus = catsysf ("$devs"); # output device mode, status and data printf "%s/power/control = %-4s, runtime_status = %-9s (%s, %s, %s)\n", $devp, $pmode, $pstatus, $class, $classdesc, $driver; } else { # output device mode and data printf "%s/power/control = %-4s (%s, %s, %s)\n", $devp, $pmode, $class, $classdesc, $driver; } } else { # control file missing --> output device data only printf "%s/power/control = (not available) (%s, %s, %s)\n", $devp, $class, $classdesc, $driver; } } exit 0; TLP-1.10.1/tlp-pd.dbus.conf000066400000000000000000000014101517565574500152540ustar00rootroot00000000000000 TLP-1.10.1/tlp-pd.dbus.service000066400000000000000000000001301517565574500157650ustar00rootroot00000000000000[D-BUS Service] Name=@BUS_NAME@ Exec=/bin/false User=root SystemdService=tlp-pd.service TLP-1.10.1/tlp-pd.in000066400000000000000000000677571517565574500140330ustar00rootroot00000000000000#!/usr/bin/python3 # TLP Profiles Daemon (tlp-pd) # tlp-pd implements the D-Bus interface org.freedesktop.UPower.PowerProfile # which lets desktop environments show a profile switch. # TLP is used as the backend to apply these profiles. # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later import argparse # noqa: I001 from gi.repository import Gio, GLib import inspect import logging import logging.handlers import os import secrets import signal import string import subprocess import sys from syslog import LOG_DAEMON from typing import Dict, List, Optional import warnings # --- Constants DAEMON_NAME = "TLP Profiles Daemon" AVAILABLE_PROFILES = ["power-saver", "balanced", "performance"] PROFILE_MAXLEN = 12 MAX_HOLDS = 16 COOKIE_ATTEMPTS = 10 MAX_UINT32 = 0xFFFFFFFF # --- D-Bus constants BUS_NAME = "org.freedesktop.UPower.PowerProfiles" INTERFACE_NAME = BUS_NAME OBJECT_PATH = "/org/freedesktop/UPower/PowerProfiles" BUS_NAME_LEGACY = "net.hadess.PowerProfiles" INTERFACE_NAME_LEGACY = BUS_NAME_LEGACY OBJECT_PATH_LEGACY = "/net/hadess/PowerProfiles" INTERFACE_PROPERTIES = "org.freedesktop.DBus.Properties" NAME_MAXLEN = 255 # --- TLP constants TLP_LAST_PROFILE = "@TLP_RUN@/last_pwr" TLP_PROFILE_IDX = ["2", "1", "0"] # --- Introspection XML INTROSPECTION_XML = """ """ INTROSPECTION_XML_LEGACY = """ """ # --- D-Bus service implementing the power-profiles-daemon API class ProfilesDaemon: def __init__(self, logger: logging.Logger, loglevel: str) -> None: # Initialize the profiles daemon self._logger = logger self._loglevel = loglevel self._ifaces_seen: set[str] = set() self._connection: Gio.DBusConnection = None self._registration_ids: list[int] = [] self._owner_ids: list[int] = [] # Internal state self._active_profile = self._get_tlp_profile() # Last profile applied by TLP self._selected_profile = ( self._active_profile ) # Last profile received by Set(ActiveProfile) self._profiles = [ { "Profile": GLib.Variant("s", profile), "CpuDriver": GLib.Variant("s", "tlp"), "PlatformDriver": GLib.Variant("s", "tlp"), } for profile in AVAILABLE_PROFILES ] self._performance_degraded = "" self._actions: list[str] = [] self._actions_info = [] self._version = "tlp-pd @TLPVER@" self._battery_aware = True self._holds = {} # cookie -> {profile, reason, application_id, sender} # Parse introspection data self._node_info = Gio.DBusNodeInfo.new_for_xml(INTROSPECTION_XML) self._node_info_legacy = Gio.DBusNodeInfo.new_for_xml(INTROSPECTION_XML_LEGACY) def start(self) -> None: # Connect to system bus and register objects self._connection = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) ### FIXME: Ignore premature deprecation warnings for Gio.DBusConnection.register_object() ### FIXME: Replace with register_object_with-closure2() once GLib 2.84 is generally available warnings.filterwarnings("ignore", category=DeprecationWarning) # Register main interface self._registration_ids.append( self._connection.register_object( OBJECT_PATH, self._node_info.interfaces[0], self._handle_method_call, self._get_property, self._set_property, ) ) # Register legacy interface self._registration_ids.append( # self._connection.register_object( OBJECT_PATH_LEGACY, self._node_info_legacy.interfaces[0], self._handle_method_call, self._get_property, self._set_property, ) ) warnings.filterwarnings("default", category=DeprecationWarning) # Own bus names self._owner_ids.append( Gio.bus_own_name( Gio.BusType.SYSTEM, BUS_NAME, Gio.BusNameOwnerFlags.NONE, None, None, None, ) ) self._owner_ids.append( Gio.bus_own_name( Gio.BusType.SYSTEM, BUS_NAME_LEGACY, Gio.BusNameOwnerFlags.NONE, None, None, None, ) ) def _handle_method_call( self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, method_name: str, parameters: GLib.Variant, invocation: Gio.DBusMethodInvocation, ) -> None: # Handle method calls on main or legacy interface _logtag = f"{_method_name()}" try: # Handle main and legacy interface methods if method_name == "HoldProfile": profile, reason, application_id = parameters.unpack() result = self._method_hold_profile( profile, reason, application_id, sender, interface_name ) invocation.return_value(GLib.Variant("(u)", (result,))) elif method_name == "ReleaseProfile": cookie = parameters.unpack()[0] self._method_release_profile(cookie, sender) invocation.return_value(None) elif method_name == "SetActionEnabled": action, enabled = parameters.unpack() self._method_set_action_enabled(action, enabled) invocation.return_value(None) elif method_name == "SyncProfile": profile, autosw = parameters.unpack() self._method_sync_profile(profile, autosw, sender) invocation.return_value(None) else: invocation.return_error_literal( Gio.dbus_error_quark(), Gio.DBusError.UNKNOWN_METHOD, f"{_logtag}: Unknown method '{method_name}'", ) except GLib.Error as e: self._logger.error(f"{_logtag}/{e.message}") invocation.return_error_literal(Gio.dbus_error_quark(), e.code, e.message) except Exception as e: self._logger.error(f"{_logtag}/{e.message}") invocation.return_error_literal(Gio.dbus_error_quark(), e.code, e.message) # --- Method implementations def _method_hold_profile( self, profile: str, reason: str, application_id: str, requester: str, req_iface: str, ) -> int: # Hold a new profile profile = _sanitize_input(profile, trunc_len=PROFILE_MAXLEN) reason = _sanitize_input(reason) application_id = _sanitize_input(application_id) # Note: requester and req_iface are already sanitized by _handle_method_call _logtag = f"{_method_name()}(profile='{profile}', reason='{reason}', appid='{application_id}', req='{requester}', iface='{req_iface}')" if profile not in AVAILABLE_PROFILES: raise GLib.Error( code=Gio.DBusError.INVALID_ARGS, message=f"{_logtag}: Invalid profile '{profile}'", ) # Check authorization if not self._check_polkit_auth(requester, f"{BUS_NAME}.hold-profile"): raise GLib.Error( code=Gio.DBusError.AUTH_FAILED, message=f"{_logtag}: Not authorized to hold profile", ) if len(self._holds) >= MAX_HOLDS: raise GLib.Error( code=Gio.DBusError.FAILED, message=f"{_logtag}: Maximum number of simultaneous holds ({MAX_HOLDS}) is used up", ) # Create unique uint32 cookie for _ in range(1, COOKIE_ATTEMPTS): cookie = secrets.randbits(32) if cookie not in self._holds: self._holds[cookie] = { "profile": profile, "reason": reason, "application_id": application_id, "requester": requester, "interface": req_iface, } break else: raise GLib.Error( code=Gio.DBusError.FAILED, message=f"{_logtag}: Failed to create random cookie after {COOKIE_ATTEMPTS} attempts", ) self._logger.debug(f"{_logtag}: cookie: '{cookie}'") # Update active profile based on holds self._update_profile_from_holds() return cookie def _method_release_profile(self, cookie: int, sender: str) -> None: # Release a profile hold # Note: sender is already sanitized by _handle_method_call scookie = _sanitize_uint32(str(cookie)) _logtag = f"{_method_name()}(scookie='{scookie}', sender='{sender}')" if scookie < 0: raise GLib.Error( code=Gio.DBusError.INVALID_ARGS, message=f"{_logtag}: Invalid cookie '{scookie}'", ) # Check authorization if not self._check_polkit_auth(sender, f"{BUS_NAME}.release-profile"): raise GLib.Error( code=Gio.DBusError.AUTH_FAILED, message=f"{_logtag}: Not authorized to release profile", ) if scookie not in self._holds: raise GLib.Error( code=Gio.DBusError.INVALID_ARGS, message=f"{_logtag}: Unknown cookie '{scookie}'", ) hold_rel = self._holds.pop(scookie) self._logger.debug( f"{_logtag}: hold: '{hold_rel['profile']}', appid: '{hold_rel['application_id']}'" ) # Update active profile based on remaining holds self._update_profile_from_holds() def _method_set_action_enabled(self, action: str, enabled: bool) -> None: # Set a particular action to be enabled or disabled _logtag = f"{_method_name()}()" action = _sanitize_input(action) # No actions supported raise GLib.Error( code=Gio.DBusError.NOT_SUPPORTED, message=f"{_logtag}: No such action '{action}'", ) def _method_sync_profile(self, profile: str, autosw: bool, sender: str) -> None: # Callback for TLP after profile changes # Note: sender is already sanitized by _handle_method_call profile = _sanitize_input(profile, trunc_len=PROFILE_MAXLEN) _logtag = f"{_method_name()}(profile='{profile}', sender='{sender}')" if profile not in AVAILABLE_PROFILES: self._logger.error(f"{_logtag}: Invalid profile '{profile}'") return if not self._check_polkit_auth(sender, f"{BUS_NAME}.sync-profile"): raise GLib.Error( code=Gio.DBusError.AUTH_FAILED, message=f"{_logtag}: Not authorized to sync profile", ) # Update active profile and battery aware flag self._active_profile = profile self._battery_aware = autosw self._logger.debug(f"{_logtag}") # Signal the desktop about the new profile self._signal_properties_changed( {"ActiveProfile": GLib.Variant("s", profile)}, [] ) # --- Property handlers def _get_property( self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, property_name: str, ) -> Optional[GLib.Variant]: # Handle Get() through the main or legacy interface _logtag = f"{_method_name()}(interface='{interface_name}', property='{property_name}', sender='{sender}')" # Remember receiving interface for later use with PropertiesChanged signal self._ifaces_seen.add(interface_name) if property_name == "ActiveProfile": retval = GLib.Variant("s", self._active_profile) elif property_name == "PerformanceInhibited": retval = GLib.Variant("s", "") # Deprecated elif property_name == "PerformanceDegraded": retval = GLib.Variant("s", self._performance_degraded) elif property_name == "Profiles": retval = GLib.Variant("aa{sv}", self._profiles) elif property_name == "Actions": retval = GLib.Variant("as", self._actions) elif property_name == "ActionsInfo": retval = GLib.Variant("aa{sv}", self._actions_info) elif property_name == "ActiveProfileHolds": retval = self._get_active_profile_holds() elif property_name == "Version": retval = GLib.Variant("s", self._version) elif property_name == "BatteryAware": retval = GLib.Variant("b", self._battery_aware) elif property_name == "LogLevel": retval = GLib.Variant("s", self._loglevel) else: raise GLib.Error( code=Gio.DBusError.UNKNOWN_PROPERTY, message=f"{_logtag}: Unknown property '{property_name}'", ) self._logger.debug(f"{_logtag}: {retval}") return retval def _set_property( self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, property_name: str, value: GLib.Variant, ) -> bool: # Handle Set() through the main or legacy interface _logtag = f"{_method_name()}(interface='{interface_name}', property='{property_name}', value={value}, sender='{sender}')" # Remember receiving interface for later use with PropertiesChanged signal self._ifaces_seen.add(interface_name) if property_name == "ActiveProfile": profile = _sanitize_input(value.get_string(), trunc_len=PROFILE_MAXLEN) # Check authorization if not self._check_polkit_auth(sender, f"{BUS_NAME}.switch-profile"): self._logger.error(f"{_logtag}: Not authorized to switch profile") return False # User (manually) changes profile self._drop_all_holds() if not self._apply_profile_with_tlp(profile): return False self._selected_profile = profile # Note: ActiveProfile is signaled in _method_sync_profile() after TLP applied the profile elif property_name == "BatteryAware": # BatteryAware is determined internally by TLP_AUTO_SWITCH and therefore # read-only via the D-Bus property API # Silently discard property change so as not to startle other API users self._logger.error( f"{_logtag}: Property BatteryAware can only be changed via TLP_AUTO_SWITCH" ) return True elif property_name == "LogLevel": # Check authorization if not self._check_polkit_auth(sender, f"{BUS_NAME}.set-loglevel"): self._logger.error(f"{_logtag}: Not authorized to set loglevel") return False loglevel = _sanitize_input(value.get_string(), trunc_len=5) if loglevel == "debug": self._logger.setLevel(logging.DEBUG) self._loglevel = "debug" else: self._logger.setLevel(logging.INFO) self._loglevel = "info" self._signal_properties_changed({property_name: value}, []) else: raise GLib.Error( code=Gio.DBusError.UNKNOWN_PROPERTY, message=f"{_logtag}: Property '{property_name}' is readonly or unknown", ) self._logger.debug(f"{_logtag}: ok") return True def _signal_properties_changed( self, changed_properties: Dict[str, GLib.Variant], invalidated: List[str] = [], ) -> None: # Emit PropertiesChanged signal on noted interfaces _logtag = f"{_method_name()}(changed_properties={changed_properties}, invalidated={invalidated})" for iface in self._ifaces_seen: # Determine which object path to use if iface == INTERFACE_NAME_LEGACY: object_path = OBJECT_PATH_LEGACY else: object_path = OBJECT_PATH if not self._connection.emit_signal( None, # destination object_path, INTERFACE_PROPERTIES, "PropertiesChanged", GLib.Variant("(sa{sv}as)", (iface, changed_properties, invalidated)), ): self._logger.warning( f"{_logtag}: Failed to emit PropertiesChanged signal on {iface}" ) else: self._logger.debug( f"{_logtag}: PropertiesChanged signal emitted on {iface}" ) def _get_active_profile_holds(self) -> GLib.Variant: # Get list of active profile holds active_holds = [] for cookie, hold_info in self._holds.items(): active_holds.append( { "ApplicationId": GLib.Variant("s", hold_info["application_id"]), "Profile": GLib.Variant("s", hold_info["profile"]), "Reason": GLib.Variant("s", hold_info["reason"]), } ) return GLib.Variant("aa{sv}", active_holds) # --- TLP helpers def _apply_profile_with_tlp(self, profile: str) -> bool: # Call TLP to apply the new profile _logtag = f"{_method_name()}()" if profile not in AVAILABLE_PROFILES: self._logger.error(f"{_logtag}: Invalid profile '{profile}'") return False self._logger.info(f"Run detached TLP to apply profile '{profile}'") _ = subprocess.Popen( ["tlp", f"{profile}"], start_new_session=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True, ) return True def _get_tlp_profile(self) -> str: # Read TLP statefile and translate into the last profile applied _logtag = f"{_method_name()}()" try: with open(TLP_LAST_PROFILE, "r") as f: last = f.readline().strip().split() last_pp = last[0] if len(last) > 0 else "" except (FileNotFoundError, IOError) as errmsg: self._logger.info( f"{_logtag}: {errmsg} --> TLP may not have applied a profile yet." ) return "balanced" try: idx = TLP_PROFILE_IDX.index(last_pp) except ValueError: self._logger.warning(f"{_logtag}: Unknown TLP profile '{last_pp}'") return "balanced" tlp_profile = AVAILABLE_PROFILES[idx] self._logger.debug(f"{_logtag}: Current TLP profile is '{tlp_profile}'") return tlp_profile # --- PolicyKit helper def _check_polkit_auth(self, sender: str, action_id: str) -> bool: # Check if the D-Bus sender is authorized for the PolicyKit action_id _logtag = f"{_method_name()}(sender='{sender}', action_id='{action_id}')" try: # Get proxy to PolicyKit authority proxy = Gio.DBusProxy.new_sync( self._connection, Gio.DBusProxyFlags.NONE, None, "org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", "org.freedesktop.PolicyKit1.Authority", None, ) # Call PolicyKit CheckAuthorization() result = proxy.call_sync( "CheckAuthorization", GLib.Variant( "((sa{sv})sa{ss}us)", ( ("system-bus-name", {"name": GLib.Variant("s", sender)}), action_id, {}, # No details 0x1, # AllowUserInteraction flag "", # No cancellation_id ), ), Gio.DBusCallFlags.NONE, -1, None, ) # Extract is_authorized from result is_authorized = result.unpack()[0][0] self._logger.debug(f"{_logtag}: {is_authorized} {result}") return bool(is_authorized) except GLib.Error as e: self._logger.error(f"{_logtag}: PolicyKit error: {e}") return False def _update_profile_from_holds(self) -> None: # Change active profile to the last hold or revert to user selection if len(self._holds) == 0: self._logger.info( f"Changing active profile: from hold '{self._active_profile}' to last user selection '{self._selected_profile}'" ) self._apply_profile_with_tlp(self._selected_profile) else: # Apply profile from the last hold last_cookie_key = list(self._holds)[-1] new_profile = self._holds[last_cookie_key]["profile"] new_appid = self._holds[last_cookie_key]["application_id"] old_profile = self._active_profile if new_profile != old_profile: self._logger.info( f"Changing active profile: from hold '{old_profile}' to '{new_profile}' (appid: '{new_appid}')'" ) self._apply_profile_with_tlp(new_profile) else: self._logger.info( f"Keeping active profile: hold '{new_profile}' (appid: '{new_appid}')" ) def _drop_all_holds(self) -> None: # Release all holds and notify holders _logtag = f"{_method_name()}()" self._logger.debug(f"{_logtag}") cookies2release = list(self._holds.keys()) for cookie in cookies2release: hold2release = self._holds.pop(cookie) self._logger.info( f"Auto-releasing hold: profile '{hold2release['profile']}' (appid: '{hold2release['application_id']}')'" ) if hold2release["interface"] == INTERFACE_NAME_LEGACY: object_path = OBJECT_PATH_LEGACY else: object_path = OBJECT_PATH # Emit ProfileReleased signal if not self._connection.emit_signal( hold2release["requester"], object_path, hold2release["interface"], "ProfileReleased", GLib.Variant("(u)", (cookie,)), ): self._logger.warning( f"{_logtag}: Failed to emit ProfileReleased signal to {hold2release['requester']} on {hold2release['interface']}" ) else: self._logger.debug( f"{_logtag}: PropertiesChanged signal emitted to {hold2release['requester']} on {hold2release['interface']}" ) # --- Helper functions def _method_name() -> str: # Return method name of caller return inspect.currentframe().f_back.f_code.co_name # type: ignore[reportOptionalMemberAccess] # --- Input sanitation INPUT_ALLOWED_CHARS = ( string.ascii_letters + string.digits + " !@'+-.,/:;_$&*()%<=>?#[]{|}^~" + '"' ) def _sanitize_input( input_str: str, sani_ch: str = "§", trunc_len: int = NAME_MAXLEN ) -> str: # Replace any character not in INPUT_ALLOWED_CHARS with sani_char # Truncate input to trunc_len chars (default is 255, the max length of D-Bus names) input_str = input_str[:trunc_len] result_str = "" for ch in input_str: if ch in INPUT_ALLOWED_CHARS: result_str += ch else: result_str += sani_ch return result_str def _sanitize_uint32(input: str) -> int: if not input.isdigit(): return -1 input_int = int(input) if input_int > MAX_UINT32: return -1 return input_int # --- MAIN def main() -> None: # Parse arguments parser = argparse.ArgumentParser(description=f"{DAEMON_NAME}") _ = parser.add_argument( "--debug", "-D", action="store_true", help="log debugging messages" ) args = parser.parse_args() # Check root privileges if os.geteuid() != 0: print( f"Error: root privileges are required to run the {DAEMON_NAME}.", file=sys.stderr, ) sys.exit(1) # Automatically reap tlp zombie subprocesses signal.signal(signal.SIGCHLD, signal.SIG_IGN) # --- Set up logging logger = logging.getLogger(__name__) handler = logging.handlers.SysLogHandler(address="/dev/log", facility=LOG_DAEMON) logger.addHandler(handler) # Set syslog identifier "tlp-pd" formatter = logging.Formatter(f"{os.path.basename(__file__)}: %(message)s") handler.setFormatter(formatter) # Set syslog level if args.debug: logger.setLevel(logging.DEBUG) loglevel = "debug" else: logger.setLevel(logging.INFO) loglevel = "info" # --- Create daemon instance daemon = ProfilesDaemon(logger, loglevel) daemon.start() logger.info(f"{DAEMON_NAME} started.") logger.info(f"Interfaces: {BUS_NAME}, {BUS_NAME_LEGACY}") logger.info(f"Object path: {OBJECT_PATH}") logger.info(f"Arguments: {vars(args)}") logger.info(f"Loglevel: {loglevel}") logger.info(f"Initial profile: {daemon._active_profile}") # --- Run main loop try: mainloop = GLib.MainLoop() mainloop.run() except KeyboardInterrupt: logger.info(f"{DAEMON_NAME} shutting down ...") if __name__ == "__main__": main() TLP-1.10.1/tlp-pd.policy000066400000000000000000000051011517565574500146730ustar00rootroot00000000000000 tlp-pd https://github.com/linrunner/TLP/ Switch Power Profile Privileges are required to switch profiles. no no yes Hold Power Profile Privileges are required to hold profiles. no no yes Hold Power Profile Privileges are required to hold profiles. no no yes Configure action Privileges are required to configure battery-awareness. no no yes Backend communicates a profile change. Privileges are required for the notification. auth_admin auth_admin auth_admin Configure action Privileges are required to configure loglevel. auth_admin auth_admin auth_admin TLP-1.10.1/tlp-pd.service.in000066400000000000000000000011331517565574500154420ustar00rootroot00000000000000[Unit] Description=TLP profiles daemon API service After=multi-user.target display-manager.target Documentation=https://linrunner.de/tlp [Service] Type=dbus BusName=org.freedesktop.UPower.PowerProfiles BusName=net.hadess.PowerProfiles ExecStart=@TLP_SBIN@/tlp-pd Restart=on-failure # Lockdown LockPersonality=yes KeyringMode=private MemoryDenyWriteExecute=yes NoNewPrivileges=yes ProtectClock=yes ProtectControlGroups=yes ProtectHome=yes ProtectHostname=yes ProtectSystem=full ProtectKernelLogs=yes RestrictRealtime=yes RestrictNamespaces=yes RestrictSUIDSGID=yes [Install] WantedBy=graphical.target TLP-1.10.1/tlp-rdw-nm.in000066400000000000000000000165011517565574500146110ustar00rootroot00000000000000#!/bin/sh # tlp-rdw - network manager dispatcher hook: # enable/disable radios on ifup/ifdown # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Source libraries for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/25-tlp-func-rf @TLP_FLIB@/30-tlp-func-rf-sw; do # shellcheck disable=SC1090 . "$lib" || exit 70 done # --- Functions check_switch_lock() { # switch listed radio devices # and time-lock them afterwards if actually switched # $1: device type where the event originated -- do nothing if its time-locked # $2: list of device types to switch # $3: on/off local sw_rc type # quit if the originating *radio* device is time-locked (not LAN) [ "$1" != "LAN" ] && check_timed_lock "${RDW_NM_LOCK}_$1" && return 1 for type in $2; do if [ -n "$type" ] && [ "$type" != "$1" ]; then # device type is valid and not the originating one # --> do switch with state change lock device_switch "$type" "$3" "${RDW_NM_LOCK}_${type}" "$RDW_NM_LOCKTIME"; sw_rc=$? if [ "$sw_rc" = "4" ]; then # switch failed, NetworkManager may be "asleep" -> schedule repeat: # open a detached subshell, wait 2 secs and respawn ourselves, # no more than two repetitions. case "$repeats" in "") repeats="2" ;; 2) repeats="1" ;; *) repeats="" ;; esac if [ -n "$repeats" ]; then echo_debug "nm" "+++ tlp_rdw_nm(${iface}).${action}.nm_seems_asleep: repeats=$repeats" ( sleep 2; $0 "$iface" "$action" "$repeats" < /dev/null > /dev/null ) & do_exit 0 fi fi fi done return 0 } save_iface_type () { # save interface type -- $1: interface; $2: type # rc: 0=saved/1=error [ -d "$NETD/$1" ] && { printf '%s\n' "$2" > "$RUNDIR/${1}.itype"; } 2> /dev/null return $? } get_iface_type () { # get saved interface type -- $1: interface # rc: 0=saved state found/1=not found # retval: $itype local rc itype=$(read_sysf "$RUNDIR/${1}.itype"); rc=$? rm -f "$RUNDIR/${1}.itype" return $rc } echo_env () { # record environment if [ "$X_USB_ENV_TRACE" = "1" ]; then echo_debug "nm" "tlp_rdw_nm.env: $(printenv)" fi } # --- MAIN # shellcheck disable=SC2034 _bgtask=1 # read configuration: quit on error, trace allowed read_config 0 # quit if TLP disabled check_tlp_enabled || do_exit 0 # quit if RDW disabled check_run_flag "$RDW_KILL" && do_exit 0 add_sbin2path # get args iface="$1" action="$2" repeats="$3" itype="" case "$action" in up|down) # interface up/down # quit for invalid interfaces if [ -z "$iface" ] || [ "$iface" = "none" ]; then echo_debug "nm" "tlp_rdw_nm($iface).${action}.no_interface" echo_env do_exit 0 fi # quit for virtual interfaces (up action) if [ "$action" = "up" ] && readlink "$NETD/${iface}" | grep -q '/virtual/'; then # save type for down action where $NETD/$iface won't be there anymore save_iface_type "$iface" virtual echo_debug "nm" "tlp_rdw_nm($iface).${action}.ignore_virtual" echo_env do_exit 0 fi # get saved interface type (down action) if [ "$action" = "down" ]; then get_iface_type "$iface" # quit for virtual interfaces if [ "$itype" = "virtual" ]; then echo_debug "nm" "tlp_rdw_nm($iface).${action}.ignore_virtual" do_exit 0 fi fi echo_debug "nm" "+++ tlp_rdw_nm($iface).$action: repeats=$repeats" echo_env # shellcheck disable=SC2154 if [ -n "$_addpath" ]; then # shellcheck disable=SC2154 echo_debug "path" "PATH=${_oldpath}[${_addpath}]" else # shellcheck disable=SC2154 echo_debug "path" "PATH=${_oldpath}" fi # determine interface type if [ -n "$itype" ]; then # saved type available (down action) echo_debug "nm" "tlp_rdw_nm($iface).${action}: type=$itype [saved]" elif cmd_exists "$NMCLI"; then # no saved type but nmcli is available # --> check if nmcli dev output matches interface itype="$($NMCLI dev | awk '$1 ~ /^'"$iface"'$/ { print $2; }')" if [ -z "$itype" ]; then # iface is not found in nmcli dev output: many WWAN devices have # different devices for control and the actual network connection # --> check if interface matches a WWAN device get_wwan_ifaces # shellcheck disable=SC2154 if wordinlist "$iface" "$_wanifaces"; then itype="wwan" else # fallback: # if interface type detection with nmcli failed, then try to # deduct it using interface name: it can happen if e.g. # usb network card is unplugged case "$iface" in en* | eth*) itype="ethernet" ;; wl*) itype="wifi" ;; ww*) itype="wwan" ;; *) itype="unknown" ;; esac fi fi # save interface type (up action) [ "$action" = "up" ] && save_iface_type "$iface" "$itype" echo_debug "nm" "tlp_rdw_nm($iface).${action}: type=$itype [nmcli]" else # nmcli is not available itype="unknown" echo_debug "nm" "tlp_rdw_nm($iface).${action}: type=$itype [none]" fi case "$action" in up) # interface up, disable configured interfaces case $itype in *ethernet) check_switch_lock LAN "$DEVICES_TO_DISABLE_ON_LAN_CONNECT" off ;; *wireless|wifi) check_switch_lock wifi "$DEVICES_TO_DISABLE_ON_WIFI_CONNECT" off ;; gsm|wwan) check_switch_lock wwan "$DEVICES_TO_DISABLE_ON_WWAN_CONNECT" off ;; esac ;; # up down) # interface down, enable configured interfaces case $itype in *ethernet) check_switch_lock LAN "$DEVICES_TO_ENABLE_ON_LAN_DISCONNECT" on ;; *wireless|wifi) check_switch_lock wifi "$DEVICES_TO_ENABLE_ON_WIFI_DISCONNECT" on ;; gsm|wwan) check_switch_lock wwan "$DEVICES_TO_ENABLE_ON_WWAN_DISCONNECT" on ;; esac ;; # down esac ;; # up/down *) # other calls: do nothing ;; esac # action do_exit 0 TLP-1.10.1/tlp-rdw-udev.in000066400000000000000000000056531517565574500151500ustar00rootroot00000000000000#!/bin/sh # tlp-rdw - handle dock/undock events # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Source libraries for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/25-tlp-func-rf @TLP_FLIB@/30-tlp-func-rf-sw; do # shellcheck disable=SC1090 . "$lib" || exit 70 done # --- MAIN # read configuration: quit on error, trace allowed read_config 0 check_tlp_enabled || do_exit 0 check_run_flag "$RDW_KILL" && do_exit 0 add_sbin2path # get power source get_sys_power_supply # get device/type ddev=/sys$1 devtype=$2 case $devtype in dock) # check if type is "dock_station", quit if not type=$(read_sysf "$ddev/type") [ "$type" = "dock_station" ] || do_exit 0 docked=$(read_sysf "$ddev/docked") action=$EVENT # shellcheck disable=SC2154 echo_debug "udev" "+++ rdw_udev($devtype).$action dev=$ddev type=$type docked=$docked syspwr=$_syspwr" ;; usb_dock) # shellcheck disable=SC2153 case $ACTION in add) action="dock" ;; remove) action="undock" ;; esac echo_debug "udev" "+++ rdw_udev($devtype).$action dev=$ddev syspwr=$_syspwr" ;; *) do_exit 0 ;; # unknown device type esac # quit if timed lock in progress if check_timed_lock "$RDW_DOCK_LOCK" ; then echo_debug "udev" "rdw_udev.locked" do_exit 0 fi case $action in dock) # laptop was docked # lock for 2 seconds in case dock has multiple devices set_timed_lock "$RDW_DOCK_LOCK" "$RDW_NM_LOCKTIME" # enable configured radios (obey rdw nm locks too) for dev in $DEVICES_TO_ENABLE_ON_DOCK; do [ -n "$dev" ] && ! check_timed_lock "${RDW_NM_LOCK}_${dev}" \ && device_switch "$dev" on "${RDW_NM_LOCK}_${dev}" "$RDW_NM_LOCKTIME" done # disable configured radios (obey rdw nm locks too) for dev in $DEVICES_TO_DISABLE_ON_DOCK; do [ -n "$dev" ] && ! check_timed_lock "${RDW_NM_LOCK}_${dev}" \ && device_switch "$dev" off "${RDW_NM_LOCK}_${dev}" "$RDW_NM_LOCKTIME" done ;; undock) # laptop was undocked # lock for 2 seconds in case dock has multiple devices set_timed_lock "$RDW_DOCK_LOCK" "$RDW_NM_LOCKTIME" # enable configured radios (obey rdw nm locks too) for dev in $DEVICES_TO_ENABLE_ON_UNDOCK; do [ -n "$dev" ] && ! check_timed_lock "${RDW_NM_LOCK}_${dev}" \ && device_switch "$dev" on "${RDW_NM_LOCK}_${dev}" "$RDW_NM_LOCKTIME" done # disable configured radios (obey rdw nm locks too) for dev in $DEVICES_TO_DISABLE_ON_UNDOCK; do [ -n "$dev" ] && ! check_timed_lock "${RDW_NM_LOCK}_${dev}" \ && device_switch "$dev" off "${RDW_NM_LOCK}_${dev}" "$RDW_NM_LOCKTIME" done ;; *) ;; # unknown action -> do nothing esac do_exit 0 TLP-1.10.1/tlp-rdw.in000066400000000000000000000021511517565574500141750ustar00rootroot00000000000000#!/bin/sh # tlp-rdw - enable/disable RDW # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Source libraries for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/25-tlp-func-rf @TLP_FLIB@/30-tlp-func-rf-sw; do # shellcheck disable=SC1090 . "$lib" || exit 70 done # --- MAIN # shellcheck disable=SC2034 _bgtask=1 carg1="$1" if [ "$carg1" = "--version" ]; then print_version exit 0 fi # read configuration: quit on error, trace allowed read_config 0 "$@" parse_args4config "$@" cprintf_init case $carg1 in "") if check_run_flag "$RDW_KILL"; then echo "tlp-rdw: disabled." else echo "tlp-rdw: enabled." fi ;; enable) check_root reset_run_flag "$RDW_KILL" echo "tlp-rdw: enabled." ;; disable) check_root set_run_flag "$RDW_KILL" echo "tlp-rdw: disabled." ;; *) cecho "Error: invalid command \"$carg1\"." 1>&2 echo "Usage: tlp-rdw [ enable | disable ]" 1>&2 do_exit 3 esac do_exit 0 TLP-1.10.1/tlp-rdw.rules.in000066400000000000000000000040621517565574500153310ustar00rootroot00000000000000# tlp-rdw - udev rules # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Dock/undock events # ThinkPad Advanced Mini Dock (and all older models), ThinkPad UltraBase ACTION=="change", SUBSYSTEM=="platform", KERNEL=="dock.*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p dock" # ThinkPad Mini Dock (Plus) Series 3 ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/100a/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" # ThinkPad Pro Dock [P/N 40A1] ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/1012/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" # ThinkPad Ultra Dock [P/N 40A2] ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/1010/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" # ThinkPad OneLink Pro Dock (USB3 Gigabit LAN interface) [P/N 40X1E] ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/304b/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/304f/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" # ThinkPad OneLink Dock [P/N 40X1A9] ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/3049/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" # ThinkPad OneLink Dock Plus [P/N 40A4] ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/3054/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" # ThinkPad Pro Dock "CS18" [P/N 40AH] ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/306f/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" # ThinkPad USB-C Dock Gen 2 [P/N 40AS] ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/a396/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" # Thunderbolt docks ACTION=="add|remove", SUBSYSTEM=="thunderbolt", ENV{DEVTYPE}=="thunderbolt_device", ENV{USB4_TYPE}=="hub", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" TLP-1.10.1/tlp-readconfs.in000066400000000000000000000224321517565574500153510ustar00rootroot00000000000000#!/usr/bin/perl # tlp-readconfs - read all of TLP's config files # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # Cmdline options # --outfile : filepath to contain merged configuration # --skipdefs: skip intrinsic defaults from CONF_DEF # --notrace: disable trace # --cdiff: only show differences to the default # # Return codes # 0: ok # 5: tlp.conf missing # 6: defaults.conf missing package tlp_readconfs; use strict; use warnings; # --- Modules use File::Basename; use Getopt::Long; # --- Definitions # Path substitutions use constant CONF_USR => '@TLP_CONFUSR@'; use constant CONF_DIR => '@TLP_CONFDIR@'; use constant CONF_DEF => '@TLP_CONFDEF@'; use constant CONF_REN => '@TLP_CONFREN@'; use constant CONF_DPR => '@TLP_CONFDPR@'; # Exit codes use constant EXIT_TLPCONF => 5; use constant EXIT_DEFCONF => 6; # Defaults spared by TLP_DISABLE_DEFAULTS=1 use constant ESSENTIAL_DEFAULTS => qr/TLP_ENABLE|TLP_WARN_LEVEL|TLP_AUTO_SWITCH|TLP_PROFILE_AC|TLP_PROFILE_BAT/; # --- Global vars my @config_val = (); # 2-dim array: parameter name, value, source, default-value my %config_idx = (); # hash: parameter name => index into the name-value array my $skip_defaults = 0; # skip all entries from CONF_DEF my %rename = (); # hash: OLD_PARAMETER => NEW_PARAMETER my $renrex; # compiled regex for renaming parameters my $do_rename = 0; # enable renaming (when $renrex not empty) my %dprmsg = (); # hash: PARAMETER => deprecated message my $notrace = 0; my $trace2stderr = 0; my $debug = 0; my $cdiff = 0; my $outfile; my $defsrc = basename(CONF_DEF); # --- Subroutines # Format and write debug message # @_: printf arguments including format string sub printf_debug { if ( $trace2stderr) { printf STDERR @_; } elsif ( ! $notrace && $debug ) { open (my $logpipe, "|-", "logger -p daemon.debug -t \"tlp\" --id=\$\$ --") || return 1; printf {$logpipe} @_; close ($logpipe); } return 0; } # Store parameter name, value, source in array/hash # $_[0]: parameter name (non-null string) # $_[1]: parameter value (maybe null string) # $_[2]: 0=replace/1=append parameter value # $_[3]: parameter source e.g. filepath + line no. # $_[4]: 0=user config/1=default # return: 0=new name/1=known name sub store_name_value_source { my $name = $_[0]; my $value = $_[1]; my $append = $_[2]; my $source = $_[3]; my $is_def = $_[4]; $debug = 1 if ( $name eq "TLP_DEBUG" && $value =~ /\bcfg\b/ ); if ( $name eq "TLP_DISABLE_DEFAULTS" && $value eq "1" ) { $skip_defaults = 1; printf_debug ("tlp-readconfs.skip_defaults\n"); } if ( defined $config_idx{$name} ) { # existing name if ( $append # do not append in default.conf (for good measure only) and not $is_def # do not append to stored default if TLP_DISABLE_DEFAULTS=1 and not ( $skip_defaults and $config_val[$config_idx{$name}][3] ) ) { # append value, source $config_val[$config_idx{$name}][1] .= " $value"; $config_val[$config_idx{$name}][2] .= " & $source"; printf_debug ("tlp-readconfs.replace+ [%s]: %s=\"%s\" '%s' def='%s'\n", $config_idx{$name}, $name, $config_val[$config_idx{$name}][1], $config_val[$config_idx{$name}][2], $config_val[$config_idx{$name}][3] ); } else { # replace value, source $config_val[$config_idx{$name}][1] = $value; $config_val[$config_idx{$name}][2] = $source; printf_debug ("tlp-readconfs.replace [%s]: %s=\"%s\" '%s' def='%s'\n", $config_idx{$name}, $name, $value, $source, $config_val[$config_idx{$name}][3] ); } } else { # new name --> store name, value, source and hash name if ( $is_def ) { # save value as default push(@config_val, [$name, $value, $source, $value]); printf_debug ("tlp-readconfs.ins-def [%s]: %s=\"%s\" '%s' def='%s'\n", $#config_val, $name, $value, $source, $value); } else { # save value as user config push(@config_val, [$name, $value, $source, ""]); printf_debug ("tlp-readconfs.insert [%s]: %s=\"%s\" %s def='%s'\n", $#config_val, $name, $value, $source, ""); } $config_idx{$name} = $#config_val; } return 0; } # Parse whole config file and store parameters # $_[0]: filepath # $_[1]: 0=no change/1=rename parameters # return: 0=ok/1=file non-existent sub parse_configfile { my $fname = $_[0]; my $do_ren = $_[1]; my $source; my $is_def; if ( $fname eq CONF_DEF ) { $source = $defsrc; $is_def = 1; } else { $source = $fname; $is_def = 0; } open (my $cf, "<", $fname) || return 1; my $ln = 0; while ( my $line = <$cf> ) { # strip newline chomp $line; $ln += 1; # strip #-comments outside of quoted value $line =~ s/(?:"[^"]*")(*SKIP)(*FAIL)|#.*$//; # strip trailing spaces $line =~ s/\s+$//; # select lines with format PARAMETER=value or PARAMETER="value" if ( $line =~ /^(?[A-Z_]+[0-9]*)(?=|\+=)"?(?[][0-9a-zA-Z _.:-]*)"?\s*$/ ) { my $name = $+{name}; if ( $do_ren ) { # rename PARAMETER $name =~ s/$renrex/$rename{$1}/; } my $value = $+{value}; my $append = $+{op} eq "+="; store_name_value_source ($name, $value, $append, $source . " L" . sprintf ("%04d", $ln), $is_def ); } } close ($cf); return 0; } # Output all stored parameter name, value to a file # or parameter name, value, source to stdout # $_[0]: filepath (without argument the output will be written to stdout) # return: 0=ok/1=file open error sub write_runconf { my $fname = $_[0]; my $runconf; if ( ! $fname ) { $runconf = *STDOUT; } else { open ($runconf, ">", $fname) || return 1; } foreach ( @config_val ) { my ($name, $value, $source, $default) = @$_; # skip default entries next if ( $skip_defaults # if TLP_DISABLE_DEFAULTS=1 and $source =~ /^$defsrc/ # skip default-only or default-identical and not ( $name =~ ESSENTIAL_DEFAULTS ) # do not skip essentials ); if ( $runconf eq *STDOUT ) { my $msg = ""; # stdout: check for deprecated message if ( defined $dprmsg{$name} ) { $msg = " #! $dprmsg{$name}"; } # --cdiff: do not show user config lines matching the default if ( ! $cdiff || $value ne $default ) { printf {$runconf} "%s: %s=\"%s\"%s\n", $source, $name, $value, $msg; } } else { printf {$runconf} "%s=\"%s\"\n", $name, $value; } } close ($runconf); return 0; } # Parse parameter renaming rules from file # $_[0]: rules file # return: 0=ok/1=file non-existent sub parse_renfile { my $fname = $_[0]; open (my $rf, "<", $fname) || return 1; # accumulate renaming while ( my $line = <$rf> ) { chomp $line; # select lines with format 'OLD_PARAMETERNEW_PARAMETER' if ( $line =~ /^(?[A-Z_]+[0-9]*)\s+(?[A-Z_]+[0-9]*)\s*$/ ) { my $old_name = $+{old_name}; my $new_name = $+{new_name}; $rename{$old_name} = $new_name; } } close ($rf); if ( keys %rename > 0 ) { # renaming hash not empty --> compile OLD_PARAMETER keys to match regex $renrex = qr/^(@{[join '|', map { quotemeta($_) } keys %rename]})$/; # enable renaming $do_rename = 1; } return 0; } # Parse deprecated parameters and messages from file # $_[0]: parameters file # return: 0=ok/1=file non-existent sub parse_dprfile { my $fname = $_[0]; open (my $df, "<", $fname) || return 1; # accumulate deprecated params and mesgs while ( my $line = <$df> ) { chomp $line; # select lines with format 'PARAMETER# message' if ( $line =~ /^(?[A-Z_]+[0-9]*)\s+#\s+(?.*)$/ ) { my $param_name = $+{param_name}; my $param_msg = $+{param_msg}; $dprmsg{$param_name} = $param_msg; } } close ($df); return 0; } # --- MAIN # parse arguments GetOptions ( 'outfile=s' => \$outfile, 'skipdefs' => \$skip_defaults, 'notrace' => \$notrace, 'trace2stderr' => \$trace2stderr, 'cdiff' => \$cdiff ); # read parameter renaming rules parse_renfile (CONF_REN); # read deprecated parameter messages parse_dprfile (CONF_DPR); # 1. read intrinsic defaults (no renaming) parse_configfile (CONF_DEF, 0) == 0 || exit EXIT_DEFCONF; # 2. read customization (with renaming) foreach my $conffile ( grep { -f } glob CONF_DIR . "/*.conf" ) { parse_configfile ($conffile, $do_rename); } # 3. read user settings (with renaming) parse_configfile (CONF_USR, $do_rename) == 0 || exit EXIT_TLPCONF; # save result write_runconf ($outfile); exit 0; TLP-1.10.1/tlp-rf.in000066400000000000000000000033451517565574500140160ustar00rootroot00000000000000#!/bin/sh # tlp - switch bluetooth/nfc/wifi/wwan on/off # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Source libraries for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/25-tlp-func-rf @TLP_FLIB@/30-tlp-func-rf-sw; do # shellcheck disable=SC1090 . "$lib" done # --- MAIN carg1="$1" if [ "$carg1" = "--version" ]; then print_version exit 0 fi # read configuration: quit on error, trace allowed read_config 0 "$@" parse_args4config "$@" cprintf_init add_sbin2path self=${0##*/} case $self in bluetooth|nfc|wifi|wwan) case "$carg1" in on) device_switch "$self" on # shellcheck disable=SC2154 echo_device_state "$self" "$_devs" ;; off) device_switch "$self" off echo_device_state "$self" "$_devs" ;; toggle) device_switch "$self" toggle echo_device_state "$self" "$_devs" ;; cycle) device_switch "$self" toggle echo_device_state "$self" "$_devs" device_switch "$self" toggle echo_device_state "$self" "$_devs" ;; "") get_rf_dev_state "$self" echo_device_state "$self" "$_devs" ;; *) cecho "Error: invalid command \"$carg1\"." 1>&2 echo "Usage: $self [ on | off | toggle | cycle ]" 1>&2 do_exit 3 ;; esac ;; *) cecho "Error: unknown device type \"$self\"." 1>&2 do_exit 1 ;; esac do_exit 0 TLP-1.10.1/tlp-run-on.in000066400000000000000000000021411517565574500146160ustar00rootroot00000000000000#!/bin/sh # tlp - run commands depending on power source # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Source libraries # shellcheck disable=SC2043 for lib in @TLP_TLIB@/tlp-func-base; do # shellcheck disable=SC1090 . "$lib" done # --- MAIN self=${0##*/} cmd=$1 if [ -z "$cmd" ]; then cecho "Usage: $self command [arg(s)]" 1>&2 exit 1 fi if ! cmd_exists "$cmd"; then cecho "Error: \"$cmd\" not found." 1>&2 exit 2 fi shift case $self in run-on-ac) get_sys_power_supply # shellcheck disable=SC2154 case "$_syspwr" in "$PS_AC") $cmd "$@" ;; "$PS_BAT") ;; "$PS_UNKNOWN") cecho "Error: power source unknown." 1>&2 ;; esac ;; run-on-bat) get_sys_power_supply case "$_syspwr" in "$PS_AC") ;; "$PS_BAT") $cmd "$@" ;; "$PS_UNKNOWN") cecho "Error: power source unknown." 1>&2 ;; esac ;; *) cecho "Error: unknown mode $self." 1>&2 exit 1 ;; esac TLP-1.10.1/tlp-sleep000066400000000000000000000003431517565574500141050ustar00rootroot00000000000000#!/bin/sh # tlp - systemd suspend/resume hook # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later case $1 in pre) tlp suspend ;; post) tlp resume ;; esac TLP-1.10.1/tlp-sleep.elogind000066400000000000000000000004431517565574500155260ustar00rootroot00000000000000#!/bin/sh # tlp - elogind suspend/resume hook # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later case "${1-}" in 'pre') exec tlp suspend ;; 'post') exec tlp resume ;; *) exit 64 ;; esac TLP-1.10.1/tlp-stat.in000066400000000000000000001226041517565574500143620ustar00rootroot00000000000000#!/bin/sh # tlp-stat - display power saving details # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Source libraries for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/[0-9][0-9]* @TLP_FLIB@/tlp-func-stat; do # shellcheck disable=SC1090 . "$lib" || exit 70 done # --- Constants readonly TLPUSB=@TLP_TLIB@/tlp-usblist readonly TLPPCI=@TLP_TLIB@/tlp-pcilist readonly DMIDECODE=dmidecode readonly JOURNALCTL=journalctl readonly LSPCI=lspci readonly LSUSB=lsusb readonly ASPM=/sys/module/pcie_aspm/parameters/policy readonly EFID=/sys/firmware/efi readonly NMIWD=/proc/sys/kernel/nmi_watchdog readonly OSRELEASE=/etc/os-release readonly SWITCHEROO=/sys/kernel/debug/vgaswitcheroo/switch readonly WQPE=/sys/module/workqueue/parameters/power_efficient readonly ACPITEMP_DIR="/sys/devices/virtual/thermal" readonly ACPITEMP_GLOB=" /sys/devices/virtual/thermal/thermal_zone*" readonly CORETEMP_DIR="/sys/devices/platform/coretemp.0" readonly CORETEMP_GLOB=" /sys/devices/platform/coretemp.0 /sys/devices/platform/coretemp.0/hwmon/hwmon*" readonly HWMONFAN_DIRS=" /sys/class/hwmon/hwmon*/device /sys/class/hwmon/hwmon*" readonly IBMFAN=/proc/acpi/ibm/fan readonly IBMTHERMAL=/proc/acpi/ibm/thermal readonly DEBUGLOG=/var/log/debug # --- Variables needs_root_priv= show_all=1 show_bat=0 show_conf=0 show_disk=0 show_graf=0 show_pcie=0 show_pev=0 show_proc=0 show_psup=0 show_pddiag=0 show_rfkill=0 show_system=0 show_temp=0 show_trace=0 show_trace_nm=0 show_udev=0 show_usb=0 show_verbose=0 show_warn=0 show_mode=0 show_version=0 quiet=0 # --- Functions parse_args () { # parse command-line -- $@: arguments to parse # iterate arguments until delimiter '--' reached while [ $# -gt 0 ]; do case "$1" in "-b"|"--battery") show_all=0 show_bat=1 needs_root_priv=1 ;; "-c"|"--config") show_all=0 show_conf=1 : ${needs_root_priv:=0} ;; "--cdiff") show_all=0 show_cdiff=1 : ${needs_root_priv:=0} ;; "-d"|"--disk") show_all=0 show_disk=1 needs_root_priv=1 ;; "-e"|"--pcie") show_all=0 show_pcie=1 : ${needs_root_priv:=0} ;; "-g"|"--graphics") show_all=0 show_graf=1 needs_root_priv=1 ;; "-m"|"--mode") show_all=0 show_mode=1 quiet=1 : ${needs_root_priv:=0} ;; "-p"|"--processor") show_all=0 show_proc=1 : ${needs_root_priv:=0} ;; "-q"|"--quiet") quiet=1 ;; "-r"|"--rfkill") show_all=0 show_rfkill=1 : ${needs_root_priv:=0} ;; "-s"|"--system") show_all=0 show_system=1 : ${needs_root_priv:=0} ;; "-t"|"--temp") show_all=0 show_temp=1 : ${needs_root_priv:=0} ;; "-u"|"--usb") show_all=0 show_usb=1 : ${needs_root_priv:=0} ;; "-v"|"--verbose") show_verbose=1 ;; "-w"|"--warn") show_all=0 show_warn=1 : ${needs_root_priv:=1} ;; "-P"|"--pev") show_all=0 show_pev=1 needs_root_priv=1 ;; "--pd-diag") show_all=0 show_pddiag=1 : ${needs_root_priv:=0} ;; "--psup") show_all=0 show_psup=1 : ${needs_root_priv:=0} ;; "-T"|"--trace") show_all=0 show_trace=1 needs_root_priv=1 ;; "--trace-nm") show_all=0 show_trace=1 show_trace_nm=1 needs_root_priv=1 ;; "--udev") show_all=0 show_udev=1 : ${needs_root_priv:=0} ;; "--version") show_all=0 show_version=1 quiet=1 : ${needs_root_priv:=0} ;; "--") # config values follow --> quit loop break ;; *) ########## cecho "Error: invalid option \"$1\"." 1>&2 echo "Usage: tlp-stat [ -b | --battery | -c | --config |" 1>&2 echo " -d | --disk | -e | --pcie |" 1>&2 echo " -g | --graphics | -m | --mode |" 1>&2 echo " -p | --processor | -q | --quiet |" 1>&2 echo " -r | --rfkill | -s | --system |" 1>&2 echo " -t | --temp | -u | --usb |" 1>&2 echo " -w | --warn | -v | --verbose |" 1>&2 echo " | --version | | --cdiff |" 1>&2 echo " -P | --pev | | --psup |" 1>&2 echo " | --pd-diag | | |" 1>&2 echo " -T | --trace | | --trace-nm |" 1>&2 echo " --udev ]" 1>&2 do_exit 3 ;; esac shift # next argument done # while arguments return 0 } # --- MAIN # read configuration; continue on error, no trace read_config 1 "$@" parse_args "$@" parse_args4config "$@" cprintf_init add_sbin2path : ${needs_root_priv:=1} # inhibit trace output (unless forced) # shellcheck disable=SC2034 [ "$X_TRACE_TLP_STAT" = "1" ] || _nodebug=1 # check prerequisites if [ "$needs_root_priv" = "1" ]; then check_root fi get_sys_power_supply if [ "$quiet" = "0" ]; then echo "--- TLP $TLPVER --------------------------------------------" echo fi # --- show version if [ "$show_version" = "1" ]; then print_version fi # show_version # --- show configuration if [ "$show_conf" = "1" ] || [ "$show_all" = "1" ]; then echo "+++ Configured Settings:" if [ "$TLP_DISABLE_DEFAULTS" = "1" ]; then $READCONFS --skipdefs --notrace else $READCONFS --notrace fi echo fi # show_conf if [ "$show_cdiff" = "1" ]; then echo "+++ Configured Settings (only differences to defaults):" if [ "$TLP_DISABLE_DEFAULTS" = "1" ]; then $READCONFS --cdiff --skipdefs --notrace else $READCONFS --cdiff --notrace fi echo fi # show_cdiff # --- show opmode if [ "$show_mode" = "1" ]; then printf "%s\n" "$(print_saved_profile)" fi # show_mode # --- show system info and status if [ "$show_system" = "1" ] || [ "$show_all" = "1" ] ; then echo "+++ System Info" printf "System = %s\n" "$(read_dmi sys_vendor) $(read_dmi product_version) $(read_dmi product_name)" printf "BIOS = %s\n" "$(read_dmi bios_vendor) $(read_dmi bios_version)" ecfw="$(read_dmi ec_firmware_release)" && printf "EC firmware = %s\n" "$ecfw" # --- release & kernel info printf "OS release = " if ! sed -rn "s/PRETTY_NAME=[\"'](.*)[\"']/\1/p" $OSRELEASE 2> /dev/null; then echo "unknown" fi echo "Kernel = $(uname -r -m -v)" printparm "%-14s = %s" /proc/cmdline # --- init system info if check_systemd; then systemd_version="$($SYSTEMCTL --version 2> /dev/null | head -1 | awk '{ print $2; }')" if [ -n "$systemd_version" ]; then echo "Init system = systemd ${systemd_version}" else # unable to determine version echo "Init system = systemd" fi elif check_upstart; then echo "Init system = upstart" elif check_openrc; then echo "Init system = openrc" else echo "Init system = sysvinit" fi if [ -d $EFID ]; then echo "Boot mode = UEFI" else echo "Boot mode = BIOS (CSM, Legacy)" fi printf "Suspend mode = %s\n" "$(read_sysf "$SLEEPMODE" "(not available)")" print_selinux echo # --- TLP status echo "+++ TLP Status" if check_tlp_enabled; then printf "tlp = enabled, last run: %s\n" "$(print_file_modtime_and_age "$PWRRUNFILE")" else printf "tlp = disabled\n" fi # --- RDW status if check_rdw_installed; then if ! check_tlp_enabled; then printf "tlp-rdw = disabled (TLP disabled)\n" elif check_run_flag "$RDW_KILL"; then printf "tlp-rdw = disabled\n" else printf "tlp-rdw = enabled\n" fi else printf "tlp-rdw = not installed\n" if [ -n "$DEVICES_TO_DISABLE_ON_LAN_CONNECT" ] || \ [ -n "$DEVICES_TO_DISABLE_ON_WIFI_CONNECT" ] || \ [ -n "$DEVICES_TO_DISABLE_ON_WWAN_CONNECT" ] || \ [ -n "$DEVICES_TO_ENABLE_ON_LAN_DISCONNECT" ] || \ [ -n "$DEVICES_TO_ENABLE_ON_WIFI_DISCONNECT" ] || \ [ -n "$DEVICES_TO_ENABLE_ON_WIFI_DISCONNECT" ] || \ [ -n "$DEVICES_TO_ENABLE_ON_DOCK" ] || \ [ -n "$DEVICES_TO_DISABLE_ON_DOCK" ] || \ [ -n "$DEVICES_TO_ENABLE_ON_UNDOCK" ] || \ [ -n "$DEVICES_TO_DISABLE_ON_UNDOCK" ]; then echo_message "Warning: at least one of DEVICES_TO_DISABLE/ENABLE_ON_*_CONNECT/DISCONNECT, DEVICES_TO_DISABLE/ENABLE_ON_(UN)DOCK" echo_message "is configured but tlp-rdw is not installed. The corresponding settings will not work." "warning" fi fi # --- tlp-pd status if check_tlp_pd_installed; then tlp_pd_state="installed" if check_systemd; then if check_service_state "$TLPPD_SERVICE" enabled; then tlp_pd_state="enabled" else tlp_pd_state="disabled" fi if check_service_state "$TLPPD_SERVICE" active; then tlp_pd_state="${tlp_pd_state}, running" else tlp_pd_state="${tlp_pd_state}, not running" fi else if check_tlp_pd_running; then tlp_pd_state="${tlp_pd_state}, running" else tlp_pd_state="${tlp_pd_state}, not running" fi fi printf "tlp-pd = %s\n" "$tlp_pd_state" else printf "tlp-pd = not installed\n" fi # --- actual profile printf "TLP profile = %s\n" "$(print_saved_profile)" # ---- actual power source get_sys_power_supply case $? in "$PS_AC") printf "Power source = AC\n" ;; "$PS_BAT") printf "Power source = battery\n" ;; "$PS_UNKNOWN") printf "Power source = unknown\n" ;; esac echo # -- check systemd services check_services_activation_status # -- check for power-profiles-daemon if check_ppd_service; then cprintf "" "Warning: TLP's power saving will not apply on boot because the conflicting $PPD_SERVICE is active.\n" 1>&2 echo fi fi # show_system # --- show cpu info if [ "$show_proc" = "1" ] || [ "$show_all" = "1" ]; then [ "$quiet" = "0" ] || [ "$show_all" = "1" ] && echo "+++ Processor" # check EPP availability if [ -f "${CPUD}/cpu0/cpufreq/energy_performance_preference" ]; then epp=1 else epp=0 fi if [ "$quiet" = "0" ]; then if [ "$X_IGNORE_PROCCPU" != "1" ]; then cpu_mod="$(sed -rn 's/model name[ \t]+: (.+)/\1/p' /proc/cpuinfo | head -1)" else cpu_mod="" fi if [ -z "$cpu_mod" ] && test_root && cmd_exists $DMIDECODE ; then cpu_mod="$(dmidecode -t 4 2> /dev/null | sed -rn 's/\s*Version:\s+(.+)/\1/p')" fi if [ -n "$cpu_mod" ]; then printf "CPU model = %s\n\n" "$cpu_mod" fi # -- scaling gov and freq info cnt=0 cpu2nd="" cpulast="" for cn in $(glob_dirs '/cpu[0-9]*' "$CPUD" | sed 's/.*\/cpu//' | sort -n); do cpuf="${CPUD}/cpu${cn}/cpufreq" cpua="${CPUD}/cpu${cn}/acpi_cppc" if [ -f "$cpuf/scaling_driver" ]; then cpu="cpu${cn}" # show only cpu0, unless verbose mode is enabled if [ $cnt -eq 0 ] || [ "$show_verbose" = "1" ] ; then printparm "%-54s = ##%s##" "$cpuf/scaling_driver" printparm "%-54s = ##%s##" "$cpuf/scaling_governor" printparm "%s = ##%s##" "$cpuf/scaling_available_governors" _ if [ -f "$cpuf/scaling_min_freq" ]; then printf "%-54s = %8d [kHz]\n" "$cpuf/scaling_min_freq" "$(read_sysf "$cpuf/scaling_min_freq")" fi if [ -f "$cpuf/scaling_max_freq" ]; then printf "%-54s = %8d [kHz]\n" "$cpuf/scaling_max_freq" "$(read_sysf "$cpuf/scaling_max_freq")" fi if [ -f "$cpuf/scaling_cur_freq" ] && [ $show_verbose -eq 1 ]; then cprintf "notice" "%-54s = %8d [kHz]\n" "$cpuf/scaling_cur_freq" "$(read_sysf "$cpuf/scaling_cur_freq")" fi if [ -f "$cpuf/scaling_available_frequencies" ]; then printf "%s = " "$cpuf/scaling_available_frequencies" for freq in $(read_sysf "$cpuf/scaling_available_frequencies"); do printf "%s " "$freq" done printf "[kHz]\n" fi if [ -f "$cpuf/cpuinfo_min_freq" ]; then printf "%-54s = %8d [kHz]\n" "$cpuf/cpuinfo_min_freq" "$(read_sysf "$cpuf/cpuinfo_min_freq")" fi if [ -f "$cpuf/cpuinfo_max_freq" ]; then printf "%-54s = %8d [kHz]\n" "$cpuf/cpuinfo_max_freq" "$(read_sysf "$cpuf/cpuinfo_max_freq")" fi if [ -f "$cpuf/bios_limit" ]; then printf "%-54s = %8d [kHz]\n" "$cpuf/bios_limit" "$(read_sysf "$cpuf/bios_limit")" fi if [ -f "$cpuf/boost" ] && [ $show_verbose -eq 1 ]; then printf "%-54s = %d\n" "$cpuf/boost" "$(read_sysf "$cpuf/boost")" fi if [ -f "$cpuf/amd_pstate_lowest_nonlinear_freq" ]; then printf "%-70s = %8d [kHz]\n" "$cpuf/amd_pstate_lowest_nonlinear_freq" "$(read_sysf "$cpuf/amd_pstate_lowest_nonlinear_freq")" fi if [ -f "$cpuf/amd_pstate_max_freq" ]; then printf "%-70s = %8d [kHz]\n" "$cpuf/amd_pstate_max_freq" "$(read_sysf "$cpuf/amd_pstate_max_freq")" fi if [ $show_verbose -eq 1 ]; then if [ -f "$cpuf/amd_pstate_hw_prefcore" ]; then printf "%-70s = %s\n" "$cpuf/amd_pstate_hw_prefcore" "$(read_sysf "$cpuf/amd_pstate_hw_prefcore")" fi if [ -f "$cpuf/amd_pstate_prefcore_ranking" ]; then printf "%-70s = %8d\n" "$cpuf/amd_pstate_prefcore_ranking" "$(read_sysf "$cpuf/amd_pstate_prefcore_ranking")" fi if [ -f "$cpuf/amd_pstate_highest_perf" ]; then printf "%-70s = %8d [%%]\n" "$cpuf/amd_pstate_highest_perf" "$(read_sysf "$cpuf/amd_pstate_highest_perf")" fi if [ -f "$cpua/lowest_perf" ]; then printf "%-61s = %5d\n" "$cpua/lowest_perf" "$(read_sysf "$cpua/lowest_perf")" fi if [ -f "$cpua/lowest_nonlinear_perf" ]; then printf "%-61s = %5d\n" "$cpua/lowest_nonlinear_perf" "$(read_sysf "$cpua/lowest_nonlinear_perf")" fi if [ -f "$cpua/nominal_perf" ]; then printf "%-61s = %5d\n" "$cpua/nominal_perf" "$(read_sysf "$cpua/nominal_perf")" fi if [ -f "$cpua/reference_perf" ]; then printf "%-61s = %5d\n" "$cpua/reference_perf" "$(read_sysf "$cpua/reference_perf")" fi if [ -f "$cpua/highest_perf" ]; then printf "%-61s = %5d\n" "$cpua/highest_perf" "$(read_sysf "$cpua/highest_perf")" fi if [ -f "$cpua/lowest_freq" ]; then printf "%-61s = %5d [MHz]\n" "$cpua/lowest_freq" "$(read_sysf "$cpua/lowest_freq")" fi if [ -f "$cpua/nominal_freq" ]; then printf "%-61s = %5d [MHz]\n" "$cpua/nominal_freq" "$(read_sysf "$cpua/nominal_freq")" fi fi if [ -f "$cpuf/energy_performance_preference" ]; then printparm "%s = ##%s## [EPP]" "$cpuf/energy_performance_preference" fi if [ -f "$cpuf/energy_performance_available_preferences" ]; then printparm "%s = ##%s##" "$cpuf/energy_performance_available_preferences" fi if { supports_intel_cpu_epb && [ "$epp" -eq 0 ]; } || [ "$X_SHOW_EPB" = "1" ] ; then # CPU supports EPB: use native kernel API (5.2 and later) printparm_epb "${CPUD}/cpu${cn}/power/energy_perf_bias" fi printf "\n" fi if [ $cnt -eq 1 ]; then # remember 2nd cpu core cpu2nd=$cpu else # remember last cpu core cpulast=$cpu fi cnt=$((cnt + 1)) fi done # for cn if [ "$show_verbose" = "0" ] && [ $cnt -gt 1 ]; then printf "%s/%s..%s: omitted for clarity, use -v to show all\n\n" "$CPUD" "$cpu2nd" "$cpulast" fi fi # !quiet if check_intel_pstate; then # Intel P-state printparm "%-54s = ##%s##" "$INTEL_PSTATED/status" printparm "%-54s = ##%3d## [%%]" "$CPU_MIN_PERF_PCT" printparm "%-54s = ##%3d## [%%]" "$CPU_MAX_PERF_PCT" printparm "%-54s = ##%3d##" "$CPU_TURBO_PSTATE" printparm "%-54s = ##%3d##" "$INTEL_DYN_BOOST" printparm "%-54s = ##%3d## [%%]" "$INTEL_PSTATED/turbo_pct" printparm "%-54s = ##%3d##" "$INTEL_PSTATED/num_pstates" elif check_amd_pstate; then # AMD P-state printparm "%-54s = ##%s##" "$AMD_PSTATED/status" if [ $show_verbose -eq 1 ]; then printparm "%-54s = ##%s##" "$AMD_PSTATED/prefcore" fi if [ -f "$CPU_BOOST_ALL_CTRL" ]; then printparm "%-54s = ##%d##" "$CPU_BOOST_ALL_CTRL" "not available" fi elif [ -f "$CPU_BOOST_ALL_CTRL" ]; then # turbo boost boost=$(read_sysval "$CPU_BOOST_ALL_CTRL") # simple test for attribute "w" doesn't work, so actually write if write_sysf "$boost" "$CPU_BOOST_ALL_CTRL"; then printparm "%-54s = ##%d##" "$CPU_BOOST_ALL_CTRL" else printparm "%-54s = ##%d## (CPU not supported)" "$CPU_BOOST_ALL_CTRL" fi else printparm "%-54s = (not available)" "$CPU_BOOST_ALL_CTRL" fi # --- workqueue power efficient status printparm "%-54s = ##%s##" "$WQPE" # --- nmi watchdog printparm "%-54s = ##%d##" "$NMIWD" # --- platform profile if [ "$quiet" = "0" ] || [ "$show_all" = "1" ] ; then printf "\n+++ Platform\n" fi printparm "%-54s = ##%s##" "$FWACPID/platform_profile" printparm "%-54s = ##%s##" "$FWACPID/platform_profile_choices" [ -d "$TPACPID" ] && printparm "%-54s = ##%s##" "$TPACPID/dytc_lapmode" printf "\n" fi # show_proc # --- show temperatures if [ "$show_temp" = "1" ] || [ "$show_all" = "1" ]; then if [ "$quiet" = "0" ] || [ "$show_all" = "1" ]; then echo "+++ Temperatures" fi tempshown=0 if [ -f "$IBMTHERMAL" ] && [ "$X_IGNORE_IBM_TEMPFAN" != "1" ]; then # use ThinkPad-specific sysfile echo "$IBMTHERMAL = $(read_sysf $IBMTHERMAL | cut -f2 ) [°C]" tempshown=1 fi if { [ "$tempshown" = "0" ] || [ "$show_verbose" = "1" ]; } \ && [ -d "$CORETEMP_DIR" ] && [ "$X_IGNORE_CORETEMP" != "1" ]; then # use coretemp sensors cmax=0 # find max value of all CPU packages (just in case there are several) # temp1_input is "package" for sens in $(glob_files '/temp?_input' "$CORETEMP_GLOB"); do if ctemp=$(read_sysval "$sens"); then if [ "$show_verbose" = "1" ]; then perl -e 'printf (" - %s = %3d [°C]\n", "'"$sens"'", '"$ctemp"' / 1000.0);' fi [ "$ctemp" -gt "$cmax" ] && cmax=$ctemp fi done if [ "$cmax" -gt 0 ]; then perl -e 'printf ("CPU temp (coretemp) = %4d [°C]\n", '"$cmax"' / 1000.0);' tempshown=1 fi fi if { [ "$tempshown" = "0" ] || [ "$show_verbose" = "1" ]; } \ && [ -d "$ACPITEMP_DIR" ] && [ "$X_IGNORE_ACPITEMP" != "1" ]; then # use ACPI thermal zone sensors for CPUs cmax=0 # find max value of all CPUs (just in case there are several) for sens in $(glob_files '/temp' "$ACPITEMP_GLOB"); do if [ "$(read_sysf "${sens%temp}type")" = "acpitz" ]; then if ctemp=$(read_sysval "$sens"); then if [ "$show_verbose" = "1" ]; then perl -e 'printf (" - %s = %3d [°C]\n", "'"$sens"'", '"$ctemp"' / 1000.0);' fi [ "$ctemp" -gt "$cmax" ] && cmax=$ctemp fi fi done if [ "$cmax" -gt 0 ]; then perl -e 'printf ("CPU temp (acpitz) = %4d [°C]\n", '"$cmax"' / 1000.0);' tempshown=1 fi fi if [ "$tempshown" -eq "0" ]; then # no CPU sensor detected printf "CPU temp = (not available)\n" fi # --- fan speed fanshown=0 if [ -f $IBMFAN ] && [ "$X_IGNORE_IBM_TEMPFAN" != "1" ]; then # use thinkpad-specific sysfile awk '$1 ~ /speed:/ { printf "'$IBMFAN' = %4d [/min]\n", $2 }' $IBMFAN fanshown=1 fi if { [ "$fanshown" = "0" ] || [ "$show_verbose" = "1" ]; } \ && [ "$X_IGNORE_HWMONFAN" != "1" ]; then # use hwmon # shellcheck disable=SC2086 for fan in $(glob_files '/fan?*_input' $HWMONFAN_DIRS); do if fan_speed=$(read_sysval "$fan"); then fan_name="${fan##*/}"; fan_name="${fan_name%_input}" fanshown=1 if [ "$show_verbose" = "1" ]; then printf " - %s = %4d [/min]\n" "${fan}" "${fan_speed}" fi printf "Fan speed (%s) = %4d [/min]\n" "${fan_name}" "${fan_speed}" fi done fi if [ "$fanshown" -eq "0" ]; then printf "Fan speed = (not available)\n" fi if [ "$quiet" = "0" ] || [ "$show_all" = "1" ]; then echo fi fi # show_temp # --- show laptop-mode, dirty buffers params if [ "$show_all" = "1" ]; then echo "+++ File System" printparm "%-38s = ##%5d##" /proc/sys/vm/laptop_mode printparm "%-38s = ##%5d##" /proc/sys/vm/dirty_writeback_centisecs printparm "%-38s = ##%5d##" /proc/sys/vm/dirty_expire_centisecs printparm "%-38s = ##%5d##" /proc/sys/vm/dirty_ratio printparm "%-38s = ##%5d##" /proc/sys/vm/dirty_background_ratio printparm "%-38s = ##%5d##" /proc/sys/fs/xfs/age_buffer_centisecs _ printparm "%-38s = ##%5d##" /proc/sys/fs/xfs/xfssyncd_centisecs _ printparm "%-38s = ##%5d##" /proc/sys/fs/xfs/xfsbufd_centisecs _ echo fi # show_all # --- show disk info if [ "$show_disk" = "1" ] || [ "$show_all" = "1" ]; then echo "+++ Disks" # list for storage device iteration disklist="$DISK_DEVICES" # list for output: print "(disabled)" when empty diskstat="${DISK_DEVICES:-(disabled)}" # shellcheck disable=SC2086 printf "Devices = %s\n" "$(anonymize_disk_ids $diskstat)" # iterate over list if [ -n "$disklist" ]; then for dev in $disklist; do # iterate all devices show_disk_data "$dev" done fi echo # --- sata alpm mode # shellcheck disable=SC2086 if stat -t ${ALPM_GLOB}/link_power_management_policy > /dev/null 2>&1; then echo "+++ AHCI Link Power Management (ALPM) :: SATA Links" for i in ${ALPM_GLOB} ; do printparm_ahci "$i/link_power_management_policy" done echo fi # --- ahci runtime pm # shellcheck disable=SC2086 if stat -t ${AHCI_GLOB}/power > /dev/null 2>&1; then echo "+++ AHCI Port Runtime Power Management :: SATA/ATA Ports" for dev in ${AHCI_GLOB}/power ; do printparm_ahci "$dev/control" done echo fi # -- docks cnt=0 for dock in $DOCK_GLOB; do [ ! -d "$dock" ] && break # no dock/bay detected # dock/bay detected, print header [ $cnt -eq 0 ] && echo "+++ Docks and Device Bays" cnt=$((cnt+1)) # get dock type { read -r dock_type < "$dock/type"; } 2>/dev/null # get dock state if check_is_docked; then # docked case $dock_type in ata_bay) dock_state="drive present" ;; battery_bay) dock_state="battery present" ;; dock_station) dock_state="docked" ;; *) dock_state="docked" dock_type="unknown" ;; esac else # not docked case $dock_type in ata_bay) dock_state="no drive (or powered off)" ;; battery_bay) dock_state="no battery " ;; dock_station) dock_state="undocked" ;; *) dock_state="undocked" dock_type="unknown" ;; esac fi # print dock data printf "%s: %-13s = %s\n" "$dock" "$dock_type" "$dock_state" done [ $cnt -gt 0 ] && echo fi # show_disk # --- show hybrid graphics switch (nouveau, radeon only) if [ "$show_graf" = "1" ] || [ "$show_all" = "1" ]; then if [ -f $SWITCHEROO ]; then echo "+++ Hybrid Graphics Switch" printparm_ml " " $SWITCHEROO fi # --- gpu data for all drivers show_gpu_data "$show_verbose" fi # show_graf # --- show rfkill state if [ "$show_rfkill" = "1" ] || [ "$show_all" = "1" ]; then echo "+++ Wireless" for i in bluetooth nfc wifi wwan; do get_rf_dev_state $i # shellcheck disable=SC2154 echo_device_state "$i" "$_devs" done echo ifshown=0 # --- bluetooth get_bluetooth_ifaces # shellcheck disable=SC2154 for iface in $_bifaces; do if [ -n "$iface" ]; then ifshown=1 # get bluetooth driver get_bluetooth_driver "$iface" printf "%-30s: bluetooth, " "$iface($_btdrv)" if bluetooth_in_use "$iface"; then echo "connected" else echo "not connected" fi fi done # --- wifi get_wifi_ifaces # shellcheck disable=SC2154 for iface in $_wifaces; do if [ -n "$iface" ]; then ifshown=1 # get wifi power mgmt state if cmd_exists "$IW"; then wifipm=$($IW dev "$iface" get power_save 2> /dev/null | \ grep 'Power save' | \ sed -r 's/.*Power save: (on|off).*/\1/') else wifipm="" fi # get wifi driver get_wifi_driver "$iface" printf "%-30s: wifi, " "$iface($_wifidrv)" if wireless_in_use "$iface"; then printf "connected, " else printf "not connected, " fi printf "power management = " case $wifipm in on|off) printf "%s" "$wifipm" ;; *) printf "unknown" ;; esac printf "\n" fi done # --- wwan get_wwan_ifaces # shellcheck disable=SC2154 for iface in $_wanifaces; do if [ -n "$iface" ]; then ifshown=1 # get wwan driver get_wwan_driver "$iface" printf "%-30s: wwan, " "$iface($_wwandrv)" if wireless_in_use "$iface"; then printf "connected" else printf "not connected" fi printf "\n" fi done [ "$ifshown" = "1" ] && echo [ "$show_verbose" = "1" ] && print_nm_rfkill_states fi # show_rfkill # --- show sound power mode if [ "$show_all" = "1" ]; then echo "+++ Audio" if [ -d /sys/module/snd_hda_intel ]; then printparm "%-58s = ##%s##" "/sys/module/snd_hda_intel/parameters/power_save" printparm "%-58s = ##%s##" "/sys/module/snd_hda_intel/parameters/power_save_controller" fi if [ -d /sys/module/snd_ac97_codec ]; then printparm "%s = ##%s##" "/sys/module/snd_ac97_codec/parameters/power_save" fi echo fi # show_all # --- show pcie info if [ "$show_pcie" = "1" ] || [ "$show_all" = "1" ]; then # --- aspm state echo "+++ PCIe Active State Power Management" if [ -f $ASPM ]; then pol=$(read_sysf $ASPM | sed -r 's/[[:space:]]+$//') apol=$(printf "%s" "$pol" | sed -r 's/.*\[(.*)\].*/\1/') if write_sysf "$apol" $ASPM; then echo "$ASPM = $pol" else echo "$ASPM = $pol (using BIOS preferences)" fi else echo "$ASPM = (not available)" fi echo # --- runtime pm echo "+++ PCIe Runtime Power Management" if [ -z "$RUNTIME_PM_ON_AC" ] && [ -z "$RUNTIME_PM_ON_BAT" ]; then echo "Autosuspend = disabled" else echo "Autosuspend = enabled" fi echo "Enable devices = ${RUNTIME_PM_ENABLE:-(disabled)}" echo "Disable devices = ${RUNTIME_PM_DISABLE:-(disabled)}" echo "Device denylist = ${RUNTIME_PM_DENYLIST:=(disabled)}" echo "Driver denylist = ${RUNTIME_PM_DRIVER_DENYLIST:-(disabled)}" echo if cmd_exists $TLPPCI; then if cmd_exists $LSPCI; then if [ $show_verbose -eq 1 ]; then $TLPPCI --verbose else $TLPPCI fi else cecho "Error: missing command $LSPCI. Information on PCIe devices cannot be displayed." 1>&2 fi else cecho "Error: missing subcommand $TLPPCI. Information on PCIe devices cannot be displayed." 1>&2 fi echo fi # show_pcie # --- show usb autosuspend if [ "$show_usb" = "1" ] || [ "$show_all" = "1" ]; then echo "+++ USB" if [ "$USB_AUTOSUSPEND" = "1" ]; then echo "Autosuspend = enabled" else echo "Autosuspend = disabled" fi echo "Device allowlist = ${USB_ALLOWLIST:=(not configured)}" echo "Device denylist = ${USB_DENYLIST:=(not configured)}" if [ "${USB_EXCLUDE_AUDIO:-0}" = "1" ]; then echo "Exclude audio = enabled" else echo "Exclude audio = disabled" fi if [ "${USB_EXCLUDE_BTUSB:-0}" = "1" ]; then echo "Exclude bluetooth = enabled" else echo "Exclude bluetooth = disabled" fi if [ "${USB_EXCLUDE_PHONE:-0}" = "1" ]; then echo "Exclude phones = enabled" else echo "Exclude phones = disabled" fi if [ "${USB_EXCLUDE_PRINTER:-0}" = "1" ]; then echo "Exclude printers = enabled" else echo "Exclude printers = disabled" fi if [ "${USB_EXCLUDE_WWAN:-1}" = "1" ]; then echo "Exclude WWAN = enabled" else echo "Exclude WWAN = disabled" fi echo if cmd_exists $TLPUSB; then if cmd_exists $LSUSB; then if [ $show_verbose -eq 1 ]; then $TLPUSB --verbose else $TLPUSB fi else cecho "Error: missing command $LSUSB. Information on USB devices cannot be displayed." 1>&2 fi else cecho "Error: missing subcommand $TLPUSB. Information on USB devices cannot be displayed." 1>&2 fi echo fi # show_usb # -- show battery info if [ "$show_bat" = "1" ] || [ "$show_all" = "1" ]; then select_batdrv batdrv_show_battery_data $show_verbose fi # show_bat # --- show warnings if [ "$show_warn" = "1" ] || [ "$show_disk" = "1" ] || [ "$show_all" = "1" ]; then # ata errors (possibly) caused by SATA_LINKPWR_ON_AC/BAT != max_performance ecnt=$(check_ata_errors) if [ "$ecnt" -ne 0 ]; then echo "+++ Warnings" printf "* Kernel log shows ata errors (%d) possibly caused by the configuration\n" "$ecnt" printf " SATA_LINKPWR_ON_AC/BAT=min_power or medium_power.\n" printf " Consider using medium_power or max_performance instead.\n" printf " See the FAQ: https://linrunner.de/en/tlp/docs/tlp-faq.html#warnings\n" printf " Details:\n" dmesg | grep -E -A 5 "${RE_ATA_ERROR}" echo elif [ "$show_warn" = "1" ]; then echo "No warnings detected." echo "" fi fi # show_warn # -- show recommendations if [ "$show_bat" = "1" ] || [ "$show_system" = "1" ] || [ "$show_all" = "1" ]; then # battery plugin specific recommendations if [ "$show_bat" = "1" ] || [ "$show_all" = "1" ]; then reout="$(batdrv_recommendations)" if [ -n "$reout" ]; then reout="$reout\n" fi else reout="" fi # other recommendations if [ "$show_system" = "1" ] || [ "$show_all" = "1" ]; then if check_ppd_running; then if check_systemd; then reout="${reout}Uninstall power-profiles-daemon or run 'systemctl mask $PPD_SERVICE' to ensure the full functionality of TLP\n" else reout="${reout}Uninstall power-profiles-daemon to ensure the full functionality of TLP\n" fi fi fi if [ "$show_all" = "1" ]; then cmd_exists ethtool || reout="${reout}Install ethtool to disable Wake-on-LAN\n" cmd_exists smartctl || reout="${reout}Install smartmontools for disk drive health info\n" fi if [ -n "$reout" ]; then cecho "+++ Recommendations" "notice" # shellcheck disable=SC2059 # don't change to %s, $reout contains blanks and \n! cecho "$(printf "$reout" | sed -r 's/^/\* /')" "notice" echo fi fi # show_all # --- show udev power_supply events if [ "$show_pev" = "1" ]; then # check for udevadm if cmd_exists "$UDEVADM"; then echo "+++ Monitor power supply events -- cancel with ^C" echo $UDEVADM monitor --udev --property --subsystem-match=power_supply fi fi # show_pev # --- show power_supply diagnostic if [ "$show_psup" = "1" ]; then printf "+++ Power supply diagnostic\n" for ps in /sys/class/power_supply/*; do # shellcheck disable=SC2063 printparm "%s: ##%s##" "$ps/type" _ printparm "%s: ##%s##" "$ps/usb_type" _ printparm "%s: ##%s##" "$ps/present" _ printparm "%s: ##%s##" "$ps/online" _ printparm "%s: ##%s##" "$ps/capacity" _ printparm "%s: ##%s##" "$ps/cycle_count" _ printparm "%s: ##%s##" "$ps/charge_full_design" _ printparm "%s: ##%s##" "$ps/charge_full" _ printparm "%s: ##%s##" "$ps/charge_now" _ printparm "%s: ##%s##" "$ps/current_now" _ printparm "%s: ##%s##" "$ps/energy_full_design" _ printparm "%s: ##%s##" "$ps/energy_full" _ printparm "%s: ##%s##" "$ps/energy_now" _ printparm "%s: ##%s##" "$ps/power_now" _ printparm "%s: ##%s##" "$ps/voltage_max" _ printparm "%s: ##%s##" "$ps/voltage_min" _ printparm "%s: ##%s##" "$ps/voltage_min_design" _ printparm "%s: ##%s##" "$ps/voltage_now" _ printparm "%s: ##%s##" "$ps/charge_control_start_threshold" _ printparm "%s: ##%s##" "$ps/charge_control_start_available_thresholds" _ printparm "%s: ##%s##" "$ps/charge_control_end_threshold" _ printparm "%s: ##%s##" "$ps/charge_control_end_available_thresholds" _ printparm "%s: ##%s##" "$ps/charge_behaviour" _ printparm "%s: ##%s##" "$ps/charge_types" _ printparm "%s: ##%s##" "$ps/charge_type" _ printparm "%s: ##%s##" "$ps/status" _ printparm "%s: ##%s##" "$ps/device/path" _ done printf "\n+++ udev diagnostic\n" check_udev_rule_ps fi # show_psup # --- show tlp-pd diagnostic if [ "$show_pddiag" = "1" ]; then printf "+++ tlp-pd Diagnostic\n" if [ "$(id -u)" = "0" ]; then echo_message "Notice: Please do not run 'tlp-stat --pd-diag' as root or with sudo" fi print_desktop_session if check_tlp_pd_installed; then echo_message "tlp-pd is installed." "success" if check_systemd; then if check_service_state "$TLPPD_SERVICE" enabled; then echo_message "tlp-pd.service is enabled." "success" else echo_message "tlp-pd.service is disabled." "err" fi fi if check_tlp_pd_running; then echo_message "tlp-pd is running." "success" else echo_message "tlp-pd is not running." "err" fi else echo_message "tlp-pd is not installed." "err" fi if cmd_exists "$GDBUS"; then if pd_version="$($GDBUS call -y -d "$TLPPD_BUS_NAME" -o "$TLPPD_OBJECT_PATH" -m "${PROPERTY_INTERFACE_NAME}.Get" \ "$TLPPD_INTERFACE_NAME" "Version" 2> /dev/null)"; then pd_version="$(echo "$pd_version" | sed -r "s/.*<'(.+)'>,.*/\1/")" printf "D-Bus Version: %s\n" "$pd_version" if [ "${pd_version#tlp-pd}" = "$pd_version" ]; then msg="Notice: a foreign Power Profile D-Bus service is running:" if check_ppd_running; then msg="$msg $PPD" elif check_tuned_ppd_running; then msg="$msg $TUNEDPPD" else msg="$msg unknown" fi echo_message "$msg" else if cmd_exists "$GDBUS"; then printf "D-Bus Properties:\n" $GDBUS call -y -d "$TLPPD_BUS_NAME" -o "$TLPPD_OBJECT_PATH" -m "${PROPERTY_INTERFACE_NAME}.GetAll" \ "$TLPPD_INTERFACE_NAME" 2> /dev/null | \ # Hack: sed script is customized to the GetAll() output instead of doing proper XML rendering sed -r -e 's/\{/\{ /' -e 's/}, \{/\n },\n \{ /g' -e 's/>,/>,\n /g' -e 's/<\[\{/<\[\n\ { /' -e 's/>\}\]>/\n \}\]>/' fi fi fi fi printf "\n" fi # show_pddiag # --- show udev diagnostic if [ "$show_udev" = "1" ]; then printf "+++ udev diagnostic\n" check_udev_rule_ps check_udev_rule_usb fi # show_udev # --- show debug log if [ "$show_trace" = "1" ]; then # check for systemd journal jdone=0 if [ "$show_trace_nm" = "1" ]; then log_ids="SYSLOG_IDENTIFIER=tlp SYSLOG_IDENTIFIER=tlp-pd SYSLOG_IDENTIFIER=NetworkManager" else log_ids="SYSLOG_IDENTIFIER=tlp SYSLOG_IDENTIFIER=tlp-pd" fi if cmd_exists $JOURNALCTL; then # retrieve trace output from journal, rc=1 if journald has no data available if [ $show_verbose -eq 1 ]; then # verbose: show all output # shellcheck disable=SC2086 $JOURNALCTL -p debug --output=short-precise --no-pager $log_ids 2> /dev/null && jdone=1 else # non-verbose: show output since last reboot only # shellcheck disable=SC2086 $JOURNALCTL -p debug --output=short-precise --no-pager $log_ids -b 2> /dev/null && jdone=1 fi fi if [ "$jdone" = "0" ]; then # no journald data available --> retrieve trace output from logfile if [ -f $DEBUGLOG ]; then if [ "$show_trace_nm" = "1" ]; then grep -E '(tlp|NetworkManager)\[' $DEBUGLOG else grep -E 'tlp\[' $DEBUGLOG fi else cecho "Error: $DEBUGLOG does not exist." 1>&2 echo 1>&2 echo "Solution: create an rsyslog conffile /etc/rsyslog.d/90-debug.conf with the following contents" 1>&2 echo " *.=debug;\\" 1>&2 echo " mail,authpriv,cron.none;\\" 1>&2 echo " local0,local1,local3,local4,\\" 1>&2 echo " local5,local6,local7.none -/var/log/debug" 1>&2 echo "and restart the rsyslog daemon." 1>&2 echo 1>&2 fi fi if [ "$show_trace_nm" = "1" ]; then echo print_nm_rfkill_states fi fi # show_trace do_exit 0 TLP-1.10.1/tlp-usb-udev.in000066400000000000000000000037701517565574500151430ustar00rootroot00000000000000#!/bin/sh # tlp - handle added usb devices # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # # Remark: the calling udev rule is triggered for "base" devices only, # not for the corresponding subdevices. # --- Source libraries for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/15-tlp-func-disk @TLP_FLIB@/20-tlp-func-usb; do # shellcheck disable=SC1090 . "$lib" || exit 70 done # --- MAIN # shellcheck disable=SC2034 _bgtask=1 # read configuration: quit on error, trace allowed read_config 0 # quit if TLP disabled check_tlp_enabled || do_exit 0 if [ "$X_USB_ENV_TRACE" = "1" ]; then echo_debug "usb" "tlp_usb_udev.env = $(printenv)" fi case "$1" in usb) # usb devices in general [ "$USB_AUTOSUSPEND" = "1" ] || do_exit 0 # quit if usb autosuspend disabled # USB autosuspend has two principal operation modes: # # Mode 1 (optional): # - System startup is handled by tlp-functions:set_usb_suspend() # - Startup completion is signaled by "flag file" $USB_DONE # - Newly added devices are handled by this udev script # - Mode 1 is enabled by the private config variable X_TLP_USB_MODE=1 # # Mode 2 (default): # - Everything - including system startup, but not shutdown - is handled by this udev script # quit if mode 1 and no startup completion flag [ "$X_TLP_USB_MODE" = "1" ] && ! check_run_flag "$USB_DONE" && do_exit 0 # handle device usb_suspend_device "/sys$2" "udev" ;; disk) # (s)ata disks attached via usb echo_debug "usb" "tlp_usb_udev.disk: bus=$ID_BUS dev=/sys$2 " get_next_power_profile usb-udev-disk dev="${2##*/block/}" # shellcheck disable=SC2154 set_ahci_disk_runtime_pm "$_pp_next" "$dev" set_disk_apm_level "$_pp_next" "$dev" set_disk_spindown_timeout "$_pp_next" "$dev" set_disk_iosched "$dev" ;; esac do_exit 0 TLP-1.10.1/tlp-usblist000066400000000000000000000070121517565574500144620ustar00rootroot00000000000000#!/usr/bin/perl # tlp-usblist - list usb device info with autosuspend attributes # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later package tlp_usblist; use strict; use warnings; # --- Constants use constant USBD => "/sys/bus/usb/devices"; # --- Modules use Getopt::Long; # --- Global vars my %usbdevices; my $verbose = 0; # --- Subroutines # Read content from a sysfile # $_[0]: input file # return: content / empty string if nonexistent or not readable sub catsysf { my $fname = "$_[0]"; my $sysval = ""; if ( open (my $sysf, "<", $fname) ) { chomp ($sysval = <$sysf>); close ($sysf); } return $sysval; } # Read device driver from DEVICE/uevent # $_[0]: (sub)device base path # return: driver / empty string if uevent nonexistent or not readable sub getdriver { my $dpath = "$_[0]"; my $driver = ""; if ( open (my $sysf, "<", $dpath . "/uevent") ) { # read file line by line while (<$sysf>) { # match line content and return DRIVER= value if ( s/^DRIVER=(.*)/$1/ ) { chomp ($driver = $_); last; # break loop } } close ($sysf); } return $driver } # Get drivers associated with USB device by iterating subdevices # $_[0]: device base path # return: driver list / "no driver" if none found sub usbdriverlist { my $dpath = "$_[0]"; my $driverlist = ""; # iterate subdevices foreach my $subdev (glob $dpath . "/*:*") { # get subdevice driver my $driver = getdriver ("$subdev"); if ( $driver ) { if (index ($driverlist, $driver) == -1) { if ($driverlist) { $driverlist = $driverlist . ", " . $driver; } else { $driverlist = $driver; } } # if index } # if $driver } # foreach $subdev if (! $driverlist) { $driverlist = "no driver"; } return $driverlist } # --- MAIN # parse arguments GetOptions ('verbose' => \$verbose); # Read USB device tree attributes as arrays into %usbdevices hash, indexed by Bus_Device foreach my $udev (grep { ! /:/ } glob USBD . "/*") { my $usbv = "(autosuspend not available)"; # get device id my $usbk = sprintf ("%03d_%03d", catsysf ("$udev/busnum"), catsysf ("$udev/devnum") ); # get device mode and timeout if ( length (my $ptimeout = catsysf ("$udev/power/autosuspend_delay_ms")) && length (my $pmode = catsysf ("$udev/power/control")) ) { if ( $verbose ) { # get device status my $pstatus = catsysf ("$udev/power/runtime_status"); # format: device mode, timeout, status $usbv = sprintf ("control = %-5s autosuspend_delay_ms = %4d, runtime_status = %-9s", $pmode . ",", $ptimeout, $pstatus); } else { # format: device mode, timeout $usbv = sprintf ("control = %-5s autosuspend_delay_ms = %4d", $pmode . ",", $ptimeout); } } # store formatted result in hash @{$usbdevices{$usbk}} = ($udev, $usbv); } # Output device list with attributes and drivers foreach (`lsusb 2> /dev/null`) { my ($bus, $dev, $usbid, $desc) = /Bus (\S+) Device (\S+): ID (\S+)[ ]+(.*)/; if (length ($bus) and length ($dev) and length ($usbid) ) { my $usbk = $bus . "_" . $dev; $desc =~ s/\s+$//; $desc ||= ""; print "Bus $bus Device $dev ID $usbid $usbdevices{$usbk}[1] -- $desc (" . usbdriverlist($usbdevices{$usbk}[0]) . ")\n"; } } exit 0; TLP-1.10.1/tlp.conf.in000066400000000000000000000543671517565574500143470ustar00rootroot00000000000000# ---------------------------------------------------------------------------- # /etc/tlp.conf - TLP user configuration (version @TLPVER@) # See full explanation: https://linrunner.de/tlp/settings # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # # Settings are read in the following order: # # 1. Intrinsic defaults # 2. /etc/tlp.d/*.conf - Drop-in customization snippets # 3. /etc/tlp.conf - User configuration (this file) # # TLP profiles: a part of TLP's parameters is divided into two or three # groups: # - performance: parameters ending in _AC are used when AC power is # connected or when the command 'tlp performance' is run. # - balanced: parameters ending in _BAT are used when operating # on battery power or when the command 'tlp balanced' is run. # - power-saver: parameters ending in _SAV are used when the command # 'tlp power-saver' is run. If there is no _SAV parameter available # for a feature, the _BAT parameter will be used instead. # - Any remaining parameters not divided apply to all TLP profiles. # # Please note: # - If parameters are specified more than once, the last occurrence takes # precedence. This also means that any parameters defined here will take # precedence over any drop-ins. # - You can however, append values to a parameter already defined as default # or in a previously read file: use PARAMETER+="add values". # - Important: in its native state, all of the parameters in this file are # commented out with a leading '#', so the defaults (if any) apply. # Remove the leading '#' if you want to enable a feature without a default # or if you want to set a value other than the default. # - Parameters must always be specified for all TLP profiles, i.e. in the # AC, BAT and SAV category (where applicable). If you omit one of them, # the missing profile will receive its value from another profile, since # a change will only occur if different values are defined. # - To completely disable a feature, use PARAMETER="". # Legend for defaults: # - Default *: effective value when the parameter is missing or the line has # a leading '#'. # - Default : feature disabled, use kernel or hardware defaults. # # ---------------------------------------------------------------------------- # tlp - Parameters for power saving # Set to 0 to disable, 1 to enable TLP. # Default: 1 #TLP_ENABLE=1 # Set to 1 to disable (almost) all defaults of TLP. # This means that TLP only applies settings that have been explicitly activated # i.e. parameters without leading '#'. # Notes: # - Helpful if one wants to use only selected features of TLP # - After activation, use tlp-stat -c to display your effective configuration # - TLP's operation relies on the defaults of TLP_ENABLE, TLP_WARN_LEVEL, # TLP_AUTO_SWITCH, and TLP_PROFILE_AC/BAT, which are *not* disabled # #TLP_DISABLE_DEFAULTS=1 # Control how warnings about invalid settings are issued: # 0=disabled # 1=background tasks (boot, resume, change of power source) report to syslog # 2=shell commands report to the terminal (stderr) # 3=combination of 1 and 2 # Default: 3 #TLP_WARN_LEVEL=3 # Colorize error, warning, notice and success messages. Colors are specified # with ANSI codes: # 1=bold black, 90=grey, 91=red, 92=green, 93=yellow, 94=blue, 95=magenta, # 96=cyan, 97=white. # Other colors are possible, refer to: # https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit # Colors must be specified in the order # " ". # By default, errors are shown in red, warnings in yellow, notices in bold # and success in green. # Default: "91 93 1 92" #TLP_MSG_COLORS="91 93 1 92" # Control automatic switching of the TLP profile when connecting or removing # the charger, when booting the system or when executing 'tlp start': # 0=disabled - never switch, use TLP_PROFILE_DEFAULT if configured # 1=auto - always switch, select TLP_PROFILE_AC on AC and TLP_PROFILE_BAT # on battery power. # 2=smart - like auto, but skips the switch if you have manually changed the # profile away from the previous power source's default profile # (as defined by TLP_PROFILE_AC/BAT). # Note: the same applies if the charger was connected/removed during suspend. # Default: 2 #TLP_AUTO_SWITCH=2 # TLP profiles to use when automatic switching is enabled # (TLP_AUTO_SWITCH=1 or 2): # PRF=performance, BAL=balanced, SAV=power-saver. # Default: PRF (AC power), BAL (battery power) #TLP_PROFILE_AC=BAL #TLP_PROFILE_BAT=SAV # TLP profile to use when automatic switching is disabled # (TLP_AUTO_SWITCH=0) or no power supply is detected: # PRF=performance, BAL=balanced, SAV=power-saver. # Note: legacy values AC and BAT continue to work. They are mapped to # PRF and BAL, respectively. # Default: #TLP_PROFILE_DEFAULT=BAL # Power supply classes to ignore when determining TLP profile: # AC, USB, BAT. # Separate multiple classes with spaces. # Note: try on laptops where operation mode AC/BAT is incorrectly detected. # Default: #TLP_PS_IGNORE="BAT" # Seconds laptop mode has to wait after the disk goes idle before doing a # sync. Non-zero value enables, zero disables laptop mode. # Default: 0 (AC), 2 (BAT) #DISK_IDLE_SECS_ON_AC=0 #DISK_IDLE_SECS_ON_BAT=2 # Dirty page values (timeouts in secs). # Default: 15 (AC), 60 (BAT) #MAX_LOST_WORK_SECS_ON_AC=15 #MAX_LOST_WORK_SECS_ON_BAT=60 # Select a CPU scaling driver operation mode. # Intel CPU with intel_pstate driver: # active, passive. # AMD Zen 2 or newer CPU with amd-pstate driver as of kernel 6.3/6.4(*): # active, passive, guided(*). # Default: #CPU_DRIVER_OPMODE_ON_AC=active #CPU_DRIVER_OPMODE_ON_BAT=active #CPU_DRIVER_OPMODE_ON_SAV=active # Select a CPU frequency scaling governor. # Intel CPU with intel_pstate driver or # AMD CPU with amd-pstate driver in active mode ('amd-pstate-epp'): # performance, powersave(*). # Intel CPU with intel_pstate driver in passive mode ('intel_cpufreq') or # AMD CPU with amd-pstate driver in passive or guided mode ('amd-pstate') or # Intel, AMD and other CPU brands with acpi-cpufreq driver: # conservative, ondemand(*), userspace, powersave, performance, schedutil(*) # Use tlp-stat -p to show the active driver and available governors. # Important: # Governors marked (*) above are power efficient for *almost all* workloads # and therefore kernel and most distributions have chosen them as defaults. # You should have done your research about advantages/disadvantages *before* # changing the governor. # Default: #CPU_SCALING_GOVERNOR_ON_AC=performance #CPU_SCALING_GOVERNOR_ON_BAT=powersave #CPU_SCALING_GOVERNOR_ON_SAV=powersave # Set the min/max frequency available for the scaling governor. # Possible values depend on your CPU. For available frequencies see # the output of tlp-stat -p. # Notes: # - Min/max frequencies must always be specified for both AC *and* BAT # - Not recommended for use with the intel_pstate driver, use # CPU_MIN/MAX_PERF_ON_AC/BAT below instead # Default: #CPU_SCALING_MIN_FREQ_ON_AC=0 #CPU_SCALING_MAX_FREQ_ON_AC=0 #CPU_SCALING_MIN_FREQ_ON_BAT=0 #CPU_SCALING_MAX_FREQ_ON_BAT=0 #CPU_SCALING_MIN_FREQ_ON_SAV=0 #CPU_SCALING_MAX_FREQ_ON_SAV=0 # Set CPU energy/performance policies EPP and EPB: # performance, balance_performance, default, balance_power, power. # Values are given in order of increasing power saving. # Requires: # * Intel CPU # EPP: Intel Core i 6th gen. or newer CPU with intel_pstate driver # EPB: Intel Core i 2nd gen. or newer CPU with intel_pstate driver # EPP and EPB are mutually exclusive: when EPP is available, Intel CPUs # will not honor EPB. Only the matching feature will be applied by TLP. # * AMD Zen 2 or newer CPU # EPP: amd-pstate driver in active mode ('amd-pstate-epp') as of kernel 6.3 # Default: balance_performance (AC), balance_power (BAT), power (SAV) #CPU_ENERGY_PERF_POLICY_ON_AC=balance_performance #CPU_ENERGY_PERF_POLICY_ON_BAT=balance_power #CPU_ENERGY_PERF_POLICY_ON_SAV=power # Set Intel CPU P-state performance: 0..100 (%). # Limit the max/min P-state to control the power dissipation of the CPU. # Values are stated as a percentage of the available performance. # Requires Intel Core i 2nd gen. or newer CPU with intel_pstate driver. # Default: #CPU_MIN_PERF_ON_AC=0 #CPU_MAX_PERF_ON_AC=100 #CPU_MIN_PERF_ON_BAT=0 #CPU_MAX_PERF_ON_BAT=80 #CPU_MIN_PERF_ON_SAV=0 #CPU_MAX_PERF_ON_SAV=60 # Set the CPU "turbo boost" (Intel) or "core performance boost" (AMD) feature: # 0=disable, 1=allow. # Allows to raise the maximum frequency/P-state of some cores if the # CPU chip is not fully utilized and below it's intended thermal budget. # Note: a value of 1 does *not* activate boosting, it just allows it. # Default: #CPU_BOOST_ON_AC=1 #CPU_BOOST_ON_BAT=1 #CPU_BOOST_ON_SAV=0 # Set CPU dynamic boost feature: # 0=disable, 1=enable. # Improve performance by increasing minimum P-state limit dynamically # whenever a task previously waiting on I/O is selected to run. # Requires Intel Core i 6th gen. or newer CPU with intel_pstate driver # in active mode. # Note: AMD CPUs currently have no tunable for this. # Default: #CPU_HWP_DYN_BOOST_ON_AC=1 #CPU_HWP_DYN_BOOST_ON_BAT=1 #CPU_HWP_DYN_BOOST_ON_SAV=0 # Kernel NMI Watchdog: # 0=disable (default, saves power), 1=enable (for kernel debugging only). # Default: 0 #NMI_WATCHDOG=0 # Select platform profile: # performance, balanced, low-power. # Controls system operating characteristics around power/performance levels, # thermal and fan speed. Values are given in order of increasing power saving. # Note: check the output of tlp-stat -p to determine availability on your # hardware and additional profiles such as: balanced-performance, quiet, cool. # Default: performance (AC), balanced (BAT), low-power (SAV) #PLATFORM_PROFILE_ON_AC=performance #PLATFORM_PROFILE_ON_BAT=balanced #PLATFORM_PROFILE_ON_SAV=low-power # System suspend mode: # s2idle: Idle standby - a pure software, light-weight, system sleep state, # deep: Suspend to RAM - the whole system is put into a low-power state, # except for memory, usually resulting in higher savings than s2idle. # CAUTION: changing suspend mode may lead to system instability and even # data loss. As for the availability of different modes on your system, # check the output of tlp-stat -s. If unsure, stick with the system default # by not enabling this. # Default: #MEM_SLEEP_ON_AC=s2idle #MEM_SLEEP_ON_BAT=deep # Define disk devices on which the following DISK/AHCI_RUNTIME parameters act. # Separate multiple devices with spaces. # Devices can be specified by disk ID also (lookup with: tlp diskid). # Default: "nvme0n1 sda" #DISK_DEVICES="nvme0n1 sda" # Disk advanced power management level: 1..254, 255 (max saving, min, off). # Levels 1..127 may spin down the disk; 255 allowable on most drives. # Separate values for multiple disks with spaces. Use the special value 'keep' # to keep the hardware default for the particular disk. # Default: 254 (AC), 128 (BAT) #DISK_APM_LEVEL_ON_AC="254 254" #DISK_APM_LEVEL_ON_BAT="128 128" # Exclude disk classes from advanced power management (APM): # sata, ata, usb, ieee1394. # Separate multiple classes with spaces. # CAUTION: USB and IEEE1394 disks may fail to mount or data may get corrupted # with APM enabled. Be careful and make sure you have backups of all affected # media before removing 'usb' or 'ieee1394' from the denylist! # Default: "usb ieee1394" #DISK_APM_CLASS_DENYLIST="usb ieee1394" # Hard disk spin down timeout: # 0: spin down disabled # 1..240: timeouts from 5s to 20min (in units of 5s) # 241..251: timeouts from 30min to 5.5 hours (in units of 30min) # See 'man hdparm' for details. # Separate values for multiple disks with spaces. Use the special value 'keep' # to keep the hardware default for the particular disk. # Default: #DISK_SPINDOWN_TIMEOUT_ON_AC="0 0" #DISK_SPINDOWN_TIMEOUT_ON_BAT="0 0" # Select I/O scheduler for the disk devices. # Multi queue (blk-mq) schedulers: # mq-deadline(*), none, kyber, bfq # Single queue schedulers: # deadline(*), cfq, bfq, noop # (*) recommended. # Separate values for multiple disks with spaces. Use the special value 'keep' # to keep the kernel default scheduler for the particular disk. # Notes: # - Multi queue (blk-mq) may need kernel boot option 'scsi_mod.use_blk_mq=1' # and 'modprobe mq-deadline-iosched|kyber|bfq' on kernels < 5.0 # - Single queue schedulers are legacy now and were removed together with # the old block layer in kernel 5.0 # Default: keep #DISK_IOSCHED="mq-deadline mq-deadline" # AHCI link power management (ALPM) for SATA disks: # min_power, med_power_with_dipm(*), medium_power, max_performance. # (*) recommended. # Multiple values separated with spaces are tried sequentially until success. # Default: med_power_with_dipm (AC & BAT) #SATA_LINKPWR_ON_AC="med_power_with_dipm" #SATA_LINKPWR_ON_BAT="med_power_with_dipm" # Exclude SATA links from AHCI link power management (ALPM). # SATA links are specified by their host. Refer to the output of # tlp-stat -d to determine the host; the format is "hostX". # Separate multiple hosts with spaces. # Default: #SATA_LINKPWR_DENYLIST="host1" # Runtime Power Management for NVMe, SATA, ATA and USB disks # as well as SATA ports: # on=disable, auto=enable. # Note: SATA controllers are PCIe bus devices and handled by RUNTIME_PM # further down. # Default: on (AC), auto (BAT) #AHCI_RUNTIME_PM_ON_AC=on #AHCI_RUNTIME_PM_ON_BAT=auto # Seconds of inactivity before disk is suspended. # Note: effective only when AHCI_RUNTIME_PM_ON_AC/BAT is activated. # Default: 15 #AHCI_RUNTIME_PM_TIMEOUT=15 # Power off optical drive in UltraBay/MediaBay: 0=disable, 1=enable. # Drive can be powered on again by releasing (and reinserting) the eject lever # or by pressing the disc eject button on newer models. # Note: an UltraBay/MediaBay hard disk is never powered off. # Default: 0 #BAY_POWEROFF_ON_AC=0 #BAY_POWEROFF_ON_BAT=0 # Optical drive device to power off # Default: sr0 #BAY_DEVICE="sr0" # Intel GPU power management. # Select power profile for the Intel Xe GPU: base, power_saving. # Note: requires xe driver. # Default: base (AC), power_saving (BAT), power_saving (SAV) # INTEL_GPU_POWER_PROFILE_ON_AC=base # INTEL_GPU_POWER_PROFILE_ON_BAT=base # INTEL_GPU_POWER_PROFILE_ON_SAV=power_saving # Set the min/max/turbo frequency for the Intel GPU. # Parameters actually used depend on the driver: # - i915 driver supports min, max and turbo(boost) frequency. # - xe driver supports min and max frequency only. # Default: #INTEL_GPU_MIN_FREQ_ON_AC=0 #INTEL_GPU_MIN_FREQ_ON_BAT=0 #INTEL_GPU_MIN_FREQ_ON_SAV=0 #INTEL_GPU_MAX_FREQ_ON_AC=0 #INTEL_GPU_MAX_FREQ_ON_BAT=0 #INTEL_GPU_MAX_FREQ_ON_SAV=0 #INTEL_GPU_BOOST_FREQ_ON_AC=0 #INTEL_GPU_BOOST_FREQ_ON_BAT=0 #INTEL_GPU_BOOST_FREQ_ON_SAV=0 # AMD GPU power management. # Performance level (DPM): high, auto, low; auto is recommended. # Note: requires amdgpu or radeon driver. # Default: auto (AC), auto (BAT), low (SAV) #RADEON_DPM_PERF_LEVEL_ON_AC=auto #RADEON_DPM_PERF_LEVEL_ON_BAT=auto #RADEON_DPM_PERF_LEVEL_ON_SAV=low # Display panel adaptive backlight modulation (ABM) level: 0(off), 1..4. # Values 1..4 control the maximum brightness reduction allowed by the ABM # algorithm, where 1 represents the least and 4 the most power saving. # Notes: # - Requires AMD Vega or newer GPU with amdgpu driver as of kernel 6.9 # - Savings are made at the expense of color balance # Default: 0 (AC), 1 (BAT), 3 (SAV) #AMDGPU_ABM_LEVEL_ON_AC=0 #AMDGPU_ABM_LEVEL_ON_BAT=1 #AMDGPU_ABM_LEVEL_ON_SAV=3 # Wi-Fi power saving mode: on=enable, off=disable. # Default: off (AC), on (BAT) #WIFI_PWR_ON_AC=off #WIFI_PWR_ON_BAT=on # Disable Wake-on-LAN: Y/N. # Default: Y #WOL_DISABLE=Y # Enable audio power saving for Intel HDA, AC97 devices (timeout in secs). # A value of 0 disables, >= 1 enables power saving. # Note: 1 is recommended for Linux desktop environments with PulseAudio, # systems without PulseAudio may require 10. # Default: 1 #SOUND_POWER_SAVE_ON_AC=1 #SOUND_POWER_SAVE_ON_BAT=1 # Disable controller too (HDA only): Y/N. # Note: effective only when SOUND_POWER_SAVE_ON_AC/BAT is activated. # Default: Y #SOUND_POWER_SAVE_CONTROLLER=Y # PCIe Active State Power Management (ASPM): # default(*), performance, powersave, powersupersave. # (*) keeps BIOS ASPM defaults (recommended) # Default: default #PCIE_ASPM_ON_AC=default #PCIE_ASPM_ON_BAT=default #PCIE_ASPM_ON_SAV=default # Autosuspend PCIe devices (Runtime Power Management): # on=disable, auto=enable. # Default: on (AC), auto (BAT) #RUNTIME_PM_ON_AC=on #RUNTIME_PM_ON_BAT=auto # Exclude listed PCIe device adresses from autosuspend. # Note: this preserves the kernel driver default, to force a certain state # use RUNTIME_PM_ENABLE/DISABLE instead. # Separate multiple addresses with spaces. # Use lspci to get the adresses (1st column). # Default: #RUNTIME_PM_DENYLIST="11:22.3 44:55.6" # Exclude PCIe devices assigned to the listed drivers from autosuspend. # Note: this preserves the kernel driver default, to force a certain state # use RUNTIME_PM_ENABLE/DISABLE instead. # Separate multiple drivers with spaces, use "" to disable completely. # Default: "amdgpu mei_me nouveau nvidia xhci_hcd" #RUNTIME_PM_DRIVER_DENYLIST="amdgpu mei_me nouveau nvidia xhci_hcd" # Permanently enable/disable autosuspend for listed PCIe device addresses # (independent of the power source). This has priority over all preceding # autosuspend settings. Separate multiple addresses with spaces. # Use lspci to get the adresses (1st column). # Default: #RUNTIME_PM_ENABLE="11:22.3" #RUNTIME_PM_DISABLE="44:55.6" # Autosuspend USB devices: # 0=disable, 1=enable. # Default: 1 #USB_AUTOSUSPEND=1 # Exclude listed devices from USB autosuspend (separate with spaces). # Use lsusb to get the ids. # Note: input devices (usbhid) and libsane-supported scanners are excluded # automatically. # Default: #USB_DENYLIST="1111:2222 3333:4444" # Exclude audio devices from USB autosuspend: # 0=do not exclude, 1=exclude. # Default: 1 #USB_EXCLUDE_AUDIO=1 # Exclude bluetooth devices from USB autosuspend: # 0=do not exclude, 1=exclude. # Default: 0 #USB_EXCLUDE_BTUSB=0 # Exclude phone devices from USB autosuspend: # 0=do not exclude, 1=exclude (enable charging). # Default: 0 #USB_EXCLUDE_PHONE=0 # Exclude printers from USB autosuspend: # 0=do not exclude, 1=exclude. # Default: 1 #USB_EXCLUDE_PRINTER=1 # Exclude WWAN devices from USB autosuspend: # 0=do not exclude, 1=exclude. # Default: 0 #USB_EXCLUDE_WWAN=0 # Allow USB autosuspend for listed devices even if already denylisted or # excluded above (separate with spaces). Use lsusb to get the ids. # Default: 0 #USB_ALLOWLIST="1111:2222 3333:4444" # Radio devices to disable on startup: bluetooth, nfc, wifi, wwan. # Separate multiple devices with spaces. # Default: #DEVICES_TO_DISABLE_ON_STARTUP="bluetooth nfc wifi wwan" # Radio devices to enable on startup: bluetooth, nfc, wifi, wwan. # Separate multiple devices with spaces. # Default: #DEVICES_TO_ENABLE_ON_STARTUP="wifi" # Radio devices to enable when switching to the performance profile: # bluetooth, nfc, wifi, wwan. # Default: #DEVICES_TO_ENABLE_ON_AC="bluetooth nfc wifi wwan" # Radio devices to disable when switching to the balanced or power-saver # profile: # bluetooth, nfc, wifi, wwan. # Default: #DEVICES_TO_DISABLE_ON_BAT="bluetooth nfc wifi wwan" # Radio devices to disable when not in use (not connected) and switching # to the balanced or power-saver profile: # bluetooth, nfc, wifi, wwan. # Default: #DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE="bluetooth nfc wifi wwan" # Battery Care -- Charge thresholds # Charging will start once the charger is connected and the battery level # is below the start threshold. Charging stops when the battery level # reaches or exceeds the stop threshold. # Required hardware: Lenovo ThinkPads and other laptop brands are driven # via specific plugins: # - Use the tlp-stat -b command to see if a plugin for your hardware is # active and to look up vendor-specific threshold values. Some # laptops support only 1 (on)/0 (off) instead of a percentage level. # - If your hardware supports a start *and* a stop threshold, you must # specify both, otherwise TLP will refuse to apply the single threshold. # - If your hardware supports only a stop threshold, set the start # value to 0. # - The names of the batteries shown by tlp-stat -b don't have to match # the _BAT0 or _BAT1 parameter qualifiers. Please refer to [2] # to see which qualifier applies to which battery. # For further explanation and all vendor specific details refer to # [1] https://linrunner.de/tlp/settings/battery.html # [2] https://linrunner.de/tlp/settings/bc-vendors.html # BAT0: Main battery # Default: # Charging starts once the battery level is below this threshold. #START_CHARGE_THRESH_BAT0=75 # Charging stops when the battery level reaches or exceeds this threshold. #STOP_CHARGE_THRESH_BAT0=80 # BAT1: Secondary battery (primary on some laptops) # Default: # Charging starts once the battery level is below this threshold. #START_CHARGE_THRESH_BAT1=75 # Charging stops when the battery level reaches or exceeds this threshold. #STOP_CHARGE_THRESH_BAT1=80 # Restore charge thresholds when AC is unplugged: 0=disable, 1=enable. # Default: 1 #RESTORE_THRESHOLDS_ON_BAT=0 # ---------------------------------------------------------------------------- # tlp-rdw - Radio Device Wizard # Note: requires installation of the optional package tlp-rdw. # Possible devices: bluetooth, wifi, wwan. # Separate multiple radio devices with spaces. # Default: (for all parameters below) # Radio devices to disable on connect. #DEVICES_TO_DISABLE_ON_LAN_CONNECT="wifi wwan" #DEVICES_TO_DISABLE_ON_WIFI_CONNECT="wwan" #DEVICES_TO_DISABLE_ON_WWAN_CONNECT="wifi" # Radio devices to enable on disconnect. #DEVICES_TO_ENABLE_ON_LAN_DISCONNECT="wifi wwan" #DEVICES_TO_ENABLE_ON_WIFI_DISCONNECT="" #DEVICES_TO_ENABLE_ON_WWAN_DISCONNECT="" # Radio devices to enable/disable when docked. # Note: not all docks can be recognized, especially USB-C docks. If a LAN # cable is connected to the dock, use DEVICES_TO_DISABLE_ON_LAN_CONNECT # and DEVICES_TO_ENABLE_ON_LAN_DISCONNECT instead. #DEVICES_TO_ENABLE_ON_DOCK="" #DEVICES_TO_DISABLE_ON_DOCK="" # Radio devices to enable/disable when undocked. #DEVICES_TO_ENABLE_ON_UNDOCK="wifi" #DEVICES_TO_DISABLE_ON_UNDOCK="" TLP-1.10.1/tlp.in000066400000000000000000000405021517565574500134050ustar00rootroot00000000000000#!/bin/sh # tlp - adjust power settings # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Source libraries for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/[0-9][0-9]*; do # shellcheck disable=SC1090 . "$lib" || exit 70 done # --- Constants # --- Subroutines apply_common_settings () { # apply settings common to all modes # $1: profile: PP_PRF=0/PP_BAL=1/PP_SAV=2 set_laptopmode "$1" set_dirty_parms "$1" set_platform_profile "$1" set_cpu_driver_opmode "$1" set_cpu_scaling_governor "$1" set_cpu_scaling_min_max_freq "$1" set_intel_cpu_perf_pct "$1" set_cpu_boost_all "$1" set_cpu_dyn_boost "$1" set_cpu_perf_policy "$1" set_nmi_watchdog set_mem_sleep "$1" set_ahci_port_runtime_pm "$1" set_runtime_pm "$1" set_ahci_disk_runtime_pm "$1" set_sata_link_power "$1" set_disk_apm_level "$1" set_disk_spindown_timeout "$1" set_disk_iosched set_pcie_aspm "$1" set_intel_gpu_power_profile "$1" set_intel_gpu_min_max_boost_freq "$1" set_amdgpu_profile "$1" set_abm_level "$1" set_wifi_power_mode "$1" disable_wake_on_lan set_sound_power_mode "$1" return 0 } apply_suspend_settings () { # apply settings before suspending set_ahci_port_runtime_pm "$PP_SUS" set_ahci_disk_runtime_pm "$PP_SUS" set_pcie_aspm "$PP_SUS" return 0 } show_usage () { echo "Usage: tlp {performance | ac |" 1>&2 echo " balanced | bat |" 1>&2 echo " power-saver | usb |" 1>&2 echo " setcharge | chargeonce | fullcharge |" 1>&2 echo " discharge | recalibrate |" 1>&2 echo " bayoff | diskid |" 1>&2 echo " --version}" 1>&2 do_exit 3 } parse_args () { # parse command-line arguments # $@: arguments to parse # retval: $_cmd: command; # $_cmd2: subcommand; # $_carg1, # $_carg2, # $_carg3: command arguments # parsing control: 'nil' means that the element is still expected _cmd="nil" _cmd2="nil" _carg1="nil" _carg2="nil" _carg3="nil" # iterate arguments until exhausted or delimiter '--' reached while [ $# -gt 0 ]; do if [ "$1" = "--" ]; then break; elif [ "$_cmd" = "nil" ]; then # command case "$1" in ac|auto|bat|balanced|bayoff|diskid|performance|power-saver|resume|suspend|start|usb) # commands without further arguments _cmd="$1" _cmd2="" _carg1="" _carg2="" _carg3="" ;; chargeonce|fullcharge|recalibrate) # commands with one or no arguments _cmd="$1" _cmd2="" _carg2="" _carg3="" ;; discharge) # command with up to two arguments _cmd="$1" _cmd2="" _carg3="" ;; setcharge) # command with up to three arguments _cmd="$1" _cmd2="" ;; init) # command with subcommand and no arguments _cmd="$1" if [ -z "$2" ]; then cecho "Error: missing subcommand" 1>&2 echo "Usage: tlp init {start|stop|restart|force-reload}" 1>&2 do_exit 3 fi _carg1="" _carg2="" _carg3="" ;; stat) # unsupported command cecho "Error: 'tlp stat' no longer supported, use 'tlp-stat' instead." 1>&2 do_exit 3 ;; noop) # no operation _cmd="$1" _cmd2="" ;; --version) _cmd="version" _cmd2="" ;; *) # unknown command cecho "Error: unknown command \"$1\"." 1>&2 show_usage do_exit 3 ;; esac elif [ "$_cmd2" = "nil" ]; then # subcommand case "$1" in start|stop|restart|force-reload) _cmd2="$1" ;; *) # unknown subcommand cecho "Error: unknown subcommand \"$1\"." 1>&2 echo "Usage: tlp init {start|stop|restart|force-reload}" 1>&2 do_exit 3 ;; esac elif [ "$_carg1" = "nil" ]; then # first command argument _carg1="$1" elif [ "$_carg2" = "nil" ]; then # second command argument _carg2="$1" elif [ "$_carg3" = "nil" ]; then # third command argument _carg3="$1" fi shift # next argument done # while arguments if [ "$_cmd" = "nil" ]; then # no command parsed show_usage do_exit 3 fi # clear missing arguments [ "$_carg1" = "nil" ] && _carg1="" [ "$_carg2" = "nil" ] && _carg2="" [ "$_carg3" = "nil" ] && _carg3="" return 0 } # --- MAIN parse_args "$@" if [ "$_cmd" = "version" ]; then print_version exit 0 fi # read configuration: quit on error, trace allowed read_config 0 "$@" parse_args4config "$@" cprintf_init check_tlp_enabled 1 || do_exit 1 add_sbin2path if [ -z "$_cmd2" ]; then echo_debug "run" "+++ $_cmd ($TLPVER) ++++++++++++++++++++++++++++++++++++++++" else echo_debug "run" "+++ $_cmd $_cmd2 ($TLPVER) ++++++++++++++++++++++++++++++++++++++++" fi # shellcheck disable=SC2154 if [ -n "$_addpath" ]; then # shellcheck disable=SC2154 echo_debug "path" "PATH=${_oldpath}[${_addpath}]" else # shellcheck disable=SC2154 echo_debug "path" "PATH=${_oldpath}" fi echo_debug "run" "SHELL=$(print_shell); umask=$(umask)" get_next_power_profile "$_cmd" # shellcheck disable=SC2154 case "$_syspwr" in "$PS_AC") echo_debug "run" "power_source=ac($_syspwr)" ;; "$PS_BAT") echo_debug "run" "power_source=bat($_syspwr)" ;; *) echo_debug "run" "power_source=unknown($_syspwr)" ;; esac # shellcheck disable=SC2154 echo_debug "run" "power_profile=$(pp2str "$_pp_next")($_pp_next)" # process command exitcode=0 case "$_cmd" in init) # system initialization/shutdown: sysv, upstart, systemd, ... check_root # try to obtain lock (with timeout) locked=0 if lock_tlp; then locked=1 else echo "Failed to get lock, continuing anyway." 1>&2 fi # do init business ... # shellcheck disable=SC2034 _bgtask=1 case "$_cmd2" in start) # apply power save settings has_power_profile_changed printf "Applying power save settings..." apply_common_settings "$_pp_next" notify_profile_daemon "$(pp2str "$_pp_next")" poweroff_drivebay "$_pp_next" 0 [ "$X_TLP_USB_MODE" = "1" ] && set_usb_suspend 0 auto echo "done." # apply battery settings printf "Setting battery charge thresholds..." init_batteries_thresholds echo "done." # apply radio states set_radio_device_states start ;; restart|force-reload) # apply power save settings has_power_profile_changed printf "Applying power save settings..." apply_common_settings "$_pp_next" notify_profile_daemon "$(pp2str "$_pp_next")" poweroff_drivebay "$_pp_next" 0 [ "$X_TLP_USB_MODE" = "1" ] && set_usb_suspend 0 auto echo "done." # apply battery settings printf "Setting battery charge thresholds..." init_batteries_thresholds echo "done." ;; stop) # remove usb startup flag [ -f "$USB_DONE" ] && rm "$USB_DONE" # clear saved power state clear_saved_power_profile if [ "$X_TLP_SHUTDOWN_ACMODE" = "1" ]; then # workaround (optional): apply ac settings printf "Applying power save settings..." apply_common_settings 0 poweroff_drivebay "$_pp_next" 0 echo "done." fi # apply radio states set_radio_device_states stop ;; esac save_runconf # unlock if necessary [ $locked -eq 0 ] || unlock_tlp ;; auto) # apply profile depending on power source and manual/persistent modes (called by udev rule) # -- but only if not previously run for the same power profile # rationale: filter out duplicate power_supply udev events check_root # shellcheck disable=SC2034 _bgtask=1 check_services_activation_status if lock_tlp_nb; then if has_power_profile_changed; then apply_common_settings "$_pp_next" notify_profile_daemon "$(pp2str "$_pp_next")" poweroff_drivebay "$_pp_next" 0 set_radio_device_states "$_pp_next" save_runconf fi # shellcheck disable=SC2154 if [ "$RESTORE_THRESHOLDS_ON_BAT" = "1" ] \ && [ "$_syspwr" = "$PS_BAT" ] \ && [ "$_syspwr" != "$_ps_last" ]; then # Power source has changed to battery # --> apply charge thresholds if there is no force-discharge in progress if ! lockpeek_tlp tlp_discharge; then init_batteries_thresholds fi fi unlock_tlp fi ;; start) # apply profile depending on power source and manual/persistent modes (interactive mode) check_services_activation_status check_root if lock_tlp; then has_power_profile_changed apply_common_settings "$_pp_next" notify_profile_daemon "$(pp2str "$_pp_next")" poweroff_drivebay "$_pp_next" 0 set_usb_suspend 0 auto # apply charge thresholds if there is no force-discharge in progress if ! lockpeek_tlp tlp_discharge; then init_batteries_thresholds fi set_radio_device_states "$_pp_next" save_runconf unlock_tlp echo_started_profile "$_pp_next" "start" else echo_tlp_locked fi ;; performance|ac|balanced|bat|power-saver) # apply profile for $_cmd check_services_activation_status check_root if lock_tlp; then has_power_profile_changed apply_common_settings "$_pp_next" notify_profile_daemon "$(pp2str "$_pp_next")" poweroff_drivebay "$_pp_next" 0 [ "$X_TLP_USB_MODE" = "1" ] && set_usb_suspend 0 auto set_radio_device_states "$_pp_next" save_runconf unlock_tlp echo_started_profile "$_pp_next" else echo_tlp_locked fi ;; suspend) # handle suspend/hibernate check_root if [ "${X_TLP_SUSPEND_RFKILL_PERSIST:-1}" != "0" ]; then if [ -x "@TLP_SDSL@/system76-driver_bluetooth-suspend" ] \ && [ ! -x "@TLP_SDSL@/pop-default-settings_bluetooth-suspend" ] ; then # system76's suspend/resume hack is active --> do not save bluetooth state # refer to https://github.com/linrunner/TLP/issues/872 save_device_states "wwan" else save_device_states "bluetooth wwan" fi fi suspend_drivebay "$_pp_next" case "$X_TLP_SUSPEND_ACMODE" in 1) # workaround optional: apply performance profile if lock_tlp; then apply_common_settings "$PP_PRF" save_runconf unlock_tlp fi ;; 0) # workaround disabled: do nothing ;; *) # default: apply only selected settings to avoid freezes on wakeup if lock_tlp; then apply_suspend_settings save_runconf unlock_tlp fi ;; esac ;; resume) # handle resume check_root if lock_tlp; then restore_device_states has_power_profile_changed apply_common_settings "$_pp_next" notify_profile_daemon "$(pp2str "$_pp_next")" resume_drivebay "$_pp_next" # shellcheck disable=SC2154 if [ "$RESTORE_THRESHOLDS_ON_BAT" = "1" ] \ && [ "$_syspwr" = "$PS_BAT" ] \ && [ "$_syspwr" != "$_ps_last" ]; then # Power source has changed to battery # --> apply charge thresholds if there is no force-discharge in progress if ! lockpeek_tlp tlp_discharge; then init_batteries_thresholds fi else # Specific hardware which resets the EC when resuming # --> apply charge thresholds # Note: no hardware capabable of force-discharge concerned so far init_batteries_thresholds "asus huawei lg" fi save_runconf unlock_tlp fi ;; usb) # enable usb autosuspend check_root set_usb_suspend 1 auto ;; bayoff) # power off drive bay check_root poweroff_drivebay "$_pp_next" 1 ;; setcharge) # set charge thresholds (temporarily) check_root # quoting args will break $# in setcharge_battery() # shellcheck disable=SC2086 setcharge_battery $_carg1 $_carg2 $_carg3 exitcode=$? ;; fullcharge) # charge battery to 100% (temporarily) if check_ac_power fullcharge; then check_root # quoting args will break $# in setcharge_battery() # shellcheck disable=SC2086 setcharge_battery DEF DEF $_carg1 exitcode=$? if [ $exitcode -eq 0 ]; then cecho "Charging starts now, keep AC connected." "notice" 1>&2 fi else exitcode=2 fi ;; chargeonce) # charge battery to stop threshold once if check_ac_power chargeonce; then check_root # shellcheck disable=SC2086 chargeonce_battery "$_carg1" exitcode=$? else exitcode=2 fi ;; discharge) # discharge battery to target soc or completely discharge_battery discharge "$_carg1" "$_carg2" exitcode=$? ;; recalibrate) # recalibrate battery, i.e. discharge completely and charge to 100% discharge_battery recalibrate "$_carg1" exitcode=$? ;; diskid) # show disk id's show_disk_ids ;; noop) # debug: no operation check_root select_batdrv batdrv_select_battery "DEF" save_runconf echo_message "Debug: no operation performed." echo_message "Error: message color test." echo_message "Warning: message color test." echo_message "Notice: message color test." echo_message "Success: message color test." "success" _bgtask=1 echo_message "Debug: no operation performed." echo_message "Error: message color test." echo_message "Warning: message color test." echo_message "Notice: message color test." echo_message "Success: message color test." "success" ;; esac do_exit $exitcode TLP-1.10.1/tlp.init000066400000000000000000000014651517565574500137470ustar00rootroot00000000000000#!/bin/sh # tlp - system startup/shutdown # # Copyright (c) 2026 Thomas Koch and others. # This software is licensed under the GPL v2 or later. # # chkconfig: 2345 98 01 ### BEGIN INIT INFO # Provides: tlp # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: tlp start/stop script # Description: Initialize tlp ### END INIT INFO [ -r /lib/lsb/init-functions ] && . /lib/lsb/init-functions TLP=/usr/sbin/tlp [ -x $TLP ] || exit 0 case "$1" in status) tlp-stat -s ;; start|\ stop|\ restart|\ force-reload) $TLP init $1 ;; *) echo "Usage: $0 start|stop|restart|force-reload|status" 1>&2 exit 3 ;; esac exit 0 TLP-1.10.1/tlp.rules.in000066400000000000000000000014321517565574500145350ustar00rootroot00000000000000# tlp - udev rules # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # handle change of power source ac/bat, ignore input device batteries ACTION=="change", SUBSYSTEM=="power_supply", KERNEL!="hidpp_battery*", RUN+="@TLP_SBIN@/tlp auto" # handle added usb devices (exclude subdevices via DRIVER=="USB") ACTION=="add", SUBSYSTEM=="usb", DRIVER=="usb", ENV{DEVTYPE}=="usb_device", RUN+="@TLP_ULIB@/tlp-usb-udev usb %p" # handle added usb disk devices (exclude partitions via ENV{DEVTYPE}=="disk") ACTION=="add", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", RUN+="@TLP_ULIB@/tlp-usb-udev disk %p" ACTION=="add", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", RUN+="@TLP_ULIB@/tlp-usb-udev disk %p" TLP-1.10.1/tlp.service.in000066400000000000000000000014121517565574500150410ustar00rootroot00000000000000# tlp - systemd startup/shutdown service # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later [Unit] Description=TLP system startup/shutdown After=multi-user.target NetworkManager.service Before=shutdown.target Documentation=https://linrunner.de/tlp [Service] Type=oneshot RemainAfterExit=yes ExecStart=@TLP_SBIN@/tlp init start ExecReload=@TLP_SBIN@/tlp start ExecStop=@TLP_SBIN@/tlp init stop # Lockdown LockPersonality=yes KeyringMode=private MemoryDenyWriteExecute=yes NoNewPrivileges=no ProtectClock=yes ProtectControlGroups=yes ProtectHome=yes ProtectHostname=yes ProtectSystem=full ProtectKernelLogs=yes RestrictRealtime=yes RestrictNamespaces=yes RestrictSUIDSGID=yes [Install] WantedBy=multi-user.target TLP-1.10.1/tlp.upstart.in000066400000000000000000000006601517565574500151070ustar00rootroot00000000000000# tlp - system startup/shutdown # # Copyright (c) 2026 Thomas Koch and others. # This software is licensed under the GPL v2 or later. description "tlp" start on ( virtual-filesystems and runlevel [2345] ) stop on runlevel [!2345] env TLP=@TLP_SBIN@/tlp pre-start script [ -x $TLP ] || exit 4 $TLP init start end script post-stop script [ -x $TLP ] || exit 4 $TLP init stop end script TLP-1.10.1/tlpctl.in000066400000000000000000000271661517565574500141230ustar00rootroot00000000000000#!/usr/bin/python3 # TLP Profiles Daemon (tlp-pd) control program # tlpctl is an adaptation of powerprofilesctl, # offers a subset of its commands and is # supplemented by TLP-specific shortcuts. # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-3.0-only import argparse # noqa: I001 import errno from gi.repository import Gio, GLib import os import signal import subprocess import sys # --- Constants PROGRAM_NAME = "tlpctl" PROGRAM_LONG = "TLP Profiles Control" PROGRAM_DESC = "Control TLP power profiles" DAEMON_LONG = "TLP Profiles Daemon" # --- D-Bus constants PD_BUS_NAME = "org.freedesktop.UPower.PowerProfiles" PD_INTERFACE_NAME = PD_BUS_NAME PD_OBJECT_PATH = "/org/freedesktop/UPower/PowerProfiles" PROPERTIES_IFACE = "org.freedesktop.DBus.Properties" # --- Helper functions def get_proxy(interface): # Call the daemon via proxy bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) return Gio.DBusProxy.new_sync( bus, Gio.DBusProxyFlags.NONE, None, PD_BUS_NAME, PD_OBJECT_PATH, interface, None, ) def get_pd_property(prop): # Get tlp-pd property proxy = get_proxy(PROPERTIES_IFACE) try: return proxy.Get("(ss)", PD_INTERFACE_NAME, prop) except GLib.Error as error: sys.stderr.write(f"{error}\n") sys.exit(1) def get_profile_choices(): # Get supported profiles return [profile["Profile"] for profile in get_pd_property("Profiles")] def list_profiles_brief(): # Print a brief list of available profiles, active profile marked with '*' profiles = get_pd_property("Profiles") active_profile = get_pd_property("ActiveProfile") for profile in reversed(profiles): marker = "*" if profile["Profile"] == active_profile else " " print(f"{marker} {profile['Profile']}") def set_profile(profile): # Switch TLP profile via tlp-pd proxy = get_proxy(PROPERTIES_IFACE) proxy.Set( "(ssv)", PD_INTERFACE_NAME, "ActiveProfile", GLib.Variant.new_string(profile), ) def command(func): # Execute a tlpctl command def wrapper(*args, **kwargs): try: func(*args, **kwargs) except GLib.Error as error: sys.stderr.write(f"Error: {error}\n") sys.exit(1) except ValueError as error: sys.stderr.write(f"Error: {error}\n") sys.exit(1) return wrapper # --- powerprofilessctl-compatible commands @command def _version(args): client_version = "@TLPVER@" daemon_ver = get_pd_property("Version") print(f"Client: {client_version}") print(f"Daemon: {daemon_ver}") @command def _set_profile(args): # Set active profile set_profile(args.profile[0]) @command def _get(args): # Get active profile print(get_pd_property("ActiveProfile")) @command def _list(args): # Print a list of profiles with associateddrivers, # active profile marked with '*', # followed by BatteryAware and LogLevel properties profiles = get_pd_property("Profiles") reason = get_pd_property("PerformanceDegraded") degraded = reason != "" active_profile = get_pd_property("ActiveProfile") print("Available power profiles (* = active):\n") for profile in reversed(profiles): marker = "*" if profile["Profile"] == active_profile else " " print(f"{marker} {profile['Profile']}:") for driver in ["CpuDriver", "PlatformDriver"]: if driver not in profile: continue value = profile[driver] print(" %-15s: %s" % (driver, value)) if profile["Profile"] == "performance": print( " %-15s: %s" % ( "Degraded", f"yes ({reason})" if degraded else "no", ) ) print("") print("Dynamic changes from charger and battery events: ", end="") if get_pd_property("BatteryAware"): print("yes") else: print("no") print(f"tlp-pd LogLevel: {get_pd_property('LogLevel')}") @command def _list_holds(_args): # List active profile holds with command and reason holds = get_pd_property("ActiveProfileHolds") index = 0 for hold in holds: if index > 0: print("") print("Hold:") print(" Profile: ", hold["Profile"]) print(" Application ID: ", hold["ApplicationId"]) print(" Reason: ", hold["Reason"]) index += 1 @command def _launch(args): # Run a command using a specific power profile (hold) reason = args.reason profile = args.profile appid = args.appid if not args.arguments: raise ValueError("No command to launch") if not args.appid: appid = args.arguments[0] if not profile: profile = "performance" if not reason: reason = f"Running {args.arguments}" ret = 0 proxy = get_proxy(PD_INTERFACE_NAME) cookie = proxy.HoldProfile("(sss)", profile, reason, appid) # Run the command try: with subprocess.Popen(args.arguments) as launched_app: # Redirect the same signal to the child def receive_signal(signum, _stack): launched_app.send_signal(signum) redirected_signals = [ signal.SIGTERM, signal.SIGINT, signal.SIGABRT, ] for sig in redirected_signals: signal.signal(sig, receive_signal) try: launched_app.wait() ret = launched_app.returncode except KeyboardInterrupt: ret = launched_app.returncode for sig in redirected_signals: signal.signal(sig, signal.SIG_DFL) except FileNotFoundError as error: sys.stderr.write(f"Error: command '{appid}' not found\n{error}\n") ret = errno.ENOENT proxy.ReleaseProfile("(u)", cookie) if ret < 0: # Use standard POSIX signal exit code. os.kill(os.getpid(), -ret) return sys.exit(ret) # --- TLP-specific commands @command def _performance(_args): # Switch to performance profile set_profile("performance") print("Switched to performance profile.") @command def _balanced(_args): # Switch to balanced profile set_profile("balanced") print("Switched to balanced profile.") @command def _power_saver(_args): # Switch to power-saver profile set_profile("power-saver") print("Switched to power-saver profile.") @command def _set_loglevel(args): # Set tlp-pd loglevel loglevel = args.loglevel[0] if loglevel not in ["info", "debug"]: sys.stderr.write( f"Error: invalid loglevel '{loglevel}'. Must be 'info' or 'debug'.\n" ) sys.exit(1) proxy = get_proxy(PROPERTIES_IFACE) proxy.Set( "(ssv)", PD_INTERFACE_NAME, "LogLevel", GLib.Variant.new_string(loglevel), ) print(f"tlp-pd loglevel set to '{loglevel}'.") # --- Parse arguments def get_parser(): parser = argparse.ArgumentParser( prog=f"{PROGRAM_NAME}", description=f"{PROGRAM_DESC}", epilog=f"Use '{PROGRAM_NAME} --help' to get detailed help for individual commands", ) subparsers = parser.add_subparsers(help="Individual command help", dest="command") # --- TLP-specific profile commands (shortcuts) parser_performance = subparsers.add_parser( "performance", help="Switch to performance profile (shortcut for 'set performance')", ) parser_performance.set_defaults(func=_performance) parser_balanced = subparsers.add_parser( "balanced", help="Switch to balanced profile (shortcut for 'set balanced')" ) parser_balanced.set_defaults(func=_balanced) parser_power_saver = subparsers.add_parser( "power-saver", help="Switch to power-saver profile (shortcut for 'set power-saver')", ) parser_power_saver.set_defaults(func=_power_saver) # --- Original powerprofilesctl commands parser_list = subparsers.add_parser("list", help="List available power profiles") parser_list.set_defaults(func=_list) # list-actiond parser_list_holds = subparsers.add_parser( "list-holds", help="List current profile holds (from launch command)" ) parser_list_holds.set_defaults(func=_list_holds) # get parser_get = subparsers.add_parser( "get", help="Print the currently active TLP profile", description=f"{help}." ) parser_get.set_defaults(func=_get) # set parser_set = subparsers.add_parser("set", help="Set the active TLP profile") parser_set.add_argument( "profile", nargs=1, help="Profile to activate", choices=get_profile_choices(), ) parser_set.set_defaults(func=_set_profile) # launch parser_launch = subparsers.add_parser( "launch", help="Run a command using a specific TLP profile (hold)", description="Applies the given profile, runs the command, " "and returns to the original profile.", ) parser_launch.add_argument( "arguments", nargs="*", help="Command to launch", ) parser_launch.add_argument( "--profile", "-p", required=False, help="Profile that is kept in hold while the command is running", ) parser_launch.add_argument( "--reason", "-r", required=False, help="Reason to be noted on the hold" ) parser_launch.add_argument( "--appid", "-i", required=False, help="AppID to be noted on the hold" ) parser_launch.set_defaults(func=_launch) # loglevel parser_set = subparsers.add_parser( "loglevel", help="Set the loglevel of the tlp-pd daemon" ) parser_set.add_argument( "loglevel", nargs=1, choices=["info", "debug", ""], ) parser_set.set_defaults(func=_set_loglevel) # version parser_version = subparsers.add_parser( "version", help="Print version information and exit" ) parser_version.set_defaults(func=_version) # Workaround: argparse treats arguments starting with - or -- as optional arguments (flags), not subcommands parser.add_argument( "--version", action="store_true", help="Print version information and exit" ) # shtab completion support (hide from help text) if "--print-completion" in sys.argv: try: import shtab shtab.add_argument_to(parser, ["--print-completion"]) except ImportError: print( "Warning: shtab is not installed. Install with: pip install shtab", file=sys.stderr, ) return parser def check_unknown_args(args, unknown_args, cmd): if cmd != "launch": return False for idx, unknown_arg in enumerate(unknown_args): arg = args[idx] if arg == cmd: return True if unknown_arg == arg: return False return True # --- MAIN def main(): parser = get_parser() args, unknown = parser.parse_known_args() if not args.command: # No command given if args.version: # --version _version(args) else: # Print a brief profile list list_profiles_brief() else: if check_unknown_args(sys.argv[1:], unknown, args.command): args.arguments += unknown unknown = [] if unknown: msg = argparse._("unrecognized arguments: %s") parser.error(msg % " ".join(unknown)) # Run given command args.func(args) if __name__ == "__main__": main() TLP-1.10.1/unit-tests/000077500000000000000000000000001517565574500143745ustar00rootroot00000000000000TLP-1.10.1/unit-tests/charge-thresholds_cros-ec-v2000077500000000000000000000155531517565574500217010ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for laptops with ChromeOS EC (cmd v2) # Requirements: # * Hardware: Chromebook # * Software: # - chrultrabook/coreboot custom UEFI firmware mod # - kernel module cros_charge-control (Linux 6.12.8/6.13+) # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ ChromeOS EC (cmd v2) laptops +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="0" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="101" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="86" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 100 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="0" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="101" 2>&1| sed -r "s/${bata}/BATA/" Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="ABCDE" STOP_CHARGE_THRESH_${bata}="XYZZY" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at STOP_CHARGE_THRESH_BATA="XYZZY": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="100" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 100 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="86" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 86 $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="80" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 80 $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="80" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 80 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 100 $ sudo tlp setcharge -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/${bata}/BATA/" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 ${bata} -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 100 (no change) $ sudo tlp setcharge 0 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (0) for BATA is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 101 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (101) for BATA is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge ABCDE 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (0) for BATA is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 XYZZY -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (XYZZY) for BATA is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 97 100 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 100 (no change) $ sudo tlp setcharge 0 86 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 86 $ sudo tlp setcharge 0 80 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 80 $ sudo tlp setcharge 0 80 -- ${xinc} X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 80 $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge DEF DEF -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: stop = 100 $ sudo tlp setcharge ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ sudo tlp setcharge 0 3 ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ sudo tlp setcharge XYZZY ABCDE ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- ${xinc} | grep "${bata}/charge_control_end_threshold" | sed -r "s/${bata}/BATA/" /sys/class/power_supply/BATA/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- ${xinc} X_THRESH_SIMULATE_READERR=1 | grep "${bata}/charge_control_end_threshold" | sed -r "s/${bata}/BATA/" /sys/class/power_supply/BATA/charge_control_end_threshold = (not available) [%] $ # TLP-1.10.1/unit-tests/charge-thresholds_cros-ec-v3000077500000000000000000000200251517565574500216700ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for laptops with ChromeOS EC (cmd v3) # Requirements: # * Hardware: yet unknown, possibly Chromebooks from July 2021 # * Software: # - chrultrabook/coreboot custom UEFI firmware mod # - kernel module cros_charge-control (Linux 6.12.8/6.13+) # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ ChromeOS EC (cmd v3) laptops +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="100" STOP_CHARGE_THRESH_${bata}="100" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BATA="100": not specified, invalid or out of range (0..99). Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="0" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="101" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="97" STOP_CHARGE_THRESH_${bata}="97" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/g;s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BATA >= STOP_CHARGE_THRESH_BATA. Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="95" STOP_CHARGE_THRESH_${bata}="96" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" X_SOC_CHECK=0 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: start = 60 stop = 100 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="100" STOP_CHARGE_THRESH_${bata}="100" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at START_CHARGE_THRESH_BATA="100": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="0" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="101" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="97" STOP_CHARGE_THRESH_${bata}="97" 2>&1 | sed -r "s/${bata}/BATA/g" Error in configuration: START_CHARGE_THRESH_BATA >= STOP_CHARGE_THRESH_BATA. Aborted. $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="95" STOP_CHARGE_THRESH_${bata}="96" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: start = 95 stop = 96 $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="95" STOP_CHARGE_THRESH_${bata}="96" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: start = 95 (no change) stop = 96 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: start = 0 stop = 100 $ sudo tlp setcharge -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- ${xinc} X_SOC_CHECK=0 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: start = 60 stop = 100 (no change) $ sudo tlp setcharge 100 100 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: start charge threshold (100) for BATA is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (0) for BATA is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 101 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (101) for BATA is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge XYZZY 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: start charge threshold (XYZZY) for BATA is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 XYZZY -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (XYZZY) for BATA is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 97 97 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: start threshold >= stop threshold for BATA. Aborted. $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge 95 96 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: start = 95 stop = 96 $ sudo tlp setcharge 95 96 -- ${xinc} X_THRESH_SIMULATE_READERR="1" 2>&1 | sed -r "s/${bata}/BATA/" Error: could not read current charge threshold(s) for BATA. Aborted. $ sudo tlp setcharge 95 96 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: start = 95 (no change) stop = 96 (no change) $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge DEF DEF -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge threshold(s) for battery BATA: start = 0 stop = 100 $ sudo tlp setcharge ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ sudo tlp setcharge 0 3 ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ sudo tlp setcharge XYZZY ABCDE ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- ${xinc}| grep -E "${bata}/charge_(control_end|behaviour)" | sed -r "s/${bata}/BATA/" /sys/class/power_supply/BATA/charge_control_end_threshold = 100 [%] /sys/class/power_supply/BATA/charge_behaviour = [auto] inhibit-charge force-discharge $ sudo tlp-stat -b -- ${xinc} X_THRESH_SIMULATE_READERR=1 | grep -E "${bata}/charge_(control|behaviour)" | sed -r "s/${bata}/BATA/" /sys/class/power_supply/BATA/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BATA/charge_control_end_threshold = (not available) [%] /sys/class/power_supply/BATA/charge_behaviour = [auto] inhibit-charge force-discharge $ # TLP-1.10.1/unit-tests/charge-thresholds_dell000077500000000000000000000202061517565574500207300ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for Dell laptops # Requirements: # * Hardware: Dell Laptop # * Software: kernel module dell_laptop (Linux 6.12+) # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ Dell laptops +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} X_THRESH_SIMULATE_LOCKEDBIOS=1 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error: failed to set 'Custom' charge type for battery BAT0. Battery skipped. Remove the BIOS Admin password to allow the thresholds to be set. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (50..95). Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (55..100). Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (55..100). Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="91" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 5. Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge -- ${xinc} X_THRESH_SIMULATE_LOCKEDBIOS=1 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: Error: failed to set 'Custom' charge type for battery BAT0. Aborted. Remove the BIOS Admin password to allow the thresholds to be set. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (50..95). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="0" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="91" Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 5. Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" Setting temporary charge thresholds for battery BAT0: start = 90 stop = 95 $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" Setting temporary charge thresholds for battery BAT0: start = 90 (no change) stop = 95 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds for battery BAT0: stop = 100 start = 95 $ sudo tlp setcharge -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- ${xinc} X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge 61 99 -- ${xinc} X_THRESH_SIMULATE_WRITEERR=1 X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 61 (Error: write failed) stop = 99 (Error: write failed) Remove the BIOS Admin password to allow the thresholds to be set. $ sudo tlp setcharge 100 100 -- ${xinc} Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (50..95). Aborted. $ sudo tlp setcharge 50 0 -- ${xinc} Error: stop charge threshold (0) for battery BAT0 is not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge 50 101 -- ${xinc} Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge XYZZY 0 -- ${xinc} Error: start charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (50..95). Aborted. $ sudo tlp setcharge 50 XYZZY -- ${xinc} Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge 90 91 -- ${xinc} Error: start threshold > stop threshold - 5 for battery BAT0. Aborted. $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge 90 95 -- ${xinc} Setting temporary charge thresholds for battery BAT0: start = 90 stop = 95 $ sudo tlp setcharge 90 95 -- ${xinc} X_THRESH_SIMULATE_READERR="1" Error: could not read current charge threshold(s) for battery BAT0. Aborted. $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge 90 95 -- ${xinc} X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds for battery BAT0: start = 90 stop = 95 $ sudo tlp setcharge 90 95 -- ${xinc} Setting temporary charge thresholds for battery BAT0: start = 90 (no change) stop = 95 (no change) $ sudo tlp setcharge DEF DEF -- ${xinc} Setting temporary charge thresholds for battery BAT0: stop = 100 start = 95 $ sudo tlp setcharge BAT2 -- ${xinc} Error: battery BAT2 not present. $ sudo tlp setcharge 0 3 BAT2 -- ${xinc} Error: battery BAT2 not present. $ sudo tlp setcharge XYZZY ABCDE BAT2 -- ${xinc} Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- ${xinc} | grep -E 'BAT0/charge_(control|behaviour)' /sys/class/power_supply/BAT0/charge_control_start_threshold = 95 [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- ${xinc} X_THRESH_SIMULATE_READERR=1 | grep -E 'BAT0/charge_(control|behaviour)' /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] $ # $ # --- Reset test machine to configured thresholds $ sudo tlp setcharge BAT0 - ${xinc} > /dev/null 2>&1 $ # TLP-1.10.1/unit-tests/charge-thresholds_lenovo000077500000000000000000000133351517565574500213170ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for Lenovo laptops # Requirements: # * Hardware: Lenovo Laptop - Non-ThinkPad or ThinkBook series OR simulation # * Software: kernel module ideapad_laptop (Linux 6.17+) # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ Lenovo ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- ${xinc} "${bctoff}" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} "${bctoff}" START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": not specified or invalid (must be 0 or 1). Battery skipped. TLP started $ sudo tlp start -- ${xinc} "${bctoff}" START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="1" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} "${bcton}" NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="0" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} "${bcton}" X_THRESH_SIMULATE_WRITEERR=1 START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing charge type failed. TLP started $ sudo tlp start -- ${xinc} "${bcton}" START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="0" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # Test machine state (real hardware): threshold disabled $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- ${xinc} "${bctoff}" START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge -- ${xinc} "${bctoff}" START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge -- ${xinc} "${bctoff}" START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" Setting temporary charge type for battery BAT0: charge type = Long_Life $ sudo tlp setcharge -- ${xinc} "${bctoff2}" START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" Setting temporary charge type for battery BAT0: charge type = Long_Life $ sudo tlp setcharge -- ${xinc} "${bcton}" START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" Setting temporary charge type for battery BAT0: charge type = Long_Life (no change) $ sudo tlp setcharge -- ${xinc} "${bcton2}" START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" Setting temporary charge type for battery BAT0: charge type = Long_Life (no change) $ sudo tlp setcharge -- ${xinc} "${bcton}" START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="0" Setting temporary charge type for battery BAT0: charge type = Standard $ sudo tlp setcharge -- ${xinc} "${bcton2}" START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="0" Setting temporary charge type for battery BAT0: charge type = Standard $ # Test machine state (real hardware): threshold disabled $ # $ sudo tlp setcharge -- ${xinc} "${bctoff}" X_THRESH_SIMULATE_WRITEERR=1 START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" Setting temporary charge type for battery BAT0: charge type = Long_Life (Error: write failed) $ sudo tlp setcharge -- ${xinc} "${bctoff}" NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments, tlp-stat $ sudo tlp setcharge 42 24 -- ${xinc} "${bctoff}" Error: charge type (24) for battery BAT0 is not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge ABCDE XYZZY -- ${xinc} "${bctoff}" Error: charge type (XYZZY) for battery BAT0 is not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge 42 1 -- ${xinc} "${bctoff}" X_THRESH_SIMULATE_WRITEERR=1 Setting temporary charge type for battery BAT0: charge type = Long_Life (Error: write failed) $ sudo tlp setcharge 42 1 -- ${xinc} "${bctoff}" Setting temporary charge type for battery BAT0: charge type = Long_Life $ sudo tlp setcharge 42 1 -- ${xinc} "${bctoff2}" Setting temporary charge type for battery BAT0: charge type = Long_Life $ sudo tlp setcharge 42 1 -- ${xinc} "${bcton}" Setting temporary charge type for battery BAT0: charge type = Long_Life (no change) $ sudo tlp setcharge 42 1 -- ${xinc} "${bcton2}" Setting temporary charge type for battery BAT0: charge type = Long_Life (no change) $ sudo tlp-stat -b -- ${xinc} "${bcton}" | grep "/charge_types" /sys/class/power_supply/BAT0/charge_types = Standard [Long_Life] $ sudo tlp-stat -b -- ${xinc} "${bcton2}" | grep "/charge_types" /sys/class/power_supply/BAT0/charge_types = Fast Standard [Long_Life] $ sudo tlp setcharge DEF DEF -- ${xinc} "${bcton}" Setting temporary charge type for battery BAT0: charge type = Standard $ sudo tlp setcharge DEF DEF -- ${xinc} "${bcton2}" Setting temporary charge type for battery BAT0: charge type = Standard $ sudo tlp setcharge DEF DEF -- ${xinc} "${bctoff}" Setting temporary charge type for battery BAT0: charge type = Standard (no change) $ sudo tlp-stat -b -- ${xinc} "${bctoff}" | grep "/charge_types" /sys/class/power_supply/BAT0/charge_types = [Standard] Long_Life $ sudo tlp-stat -b -- ${xinc} "${bctoff2}" | grep "/charge_types" /sys/class/power_supply/BAT0/charge_types = [Fast] Standard Long_Life $ # Test machine state (real hardware): threshold disabled $ # $ sudo tlp setcharge 42 24 BAT2 -- ${xinc} "${bctoff}" Error: battery BAT2 not present. $ sudo tlp setcharge BAT2 -- ${xinc} "${bctoff}" Error: battery BAT2 not present. $ # TLP-1.10.1/unit-tests/charge-thresholds_macbook000077500000000000000000000115451517565574500214310ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for Macbooks # Requirements: # * Hardware: Apple Silcon Macbook w/ MacOS firmware 13.0+ # * Software: Asahi Linux kernel 6.3+ # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ Apple Silicon Macbooks ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0=95 STOP_CHARGE_THRESH_BAT0=100 START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="80" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="9" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="9": not specified or invalid (must be 80 or 100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified or invalid (must be 80 or 100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="77" STOP_CHARGE_THRESH_BAT0="80" X_SOC_CHECK=0 Setting temporary charge thresholds for battery macsmc-battery: stop = 80 start = 75 (due to hardware constraint) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="66" STOP_CHARGE_THRESH_BAT0="80" X_SOC_CHECK=0 Setting temporary charge thresholds for battery macsmc-battery: stop = 80 (no change) start = 75 (no change) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="42" Error in configuration at STOP_CHARGE_THRESH_BAT0="42": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds for battery macsmc-battery: stop = 100 start = 100 (due to hardware constraint) $ sudo tlp setcharge -- NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: battery charge thresholds not available. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 77 80 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery macsmc-battery: stop = 80 start = 75 (due to hardware constraint) $ sudo tlp setcharge 66 80 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery macsmc-battery: stop = 80 (no change) start = 75 (no change) $ sudo tlp setcharge 0 XYZZY Error: stop charge threshold (XYZZY) for battery macsmc-battery is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge 0 42 Error: stop charge threshold (42) for battery macsmc-battery is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge 0 101 Error: stop charge threshold (101) for battery macsmc-battery is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge DEF DEF -- X_THRESH_SIMULATE_READERR="1" Error: could not read current stop charge threshold for battery macsmc-battery. Aborted. $ sudo tlp setcharge BAT1 Error: battery BAT1 not present. $ sudo tlp setcharge 0 3 BAT1 Error: battery BAT1 not present. $ sudo tlp setcharge XYZZY ABCDE BAT1 Error: battery BAT1 not present. $ # --- Reset to hardware defaults $ sudo tlp setcharge DEF DEF Setting temporary charge thresholds for battery macsmc-battery: stop = 100 start = 100 (due to hardware constraint) $ # $ # --- tlp-stat $ sudo tlp-stat -b -- | grep -E 'charge_control' /sys/class/power_supply/macsmc-battery/charge_control_start_threshold = 100 [%] /sys/class/power_supply/macsmc-battery/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'charge_control' /sys/class/power_supply/macsmc-battery/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/macsmc-battery/charge_control_end_threshold = (not available) [%] TLP-1.10.1/unit-tests/charge-thresholds_msi000077500000000000000000000127331517565574500206060ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for MSI laptops # Requirements: # * Hardware: MSI laptop supported by msi_ec driver # * Software: kernel 6.3+ # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ MSI ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT1="70" STOP_CHARGE_THRESH_BAT1="90" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="9" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT1="9": not specified, invalid or out of range (10..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="101" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT1="101": not specified, invalid or out of range (10..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT1="97" STOP_CHARGE_THRESH_BAT1="97" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="70" STOP_CHARGE_THRESH_BAT1="90" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: stop = 90 start = 80 (due to hardware constraint) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="70" STOP_CHARGE_THRESH_BAT1="90" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: stop = 90 (no change) start = 80 (no change) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT1="XYZZY": not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="9" Error in configuration at STOP_CHARGE_THRESH_BAT1="9": not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="101" Error in configuration at STOP_CHARGE_THRESH_BAT1="101": not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="97" STOP_CHARGE_THRESH_BAT1="97" Setting temporary charge thresholds for battery BAT1: stop = 97 start = 87 (due to hardware constraint) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" Setting temporary charge thresholds for battery BAT1: stop = 96 start = 86 (due to hardware constraint) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" Setting temporary charge thresholds for battery BAT1: stop = 96 (no change) start = 86 (no change) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" Setting temporary charge thresholds for battery BAT1: stop = 100 start = 90 (due to hardware constraint) $ sudo tlp setcharge -- NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" Error: battery charge thresholds not available. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 70 90 BAT1 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: stop = 90 start = 80 (due to hardware constraint) $ sudo tlp setcharge 70 90 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: stop = 90 (no change) start = 80 (no change) $ sudo tlp setcharge 0 XYZZY Error: stop charge threshold (XYZZY) for battery BAT1 is not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge 0 9 Error: stop charge threshold (9) for battery BAT1 is not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge 0 101 Error: stop charge threshold (101) for battery BAT1 is not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge 97 97 Setting temporary charge thresholds for battery BAT1: stop = 97 start = 87 (due to hardware constraint) $ sudo tlp setcharge 95 96 Setting temporary charge thresholds for battery BAT1: stop = 96 start = 86 (due to hardware constraint) $ sudo tlp setcharge 95 96 Setting temporary charge thresholds for battery BAT1: stop = 96 (no change) start = 86 (no change) $ sudo tlp setcharge BAT0 Error: battery BAT0 not present. $ sudo tlp setcharge 0 3 BAT0 Error: battery BAT0 not present. $ sudo tlp setcharge XYZZY ABCDE BAT0 Error: battery BAT0 not present. $ # $ # --- Reset to hardware defaults $ sudo tlp setcharge DEF DEF Setting temporary charge thresholds for battery BAT1: stop = 100 start = 90 (due to hardware constraint) $ # --- tlp-stat $ sudo tlp-stat -b | grep -E 'charge_control' /sys/class/power_supply/BAT1/charge_control_start_threshold = 90 [%] /sys/class/power_supply/BAT1/charge_control_end_threshold = 100 [%] $ # TLP-1.10.1/unit-tests/charge-thresholds_simulate1000077500000000000000000000551241517565574500217230ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for non-ThinkPads (simulated) - Part 1: ASUS..LG # Requirements: # * Hardware: non-legacy ThinkPad # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ ASUS +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- initialize $ sudo tlp start -- START_CHARGE_THRESH_BAT0="35" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1=1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="86" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Notice: some ASUS laptops silently ignore charge thresholds other than 40, 60 or 80. Please check if STOP_CHARGE_THRESH_BAT0="86" works as expected. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=asus NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" Setting temporary charge threshold for BAT0: stop = 100 $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="100" Setting temporary charge threshold for BAT0: stop = 100 $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="86" X_SOC_CHECK=0 Notice: some ASUS laptops silently ignore charge thresholds other than 40, 60 or 80. Please check if STOP_CHARGE_THRESH_BAT0="86" works as expected. Setting temporary charge threshold for BAT0: stop = 86 $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="80" X_SOC_CHECK=0 Setting temporary charge threshold for BAT0: stop = 80 $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="80" X_SOC_CHECK=0 Setting temporary charge threshold for BAT0: stop = 80 $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge threshold for BAT0: stop = 100 $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=asus NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- X_BAT_PLUGIN_SIMULATE=asus Setting temporary charge threshold for BAT0: stop = 100 $ sudo tlp setcharge 0 0 -- X_BAT_PLUGIN_SIMULATE=asus Error: stop charge threshold (0) for BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 101 -- X_BAT_PLUGIN_SIMULATE=asus Error: stop charge threshold (101) for BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge ABCDE 0 -- X_BAT_PLUGIN_SIMULATE=asus Error: stop charge threshold (0) for BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 XYZZY -- X_BAT_PLUGIN_SIMULATE=asus Error: stop charge threshold (XYZZY) for BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 97 100 -- X_BAT_PLUGIN_SIMULATE=asus Setting temporary charge threshold for BAT0: stop = 100 $ sudo tlp setcharge 95 66 -- X_BAT_PLUGIN_SIMULATE=asus X_SOC_CHECK=0 Notice: some ASUS laptops silently ignore charge thresholds other than 40, 60 or 80. Please check if 66 works as expected. Setting temporary charge threshold for BAT0: stop = 66 $ sudo tlp setcharge 95 60 -- X_BAT_PLUGIN_SIMULATE=asus X_SOC_CHECK=0 Setting temporary charge threshold for BAT0: stop = 60 $ sudo tlp setcharge 95 60 -- X_BAT_PLUGIN_SIMULATE=asus X_THRESH_SIMULATE_START="35" X_THRESH_SIMULATE_STOP="100" X_SOC_CHECK=0 Setting temporary charge threshold for BAT0: stop = 60 $ sudo tlp setcharge 95 60 -- X_BAT_PLUGIN_SIMULATE=asus X_SOC_CHECK=0 Setting temporary charge threshold for BAT0: stop = 60 $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=asus Setting temporary charge threshold for BAT0: stop = 100 $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=asus Error: battery BAT2 not present. $ sudo tlp setcharge 0 3 BAT2 -- X_BAT_PLUGIN_SIMULATE=asus Error: battery BAT2 not present. $ sudo tlp setcharge XYZZY ABCDE BAT2 -- X_BAT_PLUGIN_SIMULATE=asus Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=asus | grep "BAT0/charge_control_end_threshold" /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=asus X_THRESH_SIMULATE_READERR=1 | grep "BAT0/charge_control_end_threshold" /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] $ # $ # +++ Huawei ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_THRESH_SIMULATE_START="59" X_THRESH_SIMULATE_STOP="99" 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing charge thresholds failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing charge thresholds failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="100" X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing charge thresholds failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="1" STOP_CHARGE_THRESH_BAT0="101" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="99" STOP_CHARGE_THRESH_BAT0="98" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0. Skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="95" X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing charge thresholds failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" X_THRESH_SIMULATE_START="0" X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing charge thresholds failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=huawei NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds: start = 60, stop = 100 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="99" STOP_CHARGE_THRESH_BAT0="100" X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds: start = 99, stop = 100 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="102" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at START_CHARGE_THRESH_BAT0="102": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="1" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="0" Error in configuration at START_CHARGE_THRESH_BAT0="ABCDE": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="1" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="99" STOP_CHARGE_THRESH_BAT0="98" Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0. Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="95" X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds: start = 95, stop = 95 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="95" X_THRESH_SIMULATE_START="95" X_THRESH_SIMULATE_STOP="95" Setting temporary charge thresholds: start = 95, stop = 95 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" X_THRESH_SIMULATE_START="0" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds: start = 0, stop = 100 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=huawei NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- X_BAT_PLUGIN_SIMULATE=huawei X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds: start = 60, stop = 100 (Error: write failed) $ sudo tlp setcharge 99 100 -- X_BAT_PLUGIN_SIMULATE=huawei X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds: start = 99, stop = 100 (Error: write failed) $ sudo tlp setcharge 0 0 -- X_BAT_PLUGIN_SIMULATE=huawei X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Error: stop charge threshold (0) is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 102 101 -- X_BAT_PLUGIN_SIMULATE=huawei Error: start charge threshold (102) is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 1 101 -- X_BAT_PLUGIN_SIMULATE=huawei Error: stop charge threshold (101) is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge ABCDE 0 -- X_BAT_PLUGIN_SIMULATE=huawei Error: start charge threshold (ABCDE) is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 1 XYZZY -- X_BAT_PLUGIN_SIMULATE=huawei Error: stop charge threshold (XYZZY) is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 99 98 -- X_BAT_PLUGIN_SIMULATE=huawei Error: start threshold > stop threshold. Aborted. $ sudo tlp setcharge 95 95 -- X_BAT_PLUGIN_SIMULATE=huawei X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds: start = 95, stop = 95 (Error: write failed) $ sudo tlp setcharge 95 95 -- X_BAT_PLUGIN_SIMULATE=huawei Setting temporary charge thresholds: start = 95, stop = 95 (Error: write failed) $ sudo tlp setcharge 95 95 -- -- X_BAT_PLUGIN_SIMULATE=huawei X_THRESH_SIMULATE_START="95" X_THRESH_SIMULATE_STOP="95" Setting temporary charge thresholds: start = 95, stop = 95 (Error: write failed) $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=huawei Error: battery BAT2 not present. $ sudo tlp setcharge 0 3 BAT2 -- X_BAT_PLUGIN_SIMULATE=huawei Error: battery BAT2 not present. $ sudo tlp setcharge XYZZY ABCDE BAT2 -- X_BAT_PLUGIN_SIMULATE=huawei Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=huawei X_THRESH_SIMULATE_START="0" X_THRESH_SIMULATE_STOP="0" | grep "charge_control_thresholds" /sys/devices/platform/huawei-wmi/charge_control_thresholds = 0 0 [%] (disabled) $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=huawei X_THRESH_SIMULATE_START="0" X_THRESH_SIMULATE_STOP="100" | grep "charge_control_thresholds" /sys/devices/platform/huawei-wmi/charge_control_thresholds = 0 100 [%] (disabled) $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=huawei X_THRESH_SIMULATE_START="75" X_THRESH_SIMULATE_STOP="80" | grep "charge_control_thresholds" /sys/devices/platform/huawei-wmi/charge_control_thresholds = 75 80 [%] $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=huawei | grep "charge_control_thresholds" /sys/devices/platform/huawei-wmi/charge_control_thresholds = (not available) $ # $ # +++ Lenovo ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": conservation mode not specified or invalid (must be 0 or 1). Skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" X_THRESH_SIMULATE_STOP="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing conservation mode failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" X_THRESH_SIMULATE_STOP="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": conservation mode not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": conservation mode not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" X_THRESH_SIMULATE_STOP="0" Setting temporary charge threshold for all batteries: conservation mode = 1 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 42 24 -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy Error: conservation mode (24) not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge ABCDE XYZZY -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy Error: conservation mode (XYZZY) not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge 42 1 -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy X_THRESH_SIMULATE_STOP="0" Setting temporary charge threshold for all batteries: conservation mode = 1 (Error: write failed) $ sudo tlp setcharge 42 1 -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy X_THRESH_SIMULATE_STOP="1" Setting temporary charge threshold for all batteries: conservation mode = 1 (no change) $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy X_THRESH_SIMULATE_STOP="0" Setting temporary charge threshold for all batteries: conservation mode = 0 (no change) $ sudo tlp setcharge 42 24 BAT2 -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy Error: battery BAT2 not present. $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=lenovo-legacy | grep "/conservation_mode" /sys/bus/platform/drivers/ideapad_acpi/VPC2004:00/conservation_mode = (not available) $ # $ # +++ LG ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # New kernel API >= 5.18 $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lg START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lg START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": not specified or invalid (must be 80 or 100). Skipped. TLP started $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lg START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="80" X_THRESH_SIMULATE_STOP="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lg START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" X_THRESH_SIMULATE_STOP="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=lg NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=lg START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=lg START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=lg START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge threshold for battery BAT0: stop = 100 $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 42 24 -- X_BAT_PLUGIN_SIMULATE=lg Error: stop charge threshold (24) for battery BAT0 not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge 42 100 -- X_BAT_PLUGIN_SIMULATE=lg X_THRESH_SIMULATE_STOP="80" X_SOC_CHECK=0 Setting temporary charge threshold for battery BAT0: stop = 100 $ sudo tlp setcharge 42 80 -- X_BAT_PLUGIN_SIMULATE=lg X_THRESH_SIMULATE_STOP="80" X_SOC_CHECK=0 Setting temporary charge threshold for battery BAT0: stop = 80 (no change) $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=lg X_THRESH_SIMULATE_STOP="100" Setting temporary charge threshold for battery BAT0: stop = 100 (no change) $ sudo tlp setcharge ABCDE XYZZY -- X_BAT_PLUGIN_SIMULATE=lg Error: stop charge threshold (XYZZY) for battery BAT0 not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge DEF DEF -- X_THRESH_SIMULATE_READERR="1" Error: could not read current charge threshold(s) for battery BAT0. Aborted. $ sudo tlp setcharge 42 80 BAT2 -- X_BAT_PLUGIN_SIMULATE=lg X_SOC_CHECK=0 Error: battery BAT2 not present. $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=lg Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=lg | grep "BAT0/charge_control_end_threshold" /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=lg X_THRESH_SIMULATE_READERR=1 | grep "BAT0/charge_control_end_threshold" /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] $ # $ # --- Reset test machine to configured thresholds $ sudo tlp setcharge BAT0 > /dev/null 2>&1 $ # TLP-1.10.1/unit-tests/charge-thresholds_simulate2000077500000000000000000001000661517565574500217200ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for non-ThinkPads (simulated) - Part 2: Macbook..Toshiba # Requirements: # * Hardware: non-legacy ThinkPad # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ Apple Silicon Macbooks ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- initialize, simulation precautions for the start threshold $ sudo tlp start -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="80" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="9" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="9": not specified or invalid (must be 80 or 100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified or invalid (must be 80 or 100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge 60 100 > /dev/null 2>&1 # re-initialize $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="77" STOP_CHARGE_THRESH_BAT0="80" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 80 start = 75 (due to hardware constraint) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="66" STOP_CHARGE_THRESH_BAT0="80" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 80 (no change) start = 75 (no change) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="42" Error in configuration at STOP_CHARGE_THRESH_BAT0="42": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=macbook START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds for battery BAT0: stop = 100 start = 100 (due to hardware constraint) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=macbook NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 > /dev/null 2>&1 # re-initialize $ sudo tlp setcharge 77 80 -- X_BAT_PLUGIN_SIMULATE=macbook X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 80 start = 75 (due to hardware constraint) $ sudo tlp setcharge 66 80 -- X_BAT_PLUGIN_SIMULATE=macbook X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 80 (no change) start = 75 (no change) $ sudo tlp setcharge 0 XYZZY -- X_BAT_PLUGIN_SIMULATE=macbook Error: stop charge threshold (XYZZY) for battery BAT0 is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge 0 42 -- X_BAT_PLUGIN_SIMULATE=macbook Error: stop charge threshold (42) for battery BAT0 is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge 0 101 -- X_BAT_PLUGIN_SIMULATE=macbook Error: stop charge threshold (101) for battery BAT0 is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=macbook Setting temporary charge thresholds for battery BAT0: stop = 100 start = 100 (due to hardware constraint) $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=macbook X_THRESH_SIMULATE_READERR="1" Error: could not read current stop charge threshold for battery BAT0. Aborted. $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=macbook Error: battery BAT2 not present. $ sudo tlp setcharge 0 3 BAT2 -- X_BAT_PLUGIN_SIMULATE=macbook Error: battery BAT2 not present. $ sudo tlp setcharge XYZZY ABCDE BAT2 -- X_BAT_PLUGIN_SIMULATE=macbook Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=macbook X_THRESH_SIMULATE_START=100 | grep -E 'BAT0/charge_control' /sys/class/power_supply/BAT0/charge_control_start_threshold = 100 [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=macbook X_THRESH_SIMULATE_READERR=1 | grep -E 'BAT0/charge_control' /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] $ # $ # +++ MSI ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- initialize $ sudo tlp start -- START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="70" STOP_CHARGE_THRESH_BAT0="90" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="9" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="9": not specified, invalid or out of range (10..100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (10..100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge 70 100 > /dev/null 2>&1 # re-initialize $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="70" STOP_CHARGE_THRESH_BAT0="90" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 90 start = 80 (due to hardware constraint) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="70" STOP_CHARGE_THRESH_BAT0="90" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 90 (no change) start = 80 (no change) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="9" Error in configuration at STOP_CHARGE_THRESH_BAT0="9": not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 97 start = 87 (due to hardware constraint) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 96 start = 86 (due to hardware constraint) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 96 (no change) start = 86 (no change) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds for battery BAT0: stop = 100 start = 90 (due to hardware constraint) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=msi NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 70 100 > /dev/null 2>&1 # re-initialize $ sudo tlp setcharge 70 90 -- X_BAT_PLUGIN_SIMULATE=msi X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 90 start = 80 (due to hardware constraint) $ sudo tlp setcharge 70 90 -- X_BAT_PLUGIN_SIMULATE=msi X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 90 (no change) start = 80 (no change) $ sudo tlp setcharge 0 XYZZY -- X_BAT_PLUGIN_SIMULATE=msi Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge 0 9 -- X_BAT_PLUGIN_SIMULATE=msi Error: stop charge threshold (9) for battery BAT0 is not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge 0 101 -- X_BAT_PLUGIN_SIMULATE=msi Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (10..100). Aborted. $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=msi Setting temporary charge thresholds for battery BAT0: stop = 100 start = 90 (due to hardware constraint) $ sudo tlp setcharge 97 97 -- X_BAT_PLUGIN_SIMULATE=msi X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 97 start = 87 (due to hardware constraint) $ sudo tlp setcharge 95 96 -- X_BAT_PLUGIN_SIMULATE=msi X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 96 start = 86 (due to hardware constraint) $ sudo tlp setcharge 95 96 -- X_BAT_PLUGIN_SIMULATE=msi X_THRESH_SIMULATE_READERR="1" Error: could not read current stop charge threshold for battery BAT0. Aborted. $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge 95 96 -- X_BAT_PLUGIN_SIMULATE=msi X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds for battery BAT0: stop = 96 start = 86 (due to hardware constraint) $ sudo tlp setcharge 95 96 -- X_BAT_PLUGIN_SIMULATE=msi X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: stop = 96 (no change) start = 86 (no change) $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=msi Error: battery BAT2 not present. $ sudo tlp setcharge 0 3 BAT2 -- X_BAT_PLUGIN_SIMULATE=msi Error: battery BAT2 not present. $ sudo tlp setcharge XYZZY ABCDE BAT2 -- X_BAT_PLUGIN_SIMULATE=msi Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=msi | grep -E 'BAT0/charge_control' /sys/class/power_supply/BAT0/charge_control_start_threshold = 86 [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = 96 [%] $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=msi X_THRESH_SIMULATE_READERR=1 | grep -E 'BAT0/charge_control' /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] $ # $ # +++ Samsung ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=samsung START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=samsung START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": life extender not specified or invalid (must be 0 or 1). Skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=samsung START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" X_THRESH_SIMULATE_STOP="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing life extender failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=samsung START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" X_THRESH_SIMULATE_STOP="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=samsung NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=samsung START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": life extender not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=samsung START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="1" X_THRESH_SIMULATE_STOP="0" Setting temporary charge threshold for all batteries: life extender = 1 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=samsung NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 42 24 -- X_BAT_PLUGIN_SIMULATE=samsung Error: life extender (24) not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge ABCDE XYZZY -- X_BAT_PLUGIN_SIMULATE=samsung Error: life extender (XYZZY) not specified or invalid (must be 0 or 1). Aborted. $ sudo tlp setcharge 42 1 -- X_BAT_PLUGIN_SIMULATE=samsung X_THRESH_SIMULATE_STOP="0" Setting temporary charge threshold for all batteries: life extender = 1 (Error: write failed) $ sudo tlp setcharge 42 1 -- X_BAT_PLUGIN_SIMULATE=samsung X_THRESH_SIMULATE_STOP="1" X_SOC_CHECK=0 Setting temporary charge threshold for all batteries: life extender = 1 (no change) $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=samsung X_THRESH_SIMULATE_STOP="0" Setting temporary charge threshold for all batteries: life extender = 0 (no change) $ sudo tlp setcharge 42 24 BAT2 -- X_BAT_PLUGIN_SIMULATE=samsung Error: battery BAT2 not present. $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=samsung Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=samsung | grep "battery_life_extender" /sys/devices/platform/samsung/battery_life_extender = (not available) $ # $ # +++ Sony ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": care limiter not specified or invalid (must be 50, 80 or 100). Skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="50" X_THRESH_SIMULATE_STOP="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing care limiter failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="80" X_THRESH_SIMULATE_STOP="50" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing care limiter failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="100" X_THRESH_SIMULATE_STOP="80" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing care limiter failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" X_THRESH_SIMULATE_STOP="50" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error: writing care limiter failed. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=sony NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": care limiter not specified or invalid (must be 50, 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": care limiter not specified or invalid (must be 50, 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="50" X_THRESH_SIMULATE_STOP="0" Setting temporary charge threshold for all batteries: care limiter = 50 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="80" X_THRESH_SIMULATE_STOP="50" Setting temporary charge threshold for all batteries: care limiter = 80 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=sony START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="100" X_THRESH_SIMULATE_STOP="80" Setting temporary charge threshold for all batteries: care limiter = 100 (Error: write failed) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=sony NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 42 24 -- X_BAT_PLUGIN_SIMULATE=sony Error: care limiter (24) not specified or invalid (must be 50, 80 or 100). Aborted. $ sudo tlp setcharge ABCDE XYZZY -- X_BAT_PLUGIN_SIMULATE=sony Error: care limiter (XYZZY) not specified or invalid (must be 50, 80 or 100). Aborted. $ sudo tlp setcharge 42 50 -- X_BAT_PLUGIN_SIMULATE=sony X_THRESH_SIMULATE_STOP="0" Setting temporary charge threshold for all batteries: care limiter = 50 (Error: write failed) $ sudo tlp setcharge 42 80 -- X_BAT_PLUGIN_SIMULATE=sony X_THRESH_SIMULATE_STOP="50" Setting temporary charge threshold for all batteries: care limiter = 80 (Error: write failed) $ sudo tlp setcharge 42 100 -- X_BAT_PLUGIN_SIMULATE=sony X_THRESH_SIMULATE_STOP="80" Setting temporary charge threshold for all batteries: care limiter = 100 (Error: write failed) $ sudo tlp setcharge 42 80 -- X_BAT_PLUGIN_SIMULATE=sony X_THRESH_SIMULATE_STOP="80" X_SOC_CHECK=0 Setting temporary charge threshold for all batteries: care limiter = 80 (no change) $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=sony X_THRESH_SIMULATE_STOP="0" X_SOC_CHECK=0 Setting temporary charge threshold for all batteries: care limiter = 100 (no change) $ sudo tlp setcharge 42 24 BAT2 -- X_BAT_PLUGIN_SIMULATE=sony Error: battery BAT2 not present. $ sudo tlp setcharge 2 -- X_BAT_PLUGIN_SIMULATE=sony Error: battery 2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=sony | grep "battery_care_limiter" /sys/devices/platform/sony-laptop/battery_care_limiter = (not available) [%] $ # $ # +++ System76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="100" Error in configuration at START_CHARGE_THRESH_BAT0="ABCDE": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Aborted. $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 95 (no change) stop = 96 (no change) $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds for battery BAT0: start = 90 stop = 100 $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=system76 NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- X_BAT_PLUGIN_SIMULATE=system76 Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge 100 100 -- X_BAT_PLUGIN_SIMULATE=system76 Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 0 -- X_BAT_PLUGIN_SIMULATE=system76 Error: stop charge threshold (0) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 101 -- X_BAT_PLUGIN_SIMULATE=system76 Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge ABCDE 0 -- X_BAT_PLUGIN_SIMULATE=system76 Error: start charge threshold (ABCDE) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 XYZZY -- X_BAT_PLUGIN_SIMULATE=system76 Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 97 97 -- X_BAT_PLUGIN_SIMULATE=system76 Error: start threshold >= stop threshold for battery BAT0. Aborted. $ sudo tlp setcharge 95 96 -- X_BAT_PLUGIN_SIMULATE=system76 X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge 95 96 -- X_BAT_PLUGIN_SIMULATE=system76 X_THRESH_SIMULATE_READERR="1" Error: could not read current charge threshold(s) for battery BAT0. Aborted. $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge 95 96 -- X_BAT_PLUGIN_SIMULATE=system76 X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge 95 96 -- X_BAT_PLUGIN_SIMULATE=system76 X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 95 (no change) stop = 96 (no change) $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=system76 Setting temporary charge thresholds for battery BAT0: start = 90 stop = 100 $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=system76 Error: battery BAT2 not present. $ sudo tlp setcharge 0 3 BAT2 -- X_BAT_PLUGIN_SIMULATE=system76 Error: battery BAT2 not present. $ sudo tlp setcharge XYZZY ABCDE BAT2 -- X_BAT_PLUGIN_SIMULATE=system76 Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=system76 | grep -E 'BAT0/charge_control' /sys/class/power_supply/BAT0/charge_control_start_threshold = 90 [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=system76 X_THRESH_SIMULATE_READERR=1 | grep -E 'BAT0/charge_control' /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] $ # $ # +++ Toshiba/Dynabook ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- initialize $ sudo tlp start -- START_CHARGE_THRESH_BAT0="35" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp start $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=toshiba START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=toshiba START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": not specified or invalid (must be 80 or 100). Battery skipped. TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=toshiba START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="80" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=toshiba START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- X_BAT_PLUGIN_SIMULATE=toshiba NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=toshiba START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="24" Error in configuration at STOP_CHARGE_THRESH_BAT0="24": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=toshiba START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge -- X_BAT_PLUGIN_SIMULATE=toshiba START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge threshold for battery BAT0: stop = 100 (no change) $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 42 24 -- X_BAT_PLUGIN_SIMULATE=toshiba Error: stop charge threshold (24) for battery BAT0 is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge ABCDE XYZZY -- X_BAT_PLUGIN_SIMULATE=toshiba Error: stop charge threshold (XYZZY) for battery BAT0 is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge 42 100 -- X_BAT_PLUGIN_SIMULATE=toshiba Setting temporary charge threshold for battery BAT0: stop = 100 (no change) $ sudo tlp setcharge 42 80 -- X_BAT_PLUGIN_SIMULATE=toshiba X_SOC_CHECK=0 Setting temporary charge threshold for battery BAT0: stop = 80 $ sudo tlp setcharge DEF DEF -- X_BAT_PLUGIN_SIMULATE=toshiba Setting temporary charge threshold for battery BAT0: stop = 100 $ sudo tlp setcharge 42 80 BAT2 -- X_BAT_PLUGIN_SIMULATE=toshiba Error: battery BAT2 not present. $ sudo tlp setcharge BAT2 -- X_BAT_PLUGIN_SIMULATE=toshiba Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- X_BAT_PLUGIN_SIMULATE=toshiba | grep -E 'BAT0/charge_control' /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] $ # $ # --- Reset test machine to configured thresholds $ sudo tlp setcharge BAT0 > /dev/null 2>&1 $ # TLP-1.10.1/unit-tests/charge-thresholds_system76000077500000000000000000000150171517565574500215150ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for System76 laptops # Requirements: # * Hardware: System76 laptop w/ open source EC firmware # * Software: kernel 5.16+ # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ System76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="100" Error in configuration at START_CHARGE_THRESH_BAT0="ABCDE": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="XYZZY" Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" Setting temporary charge thresholds for battery BAT0: start = 95 (no change) stop = 96 (no change) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds for battery BAT0: start = 0 stop = 100 $ sudo tlp setcharge -- NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: battery charge thresholds not available. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge 100 100 Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 0 Error: stop charge threshold (0) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 101 Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge ABCDE 0 Error: start charge threshold (ABCDE) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 XYZZY Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 97 97 Error: start threshold >= stop threshold for battery BAT0. Aborted. $ sudo tlp setcharge 95 96 Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge 95 96 -- X_THRESH_SIMULATE_READERR="1" Error: could not read current charge threshold(s) for battery BAT0. Aborted. $ sudo tlp setcharge 95 96 -- X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge 95 96 Setting temporary charge thresholds for battery BAT0: start = 95 (no change) stop = 96 (no change) $ sudo tlp setcharge BAT1 Error: battery BAT1 not present. $ sudo tlp setcharge 0 3 BAT1 Error: battery BAT1 not present. $ sudo tlp setcharge XYZZY ABCDE BAT1 Error: battery BAT1 not present. $ # $ # --- Reset to hardware defaults $ sudo tlp setcharge DEF DEF Setting temporary charge thresholds for battery BAT0: start = 90 stop = 100 $ # $ # --- tlp-stat $ sudo tlp-stat -b -- | grep -E 'charge_control' /sys/class/power_supply/BAT0/charge_control_start_threshold = 90 [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'charge_control' /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] TLP-1.10.1/unit-tests/charge-thresholds_thinkpad000077500000000000000000000166471517565574500216300ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for ThinkPads # Requirements: # * Hardware: non-legacy ThinkPad # * Kernel: >= 5.17 # * Batteries: BAT0 only # * Power source AC # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ ThinkPad (BAT0) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes $ sudo tlp start -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Aborted. $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" Setting temporary charge thresholds for battery BAT0: start = 95 (no change) stop = 96 (no change) $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds for battery BAT0: stop = 100 start = 96 $ sudo tlp setcharge -- NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes $ sudo tlp setcharge 60 100 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge 100 100 Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 0 Error: stop charge threshold (0) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 101 Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge XYZZY 0 Error: start charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 XYZZY Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 97 97 Error: start threshold >= stop threshold for battery BAT0. Aborted. $ sudo tlp setcharge 95 96 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge 95 96 -- X_THRESH_SIMULATE_READERR="1" Error: could not read current charge threshold(s) for battery BAT0. Aborted. $ [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes $ sudo tlp setcharge 95 96 -- X_SOC_CHECK=0 X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds for battery BAT0: start = 95 stop = 96 $ sudo tlp setcharge 95 96 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 95 (no change) stop = 96 (no change) $ sudo tlp setcharge DEF DEF Setting temporary charge thresholds for battery BAT0: stop = 100 start = 96 $ sudo tlp setcharge BAT2 Error: battery BAT2 not present. $ sudo tlp setcharge 0 3 BAT2 Error: battery BAT2 not present. $ sudo tlp setcharge XYZZY ABCDE BAT2 Error: battery BAT2 not present. $ # $ # --- tlp discharge $ sudo tlp discharge 100 Error: target charge level (100) for battery BAT0 is out of range (0..99). $ sudo tlp discharge BAT0 100 Error: target charge level (100) for battery BAT0 is out of range (0..99). $ sudo tlp discharge BAT2 Error: battery BAT2 not present. $ sudo tlp discharge BAT2 100 Error: battery BAT2 not present. $ # $ # --- tlp-stat $ # steps require a kernel >= 5.17 -- with 'charge_behaviour' $ sudo tlp-stat -b | grep -E 'charge_(control|behaviour)' /sys/class/power_supply/BAT0/charge_control_start_threshold = 96 [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] /sys/class/power_supply/BAT0/charge_behaviour = [auto] inhibit-charge force-discharge $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'charge_(control|behaviour)' /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] /sys/class/power_supply/BAT0/charge_behaviour = [auto] inhibit-charge force-discharge $ # $ # --- Reset test machine to configured thresholds $ sudo tlp setcharge BAT0 > /dev/null 2>&1 $ # TLP-1.10.1/unit-tests/charge-thresholds_thinkpad-BAT1000077500000000000000000000155351517565574500223100ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for ThinkPads # Requirements: # * Hardware: non-legacy ThinkPad # * Kernel: >= 5.17 # * Batteries: BAT1 + (optional) BAT0 # * Power source AC # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ ThinkPad (BAT1) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="61" STOP_CHARGE_THRESH_BAT1="100" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="100" STOP_CHARGE_THRESH_BAT1="100" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BAT1="100": not specified, invalid or out of range (0..99). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="0" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT1="0": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="101" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT1="101": not specified, invalid or out of range (1..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="97" STOP_CHARGE_THRESH_BAT1="97" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BAT1 >= STOP_CHARGE_THRESH_BAT1. Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge BAT1 -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT1="60" STOP_CHARGE_THRESH_BAT1="100" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: start = 60 stop = 100 (no change) $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="100" STOP_CHARGE_THRESH_BAT1="100" Error in configuration at START_CHARGE_THRESH_BAT1="100": not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="0" Error in configuration at STOP_CHARGE_THRESH_BAT1="0": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="101" Error in configuration at STOP_CHARGE_THRESH_BAT1="101": not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="97" STOP_CHARGE_THRESH_BAT1="97" Error in configuration: START_CHARGE_THRESH_BAT1 >= STOP_CHARGE_THRESH_BAT1. Aborted. $ sudo tlp setcharge BAT1 -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" Setting temporary charge thresholds for battery BAT1: start = 95 stop = 96 $ sudo tlp setcharge BAT1 -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" Setting temporary charge thresholds for battery BAT1: start = 95 (no change) stop = 96 (no change) $ sudo tlp setcharge BAT1 -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" Setting temporary charge thresholds for battery BAT1: stop = 100 start = 96 $ sudo tlp setcharge BAT1 -- NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 BAT1 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: start = 60 stop = 100 (no change) $ sudo tlp setcharge 100 100 BAT1 Error: start charge threshold (100) for battery BAT1 is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 0 BAT1 Error: stop charge threshold (0) for battery BAT1 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 0 101 BAT1 Error: stop charge threshold (101) for battery BAT1 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge XYZZY 0 BAT1 Error: start charge threshold (XYZZY) for battery BAT1 is not specified, invalid or out of range (0..99). Aborted. $ sudo tlp setcharge 0 XYZZY BAT1 Error: stop charge threshold (XYZZY) for battery BAT1 is not specified, invalid or out of range (1..100). Aborted. $ sudo tlp setcharge 97 97 BAT1 Error: start threshold >= stop threshold for battery BAT1. Aborted. $ sudo tlp setcharge 95 96 BAT1 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: start = 95 stop = 96 $ sudo tlp setcharge 95 96 BAT1 -- X_THRESH_SIMULATE_READERR="1" Error: could not read current charge threshold(s) for battery BAT1. Aborted. $ sudo tlp setcharge 95 96 BAT1 -- X_SOC_CHECK=0 X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds for battery BAT1: start = 95 stop = 96 $ sudo tlp setcharge 95 96 BAT1 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: start = 95 (no change) stop = 96 (no change) $ sudo tlp setcharge DEF DEF BAT1 Setting temporary charge thresholds for battery BAT1: stop = 100 start = 96 $ sudo tlp setcharge 61 91 BAT1 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT1: start = 61 stop = 91 $ # $ # --- tlp discharge $ sudo tlp discharge BAT1 100 Error: target charge level (100) for battery BAT1 is out of range (0..99). $ # $ # --- tlp-stat $ # steps require a kernel >= 5.17 -- with 'charge_behaviour' $ sudo tlp-stat -b | grep -E 'BAT1/charge_(control|behaviour)' /sys/class/power_supply/BAT1/charge_control_start_threshold = 61 [%] /sys/class/power_supply/BAT1/charge_control_end_threshold = 91 [%] /sys/class/power_supply/BAT1/charge_behaviour = [auto] inhibit-charge force-discharge $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'BAT1/charge_(control|behaviour)' /sys/class/power_supply/BAT1/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BAT1/charge_control_end_threshold = (not available) [%] /sys/class/power_supply/BAT1/charge_behaviour = [auto] inhibit-charge force-discharge $ # $ # --- Reset test machine to configured thresholds $ sudo tlp setcharge BAT0 > /dev/null 2>&1 $ sudo tlp setcharge BAT1 > /dev/null 2>&1 $ # TLP-1.10.1/unit-tests/charge-thresholds_thinkpad-legacy000077500000000000000000000204731517565574500230620ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for battery Legacy ThinkPads # Requirements: # * Hardware: Legacy ThinkPad (<= X201/T410) # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ Legacy ThinkPad +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (2..96). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="2" STOP_CHARGE_THRESH_BAT0="2" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="2": not specified, invalid or out of range (6..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="2" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (6..100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="96" STOP_CHARGE_THRESH_BAT0="99" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 4. Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="99" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (2..96). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="2" STOP_CHARGE_THRESH_BAT0="2" Error in configuration at STOP_CHARGE_THRESH_BAT0="2": not specified, invalid or out of range (6..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="2" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (6..100). Aborted. $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="96" STOP_CHARGE_THRESH_BAT0="99" Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 4. Aborted. $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="99" Setting temporary charge thresholds for battery BAT0: start = 95 stop = 99 $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="99" Setting temporary charge thresholds for battery BAT0: start = 95 (no change) stop = 99 (no change) $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds for battery BAT0: start = 96 stop = 100 $ sudo tlp setcharge -- TPSMAPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 60 stop = 100 (no change) $ sudo tlp setcharge 100 100 Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (2..96). Aborted. $ sudo tlp setcharge 2 2 Error: stop charge threshold (2) for battery BAT0 is not specified, invalid or out of range (6..100). Aborted. $ sudo tlp setcharge 2 101 Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (6..100). Aborted. $ sudo tlp setcharge XYZZY 0 Error: start charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (2..96). Aborted. $ sudo tlp setcharge 2 XYZZY Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (6..100). Aborted. $ sudo tlp setcharge 96 99 Error: start threshold > stop threshold - 4 for battery BAT0. Aborted. $ sudo tlp setcharge 95 99 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 95 stop = 99 $ sudo tlp setcharge 95 99 -- X_THRESH_SIMULATE_READERR="1" Error: could not read current charge threshold(s) for BAT0. Aborted. $ sudo tlp setcharge 95 99 -- X_SOC_CHECK=0 X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds for battery BAT0: start = 95 stop = 99 $ sudo tlp setcharge 95 99 -- X_SOC_CHECK=0 Setting temporary charge thresholds for battery BAT0: start = 95 (no change) stop = 99 (no change) $ sudo tlp setcharge DEF DEF Setting temporary charge thresholds for battery BAT0: start = 96 stop = 100 $ sudo tlp setcharge BAT1 Error: battery BAT1 not present. $ sudo tlp setcharge 0 3 BAT1 Error: battery BAT1 not present. $ sudo tlp setcharge XYZZY ABCDE BAT1 Error: battery BAT1 not present. $ # $ # --- tlp discharge $ sudo tlp discharge 100 Error: target charge level (100) for battery BAT0 is out of range (0..99). $ sudo tlp discharge BAT0 100 Error: target charge level (100) for battery BAT0 is out of range (0..99). $ sudo tlp discharge BAT1 Error: battery BAT1 not present. $ sudo tlp discharge BAT1 100 Error: battery BAT1 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b | grep -E 'charge_thresh|force_discharge' /sys/devices/platform/smapi/BAT0/start_charge_thresh = 96 [%] /sys/devices/platform/smapi/BAT0/stop_charge_thresh = 100 [%] /sys/devices/platform/smapi/BAT0/force_discharge = 0 $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'charge_thresh|force_discharge' /sys/devices/platform/smapi/BAT0/start_charge_thresh = (not available) [%] /sys/devices/platform/smapi/BAT0/stop_charge_thresh = (not available) [%] /sys/devices/platform/smapi/BAT0/force_discharge = 0 $ # $ # --- Feature Detection Edge Cases and Kernel Module Recommendations $ sudo ./kmod-helper tp_smapi restore $ sudo ./kmod-helper tp_smapi enable $ sudo tlp-stat -b | head -7 | tail -4 Plugin: thinkpad-legacy Supported features: charge thresholds, chargeonce, discharge, recalibrate Driver usage: * tp-smapi (tp_smapi) = active (status, charge thresholds, force-discharge) $ sudo tlp-stat -b -- NATACPI_ENABLE=0 | head -7 | tail -4 Plugin: thinkpad-legacy Supported features: charge thresholds, chargeonce, discharge, recalibrate Driver usage: * tp-smapi (tp_smapi) = active (status, charge thresholds, force-discharge) $ sudo tlp-stat -b -- NATACPI_ENABLE=0 TPACPI_ENABLE=0 | head -7 | tail -4 Plugin: thinkpad-legacy Supported features: charge thresholds, chargeonce, discharge, recalibrate Driver usage: * tp-smapi (tp_smapi) = active (status, charge thresholds, force-discharge) $ sudo ./kmod-helper tp_smapi disable $ sudo tlp-stat -b | head -7 | tail -4 Plugin: thinkpad-legacy Supported features: none available Driver usage: * tp-smapi (tp_smapi) = inactive (kernel module 'tp_smapi' load error) $ sudo ./kmod-helper tp_smapi enable $ sudo ./kmod-helper tp_smapi remove $ sudo tlp-stat -b | head -7 | tail -4 Plugin: thinkpad-legacy Supported features: none available Driver usage: * tp-smapi (tp_smapi) = inactive (kernel module 'tp_smapi' not installed) $ sudo tlp-stat -b | grep -A1 '+++ Recommendations' +++ Recommendations * Install tp-smapi kernel modules for ThinkPad battery thresholds and recalibration $ sudo ./kmod-helper tp_smapi restore $ # $ # --- Reset test machine to configured thresholds $ sudo tlp setcharge BAT0 > /dev/null 2>&1 $ # TLP-1.10.1/unit-tests/charge-thresholds_toshiba000077500000000000000000000050531517565574500214440ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for Toshiba/Dynabook laptops # Requirements: # * Hardware: Toshiba/Dynabook laptop w/ toshiba_acpi driver # * Battery: BAT1 # * Software: kernel 6.0+ # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ Toshiba/Dynabook ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="42" STOP_CHARGE_THRESH_BAT1="24" 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT1="24": not specified or invalid (must be 80 or 100). Battery skipped. TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="42" STOP_CHARGE_THRESH_BAT1="80" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o threshold arguments $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="42" STOP_CHARGE_THRESH_BAT1="24" Error in configuration at STOP_CHARGE_THRESH_BAT1="24": not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" Setting temporary charge threshold for BAT1: stop = 100 (no change) $ # $ # --- tlp setcharge w/ threshold arguments $ sudo tlp setcharge 42 24 BAT1 Error: stop charge threshold (24) for BAT1 is not specified or invalid (must be 80 or 100). Aborted. $ sudo tlp setcharge 42 100 BAT1 Setting temporary charge threshold for BAT1: stop = 100 (no change) $ sudo tlp setcharge 42 80 BAT1 -- X_SOC_CHECK=0 Setting temporary charge threshold for BAT1: stop = 80 $ sudo tlp setcharge 42 80 Error: battery BAT0 not present. $ sudo tlp setcharge 2 Error: battery 2 not present. $ # $ # --- Reset to hardware defaults $ sudo tlp setcharge DEF DEF BAT1 Setting temporary charge thresholds for BAT1: stop = 100 $ # $ # --- tlp-stat $ sudo tlp-stat -b | sudo tlp-stat -b | grep -E 'charge_control /sys/class/power_supply/BAT1/charge_control_end_threshold = 100 [%] $ # TLP-1.10.1/unit-tests/charge-thresholds_tuxedo000077500000000000000000000211501517565574500213170ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for Tuxedo laptops # Requirements: # * Hardware: Tuxedo Laptop (Clevo OEM chassis) # * Software: kernel module clevo_acpi (out-of-tree) # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ Tuxedo laptops +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}= STOP_CHARGE_THRESH_${bata}= START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="100" STOP_CHARGE_THRESH_${bata}="100" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BATA="100": not specified, invalid or not in {40, 50, 60, 70, 80, 95}. Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="50" STOP_CHARGE_THRESH_${bata}="0" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or not in {60, 70, 80, 90, 100}. Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="50" STOP_CHARGE_THRESH_${bata}="101" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/;s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or not in {60, 70, 80, 90, 100}. Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="80" STOP_CHARGE_THRESH_${bata}="80" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/g;s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BATA >= STOP_CHARGE_THRESH_BATA. Battery skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="80" STOP_CHARGE_THRESH_${bata}="90" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" X_SOC_CHECK=0 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: start = 60 stop = 100 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="100" STOP_CHARGE_THRESH_${bata}="100" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at START_CHARGE_THRESH_BATA="100": not specified, invalid or not in {40, 50, 60, 70, 80, 95}. Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="50" STOP_CHARGE_THRESH_${bata}="0" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or not in {60, 70, 80, 90, 100}. Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="40" STOP_CHARGE_THRESH_${bata}="85" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at STOP_CHARGE_THRESH_BATA="85": not specified, invalid or not in {60, 70, 80, 90, 100}. Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="45" STOP_CHARGE_THRESH_${bata}="90" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration at START_CHARGE_THRESH_BATA="45": not specified, invalid or not in {40, 50, 60, 70, 80, 95}. Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="80" STOP_CHARGE_THRESH_${bata}="70" 2>&1 | sed -r "s/${bata}/BATA/" Error in configuration: START_CHARGE_THRESH_BATA >= STOP_CHARGE_THRESH_BAT0. Aborted. $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="70" STOP_CHARGE_THRESH_${bata}="90" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: start = 70 stop = 90 $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="70" STOP_CHARGE_THRESH_${bata}="90" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: start = 70 (no change) stop = 90 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: stop = 100 start = 95 $ sudo tlp setcharge -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/${bata}/BATA/" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- ${xinc} X_SOC_CHECK=0 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: start = 60 stop = 100 (no change) $ sudo tlp setcharge 50 90 -- ${xinc} X_THRESH_SIMULATE_WRITEERR=1 X_SOC_CHECK=0 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: start = 50 (Error: write failed) stop = 90 (Error: write failed) $ sudo tlp setcharge 100 100 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: start charge threshold (100) for BATA is not specified, invalid or not in {40, 50, 60, 70, 80, 95}. Aborted. $ sudo tlp setcharge 50 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (0) for BATA is not specified, invalid or not in {60, 70, 80, 90, 100}. Aborted. $ sudo tlp setcharge 40 85 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (85) for BATA is not specified, invalid or not in {60, 70, 80, 90, 100}. Aborted. $ sudo tlp setcharge XYZZY 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: start charge threshold (XYZZY) for BATA is not specified, invalid or not in {40, 50, 60, 70, 80, 95}. Aborted. $ sudo tlp setcharge 50 XYZZY -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: stop charge threshold (XYZZY) for BATA is not specified, invalid or not in {60, 70, 80, 90, 100}. Aborted. $ sudo tlp setcharge 80 70 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Error: start threshold >= stop threshold for BATA. Aborted. $ sudo tlp setcharge 70 90 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: start = 70 stop = 90 $ sudo tlp setcharge 70 90 -- ${xinc} X_THRESH_SIMULATE_READERR="1" 2>&1 | sed -r "s/${bata}/BATA/" Error: could not read current charge threshold(s) for battery BATA. Aborted. $ sudo tlp setcharge 70 90 -- ${xinc} X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: start = 70 stop = 90 $ sudo tlp setcharge 70 90 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: start = 70 (no change) stop = 90 (no change) $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge DEF DEF -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" Setting temporary charge thresholds for battery BATA: stop = 100 start = 95 $ sudo tlp setcharge ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ sudo tlp setcharge 0 3 ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ sudo tlp setcharge XYZZY ABCDE ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" Error: battery BATB not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- ${xinc} | grep -E 'BAT0/charge_(control|behaviour)' /sys/class/power_supply/BAT0/charge_control_start_threshold = 95 [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- ${xinc} X_THRESH_SIMULATE_READERR=1 | grep -E 'BAT0/charge_(control|behaviour)' /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] $ # TLP-1.10.1/unit-tests/charge-thresholds_wilco-ec000077500000000000000000000161551517565574500215220ustar00rootroot00000000000000#!/usr/bin/env clitest # Test charge thresholds for laptops with Wilco EC # Requirements: # * Software: kernel module wilco_charger (Linux 5.9+) # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # +++ laptops with Wilco EC +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ $ # $ # --- tlp start $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (50..95). Skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (55..100). Skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (55..100). Skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="91" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 5. Skipped. TLP started $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ sudo tlp start -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 2>&1 | sed -r "s/(TLP started).*/\1/" TLP started $ # $ # --- tlp setcharge w/o arguments $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 Setting temporary charge thresholds: start = 60 stop = 100 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (50..95). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="0" Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="101" Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="91" Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 5. Aborted. $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" Setting temporary charge thresholds: start = 90 stop = 95 $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" Setting temporary charge thresholds: start = 90 (no change) stop = 95 (no change) $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Setting temporary charge thresholds: stop = 100 start = 95 $ sudo tlp setcharge -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" Error: there is no hardware driver support for charge thresholds. $ # $ # --- tlp setcharge w/ arguments $ sudo tlp setcharge 60 100 -- ${xinc} X_SOC_CHECK=0 Setting temporary charge thresholds: start = 60 stop = 100 (no change) $ sudo tlp setcharge 61 99 -- ${xinc} X_THRESH_SIMULATE_WRITEERR=1 X_SOC_CHECK=0 Setting temporary charge thresholds: start = 61 (Error: write failed) stop = 99 (Error: write failed) $ sudo tlp setcharge 100 100 -- ${xinc} Error: start charge threshold (100) is not specified, invalid or out of range (50..95). Aborted. $ sudo tlp setcharge 50 0 -- ${xinc} Error: stop charge threshold (0) is not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge 50 101 -- ${xinc} Error: stop charge threshold (101) is not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge XYZZY 0 -- ${xinc} Error: start charge threshold (XYZZY) is not specified, invalid or out of range (50..95). Aborted. $ sudo tlp setcharge 50 XYZZY -- ${xinc} Error: stop charge threshold (XYZZY) is not specified, invalid or out of range (55..100). Aborted. $ sudo tlp setcharge 90 91 -- ${xinc} Error: start threshold > stop threshold - 5. Aborted. $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge 90 95 -- ${xinc} Setting temporary charge thresholds: start = 90 stop = 95 $ sudo tlp setcharge 90 95 -- ${xinc} X_THRESH_SIMULATE_READERR="1" Error: could not read current charge threshold(s). Aborted. $ [ -n "${xinc}" ] && [ -n "$VWRITE_SLEEP" ] && sleep "$VWRITE_SLEEP" # workaround: mitigate void writes (in simulation) $ sudo tlp setcharge 90 95 -- ${xinc} X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" Setting temporary charge thresholds: start = 90 stop = 95 $ sudo tlp setcharge 90 95 -- ${xinc} Setting temporary charge thresholds: start = 90 (no change) stop = 95 (no change) $ sudo tlp setcharge DEF DEF -- ${xinc} Setting temporary charge thresholds: stop = 100 start = 95 $ sudo tlp setcharge BAT2 -- ${xinc} Error: battery BAT2 not present. $ sudo tlp setcharge 0 3 BAT2 -- ${xinc} Error: battery BAT2 not present. $ sudo tlp setcharge XYZZY ABCDE BAT2 -- ${xinc} Error: battery BAT2 not present. $ # $ # --- tlp-stat $ sudo tlp-stat -b -- ${xinc} | grep -E 'wilco-charger/charge_control' /sys/class/power_supply/wilco-charger/charge_control_start_threshold = 95 [%] /sys/class/power_supply/wilco-charger/charge_control_end_threshold = 100 [%] $ sudo tlp-stat -b -- ${xinc} X_THRESH_SIMULATE_READERR=1 | grep -E 'wilco-charger/charge_control' /sys/class/power_supply/wilco-charger/charge_control_start_threshold = (not available) [%] /sys/class/power_supply/wilco-charger/charge_control_end_threshold = (not available) [%] $ # $ # --- Reset test machine to configured thresholds $ sudo tlp setcharge BAT0 - ${xinc} > /dev/null 2>&1 $ # TLP-1.10.1/unit-tests/kmod-helper000077500000000000000000000020371517565574500165330ustar00rootroot00000000000000#!/bin/sh # Kernel module helper for battery care testing: disable/remove DKMS modules # $1: module name # $2: disable/enable/remove/restore # Constants KILLFILE=/etc/modprobe.d/kmod-helper.conf # Functions unload_kmod () { # $1: module name modprobe -r "$1" } # MAIN kernel=$(uname -r) module=$1 if ! modfile="$(modinfo -F filename "$module" 2> /dev/null)"; then echo "Error: could not locate kernel module \"$module\"." 1>&2 exit 1 fi modsave="${modfile}-save" case "$2" in disable) unload_kmod "$module" echo "install ${module} killmod" > $KILLFILE ;; enable) rm -f "$KILLFILE" ;; remove) unload_kmod "$module" if [ -f "$modfile" ]; then mv "$modfile" "$modsave" else exit 2 fi ;; restore) if [ -f "$modsave" ]; then mv "$modsave" "$modfile" else exit 2 fi ;; *) echo "Error: unknown action \"$2\"." 1>&2 exit 1 ;; esac exit 0 TLP-1.10.1/unit-tests/service-warnings000077500000000000000000000101211517565574500176030ustar00rootroot00000000000000#!/usr/bin/env clitest # Test services check # Requirements: # * installed: systemd, power-profiles-daemon # * RESTORE_DEVICE_STATE_ON_STARTUP, DEVICES_TO_DISABLE_ON_STARTUP, DEVICES_TO_ENABLE_ON_STARTUP not configured # Copyright (c) 2026 Thomas Koch . # SPDX-License-Identifier: GPL-2.0-or-later # $ # --- tlp.service $ # prepare $ sudo systemctl -q disable tlp.service $ # test $ sudo tlp start > /dev/null Error: TLP's power saving will not apply on boot because tlp.service is not enabled --> Run 'systemctl enable tlp.service' to ensure the full functionality of TLP. $ sudo tlp-stat -s > /dev/null Error: TLP's power saving will not apply on boot because tlp.service is not enabled --> Run 'systemctl enable tlp.service' to ensure the full functionality of TLP. $ # restore $ sudo systemctl -q enable tlp.service $ # $ # --- tlp-pd.service $ # prepare $ sudo systemctl -q disable tlp-pd.service $ sudo tlp start > /dev/null Error: After the next restart, you won't be able to switch TLP profiles by mouse click because tlp-pd.service is not enabled --> Run 'systemctl enable tlp-pd.service' to ensure the full functionality of TLP. $ sudo tlp-stat -s > /dev/null Error: After the next restart, you won't be able to switch TLP profiles by mouse click because tlp-pd.service is not enabled --> Run 'systemctl enable tlp-pd.service' to ensure the full functionality of TLP. $ # restore $ sudo systemctl -q enable tlp-pd.service $ # $ # --- systemd-rfkill.service/.socket $ # prepare $ sudo systemctl -q unmask systemd-rfkill.service $ sudo systemctl -q unmask systemd-rfkill.socket $ # test $ sudo tlp start -- RESTORE_DEVICE_STATE_ON_STARTUP=1 > /dev/null Warning: TLP's radio device switching on boot may not work as expected because RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and systemd-rfkill.service is not masked --> Run 'systemctl mask systemd-rfkill.service' to ensure the full functionality of TLP. Warning: TLP's radio device switching on boot may not work as expected because RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and systemd-rfkill.socket is not masked --> Run 'systemctl mask systemd-rfkill.socket' to ensure the full functionality of TLP. $ sudo tlp-stat -s -- RESTORE_DEVICE_STATE_ON_STARTUP=1 > /dev/null Warning: TLP's radio device switching on boot may not work as expected because RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and systemd-rfkill.service is not masked --> Run 'systemctl mask systemd-rfkill.service' to ensure the full functionality of TLP. Warning: TLP's radio device switching on boot may not work as expected because RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and systemd-rfkill.socket is not masked --> Run 'systemctl mask systemd-rfkill.socket' to ensure the full functionality of TLP. $ sudo tlp-stat -s -- DEVICES_TO_ENABLE_ON_STARTUP="dummy" > /dev/null Warning: TLP's radio device switching on boot may not work as expected because DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP is configured and systemd-rfkill.service is not masked --> Run 'systemctl mask systemd-rfkill.service' to ensure the full functionality of TLP. Warning: TLP's radio device switching on boot may not work as expected because DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP is configured and systemd-rfkill.socket is not masked --> Run 'systemctl mask systemd-rfkill.socket' to ensure the full functionality of TLP. $ sudo tlp-stat -s -- DEVICES_TO_DISABLE_ON_STARTUP="dummy" > /dev/null Warning: TLP's radio device switching on boot may not work as expected because DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP is configured and systemd-rfkill.service is not masked --> Run 'systemctl mask systemd-rfkill.service' to ensure the full functionality of TLP. Warning: TLP's radio device switching on boot may not work as expected because DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP is configured and systemd-rfkill.socket is not masked --> Run 'systemctl mask systemd-rfkill.socket' to ensure the full functionality of TLP. $ # restore $ sudo systemctl -q mask systemd-rfkill.service $ sudo systemctl -q mask systemd-rfkill.socket $ # TLP-1.10.1/unit-tests/test-TEMPLATE.sh000077500000000000000000000050551517565574500171300ustar00rootroot00000000000000#!/bin/sh # Test: TEMPLATE # # Tested parameters: # - # - # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Constants # TEMPLATE # --- Functions check_feature_one () { # TEMPLATE # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local errcnt=0 printf_msg "check_feature_one {{{\n" printf_msg " initial: \n" printf_msg " result: \n" # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_feature_two () { # TEMPLATE # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local errcnt=0 printf_msg "check_feature_two {{{\n" printf_msg " initial: \n" printf_msg " result: \n" # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_feature_three () { # TEMPLATE # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local errcnt=0 printf_msg "check_feature_three {{{\n" printf_msg " initial: \n" # TEMPLATE printf_msg " result: \n" # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } # --- MAIN # source library readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } # read args if [ $# -eq 0 ]; then do_feature_one="1" do_feature_two="1" do_feature_three="1" else while [ $# -gt 0 ]; do case "$1" in feature_one) do_feature_one="1" ;; feature_two) do_feature_two="1" ;; feature_three) do_feature_three="1" ;; esac shift # next argument done # while arguments fi # check prerequisites and initialize check_tlp cache_root_cred start_report _basename="${0##*/}" # shellcheck disable=SC2034 _logfile="$(date -Iseconds)_${_basename%.*}.log" _testcnt=0 _failcnt=0 report_test "$_basename" printf_msg "+++ %s\n\n" "$_basename" # initialize TLP sudo tlp start > /dev/null [ "$do_feature_one" = "1" ] && check_feature_one [ "$do_feature_two" = "1" ] && check_feature_two [ "$do_feature_three" = "1" ] && check_feature_three report_result "$_testcnt" "$_failcnt" print_report # --- Exit exit $_failcnt TLP-1.10.1/unit-tests/test-bc_all-simulate.sh000077500000000000000000000011621517565574500207450ustar00rootroot00000000000000#!/bin/sh # Note: the simulation is intended to run on a ThinkPad, which has a superset # of all battery care features readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report run_clitest "$spath/charge-thresholds_simulate1" run_clitest "$spath/charge-thresholds_simulate2" "$spath/test-bc_cros-ec-all-simulate.sh" "$spath/test-bc_dell-simulate.sh" "$spath/test-bc_lenovo-simulate.sh" "$spath/test-bc_tuxedo-simulate.sh" print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-bc_all.sh000077500000000000000000000005151517565574500171250ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report "$spath/test-bc_thinkpad.sh" "$spath/test-bc_all-simulate.sh" print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-bc_cros-ec-all-simulate.sh000077500000000000000000000020641517565574500223000ustar00rootroot00000000000000#!/bin/sh # Note: the simulation is intended to run on a ThinkPad, which has a superset # of all battery care features readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report if bat_present BAT0; then export bata=BAT0 export batb=BAT2 elif bat_present BAT1; then export bata=BAT1 export batb=BAT2 else # shellcheck disable=SC2059 printf "${ANSI_RED}Error:${ANSI_BLACK} neither BAT0 nor BAT1 exists.\n\n" report_test "${0##*/}" report_line "${ANSI_RED}FAIL:${ANSI_BLACK} not run - neither BAT0 nor BAT1 exists" print_report exit 1 fi export xinc="X_BAT_PLUGIN_SIMULATE=cros-ec X_BAT_CROSCC_SIMULATE_ECVER=2" sudo tlp setcharge 70 100 ${bata} > /dev/null 2>&1 # preset start threshold for simulation "$spath/test-bc_cros-ec-v2.sh" "(cros_charge-control)" export xinc="X_BAT_PLUGIN_SIMULATE=cros-ec" "$spath/test-bc_cros-ec-v3.sh" print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-bc_cros-ec-v2.sh000077500000000000000000000017111517565574500202340ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report if bat_present BAT0; then export bata=BAT0 export batb=BAT2 elif bat_present BAT1; then export bata=BAT1 export batb=BAT2 else # shellcheck disable=SC2059 printf "${ANSI_RED}Error:${ANSI_BLACK} neither BAT0 nor BAT1 exists.\n\n" report_test "${0##*/}" report_line "${ANSI_RED}FAIL:${ANSI_BLACK} not run - neither BAT0 nor BAT1 exists" print_report exit 1 fi # shellcheck disable=SC2154 echo " # bata=${bata} batb=${batb} xinc=${xinc}" run_clitest "$spath/charge-thresholds_cros-ec-v2" "$1" # reset test machine to configured thresholds sleep "$VWRITE_SLEEP" sudo tlp setcharge ${bata} > /dev/null 2>&1 # reset test machine to configured thresholds print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-bc_cros-ec-v3.sh000077500000000000000000000017031517565574500202360ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report if bat_present BAT0; then export bata=BAT0 export batb=BAT2 elif bat_present BAT1; then export bata=BAT1 export batb=BAT2 else # shellcheck disable=SC2059 printf "${ANSI_RED}Error:${ANSI_BLACK} neither BAT0 nor BAT1 exists.\n\n" report_test "${0##*/}" report_line "${ANSI_RED}FAIL:${ANSI_BLACK} not run - neither BAT0 nor BAT1 exists" print_report exit 1 fi # shellcheck disable=SC2154 echo " # bata=${bata} batb=${batb} xinc=${xinc}" run_clitest "$spath/charge-thresholds_cros-ec-v3" # reset test machine to configured thresholds sleep "$VWRITE_SLEEP" sudo tlp setcharge ${bata} > /dev/null 2>&1 # reset test machine to configured thresholds print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-bc_dell-simulate.sh000077500000000000000000000007761517565574500211270ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report export xinc="X_BAT_PLUGIN_SIMULATE=dell" echo " # xinc=${xinc}" 1>&2 run_clitest "$spath/charge-thresholds_dell" "" "$1" # reset test machine to configured thresholds sleep "$VWRITE_SLEEP" sudo tlp setcharge BAT0 > /dev/null 2>&1 print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-bc_lenovo-simulate.sh000077500000000000000000000012651517565574500215030ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred start_report export xinc="X_BAT_PLUGIN_SIMULATE=lenovo" export bctoff="X_THRESH_SIMULATE_BCT=""[Standard] Long_Life""" export bctoff2="X_THRESH_SIMULATE_BCT=""[Fast] Standard Long_Life""" export bcton="X_THRESH_SIMULATE_BCT=""Standard [Long_Life]""" export bcton2="X_THRESH_SIMULATE_BCT=""Fast Standard [Long_Life]""" echo " # xinc=${xinc} bcton=${bcton} bctoff=${bctoff} bcton2=${bcton2} bctoff2=${bctoff2}" 1>&2 run_clitest "$spath/charge-thresholds_lenovo" "" "$1" print_report TLP-1.10.1/unit-tests/test-bc_thinkpad.sh000077500000000000000000000013001517565574500201500ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report if cat /sys/class/power_supply/BAT1/charge_control_end_threshold > /dev/null 2>&1; then run_clitest "$spath/charge-thresholds_thinkpad-BAT1" elif cat /sys/class/power_supply/BAT0/charge_control_end_threshold > /dev/null 2>&1; then run_clitest "$spath/charge-thresholds_thinkpad" elif cat /sys/devices/platform/smapi/BAT0/stop_charge_thresh > /dev/null 2>&1; then run_clitest "$spath/charge-thresholds_thinkpad-legacy" fi print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-bc_tuxedo-simulate.sh000077500000000000000000000017121517565574500215060ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report if bat_present BAT0; then export bata=BAT0 export batb=BAT2 elif bat_present BAT1; then export bata=BAT1 export batb=BAT2 else # shellcheck disable=SC2059 printf "${ANSI_RED}Error:${ANSI_BLACK} neither BAT0 nor BAT1 exists.\n\n" report_test "${0##*/}" report_line "${ANSI_RED}FAIL:${ANSI_BLACK} not run - neither BAT0 nor BAT1 exists" print_report exit 1 fi export xinc="X_BAT_PLUGIN_SIMULATE=tuxedo X_BAT_PLUGIN_SIM_KMOD=thinkpad_acpi" echo " # xinc=${xinc} bata=${bata} batb=${batb}" 1>&2 run_clitest "$spath/charge-thresholds_tuxedo" # reset test machine to configured thresholds sleep "$VWRITE_SLEEP" sudo tlp setcharge ${bata} > /dev/null 2>&1 print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-bc_tuxedo.sh000077500000000000000000000016251517565574500176700ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report if bat_present BAT0; then export bata=BAT0 export batb=BAT2 elif bat_present BAT1; then export bata=BAT1 export batb=BAT2 else # shellcheck disable=SC2059 printf "${ANSI_RED}Error:${ANSI_BLACK} neither BAT0 nor BAT1 exists.\n\n" report_test "${0##*/}" report_line "${ANSI_RED}FAIL:${ANSI_BLACK} not run - neither BAT0 nor BAT1 exists" print_report exit 1 fi export xinc="" echo " # bata=${bata} batb=${batb}" 1>&2 run_clitest "$spath/charge-thresholds_tuxedo" # reset test machine to configured thresholds sudo tlp setcharge ${bata} > /dev/null 2>&1 # reset test machine to configured thresholds print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-cpufreq.sh000077500000000000000000001226411517565574500173630ustar00rootroot00000000000000#!/bin/sh # Test CPU related features # # Tested parameters: # * CPU_SCALING_GOVERNOR_ON_AC/BAT # * CPU_SCALING_MIN/MAX_FREQ_ON_AC/BAT # * CPU_ENERGY_PERF_POLICY_ON_AC/BAT # * CPU_MIN/MAX_PERF_ON_AC/BAT # * CPU_BOOST_ON_AC/BAT # * CPU_HWP_DYN_BOOST_ON_AC/BAT # * PLATFORM_PROFIiLE_ON_AC/BAT # # Supported CPU scaling drivers: # * acpi-cpufreq # * apple-cpufreq # * amd-pstate # * amd-pstate-epp # * intel_pstate # * intel_cpufreq # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Constants readonly CPUD="/sys/devices/system/cpu" readonly CPU0="${CPUD}/cpu0" readonly INTELPSD="/sys/devices/system/cpu/intel_pstate" readonly AMDPSD="/sys/devices/system/cpu/amd_pstate" readonly FWACPID="/sys/firmware/acpi" # --- Functions print_nth_arg () { # Get n-th argument # $1: n # $2..$m: arguments local n="$1" [ "$1" -gt 0 ] || return until [ "$n" -eq 0 ] || [ $# -eq 0 ]; do shift n=$((n - 1)) done printf "%s" "$1" } # --- Tests check_cpu_driver_opmode () { # apply cpu driver operation mode local opm opm_save opm_seq local prof local rc=0 local errcnt=0 printf_msg "check_cpu_driver_opmode {{{\n" case "$_cpu_driver" in amd?pstate?epp|amd?pstate) # save initial opmode opm_save="$(read_sysf "${AMDPSD}/status")" printf_msg " initial(%s): %s\n" "$prof_save" "$opm_save" for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # iterate opmodes supported by the driver, return to initial opmode case "$opm_save" in active) opm_seq="guided passive active" ;; guided) opm_seq="passive active guided" ;; passive) opm_seq="active guided passive" ;; esac for opm in $opm_seq; do case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_DRIVER_OPMODE_ON_AC="$opm" CPU_DRIVER_OPMODE_ON_BAT="$opm_save" CPU_DRIVER_OPMODE_ON_SAV="$opm_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_DRIVER_OPMODE_ON_BAT="$opm" CPU_DRIVER_OPMODE_ON_SAV="$opm_save" CPU_DRIVER_OPMODE_ON_AC="$opm_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_DRIVER_OPMODE_ON_SAV="$opm" CPU_DRIVER_OPMODE_ON_AC="$opm_save" CPU_DRIVER_OPMODE_ON_BAT="$opm_save" \ > /dev/null 2>&1 ;; esac # expect change compare_sysf "$opm" "${AMDPSD}/status" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$opm" else printf_msg " %s=err(%s)" "$opm" "$rc" errcnt=$((errcnt + 1)) fi done # opm printf_msg "\n" done # prof # print resulting opmode printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "${AMDPSD}/status")" ;; # amd_pstate intel_pstate) # save initial policy opm_save="$(read_sysf "${INTELPSD}/status")" printf_msg " initial(%s): %s\n" "$prof_save" "$opm_save" for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # iterate opmodes supported by the driver, return to initial opmode case "$opm_save" in active) opm_seq="passive active" ;; passive) opm_seq="active passive" ;; esac for opm in $opm_seq; do case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_DRIVER_OPMODE_ON_AC="$opm" CPU_DRIVER_OPMODE_ON_BAT="$opm_save" CPU_DRIVER_OPMODE_ON_SAV="$opm_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_DRIVER_OPMODE_ON_BAT="$opm" CPU_DRIVER_OPMODE_ON_SAV="$opm_save" CPU_DRIVER_OPMODE_ON_AC="$opm_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_DRIVER_OPMODE_ON_SAV="$opm" CPU_DRIVER_OPMODE_ON_AC="$opm_save" CPU_DRIVER_OPMODE_ON_BAT="$opm_save" \ > /dev/null 2>&1 ;; esac # expect change compare_sysf "$opm" "${INTELPSD}/status" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$opm" else printf_msg " %s=err(%s)" "$opm" "$rc" errcnt=$((errcnt + 1)) fi done # opm printf_msg "\n" done # prof # print resulting opmode printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "${AMDPSD}/status")" ;; # intel_pstate *) printf_msg "*** unsupported cpu\n" ;; esac # _cpu_driver printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_cpu_scaling_governor () { # apply cpu scaling governor local gov gov_save gov_seq local prof local rc=0 local errcnt=0 printf_msg "check_cpu_scaling_governor {{{\n" case "$_cpu_driver" in amd?pstate|amd?pstate?epp|intel_pstate) # save initial governor gov_save="$(read_sysf "${CPU0}/cpufreq/scaling_governor")" printf_msg " initial(%s): %s\n" "$prof_save" "$gov_save" for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # iterate governors supported by the driver, return to initial governor case "$gov_save" in performance) gov_seq="powersave performance" ;; powersave) gov_seq="performance powersave" ;; esac for gov in $gov_seq; do case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_GOVERNOR_ON_AC="$gov" CPU_SCALING_GOVERNOR_ON_BAT="$gov_save" CPU_SCALING_GOVERNOR_ON_SAV="$gov_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_GOVERNOR_ON_BAT="$gov" CPU_SCALING_GOVERNOR_ON_SAV="$gov_save" CPU_SCALING_GOVERNOR_ON_AC="$gov_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_GOVERNOR_ON_SAV="$gov" CPU_SCALING_GOVERNOR_ON_AC="$gov_save" CPU_SCALING_GOVERNOR_ON_BAT="$gov_save" \ > /dev/null 2>&1 ;; esac # expect change glob_compare_sysf "$gov" ${CPUD}/cpu*/cpufreq/scaling_governor rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$gov" else printf_msg " %s=err(%s)" "$gov" "$rc" errcnt=$((errcnt + 1)) fi done # gov printf_msg "\n" done # prof # print resulting governor printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "${CPU0}/cpufreq/scaling_governor")" ;; # amd/intel_pstate acpi-cpufreq|apple-cpufreq|intel_cpufreq) # save initial governor gov_save="$(read_sysf "${CPU0}/cpufreq/scaling_governor")" printf_msg " initial(%s): %s\n" "$prof_save" "$gov_save" for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # iterate governors supported by the driver, return to initial governor case "$gov_save" in performance) gov_seq="schedutil conservative ondemand powersave performance" ;; schedutil) gov_seq="performance conservative ondemand powersave performance schedutil" ;; conservative) gov_seq="performance ondemand powersave performance schedutil conservative" ;; ondemand) gov_seq="performance powersave performance schedutil conservative ondemand" ;; powersave) gov_seq="performance performance schedutil conservative ondemand powersave" ;; esac for gov in $gov_seq; do # apply target governor case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_GOVERNOR_ON_AC="$gov" CPU_SCALING_GOVERNOR_ON_BAT="$gov_save" CPU_SCALING_GOVERNOR_ON_SAV="$gov_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_GOVERNOR_ON_BAT="$gov" CPU_SCALING_GOVERNOR_ON_SAV="$gov_save" CPU_SCALING_GOVERNOR_ON_AC="$gov_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_GOVERNOR_ON_SAV="$gov" CPU_SCALING_GOVERNOR_ON_AC="$gov_save" CPU_SCALING_GOVERNOR_ON_BAT="$gov_save" \ > /dev/null 2>&1 ;; esac # expect change glob_compare_sysf "$gov" ${CPUD}/cpu*/cpufreq/scaling_governor rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$gov" else printf_msg " %s=err(%s)" "$gov" "$rc" errcnt=$((errcnt + 1)) fi done # gov printf_msg "\n" done # prof # print resulting governor printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "${CPU0}/cpufreq/scaling_governor")" ;; # acpi/apple/intel-cpufreq *) printf_msg "*** unknown cpu driver\n" ;; esac # _cpu_driver # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_cpu_scaling_freq () { # apply cpu min/max scaling frequency local min min_save="" max max_save local favail fcnt local prof local rc=0 local errcnt=0 printf_msg "check_cpu_scaling_freq {{{\n" case "$_cpu_driver" in amd?pstate|amd?pstate?epp|intel_pstate|acpi-cpufreq|apple-cpufreq|intel_cpufreq) # save initial frequencies min_save="$(read_sysf "${CPU0}/cpufreq/scaling_min_freq")" max_save="$(read_sysf "${CPU0}/cpufreq/scaling_max_freq")" printf_msg " initial(%s): min/%s max/%s\n" "$prof_save" "$min_save" "$max_save" # target frequencies: increase min, decrease max if favail=$(read_sysf "${CPU0}/cpufreq/scaling_available_frequencies"); then fcnt="$(echo "$favail" | wc -w)" # shellcheck disable=SC2086 min=$(print_nth_arg $((fcnt - 1)) $favail) max=$(print_nth_arg 2 "$favail") else min=$((min_save + 100000)) max=$((max_save - 100000)) fi for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:\n" "$prof" # apply target frequencies case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_MIN_FREQ_ON_AC="$min" CPU_SCALING_MIN_FREQ_ON_BAT="$min_save" CPU_SCALING_MIN_FREQ_ON_SAV="$min_save" \ CPU_SCALING_MAX_FREQ_ON_AC="$max" CPU_SCALING_MAX_FREQ_ON_BAT="$max_save" CPU_SCALING_MAX_FREQ_ON_SAV="$min_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_MIN_FREQ_ON_BAT="$min" CPU_SCALING_MIN_FREQ_ON_SAV="$min_save" CPU_SCALING_MIN_FREQ_ON_AC="$min_save" \ CPU_SCALING_MAX_FREQ_ON_BAT="$max" CPU_SCALING_MAX_FREQ_ON_SAV="$max_save" CPU_SCALING_MAX_FREQ_ON_AC="$min_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_MIN_FREQ_ON_SAV="$min" CPU_SCALING_MIN_FREQ_ON_AC="$min_save" CPU_SCALING_MIN_FREQ_ON_BAT="$min_save" \ CPU_SCALING_MAX_FREQ_ON_SAV="$max" CPU_SCALING_MAX_FREQ_ON_AC="$max_save" CPU_SCALING_MAX_FREQ_ON_BAT="$min_save" \ > /dev/null 2>&1 ;; esac # expect target frequencies glob_compare_sysf "$min" ${CPUD}/cpu*/cpufreq/scaling_min_freq rc=$? if [ "$rc" -eq 0 ]; then printf_msg " min/%s=ok" "$min" else printf_msg " min/%s=err(%s)" "$min" "$rc" errcnt=$((errcnt + 1)) fi glob_compare_sysf "$max" ${CPUD}/cpu*/cpufreq/scaling_max_freq rc=$? if [ "$rc" -eq 0 ]; then printf_msg " max/%s=ok" "$max" else printf_msg " max/%s=err(%s)" "$max" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" # revert to initial frequencies case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_MIN_FREQ_ON_AC="$min_save" CPU_SCALING_MIN_FREQ_ON_BAT="$min" CPU_SCALING_MIN_FREQ_ON_SAV="$min" \ CPU_SCALING_MAX_FREQ_ON_AC="$max_save" CPU_SCALING_MAX_FREQ_ON_BAT="$max" CPU_SCALING_MAX_FREQ_ON_SAV="$min" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_MIN_FREQ_ON_BAT="$min_save" CPU_SCALING_MIN_FREQ_ON_SAV="$min" CPU_SCALING_MIN_FREQ_ON_AC="$min" \ CPU_SCALING_MAX_FREQ_ON_BAT="$max_save" CPU_SCALING_MAX_FREQ_ON_SAV="$max" CPU_SCALING_MAX_FREQ_ON_AC="$min" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_SCALING_MIN_FREQ_ON_SAV="$min_save" CPU_SCALING_MIN_FREQ_ON_AC="$min" CPU_SCALING_MIN_FREQ_ON_BAT="$min" \ CPU_SCALING_MAX_FREQ_ON_SAV="$max_save" CPU_SCALING_MAX_FREQ_ON_AC="$max" CPU_SCALING_MAX_FREQ_ON_BAT="$min" \ > /dev/null 2>&1 ;; esac # sleep 0.1 # expect initial frequencies glob_compare_sysf "$min_save" ${CPUD}/cpu*/cpufreq/scaling_min_freq rc=$? if [ "$rc" -eq 0 ]; then printf_msg " min/%s=ok" "$min_save" else printf_msg " min/%s=err(%s)" "$min_save" "$rc" errcnt=$((errcnt + 1)) fi glob_compare_sysf "$max_save" ${CPUD}/cpu*/cpufreq/scaling_max_freq rc=$? if [ "$rc" -eq 0 ]; then printf_msg " max/%s=ok" "$max_save" else printf_msg " max/%s=err(%s)" "$max_save" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof # print resulting frequencies printf_msg " result(%s): min/%s max/%s\n" "$prof" "$(read_sysf "${CPU0}/cpufreq/scaling_min_freq")" "$(read_sysf "${CPU0}/cpufreq/scaling_max_freq")" ;; # drivers supporting freq changes *) printf_msg "*** unsupported cpu driver" ;; esac # _cpu_driver # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_cpu_epp () { # apply cpu energy vs. performance policy local pol pol_save pol_seq local prof local rc=0 local errcnt=0 printf_msg "check_cpu_epp {{{\n" case "$_cpu_driver" in amd?pstate?epp|intel_pstate|intel_cpufreq) if [ -f "${CPU0}/cpufreq/energy_performance_preference" ]; then # save initial policy pol_save="$(read_sysf "${CPU0}/cpufreq/energy_performance_preference")" printf_msg " initial(%s): %s\n" "$prof_save" "$pol_save" for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # iterate policies supported by the driver, return to initial policy case "$pol_save" in performance) pol_seq="balance_performance balance_power power performance" ;; balance_performance) pol_seq="performance balance_power power balance_performance" ;; balance_power) pol_seq="performance balance_performance power balance_power" ;; power) pol_seq="performance balance_performance balance_power power" ;; esac for pol in $pol_seq; do case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_ENERGY_PERF_POLICY_ON_AC="$pol" CPU_ENERGY_PERF_POLICY_ON_BAT="$pol_save" CPU_ENERGY_PERF_POLICY_ON_SAV="$pol_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_ENERGY_PERF_POLICY_ON_BAT="$pol" CPU_ENERGY_PERF_POLICY_ON_SAV="$pol_save" CPU_ENERGY_PERF_POLICY_ON_AC="$pol_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_ENERGY_PERF_POLICY_ON_SAV="$pol" CPU_ENERGY_PERF_POLICY_ON_AC="$pol_save" CPU_ENERGY_PERF_POLICY_ON_BAT="$pol_save" \ > /dev/null 2>&1 ;; esac # expect policy change glob_compare_sysf "$pol" ${CPUD}/cpu*/cpufreq/energy_performance_preference rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$pol" else printf_msg " %s=err(%s)" "$pol" "$rc" errcnt=$((errcnt + 1)) fi done # pol printf_msg "\n" done # prof # print resulting policy printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "${CPU0}/cpufreq/energy_performance_preference")" else printf_msg "*** unsupported cpu\n" fi ;; *) printf_msg "*** unsupported cpu\n" ;; esac # _cpu_driver printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_cpu_perf_pct () { # apply intel_pstate min/max performance (%) local min min_save max max_save local prof local rc=0 local errcnt=0 printf_msg "check_cpu_perf_pct {{{\n" case "$_cpu_driver" in intel_pstate|intel_cpufreq) # save initial performance min_save="$(read_sysf "$INTELPSD/min_perf_pct")" max_save="$(read_sysf "$INTELPSD/max_perf_pct")" printf_msg " initial(%s): min/%s max/%s\n" "$prof_save" "$min_save" "$max_save" # target performance: increase min, decrease max min=$((min_save + 10)) max=$((max_save - 10)) for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:\n" "$prof" # apply target performance case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_MIN_PERF_ON_AC="$min" CPU_MIN_PERF_ON_BAT="$min_save" CPU_MIN_PERF_ON_SAV="$min_save"\ CPU_MAX_PERF_ON_AC="$max" CPU_MAX_PERF_ON_BAT="$min_save" CPU_MAX_PERF_ON_SAV="$min_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_MIN_PERF_ON_BAT="$min" CPU_MIN_PERF_ON_SAV="$min_save" CPU_MIN_PERF_ON_AC="$min_save"\ CPU_MAX_PERF_ON_BAT="$max" CPU_MAX_PERF_ON_SAV="$min_save" CPU_MAX_PERF_ON_AC="$min_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_MIN_PERF_ON_SAV="$min" CPU_MIN_PERF_ON_AC="$min_save" CPU_MIN_PERF_ON_BAT="$min_save"\ CPU_MAX_PERF_ON_SAV="$max" CPU_MAX_PERF_ON_AC="$min_save" CPU_MAX_PERF_ON_BAT="$min_save" \ > /dev/null 2>&1 ;; esac # expect performance change compare_sysf "$min" "$INTELPSD/min_perf_pct" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " min/%s=ok" "$min" else printf_msg " min/%s=err(%s)" "$min" "$rc" errcnt=$((errcnt + 1)) fi compare_sysf "$max" "$INTELPSD/max_perf_pct" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " max/%s=ok" "$max" else printf_msg " max/%s=err(%s)" "$max" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" # revert to initial performance case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_MIN_PERF_ON_AC="$min_save" CPU_MIN_PERF_ON_BAT="$min" CPU_MIN_PERF_ON_SAV="$min"\ CPU_MAX_PERF_ON_AC="$max_save" CPU_MAX_PERF_ON_BAT="$min" CPU_MAX_PERF_ON_SAV="$min" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_MIN_PERF_ON_BAT="$min_save" CPU_MIN_PERF_ON_SAV="$min" CPU_MIN_PERF_ON_AC="$min"\ CPU_MAX_PERF_ON_BAT="$max_save" CPU_MAX_PERF_ON_SAV="$min" CPU_MAX_PERF_ON_AC="$min" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_MIN_PERF_ON_SAV="$min_save" CPU_MIN_PERF_ON_AC="$min" CPU_MIN_PERF_ON_BAT="$min"\ CPU_MAX_PERF_ON_SAV="$max_save" CPU_MAX_PERF_ON_AC="$min" CPU_MAX_PERF_ON_BAT="$min" \ > /dev/null 2>&1 ;; esac # expect initial performance compare_sysf "$min_save" "$INTELPSD/min_perf_pct" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " min/%s=ok" "$min_save" else printf_msg " min/%s=err(%s)" "$min_save" "$rc" errcnt=$((errcnt + 1)) fi compare_sysf "$max_save" "$INTELPSD/max_perf_pct" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " max/%s=ok" "$max_save" else printf_msg " max/%s=err(%s)" "$max_save" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof # print resulting min/max performance printf_msg " result(%s): min/%s max/%s\n" "$prof" "$(read_sysf "$INTELPSD/min_perf_pct")" "$(read_sysf "$INTELPSD/max_perf_pct")" ;; # intel_pstate/cpufreq *) printf_msg "*** unsupported cpu driver\n" ;; esac # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_cpu_boost () { # apply cpu boost/turbo mode and dynamic boost local boost boost_save no_turbo no_turbo_save dyn_boost dyn_boost_save local prof local rc=0 local errcnt=0 printf_msg "check_cpu_boost {{{\n" case "$_cpu_driver" in intel_pstate|intel_cpufreq) # save initial states no_turbo_save="$(read_sysf "$INTELPSD/no_turbo")" dyn_boost_save="$(read_sysf "$INTELPSD/hwp_dynamic_boost" "")" printf_msg " initial(%s): no_turbo/%s dyn_boost/%s\n" "$prof_save" "$no_turbo_save" "$dyn_boost_save" # target turbo state: inverted no_turbo="$((no_turbo_save ^ 1))" for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:\n" "$prof" # apply inverted turbo state # note: the CPU_BOOST_ON_AC/BAT/SAV setting must be the inverse of the new no_turbo state # so use $no_turbo_save as parameter value case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_AC="$no_turbo_save" CPU_BOOST_ON_BAT="$no_turbo" CPU_BOOST_ON_SAV="$no_turbo" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_BAT="$no_turbo_save" CPU_BOOST_ON_SAV="$no_turbo" CPU_BOOST_ON_AC="$no_turbo" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_SAV="$no_turbo_save" CPU_BOOST_ON_AC="$no_turbo" CPU_BOOST_ON_BAT="$no_turbo" \ > /dev/null 2>&1 ;; esac # expect turbo state change # note: the actual no_turbo state is the inverse of the CPU_BOOST_ON_AC/BAT/SAV parameter value, # so use $no_turbo for comparison compare_sysf "$no_turbo" "$INTELPSD/no_turbo" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " no_turbo/%s=ok" "$no_turbo" else printf_msg " no_turbo/%s=err(%s)" "$no_turbo" "$rc" errcnt=$((errcnt + 1)) fi if [ -f "$INTELPSD/hwp_dynamic_boost" ]; then # invert dyn boost state dyn_boost="$((dyn_boost_save ^ 1))" # apply inverted dyn boost state case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_HWP_DYN_BOOST_ON_AC="$dyn_boost" CPU_HWP_DYN_BOOST_ON_BAT="$dyn_boost_save" CPU_HWP_DYN_BOOST_ON_SAV="$dyn_boost_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_HWP_DYN_BOOST_ON_BAT="$dyn_boost" CPU_HWP_DYN_BOOST_ON_SAV="$dyn_boost_save" CPU_HWP_DYN_BOOST_ON_AC="$dyn_boost_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_HWP_DYN_BOOST_ON_SAV="$dyn_boost" CPU_HWP_DYN_BOOST_ON_AC="$dyn_boost_save" CPU_HWP_DYN_BOOST_ON_BAT="$dyn_boost_save" \ > /dev/null 2>&1 ;; esac # expect dyn boost state change compare_sysf "$dyn_boost" "$INTELPSD/hwp_dynamic_boost" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " dyn_boost/%s=ok" "$dyn_boost" else printf_msg " dyn_boost/%s=err(%s)" "$dyn_boost" "$rc" errcnt=$((errcnt + 1)) fi else printf_msg " dyn_boost/" fi printf_msg "\n" # revert to initial turbo state # note: the CPU_BOOST_ON_AC/BAT/SAV setting must be the inverse of the new no_turbo state # so use $no_turbo as parameter value case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_AC="$no_turbo" CPU_BOOST_ON_BAT="$no_turbo_save" CPU_BOOST_ON_SAV="$no_turbo_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_BAT="$no_turbo" CPU_BOOST_ON_SAV="$no_turbo_save" CPU_BOOST_ON_AC="$no_turbo_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_SAV="$no_turbo" CPU_BOOST_ON_AC="$no_turbo_save" CPU_BOOST_ON_BAT="$no_turbo_save" \ > /dev/null 2>&1 ;; esac # expect initial turbo state # note: the actual no_turbo state is the inverse of the CPU_BOOST_ON_AC/BAT/SAV parameter value, # so use $no_turbo_save for comparison compare_sysf "$no_turbo_save" "$INTELPSD/no_turbo" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " no_turbo/%s=ok" "$no_turbo_save" else printf_msg " no_turbo/%s=err(%s)" "$no_turbo_save" "$rc" errcnt=$((errcnt + 1)) fi if [ -f "$INTELPSD/hwp_dynamic_boost" ]; then # revert to initial dyn boost state case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_HWP_DYN_BOOST_ON_AC="$dyn_boost_save" CPU_HWP_DYN_BOOST_ON_BAT="$dyn_boost" CPU_HWP_DYN_BOOST_ON_SAV="$dyn_boost" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_HWP_DYN_BOOST_ON_BAT="$dyn_boost_save" CPU_HWP_DYN_BOOST_ON_SAV="$dyn_boost" CPU_HWP_DYN_BOOST_ON_AC="$dyn_boost" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_HWP_DYN_BOOST_ON_SAV="$dyn_boost_save" CPU_HWP_DYN_BOOST_ON_AC="$dyn_boost" CPU_HWP_DYN_BOOST_ON_BAT="$dyn_boost" \ > /dev/null 2>&1 ;; esac # expect initial dyn boost state compare_sysf "$dyn_boost_save" "$INTELPSD/hwp_dynamic_boost" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " dyn_boost/%s=ok" "$dyn_boost_save" else printf_msg " dyn_boost/%s=err(%s)" "$dyn_boost_save" "$rc" errcnt=$((errcnt + 1)) fi else printf_msg " dyn_boost/" fi printf_msg "\n" done # prof # print resulting turbo, dyn boost states printf_msg " result(%s): no_turbo/%s dyn_boost/%s\n" \ "$prof_save" \ "$(read_sysf "$INTELPSD/no_turbo")" \ "$(read_sysf "$INTELPSD/hwp_dynamic_boost" "")" ;; # intel_pstate/cpufreq acpi-cpufreq|amd?pstate*) if [ -f "${CPUD}/cpufreq/boost" ]; then # save initial boost state boost_save="$(read_sysf "${CPUD}/cpufreq/boost")" printf_msg " initial(%s): boost/%s\n" "$prof_save" "$boost_save" # invert boost state boost="$((boost_save ^ 1))" for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # apply inverted boost state case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_AC="$boost" CPU_BOOST_ON_BAT="$boost_save" CPU_BOOST_ON_SAV="$boost_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_BAT="$boost" CPU_BOOST_ON_SAV="$boost_save" CPU_BOOST_ON_AC="$boost_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_SAV="$boost" CPU_BOOST_ON_AC="$boost_save" CPU_BOOST_ON_BAT="$boost_save" \ > /dev/null 2>&1 ;; esac # expect boost state change compare_sysf "$boost" "${CPUD}/cpufreq/boost" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " boost/%s=ok" "$boost" else printf_msg " boost/%s=err(%s)" "$boost" "$rc" errcnt=$((errcnt + 1)) fi # revert to initial boost state case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_AC="$boost_save" CPU_BOOST_ON_BAT="$boost" CPU_BOOST_ON_SAV="$boost" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_BAT="$boost_save" CPU_BOOST_ON_SAV="$boost" CPU_BOOST_ON_AC="$boost" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ CPU_BOOST_ON_SAV="$boost_save" CPU_BOOST_ON_AC="$boost" CPU_BOOST_ON_BAT="$boost" \ > /dev/null 2>&1 ;; esac # expect initial boost state compare_sysf "$boost_save" "${CPUD}/cpufreq/boost" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " boost/%s=ok" "$boost_save" else printf_msg " boost/%s=err(%s)" "$boost_save" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof # print resulting boost state printf_msg " result(%s): boost/%s\n" "$prof_save" "$(read_sysf "${CPUD}/cpufreq/boost")" else printf_msg "*** unsupported cpu\n" fi ;; # acpi-cpufreq/amd-pstate *) printf_msg "*** unsupported cpu driver\n" ;; esac # _cpu_driver printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_platform_profile () { # apply plaform profile local pprof pprof_save local prof local rc=0 local errcnt=0 printf_msg "check_platform_profile {{{\n" # save initial platform profile / check availability if pprof_save="$(read_sysf "${FWACPID}/platform_profile")"; then printf_msg " initial(%s): %s\n" "$prof_save" "$pprof_save" # iterate profiles w/ standard platform profile values (hoping all drivers support them) for prof in $prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" case "$prof" in performance) pprof="performance" sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ PLATFORM_PROFILE_ON_AC="$pprof" PLATFORM_PROFILE_ON_BAT="$pprof_save" PLATFORM_PROFILE_SAV="$pprof_save" \ > /dev/null 2>&1 ;; balanced) pprof="balanced" sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ PLATFORM_PROFILE_ON_BAT="$pprof" PLATFORM_PROFILE_ON_SAV="$pprof_save" PLATFORM_PROFILE_AC="$pprof_save" \ > /dev/null 2>&1 ;; power-saver) pprof="low-power" sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ PLATFORM_PROFILE_ON_SAV="$pprof" PLATFORM_PROFILE_ON_AC="$pprof_save" PLATFORM_PROFILE_BAT="$pprof_save" \ > /dev/null 2>&1 ;; esac # expect platform profile change compare_sysf "$pprof" "${FWACPID}/platform_profile" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$pprof" else printf_msg " %s=err(%s)" "$pprof" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof # print resulting platform profile printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "${FWACPID}/platform_profile")" else printf_msg "*** unsupported platform\n" fi printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } # --- MAIN # source library readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } # read args if [ $# -eq 0 ]; then do_opmode="1" do_governor="1" do_freq="1" do_epp="1" do_perf="1" do_boost="1" do_profile="1" else while [ $# -gt 0 ]; do case "$1" in opmode) do_opmode="1" ;; governor) do_governor="1" ;; freq) do_freq="1" ;; epp) do_epp="1" ;; perf_pct) do_perf="1" ;; boost) do_boost="1" ;; profile) do_profile="1" ;; esac shift # next argument done # while arguments fi # check prerequisites and initialize check_tlp _cpu_driver=$(read_sysf "${CPU0}/cpufreq/scaling_driver") || { printf_msg "Error: could not determine cpu scaling driver." exit 128 } cache_root_cred start_report # shellcheck disable=SC2034 _basename="${0##*/}" # shellcheck disable=SC2034 _logfile="$(date -Iseconds)_${_basename%.*}.log" _testcnt=0 _failcnt=0 report_test "$_basename" printf_msg "+++ %s --- cpu_driver: %s\n\n" "$_basename" "$_cpu_driver" # save initial profile read_saved_profile # shellcheck disable=SC2154 prof_save="$(pp2str "$_prof")" # iterate supported profiles, return to initial profile case "$prof_save" in performance) prof_seq="balanced power-saver performance" ;; balanced) prof_seq="power-saver performance balanced" ;; power-saver) prof_seq="performance balanced power-saver" ;; esac # --- TEST [ "$do_opmode" = "1" ] && check_cpu_driver_opmode [ "$do_governor" = "1" ] && check_cpu_scaling_governor [ "$do_freq" = "1" ] && check_cpu_scaling_freq [ "$do_epp" = "1" ] && check_cpu_epp [ "$do_perf" = "1" ] && check_cpu_perf_pct [ "$do_boost" = "1" ] && check_cpu_boost [ "$do_profile" = "1" ] && check_platform_profile report_result "$_testcnt" "$_failcnt" print_report # --- Exit exit $_failcnt TLP-1.10.1/unit-tests/test-disabled-defaults.sh000077500000000000000000000110371517565574500212660ustar00rootroot00000000000000#!/bin/sh # Test: # - Iterate all profiles with TLP_DISABLE_DEFAULTS=1 # - Check a default-configured tunable to ensure it does not change # - Use EPP as sample, it's available on most hardware # Tested parameters: # - TLP_DISABLE_DEFAULTS=1 # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Constants readonly CPUD="/sys/devices/system/cpu" readonly CPU0="${CPUD}/cpu0" readonly DEFAULTS="/usr/share/tlp/defaults.conf" readonly READCONFS="/usr/share/tlp/tlp-readconfs" # --- Functions check_disabled_defaults () { # Iterate all profiles and check if EPP changes # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local pol pol_save local prof local rc=0 local errcnt=0 printf_msg "check_disabled_defaults {{{\n" if $READCONFS | grep -v '^defaults.conf' | grep -E -q 'CPU_ENERGY_PERF_POLICY'; then printf_msg "*** Error: For the test to succeed, your configuration must not include CPU_ENERGY_PERF_POLICY_ON_AC/BAT/SAV.\n" errcnt=1 elif [ -f "${CPU0}/cpufreq/energy_performance_preference" ]; then # save initial policy pol_save="$(read_sysf "${CPU0}/cpufreq/energy_performance_preference")" printf_msg " initial(%s): %s\n" "$prof_save" "$pol_save" for prof in $prof_seq; do # --- test profile printf_msg " %s:\n" "$prof" pol_save="$(read_sysf "${CPU0}/cpufreq/energy_performance_preference")" case "$prof" in performance) pol="$(sed -rn 's/CPU_ENERGY_PERF_POLICY_ON_AC=(.+)/\1/p' "$DEFAULTS")" ;; balanced) pol="$(sed -rn 's/CPU_ENERGY_PERF_POLICY_ON_BAT=(.+)/\1/p' "$DEFAULTS")" ;; power-saver) pol="$(sed -rn 's/CPU_ENERGY_PERF_POLICY_ON_SAV=(.+)/\1/p' "$DEFAULTS")" ;; esac # defaults disabled --> don't expect policy change printf_msg " TLP_AUTO_SWITCH=2 TLP_DISABLE_DEFAULTS=1:" sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" TLP_DISABLE_DEFAULTS=1 > /dev/null 2>&1 compare_sysf "$pol_save" "${CPU0}/cpufreq/energy_performance_preference" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$pol_save" else printf_msg " %s=err(%s)" "$pol" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" # defaults enabled --> expect policy change printf_msg " TLP_AUTO_SWITCH=2:" sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" > /dev/null 2>&1 compare_sysf "$pol" "${CPU0}/cpufreq/energy_performance_preference" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$pol" else printf_msg " %s=err(%s)" "$pol" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof # print resulting policy printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "${CPU0}/cpufreq/energy_performance_preference")" else printf_msg "*** unsupported cpu or driver\n" fi printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } # --- MAIN # source library readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } # read args if [ $# -eq 0 ]; then do_disabled_defaults="1" else while [ $# -gt 0 ]; do case "$1" in disabled_defaults) do_disabled_defaults="1" ;; esac shift # next argument done # while arguments fi # check prerequisites and initialize check_tlp cache_root_cred start_report # shellcheck disable=SC2034 _basename="${0##*/}" # shellcheck disable=SC2034 _logfile="$(date -Iseconds)_${_basename%.*}.log" _testcnt=0 _failcnt=0 report_test "$_basename" printf_msg "+++ %s\n\n" "$_basename" # save initial profile read_saved_profile # shellcheck disable=SC2154 prof_save="$(pp2str "$_prof")" # iterate supported profiles, return to initial profile case "$prof_save" in performance) prof_seq="balanced power-saver performance" ;; balanced) prof_seq="power-saver performance balanced" ;; power-saver) prof_seq="performance balanced power-saver" ;; esac [ "$do_disabled_defaults" = "1" ] && check_disabled_defaults report_result "$_testcnt" "$_failcnt" print_report # --- Exit exit $_failcnt TLP-1.10.1/unit-tests/test-func000077500000000000000000000222111517565574500162300ustar00rootroot00000000000000#!/bin/sh # func-test - Unit Test Helper Functions # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # shellcheck disable=SC2034 # ---------------------------------------------------------------------------- # Constants # ansi codes readonly ANSI_RED="\033[31m" readonly ANSI_GREEN="\033[32m" readonly ANSI_BLACK="\033[m" # power supplies readonly PS_AC=0 readonly PS_BAT=1 readonly PS_UNKNOWN=128 # power profiles readonly PP_PRF=0 readonly PP_BAL=1 readonly PP_SAV=2 readonly PP_SUS=3 readonly PP_USER="0 1 2" # manual/persistent mode exceptional cases readonly PP_NONE=255 # statefile readonly LASTPWR='/run/tlp/last_pwr' readonly MANUALMODE='/run/tlp/manual_mode' # workaround: mitigate void threshold writes export VWRITE_SLEEP=2 # ---------------------------------------------------------------------------- # Functions # --- Checks is_uint () { # check for unsigned integer -- $1: string printf "%s" "$1" | grep -E -q "^[0-9]+$" 2> /dev/null } toupper () { # print string in uppercase -- $1: string printf "%s" "$1" | tr "[:lower:]" "[:upper:]" } wordinlist () { # test if word in list # $1: word, $2: whitespace-separated list of words local word if [ -n "${1-}" ]; then for word in ${2-}; do [ "${word}" != "${1}" ] || return 0 # exact match done fi return 1 # no match } get_listitem () { # print the active list item, which is the one enclosed in [] # $1: list printf "%s" "$(echo "$1" | sed -r 's/.*\[([A-Za-z0-9_]+)\].*/\1/')" } test_root () { # test root privilege -- rc: 0=root, 1=not root [ "$(id -u)" = "0" ] } check_tlp () { # test if tlp installed if [ ! -f /usr/sbin/tlp ]; then printf_msg "Error: %s not installed." "$TLP" 1>&2 exit 254 fi } on_ac () { # Detect AC power # rc: 0=AC/1=BAT # Note: compared to get_sys_power_supply() this is primitive. but it will do. upower -i /org/freedesktop/UPower/devices/line_power_AC 2> /dev/null | grep -qE 'online:\s+yes' } bat_present () { # Check for battery # $1: battery name # rc: 0=present/1=absent [ "$(read_sysf "/sys/class/power_supply/$1/present")" = "1" ] } pp2str () { # Convert profile code to string # $1: profile: PP_PRF=0/PP_BAL=1/PP_SAV=2 case "$1" in "$PP_PRF") printf "performance" ;; "$PP_BAL") printf "balanced" ;; "$PP_SAV") printf "power-saver" ;; *) printf "unknown" ;; esac } id2pp () { # convert power profile id to code # $1: id: PRF/AC/BAL/BAT/SAV case "$(toupper "$1")" in PRF|AC) printf "%s" "$PP_PRF" ;; BAL|BAT) printf "%s" "$PP_BAL" ;; SAV) printf "%s" "$PP_SAV" ;; *) printf "%s" "$PP_NONE" ;; esac } # --- sysfs read_sysf () { # read and print contents of a sysfile # return 1 and print default if read fails # $1: sysfile # $2: default # rc: 0=ok/1=error if cat "$1" 2> /dev/null; then return 0 else printf "%s" "$2" return 1 fi } write_sysf () { # write string to a sysfile # $1: string # $2: sysfile # rc: 0=ok/1=error { printf '%s\n' "$1" > "$2"; } 2> /dev/null } compare_sysf () { # Compare a string to the contents of a sysfile # expression # $1: string # $2: file local cmp_str="$1" local sys_str if [ -f "$2" ]; then sys_str="$(read_sysf "$2")" if [ "$sys_str" != "$cmp_str" ]; then printf_msg "\n*** Deviation at %s: %s (act) != %s (exp)\n" "$2" "$sys_str" "$cmp_str" return 1 fi else # file is missing if [ -n "$cmp_str" ]; then printf_msg "\n*** Deviation for %s: sysfile does not exist.\n" "$2" return 2 fi fi return 0 } compare_sysf_list () { # Compare a string to the contents of a sysfile # expression # $1: string # $2: file local cmp_str="$1" local sys_str if [ -f "$2" ]; then sys_str="$(get_listitem "$(read_sysf "$2")")" if [ "$sys_str" != "$cmp_str" ]; then printf_msg "\n*** Deviation at %s: %s (act) != %s (exp)\n" "$2" "$sys_str" "$cmp_str" return 1 fi else # file is missing if [ -n "$cmp_str" ]; then printf_msg "\n*** Deviation for %s: sysfile does not exist.\n" "$2" return 2 fi fi return 0 } glob_compare_sysf () { # Compare a string to the contents of sysfiles selected by a glob # expression # $1: string # $2..$n: file, ... local cmp_str="$1" local file_pat="$*" file_pat="${file_pat#* }" local sys_str local cnt=0 while shift && [ $# -gt 0 ]; do if [ -f "$1" ] && sys_str=$(read_sysf "$1"); then cnt=$((cnt + 1)) if [ "$sys_str" != "$cmp_str" ]; then printf_msg "\n*** Deviation at %s: %s (act) != %s (exp)\n" "$1" "$sys_str" "$cmp_str" return 1 fi fi done if [ "$cnt" -eq 0 ]; then printf_msg "\n*** Deviation for %s: no matching sysfile(s) exist(s).\n" "$file_pat" return 2 fi return 0 } read_saved_profile () { # read initial saved profile and power source # retval: $_prof, $_ps if [ ! -f "$LASTPWR" ]; then # last_pwr non-existent sudo tlp start > /dev/null 2>&1 fi read -r _prof _ps < "$LASTPWR" if [ -z "$_prof" ] || [ "$_prof" = "$PP_NONE" ] \ || [ -z "$_ps" ] || [ "$_ps" = "$PS_UNKNOWN" ]; then # last_pwr invalid sudo tlp start > /dev/null 2>&1 read -r _prof _ps < "$LASTPWR" fi } remove_saved_profile () { # delete saved profile and power source sudo rm -f "$LASTPWR" } # --- Run cache_root_cred () { # cache user credentials before actual testing sudo true } run_clitest () { # Run clitest script and record result line to file # $1: script filepath # $2: suffix # global param: $_report_file if [ -f "$_report_file" ]; then printf "%-50s --> " "${1##*/} $2" >> "$_report_file" clitest --color always "$1" | tee /dev/fd/2 | grep -E '(OK|FAIL):' >> "$_report_file" else clitest --color always "$1" 1>&2 fi printf "\n" 1>&2 } threshold_trap () { # trap: called from bc tests # Reset test machine to configured thresholds printf " Test cancelled. Restoring configured thresholds ...\n" 1>&2 if cat /sys/class/power_supply/BAT0/charge_control_end_threshold > /dev/null 2>&1; then sleep $VWRITE_SLEEP sudo tlp setcharge BAT0 > /dev/null 2>&1 fi if cat /sys/class/power_supply/BAT1/charge_control_end_threshold > /dev/null 2>&1; then sleep $VWRITE_SLEEP sudo tlp setcharge BAT1 > /dev/null 2>&1 fi exit 1 } set_threshold_trap () { # enable ^C hook # bc test scripts are nested, do not enable multiple hook instances if [ -z "$_nested_trap" ]; then trap threshold_trap INT export _nested_trap=1 fi } reset_threshold_trap () { # disable ^C hook trap - INT } # --- Messages, Reports printf_msg () { # print message to stderr and logfile # $1: format string # $2..$n: message string(s) local fmt="$1" shift # shellcheck disable=SC2154,SC2059 printf "$fmt" "$@" | tee -a "${_logfile:-/dev/null}" 1>&2 } start_report () { # Create report file # retval: $_report_file, $_nest_level if [ -z "$_report_file" ]; then # first call -> create report temp file if ! _report_file="$(mktemp --tmpdir "tlp-test-report.XXX")"; then printf "Error: failed to create report file.\n" 1>&2 fi export _report_file export _nest_level=0 else # increment level if is_uint "$_nest_level"; then export _nest_level="$((_nest_level + 1))" else export _nest_level=1 fi fi } report_test () { # Write test name to report if [ -f "$_report_file" ]; then printf "%-50s --> " "$1" >> "$_report_file" fi } report_line () { # Write text line to report # $1: text if [ -f "$_report_file" ]; then # note: use output string in format for proper ansi esc sequence interpolation # shellcheck disable=SC2059 printf "$1\n" >> "$_report_file" fi } report_result () { # Write test result to terminal and report # $1: # of tests # $2: # of errors if [ "$2" -eq 0 ]; then printf_msg "${ANSI_GREEN}OK:${ANSI_BLACK} %s of %s tests passed\n\n" "$1" "$1" report_line "${ANSI_GREEN}OK:${ANSI_BLACK} $1 of $1 tests passed" else printf_msg "${ANSI_RED}FAIL:${ANSI_BLACK} %s of %s tests failed\n\n" "$2" "$1" report_line "${ANSI_RED}FAIL:${ANSI_BLACK} $2 of $1 tests failed" fi } print_report () { [ "$_nest_level" -gt 0 ] && return if [ -f "$_report_file" ]; then printf "+++ Test Summary ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n" cat "$_report_file" printf "\n" rm -f "$_report_file" else printf "Error: missing report file ''%s''.\n" "$_report_file" 1>&2 fi } TLP-1.10.1/unit-tests/test-gpufreq.sh000077500000000000000000000466651517565574500174020ustar00rootroot00000000000000#!/bin/sh # Test GPU related features # # Tested parameters: # * INTEL_GPU_MIN_FREQ_ON_AC/BAT # * INTEL_GPU_MAX_FREQ_ON_AC/BAT # * INTEL_GPU_BOOST_FREQ_ON_AC/BAT # * RADEON_DPM_PERF_LEVEL_ON_AC/BAT # * AMDGPU_ABM_LEVEL_ON_AC/BAT # # Supported GPU drivers: # * i915 # * amdgpu # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Constants readonly GPUGLOB='/sys/class/drm/card[0-9]' # --- Tests check_intel_gpu_power_profile () { # apply Intel gpu power profile # global param: $_gpu_base, $_gpu_driver, $_testcnt, $_failcnt, $_prof_save, $_prof_seq # retval: $_testcnt++, $_failcnt++ local pp pp_save pp_seq pp_sysf local prof gtfd local rc=0 local errcnt=0 printf_msg "check_intel_gpu_power_profile (%s) {{{\n" "$_gpu_driver" if [ "$_gpu_driver" = "xe" ]; then for gtfd in "${_gpu_base}"/device/tile*/gt*/freq*; do [ -d "$gtfd" ] || break pp_sysf="${gtfd}/power_profile" if [ ! -f "$pp_sysf" ]; then printf_msg " Error: sysfile %s not present.\n" "$pp_sysf" _testcnt=$((_testcnt + 1)) _failcnt=$((_failcnt + 1)) printf_msg "}}} errcnt=%s\n\n" "1" return 1 fi done # save initial abm level pp_save="$(get_listitem "$(read_sysf "$pp_sysf")")" printf_msg " initial(%s): %s\n" "$_prof_save" "$pp_save" for prof in $_prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # iterate abm levels supported by the driver, return to initial level case "$pp_save" in base) pp_seq="power_saving base" ;; power_saving) pp_seq="base power_saving" ;; esac for pp in $pp_seq; do case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_POWER_PROFILE_ON_AC="$pp" INTEL_GPU_POWER_PROFILE_ON_BAT="$pp_save" INTEL_GPU_POWER_PROFILE_ON_SAV="$pp_save" \ INTEL_GPU_MIN_FREQ_ON_AC="" INTEL_GPU_MIN_FREQ_ON_BAT="" INTEL_GPU_MIN_FREQ_ON_SAV="" \ INTEL_GPU_MAX_FREQ_ON_AC="" INTEL_GPU_MAX_FREQ_ON_BAT="" INTEL_GPU_MAX_FREQ_ON_SAV="" \ INTEL_GPU_BOOST_FREQ_ON_AC="$boost" INTEL_GPU_BOOST_FREQ_ON_BAT="$boost_save" INTEL_GPU_MIN_FREQ_ON_SAV="" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_POWER_PROFILE_ON_AC="$pp_save" INTEL_GPU_POWER_PROFILE_ON_BAT="$pp" INTEL_GPU_POWER_PROFILE_ON_SAV="$pp_save" \ INTEL_GPU_MIN_FREQ_ON_AC="" INTEL_GPU_MIN_FREQ_ON_BAT="" INTEL_GPU_MIN_FREQ_ON_SAV="" \ INTEL_GPU_MAX_FREQ_ON_AC="" INTEL_GPU_MAX_FREQ_ON_BAT="" INTEL_GPU_MAX_FREQ_ON_SAV="" \ INTEL_GPU_BOOST_FREQ_ON_AC="$boost" INTEL_GPU_BOOST_FREQ_ON_BAT="$boost_save" INTEL_GPU_MIN_FREQ_ON_SAV="" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_POWER_PROFILE_ON_AC="$pp_save" INTEL_GPU_POWER_PROFILE_ON_BAT="$pp_save" INTEL_GPU_POWER_PROFILE_ON_SAV="$pp" \ INTEL_GPU_MIN_FREQ_ON_AC="" INTEL_GPU_MIN_FREQ_ON_BAT="" INTEL_GPU_MIN_FREQ_ON_SAV="" \ INTEL_GPU_MAX_FREQ_ON_AC="" INTEL_GPU_MAX_FREQ_ON_BAT="" INTEL_GPU_MAX_FREQ_ON_SAV="" \ INTEL_GPU_BOOST_FREQ_ON_AC="$boost" INTEL_GPU_BOOST_FREQ_ON_BAT="$boost_save" INTEL_GPU_MIN_FREQ_ON_SAV="" \ > /dev/null 2>&1 ;; esac # expect change compare_sysf_list "$pp" "$pp_sysf" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$pp" else printf_msg " %s=err(%s)" "$pp" "$rc" errcnt=$((errcnt + 1)) fi done # abm printf_msg "\n" done # prof else printf_msg "*** unsupported gpu\n" fi # print resulting abm level printf_msg " result(%s): %s\n" "$prof" "$(get_listitem "$(read_sysf "$pp_sysf")")" printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_intel_gpu_freq () { # apply Intel gpu min/max/boost frequencies # global param: $_gpu_base, $_gpu_driver, $_testcnt, $_failcnt, $_prof_save, $_prof_seq # retval: $_testcnt++, $_failcnt++ local min_sysf="" max_sysf="" boost_sysf="" local min min_save max max_save boost boost_save local prof gtfd local rc=0 local errcnt=0 printf_msg "check_intel_gpu_freq (%s) {{{\n" "$_gpu_driver" case "$_gpu_driver" in i915) min_sysf="${_gpu_base}/gt_min_freq_mhz" max_sysf="${_gpu_base}/gt_max_freq_mhz" boost_sysf="${_gpu_base}/gt_boost_freq_mhz" ;; xe) for gtfd in "${_gpu_base}"/device/tile*/gt*/freq*; do [ -d "$gtfd" ] || break min_sysf="${gtfd}/min_freq" max_sysf="${gtfd}/max_freq" boost_sysf="" break done ;; esac if [ -f "$min_sysf" ]; then # save initial frequencies and calculate target frequencies min_save="$(read_sysf "$min_sysf")" max_save="$(read_sysf "$max_sysf")" if [ "$_gpu_driver" = "i915" ]; then min=$((min_save + 100)) else min=$((min_save + 100)) fi max=$((max_save - 200)) if [ "$_gpu_driver" = "i915" ]; then boost_save="$(read_sysf "$boost_sysf")" boost=$((boost_save - 100)) printf_msg " initial(%s): min/%s max/%s boost/%s\n" "$_prof_save" "$min_save" "$max_save" "$boost_save" else boost_save=0 boost=0 printf_msg " initial(%s): min/%s max/%s\n" "$_prof_save" "$min_save" "$max_save" fi for prof in $_prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:\n" "$prof" # apply target frequencies case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_MIN_FREQ_ON_AC="$min" INTEL_GPU_MIN_FREQ_ON_BAT="$min_save" INTEL_GPU_MIN_FREQ_ON_SAV="$min_save" \ INTEL_GPU_MAX_FREQ_ON_AC="$max" INTEL_GPU_MAX_FREQ_ON_BAT="$max_save" INTEL_GPU_MAX_FREQ_ON_SAV="$max_save" \ INTEL_GPU_BOOST_FREQ_ON_AC="$boost" INTEL_GPU_BOOST_FREQ_ON_BAT="$boost_save" INTEL_GPU_MIN_FREQ_ON_SAV="$boost_save" \ INTEL_GPU_POWER_PROFILE_ON_AC="" INTEL_GPU_POWER_PROFILE_ON_BAT="" INTEL_GPU_POWER_PROFILE_ON_SAV="" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_MIN_FREQ_ON_BAT="$min" INTEL_GPU_MIN_FREQ_ON_SAV="$min_save" INTEL_GPU_MIN_FREQ_ON_AC="$min_save" \ INTEL_GPU_MAX_FREQ_ON_BAT="$max" INTEL_GPU_MAX_FREQ_ON_SAV="$max_save" INTEL_GPU_MAX_FREQ_ON_AC="$max_save" \ INTEL_GPU_BOOST_FREQ_ON_BAT="$boost" INTEL_GPU_BOOST_FREQ_ON_SAV="$boost_save" INTEL_GPU_BOOST_FREQ_ON_AC="$boost_save" \ INTEL_GPU_POWER_PROFILE_ON_AC="" INTEL_GPU_POWER_PROFILE_ON_BAT="" INTEL_GPU_POWER_PROFILE_ON_SAV="" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_MIN_FREQ_ON_SAV="$min" INTEL_GPU_MIN_FREQ_ON_AC="$min_save" INTEL_GPU_MIN_FREQ_ON_AC="$min_save" \ INTEL_GPU_MAX_FREQ_ON_SAV="$max" INTEL_GPU_MAX_FREQ_ON_AC="$max_save" INTEL_GPU_MAX_FREQ_ON_AC="$max_save" \ INTEL_GPU_BOOST_FREQ_ON_SAV="$boost" INTEL_GPU_BOOST_FREQ_ON_AC="$boost_save" INTEL_GPU_BOOST_FREQ_ON_AC="$boost_save" \ INTEL_GPU_POWER_PROFILE_ON_AC="" INTEL_GPU_POWER_PROFILE_ON_BAT="" INTEL_GPU_POWER_PROFILE_ON_SAV="" \ > /dev/null 2>&1 ;; esac # expect target frequencies compare_sysf "$min" "$min_sysf" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " min/%s=ok" "$min" else printf_msg " min/%s=err(%s)" "$min" "$rc" errcnt=$((errcnt + 1)) fi compare_sysf "$max" "$max_sysf" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " max/%s=ok" "$max" else printf_msg " max/%s=err(%s)" "$max" "$rc" errcnt=$((errcnt + 1)) fi if [ "$_gpu_driver" = "i915" ]; then compare_sysf "$boost" "$boost_sysf" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " boost/%s=ok" "$boost" else printf_msg " boost/%s=err(%s)" "$boost" "$rc" errcnt=$((errcnt + 1)) fi fi printf_msg "\n" # revert to initial frequencies case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_MIN_FREQ_ON_AC="$min_save" INTEL_GPU_MIN_FREQ_ON_BAT="$min" INTEL_GPU_MIN_FREQ_ON_SAV="$min" \ INTEL_GPU_MAX_FREQ_ON_AC="$max_save" INTEL_GPU_MAX_FREQ_ON_BAT="$max" INTEL_GPU_MAX_FREQ_ON_SAV="$max" \ INTEL_GPU_BOOST_FREQ_ON_AC="$boost_save" INTEL_GPU_BOOST_FREQ_ON_BAT="$boost" INTEL_GPU_MIN_FREQ_ON_SAV="$boost" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_MIN_FREQ_ON_BAT="$min_save" INTEL_GPU_MIN_FREQ_ON_SAV="$min" INTEL_GPU_MIN_FREQ_ON_AC="$min" \ INTEL_GPU_MAX_FREQ_ON_BAT="$max_save" INTEL_GPU_MAX_FREQ_ON_SAV="$max" INTEL_GPU_MAX_FREQ_ON_AC="$max" \ INTEL_GPU_BOOST_FREQ_ON_BAT="$boost_save" INTEL_GPU_BOOST_FREQ_ON_SAV="$boost" INTEL_GPU_BOOST_FREQ_ON_AC="$boost" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ INTEL_GPU_MIN_FREQ_ON_SAV="$min_save" INTEL_GPU_MIN_FREQ_ON_AC="$min" INTEL_GPU_MIN_FREQ_ON_AC="$min" \ INTEL_GPU_MAX_FREQ_ON_SAV="$max_save" INTEL_GPU_MAX_FREQ_ON_AC="$max" INTEL_GPU_MAX_FREQ_ON_AC="$max" \ INTEL_GPU_BOOST_FREQ_ON_SAV="$boost_save" INTEL_GPU_BOOST_FREQ_ON_AC="$boost" INTEL_GPU_BOOST_FREQ_ON_AC="$boost" \ > /dev/null 2>&1 ;; esac # expect change: initial frequencies compare_sysf "$min_save" "$min_sysf" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " min/%s=ok" "$min_save" else printf_msg " min/%s=err(%s)" "$min_save" "$rc" errcnt=$((errcnt + 1)) fi compare_sysf "$max_save" "$max_sysf" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " max/%s=ok" "$max_save" else printf_msg " max/%s=err(%s)" "$max_save" "$rc" errcnt=$((errcnt + 1)) fi if [ "$_gpu_driver" = "i915" ]; then compare_sysf "$boost_save" "$boost_sysf" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " boost/%s=ok" "$boost_save" else printf_msg " boost/%s=err(%s)" "$boost_save" "$rc" errcnt=$((errcnt + 1)) fi fi printf_msg "\n" done # prof # print resulting frequencies printf_msg " result(%s): min/%s max/%s boost/%s\n" \ "$prof" \ "$(read_sysf "$min_sysf")" \ "$(read_sysf "$max_sysf")" \ "$(read_sysf "$boost_sysf")" else printf_msg "*** unsupported gpu\n" fi # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_amd_gpu_dpm_level () { # apply AMD gpu dpm level # global param: $_gpu_base, $_testcnt, $_failcnt, $_prof_save, $_prof_seq # retval: $_testcnt++, $_failcnt++ local dpm dpm_save dpm_seq local prof local rc=0 local errcnt=0 printf_msg "check_amd_gpu_dpm_level {{{\n" # save initial dpm level dpm_save="$(read_sysf "${_gpu_base}/device/power_dpm_force_performance_level")" printf_msg " initial(%s): %s\n" "$_prof_save" "$dpm_save" for prof in $_prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # iterate dpm levels supported by the driver, return to initial level case "$dpm_save" in auto) dpm_seq="low high auto" ;; low) dpm_seq="high auto low" ;; high) dpm_seq="auto low high" ;; esac for dpm in $dpm_seq; do # apply dpm level case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ RADEON_DPM_PERF_LEVEL_ON_AC="$dpm" RADEON_DPM_PERF_LEVEL_ON_BAT="$dpm_save" RADEON_DPM_PERF_LEVEL_ON_SAV="$dpm_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ RADEON_DPM_PERF_LEVEL_ON_BAT="$dpm" RADEON_DPM_PERF_LEVEL_ON_SAV="$dpm_save" RADEON_DPM_PERF_LEVEL_ON_AC="$dpm_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ RADEON_DPM_PERF_LEVEL_ON_SAV="$dpm" RADEON_DPM_PERF_LEVEL_ON_AC="$dpm_save" RADEON_DPM_PERF_LEVEL_ON_BAT="$dpm_save" \ > /dev/null 2>&1 ;; esac # expect change compare_sysf "$dpm" "${_gpu_base}/device/power_dpm_force_performance_level" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$dpm" else printf_msg " %s=err(%s)" "$dpm" "$rc" errcnt=$((errcnt + 1)) fi done # dpm printf_msg "\n" done # prof # print resulting dpm level printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "${_gpu_base}/device/power_dpm_force_performance_level")" printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_amd_gpu_abm_level () { # apply AMD gpu dpm level # global param: $_gpu_base, $_testcnt, $_failcnt, $_prof_save # retval: $_testcnt++, $_failcnt++ local abm abm_save abm_seq abm_sysf local prof local rc=0 local errcnt=0 printf_msg "check_amd_gpu_abm_level {{{\n" abm_sysf="${_gpu_base}/${_gpu_base##/*/}-eDP-1/amdgpu/panel_power_savings" if [ ! -f "$abm_sysf" ]; then printf_msg " Error: sysfile %s not present.\n" "$abm_sysf" _testcnt=$((_testcnt + 1)) _failcnt=$((_failcnt + 1)) printf_msg "}}} errcnt=%s\n\n" "1" return 1 fi # save initial abm level abm_save="$(read_sysf "$abm_sysf")" printf_msg " initial(%s): %s\n" "$_prof_save" "$abm_save" for prof in $_prof_seq; do # --- test profile; ensure different values from other profiles do not spill over printf_msg " %s:" "$prof" # iterate abm levels supported by the driver, return to initial level case "$abm_save" in 0) abm_seq="1 2 3 4 0" ;; 1) abm_seq="2 3 4 0 1" ;; 2) abm_seq="3 4 0 1 2" ;; 3) abm_seq="4 0 1 2 3" ;; esac for abm in $abm_seq; do case "$prof" in performance) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ AMDGPU_ABM_LEVEL_ON_AC="$abm" AMDGPU_ABM_LEVEL_ON_BAT="$abm_save" AMDGPU_ABM_LEVEL_ON_SAV="$abm_save" \ > /dev/null 2>&1 ;; balanced) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ AMDGPU_ABM_LEVEL_ON_BAT="$abm" AMDGPU_ABM_LEVEL_ON_SAV="$abm_save" AMDGPU_ABM_LEVEL_ON_AC="$abm_save" \ > /dev/null 2>&1 ;; power-saver) sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" \ AMDGPU_ABM_LEVEL_ON_SAV="$abm" AMDGPU_ABM_LEVEL_ON_AC="$abm_save" AMDGPU_ABM_LEVEL_ON_BAT="$abm_save" \ > /dev/null 2>&1 ;; esac # expect change compare_sysf "$abm" "$abm_sysf" rc=$? if [ "$rc" -eq 0 ]; then printf_msg " %s=ok" "$abm" else printf_msg " %s=err(%s)" "$abm" "$rc" errcnt=$((errcnt + 1)) fi done # abm printf_msg "\n" done # prof # print resulting abm level printf_msg " result(%s): %s\n" "$prof" "$(read_sysf "$abm_sysf")" printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } # --- MAIN # source library readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } # check prerequisites and initialize check_tlp cache_root_cred start_report # shellcheck disable=SC2034 _basename="${0##*/}" # shellcheck disable=SC2034 _logfile="$(date -Iseconds)_${_basename%.*}.log" _testcnt=0 _failcnt=0 report_test "$_basename" printf_msg "+++ %s\n\n" "$_basename" # save initial profile read_saved_profile # shellcheck disable=SC2154 _prof_save="$(pp2str "$_prof")" # iterate supported profiles, return to initial profile case "$_prof_save" in performance) _prof_seq="balanced power-saver performance" ;; balanced) _prof_seq="power-saver performance balanced" ;; power-saver) _prof_seq="performance balanced power-saver" ;; esac # --- TEST: iterate GPUs for _gpu_base in $GPUGLOB; do [ -d "$_gpu_base" ] || break # determine gpu driver _gpu_driver="$(readlink "${_gpu_base}/device/driver")" _gpu_driver="${_gpu_driver##*/}" printf_msg "gpu: %s, driver: %s\n\n" "$_gpu_base" "$_gpu_driver" # checks case "$_gpu_driver" in i915) check_intel_gpu_freq ;; xe) check_intel_gpu_power_profile check_intel_gpu_freq ;; amdgpu) check_amd_gpu_dpm_level check_amd_gpu_abm_level ;; *) printf "GPU driver %s has no test coverage. Skipped.\n" "$_gpu_driver" ;; esac done # gpud report_result "$_testcnt" "$_failcnt" print_report # --- Exit exit $_failcnt TLP-1.10.1/unit-tests/test-pm_all.sh000077500000000000000000000007501517565574500171560ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred start_report "$spath/test-profiles.sh" "$spath/test-cpufreq.sh" "$spath/test-disabled-defaults.sh" "$spath/test-gpufreq.sh" "$spath/test-rf-switch.sh" "$spath/test-service-warnings.sh" # shellcheck disable=SC2154 TLP_TEST_REPORT="$_report_file" "$spath/test-tlpctl.py" print_report TLP-1.10.1/unit-tests/test-profiles.sh000077500000000000000000000565351517565574500175510ustar00rootroot00000000000000#!/bin/sh # Test: # - select power profiles: performance, balance, power-saver, ac (manual mode), bat (manual mode) # - run persistent mode # # Tested parameters: # - TLP_AUTO_SWITCH # - TLP_PROFILE_AC # - TLP_PROFILE_BAT # - TLP_PROFILE_DEFAULT # - TLP_PERSISTENT_DEFAULT # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Constants readonly UDEVADM="udevadm" readonly TEMPCONF='/etc/tlp.d/99-unit-test.conf' # --- Tests check_profile_select () { # select performance/balanced/power-saver profiles as well as ac/bat manual mode # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local prof_seq local prof prof_save prof_xpect local ps_save local mm_save mm_xpect local rc=0 local errcnt=0 printf_msg "check_profile_select {{{\n" # save initial profile, power source and manual mode read_saved_profile # shellcheck disable=SC2154 prof_save="$_prof" # shellcheck disable=SC2154 ps_save="$_ps" mm_save="$(read_sysf "$MANUALMODE")" # iterate supported profiles, return to initial profile case "$prof_save" in "$PP_PRF") prof_seq="balanced power-saver ac bat start auto suspend resume0 resume usb usb0 performance" ;; "$PP_BAL") prof_seq="power-saver ac bat start auto performance suspend resume0 resume usb usb0 balanced" ;; "$PP_SAV") prof_seq="ac bat start auto performance balanced suspend resume0 resume usb usb0 power-saver" ;; esac printf_msg " initial: last_pwr/%s manual_mode/%s\n" "$prof_save $ps_save" "$mm_save" for prof in $prof_seq; do printf_msg " %-12s:" "$prof" case "$prof" in performance) prof_xpect="$PP_PRF $ps_save" mm_xpect="" prof_save="$PP_PRF" ;; ac) prof_xpect="$PP_PRF $ps_save" mm_xpect="$PP_PRF" prof_save="$PP_PRF" ;; balanced) prof_xpect="$PP_BAL $ps_save" mm_xpect="" prof_save="$PP_BAL" ;; bat) prof_xpect="$PP_BAL $ps_save" mm_xpect="$PP_BAL" prof_save="$PP_BAL" ;; power-saver) prof_xpect="$PP_SAV $ps_save" mm_xpect="" prof_save="$PP_SAV" remove_saved_profile ;; start|auto) if on_ac; then prof_xpect="$PP_PRF $ps_save" prof_save="$PP_PRF" else prof_xpect="$PP_BAL $ps_save" prof_save="$PP_BAL" fi mm_xpect="" remove_saved_profile ;; usb) prof_xpect="$ps_save $ps_save" mm_xpect="" ;; usb0) prof="usb" prof_xpect="" mm_xpect="" remove_saved_profile ;; suspend) prof_xpect="$prof_save $ps_save" mm_xpect="" ;; resume) prof_xpect="$ps_save $ps_save" mm_xpect="" ;; resume0) prof="resume" prof_xpect="$ps_save $ps_save" mm_xpect="" remove_saved_profile ;; esac sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 TLP_PROFILE_DEFAULT="" > /dev/null 2>&1 # check expect results compare_sysf "$prof_xpect" "$LASTPWR"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " last_pwr/%s=ok" "$prof_xpect" else printf_msg " last_pwr/%s=err(%s)" "$prof_xpect" "$rc" errcnt=$((errcnt + 1)) fi compare_sysf "$mm_xpect" "$MANUALMODE"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " manual_mode/%s=ok" "$mm_xpect" else printf_msg " manual_mode/%s=err(%s)" "$mm_xpect" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof read_saved_profile printf_msg " result: last_pwr/%s manual_mode/%s\n" "$_prof $_ps" "$(read_sysf "$MANUALMODE")" # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_default_mode () { # run default mode PRF/BAL/SAV/AC/BAT # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local prof_seq local prof prof_save prof_xpect local ps_save local mm_save mm_xpect local rc=0 local errcnt=0 printf_msg "check_default_mode {{{\n" # save initial profile, power source and manual mode read_saved_profile; prof_save="$_prof"; ps_save="$_ps" mm_save="$(read_sysf "$MANUALMODE")" # iterate supported profiles, return to initial profile case "$prof_save" in "$PP_PRF") prof_seq="BAL SAV AC BAT none PRF" ;; "$PP_BAL") prof_seq="SAV AC BAT PRF none BAL" ;; "$PP_SAV") prof_seq="AC BAT PRF BAL none SAV" ;; esac printf_msg " initial: last_pwr/%s manual_mode/%s\n" "$prof_save $ps_save" "$mm_save" for prof in $prof_seq; do printf_msg " TLP_AUTO_SWITCH=0 TLP_PROFILE_DEFAULT=%-5s" "${prof}:" case "$prof" in PRF) prof_xpect="$PP_PRF $ps_save" ;; AC) prof_xpect="$PP_PRF $ps_save" ;; BAL) prof_xpect="$PP_BAL $ps_save" ;; BAT) prof_xpect="$PP_BAL $ps_save" ;; SAV) prof_xpect="$PP_SAV $ps_save" ;; none) if on_ac; then prof_xpect="$PP_PRF $ps_save" else prof_xpect="$PP_BAL $ps_save" fi ;; esac if [ "$prof" = "none" ]; then sudo tlp start -- TLP_AUTO_SWITCH=0 TLP_PROFILE_DEFAULT="" TLP_PERSISTENT_DEFAULT=0 > /dev/null 2>&1 else sudo tlp start -- TLP_AUTO_SWITCH=0 TLP_PROFILE_DEFAULT="$prof" TLP_PERSISTENT_DEFAULT=0 > /dev/null 2>&1 fi # expect changing profiles compare_sysf "$prof_xpect" "$LASTPWR"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " last_pwr/%s=ok" "$prof_xpect" else printf_msg " last_pwr/%s=err(%s)" "$prof_xpect" "$rc" errcnt=$((errcnt + 1)) fi # do not expect manual mode mm_xpect="" compare_sysf "$mm_xpect" "$MANUALMODE"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " manual_mode/%s=ok" "$mm_xpect" else printf_msg " manual_mode/%s=err(%s)" "$mm_xpect" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof read_saved_profile printf_msg " result: last_pwr/%s manual_mode/%s\n" "$_prof $_ps" "$(read_sysf "$MANUALMODE")" # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_persistent_mode () { # run persistent mode PRF/BAL/SAV/AC/BAT # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local prof_seq local prof prof_save prof_xpect local ps_save local mm_save mm_xpect local rc=0 local errcnt=0 printf_msg "check_persistent_mode {{{\n" # save initial profile, power source and manual mode read_saved_profile; prof_save="$_prof"; ps_save="$_ps" mm_save="$(read_sysf "$MANUALMODE")" # iterate supported profiles, return to initial profile case "$prof_save" in "$PP_PRF") prof_seq="BAL SAV AC BAT none PRF" ;; "$PP_BAL") prof_seq="SAV AC BAT PRF none BAL" ;; "$PP_SAV") prof_seq="AC BAT PRF BAL none SAV" ;; esac printf_msg " initial: last_pwr/%s manual_mode/%s\n" "$prof_save $ps_save" "$mm_save" for prof in $prof_seq; do printf_msg " TLP_AUTO_SWITCH=2 TLP_PERSISTENT_DEFAULT=1 TLP_PROFILE_DEFAULT=%-5s" "${prof}:" case "$prof" in PRF) prof_xpect="$PP_PRF $ps_save" ;; AC) prof_xpect="$PP_PRF $ps_save" ;; BAL) prof_xpect="$PP_BAL $ps_save" ;; BAT) prof_xpect="$PP_BAL $ps_save" ;; SAV) prof_xpect="$PP_SAV $ps_save" ;; none) if on_ac; then prof_xpect="$PP_PRF $ps_save" else prof_xpect="$PP_BAL $ps_save" fi ;; esac sudo tlp auto -- TLP_AUTO_SWITCH=2 TLP_PERSISTENT_DEFAULT=1 TLP_PROFILE_DEFAULT="$prof" > /dev/null 2>&1 # expect changing profiles compare_sysf "$prof_xpect" "$LASTPWR"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " last_pwr/%s=ok" "$prof_xpect" else printf_msg " last_pwr/%s=err(%s)" "$prof_xpect" "$rc" errcnt=$((errcnt + 1)) fi # do not expect manual mode mm_xpect="" compare_sysf "$mm_xpect" "$MANUALMODE"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " manual_mode/%s=ok" "$mm_xpect" else printf_msg " manual_mode/%s=err(%s)" "$mm_xpect" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof read_saved_profile printf_msg " result: last_pwr/%s manual_mode/%s\n" "$_prof $_ps" "$(read_sysf "$MANUALMODE")" # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_power_supply () { # run 'tlp start' with simulated power supply AC/battery/unknown # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local ps ps_seq local prof_save prof_xpect local prof_ac prof_bat prof_def prof_seq local ps_save local mm_save mm_xpect local rc=0 local errcnt=0 printf_msg "check_power_supply {{{\n" # save initial profile, power source and manual mode read_saved_profile; prof_save="$_prof"; ps_save="$_ps" mm_save="$(read_sysf "$MANUALMODE")" # iterate power supplies, return to initial power supply case "$prof_save" in "$PP_PRF") prof_seq="BAL SAV PRF" ;; "$PP_BAL") prof_seq="SAV PRF BAL" ;; "$PP_SAV") prof_seq="PRF BAL SAV" ;; esac printf_msg " initial: last_pwr/%s manual_mode/%s\n" "$prof_save $ps_save" "$mm_save" for prof_ac in $prof_seq; do case "$prof_ac" in PRF) prof_bat=BAL prof_def=SAV ;; BAL) prof_bat=SAV prof_def=PRF ;; SAV) prof_bat=PRF prof_def=BAL ;; esac ps_seq="$PS_BAT $PS_UNKNOWN $PS_AC" for ps in $ps_seq; do printf_msg " X_SIMULATE_PS=%-3s TLP_PROFILE_AC=%s TLP_PROFILE_BAT=%s TLP_PROFILE_DEFAULT=%s:" \ "$ps" "$prof_ac" "$prof_bat" "$prof_def" case "$ps" in "$PS_AC") prof_xpect="$(id2pp "$prof_ac") $ps" ;; "$PS_BAT") prof_xpect="$(id2pp "$prof_bat") $ps" ;; "$PS_UNKNOWN") if [ "$prof_def" = "none" ]; then prof_xpect="$(id2pp "$prof_bat") $ps" else prof_xpect="$(id2pp "$prof_def") $ps" fi ;; esac sudo tlp start -- TLP_AUTO_SWITCH=2 TLP_PROFILE_AC="$prof_ac" TLP_PROFILE_BAT="$prof_bat" \ TLP_PROFILE_DEFAULT=$prof_def TLP_PERSISTENT_DEFAULT=0 X_SIMULATE_PS="$ps" > /dev/null 2>&1 # expect changing profiles compare_sysf "$prof_xpect" "$LASTPWR"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " last_pwr/%s=ok" "$prof_xpect $ps_save" else printf_msg " last_pwr/%s=err(%s)" "$prof_xpect $ps_save" "$rc" errcnt=$((errcnt + 1)) fi # do not expect manual mode mm_xpect="" compare_sysf "$mm_xpect" "$MANUALMODE"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " manual_mode/%s=ok" "$mm_xpect" else printf_msg " manual_mode/%s=err(%s)" "$mm_xpect" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # ps printf_msg "\n" done # prof read_saved_profile printf_msg " result: last_pwr/%s manual_mode/%s\n" "$_prof $_ps" "$(read_sysf "$MANUALMODE")" # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_auto_switch () { # test TLP_AUTO_SWITCH=0/1/2 # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local prof_seq local prof prof_save prof_xpect local ps_now ps_next local as local mm_xpect mm_save local rc=0 local errcnt=0 printf_msg "check_auto_switch {{{\n" # read initial profile read_saved_profile; prof_save="$_prof" # iterate supported profiles, return to initial profile case "$_prof" in "$PP_PRF") prof_seq="balanced power-saver performance" ;; "$PP_BAL") prof_seq="power-saver performance balanced" ;; "$PP_SAV") prof_seq="performance balanced power-saver" ;; esac for as in 0 1 2; do # iterate auto switch modes read_saved_profile printf_msg " TLP_AUTO_SWITCH=%s TLP_PROFILE_AC=PRF TLP_PROFILE_BAT=BAL: last_pwr/%s manual_mode/%s\n" "$as" "$_prof $_ps" "$mm_save" case "$as" in 0|1) # auto switch: disabled|enabled # interate power sources: AC, battery for ps_now in 0 1; do for mode in auto resume; do printf_msg " %-6s X_SIMULATE_PS=%s:" "$mode" "$ps_now" sudo tlp "$mode" -- TLP_AUTO_SWITCH="$as" \ TLP_PROFILE_AC=PRF TLP_PROFILE_BAT=BAL TLP_PROFILE_DEFAULT="" TLP_PERSISTENT_DEFAULT=0 \ X_SIMULATE_PS="$ps_now" > /dev/null 2>&1 case "$as" in 0) # auto switch disabled, do not expect profile change prof_xpect="$_prof $ps_now" ;; 1) # auto swich enable, expect profile according to power source prof_xpect="$ps_now $ps_now" ;; esac compare_sysf "$prof_xpect" "$LASTPWR"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " last_pwr/%s=ok" "$prof_xpect" else printf_msg " last_pwr/%s=err(%s)" "$prof_xpect" "$rc" errcnt=$((errcnt + 1)) fi # do not expect manual mode mm_xpect="" compare_sysf "$mm_xpect" "$MANUALMODE"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " manual_mode/%s=ok" "$mm_xpect" else printf_msg " manual_mode/%s=err(%s)" "$mm_xpect" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # auto/resume done # ps_now ;; # disabled|enabled 2) # auto switch: smart for mode in auto resume; do for ps_now in 0 1; do # calc opposite power source ps_next="$((! ps_now))" for prof in $prof_seq; do # prepare simulated active profile and power source printf_msg " %-6s (prof=%-11s ps_now=%s) --> ps_next=%s:" "$mode" "$prof" "$ps_now" "$ps_next" sudo tlp "$prof" -- TLP_AUTO_SWITCH=2 \ TLP_PROFILE_AC=PRF TLP_PROFILE_BAT=BAL TLP_PERSISTENT_DEFAULT=0 \ X_SIMULATE_PS="$ps_now" > /dev/null 2>&1 # determine expected profile case "$ps_now" in 0) # simulated power source: AC case "$prof" in performance) prof_xpect="$PP_BAL $ps_next" ;; balanced) prof_xpect="$PP_BAL $ps_next" ;; power-saver) prof_xpect="$PP_SAV $ps_next" ;; esac ;; 1) # simulated power source: battery case "$prof" in performance) prof_xpect="$PP_PRF $ps_next" ;; balanced) prof_xpect="$PP_PRF $ps_next" ;; power-saver) prof_xpect="$PP_SAV $ps_next" ;; esac ;; esac # check auto/resume on opposite power source sudo tlp "$mode" -- TLP_AUTO_SWITCH="$as" \ TLP_PROFILE_AC=PRF TLP_PROFILE_BAT=BAL TLP_PERSISTENT_DEFAULT=0 \ X_SIMULATE_PS="$ps_next" > /dev/null 2>&1 # check against expectations compare_sysf "$prof_xpect" "$LASTPWR"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " last_pwr/%s=ok" "$prof_xpect" else printf_msg " last_pwr/%s=err(%s)" "$prof_xpect" "$rc" errcnt=$((errcnt + 1)) fi mm_xpect="" compare_sysf "$mm_xpect" "$MANUALMODE"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " manual_mode/%s=ok" "$mm_xpect" else printf_msg " manual_mode/%s=err(%s)" "$mm_xpect" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" done # prof printf_msg "\n" done # ps_now done # auto/resume ;; # smart esac # as # restore initial profile sudo tlp "$(pp2str "$prof_save")" > /dev/null 2>&1 read_saved_profile printf_msg " result: last_pwr/%s manual_mode/%s\n\n" "$_prof $_ps" "$(read_sysf "$MANUALMODE")" done # as # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } check_ps_udev_no_switch () { # cover special case of USB-C disk unplug: # 1. configure TLP_AUTO_SWITCH=1 # 2. apply power-saver profile # 3. simulate udev power_supply change event # 4. check if logic works properly and power-saver profile doesn't change # Refer to: # - https://thinkpad-forum.de/threads/tlp-1-9-alpha-testergebnisse.244864/page-3#post-2451309 # - 7eec753, 61ac2ea # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local prof_save prof_xpect local ps ppi local rc=0 local errcnt=0 printf_msg "check_ps_udev_no_switch {{{\n" # save initial profile read_saved_profile; prof_save="$_prof"; ps="$_ps" printf_msg " initial: last_pwr/%s\n" "$prof_save $ps" # 1. create temp config echo "TLP_AUTO_SWITCH=1" | sudo tee $TEMPCONF > /dev/null # 2. apply power-saver profile printf_msg " Apply power-saver:" sudo tlp power-saver -- TLP_PERSISTENT_DEFAULT=0 > /dev/null 2>&1 # expect power-saver prof_xpect="2" compare_sysf "$prof_xpect $ps" "$LASTPWR"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " last_pwr/%s=ok" "$prof_xpect $ps" else printf_msg " last_pwr/%s=err(%s)" "$prof_xpect $ps" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" # 3. simulate udev power_supply change event printf_msg " Simulate PS udev event w/TLP_AUTO_SWITCH=1:" sudo ${UDEVADM} trigger --type=all --action=change --subsystem-match=power_supply --sysname-match=AC # wait for tlp processing sleep 2 # 4. check if logic works properly and power-saver profile didnt't change compare_sysf "$prof_xpect $ps" "$LASTPWR"; rc=$? if [ "$rc" -eq 0 ]; then printf_msg " last_pwr/%s=ok" "$prof_xpect $ps" else printf_msg " last_pwr/%s=err(%s)" "$prof_xpect $ps" "$rc" errcnt=$((errcnt + 1)) fi printf_msg "\n" # restore initial profile case "$prof_save" in "$PP_PRF") ppi="performance" ;; "$PP_BAL") ppi="balanced" ;; "$PP_SAV") ppi="power-saver" ;; esac sudo tlp ${ppi} -- TLP_PERSISTENT_DEFAULT=0 > /dev/null 2>&1 read_saved_profile printf_msg " result: last_pwr/%s\n" "$_prof $_ps" # remove temp config sudo rm -f $TEMPCONF # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } # --- MAIN # source library readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } # read args if [ $# -eq 0 ]; then do_profile="1" do_persist="1" do_power="1" do_psudev="1" do_default="1" do_switch="1" else while [ $# -gt 0 ]; do case "$1" in profile) do_profile="1" ;; default) do_default="1" ;; persist) do_persist="1" ;; power) do_power="1" ;; switch) do_switch="1" ;; psudev) do_psudev="1" ;; esac shift # next argument done # while arguments fi # check prerequisites and initialize check_tlp cache_root_cred start_report _basename="${0##*/}" # shellcheck disable=SC2034 _logfile="$(date -Iseconds)_${_basename%.*}.log" _testcnt=0 _failcnt=0 report_test "$_basename" printf_msg "+++ %s\n\n" "$_basename" # --- TEST [ "$do_profile" = "1" ] && check_profile_select [ "$do_default" = "1" ] && check_default_mode [ "$do_persist" = "1" ] && check_persistent_mode [ "$do_power" = "1" ] && check_power_supply [ "$do_switch" = "1" ] && check_auto_switch [ "$do_psudev" = "1" ] && check_ps_udev_no_switch report_result "$_testcnt" "$_failcnt" print_report # --- Exit exit $_failcnt TLP-1.10.1/unit-tests/test-rf-switch.sh000077500000000000000000000074031517565574500176220ustar00rootroot00000000000000#!/bin/sh # Test (as user and as root): # - bluetooth [on|off] # - nfc [on|off] (dummy only) # - wifi [on|off] # - wwan [on|off] # # Tested parameters: # - none yet # # Copyright (c) 2026 Thomas Koch and others. # SPDX-License-Identifier: GPL-2.0-or-later # --- Constants # --- Functions read_rf_state () { # $1: radio type: bluetooth/wifi/wwan/nfc if wordinlist "$1" "bluetooth nfc wifi wwan"; then state="$($1 | sed -r 's/'"$1"'.+= (on|off|none).*/\1/')" if wordinlist "$state" "on off none"; then printf "%s" "$state" return 0 else printf "unknown" printf_msg " Error: unrecognizable %s state \"%s\".\n" "$1" "$state" return 1 fi else printf_msg " Error: unknown radio type '%s'.\n" "$1" exit 254 fi } check_radio () { # TEMPLATE # $1: radio command: wifi/bluetooth/wwan/nfc # global param: $_testcnt, $_failcnt # retval: $_testcnt++, $_failcnt++ local rf_cmd="$1" local errcnt=0 local rf_save rf_seq local sdo printf_msg "check_radio (%s) {{{\n" "$rf_cmd" if rf_save="$(read_rf_state "$rf_cmd")"; then case "$rf_save" in off) rf_seq="on off" ;; on) rf_seq="off on" ;; none) rf_seq="" if wordinlist "$rf_cmd" "bluetooth wifi"; then printf_msg " no device - REALLY?\n" errcnt=1 else printf_msg " no device.\n" fi ;; *) rf_seq="" errcnt=1 ;; esac if [ -n "$rf_seq" ]; then printf_msg " initial: %s\n" "$rf_save" for sdo in "" "sudo"; do for next_state in $rf_seq; do # shellcheck disable=SC2086 $sdo $rf_cmd "$next_state" 1> /dev/null printf_msg " %-4s %s %-3s -> " "$sdo" "$rf_cmd" "$next_state" new_state="$(read_rf_state "$rf_cmd")" if [ "$new_state" = "$next_state" ]; then printf_msg "%-3s (ok)\n" "$new_state" else printf_msg "Deviation: %-3s (act) != %-3s (exp)\n" "$new_state" "$next_state" errcnt=$((errcnt + 1)) fi done done printf_msg " result: %s\n" "$(read_rf_state "$rf_cmd")" fi fi # print summary printf_msg "}}} errcnt=%s\n\n" "$errcnt" _testcnt=$((_testcnt + 1)) [ "$errcnt" -gt 0 ] && _failcnt=$((_failcnt + 1)) return $errcnt } # --- MAIN # source library readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } # read args if [ $# -eq 0 ]; then do_wifi="1" do_bluetooth="1" do_wwan="1" do_nfc="1" else while [ $# -gt 0 ]; do case "$1" in wifi) do_wifi="1" ;; bluetooth) do_bluetooth="1" ;; wwan) do_wwan="1" ;; nfc) do_nfc="1" ;; esac shift # next argument done # while arguments fi # check prerequisites and initialize check_tlp cache_root_cred start_report _basename="${0##*/}" # shellcheck disable=SC2034 _logfile="$(date -Iseconds)_${_basename%.*}.log" _testcnt=0 _failcnt=0 report_test "$_basename" printf_msg "+++ %s\n\n" "$_basename" [ "$do_wifi" = "1" ] && check_radio wifi [ "$do_bluetooth" = "1" ] && check_radio bluetooth [ "$do_wwan" = "1" ] && check_radio wwan [ "$do_nfc" = "1" ] && check_radio nfc report_result "$_testcnt" "$_failcnt" print_report # --- Exit exit $_failcnt TLP-1.10.1/unit-tests/test-service-warnings.sh000077500000000000000000000006141517565574500211770ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred set_threshold_trap start_report # shellcheck disable=SC2034 _basename="${0##*/}" printf_msg "+++ %s\n\n" "$_basename" run_clitest "$spath/service-warnings" print_report reset_threshold_trap TLP-1.10.1/unit-tests/test-tlpctl.py000077500000000000000000000207271517565574500172400ustar00rootroot00000000000000#!/usr/bin/python3 # Test: # - tlpctl [set] performance|balance|power-saver, # - tlpctl get # - tlpctl # - tlpctl list # - tlpctl loglevel import os import re import socket import time from typing import List from testing import ( TestReport, make_regex_filter, run_executable, run_executable_rc, test_executable, ) # --- Constants TLPCTL = "tlpctl" SSH = "ssh" AVAILABLE_PROFILES = ["performance", "balanced", "power-saver"] WAIT_PROFILE_ACTIVATION = 1.5 # --- Helper functions def reordered_profile_sequence(new_last: str) -> List[str]: # Cyclic reorder of AVAILABLE_PROFILES sequence # Args: # new_last: dedicated last element # # Returns: # reordered sequence try: idx = AVAILABLE_PROFILES.index(new_last) return AVAILABLE_PROFILES[idx + 1 :] + AVAILABLE_PROFILES[: idx + 1] except ValueError: return [] # --- Filter functions def extract_loglevel(output: str) -> str: # Extract loglevel from 'tlpctl list' output # # Args: # output: output from 'tlpctl list' # # Returns: # loglevel "info" or "debug" match = re.search(r"tlp-pd LogLevel:\s*(\S+)", output) if match: return match.group(1) else: return "" # --- Test cases def test_set_direct(report: TestReport): # Cycle available profiles with 'tlpctl ', returning to the initial profile # Check results with 'tlpctl get' # # Args: none # Returns: none errcnt = 0 initial = run_executable(TLPCTL, ["get"]).rstrip("\n") sequence = reordered_profile_sequence(initial) print("Check tlpctl " + repr(sequence) + " {{{") for profile in sequence: print(f" tlpctl {profile}: ", end="") if not test_executable( executable_path=TLPCTL, args=[profile], expected_output=f"Switched to {profile} profile.\n", ): errcnt += 1 # Wait, tlp activates the profile asynchronously time.sleep(WAIT_PROFILE_ACTIVATION) print(" check result w/ tlpctl get: ", end="") if not test_executable( executable_path=TLPCTL, args=["get"], expected_output=f"{profile}\n", ): errcnt += 1 print("}}} " + f"errcnt={str(errcnt)}\n") report.count_test(errcnt) def test_set(report: TestReport): # Cycle available profiles with 'tlpctl ', returning to the initial profile # Check results with 'tlpctl' # # Args: none # Returns: none errcnt = 0 initial = run_executable(TLPCTL, ["get"]).rstrip("\n") sequence = reordered_profile_sequence(initial) print("Check tlpctl set " + repr(sequence) + " {{{") for profile in sequence: print(f" tlpctl set {profile}: ", end="") if not test_executable( executable_path=TLPCTL, args=["set", profile], expected_output="", ): errcnt += 1 # Wait, tlp activates the profile asynchronously time.sleep(WAIT_PROFILE_ACTIVATION) # Assemble expected output considering current profile exp_list = "" for item in AVAILABLE_PROFILES: if item == profile: exp_list += f"* {item}\n" else: exp_list += f" {item}\n" print(" check result w/ tlpctl: ", end="") if not test_executable( executable_path=TLPCTL, args=[], expected_output=exp_list, ): errcnt += 1 print("}}} " + f"errcnt={str(errcnt)}\n") report.count_test(errcnt) TLPCTL_OUTPUT_LIST = """Available power profiles (* = active): @P@ performance: CpuDriver : tlp PlatformDriver : tlp Degraded : no @B@ balanced: CpuDriver : tlp PlatformDriver : tlp @S@ power-saver: CpuDriver : tlp PlatformDriver : tlp Dynamic changes from charger and battery events: NA tlp-pd LogLevel: @L@ """ def test_list(report: TestReport): # Check output of 'tlpctl list' for active profile marker and loglevel # # Args: none # Returns: none ### global _testcnt ### lobal _failcnt errcnt = 0 initial = run_executable(TLPCTL, ["get"]).rstrip("\n") sequence = reordered_profile_sequence(initial) loglevel = run_executable(TLPCTL, ["list"]).rstrip("\n") loglevel = extract_loglevel(loglevel) print("Check tlpctl list " + repr(sequence) + " {{{") for profile in sequence: # Set profile print(f" tlpctl set {profile}: ", end="") if not test_executable( executable_path=TLPCTL, args=["set", profile], expected_output="", ): errcnt += 1 time.sleep(WAIT_PROFILE_ACTIVATION) # Synthesize expected output from the template exp_out = TLPCTL_OUTPUT_LIST if profile == "performance": exp_out = exp_out.replace("@P@", "*") exp_out = exp_out.replace("@B@", " ") exp_out = exp_out.replace("@S@", " ") elif profile == "balanced": exp_out = exp_out.replace("@P@", " ") exp_out = exp_out.replace("@B@", "*") exp_out = exp_out.replace("@S@", " ") else: exp_out = exp_out.replace("@P@", " ") exp_out = exp_out.replace("@B@", " ") exp_out = exp_out.replace("@S@", "*") exp_out = exp_out.replace("@L@", loglevel) battery_aware_filter = make_regex_filter( r"(battery events:) (yes|no)", r"\1 NA", flags=re.NOFLAG ) print(" check result w/ tlpctl list: ", end="") if not test_executable( executable_path=TLPCTL, args=["list"], expected_output=exp_out, filters=[battery_aware_filter], ): errcnt += 1 print("}}} " + f"errcnt={str(errcnt)}\n") report.count_test(errcnt) def test_loglevel(report: TestReport): # Check output of 'tlpctl loglevel' # # Args: none # Returns: none global _testcnt global _failcnt errcnt = 0 loglevel = run_executable(TLPCTL, ["list"]).rstrip("\n") loglevel = extract_loglevel(loglevel) if loglevel == "info": seqlevel = ["debug", "info"] else: seqlevel = ["info", "debug"] print("Check tlpctl loglevel " + repr(seqlevel) + " {{{") for loglevel in seqlevel: print(f" tlpctl loglevel {loglevel}: ", end="") if not test_executable( executable_path=TLPCTL, args=["loglevel", loglevel], expected_output=f"tlp-pd loglevel set to '{loglevel}'.\n", ): errcnt += 1 print("}}} " + f"errcnt={str(errcnt)}\n") report.count_test(errcnt) def test_polkit_noauth(report: TestReport): # Run 'tlpctl ' in a ssh session, expecting a not authorized error # Checks with 'tlpctl' if profile is unchanged # # Args: none # Returns: none errcnt = 0 initial = run_executable(TLPCTL, ["get"]).rstrip("\n") profile = reordered_profile_sequence(initial)[1] print("Check polkit not authorized {{{") myhostname = socket.gethostname() print(f" ssh {myhostname} tlpctl set '{initial}'-> '{profile}'") rc = run_executable_rc( executable_path=SSH, args=[myhostname, "tlpctl", "set", profile], ) if rc == 1: # "not authorized" (as expected) -> check if profile did not change # Wait, tlp activates the profile asynchronously time.sleep(WAIT_PROFILE_ACTIVATION) # Expect profile unchanged print(" unchanged? ", end="") if not test_executable( executable_path=TLPCTL, args=["get"], expected_output=f"{initial}\n", ): # Profile changed despite "not authorized" errcnt += 1 else: print(f" rc={rc}: not authorized=ok") elif rc == 0: # "authorized" (not expected) print(f" rc={rc}: authorized=NOK") errcnt += 1 elif rc == -1: print(f" rc={rc}: timeout (NOK)") errcnt += 1 else: print(f" rc={rc}: unknown result (NOK)") errcnt += 1 print("}}} " + f"errcnt={str(errcnt)}\n") report.count_test(errcnt) # --- Run tests if __name__ == "__main__": report = TestReport() print(f"+++ {os.path.basename(__file__)}\n") test_set_direct(report) test_set(report) test_list(report) test_loglevel(report) test_polkit_noauth(report) report.print_result() TLP-1.10.1/unit-tests/test_all.sh000077500000000000000000000004271517565574500165450ustar00rootroot00000000000000#!/bin/sh readonly TESTLIB="test-func" spath="${0%/*}" # shellcheck disable=SC1090 . "$spath/$TESTLIB" || { printf "Error: missing library %s\n" "$spath/$TESTLIB" 1>&2 exit 70 } cache_root_cred start_report "$spath/test-pm_all.sh" "$spath/test-bc_all.sh" print_report TLP-1.10.1/unit-tests/testing.py000077500000000000000000000173311517565574500164330ustar00rootroot00000000000000#!/usr/bin/python3 # test_func.py: Unit Test Helpers import difflib import os import re import subprocess import sys import tempfile from typing import Callable, List, Optional # --- Testing functions --- def run_executable( executable_path: str, args: List[str] = [], input_data: str = "", timeout: int = 10, ) -> str: # Run an executable and capture its stdout # # Args: # executable_path: Path to the executable # args: List of command-line arguments # input_data: Input to pass via stdin (optional) # timeout: Timeout in seconds # # Returns: # stdout as a string cmd = [executable_path] if args: cmd.extend(args) try: result = subprocess.run( cmd, input=input_data, text=True, capture_output=True, timeout=timeout, check=True, ) return result.stdout except subprocess.TimeoutExpired: sys.stderr.write(f"Error: {cmd} timed out after {timeout} seconds.\n") return "" except subprocess.CalledProcessError as err: sys.stderr.write(f"Error: {cmd} failed.\n{err.stderr}\n") return "" def run_executable_rc( executable_path: str, args: List[str] = [], input_data: str = "", timeout: int = 10, ) -> int: # Run an executable and return its exitcode; discard output # # Args: # executable_path: Path to the executable # args: List of command-line arguments # input_data: Input to pass via stdin (optional) # timeout: Timeout in seconds # # Returns: # returncode as an int cmd = [executable_path] if args: cmd.extend(args) try: result = subprocess.run( cmd, input=input_data, text=True, capture_output=True, timeout=timeout, check=False, ) return result.returncode except subprocess.TimeoutExpired: sys.stderr.write(f"Error: {cmd} timed out after {timeout} seconds.\n") return -1 def filter_output( output: str, filters: Optional[List[Callable[[str], str]]] = None, ) -> str: # Apply filters to the output (e.g., remove timestamps, normalize whitespace) # # Args: # output: Raw output from the executable # filters: List of filter functions (e.g., regex substitutions) # # Returns: # Filtered output if not filters: return output filtered = output for filter_func in filters: filtered = filter_func(filtered) return filtered def make_regex_filter( pattern: str, replacement: str, flags: int = 0 ) -> Callable[[str], str]: # Create a filter function that replaces regex pattern matches with a string # # Args: # pattern: regex pattern to search for # replacement: string to replace matches with (supports backreferences e.g. \1) # flags: optional re module flags (e.g. re.IGNORECASE, re.MULTILINE) # # Returns: # A filter function compatible with filter_output() def _filter(output: str) -> str: return re.sub(pattern, replacement, output, flags=flags) return _filter def compare_outputs( actual: str, expected: str, ignore_whitespace: bool = False, ) -> bool: # Compare actual and expected outputs # # Args: # actual: Actual output from the executable # expected: Expected output # ignore_whitespace: If True, normalize whitespace before comparison # # Returns: # True if outputs match, False otherwise if ignore_whitespace: actual = " ".join(actual.split()) expected = " ".join(expected.split()) return actual == expected def generate_diff( actual: str, expected: str, ) -> str: # Generate a diff between actual and expected outputs # # Args: # actual: Actual output # expected: Expected output # # Returns: # Diff as a string diff = difflib.ndiff( expected.splitlines(), actual.splitlines(), ) return "\n".join(diff) def test_executable( executable_path: str, args: List[str] = [], expected_output: str = "", input_data: str = "", filters: Optional[List[Callable[[str], str]]] = None, ignore_whitespace: bool = False, ) -> bool: # Test an executable by comparing its output to an expected output. # # Args: # executable_path: Path to the executable. # expected_output: Expected output. # args: Command-line arguments. # input_data: Input to pass via stdin. # filters: List of filter functions. # ignore_whitespace: If True, normalize whitespace before comparison. # # Returns: # True if test passes, False otherwise. actual_output = run_executable(executable_path, args, input_data) filtered_output = filter_output(actual_output, filters) if compare_outputs(filtered_output, expected_output, ignore_whitespace): print("✅ passed") return True else: print("❌ failed") print("Diff exp->act:") print(generate_diff(filtered_output, expected_output)) print() return False # --- Reporting ANSI_RED = "\033[31m" ANSI_GREEN = "\033[32m" ANSI_BLACK = "\033[m" class TestReport: def __init__(self): self._testcnt = 0 self._failcnt = 0 report_fn = os.getenv("TLP_TEST_REPORT") if report_fn is None: new_report = True else: try: self._report_file = open(report_fn, "a") # type: ignore[reportArgumentType] new_report = False except FileNotFoundError as e: sys.stderr.write( f"Warning: given report file '{report_fn}' does not exist.\n" ) sys.stderr.write(f"{e}\n") sys.stderr.write("Creating a new one instead.\n") new_report = True except IOError as e: sys.stderr.write( f"Warning: could not open given report file '{report_fn}'.\n" ) sys.stderr.write(f"{e}\n") sys.stderr.write("Creating a new one instead.\n") new_report = True if new_report: try: # Create temporary file self._report_file = tempfile.NamedTemporaryFile( "w", prefix="tlp-test-report.", delete=False ) except OSError as e: sys.stderr.write("Error: could not create report file.\n") sys.stderr.write(f"{e}\n") sys.exit(1) self._report_file.write(f"{os.path.basename(sys.argv[0]):<50} --> ") def count_test(self, errors: int): # Increment report counters # # Args: # tested: number of tests run # failed: number of failed tests self._testcnt += 1 if errors > 0: self._failcnt += 1 def print_line(self, line: str): # Write text line to stdout and report file # # Args: # line: message text print(line) try: self._report_file.write(f"{line}\n") except IOError: pass # Ignore write errors def print_result(self): # Write test result to terminal and report file, # then close the report file if self._failcnt == 0: self.print_line( f"{ANSI_GREEN}OK:{ANSI_BLACK} {self._testcnt} of {self._testcnt} tests passed\n" ) else: self.print_line( f"{ANSI_GREEN}OK:{ANSI_BLACK} {self._failcnt} of {self._testcnt} tests failed\n" ) self._report_file.close() TLP-1.10.1/unit-tests/unit-tests.rst000066400000000000000000000006271517565574500172520ustar00rootroot00000000000000Unit Tests for TLP ================== Tests cover only part of the functionality of TLP. For objectives see the individual files. Prequisites ----------- Software ^^^^^^^^ Tests are executed with the tool `clitest `_. Hardware ^^^^^^^^ Unit tests must be run on real hardware, not in a virtual machine or container. See the individual files for hardware details.