[PATCH v7 13/22] hw/i3c/dw-i3c: Add data TX and RX

Jamin Lin
2026-02-25T02:14:46Z

This adds data and CCC transmission, reception, and the associated
queues required for data transmission and reception to happen.

The I3C controller transmits data by the user writing into a command
queue. When the queue has a command and an argument in it, the
controller starts executing the command.

The controller can execute 1 of 3 ways:
1. A larger data transfer that involves using the TX and RX queues. This
is the most common way the controller does transactions.

2. A small data transfer that involves sending a couple bytes passed
into the command queue argument.

3. An address assignment command. This is how the controller does
ENTDAA. When ENTDAA succeeds in assigning an address to a target, it
updates the controller's char table with the target's PID, BCR, and
DCR.

The controller determines what addresses to send by looking at the index
in the device address table specified by the argument in the command
queue. ENTDAA also uses these addresses to assign to targets on the bus.

When the controller is done executing a command, it puts a response in
the response queue indicating how command execution went.

In order for the user to send and receive data to/from the controller,
the user reads/writes to a bidirectional TX/RX port.

Signed-off-by: Joe Komlodi <komlodi@google.com>
Reviewed-by: Stephen Longfield <slongfield@google.com>
Reviewed-by: Patrick Venture <venture@google.com>
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
include/hw/i3c/aspeed_i3c.h | 1 +
include/hw/i3c/dw-i3c.h | 143 +++++-
hw/i3c/dw-i3c.c | 882 +++++++++++++++++++++++++++++++++++-
hw/i3c/trace-events | 10 +
4 files changed, 1030 insertions(+), 6 deletions(-)

diff --git a/include/hw/i3c/aspeed_i3c.h b/include/hw/i3c/aspeed_i3c.h
index ade5c42d39..b8827d31d7 100644
--- a/include/hw/i3c/aspeed_i3c.h
+++ b/include/hw/i3c/aspeed_i3c.h
@@ -2,6 +2,7 @@
* ASPEED I3C Controller
*
* Copyright (C) 2021 ASPEED Technology Inc.
+ * Copyright (C) 2023 Google, LLC
*
* This code is licensed under the GPL version 2 or later. See
* the COPYING file in the top-level directory.
diff --git a/include/hw/i3c/dw-i3c.h b/include/hw/i3c/dw-i3c.h
index 7143e8ca7a..c50d67cc6b 100644
--- a/include/hw/i3c/dw-i3c.h
+++ b/include/hw/i3c/dw-i3c.h
@@ -10,20 +10,159 @@
#ifndef DW_I3C_H
#define DW_I3C_H

+#include "qemu/fifo32.h"
+#include "hw/i3c/i3c.h"
#include "hw/core/sysbus.h"

#define TYPE_DW_I3C "dw.i3c"
OBJECT_DECLARE_SIMPLE_TYPE(DWI3C, DW_I3C)

