virtio-rng.c 5.9 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 21 22 23 24 25 26 27

static bool is_guest_ready(VirtIORNG *vrng)
{
    if (virtio_queue_ready(vrng->vq)
        && (vrng->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) {
        return true;
    }
    return false;
}

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

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

36 37
static void virtio_rng_process(VirtIORNG *vrng);

38 39 40 41
/* 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;
42
    VirtQueueElement elem;
43 44 45 46 47 48 49
    size_t len;
    int offset;

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

50 51
    vrng->quota_remaining -= size;

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

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

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

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

75 76 77 78 79 80
    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);
81
    size = MIN(vrng->quota_remaining, size);
82
    if (size) {
83 84 85 86
        rng_backend_request_entropy(vrng->rng, size, chr_read, vrng);
    }
}

87 88 89 90 91 92
static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
{
    VirtIORNG *vrng = DO_UPCAST(VirtIORNG, vdev, vdev);
    virtio_rng_process(vrng);
}

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
static uint32_t get_features(VirtIODevice *vdev, uint32_t f)
{
    return f;
}

static void virtio_rng_save(QEMUFile *f, void *opaque)
{
    VirtIORNG *vrng = opaque;

    virtio_save(&vrng->vdev, f);
}

static int virtio_rng_load(QEMUFile *f, void *opaque, int version_id)
{
    VirtIORNG *vrng = opaque;

    if (version_id != 1) {
        return -EINVAL;
    }
    virtio_load(&vrng->vdev, f);

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

120 121 122
    return 0;
}

123 124 125 126
static void check_rate_limit(void *opaque)
{
    VirtIORNG *s = opaque;

127
    s->quota_remaining = s->conf.max_bytes;
128 129
    virtio_rng_process(s);
    qemu_mod_timer(s->rate_limit_timer,
130
                   qemu_get_clock_ms(vm_clock) + s->conf.period_ms);
131 132
}

133
static int virtio_rng_device_init(VirtIODevice *vdev)
134
{
135 136
    DeviceState *qdev = DEVICE(vdev);
    VirtIORNG *vrng = VIRTIO_RNG(vdev);
137 138
    Error *local_err = NULL;

139 140 141 142 143 144 145 146 147 148 149
    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);
150
    }
151

152 153 154
    virtio_init(vdev, "virtio-rng", VIRTIO_ID_RNG, 0);

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

    rng_backend_open(vrng->rng, &local_err);
    if (local_err) {
        qerror_report_err(local_err);
        error_free(local_err);
164
        return -1;
165 166 167
    }

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

169 170
    vrng->vdev.get_features = get_features;

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

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

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

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

183 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
    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)