2019. május 19., vasárnap

Limit the CPU maximum frequency to avoid thermal shutdowns under Ubuntu 18.04

I used to have similar problems for multiple laptops. It seems that the CPU in the laptops tends to overheat more easily over time and shut down. Replacing the CPU fan and quality thermal paste never helped me in these situations. So far I limited the max frequency on Ubuntu, but it might happen that you just leave your laptop while doing some processing for a moment under the Sun and it just overheats the whole laptop body, causing a shut down eventually.
I learned that the newest laptops with Intel chips don't work with cpufreq-set properly, but only with likwid tools.
Installing this package:
sudo apt install likwid
I wrote the following python script to decrease/increase the max CPU frequency (manipulate_cpu_freq.py) under Ubuntu 18.04 (requires Python 3.7):
#!/usr/bin/python3.7

import argparse
import os
import subprocess

parser = argparse.ArgumentParser(description = "Manipulate CPU frequencies", prefix_chars = '-')
parser.add_argument("-d", "--decrease", help = "decrease the max frequency", type = bool, default = False)
parser.add_argument("-i", "--increase", help = "increase the max frequency", type = bool, default = False)
parser.add_argument("-s", "--silent", help = "silent mode", type = bool, default = False)
args = parser.parse_args()

query_freqs_output = subprocess.run(["likwid-setFrequencies", "-l"], capture_output = True)
query_freqs_output = query_freqs_output.stdout.decode('utf-8').split('\n')[1]
query_freqs_output = query_freqs_output.split(' ')
available_freqs = list(map(float, query_freqs_output))

query_curr_freq_output = subprocess.run(["likwid-setFrequencies", "-p"], capture_output = True)
query_curr_freq_output = query_curr_freq_output.stdout.decode('utf-8').split('\n')[1]
query_curr_freq_output = query_curr_freq_output.split('/')[-1]
current_freq = float(query_curr_freq_output.split(' ')[0])
curr_freq_index = min(range(len(available_freqs)), key = lambda i: abs(available_freqs[i]-current_freq))

if not args.silent:
  print("Available frequencies:", available_freqs)
  print("Current frequency:", current_freq)

if args.decrease:
  print("Decrease the frequency")
  if curr_freq_index == 0:
    print("Warning: Can't decrease the frequency because it is already at min")
    exit(1)

  print("Set to frequency", available_freqs[curr_freq_index-1], "Ghz")
  subprocess.run(["likwid-setFrequencies", "-y", str(available_freqs[curr_freq_index-1])])
  exit(0)

if args.increase:
  print("Increase the frequency")
  if curr_freq_index == len(available_freqs)-1:
    print("Warning: Can't increase the frequency because it is already at max")
    exit(1)

  print("Set to frequency", available_freqs[curr_freq_index+1], "Ghz")
  subprocess.run(["likwid-setFrequencies", "-y", str(available_freqs[curr_freq_index+1])])
  exit(0)
And I use a script running in the background to monitor the CPU temperature (run_cpu_policy.sh):
#!/bin/bash

while true
do
  CPU_TEMP=$(cat /sys/devices/virtual/thermal/thermal_zone0/temp)
  echo CPU Temperature: $(echo ${CPU_TEMP}/1000 | bc)°C
  if [ "$CPU_TEMP" -gt 76000 ]; then
    echo Decrease the max CPU frequency
    sudo manipulate_cpu_freq.py -s 1 -d 1
  fi
  if [ "$CPU_TEMP" -le 68000 ]; then
    echo Increase the max CPU frequency
    sudo manipulate_cpu_freq.py -s 1 -i 1
  fi
  sleep 10
done
Surely, you must check which sys point (e.g. /sys/devices/virtual/thermal/thermal_zone0/temp) contains your CPU temperature and adapt the script above. I increase the CPU max frequency when the temperature is below 68°C and decrease if it is above 76°C. It is very conservative policy, but the temperature may reach quickly above 100°C (around thermal shutdown threshold), if it sits above 80°C permanently thus I try to keep always below 80°C, just to be sure.
I had to develop the above solution yesterday because I got two thermal shutdowns because of the sunny, hot day while running intensive computations on my laptop CPU (Intel i7-6600U) continuously.
You can run the script after every startup with adding to the cron jobs (/etc/crontab):
@reboot root systemd-run --scope sudo -u YOUR_USER screen -dmS cpu_policy /home/YOUR_USER/run_cpu_policy.sh
Be sure to have screen installed:
sudo apt install screen
You can check it while running:
screen -r cpu_policy

