virtio_mmio: Support non-transitional version 2 devices

The non-legacy virtio MMIO specification drops the use of PFNs and
replaces them with physical addresses. Whilst many implementations are
so-called transitional devices, also implementing the legacy
specification, TinyEMU[1] does not. Device-specific configuration
registers have also changed to being little-endian, and must be accessed
using a single aligned access for registers up to 32 bits, and two
32-bit aligned accesses for 64-bit registers.

[1] https://bellard.org/tinyemu/

Reviewed by:	br, brooks (mentor)
Approved by:	br, brooks (mentor)
Differential Revision:	https://reviews.freebsd.org/D24681
This commit is contained in:
Jessica Clarke 2020-05-06 23:28:51 +00:00
parent 883a2dc672
commit 046096d5c7
2 changed files with 146 additions and 22 deletions

View File

@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$");
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/endian.h>
#include <machine/bus.h>
#include <machine/resource.h>
@ -76,6 +77,8 @@ static int vtmmio_read_ivar(device_t, device_t, int, uintptr_t *);
static int vtmmio_write_ivar(device_t, device_t, int, uintptr_t);
static uint64_t vtmmio_negotiate_features(device_t, uint64_t);
static int vtmmio_with_feature(device_t, uint64_t);
static void vtmmio_set_virtqueue(struct vtmmio_softc *sc,
struct virtqueue *vq, uint32_t size);
static int vtmmio_alloc_virtqueues(device_t, int, int,
struct vq_alloc_info *);
static int vtmmio_setup_intr(device_t, enum intr_type);
@ -223,6 +226,16 @@ vtmmio_attach(device_t dev)
return (ENXIO);
}
sc->vtmmio_version = vtmmio_read_config_4(sc, VIRTIO_MMIO_VERSION);
if (sc->vtmmio_version < 1 || sc->vtmmio_version > 2) {
device_printf(dev, "Unsupported version: %x\n",
sc->vtmmio_version);
bus_release_resource(dev, SYS_RES_MEMORY, 0,
sc->res[0]);
sc->res[0] = NULL;
return (ENXIO);
}
vtmmio_reset(sc);
/* Tell the host we've noticed this device. */
@ -404,6 +417,46 @@ vtmmio_with_feature(device_t dev, uint64_t feature)
return ((sc->vtmmio_features & feature) != 0);
}
static void
vtmmio_set_virtqueue(struct vtmmio_softc *sc, struct virtqueue *vq,
uint32_t size)
{
vm_paddr_t paddr;
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_NUM, size);
#if 0
device_printf(dev, "virtqueue paddr 0x%08lx\n",
(uint64_t)paddr);
#endif
if (sc->vtmmio_version == 1) {
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_ALIGN,
VIRTIO_MMIO_VRING_ALIGN);
paddr = virtqueue_paddr(vq);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN,
paddr >> PAGE_SHIFT);
} else {
paddr = virtqueue_desc_paddr(vq);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_DESC_LOW,
paddr);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_DESC_HIGH,
paddr >> 32);
paddr = virtqueue_avail_paddr(vq);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_AVAIL_LOW,
paddr);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_AVAIL_HIGH,
paddr >> 32);
paddr = virtqueue_used_paddr(vq);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_USED_LOW,
paddr);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_USED_HIGH,
paddr >> 32);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_READY, 1);
}
}
static int
vtmmio_alloc_virtqueues(device_t dev, int flags, int nvqs,
struct vq_alloc_info *vq_info)
@ -448,15 +501,7 @@ vtmmio_alloc_virtqueues(device_t dev, int flags, int nvqs,
break;
}
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_NUM, size);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_ALIGN,
VIRTIO_MMIO_VRING_ALIGN);
#if 0
device_printf(dev, "virtqueue paddr 0x%08lx\n",
(uint64_t)virtqueue_paddr(vq));
#endif
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN,
virtqueue_paddr(vq) >> PAGE_SHIFT);
vtmmio_set_virtqueue(sc, vq, size);
vqx->vtv_vq = *info->vqai_vq = vq;
vqx->vtv_no_intr = info->vqai_intr == NULL;
@ -568,10 +613,54 @@ vtmmio_read_dev_config(device_t dev, bus_size_t offset,
bus_size_t off;
uint8_t *d;
int size;
uint64_t low32, high32;
sc = device_get_softc(dev);
off = VIRTIO_MMIO_CONFIG + offset;
/*
* The non-legacy MMIO specification adds the following restriction:
*
* 4.2.2.2: For the device-specific configuration space, the driver
* MUST use 8 bit wide accesses for 8 bit wide fields, 16 bit wide
* and aligned accesses for 16 bit wide fields and 32 bit wide and
* aligned accesses for 32 and 64 bit wide fields.
*
* The endianness also varies between non-legacy and legacy:
*
* 2.4: Note: The device configuration space uses the little-endian
* format for multi-byte fields.
*
* 2.4.3: Note that for legacy interfaces, device configuration space
* is generally the guests native endian, rather than PCIs
* little-endian. The correct endian-ness is documented for each
* device.
*/
if (sc->vtmmio_version > 1) {
switch (length) {
case 1:
*(uint8_t *)dst = vtmmio_read_config_1(sc, off);
break;
case 2:
*(uint16_t *)dst =
le16toh(vtmmio_read_config_2(sc, off));
break;
case 4:
*(uint32_t *)dst =
le32toh(vtmmio_read_config_4(sc, off));
break;
case 8:
low32 = le32toh(vtmmio_read_config_4(sc, off));
high32 = le32toh(vtmmio_read_config_4(sc, off + 4));
*(uint64_t *)dst = (high32 << 32) | low32;
break;
default:
panic("%s: invalid length %d\n", __func__, length);
}
return;
}
for (d = dst; length > 0; d += size, off += size, length -= size) {
#ifdef ALLOW_WORD_ALIGNED_ACCESS
if (length >= 4) {
@ -601,6 +690,37 @@ vtmmio_write_dev_config(device_t dev, bus_size_t offset,
sc = device_get_softc(dev);
off = VIRTIO_MMIO_CONFIG + offset;
/*
* The non-legacy MMIO specification adds size and alignment
* restrctions. It also changes the endianness from native-endian to
* little-endian. See vtmmio_read_dev_config.
*/
if (sc->vtmmio_version > 1) {
switch (length) {
case 1:
vtmmio_write_config_1(sc, off, *(uint8_t *)src);
break;
case 2:
vtmmio_write_config_2(sc, off,
htole16(*(uint16_t *)src));
break;
case 4:
vtmmio_write_config_4(sc, off,
htole32(*(uint32_t *)src));
break;
case 8:
vtmmio_write_config_4(sc, off,
htole32(*(uint64_t *)src));
vtmmio_write_config_4(sc, off + 4,
htole32((*(uint64_t *)src) >> 32));
break;
default:
panic("%s: invalid length %d\n", __func__, length);
}
return;
}
for (s = src; length > 0; s += size, off += size, length -= size) {
#ifdef ALLOW_WORD_ALIGNED_ACCESS
if (length >= 4) {
@ -685,15 +805,7 @@ vtmmio_reinit_virtqueue(struct vtmmio_softc *sc, int idx)
if (error)
return (error);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_NUM, size);
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_ALIGN,
VIRTIO_MMIO_VRING_ALIGN);
#if 0
device_printf(sc->dev, "virtqueue paddr 0x%08lx\n",
(uint64_t)virtqueue_paddr(vq));
#endif
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN,
virtqueue_paddr(vq) >> PAGE_SHIFT);
vtmmio_set_virtqueue(sc, vq, size);
return (0);
}
@ -719,7 +831,10 @@ vtmmio_free_virtqueues(struct vtmmio_softc *sc)
vqx = &sc->vtmmio_vqs[idx];
vtmmio_select_virtqueue(sc, idx);
if (sc->vtmmio_version == 1)
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_PFN, 0);
else
vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_READY, 0);
virtqueue_free(vqx->vtv_vq);
vqx->vtv_vq = NULL;

