spapr_events: add support for dedicated hotplug event source

Hotplug events were previously delivered using an EPOW interrupt
and were queued by linux guests into a circular buffer. For traditional
EPOW events like shutdown/resets, this isn't an issue, but for hotplug
events there are cases where this buffer can be exhausted, resulting
in the loss of hotplug events, resets, etc.

Newer-style hotplug event are delivered using a dedicated event source.
We enable this in supported guests by adding standard an additional
event source in the guest device-tree via /event-sources, and, if
the guest advertises support for the newer-style hotplug events,
using the corresponding interrupt to signal the available of
hotplug/unplug events.

Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
This commit is contained in:
Michael Roth 2016-10-26 21:20:26 -05:00 committed by David Gibson
parent 9f992cca93
commit ffbb1705a3
4 changed files with 177 additions and 41 deletions

View File

@ -973,7 +973,7 @@ static void *spapr_build_fdt(sPAPRMachineState *spapr,
}
/* /event-sources */
spapr_dt_events(fdt, spapr->check_exception_irq);
spapr_dt_events(spapr, fdt);
/* /rtas */
spapr_dt_rtas(spapr, fdt);
@ -1789,6 +1789,11 @@ static void ppc_spapr_init(MachineState *machine)
spapr_ovec_set(spapr->ov5, OV5_FORM1_AFFINITY);
/* advertise support for dedicated HP event source to guests */
if (spapr->use_hotplug_event_source) {
spapr_ovec_set(spapr->ov5, OV5_HP_EVT);
}
/* init CPUs */
if (machine->cpu_model == NULL) {
machine->cpu_model = kvm_enabled() ? "host" : smc->tcg_default_cpu;
@ -1912,7 +1917,7 @@ static void ppc_spapr_init(MachineState *machine)
}
g_free(filename);
/* Set up EPOW events infrastructure */
/* Set up RTAS event infrastructure */
spapr_events_init(spapr);
/* Set up the RTC RTAS interfaces */

View File

