Autorotate fix on linux

How to fix the incorrect orientation of screen autorotate via systemd and iio-sensor-proxy

linux
Author

red

Published

January 1, 2025

Since Microsoft has chosen to be even shittier than usual, with Windows 11 force feeding security risks called recall, in-system ads and pushing edge/bing onto each and everything, I switched all my devices to some sort of linux. What remained was an old Asus Transformer Mini Tablet with a stylus I use to read and annotate pdfs.

Linux on touch devices is very hit and miss but a recommendation for KDE Plasma Mobile from the fediverse turned out to be right. The Fedora 41 Spin with KDE Plasma Mobile works shockingly well with one exception: The Screen auto-rotate was completely off, mirrored in both x and y axis. This can be fixed by creating a acceleration-mount-matrix (henceforth acceleration matrix) for the iio-sensor-proxy library.

Before you get started…

This is by no means an easy process, it involves:

  1. Checking whether your distro / desktop environment even uses iio-proxy-sensor!
  2. Coming up with a correct acceleration matrix for your device—sensor combination.
  3. Getting the correct modalias-like match pattern for udevadm to pick up your sensor and pull its configuration from the udev/hwdbfiles.
  4. Verifying that device—sensor alias is picked up, your custom acceleration matrix is applied and it works.

For you to succeed you should be comfortable with the shell and have a basic understanding of how linux, and in this case udev works.

Further requirements

  • Potentially Adding an SELinux exception for iio-sensor-proxy if your distro uses SELinux.
  • Getting remote access via ssh to effectively test and observe raw accelerometer data on a second device while you physically manipulate your tablet.

Developing a fix

Why misalignment of device and screen orientation happens

For my device iio-proxy expects it to be in Portrait-first orientation, but the sensor is actually in landscape-first orientation. So the x and y axis are shifted. Furthermore, the sensor seems to be mounted up-side-down so the z axis is inverted.

Portrait First

Landscape First
Figure 1: Screen Orientations

The Fix

  • provide the correct acceleration mount matrix and modalias-like in /etc/udev/hwdb.d/61-sensor-local.hwdb to align sensor data and device orientation.
# /etc/udev/hwdb.d/61-sensor-local.hwdb

# Asus Transformer Mini T103HAF
sensor:modalias:platform:HID-SENSOR-200073*:dmi:*svn*ASUSTeK*:pnT103HAF:*
ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, -1

What this does

  • During startup the linux kernel detects your machine’s devices and tries to match them to relevant drivers. This is a nontrivial affair and is well explained in the suse wiki.
  • During that process udev gets triggered to add our accelerometer and it also looks up the device in the hwdb to see whether any properties should be added. To fix screen autorotate we want udev to add our custom acceleration matrix to our accelerometer, thus correcting its output.
  • The line ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, -1 is the inline representation of the matrix below (with values for my device).

\[ \begin{bmatrix} accel_x & accel_y & accel_z \end{bmatrix} * \begin{bmatrix} x_1 & y_1 & z_1\\ x_2 & y_2 & z_2\\ x_3 & y_3 & z_3\\ \end{bmatrix} = \begin{bmatrix} ca_x & ca_y & ca_z \end{bmatrix} \] With \(ca_i\) being the corrected acceleration values.

  • For more information and explanation of what this matrix is and does please see the iio-sensor-proxy documentation.

Getting the sensor modalias-like

  • Thankfully hwdb.d/60-sensor.hwdb itself has a good description where to start, it should look something like sensor:modalias:<parent modalias pattern>:dmi:<dmi pattern>
    • For more concrete examples search the file for your manufacturer and potentially similar model, so you have a starting point.

The easy way

This should print your sensors modalias, if it works, great! You can go directly to Getting the dmi-string-part. Otherwise read on.

$ cat /sys/`udevadm info -q path -n /dev/iio:device0`/../modalias
platform:HID-SENSOR-200073

Getting the device name

$ udevadm info --export-db | grep iio gives us our first clue: the accelerometer is iio:device0 and an intelligible part of the name is HID-SENSOR-200073.5.

$ udevadm info --export-db | grep iio

