qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] hw/i2c: Add remote-i2c-master
@ 2025-10-07 13:13 Ilya Chichkov
  0 siblings, 0 replies; only message in thread
From: Ilya Chichkov @ 2025-10-07 13:13 UTC (permalink / raw)
  To: qemu-devel
  Cc: Philippe Mathieu-Daudé, Corey Minyard, Bernhard Beschow,
	Richard Henderson, Cédric Le Goater, Nikita Shubin,
	Ilya Chichkov

That device allows a QEMU guest to
create a character device on the host using CUSE (Character in
Userspace). This enables host userspace tools (e.g., i2c-tools) to
interact with the guest's virtual I2C bus directly.

The module acts as a bridge, translating I2C commands from the host into QEMU's virtual I2C operations. This is primarily useful for developing and debugging I2C device drivers.

Key components:
- New device type `remote-i2c-master`
- Integration with libfuse for CUSE device management
- Asynchronous I2C command handling using bottom-half timers
---
 docs/system/devices/remote-i2c-master.rst |  81 ++++
 hw/i2c/Kconfig                            |   5 +
 hw/i2c/meson.build                        |   1 +
 hw/i2c/remote-i2c-master.c                | 535 ++++++++++++++++++++++
 hw/i2c/trace-events                       |  15 +
 include/hw/i2c/remote-i2c-master.h        |  66 +++
 6 files changed, 703 insertions(+)
 create mode 100644 docs/system/devices/remote-i2c-master.rst
 create mode 100644 hw/i2c/remote-i2c-master.c
 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..90ebb7519a