-#define DW_I3C_NR_REGS (0x300 >> 2)
+/*
+ * Sufficiently large enough to handle configurations with large device address
+ * tables.
+ */
+#define DW_I3C_NR_REGS (0x1000 >> 2)
+
+/* From datasheet. */
+#define DW_I3C_CMD_ATTR_TRANSFER_CMD 0
+#define DW_I3C_CMD_ATTR_TRANSFER_ARG 1
+#define DW_I3C_CMD_ATTR_SHORT_DATA_ARG 2
+#define DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD 3
+
+/* Enum values from datasheet. */
+typedef enum DWI3CRespQueueErr {
+ DW_I3C_RESP_QUEUE_ERR_NONE = 0,
+ DW_I3C_RESP_QUEUE_ERR_CRC = 1,
+ DW_I3C_RESP_QUEUE_ERR_PARITY = 2,
+ DW_I3C_RESP_QUEUE_ERR_FRAME = 3,
+ DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK = 4,
+ DW_I3C_RESP_QUEUE_ERR_DAA_NACK = 5,
+ DW_I3C_RESP_QUEUE_ERR_OVERFLOW = 6,
+ DW_I3C_RESP_QUEUE_ERR_ABORTED = 8,
+ DW_I3C_RESP_QUEUE_ERR_I2C_NACK = 9,
+} DWI3CRespQueueErr;
+
+typedef enum DWI3CTransferState {
+ DW_I3C_TRANSFER_STATE_IDLE = 0x00,
+ DW_I3C_TRANSFER_STATE_START = 0x01,
+ DW_I3C_TRANSFER_STATE_RESTART = 0x02,
+ DW_I3C_TRANSFER_STATE_STOP = 0x03,
+ DW_I3C_TRANSFER_STATE_START_HOLD = 0x04,
+ DW_I3C_TRANSFER_STATE_BROADCAST_W = 0x05,
+ DW_I3C_TRANSFER_STATE_BROADCAST_R = 0x06,
+ DW_I3C_TRANSFER_STATE_DAA = 0x07,
+ DW_I3C_TRANSFER_STATE_DAA_GEN = 0x08,
+ DW_I3C_TRANSFER_STATE_CCC_BYTE = 0x0b,
+ DW_I3C_TRANSFER_STATE_HDR_CMD = 0x0c,
+ DW_I3C_TRANSFER_STATE_WRITE = 0x0d,
+ DW_I3C_TRANSFER_STATE_READ = 0x0e,
+ DW_I3C_TRANSFER_STATE_IBI_READ = 0x0f,
+ DW_I3C_TRANSFER_STATE_IBI_DIS = 0x10,
+ DW_I3C_TRANSFER_STATE_HDR_DDR_CRC = 0x11,
+ DW_I3C_TRANSFER_STATE_CLK_STRETCH = 0x12,
+ DW_I3C_TRANSFER_STATE_HALT = 0x13,
+} DWI3CTransferState;
+
+typedef enum DWI3CTransferStatus {
+ DW_I3C_TRANSFER_STATUS_IDLE = 0x00,
+ DW_I3C_TRANSFER_STATUS_BROACAST_CCC = 0x01,
+ DW_I3C_TRANSFER_STATUS_DIRECT_CCC_W = 0x02,
+ DW_I3C_TRANSFER_STATUS_DIRECT_CCC_R = 0x03,
+ DW_I3C_TRANSFER_STATUS_ENTDAA = 0x04,
+ DW_I3C_TRANSFER_STATUS_SETDASA = 0x05,
+ DW_I3C_TRANSFER_STATUS_I3C_SDR_W = 0x06,
+ DW_I3C_TRANSFER_STATUS_I3C_SDR_R = 0x07,
+ DW_I3C_TRANSFER_STATUS_I2C_SDR_W = 0x08,
+ DW_I3C_TRANSFER_STATUS_I2C_SDR_R = 0x09,
+ DW_I3C_TRANSFER_STATUS_HDR_TS_W = 0x0a,
+ DW_I3C_TRANSFER_STATUS_HDR_TS_R = 0x0b,
+ DW_I3C_TRANSFER_STATUS_HDR_DDR_W = 0x0c,
+ DW_I3C_TRANSFER_STATUS_HDR_DDR_R = 0x0d,
+ DW_I3C_TRANSFER_STATUS_IBI = 0x0e,
+ DW_I3C_TRANSFER_STATUS_HALT = 0x0f,
+} DWI3CTransferStatus;
+
+/*
+ * Transfer commands and arguments are 32-bit wide values that the user passes
+ * into the command queue. We interpret each 32-bit word based on the cmd_attr
+ * field.
+ */
+typedef struct DWI3CTransferCmd {
+ uint8_t cmd_attr:3;
+ uint8_t tid:4; /* Transaction ID */
+ uint16_t cmd:8;
+ uint8_t cp:1; /* Command present */
+ uint8_t dev_index:5;
+ uint8_t speed:3;
+ uint8_t resv0:1;
+ uint8_t dbp:1; /* Defining byte present */
+ uint8_t roc:1; /* Response on completion */
+ uint8_t sdap:1; /* Short data argument present */
+ uint8_t rnw:1; /* Read not write */
+ uint8_t resv1:1;
+ uint8_t toc:1; /* Termination (I3C STOP) on completion */
+ uint8_t pec:1; /* Parity error check enabled */
+} DWI3CTransferCmd;
+
+typedef struct DWI3CTransferArg {
+ uint8_t cmd_attr:3;
+ uint8_t resv:5;
+ uint8_t db; /* Defining byte */
+ uint16_t data_len;
+} DWI3CTransferArg;
+
+typedef struct DWI3CShortArg {
+ uint8_t cmd_attr:3;
+ uint8_t byte_strb:3;
+ uint8_t resv:2;
+ uint8_t byte0;
+ uint8_t byte1;
+ uint8_t byte2;
+} DWI3CShortArg;
+
+typedef struct DWI3CAddrAssignCmd {
+ uint8_t cmd_attr:3;
+ uint8_t tid:4; /* Transaction ID */
+ uint16_t cmd:8;
+ uint8_t resv0:1;
+ uint8_t dev_index:5;
+ uint16_t dev_count:5;
+ uint8_t roc:1; /* Response on completion */
+ uint8_t resv1:3;
+ uint8_t toc:1; /* Termination (I3C STOP) on completion */
+ uint8_t resv2:1;
+} DWI3CAddrAssignCmd;
+
+typedef union DWI3CCmdQueueData {
+ uint32_t word;
+ DWI3CTransferCmd transfer_cmd;
+ DWI3CTransferArg transfer_arg;
+ DWI3CShortArg short_arg;
+ DWI3CAddrAssignCmd addr_assign_cmd;
+} DWI3CCmdQueueData;

struct DWI3C {
SysBusDevice parent_obj;

MemoryRegion mr;
qemu_irq irq;
+ I3CBus *bus;
+
+ Fifo32 cmd_queue;
+ Fifo32 resp_queue;
+ Fifo32 tx_queue;
+ Fifo32 rx_queue;

- uint8_t id;
+ struct {
+ uint8_t id;
+ uint8_t cmd_resp_queue_capacity_bytes;
+ uint16_t tx_rx_queue_capacity_bytes;
+ uint8_t num_addressable_devices;
+ uint16_t dev_addr_table_pointer;
+ uint16_t dev_addr_table_depth;
+ uint16_t dev_char_table_pointer;
+ uint16_t dev_char_table_depth;
+ } cfg;
uint32_t regs[DW_I3C_NR_REGS];
};

diff --git a/hw/i3c/dw-i3c.c b/hw/i3c/dw-i3c.c
index 0b99c7edfb..2453d74a8c 100644
--- a/hw/i3c/dw-i3c.c
+++ b/hw/i3c/dw-i3c.c
@@ -336,12 +336,179 @@ static const uint32_t dw_i3c_ro[DW_I3C_NR_REGS] = {
[R_SLAVE_CONFIG] = 0xffffffff,
};

+static inline bool dw_i3c_has_hdr_ts(DWI3C *s)
+{
+ return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_TS);
+}
+
+static inline bool dw_i3c_has_hdr_ddr(DWI3C *s)
+{
+ return ARRAY_FIELD_EX32(s->regs, HW_CAPABILITY, HDR_DDR);
+}
+
+static inline bool dw_i3c_can_transmit(DWI3C *s)
+{
+ /*
+ * We can only transmit if we're enabled and the resume bit is cleared.
+ * The resume bit is set on a transaction error, and software must clear it.
+ */
+ return ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_EN) &&
+ !ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_RESUME);
+}
+
+static inline uint8_t dw_i3c_fifo_threshold_from_reg(uint8_t regval)
+{
+ return regval = regval ? (2 << regval) : 1;
+}
+
static void dw_i3c_update_irq(DWI3C *s)
{
bool level = !!(s->regs[R_INTR_SIGNAL_EN] & s->regs[R_INTR_STATUS]);
qemu_set_irq(s->irq, level);
}