2019. március 9., szombat

Configuring a USB DisplayLink monitor on Ubuntu 18.04

This blog post is about my experiences of configuring a USB DisplayLink monitor (Mobile Pixels Duex) under Ubuntu Linux 18.04 with KDE

Installation
--------------
Download and execute the installer from:


The installation was very smooth under Ubuntu 18.04. Since the DisplayLink installer compiles a kernel module, be sure that the kernel headers, dkms, make and g++ packages are installed on your system.

Screen setup
------------------
After connecting Duex, it was switched on, but the display was not configured. The display preferences dialog under KDE System Settings was unable to handle the Duex and the laptop screen changed to full resolution by some reason when Duex was connected. xrandr can configure Duex correctly, two commands can do the trick on command line:

- Optional step to reset back the laptop screen resolution if you use other resolution than the native. Example command:
 xrandr --output eDP1 --mode 1600x900 --primary  
- Set Duex to extended display on the right side of the main screen (check "xrandr --help" for other configurations):
 xrandr --output DVI-I-1-1 --right-of eDP1 --mode 1920x1080 --noprimary  

Disconnecting and reconnecting the Duex any time after this point will be just fine, no other xrandr tweaking is needed, but these xrandr changes are not permanent after every reboot they must be repeated once Duex is connected first time. eDP1 and DVI-I-1-1 might be different on your system. You can check the right IDs with a simple "xrandr" command in command line, it will list all available display connection IDs.

Brightness control
------------------
Duex is very bright by default and it is not possible to change the brightness under Linux because of the lack of support in the DisplayLink driver. Because the brightness value is permanent after it is changed on the device, my workaround was to install the DisplayLink driver in a Windows 10 virtual machine, adjust the brightness to your preference with the ScreenBright application and remove Duex from the virtual machine. Since the brightness change is permanent, the setting remains after disconnecting and reconnecting Duex. It is a dirty workaround, but at least you are not locked to a certain brightness level permanently. The same method works just fine without virtual machines by connecting Duex to a real Windows PC or laptop temporary.

Brightness control (in software)
---------------------------------------
Without changing the backlight of Duex with hardware controls, xorg can apply brightness/gamma values in software to correct the screen. This method does not change the backlight level of the Duex, but it would provide a way to finetune the brightness level. This is not available yet, but at least doable. Modesetting driver used by Displaylink added support for software gamma support in Jan 2018, but this change has not landed in Ubuntu yet:


Software gamma support must be also implemented in the open source parts of the DisplayLink driver. The efforts can be tracked via:


Further tweaking
----------------------
If your laptop has an integrated Watcom touchscreen in your screen, the touch will point the mouse pointer to wrong coordinates if Duex is connected because overall display dimensions changed with the extended screen. You have to map your Wacom device back to the laptop screen once Duex is connected or disconnected with xsetwacom:

 xsetwacom set "Wacom Pen and multitouch sensor Finger touch" MapToOutput eDP1  

The correct wacom device ID can be checked with "xinput" on command line and the correct display ID (eDP1) with "xrandr". These steps can be automated when Duex is connected/disconnected with srandrd (https://github.com/jceb/srandrd). The following script run with srandrd works (correct the display/touchscreen IDs if needed):

 #!/bin/bash  
 case "${SRANDRD_OUTPUT} ${SRANDRD_EVENT}" in  
  "DVI-I-1-1 connected") sleep 5 && xsetwacom set "Wacom Pen and multitouch sensor Finger touch" MapToOutput eDP1 ;;  
  "DVI-I-1-1 disconnected") sleep 5 && xsetwacom set "Wacom Pen and multitouch sensor Finger touch" MapToOutput eDP1 ;;   
 esac