[PATCH preview 1/1] rust: add rust bindings/funcs for i2c bus

1 view
Skip to first unread message

Chao Liu

unread,
Oct 21, 2025, 11:10:31 PM (2 days ago) Oct 21
to chen...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com, Chao Liu, Paolo Bonzini
Suggested-by: Paolo Bonzini <pbon...@redhat.com>
Signed-off-by: Chao Liu <chao...@openatom.club>
---
rust/hw/core/meson.build | 1 +
rust/hw/core/src/i2cbus.rs | 172 +++++++++++++++++++++++++++++++++++++
rust/hw/core/src/lib.rs | 3 +
rust/hw/core/src/qdev.rs | 18 +++-
rust/hw/core/wrapper.h | 1 +
5 files changed, 194 insertions(+), 1 deletion(-)
create mode 100644 rust/hw/core/src/i2cbus.rs

diff --git a/rust/hw/core/meson.build b/rust/hw/core/meson.build
index 1560dd20c6..ab752df8fc 100644
--- a/rust/hw/core/meson.build
+++ b/rust/hw/core/meson.build
@@ -53,6 +53,7 @@ _hwcore_rs = static_library(
'src/irq.rs',
'src/qdev.rs',
'src/sysbus.rs',
+ 'src/i2cbus.rs',
],
{'.': _hwcore_bindings_inc_rs}
),
diff --git a/rust/hw/core/src/i2cbus.rs b/rust/hw/core/src/i2cbus.rs
new file mode 100644
index 0000000000..9e2741bec1
--- /dev/null
+++ b/rust/hw/core/src/i2cbus.rs
@@ -0,0 +1,172 @@
+// Copyright 2025 HUST OpenAtom Open Source Club
+// Author(s): Chao Liu <chao...@openatom.club>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Bindings to access `i2c` functionality from Rust.
+
+use std::ffi::CStr;
+
+use common::Opaque;
+use qom::prelude::*;
+
+use crate::{
+ bindings,
+ qdev::{BusState, DeviceImpl, DeviceState},
+};
+
+/// A safe wrapper around [`bindings::I2CBus`].
+#[repr(transparent)]
+#[derive(Debug, common::Wrapper)]
+pub struct I2CBus(Opaque<bindings::I2CBus>);
+
+unsafe impl Send for I2CBus {}
+unsafe impl Sync for I2CBus {}
+
+unsafe impl ObjectType for I2CBus {
+ type Class = bindings::BusClass;
+ const TYPE_NAME: &'static CStr =
+ unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_I2C_BUS) };
+}
+
+qom_isa!(I2CBus: BusState, Object);
+
+// TODO: add virtual methods
+pub trait I2CBusImpl: DeviceImpl + IsA<I2CBus> {}
+
+impl bindings::BusClass {
+ /// Fill in the virtual methods of `BusClass` based on the
+ /// definitions in the `I2CBusImpl` trait.
+ pub fn class_init<T: I2CBusImpl>(self: &mut bindings::BusClass) {
+ self.parent_class.class_init::<T>();
+ }
+}
+
+/// Trait for methods of [`I2CBus`] and its subclasses.
+pub trait I2CBusMethods: ObjectDeref
+where
+ Self::Target: IsA<I2CBus>,
+{
+ /// Initialize an I2C bus
+ fn init_bus(&self, parent: &DeviceState, name: &str) -> *mut bindings::I2CBus {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_init_bus(parent.as_mut_ptr(), name.as_ptr() as *const _)
+ }
+ }
+
+ /// Start a transfer on an I2C bus
+ fn start_transfer(&self, address: u8, is_recv: bool) -> i32 {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_start_transfer(self.upcast().as_mut_ptr(), address, is_recv)
+ }
+ }
+
+ /// Start a receive transfer on an I2C bus
+ fn start_recv(&self, address: u8) -> i32 {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_start_recv(self.upcast().as_mut_ptr(), address)
+ }
+ }
+
+ /// Start a send transfer on an I2C bus
+ fn start_send(&self, address: u8) -> i32 {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_start_send(self.upcast().as_mut_ptr(), address)
+ }
+ }
+
+ /// Start an asynchronous send transfer on an I2C bus
+ fn start_send_async(&self, address: u8) -> i32 {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_start_send_async(self.upcast().as_mut_ptr(), address)
+ }
+ }
+
+ /// End a transfer on an I2C bus
+ fn end_transfer(&self) {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_end_transfer(self.upcast().as_mut_ptr())
+ }
+ }
+
+ /// Send NACK on an I2C bus
+ fn nack(&self) {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_nack(self.upcast().as_mut_ptr())
+ }
+ }
+
+ /// Send ACK on an I2C bus
+ fn ack(&self) {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_ack(self.upcast().as_mut_ptr())
+ }
+ }
+
+ /// Send data on an I2C bus
+ fn send(&self, data: u8) -> i32 {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_send(self.upcast().as_mut_ptr(), data)
+ }
+ }
+
+ /// Send data asynchronously on an I2C bus
+ fn send_async(&self, data: u8) -> i32 {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_send_async(self.upcast().as_mut_ptr(), data)
+ }
+ }
+
+ /// Receive data from an I2C bus
+ fn recv(&self) -> u8 {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_recv(self.upcast().as_mut_ptr())
+ }
+ }
+
+ /// Check if the I2C bus is busy.
+ ///
+ /// Returns `true` if the bus is busy, `false` otherwise.
+ fn is_busy(&self) -> bool {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_bus_busy(self.upcast().as_mut_ptr()) != 0
+ }
+ }
+
+ /// Schedule pending master on an I2C bus
+ fn schedule_pending_master(&self) {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_schedule_pending_master(self.upcast().as_mut_ptr())
+ }
+ }
+
+ /// Set master for an I2C bus
+ fn set_master(&self, bh: *mut bindings::QEMUBH) {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_bus_master(self.upcast().as_mut_ptr(), bh)
+ }
+ }
+
+ /// Release an I2C bus
+ fn release(&self) {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_bus_release(self.upcast().as_mut_ptr())
+ }
+ }
+}
+
+impl<R: ObjectDeref> I2CBusMethods for R where R::Target: IsA<I2CBus> {}
\ No newline at end of file
diff --git a/rust/hw/core/src/lib.rs b/rust/hw/core/src/lib.rs
index b40801eb84..653aa57a3e 100644
--- a/rust/hw/core/src/lib.rs
+++ b/rust/hw/core/src/lib.rs
@@ -13,3 +13,6 @@

