PHB5 has big and small PHBs, that have 4k and 2k interrupt sources respective. The LSI interrupt sources are set with a 9-bit register field, that selects the aligned block of 8 irqs, giving a 4k range. This is initialized by hardware to 0x1ff in order to select the last 8 irq sources.
The small PHB is meant to ignore the MSB of the source ID, so the LSI default would select the last 8 of 2k irqs. The PHB model does not mask this bit consistently, which can cause the irq to be miscalculated and the source irq array to be overrun. Move the lsi_base calculation into a helper function that does the masking and some extra checking. Signed-off-by: Nicholas Piggin <[email protected]> --- hw/pci-host/pnv_phb4.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/hw/pci-host/pnv_phb4.c b/hw/pci-host/pnv_phb4.c index fb129f3266..5486ef44ea 100644 --- a/hw/pci-host/pnv_phb4.c +++ b/hw/pci-host/pnv_phb4.c @@ -458,6 +458,27 @@ static void pnv_phb4_update_all_msi_regions(PnvPHB4 *phb) } } +static int pnv_phb4_get_lsi_base(PnvPHB4 *phb) +{ + int lsi_base, nr_irqs; + + lsi_base = GETFIELD(PHB_LSI_SRC_ID, phb->regs[PHB_LSI_SOURCE_ID >> 3]); + if (!phb->big_phb) { + /* Small PHBs ignore the top bit of the source ID */ + lsi_base &= ~0x100; + } + lsi_base <<= 3; + + if (phb->big_phb) { + nr_irqs = PNV_PHB4_MAX_INTs; + } else { + nr_irqs = PNV_PHB4_MAX_INTs >> 1; + } + g_assert(lsi_base + 8 <= nr_irqs); + + return lsi_base; +} + static void pnv_phb4_update_xsrc(PnvPHB4 *phb) { int shift, flags, i, lsi_base; @@ -487,9 +508,7 @@ static void pnv_phb4_update_xsrc(PnvPHB4 *phb) phb->xsrc.esb_shift = shift; phb->xsrc.esb_flags = flags; - lsi_base = GETFIELD(PHB_LSI_SRC_ID, phb->regs[PHB_LSI_SOURCE_ID >> 3]); - lsi_base <<= 3; - lsi_base &= (xsrc->nr_irqs - 1); + lsi_base = pnv_phb4_get_lsi_base(phb); /* TODO: need a xive_source_irq_reset_lsi() */ bitmap_zero(xsrc->lsi_map, xsrc->nr_irqs); @@ -1597,8 +1616,7 @@ static void pnv_phb4_set_irq(void *opaque, int irq_num, int level) if (irq_num > 3) { phb_error(phb, "IRQ %x is not an LSI", irq_num); } - lsi_base = GETFIELD(PHB_LSI_SRC_ID, phb->regs[PHB_LSI_SOURCE_ID >> 3]); - lsi_base <<= 3; + lsi_base = pnv_phb4_get_lsi_base(phb); qemu_set_irq(phb->qirqs[lsi_base + irq_num], level); } -- 2.45.2