+static void dw_i3c_end_transfer(DWI3C *s, bool is_i2c)
+{
+ if (is_i2c) {
+ legacy_i2c_end_transfer(s->bus);
+ } else {
+ i3c_end_transfer(s->bus);
+ }
+}
+
+static int dw_i3c_send_start(DWI3C *s, uint8_t addr, bool is_recv, bool is_i2c)
+{
+ int ret;
+
+ if (is_i2c) {
+ ret = legacy_i2c_start_transfer(s->bus, addr, is_recv);
+ } else {
+ ret = i3c_start_transfer(s->bus, addr, is_recv);
+ }
+ if (ret) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed on TX with addr 0x%.2x\n",
+ path, addr);
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_HALT);
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+ DW_I3C_TRANSFER_STATUS_HALT);
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
+ ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
+ }
+
+ return ret;
+}
+
+static int dw_i3c_send(DWI3C *s, const uint8_t *data, uint32_t num_to_send,
+ uint32_t *num_sent, bool is_i2c)
+{
+ int ret;
+ uint32_t i;
+
+ *num_sent = 0;
+ if (is_i2c) {
+ /* Legacy I2C must be byte-by-byte. */
+ for (i = 0; i < num_to_send; i++) {
+ ret = legacy_i2c_send(s->bus, data[i]);
+ if (ret) {
+ break;
+ }
+ (*num_sent)++;
+ }
+ } else {
+ ret = i3c_send(s->bus, data, num_to_send, num_sent);
+ }
+ if (ret) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed sending byte 0x%.2x\n",
+ path, data[*num_sent]);
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_HALT);
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+ DW_I3C_TRANSFER_STATUS_HALT);
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
+ ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
+ }
+
+ trace_dw_i3c_send(s->cfg.id, *num_sent);
+
+ return ret;
+}
+
+static int dw_i3c_send_byte(DWI3C *s, uint8_t byte, bool is_i2c)
+{
+ /*
+ * Ignored, the caller will know if we sent 0 or 1 bytes depending on if
+ * we were ACKed/NACKed.
+ */
+ uint32_t num_sent;
+ return dw_i3c_send(s, &byte, 1, &num_sent, is_i2c);
+}
+
+static int dw_i3c_recv_data(DWI3C *s, bool is_i2c, uint8_t *data,
+ uint16_t num_to_read, uint32_t *num_read)
+{
+ int ret;
+
+ if (is_i2c) {
+ for (uint16_t i = 0; i < num_to_read; i++) {
+ data[i] = legacy_i2c_recv(s->bus);
+ }
+ /* I2C devices can neither NACK a read, nor end transfers early. */
+ *num_read = num_to_read;
+ trace_dw_i3c_recv_data(s->cfg.id, *num_read);
+ return 0;
+ }
+ /* I3C devices can NACK if the controller sends an unsupported CCC. */
+ ret = i3c_recv(s->bus, data, num_to_read, num_read);
+ if (ret) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: NACKed receiving byte\n",
+ object_get_canonical_path(OBJECT(s)));
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_HALT);
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
+ DW_I3C_TRANSFER_STATUS_HALT);
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TRANSFER_ERR, 1);
+ ARRAY_FIELD_DP32(s->regs, DEVICE_CTRL, I3C_RESUME, 1);
+ }
+
+ trace_dw_i3c_recv_data(s->cfg.id, *num_read);
+
+ return ret;
+}
+
+static inline bool dw_i3c_target_is_i2c(DWI3C *s, uint16_t offset)
+{
+ /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
+ uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
+ ADDR) / sizeof(uint32_t)) + offset;
+ return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
+ LEGACY_I2C_DEVICE);
+}
+
+static uint8_t dw_i3c_target_addr(DWI3C *s, uint16_t offset)
+{
+ if (offset > s->cfg.num_addressable_devices) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Device addr table offset %d out of "
+ "bounds\n", path, offset);
+ /* If we're out of bounds, return an address of 0. */
+ return 0;
+ }
+
+ /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
+ uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
+ ADDR) / sizeof(uint32_t)) + offset;
+ /* I2C devices use a static address. */
+ if (dw_i3c_target_is_i2c(s, offset)) {
+ return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
+ DEV_STATIC_ADDR);
+ }
+ return FIELD_EX32(s->regs[dev_index], DEVICE_ADDR_TABLE_LOC1,
+ DEV_DYNAMIC_ADDR);
+}
+
static uint32_t dw_i3c_intr_status_r(DWI3C *s)
{
/* Only return the status whose corresponding EN bits are set. */
@@ -376,6 +543,56 @@ static void dw_i3c_intr_force_w(DWI3C *s, uint32_t val)
dw_i3c_update_irq(s);
}

