[RFC PREVIEW 1/4] rust: hw: add rust bindings/funcs for i2c bus

18 views
Skip to first unread message

chenmiao

unread,
Oct 23, 2025, 1:22:16 PMOct 23
to chao...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com, Chao Liu, Paolo Bonzini, Chen Miao
From: Chao Liu <chao...@openatom.club>

We have implemented the I2CBus and I2CSlave infrastructure in Rust by referring
to the SysBus device model.

Initially, we assumed that the I2CBus was at the same hierarchical level as the
PL011 device. Therefore, we followed the implementation paradigm of the PL011
device as a reference. However, in the end, we discovered that the I2CBus is
actually at the same level as the SysBus. As a result, we adopted the binding
implementation paradigm used for SysBus devices. With this adjustment, we
successfully compiled the code locally.

During the implementation process, we found that the current two paradigms in
Rust — bindings and impl — are extremely complex and lack comprehensive
documentation. There is no clear explanation as to why Bus and Device models
need to be implemented using different approaches. Furthermore, the
implementation of Bus and Device following these paradigms still has many
limitations. At present, at least vmstate is not easily supported.

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 | 315 +++++++++++++++++++++++++++++++++++++
rust/hw/core/src/lib.rs | 3 +
rust/hw/core/src/qdev.rs | 16 ++
rust/hw/core/wrapper.h | 1 +
5 files changed, 336 insertions(+)
create mode 100644 rust/hw/core/src/i2cbus.rs

diff --git a/rust/hw/core/meson.build b/rust/hw/core/meson.build
index 1560dd2..ef1f46f 100644
--- a/rust/hw/core/meson.build
+++ b/rust/hw/core/meson.build
@@ -51,6 +51,7 @@ _hwcore_rs = static_library(
'src/lib.rs',
'src/bindings.rs',
'src/irq.rs',
+ 'src/i2cbus.rs',
'src/qdev.rs',
'src/sysbus.rs',
],
diff --git a/rust/hw/core/src/i2cbus.rs b/rust/hw/core/src/i2cbus.rs
new file mode 100644
index 0000000..8f5dd67
--- /dev/null
+++ b/rust/hw/core/src/i2cbus.rs
@@ -0,0 +1,315 @@
+// Copyright 2025 HUST OpenAtom Open Source Club.
+// Author(s): Chao Liu <chao...@openatom.club>
+// Author(s): Chen Miao <chen...@openatom.club>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Bindings to access `i2c` functionality from Rust.
+
+use std::{ffi::CStr, ptr::addr_of_mut};
+
+pub use bindings::I2CSlaveClass;
+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().cast()) }
+ }
+
+ /// 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()) }
+ }
+
+ /// Sets the I2C bus master.
+ ///
+ /// # Safety
+ ///
+ /// This function is unsafe because:
+ /// - `bh` must be a valid pointer to a `QEMUBH`.
+ /// - The caller must ensure that `self` is in a valid state.
+ /// - The caller must guarantee no data races occur during execution.
+ unsafe 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> {}
+
+/// 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);
+
+// 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) {
+ self.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 init_new(name: &str, addr: u8) -> *mut bindings::I2CSlave {
+ assert!(bql::is_locked());
+ unsafe { bindings::i2c_slave_new(name.as_ptr().cast(), 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().cast(), 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(),
+ addr_of_mut!(util::bindings::error_fatal),
+ )
+ }
+ }
+}
+
+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,
+ }
+ }
+}
diff --git a/rust/hw/core/src/lib.rs b/rust/hw/core/src/lib.rs
index b40801e..653aa57 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 c3097a2..e92ea34 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 {
diff --git a/rust/hw/core/wrapper.h b/rust/hw/core/wrapper.h
index 3bdbd12..399be59 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.43.0

Paolo Bonzini

unread,
Oct 23, 2025, 1:45:42 PMOct 23
to chenmiao, chao...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com
On 10/23/25 19:22, chenmiao wrote:
> During the implementation process, we found that the current two paradigms in
> Rust — bindings and impl — are extremely complex and lack comprehensive
> documentation. There is no clear explanation as to why Bus and Device models
> need to be implemented using different approaches.

Yes, documentation can be improved and reporting what confused you is a
good way to move it forward.

> +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> {}

There are none, which is why it reuses BusClass.

> +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>();
> + }
> +}

