virtio-rng.c 6.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
/*
 * A virtio device implementing a hardware random number generator.
 *
 * Copyright 2012 Red Hat, Inc.
 * Copyright 2012 Amit Shah <amit.shah@redhat.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.
 */

12
#include "qemu/iov.h"
13
#include "hw/qdev.h"
14
#include "qapi/qmp/qerror.h"
P
Paolo Bonzini 已提交
15 16
#include "hw/virtio/virtio.h"
#include "hw/virtio/virtio-rng.h"
17
#include "sysemu/rng.h"
18 19 20

static bool is_guest_ready(VirtIORNG *vrng)
{
21
    VirtIODevice *vdev = VIRTIO_DEVICE(vrng);
22
    if (virtio_queue_ready(vrng->vq)
23
        && (vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
24 25 26 27 28
        return true;
    }
    return false;
}

29
static size_t get_request_size(VirtQueue *vq, unsigned quota)
30
{
31
    unsigned int in, out;
32

33
    virtqueue_get_avail_bytes(vq, &in, &out, quota, 0);
34
    return in;
35 36
}

37 38
static void virtio_rng_process(VirtIORNG *vrng);

39 40 41 42
/* Send data from a char device over to the guest */
static void chr_read(void *opaque, const void *buf, size_t size)
{
    VirtIORNG *vrng = opaque;
43
    VirtIODevice *vdev = VIRTIO_DEVICE(vrng);
44
    VirtQueueElement elem;
45 46 47 48 49 50 51
    size_t len;
    int offset;

    if (!is_guest_ready(vrng)) {
        return;
    }

52 53
    vrng->quota_remaining -= size;

54 55
    offset = 0;
    while (offset < size) {
56
        if (!virtqueue_pop(vrng->vq, &elem)) {
57 58
            break;
        }
59
        len = iov_from_buf(elem.in_sg, elem.in_num,
60 61 62
                           0, buf + offset, size - offset);
        offset += len;

63
        virtqueue_push(vrng->vq, &elem, len);
64
    }
65
    virtio_notify(vdev, vrng->vq);
66 67
}

68
static void virtio_rng_process(VirtIORNG *vrng)
69
{
70
    size_t size;
71
    unsigned quota;
72 73 74 75

    if (!is_guest_ready(vrng)) {
        return;
    }
76

77 78 79 80 81 82
    if (vrng->quota_remaining < 0) {
        quota = 0;
    } else {
        quota = MIN((uint64_t)vrng->quota_remaining, (uint64_t)UINT32_MAX);
    }
    size = get_request_size(vrng->vq, quota);
83
    size = MIN(vrng->quota_remaining, size);
84
    if (size) {
85 86 87 88
        rng_backend_request_entropy(vrng->rng, size, chr_read, vrng);
    }
}

89 90
static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
{
91
    VirtIORNG *vrng = VIRTIO_RNG(vdev);
92 93 94
    virtio_rng_process(vrng);
}

95 96 97 98 99 100 101
static uint32_t get_features(VirtIODevice *vdev, uint32_t f)
{
    return f;
}

static void virtio_rng_save(QEMUFile *f, void *opaque)
{
102
    VirtIODevice *vdev = opaque;
103

104
    virtio_save(vdev, f);
105 106 107 108 109
}

static int virtio_rng_load(QEMUFile *f, void *opaque, int version_id)
{
    VirtIORNG *vrng = opaque;
110
    VirtIODevice *vdev = VIRTIO_DEVICE(vrng);
111 112 113 114

    if (version_id != 1) {
        return -EINVAL;
    }
115
    virtio_load(vdev, f);
116

117
    /* We may have an element ready but couldn't process it due to a quota
A
Amit Shah 已提交
118 119 120
     * limit.  Make sure to try again after live migration when the quota may
     * have been reset.
     */
121 122
    virtio_rng_process(vrng);

123 124 125
    return 0;
}

126 127
static void check_rate_limit(void *opaque)
{
128
    VirtIORNG *vrng = opaque;
129

130 131 132 133
    vrng->quota_remaining = vrng->conf.max_bytes;
    virtio_rng_process(vrng);
    qemu_mod_timer(vrng->rate_limit_timer,
                   qemu_get_clock_ms(vm_clock) + vrng->conf.period_ms);
134 135
}

136
static int virtio_rng_device_init(VirtIODevice *vdev)
137
{
138 139
    DeviceState *qdev = DEVICE(vdev);
    VirtIORNG *vrng = VIRTIO_RNG(vdev);
140 141
    Error *local_err = NULL;

142 143 144 145 146 147 148 149 150 151 152
    if (vrng->conf.rng == NULL) {
        vrng->conf.default_backend = RNG_RANDOM(object_new(TYPE_RNG_RANDOM));

        object_property_add_child(OBJECT(qdev),
                                  "default-backend",
                                  OBJECT(vrng->conf.default_backend),
                                  NULL);

        object_property_set_link(OBJECT(qdev),
                                 OBJECT(vrng->conf.default_backend),
                                 "rng", NULL);
153
    }
154

155 156 157
    virtio_init(vdev, "virtio-rng", VIRTIO_ID_RNG, 0);

    vrng->rng = vrng->conf.rng;
158 159
    if (vrng->rng == NULL) {
        qerror_report(QERR_INVALID_PARAMETER_VALUE, "rng", "a valid object");
160
        return -1;
161 162 163 164 165 166
    }

    rng_backend_open(vrng->rng, &local_err);
    if (local_err) {
        qerror_report_err(local_err);
        error_free(local_err);
167
        return -1;
168 169 170
    }

    vrng->vq = virtio_add_queue(vdev, 8, handle_input);
171

172 173
    assert(vrng->conf.max_bytes <= INT64_MAX);
    vrng->quota_remaining = vrng->conf.max_bytes;
174 175 176 177 178

    vrng->rate_limit_timer = qemu_new_timer_ms(vm_clock,
                                               check_rate_limit, vrng);

    qemu_mod_timer(vrng->rate_limit_timer,
179
                   qemu_get_clock_ms(vm_clock) + vrng->conf.period_ms);
180

181
    register_savevm(qdev, "virtio-rng", -1, 1, virtio_rng_save,
182 183
                    virtio_rng_load, vrng);

184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
    return 0;
}

static int virtio_rng_device_exit(DeviceState *qdev)
{
    VirtIORNG *vrng = VIRTIO_RNG(qdev);
    VirtIODevice *vdev = VIRTIO_DEVICE(qdev);

    qemu_del_timer(vrng->rate_limit_timer);
    qemu_free_timer(vrng->rate_limit_timer);
    unregister_savevm(qdev, "virtio-rng", vrng);
    virtio_common_cleanup(vdev);
    return 0;
}

static Property virtio_rng_properties[] = {
    DEFINE_VIRTIO_RNG_PROPERTIES(VirtIORNG, conf),
    DEFINE_PROP_END_OF_LIST(),
};

static void virtio_rng_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
    dc->exit = virtio_rng_device_exit;
    dc->props = virtio_rng_properties;
    vdc->init = virtio_rng_device_init;
    vdc->get_features = get_features;
}

static void virtio_rng_initfn(Object *obj)
{
    VirtIORNG *vrng = VIRTIO_RNG(obj);

    object_property_add_link(obj, "rng", TYPE_RNG_BACKEND,
                             (Object **)&vrng->conf.rng, NULL);
}

static const TypeInfo virtio_rng_info = {
    .name = TYPE_VIRTIO_RNG,
    .parent = TYPE_VIRTIO_DEVICE,
    .instance_size = sizeof(VirtIORNG),
    .instance_init = virtio_rng_initfn,
    .class_init = virtio_rng_class_init,
};

static void virtio_register_types(void)
{
    type_register_static(&virtio_rng_info);
}

type_init(virtio_register_types)