@ -40,6 +40,7 @@
#include "hw/ppc/spapr_drc.h"
#include "qemu/help_option.h"
#include "qemu/bcd.h"
#include "hw/ppc/spapr_ovec.h"
#include <libfdt.h>
struct rtas_error_log {
@ -206,27 +207,132 @@ struct hp_log_full {
struct rtas_event_log_v6_hp hp;
} QEMU_PACKED;
#define EVENT_MASK_INTERNAL_ERRORS 0x80000000
#define EVENT_MASK_EPOW 0x40000000
#define EVENT_MASK_HOTPLUG 0x10000000
#define EVENT_MASK_IO 0x08000000
typedef enum EventClass {
EVENT_CLASS_INTERNAL_ERRORS = 0,
EVENT_CLASS_EPOW = 1,
EVENT_CLASS_RESERVED = 2,
EVENT_CLASS_HOT_PLUG = 3,
EVENT_CLASS_IO = 4,
EVENT_CLASS_MAX
} EventClassIndex;
#define EVENT_CLASS_MASK(index) (1 << (31 - index))
void spapr_dt_events(void *fdt, uint32_t check_exception_irq)
static const char * const event_names[EVENT_CLASS_MAX] = {
[EVENT_CLASS_INTERNAL_ERRORS] = "internal-errors",
[EVENT_CLASS_EPOW] = "epow-events",
[EVENT_CLASS_HOT_PLUG] = "hot-plug-events",
[EVENT_CLASS_IO] = "ibm,io-events",
};
struct sPAPREventSource {
int irq;
uint32_t mask;
bool enabled;
};
static sPAPREventSource *spapr_event_sources_new(void)
{
int event_sources, epow_events;
uint32_t irq_ranges[] = {cpu_to_be32(check_exception_irq), cpu_to_be32(1)};
uint32_t interrupts[] = {cpu_to_be32(check_exception_irq), 0};
return g_new0(sPAPREventSource, EVENT_CLASS_MAX);
}
static void spapr_event_sources_register(sPAPREventSource *event_sources,
EventClassIndex index, int irq)
{
/* we only support 1 irq per event class at the moment */
g_assert(event_sources);
g_assert(!event_sources[index].enabled);
event_sources[index].irq = irq;
event_sources[index].mask = EVENT_CLASS_MASK(index);
event_sources[index].enabled = true;
}
static const sPAPREventSource *
spapr_event_sources_get_source(sPAPREventSource *event_sources,
EventClassIndex index)
{
g_assert(index < EVENT_CLASS_MAX);
g_assert(event_sources);
return &event_sources[index];
}
void spapr_dt_events(sPAPRMachineState *spapr, void *fdt)
{
uint32_t irq_ranges[EVENT_CLASS_MAX * 2];
int i, count = 0, event_sources;
sPAPREventSource *events = spapr->event_sources;
g_assert(events);
_FDT(event_sources = fdt_add_subnode(fdt, 0, "event-sources"));
_FDT(fdt_setprop(fdt, event_sources, "interrupt-controller", NULL, 0));
_FDT(fdt_setprop_cell(fdt, event_sources, "#interrupt-cells", 2));
_FDT(fdt_setprop(fdt, event_sources, "interrupt-ranges",
irq_ranges, sizeof(irq_ranges)));
for (i = 0, count = 0; i < EVENT_CLASS_MAX; i++) {
int node_offset;
uint32_t interrupts[2];
const sPAPREventSource *source =
spapr_event_sources_get_source(events, i);
const char *source_name = event_names[i];
_FDT(epow_events = fdt_add_subnode(fdt, event_sources, "epow-events"));
_FDT(fdt_setprop(fdt, epow_events, "interrupts",
interrupts, sizeof(interrupts)));
if (!source->enabled) {
continue;
}
interrupts[0] = cpu_to_be32(source->irq);
interrupts[1] = 0;
_FDT(node_offset = fdt_add_subnode(fdt, event_sources, source_name));
_FDT(fdt_setprop(fdt, node_offset, "interrupts", interrupts,
sizeof(interrupts)));
irq_ranges[count++] = interrupts[0];
irq_ranges[count++] = cpu_to_be32(1);
}
irq_ranges[count] = cpu_to_be32(count);
count++;
_FDT((fdt_setprop(fdt, event_sources, "interrupt-controller", NULL, 0)));
_FDT((fdt_setprop_cell(fdt, event_sources, "#interrupt-cells", 2)));
_FDT((fdt_setprop(fdt, event_sources, "interrupt-ranges",
irq_ranges, count * sizeof(uint32_t))));
}
static const sPAPREventSource *
rtas_event_log_to_source(sPAPRMachineState *spapr, int log_type)
{
const sPAPREventSource *source;
g_assert(spapr->event_sources);
switch (log_type) {
case RTAS_LOG_TYPE_HOTPLUG:
source = spapr_event_sources_get_source(spapr->event_sources,
EVENT_CLASS_HOT_PLUG);
if (spapr_ovec_test(spapr->ov5_cas, OV5_HP_EVT)) {
g_assert(source->enabled);
break;
}
/* fall back to epow for legacy hotplug interrupt source */
case RTAS_LOG_TYPE_EPOW:
source = spapr_event_sources_get_source(spapr->event_sources,
EVENT_CLASS_EPOW);
break;
default:
source = NULL;
}
return source;
}
static int rtas_event_log_to_irq(sPAPRMachineState *spapr, int log_type)
{
const sPAPREventSource *source;
source = rtas_event_log_to_source(spapr, log_type);
g_assert(source);
g_assert(source->enabled);
return source->irq;
}
static void rtas_event_log_queue(int log_type, void *data, bool exception)
@ -247,19 +353,15 @@ static sPAPREventLogEntry *rtas_event_log_dequeue(uint32_t event_mask,
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
sPAPREventLogEntry *entry = NULL;
/* we only queue EPOW events atm. */
if ((event_mask & EVENT_MASK_EPOW) == 0) {
return NULL;
}
QTAILQ_FOREACH(entry, &spapr->pending_events, next) {
const sPAPREventSource *source =
rtas_event_log_to_source(spapr, entry->log_type);
if (entry->exception != exception) {
continue;
}
/* EPOW and hotplug events are surfaced in the same manner */
if (entry->log_type == RTAS_LOG_TYPE_EPOW ||
entry->log_type == RTAS_LOG_TYPE_HOTPLUG) {
if (source->mask & event_mask) {
break;
}
}
@ -276,19 +378,15 @@ static bool rtas_event_log_contains(uint32_t event_mask, bool exception)
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
sPAPREventLogEntry *entry = NULL;
/* we only queue EPOW events atm. */
if ((event_mask & EVENT_MASK_EPOW) == 0) {
return false;
}
QTAILQ_FOREACH(entry, &spapr->pending_events, next) {
const sPAPREventSource *source =
rtas_event_log_to_source(spapr, entry->log_type);
if (entry->exception != exception) {
continue;
}
/* EPOW and hotplug events are surfaced in the same manner */
if (entry->log_type == RTAS_LOG_TYPE_EPOW ||
entry->log_type == RTAS_LOG_TYPE_HOTPLUG) {
if (source->mask & event_mask) {
return true;
}
}
@ -376,7 +474,9 @@ static void spapr_powerdown_req(Notifier *n, void *opaque)
rtas_event_log_queue(RTAS_LOG_TYPE_EPOW, new_epow, true);
qemu_irq_pulse(xics_get_qirq(spapr->xics, spapr->check_exception_irq));
qemu_irq_pulse(xics_get_qirq(spapr->xics,
rtas_event_log_to_irq(spapr,
RTAS_LOG_TYPE_EPOW)));
}
static void spapr_hotplug_set_signalled(uint32_t drc_index)
@ -458,7 +558,9 @@ static void spapr_hotplug_req_event(uint8_t hp_id, uint8_t hp_action,
rtas_event_log_queue(RTAS_LOG_TYPE_HOTPLUG, new_hp, true);
qemu_irq_pulse(xics_get_qirq(spapr->xics, spapr->check_exception_irq));
qemu_irq_pulse(xics_get_qirq(spapr->xics,
rtas_event_log_to_irq(spapr,
RTAS_LOG_TYPE_HOTPLUG)));
}
void spapr_hotplug_req_add_by_index(sPAPRDRConnector *drc)
@ -504,6 +606,7 @@ static void check_exception(PowerPCCPU *cpu, sPAPRMachineState *spapr,
uint64_t xinfo;
sPAPREventLogEntry *event;
struct rtas_error_log *hdr;
int i;
if ((nargs < 6) || (nargs > 7) || nret != 1) {
rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
@ -540,8 +643,14 @@ static void check_exception(PowerPCCPU *cpu, sPAPRMachineState *spapr,
* do the latter here, since our code relies on edge-triggered
* interrupts.
*/
if (rtas_event_log_contains(mask, true)) {
qemu_irq_pulse(xics_get_qirq(spapr->xics, spapr->check_exception_irq));
for (i = 0; i < EVENT_CLASS_MAX; i++) {
if (rtas_event_log_contains(EVENT_CLASS_MASK(i), true)) {
const sPAPREventSource *source =
spapr_event_sources_get_source(spapr->event_sources, i);
g_assert(source->enabled);
qemu_irq_pulse(xics_get_qirq(spapr->xics, source->irq));
}
}
return;
@ -593,8 +702,27 @@ out_no_events:
void spapr_events_init(sPAPRMachineState *spapr)
{
QTAILQ_INIT(&spapr->pending_events);
spapr->check_exception_irq = xics_spapr_alloc(spapr->xics, 0, false,
&error_fatal);
spapr->event_sources = spapr_event_sources_new();
spapr_event_sources_register(spapr->event_sources, EVENT_CLASS_EPOW,
xics_spapr_alloc(spapr->xics, 0, false,
&error_fatal));
/* NOTE: if machine supports modern/dedicated hotplug event source,
* we add it to the device-tree unconditionally. This means we may
* have cases where the source is enabled in QEMU, but unused by the
* guest because it does not support modern hotplug events, so we
* take care to rely on checking for negotiation of OV5_HP_EVT option
* before attempting to use it to signal events, rather than simply
* checking that it's enabled.
*/
if (spapr->use_hotplug_event_source) {
spapr_event_sources_register(spapr->event_sources, EVENT_CLASS_HOT_PLUG,
xics_spapr_alloc(spapr->xics, 0, false,
&error_fatal));
}
spapr->epow_notifier.notify = spapr_powerdown_req;
qemu_register_powerdown_notifier(&spapr->epow_notifier);
spapr_rtas_register(RTAS_CHECK_EXCEPTION, "check-exception",

View File

@ -13,6 +13,7 @@ struct sPAPRPHBState;
struct sPAPRNVRAM;
typedef struct sPAPRConfigureConnectorState sPAPRConfigureConnectorState;
typedef struct sPAPREventLogEntry sPAPREventLogEntry;
typedef struct sPAPREventSource sPAPREventSource;
#define HPTE64_V_HPTE_DIRTY 0x0000000000000040ULL
#define SPAPR_ENTRY_POINT 0x100
@ -77,9 +78,10 @@ struct sPAPRMachineState {
sPAPROptionVector *ov5_cas; /* negotiated (via CAS) option vectors */
bool cas_reboot;
uint32_t check_exception_irq;
Notifier epow_notifier;
QTAILQ_HEAD(, sPAPREventLogEntry) pending_events;
bool use_hotplug_event_source;
sPAPREventSource *event_sources;
/* Migration state */
int htab_save_index;
@ -584,7 +586,7 @@ struct sPAPREventLogEntry {
};
void spapr_events_init(sPAPRMachineState *sm);
void spapr_dt_events(void *fdt, uint32_t check_exception_irq);
void spapr_dt_events(sPAPRMachineState *sm, void *fdt);
int spapr_h_cas_compose_response(sPAPRMachineState *sm,
target_ulong addr, target_ulong size,
bool cpu_update,

View File

@ -45,6 +45,7 @@ typedef struct sPAPROptionVector sPAPROptionVector;
/* option vector 5 */
#define OV5_DRCONF_MEMORY OV_BIT(2, 2)
#define OV5_FORM1_AFFINITY OV_BIT(5, 0)
#define OV5_HP_EVT OV_BIT(6, 5)
/* interfaces */
sPAPROptionVector *spapr_ovec_new(void);