View File

@ -44,6 +44,7 @@ struct vtmmio_softc {
uint64_t vtmmio_features;
uint32_t vtmmio_flags;
uint32_t vtmmio_version;
/* This "bus" will only ever have one child. */
device_t vtmmio_child_dev;
@ -64,16 +65,24 @@ int vtmmio_attach(device_t);
#define VIRTIO_MMIO_HOST_FEATURES_SEL 0x014
#define VIRTIO_MMIO_GUEST_FEATURES 0x020
#define VIRTIO_MMIO_GUEST_FEATURES_SEL 0x024
#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028
#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 /* version 1 only */
#define VIRTIO_MMIO_QUEUE_SEL 0x030
#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034
#define VIRTIO_MMIO_QUEUE_NUM 0x038
#define VIRTIO_MMIO_QUEUE_ALIGN 0x03c
#define VIRTIO_MMIO_QUEUE_PFN 0x040
#define VIRTIO_MMIO_QUEUE_ALIGN 0x03c /* version 1 only */
#define VIRTIO_MMIO_QUEUE_PFN 0x040 /* version 1 only */
#define VIRTIO_MMIO_QUEUE_READY 0x044 /* requires version 2 */
#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050
#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060
#define VIRTIO_MMIO_INTERRUPT_ACK 0x064
#define VIRTIO_MMIO_STATUS 0x070
#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 /* requires version 2 */
#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 /* requires version 2 */
#define VIRTIO_MMIO_QUEUE_AVAIL_LOW 0x090 /* requires version 2 */
#define VIRTIO_MMIO_QUEUE_AVAIL_HIGH 0x094 /* requires version 2 */
#define VIRTIO_MMIO_QUEUE_USED_LOW 0x0a0 /* requires version 2 */
#define VIRTIO_MMIO_QUEUE_USED_HIGH 0x0a4 /* requires version 2 */
#define VIRTIO_MMIO_CONFIG_GENERATION 0x100 /* requires version 2 */
#define VIRTIO_MMIO_CONFIG 0x100
#define VIRTIO_MMIO_INT_VRING (1 << 0)
#define VIRTIO_MMIO_INT_CONFIG (1 << 1)