@@ -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);
}
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 <npiggin@gmail.com> --- hw/pci-host/pnv_phb4.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-)