[RFC PATCH v2 2/5] rust/hw/core: Add rust bindings/funcs for i2c bus

1 view
Skip to first unread message

chenmiao

unread,
Oct 28, 2025, 4:04:02 AMOct 28
to chao...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com, Chao Liu
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.

Signed-off-by: Chao Liu <chao...@openatom.club>
Signed-off-by: chenmiao <chen...@openatom.club>

---
Changes in V2:
- Rename the i2cbus.rs to i2c.rs.
- Add some Safety comment for `unsafe function`.
- Add the callback function for class_init.

---
rust/hw/core/meson.build | 1 +
rust/hw/core/src/i2c.rs | 399 +++++++++++++++++++++++++++++++++++++++
rust/hw/core/src/lib.rs | 3 +
rust/hw/core/wrapper.h | 1 +
4 files changed, 404 insertions(+)
create mode 100644 rust/hw/core/src/i2c.rs

diff --git a/rust/hw/core/meson.build b/rust/hw/core/meson.build
index efcda50fef..1a27c1cff7 100644
--- a/rust/hw/core/meson.build
+++ b/rust/hw/core/meson.build
@@ -52,6 +52,7 @@ _hwcore_rs = static_library(
'src/bindings.rs',
'src/bus.rs',
'src/irq.rs',
+ 'src/i2c.rs',
'src/qdev.rs',
'src/sysbus.rs',
],
diff --git a/rust/hw/core/src/i2c.rs b/rust/hw/core/src/i2c.rs
new file mode 100644
index 0000000000..2d297dbd6e
--- /dev/null
+++ b/rust/hw/core/src/i2c.rs
@@ -0,0 +1,399 @@
+// 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::NonNull};
+
+pub use crate::bindings::I2CSlaveClass;
+use common::Opaque;
+use qom::{prelude::*, Owned};
+use util::Result;
+
+use crate::{
+ bindings,
+ bus::{BusClass, BusState},
+ qdev::{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 = 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> {}
+
+/// Trait for methods of [`I2CBus`] and its subclasses.
+pub trait I2CBusMethods: ObjectDeref
+where
+ Self::Target: IsA<I2CBus>,
+{
+ /// # Safety
+ ///
+ /// 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()) }
+ }
+
+ /// # Safety
+ ///
+ /// 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) }
+ }
+
+ /// # Safety
+ ///
+ /// 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) }
+ }
+
+ /// # Safety
+ ///
+ /// 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) }
+ }
+
+ /// # Safety
+ ///
+ /// 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) }
+ }
+
+ /// # Safety
+ ///
+ /// 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()) }
+ }
+
+ /// # Safety
+ ///
+ /// Send NACK on an I2C bus
+ fn nack(&self) {
+ assert!(bql::is_locked());
+ unsafe { bindings::i2c_nack(self.upcast().as_mut_ptr()) }
+ }
+
+ /// # Safety
+ ///
+ /// Send ACK on an I2C bus
+ fn ack(&self) {
+ assert!(bql::is_locked());
+ unsafe { bindings::i2c_ack(self.upcast().as_mut_ptr()) }
+ }
+
+ /// # Safety
+ ///
+ /// 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) }
+ }
+
+ /// # Safety
+ ///
+ /// 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) }
+ }
+
+ /// # Safety
+ ///
+ /// Receive data from an I2C bus
+ fn recv(&self) -> u8 {
+ assert!(bql::is_locked());
+ unsafe { bindings::i2c_recv(self.upcast().as_mut_ptr()) }
+ }
+
+ /// # Safety
+ ///
+ /// 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 }
+ }
+
+ /// # Safety
+ ///
+ /// 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.
+ ///
+ /// TODO: `bindings:QEMUBH` should be wrapped by Opaque<>.
+ unsafe fn set_master(&self, bh: *mut bindings::QEMUBH) {
+ assert!(bql::is_locked());
+ unsafe { bindings::i2c_bus_master(self.upcast().as_mut_ptr(), bh) }
+ }
+
+ /// # Safety
+ ///
+ /// 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.
+ const SEND: Option<fn(&Self, data: u8) -> Result<bool>> = None;
+
+ /// Master to slave (asynchronous). Receiving slave must call `i2c_ack()`.
+ const SEND_ASYNC: Option<fn(&Self, data: u8)> = None;
+
+ /// Slave to master. This cannot fail, the device should always return something here.
+ const RECV: Option<fn(&Self) -> Result<bool>> = None;
+
+ /// 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.
+ const EVENT: Option<fn(&Self, event: I2CEvent) -> Result<I2CEvent>> = None;
+
+ /// 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.
+ const MATCH_AND_ADD: Option<
+ fn(
+ &Self,
+ address: u8,
+ broadcast: bool,
+ current_devs: *mut bindings::I2CNodeList,
+ ) -> Result<bool>,
+ > = None;
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer that
+/// can be downcasted to type `T`. We also expect the device is
+/// readable/writeable from one thread at any time.
+unsafe extern "C" fn rust_i2c_slave_send_fn<T: I2CSlaveImpl>(
+ obj: *mut bindings::I2CSlave,
+ data: u8,
+) -> std::os::raw::c_int {
+ let state = NonNull::new(obj).unwrap().cast::<T>();
+ match T::SEND.unwrap()(unsafe { state.as_ref() }, data) {
+ Ok(_) => 0,
+ Err(_) => 1,
+ }
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer that
+/// can be downcasted to type `T`. We also expect the device is
+/// readable/writeable from one thread at any time.
+unsafe extern "C" fn rust_i2c_slave_send_async_fn<T: I2CSlaveImpl>(
+ obj: *mut bindings::I2CSlave,
+ data: u8,
+) {
+ let state = NonNull::new(obj).unwrap().cast::<T>();
+ T::SEND_ASYNC.unwrap()(unsafe { state.as_ref() }, data);
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer that
+/// can be downcasted to type `T`. We also expect the device is
+/// readable/writeable from one thread at any time.
+unsafe extern "C" fn rust_i2c_slave_event_fn<T: I2CSlaveImpl>(
+ obj: *mut bindings::I2CSlave,
+ event: bindings::i2c_event,
+) -> std::os::raw::c_int {
+ let state = NonNull::new(obj).unwrap().cast::<T>();
+ match T::EVENT.unwrap()(unsafe { state.as_ref() }, I2CEvent::from(event)) {
+ Ok(e) => bindings::i2c_event::from(e).try_into().unwrap(),
+ Err(_) => -1,
+ }
+}
+
+/// # Safety
+///
+/// We expect the FFI user of this function to pass a valid pointer that
+/// can be downcasted to type `T`. We also expect the device is
+/// readable/writeable from one thread at any time.
+unsafe extern "C" fn rust_i2c_slave_match_and_add_fn<T: I2CSlaveImpl>(
+ obj: *mut bindings::I2CSlave,
+ address: u8,
+ broadcast: bool,
+ current_devs: *mut bindings::I2CNodeList,
+) -> bool {
+ let state = NonNull::new(obj).unwrap().cast::<T>();
+ T::MATCH_AND_ADD.unwrap()(unsafe { state.as_ref() }, address, broadcast, current_devs).is_ok()
+}
+
+impl I2CSlaveClass {
+ /// Fill in the virtual methods of `I2CSlaveClass` based on the
+ /// definitions in the `I2CSlaveImpl` trait.
+ pub fn class_init<T: I2CSlaveImpl>(&mut self) {
+ if <T as I2CSlaveImpl>::SEND.is_some() {
+ self.send = Some(rust_i2c_slave_send_fn::<T>);
+ }
+ if <T as I2CSlaveImpl>::SEND_ASYNC.is_some() {
+ self.send_async = Some(rust_i2c_slave_send_async_fn::<T>);
+ }
+ if <T as I2CSlaveImpl>::EVENT.is_some() {
+ self.event = Some(rust_i2c_slave_event_fn::<T>);
+ }
+ if <T as I2CSlaveImpl>::MATCH_AND_ADD.is_some() {
+ self.match_and_add = Some(rust_i2c_slave_match_and_add_fn::<T>);
+ }
+ 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) -> Owned<I2CSlave> {
+ assert!(bql::is_locked());
+ unsafe {
+ let slave = bindings::i2c_slave_new(name.as_ptr().cast(), addr);
+ Owned::from(I2CSlave::from_raw(slave))
+ }
+ }
+
+ /// 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) -> Owned<I2CSlave> {
+ assert!(bql::is_locked());
+ unsafe {
+ let slave =
+ bindings::i2c_slave_create_simple(bus.as_mut_ptr(), name.as_ptr().cast(), addr);
+ Owned::from(I2CSlave::from_raw(slave))
+ }
+ }
+
+ /// 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
+ }
+}
+
+impl<R: ObjectDeref> I2CSlaveMethods for R where R::Target: IsA<I2CSlave> {}
+
+/// Enum representing I2C events
+#[repr(i32)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum I2CEvent {
+ StartRecv = 0,
+ StartSend = 1,
+ StartSendAsync = 2,
+ Finish = 3,
+ Nack = 4,
+}
+
+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 10cc516664..3f3aecb258 100644
--- a/rust/hw/core/src/lib.rs
+++ b/rust/hw/core/src/lib.rs
@@ -16,3 +16,6 @@