+static uint32_t dw_i3c_pop_rx(DWI3C *s)
+{
+ if (fifo32_is_empty(&s->rx_queue)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read RX FIFO when empty\n",
+ path);
+ return 0;
+ }
+
+ uint32_t val = fifo32_pop(&s->rx_queue);
+ ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR,
+ fifo32_num_used(&s->rx_queue));
+
+ /* Threshold is 2^RX_BUF_THLD. */
+ uint8_t threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
+ RX_BUF_THLD);
+ threshold = dw_i3c_fifo_threshold_from_reg(threshold);
+ if (fifo32_num_used(&s->rx_queue) < threshold) {
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 0);
+ dw_i3c_update_irq(s);
+ }
+
+ trace_dw_i3c_pop_rx(s->cfg.id, val);
+ return val;
+}
+
+static uint32_t dw_i3c_resp_queue_port_r(DWI3C *s)
+{
+ if (fifo32_is_empty(&s->resp_queue)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to read response FIFO when "
+ "empty\n", path);
+ return 0;
+ }
+
+ uint32_t val = fifo32_pop(&s->resp_queue);
+ ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
+ fifo32_num_used(&s->resp_queue));
+
+ /* Threshold is the register value + 1. */
+ uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+ RESP_BUF_THLD) + 1;
+ if (fifo32_num_used(&s->resp_queue) < threshold) {
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 0);
+ dw_i3c_update_irq(s);
+ }
+
+ return val;
+}
+
static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
{
DWI3C *s = DW_I3C(opaque);
@@ -392,16 +609,635 @@ static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
case R_INTR_STATUS:
value = dw_i3c_intr_status_r(s);
break;
+ case R_RX_TX_DATA_PORT:
+ value = dw_i3c_pop_rx(s);
+ break;
+ case R_RESPONSE_QUEUE_PORT:
+ value = dw_i3c_resp_queue_port_r(s);
+ break;
default:
value = s->regs[addr];
break;
}

- trace_dw_i3c_read(s->id, offset, value);
+ trace_dw_i3c_read(s->cfg.id, offset, value);

return value;
}

