Compare commits
127 Commits
issue_temp
...
c99b45f9d4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c99b45f9d4 | ||
|
|
88216b119a | ||
|
|
d5ee6de8eb | ||
|
|
e897f95385 | ||
|
|
2bd4626d8a | ||
|
|
8300044558 | ||
|
|
5fc826aac0 | ||
|
|
a88689b8bb | ||
|
|
c0b8c180bf | ||
|
|
422769aafd | ||
|
|
9cb8537425 | ||
|
|
fbcbbb93c7 | ||
|
|
232fcc3882 | ||
|
|
a6026e8bb4 | ||
|
|
8d8096a3b3 | ||
|
|
1cb4fd7ae0 | ||
|
|
0b10acf5f6 | ||
|
|
49622cd79b | ||
|
|
919b61f3c9 | ||
|
|
b1d887c167 | ||
|
|
d118ddc0a3 | ||
|
|
3cf2f1aa0f | ||
|
|
f2ee9ed38d | ||
|
|
b5e52c03f4 | ||
|
|
dd115d3ca8 | ||
|
|
b7eeeb7842 | ||
|
|
e9ec389f5e | ||
|
|
763e948882 | ||
|
|
b49d1b39d4 | ||
|
|
64627f1af5 | ||
|
|
67f0423f30 | ||
|
|
c55e54d1a5 | ||
|
|
c3e052ce45 | ||
|
|
e9e0db9533 | ||
|
|
69b45ff9b3 | ||
|
|
f024e840f4 | ||
|
|
c89c12475a | ||
|
|
f155eea88a | ||
|
|
ba20b714c2 | ||
|
|
6e54199a2b | ||
|
|
d276d55513 | ||
|
|
f68bfd2a02 | ||
|
|
d40f53969d | ||
|
|
862971156e | ||
|
|
1008af57c9 | ||
|
|
2b73d324af | ||
|
|
fe93e48c8e | ||
|
|
95aa5b1e02 | ||
|
|
952128dc21 | ||
|
|
74937fdee5 | ||
|
|
6d70e21985 | ||
|
|
5486651b93 | ||
|
|
d63dfc695e | ||
|
|
430032aec8 | ||
|
|
ab6047dfcf | ||
|
|
6302933e80 | ||
|
|
be756a1f86 | ||
|
|
4d64bfb8ae | ||
|
|
644c0982e4 | ||
|
|
33f0412bea | ||
|
|
52d83c41bb | ||
|
|
e588ade2ef | ||
|
|
11b5f12f9e | ||
|
|
f585babd71 | ||
|
|
75ad31da7a | ||
|
|
9d1d079d10 | ||
|
|
db1b16cdbf | ||
|
|
56a91aaf6f | ||
|
|
7ac672dd7d | ||
|
|
2ecbf9f972 | ||
|
|
25eabce346 | ||
|
|
4972b29a19 | ||
|
|
f117fcb1fe | ||
|
|
f9c7f51549 | ||
|
|
d0f7c152af | ||
|
|
5ce7af6463 | ||
|
|
d2361431cb | ||
|
|
c7e292e282 | ||
|
|
668f023868 | ||
|
|
d18ad0e07f | ||
|
|
99c0d7cda2 | ||
|
|
d33afeb933 | ||
|
|
05e88b827b | ||
|
|
a5575182e2 | ||
|
|
6c4f3a77a5 | ||
|
|
7d9fd63ef2 | ||
|
|
d19c686b27 | ||
|
|
873e016006 | ||
|
|
542d2a5ee0 | ||
|
|
6209e93963 | ||
|
|
5c16e35ca6 | ||
|
|
003278cafd | ||
|
|
5a96b88944 | ||
|
|
0189170a40 | ||
|
|
671e252198 | ||
|
|
b15edc8076 | ||
|
|
8677de6f96 | ||
|
|
938482654c | ||
|
|
57faf388e9 | ||
|
|
9636a5de83 | ||
|
|
89e3ba8990 | ||
|
|
105f87c0ff | ||
|
|
76e097663c | ||
|
|
c1f5f95c37 | ||
|
|
b8ef7b72e5 | ||
|
|
73df1ff98e | ||
|
|
4914021253 | ||
|
|
79f1db481c | ||
|
|
ce4a75c539 | ||
|
|
b571aefa3b | ||
|
|
0f10e10ce0 | ||
|
|
678aba3f88 | ||
|
|
963fb1c75d | ||
|
|
816ea27e03 | ||
|
|
9cdc36406e | ||
|
|
cf3cea5997 | ||
|
|
ff0f912a86 | ||
|
|
301a57aa0f | ||
|
|
d009138d22 | ||
|
|
5c96274e52 | ||
|
|
f10c269c9a | ||
|
|
2792cac017 | ||
|
|
49c056e311 | ||
|
|
8ef50d57ce | ||
|
|
6516f44621 | ||
|
|
494ea7f595 | ||
|
|
dcfa39acb8 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -29,7 +29,7 @@ wheels/
|
||||
# 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.
|
||||
*.manifest
|
||||
*.spec
|
||||
#*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
185
README.md
185
README.md
@@ -1,31 +1,32 @@
|
||||
# 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.
|
||||
|
||||
### 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!
|
||||
|
||||
### 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)
|
||||
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)
|
||||
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,37 @@ On a lot of modern CPUs from Intel one can configure the TDP up or down based on
|
||||
## 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.
|
||||
|
||||
### Secure Boot
|
||||
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.
|
||||
### 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 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
|
||||
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
|
||||
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
|
||||
|
||||
### 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
|
||||
```
|
||||
Thanks to *felixonmars* for creating and maintaining this package.
|
||||
|
||||
### Artix Linux
|
||||
```
|
||||
makepkg -si
|
||||
sudo rc-update add lenovo_fix default
|
||||
sudo rc-service lenovo_fix start
|
||||
```
|
||||
|
||||
### 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
|
||||
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/).
|
||||
|
||||
@@ -64,21 +73,55 @@ You should make sure that **_thermald_** is not setting it back down. Stopping/d
|
||||
sudo systemctl stop 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
|
||||
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
|
||||
sudo ./install.sh
|
||||
sudo ./lenovo-throttling-fix/install.sh
|
||||
```
|
||||
Feedback about Fedora installation is welcome.
|
||||
|
||||
### 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
|
||||
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
|
||||
@@ -87,7 +130,20 @@ To permanently stop and disable the execution just issue:
|
||||
systemctl stop 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're using OpenRC instead of systemd:
|
||||
```
|
||||
rc-service lenovo_fix stop
|
||||
rc-update del lenovo_fix default
|
||||
```
|
||||
|
||||
If you also need to remove the tool from the system:
|
||||
```
|
||||
rm -rf /opt/lenovo_fix /etc/systemd/system/lenovo_fix.service
|
||||
# to purge also the config file
|
||||
@@ -95,8 +151,18 @@ rm /etc/lenovo_fix.conf
|
||||
```
|
||||
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
|
||||
OpenRC: sudo rc-service lenovo_fix restart
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
My T480s with i7-8550u is stable with:
|
||||
@@ -115,8 +181,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.
|
||||
|
||||
## 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
|
||||
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:
|
||||
```
|
||||
./lenovo_fix.py --debug
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
[GENERAL]
|
||||
# Enable or disable the script execution
|
||||
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
|
||||
[BATTERY]
|
||||
@@ -17,6 +20,8 @@ PL2_Duration_S: 0.002
|
||||
Trip_Temp_C: 85
|
||||
# Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL)
|
||||
cTDP: 0
|
||||
# Disable BDPROCHOT (EXPERIMENTAL)
|
||||
Disable_BDPROCHOT: False
|
||||
|
||||
## Settings to apply while connected to AC power
|
||||
[AC]
|
||||
@@ -36,8 +41,11 @@ Trip_Temp_C: 95
|
||||
HWP_Mode: False
|
||||
# Set cTDP to normal=0, down=1 or up=2 (EXPERIMENTAL)
|
||||
cTDP: 0
|
||||
# Disable BDPROCHOT (EXPERIMENTAL)
|
||||
Disable_BDPROCHOT: False
|
||||
|
||||
[UNDERVOLT]
|
||||
# All voltage values are expressed in mV and *MUST* be negative (i.e. undervolt)!
|
||||
[UNDERVOLT.BATTERY]
|
||||
# CPU core voltage offset (mV)
|
||||
CORE: 0
|
||||
# Integrated GPU voltage offset (mV)
|
||||
@@ -48,3 +56,32 @@ CACHE: 0
|
||||
UNCORE: 0
|
||||
# Analog I/O voltage offset (mV)
|
||||
ANALOGIO: 0
|
||||
|
||||
# All voltage values are expressed in mV and *MUST* be negative (i.e. undervolt)!
|
||||
[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:
|
||||
|
||||
45
install.sh
45
install.sh
@@ -2,9 +2,15 @@
|
||||
|
||||
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
|
||||
elif pidof openrc 2>&1 1>/dev/null; then
|
||||
rc-service lenovo_fix stop >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_DIR" &>/dev/null
|
||||
mkdir -p "$INSTALL_DIR" >/dev/null 2>&1
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
@@ -16,19 +22,38 @@ else
|
||||
echo "Config file already exists, skipping."
|
||||
fi
|
||||
|
||||
echo "Copying systemd service file..."
|
||||
cp systemd/lenovo_fix.service /etc/systemd/system
|
||||
if pidof systemd 2>&1 1>/dev/null; then
|
||||
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/
|
||||
elif pidof openrc-init 2>&1 1>/dev/null; then
|
||||
echo "Copying OpenRC service file"
|
||||
cp -R openrc/lenovo_fix /etc/init.d/lenovo_fix
|
||||
chmod 755 /etc/init.d/lenovo_fix
|
||||
fi
|
||||
|
||||
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"
|
||||
virtualenv -p /usr/bin/python3 venv
|
||||
/usr/bin/python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo "Enabling and starting systemd service..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable lenovo_fix.service
|
||||
systemctl restart lenovo_fix.service
|
||||
if pidof systemd 2>&1 1>/dev/null; then
|
||||
echo "Enabling and starting systemd service..."
|
||||
systemctl daemon-reload
|
||||
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
|
||||
elif pidof openrc-init 2>&1 1>/dev/null; then
|
||||
echo "Enabling and starting OpenRC service..."
|
||||
rc-update add lenovo_fix default
|
||||
rc-service lenovo_fix start
|
||||
fi
|
||||
|
||||
echo "All done."
|
||||
|
||||
649
lenovo_fix.py
649
lenovo_fix.py
@@ -1,35 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import dbus
|
||||
import glob
|
||||
import gzip
|
||||
import os
|
||||
import psutil
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from collections import defaultdict
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from datetime import datetime
|
||||
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 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]
|
||||
|
||||
UNDERVOLT_KEYS = ('UNDERVOLT', 'UNDERVOLT.AC', 'UNDERVOLT.BATTERY')
|
||||
ICCMAX_KEYS = ('ICCMAX', 'ICCMAX.AC', 'ICCMAX.BATTERY')
|
||||
power = {'source': None, 'method': 'polling'}
|
||||
HWP_VALUE = 0x20
|
||||
HWP_INTERVAL = 60
|
||||
|
||||
|
||||
platform_info_bits = {
|
||||
'maximum_non_turbo_ratio': [8, 15],
|
||||
@@ -40,7 +43,7 @@ platform_info_bits = {
|
||||
'feature_programmable_tdp_limit': [29, 29],
|
||||
'number_of_additional_tdp_profiles': [33, 34],
|
||||
'feature_programmable_temperature_target': [30, 30],
|
||||
'feature_low_power_mode': [32, 32]
|
||||
'feature_low_power_mode': [32, 32],
|
||||
}
|
||||
|
||||
thermal_status_bits = {
|
||||
@@ -65,8 +68,21 @@ thermal_status_bits = {
|
||||
'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,),
|
||||
'Comet Lake': (0xA6,),
|
||||
}
|
||||
|
||||
|
||||
class bcolors:
|
||||
YELLOW = '\033[93m'
|
||||
GREEN = '\033[92m'
|
||||
RED = '\033[91m'
|
||||
RESET = '\033[0m'
|
||||
@@ -75,6 +91,35 @@ class bcolors:
|
||||
|
||||
OK = bcolors.GREEN + bcolors.BOLD + 'OK' + 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):
|
||||
@@ -83,8 +128,7 @@ def writemsr(msr, val):
|
||||
try:
|
||||
subprocess.check_call(('modprobe', 'msr'))
|
||||
except subprocess.CalledProcessError:
|
||||
print('[E] Unable to load the msr module.')
|
||||
sys.exit(1)
|
||||
fatal('Unable to load the msr module.')
|
||||
try:
|
||||
for addr in msr_list:
|
||||
f = os.open(addr, os.O_WRONLY)
|
||||
@@ -93,8 +137,10 @@ def writemsr(msr, val):
|
||||
os.close(f)
|
||||
except (IOError, OSError) as e:
|
||||
if e.errno == EPERM or e.errno == EACCES:
|
||||
print('[E] Unable to write to MSR. Try to disable Secure Boot.')
|
||||
sys.exit(1)
|
||||
fatal(
|
||||
'Unable to write to MSR. Try to disable Secure Boot '
|
||||
'and check if your kernel does not restrict access to MSR.'
|
||||
)
|
||||
else:
|
||||
raise e
|
||||
|
||||
@@ -103,15 +149,13 @@ def writemsr(msr, val):
|
||||
def readmsr(msr, from_bit=0, to_bit=63, cpu=None, flatten=False):
|
||||
assert cpu is None or cpu in range(cpu_count())
|
||||
if from_bit > to_bit:
|
||||
print('[E] Wrong readmsr bit params')
|
||||
sys.exit(1)
|
||||
fatal('Wrong readmsr bit params')
|
||||
msr_list = ['/dev/cpu/{:d}/msr'.format(x) for x in range(cpu_count())]
|
||||
if not os.path.exists(msr_list[0]):
|
||||
try:
|
||||
subprocess.check_call(('modprobe', 'msr'))
|
||||
except subprocess.CalledProcessError:
|
||||
print('[E] Unable to load the msr module.')
|
||||
sys.exit(1)
|
||||
fatal('Unable to load the msr module.')
|
||||
try:
|
||||
output = []
|
||||
for addr in msr_list:
|
||||
@@ -125,24 +169,50 @@ def readmsr(msr, from_bit=0, to_bit=63, cpu=None, flatten=False):
|
||||
return output[cpu] if cpu is not None else output
|
||||
except (IOError, OSError) as e:
|
||||
if e.errno == EPERM or e.errno == EACCES:
|
||||
print('[E] Unable to read from MSR. Try to disable Secure Boot.')
|
||||
sys.exit(1)
|
||||
fatal('Unable to read from MSR. Try to disable Secure Boot.')
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def is_on_battery():
|
||||
with open(SYSFS_POWER_PATH) as f:
|
||||
def is_on_battery(config):
|
||||
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()))
|
||||
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():
|
||||
features_msr_value = readmsr(0xce, cpu=0)
|
||||
features_msr_value = readmsr(0xCE, cpu=0)
|
||||
cpu_platform_info = {}
|
||||
for key, value in platform_info_bits.items():
|
||||
cpu_platform_info[key] = int(get_value_for_bits(features_msr_value, value[0], value[1]))
|
||||
@@ -150,41 +220,51 @@ def get_cpu_platform_info():
|
||||
|
||||
|
||||
def get_reset_thermal_status():
|
||||
#read thermal status
|
||||
thermal_status_msr_value = readmsr(0x19c)
|
||||
# read thermal status
|
||||
thermal_status_msr_value = readmsr(0x19C)
|
||||
thermal_status = []
|
||||
for core in range(cpu_count()):
|
||||
thermal_status_core = {}
|
||||
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.append(thermal_status_core)
|
||||
#reset log bits
|
||||
writemsr(0x19c, 0)
|
||||
# reset log bits
|
||||
writemsr(0x19C, 0)
|
||||
return thermal_status
|
||||
|
||||
|
||||
def get_time_unit():
|
||||
# 0.000977 is the time unit of my CPU
|
||||
# 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():
|
||||
# 0.125 is the power unit of my CPU
|
||||
# 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():
|
||||
# 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):
|
||||
time_unit = get_time_unit()
|
||||
for Y in range(2**5):
|
||||
for Z in range(2**2):
|
||||
if t <= (2**Y) * (1. + Z / 4.) * time_unit:
|
||||
for Y in range(2 ** 5):
|
||||
for Z in range(2 ** 2):
|
||||
if t <= (2 ** Y) * (1.0 + Z / 4.0) * time_unit:
|
||||
return (Y, Z)
|
||||
raise ValueError('Unable to find a good combination!')
|
||||
|
||||
@@ -208,19 +288,84 @@ def calc_undervolt_mv(msr_value):
|
||||
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):
|
||||
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)
|
||||
writemsr(0x150, write_value)
|
||||
if args.debug:
|
||||
write_value &= 0xFFFFFFFF
|
||||
writemsr(0x150, 0x8000001000000000 | (VOLTAGE_PLANES[plane] << 40))
|
||||
read_value = readmsr(0x150, flatten=True)
|
||||
read_value = get_undervolt(plane)[plane]
|
||||
read_offset_mv = calc_undervolt_mv(read_value)
|
||||
match = OK if write_value == read_value else ERR
|
||||
print('[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))
|
||||
log(
|
||||
'[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():
|
||||
@@ -229,29 +374,72 @@ def load_config():
|
||||
|
||||
# config values sanity check
|
||||
for power_source in ('AC', 'BATTERY'):
|
||||
for option in (
|
||||
'Update_Rate_s',
|
||||
'PL1_Tdp_W',
|
||||
'PL1_Duration_s',
|
||||
'PL2_Tdp_W',
|
||||
'PL2_Duration_S',
|
||||
):
|
||||
config.set(power_source, option, str(max(0.1, config.getfloat(power_source, option))))
|
||||
for option in ('Update_Rate_s', 'PL1_Tdp_W', 'PL1_Duration_s', 'PL2_Tdp_W', 'PL2_Duration_S'):
|
||||
value = config.getfloat(power_source, option, fallback=None)
|
||||
if value is not None:
|
||||
value = config.set(power_source, option, str(max(0.001, value)))
|
||||
elif option == 'Update_Rate_s':
|
||||
fatal('The mandatory "Update_Rate_s" parameter is missing.')
|
||||
|
||||
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))
|
||||
if trip_temp != 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(
|
||||
power_source, trip_temp, valid_trip_temp))
|
||||
log(
|
||||
'[!] 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:
|
||||
value = config.getfloat('UNDERVOLT', plane)
|
||||
if key in config:
|
||||
value = config.getfloat(key, plane)
|
||||
valid_value = min(0, value)
|
||||
if value != valid_value:
|
||||
config.set('UNDERVOLT', plane, str(valid_value))
|
||||
print('[!] Overriding invalid "UNDERVOLT" value in "{:s}" voltage plane: {:.0f} -> {:.0f}'.format(
|
||||
plane, value, valid_value))
|
||||
config.set(key, plane, str(valid_value))
|
||||
log(
|
||||
'[!] 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
|
||||
|
||||
@@ -260,7 +448,7 @@ def calc_reg_values(platform_info, config):
|
||||
regs = defaultdict(dict)
|
||||
for power_source in ('AC', 'BATTERY'):
|
||||
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:
|
||||
# the critical temperature for my CPU is 100 'C
|
||||
critical_temp = get_critical_temp()
|
||||
@@ -268,88 +456,137 @@ def calc_reg_values(platform_info, config):
|
||||
global TRIP_TEMP_RANGE
|
||||
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
|
||||
else:
|
||||
log('[I] {:s} trip temperature is disabled in config.'.format(power_source))
|
||||
|
||||
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)
|
||||
|
||||
PL2 = int(round(config.getfloat(power_source, 'PL2_Tdp_W') / power_unit))
|
||||
Y, Z = calc_time_window_vars(config.getfloat(power_source, 'PL2_Duration_s'))
|
||||
if PL2_Tdp_W is None:
|
||||
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)
|
||||
|
||||
regs[power_source]['MSR_PKG_POWER_LIMIT'] = PL1 | (1 << 15) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (
|
||||
TW2 << 49)
|
||||
regs[power_source]['MSR_PKG_POWER_LIMIT'] = (
|
||||
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
|
||||
c_tdp_target_value = config.getint(power_source, 'cTDP', fallback=None)
|
||||
if c_tdp_target_value is not None:
|
||||
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:
|
||||
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:
|
||||
valid_c_tdp_target_value = max(0, c_tdp_target_value)
|
||||
regs[power_source]['MSR_CONFIG_TDP_CONTROL'] = valid_c_tdp_target_value
|
||||
return regs
|
||||
|
||||
|
||||
def set_hwp(pref):
|
||||
# set HWP energy performance hints
|
||||
assert pref in ('performance', 'balance_performance', 'default', 'balance_power', 'power')
|
||||
CPUs = [
|
||||
'/sys/devices/system/cpu/cpu{:d}/cpufreq/energy_performance_preference'.format(x) for x in range(cpu_count())
|
||||
]
|
||||
for i, c in enumerate(CPUs):
|
||||
with open(c, 'wb') as f:
|
||||
f.write(pref.encode())
|
||||
def set_hwp():
|
||||
# set HWP energy performance preference
|
||||
cur_val = readmsr(0x774, cpu=0)
|
||||
new_val = (cur_val & 0xFFFFFFFF00FFFFFF) | (HWP_VALUE << 24)
|
||||
|
||||
writemsr(0x774, new_val)
|
||||
if args.debug:
|
||||
with open(c) as f:
|
||||
read_value = f.read().strip()
|
||||
match = OK if pref == read_value else ERR
|
||||
print('[D] HWP for cpu{:d} - write "{:s}" - read "{:s}" - match {}'.format(i, pref, read_value, match))
|
||||
read_value = readmsr(0x774, from_bit=24, to_bit=31)[0]
|
||||
match = OK if HWP_VALUE == read_value else ERR
|
||||
log('[D] HWP - write "{:#02x}" - read "{:#02x}" - match {}'.format(HWP_VALUE, 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):
|
||||
try:
|
||||
mchbar_mmio = MMIO(0xfed159a0, 8)
|
||||
mchbar_mmio = MMIO(0xFED159A0, 8)
|
||||
except MMIOError:
|
||||
print('[E] Unable to open /dev/mem. Try to disable Secure Boot.')
|
||||
sys.exit(1)
|
||||
warning('Unable to open /dev/mem. TDP override might not work correctly.')
|
||||
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():
|
||||
#print thermal status
|
||||
# log thermal status
|
||||
if args.debug:
|
||||
thermal_status = get_reset_thermal_status()
|
||||
for index, core_thermal_status in enumerate(thermal_status):
|
||||
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
|
||||
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
|
||||
if 'MSR_TEMPERATURE_TARGET' in regs[power['source']]:
|
||||
write_value = regs[power['source']]['MSR_TEMPERATURE_TARGET']
|
||||
writemsr(0x1a2, write_value)
|
||||
writemsr(0x1A2, write_value)
|
||||
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
|
||||
print('[D] TEMPERATURE_TARGET - write {:#x} - read {:#x} - match {}'.format(
|
||||
write_value >> 24, read_value, match))
|
||||
log(
|
||||
'[D] TEMPERATURE_TARGET - write {:#x} - read {:#x} - match {}'.format(
|
||||
write_value >> 24, read_value, match
|
||||
)
|
||||
)
|
||||
|
||||
# set cTDP
|
||||
if 'MSR_CONFIG_TDP_CONTROL' in regs[power['source']]:
|
||||
write_value = regs[power['source']]['MSR_CONFIG_TDP_CONTROL']
|
||||
writemsr(0x64b, write_value)
|
||||
writemsr(0x64B, write_value)
|
||||
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
|
||||
print('[D] CONFIG_TDP_CONTROL - write {:#x} - read {:#x} - match {}'.format(
|
||||
write_value, read_value, match))
|
||||
log(
|
||||
'[D] CONFIG_TDP_CONTROL - write {:#x} - read {:#x} - match {}'.format(
|
||||
write_value, read_value, match
|
||||
)
|
||||
)
|
||||
|
||||
# set PL1/2 on MSR
|
||||
write_value = regs[power['source']]['MSR_PKG_POWER_LIMIT']
|
||||
@@ -357,52 +594,201 @@ def power_thread(config, regs, exit_event):
|
||||
if args.debug:
|
||||
read_value = readmsr(0x610, 0, 55, flatten=True)
|
||||
match = OK if write_value == read_value else ERR
|
||||
print('[D] MSR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format(
|
||||
write_value, read_value, match))
|
||||
log(
|
||||
'[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
|
||||
mchbar_mmio.write32(0, write_value & 0xffffffff)
|
||||
mchbar_mmio.write32(0, write_value & 0xFFFFFFFF)
|
||||
mchbar_mmio.write32(4, write_value >> 32)
|
||||
if args.debug:
|
||||
read_value = mchbar_mmio.read32(0) | (mchbar_mmio.read32(4) << 32)
|
||||
match = OK if write_value == read_value else ERR
|
||||
print('[D] MCHBAR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format(
|
||||
write_value, read_value, match))
|
||||
log(
|
||||
'[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')
|
||||
enable_hwp_mode = config.getboolean('AC', 'HWP_Mode', fallback=False)
|
||||
if power['source'] == 'AC' and enable_hwp_mode:
|
||||
cpu_usage = float(psutil.cpu_percent(interval=wait_t))
|
||||
# set full performance mode only when load is greater than this threshold (~ at least 1 core full speed)
|
||||
performance_mode = cpu_usage > 100. / (cpu_count() * 1.25)
|
||||
# check again if we are on AC, since in the meantime we might have switched to BATTERY
|
||||
if not is_on_battery():
|
||||
set_hwp('performance' if performance_mode else 'balance_performance')
|
||||
# set HWP less frequently. Just to be safe since (e.g.) TLP might reset this value
|
||||
if (
|
||||
enable_hwp_mode
|
||||
and next_hwp_write <= time()
|
||||
and (
|
||||
(power['method'] == 'dbus' and power['source'] == 'AC')
|
||||
or (power['method'] == 'polling' and not is_on_battery(config))
|
||||
)
|
||||
):
|
||||
set_hwp()
|
||||
next_hwp_write = time() + HWP_INTERVAL
|
||||
|
||||
else:
|
||||
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():
|
||||
global args
|
||||
|
||||
if os.geteuid() != 0:
|
||||
print('[E] No root no party. Try again with sudo.')
|
||||
sys.exit(1)
|
||||
|
||||
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('--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()
|
||||
|
||||
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()
|
||||
power['source'] = 'BATTERY' if is_on_battery(config) else 'AC'
|
||||
|
||||
platform_info = get_cpu_platform_info()
|
||||
if args.debug:
|
||||
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)
|
||||
|
||||
if not config.getboolean('GENERAL', 'Enabled'):
|
||||
log('[I] Throttled is disabled in config file... Quitting. :(')
|
||||
return
|
||||
|
||||
exit_event = Event()
|
||||
@@ -411,11 +797,13 @@ def main():
|
||||
thread.start()
|
||||
|
||||
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):
|
||||
if not sleeping:
|
||||
undervolt(config)
|
||||
set_icc_max(config)
|
||||
|
||||
def handle_ac_callback(*args):
|
||||
try:
|
||||
@@ -427,15 +815,26 @@ def main():
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
bus = dbus.SystemBus()
|
||||
|
||||
# add dbus receiver only if undervolt is enabled in config
|
||||
if any(config.getfloat('UNDERVOLT', plane) != 0 for plane in VOLTAGE_PLANES):
|
||||
bus.add_signal_receiver(handle_sleep_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager',
|
||||
'org.freedesktop.login1')
|
||||
# add dbus receiver only if undervolt/IccMax is enabled in config
|
||||
if any(
|
||||
config.getfloat(key, plane, fallback=0) != 0 for plane in VOLTAGE_PLANES for key in UNDERVOLT_KEYS + ICCMAX_KEYS
|
||||
):
|
||||
bus.add_signal_receiver(
|
||||
handle_sleep_callback, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1'
|
||||
)
|
||||
bus.add_signal_receiver(
|
||||
handle_ac_callback,
|
||||
signal_name="PropertiesChanged",
|
||||
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:
|
||||
loop = GLib.MainLoop()
|
||||
@@ -446,6 +845,8 @@ def main():
|
||||
exit_event.set()
|
||||
loop.quit()
|
||||
thread.join(timeout=1)
|
||||
if args.monitor is not None:
|
||||
monitor_thread.join(timeout=0.1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
5
openrc/lenovo_fix
Normal file
5
openrc/lenovo_fix
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/openrc-run
|
||||
command="env python3 /usr/lib/throttled/lenovo_fix.py"
|
||||
pidfile=${pidfile-/var/run/lenovo_fix.pid}
|
||||
description="Stop Intel throttling"
|
||||
command_background="yes"
|
||||
31
package/PKGBUILD
Normal file
31
package/PKGBUILD
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
pkgname=throttled
|
||||
pkgver=0.6
|
||||
pkgrel=4
|
||||
pkgdesc="Workaround for Intel throttling issues in Linux."
|
||||
arch=('any')
|
||||
url="https://github.com/erpalma/throttled"
|
||||
license=('MIT')
|
||||
depends=('python-dbus' 'python-psutil' 'python-gobject')
|
||||
conflicts=('lenovo-throttling-fix-git' 'lenovo-throttling-fix')
|
||||
replaces=('lenovo-throttling-fix')
|
||||
backup=('etc/lenovo_fix.conf')
|
||||
source=("git+https://github.com/Hyper-KVM/throttled.git#branch=openrc")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
|
||||
build() {
|
||||
cd "${srcdir}/throttled/"
|
||||
python -m compileall *.py
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "${srcdir}/throttled/"
|
||||
install -Dm644 etc/lenovo_fix.conf "$pkgdir"/etc/lenovo_fix.conf
|
||||
install -Dm644 systemd/lenovo_fix.service "$pkgdir"/usr/lib/systemd/system/lenovo_fix.service
|
||||
install -Dm755 lenovo_fix.py "$pkgdir"/usr/lib/$pkgname/lenovo_fix.py
|
||||
install -Dm755 openrc/lenovo_fix "$pkgdir/etc/init.d/lenovo_fix"
|
||||
install -Dm755 mmio.py "$pkgdir"/usr/lib/$pkgname/mmio.py
|
||||
cp -a __pycache__ "$pkgdir"/usr/lib/$pkgname/
|
||||
install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE
|
||||
}
|
||||
18
pyproject.toml
Normal file
18
pyproject.toml
Normal 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
|
||||
)/
|
||||
'''
|
||||
@@ -1,4 +1,3 @@
|
||||
configparser==3.5.0
|
||||
configparser==3.8.1
|
||||
dbus-python==1.2.8
|
||||
psutil==5.4.6
|
||||
PyGObject==3.28.3
|
||||
PyGObject==3.32.2
|
||||
|
||||
2
runit/lenovo_fix/run
Executable file
2
runit/lenovo_fix/run
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
PYTHONUNBUFFERED=1 exec /opt/lenovo_fix/venv/bin/python3 /opt/lenovo_fix/lenovo_fix.py
|
||||
@@ -3,7 +3,9 @@ Description=Stop Intel throttling
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/lenovo_fix/venv/bin/python3 /opt/lenovo_fix/lenovo_fix.py
|
||||
ExecStart=/usr/bin/throttled --conf /etc/throttled.conf
|
||||
# Setting PYTHONUNBUFFERED is necessary to see the output of this service in the journal
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
|
||||
|
||||
74
throttled.spec
Normal file
74
throttled.spec
Normal 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
|
||||
Reference in New Issue
Block a user