mod bus;
pub use bus::*;
+
+mod i2c;
+pub use i2c::*;
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.43.0

chenmiao

unread,
Oct 28, 2025, 4:18:04 AMOct 28
to chao...@openatom.club, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com, Chao Liu

chenmiao

unread,
Oct 28, 2025, 6:18:47 AMOct 28
to zhao...@intel.com, pbon...@redhat.com, manos.pit...@linaro.org, richard....@linaro.org, phi...@linaro.org, chao...@openatom.club, qemu...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com

Paolo Bonzini

unread,
Oct 28, 2025, 6:45:50 AMOct 28
to chenmiao, zhao...@intel.com, manos.pit...@linaro.org, richard....@linaro.org, phi...@linaro.org, chao...@openatom.club, qemu...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
On Tue, Oct 28, 2025 at 11:18 AM chenmiao <chen...@openatom.club> 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.

I don't think they need to be implemented using different approaches.
The difference between the two is that:

- the currently implemented devices do not expose any bus, they stay
on a bus. This means the bus is never a child of the device

- the currently implemented buses are all in C code, whereas there are
devices implemented in Rust.

I agree that the Rust-to-C bridge code is complex, but it does have
documentation, much more so than the C versions in fact. If there are
specific aspects of the documentation that you would like to see
improved, you can help by explaining what problems and sources of
confusion you encountered.

