From 44c2f0cc69b22f4d8430332ee84cbd0e501fb4a6 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Tue, 16 Sep 2025 14:50:34 +0100 Subject: [PATCH 1/4] [ot] hw/opentitan: ot_i2c_transport: OT I2C Transport implementation The intention of this device is to be an I2C host device that is driven externally by OpenTitanTool. It will be used to issue read and write operations to other I2C devices on its bus - of primary interest is issuing commands to the OpenTitan I2C host/target block. This "bus device" is present on the bus, but it cannot be interacted with by other bus devices and cannot have transactions targetting it. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_i2c_transport.c | 318 ++++++++++++++++++++++++ include/hw/opentitan/ot_i2c_transport.h | 36 +++ 2 files changed, 354 insertions(+) create mode 100644 hw/opentitan/ot_i2c_transport.c create mode 100644 include/hw/opentitan/ot_i2c_transport.h diff --git a/hw/opentitan/ot_i2c_transport.c b/hw/opentitan/ot_i2c_transport.c new file mode 100644 index 0000000000000..28fc78e807b5f --- /dev/null +++ b/hw/opentitan/ot_i2c_transport.c @@ -0,0 +1,318 @@ +/* + * OpenTitanTool I2C transport device for OpenTitan + * + * Copyright (c) 2025 lowRISC contributors. + * + * Author(s): + * Alice Ziuziakowska + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "qemu/typedefs.h" +#include "qom/object.h" +#include "chardev/char-fe.h" +#include "hw/i2c/i2c.h" +#include "hw/opentitan/ot_common.h" +#include "hw/opentitan/ot_i2c_transport.h" +#include "hw/qdev-properties-system.h" +#include "hw/qdev-properties.h" +#include "trace.h" + +typedef enum { + /* Waiting for start condition */ + CMD_I2C_IDLE, + /* Waiting for address + r/w byte */ + CMD_I2C_START, + CMD_I2C_REPEATED_START, + /* read transaction - waiting to read byte */ + CMD_I2C_READ, + /* write transaction - waiting to write byte */ + CMD_I2C_WRITE, + /* write transaction - waiting for byte to write */ + CMD_I2C_WRITE_PAYLOAD, + /* parse error state */ + CMD_I2C_ERR, +} CmdParserState; + +#define OT_I2C_TRANSPORT_STALL_NS 100000u /* 100us */ + +struct OtI2CTransportState { + I2CSlave parent_obj; + I2CBus *bus; + + /* chardev i2c command parser state */ + CmdParserState parser_state; + bool is_read_command; + + /* Saved target address of transaction for repeated start conditions */ + uint8_t address; + + /* + * Whether we are stalling the chardev. + * This is to allow execution to return to the vCPU briefly + * to process any I2C transfers. + */ + bool stall; + QEMUTimer *stall_timer; + + CharBackend chr; +}; + +struct OtI2CTransportClass { + I2CSlaveClass parent_class; +}; + +static void ot_i2c_transport_put_byte(OtI2CTransportState *s, uint8_t data) +{ + qemu_chr_fe_write(&s->chr, &data, 1u); +} + +static void ot_i2c_transport_put_nack(OtI2CTransportState *s) +{ + ot_i2c_transport_put_byte(s, '!'); +} + +static void ot_i2c_transport_put_ack(OtI2CTransportState *s) +{ + ot_i2c_transport_put_byte(s, '.'); +} + +/* + * Stall the chardev to slow down I2C transaction processing, so that QEMU can + * return control to the vCPU to process and prepare response data for OT I2C. + */ +static void ot_i2c_transport_stall(OtI2CTransportState *s) +{ + s->stall = true; + timer_del(s->stall_timer); + uint64_t now = qemu_clock_get_ns(OT_VIRTUAL_CLOCK); + timer_mod(s->stall_timer, (int64_t)(now + OT_I2C_TRANSPORT_STALL_NS)); +} + +static void ot_i2c_transport_start_transfer(OtI2CTransportState *s) +{ + if (i2c_start_transfer(s->bus, s->address, s->is_read_command) != 0) { + ot_i2c_transport_put_nack(s); + } else { + ot_i2c_transport_put_ack(s); + } + ot_i2c_transport_stall(s); +} + +static void ot_i2c_transport_do_read(OtI2CTransportState *s) +{ + ot_i2c_transport_put_byte(s, i2c_recv(s->bus)); + ot_i2c_transport_stall(s); +} + +static void ot_i2c_transport_do_write(OtI2CTransportState *s, uint8_t data) +{ + if (i2c_send(s->bus, data) != 0) { + ot_i2c_transport_put_nack(s); + } else { + ot_i2c_transport_put_ack(s); + } + ot_i2c_transport_stall(s); +} + +static void ot_i2c_transport_parse_error(OtI2CTransportState *s) +{ + s->parser_state = CMD_I2C_ERR; + ot_i2c_transport_put_byte(s, 'x'); +} + +static void ot_i2c_transport_command_byte(OtI2CTransportState *s, uint8_t byte) +{ + switch (s->parser_state) { + case CMD_I2C_IDLE: + if (byte == 's') { + s->parser_state = CMD_I2C_START; + break; + } + s->parser_state = CMD_I2C_ERR; + break; + case CMD_I2C_START: + case CMD_I2C_REPEATED_START: + /* + * If a transaction is already in progress (repeated start), QEMU does + * not re-scan the bus to find devices even if the provided address + * is different, so we do not bother storing it here as the address + * that the transaction was started with will be used anyway. + */ + if (s->parser_state == CMD_I2C_START) { + s->address = (byte >> 1u); + } + s->is_read_command = (byte & 1u) == 1u; + ot_i2c_transport_start_transfer(s); + s->parser_state = s->is_read_command ? CMD_I2C_READ : CMD_I2C_WRITE; + break; + case CMD_I2C_READ: + if (byte == 'R') { + ot_i2c_transport_do_read(s); + break; + } + if (byte == 'S') { + s->parser_state = CMD_I2C_IDLE; + i2c_end_transfer(s->bus); + break; + } + if (byte == 's') { + s->parser_state = CMD_I2C_REPEATED_START; + break; + } + s->parser_state = CMD_I2C_ERR; + i2c_end_transfer(s->bus); + ot_i2c_transport_parse_error(s); + break; + case CMD_I2C_WRITE: + if (byte == 'W') { + s->parser_state = CMD_I2C_WRITE_PAYLOAD; + break; + } + if (byte == 'S') { + s->parser_state = CMD_I2C_IDLE; + i2c_end_transfer(s->bus); + break; + } + if (byte == 's') { + s->parser_state = CMD_I2C_REPEATED_START; + break; + } + s->parser_state = CMD_I2C_ERR; + i2c_end_transfer(s->bus); + ot_i2c_transport_parse_error(s); + break; + case CMD_I2C_WRITE_PAYLOAD: + ot_i2c_transport_do_write(s, byte); + s->parser_state = CMD_I2C_WRITE; + break; + case CMD_I2C_ERR: + break; + default: + g_assert_not_reached(); + } +} + +static void ot_i2c_transport_unstall(void *opaque) +{ + OtI2CTransportState *s = OT_I2C_TRANSPORT(opaque); + s->stall = false; +} + +static int ot_i2c_transport_can_receive(void *opaque) +{ + OtI2CTransportState *s = OT_I2C_TRANSPORT(opaque); + if (s->parser_state == CMD_I2C_ERR) { + return 0; + } + return s->stall ? 0 : 1u; +} + +static void ot_i2c_transport_receive(void *opaque, const uint8_t *buf, int size) +{ + OtI2CTransportState *s = OT_I2C_TRANSPORT(opaque); + g_assert(size == 1); + ot_i2c_transport_command_byte(s, *buf); +} + +static int ot_i2c_transport_be_change(void *opaque) +{ + OtI2CTransportState *s = OT_I2C_TRANSPORT(opaque); + + qemu_chr_fe_set_handlers(&s->chr, ot_i2c_transport_can_receive, + ot_i2c_transport_receive, NULL, + ot_i2c_transport_be_change, s, NULL, true); + + return 0; +} + +static void ot_i2c_transport_realize(DeviceState *dev, Error **errp) +{ + (void)errp; + OtI2CTransportState *state = OT_I2C_TRANSPORT(dev); + BusState *bus = qdev_get_parent_bus(dev); + state->bus = I2C_BUS(bus); + qemu_chr_fe_set_handlers(&state->chr, ot_i2c_transport_can_receive, + ot_i2c_transport_receive, NULL, + ot_i2c_transport_be_change, state, NULL, true); +} + +static void ot_i2c_transport_init(Object *obj) +{ + OtI2CTransportState *s = OT_I2C_TRANSPORT(obj); + s->parser_state = CMD_I2C_IDLE; + s->is_read_command = false; + s->address = 0u; + s->stall = false; + s->stall_timer = + timer_new_ns(OT_VIRTUAL_CLOCK, &ot_i2c_transport_unstall, (void *)s); +} + +static Property ot_i2c_transport_properties[] = { + DEFINE_PROP_CHR("chardev", OtI2CTransportState, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +/* + * This device does not support being a target device, and does not respond to + * transactions. Overwrite the default `match_and_add` implementation to + * always not match any address, even for a broadcast. + */ +static bool ot_i2c_transport_match_and_add( + I2CSlave *s, uint8_t address, bool broadcast, I2CNodeList *current_devs) +{ + (void)s; + (void)address; + (void)broadcast; + (void)current_devs; + return false; +} + +static void ot_i2c_transport_class_init(ObjectClass *klass, void *data) +{ + (void)data; + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "OpenTitanTool I2C Transport"; + dc->realize = ot_i2c_transport_realize; + device_class_set_props(dc, ot_i2c_transport_properties); + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + + I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass); + sc->match_and_add = &ot_i2c_transport_match_and_add; +} + +static const TypeInfo ot_i2c_transport = { + .name = TYPE_OT_I2C_TRANSPORT, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(OtI2CTransportState), + .instance_init = ot_i2c_transport_init, + .class_init = ot_i2c_transport_class_init, + .class_size = sizeof(OtI2CTransportClass), +}; + +static void register_types(void) +{ + type_register_static(&ot_i2c_transport); +} + +type_init(register_types); diff --git a/include/hw/opentitan/ot_i2c_transport.h b/include/hw/opentitan/ot_i2c_transport.h new file mode 100644 index 0000000000000..c78de7b5a6862 --- /dev/null +++ b/include/hw/opentitan/ot_i2c_transport.h @@ -0,0 +1,36 @@ +/* + * OpenTitanTool I2C transport device for OpenTitan + * + * Copyright (c) 2025 lowRISC contributors. + * + * Author(s): + * Alice Ziuziakowska + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef HW_OPENTITAN_OT_I2C_TRANSPORT_H +#define HW_OPENTITAN_OT_I2C_TRANSPORT_H + +#include "qom/object.h" + +#define TYPE_OT_I2C_TRANSPORT "ot-i2c-transport" +OBJECT_DECLARE_TYPE(OtI2CTransportState, OtI2CTransportClass, OT_I2C_TRANSPORT) + +#endif /* HW_OPENTITAN_OT_I2C_TRANSPORT_H */ From 9ba6699cddfede88b8029521186b6fb1b9f53190 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Tue, 16 Sep 2025 14:53:01 +0100 Subject: [PATCH 2/4] [ot] hw: opentitan: add I2C transport device to Kconfig and meson Signed-off-by: Alice Ziuziakowska --- hw/opentitan/Kconfig | 4 ++++ hw/opentitan/meson.build | 1 + hw/riscv/Kconfig | 2 ++ 3 files changed, 7 insertions(+) diff --git a/hw/opentitan/Kconfig b/hw/opentitan/Kconfig index 1278791b7f8ed..94ec7d8b93075 100644 --- a/hw/opentitan/Kconfig +++ b/hw/opentitan/Kconfig @@ -78,6 +78,10 @@ config OT_I2C select I2C bool +config OT_I2C_TRANSPORT + select I2C + bool + config OT_IBEX_WRAPPER select OT_VMAPPER bool diff --git a/hw/opentitan/meson.build b/hw/opentitan/meson.build index 6b551af640ede..b96b7fc08074b 100644 --- a/hw/opentitan/meson.build +++ b/hw/opentitan/meson.build @@ -25,6 +25,7 @@ system_ss.add(when: 'CONFIG_OT_GPIO_DJ', if_true: files('ot_gpio_dj.c')) system_ss.add(when: 'CONFIG_OT_GPIO_EG', if_true: files('ot_gpio_eg.c')) system_ss.add(when: 'CONFIG_OT_HMAC', if_true: [files('ot_hmac.c'), libtomcrypt_dep]) system_ss.add(when: 'CONFIG_OT_I2C', if_true: files('ot_i2c.c')) +system_ss.add(when: 'CONFIG_OT_I2C_TRANSPORT', if_true: files('ot_i2c_transport.c')) system_ss.add(when: 'CONFIG_OT_IBEX_WRAPPER', if_true: files('ot_ibex_wrapper.c')) system_ss.add(when: 'CONFIG_OT_KEY_SINK', if_true: files('ot_key_sink.c')) system_ss.add(when: 'CONFIG_OT_KEYMGR', if_true: files('ot_keymgr.c')) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index ff25e2b5ab4c0..cf3f7a20d9529 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -38,6 +38,7 @@ config OT_DARJEELING select OT_GPIO_DJ select OT_HMAC select OT_I2C + select OT_I2C_TRANSPORT select OT_IBEX_WRAPPER select OT_KEYMGR_DPE select OT_KMAC @@ -82,6 +83,7 @@ config OT_EARLGREY select OT_GPIO_EG select OT_HMAC select OT_I2C + select OT_I2C_TRANSPORT select OT_IBEX_WRAPPER select OT_KEYMGR select OT_KMAC From d5b0bf8c0e6b2797fdce00cad478160caf234eb3 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Tue, 23 Sep 2025 12:15:06 +0100 Subject: [PATCH 3/4] [ot] docs: opentitan: document I2C transport in `i2c_transport.md` Signed-off-by: Alice Ziuziakowska --- docs/opentitan/i2c_transport.md | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/opentitan/i2c_transport.md diff --git a/docs/opentitan/i2c_transport.md b/docs/opentitan/i2c_transport.md new file mode 100644 index 0000000000000..d15cb41b6d9db --- /dev/null +++ b/docs/opentitan/i2c_transport.md @@ -0,0 +1,47 @@ +# OpenTitan I2C Transport + +The `ot-i2c-transport` device provides a way to execute I2C transactions with QEMU I2C bus devices +through an externally driven `chardev` interface. + +The device can perform read or write transactions with bus devices and is intended to be used to +drive the Opentitan I2C device Target mode functionality through `opentitanlib`. + +## Configuration + +A chardev and transport device can be created in QEMU with the following arguments: + +`-chardev pty,i2ctp -device ot-i2c-transport,bus=,chardev=i2ctp` + +Where `bus` is any of the system's I2C buses. On earlgrey for example, these are `ot-i2c0`, +`ot-i2c1`, and `ot-i2c2`. + +## Protocol + +- The protocol over the chardev mirrors the I2C specification closely. + +- A start and repeated start condition is signalled with a `s` byte, followed by a byte containing +the 7-bit target address of the transaction and the read/write bit as the least significant bit +(0 indicates a write transfer, 1 indicates a read transfer). + +- On a repeated start condition, the I2C target address of the first transfer will be used, and +the provided address is ignored (transfer direction can be changed). This is a limitation of QEMU. + +- When a read transfer is active, a `R` byte can be sent to read a byte of data from the target +I2C device. + +- When a write transfer is active, a `W` byte, followed by a data byte, can be sent to write that +byte to the target I2C device. + +- A stop condition to end the transaction is signalled with a `S` byte. + +- Following a start/repeated start condition and the address byte, as well as after every byte +written during a write transfer, an ACK is signalled with a `.` byte, and a NACK is signalled with +a `!` byte. If the start of a transfer is NACKed, the transaction should be terminated with a stop +condition. + +- On a parser error, a `x` byte will be returned, and the parser will not accept any more command +bytes until reset. + +- The implementation will throttle processing of written bytes to allow control to return to the +vCPU, so that OT I2C may process the current transaction and prepare response bytes for upcoming +reads. From f3c8d367fee8f46f032ec91d10466988d02446c9 Mon Sep 17 00:00:00 2001 From: Alice Ziuziakowska Date: Tue, 23 Sep 2025 12:18:56 +0100 Subject: [PATCH 4/4] [ot] hw/opentitan: ot_i2c: implement `I2CSlaveClass` `send` Needed for `ot_i2c_transport` to be able to communicate with `ot_i2c` in target mode. Signed-off-by: Alice Ziuziakowska --- hw/opentitan/ot_i2c.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/hw/opentitan/ot_i2c.c b/hw/opentitan/ot_i2c.c index 1f927612b6579..c4aa0acff1b9d 100644 --- a/hw/opentitan/ot_i2c.c +++ b/hw/opentitan/ot_i2c.c @@ -1165,10 +1165,13 @@ static int ot_i2c_target_event(I2CSlave *target, enum i2c_event event) } switch (event) { + case I2C_START_SEND: case I2C_START_SEND_ASYNC: /* Set the first byte to the target address + RW bit as 0. */ ot_i2c_target_set_acqdata(s, target->address << 1u, SIGNAL_START); - i2c_ack(s->bus); + if (event == I2C_START_SEND_ASYNC) { + i2c_ack(s->bus); + } break; case I2C_START_RECV: /* Set the first byte to the target address + RW bit as 1. */ @@ -1226,6 +1229,18 @@ static uint8_t ot_i2c_target_recv(I2CSlave *target) return data; } +static int ot_i2c_target_send(I2CSlave *target, uint8_t data) +{ + BusState *abus = qdev_get_parent_bus(DEVICE(target)); + OtI2CState *s = OT_I2C(abus->parent); + if (!ot_i2c_target_enabled(s)) { + return -1; + } + + ot_i2c_target_set_acqdata(s, data, SIGNAL_NONE); + return 0; +} + static void ot_i2c_target_send_async(I2CSlave *target, uint8_t data) { BusState *abus = qdev_get_parent_bus(DEVICE(target)); @@ -1246,6 +1261,7 @@ static void ot_i2c_target_class_init(ObjectClass *klass, void *data) dc->desc = "OpenTitan I2C Target"; sc->event = &ot_i2c_target_event; + sc->send = &ot_i2c_target_send; sc->send_async = &ot_i2c_target_send_async; sc->recv = &ot_i2c_target_recv; }