virtio_mmio: unify device and driver interfaces into a

single header file.

Signed-off-by: Felipe Neves <felipe.neves@linaro.org>
This commit is contained in:
Felipe Neves
2023-12-08 14:28:10 -03:00
committed by Arnaud Pouliquen
parent ead7ad8bb0
commit a76b28b248
3 changed files with 169 additions and 183 deletions

View File

@@ -8,6 +8,7 @@
#ifndef OPENAMP_VIRTIO_MMIO_H
#define OPENAMP_VIRTIO_MMIO_H
#include <metal/utilities.h>
#include <metal/device.h>
#include <openamp/virtio.h>
#include <openamp/virtqueue.h>
@@ -167,6 +168,73 @@ struct virtio_mmio_device {
void *user_data;
};
/**
* @brief This is called when the driver side should be notifyed
*
* @param dev The device that need to notify the other side
*/
typedef void (*virtio_mmio_notify)(struct virtio_device *dev);
#ifdef WITH_VIRTIO_MMIO_DEV
/**
* @brief Define an Empty MMIO register table with only the strict necessary
* for a driver to recognise the device
*
* @note The initial state NOT_READY is the current approach to block the
* device side usage from driver until it gets properly configured.
*/
#define EMPTY_MMIO_TABLE { \
.magic = VIRTIO_MMIO_MAGIC_VALUE_STRING, \
.version = 2, \
.status = VIRTIO_CONFIG_STATUS_NOT_READY, \
}
/** @brief MMIO Device Registers: 256 bytes in practice */
struct virtio_mmio_dev_table {
/* 0x00 R should be 0x74726976 */
uint32_t magic;
/* 0x04 R */
uint32_t version;
/* padding */
uint32_t padding[26];
/* 0x70 RW Writing non-zero values to this register sets the status flags,
* indicating the driver progress.
* Writing zero (0x0) to this register triggers a device reset.
*/
uint32_t status;
};
#endif
/** @brief VirtIO mmio dev instance, should be init with mmio_dev_init */
struct virtio_mmio_dev {
/** VirtIO device instance */
struct virtio_device vdev;
/** Number of descriptors per ring */
int vring_size;
/** Array of virtqueues */
struct virtqueue *vqs;
/** Metal IO Region used to access the MMIO registers */
struct metal_io_region *io;
/** Called when an interrupt should be sent to the other side */
virtio_mmio_notify notify;
/** The features supported by this device */
uint64_t device_features;
/** The features supported by the driver from the other side */
uint64_t driver_features;
};
/**
* @brief Register a VIRTIO device with the VIRTIO stack.
*
@@ -214,6 +282,24 @@ int virtio_mmio_device_init(struct virtio_mmio_device *vmdev, uintptr_t virt_mem
*/
void virtio_mmio_isr(struct virtio_device *vdev);
/**
* @brief This should be called to initialize a virtio mmio device,
* the configure function should be called next by the device driver
*
* @param dev The device to initialize
* @param io The memory region in wich the device should operate
* @param callback The callback that will be called when the other side should be notifyed
*/
void virtio_mmio_dev_init(struct virtio_mmio_dev *dev, struct metal_io_region *io, virtio_mmio_notify callback);
/**
* @brief Should be called by the app when it receive an interrupt for the mmio device
*
* @param dev The virtio mmio device
*/
void virtio_mmio_dev_interrupt(struct virtio_mmio_dev *dev);
#ifdef __cplusplus
}
#endif

View File

