From nobody Fri Nov 14 22:20:19 2025 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1759845436; cv=none; d=zohomail.com; s=zohoarc; b=EEs3es9QaxLyvL4k/uSMWKEqU086Otrq556rFw7rHQwk8o1PIHFJ0e9vDHbU7y2f6o1AibfHqcrrgnxQ6MdGJRuyVkIgTL4ua8ln1IqgE8GppJpNYKgHQ/nNUB7lJCTDteji65V9EVqu7roUT97xg1Rc8eJN7r1A650RbSoYiNk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1759845436; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=OPGlPVwCrMb1BR/Nwhk/rS1uPnZWnjsPbXGIrC8ai/Y=; b=oMEKRoCTffHkUUAU0ecy60Fk10ozU5tBhLbTgU7uvBNV34hkaaG/E2i0ORnjd3x+dR7W+vD/CY9Acs57U3aSM4n1tb0M3uJRpqB3gD1mKYoxQ3DCf5nAyZOc+/JfGTwRVOMjlEZWFeyG8o8AIOU6LSq2J4kCYc30vJk5OEyTNJI= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1759845436400590.9695551817177; Tue, 7 Oct 2025 06:57:16 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1v68AR-00023z-JS; Tue, 07 Oct 2025 09:55:51 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1v67VU-00077G-U5 for qemu-devel@nongnu.org; Tue, 07 Oct 2025 09:13:34 -0400 Received: from mail-lf1-x134.google.com ([2a00:1450:4864:20::134]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1v67VF-0002gw-0c for qemu-devel@nongnu.org; Tue, 07 Oct 2025 09:13:32 -0400 Received: by mail-lf1-x134.google.com with SMTP id 2adb3069b0e04-5797c8612b4so8267264e87.2 for ; Tue, 07 Oct 2025 06:13:12 -0700 (PDT) Received: from gmail.com ([89.207.88.242]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-58b01135a82sm6081151e87.39.2025.10.07.06.13.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 07 Oct 2025 06:13:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1759842787; x=1760447587; darn=nongnu.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=OPGlPVwCrMb1BR/Nwhk/rS1uPnZWnjsPbXGIrC8ai/Y=; b=jbUwcyU9hR8SE9vsLnajlaRWN8FZ7h97DJbZ9kEfcUeqWRKWcKo43Y2sDlqf9dHDJD 2Wv9IDq3YJVtc4Er+n2HWkn/Ze3etgMTWF25F8a/jwPRz0AqCOrE4wE3/yw+FFWrTCU9 WEgHGL1K2qeWDK8T1z0AEPymUQ+N1/TeAeRREaXyq9BfBQ9fDt/n+uhtxMfdS/htucsj 9XjbkdYKI6ZcznkkIwWFXbATRxjQ52HPUj5/f52jAbIXiCQUneAYSWustzlBciNIJ6ES Zc6wtvpEGeUr/S7BfF/QA3CpQetMSKm13KRhY7keDuMIiNttjJhX1RvATidX051EHz3y d+jQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1759842787; x=1760447587; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=OPGlPVwCrMb1BR/Nwhk/rS1uPnZWnjsPbXGIrC8ai/Y=; b=XsXzNFKAK38PCplPgYhHbMSNIaSxs9pFO3Agnkbw0eZcyB9IWuulJwpFWh6BkGOwS2 R56xJNp3Cu0zLNqy+d0vljq2akxr+65xxBNS3Gwrnfsu+y3BCUHGynIRdnSPhmyrRuCV BtspNr+jnnXH3iTO81/Aq+2fc+LqmoU6HM3tesRhQ4XYLxM4fqifPaL/HpSq8U7dwdc7 jWVeZboR5Mfig7//7qNxLf8XHeeS0Q1pHGKvWxQkOFSDJJdDYXsw+wifDJ+D6JnsW9lC w+F6UJ5xBt8Fd4ugEfAptXCP1hwMp4bRRB3Bd2+W1d3X1wJ1Au+3alrtBQvSdI8HYxkh aJnA== X-Gm-Message-State: AOJu0YynEUZvR3lwZvIzRqn9bUf8EI9khVkUFC2hziEAPLFUfJOBd5Q/ XVJOuid6BtKJ4EWx099mgsPIEFZFc3kWc/kmIbKTkoPwMBcmG3dS4+FZU5eobcCrMjI= X-Gm-Gg: ASbGnct0muiK37bnvzFi/5a7PRaEGGlYUsOOdC/juGinfs47ywntcnjO36ecgyietDL WltxDCnjmfgVLG9s6JFAouoO3T8g1OECR3ZptNDhoL+qlLHA2dhq9+LhukNGu84Mhvy8hg945/P b/6RvOucmiNRRE7uERXD+OhDBhWLzYDewcaFMWmebx2u5cE5la92GCKPxnXVOi0v7WeZg4jnhId LPZOZfQZPAjkhXx6flqjOlPRvFcPHlJYlxeo+v1fUjJ50cWqPxHTh+YfK5R8eBMvQGzqe4InaeG p/f9GFD5A8zbmpRWx6ZxaP9ueys+GV/DPsS3x7ag80HVTuc0T05LSVSfQy/1yQ5GSoDCIxwA0PN xUbv1XkKGQARqP6QjiyAeDILuT2AFPDUJ3H355O/UOzwZIviwJrrvrDAgLxdQf2+i X-Google-Smtp-Source: AGHT+IFaeGc95XGdtHxqma0a9RTvuYKSDVpXg7MDyk28503L+Bg+Wuvt/v41iuSUaDscTIgWFm7gAQ== X-Received: by 2002:a05:6512:10cd:b0:581:f381:4ecc with SMTP id 2adb3069b0e04-58cbbdd82a7mr4694454e87.30.1759842786318; Tue, 07 Oct 2025 06:13:06 -0700 (PDT) From: Ilya Chichkov To: qemu-devel@nongnu.org Cc: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Corey Minyard , Bernhard Beschow , Richard Henderson , =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= , Nikita Shubin , Ilya Chichkov Subject: [PATCH] hw/i2c: Add remote-i2c-master Date: Tue, 7 Oct 2025 16:13:02 +0300 Message-ID: <20251007131302.485828-1-ilya.chichkov.dev@gmail.com> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::134; envelope-from=ilya.chichkov.dev@gmail.com; helo=mail-lf1-x134.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_PASS=-0.001, T_SPF_HELO_TEMPERROR=0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Tue, 07 Oct 2025 09:55:47 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1759845442445116600 Content-Type: text/plain; charset="utf-8" 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 QE= MU's virtual I2C operations. This is primarily useful for developing and de= bugging 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/device= s/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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Overview +-------- +This module implements a virtual I2C controller device using FUSE (Filesys= tem in Userspace) +and CUSE (Character device in Userspace) technology. It allows userspace p= rograms to +emulate I2C controllers that appear as real character devices in the syste= m. + +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 inte= rfaces. + +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[] =3D FUSE_OPT_DEBUG; + char *fuse_argv[] =3D { fuse_opt_dummy, fuse_opt_fore, fuse_opt_debug }; + +Examples +-------- + +Basic Usage +~~~~~~~~~~~ + +.. code-block:: bash + + ./qemu-system-arm -M ast2600-evb -device tmp105,address=3D0x40,bus=3Da= speed.i2c.bus.0 -device remote-i2c-master,i2cbus=3Daspeed.i2c.bus.0,devname= =3Di2c-33 + + $ i2cget -y 33 0x40 0x2 + 0x4b + + $ i2cget -y 33 0x40 0x3 + 0x50 + +See Also +-------- +- `FUSE Documentation ` +- `Character devices in user space ` \ 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-ma= ster.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 + * + */ +#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 =3D 0, + REMOTE_I2C_START_SEND =3D 1, + REMOTE_I2C_FINISH =3D 2, + REMOTE_I2C_NACK =3D 3, + REMOTE_I2C_RECV =3D 4, + REMOTE_I2C_SEND =3D 5, +} RemoteI2CCommand; + +typedef enum AUXCommand { + WRITE_I2C =3D 0, + READ_I2C =3D 1, + WRITE_I2C_STATUS =3D 2, + WRITE_I2C_MOT =3D 4, + READ_I2C_MOT =3D 5, + WRITE_AUX =3D 8, + READ_AUX =3D 9 +} AUXCommand; + +typedef enum AUXReply { + AUX_I2C_ACK =3D 0, + AUX_NACK =3D 1, + AUX_DEFER =3D 2, + AUX_I2C_NACK =3D 4, + AUX_I2C_DEFER =3D 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 =3D fuse_req_userdata(req); + + fuse_reply_open(req, fi); + + s->is_open =3D true; + + trace_remote_i2c_master_i2cdev_open(); +} + +static void i2cdev_release(fuse_req_t req, struct fuse_file_info *fi) +{ + RemoteI2CControllerState *s =3D fuse_req_userdata(req); + + s->is_open =3D 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 =3D NULL; + size_t bsize =3D 0; + + bsize =3D 1; + buf =3D 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 =3D (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 =3D { + .iov_base =3D arg, + .iov_len =3D sizeof(size_t) + }; + + switch (i2c->ioctl_state) { + case I2C_IOCTL_START: + i2c->ioctl_state =3D 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 =3D 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 =3D (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 =3D 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] =3D { 0 }; + size_t i =3D 0; + buf[0] =3D in_val->read_write; + buf[1] =3D (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] =3D 2; + buf[3] =3D in_val->command; + buf[4] =3D data.byte; + break; + case I2C_SMBUS_WORD_DATA: + buf[2] =3D 3; + buf[3] =3D in_val->command; + buf[4] =3D (uint8_t)(data.word & 0xFF); + buf[5] =3D (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 =3D data.block[0]; + buf[2] =3D len + 1; + buf[3] =3D in_val->command; + for (i =3D 0; i < len; i++) { + buf[4 + i] =3D data.block[i + 1]; + } + } + break; + } + + /* Send data to I2C bus */ + i2c_start_send(i2c->i2c_bus, i2c->address); + for (i =3D 0; i < buf[2]; i++) { + i2c_send(i2c->i2c_bus, buf[3 + i]); + } + + i2c->address =3D 0x0; + i2c->ioctl_state =3D 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 =3D (union i2c_smbus_data *)( + in_buf + sizeof(struct i2c_smbus_ioctl_data) + ); + uint8_t receive_byte =3D 0; + size_t i =3D 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 =3D i2c_recv(i2c->i2c_bus); + break; + case I2C_SMBUS_WORD_DATA: + receive_byte =3D i2c_recv(i2c->i2c_bus); + smbus_data->word =3D ((uint16_t)receive_byte) & 0xFF; + receive_byte =3D i2c_recv(i2c->i2c_bus); + smbus_data->word |=3D (((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 =3D smbus_data->block[0]; + for (i =3D 0; i < len; i++) { + smbus_data->block[1 + i] =3D i2c_recv(i2c->i2c_bus); + } + } + break; + } + + i2c->ioctl_state =3D 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 =3D i2c->i2c_bus; + const struct i2c_smbus_ioctl_data *in_val; + struct iovec in_iov[2]; + + in_val =3D in_buf; + in_iov[0].iov_base =3D in_arg; + in_iov[0].iov_len =3D sizeof(struct i2c_smbus_ioctl_data); + + i2c->req =3D req; + i2c->in_val =3D in_val; + i2c->in_buf =3D 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 =3D I2C_IOCTL_GET; + return; + } + break; + case I2C_IOCTL_GET: + /* prepare client buf */ + if (in_val->read_write) { + struct iovec out_iov =3D { + .iov_base =3D in_val->data, + .iov_len =3D sizeof(union i2c_smbus_data *) + }; + + in_iov[1].iov_base =3D in_val->data; + in_iov[1].iov_len =3D sizeof(union i2c_smbus_data *); + + fuse_reply_ioctl_retry(req, in_iov, 2, &out_iov, 1); + i2c->ioctl_state =3D I2C_IOCTL_RECV; + } else { + in_iov[1].iov_base =3D in_val->data; + in_iov[1].iov_len =3D sizeof(union i2c_smbus_data); + fuse_reply_ioctl_retry(req, in_iov, 2, NULL, 0); + i2c->ioctl_state =3D I2C_IOCTL_SEND; + } + break; + case I2C_IOCTL_RECV: + case I2C_IOCTL_SEND: + { + i2c->is_recv =3D (i2c->ioctl_state =3D=3D 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 =3D I2C_IOCTL_START; + i2c->last_ioctl =3D 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 =3D fuse_req_userdata(req); + unsigned int ctl =3D cmd; + + trace_remote_i2c_master_i2cdev_ioctl(cmd); + + if (flags & FUSE_IOCTL_COMPAT) { + fuse_reply_err(req, ENOSYS); + return; + } + + if (s->ioctl_state =3D=3D I2C_IOCTL_START) { + s->last_ioctl =3D ctl; + } else if (s->last_ioctl !=3D ctl) { + s->last_ioctl =3D 0; + s->ioctl_state =3D 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 =3D=3D I2C_IOCTL_FINISHED) { + s->ioctl_state =3D I2C_IOCTL_START; + s->last_ioctl =3D 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 =3D fuse_req_userdata(req); + + s->ph =3D ph; + fuse_reply_poll(req, 0); +} + +static const struct cuse_lowlevel_ops i2cdev_ops =3D { + .init =3D i2cdev_init, + .open =3D i2cdev_open, + .release =3D i2cdev_release, + .read =3D i2cdev_read, + .ioctl =3D i2cdev_ioctl, + .poll =3D i2cdev_poll, +}; + +static void read_from_fuse_export(void *opaque) +{ + RemoteI2CControllerState *s =3D opaque; + int ret; + + do { + ret =3D fuse_session_receive_buf(s->fuse_session, &s->fuse_buf); + } while (ret =3D=3D -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 =3D NULL; + char fuse_opt_dummy[] =3D FUSE_OPT_DUMMY; + char fuse_opt_fore[] =3D FUSE_OPT_FORE; + char fuse_opt_debug[] =3D FUSE_OPT_DEBUG; + char *fuse_argv[] =3D { fuse_opt_dummy, fuse_opt_fore, fuse_opt_debug = }; + char dev_name[128]; + struct cuse_info ci =3D { 0 }; + char *curdir =3D get_current_dir_name(); + int ret; + + /* Set device name for CUSE dev info */ + sprintf(dev_name, "DEVNAME=3D%s", i2c->devname); + const char *dev_info_argv[] =3D { dev_name }; + + memset(&ci, 0, sizeof(ci)); + ci.dev_major =3D 0; + ci.dev_minor =3D 0; + ci.dev_info_argc =3D 1; + ci.dev_info_argv =3D dev_info_argv; + ci.flags =3D CUSE_UNRESTRICTED_IOCTL; + + int multithreaded; + session =3D cuse_lowlevel_setup(ARRAY_SIZE(fuse_argv), fuse_argv, &ci, + &i2cdev_ops, &multithreaded, i2c); + if (session =3D=3D NULL) { + error_setg(errp, "cuse_lowlevel_setup() failed"); + errno =3D EINVAL; + return -1; + } + + /* FIXME: fuse_daemonize() calls chdir("/") */ + ret =3D chdir(curdir); + if (ret =3D=3D -1) { + error_setg(errp, "chdir() failed"); + return -1; + } + + i2c->ctx =3D 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 =3D session; + + trace_remote_i2c_master_fuse_export(); + return 0; +} + +static void remote_i2c_timer_cb(void *opaque) +{ + RemoteI2CControllerState *s =3D opaque; + s->is_recv =3D (s->ioctl_state =3D=3D 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 =3D 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 =3D=3D I2C_IOCTL_FINISHED) { + s->ioctl_state =3D I2C_IOCTL_START; + s->last_ioctl =3D 0; + } +} + +static void remote_i2c_realize(DeviceState *dev, Error **errp) +{ + RemoteI2CControllerState *s =3D REMOTE_I2C_MASTER(dev); + + s->bh =3D qemu_bh_new(remote_i2c_bh, s); + + s->timer =3D timer_new(QEMU_CLOCK_VIRTUAL, SCALE_MS, + &remote_i2c_timer_cb, s); + + s->is_open =3D false; + i2c_fuse_export(s, errp); +} + +static const Property remote_i2c_props[] =3D { + 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 =3D DEVICE_CLASS(klass); + + device_class_set_props(dc, remote_i2c_props); + dc->realize =3D remote_i2c_realize; + dc->desc =3D "Remote I2C Controller"; +} + +static const TypeInfo remote_i2c_type =3D { + .name =3D TYPE_REMOTE_I2C_MASTER, + .parent =3D TYPE_DEVICE, + .instance_size =3D sizeof(RemoteI2CControllerState), + .class_init =3D 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" =20 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 valu= e) "%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. Sta= te: 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 + * + */ +#ifndef HW_REMOTE_I2C_MASTER_H +#define HW_REMOTE_I2C_MASTER_H + +#include "hw/sysbus.h" + +#define FUSE_USE_VERSION 31 +#include +#include +#undef FUSE_USE_VERSION + +#include +#include + +#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 */ --=20 2.43.0