qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC PATCH v1] hw/misc: add i2c slave device that passes i2c ops outside


From: Karol Nowak
Subject: [RFC PATCH v1] hw/misc: add i2c slave device that passes i2c ops outside
Date: Thu, 23 Mar 2023 10:09:02 +0000

Hi,

There is a feature I prepared which may be practical for some QEMU users.

The feature provides a new I2C slave device 
that prepares a message depending what i2c-slave callback was called
and sends it outside of QEMU through the character device to a client
that receives that message, processes it and send back a response.
Thanks to that feature,
a user can emulate a logic of I2C device outside of QEMU.
The message contains 3 bytes ended with CRLF: BBB\r\l
Basically, the I2C slave does 4 steps in each i2c-slave callback:
* encode
* send
* receive
* decode

I put more details in esp32_i2c_tcp_slave.c
and also provided a demo client in python that uses TCP.

The feature still needs some improvements, but the question is:
* Do you find the feature useful?


NOTE:
The feature originally was prepared for espressif/qemu
that's why there are references to esp32


Signed-off-by: Karol Nowak <knw@spyro-soft.com>
---
 hw/misc/esp32_i2c_tcp_slave.c         | 288 ++++++++++++++++++++++++++
 include/hw/misc/esp32_i2c_tcp_slave.h |  19 ++
 tests/i2c-tcp-demo/i2c-tcp-demo.py    | 133 ++++++++++++
 3 files changed, 440 insertions(+)
 create mode 100644 hw/misc/esp32_i2c_tcp_slave.c
 create mode 100644 include/hw/misc/esp32_i2c_tcp_slave.h
 create mode 100644 tests/i2c-tcp-demo/i2c-tcp-demo.py

