[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: |
Mon, 17 Apr 2023 09:34:25 -0500 |
On Mon, Apr 17, 2023 at 10:18:08AM +0000, Karol Nowak wrote:
> Hi Corey,
>
>
> thank you for your response.
>
>
> Could you give me some hints how to make IO operations non-blocking in QEMU?
> Is there a code reference in the source code of QEMU I could use?
>
You can look at hw/ipmi/ipmi_bmc_extern.c for an example.
-corey
>
> Karol
>
>
> ________________________________
> From: Corey Minyard <tcminyard@gmail.com> on behalf of Corey Minyard
> <minyard@acm.org>
> Sent: Thursday, March 23, 2023 5:03 PM
> To: Karol Nowak <knw@spyro-soft.com>
> Cc: qemu-devel@nongnu.org <qemu-devel@nongnu.org>; philmd@linaro.org
> <philmd@linaro.org>; clg@kaod.org <clg@kaod.org>
> Subject: Re: [RFC PATCH v1] hw/misc: add i2c slave device that passes i2c ops
> outside
>
> [You don't often get email from minyard@acm.org. Learn why this is important
> at https://aka.ms/LearnAboutSenderIdentification ]
>
> ⚠ This email originates from outside the organization or the sender could not
> be verified.
>
> 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.