[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20210314041749-mutt-send-email-mst@kernel.org>
Date: Sun, 14 Mar 2021 04:26:57 -0400
From: "Michael S. Tsirkin" <mst@...hat.com>
To: Laurent Vivier <laurent@...ier.eu>
Cc: linux-kernel@...r.kernel.org,
virtualization@...ts.linux-foundation.org,
Jason Wang <jasowang@...hat.com>
Subject: Re: [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,
> 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
Powered by blists - more mailing lists