Thread (1 message) 1 message, 1 author, 2021-03-14

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

From: "Michael S. Tsirkin" <mst@redhat.com>
Date: 2021-03-14 08:27:48
Also in: lkml

On Sat, Mar 13, 2021 at 06:10:29PM +0100, Laurent Vivier wrote:
quoted hunk
Le 11/03/2021 à 16:44, Michael S. Tsirkin a écrit :
quoted
On Tue, Mar 09, 2021 at 11:43:13PM +0100, Laurent Vivier wrote:
quoted
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

_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help