diff --git a/hw/misc/esp32_i2c_tcp_slave.c b/hw/misc/esp32_i2c_tcp_slave.c
new file mode 100644
index 0000000000..db3b6d366a
--- /dev/null
+++ b/hw/misc/esp32_i2c_tcp_slave.c
@@ -0,0 +1,288 @@
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "hw/i2c/i2c.h"
+#include "hw/irq.h"
+#include "hw/misc/esp32_i2c_tcp_slave.h"
+#include "qemu/module.h"
+
+#include "qapi/qmp/json-writer.h"
+#include "chardev/char-fe.h"
+#include "io/channel-socket.h"
+#include "chardev/char-io.h"
+#include "chardev/char-socket.h"
+#include "qapi/error.h"
+
+/*
+ * Description:
+ * To allow to emulate a I2C slave device which is not supported by QEMU,
+ * a new I2C slave device was created that encapsulates I2C operations
+ * and passes them through a selected chardev to the host
+ * where a client resides that implements a logic of emulated device.
+ *
+ *
+ * Architecture:
+ *    ---------------------------
+ *    | QEMU                    |
+ *    |                         |         -----------------------
+ *    |  ESP32 Firmware writes  |         |                     |
+ *    |  to I2C Slave           |         | I2C Slave Emulation |
+ *    |                         |         |                     |
+ *    |  -----------------------&---------&----                 |
+ *    |  | I2C Slave at 0x7F    &   tcp   &     recv msg        |
+ *    |  -----------------------&---------&---- process msg     |
+ *    |                         |         |     send respone    |
+ *    |                         |         |                     |
+ *    |                         |         |                     |
+ *    ---------------------------         |----------------------
+ *
+ *
+ * Syntax & protocol:
+ *      QEMU I2C Slave sends a msg in following format: BBB\r\n
+ *      where each 'B' represents a single byte 0-255
+ *      QEMU I2C Slave expects a respone message in the same format as fast as possible
+ *      Example:
+ *         req: 0x45 0x01 0x00 \r\n
+ *        resp: 0x45 0x01 0x00 \r\n
+ *
+ *      The format BBB\r\n
+ *        first 'B' is a message type
+ *        second 'B' is a data value
+ *        third 'B' is an error value (not used at the moment)
+ *
+ *      There are three types of message
+ *        'E' or 0x45 - Event:
+ *        'S' or 0x53 - Send: byte sent to emulated I2C Slave
+ *        'R' or 0x52 - Recv: byte to be received by I2C Master
+ *
+ *
+ *      'E' message
+ *        second byte is an event type:
+ *         0x0: I2C_START_RECV
+ *         0x1: I2C_START_SEND
+ *         0x2: I2C_START_SEND_ASYNC
+ *         0x3: I2C_FINISH
+ *         0x4: I2C_NACK
+ *
+ *        Example:
+ *            0x45 0x01 0x00  - start send
+ *            0x45 0x03 0x00  - finish
+ *
+ *        In case of 'E' message, a response is the same as a request message
+ *
+ *      'S' message
+ *        second byte is a byte transmitted from I2C Master to I2C slave device
+ *        the byte to by processed by I2C Slave Device
+ *
+ *        Example:
+ *            0x53 0x20 0x00
+ *
+ *        In case of 'S' message, a response is the same as a request message
+ *
+ *      'R' message
+ *        the I2C Master expect a byte from the emulated i2c slave device
+ *        A client has to modify the second byte of the request message
+ *         and send it back as a response.
+ *
+ *        Example:
+ *             req: 0x52 0x00 0x00
+ *            resp: 0x52 0x11 0x00
+ *
+ *
+ * Examples of Transmission:
+ *     1) i2cset -c 0x7F -r 0x20 0x11 0x22 0x33 0x44 0x55
+ *          req: 45 01 00
+ *         resp: 45 01 00
+ *
+ *          req: 53 20 00
+ *         resp: 53 20 00
+ *
+ *          req: 53 11 00
+ *         resp: 53 11 00
+ *
+ *          req: 53 22 00
+ *         resp: 53 22 00
+ *
+ *          req: 53 33 00
+ *         resp: 53 33 00
+ *
+ *          req: 53 44 00
+ *         resp: 53 44 00
+ *
+ *          req: 53 55 00
+ *         resp: 53 55 00
+ *
+ *          req: 45 03 00
+ *         resp: 45 03 00
+ *
+ *     2) i2cget -c 0x7F -r 0x20 -l 0x03
+ *          req: 45 01 00
+ *         resp: 45 01 00
+ *
+ *          req: 53 20 00
+ *         resp: 53 20 00
+ *
+ *          req: 45 03 00
+ *         resp: 45 03 00
+ *
+ *          req: 45 00 00
+ *         resp: 45 00 00
+ *
+ *          req: 52 00 00
+ *         resp: 52 11 00
+ *
+ *          req: 52 00 00
+ *         resp: 52 22 00
+ *
+ *          req: 52 00 00
+ *         resp: 52 33 00
+ *
+ *          req: 45 03 00
+ *         resp: 45 03 00
+ *
+ *
+ * To start i2c.socket server, set QEMU param:
+ *   -chardev socket,port=16001,wait=no,host=localhost,server=on,ipv4=on,id=i2c.socket
+ *
+ * Simple demo I2C Slave Emulation in Python:
+ *   tests/i2c-tcp-demo/i2c-tcp-demo.py
+ *
+ * Limitations:
+ *   - there is no recv timeout which may lead to qemu hang
+ *
+ */
+
+#define CHARDEV_NAME "i2c.socket"
+
+static Chardev *chardev;
+static CharBackend char_backend;
+static bool chardev_open;
+
+typedef struct {
+    uint8_t id;
+    uint8_t byte;
+    uint8_t err;
+} packet;
+
+static int chr_can_receive(void *opaque)
+{
+    return CHR_READ_BUF_LEN;
+}
+
+static void chr_event(void *opaque, QEMUChrEvent event)
+{
+    switch (event) {
+    case CHR_EVENT_OPENED:
+        qemu_log("connected\n");
+        chardev_open = true;
+        break;
+    case CHR_EVENT_CLOSED:
+        qemu_log("disconnected\n");
+        chardev_open = false;
+        break;
+    case CHR_EVENT_BREAK:
+    case CHR_EVENT_MUX_IN:
+    case CHR_EVENT_MUX_OUT:
+        /* Ignore */
+        break;
+    }
+}
+
+static void send_packet(packet *p)
+{
+    static const char *PACKET_FMT = "%c%c%c\r\n";
+    static char buff[32];
+
+    /* encode */
+    int len = snprintf(buff, sizeof(buff), PACKET_FMT, p->id, p->byte, p->err);
+
+    /* send */
+    qemu_chr_fe_write_all(&char_backend, (uint8_t *)buff, len);
+
+    /* receive */
+    qemu_chr_fe_read_all(&char_backend, (uint8_t *)buff, len);
+
+    /* decode */
+    sscanf(buff, PACKET_FMT, &p->id, &p->byte, &p->err);
+}
+
+static uint8_t slave_rx(I2CSlave *i2c)
+{
+    packet p = {.id = 'R',
+                .byte = 0,
+                .err = 0};
+
+    send_packet(&p);
+
+    return p.byte;
+}
+
+static int slave_tx(I2CSlave *i2c, uint8_t data)
+{
+    packet p = {.id = 'S',
+                .byte = data,
+                .err = 0};
+
+    send_packet(&p);
+
+    return 0;
+}
+
+static int slave_event(I2CSlave *i2c, enum i2c_event event)
+{
+    packet p = {.id = 'E',
+                .byte = event,
+                .err = 0};
+
+    send_packet(&p);
+
+    return 0;
+}
+
+static void slave_realize(DeviceState *dev, Error **errp)
+{
+}
+
+static void slave_init(Object *obj)
+{
+    Error *err = NULL;
+    chardev = qemu_chr_find(CHARDEV_NAME);
+    if (!chardev) {
+        error_report("chardev '%s' not found", CHARDEV_NAME);
+        return;
+    }
+
+    if (!qemu_chr_fe_init(&char_backend, chardev, &err)) {
+        error_report_err(err);
+        return;
+    }
+
+    qemu_chr_fe_set_handlers(&char_backend, chr_can_receive, NULL, chr_event,
+                             NULL, NULL, NULL, true);
+}
+
+static void slave_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+    dc->realize = slave_realize;
+    k->event = slave_event;
+    k->recv = slave_rx;
+    k->send = slave_tx;
+}
+
+static const TypeInfo esp32_i2c_tcp_info = {
+    .name = TYPE_ESP32_I2C_TCP,
+    .parent = TYPE_I2C_SLAVE,
+    .instance_size = sizeof(ESP32_I2C_TCP_State),
+    .instance_init = slave_init,
+    .class_init = slave_class_init,
+};
+
+static void esp32_i2c_tcp_type_init(void)
+{
+    type_register_static(&esp32_i2c_tcp_info);
+}
+
+type_init(esp32_i2c_tcp_type_init);
diff --git a/include/hw/misc/esp32_i2c_tcp_slave.h b/include/hw/misc/esp32_i2c_tcp_slave.h
new file mode 100644
index 0000000000..e36bac7ffe
--- /dev/null
+++ b/include/hw/misc/esp32_i2c_tcp_slave.h
@@ -0,0 +1,19 @@
+/*
+ */
+#ifndef QEMU_ESP32_I2C_TCP_SLAVE_H
+#define QEMU_ESP32_I2C_TCP_SLAVE_H
+
+#include "hw/i2c/i2c.h"
+#include "qom/object.h"
+
+#define TYPE_ESP32_I2C_TCP "esp32_i2c_tcp"
+OBJECT_DECLARE_SIMPLE_TYPE(ESP32_I2C_TCP_State, ESP32_I2C_TCP)
+
+/**
+ */
+struct ESP32_I2C_TCP_State {
+    /*< private >*/
+    I2CSlave i2c;
+};
+
+#endif
diff --git a/tests/i2c-tcp-demo/i2c-tcp-demo.py b/tests/i2c-tcp-demo/i2c-tcp-demo.py
new file mode 100644
index 0000000000..d4bec457f3
--- /dev/null
+++ b/tests/i2c-tcp-demo/i2c-tcp-demo.py
@@ -0,0 +1,133 @@
+import json
+from twisted.internet import task
+from twisted.internet.defer import Deferred
+from twisted.internet.protocol import ClientFactory
+from twisted.protocols.basic import LineReceiver
+from dataclasses import dataclass
+from enum import Enum
+
+# i2cset -c 0x7F -r 0x20 0x11 0x22 0x33 0x44 0x55
+# i2cget -c 0x7F -r 0x20 -l 0x0A
+
+HOST = "localhost"
+PORT = 16001
+
+
+class EVENT(Enum):
+    I2C_START_RECV = 0
+    I2C_START_SEND = 1
+    I2C_START_SEND_ASYNC = 2
+    I2C_FINISH = 3
+    I2C_NACK = 4
+
+
+@dataclass
+class I2CSlave:
+    mem: bytearray = bytearray(256)
+    mem_addr: int = 0
+    curr_addr: int = 0
+    first_send: bool = True
+    recv_conuter: int = 0
+
+
+i2cslave = I2CSlave()
+
+
+def dump_mem():
+    print("Mem:")
+    bytes_per_row = 32
+    rows = int(256 / bytes_per_row)
+    for i in range(0, rows):
+        begin = i*bytes_per_row
+        end = begin+bytes_per_row
+        prefix = hex(begin)
+        if i == 0:
+            prefix = "0x00"
+        print(prefix + ": " + i2cslave.mem[begin:end].hex(" "))
+
+    print("\n")
+
+
+def event_handler(packet):
+    evt = EVENT(packet[1])
+    print("Event handler: " + evt.name)
+
+    if evt is EVENT.I2C_FINISH:
+        i2cslave.recv_conuter = 0
+        i2cslave.first_send = True
+        dump_mem()
+
+    return packet
+
+
+def recv_handler(packet):
+    print("Recv handler: byte number " + str(i2cslave.recv_conuter) +
+          " from addr=" + hex(i2cslave.mem_addr) +
+          ", val=" + hex(i2cslave.mem[i2cslave.mem_addr]))
+    i2cslave.recv_conuter += 1
+    resp = bytearray(packet)
+    resp[1] = i2cslave.mem[i2cslave.mem_addr]
+    i2cslave.mem_addr += 1
+    if i2cslave.mem_addr == 256:
+        i2cslave.mem_addr = 0
+    return bytes(resp)
+
+
+def send_handler(packet):
+    print("Send handler: ", end='')
+    if i2cslave.first_send == True:
+        print("address byte: ", hex(packet[1]))
+        i2cslave.mem_addr = packet[1]
+        i2cslave.first_send = False
+    else:
+        print("data byte: ", hex(packet[1]))
+        i2cslave.mem[i2cslave.mem_addr] = packet[1]
+        i2cslave.mem_addr += 1
+        if i2cslave.mem_addr == 256:
+            i2cslave.mem_addr = 0
+    return packet
+
+
+handlers = {'E': event_handler, 'R': recv_handler, 'S': send_handler}
+
+
+class PacketReceiver(LineReceiver):
+    def __init__(self) -> None:
+        super().__init__()
+
+    def connectionMade(self):
+        print("connected")
+
+    def lineReceived(self, line):
+        # print(line.hex(" "))
+        resp = line
+        if len(line) == 3:
+            resp = handlers[chr(line[0])](line)
+
+        self.sendLine(resp)
+
+
+class PacketReceiverFactory(ClientFactory):
+    protocol = PacketReceiver
+
+    def __init__(self):
+        self.done = Deferred()
+
+    def clientConnectionFailed(self, connector, reason):
+        print("connection failed:", reason.getErrorMessage())
+        self.done.errback(reason)
+
+    def clientConnectionLost(self, connector, reason):
+        print("connection lost:", reason.getErrorMessage())
+        self.done.callback(None)
+
+
+def main(reactor):
+    dump_mem()
+    factory = PacketReceiverFactory()
+    reactor.connectTCP(HOST, PORT, factory)
+    return factory.done
+
+
+if __name__ == "__main__":
+    task.react(main)
--
2.34.1


Please consider the environment before printing this e-mail.
This e-mail (including any attachments) is intended solely for the use of the individual or entity to which it is addressed and may contain confidential information. This message is not a binding agreement and does not conclude an agreement without the express confirmation of the sender’s superior or a director of the company.
If you are not the intended recipient, you should immediately notify the sender and delete the message along all the attachments. Any disclosure, copying, distribution or any other action is prohibited and may be illegal. No e-mail transmission can be guaranteed to be 100% secure or error-free, as information could be intercepted, corrupted, lost, destroyed, arrive late or incomplete, or contain viruses. Although Spyrosoft has taken precautions to ensure that this e-mail is free from viruses, the company does not accept liability for any errors or omissions in the content of this message, which arise as a result of the e-mail transmission. This e-mail is deemed to be professional in nature. Spyrosoft does not permit the employees to send emails which contravene provisions of the law.

reply via email to

[Prev in Thread] Current Thread [Next in Thread]