summaryrefslogtreecommitdiffstats
path: root/qemu/hw/s390x/sclpquiesce.c
blob: ffa555313561f91a88b34175039619714e1fcb6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
 * SCLP event type
 *    Signal Quiesce - trigger system powerdown request
 *
 * Copyright IBM, Corp. 2012
 *
 * Authors:
 *  Heinz Graalfs <graalfs@de.ibm.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or (at your
 * option) any later version.  See the COPYING file in the top-level directory.
 *
 */
#include <hw/qdev.h>
#include "sysemu/sysemu.h"
#include "hw/s390x/sclp.h"
#include "hw/s390x/event-facility.h"

typedef struct SignalQuiesce {
    EventBufferHeader ebh;
    uint16_t timeout;
    uint8_t unit;
} QEMU_PACKED SignalQuiesce;

static bool can_handle_event(uint8_t type)
{
    return type == SCLP_EVENT_SIGNAL_QUIESCE;
}

static unsigned int send_mask(void)
{
    return SCLP_EVENT_MASK_SIGNAL_QUIESCE;
}

static unsigned int receive_mask(void)
{
    return 0;
}

static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
                           int *slen)
{
    SignalQuiesce *sq = (SignalQuiesce *) evt_buf_hdr;

    if (*slen < sizeof(SignalQuiesce)) {
        return 0;
    }

    if (!event->event_pending) {
        return 0;
    }
    event->event_pending = false;

    sq->ebh.length = cpu_to_be16(sizeof(SignalQuiesce));
    sq->ebh.type = SCLP_EVENT_SIGNAL_QUIESCE;
    sq->ebh.flags |= SCLP_EVENT_BUFFER_ACCEPTED;
    /*
     * system_powerdown does not have a timeout. Fortunately the
     * timeout value is currently ignored by Linux, anyway
     */
    sq->timeout = cpu_to_be16(0);
    sq->unit = cpu_to_be16(0);
    *slen -= sizeof(SignalQuiesce);

    return 1;
}

static const VMStateDescription vmstate_sclpquiesce = {
    .name = "sclpquiesce",
    .version_id = 0,
    .minimum_version_id = 0,
    .fields = (VMStateField[]) {
        VMSTATE_BOOL(event_pending, SCLPEvent),
        VMSTATE_END_OF_LIST()
     }
};

typedef struct QuiesceNotifier QuiesceNotifier;

static struct QuiesceNotifier {
    Notifier notifier;
    SCLPEvent *event;
} qn;

static void quiesce_powerdown_req(Notifier *n, void *opaque)
{
    QuiesceNotifier *qn = container_of(n, QuiesceNotifier, notifier);
    SCLPEvent *event = qn->event;

    event->event_pending = true;
    /* trigger SCLP read operation */
    sclp_service_interrupt(0);
}

static int quiesce_init(SCLPEvent *event)
{
    qn.notifier.notify = quiesce_powerdown_req;
    qn.event = event;

    qemu_register_powerdown_notifier(&qn.notifier);

    return 0;
}

static void quiesce_reset(DeviceState *dev)
{
   SCLPEvent *event = SCLP_EVENT(dev);

   event->event_pending = false;
}

static void quiesce_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    SCLPEventClass *k = SCLP_EVENT_CLASS(klass);

    dc->reset = quiesce_reset;
    dc->vmsd = &vmstate_sclpquiesce;
    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
    k->init = quiesce_init;

    k->get_send_mask = send_mask;
    k->get_receive_mask = receive_mask;
    k->can_handle_event = can_handle_event;
    k->read_event_data = read_event_data;
    k->write_event_data = NULL;
}

static const TypeInfo sclp_quiesce_info = {
    .name          = "sclpquiesce",
    .parent        = TYPE_SCLP_EVENT,
    .instance_size = sizeof(SCLPEvent),
    .class_init    = quiesce_class_init,
    .class_size    = sizeof(SCLPEventClass),
};

static void register_types(void)
{
    type_register_static(&sclp_quiesce_info);
}

type_init(register_types)