Hi Mark

> Subject: Re: [PATCH v4 02/20] hw/i3c: Add bus support
> 
> On 09/02/2026 09:16, Jamin Lin wrote:
> 
> > Adds an I3C bus and a target class.
> > The bus supports:
> > - I3C data transmission and reception
> > - CCCs (including ENTDAA)
> > - IBIs
> > - legacy I2C transactions
> >
> > General usage of the bus is similar to I2C. Users are expected to
> > initialize a bus via i3c_init_bus, and use the bus returned from the
> > init function to do transactions on the bus.
> >
> > In order to handle IBIs, the controller provides callbacks to handle
> > receiving an IBI from a target, receiving (optional) additional IBI
> > bytes from a target, and handling when a target is done with its IBI.
> >
> > Similarly, target creation is done via i3c_target_create_simple and
> > users use the provided I3CTarget to handle transactions.
> > The target has functions provided that it can use to invoke an IBI and
> > send additional bytes.
> >
> > Along with the send, recv, and event callbacks that are expected of an
> > I3C target, which are similar to I2C, there is a separate callback for
> > CCC handling.
> > This is to help encapsulate CCC handling and keep it separate from
> > target-specific read/write functionality.
> >
> > To avoid repition for required CCCs among I3C targets, there is some
> > class-level CCC handling added. The CCC is then passed to the target
> > in case it needs to handle it in some way.
> >
> > Signed-off-by: Joe Komlodi <[email protected]>
> > Reviewed-by: Patrick Venture <[email protected]>
> > Reviewed-by: Titus Rwantare <[email protected]>
> > Reviewed-by: Jamin Lin <[email protected]>
> > Signed-off-by: Jamin Lin <[email protected]>
> > ---
> >   include/hw/i3c/i3c.h | 277 ++++++++++++++++++
> >   hw/i3c/core.c        | 652
> +++++++++++++++++++++++++++++++++++++++++++
> >   hw/i3c/meson.build   |   1 +
> >   hw/i3c/trace-events  |  16 ++
> >   4 files changed, 946 insertions(+)
> >   create mode 100644 include/hw/i3c/i3c.h
> >   create mode 100644 hw/i3c/core.c
> >
> > diff --git a/include/hw/i3c/i3c.h b/include/hw/i3c/i3c.h new file mode
> > 100644 index 0000000000..455c2a4a4f
> > --- /dev/null
> > +++ b/include/hw/i3c/i3c.h
> > @@ -0,0 +1,277 @@
> > +/*
> > + * QEMU I3C bus interface.
> > + *
> > + * Copyright 2025 Google LLC
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later  */
> > +
> > +#ifndef QEMU_INCLUDE_HW_I3C_I3C_H_
> > +#define QEMU_INCLUDE_HW_I3C_I3C_H_
> > +
> > +#include "hw/core/qdev.h"
> > +#include "qom/object.h"
> > +#include "hw/i2c/i2c.h"
> > +
> > +#define TYPE_I3C_TARGET "i3c-target"
> > +OBJECT_DECLARE_TYPE(I3CTarget, I3CTargetClass, I3C_TARGET)
> > +
> > +typedef enum I3CEvent {
> > +    I3C_START_RECV,
> > +    I3C_START_SEND,
> > +    I3C_STOP,
> > +    I3C_NACK,
> > +} I3CEvent;
> > +
> > +typedef enum I3CCCC {
> > +    /* Broadcast CCCs */
> > +    I3C_CCC_ENEC      = 0x00,
> > +    I3C_CCC_DISEC     = 0x01,
> > +    I3C_CCC_ENTAS0    = 0x02,
> > +    I3C_CCC_ENTAS1    = 0x03,
> > +    I3C_CCC_ENTAS2    = 0x04,
> > +    I3C_CCC_ENTAS3    = 0x05,
> > +    I3C_CCC_RSTDAA    = 0x06,
> > +    I3C_CCC_ENTDAA    = 0x07,
> > +    I3C_CCC_DEFTGTS   = 0x08,
> > +    I3C_CCC_SETMWL    = 0x09,
> > +    I3C_CCC_SETMRL    = 0x0a,
> > +    I3C_CCC_ENTTM     = 0x0b,
> > +    I3C_CCC_SETBUSCON = 0x0c,
> > +    I3C_CCC_ENDXFER   = 0x12,
> > +    I3C_CCC_ENTHDR0   = 0x20,
> > +    I3C_CCC_ENTHDR1   = 0x21,
> > +    I3C_CCC_ENTHDR2   = 0x22,
> > +    I3C_CCC_ENTHDR3   = 0x23,
> > +    I3C_CCC_ENTHDR4   = 0x24,
> > +    I3C_CCC_ENTHDR5   = 0x25,
> > +    I3C_CCC_ENTHDR6   = 0x26,
> > +    I3C_CCC_ENTHDR7   = 0x27,
> > +    I3C_CCC_SETXTIME  = 0x28,
> > +    I3C_CCC_SETAASA   = 0x29,
> > +    I3C_CCC_RSTACT    = 0x2a,
> > +    I3C_CCC_DEFGRPA   = 0x2b,
> > +    I3C_CCC_RSTGRPA   = 0x2c,
> > +    I3C_CCC_MLANE     = 0x2d,
> > +    /* Direct CCCs */
> > +    I3C_CCCD_ENEC       = 0x80,
> > +    I3C_CCCD_DISEC      = 0x81,
> > +    I3C_CCCD_ENTAS0     = 0x82,
> > +    I3C_CCCD_ENTAS1     = 0x83,
> > +    I3C_CCCD_ENTAS2     = 0x84,
> > +    I3C_CCCD_ENTAS3     = 0x85,
> > +    I3C_CCCD_SETDASA    = 0x87,
> > +    I3C_CCCD_SETNEWDA   = 0x88,
> > +    I3C_CCCD_SETMWL     = 0x89,
> > +    I3C_CCCD_SETMRL     = 0x8a,
> > +    I3C_CCCD_GETMWL     = 0x8b,
> > +    I3C_CCCD_GETMRL     = 0x8c,
> > +    I3C_CCCD_GETPID     = 0x8d,
> > +    I3C_CCCD_GETBCR     = 0x8e,
> > +    I3C_CCCD_GETDCR     = 0x8f,
> > +    I3C_CCCD_GETSTATUS  = 0x90,
> > +    I3C_CCCD_GETACCCR   = 0x91,
> > +    I3C_CCCD_ENDXFER    = 0x92,
> > +    I3C_CCCD_SETBRGTGT  = 0x93,
> > +    I3C_CCCD_GETMXDS    = 0x94,
> > +    I3C_CCCD_GETCAPS    = 0x95,
> > +    I3C_CCCD_SETROUTE   = 0x96,
> > +    I3C_CCCD_SETXTIME   = 0x98,
> > +    I3C_CCCD_GETXTIME   = 0x99,
> > +    I3C_CCCD_RSTACT     = 0x9a,
> > +    I3C_CCCD_SETGRPA    = 0x9b,
> > +    I3C_CCCD_RSTGRPA    = 0x9c,
> > +    I3C_CCCD_MLANE      = 0x9d,
> > +} I3CCCC;
> > +
> > +#define CCC_IS_DIRECT(_ccc) (_ccc & 0x80)
> > +
> > +#define I3C_BROADCAST 0x7e
> > +#define I3C_HJ_ADDR 0x02
> > +#define I3C_ENTDAA_SIZE 8
> > +
> > +struct I3CTargetClass {
> > +    DeviceClass parent;
> 
> This should be parent_class as per our style guidelines as
> https://qemu.readthedocs.io/en/v10.0.3/devel/style.html#qemu-specific-idiom
> s.
> 
Thanks for the review and suggestion.
Will fix it.
> > +    /*
> > +     * Controller to target. Returns 0 for success, non-zero for NAK or
> other
> > +     * error.
> > +     */
> > +    int (*send)(I3CTarget *s, const uint8_t *data, uint32_t num_to_send,
> > +                uint32_t *num_sent);
> > +    /*
> > +     * Target to controller. I3C targets are able to terminate reads 
> > early, so
> > +     * this returns the number of bytes read from the target.
> > +     */
> > +    uint32_t (*recv)(I3CTarget *s, uint8_t *data, uint32_t num_to_read);
> > +    /* Notify the target of a bus state change. */
> > +    int (*event)(I3CTarget *s, enum I3CEvent event);
> > +    /*
> > +     * Handle a read CCC transmitted from a controller.
> > +     * CCCs are I3C commands that I3C targets support.
> > +     * The target can NACK the CCC if it does not support it.
> > +     */
> > +    int (*handle_ccc_read)(I3CTarget *s, uint8_t *data, uint32_t
> num_to_read,
> > +                           uint32_t *num_read);
> > +    /*
> > +     * Handle a write CCC transmitted from a controller.
> > +     * CCCs are I3C commands that I3C targets support.
> > +     * The target can NACK the CCC if it does not support it.
> > +     */
> > +    int (*handle_ccc_write)(I3CTarget *s, const uint8_t *data,
> > +                            uint32_t num_to_send, uint32_t
> > + *num_sent);
> > +
> > +    /*
> > +     * Matches and adds the candidate if the address matches the
> candidate's
> > +     * address.
> > +     * Returns true if the address matched, or if this was a broadcast, and
> > +     * updates the device list. Otherwise returns false.
> > +     */
> > +    bool (*target_match)(I3CTarget *candidate, uint8_t address, bool
> is_read,
> > +                         bool broadcast, bool in_entdaa); };
> > +
> > +struct I3CTarget {
> > +    DeviceState qdev;
> 
> And this should be parent_obj for the same reason.
> 
Will fix it.
> > +    uint8_t address;
> > +    uint8_t static_address;
> > +    uint8_t dcr;
> > +    uint8_t bcr;
> > +    uint64_t pid;
> > +
> > +    /* CCC State tracking. */
> > +    I3CCCC curr_ccc;
> > +    uint8_t ccc_byte_offset;
> > +    bool in_ccc;
> > +    bool in_test_mode;
> > +};
> > +
> > +struct I3CNode {
> > +    I3CTarget *target;
> > +    QLIST_ENTRY(I3CNode) next;
> > +};
> > +
> > +typedef struct I3CNode I3CNode;
> > +
> > +typedef QLIST_HEAD(I3CNodeList, I3CNode) I3CNodeList;
> > +
> > +#define TYPE_I3C_BUS "i3c-bus"
> > +OBJECT_DECLARE_TYPE(I3CBus, I3CBusClass, I3C_BUS)
> > +
> > +struct I3CBus {
> > +    BusState qbus;
> 
> This should be parent_obj.
> 
Will fix it
> > +
> > +    /* Legacy I2C. */
> > +    I2CBus *i2c_bus;
> > +
> > +    I3CNodeList current_devs;
> > +    bool broadcast;
> > +    uint8_t ccc;
> > +    bool in_ccc;
> > +    bool in_entdaa;
> > +    uint8_t saved_address;
> > +};
> > +
> > +struct I3CBusClass {
> > +    DeviceClass parent;
> 
> This should be parent_class.
> 
Will fix it.
Additionally, it appears that the data type should be BusClass, and I will 
update it accordingly.

> > +    /* Handle an incoming IBI request from a target */
> > +    int (*ibi_handle) (I3CBus *bus, uint8_t addr, bool is_recv);
> > +    /* Receive data from an IBI request */
> > +    int (*ibi_recv) (I3CBus *bus, uint8_t data);
> > +    /* Do anything that needs to be done, since the IBI is finished. */
> > +    int (*ibi_finish) (I3CBus *bus);
> > +};
> > +
> > +I3CBus *i3c_init_bus(DeviceState *parent, const char *name); I3CBus
> > +*i3c_init_bus_type(const char *type, DeviceState *parent,
> > +                          const char *name); void
> > +i3c_set_target_address(I3CTarget *dev, uint8_t address); bool
> > +i3c_bus_busy(I3CBus *bus);
> > +
> > +/*
> > + * Start a transfer on an I3C bus.
> > + * If is_recv is known at compile-time (i.e. a device will always be
> > +sending or
> > + * will always be receiving at a certain point), prefer to use
> > +i3c_start_recv or
> > + * i3c_start_send instead.
> > + *
> > + * Returns 0 on success, non-zero on an error.
> > + */
> > +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv);
> > +
> > +/*
> > + * Start a receive transfer on an I3C bus.
> > + *
> > + * Returns 0 on success, non-zero on an error  */ int
> > +i3c_start_recv(I3CBus *bus, uint8_t address);
> > +
> > +/*
> > + * Start a send transfer on an I3C bus.
> > + *
> > + * Returns 0 on success, non-zero on an error  */ int
> > +i3c_start_send(I3CBus *bus, uint8_t address);
> > +
> > +void i3c_end_transfer(I3CBus *bus);
> > +void i3c_nack(I3CBus *bus);
> > +int i3c_send_byte(I3CBus *bus, uint8_t data); int i3c_send(I3CBus
> > +*bus, const uint8_t *data, uint32_t num_to_send,
> > +             uint32_t *num_sent);
> > +/*
> > + * I3C receives can only NACK on a CCC. The target should NACK a CCC
> > +it does not
> > + * support.
> > + */
> > +int i3c_recv_byte(I3CBus *bus, uint8_t *data); int i3c_recv(I3CBus
> > +*bus, uint8_t *data, uint32_t num_to_read,
> > +             uint32_t *num_read);
> > +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event);
> > +int i3c_do_entdaa(I3CBus *bus, uint8_t address, uint64_t *pid, uint8_t 
> > *bcr,
> > +                  uint8_t *dcr);
> > +int i3c_start_device_transfer(I3CTarget *dev, int send_length); bool
> > +i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t address,
> > +                             enum I3CEvent event); int
> > +i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv); int
> > +i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data); int
> > +i3c_target_ibi_finish(I3CTarget *t, uint8_t data);
> > +
> > +/*
> > + * Legacy I2C functions.
> > + *
> > + * These are wrapper for I2C functions that take in an I3C bus
> > +instead of an I2C
> > + * bus. Internally they use the I2C bus (and devices attached to it)
> > +that's a
> > + * part of the I3C bus
> > + */
> > +void legacy_i2c_nack(I3CBus *bus);
> > +uint8_t legacy_i2c_recv(I3CBus *bus); int legacy_i2c_send(I3CBus
> > +*bus, uint8_t data); int legacy_i2c_start_transfer(I3CBus *bus,
> > +uint8_t address, bool is_recv); int legacy_i2c_start_recv(I3CBus
> > +*bus, uint8_t address); int legacy_i2c_start_send(I3CBus *bus,
> > +uint8_t address); void legacy_i2c_end_transfer(I3CBus *bus); I2CSlave
> > +*legacy_i2c_device_create_simple(I3CBus *bus, const char *name,
> > +                                          uint8_t addr);
> > +
> > +/**
> > + * Create an I3C Target.
> > + *
> > + * The target returned from this function still needs to be realized.
> > + */
> > +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
> > +                          uint8_t bcr, uint64_t pid);
> > +
> > +/**
> > + * Create and realize an I3C target.
> > + *
> > + * Create the target, initialize it, put it on the specified I3C bus,
> > +and
> > + * realize it.
> > + */
> > +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name,
> > +                                    uint8_t addr, uint8_t dcr,
> uint8_t bcr,
> > +                                    uint64_t pid);
> > +
> > +/* Realize and drop the reference count on an I3C target. */ bool
> > +i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error
> > +**errp);
> > +
> > +#endif  /* QEMU_INCLUDE_HW_I3C_I3C_H_ */
> > diff --git a/hw/i3c/core.c b/hw/i3c/core.c new file mode 100644 index
> > 0000000000..fb4bfe5aec
> > --- /dev/null
> > +++ b/hw/i3c/core.c
> > @@ -0,0 +1,652 @@
> > +/*
> > + * QEMU I3C bus interface.
> > + *
> > + * Copyright 2025 Google LLC
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later  */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qemu/log.h"
> > +#include "qapi/error.h"
> > +#include "trace.h"
> > +#include "hw/i3c/i3c.h"
> > +#include "hw/core/qdev-properties.h"
> > +
> > +/*
> > + * In test mode (enabled by ENTTM CCC) we're supposed to send a
> > +random PID
> > + * during ENTDAA, so we'll just send "QEMU".
> > + */
> > +#define TEST_MODE_PROVISIONED_ID 0x0000554d4551ULL
> > +
> > +static const Property i3c_props[] = {
> > +    DEFINE_PROP_UINT8("static-address", struct I3CTarget, static_address,
> 0),
> > +    DEFINE_PROP_UINT8("dcr", struct I3CTarget, dcr, 0),
> > +    DEFINE_PROP_UINT8("bcr", struct I3CTarget, bcr, 0),
> > +    DEFINE_PROP_UINT64("pid", struct I3CTarget, pid, 0), };
> > +
> > +static const TypeInfo i3c_bus_info = {
> > +    .name = TYPE_I3C_BUS,
> > +    .parent = TYPE_BUS,
> > +    .instance_size = sizeof(I3CBus),
> > +    .class_size = sizeof(I3CBusClass), };
> > +
> > +I3CBus *i3c_init_bus(DeviceState *parent, const char *name) {
> > +    return i3c_init_bus_type(TYPE_I3C_BUS, parent, name); }
> > +
> > +I3CBus *i3c_init_bus_type(const char *type, DeviceState *parent,
> > +                          const char *name) {
> > +    I3CBus *bus;
> > +
> > +    bus = I3C_BUS(qbus_new(type, parent, name));
> > +    QLIST_INIT(&bus->current_devs);
> > +    bus->broadcast = false;
> > +    bus->in_entdaa = false;
> > +    bus->in_ccc = false;
> > +
> > +    /* I2C init. */
> > +    g_autofree gchar *i2c_bus_name = g_strdup_printf("%s-legacy-i2c",
> name);
> > +    bus->i2c_bus = i2c_init_bus(parent, i2c_bus_name);
> > +
> > +    return bus;
> > +}
> > +
> > +bool i3c_bus_busy(I3CBus *bus)
> > +{
> > +    return !QLIST_EMPTY(&bus->current_devs); }
> > +
> > +static bool i3c_target_match(I3CTarget *candidate, uint8_t address,
> > +                             bool is_recv, bool broadcast, bool
> > +in_entdaa) {
> > +    /* Once a target has a dynamic address, it only responds to that. */
> > +    uint8_t targ_addr = candidate->address ? candidate->address :
> > +
> > +candidate->static_address;
> > +
> > +    if (in_entdaa) {
> > +        if (address != I3C_BROADCAST) {
> > +            g_autofree char *path =
> > +                object_get_canonical_path(OBJECT(candidate));
> > +            qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C Address
> 0x%.2x sent during "
> > +                          "ENTDAA instead of a broadcast address\n",
> > +                          path, address);
> > +            return false;
> > +        }
> > +
> > +        /*
> > +         * Targets should only ACK ENTDAA broadcasts if they have no
> dynamic
> > +         * address.
> > +         */
> > +        return candidate->address == 0;
> > +    }
> > +
> > +    /* Return if our addresses match, or if it's a broadcast. */
> > +    return targ_addr == address || broadcast; }
> > +
> > +bool i3c_target_match_and_add(I3CBus *bus, I3CTarget *target, uint8_t
> address,
> > +                              enum I3CEvent event) {
> > +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(target);
> > +    bool matched = tc->target_match(target, address, event ==
> I3C_START_RECV,
> > +                                    bus->broadcast,
> bus->in_entdaa);
> > +
> > +    if (matched) {
> > +        I3CNode *node = g_new(struct I3CNode, 1);
> > +        node->target = target;
> > +        QLIST_INSERT_HEAD(&bus->current_devs, node, next);
> > +    }
> > +    return matched;
> > +}
> > +
> > +bool i3c_scan_bus(I3CBus *bus, uint8_t address, enum I3CEvent event)
> > +{
> > +    BusChild *child;
> > +    I3CNode *node, *next;
> > +
> > +    /* Clear out any devices from a previous (re-)START. */
> > +    QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
> > +        QLIST_REMOVE(node, next);
> > +        g_free(node);
> > +    }
> > +
> > +    QTAILQ_FOREACH(child, &bus->qbus.children, sibling) {
> > +        DeviceState *qdev = child->child;
> > +        I3CTarget *target = I3C_TARGET(qdev);
> > +
> > +        if (i3c_target_match_and_add(bus, target, address, event)) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    /* No one on the bus could respond. */
> > +    return false;
> > +}
> > +
> > +/* Class-level event handling, since we do some CCCs at the class
> > +level. */ static int i3c_target_event(I3CTarget *t, enum I3CEvent
> > +event) {
> > +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> > +    trace_i3c_target_event(t->address, event);
> > +
> > +    if (event == I3C_STOP) {
> > +        t->curr_ccc = 0;
> > +        t->ccc_byte_offset = 0;
> > +        t->in_ccc = false;
> > +    }
> > +    return tc->event(t, event);
> > +}
> > +
> > +/*
> > + * Sends a START or repeated START and the address for an I3C transaction.
> > + *
> > + * This function returns 0 if a device on the bus was able to respond
> > +to the
> > + * address, and non-zero otherwise.
> > + * A non-zero return represents a NACK.
> > + */
> > +static int i3c_do_start_transfer(I3CBus *bus, uint8_t address,
> > +                                 enum I3CEvent event) {
> > +    I3CTargetClass *tc;
> > +    I3CNode *node;
> > +
> > +    if (address == I3C_BROADCAST) {
> > +        bus->broadcast = true;
> > +        /* If we're not in ENTDAA, a broadcast is the start of a new CCC.
> */
> > +        if (!bus->in_entdaa) {
> > +            bus->in_ccc = false;
> > +        }
> > +    } else {
> > +        bus->broadcast = false;
> > +    }
> > +
> > +    /* No one responded to the address, NACK it. */
> > +    if (!i3c_scan_bus(bus, address, event)) {
> > +        return -1;
> > +    }
> > +
> > +    QLIST_FOREACH(node, &bus->current_devs, next) {
> > +        I3CTarget *t = node->target;
> > +
> > +        tc = I3C_TARGET_GET_CLASS(t);
> > +        if (tc->event) {
> > +            int rv = i3c_target_event(t, event);
> > +            if (rv && !bus->broadcast) {
> > +                return rv;
> > +            }
> > +        }
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +int i3c_start_transfer(I3CBus *bus, uint8_t address, bool is_recv) {
> > +    trace_i3c_start_transfer(address, is_recv);
> > +    return i3c_do_start_transfer(bus, address, is_recv
> > +                                               ?
> I3C_START_RECV
> > +                                               :
> I3C_START_SEND); }
> > +
> > +int i3c_start_recv(I3CBus *bus, uint8_t address) {
> > +    trace_i3c_start_transfer(address, true);
> > +    return i3c_do_start_transfer(bus, address, I3C_START_RECV); }
> > +
> > +int i3c_start_send(I3CBus *bus, uint8_t address) {
> > +    trace_i3c_start_transfer(address, false);
> > +    return i3c_do_start_transfer(bus, address, I3C_START_SEND); }
> > +
> > +void i3c_end_transfer(I3CBus *bus)
> > +{
> > +    I3CTargetClass *tc;
> > +    I3CNode *node, *next;
> > +
> > +    trace_i3c_end_transfer();
> > +
> > +    /*
> > +     * If we're in ENTDAA, we need to notify all devices when ENTDAA is
> done.
> > +     * This is because everyone initially participates due to the 
> > broadcast,
> > +     * but gradually drops out as they get assigned addresses.
> > +     * Since the current_devs list only stores who's currently 
> > participating,
> > +     * and not everyone who previously participated, we send the STOP to
> all
> > +     * children.
> > +     */
> > +    if (bus->in_entdaa) {
> > +        BusChild *child;
> > +
> > +        QTAILQ_FOREACH(child, &bus->qbus.children, sibling) {
> > +            DeviceState *qdev = child->child;
> > +            I3CTarget *t = I3C_TARGET(qdev);
> > +            tc = I3C_TARGET_GET_CLASS(t);
> > +            if (tc->event) {
> > +                i3c_target_event(t, I3C_STOP);
> > +            }
> > +        }
> > +    } else {
> > +        QLIST_FOREACH_SAFE(node, &bus->current_devs, next, next) {
> > +            I3CTarget *t = node->target;
> > +            tc = I3C_TARGET_GET_CLASS(t);
> > +            if (tc->event) {
> > +                i3c_target_event(t, I3C_STOP);
> > +            }
> > +            QLIST_REMOVE(node, next);
> > +            g_free(node);
> > +        }
> > +    }
> > +    bus->broadcast = false;
> > +    bus->in_entdaa = false;
> > +    bus->in_ccc = false;
> > +}
> > +
> > +/*
> > + * Any CCCs that are universal across all I3C devices should be handled
> here.
> > + * Once they're handled, we pass the CCC up to the I3C target to do
> > +anything
> > + * else it may want with the bytes.
> > + */
> > +static int i3c_target_handle_ccc_write(I3CTarget *t, const uint8_t *data,
> > +                                       uint32_t num_to_send,
> uint32_t
> > +*num_sent) {
> > +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> > +    *num_sent = 0;
> > +
> > +    /* Is this the start of a new CCC? */
> > +    if (!t->in_ccc) {
> > +        t->curr_ccc = *data;
> > +        t->in_ccc = true;
> > +        *num_sent = 1;
> > +        trace_i3c_target_handle_ccc(t->address, t->curr_ccc);
> > +    }
> > +
> > +    switch (t->curr_ccc) {
> > +    case I3C_CCC_ENTDAA:
> > +        /*
> > +         * This is the last byte of ENTDAA, the controller is assigning us
> an
> > +         * address.
> > +         */
> > +        if (t->ccc_byte_offset == 8) {
> > +            t->address = *data;
> > +            t->in_ccc = false;
> > +            t->curr_ccc = 0;
> > +            t->ccc_byte_offset = 0;
> > +            *num_sent = 1;
> > +        }
> > +        break;
> > +    case I3C_CCCD_SETDASA:
> > +        t->address = t->static_address;
> > +        break;
> > +    case I3C_CCC_SETAASA:
> > +        t->address = t->static_address;
> > +        break;
> > +    case I3C_CCC_RSTDAA:
> > +        t->address = 0;
> > +        break;
> > +    case I3C_CCCD_SETNEWDA:
> > +        /* If this isn't the CCC byte, it's our new address. */
> > +        if (*num_sent == 0) {
> > +            t->address = *data;
> > +            *num_sent = 1;
> > +        }
> > +        break;
> > +    case I3C_CCC_ENTTM:
> > +        /*
> > +         * If there are still more to look at, the next byte is the test 
> > mode
> > +         * byte.
> > +         */
> > +        if (*num_sent != num_to_send) {
> > +            /* Enter test mode if the byte is non-zero. Otherwise exit. */
> > +            t->in_test_mode = !!data[*num_sent];
> > +            ++*num_sent;
> > +        }
> > +        break;
> > +    /* Ignore other CCCs it's better to handle on a device-by-device basis.
> */
> > +    default:
> > +        break;
> > +    }
> > +    return tc->handle_ccc_write(t, data, num_to_send, num_sent); }
> > +
> > +int i3c_send_byte(I3CBus *bus, uint8_t data) {
> > +    /*
> > +     * Ignored, the caller can determine how many were sent based on if
> this was
> > +     * ACKed/NACKed.
> > +     */
> > +    uint32_t num_sent;
> > +    return i3c_send(bus, &data, 1, &num_sent); }
> > +
> > +int i3c_send(I3CBus *bus, const uint8_t *data, uint32_t num_to_send,
> > +             uint32_t *num_sent)
> > +{
> > +    I3CTargetClass *tc;
> > +    I3CTarget *t;
> > +    I3CNode *node;
> > +    int ret = 0;
> > +
> > +    /* If this message is a broadcast and no CCC has been found, grab it. 
> > */
> > +    if (bus->broadcast && !bus->in_ccc) {
> > +        bus->ccc = *data;
> > +        bus->in_ccc = true;
> > +        /*
> > +         * We need to keep track if we're currently in ENTDAA.
> > +         * On any other CCC, the CCC is over on a RESTART or STOP, but
> ENTDAA
> > +         * is only over on a STOP.
> > +         */
> > +        if (bus->ccc == I3C_CCC_ENTDAA) {
> > +            bus->in_entdaa = true;
> > +        }
> > +    }
> > +
> > +    QLIST_FOREACH(node, &bus->current_devs, next) {
> > +        t = node->target;
> > +        tc = I3C_TARGET_GET_CLASS(t);
> > +        if (bus->in_ccc) {
> > +            if (!tc->handle_ccc_write) {
> > +                ret = -1;
> > +                continue;
> > +            }
> > +            ret = i3c_target_handle_ccc_write(t, data, num_to_send,
> num_sent);
> > +            /* Targets should only NACK on a direct CCC. */
> > +            if (ret && !CCC_IS_DIRECT(bus->ccc)) {
> > +                ret = 0;
> > +            }
> > +        } else {
> > +            if (tc->send) {
> > +                ret = ret || tc->send(t, data, num_to_send, num_sent);
> > +            } else {
> > +                ret = -1;
> > +            }
> > +        }
> > +    }
> > +
> > +    trace_i3c_send(*num_sent, num_to_send, ret == 0);
> > +
> > +    return ret ? -1 : 0;
> > +}
> > +
> > +static int i3c_target_handle_ccc_read(I3CTarget *t, uint8_t *data,
> > +                                      uint32_t num_to_read,
> uint32_t
> > +*num_read) {
> > +    I3CTargetClass *tc = I3C_TARGET_GET_CLASS(t);
> > +    uint8_t read_count = 0;
> > +    uint64_t pid;
> > +
> > +    switch (t->curr_ccc) {
> > +    case I3C_CCC_ENTDAA:
> > +        if (t->in_test_mode) {
> > +            pid = TEST_MODE_PROVISIONED_ID;
> > +        } else {
> > +            pid = t->pid;
> > +        }
> > +        /* Return the 6-byte PID, followed by BCR then DCR. */
> > +        while (t->ccc_byte_offset < 6) {
> > +            if (read_count >= num_to_read) {
> > +                break;
> > +            }
> > +            data[read_count] = (pid >> (t->ccc_byte_offset * 8)) & 0xff;
> > +            t->ccc_byte_offset++;
> > +            read_count++;
> > +        }
> > +        if (read_count < num_to_read) {
> > +            data[read_count] = t->bcr;
> > +            t->ccc_byte_offset++;
> > +            read_count++;
> > +        }
> > +        if (read_count < num_to_read) {
> > +            data[read_count] = t->dcr;
> > +            t->ccc_byte_offset++;
> > +            read_count++;
> > +        }
> > +        *num_read = read_count;
> > +        break;
> > +    case I3C_CCCD_GETPID:
> > +        while (t->ccc_byte_offset < 6) {
> > +            if (read_count >= num_to_read) {
> > +                break;
> > +            }
> > +            data[read_count] = (t->pid >> (t->ccc_byte_offset * 8)) &
> 0xff;
> > +            t->ccc_byte_offset++;
> > +            read_count++;
> > +        }
> > +        *num_read = read_count;
> > +        break;
> > +    case I3C_CCCD_GETBCR:
> > +        *data = t->bcr;
> > +        *num_read = 1;
> > +        break;
> > +    case I3C_CCCD_GETDCR:
> > +        *data = t->dcr;
> > +        *num_read = 1;
> > +        break;
> > +    default:
> > +        /* Unhandled on the I3CTarget class level. */
> > +        break;
> > +    }
> > +
> > +    return tc->handle_ccc_read(t, data, num_to_read, num_read); }
> > +
> > +int i3c_recv_byte(I3CBus *bus, uint8_t *data) {
> > +     /*
> > +      * Ignored, the caller can determine how many bytes were read
> based on if
> > +      * this is ACKed/NACKed.
> > +      */
> > +    uint32_t num_read;
> > +    return i3c_recv(bus, data, 1, &num_read); }
> > +
> > +int i3c_recv(I3CBus *bus, uint8_t *data, uint32_t num_to_read,
> > +             uint32_t *num_read)
> > +{
> > +    int ret = 0;
> > +    I3CTargetClass *tc;
> > +    I3CTarget *t;
> > +
> > +    *data = 0xff;
> > +    if (!QLIST_EMPTY(&bus->current_devs)) {
> > +        tc =
> I3C_TARGET_GET_CLASS(QLIST_FIRST(&bus->current_devs)->target);
> > +        t = QLIST_FIRST(&bus->current_devs)->target;
> > +        if (bus->in_ccc) {
> > +            if (!tc->handle_ccc_read) {
> > +                return -1;
> > +            }
> > +            ret = i3c_target_handle_ccc_read(t, data, num_to_read,
> num_read);
> > +        } else {
> > +            if (tc->recv) {
> > +                /*
> > +                 * Targets cannot NACK on a direct transfer, so the data
> > +                 * is returned directly.
> > +                 */
> > +                *num_read = tc->recv(t, data, num_to_read);
> > +            }
> > +        }
> > +    }
> > +
> > +    trace_i3c_recv(*num_read, num_to_read, ret == 0);
> > +
> > +    return ret;
> > +}
> > +
> > +void i3c_nack(I3CBus *bus)
> > +{
> > +    I3CTargetClass *tc;
> > +    I3CNode *node;
> > +
> > +    if (QLIST_EMPTY(&bus->current_devs)) {
> > +        return;
> > +    }
> > +
> > +    QLIST_FOREACH(node, &bus->current_devs, next) {
> > +        tc = I3C_TARGET_GET_CLASS(node->target);
> > +        if (tc->event) {
> > +            i3c_target_event(node->target, I3C_NACK);
> > +        }
> > +    }
> > +}
> > +
> > +int i3c_target_send_ibi(I3CTarget *t, uint8_t addr, bool is_recv) {
> > +    I3CBus *bus = I3C_BUS(t->qdev.parent_bus);
> > +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> > +    trace_i3c_target_send_ibi(addr, is_recv);
> > +    return bc->ibi_handle(bus, addr, is_recv); }
> > +
> > +int i3c_target_send_ibi_bytes(I3CTarget *t, uint8_t data) {
> > +    I3CBus *bus = I3C_BUS(t->qdev.parent_bus);
> > +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> > +    trace_i3c_target_send_ibi_bytes(data);
> > +    return bc->ibi_recv(bus, data);
> > +}
> > +
> > +int i3c_target_ibi_finish(I3CTarget *t, uint8_t data) {
> > +    I3CBus *bus = I3C_BUS(t->qdev.parent_bus);
> > +    I3CBusClass *bc = I3C_BUS_GET_CLASS(bus);
> > +    trace_i3c_target_ibi_finish();
> > +    return bc->ibi_finish(bus);
> > +}
> > +
> > +static bool i3c_addr_is_rsvd(uint8_t addr) {
> > +    const bool is_rsvd[255] = {
> > +        [0x00] = true,
> > +        [0x01] = true,
> > +        [0x02] = true,
> > +        [0x3e] = true,
> > +        [0x5e] = true,
> > +        [0x6e] = true,
> > +        [0x76] = true,
> > +        [0x7a] = true,
> > +        [0x7c] = true,
> > +        [0x7e] = true,
> > +        [0x7f] = true,
> > +    };
> > +
> > +    return is_rsvd[addr];
> > +}
> > +
> > +I3CTarget *i3c_target_new(const char *name, uint8_t addr, uint8_t dcr,
> > +                          uint8_t bcr, uint64_t pid) {
> > +    DeviceState *dev;
> > +
> > +    dev = qdev_new(name);
> > +    qdev_prop_set_uint8(dev, "static-address", addr);
> > +    qdev_prop_set_uint8(dev, "dcr", dcr);
> > +    qdev_prop_set_uint8(dev, "bcr", bcr);
> > +    qdev_prop_set_uint64(dev, "pid", pid);
> > +
> > +    if (i3c_addr_is_rsvd(addr)) {
> > +        g_autofree char *path =
> object_get_canonical_path(OBJECT(dev));
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: I3C target created
> with reserved "
> > +                      "address 0x%.2x\n", path, addr);
> > +    }
> > +    return I3C_TARGET(dev);
> > +}
> > +
> > +bool i3c_target_realize_and_unref(I3CTarget *dev, I3CBus *bus, Error
> > +**errp) {
> > +    return qdev_realize_and_unref(&dev->qdev, &bus->qbus, errp); }
> > +
> > +I3CTarget *i3c_target_create_simple(I3CBus *bus, const char *name,
> uint8_t addr,
> > +                                    uint8_t dcr, uint8_t bcr,
> > +uint64_t pid) {
> > +    I3CTarget *dev = i3c_target_new(name, addr, dcr, bcr, pid);
> > +    dev->address = 0;
> > +    i3c_target_realize_and_unref(dev, bus, &error_abort);
> > +
> > +    return dev;
> > +}
> > +
> > +/* Legacy I2C functions. */
> > +void legacy_i2c_nack(I3CBus *bus)
> > +{
> > +    trace_legacy_i2c_nack();
> > +    i2c_nack(bus->i2c_bus);
> > +}
> > +
> > +uint8_t legacy_i2c_recv(I3CBus *bus)
> > +{
> > +    uint8_t byte = i2c_recv(bus->i2c_bus);
> > +    trace_legacy_i2c_recv(byte);
> > +    return byte;
> > +}
> > +
> > +int legacy_i2c_send(I3CBus *bus, uint8_t data) {
> > +    trace_legacy_i2c_send(data);
> > +    return i2c_send(bus->i2c_bus, data); }
> > +
> > +int legacy_i2c_start_transfer(I3CBus *bus, uint8_t address, bool
> > +is_recv) {
> > +    trace_legacy_i2c_start_transfer(address, is_recv);
> > +    return i2c_start_transfer(bus->i2c_bus, address, is_recv); }
> > +
> > +int legacy_i2c_start_recv(I3CBus *bus, uint8_t address) {
> > +    trace_legacy_i2c_start_transfer(address, true);
> > +    return i2c_start_transfer(bus->i2c_bus, address,
> > +/*is_recv=*/true); }
> > +
> > +int legacy_i2c_start_send(I3CBus *bus, uint8_t address) {
> > +    trace_legacy_i2c_start_transfer(address, false);
> > +    return i2c_start_transfer(bus->i2c_bus, address,
> > +/*is_recv=*/false); }
> > +
> > +void legacy_i2c_end_transfer(I3CBus *bus) {
> > +    trace_legacy_i2c_end_transfer();
> > +    i2c_end_transfer(bus->i2c_bus);
> > +}
> > +
> > +I2CSlave *legacy_i2c_device_create_simple(I3CBus *bus, const char
> *name,
> > +                                          uint8_t addr) {
> > +    I2CSlave *dev = i2c_slave_new(name, addr);
> > +
> > +    i2c_slave_realize_and_unref(dev, bus->i2c_bus, &error_abort);
> > +    return dev;
> > +}
> > +
> > +static void i3c_target_class_init(ObjectClass *klass, const void
> > +*data) {
> > +    DeviceClass *k = DEVICE_CLASS(klass);
> > +    I3CTargetClass *sc = I3C_TARGET_CLASS(klass);
> > +    set_bit(DEVICE_CATEGORY_MISC, k->categories);
> > +    k->bus_type = TYPE_I3C_BUS;
> > +    device_class_set_props(k, i3c_props);
> > +    sc->target_match = i3c_target_match; }
> > +
> > +static const TypeInfo i3c_target_type_info = {
> > +    .name = TYPE_I3C_TARGET,
> > +    .parent = TYPE_DEVICE,
> > +    .instance_size = sizeof(I3CTarget),
> > +    .abstract = true,
> > +    .class_size = sizeof(I3CTargetClass),
> > +    .class_init = i3c_target_class_init, };
> > +
> > +static void i3c_register_types(void)
> > +{
> > +    type_register_static(&i3c_bus_info);
> > +    type_register_static(&i3c_target_type_info);
> > +}
> > +
> > +type_init(i3c_register_types)
> 
> These days the DEFINE_TYPES() macro is recommended over writing an
> explicit registration function.
Will fix it

Thanks,
Jamin
> 
> > diff --git a/hw/i3c/meson.build b/hw/i3c/meson.build index
> > ebf20325cb..fb127613fe 100644
> > --- a/hw/i3c/meson.build
> > +++ b/hw/i3c/meson.build
> > @@ -1,3 +1,4 @@
> >   i3c_ss = ss.source_set()
> > +i3c_ss.add(when: 'CONFIG_I3C', if_true: files('core.c'))
> >   i3c_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_i3c.c'))
> >   system_ss.add_all(when: 'CONFIG_I3C', if_true: i3c_ss) diff --git
> > a/hw/i3c/trace-events b/hw/i3c/trace-events index
> > 3ead84eb45..cdf7cb07f6 100644
> > --- a/hw/i3c/trace-events
> > +++ b/hw/i3c/trace-events
> > @@ -5,3 +5,19 @@ aspeed_i3c_read(uint64_t offset, uint64_t data) "I3C
> read: offset 0x%" PRIx64 "
> >   aspeed_i3c_write(uint64_t offset, uint64_t data) "I3C write: offset 0x%"
> PRIx64 " data 0x%" PRIx64
> >   aspeed_i3c_device_read(uint32_t deviceid, uint64_t offset, uint64_t data)
> "I3C Dev[%u] read: offset 0x%" PRIx64 " data 0x%" PRIx64
> >   aspeed_i3c_device_write(uint32_t deviceid, uint64_t offset, uint64_t
> > data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
> > +
> > +# core.c
> > +i3c_target_event(uint8_t address, uint8_t event) "I3C target 0x%"
> > +PRIx8 " event 0x%" PRIx8 i3c_target_handle_ccc(uint8_t address,
> > +uint8_t ccc) "I3C target 0x%" PRIx8 " handling CCC 0x%" PRIx8
> i3c_target_send_ibi(uint8_t address, bool is_recv) "I3C target IBI address 
> 0x%"
> PRIx8 " RnW=%d"
> > +i3c_target_send_ibi_bytes(uint8_t byte) "I3C target IBI byte 0x%"
> > +PRIx8
> > +i3c_target_ibi_finish(void) "I3C target IBI finish"
> > +i3c_start_transfer(uint8_t address, bool is_recv) "I3C START with address
> 0x%" PRIx8 " is_recv=%d"
> > +i3c_end_transfer(void) "I3C transfer done"
> > +i3c_send(uint32_t num_sent, uint32_t num_to_send, bool ack) "I3C send %"
> PRId32 "/%" PRId32 " bytes, ack=%d"
> > +i3c_recv(uint32_t num_read, uint32_t num_to_read, bool ack) "I3C recv %"
> PRId32 "/%" PRId32 " bytes, ack=%d"
> > +legacy_i2c_nack(void) "Legacy I2C NACK"
> > +legacy_i2c_recv(uint8_t byte) "Legacy I2C recv 0x%" PRIx8
> > +legacy_i2c_send(uint8_t byte) "Legacy I2C send 0x%" PRIx8
> > +legacy_i2c_start_transfer(uint8_t address, bool is_recv) "Legacy I2C START
> with address 0x%" PRIx8 " is_recv=%d"
> > +legacy_i2c_end_transfer(void) "Legacy I2C STOP"
> 
> 
> ATB,
> 
> Mark.

Reply via email to