5e79b155a8
The XiveNotifier offers a simple interface, between the XiveSource object and the main interrupt controller of the machine. It will forward event notifications to the XIVE Interrupt Virtualization Routing Engine (IVRE). Signed-off-by: Cédric Le Goater <clg@kaod.org> [dwg: Adjust type name string for XiveNotifier] Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
463 lines
12 KiB
C
463 lines
12 KiB
C
/*
|
|
* 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_lsi_trigger(XiveSource *xsrc, uint32_t srcno)
|
|
{
|
|
uint8_t old_pq = xive_source_esb_get(xsrc, srcno);
|
|
|
|
xsrc->status[srcno] |= XIVE_STATUS_ASSERTED;
|
|
|
|
switch (old_pq) {
|
|
case XIVE_ESB_RESET:
|
|
xive_source_esb_set(xsrc, srcno, XIVE_ESB_PENDING);
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns whether the event notification should be forwarded.
|
|
*/
|
|
static bool xive_source_esb_trigger(XiveSource *xsrc, uint32_t srcno)
|
|
{
|
|
bool ret;
|
|
|
|
assert(srcno < xsrc->nr_irqs);
|
|
|
|
ret = xive_esb_trigger(&xsrc->status[srcno]);
|
|
|
|
if (xive_source_irq_is_lsi(xsrc, srcno) &&
|
|
xive_source_esb_get(xsrc, srcno) == XIVE_ESB_QUEUED) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"XIVE: queued an event on LSI IRQ %d\n", srcno);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Returns whether the event notification should be forwarded.
|
|
*/
|
|
static bool xive_source_esb_eoi(XiveSource *xsrc, uint32_t srcno)
|
|
{
|
|
bool ret;
|
|
|
|
assert(srcno < xsrc->nr_irqs);
|
|
|
|
ret = xive_esb_eoi(&xsrc->status[srcno]);
|
|
|
|
/*
|
|
* LSI sources do not set the Q bit but they can still be
|
|
* asserted, in which case we should forward a new event
|
|
* notification
|
|
*/
|
|
if (xive_source_irq_is_lsi(xsrc, srcno) &&
|
|
xsrc->status[srcno] & XIVE_STATUS_ASSERTED) {
|
|
ret = xive_source_lsi_trigger(xsrc, srcno);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Forward the source event notification to the Router
|
|
*/
|
|
static void xive_source_notify(XiveSource *xsrc, int srcno)
|
|
{
|
|
XiveNotifierClass *xnc = XIVE_NOTIFIER_GET_CLASS(xsrc->xive);
|
|
|
|
if (xnc->notify) {
|
|
xnc->notify(xsrc->xive, 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 (xive_source_irq_is_lsi(xsrc, srcno)) {
|
|
if (val) {
|
|
notify = xive_source_lsi_trigger(xsrc, srcno);
|
|
} else {
|
|
xsrc->status[srcno] &= ~XIVE_STATUS_ASSERTED;
|
|
}
|
|
} else {
|
|
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 %s %c%c%c\n", i + offset,
|
|
xive_source_irq_is_lsi(xsrc, i) ? "LSI" : "MSI",
|
|
pq & XIVE_ESB_VAL_P ? 'P' : '-',
|
|
pq & XIVE_ESB_VAL_Q ? 'Q' : '-',
|
|
xsrc->status[i] & XIVE_STATUS_ASSERTED ? 'A' : ' ');
|
|
}
|
|
}
|
|
|
|
static void xive_source_reset(void *dev)
|
|
{
|
|
XiveSource *xsrc = XIVE_SOURCE(dev);
|
|
|
|
/* Do not clear the LSI bitmap */
|
|
|
|
/* 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);
|
|
Object *obj;
|
|
Error *local_err = NULL;
|
|
|
|
obj = object_property_get_link(OBJECT(dev), "xive", &local_err);
|
|
if (!obj) {
|
|
error_propagate(errp, local_err);
|
|
error_prepend(errp, "required link 'xive' not found: ");
|
|
return;
|
|
}
|
|
|
|
xsrc->xive = XIVE_NOTIFIER(obj);
|
|
|
|
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);
|
|
xsrc->lsi_map = bitmap_new(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,
|
|
};
|
|
|
|
/*
|
|
* XIVE Fabric
|
|
*/
|
|
static const TypeInfo xive_fabric_info = {
|
|
.name = TYPE_XIVE_NOTIFIER,
|
|
.parent = TYPE_INTERFACE,
|
|
.class_size = sizeof(XiveNotifierClass),
|
|
};
|
|
|
|
static void xive_register_types(void)
|
|
{
|
|
type_register_static(&xive_source_info);
|
|
type_register_static(&xive_fabric_info);
|
|
}
|
|
|
|
type_init(xive_register_types)
|