@@ -1,107 +0,0 @@
/*
* Copyright (c) 2023, STMicroelectronics
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef OPENAMP_VIRTIO_MMIO_DEV_H
#define OPENAMP_VIRTIO_MMIO_DEV_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <metal/utilities.h>
#include <metal/device.h>
#include <openamp/virtio_mmio.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Define an Empty MMIO register table with only the strict necessary
* for a driver to recognise the device
*
* @note The initial state NOT_READY is the current approach to block the
* device side usage from driver until it gets properly configured.
*/
#define EMPTY_MMIO_TABLE { \
.magic = VIRTIO_MMIO_MAGIC_VALUE_STRING, \
.version = 2, \
.status = VIRTIO_CONFIG_STATUS_NOT_READY, \
}
/**
* @brief MMIO Device Registers: 256 bytes in practice, more if the configuration space is used.
* This is a trimmed down version with only the essential values needed to be detected correctly.
*/
struct mmio_table {
/* 0x00 R should be 0x74726976 */
uint32_t magic;
/* 0x04 R */
uint32_t version;
/* padding */
uint32_t padding[26];
/* 0x70 RW Writing non-zero values to this register sets the status flags,
* indicating the driver progress.
* Writing zero (0x0) to this register triggers a device reset.
*/
uint32_t status;
};
/**
* @brief This is called when the other side should be notifyed
*
* @param dev The device that need to notify the other side
*/
typedef void (*virtio_notify)(struct virtio_device *dev);
/**
* @brief VirtIO mmio dev instance, should be init with mmio_dev_init,
* then the VirtIO driver should set it's data using mmio_dev_set_device_data
*
* @param vdev VirtIO device instance
* @param vring_size Number of descriptors per ring
* @param vqs Array of virtqueues
* @param io Metal IO Region used to access the MMIO registers
* @param notify Called when an interrupt should be sent to the other side
* @param device_features The features supported by this device
* @param driver_features The features supported by the driver from the other side
*/
struct virtio_mmio_dev {
struct virtio_device vdev;
int vring_size;
struct virtqueue *vqs;
struct metal_io_region *io;
virtio_notify notify;
uint64_t device_features;
uint64_t driver_features;
};
/**
* @brief This should be called to initialize a virtio mmio device,
* the configure function should be called next by the device driver
*
* @param dev The device to initialize
* @param io The memory region in wich the device should operate
* @param callback The callback that will be called when the other side should be notifyed
*/
void mmio_dev_init(struct virtio_mmio_dev *dev, struct metal_io_region *io, virtio_notify callback);
/**
* @brief Should be called by the app when it receive an interrupt for the mmio device
*
* @param dev The virtio mmio device
*/
void mmio_dev_interrupt(struct virtio_mmio_dev *dev);
#ifdef __cplusplus
}
#endif
#endif /* OPENAMP_VIRTIO_MMIO_DEV_H */

View File