+static void dw_i3c_resp_queue_push(DWI3C *s, uint8_t err, uint8_t tid,
+ uint8_t ccc_type, uint16_t data_len)
+{
+ uint32_t val = 0;
+ val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, ERR_STATUS, err);
+ val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, TID, tid);
+ val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, CCCT, ccc_type);
+ val = FIELD_DP32(val, RESPONSE_QUEUE_PORT, DL, data_len);
+ if (!fifo32_is_full(&s->resp_queue)) {
+ trace_dw_i3c_resp_queue_push(s->cfg.id, val);
+ fifo32_push(&s->resp_queue, val);
+ }
+
+ ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, RESP_BUF_BLR,
+ fifo32_num_used(&s->resp_queue));
+ /* Threshold is the register value + 1. */
+ uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+ RESP_BUF_THLD) + 1;
+ if (fifo32_num_used(&s->resp_queue) >= threshold) {
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RESP_RDY, 1);
+ dw_i3c_update_irq(s);
+ }
+}
+
+static void dw_i3c_push_tx(DWI3C *s, uint32_t val)
+{
+ if (fifo32_is_full(&s->tx_queue)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to TX FIFO when "
+ "full\n", object_get_canonical_path(OBJECT(s)));
+ return;
+ }
+
+ trace_dw_i3c_push_tx(s->cfg.id, val);
+ fifo32_push(&s->tx_queue, val);
+ ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC,
+ fifo32_num_free(&s->tx_queue));
+
+ /* Threshold is 2^TX_BUF_THLD. */
+ uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
+ TX_BUF_THLD);
+ empty_threshold =
+ dw_i3c_fifo_threshold_from_reg(empty_threshold);
+ if (fifo32_num_free(&s->tx_queue) < empty_threshold) {
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 0);
+ dw_i3c_update_irq(s);
+ }
+}
+
+static uint32_t dw_i3c_pop_tx(DWI3C *s)
+{
+ if (fifo32_is_empty(&s->tx_queue)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to pop from TX FIFO when "
+ "empty\n", path);
+ return 0;
+ }
+
+ uint32_t val = fifo32_pop(&s->tx_queue);
+ trace_dw_i3c_pop_tx(s->cfg.id, val);
+ ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, TX_BUF_EMPTY_LOC,
+ fifo32_num_free(&s->tx_queue));
+
+ /* Threshold is 2^TX_BUF_THLD. */
+ uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
+ TX_BUF_THLD);
+ empty_threshold =
+ dw_i3c_fifo_threshold_from_reg(empty_threshold);
+ if (fifo32_num_free(&s->tx_queue) >= empty_threshold) {
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, TX_THLD, 1);
+ dw_i3c_update_irq(s);
+ }
+ return val;
+}
+
+static void dw_i3c_push_rx(DWI3C *s, uint32_t val)
+{
+ if (fifo32_is_full(&s->rx_queue)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to push to RX FIFO when "
+ "full\n", path);
+ return;
+ }
+ trace_dw_i3c_push_rx(s->cfg.id, val);
+ fifo32_push(&s->rx_queue, val);
+
+ ARRAY_FIELD_DP32(s->regs, DATA_BUFFER_STATUS_LEVEL, RX_BUF_BLR,
+ fifo32_num_used(&s->rx_queue));
+ /* Threshold is 2^RX_BUF_THLD. */
+ uint8_t threshold = ARRAY_FIELD_EX32(s->regs, DATA_BUFFER_THLD_CTRL,
+ RX_BUF_THLD);
+ threshold = dw_i3c_fifo_threshold_from_reg(threshold);
+ if (fifo32_num_used(&s->rx_queue) >= threshold) {
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, RX_THLD, 1);
+ dw_i3c_update_irq(s);
+ }
+}
+
+static void dw_i3c_short_transfer(DWI3C *s, DWI3CTransferCmd cmd,
+ DWI3CShortArg arg)
+{
+ uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
+ uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
+ bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index);
+ uint8_t data[4]; /* Max we can send on a short transfer is 4 bytes. */
+ uint8_t len = 0;
+ uint32_t bytes_sent; /* Ignored on short transfers. */
+
+ /* Can't do reads on a short transfer. */
+ if (cmd.rnw) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Cannot do a read on a short "
+ "transfer\n", path);
+ return;
+ }
+
+ if (dw_i3c_send_start(s, addr, /*is_recv=*/false, is_i2c)) {
+ err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+ goto transfer_done;
+ }
+
+ /* Are we sending a command? */
+ if (cmd.cp) {
+ data[len] = cmd.cmd;
+ len++;
+ /*
+ * byte0 is the defining byte for a command, and is only sent if a
+ * command is present and if the command has a defining byte present.
+ * (byte_strb & 0x01) is always treated as set by the controller, and is
+ * ignored.
+ */
+ if (cmd.dbp) {
+ data[len] += arg.byte0;
+ len++;
+ }
+ }
+
+ /* Send the bytes passed in the argument. */
+ if (arg.byte_strb & 0x02) {
+ data[len] = arg.byte1;
+ len++;
+ }
+ if (arg.byte_strb & 0x04) {
+ data[len] = arg.byte2;
+ len++;
+ }
+
+ if (dw_i3c_send(s, data, len, &bytes_sent, is_i2c)) {
+ err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+ } else {
+ /* Only go to an idle state on a successful transfer. */
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_IDLE);
+ }
+
+transfer_done:
+ if (cmd.toc) {
+ dw_i3c_end_transfer(s, is_i2c);
+ }
+ if (cmd.roc) {
+ /*
+ * ccc_type is always 0 in controller mode, data_len is 0 in short
+ * transfers.
+ */
+ dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
+ /*data_len=*/0);
+ }
+}
+
+/* Returns number of bytes transmitted. */
+static uint16_t dw_i3c_tx(DWI3C *s, uint16_t num, bool is_i2c)
+{
+ uint16_t bytes_sent = 0;
+ union {
+ uint8_t b[sizeof(uint32_t)];
+ uint32_t val;
+ } val32;
+
+ while (bytes_sent < num) {
+ val32.val = dw_i3c_pop_tx(s);
+ for (uint8_t i = 0; i < sizeof(val32.val); i++) {
+ if (dw_i3c_send_byte(s, val32.b[i], is_i2c)) {
+ return bytes_sent;
+ }
+ bytes_sent++;
+
+ /* We're not sending the full 32-bits, break early. */
+ if (bytes_sent >= num) {
+ break;
+ }
+ }
+ }
+
+ return bytes_sent;
+}
+
+/* Returns number of bytes received. */
+static uint16_t dw_i3c_rx(DWI3C *s, uint16_t num, bool is_i2c)
+{
+ /*
+ * Allocate a temporary buffer to read data from the target.
+ * Zero it and word-align it as well in case we're reading unaligned data.
+ */
+ g_autofree uint8_t *data = g_new0(uint8_t, num + (4 - (num & 0x03)));
+ uint32_t *data32 = (uint32_t *)data;
+ /*
+ * 32-bits since the I3C API wants a 32-bit number, even though the
+ * controller can only do 16-bit transfers.
+ */
+ uint32_t num_read = 0;
+
+ /* Can NACK if the target receives an unsupported CCC. */
+ if (dw_i3c_recv_data(s, is_i2c, data, num, &num_read)) {
+ return 0;
+ }
+
+ for (uint16_t i = 0; i < num_read / 4; i++) {
+ dw_i3c_push_rx(s, *data32);
+ data32++;
+ }
+ /*
+ * If we're pushing data that isn't 32-bit aligned, push what's left.
+ * It's software's responsibility to know what bits are valid in the partial
+ * data.
+ */
+ if (num_read & 0x03) {
+ dw_i3c_push_rx(s, *data32);
+ }
+
+ return num_read;
+}
+
+static int dw_i3c_transfer_ccc(DWI3C *s, DWI3CTransferCmd cmd,
+ DWI3CTransferArg arg)
+{
+ /* CCC start is always a write. CCCs cannot be done on I2C devices. */
+ if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
+ /*is_i2c=*/false)) {
+ return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+ }
+ trace_dw_i3c_transfer_ccc(s->cfg.id, cmd.cmd);
+ if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) {
+ return DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+ }
+
+ /* On a direct CCC, we do a restart and then send the target's address. */
+ if (CCC_IS_DIRECT(cmd.cmd)) {
+ bool is_recv = cmd.rnw;
+ uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
+ if (dw_i3c_send_start(s, addr, is_recv, /*is_i2c=*/false)) {
+ return DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+ }
+ }
+
+ return DW_I3C_RESP_QUEUE_ERR_NONE;
+}
+
+static void dw_i3c_transfer(DWI3C *s, DWI3CTransferCmd cmd,
+ DWI3CTransferArg arg)
+{
+ bool is_recv = cmd.rnw;
+ uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
+ uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index);
+ bool is_i2c = dw_i3c_target_is_i2c(s, cmd.dev_index);
+ uint16_t bytes_transferred = 0;
+
+ if (cmd.cp) {
+ /* We're sending a CCC. */
+ err = dw_i3c_transfer_ccc(s, cmd, arg);
+ if (err != DW_I3C_RESP_QUEUE_ERR_NONE) {
+ goto transfer_done;
+ }
+ } else {
+ if (ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_BROADCAST_ADDR_INC) &&
+ is_i2c == false) {
+ if (dw_i3c_send_start(s, I3C_BROADCAST,
+ /*is_recv=*/false, is_i2c)) {
+ err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+ goto transfer_done;
+ }
+ }
+ /* Otherwise we're doing a private transfer. */
+ if (dw_i3c_send_start(s, addr, is_recv, is_i2c)) {
+ err = DW_I3C_RESP_QUEUE_ERR_I2C_NACK;
+ goto transfer_done;
+ }
+ }
+
+ if (is_recv) {
+ bytes_transferred = dw_i3c_rx(s, arg.data_len, is_i2c);
+ } else {
+ bytes_transferred = dw_i3c_tx(s, arg.data_len, is_i2c);
+ }
+
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_IDLE);
+
+transfer_done:
+ if (cmd.toc) {
+ dw_i3c_end_transfer(s, is_i2c);
+ }
+ if (cmd.roc) {
+ /*
+ * data_len is the number of bytes that still need to be TX'd, or the
+ * number of bytes RX'd.
+ */
+ uint16_t data_len = is_recv ? bytes_transferred : arg.data_len -
+ bytes_transferred;
+ /* CCCT is always 0 in controller mode. */
+ dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
+ data_len);
+ }
+
+ dw_i3c_update_irq(s);
+}
+
+static void dw_i3c_transfer_cmd(DWI3C *s, DWI3CTransferCmd cmd,
+ DWI3CCmdQueueData arg)
+{
+ uint8_t arg_attr = FIELD_EX32(arg.word, COMMAND_QUEUE_PORT, CMD_ATTR);
+
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CMD_TID, cmd.tid);
+
+ /* User is trying to do HDR transfers, see if we can do them. */
+ if (cmd.speed == 0x06 && !dw_i3c_has_hdr_ddr(s)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR DDR is not supported\n", path);
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_HALT);
+ return;
+ }
+ if (cmd.speed == 0x05 && !dw_i3c_has_hdr_ts(s)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: HDR TS is not supported\n", path);
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_HALT);
+ return;
+ }
+
+ if (arg_attr == DW_I3C_CMD_ATTR_TRANSFER_ARG) {
+ dw_i3c_transfer(s, cmd, arg.transfer_arg);
+ } else if (arg_attr == DW_I3C_CMD_ATTR_SHORT_DATA_ARG) {
+ dw_i3c_short_transfer(s, cmd, arg.short_arg);
+ } else {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Unknown command queue cmd_attr 0x%x"
+ "\n", path, arg_attr);
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_HALT);
+ }
+}
+
+static void dw_i3c_update_char_table(DWI3C *s, uint8_t offset, uint64_t pid,
+ uint8_t bcr, uint8_t dcr, uint8_t addr)
+{
+ if (offset > s->cfg.num_addressable_devices) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Device char table offset %d out of "
+ "bounds\n", path, offset);
+ /* If we're out of bounds, do nothing. */
+ return;
+ }
+
+ /*
+ * Each device offset is 128 bits apart in the table, since each device gets
+ * 4 * 32-bits of entries in the table.
+ * / sizeof(uint32_t) because we're indexing into our 32-bit reg array.
+ */
+ uint16_t dev_index = (ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
+ P_DEV_CHAR_TABLE_START_ADDR) /
+ sizeof(uint32_t)) +
+ (offset * sizeof(uint32_t));
+ s->regs[dev_index] = pid & 0xffffffff;
+ pid >>= 32;
+ s->regs[dev_index + 1] = FIELD_DP32(s->regs[dev_index + 1],
+ DEVICE_CHARACTERISTIC_TABLE_LOC2,
+ MSB_PID, pid);
+ s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2],
+ DEVICE_CHARACTERISTIC_TABLE_LOC3, DCR,
+ dcr);
+ s->regs[dev_index + 2] = FIELD_DP32(s->regs[dev_index + 2],
+ DEVICE_CHARACTERISTIC_TABLE_LOC3, BCR,
+ bcr);
+ s->regs[dev_index + 3] = FIELD_DP32(s->regs[dev_index + 3],
+ DEVICE_CHARACTERISTIC_TABLE_LOC4,
+ DEV_DYNAMIC_ADDR, addr);
+
+ /* Increment PRESENT_DEV_CHAR_TABLE_INDEX. */
+ uint8_t idx = ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
+ PRESENT_DEV_CHAR_TABLE_INDEX);
+ /* Increment and rollover. */
+ idx++;
+ if (idx >= ARRAY_FIELD_EX32(s->regs, DEV_CHAR_TABLE_POINTER,
+ DEV_CHAR_TABLE_DEPTH) / 4) {
+ idx = 0;
+ }
+ ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
+ PRESENT_DEV_CHAR_TABLE_INDEX, idx);
+}
+
+static void dw_i3c_addr_assign_cmd(DWI3C *s, DWI3CAddrAssignCmd cmd)
+{
+ uint8_t i = 0;
+ uint8_t err = DW_I3C_RESP_QUEUE_ERR_NONE;
+
+ /* Tell everyone to ENTDAA. If these error, no one is on the bus. */
+ if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
+ /*is_i2c=*/false)) {
+ err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+ goto transfer_done;
+ }
+ if (dw_i3c_send_byte(s, cmd.cmd, /*is_i2c=*/false)) {
+ err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+ goto transfer_done;
+ }
+
+ /* Go through each device in the table and assign it an address. */
+ for (i = 0; i < cmd.dev_count; i++) {
+ uint8_t addr = dw_i3c_target_addr(s, cmd.dev_index + i);
+ union {
+ uint64_t pid:48;
+ uint8_t bcr;
+ uint8_t dcr;
+ uint32_t w[2];
+ uint8_t b[8];
+ } target_info;
+
+ /* If this fails, there was no one left to ENTDAA. */
+ if (dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
+ /*is_i2c=*/false)) {
+ err = DW_I3C_RESP_QUEUE_ERR_BROADCAST_NACK;
+ break;
+ }
+
+ /*
+ * In ENTDAA, we read 8 bytes from the target, which will be the
+ * target's PID, BCR, and DCR. After that, we send it the dynamic
+ * address.
+ * Don't bother checking the number of bytes received, it must send 8
+ * bytes during ENTDAA.
+ */
+ uint32_t num_read;
+ if (dw_i3c_recv_data(s, /*is_i2c=*/false, target_info.b,
+ I3C_ENTDAA_SIZE, &num_read)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed ENTDAA CCC\n",
+ path);
+ err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK;
+ goto transfer_done;
+ }
+ if (dw_i3c_send_byte(s, addr, /*is_i2c=*/false)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Target NACKed addr 0x%.2x "
+ "during ENTDAA\n", path, addr);
+ err = DW_I3C_RESP_QUEUE_ERR_DAA_NACK;
+ break;
+ }
+ dw_i3c_update_char_table(s, cmd.dev_index + i,
+ target_info.pid, target_info.bcr,
+ target_info.dcr, addr);
+
+ /* Push the PID, BCR, and DCR to the RX queue. */
+ dw_i3c_push_rx(s, target_info.w[0]);
+ dw_i3c_push_rx(s, target_info.w[1]);
+ }
+
+transfer_done:
+ /* Do we send a STOP? */
+ if (cmd.toc) {
+ dw_i3c_end_transfer(s, /*is_i2c=*/false);
+ }
+ /*
+ * For addr assign commands, the length field is the number of devices
+ * left to assign. CCCT is always 0 in controller mode.
+ */
+ if (cmd.roc) {
+ dw_i3c_resp_queue_push(s, err, cmd.tid, /*ccc_type=*/0,
+ cmd.dev_count - i);
+ }
+}
+
+static uint32_t dw_i3c_cmd_queue_pop(DWI3C *s)
+{
+ if (fifo32_is_empty(&s->cmd_queue)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Tried to dequeue command queue "
+ "when it was empty\n", path);
+ return 0;
+ }
+ uint32_t val = fifo32_pop(&s->cmd_queue);
+
+ uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+ CMD_BUF_EMPTY_THLD);
+ uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs,
+ QUEUE_STATUS_LEVEL,
+ CMD_QUEUE_EMPTY_LOC);
+ cmd_queue_empty_loc++;
+ ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC,
+ cmd_queue_empty_loc);
+ if (cmd_queue_empty_loc >= empty_threshold) {
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 1);
+ dw_i3c_update_irq(s);
+ }
+
+ return val;
+}
+
+static void dw_i3c_cmd_queue_execute(DWI3C *s)
+{
+ ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
+ DW_I3C_TRANSFER_STATE_IDLE);
+ if (!dw_i3c_can_transmit(s)) {
+ return;
+ }
+
+ /*
+ * We only start executing when a command is passed into the FIFO.
+ * We expect there to be a multiple of 2 items in the queue. The first item
+ * should be an argument to a command, and the command should be the second
+ * item.
+ */
+ if (fifo32_num_used(&s->cmd_queue) & 1) {
+ return;
+ }
+
+ while (!fifo32_is_empty(&s->cmd_queue)) {
+ DWI3CCmdQueueData arg;
+ arg.word = dw_i3c_cmd_queue_pop(s);
+ DWI3CCmdQueueData cmd;
+ cmd.word = dw_i3c_cmd_queue_pop(s);
+ trace_dw_i3c_cmd_queue_execute(s->cfg.id, cmd.word, arg.word);
+
+ uint8_t cmd_attr = FIELD_EX32(cmd.word, COMMAND_QUEUE_PORT, CMD_ATTR);
+ switch (cmd_attr) {
+ case DW_I3C_CMD_ATTR_TRANSFER_CMD:
+ dw_i3c_transfer_cmd(s, cmd.transfer_cmd, arg);
+ break;
+ case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD:
+ /* Arg is discarded for addr assign commands. */
+ dw_i3c_addr_assign_cmd(s, cmd.addr_assign_cmd);
+ break;
+ case DW_I3C_CMD_ATTR_TRANSFER_ARG:
+ case DW_I3C_CMD_ATTR_SHORT_DATA_ARG:
+ {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received "
+ "argument packet when it expected a command "
+ "packet\n", path);
+ }
+ break;
+ default:
+ /*
+ * The caller's check before queueing an item should prevent this
+ * from happening.
+ */
+ g_assert_not_reached();
+ break;
+ }
+ }
+}
+
+static void dw_i3c_cmd_queue_push(DWI3C *s, uint32_t val)
+{
+ if (fifo32_is_full(&s->cmd_queue)) {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received packet when "
+ "already full\n", path);
+ return;
+ }
+ trace_dw_i3c_cmd_queue_push(s->cfg.id, val);
+ fifo32_push(&s->cmd_queue, val);
+
+ uint8_t empty_threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
+ CMD_BUF_EMPTY_THLD);
+ uint8_t cmd_queue_empty_loc = ARRAY_FIELD_EX32(s->regs,
+ QUEUE_STATUS_LEVEL,
+ CMD_QUEUE_EMPTY_LOC);
+ if (cmd_queue_empty_loc) {
+ cmd_queue_empty_loc--;
+ ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, CMD_QUEUE_EMPTY_LOC,
+ cmd_queue_empty_loc);
+ }
+ if (cmd_queue_empty_loc < empty_threshold) {
+ ARRAY_FIELD_DP32(s->regs, INTR_STATUS, CMD_QUEUE_RDY, 0);
+ dw_i3c_update_irq(s);
+ }
+}
+
+static void dw_i3c_cmd_queue_port_w(DWI3C *s, uint32_t val)
+{
+ uint8_t cmd_attr = FIELD_EX32(val, COMMAND_QUEUE_PORT, CMD_ATTR);
+
+ switch (cmd_attr) {
+ /* If a command is received we can start executing it. */
+ case DW_I3C_CMD_ATTR_TRANSFER_CMD:
+ case DW_I3C_CMD_ATTR_ADDR_ASSIGN_CMD:
+ dw_i3c_cmd_queue_push(s, val);
+ dw_i3c_cmd_queue_execute(s);
+ break;
+ /* If we get an argument just push it. */
+ case DW_I3C_CMD_ATTR_TRANSFER_ARG:
+ case DW_I3C_CMD_ATTR_SHORT_DATA_ARG:
+ dw_i3c_cmd_queue_push(s, val);
+ break;
+ default:
+ {
+ g_autofree char *path = object_get_canonical_path(OBJECT(s));
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Command queue received packet "
+ "with unknown cmd attr 0x%x\n", path, cmd_attr);
+ }
+ break;
+ }
+}
+
static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
@@ -409,7 +1245,7 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
uint32_t addr = offset >> 2;
uint32_t val32 = (uint32_t)value;

