Compare commits

..

114 Commits

Author SHA1 Message Date
cryobry
7ccb294062 Development fork for copr 2020-02-18 12:01:41 -05:00
Francesco Palmarini
8d8096a3b3 Merge pull request #166 from kikislater/patch-1
Confusing documentation about HWP parameter
2020-02-15 18:17:09 +01:00
Francesco Palmarini
1cb4fd7ae0 Merge pull request #176 from nariox/update-readme
Update Readme to remove static-fix from top
2020-02-15 18:16:29 +01:00
Francesco Palmarini
0b10acf5f6 Merge pull request #168 from nariox/master
Implement "Disable BDPROCHOT"
2020-02-15 18:15:37 +01:00
Pedro Nariyoshi
49622cd79b Update Readme to remove static-fix from top 2020-02-12 11:49:57 -05:00
Pedro Nariyoshi
919b61f3c9 Revert "Move static fix down"
This reverts commit b1d887c167.
2020-02-12 11:36:14 -05:00
nariox
b1d887c167 Move static fix down
The static fix takes quite a bit of space, but doesn't  really relate to throttled. Moved it down, so readme is a bit cleaner.
2020-02-12 11:27:30 -05:00
Pedro Nariyoshi
d118ddc0a3 Remove extra line to make PR cleaner 2020-01-29 10:29:56 -05:00
Pedro Nariyoshi
3cf2f1aa0f Add default values for Disable_BDPROCHOT 2020-01-29 10:28:22 -05:00
Pedro Nariyoshi
f2ee9ed38d Implement Disable_BDPROCHOT 2020-01-29 10:27:05 -05:00
Pedro Nariyoshi
b5e52c03f4 Parse Disable BDPROCHOT option 2020-01-29 09:53:21 -05:00
Sylvain POULAIN
dd115d3ca8 Confusing documentation about HWP parameter
I was looking for this setting in tlp conf file ...
2020-01-26 11:52:50 +04:00
Francesco Palmarini
b7eeeb7842 Update README.md 2020-01-02 10:05:23 +01:00
Francesco Palmarini
e9ec389f5e Add Thinkpad E480 to support list (fix #58) 2019-12-30 09:34:52 +01:00
erpalma
763e948882 reworked logging, now supports log to file (fix #152 and #139) 2019-11-26 17:41:26 +01:00
Francesco Palmarini
b49d1b39d4 Merge pull request #151 from mkogan1/master
Update README.md Fedora installation section
2019-11-25 11:30:57 +01:00
Mark Kogan
64627f1af5 Update README.md Fedora installation section 2019-11-24 18:46:22 +02:00
Francesco Palmarini
67f0423f30 Merge pull request #148 from precupstefan/patch-1
Update README.md
2019-11-02 21:56:40 +01:00
Stefan
c55e54d1a5 Update README.md 2019-11-02 11:39:08 +02:00
Francesco Palmarini
c3e052ce45 Merge pull request #143 from klausenbusk/upstream
Mention the official intel_rapl driver
2019-09-30 10:26:22 +02:00
Kristian Klausen
e9e0db9533 Mention the official intel_rapl driver
On systems where the EC doesn't reset the values
(ex: ASUS Zenbook UX430UNR), it is probably prefered to use the
official intel_rapl driver.
2019-09-29 13:22:33 +02:00
Francesco Palmarini
69b45ff9b3 Merge pull request #141 from Rush/patch-1
Update README.md
2019-09-25 21:21:36 +02:00
Damian Kaczmarek
f024e840f4 Update README.md 2019-09-25 13:43:19 -05:00
erpalma
c89c12475a Relaxed checks for /dev/mem access. Fix #136 2019-09-06 09:34:22 +02:00
Francesco Palmarini
f155eea88a Add ThinkPad E590 (see #120) 2019-09-02 14:58:13 +02:00
erpalma
ba20b714c2 Merge branch 'master' of github.com:erpalma/lenovo-throttling-fix 2019-08-20 11:32:17 +02:00
erpalma
6e54199a2b Add a check for mismatched CORE/CACHE undervolt values (see #130, #133) 2019-08-20 11:31:22 +02:00
erpalma
d276d55513 updated requirements 2019-08-20 11:31:14 +02:00
Francesco Palmarini
f68bfd2a02 Merge pull request #129 from thomwiggers/now_in_arch_community_repo
Throttled is now in the Arch Community repository
2019-07-30 17:21:11 +02:00
Thom Wiggers
d40f53969d It's now in the Arch Community repository 2019-07-30 16:25:52 +02:00
erpalma
862971156e Merge branch 'master' of github.com:erpalma/lenovo-throttling-fix 2019-07-12 15:13:59 +02:00
erpalma
1008af57c9 fix PL2_Duration_S parsing + set Clamp Mode bit (16) to the boot value (thanks Andrea Arcangeli) 2019-07-12 15:09:54 +02:00
Francesco Palmarini
2b73d324af fix #126 2019-07-12 09:54:06 +02:00
Francesco Palmarini
fe93e48c8e fix #123 2019-06-21 09:20:12 +02:00
erpalma
95aa5b1e02 new HWP handling (see #121) 2019-06-10 12:13:26 +02:00
erpalma
952128dc21 code formatting 2019-04-16 16:50:17 +02:00
erpalma
74937fdee5 use dbus method in HWP if available 2019-04-11 18:07:24 +02:00
Francesco Palmarini
6d70e21985 Merge pull request #109 from abn/fedora-copr-rpm
Add copr repository instructions for Fedora
2019-03-14 12:46:49 +01:00
Arun Babu Neelicattu
5486651b93 Add copr repository instructions for Fedora 2019-03-14 00:53:59 +01:00
Francesco Palmarini
d63dfc695e Update README.md 2019-03-06 14:48:52 +01:00
erpalma
430032aec8 Add IccMax sections in config file (commented out) 2019-03-06 12:37:36 +01:00
Francesco Palmarini
ab6047dfcf Update README.md 2019-03-06 12:35:56 +01:00
erpalma
6302933e80 Merge remote-tracking branch 'origin/master' 2019-03-06 12:26:51 +01:00
erpalma
be756a1f86 Add IccMax override feature 2019-03-06 12:26:37 +01:00
Francesco Palmarini
4d64bfb8ae Merge pull request #105 from moinmoi/master
added void/runit support
2019-03-06 09:18:31 +01:00
moinmoi
644c0982e4 added void/runit support 2019-03-05 22:50:43 +01:00
erpalma
33f0412bea typo, fix #102 2019-02-18 10:44:56 +01:00
Francesco Palmarini
52d83c41bb Merge pull request #101 from c-edw/AddSudo
readme: Prefix package install commands with sudo.
2019-02-15 10:18:33 +01:00
Connor E
e588ade2ef readme: Prefix package install commands with sudo. 2019-02-15 02:07:52 +00:00
Francesco Palmarini
11b5f12f9e Update README.md 2019-02-13 10:57:58 +01:00
Francesco Palmarini
f585babd71 Merge pull request #99 from gpilleux/patch-2
New model added
2019-02-13 10:56:42 +01:00
gpilleux
75ad31da7a New model added 2019-02-12 12:41:14 -03:00
Francesco Palmarini
9d1d079d10 Merge pull request #96 from pdelteil/patch-1
Update README.md
2019-02-06 22:52:10 +01:00
Philippe Delteil
db1b16cdbf Update README.md 2019-02-06 16:31:44 -03:00
erpalma
56a91aaf6f fix logging to systemd and switch to stderr for warning and fatal 2019-01-24 08:52:56 +01:00
Francesco Palmarini
7ac672dd7d Update README.md 2019-01-22 12:07:32 +01:00
Francesco Palmarini
2ecbf9f972 Merge pull request #95 from erpalma/testing
Merge Testing Branch
2019-01-22 11:34:38 +01:00
erpalma
25eabce346 Add more power detection methods 2019-01-21 16:54:40 +01:00
erpalma
4972b29a19 add fatal/warning error handler 2019-01-21 16:54:23 +01:00
Francesco Palmarini
f117fcb1fe Update README.md 2019-01-18 09:34:11 +01:00
Francesco Palmarini
f9c7f51549 Merge pull request #86 from k0a1a/patch-1
Missing Debian/Ubuntu python3-wheel dep
2018-12-29 14:44:52 +01:00
Danja Vasiliev
d0f7c152af Missing Debian/Ubuntu python3-wheel dep
added python3-wheel
2018-12-28 17:34:49 +01:00
erpalma
5ce7af6463 add experimental code to check if the hardware is compatible with the tool 2018-12-27 14:00:20 +01:00
erpalma
d2361431cb error handling cleanup 2018-12-27 13:58:16 +01:00
erpalma
c7e292e282 add power profile-based undervolting (fix #79) 2018-12-11 11:40:23 +01:00
erpalma
668f023868 print the actual power source (AC/BATTERY) in --monitor mode 2018-12-11 11:08:30 +01:00
erpalma
d18ad0e07f update requirements 2018-12-11 11:07:46 +01:00
Francesco Palmarini
99c0d7cda2 Merge pull request #82 from jkrcma/patch-1
Update openSUSE installation notes
2018-12-11 10:28:58 +01:00
Jakub Krčma
d33afeb933 Update openSUSE installation notes 2018-12-10 15:31:38 +01:00
Francesco Palmarini
05e88b827b Merge pull request #81 from LittleFox94/patch-1
Update README.md: add python3-venv for debian
2018-12-06 15:39:32 +01:00
LittleFox
a5575182e2 Update README.md: add python3-venv for debian
Add the missing dependency python3-venv to the debian instructions
2018-12-06 15:19:04 +01:00
Francesco Palmarini
6c4f3a77a5 Update README.md 2018-12-04 17:37:25 +01:00
Francesco Palmarini
7d9fd63ef2 Merge pull request #74 from moldabekov/patch-1
GNU Make is missing
2018-11-23 14:40:12 +01:00
Mark
d19c686b27 GNU Make is missing
In default Fedora installation (28/29) GNU make is missing by default. You need to install it before running `install.sh`
2018-11-23 17:23:00 +06:00
Francesco Palmarini
873e016006 Update README.md 2018-11-19 19:17:42 +01:00
Francesco Palmarini
542d2a5ee0 Update README.md 2018-11-19 19:17:13 +01:00
Francesco Palmarini
6209e93963 Merge pull request #68 from henning-schild/henning/staging
README.md: add X280
2018-11-08 14:50:52 +01:00
Henning Schild
5c16e35ca6 README.md: add X280 2018-11-08 13:00:00 +01:00
erpalma
003278cafd typo.... (bad day) 2018-11-07 16:26:55 +01:00
erpalma
5a96b88944 try to load configs modules + decode gzip output (see #67) 2018-11-07 16:21:54 +01:00
erpalma
0189170a40 fix #66 2018-11-07 11:24:17 +01:00
Francesco Palmarini
671e252198 Update README.md 2018-11-07 11:22:00 +01:00
erpalma
b15edc8076 Merge branch 'master' of github.com:erpalma/lenovo-throttling-fix 2018-11-07 11:20:06 +01:00
erpalma
8677de6f96 add some more monitoring stuff: Vcore, Package, Graphics and DRAM power consumption 2018-11-07 11:19:23 +01:00
Francesco Palmarini
938482654c Update README.md 2018-11-06 12:19:19 +01:00
erpalma
57faf388e9 debug leftover 2018-11-06 12:15:04 +01:00
erpalma
9636a5de83 add realtime monitoring of throttling causes 2018-11-06 12:10:52 +01:00
Francesco Palmarini
89e3ba8990 Update README.md 2018-11-06 11:09:42 +01:00
erpalma
105f87c0ff switch to black formatter 2018-11-06 11:07:54 +01:00
erpalma
76e097663c further kernel config checks, related to #65 2018-11-06 11:06:03 +01:00
Francesco Palmarini
c1f5f95c37 Merge pull request #65 from tSte/update-msr-access-restriction-message
Update MSR access restriction message
2018-11-06 10:33:49 +01:00
tSte
b8ef7b72e5 Update MSR access debug message; Update readme 2018-11-06 07:11:37 +01:00
Francesco Palmarini
73df1ff98e Merge pull request #63 from keachi/install_directory
do not overwrite an existing file
2018-10-31 14:07:05 +01:00
Tobias Rueetschi
4914021253 do not overwrite an existing file 2018-10-31 13:57:45 +01:00
Francesco Palmarini
79f1db481c Merge pull request #59 from floriankisser/master
use builtin venv
2018-10-21 11:12:05 +02:00
Florian Kisser
ce4a75c539 use builtin venv 2018-10-19 19:48:11 +02:00
Francesco Palmarini
b571aefa3b Update requirements.txt 2018-10-18 11:38:47 +02:00
Francesco Palmarini
0f10e10ce0 Merge pull request #57 from stieg/master
Add python3-virtualenv package to fedora install procedure
2018-10-12 10:20:15 +02:00
Andrew Stiegmann (stieg)
678aba3f88 Add python3-virtualenv package to fedora install procedure; cleanup line endings 2018-10-11 10:30:36 -10:00
Francesco Palmarini
963fb1c75d Update README.md 2018-10-03 11:34:20 +02:00
Francesco Palmarini
816ea27e03 Merge pull request #56 from m-kuhn/patch-1
Fix installation instructions for Fedora
2018-10-03 08:59:05 +02:00
Matthias Kuhn
9cdc36406e Fix installation instructions for Fedora 2018-10-03 06:47:35 +02:00
Francesco Palmarini
cf3cea5997 Update README.md 2018-09-20 09:51:40 +02:00
erpalma
ff0f912a86 Merge branch 'master' of github.com:erpalma/lenovo-throttling-fix 2018-09-20 09:49:44 +02:00
erpalma
301a57aa0f fix a bug caused by args swap 2018-09-20 09:48:01 +02:00
Francesco Palmarini
d009138d22 Update README.md 2018-09-14 08:49:53 +02:00
erpalma
5c96274e52 Merge branch 'master' of github.com:erpalma/lenovo-throttling-fix 2018-09-11 17:15:32 +02:00
erpalma
f10c269c9a built-in cpu usage calculation + drop psutil dependency 2018-09-11 17:14:01 +02:00
A. Binzxxxxxx
2792cac017 Update README.md
adding not about masking thermald to keep it disabled even after a package update
2018-09-04 11:41:55 +02:00
erpalma
49c056e311 Merge branch 'master' of github.com:erpalma/lenovo-throttling-fix 2018-09-03 17:07:27 +02:00
erpalma
8ef50d57ce config values can be commented out to disable (fix #51) 2018-09-03 17:07:00 +02:00
erpalma
6516f44621 config values can be commented out to disable (fix #52) 2018-09-03 17:05:42 +02:00
erpalma
494ea7f595 Sysfs_Power_Path can now be set in the config file (#48) 2018-09-01 10:10:49 +02:00
erpalma
dcfa39acb8 updated dependencies 2018-09-01 10:10:38 +02:00
11 changed files with 840 additions and 209 deletions

View File

@@ -1,20 +0,0 @@
---
name: Problems and Questions
about: Problems and Questions
---
My machine hangs or acts strange:
-> did you try with less undervolting or without undervolting?
My machine is still throttling:
-> did you turn of/stop,disable,uninstall thermald?
-> did you check for dust and do you have clean fans and such?
Keep in mind your hardware might put you some limits which you can't fix with software.
Yes I did read the readme carefully ;)
What is it about?
Details?
which CPU? Laptop?
Configuration?

2
.gitignore vendored
View File

@@ -29,7 +29,7 @@ wheels/
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it. # before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest *.manifest
*.spec #*.spec
# Installer logs # Installer logs
pip-log.txt pip-log.txt

171
README.md
View File

@@ -1,31 +1,32 @@
# Fix Intel CPU Throttling on Linux # Fix Intel CPU Throttling on Linux
Workaround for Linux throttling issues on Lenovo T480 / T480s / X1C6 notebooks as described [here](https://www.reddit.com/r/thinkpad/comments/870u0a/t480s_linux_throttling_bug/). This tool was originally developed to fix Linux CPU throttling issues affecting Lenovo T480 / T480s / X1C6 as described [here](https://www.reddit.com/r/thinkpad/comments/870u0a/t480s_linux_throttling_bug/).
This script forces the CPU package power limit (PL1/2) to **44 W** (29 W on battery) and the temperature trip point to **95 'C** (85 'C on battery) by overriding default values in MSR and MCHBAR every 5 seconds (30 on battery) to block the Embedded Controller from resetting these values to default. The CPU package power limit (PL1/2) is forced to a value of **44 W** (29 W on battery) and the temperature trip point to **95 'C** (85 'C on battery) by overriding default values in MSR and MCHBAR every 5 seconds (30 on battery) to block the Embedded Controller from resetting these values to default.
On systems where the EC doesn't reset the values (ex: ASUS Zenbook UX430UNR), the power limit can be altered by using the official intel_rapl driver (see [Static fix](#static-fix) for more information)
### Tested hardware
Other users have confirmed that the tool is also working for these laptops:
- Lenovo T480, T480s, X1C5, X1C6, T580, L480, T470, X280, ThinkPad Anniversary Edition 25, E590 w/ RX 550X, P43s, E480, E580
- Dell XPS 9365, 9370, Latitude 7390 2-in-1
- Microsoft Surface Book 2
### Supported hardware
Other users have confirmed that the script is also working for these laptops:
- Lenovo T480
- Lenovo T480s
- Lenovo X1C5
- Lenovo X1C6
- Lenovo T580
- Lenovo L480
- Lenovo T470
- Dell XPS 9370
I will keep this list updated. I will keep this list updated.
### Is this script really doing something on my PC?? ### Is this tool really doing something on my PC??
I suggest you to use the excellent **[s-tui](https://github.com/amanusk/s-tui)** tool to check and monitor the CPU usage, frequency, power and temperature under load! I suggest you to use the excellent **[s-tui](https://github.com/amanusk/s-tui)** tool to check and monitor the CPU usage, frequency, power and temperature under load!
### Undervolt ### Undervolt
The script now also supports **undervolting** the CPU by configuring voltage offsets for CPU, cache, GPU, System Agent and Analog I/O planes. The script will re-apply undervolt on resume from standby and hibernate by listening to DBus signals. The tool supports **undervolting** the CPU by configuring voltage offsets for CPU, cache, GPU, System Agent and Analog I/O planes. The tool will re-apply undervolt on resume from standby and hibernate by listening to DBus signals. You can now either use the `UNDERVOLT` key in config to set global values or the `UNDERVOLT.AC` and `UNDERVOLT.BATTERY` keys to selectively set undervolt values for the two power profiles.
### IccMax (EXPERTS ONLY)
The tool now supports overriding the **IccMax** by configuring the maximum allowed current for CPU, cache and GPU planes. The tool will re-apply IccMax on resume from standby and hibernate. You can now either use the `ICCMAX` key in config to set global values or the `ICCMAX.AC` and `ICCMAX.BATTERY` keys to selectively set current values for the two power profiles. **NOTE:** the values specified in the config file are the actual current limit of your system, so those are not a offset from the default values as for the undervolt. As such, you should first find your system default values with the `--monitor` command.
### HWP override (EXPERIMENTAL) ### HWP override (EXPERIMENTAL)
I have found that under load my CPU was not always hitting max turbo frequency, in particular when using one/two cores only. For instance, when running [prime95](https://www.mersenne.org/download/) (1 core, test #1) my CPU is limited to about 3500 MHz over the theoretical 4000 MHz maximum. The reason is the value for the HWP energy performance [hints](http://manpages.ubuntu.com/manpages/artful/man8/x86_energy_perf_policy.8.html). By default TLP sets this value to "balance_performance" on AC in order to reduce the power consumption/heat in idle. By setting this value to "performance" I was able to reach 3900 MHz in the prime95 single core test, achieving a +400 MHz boost. Since this value forces the CPU to full speed even during idle, a new experimental feature allows to automatically set HWP to performance under load and revert it to balanced when idle. This feature can be enabled (in AC mode *only*) by setting to `True` the `HWP_Mode` parameter in the config file. I have found that under load my CPU was not always hitting max turbo frequency, in particular when using one/two cores only. For instance, when running [prime95](https://www.mersenne.org/download/) (1 core, test #1) my CPU is limited to about 3500 MHz over the theoretical 4000 MHz maximum. The reason is the value for the HWP energy performance [hints](http://manpages.ubuntu.com/manpages/artful/man8/x86_energy_perf_policy.8.html). By default TLP sets this value to `balance_performance` on AC in order to reduce the power consumption/heat in idle. By setting this value to `performance` I was able to reach 3900 MHz in the prime95 single core test, achieving a +400 MHz boost. Since this value forces the CPU to full speed even during idle, a new experimental feature allows to automatically set HWP to performance under load and revert it to balanced when idle. This feature can be enabled (in AC mode *only*) by setting to `True` the `HWP_Mode` parameter in the lenovo_fix config file : https://github.com/erpalma/throttled/blob/master/etc/lenovo_fix.conf#L39 .
I have run **[Geekbench 4](https://browser.geekbench.com/v4/cpu/8656840)** and now I can get a score of 5391/17265! On balance_performance I can reach only 4672/16129, so **15% improvement** in single core and 7% in multicore, not bad ;) I have run **[Geekbench 4](https://browser.geekbench.com/v4/cpu/8656840)** and now I can get a score of 5391/17265! On `balance_performance` I can reach only 4672/16129, so **15% improvement** in single core and 7% in multicore, not bad ;)
### setting cTDP (EXPERIMENTAL) ### setting cTDP (EXPERIMENTAL)
On a lot of modern CPUs from Intel one can configure the TDP up or down based on predefined profiles. This is what this option does. For a i7-8650U normal would be 15W, up profile is setting it to 25W and down to 10W. You can lookup the values of your CPU at the Intel product website. On a lot of modern CPUs from Intel one can configure the TDP up or down based on predefined profiles. This is what this option does. For a i7-8650U normal would be 15W, up profile is setting it to 25W and down to 10W. You can lookup the values of your CPU at the Intel product website.
@@ -33,29 +34,30 @@ On a lot of modern CPUs from Intel one can configure the TDP up or down based on
## Requirements ## Requirements
A stripped down version of the python module `python-periphery` is now built-in and it is used for accessing the MCHBAR register by memory mapped I/O. You also need `dbus` and `gobject` python bindings for listening to dbus signals on resume from sleep/hibernate. A stripped down version of the python module `python-periphery` is now built-in and it is used for accessing the MCHBAR register by memory mapped I/O. You also need `dbus` and `gobject` python bindings for listening to dbus signals on resume from sleep/hibernate.
### Secure Boot ### Writing to MSR and PCI BAR
Right now it is mandatory to **disable Secure Boot** (in BIOS) in order to avoid [Kernel Lockdown](https://lwn.net/Articles/706637/). In particular Lockdown restricts access to MSR and PCI BAR (via /dev/mem) which are required by this script. Right now it is mandatory to **disable Secure Boot** (in BIOS) in order to avoid [Kernel Lockdown](https://lwn.net/Articles/706637/). In particular Lockdown restricts access to MSR and PCI BAR (via /dev/mem) which are required by this tool.
Note that some kernels (e.g. [linux-hardened](https://www.archlinux.org/packages/extra/x86_64/linux-hardened/)) will prevent from writing to `/dev/mem` too. Specifically, you need a kernel with `CONFIG_DEVMEM` and `CONFIG_X86_MSR` set.
### Thermald ### Thermald
As discovered by *DEvil0000* the Linux Thermal Monitor ([thermald](https://github.com/intel/thermal_daemon)) can conflict with the purpose of this script. In particular, thermald might be pre-installed (e.g. on Ubuntu) and configured in such a way to keep the CPU temperature below a certain threshold (~80 'C) by applying throtthling or messing up with RAPL or other CPU-specific registers. I strongly suggest to either disable/uninstall it or to review its default configuration. As discovered by *DEvil0000* the Linux Thermal Monitor ([thermald](https://github.com/intel/thermal_daemon)) can conflict with the purpose of this tool. In particular, thermald might be pre-installed (e.g. on Ubuntu) and configured in such a way to keep the CPU temperature below a certain threshold (~80 'C) by applying throtthling or messing up with RAPL or other CPU-specific registers. I strongly suggest to either disable/uninstall it or to review its default configuration.
### Update ### Update
The scripts is now running with Python3 by default (tested w/ 3.6) and a virtualenv is automatically created in `/opt/lenovo_fix`. Python2 should probably still work. The tool is now running with Python3 by default (tested w/ 3.6) and a virtualenv is automatically created in `/opt/lenovo_fix`. Python2 should probably still work.
## Installation ## Installation
### Arch Linux [AUR package](https://aur.archlinux.org/packages/lenovo-throttling-fix-git/): ### Arch Linux [community package](https://www.archlinux.org/packages/community/x86_64/throttled/):
``` ```
yay -S lenovo-throttling-fix-git pacman -S throttled
sudo systemctl enable --now lenovo_fix.service sudo systemctl enable --now lenovo_fix.service
``` ```
Thanks to *felixonmars* for creating and maintaining this package. Thanks to *felixonmars* for creating and maintaining this package.
### Debian/Ubuntu ### Debian/Ubuntu
``` ```
sudo apt install git virtualenv build-essential python3-dev libdbus-glib-1-dev libgirepository1.0-dev libcairo2-dev sudo apt install git build-essential python3-dev libdbus-glib-1-dev libgirepository1.0-dev libcairo2-dev python3-venv python3-wheel
git clone https://github.com/erpalma/lenovo-throttling-fix.git git clone https://github.com/erpalma/lenovo-throttling-fix.git
sudo ./install.sh sudo ./lenovo-throttling-fix/install.sh
``` ```
If you own a X1C6 you can also check a tutorial for Ubuntu 18.04 [here](https://mensfeld.pl/2018/05/lenovo-thinkpad-x1-carbon-6th-gen-2018-ubuntu-18-04-tweaks/). If you own a X1C6 you can also check a tutorial for Ubuntu 18.04 [here](https://mensfeld.pl/2018/05/lenovo-thinkpad-x1-carbon-6th-gen-2018-ubuntu-18-04-tweaks/).
@@ -64,21 +66,55 @@ You should make sure that **_thermald_** is not setting it back down. Stopping/d
sudo systemctl stop thermald.service sudo systemctl stop thermald.service
sudo systemctl disable thermald.service sudo systemctl disable thermald.service
``` ```
If you want to keep it disabled even after a package update you should also run:
```
sudo systemctl mask thermald.service
```
### Fedora ### Fedora
A [copr repository](https://copr.fedorainfracloud.org/coprs/abn/throttled/) is available and can be used as detailed below. You can find the configuration installed at `/etc/throttled.conf`. The issue tracker for this packaging is available [here](https://github.com/abn/throttled-rpm/issues).
``` ```
dnf install cairo-gobject-devel gobject-introspection-devel dbus-glib-devel python-virtualenv python3-devel sudo dnf copr enable abn/throttled
sudo dnf install -y throttled
sudo systemctl enable --now throttled
```
If you prefer to install from source, you can use the following commands.
```
sudo dnf install python3-cairo-devel cairo-gobject-devel gobject-introspection-devel dbus-glib-devel python3-devel make libX11-devel
git clone https://github.com/erpalma/lenovo-throttling-fix.git git clone https://github.com/erpalma/lenovo-throttling-fix.git
sudo ./install.sh sudo ./lenovo-throttling-fix/install.sh
``` ```
Feedback about Fedora installation is welcome. Feedback about Fedora installation is welcome.
### openSUSE ### openSUSE
User *brycecordill* reported that the following dependecies are required for installing in openSUSE. I guess that python2 dependecies can be safely dropped. I would really appreciate any feedback from openSUSE users. User *brycecordill* reported that the following dependencies are required for installing in openSUSE, tested on openSUSE 15.0 Leap.
``` ```
zypper install gcc python2-pip python3-devel python-devel dbus-1-glib-devel python3-cairo-devel cairo-devel python2-cairo-devel python3-gobject-cairo gobject-introspection-devel python-virtualenv sudo zypper install gcc make python3-devel dbus-1-glib-devel python3-cairo-devel cairo-devel python3-gobject-cairo gobject-introspection-devel
git clone https://github.com/erpalma/lenovo-throttling-fix.git git clone https://github.com/erpalma/lenovo-throttling-fix.git
sudo ./install.sh sudo ./lenovo-throttling-fix/install.sh
```
### Gentoo
An overlay is [available](https://github.com/erpalma/throttled-overlay).
```
layman -o https://github.com/erpalma/throttled-overlay/raw/master/repositories.xml -f -a throttled
sudo emerge -av sys-power/throttled
systemctl enable throttled.service
systemctl start throttled.service
```
### Void
The installation itself will create a runit service as lenovo_fix, enable it and start it. Before installation, make sure dbus is running `sv up dbus`.
```
sudo xbps-install -Sy gcc git python3-devel dbus-glib-devel libgirepository-devel cairo-devel python3-wheel pkg-config make
git clone https://github.com/erpalma/lenovo-throttling-fix.git
sudo ./lenovo-throttling-fix/install.sh
``` ```
### Uninstall ### Uninstall
@@ -87,7 +123,14 @@ To permanently stop and disable the execution just issue:
systemctl stop lenovo_fix.service systemctl stop lenovo_fix.service
systemctl disable lenovo_fix.service systemctl disable lenovo_fix.service
``` ```
If you also need to remove the script from the system:
If you're running runit instead of systemd:
```
sv down lenovo_fix
rm /var/service/lenovo_fix
```
If you also need to remove the tool from the system:
``` ```
rm -rf /opt/lenovo_fix /etc/systemd/system/lenovo_fix.service rm -rf /opt/lenovo_fix /etc/systemd/system/lenovo_fix.service
# to purge also the config file # to purge also the config file
@@ -95,8 +138,17 @@ rm /etc/lenovo_fix.conf
``` ```
On Arch you should probably use `pacman -R lenovo-throttling-fix-git` instead. On Arch you should probably use `pacman -R lenovo-throttling-fix-git` instead.
### Update
If you update the tool you should manually check your config file for changes or additional features and modify it accordingly. The update process is then as simple as:
```
cd lenovo-throttling-fix
git pull
sudo ./install.sh
sudo systemctl restart lenovo_fix.service
```
## Configuration ## Configuration
The configuration has moved to `/etc/lenovo_fix.conf`. Makefile does not overwrite your previous config file, so you need to manually check for differences in config file structure when updating the tool. If you want to overwrite the config with new defaults just issue `sudo cp etc/lenovo_fix.conf /etc`. There exist two profiles `AC` and `BATTERY` and the script can be totally disabled by setting `Enabled: False` in the `GENERAL` section. Undervolt is applied if any voltage plane in the config file (section UNDERVOLT) was set. Notice that the offset is in *mV* and only undervolting (*i.e.* negative values) is supported. The configuration has moved to `/etc/lenovo_fix.conf`. Makefile does not overwrite your previous config file, so you need to manually check for differences in config file structure when updating the tool. If you want to overwrite the config with new defaults just issue `sudo cp etc/lenovo_fix.conf /etc`. There exist two profiles `AC` and `BATTERY` and the tool can be totally disabled by setting `Enabled: False` in the `GENERAL` section. Undervolt is applied if any voltage plane in the config file (section UNDERVOLT) was set. Notice that the offset is in *mV* and only undervolting (*i.e.* negative values) is supported.
All fields accept floating point values as well as integers. All fields accept floating point values as well as integers.
My T480s with i7-8550u is stable with: My T480s with i7-8550u is stable with:
@@ -115,8 +167,63 @@ ANALOGIO: 0
``` ```
**IMPORTANT:** Please notice that *my* system is stable with these values. Your notebook might crash even with slight undervolting! You should test your system and slowly incresing undervolt to find the maximum stable value for your CPU. You can check [this](https://www.notebookcheck.net/Intel-Extreme-Tuning-Utility-XTU-Undervolting-Guide.272120.0.html) tutorial if you don't know where to start. **IMPORTANT:** Please notice that *my* system is stable with these values. Your notebook might crash even with slight undervolting! You should test your system and slowly incresing undervolt to find the maximum stable value for your CPU. You can check [this](https://www.notebookcheck.net/Intel-Extreme-Tuning-Utility-XTU-Undervolting-Guide.272120.0.html) tutorial if you don't know where to start.
## Monitoring
With the flag `--monitor` the tool *constantly* monitors the throttling status, indicating the cause among thermal limit, power limit, current limit or cross-origin. The last cause is often related to an external event (e.g. by the GPU). The update rate can be adjusted and defaults to 1 second. Example output:
```
./lenovo_fix.py --monitor
[I] Detected CPU architecture: Intel Kaby Lake (R)
[I] Loading config file.
[I] Starting main loop.
[D] Undervolt offsets: CORE: -105.00 mV | GPU: -85.00 mV | CACHE: -105.00 mV | UNCORE: -85.00 mV | ANALOGIO: 0.00 mV
[D] IccMax: CORE: 64.00 A | GPU: 31.00 A | CACHE: 6.00 A
[D] Realtime monitoring of throttling causes:
[AC] Thermal: OK - Power: OK - Current: OK - Cross-domain (e.g. GPU): OK || VCore: 549 mV - Package: 2.6 W - Graphics: 0.4 W - DRAM: 1.2 W
```
## Static Fix
You can alternatively set the power limits using intel_rapl driver (modifying MCHBAR values requires [Linux 5.3+](https://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux.git/commit/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c?h=for-5.4&id=555c45fe0d04bd817e245a125d242b6a86af4593)). Bear in mind, some embedded controllers (EC) control the power limit values and will reset them from time to time):
```
# MSR
# PL1
echo 44000000 | sudo tee /sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw # 44 watt
echo 28000000 | sudo tee /sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/constraint_0_time_window_us # 28 sec
# PL2
echo 44000000 | sudo tee /sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw # 44 watt
echo 2440 | sudo tee /sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/constraint_1_time_window_us # 0.00244 sec
# MCHBAR
# PL1
echo 44000000 | sudo tee /sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/constraint_0_power_limit_uw # 44 watt
# ^ Only required change on a ASUS Zenbook UX430UNR
echo 28000000 | sudo tee /sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/constraint_0_time_window_us # 28 sec
# PL2
echo 44000000 | sudo tee /sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/constraint_1_power_limit_uw # 44 watt
echo 2440 | sudo tee /sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/constraint_1_time_window_us # 0.00244 sec
```
If you want to change the values automatic on boot you can use [systemd-tmpfiles](https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html):
```
# /etc/tmpfiles.d/power_limit.conf
# MSR
# PL1
w /sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw - - - - 44000000
w /sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/constraint_0_time_window_us - - - - 28000000
# PL2
w /sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw - - - - 44000000
w /sys/devices/virtual/powercap/intel-rapl/intel-rapl:0/constraint_1_time_window_us - - - - 2440
# MCHBAR
# PL1
w /sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/constraint_0_power_limit_uw - - - - 44000000
# ^ Only required change on a ASUS Zenbook UX430UNR
w /sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/constraint_0_time_window_us - - - - 28000000
# PL2
w /sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/constraint_1_power_limit_uw - - - - 44000000
w /sys/devices/virtual/powercap/intel-rapl-mmio/intel-rapl-mmio:0/constraint_1_time_window_us - - - - 2440
```
## Debug ## Debug
You can enable the `--debug` option to read back written values and check if the script is working properly. At the statup it will also show the CPUs platform info which contains information about multiplier values and features present for this CPU. Additionally the script will print the thermal status per core which is handy when it comes to figuring out the reason for CPU throttle. Status fields stands for the current throttle reason or condition and log shows if this was a throttle reason since the last interval. You can enable the `--debug` option to read back written values and check if the tool is working properly. At the statup it will also show the CPUs platform info which contains information about multiplier values and features present for this CPU. Additionally the tool will print the thermal status per core which is handy when it comes to figuring out the reason for CPU throttle. Status fields stands for the current throttle reason or condition and log shows if this was a throttle reason since the last interval.
This is an example output: This is an example output:
``` ```
./lenovo_fix.py --debug ./lenovo_fix.py --debug

View File

@@ -1,5 +1,8 @@
[GENERAL] [GENERAL]
# Enable or disable the script execution
Enabled: True Enabled: True
# SYSFS path for checking if the system is running on AC power
Sysfs_Power_Path: /sys/class/power_supply/AC*/online
## Settings to apply while connected to Battery power ## Settings to apply while connected to Battery power
[BATTERY] [BATTERY]
@@ -17,6 +20,8 @@ PL2_Duration_S: 0.002
Trip_Temp_C: 85 Trip_Temp_C: 85
# Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL) # Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL)
cTDP: 0 cTDP: 0
# Disable BDPROCHOT (EXPERIMENTAL)
Disable_BDPROCHOT: False
## Settings to apply while connected to AC power ## Settings to apply while connected to AC power
[AC] [AC]
@@ -36,8 +41,10 @@ Trip_Temp_C: 95
HWP_Mode: False HWP_Mode: False
# Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL) # Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL)
cTDP: 0 cTDP: 0
# Disable BDPROCHOT (EXPERIMENTAL)
Disable_BDPROCHOT: False
[UNDERVOLT] [UNDERVOLT.BATTERY]
# CPU core voltage offset (mV) # CPU core voltage offset (mV)
CORE: 0 CORE: 0
# Integrated GPU voltage offset (mV) # Integrated GPU voltage offset (mV)
@@ -48,3 +55,31 @@ CACHE: 0
UNCORE: 0 UNCORE: 0
# Analog I/O voltage offset (mV) # Analog I/O voltage offset (mV)
ANALOGIO: 0 ANALOGIO: 0
[UNDERVOLT.AC]
# CPU core voltage offset (mV)
CORE: 0
# Integrated GPU voltage offset (mV)
GPU: 0
# CPU cache voltage offset (mV)
CACHE: 0
# System Agent voltage offset (mV)
UNCORE: 0
# Analog I/O voltage offset (mV)
ANALOGIO: 0
# [ICCMAX.AC]
# # CPU core max current (A)
# CORE:
# # Integrated GPU max current (A)
# GPU:
# # CPU cache max current (A)
# CACHE:
# [ICCMAX.BATTERY]
# # CPU core max current (A)
# CORE:
# # Integrated GPU max current (A)
# GPU:
# # CPU cache max current (A)
# CACHE:

View File

@@ -2,9 +2,13 @@
INSTALL_DIR="/opt/lenovo_fix" INSTALL_DIR="/opt/lenovo_fix"
systemctl stop lenovo_fix.service &>/dev/null if pidof systemd 2>&1 1>/dev/null; then
systemctl stop lenovo_fix.service >/dev/null 2>&1
elif pidof runit 2>&1 1>/dev/null; then
sv down lenovo_fix >/dev/null 2>&1
fi
mkdir -p "$INSTALL_DIR" &>/dev/null mkdir -p "$INSTALL_DIR" >/dev/null 2>&1
set -e set -e
cd "$(dirname "$0")" cd "$(dirname "$0")"
@@ -16,19 +20,30 @@ else
echo "Config file already exists, skipping." echo "Config file already exists, skipping."
fi fi
echo "Copying systemd service file..." if pidof systemd 2>&1 1>/dev/null; then
cp systemd/lenovo_fix.service /etc/systemd/system echo "Copying systemd service file..."
cp systemd/lenovo_fix.service /etc/systemd/system
elif pidof runit 2>&1 1>/dev/null; then
echo "Copying runit service file"
cp -R runit/lenovo_fix /etc/sv/
fi
echo "Building virtualenv..." echo "Building virtualenv..."
cp requirements.txt lenovo_fix.py mmio.py "$INSTALL_DIR" cp -n requirements.txt lenovo_fix.py mmio.py "$INSTALL_DIR"
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
virtualenv -p /usr/bin/python3 venv /usr/bin/python3 -m venv venv
. venv/bin/activate . venv/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
echo "Enabling and starting systemd service..." if pidof systemd 2>&1 1>/dev/null; then
systemctl daemon-reload echo "Enabling and starting systemd service..."
systemctl enable lenovo_fix.service systemctl daemon-reload
systemctl restart lenovo_fix.service systemctl enable lenovo_fix.service
systemctl restart lenovo_fix.service
elif pidof runit 2>&1 1>/dev/null; then
echo "Enabling and starting runit service..."
ln -sv /etc/sv/lenovo_fix /var/service/
sv up lenovo_fix
fi
echo "All done." echo "All done."

View File

@@ -1,35 +1,38 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import print_function
import argparse import argparse
import configparser import configparser
import dbus import glob
import gzip
import os import os
import psutil import re
import struct import struct
import subprocess import subprocess
import sys import sys
from collections import defaultdict from collections import defaultdict
from dbus.mainloop.glib import DBusGMainLoop from datetime import datetime
from errno import EACCES, EPERM from errno import EACCES, EPERM
from multiprocessing import cpu_count
from platform import uname
from threading import Event, Thread
from time import time
import dbus
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib from gi.repository import GLib
from mmio import MMIO, MMIOError from mmio import MMIO, MMIOError
from multiprocessing import cpu_count
from threading import Event, Thread
SYSFS_POWER_PATH = '/sys/class/power_supply/AC/online'
VOLTAGE_PLANES = {
'CORE': 0,
'GPU': 1,
'CACHE': 2,
'UNCORE': 3,
'ANALOGIO': 4,
}
DEFAULT_SYSFS_POWER_PATH = '/sys/class/power_supply/AC*/online'
VOLTAGE_PLANES = {'CORE': 0, 'GPU': 1, 'CACHE': 2, 'UNCORE': 3, 'ANALOGIO': 4}
CURRENT_PLANES = {'CORE': 0, 'GPU': 1, 'CACHE': 2}
TRIP_TEMP_RANGE = [40, 97] TRIP_TEMP_RANGE = [40, 97]
UNDERVOLT_KEYS = ('UNDERVOLT', 'UNDERVOLT.AC', 'UNDERVOLT.BATTERY')
ICCMAX_KEYS = ('ICCMAX', 'ICCMAX.AC', 'ICCMAX.BATTERY')
power = {'source': None, 'method': 'polling'} power = {'source': None, 'method': 'polling'}
HWP_VALUE = 0x20
HWP_INTERVAL = 60
platform_info_bits = { platform_info_bits = {
'maximum_non_turbo_ratio': [8, 15], 'maximum_non_turbo_ratio': [8, 15],
@@ -40,7 +43,7 @@ platform_info_bits = {
'feature_programmable_tdp_limit': [29, 29], 'feature_programmable_tdp_limit': [29, 29],
'number_of_additional_tdp_profiles': [33, 34], 'number_of_additional_tdp_profiles': [33, 34],
'feature_programmable_temperature_target': [30, 30], 'feature_programmable_temperature_target': [30, 30],
'feature_low_power_mode': [32, 32] 'feature_low_power_mode': [32, 32],
} }
thermal_status_bits = { thermal_status_bits = {
@@ -65,8 +68,20 @@ thermal_status_bits = {
'reading_valid': [31, 31], 'reading_valid': [31, 31],
} }
supported_cpus = {
'Haswell': (0x3C, 0x3F, 0x45, 0x46),
'Broadwell': (0x3D, 0x47, 0x4F, 0x56),
'Skylake': (0x4E, 0x55),
'Skylake-S': (0x5E,),
'Ice Lake': (0x7E,),
'Kaby Lake (R)': (0x8E, 0x9E),
'Coffee Lake': (0x9E,),
'Cannon Lake': (0x66,),
}
class bcolors: class bcolors:
YELLOW = '\033[93m'
GREEN = '\033[92m' GREEN = '\033[92m'
RED = '\033[91m' RED = '\033[91m'
RESET = '\033[0m' RESET = '\033[0m'
@@ -75,6 +90,35 @@ class bcolors:
OK = bcolors.GREEN + bcolors.BOLD + 'OK' + bcolors.RESET OK = bcolors.GREEN + bcolors.BOLD + 'OK' + bcolors.RESET
ERR = bcolors.RED + bcolors.BOLD + 'ERR' + bcolors.RESET ERR = bcolors.RED + bcolors.BOLD + 'ERR' + bcolors.RESET
LIM = bcolors.YELLOW + bcolors.BOLD + 'LIM' + bcolors.RESET
log_history = set()
def log(msg, oneshot=False, end='\n'):
outfile = args.log if args.log else sys.stdout
if msg.strip() not in log_history or oneshot is False:
tstamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
full_msg = '{:s}: {:s}'.format(tstamp, msg) if args.log else msg
print(full_msg, file=outfile, end=end)
log_history.add(msg.strip())
def fatal(msg, code=1, end='\n'):
outfile = args.log if args.log else sys.stderr
tstamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
full_msg = '{:s}: [E] {:s}'.format(tstamp, msg) if args.log else '[E] {:s}'.format(msg)
print(full_msg, file=outfile, end=end)
sys.exit(code)
def warning(msg, oneshot=True, end='\n'):
outfile = args.log if args.log else sys.stderr
if msg.strip() not in log_history or oneshot is False:
tstamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
full_msg = '{:s}: [W] {:s}'.format(tstamp, msg) if args.log else '[W] {:s}'.format(msg)
print(full_msg, file=outfile, end=end)
log_history.add(msg.strip())
def writemsr(msr, val): def writemsr(msr, val):
@@ -83,8 +127,7 @@ def writemsr(msr, val):
try: try:
subprocess.check_call(('modprobe', 'msr')) subprocess.check_call(('modprobe', 'msr'))
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
print('[E] Unable to load the msr module.') fatal('Unable to load the msr module.')
sys.exit(1)
try: try:
for addr in msr_list: for addr in msr_list:
f = os.open(addr, os.O_WRONLY) f = os.open(addr, os.O_WRONLY)
@@ -93,8 +136,10 @@ def writemsr(msr, val):
os.close(f) os.close(f)
except (IOError, OSError) as e: except (IOError, OSError) as e:
if e.errno == EPERM or e.errno == EACCES: if e.errno == EPERM or e.errno == EACCES:
print('[E] Unable to write to MSR. Try to disable Secure Boot.') fatal(
sys.exit(1) 'Unable to write to MSR. Try to disable Secure Boot '
'and check if your kernel does not restrict access to MSR.'
)
else: else:
raise e raise e
@@ -103,15 +148,13 @@ def writemsr(msr, val):
def readmsr(msr, from_bit=0, to_bit=63, cpu=None, flatten=False): def readmsr(msr, from_bit=0, to_bit=63, cpu=None, flatten=False):
assert cpu is None or cpu in range(cpu_count()) assert cpu is None or cpu in range(cpu_count())
if from_bit > to_bit: if from_bit > to_bit:
print('[E] Wrong readmsr bit params') fatal('Wrong readmsr bit params')
sys.exit(1)
msr_list = ['/dev/cpu/{:d}/msr'.format(x) for x in range(cpu_count())] msr_list = ['/dev/cpu/{:d}/msr'.format(x) for x in range(cpu_count())]
if not os.path.exists(msr_list[0]): if not os.path.exists(msr_list[0]):
try: try:
subprocess.check_call(('modprobe', 'msr')) subprocess.check_call(('modprobe', 'msr'))
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
print('[E] Unable to load the msr module.') fatal('Unable to load the msr module.')
sys.exit(1)
try: try:
output = [] output = []
for addr in msr_list: for addr in msr_list:
@@ -125,24 +168,50 @@ def readmsr(msr, from_bit=0, to_bit=63, cpu=None, flatten=False):
return output[cpu] if cpu is not None else output return output[cpu] if cpu is not None else output
except (IOError, OSError) as e: except (IOError, OSError) as e:
if e.errno == EPERM or e.errno == EACCES: if e.errno == EPERM or e.errno == EACCES:
print('[E] Unable to read from MSR. Try to disable Secure Boot.') fatal('Unable to read from MSR. Try to disable Secure Boot.')
sys.exit(1)
else: else:
raise e raise e
def get_value_for_bits(val, from_bit=0, to_bit=63): def get_value_for_bits(val, from_bit=0, to_bit=63):
mask = sum(2**x for x in range(from_bit, to_bit + 1)) mask = sum(2 ** x for x in range(from_bit, to_bit + 1))
return (val & mask) >> from_bit return (val & mask) >> from_bit
def is_on_battery(): def is_on_battery(config):
with open(SYSFS_POWER_PATH) as f: try:
for path in glob.glob(config.get('GENERAL', 'Sysfs_Power_Path', fallback=DEFAULT_SYSFS_POWER_PATH)):
with open(path) as f:
return not bool(int(f.read())) return not bool(int(f.read()))
raise
except:
warning('No valid Sysfs_Power_Path found! Trying upower method #1')
try:
out = subprocess.check_output(('upower', '-i', '/org/freedesktop/UPower/devices/line_power_AC'))
res = re.search(rb'online:\s+(yes|no)', out).group(1).decode().strip()
if res == 'yes':
return False
elif res == 'no':
return True
raise
except:
warning('Trying upower method #2')
try:
out = subprocess.check_output(('upower', '-i', '/org/freedesktop/UPower/devices/battery_BAT0'))
res = re.search(rb'state:\s+(.+)', out).group(1).decode().strip()
if res == 'discharging':
return True
elif res in ('fully-charged', 'charging'):
return False
except:
pass
warning('No valid power detection methods found. Assuming that the system is running on battery power.')
return True
def get_cpu_platform_info(): def get_cpu_platform_info():
features_msr_value = readmsr(0xce, cpu=0) features_msr_value = readmsr(0xCE, cpu=0)
cpu_platform_info = {} cpu_platform_info = {}
for key, value in platform_info_bits.items(): for key, value in platform_info_bits.items():
cpu_platform_info[key] = int(get_value_for_bits(features_msr_value, value[0], value[1])) cpu_platform_info[key] = int(get_value_for_bits(features_msr_value, value[0], value[1]))
@@ -150,41 +219,51 @@ def get_cpu_platform_info():
def get_reset_thermal_status(): def get_reset_thermal_status():
#read thermal status # read thermal status
thermal_status_msr_value = readmsr(0x19c) thermal_status_msr_value = readmsr(0x19C)
thermal_status = [] thermal_status = []
for core in range(cpu_count()): for core in range(cpu_count()):
thermal_status_core = {} thermal_status_core = {}
for key, value in thermal_status_bits.items(): for key, value in thermal_status_bits.items():
thermal_status_core[key] = int(get_value_for_bits(thermal_status_msr_value[core], value[0], value[1])) thermal_status_core[key] = int(get_value_for_bits(thermal_status_msr_value[core], value[0], value[1]))
thermal_status.append(thermal_status_core) thermal_status.append(thermal_status_core)
#reset log bits # reset log bits
writemsr(0x19c, 0) writemsr(0x19C, 0)
return thermal_status return thermal_status
def get_time_unit(): def get_time_unit():
# 0.000977 is the time unit of my CPU # 0.000977 is the time unit of my CPU
# TODO formula might be different for other CPUs # TODO formula might be different for other CPUs
return 1.0 / 2**readmsr(0x606, 16, 19, cpu=0) return 1.0 / 2 ** readmsr(0x606, 16, 19, cpu=0)
def get_power_unit(): def get_power_unit():
# 0.125 is the power unit of my CPU # 0.125 is the power unit of my CPU
# TODO formula might be different for other CPUs # TODO formula might be different for other CPUs
return 1.0 / 2**readmsr(0x606, 0, 3, cpu=0) return 1.0 / 2 ** readmsr(0x606, 0, 3, cpu=0)
def get_critical_temp(): def get_critical_temp():
# the critical temperature for my CPU is 100 'C # the critical temperature for my CPU is 100 'C
return readmsr(0x1a2, 16, 23, cpu=0) return readmsr(0x1A2, 16, 23, cpu=0)
def get_cur_pkg_power_limits():
value = readmsr(0x610, 0, 55, flatten=True)
return {
'PL1': get_value_for_bits(value, 0, 14),
'TW1': get_value_for_bits(value, 17, 23),
'PL2': get_value_for_bits(value, 32, 46),
'TW2': get_value_for_bits(value, 49, 55),
}
def calc_time_window_vars(t): def calc_time_window_vars(t):
time_unit = get_time_unit() time_unit = get_time_unit()
for Y in range(2**5): for Y in range(2 ** 5):
for Z in range(2**2): for Z in range(2 ** 2):
if t <= (2**Y) * (1. + Z / 4.) * time_unit: if t <= (2 ** Y) * (1.0 + Z / 4.0) * time_unit:
return (Y, Z) return (Y, Z)
raise ValueError('Unable to find a good combination!') raise ValueError('Unable to find a good combination!')
@@ -208,19 +287,84 @@ def calc_undervolt_mv(msr_value):
return int(round(offset / 1.024)) return int(round(offset / 1.024))
def get_undervolt(plane=None, convert=False):
planes = [plane] if plane in VOLTAGE_PLANES else VOLTAGE_PLANES
out = {}
for plane in planes:
writemsr(0x150, 0x8000001000000000 | (VOLTAGE_PLANES[plane] << 40))
read_value = readmsr(0x150, flatten=True) & 0xFFFFFFFF
out[plane] = calc_undervolt_mv(read_value) if convert else read_value
return out
def undervolt(config): def undervolt(config):
for plane in VOLTAGE_PLANES: for plane in VOLTAGE_PLANES:
write_offset_mv = config.getfloat('UNDERVOLT', plane) write_offset_mv = config.getfloat(
'UNDERVOLT.{:s}'.format(power['source']), plane, fallback=config.getfloat('UNDERVOLT', plane, fallback=0.0)
)
write_value = calc_undervolt_msr(plane, write_offset_mv) write_value = calc_undervolt_msr(plane, write_offset_mv)
writemsr(0x150, write_value) writemsr(0x150, write_value)
if args.debug: if args.debug:
write_value &= 0xFFFFFFFF write_value &= 0xFFFFFFFF
writemsr(0x150, 0x8000001000000000 | (VOLTAGE_PLANES[plane] << 40)) read_value = get_undervolt(plane)[plane]
read_value = readmsr(0x150, flatten=True)
read_offset_mv = calc_undervolt_mv(read_value) read_offset_mv = calc_undervolt_mv(read_value)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print('[D] Undervolt plane {:s} - write {:.0f} mV ({:#x}) - read {:.0f} mV ({:#x}) - match {}'.format( log(
plane, write_offset_mv, write_value, read_offset_mv, read_value, match)) '[D] Undervolt plane {:s} - write {:.0f} mV ({:#x}) - read {:.0f} mV ({:#x}) - match {}'.format(
plane, write_offset_mv, write_value, read_offset_mv, read_value, match
)
)
def calc_icc_max_msr(plane, current):
"""Return the value to be written in the MSR 150h for setting the given
IccMax (in A) to the given current plane.
"""
assert 0 < current <= 0x3FF
assert plane in CURRENT_PLANES
current = int(round(current * 4))
return 0x8000001700000000 | (CURRENT_PLANES[plane] << 40) | current
def calc_icc_max_amp(msr_value):
"""Return the max current (in A) from the given raw MSR 150h value.
"""
return (msr_value & 0x3FF) / 4.0
def get_icc_max(plane=None, convert=False):
planes = [plane] if plane in CURRENT_PLANES else CURRENT_PLANES
out = {}
for plane in planes:
writemsr(0x150, 0x8000001600000000 | (CURRENT_PLANES[plane] << 40))
read_value = readmsr(0x150, flatten=True) & 0x3FF
out[plane] = calc_icc_max_amp(read_value) if convert else read_value
return out
def set_icc_max(config):
for plane in CURRENT_PLANES:
try:
write_current_amp = config.getfloat(
'ICCMAX.{:s}'.format(power['source']), plane, fallback=config.getfloat('ICCMAX', plane, fallback=-1.0)
)
if write_current_amp > 0:
write_value = calc_icc_max_msr(plane, write_current_amp)
writemsr(0x150, write_value)
if args.debug:
write_value &= 0x3FF
read_value = get_icc_max(plane)[plane]
read_current_A = calc_icc_max_amp(read_value)
match = OK if write_value == read_value else ERR
log(
'[D] IccMax plane {:s} - write {:.2f} A ({:#x}) - read {:.2f} A ({:#x}) - match {}'.format(
plane, write_current_amp, write_value, read_current_A, read_value, match
)
)
except (configparser.NoSectionError, configparser.NoOptionError):
pass
def load_config(): def load_config():
@@ -229,29 +373,72 @@ def load_config():
# config values sanity check # config values sanity check
for power_source in ('AC', 'BATTERY'): for power_source in ('AC', 'BATTERY'):
for option in ( for option in ('Update_Rate_s', 'PL1_Tdp_W', 'PL1_Duration_s', 'PL2_Tdp_W', 'PL2_Duration_S'):
'Update_Rate_s', value = config.getfloat(power_source, option, fallback=None)
'PL1_Tdp_W', if value is not None:
'PL1_Duration_s', value = config.set(power_source, option, str(max(0.001, value)))
'PL2_Tdp_W', elif option == 'Update_Rate_s':
'PL2_Duration_S', fatal('The mandatory "Update_Rate_s" parameter is missing.')
):
config.set(power_source, option, str(max(0.1, config.getfloat(power_source, option))))
trip_temp = config.getfloat(power_source, 'Trip_Temp_C') trip_temp = config.getfloat(power_source, 'Trip_Temp_C', fallback=None)
if trip_temp is not None:
valid_trip_temp = min(TRIP_TEMP_RANGE[1], max(TRIP_TEMP_RANGE[0], trip_temp)) valid_trip_temp = min(TRIP_TEMP_RANGE[1], max(TRIP_TEMP_RANGE[0], trip_temp))
if trip_temp != valid_trip_temp: if trip_temp != valid_trip_temp:
config.set(power_source, 'Trip_Temp_C', str(valid_trip_temp)) config.set(power_source, 'Trip_Temp_C', str(valid_trip_temp))
print('[!] Overriding invalid "Trip_Temp_C" value in "{:s}": {:.1f} -> {:.1f}'.format( log(
power_source, trip_temp, valid_trip_temp)) '[!] Overriding invalid "Trip_Temp_C" value in "{:s}": {:.1f} -> {:.1f}'.format(
power_source, trip_temp, valid_trip_temp
)
)
# fix any invalid value (ie. > 0) in the undervolt settings
for key in UNDERVOLT_KEYS:
for plane in VOLTAGE_PLANES: for plane in VOLTAGE_PLANES:
value = config.getfloat('UNDERVOLT', plane) if key in config:
value = config.getfloat(key, plane)
valid_value = min(0, value) valid_value = min(0, value)
if value != valid_value: if value != valid_value:
config.set('UNDERVOLT', plane, str(valid_value)) config.set(key, plane, str(valid_value))
print('[!] Overriding invalid "UNDERVOLT" value in "{:s}" voltage plane: {:.0f} -> {:.0f}'.format( log(
plane, value, valid_value)) '[!] Overriding invalid "{:s}" value in "{:s}" voltage plane: {:.0f} -> {:.0f}'.format(
key, plane, value, valid_value
)
)
# handle the case where only one of UNDERVOLT.AC, UNDERVOLT.BATTERY keys exists
# by forcing the other key to all zeros (ie. no undervolt)
if any(key in config for key in UNDERVOLT_KEYS[1:]):
for key in UNDERVOLT_KEYS[1:]:
if key not in config:
config.add_section(key)
for plane in VOLTAGE_PLANES:
value = config.getfloat(key, plane, fallback=0.0)
config.set(key, plane, str(value))
# Check for CORE/CACHE values mismatch
for key in UNDERVOLT_KEYS:
if key in config:
if config.getfloat(key, 'CORE', fallback=0) != config.getfloat(key, 'CACHE', fallback=0):
warning('On Skylake and newer CPUs CORE and CACHE values should match!')
break
iccmax_enabled = False
# check for invalid values (ie. <= 0 or > 0x3FF) in the IccMax settings
for key in ICCMAX_KEYS:
for plane in CURRENT_PLANES:
if key in config:
try:
value = config.getfloat(key, plane)
if value <= 0 or value >= 0x3FF:
raise ValueError
iccmax_enabled = True
except ValueError:
warning('Invalid value for {:s} in {:s}'.format(plane, key), oneshot=False)
config.remove_option(key, plane)
except configparser.NoOptionError:
pass
if iccmax_enabled:
warning('Warning! Raising IccMax above design limits can damage your system!')
return config return config
@@ -260,7 +447,7 @@ def calc_reg_values(platform_info, config):
regs = defaultdict(dict) regs = defaultdict(dict)
for power_source in ('AC', 'BATTERY'): for power_source in ('AC', 'BATTERY'):
if platform_info['feature_programmable_temperature_target'] != 1: if platform_info['feature_programmable_temperature_target'] != 1:
print("[W] Setting temperature target is not supported by this CPU") warning("Setting temperature target is not supported by this CPU")
else: else:
# the critical temperature for my CPU is 100 'C # the critical temperature for my CPU is 100 'C
critical_temp = get_critical_temp() critical_temp = get_critical_temp()
@@ -268,88 +455,137 @@ def calc_reg_values(platform_info, config):
global TRIP_TEMP_RANGE global TRIP_TEMP_RANGE
TRIP_TEMP_RANGE[1] = min(TRIP_TEMP_RANGE[1], critical_temp - 3) TRIP_TEMP_RANGE[1] = min(TRIP_TEMP_RANGE[1], critical_temp - 3)
trip_offset = int(round(critical_temp - config.getfloat(power_source, 'Trip_Temp_C'))) Trip_Temp_C = config.getfloat(power_source, 'Trip_Temp_C', fallback=None)
if Trip_Temp_C is not None:
trip_offset = int(round(critical_temp - Trip_Temp_C))
regs[power_source]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24 regs[power_source]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24
else:
log('[I] {:s} trip temperature is disabled in config.'.format(power_source))
power_unit = get_power_unit() power_unit = get_power_unit()
PL1 = int(round(config.getfloat(power_source, 'PL1_Tdp_W') / power_unit))
Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL1_Duration_s')) PL1_Tdp_W = config.getfloat(power_source, 'PL1_Tdp_W', fallback=None)
PL1_Duration_s = config.getfloat(power_source, 'PL1_Duration_s', fallback=None)
PL2_Tdp_W = config.getfloat(power_source, 'PL2_Tdp_W', fallback=None)
PL2_Duration_s = config.getfloat(power_source, 'PL2_Duration_s', fallback=None)
if (PL1_Tdp_W, PL1_Duration_s, PL2_Tdp_W, PL2_Duration_s).count(None) < 4:
cur_pkg_power_limits = get_cur_pkg_power_limits()
if PL1_Tdp_W is None:
PL1 = cur_pkg_power_limits['PL1']
log('[I] {:s} PL1_Tdp_W disabled in config.'.format(power_source))
else:
PL1 = int(round(PL1_Tdp_W / power_unit))
if PL1_Duration_s is None:
TW1 = cur_pkg_power_limits['TW1']
log('[I] {:s} PL1_Duration_s disabled in config.'.format(power_source))
else:
Y, Z = calc_time_window_vars(PL1_Duration_s)
TW1 = Y | (Z << 5) TW1 = Y | (Z << 5)
PL2 = int(round(config.getfloat(power_source, 'PL2_Tdp_W') / power_unit)) if PL2_Tdp_W is None:
Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL2_Duration_s')) PL2 = cur_pkg_power_limits['PL2']
log('[I] {:s} PL2_Tdp_W disabled in config.'.format(power_source))
else:
PL2 = int(round(PL2_Tdp_W / power_unit))
if PL2_Duration_s is None:
TW2 = cur_pkg_power_limits['TW2']
log('[I] {:s} PL2_Duration_s disabled in config.'.format(power_source))
else:
Y, Z = calc_time_window_vars(PL2_Duration_s)
TW2 = Y | (Z << 5) TW2 = Y | (Z << 5)
regs[power_source]['MSR_PKG_POWER_LIMIT'] = PL1 | (1 << 15) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | ( regs[power_source]['MSR_PKG_POWER_LIMIT'] = (
TW2 << 49) PL1 | (1 << 15) | (1 << 16) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (TW2 << 49)
)
else:
log('[I] {:s} package power limits are disabled in config.'.format(power_source))
# cTDP # cTDP
c_tdp_target_value = config.getint(power_source, 'cTDP', fallback=None) c_tdp_target_value = config.getint(power_source, 'cTDP', fallback=None)
if c_tdp_target_value is not None: if c_tdp_target_value is not None:
if platform_info['feature_programmable_tdp_limit'] != 1: if platform_info['feature_programmable_tdp_limit'] != 1:
print("[W] cTDP setting not supported by this CPU") log("[W] cTDP setting not supported by this CPU")
elif platform_info['number_of_additional_tdp_profiles'] < c_tdp_target_value: elif platform_info['number_of_additional_tdp_profiles'] < c_tdp_target_value:
print("[W] the configured cTDP profile is not supported by this CPU") log("[W] the configured cTDP profile is not supported by this CPU")
else: else:
valid_c_tdp_target_value = max(0, c_tdp_target_value) valid_c_tdp_target_value = max(0, c_tdp_target_value)
regs[power_source]['MSR_CONFIG_TDP_CONTROL'] = valid_c_tdp_target_value regs[power_source]['MSR_CONFIG_TDP_CONTROL'] = valid_c_tdp_target_value
return regs return regs
def set_hwp(pref): def set_hwp():
# set HWP energy performance hints # set HWP energy performance preference
assert pref in ('performance', 'balance_performance', 'default', 'balance_power', 'power') cur_val = readmsr(0x774, cpu=0)
CPUs = [ new_val = (cur_val & 0xFFFFFFFF00FFFFFF) | (HWP_VALUE << 24)
'/sys/devices/system/cpu/cpu{:d}/cpufreq/energy_performance_preference'.format(x) for x in range(cpu_count())
] writemsr(0x774, new_val)
for i, c in enumerate(CPUs):
with open(c, 'wb') as f:
f.write(pref.encode())
if args.debug: if args.debug:
with open(c) as f: read_value = readmsr(0x774, from_bit=24, to_bit=31)[0]
read_value = f.read().strip() match = OK if HWP_VALUE == read_value else ERR
match = OK if pref == read_value else ERR log('[D] HWP - write "{:#02x}" - read "{:#02x}" - match {}'.format(HWP_VALUE, read_value, match))
print('[D] HWP for cpu{:d} - write "{:s}" - read "{:s}" - match {}'.format(i, pref, read_value, match))
def set_disable_bdprochot():
# Disable BDPROCHOT
cur_val = readmsr(0x1FC,flatten=True)
new_val = (cur_val & 0xFFFFFFFFFFFFFFFE)
writemsr(0x1FC, new_val)
if args.debug:
read_value = readmsr(0x1FC, from_bit=31, to_bit=31)[0]
match = OK if ~read_value else ERR
log('[D] BDPROCHOT - write "{:#02x}" - read "{:#02x}" - match {}'.format(0, read_value, match))
def power_thread(config, regs, exit_event): def power_thread(config, regs, exit_event):
try: try:
mchbar_mmio = MMIO(0xfed159a0, 8) mchbar_mmio = MMIO(0xFED159A0, 8)
except MMIOError: except MMIOError:
print('[E] Unable to open /dev/mem. Try to disable Secure Boot.') warning('Unable to open /dev/mem. TDP override might not work correctly.')
sys.exit(1) warning('Try to disable Secure Boot and/or enable CONFIG_DEVMEM in kernel config.')
mchbar_mmio = None
next_hwp_write = 0
while not exit_event.is_set(): while not exit_event.is_set():
#print thermal status # log thermal status
if args.debug: if args.debug:
thermal_status = get_reset_thermal_status() thermal_status = get_reset_thermal_status()
for index, core_thermal_status in enumerate(thermal_status): for index, core_thermal_status in enumerate(thermal_status):
for key, value in core_thermal_status.items(): for key, value in core_thermal_status.items():
print('[D] core {} thermal status: {} = {}'.format(index, key.replace("_", " "), value)) log('[D] core {} thermal status: {} = {}'.format(index, key.replace("_", " "), value))
# switch back to sysfs polling # switch back to sysfs polling
if power['method'] == 'polling': if power['method'] == 'polling':
power['source'] = 'BATTERY' if is_on_battery() else 'AC' power['source'] = 'BATTERY' if is_on_battery(config) else 'AC'
# set temperature trip point # set temperature trip point
if 'MSR_TEMPERATURE_TARGET' in regs[power['source']]: if 'MSR_TEMPERATURE_TARGET' in regs[power['source']]:
write_value = regs[power['source']]['MSR_TEMPERATURE_TARGET'] write_value = regs[power['source']]['MSR_TEMPERATURE_TARGET']
writemsr(0x1a2, write_value) writemsr(0x1A2, write_value)
if args.debug: if args.debug:
read_value = readmsr(0x1a2, 24, 29, flatten=True) read_value = readmsr(0x1A2, 24, 29, flatten=True)
match = OK if write_value >> 24 == read_value else ERR match = OK if write_value >> 24 == read_value else ERR
print('[D] TEMPERATURE_TARGET - write {:#x} - read {:#x} - match {}'.format( log(
write_value >> 24, read_value, match)) '[D] TEMPERATURE_TARGET - write {:#x} - read {:#x} - match {}'.format(
write_value >> 24, read_value, match
)
)
# set cTDP # set cTDP
if 'MSR_CONFIG_TDP_CONTROL' in regs[power['source']]: if 'MSR_CONFIG_TDP_CONTROL' in regs[power['source']]:
write_value = regs[power['source']]['MSR_CONFIG_TDP_CONTROL'] write_value = regs[power['source']]['MSR_CONFIG_TDP_CONTROL']
writemsr(0x64b, write_value) writemsr(0x64B, write_value)
if args.debug: if args.debug:
read_value = readmsr(0x64b, 0, 1, flatten=True) read_value = readmsr(0x64B, 0, 1, flatten=True)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print('[D] CONFIG_TDP_CONTROL - write {:#x} - read {:#x} - match {}'.format( log(
write_value, read_value, match)) '[D] CONFIG_TDP_CONTROL - write {:#x} - read {:#x} - match {}'.format(
write_value, read_value, match
)
)
# set PL1/2 on MSR # set PL1/2 on MSR
write_value = regs[power['source']]['MSR_PKG_POWER_LIMIT'] write_value = regs[power['source']]['MSR_PKG_POWER_LIMIT']
@@ -357,49 +593,197 @@ def power_thread(config, regs, exit_event):
if args.debug: if args.debug:
read_value = readmsr(0x610, 0, 55, flatten=True) read_value = readmsr(0x610, 0, 55, flatten=True)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print('[D] MSR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format( log(
write_value, read_value, match)) '[D] MSR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format(
write_value, read_value, match
)
)
if mchbar_mmio is not None:
# set MCHBAR register to the same PL1/2 values # set MCHBAR register to the same PL1/2 values
mchbar_mmio.write32(0, write_value & 0xffffffff) mchbar_mmio.write32(0, write_value & 0xFFFFFFFF)
mchbar_mmio.write32(4, write_value >> 32) mchbar_mmio.write32(4, write_value >> 32)
if args.debug: if args.debug:
read_value = mchbar_mmio.read32(0) | (mchbar_mmio.read32(4) << 32) read_value = mchbar_mmio.read32(0) | (mchbar_mmio.read32(4) << 32)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print('[D] MCHBAR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format( log(
write_value, read_value, match)) '[D] MCHBAR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format(
write_value, read_value, match
)
)
# Disable BDPROCHOT
disable_bdprochot = config.getboolean(power['source'], 'Disable_BDPROCHOT', fallback=None)
if disable_bdprochot is not None:
set_disable_bdprochot()
wait_t = config.getfloat(power['source'], 'Update_Rate_s') wait_t = config.getfloat(power['source'], 'Update_Rate_s')
enable_hwp_mode = config.getboolean('AC', 'HWP_Mode', fallback=False) enable_hwp_mode = config.getboolean('AC', 'HWP_Mode', fallback=False)
if power['source'] == 'AC' and enable_hwp_mode: # set HWP less frequently. Just to be safe since (e.g.) TLP might reset this value
cpu_usage = float(psutil.cpu_percent(interval=wait_t)) if (
# set full performance mode only when load is greater than this threshold (~ at least 1 core full speed) enable_hwp_mode
performance_mode = cpu_usage > 100. / (cpu_count() * 1.25) and next_hwp_write <= time()
# check again if we are on AC, since in the meantime we might have switched to BATTERY and (
if not is_on_battery(): (power['method'] == 'dbus' and power['source'] == 'AC')
set_hwp('performance' if performance_mode else 'balance_performance') or (power['method'] == 'polling' and not is_on_battery(config))
)
):
set_hwp()
next_hwp_write = time() + HWP_INTERVAL
else: else:
exit_event.wait(wait_t) exit_event.wait(wait_t)
def check_kernel():
if os.geteuid() != 0:
fatal('No root no party. Try again with sudo.')
kernel_config = None
try:
with open(os.path.join('/boot', 'config-{:s}'.format(uname()[2]))) as f:
kernel_config = f.read()
except IOError:
config_gz_path = os.path.join('/proc', 'config.gz')
try:
if not os.path.isfile(config_gz_path):
subprocess.check_call(('modprobe', 'configs'))
with gzip.open(config_gz_path) as f:
kernel_config = f.read().decode()
except (subprocess.CalledProcessError, IOError):
pass
if kernel_config is None:
log('[W] Unable to obtain and validate kernel config.')
return
elif not re.search('CONFIG_DEVMEM=y', kernel_config):
warning('Bad kernel config: you need CONFIG_DEVMEM=y.')
if not re.search('CONFIG_X86_MSR=(y|m)', kernel_config):
fatal('Bad kernel config: you need CONFIG_X86_MSR builtin or as module.')
def check_cpu():
try:
with open('/proc/cpuinfo') as f:
cpuinfo = {}
for row in f.readlines():
try:
key, value = map(lambda x: x.strip(), row.split(':'))
if key == 'processor' and value == '1':
break
try:
cpuinfo[key] = int(value, 0)
except ValueError:
cpuinfo[key] = value
except ValueError:
pass
if cpuinfo['vendor_id'] != 'GenuineIntel':
fatal('This tool is designed for Intel CPUs only.')
cpu_model = None
for model in supported_cpus:
if cpuinfo['model'] in supported_cpus[model]:
cpu_model = model
break
if cpuinfo['cpu family'] != 6 or cpu_model is None:
fatal('Your CPU model is not supported.')
log('[I] Detected CPU architecture: Intel {:s}'.format(cpu_model))
except:
fatal('Unable to identify CPU model.')
def monitor(exit_event, wait):
IA32_THERM_STATUS = 0x19C
IA32_PERF_STATUS = 0x198
MSR_RAPL_POWER_UNIT = 0x606
MSR_INTEL_PKG_ENERGY_STATUS = 0x611
MSR_PP1_ENERGY_STATUS = 0x641
MSR_DRAM_ENERGY_STATUS = 0x619
wait = max(0.1, wait)
rapl_power_unit = 0.5 ** readmsr(MSR_RAPL_POWER_UNIT, from_bit=8, to_bit=12, cpu=0)
power_plane_msr = {
'Package': MSR_INTEL_PKG_ENERGY_STATUS,
'Graphics': MSR_PP1_ENERGY_STATUS,
'DRAM': MSR_DRAM_ENERGY_STATUS,
}
prev_energy = {
'Package': (readmsr(MSR_INTEL_PKG_ENERGY_STATUS, cpu=0) * rapl_power_unit, time()),
'Graphics': (readmsr(MSR_PP1_ENERGY_STATUS, cpu=0) * rapl_power_unit, time()),
'DRAM': (readmsr(MSR_DRAM_ENERGY_STATUS, cpu=0) * rapl_power_unit, time()),
}
undervolt_values = get_undervolt(convert=True)
undervolt_output = ' | '.join('{:s}: {:.2f} mV'.format(plane, undervolt_values[plane]) for plane in VOLTAGE_PLANES)
log('[D] Undervolt offsets: {:s}'.format(undervolt_output))
iccmax_values = get_icc_max(convert=True)
iccmax_output = ' | '.join('{:s}: {:.2f} A'.format(plane, iccmax_values[plane]) for plane in CURRENT_PLANES)
log('[D] IccMax: {:s}'.format(iccmax_output))
log('[D] Realtime monitoring of throttling causes:\n')
while not exit_event.is_set():
value = readmsr(IA32_THERM_STATUS, from_bit=0, to_bit=15, cpu=0)
offsets = {'Thermal': 0, 'Power': 10, 'Current': 12, 'Cross-domain (e.g. GPU)': 14}
output = ('{:s}: {:s}'.format(cause, LIM if bool((value >> offsets[cause]) & 1) else OK) for cause in offsets)
# ugly code, just testing...
vcore = readmsr(IA32_PERF_STATUS, from_bit=32, to_bit=47, cpu=0) / (2.0 ** 13) * 1000
stats2 = {'VCore': '{:.0f} mV'.format(vcore)}
for power_plane in ('Package', 'Graphics', 'DRAM'):
energy_j = readmsr(power_plane_msr[power_plane], cpu=0) * rapl_power_unit
now = time()
prev_energy[power_plane], energy_w = (
(energy_j, now),
(energy_j - prev_energy[power_plane][0]) / (now - prev_energy[power_plane][1]),
)
stats2[power_plane] = '{:.1f} W'.format(energy_w)
output2 = ('{:s}: {:s}'.format(label, stats2[label]) for label in stats2)
terminator = '\n' if args.log else '\r'
log(
'[{}] {} || {}{}'.format(power['source'], ' - '.join(output), ' - '.join(output2), ' ' * 10),
end=terminator,
)
exit_event.wait(wait)
def main(): def main():
global args global args
if os.geteuid() != 0:
print('[E] No root no party. Try again with sudo.')
sys.exit(1)
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true', help='add some debug info and additional checks') exclusive_group = parser.add_mutually_exclusive_group()
exclusive_group.add_argument('--debug', action='store_true', help='add some debug info and additional checks')
exclusive_group.add_argument(
'--monitor',
metavar='update_rate',
const=1.0,
type=float,
nargs='?',
help='realtime monitoring of throttling causes (default 1s)',
)
parser.add_argument('--config', default='/etc/lenovo_fix.conf', help='override default config file path') parser.add_argument('--config', default='/etc/lenovo_fix.conf', help='override default config file path')
parser.add_argument('--force', action='store_true', help='bypass compatibility checks (EXPERTS only)')
parser.add_argument('--log', metavar='/path/to/file', help='log to file instead of stdout')
args = parser.parse_args() args = parser.parse_args()
power['source'] = 'BATTERY' if is_on_battery() else 'AC' if args.log:
try:
args.log = open(args.log, 'w')
except:
args.log = None
fatal('Unable to write to the log file!')
if not args.force:
check_kernel()
check_cpu()
log('[I] Loading config file.')
config = load_config() config = load_config()
power['source'] = 'BATTERY' if is_on_battery(config) else 'AC'
platform_info = get_cpu_platform_info() platform_info = get_cpu_platform_info()
if args.debug: if args.debug:
for key, value in platform_info.items(): for key, value in platform_info.items():
print('[D] cpu platform info: {} = {}'.format(key.replace("_", " "), value)) log('[D] cpu platform info: {} = {}'.format(key.replace("_", " "), value))
regs = calc_reg_values(platform_info, config) regs = calc_reg_values(platform_info, config)
if not config.getboolean('GENERAL', 'Enabled'): if not config.getboolean('GENERAL', 'Enabled'):
@@ -411,11 +795,13 @@ def main():
thread.start() thread.start()
undervolt(config) undervolt(config)
set_icc_max(config)
# handle dbus events for applying undervolt on resume from sleep/hybernate # handle dbus events for applying undervolt/IccMax on resume from sleep/hybernate
def handle_sleep_callback(sleeping): def handle_sleep_callback(sleeping):
if not sleeping: if not sleeping:
undervolt(config) undervolt(config)
set_icc_max(config)
def handle_ac_callback(*args): def handle_ac_callback(*args):
try: try:
@@ -427,15 +813,26 @@ def main():
DBusGMainLoop(set_as_default=True) DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus() bus = dbus.SystemBus()
# add dbus receiver only if undervolt is enabled in config # add dbus receiver only if undervolt/IccMax is enabled in config
if any(config.getfloat('UNDERVOLT', plane) != 0 for plane in VOLTAGE_PLANES): if any(
bus.add_signal_receiver(handle_sleep_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', config.getfloat(key, plane, fallback=0) != 0 for plane in VOLTAGE_PLANES for key in UNDERVOLT_KEYS + ICCMAX_KEYS
'org.freedesktop.login1') ):
bus.add_signal_receiver(
handle_sleep_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1'
)
bus.add_signal_receiver( bus.add_signal_receiver(
handle_ac_callback, handle_ac_callback,
signal_name="PropertiesChanged", signal_name="PropertiesChanged",
dbus_interface="org.freedesktop.DBus.Properties", dbus_interface="org.freedesktop.DBus.Properties",
path="/org/freedesktop/UPower/devices/line_power_AC") path="/org/freedesktop/UPower/devices/line_power_AC",
)
log('[I] Starting main loop.')
if args.monitor is not None:
monitor_thread = Thread(target=monitor, args=(exit_event, args.monitor))
monitor_thread.daemon = True
monitor_thread.start()
try: try:
loop = GLib.MainLoop() loop = GLib.MainLoop()
@@ -446,6 +843,8 @@ def main():
exit_event.set() exit_event.set()
loop.quit() loop.quit()
thread.join(timeout=1) thread.join(timeout=1)
if args.monitor is not None:
monitor_thread.join(timeout=0.1)
if __name__ == '__main__': if __name__ == '__main__':

18
pyproject.toml Normal file
View File

@@ -0,0 +1,18 @@
[tool.black]
line-length = 120
skip-string-normalization = true
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| venv
| _build
| buck-out
| build
| dist
)/
'''

View File

@@ -1,4 +1,3 @@
configparser==3.5.0 configparser==3.8.1
dbus-python==1.2.8 dbus-python==1.2.8
psutil==5.4.6 PyGObject==3.32.2
PyGObject==3.28.3

2
runit/lenovo_fix/run Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
PYTHONUNBUFFERED=1 exec /opt/lenovo_fix/venv/bin/python3 /opt/lenovo_fix/lenovo_fix.py

View File

@@ -4,6 +4,8 @@ Description=Stop Intel throttling
[Service] [Service]
Type=simple Type=simple
ExecStart=/opt/lenovo_fix/venv/bin/python3 /opt/lenovo_fix/lenovo_fix.py ExecStart=/opt/lenovo_fix/venv/bin/python3 /opt/lenovo_fix/lenovo_fix.py
# Setting PYTHONUNBUFFERED is necessary to see the output of this service in the journal
Environment=PYTHONUNBUFFERED=1
StandardOutput=syslog StandardOutput=syslog
StandardError=syslog StandardError=syslog

74
throttled.spec Normal file
View File

@@ -0,0 +1,74 @@
%global _hardened_build 1
%define debug_package %{nil}
Name: throttled
Version: 0.6
Release: 2
Summary: Workaround for Intel throttling issues in Linux
License: MIT
URL: https://github.com/erpalma/throttled
Source0: https://github.com/erpalma/throttled/archive/v%{version}.tar.gz
BuildRequires: python3-devel
BuildRequires: systemd-units
Requires: python3
Requires: python3-gobject
Requires: python3-configparser
Requires: systemd
Conflicts: thermald
%description
This tool was originally developed to fix Linux CPU throttling issues affecting
Lenovo T480 / T480s / X1C6.
The CPU package power limit (PL1/2) is forced to a value of 44 W (29 W on
battery) and the temperature trip point to 95 'C (85 'C on battery) by
overriding default values in MSR and MCHBAR every 5 seconds (30 on battery) to
block the Embedded Controller from resetting these values to default.
%prep
%autosetup
%build
%install
install -D lenovo_fix.py %{buildroot}/%{_bindir}/%{name}
install -D mmio.py %{buildroot}/%{python3_sitelib}/mmio.py
install -D etc/lenovo_fix.conf %{buildroot}/%{_sysconfdir}/%{name}.conf
install -D systemd/lenovo_fix.service %{buildroot}/%{_unitdir}/%{name}.service
%post
%systemd_post %{name}.service
%preun
%systemd_preun %{name}.service
%postun
%systemd_postun_with_restart %{name}.service
%files
%defattr(-,root,root,-)
%attr(755, root, root) %{_bindir}/%{name}
%attr(644, root, root) %{python3_sitelib}/mmio.py
%attr(644, root, root) %{python3_sitelib}/__pycache__/*
%config(noreplace) %attr(640, %{name}, %{name}) %{_sysconfdir}/%{name}.conf
%attr(644, root, root) %{_unitdir}/%{name}.service
%changelog
* Thu May 02 2019 Arun Babu Neelicattu <arun.neelicattu@twyla.ai> - 0.6-1
- Upgrade to 0.6
* Mon Mar 11 2019 Arun Babu Neelicattu <arun.neelicattu@gmail.com> - 0.5-4
- Add conflict for thermald
* Mon Mar 11 2019 Arun Babu Neelicattu <arun.neelicattu@gmail.com> - 0.5-3
- Fix unit file to use configuration
* Mon Mar 11 2019 Arun Babu Neelicattu <arun.neelicattu@gmail.com> - 0.5-2
- Add default configuration file
- Fix file permissions
* Mon Mar 11 2019 Arun Babu Neelicattu <arun.neelicattu@gmail.com> - 0.5-1
- Initial release of version 0.5