[RFC PATCH v5 0/3] iio: position: add Rust driver for ams AS5600
From: Muchamad Coirul Anwar
Date: Sat Aug 22 2026 - 02:27:50 EST
This is v5 of the Rust driver for the ams AS5600 12-bit magnetic rotary
position sensor.
Link: https://lore.kernel.org/linux-iio/20260707151542.91997-1-muchamadcoirulanwar@xxxxxxxxx/
Base tree and dependencies:
This series is based on driver-core-testing [1], not vanilla rust-next.
It depends on Gary Guo's io_projection-v6 [2] for IoBackend, IoBase,
Region, and KnownSize.
FallibleIoCapable is included in patch 1/3 following Danilo's
suggestion [3] to carry it as a prerequisite until it lands upstream.
[1] https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/log/?h=driver-core-testing
[2] https://lore.kernel.org/driver-core/20260706-io_projection-v6-0-72cd5d055d54@xxxxxxxxxxx/
[3] https://lore.kernel.org/all/DJVQ852J7SOH.26YBIJTQ9B66G@xxxxxxxxxx/
Changes since RFC v4:
I2C abstraction:
- Switch to FallibleIoCapable — IoCapable is infallible by design,
but I2C can fail at transport level (Igor)
- Restrict I/O ops to I2cClient<Bound> (Danilo)
- Add smbus_read_word() and smbus_read_word_swapped() for odd offsets
- Add FallibleIoCapable trait to io.rs with blanket impl
- Add bit_usize() to bits.rs
IIO abstraction:
- Replace raw isize mask with IioChanInfo enum (Nuno)
- Expand PinnedDrop SAFETY comment re: kernfs_drain() (Danilo)
- build_device() takes modes parameter instead of hardcoding
- channels() now returns &'static slice
Driver:
- Use smbus_read_word_swapped() instead of manual swap_bytes()
- Use ARef<I2cClient<Bound>> (Danilo)
- Drop pr_info! debug logging
- Kconfig cleanup (Jonathan)
Known limitations (unchanged):
- No power management
- No write_raw, buffer, or trigger support
Design notes:
The IIO abstraction uses iio_device_alloc (not devm_*) so the Rust
Drop controls cleanup ordering: unregister, drop driver data, then
free iio_dev.
iio_device_unregister() drains in-flight sysfs reads via kernfs_drain().
This is sufficient for INDIO_DIRECT_MODE without buffer/trigger.
Character device paths need separate analysis.
Module ownership via __iio_device_register(), not iio_info.owner.
Muchamad Coirul Anwar (3):
i2c: rust: implement SMBus access via IoBackend and FallibleIoCapable
rust: add minimal IIO subsystem abstractions
iio: position: add Rust driver for ams AS5600
drivers/iio/position/Kconfig | 11 +
drivers/iio/position/Makefile | 1 +
drivers/iio/position/as5600.rs | 189 +++++++++++++++++
rust/bindings/bindings_helper.h | 2 +
rust/kernel/bits.rs | 29 +++
rust/kernel/error.rs | 1 +
rust/kernel/i2c.rs | 302 +++++++++++++++++++++++++++
rust/kernel/iio.rs | 384 +++++++++++++++++++++++++++++++++++
rust/kernel/io.rs | 66 ++++--
rust/kernel/lib.rs | 2 +
10 files changed, 967 insertions(+), 20 deletions(-)
create mode 100644 drivers/iio/position/as5600.rs
create mode 100644 rust/kernel/iio.rs
---
Tested on BeagleBone Black (AM335x), kernel 7.2.0-rc1+,
AS5600 on i2c-2 (0x36) at 3.3V, 6mm diametric neodymium magnet.
Full test session: 2026-08-17.
Build: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
(zero warnings, zero errors)
Functional tests:
1. Probe and registration:
$ sudo insmod as5600.ko
$ echo "as5600 0x36" > /sys/bus/i2c/devices/i2c-2/new_device
$ cat /sys/bus/iio/devices/iio:device0/name
as5600
$ ls /sys/bus/i2c/devices/2-0036/driver
2-0036 bind module uevent unbind
2. Raw angle and scale (magnet present):
$ cat /sys/bus/iio/devices/iio:device0/in_angl_raw
3366
$ cat /sys/bus/iio/devices/iio:device0/in_angl_scale
0.001533981
20 consecutive reads, all within 0-4095.
Computed: 3366 * 0.001533981 = 5.163 rad (~295.9 degrees).
3. Unbind/rebind lifecycle (PinnedDrop with ARef cleanup):
$ echo "2-0036" > /sys/bus/i2c/devices/2-0036/driver/unbind
$ ls /sys/bus/iio/devices/iio:device0 2>&1
ls: cannot access '...': No such file or directory
$ echo "2-0036" > /sys/bus/i2c/drivers/as5600/bind
$ cat /sys/bus/iio/devices/iio:device0/name
as5600
$ dmesg | grep -i "oops\|panic\|bug:"
(empty)
4. Concurrent stress (Mutex serialization under contention):
10 parallel readers hammering in_angl_raw for 5 seconds.
Repeated as 20-cycle unbind/rebind loop with readers active
throughout (8 seconds total).
$ dmesg | grep -i "oops\|panic\|bug:\|rcu"
(empty)
5. Module removal under active I/O:
a) rmmod while flood readers are running — no crash.
iio_device_unregister() drains in-flight read_raw callbacks
before PinnedDrop proceeds; subsequent reads return ENOENT.
b) rmmod while a sysfs fd is held open — no crash.
iio_dev kref not released until fd is closed.
$ dmesg | grep -i "oops\|panic\|bug:"
(empty)
6. Lifecycle stress:
50x rapid unbind/rebind — no crash, device functional after all
cycles.
50x insmod/rmmod — no crash.
$ dmesg | grep -i "oops\|panic\|bug:"
(empty)
7. I2C bus disconnect:
SCL/SDA physically pulled while read loop is running. Driver
returns "Remote I/O error" immediately on each failed transfer;
no hang, no internal retry loop. Cable reconnected — reads resume
from the next iteration without rmmod (about 2 seconds downtime).
rmmod issued while bus still in error state — exits cleanly.
ARef<I2cClient> drop is put_device() only, no bus transaction.
$ dmesg | grep -i "oops\|panic\|bug:"
(empty)
8. Memory and locking:
dmesg contains no strings matching "KASAN:" or "possible deadlock".
The test kernel was not built with CONFIG_KASAN or CONFIG_PROVE_LOCKING;
the above is a pattern match against kernel log output, not sanitizer
or lockdep instrumentation.
kmemleak not available (CONFIG_DEBUG_KMEMLEAK not set).
--
2.50.0