vfio-user: add coalesced posted writes

Add new message to send multiple writes to server in a single message.
Prevents the outgoing queue from overflowing when a long latency
operation is followed by a series of posted writes.

Originally-by: John Johnson <john.g.johnson@oracle.com>
Signed-off-by: Elena Ufimtseva <elena.ufimtseva@oracle.com>
Signed-off-by: Jagannathan Raman <jag.raman@oracle.com>
Signed-off-by: John Levon <john.levon@nutanix.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20250625193012.2316242-18-john.levon@nutanix.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
This commit is contained in:
John Levon 2025-06-25 20:30:09 +01:00 committed by Cédric Le Goater
parent 98a906d9e5
commit 1a0c32a9da
5 changed files with 158 additions and 0 deletions

View file

@ -9,6 +9,8 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/lockable.h"
#include "qemu/thread.h"
#include "hw/vfio-user/device.h"
#include "hw/vfio-user/trace.h"
@ -337,6 +339,7 @@ static int vfio_user_device_io_region_write(VFIODevice *vbasedev, uint8_t index,
VFIOUserProxy *proxy = vbasedev->proxy;
int size = sizeof(*msgp) + count;
Error *local_err = NULL;
bool can_multi;
int flags = 0;
int ret;
@ -352,6 +355,43 @@ static int vfio_user_device_io_region_write(VFIODevice *vbasedev, uint8_t index,
flags |= VFIO_USER_NO_REPLY;
}
/* write eligible to be in a WRITE_MULTI msg ? */
can_multi = (proxy->flags & VFIO_PROXY_USE_MULTI) && post &&
count <= VFIO_USER_MULTI_DATA;
/*
* This should be a rare case, so first check without the lock,
* if we're wrong, vfio_send_queued() will flush any posted writes
* we missed here
*/
if (proxy->wr_multi != NULL ||
(proxy->num_outgoing > VFIO_USER_OUT_HIGH && can_multi)) {
/*
* re-check with lock
*
* if already building a WRITE_MULTI msg,
* add this one if possible else flush pending before
* sending the current one
*
* else if outgoing queue is over the highwater,
* start a new WRITE_MULTI message
*/
WITH_QEMU_LOCK_GUARD(&proxy->lock) {
if (proxy->wr_multi != NULL) {
if (can_multi) {
vfio_user_add_multi(proxy, index, off, count, data);
return count;
}
vfio_user_flush_multi(proxy);
} else if (proxy->num_outgoing > VFIO_USER_OUT_HIGH && can_multi) {
vfio_user_create_multi(proxy);
vfio_user_add_multi(proxy, index, off, count, data);
return count;
}
}
}
msgp = g_malloc0(size);
vfio_user_request_msg(&msgp->hdr, VFIO_USER_REGION_WRITE, size, flags);
msgp->offset = off;

View file

@ -39,6 +39,7 @@ enum vfio_user_command {
VFIO_USER_DMA_WRITE = 12,
VFIO_USER_DEVICE_RESET = 13,
VFIO_USER_DIRTY_PAGES = 14,
VFIO_USER_REGION_WRITE_MULTI = 15,
VFIO_USER_MAX,
};
@ -72,6 +73,7 @@ typedef struct {
#define VFIO_USER_CAP_PGSIZES "pgsizes"
#define VFIO_USER_CAP_MAP_MAX "max_dma_maps"
#define VFIO_USER_CAP_MIGR "migration"
#define VFIO_USER_CAP_MULTI "write_multiple"
/* "migration" members */
#define VFIO_USER_CAP_PGSIZE "pgsize"
@ -218,4 +220,23 @@ typedef struct {
char data[];
} VFIOUserBitmap;
/*
* VFIO_USER_REGION_WRITE_MULTI
*/
#define VFIO_USER_MULTI_DATA 8
#define VFIO_USER_MULTI_MAX 200
typedef struct {
uint64_t offset;
uint32_t region;
uint32_t count;
char data[VFIO_USER_MULTI_DATA];
} VFIOUserWROne;
typedef struct {
VFIOUserHdr hdr;
uint64_t wr_cnt;
VFIOUserWROne wrs[VFIO_USER_MULTI_MAX];
} VFIOUserWRMulti;
#endif /* VFIO_USER_PROTOCOL_H */

View file

@ -13,12 +13,14 @@
#include "hw/vfio-user/proxy.h"
#include "hw/vfio-user/trace.h"
#include "qapi/error.h"
#include "qobject/qbool.h"
#include "qobject/qdict.h"
#include "qobject/qjson.h"
#include "qobject/qnum.h"
#include "qemu/error-report.h"
#include "qemu/lockable.h"
#include "qemu/main-loop.h"
#include "qemu/thread.h"
#include "system/iothread.h"
static IOThread *vfio_user_iothread;
@ -445,6 +447,7 @@ static ssize_t vfio_user_send_one(VFIOUserProxy *proxy, Error **errp)
}
QTAILQ_REMOVE(&proxy->outgoing, msg, next);
proxy->num_outgoing--;
if (msg->type == VFIO_MSG_ASYNC) {
vfio_user_recycle(proxy, msg);
} else {
@ -481,6 +484,11 @@ static void vfio_user_send(void *opaque)
}
qio_channel_set_aio_fd_handler(proxy->ioc, proxy->ctx,
vfio_user_recv, NULL, NULL, proxy);
/* queue empty - send any pending multi write msgs */
if (proxy->wr_multi != NULL) {
vfio_user_flush_multi(proxy);
}
}
}
@ -579,11 +587,18 @@ static bool vfio_user_send_queued(VFIOUserProxy *proxy, VFIOUserMsg *msg,
{
int ret;
/* older coalesced writes go first */
if (proxy->wr_multi != NULL &&
((msg->hdr->flags & VFIO_USER_TYPE) == VFIO_USER_REQUEST)) {
vfio_user_flush_multi(proxy);
}
/*
* Unsent outgoing msgs - add to tail
*/
if (!QTAILQ_EMPTY(&proxy->outgoing)) {
QTAILQ_INSERT_TAIL(&proxy->outgoing, msg, next);
proxy->num_outgoing++;
return true;
}
@ -598,6 +613,7 @@ static bool vfio_user_send_queued(VFIOUserProxy *proxy, VFIOUserMsg *msg,
if (ret == QIO_CHANNEL_ERR_BLOCK) {
QTAILQ_INSERT_HEAD(&proxy->outgoing, msg, next);
proxy->num_outgoing = 1;
qio_channel_set_aio_fd_handler(proxy->ioc, proxy->ctx,
vfio_user_recv, proxy->ctx,
vfio_user_send, proxy);
@ -1151,12 +1167,27 @@ static bool check_migr(VFIOUserProxy *proxy, QObject *qobj, Error **errp)
return caps_parse(proxy, qdict, caps_migr, errp);
}
static bool check_multi(VFIOUserProxy *proxy, QObject *qobj, Error **errp)
{
QBool *qb = qobject_to(QBool, qobj);
if (qb == NULL) {
error_setg(errp, "malformed %s", VFIO_USER_CAP_MULTI);
return false;
}
if (qbool_get_bool(qb)) {
proxy->flags |= VFIO_PROXY_USE_MULTI;
}
return true;
}
static struct cap_entry caps_cap[] = {
{ VFIO_USER_CAP_MAX_FDS, check_max_fds },
{ VFIO_USER_CAP_MAX_XFER, check_max_xfer },
{ VFIO_USER_CAP_PGSIZES, check_pgsizes },
{ VFIO_USER_CAP_MAP_MAX, check_max_dma },
{ VFIO_USER_CAP_MIGR, check_migr },
{ VFIO_USER_CAP_MULTI, check_multi },
{ NULL }
};
@ -1215,6 +1246,7 @@ static GString *caps_json(void)
qdict_put_int(capdict, VFIO_USER_CAP_MAX_XFER, VFIO_USER_DEF_MAX_XFER);
qdict_put_int(capdict, VFIO_USER_CAP_PGSIZES, VFIO_USER_DEF_PGSIZE);
qdict_put_int(capdict, VFIO_USER_CAP_MAP_MAX, VFIO_USER_DEF_MAP_MAX);
qdict_put_bool(capdict, VFIO_USER_CAP_MULTI, true);
qdict_put_obj(dict, VFIO_USER_CAP, QOBJECT(capdict));
@ -1270,3 +1302,55 @@ bool vfio_user_validate_version(VFIOUserProxy *proxy, Error **errp)
trace_vfio_user_version(msgp->major, msgp->minor, msgp->capabilities);
return true;
}
void vfio_user_flush_multi(VFIOUserProxy *proxy)
{
VFIOUserMsg *msg;
VFIOUserWRMulti *wm = proxy->wr_multi;
Error *local_err = NULL;
proxy->wr_multi = NULL;
/* adjust size for actual # of writes */
wm->hdr.size -= (VFIO_USER_MULTI_MAX - wm->wr_cnt) * sizeof(VFIOUserWROne);
msg = vfio_user_getmsg(proxy, &wm->hdr, NULL);
msg->id = wm->hdr.id;
msg->rsize = 0;
msg->type = VFIO_MSG_ASYNC;
trace_vfio_user_wrmulti("flush", wm->wr_cnt);
if (!vfio_user_send_queued(proxy, msg, &local_err)) {
error_report_err(local_err);
vfio_user_recycle(proxy, msg);
}
}
void vfio_user_create_multi(VFIOUserProxy *proxy)
{
VFIOUserWRMulti *wm;
wm = g_malloc0(sizeof(*wm));
vfio_user_request_msg(&wm->hdr, VFIO_USER_REGION_WRITE_MULTI,
sizeof(*wm), VFIO_USER_NO_REPLY);
proxy->wr_multi = wm;
}
void vfio_user_add_multi(VFIOUserProxy *proxy, uint8_t index,
off_t offset, uint32_t count, void *data)
{
VFIOUserWRMulti *wm = proxy->wr_multi;
VFIOUserWROne *w1 = &wm->wrs[wm->wr_cnt];
w1->offset = offset;
w1->region = index;
w1->count = count;
memcpy(&w1->data, data, count);
wm->wr_cnt++;
trace_vfio_user_wrmulti("add", wm->wr_cnt);
if (wm->wr_cnt == VFIO_USER_MULTI_MAX ||
proxy->num_outgoing < VFIO_USER_OUT_LOW) {
vfio_user_flush_multi(proxy);
}
}

View file

@ -85,6 +85,8 @@ typedef struct VFIOUserProxy {
VFIOUserMsg *last_nowait;
VFIOUserMsg *part_recv;
size_t recv_left;
VFIOUserWRMulti *wr_multi;
int num_outgoing;
enum proxy_state state;
} VFIOUserProxy;
@ -92,6 +94,11 @@ typedef struct VFIOUserProxy {
#define VFIO_PROXY_CLIENT 0x1
#define VFIO_PROXY_FORCE_QUEUED 0x4
#define VFIO_PROXY_NO_POST 0x8
#define VFIO_PROXY_USE_MULTI 0x16
/* coalescing high and low water marks for VFIOProxy num_outgoing */
#define VFIO_USER_OUT_HIGH 1024
#define VFIO_USER_OUT_LOW 128
typedef struct VFIODevice VFIODevice;
@ -120,4 +127,9 @@ bool vfio_user_send_async(VFIOUserProxy *proxy, VFIOUserHdr *hdr,
void vfio_user_send_reply(VFIOUserProxy *proxy, VFIOUserHdr *hdr, int size);
void vfio_user_send_error(VFIOUserProxy *proxy, VFIOUserHdr *hdr, int error);
void vfio_user_flush_multi(VFIOUserProxy *proxy);
void vfio_user_create_multi(VFIOUserProxy *proxy);
void vfio_user_add_multi(VFIOUserProxy *proxy, uint8_t index,
off_t offset, uint32_t count, void *data);
#endif /* VFIO_USER_PROXY_H */

View file

@ -13,6 +13,7 @@ vfio_user_get_region_info(uint32_t index, uint32_t flags, uint64_t size) " index
vfio_user_region_rw(uint32_t region, uint64_t off, uint32_t count) " region %d offset 0x%"PRIx64" count %d"
vfio_user_get_irq_info(uint32_t index, uint32_t flags, uint32_t count) " index %d flags 0x%x count %d"
vfio_user_set_irqs(uint32_t index, uint32_t start, uint32_t count, uint32_t flags) " index %d start %d count %d flags 0x%x"
vfio_user_wrmulti(const char *s, uint64_t wr_cnt) " %s count 0x%"PRIx64
# container.c
vfio_user_dma_map(uint64_t iova, uint64_t size, uint64_t off, uint32_t flags, bool async_ops) " iova 0x%"PRIx64" size 0x%"PRIx64" off 0x%"PRIx64" flags 0x%x async_ops %d"