@@ -5,19 +5,19 @@
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <openamp/virtio_mmio_dev.h>
#include <openamp/virtio_mmio.h>
static inline uint32_t read_reg(struct virtio_mmio_dev *dev, uint32_t address)
static inline uint32_t virtio_mmio_dev_read_reg(struct virtio_mmio_dev *dev, uint32_t address)
{
return metal_io_read32(dev->io, address);
}
static inline void write_reg(struct virtio_mmio_dev *dev, uint32_t address, uint32_t value)
static inline void virtio_mmio_dev_write_reg(struct virtio_mmio_dev *dev, uint32_t address, uint32_t value)
{
metal_io_write32(dev->io, address, value);
}
static void mmio_dev_notify_other_side(struct virtqueue *vq)
static void virtio_mmio_dev_notify_driver(struct virtqueue *vq)
{
struct virtio_mmio_dev *dev = metal_container_of(vq->vq_dev, struct virtio_mmio_dev, vdev);
@@ -25,12 +25,12 @@ static void mmio_dev_notify_other_side(struct virtqueue *vq)
return;
/* we set the interrupt status to 1 to tell that a buffer was used */
write_reg(dev, VIRTIO_MMIO_INTERRUPT_STATUS, 1);
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_INTERRUPT_STATUS, 1);
dev->notify(&dev->vdev);
}
static int mmio_dev_create_virtqueues(struct virtio_device *vdev, unsigned int flags,
static int virtio_mmio_dev_create_virtqueues(struct virtio_device *vdev, unsigned int flags,
unsigned int nvqs, const char **names, vq_callback *callbacks)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
@@ -50,7 +50,7 @@ static int mmio_dev_create_virtqueues(struct virtio_device *vdev, unsigned int f
/* we set all infos needed by the virtqueue */
vring_info->vq->callback = callbacks[i];
vring_info->vq->vq_name = names[i];
vring_info->vq->notify = mmio_dev_notify_other_side;
vring_info->vq->notify = virtio_mmio_dev_notify_driver;
}
return 0;
@@ -59,7 +59,7 @@ static int mmio_dev_create_virtqueues(struct virtio_device *vdev, unsigned int f
/* Since the virtqueues are not actually instantiated in the create virtqueues function,
* maybe we should free the allocs from configure device ?
*/
static void mmio_dev_delete_virtqueues(struct virtio_device *vdev)
static void virtio_mmio_dev_delete_virtqueues(struct virtio_device *vdev)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
@@ -67,7 +67,7 @@ static void mmio_dev_delete_virtqueues(struct virtio_device *vdev)
return;
/* we are deleting virtqueues so we reset the device at the same time */
write_reg(dev, VIRTIO_MMIO_STATUS, VIRTIO_CONFIG_STATUS_NOT_READY);
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_STATUS, VIRTIO_CONFIG_STATUS_NOT_READY);
vdev->vrings_num = 0;
dev->vring_size = 0;
@@ -75,7 +75,7 @@ static void mmio_dev_delete_virtqueues(struct virtio_device *vdev)
metal_free_memory(vdev->vrings_info);
}
static int mmio_dev_configure_device(struct virtio_device *vdev, uint64_t device_features,
static int virtio_mmio_dev_configure_device(struct virtio_device *vdev, uint64_t device_features,
uint32_t device_type, int nvqs, int vring_size)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
@@ -90,7 +90,8 @@ static int mmio_dev_configure_device(struct virtio_device *vdev, uint64_t device
if (!dev->vqs)
return ret;
vdev->vrings_info = metal_allocate_memory(nvqs * sizeof(struct virtio_vring_info));
if (!vdev->vrings_info)
vdev->vrings_info = metal_allocate_memory(nvqs * sizeof(struct virtio_vring_info));
if (!vdev->vrings_info)
goto free_vqs;
@@ -101,15 +102,15 @@ static int mmio_dev_configure_device(struct virtio_device *vdev, uint64_t device
vdev->id.device = device_type;
/* tell the other side the max number of vring allowed */
write_reg(dev, VIRTIO_MMIO_QUEUE_NUM_MAX, vring_size);
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_QUEUE_NUM_MAX, vring_size);
/* Set the type of driver needed by the guest */
write_reg(dev->io, VIRTIO_MMIO_DEVICE_ID, device_type);
virtio_mmio_dev_write_reg(dev->io, VIRTIO_MMIO_DEVICE_ID, device_type);
/* Allow the other side to init the mmio device */
write_reg(dev, VIRTIO_MMIO_DEVICE_ID, device_type);
write_reg(dev, VIRTIO_MMIO_STATUS,
(uint32_t)read_reg(dev, VIRTIO_MMIO_STATUS) & ~VIRTIO_CONFIG_STATUS_NOT_READY);
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_DEVICE_ID, device_type);
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_STATUS,
(uint32_t)virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_STATUS) & ~VIRTIO_CONFIG_STATUS_NOT_READY);
return 0;
@@ -118,108 +119,102 @@ free_vqs:
return ret;
}
static uint8_t mmio_dev_get_status(struct virtio_device *vdev)
static uint8_t virtio_mmio_dev_get_status(struct virtio_device *vdev)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
return (uint8_t)read_reg(dev, VIRTIO_MMIO_STATUS);
return (uint8_t)virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_STATUS);
}
static void mmio_dev_set_status(struct virtio_device *vdev, uint8_t status)
static void virtio_mmio_dev_set_status(struct virtio_device *vdev, uint8_t status)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
write_reg(dev, VIRTIO_MMIO_STATUS, status);
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_STATUS, status);
}
static uint64_t mmio_dev_get_features(struct virtio_device *vdev)
static uint64_t virtio_mmio_dev_get_features(struct virtio_device *vdev)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
return dev->device_features;
}
static void mmio_dev_set_features(struct virtio_device *vdev, uint32_t feature)
static void virtio_mmio_dev_set_features(struct virtio_device *vdev, uint32_t feature)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
dev->device_features = feature;
}
static void mmio_dev_read_config(struct virtio_device *vdev, uint32_t offset, void *dst,
static void virtio_mmio_dev_read_config(struct virtio_device *vdev, uint32_t offset, void *dst,
int length)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
uint8_t *buf = dst;
/* TODO: replace to metal_io_block_read, it also need a sanity check regarding
* the offset and length of block read
*/
for (int i = 0; i < length; i++)
buf[i] = metal_io_read8(dev->io, VIRTIO_MMIO_CONFIG + i);
}
static void mmio_dev_write_config(struct virtio_device *vdev, uint32_t offset, void *src,
static void virtio_mmio_dev_write_config(struct virtio_device *vdev, uint32_t offset, void *src,
int length)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
uint8_t *buf = src;
/* TODO: replace to metal_io_block_write, it also need a sanity check regarding
* the offset and length of block being written
*/
for (int i = 0; i < length; i++)
metal_io_write8(dev->io, VIRTIO_MMIO_CONFIG + i, buf[i]);
}
static void mmio_dev_reset_device(struct virtio_device *vdev)
static void virtio_mmio_dev_reset_device(struct virtio_device *vdev)
{
struct virtio_mmio_dev *dev = metal_container_of(vdev, struct virtio_mmio_dev, vdev);
write_reg(dev, VIRTIO_MMIO_STATUS, read_reg(dev, VIRTIO_MMIO_STATUS) |
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_STATUS, virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_STATUS) |
VIRTIO_CONFIG_STATUS_NEEDS_RESET);
}
const static struct virtio_dispatch virtio_ops = {
.create_virtqueues = mmio_dev_create_virtqueues,
.delete_virtqueues = mmio_dev_delete_virtqueues,
.create_virtqueues = virtio_mmio_dev_create_virtqueues,
.delete_virtqueues = virtio_mmio_dev_delete_virtqueues,
.configure_device = mmio_dev_configure_device,
.configure_device = virtio_mmio_dev_configure_device,
.get_status = mmio_dev_get_status,
.set_status = mmio_dev_set_status,
.get_status = virtio_mmio_dev_get_status,
.set_status = virtio_mmio_dev_set_status,
.get_features = mmio_dev_get_features,
.set_features = mmio_dev_set_features,
.get_features = virtio_mmio_dev_get_features,
.set_features = virtio_mmio_dev_set_features,
.read_config = mmio_dev_read_config,
.write_config = mmio_dev_write_config,
.read_config = virtio_mmio_dev_read_config,
.write_config = virtio_mmio_dev_write_config,
.reset_device = mmio_dev_reset_device,
.reset_device = virtio_mmio_dev_reset_device,
};
void mmio_dev_init(struct virtio_mmio_dev *dev, struct metal_io_region *io, virtio_notify callback)
{
dev->io = io;
dev->notify = callback;
dev->vdev.func = &virtio_ops;
dev->driver_features = 0;
/* Init vdev struct */
dev->vdev.role = VIRTIO_DEV_DEVICE;
}
static void mmio_dev_negotiate_features(struct virtio_mmio_dev *dev)
static void virtio_mmio_dev_negotiate_features(struct virtio_mmio_dev *dev)
{
/* we update the device features each time in case the feature sel changed */
write_reg(dev, VIRTIO_MMIO_DEVICE_FEATURES, dev->device_features >>
(read_reg(dev, VIRTIO_MMIO_DEVICE_FEATURES_SEL) * 32));
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_DEVICE_FEATURES, dev->device_features >>
(virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_DEVICE_FEATURES_SEL) * 32));
/* we update the driver features each time in case the feature sel changed */
dev->driver_features |= ((uint64_t)read_reg(dev, VIRTIO_MMIO_DRIVER_FEATURES) <<
(read_reg(dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL) * 32));
dev->driver_features |= ((uint64_t)virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_DRIVER_FEATURES) <<
(virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL) * 32));
}
static void mmio_dev_receive_queues(struct virtio_mmio_dev *dev, struct virtio_device *vdev)
static void virtio_mmio_dev_receive_queues(struct virtio_mmio_dev *dev, struct virtio_device *vdev)
{
/* Now the driver should send us the virtqueues */
uint32_t queuesel = read_reg(dev, VIRTIO_MMIO_QUEUE_SEL);
uint32_t queuesel = virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_SEL);
/* Update virtqueue registers according to the queuesel index */
if (queuesel >= vdev->vrings_num)
@@ -229,18 +224,18 @@ static void mmio_dev_receive_queues(struct virtio_mmio_dev *dev, struct virtio_d
struct vring_alloc_info *alloc_info = &vinfo->info;
struct virtqueue *vq = &dev->vqs[queuesel];
/* if this queue allready exist or the driver did not set it up */
if (vinfo->vq || !read_reg(dev, VIRTIO_MMIO_QUEUE_READY)) {
/* tell the driver if the queue allready exist or not */
write_reg(dev, VIRTIO_MMIO_QUEUE_READY, vinfo->vq ? 1 : 0);
/* if this queue already exists or the driver did not set it up */
if (vinfo->vq || !virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_READY)) {
/* tell the driver if the queue already exists or not */
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_QUEUE_READY, vinfo->vq ? 1 : 0);
return;
}
/* If the host set Queue Ready then we can read the virtqueue */
alloc_info->num_descs = read_reg(dev, VIRTIO_MMIO_QUEUE_NUM);
/* If the host sets Queue Ready then we can read the virtqueue */
alloc_info->num_descs = virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_NUM);
alloc_info->vaddr = read_reg(dev, VIRTIO_MMIO_QUEUE_DESC_LOW) |
((uint64_t)read_reg(dev, VIRTIO_MMIO_QUEUE_DESC_HIGH) << 32);
alloc_info->vaddr = virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_DESC_LOW) |
((uint64_t)virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_DESC_HIGH) << 32);
/* TODO: the alignment should be set in a config instead of being hardcoded */
alloc_info->align = 4096;
@@ -252,21 +247,33 @@ static void mmio_dev_receive_queues(struct virtio_mmio_dev *dev, struct virtio_d
vinfo->vq = vq;
/* Use the vring addresses provided in case they were aligned differently */
vq->vq_ring.desc = read_reg(dev, VIRTIO_MMIO_QUEUE_DESC_LOW) |
((uint64_t)read_reg(dev, VIRTIO_MMIO_QUEUE_DESC_HIGH) << 32);
vq->vq_ring.desc = virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_DESC_LOW) |
((uint64_t)virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_DESC_HIGH) << 32);
vq->vq_ring.avail = read_reg(dev, VIRTIO_MMIO_QUEUE_AVAIL_LOW) |
((uint64_t)read_reg(dev, VIRTIO_MMIO_QUEUE_AVAIL_HIGH) << 32);
vq->vq_ring.avail = virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_AVAIL_LOW) |
((uint64_t)virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_AVAIL_HIGH) << 32);
vq->vq_ring.used = read_reg(dev, VIRTIO_MMIO_QUEUE_USED_LOW) |
((uint64_t)read_reg(dev, VIRTIO_MMIO_QUEUE_USED_HIGH) << 32);
vq->vq_ring.used = virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_USED_LOW) |
((uint64_t)virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_USED_HIGH) << 32);
}
void mmio_dev_interrupt(struct virtio_mmio_dev *dev)
void virtio_mmio_dev_init(struct virtio_mmio_dev *dev, struct metal_io_region *io, virtio_mmio_notify callback)
{
dev->io = io;
dev->notify = callback;
dev->vdev.func = &virtio_ops;
dev->driver_features = 0;
/* Init vdev struct */
dev->vdev.role = VIRTIO_DEV_DEVICE;
}
void virtio_mmio_dev_interrupt(struct virtio_mmio_dev *dev)
{
struct virtio_device *vdev = &dev->vdev;
switch (read_reg(dev, VIRTIO_MMIO_STATUS)) {
switch (virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_STATUS)) {
case VIRTIO_CONFIG_STATUS_RESET:
dev->driver_features = 0;
break;
@@ -275,7 +282,7 @@ void mmio_dev_interrupt(struct virtio_mmio_dev *dev)
* the features sel register are used to select the features bits we want to read
*/
case (VIRTIO_CONFIG_STATUS_ACK | VIRTIO_CONFIG_STATUS_DRIVER):
mmio_dev_negotiate_features(dev);
virtio_mmio_dev_negotiate_features(dev);
break;
/* Check if the features negotiated are the right ones
@@ -285,18 +292,18 @@ void mmio_dev_interrupt(struct virtio_mmio_dev *dev)
VIRTIO_CONFIG_STATUS_FEATURES_OK):
/* if the features does not match then we should not allow feature ok status */
if (dev->driver_features != dev->device_features) {
write_reg(dev, VIRTIO_MMIO_STATUS,
(uint32_t)read_reg(dev, VIRTIO_MMIO_STATUS) &
virtio_mmio_dev_write_reg(dev, VIRTIO_MMIO_STATUS,
(uint32_t)virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_STATUS) &
~VIRTIO_CONFIG_STATUS_FEATURES_OK);
return;
}
/* The features are fully negotiated ! */
vdev->features = dev->driver_features;
mmio_dev_receive_queues(dev, vdev);
virtio_mmio_dev_receive_queues(dev, vdev);
break;
case VIRTIO_CONFIG_STATUS_READY:
uint32_t notify_idx = read_reg(dev, VIRTIO_MMIO_QUEUE_NOTIFY);
uint32_t notify_idx = virtio_mmio_dev_read_reg(dev, VIRTIO_MMIO_QUEUE_NOTIFY);
/* if the virtqueue does have an available buffer, notify it */
if (notify_idx < vdev->vrings_num && vdev->vrings_info[notify_idx].vq &&
virtqueue_get_desc_size(&dev->vqs[notify_idx]) > 0)