Skip to content

Commit

Permalink
ppc/xive: introduce a XIVE interrupt source model
Browse files Browse the repository at this point in the history
The first sub-engine of the overall XIVE architecture is the Interrupt
Virtualization Source Engine (IVSE). An IVSE can be integrated into
another logic, like in a PCI PHB or in the main interrupt controller
to manage IPIs.

Each IVSE instance is associated with an Event State Buffer (ESB) that
contains a two bit state entry for each possible event source. When an
event is signaled to the IVSE, by MMIO or some other means, the
associated interrupt state bits are fetched from the ESB and
modified. Depending on the resulting ESB state, the event is forwarded
to the IVRE sub-engine of the controller doing the routing.

Each supported ESB entry is associated with either a single or a
even/odd pair of pages which provides commands to manage the source:
to EOI, to turn off the source for instance.

On a sPAPR machine, the O/S will obtain the page address of the ESB
entry associated with a source and its characteristic using the
H_INT_GET_SOURCE_INFO hcall. On PowerNV, a similar OPAL call is used.

The xive_source_notify() routine is in charge forwarding the source
event notification to the routing engine. It will be filled later on.

Signed-off-by: Cédric Le Goater <clg@kaod.org>
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
  • Loading branch information
legoater authored and dgibson committed Dec 20, 2018
1 parent 2104d4f commit 02e3ff5
Show file tree
Hide file tree
Showing 4 changed files with 644 additions and 0 deletions.
1 change: 1 addition & 0 deletions default-configs/ppc64-softmmu.mak
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ CONFIG_VIRTIO_VGA=y
CONFIG_XICS=$(CONFIG_PSERIES)
CONFIG_XICS_SPAPR=$(CONFIG_PSERIES)
CONFIG_XICS_KVM=$(call land,$(CONFIG_PSERIES),$(CONFIG_KVM))
CONFIG_XIVE=$(CONFIG_PSERIES)
CONFIG_MEM_DEVICE=y
CONFIG_DIMM=y
CONFIG_SPAPR_RNG=y
1 change: 1 addition & 0 deletions hw/intc/Makefile.objs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ obj-$(CONFIG_SH4) += sh_intc.o
obj-$(CONFIG_XICS) += xics.o
obj-$(CONFIG_XICS_SPAPR) += xics_spapr.o
obj-$(CONFIG_XICS_KVM) += xics_kvm.o
obj-$(CONFIG_XIVE) += xive.o
obj-$(CONFIG_POWERNV) += xics_pnv.o
obj-$(CONFIG_ALLWINNER_A10_PIC) += allwinner-a10-pic.o
obj-$(CONFIG_S390_FLIC) += s390_flic.o
Expand Down
382 changes: 382 additions & 0 deletions hw/intc/xive.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,382 @@
/*
* QEMU PowerPC XIVE interrupt controller model
*
* Copyright (c) 2017-2018, IBM Corporation.
*
* This code is licensed under the GPL version 2 or later. See the
* COPYING file in the top-level directory.
*/

#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qapi/error.h"
#include "target/ppc/cpu.h"
#include "sysemu/cpus.h"
#include "sysemu/dma.h"
#include "hw/qdev-properties.h"
#include "monitor/monitor.h"
#include "hw/ppc/xive.h"

/*
* XIVE ESB helpers
*/

static uint8_t xive_esb_set(uint8_t *pq, uint8_t value)
{
uint8_t old_pq = *pq & 0x3;

*pq &= ~0x3;
*pq |= value & 0x3;

return old_pq;
}

static bool xive_esb_trigger(uint8_t *pq)
{
uint8_t old_pq = *pq & 0x3;

switch (old_pq) {
case XIVE_ESB_RESET:
xive_esb_set(pq, XIVE_ESB_PENDING);
return true;
case XIVE_ESB_PENDING:
case XIVE_ESB_QUEUED:
xive_esb_set(pq, XIVE_ESB_QUEUED);
return false;
case XIVE_ESB_OFF:
xive_esb_set(pq, XIVE_ESB_OFF);
return false;
default:
g_assert_not_reached();
}
}

static bool xive_esb_eoi(uint8_t *pq)
{
uint8_t old_pq = *pq & 0x3;

switch (old_pq) {
case XIVE_ESB_RESET:
case XIVE_ESB_PENDING:
xive_esb_set(pq, XIVE_ESB_RESET);
return false;
case XIVE_ESB_QUEUED:
xive_esb_set(pq, XIVE_ESB_PENDING);
return true;
case XIVE_ESB_OFF:
xive_esb_set(pq, XIVE_ESB_OFF);
return false;
default:
g_assert_not_reached();
}
}

/*
* XIVE Interrupt Source (or IVSE)
*/

uint8_t xive_source_esb_get(XiveSource *xsrc, uint32_t srcno)
{
assert(srcno < xsrc->nr_irqs);

return xsrc->status[srcno] & 0x3;
}

uint8_t xive_source_esb_set(XiveSource *xsrc, uint32_t srcno, uint8_t pq)
{
assert(srcno < xsrc->nr_irqs);

return xive_esb_set(&xsrc->status[srcno], pq);
}