> +/// 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 = 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> {}
> +/// Trait for methods of [`I2CBus`] and its subclasses.
> +pub trait I2CBusMethods: ObjectDeref
> +where
> + Self::Target: IsA<I2CBus>


There are no virtual methods, and therefore I2CBus does not have
subclasses. Therefore you don't need these traits and you can
implement the functions directly on I2CBus.

> +{
> + /// # Safety
> + ///
> + /// 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()) }
> + }

This should return an Owned<I2CBus>.

> + /// 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.
> + ///
> + /// TODO: `bindings:QEMUBH` should be wrapped by Opaque<>.
> + unsafe fn set_master(&self, bh: *mut bindings::QEMUBH) {
> + assert!(bql::is_locked());
> + unsafe { bindings::i2c_bus_master(self.upcast().as_mut_ptr(), bh) }
> + }

I agree with Zhao, I would leave out this completely. You can add a
TODO ("i2c_bus_master missing until QEMUBH is wrapped").

> + /// # Safety
> + ///
> + /// Release an I2C bus
> + fn release(&self) {
> + assert!(bql::is_locked());
> + unsafe { bindings::i2c_bus_release(self.upcast().as_mut_ptr()) }
> + }

Same for this, which is the counterpart of i2c_bus_master. In Rust, in
fact, release() should be done with an RAII guard returned by
set_master.

