From 616a655219a92ae7cf5d6a7862e6489c6282009e Mon Sep 17 00:00:00 2001 From: Greg Kurz Date: Tue, 24 Jun 2014 19:38:54 +0200 Subject: virtio: add endian-ambivalent support to VirtIODevice Some CPU families can dynamically change their endianness. This means we can have little endian ppc or big endian arm guests for example. This has an impact on legacy virtio data structures since they are target endian. We hence introduce a new property to track the endianness of each virtio device. It is reasonnably assumed that endianness won't change while the device is in use : we hence capture the device endianness when it gets reset. We migrate this property in a subsection, after the device descriptor. This means the load code must not rely on it until it is restored. As a consequence, the vring sanity checks had to be moved after the call to vmstate_load_state(). We enforce paranoia by poisoning the property at the begining of virtio_load(). Signed-off-by: Greg Kurz Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin --- hw/virtio/virtio.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 12 deletions(-) (limited to 'hw/virtio/virtio.c') diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 7b317ce279..a0676e06af 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -545,6 +545,27 @@ void virtio_set_status(VirtIODevice *vdev, uint8_t val) vdev->status = val; } +bool target_words_bigendian(void); +static enum virtio_device_endian virtio_default_endian(void) +{ + if (target_words_bigendian()) { + return VIRTIO_DEVICE_ENDIAN_BIG; + } else { + return VIRTIO_DEVICE_ENDIAN_LITTLE; + } +} + +static enum virtio_device_endian virtio_current_cpu_endian(void) +{ + CPUClass *cc = CPU_GET_CLASS(current_cpu); + + if (cc->virtio_is_big_endian(current_cpu)) { + return VIRTIO_DEVICE_ENDIAN_BIG; + } else { + return VIRTIO_DEVICE_ENDIAN_LITTLE; + } +} + void virtio_reset(void *opaque) { VirtIODevice *vdev = opaque; @@ -552,6 +573,13 @@ void virtio_reset(void *opaque) int i; virtio_set_status(vdev, 0); + if (current_cpu) { + /* Guest initiated reset */ + vdev->device_endian = virtio_current_cpu_endian(); + } else { + /* System reset */ + vdev->device_endian = virtio_default_endian(); + } if (k->reset) { k->reset(vdev); @@ -840,6 +868,24 @@ void virtio_notify_config(VirtIODevice *vdev) virtio_notify_vector(vdev, vdev->config_vector); } +static bool virtio_device_endian_needed(void *opaque) +{ + VirtIODevice *vdev = opaque; + + assert(vdev->device_endian != VIRTIO_DEVICE_ENDIAN_UNKNOWN); + return vdev->device_endian != virtio_default_endian(); +} + +static const VMStateDescription vmstate_virtio_device_endian = { + .name = "virtio/device_endian", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(device_endian, VirtIODevice), + VMSTATE_END_OF_LIST() + } +}; + static const VMStateDescription vmstate_virtio = { .name = "virtio", .version_id = 1, @@ -847,6 +893,13 @@ static const VMStateDescription vmstate_virtio = { .minimum_version_id_old = 1, .fields = (VMStateField[]) { VMSTATE_END_OF_LIST() + }, + .subsections = (VMStateSubsection[]) { + { + .vmsd = &vmstate_virtio_device_endian, + .needed = &virtio_device_endian_needed + }, + { 0 } } }; @@ -925,6 +978,12 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id) VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev); + /* + * We poison the endianness to ensure it does not get used before + * subsections have been loaded. + */ + vdev->device_endian = VIRTIO_DEVICE_ENDIAN_UNKNOWN; + if (k->load_config) { ret = k->load_config(qbus->parent, f); if (ret) @@ -977,18 +1036,7 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id) vdev->vq[i].notification = true; if (vdev->vq[i].pa) { - uint16_t nheads; virtqueue_init(&vdev->vq[i]); - nheads = vring_avail_idx(&vdev->vq[i]) - vdev->vq[i].last_avail_idx; - /* Check it isn't doing very strange things with descriptor numbers. */ - if (nheads > vdev->vq[i].vring.num) { - error_report("VQ %d size 0x%x Guest index 0x%x " - "inconsistent with Host index 0x%x: delta 0x%x", - i, vdev->vq[i].vring.num, - vring_avail_idx(&vdev->vq[i]), - vdev->vq[i].last_avail_idx, nheads); - return -1; - } } else if (vdev->vq[i].last_avail_idx) { error_report("VQ %d address 0x0 " "inconsistent with Host index 0x%x", @@ -1011,7 +1059,33 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id) } } - return vmstate_load_state(f, &vmstate_virtio, vdev, 1); + /* Subsections */ + ret = vmstate_load_state(f, &vmstate_virtio, vdev, 1); + if (ret) { + return ret; + } + + if (vdev->device_endian == VIRTIO_DEVICE_ENDIAN_UNKNOWN) { + vdev->device_endian = virtio_default_endian(); + } + + for (i = 0; i < num; i++) { + if (vdev->vq[i].pa) { + uint16_t nheads; + nheads = vring_avail_idx(&vdev->vq[i]) - vdev->vq[i].last_avail_idx; + /* Check it isn't doing strange things with descriptor numbers. */ + if (nheads > vdev->vq[i].vring.num) { + error_report("VQ %d size 0x%x Guest index 0x%x " + "inconsistent with Host index 0x%x: delta 0x%x", + i, vdev->vq[i].vring.num, + vring_avail_idx(&vdev->vq[i]), + vdev->vq[i].last_avail_idx, nheads); + return -1; + } + } + } + + return 0; } void virtio_cleanup(VirtIODevice *vdev) @@ -1068,6 +1142,7 @@ void virtio_init(VirtIODevice *vdev, const char *name, } vdev->vmstate = qemu_add_vm_change_state_handler(virtio_vmstate_change, vdev); + vdev->device_endian = virtio_default_endian(); } hwaddr virtio_queue_get_desc_addr(VirtIODevice *vdev, int n) -- cgit v1.2.3