Michael S. Tsirkin
2021-Mar-14 08:26 UTC
[PATCH] virtio-mmio: read[wl]()/write[wl] are already little-endian
On Sat, Mar 13, 2021 at 06:10:29PM +0100, Laurent Vivier wrote:> Le 11/03/2021 ? 16:44, Michael S. Tsirkin a ?crit?: > > On Tue, Mar 09, 2021 at 11:43:13PM +0100, Laurent Vivier wrote: > >> read[wl]()/write[wl] already access memory in little-endian mode. > > > > But then they convert it to CPU right? We just convert it back ... > > In fact the problem is in QEMU. > > On a big-endian guest, the readw() returns a byte-swapped value, This means QEMU doesn't provide a > little-endian value. > > I found in QEMU virtio_mmio_read() provides a value with byte-swapped bytes. > > The problem comes from virtio_config_readX() functions that read the value using ldX_p accessors. > > Is it normal not to use the modern variant here if we are not in legacy mode? > > I think we should have something like this in virtio_mmio_read (and write): > > --- a/hw/virtio/virtio-mmio.c > +++ b/hw/virtio/virtio-mmio.c > @@ -112,15 +112,28 @@ static uint64_t virtio_mmio_read(void *opaque, hwaddr offset, unsigned size) > > if (offset >= VIRTIO_MMIO_CONFIG) { > offset -= VIRTIO_MMIO_CONFIG; > - switch (size) { > - case 1: > - return virtio_config_readb(vdev, offset); > - case 2: > - return virtio_config_readw(vdev, offset); > - case 4: > - return virtio_config_readl(vdev, offset); > - default: > - abort(); > + if (proxy->legacy) { > + switch (size) { > + case 1: > + return virtio_config_readb(vdev, offset); > + case 2: > + return virtio_config_readw(vdev, offset); > + case 4: > + return virtio_config_readl(vdev, offset); > + default: > + abort(); > + } > + } else { > + switch (size) { > + case 1: > + return virtio_config_modern_readb(vdev, offset); > + case 2: > + return virtio_config_modern_readw(vdev, offset); > + case 4: > + return virtio_config_modern_readl(vdev, offset); > + default: > + abort(); > + } > } > } > if (size != 4) {Sounds reasonable ...> And we need the same thing in virtio_pci_config_read() (and write).We already have it, see below.> And this could explain why it works with virtio-pci and not with virtio-mmio with the big-endian guest: > > with virtio-pci the bytes are swapped twice (once in virtio-mmio and then in virtio-pci), so they > are restored to the initial value, whereas with direct virtio-mmio they are swapped only once. > > Thanks, > Laurentvirtio pci does this: modern accesses use: virtio_pci_device_read static uint64_t virtio_pci_device_read(void *opaque, hwaddr addr, unsigned size) { VirtIOPCIProxy *proxy = opaque; VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus); uint64_t val = 0; if (vdev == NULL) { return val; } switch (size) { case 1: val = virtio_config_modern_readb(vdev, addr); break; case 2: val = virtio_config_modern_readw(vdev, addr); break; case 4: val = virtio_config_modern_readl(vdev, addr); break; } return val; } while legacy accesses use: virtio_pci_config_read static uint64_t virtio_pci_config_read(void *opaque, hwaddr addr, unsigned size) { VirtIOPCIProxy *proxy = opaque; VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus); uint32_t config = VIRTIO_PCI_CONFIG_SIZE(&proxy->pci_dev); uint64_t val = 0; if (addr < config) { return virtio_ioport_read(proxy, addr); } addr -= config; switch (size) { case 1: val = virtio_config_readb(vdev, addr); break; case 2: val = virtio_config_readw(vdev, addr); if (virtio_is_big_endian(vdev)) { val = bswap16(val); } break; case 4: val = virtio_config_readl(vdev, addr); if (virtio_is_big_endian(vdev)) { val = bswap32(val); } break; } return val; } the naming is configing but there you are. virtio_pci_config_read is also calling virtio_is_big_endian. static inline bool virtio_is_big_endian(VirtIODevice *vdev) { if (!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) { assert(vdev->device_endian != VIRTIO_DEVICE_ENDIAN_UNKNOWN); return vdev->device_endian == VIRTIO_DEVICE_ENDIAN_BIG; } /* Devices conforming to VIRTIO 1.0 or later are always LE. */ return false; } I note that virtio_is_big_endian is kind of wrong for modern config accesses since it checks guest feature bits - config accesses are done before features are acknowledged. Luckily this function is never called for a modern guest ... It would be nice to add virtio_legacy_is_big_endian and call that when we know it's a legacy interface. -- MST