# shortened output
P: /devices/pci0000:00/0000:00:0a.0/{33AECD58-B679-4E54-9BD9-A04D34F0C226}/001F:8086:0001.0002/HID-SENSOR-200073.5.auto/iio:device0
E: SUBSYSTEM=iio
E: DEVNAME=/dev/iio:device0
E: DEVTYPE=iio_device

Getting the modalias part

The next step is to identify the modalias pattern of the driver for our accelerometer.

  • udevadm info -a -n /dev/iio:device0 makes an attribute walk over all the sysfs attributes that can be used in udev rules to match our device. It moves from our device up to the sysfs root, passing parent devices giving us the hints we need.
$ udevadm info -a -n /dev/iio:device0

  looking at device '/devices/pci0000:00/0000:00:0a.0/{33AECD58-B679-4E54-9BD9-A04D34F0C226}/001F:8086:0001.0002/HID-SENSOR-200073.5.auto/iio:device0':
    KERNEL=="iio:device0"
    SUBSYSTEM=="iio"
    DRIVER==""

# shortened output

looking at parent device '/devices/pci0000:00/0000:00:0a.0/{33AECD58-B679-4E54-9BD9-A04D34F0C226}/001F:8086:0001.0002/HID-SENSOR-200073.5.auto':
    KERNELS=="HID-SENSOR-200073.5.auto"
    SUBSYSTEMS=="platform"
1    DRIVERS=="hid_sensor_accel_3d"
# shortened output
1
The name of the driver holding the modaliases for our device.

With the driver name hid_sensor_accel_3d we can inspect its modalias pattern (the way this driver is matched to devices).

$ modinfo hid_sensor_accel_3d | grep alias:

alias:          platform:HID-SENSOR-20007b
alias:          platform:HID-SENSOR-200073

Success! We now have the first part of our modalias-like.

# pattern from hwdb
sensor:modalias:<parent modalias pattern>:dmi:<dmi pattern>
# what we have so far
sensor:modalias:platform:HID-SENSOR-20073*:dmi:

The asterisk in HID-SENSOR-20073* denotes that we only care for the major version of the sensor, so this will match our systems HID-SENSOR-200073.5

Getting the dmi-string-part

Whats missing is the dmi string, a condensed representation of a computers hardware information1. It needs to match our exact make and model so that the acceleration matrix is added if and only if the sensor is detected on this exact model.

For my tablet the full dmi-string it looks like this

$ cat /sys/class/dmi/id/modalias
dmi:bvnAmericanMegatrendsInc.:bvrT103HAF.306:bd12/05/2017:br5.6:svnASUSTeKCOMPUTERINC.:pnT103HAF:pvr1.0:rvnASUSTeKCOMPUTERINC.:rnT103HAF:rvr1.0:cvnASUSTeKCOMPUTERINC.:ct32:cvr1.0:skuASUS-TabletSKU:

The parts that can uniquely identify these tablets:

  • the system vendor (svn) :svnASUSTeKCOMPUTERINC: and
  • the product name (pn) :pnT103HAF:2

Now we just need to construct valid match patterns that will pick up these parts in any dmi string. I constructed them by analysing the structure of the dmi string above (in what order do elements appear?) and variations in how the manufacturer ASUSTeK has been written in 60-sensor.hwdb.

  • After testing I settled on: :*svn*ASUSTeK*:pnT103HAF:*
    • The first part matches any system vendor containing ASUSTeK, the stable part of their name in these strings.
    • The second part simply picks up the product name and the rest of the dmi string.

Finally, the modalias-like!

sensor:modalias:platform:HID-SENSOR-200073*:dmi:*svn*ASUSTeK*:pnT103HAF:*

Aside: WTF do I find information on these shorthands?

There is partial “documentation” about how to decipher the field identifiers: A stackexchange comment based on an archived blog post. If you still cant find what you’re looking for: read the source code that maps DMI information to these shorthands.

Testing the modalias-like

First you have to implement your changes to hwdb in /etc/udev/hwdb.d/61-sensor-local.hwdb. Udev picks up these local additions and adds it to its database. For testing the modalias-like simply add random matrix values (different from any existing) to see if it is registered later.

# /etc/udev/hwdb.d/61-sensor-local.hwdb