- trace_dw_i3c_write(s->id, offset, value);
+ trace_dw_i3c_write(s->cfg.id, offset, value);

val32 &= ~dw_i3c_ro[addr];
switch (addr) {
@@ -433,6 +1269,10 @@ static void dw_i3c_write(void *opaque, hwaddr offset, uint64_t value,
__func__, offset, value);
break;
case R_RX_TX_DATA_PORT:
+ dw_i3c_push_tx(s, val32);
+ break;
+ case R_COMMAND_QUEUE_PORT:
+ dw_i3c_cmd_queue_port_w(s, val32);
break;
case R_RESET_CTRL:
break;
@@ -475,22 +1315,56 @@ static void dw_i3c_reset_enter(Object *obj, ResetType type)
DWI3C *s = DW_I3C(obj);

memcpy(s->regs, dw_i3c_resets, sizeof(s->regs));
+ /*
+ * The user config for these may differ from our resets array, set them
+ * manually.
+ */
+ ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, ADDR,
+ s->cfg.dev_addr_table_pointer);
+ ARRAY_FIELD_DP32(s->regs, DEVICE_ADDR_TABLE_POINTER, DEPTH,
+ s->cfg.dev_addr_table_depth);
+ ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER,
+ P_DEV_CHAR_TABLE_START_ADDR,
+ s->cfg.dev_char_table_pointer);
+ ARRAY_FIELD_DP32(s->regs, DEV_CHAR_TABLE_POINTER, DEV_CHAR_TABLE_DEPTH,
+ s->cfg.dev_char_table_depth);
}

