[PATCH 4/5] rust: i2c: add I2C wrappers

Igor Korotin via B4 Relay posted 5 patches 1 week, 1 day ago
[PATCH 4/5] rust: i2c: add I2C wrappers
Posted by Igor Korotin via B4 Relay 1 week, 1 day ago
From: Igor Korotin <igor.korotin.linux@gmail.com>

Add safe Rust wrappers for common I2C/SMbus operations on I2C clients.

These wrappers provide safe abstractions over low-level C API calls:
- SMbus byte operations: read/write byte and byte data
- SMbus word operations: read/write word data
- SMbus block operations: read/write I2C block data
- Master I2C operations: master receive and master send

All operations include proper error handling through the Result type.

Signed-off-by: Igor Korotin <igor.korotin.linux@gmail.com>
---
 rust/helpers/helpers.c    |  1 +
 rust/helpers/i2c.c        | 15 ++++++++
 rust/kernel/i2c/client.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+)

diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 79c72762ad9c..6f6b0ff8713a 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -29,6 +29,7 @@
 #include "err.c"
 #include "irq.c"
 #include "fs.c"
+#include "i2c.c"
 #include "io.c"
 #include "jump_label.c"
 #include "kunit.c"
diff --git a/rust/helpers/i2c.c b/rust/helpers/i2c.c
new file mode 100644
index 000000000000..05bbd26eaaf8
--- /dev/null
+++ b/rust/helpers/i2c.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/i2c.h>
+
+__rust_helper int rust_helper_i2c_master_recv(const struct i2c_client *client,
+				  char *buf, int count)
+{
+	return i2c_master_recv(client, buf, count);
+}
+
+__rust_helper int rust_helper_i2c_master_send(const struct i2c_client *client,
+				  const char *buf, int count)
+{
+	return i2c_master_send(client, buf, count);
+}
diff --git a/rust/kernel/i2c/client.rs b/rust/kernel/i2c/client.rs
index a597793ff038..76fa01cb0351 100644
--- a/rust/kernel/i2c/client.rs
+++ b/rust/kernel/i2c/client.rs
@@ -140,6 +140,98 @@ unsafe impl Send for I2cClient {}
 // (i.e. `I2cClient<Normal>) are thread safe.
 unsafe impl Sync for I2cClient {}
 
