aboutsummaryrefslogtreecommitdiff
path: root/hw/s390x/tod-kvm.c
blob: 5da9037e0cb25b5f29b250d475c93494b161262e (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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*
 * TOD (Time Of Day) clock - KVM implementation
 *
 * Copyright 2018 Red Hat, Inc.
 * Author(s): David Hildenbrand <david@redhat.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "system/runstate.h"
#include "hw/s390x/tod.h"
#include "target/s390x/kvm/pv.h"
#include "kvm/kvm_s390x.h"

static void kvm_s390_get_tod_raw(S390TOD *tod, Error **errp)
{
    int r;

    r = kvm_s390_get_clock_ext(&tod->high, &tod->low);
    if (r == -ENXIO) {
        r = kvm_s390_get_clock(&tod->high, &tod->low);
    }
    if (r) {
        error_setg(errp, "Unable to get KVM guest TOD clock: %s",
                   strerror(-r));
    }
}

static void kvm_s390_tod_get(const S390TODState *td, S390TOD *tod, Error **errp)
{
    if (td->stopped) {
        *tod = td->base;
        return;
    }

    kvm_s390_get_tod_raw(tod, errp);
}

static void kvm_s390_set_tod_raw(const S390TOD *tod, Error **errp)
{
    int r;

    r = kvm_s390_set_clock_ext(tod->high, tod->low);
    if (r == -ENXIO) {
        r = kvm_s390_set_clock(tod->high, tod->low);
    }
    if (r) {
        error_setg(errp, "Unable to set KVM guest TOD clock: %s",
                   strerror(-r));
    }
}

static void kvm_s390_tod_set(S390TODState *td, const S390TOD *tod, Error **errp)
{
    Error *local_err = NULL;

    /*
     * Somebody (e.g. migration) set the TOD. We'll store it into KVM to
     * properly detect errors now but take a look at the runstate to decide
     * whether really to keep the tod running. E.g. during migration, this
     * is the point where we want to stop the initially running TOD to fire
     * it back up when actually starting the migrated guest.
     */
    kvm_s390_set_tod_raw(tod, &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }

    if (runstate_is_running()) {
        td->stopped = false;
    } else {
        td->stopped = true;
        td->base = *tod;
    }
}

static void kvm_s390_tod_vm_state_change(void *opaque, bool running,
                                         RunState state)
{
    S390TODState *td = opaque;
    Error *local_err = NULL;

    /*
     * Under PV, the clock is under ultravisor control, hence we cannot restore
     * it on resume.
     */
    if (s390_is_pv()) {
        return;
    }

    if (running && td->stopped) {
        /* Set the old TOD when running the VM - start the TOD clock. */
        kvm_s390_set_tod_raw(&td->base, &local_err);
        if (local_err) {
            warn_report_err(local_err);
        }
        /* Treat errors like the TOD was running all the time. */
        td->stopped = false;
    } else if (!running && !td->stopped) {
        /* Store the TOD when stopping the VM - stop the TOD clock. */
        kvm_s390_get_tod_raw(&td->base, &local_err);
        if (local_err) {
            /* Keep the TOD running in case we could not back it up. */
            warn_report_err(local_err);
        } else {
            td->stopped = true;
        }
    }
}

static void kvm_s390_tod_realize(DeviceState *dev, Error **errp)
{
    S390TODState *td = S390_TOD(dev);
    S390TODClass *tdc = S390_TOD_GET_CLASS(td);
    Error *local_err = NULL;

    tdc->parent_realize(dev, &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }

    /*
     * We need to know when the VM gets started/stopped to start/stop the TOD.
     * As we can never have more than one TOD instance (and that will never be
     * removed), registering here and never unregistering is good enough.
     */
    qemu_add_vm_change_state_handler(kvm_s390_tod_vm_state_change, td);
}

static void kvm_s390_tod_class_init(ObjectClass *oc, void *data)
{
    S390TODClass *tdc = S390_TOD_CLASS(oc);

    device_class_set_parent_realize(DEVICE_CLASS(oc), kvm_s390_tod_realize,
                                    &tdc->parent_realize);
    tdc->get = kvm_s390_tod_get;
    tdc->set = kvm_s390_tod_set;
}

static void kvm_s390_tod_init(Object *obj)
{
    S390TODState *td = S390_TOD(obj);

    /*
     * The TOD is initially running (value stored in KVM). Avoid needless
     * loading/storing of the TOD when starting a simple VM, so let it
     * run although the (never started) VM is stopped. For migration, we
     * will properly set the TOD later.
     */
    td->stopped = false;
}

static const TypeInfo kvm_s390_tod_info = {
    .name = TYPE_KVM_S390_TOD,
    .parent = TYPE_S390_TOD,
    .instance_size = sizeof(S390TODState),
    .instance_init = kvm_s390_tod_init,
    .class_init = kvm_s390_tod_class_init,
    .class_size = sizeof(S390TODClass),
};

static void register_types(void)
{
    type_register_static(&kvm_s390_tod_info);
}
type_init(register_types);