@samitouri / QOSamiQemu / commits / 4cbf835a87

hw/i3c/dw-i3c: Add IBI handling

Adds handling for different IBI events that the controller can receive. This includes: - Handling a hot-join from a target - Handling a secondary controller on the bus requesting to be the primary bus controller - Handling an interrupt request from a target. When receiving an IBI, the controller sets an interrupt to notify software about what happened. When the IBI is finished being serviced, the controller pushes the result of the IBI and any data received from the target into the IBI queue. Signed-off-by: Joe Komlodi <komlodi@google.com> Reviewed-by: Patrick Venture <venture@google.com> Reviewed-by: Stephen Longfield <slongfield@google.com> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> Tested-by: Jithu Joseph <jithu.joseph@oss.qualcomm.com> Link: https://lore.kernel.org/qemu-devel/20260225021158.1586584-15-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater <clg@redhat.com>

Jamin Lin committed Feb 25, 2026 at 02:12 UTC 4cbf835a87d6c60e80e4b4f607aef44cd691a805
3 files changed +346
hw/i3c/dw-i3c.c
+317
@@ -19,6 +19,14 @@
19 #include "trace.h"
20 #include "hw/core/irq.h"
21
22 +/*
23 + * Disable event command values. sent along with a DISEC CCC to disable certain
24 + * events on targets.
25 + */
26 +#define DISEC_HJ 0x08
27 +#define DISEC_CR 0x02
28 +#define DISEC_INT 0x01
29 +
30 REG32(DEVICE_CTRL, 0x00)
31 FIELD(DEVICE_CTRL, I3C_BROADCAST_ADDR_INC, 0, 1)
32 FIELD(DEVICE_CTRL, I2C_SLAVE_PRESENT, 7, 1)
@@ -356,6 +364,23 @@ static inline bool dw_i3c_can_transmit(DWI3C *s)
364 !ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL, I3C_RESUME);
365 }
366
367 +static inline uint8_t dw_i3c_ibi_slice_size(DWI3C *s)
368 +{
369 + uint8_t ibi_slice_size = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
370 + IBI_DATA_THLD);
371 + /* The minimum supported slice size is 4 bytes. */
372 + if (ibi_slice_size == 0) {
373 + ibi_slice_size = 1;
374 + }
375 + ibi_slice_size *= sizeof(uint32_t);
376 + /* maximum supported size is 63 bytes. */
377 + if (ibi_slice_size >= 64) {
378 + ibi_slice_size = 63;
379 + }
380 +
381 + return ibi_slice_size;
382 +}
383 +
384 static inline uint8_t dw_i3c_fifo_threshold_from_reg(uint8_t regval)
385 {
386 return regval = regval ? (2 << regval) : 1;
@@ -509,6 +534,266 @@ static uint8_t dw_i3c_target_addr(DWI3C *s, uint16_t offset)
534 DEV_DYNAMIC_ADDR);
535 }
536
537 +static int dw_i3c_addr_table_index_from_addr(DWI3C *s, uint8_t addr)
538 +{
539 + uint8_t table_size = ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
540 + DEPTH);
541 + for (uint8_t i = 0; i < table_size; i++) {
542 + if (dw_i3c_target_addr(s, i) == addr) {
543 + return i;
544 + }
545 + }
546 + return -1;
547 +}
548 +
549 +static void dw_i3c_send_disec(DWI3C *s)
550 +{
551 + uint8_t ccc = I3C_CCC_DISEC;
552 + if (s->ibi_data.send_direct_disec) {
553 + ccc = I3C_CCCD_DISEC;
554 + }
555 +
556 + dw_i3c_send_start(s, I3C_BROADCAST, /*is_recv=*/false,
557 + /*is_i2c=*/false);
558 + dw_i3c_send_byte(s, ccc, /*is_i2c=*/false);
559 + if (s->ibi_data.send_direct_disec) {
560 + dw_i3c_send_start(s, s->ibi_data.disec_addr,
561 + /*is_recv=*/false, /*is_i2c=*/false);
562 + }
563 + dw_i3c_send_byte(s, s->ibi_data.disec_byte, /*is_i2c=*/false);
564 +}
565 +
566 +static int dw_i3c_handle_hj(DWI3C *s)
567 +{
568 + if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_HOT_JOIN)) {
569 + s->ibi_data.notify_ibi_nack = true;
570 + }
571 +
572 + bool nack_and_disable = ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL,
573 + HOT_JOIN_ACK_NACK_CTRL);
574 + if (nack_and_disable) {
575 + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
576 + IBI_QUEUE_STATUS,
577 + IBI_STATUS, 1);
578 + s->ibi_data.ibi_nacked = true;
579 + s->ibi_data.disec_byte = DISEC_HJ;
580 + return -1;
581 + }
582 + return 0;
583 +}
584 +
585 +static int dw_i3c_handle_ctlr_req(DWI3C *s, uint8_t addr)
586 +{
587 + if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_MASTER_REQ)) {
588 + s->ibi_data.notify_ibi_nack = true;
589 + }
590 +
591 + int table_offset = dw_i3c_addr_table_index_from_addr(s, addr);
592 + /* Doesn't exist in the table, NACK it, don't DISEC. */
593 + if (table_offset < 0) {
594 + return -1;
595 + }
596 +
597 + /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
598 + table_offset += (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
599 + ADDR) / sizeof(uint32_t));
600 + if (FIELD_EX32(s->regs[table_offset], DEVICE_ADDR_TABLE_LOC1, MR_REJECT)) {
601 + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
602 + IBI_QUEUE_STATUS,
603 + IBI_STATUS, 1);
604 + s->ibi_data.ibi_nacked = true;
605 + s->ibi_data.disec_addr = addr;
606 + /* Tell the requester to disable controller role requests. */
607 + s->ibi_data.disec_byte = DISEC_CR;
608 + s->ibi_data.send_direct_disec = true;
609 + return -1;
610 + }
611 + return 0;
612 +}
613 +
614 +static int dw_i3c_handle_targ_irq(DWI3C *s, uint8_t addr)
615 +{
616 + if (ARRAY_FIELD_EX32(s->regs, IBI_QUEUE_CTRL, NOTIFY_REJECTED_SLAVE_IRQ)) {
617 + s->ibi_data.notify_ibi_nack = true;
618 + }
619 +
620 + int table_offset = dw_i3c_addr_table_index_from_addr(s, addr);
621 + /* Doesn't exist in the table, NACK it, don't DISEC. */
622 + if (table_offset < 0) {
623 + return -1;
624 + }
625 +
626 + /* / sizeof(uint32_t) because we're indexing into our 32-bit reg array. */
627 + table_offset += (ARRAY_FIELD_EX32(s->regs, DEVICE_ADDR_TABLE_POINTER,
628 + ADDR) / sizeof(uint32_t));
629 + if (FIELD_EX32(s->regs[table_offset], DEVICE_ADDR_TABLE_LOC1, SIR_REJECT)) {
630 + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
631 + IBI_QUEUE_STATUS,
632 + IBI_STATUS, 1);
633 + s->ibi_data.ibi_nacked = true;
634 + s->ibi_data.disec_addr = addr;
635 + /* Tell the requester to disable interrupts. */
636 + s->ibi_data.disec_byte = DISEC_INT;
637 + s->ibi_data.send_direct_disec = true;
638 + return -1;
639 + }
640 + return 0;
641 +}
642 +
643 +static int dw_i3c_ibi_handle(I3CBus *bus, uint8_t addr, bool is_recv)
644 +{
645 + DWI3C *s = DW_I3C(bus->parent_obj.parent);
646 +
647 + trace_dw_i3c_ibi_handle(s->cfg.id, addr, is_recv);
648 + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
649 + IBI_QUEUE_STATUS, IBI_ID,
650 + (addr << 1) | is_recv);
651 + /* Is this a hot join request? */
652 + if (addr == I3C_HJ_ADDR) {
653 + return dw_i3c_handle_hj(s);
654 + }
655 + /* Is secondary controller requesting access? */
656 + if (!is_recv) {
657 + return dw_i3c_handle_ctlr_req(s, addr);
658 + }
659 + /* Is this a target IRQ? */
660 + if (is_recv) {
661 + return dw_i3c_handle_targ_irq(s, addr);
662 + }
663 +
664 + /* At this point the IBI should have been ACKed or NACKed. */
665 + g_assert_not_reached();
666 + return -1;
667 +}
668 +
669 +static int dw_i3c_ibi_recv(I3CBus *bus, uint8_t data)
670 +{
671 + DWI3C *s = DW_I3C(bus->parent_obj.parent);
672 + if (fifo8_is_full(&s->ibi_data.ibi_intermediate_queue)) {
673 + return -1;
674 + }
675 +
676 + fifo8_push(&s->ibi_data.ibi_intermediate_queue, data);
677 + trace_dw_i3c_ibi_recv(s->cfg.id, data);
678 + return 0;
679 +}
680 +
681 +static void dw_i3c_ibi_queue_push(DWI3C *s)
682 +{
683 + /* Stored value is in 32-bit chunks, convert it to byte chunks. */
684 + uint8_t ibi_slice_size = dw_i3c_ibi_slice_size(s);
685 + uint8_t num_slices = (fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) /
686 + ibi_slice_size) +
687 + ((fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) %
688 + ibi_slice_size) ? 1 : 0);
689 + uint8_t ibi_status_count = num_slices;
690 + union {
691 + uint8_t b[sizeof(uint32_t)];
692 + uint32_t val32;
693 + } ibi_data = {
694 + .val32 = 0
695 + };
696 +
697 + /* The report was suppressed, do nothing. */
698 + if (s->ibi_data.ibi_nacked && !s->ibi_data.notify_ibi_nack) {
699 + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
700 + DW_I3C_TRANSFER_STATE_IDLE);
701 + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
702 + DW_I3C_TRANSFER_STATUS_IDLE);
703 + return;
704 + }
705 +
706 + /* If we don't have any slices to push, just push the status. */
707 + if (num_slices == 0) {
708 + s->ibi_data.ibi_queue_status =
709 + FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
710 + LAST_STATUS, 1);
711 + fifo32_push(&s->ibi_queue, s->ibi_data.ibi_queue_status);
712 + ibi_status_count = 1;
713 + }
714 +
715 + for (uint8_t i = 0; i < num_slices; i++) {
716 + /* If this is the last slice, set LAST_STATUS. */
717 + if (fifo8_num_used(&s->ibi_data.ibi_intermediate_queue) <
718 + ibi_slice_size) {
719 + s->ibi_data.ibi_queue_status =
720 + FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
721 + IBI_DATA_LEN,
722 + fifo8_num_used(&s->ibi_data.ibi_intermediate_queue));
723 + s->ibi_data.ibi_queue_status =
724 + FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
725 + LAST_STATUS, 1);
726 + } else {
727 + s->ibi_data.ibi_queue_status =
728 + FIELD_DP32(s->ibi_data.ibi_queue_status, IBI_QUEUE_STATUS,
729 + IBI_DATA_LEN, ibi_slice_size);
730 + }
731 +
732 + /* Push the IBI status header. */
733 + fifo32_push(&s->ibi_queue, s->ibi_data.ibi_queue_status);
734 + /* Move each IBI byte into a 32-bit word and push it into the queue. */
735 + for (uint8_t j = 0; j < ibi_slice_size; ++j) {
736 + if (fifo8_is_empty(&s->ibi_data.ibi_intermediate_queue)) {
737 + break;
738 + }
739 +
740 + ibi_data.b[j & 3] = fifo8_pop(&s->ibi_data.ibi_intermediate_queue);
741 + /* We have 32-bits, push it to the IBI FIFO. */
742 + if ((j & 0x03) == 0x03) {
743 + fifo32_push(&s->ibi_queue, ibi_data.val32);
744 + ibi_data.val32 = 0;
745 + }
746 + }
747 + /* If the data isn't 32-bit aligned, push the leftover bytes. */
748 + if (ibi_slice_size & 0x03) {
749 + fifo32_push(&s->ibi_queue, ibi_data.val32);
750 + }
751 +
752 + /* Clear out the data length for the next iteration. */
753 + s->ibi_data.ibi_queue_status = FIELD_DP32(s->ibi_data.ibi_queue_status,
754 + IBI_QUEUE_STATUS, IBI_DATA_LEN, 0);
755 + }
756 +
757 + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR,
758 + fifo32_num_used(&s->ibi_queue));
759 + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_STATUS_CNT,
760 + ibi_status_count);
761 + /* Threshold is the register value + 1. */
762 + uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
763 + IBI_STATUS_THLD) + 1;
764 + if (fifo32_num_used(&s->ibi_queue) >= threshold) {
765 + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 1);
766 + dw_i3c_update_irq(s);
767 + }
768 +
769 + /* State update. */
770 + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_ST_STATUS,
771 + DW_I3C_TRANSFER_STATE_IDLE);
772 + ARRAY_FIELD_DP32(s->regs, PRESENT_STATE, CM_TFR_STATUS,
773 + DW_I3C_TRANSFER_STATUS_IDLE);
774 +}
775 +
776 +static int dw_i3c_ibi_finish(I3CBus *bus)
777 +{
778 + DWI3C *s = DW_I3C(bus->parent_obj.parent);
779 + bool nack_and_disable_hj = ARRAY_FIELD_EX32(s->regs, DEVICE_CTRL,
780 + HOT_JOIN_ACK_NACK_CTRL);
781 + if (nack_and_disable_hj || s->ibi_data.send_direct_disec) {
782 + dw_i3c_send_disec(s);
783 + }
784 + dw_i3c_ibi_queue_push(s);
785 +
786 + /* Clear out the intermediate values. */
787 + s->ibi_data.ibi_queue_status = 0;
788 + s->ibi_data.disec_addr = 0;
789 + s->ibi_data.disec_byte = 0;
790 + s->ibi_data.send_direct_disec = false;
791 + s->ibi_data.notify_ibi_nack = false;
792 + s->ibi_data.ibi_nacked = false;
793 +
794 + return 0;
795 +}
796 +
797 static uint32_t dw_i3c_intr_status_r(DWI3C *s)
798 {
799 /* Only return the status whose corresponding EN bits are set. */
@@ -569,6 +854,25 @@ static uint32_t dw_i3c_pop_rx(DWI3C *s)
854 return val;
855 }
856
857 +static uint32_t dw_i3c_ibi_queue_r(DWI3C *s)
858 +{
859 + if (fifo32_is_empty(&s->ibi_queue)) {
860 + return 0;
861 + }
862 +
863 + uint32_t val = fifo32_pop(&s->ibi_queue);
864 + ARRAY_FIELD_DP32(s->regs, QUEUE_STATUS_LEVEL, IBI_BUF_BLR,
865 + fifo32_num_used(&s->ibi_queue));
866 + /* Threshold is the register value + 1. */
867 + uint8_t threshold = ARRAY_FIELD_EX32(s->regs, QUEUE_THLD_CTRL,
868 + IBI_STATUS_THLD) + 1;
869 + if (fifo32_num_used(&s->ibi_queue) < threshold) {
870 + ARRAY_FIELD_DP32(s->regs, INTR_STATUS, IBI_THLD, 0);
871 + dw_i3c_update_irq(s);
872 + }
873 + return val;
874 +}
875 +
876 static uint32_t dw_i3c_resp_queue_port_r(DWI3C *s)
877 {
878 if (fifo32_is_empty(&s->resp_queue)) {
@@ -606,6 +910,9 @@ static uint64_t dw_i3c_read(void *opaque, hwaddr offset, unsigned size)
910 case R_INTR_FORCE:
911 value = 0;
912 break;
913 + case R_IBI_QUEUE_DATA:
914 + value = dw_i3c_ibi_queue_r(s);
915 + break;
916 case R_INTR_STATUS:
917 value = dw_i3c_intr_status_r(s);
918 break;
@@ -1345,8 +1652,16 @@ static void dw_i3c_realize(DeviceState *dev, Error **errp)
1652 fifo32_create(&s->resp_queue, s->cfg.cmd_resp_queue_capacity_bytes);
1653 fifo32_create(&s->tx_queue, s->cfg.tx_rx_queue_capacity_bytes);
1654 fifo32_create(&s->rx_queue, s->cfg.tx_rx_queue_capacity_bytes);
1655 + fifo32_create(&s->ibi_queue, s->cfg.ibi_queue_capacity_bytes);
1656 + /* Arbitrarily large enough to not be an issue. */
1657 + fifo8_create(&s->ibi_data.ibi_intermediate_queue,
1658 + s->cfg.ibi_queue_capacity_bytes * 8);
1659
1660 s->bus = i3c_init_bus(DEVICE(s), name);
1661 + I3CBusClass *bc = I3C_BUS_GET_CLASS(s->bus);
1662 + bc->ibi_handle = dw_i3c_ibi_handle;
1663 + bc->ibi_recv = dw_i3c_ibi_recv;
1664 + bc->ibi_finish = dw_i3c_ibi_finish;
1665 }
1666
1667 static const Property dw_i3c_properties[] = {
@@ -1355,6 +1670,8 @@ static const Property dw_i3c_properties[] = {
1670 cfg.cmd_resp_queue_capacity_bytes, 0x10),
1671 DEFINE_PROP_UINT16("tx-rx-queue-capacity-bytes", DWI3C,
1672 cfg.tx_rx_queue_capacity_bytes, 0x40),
1673 + DEFINE_PROP_UINT8("ibi-queue-capacity-bytes", DWI3C,
1674 + cfg.ibi_queue_capacity_bytes, 0x10),
1675 DEFINE_PROP_UINT8("num-addressable-devices", DWI3C,
1676 cfg.num_addressable_devices, 8),
1677 DEFINE_PROP_UINT16("dev-addr-table-pointer", DWI3C,
hw/i3c/trace-events
+2
@@ -9,6 +9,8 @@ dw_i3c_read(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] read
9 dw_i3c_write(uint32_t deviceid, uint64_t offset, uint64_t data) "I3C Dev[%u] write: offset 0x%" PRIx64 " data 0x%" PRIx64
10 dw_i3c_send(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] send %" PRId32 " bytes to bus"
11 dw_i3c_recv_data(uint32_t deviceid, uint32_t num_bytes) "I3C Dev[%u] recv %" PRId32 " bytes from bus"
12 +dw_i3c_ibi_recv(uint32_t deviceid, uint8_t ibi_byte) "I3C Dev[%u] recv IBI byte 0x%" PRIx8
13 +dw_i3c_ibi_handle(uint32_t deviceid, uint8_t addr, bool rnw) "I3C Dev[%u] handle IBI from address 0x%" PRIx8 " RnW=%d"
14 dw_i3c_pop_rx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] pop 0x%" PRIx32 " from RX FIFO"
15 dw_i3c_resp_queue_push(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to response queue"
16 dw_i3c_push_tx(uint32_t deviceid, uint32_t data) "I3C Dev[%u] push 0x%" PRIx32 " to TX FIFO"
include/hw/i3c/dw-i3c.h
+27
@@ -141,6 +141,28 @@ typedef union DWI3CCmdQueueData {
141 DWI3CAddrAssignCmd addr_assign_cmd;
142 } DWI3CCmdQueueData;
143
144 +/*
145 + * When we receive an IBI with data, we need to store it temporarily until
146 + * the target is finished sending data. Then we can set the IBI queue status
147 + * appropriately.
148 + */
149 +typedef struct DWI3CIBIData {
150 + /* Do we notify the user that an IBI was NACKed? */
151 + bool notify_ibi_nack;
152 + /* Intermediate storage of IBI_QUEUE_STATUS. */
153 + uint32_t ibi_queue_status;
154 + /* Temporary buffer to store IBI data from the target. */
155 + Fifo8 ibi_intermediate_queue;
156 + /* The address we should send a CCC_DISEC to. */
157 + uint8_t disec_addr;
158 + /* The byte we should send along with the CCC_DISEC. */
159 + uint8_t disec_byte;
160 + /* Should we send a direct DISEC CCC? (As opposed to global). */
161 + bool send_direct_disec;
162 + /* Was this IBI NACKed? */
163 + bool ibi_nacked;
164 +} DWI3CIBIData;
165 +
166 struct DWI3C {
167 SysBusDevice parent_obj;
168
@@ -152,11 +174,16 @@ struct DWI3C {
174 Fifo32 resp_queue;
175 Fifo32 tx_queue;
176 Fifo32 rx_queue;
177 + Fifo32 ibi_queue;
178 +
179 + /* Temporary storage for IBI data. */
180 + DWI3CIBIData ibi_data;
181
182 struct {
183 uint8_t id;
184 uint8_t cmd_resp_queue_capacity_bytes;
185 uint16_t tx_rx_queue_capacity_bytes;
186 + uint8_t ibi_queue_capacity_bytes;
187 uint8_t num_addressable_devices;
188 uint16_t dev_addr_table_pointer;
189 uint16_t dev_addr_table_depth;