static void dw_i3c_realize(DeviceState *dev, Error **errp)
{
DWI3C *s = DW_I3C(dev);
- g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->id);
+ g_autofree char *name = g_strdup_printf(TYPE_DW_I3C ".%d", s->cfg.id);

sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);

memory_region_init_io(&s->mr, OBJECT(s), &dw_i3c_ops, s, name,
DW_I3C_NR_REGS << 2);
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr);
+
+ fifo32_create(&s->cmd_queue, s->cfg.cmd_resp_queue_capacity_bytes);
+ fifo32_create(&s->resp_queue, s->cfg.cmd_resp_queue_capacity_bytes);
+ fifo32_create(&s->tx_queue, s->cfg.tx_rx_queue_capacity_bytes);
+ fifo32_create(&s->rx_queue, s->cfg.tx_rx_queue_capacity_bytes);
+
+ s->bus = i3c_init_bus(DEVICE(s), name);
}

static const Property dw_i3c_properties[] = {
- DEFINE_PROP_UINT8("device-id", DWI3C, id, 0),
+ DEFINE_PROP_UINT8("device-id", DWI3C, cfg.id, 0),
+ DEFINE_PROP_UINT8("command-response-queue-capacity-bytes", DWI3C,
+ cfg.cmd_resp_queue_capacity_bytes, 0x10),
+ DEFINE_PROP_UINT16("tx-rx-queue-capacity-bytes", DWI3C,
+ cfg.tx_rx_queue_capacity_bytes, 0x40),
+ DEFINE_PROP_UINT8("num-addressable-devices", DWI3C,
+ cfg.num_addressable_devices, 8),
+ DEFINE_PROP_UINT16("dev-addr-table-pointer", DWI3C,
+ cfg.dev_addr_table_pointer, 0x280),
+ DEFINE_PROP_UINT16("dev-addr-table-depth", DWI3C,
+ cfg.dev_addr_table_depth, 0x08),
+ DEFINE_PROP_UINT16("dev-char-table-pointer", DWI3C,
+ cfg.dev_char_table_pointer, 0x200),
+ DEFINE_PROP_UINT16("dev-char-table-depth", DWI3C,
+ cfg.dev_char_table_depth, 0x20),
};

