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 <tcminy...@gmail.com> on behalf of Corey Minyard > <miny...@acm.org> > Sent: Thursday, March 23, 2023 5:03 PM > To: Karol Nowak <k...@spyro-soft.com> > Cc: qemu-devel@nongnu.org <qemu-devel@nongnu.org>; phi...@linaro.org > <phi...@linaro.org>; c...@kaod.org <c...@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 miny...@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 <k...@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.