Categories
Hardware Linux

Ubuntu 20.04 on Thinkpad T14 gen 2 (Intel)

AnnouncedLate 2021
CPU, GPU, RAMIntel i5-1135G7 @ 2.40GHz 4 cores (8 threads), Iris Xe graphics, 16GiB soldered DDR4 3200 MHz (and one free slot for upgrades)
Screen14″ FHD (1920×1080) IPS 300nits Anti-glare
StorageSK Hynix NVMe 512GB
Lenovo part number 20W000R1MX (Lenovo Product Specification Reference)

TL; DR

This is a laptop with a slightly boring, but professional and discrete design, which I like. It’s a sturdy workhorse and has a quality feel to it – typical business segment. Overall Ubuntu Linux works great and this is probably as good as it gets regarding out of the box Linux compatibility, unless going for specialized commercial Linux offerings like a System76.

There are some issues with TrackPoint smoothness relating to kernel input drivers, and you may experience issues with the keyboard.

Read on for details.

What works fine

Installation alongside side Windows 10 Pro

For dual boot installation, I freed up around 300 GB of storage space for Ubuntu using Windows disk administration. Installation of Ubuntu 20.04 on new partition went without issues. Grub becomes primary boot loader with Windows as menu option.

Graphics and screen

Both Wayland and Xorg sessions work fine and perform well. Brightness controls are functional and screen is surprisingly bright and crisp.

Power management and cooling

The CPU cores are automatically clocked between 1,2GHz and 3,5GHz, depending on load. The fan rarely makes any noise at all on normal light weight usage, and it is subtle even when it needs to run faster.

The battery is a 51Wh SMP, and charge thresholds can be controlled at runtime. I typically set mine to stop charging at 85% when at home, to increase battery lifespan. You can control this by writing percentages to the files:

/sys/class/power_supply/BAT0/charge_stop_threshold
/sys/class/power_supply/BAT0/charge_start_threshold

The laptop has a USB-C port for charging, and when using the accompanying 65W A/C power supply, charging is fast. It will also accept charge from other power supplies, like common mobile quick chargers, but the BIOS may issue a warning on boot if the charger is too weak.

Update 2023-01-14: you can check out the following script for a handy tool that shows Thinkpad battery information on the command line. It also displays the charge stop and start thresholds.

Suspend to RAM

Suspend to RAM and resume works fine. There are some ACPI errors appearing in the kernel log upon resume:

ACPI BIOS Error (bug): Could not resolve symbol [\_SB.PC00.RP09.PEGP.DDNT], AE_NOT_FOUND (20210730/psargs-330)
[...]
ACPI Error: Aborting method \_SB.PC00.LPCB.EC.SEN4._TMP due to previous error (AE_NOT_FOUND) (20210730/psparse-529)

I haven’t look into these, probably buggy firmware code. It does not seem to cause any real problems.

Power usage in suspended state is nothing out of the ordinary. I would have liked to have hibernate option available, but this is apparently disabled due to locked down kernel and secure boot.

Audio

The HDA audio chip identifies as Realtek ALC257. I have not noticed any issues and it works well. The laptop speakers are a bit disappointing and sound rather weak, but I rarely rely on them anyway.

Wireless networking

The wifi chip is detected as Intel(R) Wi-Fi 6 AX201 160MHz, REV=0x354 by the iwlwifi driver. No issues experienced, wireless networking is solid.

Also note that there is no RJ45 ethernet port on this laptop model.

Bluetooth

Bluetooth is usually a bit buggy on Linux, and it can be a hit and miss experience, depending on the devices your are trying to connect. For the Thinkpad T14, it uses an Intel Bluetooth chip connected to the USB bus.

I’ve tested connectivity with a pair of Bose QC35 headsets and a Samsung Galaxy mobile phone, both work.

Special keys

Most Fn-special keys work fine: audio controls, mic mute, brightness, flight mode. There are some that generate no key events in Xorg: Fn+F9 through F11. These are labeled with symbols for chat, answer call and hangup call.

Firmware upgrades

The Thinkpad firmware can be automatically updated using the built-in fwupdmgr application. Which is a great improvement from earlier days of having to flash updates from thumb drives or being forced to use Windows.

