Autorotate fix on linux
How to fix the incorrect orientation of screen autorotate via systemd and iio-sensor-proxy
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:
- Checking whether your distro / desktop environment even uses iio-proxy-sensor!
- Coming up with a correct acceleration matrix for your device—sensor combination.
- Getting the correct modalias-like match pattern for
udevadm
to pick up your sensor and pull its configuration from theudev/hwdb
files. - 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
- iio-sensor proxy assumes that the accelerometer is mounted in a standardised orientation. If the sensor is mounted differently iio-proxy will infer the wrong screen orientation leading to a broken auto-rotate.
- For details on expected orientations/axis see the Sensor Orientation and linux kernel mount matrix docs.
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.
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 thehwdb
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.
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
.
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.
- The first part matches any system vendor containing
Finally, the modalias-like!
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 toy
y
must be mapped tox
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.
- writing them to
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
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.
- You might think about product version (pvr)
Note that to actually trigger an udevent use
udevadm trigger -v -p DEVNAME=/dev/iio:device0
, with the yourDEVNAME
.↩︎