All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] hw/i2c: Add remote I2C master with host CUSE bridge
@ 2026-06-18 13:14 Ilya Chichkov
  0 siblings, 0 replies; only message in thread
From: Ilya Chichkov @ 2026-06-18 13:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Cédric Le Goater, Corey Minyard, Ilya Chichkov

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.

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 <ilya.chichkov.dev@gmail.com>
---
 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 <ilya.chichkov.dev@gmail.com>
+ *
+ */
+#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 <ilya.chichkov.dev@gmail.com>
+ *
+ */
+#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 <ilya.chichkov.dev@gmail.com>
+ *
+ */
+#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 <ilya.chichkov.dev@gmail.com>
+ *
+ */
+#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 <ilya.chichkov.dev@gmail.com>
+ *
+ */
+#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 <ilya.chichkov.dev@gmail.com>
+ *
+ */
+#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 <ilya.chichkov.dev@gmail.com>
+ *
+ */
+#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



^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-06-18 13:16 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-18 13:14 [PATCH] hw/i2c: Add remote I2C master with host CUSE bridge Ilya Chichkov

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.