Fingerprint reader

The fingerprint reader works out of the box. You can enroll your prints in the standard Ubuntu settings app.

Webcam

It works fine, but is only a 720p camera.

Problems 💀

Poor keyboard quality control

I noticed the left trackpad mouse button had erratic click detection – some clicks were missed. I primarily use the TrackPoint and rely on the physical mouse buttons beneath the space bar. Having premium on-site support, I decided to call for assistance, suspecting hardware malfunction. Lenovo sent a service technician a few days later, and the entire keyboard was replaced, at no additional cost. The new keyboard is fine.

Speaking of keyboard, the keys are a bit too hesitant and mushy for my taste. But solid.

TrackPoint cursor movement

TrackPoint cap
TrackPoint cap

The [Elan] TrackPoint cursor movement is slightly rough and jittery (using Xorg), resulting in reduced precision. It is almost like not enough input events are generated per time during movement. Also, the following message is printed in the kernel log:

psmouse serio1: synaptics: Your touchpad (PNP: LEN2072 PNP0f13) says it can support a different bus.
If i2c-hid and hid-rmi are not used, you might want to try setting psmouse.synaptics_intertouch to 1 and report this to linux-input@vger.kernel.org.

I have experimented with the suggested psmouse module setting, and it does indeed result in a smoother cursor. However, it also causes TrackPoint to randomly stop working, which is a deal breaker. I have not found a solution to this problem yet, but trust it will improve with future kernels.

Swapped Ctrl and Fn-keys

Thinkpads come with physically swapped left control and Fn-key compared to most keyboards. So you have this layout on the left bottom row:

[Fn] [Ctrl] [Win] [Alt] [ Space ] …

Being an Emacs user, this can be uncomfortable, since the distance between the control key and frequently used letter combinations is too short. Not to mention having to mentally re-calibrate your typing when switching between the Thinkpad and other keyboards. Fortunately the BIOS allows you to software swap the function of these keys, so the Fn-key becomes the left control. But the Fn-key is physically smaller and provides a poorer “left control experience”.


Show Thinkpad battery info on the command line

You can use the following shell script to check your battery vitals from the command line. Copy-paste the code into a file tpbinfo, save it somewhere in your PATH and make it executable with:
chmod +x tpbinfo

#!/bin/sh

set -e

cd /sys/class/power_supply/BAT0

exec awk '
  NR == 1 { cstop=$1 }
  NR == 2 { cstart=$1 }
  NR == 3 { status=$0 }
  NR == 4 { enow=$1 }
  NR == 5 { efull=$1 }
  NR == 6 { efull_design=$1 }

  END {
    charge_percent = (enow / efull) * 100
    capacity_percent = (efull / efull_design) > 1 ? 100 : (efull / efull_design)*100
    print "# Thinkpad battery info"
    printf("%-23s %.1f%%\n", "Charge:", charge_percent)
    printf("%-23s %s\n", "Status:", status)
    printf("%-23s %d%%\n", "Charge start threshold:", cstart)
    printf("%-23s %d%%\n", "Charge stop threshold:", cstop)
    printf("%-23s %d%%\n", "Capacity:", capacity_percent)
  }
' charge_stop_threshold charge_start_threshold status energy_now energy_full energy_full_design

Running tpbinfo produces something like the following output:

# Thinkpad battery info
Charge:                 79,2%
Status:                 Discharging
Charge start threshold: 70%
Charge stop threshold:  85%
Capacity:               99%
Categories
Code Linux

Locking critical sections in shell scripts

A critical section is some piece of code which, due to its nature and effects, should be executed by at most one thread or process at a time. If such code is executed concurrently, the results often become undefined and arbitrary. Atomically locking a shared resource is a common pattern to synchronize execution of critical sections and ensure mutually exclusive access.

Shell scripts mostly deal in processes and files, and there are several common scenarios where code is actually a critical section. If such code is run in several processes concurrently, it could introduce race conditions and arbitrary results. Consider a script that starts a background process if it is not already running – a typical pattern to start a singleton daemon process. Such code is a critical section, because you can end up with two running daemons if the code runs concurrently (and the daemon does not check for other instances of itself). Another good example is multiple scripts writing to a shared file, or even a shared directory structure.