+impl I2cClient<device::Normal> {
+    /// The C `i2c_smbus_read_byte` function wrapper for SMbus "read byte" protocol
+    pub fn i2c_smbus_read_byte(&self) -> Result<u8> {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        let result = unsafe { bindings::i2c_smbus_read_byte(self.as_raw()) };
+        to_result(result)?;
+        Ok(result as u8)
+    }
+
+    /// The C `i2c_smbus_write_byte` function wrapper for SMbus "write byte" protocol
+    pub fn i2c_smbus_write_byte(&self, value: u8) -> Result {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        to_result(unsafe { bindings::i2c_smbus_write_byte(self.as_raw(), value) })
+    }
+
+    /// The C `i2c_smbus_read_byte_data` function wrapper for SMbus "read byte" protocol
+    pub fn i2c_smbus_read_byte_data(&self, command: u8) -> Result<u8> {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        let result = unsafe { bindings::i2c_smbus_read_byte_data(self.as_raw(), command) };
+        to_result(result)?;
+        Ok(result as u8)
+    }
+
+    /// The C `i2c_smbus_write_byte_data` function wrapper for SMbus "write byte" protocol
+    pub fn i2c_smbus_write_byte_data(&self, command: u8, value: u8) -> Result {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        to_result(unsafe { bindings::i2c_smbus_write_byte_data(self.as_raw(), command, value) })
+    }
+
+    /// The C `i2c_smbus_read_word_data` function wrapper for SMbus "read word" protocol
+    pub fn i2c_smbus_read_word_data(&self, command: u8) -> Result<u16> {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        let result = unsafe { bindings::i2c_smbus_read_word_data(self.as_raw(), command) };
+        to_result(result)?;
+        Ok(result as u16)
+    }
+
+    /// The C `i2c_smbus_write_word_data` function wrapper for SMbus "write word" protocol
+    pub fn i2c_smbus_write_word_data(&self, command: u8, value: u16) -> Result {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        to_result(unsafe { bindings::i2c_smbus_write_word_data(self.as_raw(), command, value) })
+    }
+
+    /// The C `i2c_smbus_read_i2c_block_data` function wrapper for SMbus "block read" protocol
+    pub fn i2c_smbus_read_i2c_block_data(&self, command: u8, values: &mut [u8]) -> Result<usize> {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        let result = unsafe {
+            bindings::i2c_smbus_read_i2c_block_data(
+                self.as_raw(),
+                command,
+                values.len() as u8,
+                values.as_mut_ptr(),
+            )
+        };
+        to_result(result)?;
+        Ok(result as usize)
+    }
+
+    /// The C `i2c_smbus_write_i2c_block_data` function wrapper for SMbus "block write" protocol
+    pub fn i2c_smbus_write_i2c_block_data(&self, command: u8, values: &[u8]) -> Result {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        to_result(unsafe {
+            bindings::i2c_smbus_write_i2c_block_data(
+                self.as_raw(),
+                command,
+                values.len() as u8,
+                values.as_ptr(),
+            )
+        })
+    }
+
+    /// The C `i2c_master_recv` function wrapper to issue a single I2C message in master
+    /// receive mode
+    pub fn i2c_master_recv(&self, buf: &mut [u8]) -> Result<usize> {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        let result =
+            unsafe { bindings::i2c_master_recv(self.as_raw(), buf.as_mut_ptr(), buf.len() as i32) };
+        to_result(result)?;
+        Ok(result as usize)
+    }
+
+    /// The C `i2c_master_send` function wrapper to issue a single I2C message in master
+    /// transmit mode
+    pub fn i2c_master_send(&self, buf: &[u8]) -> Result<usize> {
+        // SAFETY: self is a valid reference to a I2cClient<Normal>
+        let result =
+            unsafe { bindings::i2c_master_send(self.as_raw(), buf.as_ptr(), buf.len() as i32) };
+        to_result(result)?;
+        Ok(result as usize)
+    }
+}
+
 /// The registration of an i2c client device.
 ///
 /// This type represents the registration of a [`struct i2c_client`]. When an instance of this

-- 
2.43.0
Re: [PATCH 4/5] rust: i2c: add I2C wrappers
Posted by Markus Probst 1 week, 1 day ago
On Sat, 2026-01-31 at 14:12 +0000, Igor Korotin via B4 Relay wrote:
> From: Igor Korotin <igor.korotin.linux@gmail.com>
> 
> Add safe Rust wrappers for common I2C/SMbus operations on I2C clients.
> 
> These wrappers provide safe abstractions over low-level C API calls:
> - SMbus byte operations: read/write byte and byte data
> - SMbus word operations: read/write word data
> - SMbus block operations: read/write I2C block data
> - Master I2C operations: master receive and master send
> 
> All operations include proper error handling through the Result type.
> 
> Signed-off-by: Igor Korotin <igor.korotin.linux@gmail.com>
> ---
>  rust/helpers/helpers.c    |  1 +
>  rust/helpers/i2c.c        | 15 ++++++++
>  rust/kernel/i2c/client.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 108 insertions(+)
> 

I think I2CClient should implement the IO [1] trait instead, as
suggested by Danilo [2].

Also I think it is a little odd that read and write is possible, on
I2CClient<Normal> and not I2CClient<Bound>. Shouldn't the assigned
driver have exclusive read and write access to the device?

Thanks
- Markus Probst

[1]
https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/commit/?h=driver-core-testing&id=121d87b28e1d9061d3aaa156c43a627d3cb5e620
[2]
https://lore.kernel.org/rust-for-linux/DDDS2V0V2NVJ.16ZKXCKUA1HUV@kernel.org/
Re: [PATCH 4/5] rust: i2c: add I2C wrappers
Posted by Igor Korotin 4 days, 1 hour ago
Hello Markus

On 1/31/2026 2:28 PM, Markus Probst wrote:
> I think I2CClient should implement the IO [1] trait instead, as
> suggested by Danilo [2].

I'm not sure it is appropriate to use IO and register! here. I2C devices
are different. Not all of them use register like access. For example 
EEPROM I2C devices allow random read/write operations inside their 
address space. After all I2C doesn't implement the same way of accessing 
its memory space as for example PCI devices.

> Also I think it is a little odd that read and write is possible, on
> I2CClient<Normal> and not I2CClient<Bound>. Shouldn't the assigned
> driver have exclusive read and write access to the device?

