qemu-devel
[Top][All Lists]
Advanced

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

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


From: Corey Minyard
Subject: Re: [RFC PATCH v1] hw/misc: add i2c slave device that passes i2c ops outside
Date: Thu, 23 Mar 2023 11:03:00 -0500

On Thu, Mar 23, 2023 at 10:09:02AM +0000, Karol Nowak wrote:
> 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?

Someone else has proposed this before with a patch, and it was actually
pretty complete and mostly ok, but I pointed out an issue and never
heard back from them.  This feature is something that might be nice.  As
you say, this needs some improvements.  Some I would point out:

Obviously this can't be named esp32, it needs to be general.

All the I/O (reading and writing) has to be non-blocking.  I/O handling
in qemu is single-threaded, if you block anywhere you basically stop
qemu.  You need to implement something where whatever you do (like
handling a NAK, for instance) it doesn't block qemu.

The protocol you have implemented is basically an extension of the QEMU
protocol.  That's probably not ideal, it would be best to think about a
general protocol for extending I2C over a TCP connection.  A lot of the
details of the QEMU implementation is probably not necessary over a TCP
connection.

-corey

> 
> 
> 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]