There are a few strategies to implement locking in shell scripts, some are better than others. In this post, I will focus on one of the most robust ways: using flock(1). This nice tool gives you access to kernel level file locking from you shell. It has some clear advantages over traditional existence based file locking:

  1. It is truly atomic.
  2. The kernel manages the locks and releases them automatically when lock owning processes die. So no more stale lock files to clean up.
  3. You can block and wait for lock, indefinitely or with a timeout, and instantly get it when another process frees it. (Avoid lock polling loops with sleeping.)

It is important to understand that file locks are tied to both a file on the file system and running processes with open file descriptors to it. Even if a file used for locking exists on the file system, it does not mean the lock is taken ! The file system acts as a namespace of shared resources on which we can attach locks. Also note that the locks are advisory only – if a process does not care to check for locks, it will not participate in any synchronization and can do whatever it pleases.

Shell script with locking functions

We will look at a script which needs to protect a critical section with locking. The locking shall be done on a common file job.lock, which means any process with access to that file can obtain or check for a lock. To code along, you can copy the script to your own file and run the examples.

job.sh

#!/bin/bash

lock_acquire() {
    # Open a file descriptor to lock file
    exec {LOCKFD}>job.lock || return 1

    # Block until an exclusive lock can be obtained on the file descriptor
    flock -x $LOCKFD
}

lock_release() {
    test "$LOCKFD" || return 1
    
    # Close lock file descriptor, thereby releasing exclusive lock
    exec {LOCKFD}>&- && unset LOCKFD
}

lock_acquire || { echo >&2 "Error: failed to acquire lock"; exit 1; }

# --- Begin critical section ---

if [ -f job.dat ]; then
    value=$(<job.dat)
else
    value=0
fi
value=$((value + 1))

echo $value >job.dat

# --- End critical section ---

lock_release

The lock_acquire function uses flock -x N to obtain an exlusive lock on a file descriptor N. Since the file descriptor is opened by the script process itself, it will be the owner of the lock after flock exits. Flock is able to lock the descriptor because it is inherited from the shell process that started it. The critical section reads a number from a file if it exists, increments it by one, and writes the updated number back to the file.

Testing

First we’ll run the job script once:

$ bash job.sh 
$ ls
job.dat  job.lock  job.sh
$ cat job.dat 
1

A job.dat file is produced with a value of 1, which is entirely expected and not very interesting.

Next we’ll start 100 job processes asynchronously as fast as possible in the background, which means that many of them will run concurrently. We do this two times:

$ rm job.dat 
$ (for i in {1..100}; do bash job.sh & done; wait)
$ cat job.dat 
100
$ (for i in {1..100}; do bash job.sh & done; wait)
$ cat job.dat 
200

The for loop is started in a sub shell to avoid job control messages. The data has been incremented exactly 100 times after the first run, and incremented again by 100 after the second. If you look at the code in the critical section, it both reads, updates and then writes to the shared file, and doing this without locking would not work consistently.

Actually, let us try that, by commenting out the lock_acquire call in the script:

[...]

#lock_acquire || { echo >&2 "Error: failed to acquire lock"; exit 1; }

# --- Begin critical section ---

Then we run the test again:

$ rm job.dat
$ (for i in {1..100}; do bash job.sh & done; wait)
$ cat job.dat
3
$ (for i in {1..100}; do bash job.sh & done; wait)
$ cat job.dat
14

Which ends with a final result of 14, clearly incorrect and arbitrary. The results will vary with each run and depend on things like the speed of your computer.

Releasing the lock ?

In this case the script actually does not need to release the lock right before it exits, because the kernel will automatically do that when the process exits anyway. We will try it by re-enabling the locking call, and commenting out the lock_release call:

[...]

lock_acquire || { echo >&2 "Error: failed to acquire lock"; exit 1; }

# --- Begin critical section ---
[...]
# --- End critical section ---

#lock_release

And run the test:

$ rm job.dat
$ (for i in {1..100}; do bash job.sh & done; wait)
$ (for i in {1..100}; do bash job.sh & done; wait)
$ cat job.dat
200