# Asus Transformer Mini T103HAF
sensor:modalias:platform:HID-SENSOR-200073*:dmi:*svn*ASUSTeK*:pnT103HAF:*
 ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, -1
# run as root
red@fedora:~# systemd-hwdb update

# test if hwdb data is picked up
1red@fedora:~# sudo udevadm test $(udevadm info -q path -n /dev/iio:device0)
# output shortened
Properties:
  DEVPATH=/devices/pci0000:00/0000:00:0a.0/{33AECD58-B679-4E54-9BD9-A04D34F0C226}/001F:8086:0001.0002/HID-SENSOR-200073.5.auto/iio:device0
  DEVNAME=/dev/iio:device0
  DEVTYPE=iio_device
  MAJOR=234
  MINOR=0
  ACTION=add
  SUBSYSTEM=iio
  TAGS=:systemd:
2  ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, -1
  IIO_SENSOR_PROXY_TYPE=iio-poll-accel iio-buffer-accel
  CURRENT_TAGS=:systemd:
  SYSTEMD_WANTS=iio-sensor-proxy.service
  USEC_INITIALIZED=19906147
1
Simulate an udev event for our accelerometer3.
2
Check if the acceleration matrix is picked up. If not, check your hwdb file syntax, adjust your modalias-like and re-test until it works.

Developing the acceleration matrix

I found a python script that prints your acceleration values and lists the expected accelerometer values for the screen rotation states. Be warned, the script has some problems, for example line 87 must be replaced by scale = [scale_input] * 3, otherwise the script will crash depending on your systems layout. It has a monitor-values argument to print acceleration values, but the function doesn’t communicate if it applies an existing correction or not.

Short of rewriting the script, I used show-raw since this certainly prints the uncorrected values.

$ watch -n 1 python screen-rotation-matrix-tools.py show-raw

# Landscape / normal for tablet)
-9.67, 0.38, -2.55 g
# should be
# 0.00, 9.81, 0.00
# (so -x must be mapped to y)

# portrait / right side up
-0.33, 10.11, -0.08 g
# should be
# 9.81, 0.00, 0.00

# portrait / left side up
-0.11, -9.33, -0.52 g
# should be
# -9.81, 0.00, 0.00
# (y must be mapped to x)

# screen up
-0.13, 0.55, -9.76 g
# should be
# 0.00, 0.00, 9.81

# screen up-side-down
-0.46, 0.11, 9.55 g
# should be
# 0.00, 0.00, -9.81
# z must be inverted

In summary:

  • -x must be mapped to y
  • y must be mapped to x
  • z must be inverted.

The translation matrix must encode these operations to transform the raw acceleration input vector:

\[ \begin{bmatrix} accel_x & accel_y & accel_z \end{bmatrix} * \begin{bmatrix} 0 & -1 & 0 \\ 1 & 0 & 0 \\ 0 & 0 & -1 \\ \end{bmatrix} * \begin{bmatrix} ca_x & ca_y & ca_z \end{bmatrix} \] With \(ca_i\) being the corrected acceleration values.

Testing the rotation matrix

  • Now its a loop of deducing the acceleration matrix from these values,
    • writing them to /etc/udev/hwdb.d/61-sensor-local.hwdb
    • updating the hwdb with systemd-hwdb update and
    • triggering an udevevent with udevadm trigger -v -p DEVNAME=/dev/iio:device0
    • restart systemctl restart iio-sensor-proxy since the acceleration matrix is only read at device initialisation.

Conclusion

The fruits of my labour where functioning auto rotate on my tablet and three lines of code accepted as a pull request in systemd.

Footnotes

  1. read from SMBIOS↩︎

    • You might think about product version (pvr) :pvr1.0: but:
      • Firstly, that could be way to narrow since I have no idea how Asus versions these things.
      • Secondly, there is little information contained in “1.0”.
      • Thirdly, looking at examples from 60-sensor.hwdb very few people use :pvr:. Only in the case of Lenovo the version seems to contain crucial model information.
    ↩︎
  2. Note that to actually trigger an udevent use udevadm trigger -v -p DEVNAME=/dev/iio:device0, with the your DEVNAME.↩︎