From: "Michael S. Tsirkin" <mst@redhat.com>
To: Laurent Vivier <laurent@vivier.eu>
Cc: linux-kernel@vger.kernel.org, virtualization@lists.linux-foundation.org
Subject: Re: [PATCH] virtio-mmio: read[wl]()/write[wl] are already little-endian
Date: Sun, 14 Mar 2021 04:26:57 -0400 [thread overview]
Message-ID: <20210314041749-mutt-send-email-mst@kernel.org> (raw)
In-Reply-To: <a8a0b68c-d36d-c675-3c6d-d4fca996fdfd@vivier.eu>
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
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
WARNING: multiple messages have this Message-ID (diff)
From: "Michael S. Tsirkin" <mst@redhat.com>
To: Laurent Vivier <laurent@vivier.eu>
Cc: linux-kernel@vger.kernel.org,
virtualization@lists.linux-foundation.org,
Jason Wang <jasowang@redhat.com>
Subject: Re: [PATCH] virtio-mmio: read[wl]()/write[wl] are already little-endian
Date: Sun, 14 Mar 2021 04:26:57 -0400 [thread overview]
Message-ID: <20210314041749-mutt-send-email-mst@kernel.org> (raw)
In-Reply-To: <a8a0b68c-d36d-c675-3c6d-d4fca996fdfd@vivier.eu>
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
next prev parent reply other threads:[~2021-03-14 8:27 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-03-09 22:43 [PATCH] virtio-mmio: read[wl]()/write[wl] are already little-endian Laurent Vivier
2021-03-10 3:08 ` kernel test robot
2021-03-10 3:08 ` kernel test robot
2021-03-10 3:08 ` kernel test robot
2021-03-11 15:44 ` Michael S. Tsirkin
2021-03-11 15:44 ` Michael S. Tsirkin
2021-03-11 15:55 ` Laurent Vivier
2021-03-13 17:10 ` Laurent Vivier
2021-03-14 8:26 ` Michael S. Tsirkin [this message]
2021-03-14 8:26 ` Michael S. Tsirkin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210314041749-mutt-send-email-mst@kernel.org \
--to=mst@redhat.com \
--cc=laurent@vivier.eu \
--cc=linux-kernel@vger.kernel.org \
--cc=virtualization@lists.linux-foundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.