/*
* Returns whether the event notification should be forwarded.
*/
static bool xive_source_esb_trigger(XiveSource *xsrc, uint32_t srcno)
{
assert(srcno < xsrc->nr_irqs);

return xive_esb_trigger(&xsrc->status[srcno]);
}

/*
* Returns whether the event notification should be forwarded.
*/
static bool xive_source_esb_eoi(XiveSource *xsrc, uint32_t srcno)
{
assert(srcno < xsrc->nr_irqs);

return xive_esb_eoi(&xsrc->status[srcno]);
}

/*
* Forward the source event notification to the Router
*/
static void xive_source_notify(XiveSource *xsrc, int srcno)
{

}

/*
* In a two pages ESB MMIO setting, even page is the trigger page, odd
* page is for management
*/
static inline bool addr_is_even(hwaddr addr, uint32_t shift)
{
return !((addr >> shift) & 1);
}

static inline bool xive_source_is_trigger_page(XiveSource *xsrc, hwaddr addr)
{
return xive_source_esb_has_2page(xsrc) &&
addr_is_even(addr, xsrc->esb_shift - 1);
}

/*
* ESB MMIO loads
* Trigger page Management/EOI page
*
* ESB MMIO setting 2 pages 1 or 2 pages
*
* 0x000 .. 0x3FF -1 EOI and return 0|1
* 0x400 .. 0x7FF -1 EOI and return 0|1
* 0x800 .. 0xBFF -1 return PQ
* 0xC00 .. 0xCFF -1 return PQ and atomically PQ=00
* 0xD00 .. 0xDFF -1 return PQ and atomically PQ=01
* 0xE00 .. 0xDFF -1 return PQ and atomically PQ=10
* 0xF00 .. 0xDFF -1 return PQ and atomically PQ=11
*/
static uint64_t xive_source_esb_read(void *opaque, hwaddr addr, unsigned size)
{
XiveSource *xsrc = XIVE_SOURCE(opaque);
uint32_t offset = addr & 0xFFF;
uint32_t srcno = addr >> xsrc->esb_shift;
uint64_t ret = -1;

/* In a two pages ESB MMIO setting, trigger page should not be read */
if (xive_source_is_trigger_page(xsrc, addr)) {
qemu_log_mask(LOG_GUEST_ERROR,
"XIVE: invalid load on IRQ %d trigger page at "
"0x%"HWADDR_PRIx"\n", srcno, addr);
return -1;
}

switch (offset) {
case XIVE_ESB_LOAD_EOI ... XIVE_ESB_LOAD_EOI + 0x7FF:
ret = xive_source_esb_eoi(xsrc, srcno);

/* Forward the source event notification for routing */
if (ret) {
xive_source_notify(xsrc, srcno);
}
break;

case XIVE_ESB_GET ... XIVE_ESB_GET + 0x3FF:
ret = xive_source_esb_get(xsrc, srcno);
break;

case XIVE_ESB_SET_PQ_00 ... XIVE_ESB_SET_PQ_00 + 0x0FF:
case XIVE_ESB_SET_PQ_01 ... XIVE_ESB_SET_PQ_01 + 0x0FF:
case XIVE_ESB_SET_PQ_10 ... XIVE_ESB_SET_PQ_10 + 0x0FF:
case XIVE_ESB_SET_PQ_11 ... XIVE_ESB_SET_PQ_11 + 0x0FF:
ret = xive_source_esb_set(xsrc, srcno, (offset >> 8) & 0x3);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid ESB load addr %x\n",
offset);
}

return ret;
}

/*
* ESB MMIO stores
* Trigger page Management/EOI page
*
* ESB MMIO setting 2 pages 1 or 2 pages
*
* 0x000 .. 0x3FF Trigger Trigger
* 0x400 .. 0x7FF Trigger EOI
* 0x800 .. 0xBFF Trigger undefined
* 0xC00 .. 0xCFF Trigger PQ=00
* 0xD00 .. 0xDFF Trigger PQ=01
* 0xE00 .. 0xDFF Trigger PQ=10
* 0xF00 .. 0xDFF Trigger PQ=11
*/
static void xive_source_esb_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
XiveSource *xsrc = XIVE_SOURCE(opaque);
uint32_t offset = addr & 0xFFF;
uint32_t srcno = addr >> xsrc->esb_shift;
bool notify = false;

/* In a two pages ESB MMIO setting, trigger page only triggers */
if (xive_source_is_trigger_page(xsrc, addr)) {
notify = xive_source_esb_trigger(xsrc, srcno);
goto out;
}