> +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.
> + const SEND: Option<fn(&Self, data: u8) -> Result<bool>> = None;

Make it return an "enum I2CResult { Ack, Nack };" instead of a Result.
Likewise other function pointers here do not need a Result<>.

> + /// Master to slave (asynchronous). Receiving slave must call `i2c_ack()`.
> + const SEND_ASYNC: Option<fn(&Self, data: u8)> = None;

This requires the bottom half machinery. Leave it out.
These three are used by boards, which we don't model. We can keep the
code simple by leaving them off (in addition, init_new and
create_simple would be *class* methods, as visible from the fact that
they don't use self at all).

> + /// 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
> + }
> +}
> +
> +impl<R: ObjectDeref> I2CSlaveMethods for R where R::Target: IsA<I2CSlave> {}
> +
> +/// Enum representing I2C events
> +#[repr(i32)]
> +#[derive(Debug, Clone, Copy, PartialEq, Eq)]
> +pub enum I2CEvent {
> + StartRecv = 0,
> + StartSend = 1,
> + StartSendAsync = 2,
> + Finish = 3,
> + Nack = 4,
> +}

Make it "= bindings::I2C_START_RECV" etc. You can then use
#[derive(common::TryInto)] instead of implementing by hand the From
traits.

Paolo

Chen Miao

unread,
Oct 28, 2025, 7:18:45 AMOct 28
to Paolo Bonzini, zhao...@intel.com, manos.pit...@linaro.org, richard....@linaro.org, phi...@linaro.org, chao...@openatom.club, qemu...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
On 10/28/2025 6:45 PM, Paolo Bonzini wrote:
> On Tue, Oct 28, 2025 at 11:18 AM chenmiao <chen...@openatom.club> 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.
> I don't think they need to be implemented using different approaches.
> The difference between the two is that:
>
> - the currently implemented devices do not expose any bus, they stay
> on a bus. This means the bus is never a child of the device
>
> - the currently implemented buses are all in C code, whereas there are
> devices implemented in Rust.
>
> I agree that the Rust-to-C bridge code is complex, but it does have
> documentation, much more so than the C versions in fact. If there are
> specific aspects of the documentation that you would like to see
> improved, you can help by explaining what problems and sources of
> confusion you encountered.

That makes sense to me. Since buses are mainly implemented in C, the current
focus of Rust is on porting the device layer. Rust-implemented devices are
consumers and do not create any child objects.

I think our previous confusion stemmed from the assumption that Rust was
porting the entire hierarchy, when in fact Rust is currently only implementing
the device layer.
For this part, we directly referred to the implementation of SysBus.
>> +{
>> + /// # Safety
>> + ///
>> + /// 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()) }
>> + }
> This should return an Owned<I2CBus>.
Yes, I'll fix it later.
I'll revise all the points you have raised later.

Thanks,

Chen Miao

Paolo Bonzini

unread,
Oct 28, 2025, 7:28:55 AMOct 28
to Chen Miao, zhao...@intel.com, manos.pit...@linaro.org, richard....@linaro.org, phi...@linaro.org, chao...@openatom.club, qemu...@nongnu.org, qemu-...@nongnu.org, hust-os-ker...@googlegroups.com
I see. The difference is that:

- in sysbus there are virtual methods, but Rust does not let you override them

- in I2C there are no virtual methods added (and in fact you have
"type Class = BusClass").

> I'll revise all the points you have raised later.

Thank you!

Paolo

Chao Liu

unread,
Oct 28, 2025, 8:17:45 AMOct 28
to chenmiao, luo...@openatom.club, dz...@openatom.club, plu...@openatom.club, hust-os-ker...@googlegroups.com
Reviewed-by: Chao Liu <chao...@openatom.club>
Reply all
Reply to author
Forward
0 new messages