Re: [PATCH v3 2/5] hw/ssi: Add DWC SSI standard interrupt support
Bin Meng <[email protected]>
| Newsgroups | org.nongnu.qemu-riscv,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <CAEUhbmXRHxi-FBBA9+O+yimOZAMJcs7ZR5sQaJ56B6HPUyW4Ow@mail.gmail.com> |
Hi Kangjie, On Sun, Aug 9, 2026 at 2:31 AM Kangjie Huang <[email protected]> wrote: > > Implement raw and masked SSI interrupt status, threshold interrupts, and > the documented read-clear registers. > > TXE and RXF are derived from FIFO levels. TXO, RXO, and RXU are latched > causes. Expose the model's nine physical interrupt outputs. > > TXU, DONE, and AXIE depend on transfer engines outside the Standard PIO > scope and remain deasserted. DONECR and AXIECR are RAZ/WI. > > Signed-off-by: Kangjie Huang <[email protected]> > --- > hw/ssi/dwc_ssi.c | 129 ++++++++++++++++++++++++++++++++++++++- > include/hw/ssi/dwc_ssi.h | 18 +++++- > 2 files changed, 145 insertions(+), 2 deletions(-) Please squash this patch into patch 1. The introduction of a new model is normally done in one single commit. > > diff --git a/hw/ssi/dwc_ssi.c b/hw/ssi/dwc_ssi.c > index c0bcf8b869..43c5c5db88 100644 > --- a/hw/ssi/dwc_ssi.c > +++ b/hw/ssi/dwc_ssi.c > @@ -6,7 +6,7 @@ > * SPDX-License-Identifier: GPL-2.0-or-later > * > * Emulates the DesignWare SSI controller in Standard SPI mode, > - * covering the PIO/FIFO data path and chip selects. > + * covering the PIO/FIFO data path, interrupt outputs and chip selects. > */ > > #include "qemu/osdep.h" > @@ -26,6 +26,9 @@ > #define DWC_SSI_IDR_RESET 0xa1b2c3d5 > #define DWC_SSI_VERSION 0x3130332a > #define DWC_SSI_PIO_TX_BATCH 64 > +#define DWC_SSI_IRQ_VALID_MASK 0x000009bf > +#define DWC_SSI_IMPLEMENTED_LATCHED_IRQ_MASK \ > + (R_RISR_TXOIR_MASK | R_RISR_RXOIR_MASK | R_RISR_RXUIR_MASK) > > enum { > DWC_SSI_TMOD_TR, > @@ -217,6 +220,22 @@ REG32(XIP_WRITE_CTRL, 0x148) > #define DWC_SSI_RX_SAMPLE_DELAY_WRITABLE_MASK \ > (R_RX_SAMPLE_DELAY_RSD_MASK | R_RX_SAMPLE_DELAY_SE_MASK) > > +/* > + * TXU, DONE and AXIE depend on transfer engines outside Standard PIO. > + * Their physical outputs are exposed but remain deasserted in this model. > + */ > +static const uint32_t dwc_ssi_irq_status_mask[DWC_SSI_IRQ_COUNT] = { > + [DWC_SSI_IRQ_TXE] = R_RISR_TXEIR_MASK, > + [DWC_SSI_IRQ_TXO] = R_RISR_TXOIR_MASK, > + [DWC_SSI_IRQ_RXF] = R_RISR_RXFIR_MASK, > + [DWC_SSI_IRQ_RXO] = R_RISR_RXOIR_MASK, > + [DWC_SSI_IRQ_TXU] = R_RISR_TXUIR_MASK, > + [DWC_SSI_IRQ_RXU] = R_RISR_RXUIR_MASK, > + [DWC_SSI_IRQ_MST] = R_RISR_MSTIR_MASK, > + [DWC_SSI_IRQ_DONE] = R_RISR_DONER_MASK, > + [DWC_SSI_IRQ_AXIE] = R_RISR_AXIER_MASK, > +}; > + > static bool dwc_ssi_validate_config(DwcSsiState *s, Error **errp) > { > DeviceState *dev = DEVICE(s); > @@ -253,6 +272,45 @@ static void dwc_ssi_write_masked(DwcSsiState *s, unsigned int reg, > s->regs[reg] = (s->regs[reg] & ~mask) | (value & mask); > } > > +static uint32_t dwc_ssi_irq_raw_status(DwcSsiState *s) > +{ > + uint32_t status = s->irq_latched; > + uint32_t tx_used = fifo32_num_used(&s->tx_fifo); > + uint32_t rx_used = fifo32_num_used(&s->rx_fifo); > + uint32_t tx_threshold = > + FIELD_EX32(s->regs[R_TXFTLR], TXFTLR, TFT); > + uint32_t rx_threshold = > + FIELD_EX32(s->regs[R_RXFTLR], RXFTLR, RFT); > + > + if (tx_used <= tx_threshold) { > + status |= R_RISR_TXEIR_MASK; > + } > + if (rx_used > rx_threshold) { > + status |= R_RISR_RXFIR_MASK; > + } > + return status & DWC_SSI_IRQ_VALID_MASK; > +} > + > +static void dwc_ssi_update_irq(DwcSsiState *s) > +{ > + uint32_t status = dwc_ssi_irq_raw_status(s) & > + s->regs[R_IMR] & DWC_SSI_IRQ_VALID_MASK; > + > + for (int i = 0; i < DWC_SSI_IRQ_COUNT; i++) { > + qemu_set_irq(s->irqs[i], !!(status & dwc_ssi_irq_status_mask[i])); > + } > +} > + > +static uint32_t dwc_ssi_irq_read_clear(DwcSsiState *s, > + uint32_t clear_mask) > +{ > + uint32_t active = s->irq_latched & clear_mask; > + > + s->irq_latched &= ~clear_mask; > + dwc_ssi_update_irq(s); > + return !!active; > +} > + > static uint32_t dwc_ssi_frame_masked(DwcSsiState *s) > { > unsigned int bits = FIELD_EX32(s->regs[R_CTRLR0], CTRLR0, DFS) + 1; > @@ -322,6 +380,7 @@ static void dwc_ssi_abort_transfer(DwcSsiState *s) > s->phase = DWC_SSI_PHASE_IDLE; > s->remaining_frames = 0; > s->dummy_frame = 0; > + dwc_ssi_update_irq(s); > } > > static uint32_t dwc_ssi_status(DwcSsiState *s) > @@ -349,6 +408,8 @@ static void dwc_ssi_push_tx(DwcSsiState *s, uint32_t tx) > } > > if (fifo32_is_full(&s->tx_fifo)) { > + s->irq_latched |= R_RISR_TXOIR_MASK; > + dwc_ssi_update_irq(s); > return; > } > > @@ -357,6 +418,7 @@ static void dwc_ssi_push_tx(DwcSsiState *s, uint32_t tx) > if (s->phase != DWC_SSI_PHASE_STANDARD_TX_ONLY) { > dwc_ssi_run_transfer(s); > } > + dwc_ssi_update_irq(s); > } > > static uint32_t dwc_ssi_send_frame(DwcSsiState *s, > @@ -414,6 +476,7 @@ static void dwc_ssi_run_transfer(DwcSsiState *s) > uint32_t rx = dwc_ssi_send_frame(s, tx); > > if (fifo32_is_full(&s->rx_fifo)) { > + s->irq_latched |= R_RISR_RXOIR_MASK; > qemu_log_mask(LOG_GUEST_ERROR, > "%s: RX FIFO full, dropping frame\n", > DEVICE(s)->canonical_path); > @@ -602,9 +665,11 @@ static uint64_t dwc_ssi_read(void *opaque, hwaddr addr, unsigned int size) > value = fifo32_pop(&s->rx_fifo) & dwc_ssi_frame_masked(s); > } else { > value = 0; > + s->irq_latched |= R_RISR_RXUIR_MASK; > } > > dwc_ssi_run_transfer(s); > + dwc_ssi_update_irq(s); > return value; > } > > @@ -634,6 +699,7 @@ static uint64_t dwc_ssi_read(void *opaque, hwaddr addr, unsigned int size) > value = fifo32_num_used(&s->tx_fifo); > if (s->phase == DWC_SSI_PHASE_STANDARD_TX_ONLY) { > dwc_ssi_run_transfer(s); > + dwc_ssi_update_irq(s); > } > break; > case A_RXFLR: > @@ -643,8 +709,35 @@ static uint64_t dwc_ssi_read(void *opaque, hwaddr addr, unsigned int size) > value = dwc_ssi_status(s); > if (s->phase == DWC_SSI_PHASE_STANDARD_TX_ONLY) { > dwc_ssi_run_transfer(s); > + dwc_ssi_update_irq(s); > } > break; > + case A_ISR: > + value = dwc_ssi_irq_raw_status(s) & s->regs[R_IMR] & > + DWC_SSI_IRQ_VALID_MASK; > + break; > + case A_RISR: > + value = dwc_ssi_irq_raw_status(s); > + break; > + case A_TXEICR: > + value = dwc_ssi_irq_read_clear( > + s, R_RISR_TXOIR_MASK | R_RISR_TXUIR_MASK); > + break; > + case A_RXOICR: > + value = dwc_ssi_irq_read_clear(s, R_RISR_RXOIR_MASK); > + break; > + case A_RXUICR: > + value = dwc_ssi_irq_read_clear(s, R_RISR_RXUIR_MASK); > + break; > + case A_MSTICR: > + value = dwc_ssi_irq_read_clear(s, R_RISR_MSTIR_MASK); > + break; > + case A_ICR: > + value = dwc_ssi_irq_read_clear( > + s, R_RISR_TXOIR_MASK | R_RISR_RXUIR_MASK | > + R_RISR_RXOIR_MASK | R_RISR_MSTIR_MASK); > + break; > + > default: > if (addr >= DWC_SSI_REGS_SIZE || (addr & 0x3) != 0) { > qemu_log_mask(LOG_GUEST_ERROR, > @@ -708,6 +801,7 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, > > dwc_ssi_update_cs(s); > dwc_ssi_run_transfer(s); > + dwc_ssi_update_irq(s); > break; > } > case A_MWCR: > @@ -731,6 +825,7 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, > > dwc_ssi_update_cs(s); > dwc_ssi_run_transfer(s); > + dwc_ssi_update_irq(s); > break; > } > case A_BAUDR: > @@ -750,6 +845,7 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, > dwc_ssi_write_masked(s, R_TXFTLR, value, > DWC_SSI_TXFTLR_WRITABLE_MASK); > dwc_ssi_run_transfer(s); > + dwc_ssi_update_irq(s); > break; > case A_RXFTLR: > if (!dwc_ssi_fifo_threshold_valid( > @@ -761,6 +857,7 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, > } > dwc_ssi_write_masked(s, R_RXFTLR, value, > DWC_SSI_RXFTLR_WRITABLE_MASK); > + dwc_ssi_update_irq(s); > break; > case A_TXFLR: > case A_RXFLR: > @@ -769,6 +866,16 @@ static void dwc_ssi_write(void *opaque, hwaddr addr, > case A_IMR: > dwc_ssi_write_masked(s, R_IMR, value, > DWC_SSI_IMR_WRITABLE_MASK); > + dwc_ssi_update_irq(s); > + break; > + case A_ISR: > + case A_RISR: > + break; > + case A_TXEICR: > + case A_RXOICR: > + case A_RXUICR: > + case A_MSTICR: > + case A_ICR: > break; > case A_IDR: > case A_SSIC_VERSION_ID: > @@ -809,12 +916,15 @@ static void dwc_ssi_enter_reset(Object *obj, ResetType type) > s->phase = DWC_SSI_PHASE_IDLE; > s->remaining_frames = 0; > s->dummy_frame = 0; > + s->irq_latched = 0; > > s->regs[R_CTRLR0] = DWC_SSI_CTRLR0_RESET; > s->regs[R_SR] = DWC_SSI_SR_RESET; > s->regs[R_IMR] = s->cfg.imr_reset & DWC_SSI_IMR_WRITABLE_MASK; > s->regs[R_IDR] = DWC_SSI_IDR_RESET; > s->regs[R_SSIC_VERSION_ID] = DWC_SSI_VERSION; > + > + dwc_ssi_update_irq(s); > } > > static void dwc_ssi_hold_reset(Object *obj, ResetType type) > @@ -829,6 +939,13 @@ static void dwc_ssi_hold_reset(Object *obj, ResetType type) > } > } > > +static void dwc_ssi_exit_reset(Object *obj, ResetType type) > +{ > + DwcSsiState *s = DWC_SSI(obj); > + > + dwc_ssi_update_irq(s); > +} > + > static bool dwc_ssi_fifo_valid(const Fifo32 *fifo, uint32_t depth) > { > const Fifo8 *fifo8 = &fifo->fifo; > @@ -850,6 +967,9 @@ static int dwc_ssi_post_load(void *opaque, int version_id) > if (s->active_cs < -1 || s->active_cs >= (int)s->cfg.num_cs) { > return -EINVAL; > } > + if (s->irq_latched & ~DWC_SSI_IMPLEMENTED_LATCHED_IRQ_MASK) { > + return -EINVAL; > + } > if (!dwc_ssi_fifo_valid(&s->tx_fifo, s->cfg.fifo_depth) || > !dwc_ssi_fifo_valid(&s->rx_fifo, s->cfg.fifo_depth)) { > return -EINVAL; > @@ -878,6 +998,7 @@ static int dwc_ssi_post_load(void *opaque, int version_id) > qemu_irq_lower(s->cs_lines[s->active_cs]); > } > > + dwc_ssi_update_irq(s); > return 0; > } > > @@ -893,6 +1014,7 @@ static const VMStateDescription vmstate_dwc_ssi = { > VMSTATE_UINT32_ARRAY(regs, DwcSsiState, DWC_SSI_NUM_REGS), > VMSTATE_FIFO32(tx_fifo, DwcSsiState), > VMSTATE_FIFO32(rx_fifo, DwcSsiState), > + VMSTATE_UINT32(irq_latched, DwcSsiState), > VMSTATE_UINT32(phase, DwcSsiState), > VMSTATE_UINT32(remaining_frames, DwcSsiState), > VMSTATE_UINT32(dummy_frame, DwcSsiState), > @@ -913,6 +1035,10 @@ static void dwc_ssi_init(Object *obj) > TYPE_DWC_SSI, DWC_SSI_MMIO_SIZE); > sysbus_init_mmio(sbd, &s->mmio); > > + for (int i = 0; i < DWC_SSI_IRQ_COUNT; i++) { > + sysbus_init_irq(sbd, &s->irqs[i]); > + } > + > s->active_cs = -1; > } > > @@ -957,6 +1083,7 @@ static void dwc_ssi_class_init(ObjectClass *klass, const void *data) > device_class_set_props(dc, dwc_ssi_properties); > rc->phases.enter = dwc_ssi_enter_reset; > rc->phases.hold = dwc_ssi_hold_reset; > + rc->phases.exit = dwc_ssi_exit_reset; > } > > static const TypeInfo dwc_ssi_info = { > diff --git a/include/hw/ssi/dwc_ssi.h b/include/hw/ssi/dwc_ssi.h > index 6e4f8ea7bb..19499789af 100644 > --- a/include/hw/ssi/dwc_ssi.h > +++ b/include/hw/ssi/dwc_ssi.h > @@ -6,7 +6,7 @@ > * SPDX-License-Identifier: GPL-2.0-or-later > * > * Emulates the DesignWare SSI controller in Standard SPI mode, > - * covering the PIO/FIFO data path and chip selects. > + * covering the PIO/FIFO data path, interrupt outputs and chip selects. > */ > > #ifndef HW_SSI_DWC_SSI_H > @@ -25,6 +25,20 @@ OBJECT_DECLARE_SIMPLE_TYPE(DwcSsiState, DWC_SSI) > #define DWC_SSI_NUM_REGS \ > (DWC_SSI_REGS_SIZE / sizeof(uint32_t)) > > +/* SSI GPIO output ordering differs from RISR/ISR bit ordering. */ > +typedef enum DwcSsiIrq { > + DWC_SSI_IRQ_TXE, > + DWC_SSI_IRQ_TXO, > + DWC_SSI_IRQ_RXF, > + DWC_SSI_IRQ_RXO, > + DWC_SSI_IRQ_TXU, > + DWC_SSI_IRQ_RXU, > + DWC_SSI_IRQ_MST, > + DWC_SSI_IRQ_DONE, > + DWC_SSI_IRQ_AXIE, > + DWC_SSI_IRQ_COUNT, > +} DwcSsiIrq; > + > typedef enum DwcSsiPhase { > DWC_SSI_PHASE_IDLE, > DWC_SSI_PHASE_STANDARD_TX_ONLY, > @@ -47,6 +61,7 @@ struct DwcSsiState { > SSIBus *spi; > > qemu_irq *cs_lines; > + qemu_irq irqs[DWC_SSI_IRQ_COUNT]; > > Fifo32 tx_fifo; > Fifo32 rx_fifo; > @@ -54,6 +69,7 @@ struct DwcSsiState { > > DwcSsiConfig cfg; > > + uint32_t irq_latched; > uint32_t phase; > uint32_t remaining_frames; > uint32_t dummy_frame; > -- Regards, Bin