Re: [PATCH] virtio-mmio: read[wl]()/write[wl] are already little-endian

From: Michael S. Tsirkin
Date: Sun Mar 14 2021 - 04:27:53 EST


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,
> Laurent

virtio 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