switch (offset) {
case 0 ... 0x3FF:
notify = xive_source_esb_trigger(xsrc, srcno);
break;

case XIVE_ESB_STORE_EOI ... XIVE_ESB_STORE_EOI + 0x3FF:
if (!(xsrc->esb_flags & XIVE_SRC_STORE_EOI)) {
qemu_log_mask(LOG_GUEST_ERROR,
"XIVE: invalid Store EOI for IRQ %d\n", srcno);
return;
}

notify = xive_source_esb_eoi(xsrc, srcno);
break;

case XIVE_ESB_SET_PQ_00 ... XIVE_ESB_SET_PQ_00 + 0x0FF:
case XIVE_ESB_SET_PQ_01 ... XIVE_ESB_SET_PQ_01 + 0x0FF:
case XIVE_ESB_SET_PQ_10 ... XIVE_ESB_SET_PQ_10 + 0x0FF:
case XIVE_ESB_SET_PQ_11 ... XIVE_ESB_SET_PQ_11 + 0x0FF:
xive_source_esb_set(xsrc, srcno, (offset >> 8) & 0x3);
break;

default:
qemu_log_mask(LOG_GUEST_ERROR, "XIVE: invalid ESB write addr %x\n",
offset);
return;
}

out:
/* Forward the source event notification for routing */
if (notify) {
xive_source_notify(xsrc, srcno);
}
}

static const MemoryRegionOps xive_source_esb_ops = {
.read = xive_source_esb_read,
.write = xive_source_esb_write,
.endianness = DEVICE_BIG_ENDIAN,
.valid = {
.min_access_size = 8,
.max_access_size = 8,
},
.impl = {
.min_access_size = 8,
.max_access_size = 8,
},
};

static void xive_source_set_irq(void *opaque, int srcno, int val)
{
XiveSource *xsrc = XIVE_SOURCE(opaque);
bool notify = false;

if (val) {
notify = xive_source_esb_trigger(xsrc, srcno);
}

/* Forward the source event notification for routing */
if (notify) {
xive_source_notify(xsrc, srcno);
}
}

void xive_source_pic_print_info(XiveSource *xsrc, uint32_t offset, Monitor *mon)
{
int i;

for (i = 0; i < xsrc->nr_irqs; i++) {
uint8_t pq = xive_source_esb_get(xsrc, i);

if (pq == XIVE_ESB_OFF) {
continue;
}

monitor_printf(mon, " %08x %c%c\n", i + offset,
pq & XIVE_ESB_VAL_P ? 'P' : '-',
pq & XIVE_ESB_VAL_Q ? 'Q' : '-');
}
}

static void xive_source_reset(void *dev)
{
XiveSource *xsrc = XIVE_SOURCE(dev);

/* PQs are initialized to 0b01 (Q=1) which corresponds to "ints off" */
memset(xsrc->status, XIVE_ESB_OFF, xsrc->nr_irqs);
}

static void xive_source_realize(DeviceState *dev, Error **errp)
{
XiveSource *xsrc = XIVE_SOURCE(dev);

if (!xsrc->nr_irqs) {
error_setg(errp, "Number of interrupt needs to be greater than 0");
return;
}

if (xsrc->esb_shift != XIVE_ESB_4K &&
xsrc->esb_shift != XIVE_ESB_4K_2PAGE &&
xsrc->esb_shift != XIVE_ESB_64K &&
xsrc->esb_shift != XIVE_ESB_64K_2PAGE) {
error_setg(errp, "Invalid ESB shift setting");
return;
}

xsrc->status = g_malloc0(xsrc->nr_irqs);

memory_region_init_io(&xsrc->esb_mmio, OBJECT(xsrc),
&xive_source_esb_ops, xsrc, "xive.esb",
(1ull << xsrc->esb_shift) * xsrc->nr_irqs);

xsrc->qirqs = qemu_allocate_irqs(xive_source_set_irq, xsrc,
xsrc->nr_irqs);

qemu_register_reset(xive_source_reset, dev);
}

static const VMStateDescription vmstate_xive_source = {
.name = TYPE_XIVE_SOURCE,
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32_EQUAL(nr_irqs, XiveSource, NULL),
VMSTATE_VBUFFER_UINT32(status, XiveSource, 1, NULL, nr_irqs),
VMSTATE_END_OF_LIST()
},
};

/*
* The default XIVE interrupt source setting for the ESB MMIOs is two
* 64k pages without Store EOI, to be in sync with KVM.
*/
static Property xive_source_properties[] = {
DEFINE_PROP_UINT64("flags", XiveSource, esb_flags, 0),
DEFINE_PROP_UINT32("nr-irqs", XiveSource, nr_irqs, 0),
DEFINE_PROP_UINT32("shift", XiveSource, esb_shift, XIVE_ESB_64K_2PAGE),
DEFINE_PROP_END_OF_LIST(),
};

static void xive_source_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);

dc->desc = "XIVE Interrupt Source";
dc->props = xive_source_properties;
dc->realize = xive_source_realize;
dc->vmsd = &vmstate_xive_source;
}

static const TypeInfo xive_source_info = {
.name = TYPE_XIVE_SOURCE,
.parent = TYPE_DEVICE,
.instance_size = sizeof(XiveSource),
.class_init = xive_source_class_init,
};

static void xive_register_types(void)
{
type_register_static(&xive_source_info);
}

type_init(xive_register_types)
Loading

0 comments on commit 02e3ff5

Please sign in to comment.