BusClass should be in a separate patch and file.

> +/// 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);
> +
> +// TODO: add virtual methods

You are adding them, what's actually missing is I2CSlaveClass::class_init.

> +pub trait I2CSlaveImpl: DeviceImpl + IsA<I2CSlave> {
> + /// Master to slave. Returns non-zero for a NAK, 0 for success.
> + fn send(&self, data: u8) -> i32;

You should either provide default implementations, or make them "const"
members with an Option<fn(...) -> ...> data type, similar to DeviceImpl.

Also, make it return bool.

> +
> + /// 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;

You can leave this out as a TODO, since it's quite specialized (it's
used for I2C bus multiplexers).

> +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) {
> + self.parent_class.class_init::<T>();
This needs to fill in the virtual methods.

I didn't get patches 2-4 so I'm not sure if there was code to do so.
These two would be used by boards, so they are really be useful right
now; we don't yet have boards written in Rust.

But especially, the bus should be an Owned<I2CBus> to ensure that the
device can access it. Likewise, the return value would be an
Owned<I2CSlave>.
This is not needed - in Rust the unref would be handled via Owned<>, so
you don't need the utility realize_and_unref method.

> + }
> +}
> +
> +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,
> +}

Just use "pub type".

> diff --git a/rust/hw/core/src/qdev.rs b/rust/hw/core/src/qdev.rs
> index c3097a2..e92ea34 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);

Separate file, please.

Paolo

chenmiao

unread,
Oct 23, 2025, 9:41:22 PMOct 23
to chao...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com, Chao Liu, Chen Miao
From: Chao Liu <chao...@openatom.club>

We have implemented the I2CBus and I2CSlave infrastructure in Rust by referring
to the SysBus device model.

Initially, we assumed that the I2CBus was at the same hierarchical level as the
PL011 device. Therefore, we followed the implementation paradigm of the PL011
device as a reference. However, in the end, we discovered that the I2CBus is
actually at the same level as the SysBus. As a result, we adopted the binding
implementation paradigm used for SysBus devices. With this adjustment, we
successfully compiled the code locally.

During the implementation process, we found that the current two paradigms in
Rust — bindings and impl — are extremely complex and lack comprehensive
documentation. There is no clear explanation as to why Bus and Device models
need to be implemented using different approaches. Furthermore, the
implementation of Bus and Device following these paradigms still has many
limitations. At present, at least vmstate is not easily supported.

+/// 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().cast()) }
+ }
+
+ /// 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()) }
+ }
+
+ /// Sets the I2C bus master.
+ ///
+ /// # Safety
+ ///
+ /// This function is unsafe because:
+ /// - `bh` must be a valid pointer to a `QEMUBH`.
+ /// - The caller must ensure that `self` is in a valid state.
+ /// - The caller must guarantee no data races occur during execution.
+ unsafe 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> {}
+
+/// 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);
+
+// 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) {
+ self.parent_class.class_init::<T>();
+ }
+}
+
+ }
+}
+
+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,
+ }
+ }
+}
diff --git a/rust/hw/core/src/lib.rs b/rust/hw/core/src/lib.rs
index b40801e..653aa57 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 c3097a2..e92ea34 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);

Chen Miao

unread,
Oct 23, 2025, 9:57:10 PMOct 23
to Paolo Bonzini, chao...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com
Hello Paolo,

Thank you for your response. This email was originally intended to be sent to the internal team for review. Of course, I greatly appreciate your review, and we will refer to your feedback when making revisions. The follow-up email will be sent within the next couple of days!

Thanks,

Chen Miao
An open-source contributor driven by passion.

Chao Liu

unread,
Oct 23, 2025, 11:13:13 PMOct 23
to Paolo Bonzini, chenmiao, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com
Thanks for Paolo's review~

After we polish the patch, we’ll send the full patch series to the upstream
mailing list.

Thanks,
Chao

Chen Miao

unread,
Oct 23, 2025, 11:15:53 PMOct 23
to chao...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com
Hi Zevorn,

We need follow the Paolo's review comment to rewrite this patch.

Thanks,

Chen Miao

Chao Liu

unread,
Oct 23, 2025, 11:20:21 PMOct 23
to Chen Miao, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com
Yes, you can try to improve this patch first, and I will participate in the
patch review later.

Thanks,
Chao
Reply all
Reply to author
Forward
0 new messages