On Thu, Jun 18, 2026 at 04:14:58PM +0300, Ilya Chichkov wrote:
> Add remote-i2c-master device that exposes a QEMU I2C bus to the
> host system through a FUSE/CUSE character device. This lets external
> host programs and standard i2c-tools interact with I2C slaves emulated
> inside QEMU as if they were real devices attached to the host.

I haven't done a detailed review yet, but reading over the documentation
and glancing at things, this looks well done.

My big question, before I spend time reviewing this, is:
Why is this useful?
I'm struggling to come up with a use case.

Another question is simultaneous access by the CPU nd the external
interface.  From what I read of the design, it looks like you could have
conflicts much like you would on a real I2C multi-master bus.  I didn't
see anything in the docs about that.

Thanks,

-corey

> 
> The implementation is split into three layers:
> 
>   - A non-blocking finite state machine that drives the QEMU I2C
>     master. It is pumped by a QEMU Bottom Half and uses virtual timers
>     to yield during long transfers and to model clock stretching for
>     asynchronous slaves, so the main loop is never blocked. The FSM
>     walks IDLE -> ADDR -> SEND/RECV -> WAIT_STRETCH -> END -> FINISHED
>     and handles NACKs (ENXIO), lost arbitration (EBUSY, with optional
>     back-off and retry), stretch timeouts, and manual abort/reset.
> 
>   - An abstract RemoteI2CBackend QOM base class that decouples the
>     internal I2C hardware state machine (the frontend) from any
>     host-specific transport, exposing on_tx_complete and on_tx_error
>     virtual callbacks.
> 
>   - A concrete remote-i2c-backend-cuse backend implementing that
>     transport over CUSE. It manages the FUSE session and integrates
>     its file descriptors into QEMU's main AioContext event loop,
>     translates Linux I2C_RDWR, I2C_SMBUS and I2C_SLAVE ioctls into
>     generic byte streams for the FSM, and formats responses back into
>     Linux I2C/SMBus structures for the FUSE driver. SMBus repeated
>     start is supported for atomic write-then-read operations.
> 
> Example usage:
> 
>   -device remote-i2c-master,i2cbus=i2c-bus.0,devname=i2c-33
>   -object remote-i2c-backend-cuse,id=b0,devname=i2c-33
> 
> This creates /dev/i2c-33 on the host, usable with i2c-tools:
> 
>   i2cdetect -y -l
>   i2cget -y <bus_id> <addr> <reg>
> 
> Signed-off-by: Ilya Chichkov <[email protected]>
> ---
>  docs/system/devices/remote-i2c-master.rst |  197 ++++
>  hw/i2c/Kconfig                            |    5 +
>  hw/i2c/meson.build                        |    6 +
>  hw/i2c/remote-i2c-backend.c               |   30 +
>  hw/i2c/remote-i2c-cuse.c                  | 1186 +++++++++++++++++++++
>  hw/i2c/remote-i2c-fsm.c                   |  521 +++++++++
>  hw/i2c/remote-i2c-master.c                |  145 +++
>  hw/i2c/trace-events                       |   29 +
>  include/hw/i2c/remote-i2c-backend.h       |   70 ++
>  include/hw/i2c/remote-i2c-cuse.h          |   93 ++
>  include/hw/i2c/remote-i2c-master.h        |   77 ++
>  qapi/qom.json                             |   18 +
>  12 files changed, 2377 insertions(+)
>  create mode 100644 docs/system/devices/remote-i2c-master.rst
>  create mode 100644 hw/i2c/remote-i2c-backend.c
>  create mode 100644 hw/i2c/remote-i2c-cuse.c
>  create mode 100644 hw/i2c/remote-i2c-fsm.c
>  create mode 100644 hw/i2c/remote-i2c-master.c
>  create mode 100644 include/hw/i2c/remote-i2c-backend.h
>  create mode 100644 include/hw/i2c/remote-i2c-cuse.h
>  create mode 100644 include/hw/i2c/remote-i2c-master.h
> 
> diff --git a/docs/system/devices/remote-i2c-master.rst 
> b/docs/system/devices/remote-i2c-master.rst
> new file mode 100644
> index 0000000000..ccc8701a5b
> --- /dev/null
> +++ b/docs/system/devices/remote-i2c-master.rst
> @@ -0,0 +1,197 @@
> +Remote I2C master
> +=================
> +
> +Overview
> +--------
> +
> +The Remote I2C master exposes a QEMU I2C bus to the host system through a
> +FUSE/CUSE (Character device in Userspace) character device. It allows
> +userspace programs and scripts on the host to interact with I2C slaves
> +emulated inside QEMU as if they were real hardware devices attached to the
> +host, accessible with the standard Linux I2C interface and tools such as
> +``i2c-tools``.
> +
> +Features
> +--------
> +
> +- Virtual I2C controller exposed to the host as a character device via CUSE
> +- Implements the Linux I2C ioctl interface (``I2C_RDWR``, ``I2C_SMBUS``,
> +  ``I2C_SLAVE``)
> +- Supports standard I2C and SMBus protocols
> +- SMBus "Repeated Start" for atomic write-then-read operations
> +- Asynchronous, non-blocking transactions driven by QEMU's Bottom Halves (BH)
> +- Clock-stretching emulation for asynchronous slave devices, so QEMU's main
> +  loop is never blocked during long transfers
> +- Integration with QEMU's AioContext for asynchronous I/O
> +- Debugging support through FUSE debug mode
> +
> +Architecture
> +------------
> +
> +The device is split into three decoupled layers:
> +
> +Master frontend (FSM)
> +~~~~~~~~~~~~~~~~~~~~~~~
> +
> +A non-blocking finite state machine drives the QEMU I2C master. It is pumped
> +by a QEMU Bottom Half and uses virtual timers to yield during long transfers
> +and to model clock stretching for asynchronous slaves. The transaction walks
> +the following states::
> +
> +    IDLE -> ADDR -> SEND/RECV -> WAIT_STRETCH -> END -> FINISHED
> +
> +- ``IDLE``         : Resting state; awaits a backend dispatch, checks bus
> +  busyness and handles retry timers if arbitration was lost.
> +- ``ADDR``         : Asserts the bus and sends the slave address. A NACK
> +  aborts the transaction (``ENXIO``); an ACK transitions to SEND, RECV or
> +  WAIT_STRETCH.
> +- ``SEND``         : Pushes data bytes to the bus, synchronously in a loop or
> +  one byte at a time for async slaves.
> +- ``RECV``         : Reads data bytes from the bus, with the same yielding
> +  behaviour as SEND.
> +- ``WAIT_STRETCH`` : Yields back to QEMU to simulate clock stretching or to
> +  enforce an artificial delay; on timer expiry it bounces back to SEND/RECV.
> +- ``END``          : Transitional state that guarantees ``i2c_end_transfer``
> +  is called gracefully.
> +- ``FINISHED``     : Cleans up timers, releases the bus and invokes the
> +  backend completion callbacks.
> +
> +Error handling covers NACKs (``ENXIO``), lost arbitration (``EBUSY``, with an
> +optional back-off and retry cooldown), stretch timeouts to avoid hung
> +transactions, and manual abort/reset issued by the backend.
> +
> +Abstract backend
> +~~~~~~~~~~~~~~~~~
> +
> +An abstract ``RemoteI2CBackend`` QOM base class strictly decouples the
> +internal QEMU I2C hardware state machine (the frontend) from any
> +host-specific transport layer. It exposes the ``on_tx_complete`` and
> +``on_tx_error`` virtual callbacks used by the FSM to report results.
> +
> +CUSE backend
> +~~~~~~~~~~~~
> +
> +The concrete ``remote-i2c-backend-cuse`` backend implements the transport
> +over CUSE. It manages the FUSE/CUSE session, integrating its file descriptors
> +directly into QEMU's main AioContext event loop. It translates Linux
> +user-space ioctls (``I2C_RDWR``, ``I2C_SMBUS``, ``I2C_SLAVE``) into generic
> +byte streams for the master frontend to process, and formats QEMU's response
> +data back into Linux-compatible I2C/SMBus structures to reply to the FUSE
> +driver.
> +
> +Invocation
> +----------
> +
> +The backend can be wired implicitly through the master device::
> +
> +    -object remote-i2c-backend-cuse,id=<id>,devname=<node_name>
> +    -device remote-i2c-master,i2cbus=<bus>,backend=<id>
> +
> +That creates a character device named ``<node_name>`` (for example
> +``/dev/i2c-33``) on the host.
> +
> +Requirements
> +------------
> +
> +Kernel requirements
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +- CUSE module loaded: ``sudo modprobe cuse``
> +- FUSE support enabled
> +
> +Library dependencies
> +~~~~~~~~~~~~~~~~~~~~~~
> +
> +- libfuse3 or libfuse (version 2.9.0 or higher)
> +- FUSE development headers
> +
> +Debugging
> +---------
> +
> +FUSE debug output can be enabled by passing FUSE options to the CUSE backend
> +through ``fuse-opts`` or by ``debug=true`` for short.
> +
> +.. code-block:: bash
> +
> +    -object remote-i2c-backend-cuse,id=b0,devname=i2c-33,fuse-opts=-d
> +
> +Running the FUSE session in the foreground with debug enabled prints the
> +incoming CUSE/ioctl traffic, which is useful when diagnosing transport
> +issues.
> +
> +Limitations
> +-----------
> +
> +10-bit I2C addressing
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +Only 7-bit I2C addresses (0–127) are supported. QEMU's I2C core stores slave
> +addresses as ``uint8_t`` and all bus functions (``i2c_start_send``,
> +``i2c_start_recv``, ``i2c_scan_bus``) accept ``uint8_t address``, so 10-bit
> +addressing cannot be represented at the framework level. The CUSE backend
> +reflects this by rejecting any address outside the 0–127 range with
> +``EINVAL``. The ``I2C_M_TEN`` flag in ``I2C_RDWR`` messages is ignored.
> +
> +Troubleshooting
> +---------------
> +
> +CUSE_INIT failures
> +~~~~~~~~~~~~~~~~~~~
> +
> +If you encounter ``CUSE_INIT`` errors:
> +
> +1. Verify the CUSE module is loaded:
> +
> +   .. code-block:: bash
> +
> +       lsmod | grep cuse
> +       sudo modprobe cuse
> +
> +2. Check permissions on the CUSE control device:
> +
> +   .. code-block:: bash
> +
> +       # Ensure the user has access to /dev/cuse
> +       ls -la /dev/cuse
> +
> +Examples
> +--------
> +
> +Basic usage
> +~~~~~~~~~~~
> +
> +Start QEMU with a ``tmp105`` temperature sensor on an Aspeed I2C bus and
> +expose that bus to the host as ``/dev/i2c-33``:
> +
> +.. code-block:: bash
> +
> +    ./qemu-system-arm -M ast2600-evb \
> +        -device tmp105,address=0x40,bus=aspeed.i2c.bus.0 \
> +        -device remote-i2c-master,i2cbus=aspeed.i2c.bus.0,devname=i2c-33
> +
> +Then access the emulated sensor from the host with ``i2c-tools``:
> +
> +.. code-block:: console
> +
> +    $ i2cdetect -y -l
> +    ilya_chichkov@ilya ~ [1]> i2cdetect -y 33
> +         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
> +    00:                         -- -- -- -- -- -- -- -- 
> +    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
> +    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
> +    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
> +    40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
> +    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
> +    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
> +
> +    $ i2cget -y 33 0x40 0x2
> +    0x4b
> +
> +    $ i2cget -y 33 0x40 0x3
> +    0x50
> +
> +See also
> +--------
> +
> +- `FUSE Documentation <https://github.com/libfuse/libfuse>`_
> +- `Character devices in user space <https://lwn.net/Articles/308445/>`_
> diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
> index 596a7a3165..84d387bf41 100644
> --- a/hw/i2c/Kconfig
> +++ b/hw/i2c/Kconfig
> @@ -49,3 +49,8 @@ config PMBUS
>  config BCM2835_I2C
>      bool
>      select I2C
> +
> +config REMOTE_I2C_MASTER
> +    bool
> +    select I2C
> +    default y if I2C_DEVICES
> diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
> index c459adcb59..ea8cf1f980 100644
> --- a/hw/i2c/meson.build
> +++ b/hw/i2c/meson.build
> @@ -18,4 +18,10 @@ i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: 
> files('ppc4xx_i2c.c'))
>  i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c'))
>  i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c'))
>  i2c_ss.add(when: 'CONFIG_BCM2835_I2C', if_true: files('bcm2835_i2c.c'))
> +i2c_ss.add(when: 'CONFIG_REMOTE_I2C_MASTER', if_true: files(
> +    'remote-i2c-master.c',
> +    'remote-i2c-backend.c',
> +    'remote-i2c-cuse.c',
> +    'remote-i2c-fsm.c'
> +))
>  system_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
> diff --git a/hw/i2c/remote-i2c-backend.c b/hw/i2c/remote-i2c-backend.c
> new file mode 100644
> index 0000000000..e8d753b491
> --- /dev/null
> +++ b/hw/i2c/remote-i2c-backend.c
> @@ -0,0 +1,30 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Remote I2C Backend (Abstract Base Class)
> + *
> + * This module defines the abstract backend interface for the Remote I2C 
> Master.
> + * It provides the QEMU Object Model (QOM) base class that strictly decouples
> + * the internal QEMU I2C hardware state machine (the frontend) from
> + * host-specific transport layers.
> + *
> + * Author:
> + * Ilya Chichkov <[email protected]>
> + *
> + */
> +#include "qemu/osdep.h"
> +#include "hw/i2c/remote-i2c-backend.h"
> +
> +static const TypeInfo remote_i2c_backend_info = {
> +    .name = TYPE_REMOTE_I2C_BACKEND,
> +    .parent = TYPE_OBJECT,
> +    .instance_size = sizeof(RemoteI2CBackend),
> +    .class_size = sizeof(RemoteI2CBackendClass),
> +    .abstract = true,
> +};
> +
> +static void remote_i2c_backend_register_types(void)
> +{
> +    type_register_static(&remote_i2c_backend_info);
> +}
> +
> +type_init(remote_i2c_backend_register_types)
> diff --git a/hw/i2c/remote-i2c-cuse.c b/hw/i2c/remote-i2c-cuse.c
> new file mode 100644
> index 0000000000..387e32e886
> --- /dev/null
> +++ b/hw/i2c/remote-i2c-cuse.c
> @@ -0,0 +1,1186 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Remote I2C CUSE Backend
> + *
> + * This module provides the concrete CUSE (Character device in Userspace)
> + * implementation of the abstract Remote I2C Backend interface. It acts as 
> the
> + * physical bridge between the Linux host's I2C subsystem and QEMU's internal
> + * I2C hardware emulation.
> + *
> + * Architecture & Responsibilities:
> + * - Inherits from the abstract `RemoteI2CBackend` QOM base class.
> + * - Initializes and manages the FUSE/CUSE session, integrating its file
> + *   descriptors directly into QEMU's main AioContext event loop.
> + * - Translates Linux user-space IOCTLs (I2C_RDWR, I2C_SMBUS, I2C_SLAVE) into
> + *   generic byte streams for the Master Frontend to process.
> + * - Implements the `on_tx_complete` and `on_tx_error` virtual callbacks to
> + *   format QEMU's response data back into Linux-compatible I2C/SMBus data
> + *   structures and reply to the FUSE driver.
> + *
> + * Usage:
> + * Instantiated via the QEMU CLI as a backend object:
> + *   -object remote-i2c-backend-cuse,
> + *           id=<id>,devname=<node_name>[,fuse-opts=<opts>]
> + *
> + * Author:
> + * Ilya Chichkov <[email protected]>
> + *
> + */
> +#include "qemu/osdep.h"
> +
> +#include "qapi/error.h"
> +#include "qemu/main-loop.h"
> +#include "hw/i2c/i2c.h"
> +#include "hw/qdev-properties-system.h"
> +#include "qemu/error-report.h"
> +#include "qemu/bswap.h"
> +#include "block/aio.h"
> +#include "qemu/log.h"
> +#include "qapi/visitor.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +
> +#include "hw/i2c/remote-i2c-cuse.h"
> +#include "hw/i2c/remote-i2c-master.h" /* For FSM dispatch and commands */
> +
> +#define I2C_BUS_BUSY_CHECK_TIMER_COOLDOWN_NS 50000
> +
> +#define I2C_BUFFER_INDEX_SIZE 2
> +#define I2C_BUFFER_INDEX_COMMAND 3
> +#define I2C_BUFFER_INDEX_DATA_0 4
> +#define I2C_BUFFER_INDEX_DATA_1 5
> +
> +
> +static bool cuse_get_debug(Object *obj, Error **errp)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(obj);
> +    return cuse->debug;
> +}
> +
> +static void cuse_set_debug(Object *obj, bool value, Error **errp)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(obj);
> +    cuse->debug = value;
> +}
> +
> +static char *cuse_get_devname(Object *obj, Error **errp)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(obj);
> +    return g_strdup(cuse->devname);
> +}
> +
> +static void cuse_set_devname(Object *obj, const char *value, Error **errp)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(obj);
> +    g_free(cuse->devname);
> +    cuse->devname = g_strdup(value);
> +}
> +
> +static char *cuse_get_fuse_opts(Object *obj, Error **errp)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(obj);
> +    return g_strdup(cuse->fuse_opts);
> +}
> +
> +static void cuse_set_fuse_opts(Object *obj, const char *value, Error **errp)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(obj);
> +    g_free(cuse->fuse_opts);
> +    cuse->fuse_opts = g_strdup(value);
> +}
> +
> +
> +/*
> + * remote_i2c_serialize_smbus_write:
> + * @cuse: The concrete CUSE backend instance.
> + * @in_val: Pointer to the incoming Linux SMBus IOCTL data structure.
> + *
> + * Translates a Linux user-space SMBus write or read command request into a 
> flat
> + * payload array compatible with QEMU's generic I2C transaction buffer
> + */
> +static
> +void remote_i2c_serialize_smbus_write(RemoteI2CBackendCuse *cuse,
> +                                      const struct i2c_smbus_ioctl_data 
> *in_val)
> +{
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +    union i2c_smbus_data data;
> +    uint8_t buf[REMOTE_I2C_BACKEND_BUF_LEN] = { 0 };
> +    uint8_t len = 0;
> +
> +    memset(backend->transaction_buf, 0, REMOTE_I2C_BACKEND_BUF_LEN);
> +
> +    buf[0] = in_val->read_write;
> +    buf[1] = (uint8_t)backend->address;
> +
> +    memcpy(&data,
> +           cuse->in_data.in_buf + sizeof(struct i2c_smbus_ioctl_data),
> +           sizeof(union i2c_smbus_data));
> +
> +    if (in_val->read_write == I2C_SMBUS_READ) {
> +        if (in_val->size == I2C_SMBUS_BYTE) {
> +            backend->transaction_length = 0;
> +            return;
> +        }
> +
> +        backend->transaction_length = 1;
> +        backend->transaction_buf[0] = in_val->command;
> +        return;
> +    }
> +
> +    switch (in_val->size) {
> +    case I2C_SMBUS_QUICK:
> +        buf[I2C_BUFFER_INDEX_SIZE] = 0;
> +        break;
> +    case I2C_SMBUS_BYTE:
> +        buf[I2C_BUFFER_INDEX_SIZE] = 1;
> +        buf[I2C_BUFFER_INDEX_COMMAND] = in_val->command;
> +        break;
> +    case I2C_SMBUS_BYTE_DATA:
> +        buf[I2C_BUFFER_INDEX_SIZE] = 2;
> +        buf[I2C_BUFFER_INDEX_COMMAND] = in_val->command;
> +        buf[I2C_BUFFER_INDEX_DATA_0] = data.byte;
> +        break;
> +    case I2C_SMBUS_WORD_DATA:
> +    case I2C_SMBUS_PROC_CALL:
> +        buf[I2C_BUFFER_INDEX_SIZE] = 3;
> +        buf[I2C_BUFFER_INDEX_COMMAND] = in_val->command;
> +        buf[I2C_BUFFER_INDEX_DATA_0] = (uint8_t)(data.word & 0xFF);
> +        buf[I2C_BUFFER_INDEX_DATA_1] = (uint8_t)(data.word >> 8 & 0xFF);
> +        break;
> +    case I2C_SMBUS_BLOCK_DATA:
> +    case I2C_SMBUS_I2C_BLOCK_BROKEN:
> +    case I2C_SMBUS_I2C_BLOCK_DATA:
> +    case I2C_SMBUS_BLOCK_PROC_CALL:
> +        {
> +            len = MIN(data.block[0], I2C_SMBUS_BLOCK_MAX);
> +            buf[I2C_BUFFER_INDEX_SIZE] = len + 2; /* Command + Count + Data 
> */
> +            buf[I2C_BUFFER_INDEX_COMMAND] = in_val->command;
> +            buf[I2C_BUFFER_INDEX_DATA_0] = len;
> +            memcpy(&buf[I2C_BUFFER_INDEX_DATA_1], &data.block[1], len);
> +        }
> +        break;
> +    default:
> +        buf[I2C_BUFFER_INDEX_SIZE] = 0;
> +        break;
> +    }
> +
> +    backend->transaction_length = buf[I2C_BUFFER_INDEX_SIZE];
> +    memcpy(backend->transaction_buf, &buf[I2C_BUFFER_INDEX_COMMAND],
> +           backend->transaction_length);
> +}
> +
> +/*
> + * remote_i2c_calculate_expected_recv_len:
> + * @cuse: The concrete CUSE backend instance.
> + *
> + * Inspects active IOCTL contexts (either SMBus reads or standard I2C_RDWR
> + * message arrays) to calculate the exact number of bytes expected to be read
> + * from the emulated I2C target.
> + */
> +static void remote_i2c_calculate_expected_recv_len(RemoteI2CBackendCuse 
> *cuse)
> +{
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +    union i2c_smbus_data *smbus_data;
> +    uint16_t size = 0;
> +
> +    if (cuse->in_data.last_cmd == I2C_SMBUS) {
> +        size = cuse->in_data.in_smbus_data->size;
> +        smbus_data = (union i2c_smbus_data *)(
> +            cuse->in_data.in_buf + sizeof(struct i2c_smbus_ioctl_data)
> +        );
> +
> +        switch (size) {
> +        case I2C_SMBUS_QUICK:
> +            backend->transaction_length = 0;
> +            break;
> +        case I2C_SMBUS_BYTE:
> +        case I2C_SMBUS_BYTE_DATA:
> +            backend->transaction_length = 1;
> +            break;
> +        case I2C_SMBUS_WORD_DATA:
> +        case I2C_SMBUS_PROC_CALL:
> +            backend->transaction_length = 2;
> +            break;
> +        case I2C_SMBUS_BLOCK_DATA:
> +        case I2C_SMBUS_I2C_BLOCK_BROKEN:
> +        case I2C_SMBUS_I2C_BLOCK_DATA:
> +        case I2C_SMBUS_BLOCK_PROC_CALL:
> +            backend->transaction_length = smbus_data->block[0];
> +            if (backend->transaction_length == 0) {
> +                backend->transaction_length = I2C_SMBUS_BLOCK_MAX;
> +            }
> +            break;
> +        default:
> +            backend->transaction_length = 0;
> +            break;
> +        }
> +    } else if (cuse->in_data.last_cmd == I2C_RDWR) {
> +        if (cuse->rdwr_msgs) {
> +            backend->transaction_length = cuse->rdwr_msgs[cuse->msg_idx].len;
> +        }
> +    }
> +}
> +
> +/*
> + * remote_i2c_deserialize_smbus_read:
> + * @cuse: The concrete CUSE backend instance.
> + *
> + * Packages raw bytes retrieved from QEMU's emulated I2C target back into the
> + * appropriate native Linux SMBus data union format.
> + */
> +static void remote_i2c_deserialize_smbus_read(RemoteI2CBackendCuse *cuse)
> +{
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +    union i2c_smbus_data *smbus_data = (union i2c_smbus_data *)(
> +        cuse->in_data.in_buf + sizeof(struct i2c_smbus_ioctl_data)
> +    );
> +    uint16_t size = cuse->in_data.in_smbus_data->size;
> +    uint8_t len = 0;
> +
> +    switch (size) {
> +    case I2C_SMBUS_BYTE:
> +    case I2C_SMBUS_BYTE_DATA:
> +        smbus_data->byte = backend->transaction_buf[0];
> +        break;
> +    case I2C_SMBUS_WORD_DATA:
> +    case I2C_SMBUS_PROC_CALL:
> +        smbus_data->word = ((uint16_t)backend->transaction_buf[0]) & 0xFF;
> +        smbus_data->word |=
> +            (((uint16_t)backend->transaction_buf[1]) << 8) & 0xFF00;
> +        break;
> +    case I2C_SMBUS_BLOCK_DATA:
> +    case I2C_SMBUS_BLOCK_PROC_CALL:
> +        len = MIN(backend->transaction_buf[0], I2C_SMBUS_BLOCK_MAX);
> +        smbus_data->block[0] = len;
> +        memcpy(&smbus_data->block[1], &backend->transaction_buf[1], len);
> +        break;
> +    case I2C_SMBUS_I2C_BLOCK_DATA:
> +    case I2C_SMBUS_I2C_BLOCK_BROKEN:
> +        len = MIN(backend->transaction_length, I2C_SMBUS_BLOCK_MAX);
> +        smbus_data->block[0] = len;
> +        memcpy(&smbus_data->block[1], backend->transaction_buf, len);
> +        break;
> +    default:
> +        break;
> +    }
> +
> +    fuse_reply_ioctl(cuse->in_data.req, 0, smbus_data, sizeof(*smbus_data));
> +}
> +
> +/*
> + * remote_i2c_update_slave_address:
> + * @cuse: The concrete CUSE backend instance.
> + * @req: The active FUSE request context handle.
> + * @address: The 7-bit target I2C slave address requested by the host OS.
> + *
> + * Validates and updates the transaction target device address in the shared
> + * backend state.
> + */
> +static
> +void remote_i2c_update_slave_address(RemoteI2CBackendCuse *cuse,
> +                                     fuse_req_t req, long address)
> +{
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +
> +    if (address < 0 || address > 127) {
> +        fuse_reply_err(req, EINVAL);
> +        return;
> +    }
> +
> +    backend->address = address;
> +    trace_remote_i2c_master_i2cdev_address(backend->address);
> +}
> +
> +/*
> + * remote_i2c_advance_rdwr_sequence:
> + * @cuse: The concrete CUSE backend instance.
> + *
> + * Iterates through the batch vector array of standard native Linux `i2c_msg`
> + * blocks received during a multi-message I2C_RDWR ioctl operation. Populates
> + * the next message's direction, length, and payload buffer into the generic
> + * transaction state machine, advancing data offsets and preparing the master
> + * frontend bus state for an updated address phase.
> + */
> +static void remote_i2c_advance_rdwr_sequence(RemoteI2CBackendCuse *cuse)
> +{
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +    const struct i2c_msg *current_msg = NULL;
> +
> +    if (!cuse->rdwr_msgs || cuse->msg_idx >= cuse->nmsgs) {
> +        backend->bus_state = I2C_BUS_END;
> +        return;
> +    }
> +
> +    current_msg = &cuse->rdwr_msgs[cuse->msg_idx];
> +    remote_i2c_update_slave_address(cuse, cuse->in_data.req,
> +                                    (long)current_msg->addr);
> +
> +    backend->transaction_length = current_msg->len;
> +    backend->transaction_index = 0;
> +    backend->addr_acked = false;
> +    backend->data_acked = false;
> +    backend->timed_out = false;
> +
> +    /* Evaluate sequence direction and staging boundaries */
> +    if (current_msg->flags & I2C_M_RD) {
> +        backend->is_recv = true;
> +    } else {
> +        backend->is_recv = false;
> +
> +        if ((cuse->rdwr_data_offset + current_msg->len) <=
> +            cuse->rdwr_in_buf_size) {
> +            memcpy(backend->transaction_buf,
> +                   cuse->in_data.in_buf + cuse->rdwr_data_offset,
> +                   current_msg->len);
> +            cuse->rdwr_data_offset += current_msg->len;
> +        } else {
> +            backend->is_transaction_failed = true;
> +            backend->bus_state = I2C_BUS_FINISHED;
> +
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                          "Remote I2C Backend: Buffer overflow during RDWR "
> +                          "deserialization. Message requests %u bytes, but "
> +                          "only %zu bytes remain.\n",
> +                          current_msg->len,
> +                          (cuse->rdwr_in_buf_size - cuse->rdwr_data_offset));
> +            return;
> +        }
> +    }
> +
> +    backend->bus_state = I2C_BUS_ADDR;
> +}
> +
> +/*
> + * remote_i2c_cuse_on_tx_complete:
> + * @backend: Pointer to the abstract RemoteI2CBackend base structure.
> + *
> + * Implements the virtual execution callback triggered by the frontend master
> + * FSM when a given hardware I2C sub-transaction completes successfully.
> + * Manages protocol sequence vector chaining (such as multi-message RDWR
> + * arrays or atomic SMBus Write-then-Read phases). Once all phases complete,
> + * it packages retrieved byte payloads and terminates the active host OS 
> IOCTL
> + * over FUSE.
> + */
> +static void remote_i2c_cuse_on_tx_complete(RemoteI2CBackend *backend)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(backend);
> +    size_t available_space = 0;
> +    size_t copy_len = 0;
> +
> +    /*
> +     * If we were receiving data during an RDWR,
> +     * copy it into the output buffer
> +     */
> +    if (cuse->in_data.last_cmd == I2C_RDWR && backend->is_recv) {
> +        available_space = REMOTE_I2C_BACKEND_RDWR_BUF_LEN - 
> cuse->rdwr_out_len;
> +        copy_len = (backend->transaction_length < available_space) ?
> +                          backend->transaction_length : available_space;
> +
> +        if (copy_len > 0) {
> +            memcpy(cuse->rdwr_out_buf + cuse->rdwr_out_len,
> +                   backend->transaction_buf,
> +                   copy_len);
> +            cuse->rdwr_out_len += copy_len;
> +        }
> +    }
> +
> +    /* Process Multi-Message Protocol Chaining Vectors */
> +    if (cuse->in_data.last_cmd == I2C_SMBUS &&
> +        cuse->smbus_restart_read &&
> +        !backend->is_recv) {
> +        backend->is_recv = true;
> +        backend->transaction_index = 0;
> +        remote_i2c_calculate_expected_recv_len(cuse);
> +        remote_i2c_fsm_dispatch(backend->frontend, REMOTE_I2C_CMD_NEXT_MSG);
> +        return;
> +    } else if (cuse->in_data.last_cmd == I2C_RDWR) {
> +        cuse->msg_idx++;
> +
> +        if (cuse->msg_idx < cuse->nmsgs) {
> +            remote_i2c_advance_rdwr_sequence(cuse);
> +            remote_i2c_fsm_dispatch(backend->frontend, 
> REMOTE_I2C_CMD_NEXT_MSG);
> +            return;
> +        }
> +    }
> +
> +    if (cuse->in_data.last_cmd == I2C_RDWR) {
> +        fuse_reply_ioctl(cuse->in_data.req, cuse->nmsgs,
> +                         cuse->rdwr_out_buf, cuse->rdwr_out_len);
> +    } else if (cuse->in_data.last_cmd == I2C_SMBUS && backend->is_recv) {
> +        if (backend->transaction_length > 0) {
> +            remote_i2c_deserialize_smbus_read(cuse);
> +        } else {
> +            fuse_reply_ioctl(cuse->in_data.req, 0, NULL, 0);
> +        }
> +    } else {
> +        fuse_reply_ioctl(cuse->in_data.req, 0, NULL, 0);
> +    }
> +
> +    if (cuse->in_data.in_buf) {
> +        g_free((gpointer)cuse->in_data.in_buf);
> +        cuse->in_data.in_buf = NULL;
> +        cuse->in_data.in_smbus_data = NULL;
> +    }
> +
> +    cuse->ioctl_state = I2C_IOCTL_START;
> +    cuse->last_ioctl = 0;
> +    cuse->smbus_restart_read = false;
> +}
> +
> +/*
> + * remote_i2c_cuse_reset_session:
> + * @cuse: The concrete CUSE backend instance.
> + *
> + * Internal helper to teardown and clear temporary transaction contexts.
> + */
> +static void remote_i2c_cuse_reset_session(RemoteI2CBackendCuse *cuse)
> +{
> +    if (cuse->in_data.in_buf) {
> +        g_free((gpointer)cuse->in_data.in_buf);
> +        cuse->in_data.in_buf = NULL;
> +        cuse->in_data.in_smbus_data = NULL;
> +    }
> +
> +    cuse->ioctl_state = I2C_IOCTL_START;
> +    cuse->last_ioctl = 0;
> +    cuse->smbus_restart_read = false;
> +}
> +
> +/*
> + * remote_i2c_cuse_on_tx_error:
> + * @backend: Pointer to the abstract RemoteI2CBackend base structure.
> + * @errno_code: The POSIX error number reflecting the nature of the failure.
> + *
> + * Implements the virtual execution callback triggered by the frontend master
> + * FSM when a hardware I2C transaction fails or aborts.
> + */
> +static
> +void remote_i2c_cuse_on_tx_error(RemoteI2CBackend *backend, int errno_code)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(backend);
> +
> +    fuse_reply_err(cuse->in_data.req, errno_code);
> +    remote_i2c_cuse_reset_session(cuse);
> +}
> +
> +/*
> + * remote_i2c_cuse_init:
> + * @userdata: Arbitrary reference pointer registered at session startup.
> + * @conn: Struct mapping runtime driver features and limits for the 
> connection.
> + *
> + * Virtual callback invoked by the FUSE infrastructure once the userspace
> + * character device mapping handshake completes successfully.
> + */
> +static void remote_i2c_cuse_init(void *userdata, struct fuse_conn_info *conn)
> +{
> +    (void)userdata;
> +    trace_remote_i2c_master_i2cdev_init();
> +}
> +
> +/*
> + * remote_i2c_cuse_open:
> + * @req: The active FUSE request context handle.
> + * @fi: Driver metadata tracker for the targeted host file descriptor.
> + *
> + * Configures the communication channel when a host application requests
> + * access to the virtual character node (e.g., /dev/i2c-33). Clears baseline
> + * tracking flags, normalizes backend engine states, and invokes a safe reset
> + * of all active tracking buffers.
> + */
> +static void remote_i2c_cuse_open(fuse_req_t req, struct fuse_file_info *fi)
> +{
> +    RemoteI2CBackendCuse *cuse = fuse_req_userdata(req);
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +
> +    cuse->is_open = true;
> +    cuse->ioctl_state = I2C_IOCTL_START;
> +    cuse->last_ioctl = 0;
> +
> +    backend->bus_state = I2C_BUS_IDLE;
> +    backend->is_recv = false;
> +    backend->waiting_for_async = false;
> +    backend->timed_out = false;
> +
> +    if (cuse->in_data.in_buf) {
> +        g_free((gpointer)cuse->in_data.in_buf);
> +        cuse->in_data.in_buf = NULL;
> +    }
> +
> +    cuse->rdwr_msgs = NULL;
> +
> +    fuse_reply_open(req, fi);
> +    trace_remote_i2c_master_i2cdev_open();
> +}
> +
> +/*
> + * remote_i2c_cuse_release:
> + * @req: The active FUSE request context handle.
> + * @fi: Driver metadata tracker for the targeted host file descriptor.
> + *
> + * Handles formal close/teardown requests from user-space applications.
> + * Tears down mapping tables, releases structural heap components to avoid
> + * memory leaks, and resets the channel boundary flags to unlinked defaults.
> + */
> +static void remote_i2c_cuse_release(fuse_req_t req, struct fuse_file_info 
> *fi)
> +{
> +    RemoteI2CBackendCuse *cuse = fuse_req_userdata(req);
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +
> +    cuse->is_open = false;
> +    cuse->ioctl_state = I2C_IOCTL_START;
> +    cuse->last_ioctl = 0;
> +
> +    backend->bus_state = I2C_BUS_IDLE;
> +
> +    g_free(cuse->in_data.in_buf);
> +    cuse->in_data.in_buf = NULL;
> +
> +    cuse->rdwr_msgs = NULL;
> +
> +    fuse_reply_err(req, 0);
> +    trace_remote_i2c_master_i2cdev_release();
> +}
> +
> +/*
> + * remote_i2c_cuse_read:
> + * @req: The active FUSE request context handle.
> + * @size: Byte count window requested by the caller.
> + * @off: Seek offset identifier within the streaming channel context.
> + * @fi: Driver metadata tracker for the targeted host file descriptor.
> + *
> + * Implements standard char-node read interfaces. Because physical
> + * I2C operations are handled strictly via target-directed IOCTL vectors,
> + * this entry point is standard-compliant stub code returning 0 bytes (EOF).
> + */
> +static void remote_i2c_cuse_read(fuse_req_t req, size_t size, off_t off,
> +                                 struct fuse_file_info *fi)
> +{
> +    (void)size;
> +    (void)off;
> +    (void)fi;
> +    fuse_reply_buf(req, NULL, 0);
> +}
> +
> +/*
> + * remote_i2c_cuse_functional:
> + * @cuse: The concrete CUSE backend instance.
> + * @req: The active FUSE request context handle.
> + * @arg: Host memory location reference where data payload
> + *       outputs will be staged.
> + * @in_buf: Unused trailing verification packet buffer context.
> + *
> + * Implements the Linux I2C_FUNCS capability negotiation IOCTL
> + * interface. Emulates a two-phase FUSE configuration state loop: first
> + * prompts the kernel driver to fetch memory staging regions, and
> + * subsequently delivers the bitmask array defining what I2C/SMBus protocols
> + * this device translates.
> + */
> +static void remote_i2c_cuse_functional(RemoteI2CBackendCuse *cuse,
> +                                       fuse_req_t req,
> +                                       void *arg,
> +                                       const void *in_buf)
> +{
> +    unsigned long backend_functionality_mask = (
> +        I2C_FUNC_I2C | I2C_FUNC_SMBUS_QUICK |
> +        I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
> +        I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WORD_DATA |
> +        I2C_FUNC_SMBUS_I2C_BLOCK
> +    );
> +
> +    struct iovec target_memory_vector = {
> +        .iov_base = arg,
> +        .iov_len = sizeof(unsigned long)
> +    };
> +
> +    switch (cuse->ioctl_state) {
> +    case I2C_IOCTL_START:
> +        cuse->ioctl_state = I2C_IOCTL_GET;
> +        fuse_reply_ioctl_retry(req, NULL, 0, &target_memory_vector, 1);
> +        break;
> +    case I2C_IOCTL_GET:
> +        fuse_reply_ioctl(req, 0, &backend_functionality_mask,
> +                         sizeof(backend_functionality_mask));
> +        cuse->ioctl_state = I2C_IOCTL_FINISHED;
> +        trace_remote_i2c_master_i2cdev_functional();
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "Remote I2C Backend: Invalid IOCTL state "
> +                      "encountered in functionality query handler: %d\n",
> +                      cuse->ioctl_state);
> +        break;
> +    }
> +}
> +
> +/*
> + * remote_i2c_cuse_address:
> + * @cuse: The concrete CUSE backend instance.
> + * @req: The active FUSE request context handle.
> + * @arg: Untyped reference mapped by the host kernel conveying the target
> + *       address.
> + * @in_buf: Unused auxiliary input payload vector.
> + *
> + * Implements the standard Linux native I2C_SLAVE ioctl entry point.
> + */
> +static void i2cdev_address(RemoteI2CBackendCuse *cuse,
> +                           fuse_req_t req,
> +                           void *arg,
> +                           const void *in_buf)
> +{
> +    (void)in_buf;
> +    remote_i2c_update_slave_address(cuse, req, (long)arg);
> +    fuse_reply_ioctl(req, 0, NULL, 0);
> +    cuse->ioctl_state = I2C_IOCTL_FINISHED;
> +}
> +
> +/*
> + * remote_i2c_cuse_cmd_rdwr:
> + * @cuse: The concrete CUSE backend instance.
> + * @req: The active FUSE request context handle.
> + * @in_arg: Raw input memory reference targeting the calling process space.
> + * @in_buf: Staged kernel buffer payload delivered via the asynchronous
> + *          FUSE engine.
> + * @in_bufsz: Total layout size in bytes of the incoming data block.
> + * @out_bufsz: Output tracking limit size reserved by the calling process
> + *             framework.
> + *
> + * Implements the complex multi-phase Linux standard I2C_RDWR ioctl layer.
> + * Orchestrates an asynchronous four-phase FUSE data collection handshake 
> loop
> + * (START -> GET -> RECV -> SEND) to fetch scatter-gather message vectors and
> + * payload blocks directly from host memory space before dispatching 
> execution
> + * to the master frontend state machine.
> + */
> +static void i2cdev_cmd_rdwr(RemoteI2CBackendCuse *cuse,
> +                            fuse_req_t req,
> +                            void *in_arg,
> +                            const void *in_buf,
> +                            size_t in_bufsz,
> +                            size_t out_bufsz)
> +{
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +    struct iovec in_iov[I2C_RDWR_IOCTL_MAX_MSGS + 2];
> +    struct iovec out_iov[I2C_RDWR_IOCTL_MAX_MSGS];
> +    const struct i2c_rdwr_ioctl_data *in_val = NULL;
> +    void *buf_copy = NULL;
> +    struct i2c_msg *msgs;
> +    uint32_t out_cnt = 0;
> +    uint32_t in_cnt = 0;
> +    size_t header_len;
> +    uint32_t i = 0;
> +
> +    if (cuse->ioctl_state == I2C_IOCTL_START) {
> +        in_iov[0].iov_base = in_arg;
> +        in_iov[0].iov_len = sizeof(struct i2c_rdwr_ioctl_data);
> +        fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
> +        cuse->ioctl_state = I2C_IOCTL_GET;
> +        return;
> +    }
> +
> +    if (in_bufsz < sizeof(struct i2c_rdwr_ioctl_data)) {
> +        fuse_reply_err(req, EINVAL);
> +        return;
> +    }
> +
> +    /*
> +     * Create an isolated local copy of host memory packets for
> +     * transaction context tracking
> +     */
> +    buf_copy = g_memdup2(in_buf, in_bufsz);
> +    in_val = buf_copy;
> +
> +    if (cuse->in_data.in_buf) {
> +        g_free((gpointer)cuse->in_data.in_buf);
> +    }
> +
> +    cuse->in_data.last_cmd = I2C_RDWR;
> +    cuse->in_data.req = req;
> +    cuse->in_data.in_rdwr_data = in_val;
> +    cuse->in_data.in_buf = buf_copy;
> +
> +    switch (cuse->ioctl_state) {
> +    case I2C_IOCTL_GET:
> +        if (in_val->nmsgs > I2C_RDWR_IOCTL_MAX_MSGS) {
> +            fuse_reply_err(req, EINVAL);
> +            return;
> +        }
> +        in_iov[0].iov_base = in_arg;
> +        in_iov[0].iov_len = sizeof(struct i2c_rdwr_ioctl_data);
> +        in_iov[1].iov_base = in_val->msgs;
> +        in_iov[1].iov_len = in_val->nmsgs * sizeof(struct i2c_msg);
> +
> +        fuse_reply_ioctl_retry(req, in_iov, 2, NULL, 0);
> +        cuse->ioctl_state = I2C_IOCTL_RECV;
> +        break;
> +
> +    case I2C_IOCTL_RECV:
> +        msgs = (
> +            (struct i2c_msg *)(
> +                (uint8_t *)in_buf + sizeof(struct i2c_rdwr_ioctl_data)
> +            )
> +        );
> +
> +        in_iov[in_cnt].iov_base = in_arg;
> +        in_iov[in_cnt].iov_len = sizeof(struct i2c_rdwr_ioctl_data);
> +        in_cnt++;
> +
> +        in_iov[in_cnt].iov_base = in_val->msgs;
> +        in_iov[in_cnt].iov_len = in_val->nmsgs * sizeof(struct i2c_msg);
> +        in_cnt++;
> +
> +        for (i = 0; i < in_val->nmsgs; i++) {
> +            if (msgs[i].flags & I2C_M_RD) {
> +                out_iov[out_cnt].iov_base = msgs[i].buf;
> +                out_iov[out_cnt].iov_len = msgs[i].len;
> +                out_cnt++;
> +            } else {
> +                in_iov[in_cnt].iov_base = msgs[i].buf;
> +                in_iov[in_cnt].iov_len = msgs[i].len;
> +                in_cnt++;
> +            }
> +        }
> +
> +        fuse_reply_ioctl_retry(req, in_iov, in_cnt, out_iov, out_cnt);
> +        cuse->ioctl_state = I2C_IOCTL_SEND;
> +        break;
> +
> +    case I2C_IOCTL_SEND:
> +        header_len = sizeof(struct i2c_rdwr_ioctl_data);
> +        cuse->nmsgs = in_val->nmsgs;
> +
> +        cuse->rdwr_msgs = (struct i2c_msg *)((uint8_t *)buf_copy + 
> header_len);
> +        cuse->rdwr_data_offset = (
> +            header_len + (cuse->nmsgs * sizeof(struct i2c_msg))
> +        );
> +        cuse->rdwr_in_buf_size = in_bufsz;
> +        cuse->rdwr_out_len = 0;
> +        cuse->msg_idx = 0;
> +
> +        remote_i2c_advance_rdwr_sequence(cuse);
> +        remote_i2c_fsm_dispatch(backend->frontend, REMOTE_I2C_CMD_START_TX);
> +        break;
> +
> +    case I2C_IOCTL_FINISHED:
> +        cuse->ioctl_state = I2C_IOCTL_START;
> +        cuse->last_ioctl = 0;
> +        break;
> +
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "Remote I2C Backend: Invalid IOCTL state "
> +                      "encountered in RDWR handler: %d\n",
> +                      cuse->ioctl_state);
> +        break;
> +    }
> +
> +    trace_remote_i2c_master_i2cdev_smbus((uint8_t)cuse->ioctl_state);
> +}
> +
> +/*
> + * remote_i2c_cuse_cmd_smbus:
> + * @cuse: The concrete CUSE backend instance.
> + * @req: The active FUSE request context handle.
> + * @in_arg: Raw input memory reference targeting the calling process
> + *          space.
> + * @in_buf: Staged kernel buffer payload delivered via the asynchronous
> + *          FUSE engine.
> + * @in_bufsz: Total layout size in bytes of the incoming data block.
> + * @out_bufsz: Output tracking limit size reserved by the calling process
> + *             framework.
> + *
> + * Implements the Linux standard I2C_SMBUS ioctl layer. Orchestrates an
> + * asynchronous multi-phase FUSE ioctl state pipeline
> + * (START -> GET -> RECV/SEND) to isolate SMBus transaction structures,
> + * evaluate specialized transfer cycles (like Write-then-Read Repeated 
> Starts),
> + * and format parameters for the shared abstract frontend state engine.
> + */
> +static void remote_i2c_cuse_cmd_smbus(RemoteI2CBackendCuse *cuse,
> +                                      fuse_req_t req,
> +                                      void *in_arg,
> +                                      const void *in_buf,
> +                                      size_t in_bufsz,
> +                                      size_t out_bufsz)
> +{
> +    RemoteI2CBackend *backend = REMOTE_I2C_BACKEND(cuse);
> +    const struct i2c_smbus_ioctl_data *in_val = NULL;
> +    struct iovec in_iov[2];
> +    size_t full_size = 0;
> +    void *buf_copy = NULL;
> +
> +    if (cuse->ioctl_state == I2C_IOCTL_START) {
> +        in_iov[0].iov_base = in_arg;
> +        in_iov[0].iov_len = sizeof(struct i2c_smbus_ioctl_data);
> +        fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
> +        cuse->ioctl_state = I2C_IOCTL_GET;
> +        return;
> +    }
> +
> +    if (in_bufsz < sizeof(struct i2c_smbus_ioctl_data)) {
> +        fuse_reply_err(req, EINVAL);
> +        return;
> +    }
> +
> +    full_size = (
> +        sizeof(struct i2c_smbus_ioctl_data) + sizeof(union i2c_smbus_data)
> +    );
> +    buf_copy = g_malloc0(full_size);
> +    memcpy(buf_copy, in_buf, in_bufsz);
> +
> +    in_val = buf_copy;
> +
> +    in_iov[0].iov_base = in_arg;
> +    in_iov[0].iov_len = sizeof(struct i2c_smbus_ioctl_data);
> +
> +    cuse->in_data.last_cmd = I2C_SMBUS;
> +    cuse->in_data.req = req;
> +    cuse->in_data.in_smbus_data = in_val;
> +    cuse->in_data.in_buf = buf_copy;
> +
> +    /*
> +     * Detect if this is an SMBus Block Read/Process Call that
> +     * requires a write-then-read chain sequence.
> +     */
> +    if (in_val->read_write == I2C_SMBUS_READ &&
> +        in_val->size != I2C_SMBUS_QUICK && in_val->size != I2C_SMBUS_BYTE) {
> +        cuse->smbus_restart_read = true;
> +    } else {
> +        cuse->smbus_restart_read = false;
> +    }
> +
> +    /* Guard check for Quick commands that do not transmit data pointers */
> +    if (cuse->ioctl_state == I2C_IOCTL_GET && !in_val->read_write) {
> +        if (!in_val->data) {
> +            cuse->ioctl_state = I2C_IOCTL_SEND;
> +        }
> +    }
> +
> +    /*
> +     * Execute subsequent streaming segments of the FUSE memory
> +     * fetching engine.
> +     */
> +    switch (cuse->ioctl_state) {
> +    case I2C_IOCTL_START:
> +        break;
> +    case I2C_IOCTL_GET:
> +        if (in_val->read_write) {
> +            struct iovec out_iov = {
> +                .iov_base = in_val->data,
> +                .iov_len = sizeof(union i2c_smbus_data)
> +            };
> +            fuse_reply_ioctl_retry(req, in_iov, 1, &out_iov, 1);
> +            cuse->ioctl_state = I2C_IOCTL_RECV;
> +        } else {
> +            if (in_val->data) {
> +                in_iov[1].iov_base = in_val->data;
> +                in_iov[1].iov_len = sizeof(union i2c_smbus_data);
> +                fuse_reply_ioctl_retry(req, in_iov, 2, NULL, 0);
> +            }
> +            cuse->ioctl_state = I2C_IOCTL_SEND;
> +        }
> +        break;
> +    case I2C_IOCTL_RECV:
> +    case I2C_IOCTL_SEND:
> +        backend->is_recv = (cuse->ioctl_state == I2C_IOCTL_RECV);
> +
> +        /* If a restart read is required, the FIRST phase is always a Write 
> */
> +        if (cuse->smbus_restart_read) {
> +            backend->is_recv = false;
> +        }
> +
> +        remote_i2c_serialize_smbus_write(cuse, cuse->in_data.in_smbus_data);
> +
> +        if (backend->is_recv) {
> +            remote_i2c_calculate_expected_recv_len(cuse);
> +        }
> +
> +        remote_i2c_fsm_dispatch(backend->frontend, REMOTE_I2C_CMD_START_TX);
> +        break;
> +    case I2C_IOCTL_FINISHED:
> +        cuse->ioctl_state = I2C_IOCTL_START;
> +        cuse->last_ioctl = 0;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "Remote I2C Backend: Invalid IOCTL state "
> +                      "encountered in SMBus handler: %d\n",
> +                      cuse->ioctl_state);
> +        break;
> +    }
> +
> +    trace_remote_i2c_master_i2cdev_smbus((uint8_t)cuse->ioctl_state);
> +}
> +
> +/*
> + * remote_i2c_cuse_ioctl:
> + * @req: The active FUSE request context handle.
> + * @cmd: The specific Linux IOCTL command numeric ID arriving from the host
> + *       kernel.
> + * @arg: Untyped data pointer mapping process-space context memory arguments.
> + * @fi: Driver metadata tracker for the targeted host file descriptor.
> + * @flags: Configuration execution metrics for specialized runtime 
> environments.
> + * @in_buf: Incoming data vector packet delivered over the channel boundary.
> + * @in_bufsz: Total available length footprint of the input buffer payload.
> + * @out_bufsz: Reserved buffer limit assigned by the calling system driver.
> + *
> + * Serves as the central multiplexing traffic cop for all arriving character
> + * device IOCTL calls. Intercepts Linux storage and interface flags, checks
> + * sequence lock states, and routes payload tasks cleanly down to dedicated
> + * parser sub-modules.
> + */
> +static void remote_i2c_cuse_ioctl(fuse_req_t req, int cmd, void *arg,
> +                         struct fuse_file_info *fi, unsigned flags,
> +                         const void *in_buf, size_t in_bufsz, size_t 
> out_bufsz)
> +{
> +    RemoteI2CBackendCuse *cuse = fuse_req_userdata(req);
> +    unsigned int ctl = cmd;
> +    (void)fi;
> +
> +    trace_remote_i2c_master_i2cdev_ioctl(cmd);
> +
> +    /*
> +     * Guard against compatibility modes that violate
> +     * the 64-bit boundary model
> +     */
> +    if (flags & FUSE_IOCTL_COMPAT) {
> +        fuse_reply_err(req, ENOSYS);
> +        return;
> +    }
> +
> +    if (cuse->ioctl_state == I2C_IOCTL_START) {
> +        cuse->last_ioctl = ctl;
> +    } else if (cuse->last_ioctl != ctl) {
> +        cuse->last_ioctl = 0;
> +        cuse->ioctl_state = I2C_IOCTL_START;
> +        fuse_reply_err(req, EINVAL);
> +        return;
> +    }
> +
> +    switch (ctl) {
> +    case I2C_SLAVE_FORCE:
> +        /*
> +         * Mapped for compliance; force-lock requests are
> +         * acknowledged directly.
> +         */
> +        fuse_reply_ioctl(req, 0, NULL, 0);
> +        break;
> +    case I2C_FUNCS:
> +        remote_i2c_cuse_functional(cuse, req, arg, in_buf);
> +        break;
> +    case I2C_SLAVE:
> +        i2cdev_address(cuse, req, arg, in_buf);
> +        break;
> +    case I2C_SMBUS:
> +        remote_i2c_cuse_cmd_smbus(cuse, req, arg, in_buf, in_bufsz, 
> out_bufsz);
> +        break;
> +    case I2C_RDWR:
> +        i2cdev_cmd_rdwr(cuse, req, arg, in_buf, in_bufsz, out_bufsz);
> +        break;
> +    default:
> +        fuse_reply_err(req, ENOTTY);
> +        break;
> +    }
> +
> +    /* Normalize context states when a sub-transaction flow hits termination 
> */
> +    if (cuse->ioctl_state == I2C_IOCTL_FINISHED) {
> +        cuse->ioctl_state = I2C_IOCTL_START;
> +        cuse->last_ioctl = 0;
> +        trace_remote_i2c_master_i2cdev_ioctl_finished(cmd);
> +    }
> +}
> +
> +/*
> + * remote_i2c_cuse_poll:
> + * @req: The active FUSE request context handle.
> + * @fi: Driver metadata tracker for the targeted host file descriptor.
> + * @ph: Unused. I2C is master-initiated request-response; no async events.
> + *
> + * Reports the device as always ready for read and write, matching the
> + * behaviour of the Linux kernel i2c-dev driver.
> + */
> +static void remote_i2c_cuse_poll(fuse_req_t req, struct fuse_file_info *fi,
> +                        struct fuse_pollhandle *ph)
> +{
> +    (void)ph;
> +    fuse_reply_poll(req, POLL_IN | POLL_OUT);
> +}
> +
> +static const struct cuse_lowlevel_ops i2cdev_ops = {
> +    .init       = remote_i2c_cuse_init,
> +    .open       = remote_i2c_cuse_open,
> +    .release    = remote_i2c_cuse_release,
> +    .read       = remote_i2c_cuse_read,
> +    .ioctl      = remote_i2c_cuse_ioctl,
> +    .poll       = remote_i2c_cuse_poll,
> +};
> +
> +/*
> + * remote_i2c_read_fuse_export:
> + * @opaque: Dereferenced pointer targeting the concrete CUSE backend 
> instance.
> + *
> + * Serves as the high-speed data pump registered into the QEMU main 
> AioContext
> + * loop. Constantly flushes the character node descriptor, intercepting
> + * arriving kernel data blocks, processing loop structures, and preventing
> + * context blocking.
> + */
> +static void remote_i2c_read_fuse_export(void *opaque)
> +{
> +    RemoteI2CBackendCuse *cuse = opaque;
> +    int ret;
> +
> +    do {
> +        ret = fuse_session_receive_buf(cuse->fuse_session, &cuse->fuse_buf);
> +    } while (ret == -EINTR);
> +
> +    if (ret < 0) {
> +        return;
> +    }
> +
> +    fuse_session_process_buf(cuse->fuse_session, &cuse->fuse_buf);
> +    trace_remote_i2c_master_fuse_io_read();
> +}
> +
> +/*
> + * remote_i2c_fuse_export:
> + * @cuse: The concrete CUSE backend instance.
> + * @errp: Pointer tracking system initialization error telemetry.
> + *
> + * Configures argument vector clusters, allocates native multi-threaded
> + * system targets, builds character bindings via low-level libraries,
> + * and maps the resulting FUSE descriptor natively into QEMU's primary
> + * asynchronous loop handlers.
> + */
> +int remote_i2c_fuse_export(RemoteI2CBackendCuse *cuse, Error **errp)
> +{
> +    GPtrArray *argv_ptr = g_ptr_array_new();
> +    char *curdir = get_current_dir_name();
> +    struct fuse_session *session = NULL;
> +    struct cuse_info ci = { 0 };
> +    char dev_name[128];
> +    int multithreaded;
> +    int ret;
> +
> +    /* Build clean FUSE parameter vectors manually */
> +    g_ptr_array_add(argv_ptr, g_strdup("qemu-remote-i2c"));
> +    g_ptr_array_add(argv_ptr, g_strdup("-f"));
> +    g_ptr_array_add(argv_ptr, g_strdup("-s"));
> +
> +    if (cuse->debug) {
> +        g_ptr_array_add(argv_ptr, g_strdup("-d"));
> +    }
> +
> +    if (cuse->fuse_opts) {
> +        char **opts = g_strsplit(cuse->fuse_opts, " ", -1);
> +        for (int i = 0; opts[i] != NULL; i++) {
> +            if (opts[i][0] != '\0') {
> +                g_ptr_array_add(argv_ptr, g_strdup(opts[i]));
> +            }
> +        }
> +        g_strfreev(opts);
> +    }
> +
> +    /* Prevent stack overrides; enforce size-bounded buffer formatting */
> +    snprintf(dev_name, sizeof(dev_name), "DEVNAME=%s", cuse->devname);
> +    const char *dev_info_argv[] = { dev_name };
> +
> +    memset(&ci, 0, sizeof(ci));
> +    ci.dev_major = 0;
> +    ci.dev_minor = 0;
> +    ci.dev_info_argc = 1;
> +    ci.dev_info_argv = dev_info_argv;
> +    ci.flags = CUSE_UNRESTRICTED_IOCTL;
> +
> +    session = cuse_lowlevel_setup(argv_ptr->len, (char **)argv_ptr->pdata,
> +                                  &ci, &i2cdev_ops, &multithreaded, cuse);
> +
> +    g_ptr_array_set_free_func(argv_ptr, g_free);
> +    g_ptr_array_free(argv_ptr, TRUE);
> +
> +    if (session == NULL) {
> +        error_setg(errp, "Remote I2C Backend: cuse_lowlevel_setup() failed");
> +        errno = EINVAL;
> +        return -1;
> +    }
> +
> +    ret = chdir(curdir);
> +    if (ret == -1) {
> +        error_setg(errp,
> +                   "Remote I2C Backend: chdir() failed to restore root 
> path");
> +        return -1;
> +    }
> +
> +    /*
> +     * Link into QEMU's primary infrastructure loop for
> +     * clean data multiplexing
> +     */
> +    cuse->ctx = iohandler_get_aio_context();
> +    aio_set_fd_handler(cuse->ctx, fuse_session_fd(session),
> +                       remote_i2c_read_fuse_export, NULL,
> +                       NULL, NULL, cuse);
> +    cuse->fuse_session = session;
> +
> +    trace_remote_i2c_master_fuse_export();
> +    return 0;
> +}
> +
> +/*
> + * remote_i2c_cuse_complete:
> + * @uc: Raw object handle routing back into the UserCreatable QOM
> + *      component interface.
> + * @errp: Pointer tracking system initialization error telemetry.
> + *
> + * Implements the standard QOM post-instantiation lifecycle callback.
> + * Asserts property constraint sanity checks before spinning up runtime
> + * export workers.
> + */
> +static void remote_i2c_cuse_complete(UserCreatable *uc, Error **errp)
> +{
> +    RemoteI2CBackendCuse *cuse = REMOTE_I2C_BACKEND_CUSE(uc);
> +
> +    if (!cuse->devname) {
> +        error_setg(errp, "remote-i2c-backend-cuse requires 'devname' 
> property");
> +        return;
> +    }
> +
> +    if (remote_i2c_fuse_export(cuse, errp) < 0) {
> +        return;
> +    }
> +}
> +
> +static void remote_i2c_cuse_class_init(ObjectClass *oc, const void *data)
> +{
> +    RemoteI2CBackendClass *bc = REMOTE_I2C_BACKEND_CLASS(oc);
> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
> +
> +    bc->on_tx_complete = remote_i2c_cuse_on_tx_complete;
> +    bc->on_tx_error = remote_i2c_cuse_on_tx_error;
> +
> +    ucc->complete = remote_i2c_cuse_complete;
> +
> +    object_class_property_add_bool(oc, "debug",
> +                                   cuse_get_debug, cuse_set_debug);
> +    object_class_property_set_description(oc, "debug",
> +                                          "Enable debug logging for CUSE 
> backend");
> +
> +    object_class_property_add_str(oc, "devname",
> +                                  cuse_get_devname, cuse_set_devname);
> +    object_class_property_add_str(oc, "fuse-opts",
> +                                  cuse_get_fuse_opts,
> +                                  cuse_set_fuse_opts);
> +}
> +
> +static const TypeInfo remote_i2c_cuse_info = {
> +    .name = TYPE_REMOTE_I2C_BACKEND_CUSE,
> +    .parent = TYPE_REMOTE_I2C_BACKEND,
> +    .instance_size = sizeof(RemoteI2CBackendCuse),
> +    .class_init = remote_i2c_cuse_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +static void remote_i2c_cuse_register_types(void)
> +{
> +    type_register_static(&remote_i2c_cuse_info);
> +}
> +
> +type_init(remote_i2c_cuse_register_types)
> diff --git a/hw/i2c/remote-i2c-fsm.c b/hw/i2c/remote-i2c-fsm.c
> new file mode 100644
> index 0000000000..6cebd3cca5
> --- /dev/null
> +++ b/hw/i2c/remote-i2c-fsm.c
> @@ -0,0 +1,521 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Remote I2C Master Finite State Machine (FSM)
> + *
> + * This module implements a non-blocking, asynchronous Finite State Machine
> + * (FSM) for driving a QEMU I2C master. It bridges a remote backend (which
> + * issues transactions) with QEMU's internal I2C bus architecture.
> + *
> + * The FSM ensures that QEMU's main loop is not blocked during lengthy I2C
> + * transactions or when communicating with asynchronous I2C slave devices
> + * (which require simulated clock stretching).
> + *
> + * Execution Model:
> + * ----------------
> + * The state machine is driven by a QEMU Bottom Half (BH) loop
> + * (`remote_i2c_fsm_bh`).
> + *
> + * 1. Dispatch: The backend initiates a transaction via
> + *    `remote_i2c_fsm_dispatch` with `REMOTE_I2C_CMD_START_TX`.
> + * 2. Execution: The FSM processes a single byte or address frame, updates 
> its
> + *    internal state, and then either re-schedules the BH immediately (for
> + *    synchronous devices) or sets a timer to wake up later.
> + * 3. Completion: Once the transaction finishes or errors out, control is
> + *    returned to the backend via `on_tx_complete` or `on_tx_error` 
> callbacks.
> + *
> + * State Lifecycle:
> + * ----------------
> + * - I2C_BUS_IDLE     : Resting state. Awaits backend dispatch. Checks
> + *                      for bus busyness and handles retry timers if
> + *                      arbitration is lost.
> + * - I2C_BUS_ADDR     : Master asserts the bus and sends the slave address. 
> If
> + *                      the slave NACKs, the transaction is immediately 
> aborted
> + *                      (ENXIO). If ACKed, transitions to SEND, RECV,
> + *                      or WAIT_STRETCH.
> + * - I2C_BUS_SEND     : Pushes data bytes to the bus. Loops synchronously or
> + *                      yields asynchronously per byte.
> + * - I2C_BUS_RECV     : Reads data bytes from the bus. Similar yielding 
> behavior
> + *                      as SEND.
> + * - I2C_BUS_WAIT_STRETCH : Yields execution back to QEMU to simulate clock
> + *                      stretching for async slaves or to enforce artificial
> + *                      delays (slow_delay_value_ms). Upon timer expiration,
> + *                      bounces back to SEND/RECV.
> + * - I2C_BUS_END      : Transitional state to guarantee `i2c_end_transfer` is
> + *                      called gracefully before finalizing.
> + * - I2C_BUS_FINISHED : Cleans up timers, releases the I2C bus, and invokes
> + *                      backend callbacks.
> + *
> + * Asynchronous Support & Clock Stretching:
> + * ----------------------------------------
> + * When communicating with asynchronous slave devices
> + * (`sc->send_async != NULL`), the FSM cannot process the entire transaction
> + * buffer in a single pass.
> + * Instead, it sends/receives a single byte, transitions to
> + * `I2C_BUS_WAIT_STRETCH`, and sets a virtual timer (`s->timer_step`). When
> + * the timer fires, the BH loop resumes processing the next byte. This
> + * effectively simulates hardware clock stretching without stalling the
> + * guest OS.
> + *
> + * Error Handling:
> + * ---------------
> + * - NACKs: Immediately abort the transaction and release the bus.
> + * - Bus Busy: If arbitration is lost, the module can either error out 
> (EBUSY)
> + *   or back off and retry using a cooldown timer.
> + * - Timeouts: Monitored during `WAIT_STRETCH` to prevent hung transactions 
> if
> + *   a remote slave becomes unresponsive.
> + * - Manual Abort: The backend can issue `REMOTE_I2C_CMD_ABORT` or `RESET` to
> + *   forcefully release the bus and reset the FSM.
> + *
> + * Author:
> + * Ilya Chichkov <[email protected]>
> + *
> + */
> +#include "qemu/osdep.h"
> +
> +#include "qapi/error.h"
> +#include "qemu/main-loop.h"
> +#include "hw/i2c/i2c.h"
> +#include "hw/qdev-properties-system.h"
> +#include "qemu/error-report.h"
> +#include "qemu/bswap.h"
> +#include "block/aio.h"
> +#include "qemu/log.h"
> +#include "qapi/visitor.h"
> +#include "hw/i2c/remote-i2c-cuse.h"
> +#include "hw/i2c/remote-i2c-backend.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +
> +#include "hw/i2c/remote-i2c-master.h"
> +
> +#define I2C_BUS_BUSY_CHECK_TIMER_COOLDOWN_NS 50000
> +
> +typedef enum i2c_state_result {
> +    I2C_HANDLER_OK,
> +    I2C_HANDLER_ERROR,
> +} i2c_state_result;
> +
> +static const char *remote_i2c_bus_state_str(int state)
> +{
> +    switch (state) {
> +    case I2C_BUS_IDLE:          return "Idle";
> +    case I2C_BUS_ADDR:          return "Address";
> +    case I2C_BUS_SEND:          return "Send";
> +    case I2C_BUS_RECV:          return "Receive";
> +    case I2C_BUS_END:           return "End";
> +    case I2C_BUS_FINISHED:      return "Finished";
> +    case I2C_BUS_WAIT_STRETCH:  return "WaitStretch";
> +    default:                    return "Unknown";
> +    }
> +}
> +
> +static
> +void remote_i2c_fsm_change_bus_state(RemoteI2CMasterState *s, int new_state)
> +{
> +    uint16_t old_state = s->backend->bus_state;
> +    s->backend->bus_state = new_state;
> +
> +    trace_remote_i2c_master_bus_state_change(
> +        remote_i2c_bus_state_str(old_state),
> +        remote_i2c_bus_state_str(new_state)
> +    );
> +}
> +
> +static
> +void remote_i2c_abort_transaction(RemoteI2CMasterState *s,
> +                                  int errno_code, const char *reason)
> +{
> +    RemoteI2CBackendClass *bc = REMOTE_I2C_BACKEND_GET_CLASS(s->backend);
> +
> +    timer_del(s->timer);
> +    timer_del(s->timer_start_transmit);
> +    timer_del(s->timer_step);
> +
> +    if (s->backend->bus_state != I2C_BUS_IDLE &&
> +        s->backend->bus_state != I2C_BUS_FINISHED) {
> +        i2c_end_transfer(s->bus);
> +    }
> +
> +    i2c_bus_release(s->bus);
> +    remote_i2c_fsm_change_bus_state(s, I2C_BUS_IDLE);
> +
> +    s->backend->is_transaction_failed = true;
> +    s->backend->addr_acked = false;
> +    s->backend->data_acked = false;
> +    s->backend->timed_out = false;
> +    s->backend->transaction_index = 0;
> +    s->backend->waiting_for_async = false;
> +
> +    trace_remote_i2c_master_abort(errno_code,
> +                                  s->backend->address,
> +                                  reason ? reason : "Unknown error");
> +
> +    if (bc->on_tx_error) {
> +        bc->on_tx_error(s->backend, errno_code);
> +    }
> +}
> +
> +static i2c_state_result remote_i2c_finish_handler(RemoteI2CMasterState *s)
> +{
> +    RemoteI2CBackendClass *bc = REMOTE_I2C_BACKEND_GET_CLASS(s->backend);
> +
> +    timer_del(s->timer);
> +    timer_del(s->timer_start_transmit);
> +    timer_del(s->timer_step);
> +
> +    if (s->backend->is_transaction_failed) {
> +        remote_i2c_fsm_change_bus_state(s, I2C_BUS_IDLE);
> +        i2c_end_transfer(s->bus);
> +        i2c_bus_release(s->bus);
> +        if (bc->on_tx_error) {
> +            bc->on_tx_error(s->backend, EIO);
> +        }
> +        return I2C_HANDLER_ERROR;
> +    }
> +
> +    remote_i2c_fsm_change_bus_state(s, I2C_BUS_IDLE);
> +
> +    /*
> +     * Call on_tx_complete BEFORE ending or releasing the bus.
> +     * If the backend chains another sub-message via REMOTE_I2C_CMD_NEXT_MSG
> +     * (repeated start), it sets bus_state back to I2C_BUS_ADDR. In that case
> +     * we must NOT call i2c_end_transfer (no STOP to the slave) and must NOT
> +     * release the bus. Only issue STOP + release when the backend is truly 
> done
> +     * and bus_state remains IDLE.
> +     */
> +    if (bc->on_tx_complete) {
> +        bc->on_tx_complete(s->backend);
> +    }
> +
> +    if (s->backend->bus_state == I2C_BUS_IDLE) {
> +        i2c_end_transfer(s->bus);
> +        i2c_bus_release(s->bus);
> +    }
> +
> +    return I2C_HANDLER_OK;
> +}
> +
> +static
> +void remote_i2c_is_slave_async(RemoteI2CMasterState *s)
> +{
> +    I2CNode *node;
> +    I2CSlave *slave;
> +    I2CSlaveClass *sc;
> +
> +    node = QLIST_FIRST(&s->bus->current_devs);
> +    if (node) {
> +        slave = node->elt;
> +        sc = I2C_SLAVE_GET_CLASS(slave);
> +        s->backend->is_slave_async = (sc->send_async != NULL);
> +    } else {
> +        s->backend->is_slave_async = false;
> +    }
> +}
> +
> +static
> +void remote_i2c_send_ack(RemoteI2CMasterState *s)
> +{
> +    i2c_ack(s->bus);
> +}
> +
> +static
> +void remote_i2c_wait_stretch(RemoteI2CMasterState *s)
> +{
> +    timer_mod(s->timer,
> +              qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 
> s->backend->timeout_ms);
> +    remote_i2c_fsm_change_bus_state(s, I2C_BUS_WAIT_STRETCH);
> +}
> +
> +static
> +void remote_i2c_stretch_clk(RemoteI2CMasterState *s, int64_t expire_timer)
> +{
> +    timer_mod(s->timer_step,
> +              qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + expire_timer);
> +}
> +
> +static i2c_state_result remote_i2c_addr_handler(RemoteI2CMasterState *s)
> +{
> +    bool started_async = true;
> +
> +    s->backend->transaction_index = 0;
> +    s->backend->is_transaction_failed = false;
> +    s->backend->addr_acked = false;
> +    s->backend->data_acked = false;
> +    s->backend->timed_out = false;
> +
> +    if (s->backend->is_recv) {
> +        
> trace_remote_i2c_master_i2cdev_receive(s->backend->transaction_length);
> +
> +        /* i2c_start_recv returns non-zero if the slave NACKs the address */
> +        if (i2c_start_recv(s->bus, s->backend->address)) {
> +            goto nack;
> +        }
> +
> +        remote_i2c_is_slave_async(s);
> +        if (s->backend->is_slave_async) {
> +            remote_i2c_wait_stretch(s);
> +            return I2C_HANDLER_OK;
> +        }
> +
> +        s->backend->addr_acked = true;
> +        remote_i2c_send_ack(s);
> +        remote_i2c_fsm_change_bus_state(s, I2C_BUS_RECV);
> +    } else {
> +        trace_remote_i2c_master_i2cdev_send(s->backend->transaction_length);
> +
> +        if (i2c_start_send_async(s->bus, s->backend->address)) {
> +            /*
> +             * Fallback to synchronous send.
> +             * If it still returns non-zero, it's a NACK.
> +             */
> +            if (i2c_start_send(s->bus, s->backend->address)) {
> +                goto nack;
> +            }
> +            started_async = false;
> +        }
> +
> +        if (started_async) {
> +            remote_i2c_is_slave_async(s);
> +        } else {
> +            s->backend->is_slave_async = false;
> +        }
> +
> +        if (s->backend->is_slave_async) {
> +            remote_i2c_wait_stretch(s);
> +            return I2C_HANDLER_OK;
> +        }
> +
> +        s->backend->addr_acked = true;
> +        remote_i2c_send_ack(s);
> +        remote_i2c_fsm_change_bus_state(s, I2C_BUS_SEND);
> +    }
> +
> +    return I2C_HANDLER_OK;
> +
> +nack:
> +    remote_i2c_abort_transaction(s, ENXIO, "Address NACKed");
> +
> +    return I2C_HANDLER_ERROR;
> +}
> +
> +static i2c_state_result remote_i2c_send_handler(RemoteI2CMasterState *s)
> +{
> +    uint8_t data = 0;
> +    int ret = 0;
> +
> +    if (s->backend->is_slave_async && s->backend->data_acked) {
> +        s->backend->data_acked = false;
> +        s->backend->transaction_index++;
> +    }
> +
> +    if (s->backend->transaction_index >= s->backend->transaction_length) {
> +        remote_i2c_fsm_change_bus_state(s, I2C_BUS_END);
> +        return I2C_HANDLER_OK;
> +    }
> +
> +    if (s->backend->is_slave_async) {
> +        data = s->backend->transaction_buf[s->backend->transaction_index];
> +        trace_remote_i2c_master_send_byte(data);
> +
> +        i2c_send_async(s->bus, data);
> +        remote_i2c_wait_stretch(s);
> +        return I2C_HANDLER_OK;
> +    }
> +
> +    for (; s->backend->transaction_index < s->backend->transaction_length;
> +         s->backend->transaction_index++) {
> +        data = s->backend->transaction_buf[s->backend->transaction_index];
> +        trace_remote_i2c_master_send_byte(data);
> +
> +        ret = i2c_send(s->bus, data);
> +        if (ret != 0) {
> +            s->backend->is_transaction_failed = true;
> +            remote_i2c_fsm_change_bus_state(s, I2C_BUS_END);
> +            return I2C_HANDLER_ERROR;
> +        }
> +    }
> +
> +    remote_i2c_fsm_change_bus_state(s, I2C_BUS_END);
> +    return I2C_HANDLER_OK;
> +}
> +
> +static i2c_state_result remote_i2c_recv_handler(RemoteI2CMasterState *s)
> +{
> +    uint8_t buf = 0;
> +
> +    s->backend->data_acked = false;
> +
> +    if (s->backend->transaction_index < s->backend->transaction_length) {
> +        buf = i2c_recv(s->bus);
> +        trace_remote_i2c_master_recv_byte(buf);
> +
> +        s->backend->transaction_buf[s->backend->transaction_index] = buf;
> +        s->backend->transaction_index++;
> +    }
> +
> +    if (s->backend->transaction_index >= s->backend->transaction_length) {
> +        remote_i2c_fsm_change_bus_state(s, I2C_BUS_END);
> +        return I2C_HANDLER_OK;
> +    }
> +
> +    if (s->backend->is_slave_async) {
> +        remote_i2c_wait_stretch(s);
> +    } else {
> +        remote_i2c_send_ack(s);
> +    }
> +
> +    return I2C_HANDLER_OK;
> +}
> +
> +/*
> + * remote_i2c_fsm_bh:
> + * @opaque: Dereferenced generic pointer pointing to the 
> RemoteI2CMasterState.
> + *
> + * Serves as the primary asynchronous Bottom Half (BH) scheduler and state
> + * dispatch loop for the frontend I2C hardware machine.
> + */
> +void remote_i2c_fsm_bh(void *opaque)
> +{
> +    RemoteI2CMasterState *s = opaque;
> +
> +    s->backend->waiting_for_async = false;
> +
> +    switch (s->backend->bus_state) {
> +    case I2C_BUS_IDLE:
> +        break;
> +    case I2C_BUS_ADDR:
> +        remote_i2c_addr_handler(s);
> +        break;
> +    case I2C_BUS_SEND:
> +        remote_i2c_send_handler(s);
> +        break;
> +    case I2C_BUS_RECV:
> +        remote_i2c_recv_handler(s);
> +        break;
> +    case I2C_BUS_END:
> +        remote_i2c_fsm_change_bus_state(s, I2C_BUS_FINISHED);
> +        break;
> +    case I2C_BUS_FINISHED:
> +        remote_i2c_finish_handler(s);
> +        break;
> +    case I2C_BUS_WAIT_STRETCH:
> +    case I2C_BUS_WAIT_RELEASE:
> +        if (s->backend->timed_out) {
> +            remote_i2c_abort_transaction(s, ETIMEDOUT,
> +                                         "Timed out waiting for slave to "
> +                                         "release clock stretching");
> +            return;
> +        }
> +
> +        if (!s->delay_completed && s->slow_delay_value_ms > 0) {
> +            s->delay_completed = true;
> +            remote_i2c_stretch_clk(s, s->slow_delay_value_ms * 1000000ULL);
> +            trace_remote_i2c_master_stretch_delay(s->slow_delay_value_ms);
> +            return;
> +        }
> +
> +        s->delay_completed = false;
> +
> +        timer_del(s->timer);
> +        timer_del(s->timer_start_transmit);
> +        timer_del(s->timer_step);
> +
> +        if (!s->backend->addr_acked) {
> +            trace_remote_i2c_master_async_ack();
> +            s->backend->addr_acked = true;
> +            s->backend->data_acked = s->backend->is_recv;
> +        } else {
> +            trace_remote_i2c_master_async_ack();
> +            s->backend->data_acked = true;
> +        }
> +
> +        if (s->backend->is_recv) {
> +            remote_i2c_fsm_change_bus_state(s, I2C_BUS_RECV);
> +            remote_i2c_recv_handler(s);
> +        } else {
> +            remote_i2c_fsm_change_bus_state(s, I2C_BUS_SEND);
> +            remote_i2c_send_handler(s);
> +        }
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "Remote I2C Master: Invalid bus state %d\n",
> +                      s->backend->bus_state);
> +        remote_i2c_abort_transaction(s, EINVAL, "Invalid FSM state");
> +        break;
> +    }
> +
> +    /* Reschedule Logic */
> +    if (s->backend->bus_state != I2C_BUS_IDLE &&
> +        s->backend->bus_state != I2C_BUS_WAIT_STRETCH) {
> +        qemu_bh_schedule(s->bh);
> +    }
> +}
> +
> +static void remote_i2c_bus_start_transmit(RemoteI2CMasterState *s)
> +{
> +    RemoteI2CBackendClass *bc = REMOTE_I2C_BACKEND_GET_CLASS(s->backend);
> +
> +    if (i2c_bus_busy(s->bus)) {
> +        if (s->raise_arbitrage_lost) {
> +            if (bc->on_tx_error) {
> +                bc->on_tx_error(s->backend, EBUSY);
> +            }
> +            return;
> +        } else {
> +            timer_mod(s->timer_start_transmit,
> +                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
> +                      I2C_BUS_BUSY_CHECK_TIMER_COOLDOWN_NS);
> +            return;
> +        }
> +    }
> +
> +    remote_i2c_fsm_change_bus_state(s, I2C_BUS_ADDR);
> +
> +    i2c_bus_master(s->bus, s->bh);
> +    i2c_schedule_pending_master(s->bus);
> +}
> +
> +void remote_i2c_fsm_dispatch(RemoteI2CMasterState *s, RemoteI2CCommand cmd)
> +{
> +    switch (cmd) {
> +    case REMOTE_I2C_CMD_START_TX:
> +        remote_i2c_bus_start_transmit(s);
> +        break;
> +    case REMOTE_I2C_CMD_NEXT_MSG:
> +        /*
> +         * Repeated START: the bus is already held from the previous
> +         * sub-transaction. Skip bus acquisition and go straight to the
> +         * address phase so the slave sees Sr (repeated start) not a
> +         * full STOP+START cycle.
> +         *
> +         * Do NOT schedule the BH here. The BH reschedule logic at the
> +         * bottom of remote_i2c_fsm_bh will schedule it once state=ADDR
> +         * is visible on the next iteration.
> +         */
> +        remote_i2c_fsm_change_bus_state(s, I2C_BUS_ADDR);
> +        break;
> +    case REMOTE_I2C_CMD_ABORT:
> +        remote_i2c_abort_transaction(s, ECANCELED,
> +                                     "Transaction aborted by backend");
> +        break;
> +    case REMOTE_I2C_CMD_RESET:
> +        if (s->backend->bus_state != I2C_BUS_IDLE &&
> +            s->backend->bus_state != I2C_BUS_FINISHED) {
> +            i2c_end_transfer(s->bus);
> +        }
> +        i2c_bus_release(s->bus);
> +        remote_i2c_fsm_change_bus_state(s, I2C_BUS_IDLE);
> +        s->backend->is_transaction_failed = false;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "Remote I2C Master: Invalid command %d\n", cmd);
> +        break;
> +    }
> +}
> +
> +void remote_i2c_fsm_timer_start_transmit_cb(void *opaque)
> +{
> +    remote_i2c_bus_start_transmit(opaque);
> +}
> diff --git a/hw/i2c/remote-i2c-master.c b/hw/i2c/remote-i2c-master.c
> new file mode 100644
> index 0000000000..31e5da208a
> --- /dev/null
> +++ b/hw/i2c/remote-i2c-master.c
> @@ -0,0 +1,145 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Remote I2C Master
> + *
> + * This device exposes a QEMU I2C bus to the host system. Utilizing a 
> decoupled
> + * backend architecture (such as the CUSE backend), it allows external 
> programs
> + * or scripts on the host to interact with I2C slaves simulated inside QEMU 
> as
> + * if they were real hardware devices attached to the host.
> + *
> + * Features:
> + * - Clean Frontend/Backend separation for transport-agnostic I2C emulation.
> + * - Implements the Linux I2C ioctl interface (I2C_RDWR, I2C_SMBUS) via CUSE.
> + * - Supports standard I2C and SMBus protocols.
> + * - Handles asynchronous I2C transactions and clock stretching via QEMU
> + *   Bottom Halves (BH).
> + * - Configurable asynchronous slave timeouts (e.g., timeout-ms).
> + * - Supports SMBus "Repeated Start" for atomic Write-then-Read operations.
> + *
> + * Usage:
> + * Add the backend object and the master device to your QEMU command line:
> + *
> + *   -object remote-i2c-backend-cuse,id=my_cuse_backend,devname=i2c-33 \
> + *   -device remote-i2c-master,i2cbus=/machine/soc[0]/i2c[0]/i2c,\
> + *           backend=my_cuse_backend,timeout-ms=8000
> + *
> + * This creates a character device at /dev/i2c-33 on the host.
> + * Use standard tools (i2c-tools) or C programs to access it:
> + *
> + *   i2cdetect -y -l
> + *   i2cget -y <bus_id> <addr> <reg>
> + *
> + * Author:
> + * Ilya Chichkov <[email protected]>
> + *
> + */
> +#include "qemu/osdep.h"
> +
> +#include "qapi/error.h"
> +#include "qemu/main-loop.h"
> +#include "hw/i2c/i2c.h"
> +#include "hw/qdev-properties-system.h"
> +#include "qemu/error-report.h"
> +#include "qemu/bswap.h"
> +#include "block/aio.h"
> +#include "qemu/log.h"
> +#include "qapi/visitor.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +
> +#include "hw/i2c/remote-i2c-master.h"
> +
> +static void remote_i2c_timer_cb(void *opaque)
> +{
> +    RemoteI2CMasterState *s = opaque;
> +
> +    if (s->backend->bus_state == I2C_BUS_WAIT_STRETCH) {
> +        trace_remote_i2c_master_timeout(s->backend->timeout_ms);
> +        s->backend->timed_out = true;
> +        qemu_bh_schedule(s->bh);
> +    } else if (s->backend->bus_state != I2C_BUS_IDLE &&
> +               s->backend->bus_state != I2C_BUS_WAIT_STRETCH) {
> +        /* Retry logic */
> +        timer_mod(s->timer_start_transmit,
> +                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50000);
> +    }
> +}
> +
> +static void remote_i2c_timer_step_cb(void *opaque)
> +{
> +    RemoteI2CMasterState *s = opaque;
> +    qemu_bh_schedule(s->bh);
> +}
> +
> +static void remote_i2c_master_realize(DeviceState *dev, Error **errp)
> +{
> +    RemoteI2CMasterState *s = REMOTE_I2C_MASTER(dev);
> +
> +    if (!s->backend) {
> +        error_setg(errp, "remote-i2c-master requires a 'backend' property");
> +        return;
> +    }
> +
> +    s->backend->frontend = s;
> +    s->backend->timeout_ms = s->timeout_ms;
> +
> +    s->bh = aio_bh_new_guarded(qemu_get_aio_context(),
> +                               remote_i2c_fsm_bh,
> +                               s,
> +                               &dev->mem_reentrancy_guard);
> +
> +    s->timer = timer_new(QEMU_CLOCK_VIRTUAL, SCALE_MS,
> +                         &remote_i2c_timer_cb, s);
> +    s->timer_start_transmit = timer_new(QEMU_CLOCK_VIRTUAL,
> +                                        SCALE_NS,
> +                                        
> &remote_i2c_fsm_timer_start_transmit_cb,
> +                                        s);
> +    s->timer_step = timer_new(QEMU_CLOCK_VIRTUAL, SCALE_NS,
> +                              &remote_i2c_timer_step_cb, s);
> +}
> +
> +static void remote_i2c_master_reset(Object *obj, ResetType type)
> +{
> +    RemoteI2CMasterState *s = REMOTE_I2C_MASTER(obj);
> +
> +    /* remote_i2c_fsm_dispatch is defined in remote-i2c-fsm.c */
> +    remote_i2c_fsm_dispatch(s, REMOTE_I2C_CMD_RESET);
> +}
> +
> +static const Property remote_i2c_master_properties[] = {
> +    DEFINE_PROP_LINK("i2cbus", RemoteI2CMasterState, bus,
> +                     TYPE_I2C_BUS, I2CBus *),
> +    DEFINE_PROP_STRING("name", RemoteI2CMasterState, name),
> +    DEFINE_PROP_LINK("backend", RemoteI2CMasterState, backend,
> +                     TYPE_REMOTE_I2C_BACKEND, RemoteI2CBackend *),
> +    DEFINE_PROP_BOOL("raise_arbitrage_lost",
> +                     RemoteI2CMasterState,
> +                     raise_arbitrage_lost, false),
> +    DEFINE_PROP_UINT32("timeout-ms",
> +                       RemoteI2CMasterState, timeout_ms, 1000),
> +};
> +
> +static void remote_i2c_master_class_init(ObjectClass *klass, const void 
> *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> +
> +    device_class_set_props(dc, remote_i2c_master_properties);
> +    dc->realize = remote_i2c_master_realize;
> +    dc->desc = "Remote I2C Controller";
> +    rc->phases.enter = remote_i2c_master_reset;
> +}
> +
> +static const TypeInfo remote_i2c_master_info = {
> +    .name          = TYPE_REMOTE_I2C_MASTER,
> +    .parent        = TYPE_DEVICE,
> +    .instance_size = sizeof(RemoteI2CMasterState),
> +    .class_init    = remote_i2c_master_class_init,
> +};
> +
> +static void remote_i2c_master_register_types(void)
> +{
> +    type_register_static(&remote_i2c_master_info);
> +}
> +
> +type_init(remote_i2c_master_register_types)
> diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events
> index 1ad0e95c0e..c08cdef7d9 100644
> --- a/hw/i2c/trace-events
> +++ b/hw/i2c/trace-events
> @@ -61,3 +61,32 @@ pca954x_read_data(uint8_t value) "PCA954X read data: 
> 0x%02x"
>  
>  imx_i2c_read(const char *id, const char *reg, uint64_t ofs, uint64_t value) 
> "%s:[%s (0x%" PRIx64 ")] -> 0x%02" PRIx64
>  imx_i2c_write(const char *id, const char *reg, uint64_t ofs, uint64_t value) 
> "%s:[%s (0x%" PRIx64 ")] <- 0x%02" PRIx64
> +
> +# remote-i2c-master.c
> +remote_i2c_master_realize(void) "device realized"
> +remote_i2c_master_reset_enter(void) "reset enter"
> +remote_i2c_master_backend_select(const char *backend) "selected backend: %s"
> +
> +# remote-i2c-fsm.c
> +remote_i2c_master_bus_state_change(const char *old, const char *new) "bus 
> State: %s -> %s"
> +remote_i2c_master_async_ack(void) "async ACK received"
> +remote_i2c_master_abort(int error, uint8_t addr, const char *reason) 
> "transaction ABORTED (errno=%d) addr=0x%02x, reason: %s"
> +remote_i2c_master_timeout(uint16_t timeout) "watchdog timer expired 
> (timeout_ms=%d)"
> +remote_i2c_master_stretch_delay(uint16_t delay) "master simulating stretch 
> delay: %d"
> +remote_i2c_master_send_byte(uint8_t byte) "wire TX: 0x%02x"
> +remote_i2c_master_recv_byte(uint8_t byte) "wire RX: 0x%02x"
> +remote_i2c_master_arbitration_lost(const char *abort_transaction) 
> "arbitration lost, %s"
> +
> +# remote-i2c-cuse.c
> +remote_i2c_master_fuse_export(void) "FUSE session setup complete"
> +remote_i2c_master_fuse_io_read(void) "FUSE event loop: reading buf"
> +remote_i2c_master_i2cdev_init(void) "CUSE init"
> +remote_i2c_master_i2cdev_open(void) "CUSE open"
> +remote_i2c_master_i2cdev_release(void) "CUSE release"
> +remote_i2c_master_i2cdev_ioctl(int cmd) "IOCTL Start cmd: 0x%x"
> +remote_i2c_master_i2cdev_ioctl_finished(int cmd) "IOCTL Finish cmd: 0x%x"
> +remote_i2c_master_i2cdev_functional(void) "IOCTL: Query Functionality"
> +remote_i2c_master_i2cdev_address(uint32_t address) "IOCTL: Set slave 
> address: 0x%x"
> +remote_i2c_master_i2cdev_smbus(uint8_t state) "IOCTL: SMBus machine state: 
> 0x%x"
> +remote_i2c_master_i2cdev_send(uint32_t size) "TX start size: 0x%x"
> +remote_i2c_master_i2cdev_receive(uint32_t size) "RX start size: 0x%x"
> diff --git a/include/hw/i2c/remote-i2c-backend.h 
> b/include/hw/i2c/remote-i2c-backend.h
> new file mode 100644
> index 0000000000..bbc71435db
> --- /dev/null
> +++ b/include/hw/i2c/remote-i2c-backend.h
> @@ -0,0 +1,70 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Remote I2C Backend (Abstract Base Class)
> + *
> + * This module defines the abstract backend interface for the Remote I2C 
> Master.
> + * It provides the QEMU Object Model (QOM) base class that strictly decouples
> + * the internal QEMU I2C hardware state machine (the frontend) from
> + * host-specific transport layers.
> + *
> + * Author:
> + * Ilya Chichkov <[email protected]>
> + *
> + */
> +#ifndef HW_REMOTE_I2C_BACKEND_H
> +#define HW_REMOTE_I2C_BACKEND_H
> +
> +#include "qemu/osdep.h"
> +#include "qom/object.h"
> +
> +typedef enum i2c_bus_state {
> +    I2C_BUS_IDLE,
> +    I2C_BUS_ADDR,
> +    I2C_BUS_SEND,
> +    I2C_BUS_RECV,
> +    I2C_BUS_END,
> +    I2C_BUS_FINISHED,
> +    I2C_BUS_WAIT_STRETCH,
> +    I2C_BUS_WAIT_RELEASE,
> +} i2c_bus_state;
> +
> +typedef struct RemoteI2CMasterState RemoteI2CMasterState;
> +
> +#define TYPE_REMOTE_I2C_BACKEND "remote-i2c-backend"
> +OBJECT_DECLARE_TYPE(RemoteI2CBackend, RemoteI2CBackendClass, 
> REMOTE_I2C_BACKEND)
> +
> +#define REMOTE_I2C_BACKEND_BUF_LEN      256
> +#define REMOTE_I2C_BACKEND_RDWR_BUF_LEN 260
> +
> +struct RemoteI2CBackend {
> +    Object parent_obj;
> +
> +    RemoteI2CMasterState *frontend;
> +
> +    /* Transaction State */
> +    uint8_t  transaction_buf[REMOTE_I2C_BACKEND_BUF_LEN];
> +    uint16_t transaction_index;
> +    uint16_t transaction_length;
> +
> +    bool is_recv;
> +    bool is_master_pending;
> +    bool is_transaction_failed;
> +    bool is_slave_async;
> +    bool waiting_for_async;
> +    bool addr_acked;
> +    bool data_acked;
> +    bool timed_out;
> +    uint32_t timeout_ms;
> +
> +    i2c_bus_state bus_state;
> +    long address;
> +};
> +
> +struct RemoteI2CBackendClass {
> +    ObjectClass parent_class;
> +
> +    void (*on_tx_complete)(RemoteI2CBackend *backend);
> +    void (*on_tx_error)(RemoteI2CBackend *backend, int errno_code);
> +};
> +
> +#endif /* HW_REMOTE_I2C_BACKEND_H */
> diff --git a/include/hw/i2c/remote-i2c-cuse.h 
> b/include/hw/i2c/remote-i2c-cuse.h
> new file mode 100644
> index 0000000000..b0940122bf
> --- /dev/null
> +++ b/include/hw/i2c/remote-i2c-cuse.h
> @@ -0,0 +1,93 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Remote I2C Master
> + *
> + * This device exposes a QEMU I2C bus to the host system via a FUSE (CUSE)
> + * character device. It allows external programs or scripts on the host to
> + * interact with I2C slaves simulated inside QEMU as if they were real 
> hardware
> + * devices attached to the host.
> + *
> + * Features:
> + * - Implements the Linux I2C ioctl interface (I2C_RDWR, I2C_SMBUS).
> + * - Supports standard I2C and SMBus protocols.
> + * - Handles asynchronous I2C transactions via QEMU's Bottom Halves (BH).
> + * - Supports SMBus "Repeated Start" for atomic Write-then-Read operations.
> + *
> + * Usage:
> + * Add the device to QEMU:
> + * "-device remote-i2c-master,i2cbus=i2c-bus.0,devname=i2c-33"
> + * This creates a character device at /dev/i2c-33 on the host.
> + * Use standard tools (i2c-tools) or C programs to access it:
> + * i2cdetect -y -l
> + * i2cget -y <bus_id> <addr> <reg>
> + *
> + * Author:
> + * Ilya Chichkov <[email protected]>
> + *
> + */
> +#ifndef HW_REMOTE_I2C_MASTER_BC_CUSE_H
> +#define HW_REMOTE_I2C_MASTER_BC_CUSE_H
> +
> +#include "qom/object_interfaces.h"
> +#include "hw/i2c/remote-i2c-backend.h"
> +
> +/* OS and Transport specifics stay HERE */
> +#define FUSE_USE_VERSION 31
> +#include <fuse3/fuse.h>
> +#include <fuse3/cuse_lowlevel.h>
> +#include <linux/i2c.h>
> +#include <linux/i2c-dev.h>
> +
> +#define TYPE_REMOTE_I2C_BACKEND_CUSE "remote-i2c-backend-cuse"
> +OBJECT_DECLARE_SIMPLE_TYPE(RemoteI2CBackendCuse, REMOTE_I2C_BACKEND_CUSE)
> +
> +typedef enum i2c_ioctl_state {
> +    I2C_IOCTL_START,
> +    I2C_IOCTL_GET,
> +    I2C_IOCTL_RECV,
> +    I2C_IOCTL_SEND,
> +    I2C_IOCTL_FINISHED,
> +} i2c_ioctl_state;
> +
> +struct remote_i2c_in_data {
> +    unsigned int last_cmd;
> +    fuse_req_t req;
> +    const struct i2c_smbus_ioctl_data *in_smbus_data;
> +    const struct i2c_rdwr_ioctl_data *in_rdwr_data;
> +    void *in_buf;
> +};
> +
> +struct RemoteI2CBackendCuse {
> +    RemoteI2CBackend parent_obj;
> +
> +    /* Config Properties */
> +    char *devname;
> +    char *fuse_opts;
> +    bool debug;
> +
> +    /* FUSE Session State */
> +    AioContext *ctx;
> +    struct fuse_session *fuse_session;
> +    struct fuse_buf fuse_buf;
> +    bool is_open;
> +
> +    /* CUSE IOCTL State Helpers */
> +    i2c_ioctl_state ioctl_state;
> +    uint32_t last_ioctl;
> +    struct remote_i2c_in_data in_data;
> +    bool smbus_restart_read;
> +
> +    /* RDWR OS-Level Support */
> +    struct i2c_msg *rdwr_msgs;
> +    uint32_t nmsgs;
> +    uint32_t msg_idx;
> +    size_t rdwr_data_offset;
> +    size_t rdwr_in_buf_size;
> +    uint8_t rdwr_out_buf[REMOTE_I2C_BACKEND_RDWR_BUF_LEN];
> +    size_t rdwr_out_len;
> +};
> +
> +/* CUSE-Specific Prototypes */
> +int remote_i2c_fuse_export(RemoteI2CBackendCuse *cuse, Error **errp);
> +
> +#endif /* HW_REMOTE_I2C_MASTER_BC_CUSE_H */
> diff --git a/include/hw/i2c/remote-i2c-master.h 
> b/include/hw/i2c/remote-i2c-master.h
> new file mode 100644
> index 0000000000..1eb92caae7
> --- /dev/null
> +++ b/include/hw/i2c/remote-i2c-master.h
> @@ -0,0 +1,77 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Remote I2C Master
> + *
> + * This device exposes a QEMU I2C bus to the host system via a FUSE (CUSE)
> + * character device. It allows external programs or scripts on the host to
> + * interact with I2C slaves simulated inside QEMU as if they were real 
> hardware
> + * devices attached to the host.
> + *
> + * Features:
> + * - Implements the Linux I2C ioctl interface (I2C_RDWR, I2C_SMBUS).
> + * - Supports standard I2C and SMBus protocols.
> + * - Handles asynchronous I2C transactions via QEMU's Bottom Halves (BH).
> + * - Supports SMBus "Repeated Start" for atomic Write-then-Read operations.
> + *
> + * Usage:
> + * Add the device to QEMU:
> + * "-device remote-i2c-master,i2cbus=i2c-bus.0,devname=i2c-33"
> + * This creates a character device at /dev/i2c-33 on the host.
> + * Use standard tools (i2c-tools) or C programs to access it:
> + * i2cdetect -y -l
> + * i2cget -y <bus_id> <addr> <reg>
> + *
> + * Author:
> + * Ilya Chichkov <[email protected]>
> + *
> + */
> +#ifndef HW_REMOTE_I2C_MASTER_H
> +#define HW_REMOTE_I2C_MASTER_H
> +
> +#include "hw/sysbus.h"
> +#include <linux/i2c.h>
> +#include <linux/i2c-dev.h>
> +#include "hw/i2c/remote-i2c-backend.h"
> +
> +#define TYPE_REMOTE_I2C_MASTER "remote-i2c-master"
> +
> +#define REMOTE_I2C_MASTER(obj) \
> +    OBJECT_CHECK(RemoteI2CMasterState, (obj), TYPE_REMOTE_I2C_MASTER)
> +
> +typedef enum {
> +    REMOTE_I2C_CMD_START_TX,
> +    REMOTE_I2C_CMD_NEXT_MSG,
> +    REMOTE_I2C_CMD_ABORT,
> +    REMOTE_I2C_CMD_RESET
> +} RemoteI2CCommand;
> +
> +typedef struct RemoteI2CMasterState {
> +    DeviceState parent_obj;
> +
> +    I2CBus *bus;
> +    char *name;
> +
> +    /* QOM Link to the abstract Backend */
> +    RemoteI2CBackend *backend;
> +
> +    QEMUTimer *timer;
> +    QEMUTimer *timer_start_transmit;
> +    QEMUTimer *timer_step;
> +    QEMUTimer *slow_master_timer;
> +    QEMUBH *bh;
> +    MemReentrancyGuard bh_guard;
> +
> +    uint32_t timeout_ms;
> +    uint32_t default_stretch_delay_ms;
> +    bool slow_delay_enable;
> +    uint32_t slow_delay_value_ms;
> +    bool raise_arbitrage_lost;
> +    bool delay_completed;
> +
> +} RemoteI2CMasterState;
> +
> +void remote_i2c_fsm_dispatch(RemoteI2CMasterState *s, RemoteI2CCommand cmd);
> +void remote_i2c_fsm_timer_start_transmit_cb(void *opaque);
> +void remote_i2c_fsm_bh(void *opaque);
> +
> +#endif /* HW_REMOTE_I2C_MASTER_H */
> diff --git a/qapi/qom.json b/qapi/qom.json
> index dd45ac1087..b515f1e4e7 100644
> --- a/qapi/qom.json
> +++ b/qapi/qom.json
> @@ -1258,6 +1258,7 @@
>      'tls-creds-psk',
>      'tls-creds-x509',
>      'tls-cipher-suites',
> +    'remote-i2c-backend-cuse',
>      { 'name': 'x-remote-object', 'features': [ 'unstable' ] },
>      { 'name': 'x-vfio-user-server', 'features': [ 'unstable' ] }
>    ] }
> @@ -1334,6 +1335,7 @@
>        'tls-creds-psk':              'TlsCredsPskProperties',
>        'tls-creds-x509':             'TlsCredsX509Properties',
>        'tls-cipher-suites':          'TlsCredsProperties',
> +      'remote-i2c-backend-cuse':    'RemoteI2CBackendCuseProperties',
>        'x-remote-object':            'RemoteObjectProperties',
>        'x-vfio-user-server':         'VfioUserServerProperties'
>    } }
> @@ -1377,3 +1379,19 @@
>  ##
>  { 'command': 'object-del', 'data': {'id': 'str'},
>    'allow-preconfig': true }
> +
> +##
> +# @RemoteI2CBackendCuseProperties:
> +#
> +# Properties for the CUSE remote I2C backend.
> +#
> +# @devname: The CUSE device name to create (e.g., 'i2c-33').
> +# @fuse-opts: Optional FUSE mount options.
> +# @debug: Whether to enable debug output.
> +#
> +# Since: (your version)
> +##
> +{ 'struct': 'RemoteI2CBackendCuseProperties',
> +  'data': { 'devname': 'str',
> +            '*fuse-opts': 'str',
> +            '*debug': 'bool' } }
> -- 
> 2.43.0
> 

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to