`Bound` can be safely dereferenced to `Normal`. Since `Normal` represents
the minimal required typestate for device operations, any API that works
with `Normal` automatically works with `Bound` or `Core` as well. Requiring
`Bound` would unnecessarily restrict the API and force duplication or 
unsafe casts.

> Thanks
> - Markus Probst
> 
> [1]
> https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/commit/?h=driver-core-testing&id=121d87b28e1d9061d3aaa156c43a627d3cb5e620
> [2]
> https://lore.kernel.org/rust-for-linux/DDDS2V0V2NVJ.16ZKXCKUA1HUV@kernel.org/
Cheers
Igor
Re: [PATCH 4/5] rust: i2c: add I2C wrappers
Posted by Danilo Krummrich 4 days, 1 hour ago
On Wed Feb 4, 2026 at 5:49 PM CET, Igor Korotin wrote:
> `Bound` can be safely dereferenced to `Normal`. Since `Normal` represents
> the minimal required typestate for device operations, any API that works
> with `Normal` automatically works with `Bound` or `Core` as well. Requiring
> `Bound` would unnecessarily restrict the API and force duplication or 
> unsafe casts.

We cannot allow drivers to retain access to device resources of unbound devices.
To prevent that we never give out a pci::Bar or IoMem instance without a Devres
container.
Re: [PATCH 4/5] rust: i2c: add I2C wrappers
Posted by Danilo Krummrich 4 days, 1 hour ago
On Wed Feb 4, 2026 at 5:49 PM CET, Igor Korotin wrote:
> I'm not sure it is appropriate to use IO and register! here. I2C devices
> are different. Not all of them use register like access. For example 
> EEPROM I2C devices allow random read/write operations inside their 
> address space. After all I2C doesn't implement the same way of accessing 
> its memory space as for example PCI devices.

Conceptually, it is not different, it is some device memory connected through
some bus.

Memory mapped I/O allows "random read/write operations" as well, see
memcpy_fromio() and memcpy_toio().

The same thing is true for DMA memory. While it's not owned by the device, it's
still shared with a device.

Gary just started working on a generic IoView<'io, T> type which will serve as a
general abstraction to interact with any kind of memory that is shared between a
device and the CPU [1].

So, for I2C this would mean that if you have register like accesses you can use
the register!() abstractions. If you want random read/write operations, you can
use IoView to copy from / to system memory or even to a user buffer directly.

Of course you can also still use Io::read32() and friends.

[1] https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Generic.20I.2FO.20backends/with/571786051
Re: [PATCH 4/5] rust: i2c: add I2C wrappers
Posted by Igor Korotin 5 hours ago
Hello Danilo

On 2/4/2026 4:59 PM, Danilo Krummrich wrote:
> On Wed Feb 4, 2026 at 5:49 PM CET, Igor Korotin wrote:
>> I'm not sure it is appropriate to use IO and register! here. I2C devices
>> are different. Not all of them use register like access. For example
>> EEPROM I2C devices allow random read/write operations inside their
>> address space. After all I2C doesn't implement the same way of accessing
>> its memory space as for example PCI devices.
> 
> Conceptually, it is not different, it is some device memory connected through
> some bus.
> 
> Memory mapped I/O allows "random read/write operations" as well, see
> memcpy_fromio() and memcpy_toio().
> 
> The same thing is true for DMA memory. While it's not owned by the device, it's
> still shared with a device.
> 
> Gary just started working on a generic IoView<'io, T> type which will serve as a
> general abstraction to interact with any kind of memory that is shared between a
> device and the CPU [1].
> 
> So, for I2C this would mean that if you have register like accesses you can use
> the register!() abstractions. If you want random read/write operations, you can
> use IoView to copy from / to system memory or even to a user buffer directly.
> 
> Of course you can also still use Io::read32() and friends.
> 
> [1] https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Generic.20I.2FO.20backends/with/571786051

thanks, that makes sense.

I don’t have a strong prior opinion on how this should look yet, so I’m 
fine aligning the I2C abstractions with the generic direction you 
describe. Using register!() for register-like accesses and IoView for 
more general read/write patterns seems reasonable.

At this point I’ll treat this as the intended model and adjust the I2C 
side accordingly once IoView is in place and its API settles.

Regards,
Igor