mod sysbus;
pub use sysbus::*;
+
+mod i2cbus;
+pub use i2cbus::*;
diff --git a/rust/hw/core/src/qdev.rs b/rust/hw/core/src/qdev.rs
index c3097a284d..907f54cb7a 100644
--- a/rust/hw/core/src/qdev.rs
+++ b/rust/hw/core/src/qdev.rs
@@ -37,6 +37,22 @@ unsafe impl Sync for Clock {}
unsafe impl Send for DeviceState {}
unsafe impl Sync for DeviceState {}

+/// A safe wrapper around [`bindings::BusState`].
+#[repr(transparent)]
+#[derive(Debug, common::Wrapper)]
+pub struct BusState(Opaque<bindings::BusState>);
+
+unsafe impl Send for BusState {}
+unsafe impl Sync for BusState {}
+
+unsafe impl ObjectType for BusState {
+ type Class = bindings::BusClass;
+ const TYPE_NAME: &'static CStr =
+ unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_BUS) };
+}
+
+qom_isa!(BusState: Object);
+
/// Trait providing the contents of the `ResettablePhases` struct,
/// which is part of the QOM `Resettable` interface.
pub trait ResettablePhasesImpl {
@@ -452,4 +468,4 @@ unsafe impl ObjectType for Clock {

qom_isa!(Clock: Object);

-impl_vmstate_c_struct!(Clock, bindings::vmstate_clock);
+impl_vmstate_c_struct!(Clock, bindings::vmstate_clock);
\ No newline at end of file
diff --git a/rust/hw/core/wrapper.h b/rust/hw/core/wrapper.h
index 3bdbd1249e..399be594da 100644
--- a/rust/hw/core/wrapper.h
+++ b/rust/hw/core/wrapper.h
@@ -30,3 +30,4 @@ typedef enum memory_order {
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "hw/irq.h"
+#include "hw/i2c/i2c.h"
--
2.51.0

Chao Liu

unread,
Oct 22, 2025, 12:39:19 PM (14 hours ago) Oct 22
to chen...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com, Chao Liu, Paolo Bonzini
Implement secure secure Rust bindings for I2CBus
and I2CSlave, with reference to sysbus.

Suggested-by: Paolo Bonzini <pbon...@redhat.com>
Signed-off-by: Chao Liu <chao...@openatom.club>
Signed-off-by: Chen Miao <chen...@openatom.club>
---
rust/hw/core/meson.build | 1 +
rust/hw/core/src/i2cbus.rs | 349 +++++++++++++++++++++++++++++++++++++
rust/hw/core/src/lib.rs | 3 +
rust/hw/core/src/qdev.rs | 18 +-
rust/hw/core/wrapper.h | 1 +
5 files changed, 371 insertions(+), 1 deletion(-)
create mode 100644 rust/hw/core/src/i2cbus.rs

diff --git a/rust/hw/core/meson.build b/rust/hw/core/meson.build
index 1560dd20c6..ab752df8fc 100644
--- a/rust/hw/core/meson.build
+++ b/rust/hw/core/meson.build
@@ -53,6 +53,7 @@ _hwcore_rs = static_library(
'src/irq.rs',
'src/qdev.rs',
'src/sysbus.rs',
+ 'src/i2cbus.rs',
],
{'.': _hwcore_bindings_inc_rs}
),
diff --git a/rust/hw/core/src/i2cbus.rs b/rust/hw/core/src/i2cbus.rs
new file mode 100644
index 0000000000..31228530ca
--- /dev/null
+++ b/rust/hw/core/src/i2cbus.rs
@@ -0,0 +1,349 @@
+// Copyright 2024 Red Hat, Inc.
+// Author(s): Paolo Bonzini <pbon...@redhat.com>
+/// A safe wrapper around [`bindings::I2CSlave`].
+#[repr(transparent)]
+#[derive(Debug, common::Wrapper)]
+pub struct I2CSlave(Opaque<bindings::I2CSlave>);
+
+unsafe impl Send for I2CSlave {}
+unsafe impl Sync for I2CSlave {}
+
+unsafe impl ObjectType for I2CSlave {
+ type Class = I2CSlaveClass;
+ const TYPE_NAME: &'static CStr =
+ unsafe { CStr::from_bytes_with_nul_unchecked(bindings::TYPE_I2C_SLAVE) };
+}
+
+qom_isa!(I2CSlave: DeviceState, Object);
+
+/// A safe wrapper around [`bindings::I2CSlaveClass`].
+#[repr(transparent)]
+#[derive(Debug, common::Wrapper)]
+pub struct I2CSlaveClass(Opaque<bindings::I2CSlaveClass>);
+
+unsafe impl Send for I2CSlaveClass {}
+unsafe impl Sync for I2CSlaveClass {}
+
+// TODO: add virtual methods
+pub trait I2CSlaveImpl: DeviceImpl + IsA<I2CSlave> {
+ /// Master to slave. Returns non-zero for a NAK, 0 for success.
+ fn send(&self, data: u8) -> i32;
+
+ /// Master to slave (asynchronous). Receiving slave must call i2c_ack().
+ fn send_async(&self, data: u8);
+
+ /// Slave to master. This cannot fail, the device should always return something here.
+ fn recv(&self) -> u8;
+
+ /// Notify the slave of a bus state change. For start event,
+ /// returns non-zero to NAK an operation. For other events the
+ /// return code is not used and should be zero.
+ fn event(&self, event: bindings::i2c_event) -> i32;
+
+ /// Check if this device matches the address provided. Returns bool of
+ /// true if it matches (or broadcast), and updates the device list, false
+ /// otherwise.
+ ///
+ /// If broadcast is true, match should add the device and return true.
+ fn match_and_add(&self, address: u8, broadcast: bool, current_devs: *mut bindings::I2CNodeList) -> bool;
+}
+
+impl I2CSlaveClass {
+ /// Fill in the virtual methods of `I2CSlaveClass` based on the
+ /// definitions in the `I2CSlaveImpl` trait.
+ pub fn class_init<T: I2CSlaveImpl>(self: &mut I2CSlaveClass) {
+ unsafe {
+ // SAFETY: We need to access the underlying C structure to call parent_class.class_init
+ let class_ptr = self.as_mut_ptr() as *mut bindings::I2CSlaveClass;
+ (*class_ptr).parent_class.class_init::<T>();
+ }
+ }
+}
+
+/// Trait for methods of [`I2CSlave`] and its subclasses.
+pub trait I2CSlaveMethods: ObjectDeref
+where
+ Self::Target: IsA<I2CSlave>,
+{
+ /// Create an I2C slave device on the heap.
+ ///
+ /// # Arguments
+ /// * `name` - a device type name
+ /// * `addr` - I2C address of the slave when put on a bus
+ ///
+ /// This only initializes the device state structure and allows
+ /// properties to be set. Type `name` must exist. The device still
+ /// needs to be realized.
+ fn new(name: &str, addr: u8) -> *mut bindings::I2CSlave {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_slave_new(name.as_ptr() as *const _, addr)
+ }
+ }
+
+ /// Create and realize an I2C slave device on the heap.
+ ///
+ /// # Arguments
+ /// * `bus` - I2C bus to put it on
+ /// * `name` - I2C slave device type name
+ /// * `addr` - I2C address of the slave when put on a bus
+ ///
+ /// Create the device state structure, initialize it, put it on the
+ /// specified `bus`, and drop the reference to it (the device is realized).
+ fn create_simple(&self, bus: &I2CBus, name: &str, addr: u8) -> *mut bindings::I2CSlave {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_slave_create_simple(bus.as_mut_ptr(), name.as_ptr() as *const _, addr)
+ }
+ }
+
+ /// Set the I2C bus address of a slave device
+ ///
+ /// # Arguments
+ /// * `address` - I2C address of the slave when put on a bus
+ fn set_address(&self, address: u8) {
+ assert!(bql::is_locked());
+ unsafe {
+ bindings::i2c_slave_set_address(self.upcast().as_mut_ptr(), address)
+ }
+ }
+
+ /// Get the I2C bus address of a slave device
+ fn get_address(&self) -> u8 {
+ assert!(bql::is_locked());
+ // SAFETY: the BQL ensures that no one else writes to the I2CSlave structure,
+ // and the I2CSlave must be initialized to get an IsA<I2CSlave>.
+ let slave = unsafe { *self.upcast().as_ptr() };
+ slave.address
+ }
+
+ /// Realize and drop a reference an I2C slave device
+ ///
+ /// # Arguments
+ /// * `bus` - I2C bus to put it on
+ ///
+ /// Returns: `true` on success, `false` on failure.
+ ///
+ /// Call 'realize' on the device, put it on the specified `bus`, and drop the
+ /// reference to it.
+ fn realize_and_unref(&self, bus: &I2CBus) -> bool {
+ assert!(bql::is_locked());
+ unsafe {
+ // Correct signature: i2c_slave_realize_and_unref(dev, bus, errp)
+ bindings::i2c_slave_realize_and_unref(
+ self.upcast().as_mut_ptr(),
+ bus.as_mut_ptr(),
+ std::ptr::null_mut() // errp: use null pointer to ignore errors
+ )
+ }
+ }
+}
+
+impl<R: ObjectDeref> I2CSlaveMethods for R where R::Target: IsA<I2CSlave> {}
+
+/// Enum representing I2C events
+#[repr(u32)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum I2CEvent {
+ StartRecv = bindings::I2C_START_RECV,
+ StartSend = bindings::I2C_START_SEND,
+ StartSendAsync = bindings::I2C_START_SEND_ASYNC,
+ Finish = bindings::I2C_FINISH,
+ Nack = bindings::I2C_NACK,
+}
+
+impl From<bindings::i2c_event> for I2CEvent {
+ fn from(event: bindings::i2c_event) -> Self {
+ match event {
+ bindings::I2C_START_RECV => I2CEvent::StartRecv,
+ bindings::I2C_START_SEND => I2CEvent::StartSend,
+ bindings::I2C_START_SEND_ASYNC => I2CEvent::StartSendAsync,
+ bindings::I2C_FINISH => I2CEvent::Finish,
+ bindings::I2C_NACK => I2CEvent::Nack,
+ _ => panic!("Unknown I2C event: {}", event),
+ }
+ }
+}
+
+impl From<I2CEvent> for bindings::i2c_event {
+ fn from(event: I2CEvent) -> Self {
+ match event {
+ I2CEvent::StartRecv => bindings::I2C_START_RECV,
+ I2CEvent::StartSend => bindings::I2C_START_SEND,
+ I2CEvent::StartSendAsync => bindings::I2C_START_SEND_ASYNC,
+ I2CEvent::Finish => bindings::I2C_FINISH,
+ I2CEvent::Nack => bindings::I2C_NACK,
+ }
+ }
+}
Reply all
Reply to author
Forward
0 new messages