static void dw_i3c_class_init(ObjectClass *klass, const void *data)
diff --git a/hw/i3c/trace-events b/hw/i3c/trace-events
index 2d944387db..044ff06a01 100644
--- a/hw/i3c/trace-events
+++ b/hw/i3c/trace-events
@@ -7,6 +7,16 @@ aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%" PRIx64
# dw-i3c,c
dw_i3c_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
dw_i3c_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
+dw_i3c_send(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] send %" PRId32 " bytes to bus"
+dw_i3c_recv_data(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] recv %" PRId32 " bytes from bus"
+dw_i3c_pop_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from RX FIFO"
+dw_i3c_resp_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to response queue"
+dw_i3c_push_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to TX FIFO"
+dw_i3c_pop_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from TX FIFO"
+dw_i3c_push_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to RX FIFO"
+dw_i3c_transfer_ccc(uint32_t deviceid, uint8_t ccc) "I3C Dev[%u] transfer CCC 0x%" PRIx8
+dw_i3c_cmd_queue_execute(uint32_t deviceid, uint32_t cmd, uint32_t arg) "I3C Dev[%u] execute command 0x%" PRIx32 " arg 0x%" PRIx32
+dw_i3c_cmd_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to cmd queue"

# core.c
i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%" PRIx8 " event 0x%" PRIx8
--
2.43.0