It still works fine.

Starting a daemon process from your shell init scripts

A common use case is the need to start a single daemon process from your shell init scripts, unless one is already running. So you only ever want one such thing running. Consider the following:

if ! ps -ef|grep some-daemon|grep -qv grep; then
    some-daemon & pid=$!
    echo Started some-daemon with pid $pid
fi

This code is racy unless it is protected by locking. If you were to start two terminals more or less simultaneously, both executing your shell init scripts, you could possibly end up with two running daemon processes.

To protect this with flock, you could do the following:

if exec {bashrc_fd}<~/.bashrc && flock -nx $bashrc_fd; then
    # --- Begin critical section ---
    if ! ps -ef|grep some-daemon|grep -qv grep; then
        some-daemon & pid=$!
        echo Started some-daemon with pid $pid
    fi
    # --- End critical section ---

    flock -u $bashrc_fd && exec {bashrc_fd}>&-
fi

Here we open a read-only file descriptor to ~/.bashrc and then try to grab an exclusive lock on it, but we do it non-blocking with option -n. If some other bash process is already executing that part of the init file, flock will not succeed and immediately exit with a non-zero code, so the block is skipped. It has the effect that only one bash process will execute the code, and others running at the same time will skip it.

You may notice that we explicitly release the lock using flock -u $bashrc_fd after the critical section. Normally it is enough to close the file descriptor used for locking, but when starting child processes, those may inherit and keep such descriptors open. So the parent process closing its copy of the descriptor may not be enough actually release lock. Therefore we do it explicitly.

Closing notes

The manual page for the flock command lists a few good examples of how you can use it your scripts. However, none of those examples show how you can make the current shell process own and control the locks without using sub shells or flock invocations to wrap critical sections/commands.

The manual page for the flock(2) system call is a good read if you are interested in more details about how it works.

Read more about handling file descriptors with Bash in this part of the bash(1) manual.

Categories
Linux

Extract media file tags with ffprobe

The ffprobe tool, which is part of ffmpeg, can be used to extract all sorts of technical information from media streams. It is very capable, but is perhaps not the most intuitive tool with regards to its options. Therefore, examples are handy to get started. This post will collect examples focusing on metadata tags and the JSON output format.

Extract format section to JSON

The format section is a global part of the media container and provides generic information about the container streams. It also contains the metadata tags, like title, artist, year, etc. In this example, the file is an MP4 audio file.

$ ffprobe -v quiet -of json -show_entries format audio.m4a
{
    "format": {
        "filename": "audio.m4a",
        "nb_streams": 1,
        "nb_programs": 0,
        "format_name": "mov,mp4,m4a,3gp,3g2,mj2",
        "format_long_name": "QuickTime / MOV",
        "start_time": "0.000000",
        "duration": "3.024000",
        "size": "27730",
        "bit_rate": "73359",
        "probe_score": 100,
        "tags": {
            "major_brand": "M4A ",
            "minor_version": "512",
            "compatible_brands": "isomiso2",
            "genre": "Tone",
            "artist": "A",
            "title": "A title",
            "album": "X",
            "date": "2018",
            "encoder": "Lavf58.13.100"
        }
    }
}
-v quietBe quiet with diagnostics to stderr.
-of jsonSelect JSON output format.
-show_entries formatSelect the format section.
Options breakdown

Extract only the metadata tags

$ ffprobe -v quiet -of json -show_entries format_tags Movie.mkv 
{
    "format": {
        "tags": {
            "title": "The Movie",
            "DATE_RELEASED": "2008",
            "creation_time": "2022-02-11T18:26:58.000000Z",
            "ENCODER": "Lavf58.29.100"
        }
    }
}

Here we select the format_tags section from a video file in Matroska format, omitting the other bits of information under format. Finally, we can use jq to select parts of the JSON data:

$ ffprobe -v quiet -of json -show_entries format_tags Movie.mkv|\
          jq -r .format.tags.title
The Movie

Other output formats

ffprobe supports several output formats. Personally I like JSON, since it is very well defined with regard to syntax and escaping of special characters, etc. However, depending on use case, other forms may be more suitable for extraction of data in contexts like shell scripts.