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:
		
							parent
							
								
									9f992cca93
								
							
						
					
					
						commit
						ffbb1705a3
					
				@ -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 */
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user