--- /dev/null
+++ b/docs/system/devices/remote-i2c-master.rst
@@ -0,0 +1,81 @@
+Remote I2C master
+=================
+
+Overview
+--------
+This module implements a virtual I2C controller device using FUSE (Filesystem in Userspace)
+and CUSE (Character device in Userspace) technology. It allows userspace programs to
+emulate I2C controllers that appear as real character devices in the system.
+
+Features
+--------
+- Virtual I2C controller emulation via CUSE
+- Full I2C character device interface
+- Support for unrestricted IOCTL operations
+- Integration with QEMU's AIO context for asynchronous operations
+- Debugging support through FUSE debug mode
+
+Architecture
+------------
+The module creates a virtual character device using CUSE that behaves like a physical I2C
+controller. The device can be accessed through standard I2C tools and interfaces.
+
+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
+
+Troubleshooting
+~~~~~~~~~~~~~~~
+
+CUSE_INIT Failures
+^^^^^^^^^^^^^^^^^^
+If you encounter ``CUSE_INIT`` errors:
+
+1. Verify CUSE module is loaded:
+   .. code-block:: bash
+
+      lsmod | grep cuse
+      sudo modprobe cuse
+
+2. Check permissions:
+   .. code-block:: bash
+
+      # Ensure user has access to CUSE devices
+      ls -la /dev/cuse
+
+Debugging
+---------
+
+Enable debug output by including the debug option:
+
+.. code-block:: c
+
+   char fuse_opt_debug[] = FUSE_OPT_DEBUG;
+   char *fuse_argv[] = { fuse_opt_dummy, fuse_opt_fore, fuse_opt_debug };
+
+Examples
+--------
+
+Basic Usage
+~~~~~~~~~~~
+
+.. 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
+
+    $ 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/>`
\ No newline at end of file
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..ca6378ba3a 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -18,4 +18,5 @@ 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'))
 system_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
diff --git a/hw/i2c/remote-i2c-master.c b/hw/i2c/remote-i2c-master.c
new file mode 100644
index 0000000000..ae50e84126
--- /dev/null
+++ b/hw/i2c/remote-i2c-master.c
@@ -0,0 +1,535 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Remote I2C master
+ *
+ * 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 "block/aio.h"
+#include "qemu/log.h"
+#include "trace.h"
+
+#include "hw/i2c/remote-i2c-master.h"
+
+
+#define FUSE_OPT_DUMMY "\0\0"
+#define FUSE_OPT_FORE  "-f\0\0"
+#define FUSE_OPT_NOMULTI "-s\0\0"
+#define FUSE_OPT_DEBUG "-d\0\0"
+
+typedef enum {
+    REMOTE_I2C_START_RECV = 0,
+    REMOTE_I2C_START_SEND = 1,
+    REMOTE_I2C_FINISH = 2,
+    REMOTE_I2C_NACK = 3,
+    REMOTE_I2C_RECV = 4,
+    REMOTE_I2C_SEND = 5,
+} RemoteI2CCommand;
+
+typedef enum AUXCommand {
+    WRITE_I2C = 0,
+    READ_I2C = 1,
+    WRITE_I2C_STATUS = 2,
+    WRITE_I2C_MOT = 4,
+    READ_I2C_MOT = 5,
+    WRITE_AUX = 8,
+    READ_AUX = 9
+} AUXCommand;
+
+typedef enum AUXReply {
+    AUX_I2C_ACK = 0,
+    AUX_NACK = 1,
+    AUX_DEFER = 2,
+    AUX_I2C_NACK = 4,
+    AUX_I2C_DEFER = 8
+} AUXReply;
+
+struct remote_i2c_cmd {
+    uint8_t cmd;
+    uint8_t addr;
+    uint8_t len;
+    uint8_t data[];
+} __attribute__((packed));
+
+static void i2cdev_init(void *userdata, struct fuse_conn_info *conn)
+{
+    (void)userdata;
+
+    trace_remote_i2c_master_i2cdev_init();
+}
+
+static void i2cdev_open(fuse_req_t req, struct fuse_file_info *fi)
+{
+    RemoteI2CControllerState *s = fuse_req_userdata(req);
+
+    fuse_reply_open(req, fi);
+
+    s->is_open = true;
+
+    trace_remote_i2c_master_i2cdev_open();
+}
+
+static void i2cdev_release(fuse_req_t req, struct fuse_file_info *fi)
+{
+    RemoteI2CControllerState *s = fuse_req_userdata(req);
+
+    s->is_open = false;
+
+    fuse_reply_err(req, 0);
+
+    trace_remote_i2c_master_i2cdev_release();
+}
+
+static void i2cdev_read(fuse_req_t req, size_t size, off_t off,
+                        struct fuse_file_info *fi)
+{
+    /* unused? */
+    char *buf = NULL;
+    size_t bsize = 0;
+
+    bsize = 1;
+    buf = g_realloc(buf, bsize);
+    memset(buf, 44, 1);
+    fuse_reply_buf(req, buf, bsize);
+    g_free(buf);
+
+    trace_remote_i2c_master_i2cdev_read();
+}
+
+static void i2cdev_functional(RemoteI2CControllerState *i2c,
+                              fuse_req_t req,
+                              void *arg,
+                              const void *in_buf)
+{
+    unsigned long funcs = (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 iov = {
+        .iov_base = arg,
+        .iov_len = sizeof(size_t)
+    };
+
+    switch (i2c->ioctl_state) {
+    case I2C_IOCTL_START:
+        i2c->ioctl_state = I2C_IOCTL_GET;
+        /* Sending arg size of 'size_t' */
+        fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+        break;
+    case I2C_IOCTL_GET:
+        /* Sending I2C functional size of unsigned long */
+        fuse_reply_ioctl(req, 0, &funcs, sizeof(funcs));
+        i2c->ioctl_state = I2C_IOCTL_FINISHED;
+        trace_remote_i2c_master_i2cdev_functional();
+        break;
+    default:
+        /* assert */
+        break;
+    }
+}
+
+static void i2cdev_address(RemoteI2CControllerState *i2c,
+                           fuse_req_t req,
+                           void *arg,
+                           const void *in_buf)
+{
+    i2c->address = (long)arg;
+
+    trace_remote_i2c_master_i2cdev_address(i2c->address);
+
+    if (i2c->address < 0 || i2c->address > 127) {
+        fuse_reply_err(req, EINVAL);
+        return;
+    }
+    fuse_reply_ioctl(req, 0, NULL, 0);
+    i2c->ioctl_state = I2C_IOCTL_FINISHED;
+}
+
+static void send_data_to_slave(RemoteI2CControllerState *i2c,
+                               fuse_req_t req,
+                               const struct i2c_smbus_ioctl_data *in_val,
+                               const void *in_buf)
+{
+    union i2c_smbus_data data;
+    uint8_t buf[64] = { 0 };
+    size_t i = 0;
+    buf[0] = in_val->read_write;
+    buf[1] = (uint8_t)i2c->address;
+
+    /* Get SMBus data structure */
+    memcpy(&data,
+           in_buf + sizeof(struct i2c_smbus_ioctl_data),
+           sizeof(union i2c_smbus_data));
+
+    /* Parse data from SMBus struct */
+    switch (in_val->size) {
+    case I2C_SMBUS_BYTE_DATA:
+        buf[2] = 2;
+        buf[3] = in_val->command;
+        buf[4] = data.byte;
+    break;
+    case I2C_SMBUS_WORD_DATA:
+        buf[2] = 3;
+        buf[3] = in_val->command;
+        buf[4] = (uint8_t)(data.word & 0xFF);
+        buf[5] = (uint8_t)(data.word >> 8 & 0xFF);
+    break;
+    case I2C_SMBUS_I2C_BLOCK_BROKEN:
+    case I2C_SMBUS_BLOCK_DATA:
+    case I2C_SMBUS_I2C_BLOCK_DATA:
+    {
+        uint8_t len = data.block[0];
+        buf[2] = len + 1;
+        buf[3] = in_val->command;
+        for (i = 0; i < len; i++) {
+            buf[4 + i] = data.block[i + 1];
+        }
+    }
+    break;
+    }
+
+    /* Send data to I2C bus */
+    i2c_start_send(i2c->i2c_bus, i2c->address);
+    for (i = 0; i < buf[2]; i++) {
+        i2c_send(i2c->i2c_bus, buf[3 + i]);
+    }
+
+    i2c->address = 0x0;
+    i2c->ioctl_state = I2C_IOCTL_FINISHED;
+    fuse_reply_ioctl(req, 0, NULL, 0);
+
+    trace_remote_i2c_master_i2cdev_send(in_val->size);
+}
+
+static void recv_data_from_slave(RemoteI2CControllerState *i2c,
+                                 fuse_req_t req,
+                                 const struct i2c_smbus_ioctl_data *in_val,
+                                 const void *in_buf)
+{
+    union i2c_smbus_data *smbus_data = (union i2c_smbus_data *)(
+        in_buf + sizeof(struct i2c_smbus_ioctl_data)
+    );
+    uint8_t receive_byte = 0;
+    size_t i = 0;
+
+    /* Send command to slave */
+    i2c_start_send(i2c->i2c_bus, i2c->address);
+    i2c_send(i2c->i2c_bus, in_val->command);
+    i2c_start_recv(i2c->i2c_bus, i2c->address);
+
+    /* Receive data from slave */
+    switch (in_val->size) {
+    case I2C_SMBUS_BYTE_DATA:
+        smbus_data->byte = i2c_recv(i2c->i2c_bus);
+    break;
+    case I2C_SMBUS_WORD_DATA:
+        receive_byte = i2c_recv(i2c->i2c_bus);
+        smbus_data->word = ((uint16_t)receive_byte) & 0xFF;
+        receive_byte = i2c_recv(i2c->i2c_bus);
+        smbus_data->word |= (((uint16_t)receive_byte) << 8) & 0xFF00;
+    break;
+    case I2C_SMBUS_I2C_BLOCK_BROKEN:
+    case I2C_SMBUS_BLOCK_DATA:
+    case I2C_SMBUS_I2C_BLOCK_DATA:
+    {
+        uint8_t len = smbus_data->block[0];
+        for (i = 0; i < len; i++) {
+            smbus_data->block[1 + i] = i2c_recv(i2c->i2c_bus);
+        }
+    }
+    break;
+    }
+
+    i2c->ioctl_state = I2C_IOCTL_FINISHED;
+    fuse_reply_ioctl(req, 0, smbus_data, sizeof(union i2c_smbus_data *));
+
+    trace_remote_i2c_master_i2cdev_receive(in_val->size);
+}
+
+static void i2cdev_cmd_smbus(RemoteI2CControllerState *i2c,
+                             fuse_req_t req,
+                             void *in_arg,
+                             const void *in_buf,
+                             size_t in_bufsz,
+                             size_t out_bufsz)
+{
+    I2CBus *i2c_bus = i2c->i2c_bus;
+    const struct i2c_smbus_ioctl_data *in_val;
+    struct iovec in_iov[2];
+
+    in_val = in_buf;
+    in_iov[0].iov_base = in_arg;
+    in_iov[0].iov_len = sizeof(struct i2c_smbus_ioctl_data);
+
+    i2c->req = req;
+    i2c->in_val = in_val;
+    i2c->in_buf = in_buf;
+
+    trace_remote_i2c_master_i2cdev_smbus((uint8_t)i2c->ioctl_state);
+
+    switch (i2c->ioctl_state) {
+    case I2C_IOCTL_START:
+        if (!in_bufsz) {
+            fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+            i2c->ioctl_state = I2C_IOCTL_GET;
+            return;
+        }
+        break;
+    case I2C_IOCTL_GET:
+        /* prepare client buf */
+        if (in_val->read_write) {
+            struct iovec out_iov = {
+                .iov_base = in_val->data,
+                .iov_len = sizeof(union i2c_smbus_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, &out_iov, 1);
+            i2c->ioctl_state = I2C_IOCTL_RECV;
+        } else {
+            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);
+            i2c->ioctl_state = I2C_IOCTL_SEND;
+        }
+        break;
+    case I2C_IOCTL_RECV:
+    case I2C_IOCTL_SEND:
+        {
+            i2c->is_recv = (i2c->ioctl_state == I2C_IOCTL_RECV);
+            if (i2c_bus_busy(i2c_bus)) {
+                timer_mod(i2c->timer,
+                            qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 5);
+            } else {
+                i2c_bus_master(i2c_bus, i2c->bh);
+                i2c_schedule_pending_master(i2c_bus);
+            }
+        }
+        break;
+    case I2C_IOCTL_FINISHED:
+        i2c->ioctl_state = I2C_IOCTL_START;
+        i2c->last_ioctl = 0;
+        break;
+    }
+}
+
+static void i2cdev_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)
+{
+    RemoteI2CControllerState *s = fuse_req_userdata(req);
+    unsigned int ctl = cmd;
+
+    trace_remote_i2c_master_i2cdev_ioctl(cmd);
+
+    if (flags & FUSE_IOCTL_COMPAT) {
+        fuse_reply_err(req, ENOSYS);
+        return;
+    }
+
+    if (s->ioctl_state == I2C_IOCTL_START) {
+        s->last_ioctl = ctl;
+    } else if (s->last_ioctl != ctl) {
+        s->last_ioctl = 0;
+        s->ioctl_state = I2C_IOCTL_START;
+        fuse_reply_err(req, EINVAL);
+        return;
+    }
+
+    switch (ctl) {
+    case I2C_SLAVE_FORCE:
+        fuse_reply_ioctl(req, 0, NULL, 0);
+        break;
+    case I2C_FUNCS:
+        i2cdev_functional(s, req, arg, in_buf);
+    break;
+    case I2C_SLAVE:
+        i2cdev_address(s, req, arg, in_buf);
+        break;
+    case I2C_SMBUS: {
+        i2cdev_cmd_smbus(s, req, arg, in_buf, in_bufsz, out_bufsz);
+    }
+    break;
+    default:
+        fuse_reply_err(req, EINVAL);
+    break;
+    }
+
+    if (s->ioctl_state == I2C_IOCTL_FINISHED) {
+        s->ioctl_state = I2C_IOCTL_START;
+        s->last_ioctl = 0;
+        trace_remote_i2c_master_i2cdev_ioctl_finished(cmd);
+    }
+}
+
+static void i2cdev_poll(fuse_req_t req, struct fuse_file_info *fi,
+                         struct fuse_pollhandle *ph)
+{
+    RemoteI2CControllerState *s = fuse_req_userdata(req);
+
+    s->ph = ph;
+    fuse_reply_poll(req, 0);
+}
+
+static const struct cuse_lowlevel_ops i2cdev_ops = {
+    .init       = i2cdev_init,
+    .open       = i2cdev_open,
+    .release    = i2cdev_release,
+    .read       = i2cdev_read,
+    .ioctl      = i2cdev_ioctl,
+    .poll       = i2cdev_poll,
+};
+
+static void read_from_fuse_export(void *opaque)
+{
+    RemoteI2CControllerState *s = opaque;
+    int ret;
+
+    do {
+        ret = fuse_session_receive_buf(s->fuse_session, &s->fuse_buf);
+    } while (ret == -EINTR);
+
+    if (ret < 0) {
+        return;
+    }
+
+    fuse_session_process_buf(s->fuse_session, &s->fuse_buf);
+
+    trace_remote_i2c_master_fuse_io_read();
+}
+
+static int i2c_fuse_export(RemoteI2CControllerState *i2c, Error **errp)
+{
+    struct fuse_session *session = NULL;
+    char fuse_opt_dummy[] = FUSE_OPT_DUMMY;
+    char fuse_opt_fore[] = FUSE_OPT_FORE;
+    char fuse_opt_debug[] = FUSE_OPT_DEBUG;
+    char *fuse_argv[] = { fuse_opt_dummy, fuse_opt_fore, fuse_opt_debug };
+    char dev_name[128];
+    struct cuse_info ci = { 0 };
+    char *curdir = get_current_dir_name();
+    int ret;
+
+    /* Set device name for CUSE dev info */
+    sprintf(dev_name, "DEVNAME=%s", i2c->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;
+
+    int multithreaded;
+    session = cuse_lowlevel_setup(ARRAY_SIZE(fuse_argv), fuse_argv, &ci,
+                                  &i2cdev_ops, &multithreaded, i2c);
+    if (session == NULL) {
+        error_setg(errp, "cuse_lowlevel_setup() failed");
+        errno = EINVAL;
+        return -1;
+    }
+
+    /* FIXME: fuse_daemonize() calls chdir("/") */
+    ret = chdir(curdir);
+    if (ret == -1) {
+        error_setg(errp, "chdir() failed");
+        return -1;
+    }
+
+    i2c->ctx = iohandler_get_aio_context();
+
+    aio_set_fd_handler(i2c->ctx, fuse_session_fd(session),
+                       read_from_fuse_export, NULL,
+                       NULL, NULL, i2c);
+
+    i2c->fuse_session = session;
+
+    trace_remote_i2c_master_fuse_export();
+    return 0;
+}
+
+static void remote_i2c_timer_cb(void *opaque)
+{
+    RemoteI2CControllerState *s = opaque;
+    s->is_recv = (s->ioctl_state == I2C_IOCTL_RECV);
+    if (i2c_bus_busy(s->i2c_bus)) {
+        timer_mod(s->timer,
+                  qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 5);
+    } else {
+        i2c_bus_master(s->i2c_bus, s->bh);
+        i2c_schedule_pending_master(s->i2c_bus);
+    }
+}
+
+
+static void remote_i2c_bh(void *opaque)
+{
+    RemoteI2CControllerState *s = opaque;
+
+    if (s->is_recv) {
+        recv_data_from_slave(s, s->req, s->in_val, s->in_buf);
+    } else {
+        send_data_to_slave(s, s->req, s->in_val, s->in_buf);
+    }
+    i2c_end_transfer(s->i2c_bus);
+    i2c_bus_release(s->i2c_bus);
+
+    if (s->ioctl_state == I2C_IOCTL_FINISHED) {
+        s->ioctl_state = I2C_IOCTL_START;
+        s->last_ioctl = 0;
+    }
+}
+
+static void remote_i2c_realize(DeviceState *dev, Error **errp)
+{
+    RemoteI2CControllerState *s = REMOTE_I2C_MASTER(dev);
+
+    s->bh = qemu_bh_new(remote_i2c_bh, s);
+
+    s->timer = timer_new(QEMU_CLOCK_VIRTUAL, SCALE_MS,
+                         &remote_i2c_timer_cb, s);
+
+    s->is_open = false;
+    i2c_fuse_export(s, errp);
+}
+
+static const Property remote_i2c_props[] = {
+    DEFINE_PROP_LINK("i2cbus", RemoteI2CControllerState, i2c_bus,
+                     TYPE_I2C_BUS, I2CBus *),
+    DEFINE_PROP_STRING("devname", RemoteI2CControllerState, devname),
+};
+
+static void remote_i2c_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_props(dc, remote_i2c_props);
+    dc->realize = remote_i2c_realize;
+    dc->desc = "Remote I2C Controller";
+}
+
+static const TypeInfo remote_i2c_type = {
+    .name = TYPE_REMOTE_I2C_MASTER,
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(RemoteI2CControllerState),
+    .class_init = remote_i2c_class_init
+};
+
+static void remote_i2c_register(void)
+{
+    type_register_static(&remote_i2c_type);
+}
+
+type_init(remote_i2c_register)
diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events
index 1ad0e95c0e..960d46efb1 100644
--- a/hw/i2c/trace-events
+++ b/hw/i2c/trace-events
@@ -61,3 +61,18 @@ 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_fuse_export(void)
+remote_i2c_master_fuse_io_read(void)
+remote_i2c_master_i2cdev_init(void)
+remote_i2c_master_i2cdev_open(void)
+remote_i2c_master_i2cdev_release(void)
+remote_i2c_master_i2cdev_read(void)
+remote_i2c_master_i2cdev_functional(void) "Send current master functional"
+remote_i2c_master_i2cdev_address(uint32_t address) "Set slave address: 0x%x"
+remote_i2c_master_i2cdev_send(uint32_t size) "Send data to slave. Size: 0x%x"
+remote_i2c_master_i2cdev_receive(uint32_t size) "Received data from slave. Size: 0x%x"
+remote_i2c_master_i2cdev_smbus(uint8_t state) "Received SMBus command. State: 0x%x"
+remote_i2c_master_i2cdev_ioctl(int cmd) "Start, command: 0x%x"
+remote_i2c_master_i2cdev_ioctl_finished(int cmd) "Finished, command: 0x%x"
diff --git a/include/hw/i2c/remote-i2c-master.h b/include/hw/i2c/remote-i2c-master.h
new file mode 100644
index 0000000000..f6f6b36654
--- /dev/null
+++ b/include/hw/i2c/remote-i2c-master.h
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Remote I2C master
+ *
+ * Author:
+ *   Ilya Chichkov <ilya.chichkov.dev@gmail.com>
+ *
+ */
+#ifndef HW_REMOTE_I2C_MASTER_H
+#define HW_REMOTE_I2C_MASTER_H
+
+#include "hw/sysbus.h"
+
+#define FUSE_USE_VERSION 31
+#include <fuse3/fuse.h>
+#include <fuse3/cuse_lowlevel.h>
+#undef FUSE_USE_VERSION
+
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+
+#define TYPE_REMOTE_I2C_MASTER "remote-i2c-master"
+
+#define REMOTE_I2C_MASTER(obj) \
+    OBJECT_CHECK(RemoteI2CControllerState, (obj), TYPE_REMOTE_I2C_MASTER)
+
+#define REMOTE_I2C_MASTER_BUF_LEN   256
+
+typedef enum i2c_ioctl_state {
+    I2C_IOCTL_START,
+    I2C_IOCTL_GET,
+    I2C_IOCTL_RECV,
+    I2C_IOCTL_SEND,
+    I2C_IOCTL_FINISHED,
+} i2c_ioctl_state;
+
+typedef struct RemoteI2CControllerState {
+    DeviceState parent_obj;
+
+    I2CBus *i2c_bus;
+
+    long address;
+    QEMUTimer *timer;
+    QEMUBH *bh;
+
+    char *name;
+    char *devname;
+
+    struct fuse_session *fuse_session;
+    struct fuse_buf fuse_buf;
+    struct fuse_pollhandle *ph;
+    bool is_open;
+
+    /* specific CUSE helpers */
+    i2c_ioctl_state ioctl_state;
+    uint32_t last_ioctl;
+
+    fuse_req_t req;
+    const struct i2c_smbus_ioctl_data *in_val;
+    const void *in_buf;
+    bool is_recv;
+
+    AioContext *ctx;
+} RemoteI2CControllerState;
+
+#endif /* HW_GD32_I2C_H */
-- 
2.43.0



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

only message in thread, other threads:[~2025-10-07 13:57 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-07 13:13 [PATCH] hw/i2c: Add remote-i2c-master Ilya Chichkov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).