From 8d82bd5f20fac5d8b4dab510d2294e076a6dd93d Mon Sep 17 00:00:00 2001 From: Thanos Makatos Date: Tue, 5 Oct 2021 13:54:26 +0100 Subject: make migration state callback optionally asynchronous (#608) Some devices need the migration state callback to be asynchronous. The simplest way to implement this is to require from the callback to return -1 and set errno to EBUSY, not process any other new messages (vfu_ctx_run returns -1 and sets errno to EBUSY), and provide a way to the user to complete migration (vfu_migr_done). Signed-off-by: Thanos Makatos Reviewed-by: John Levon Reviewed-by: Swapnil Ingle --- lib/libvfio-user.c | 76 ++++++++++++++++++++++++++++++++++++++++++++---------- lib/migration.c | 5 ++++ lib/private.h | 2 ++ 3 files changed, 69 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/libvfio-user.c b/lib/libvfio-user.c index 25b58e0..a56d34c 100644 --- a/lib/libvfio-user.c +++ b/lib/libvfio-user.c @@ -335,7 +335,18 @@ handle_region_access(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) ret = region_access(vfu_ctx, in_ra->region, buf, in_ra->count, in_ra->offset, msg->hdr.cmd == VFIO_USER_REGION_WRITE); - + if (ret == -1 && in_ra->region == VFU_PCI_DEV_MIGR_REGION_IDX + && errno == EBUSY && (vfu_ctx->flags & LIBVFIO_USER_FLAG_ATTACH_NB)) { + /* + * We don't support async behavior for the non-blocking mode simply + * because we don't have a use case yet, the only user of migration + * is SPDK and it operates in non-blocking mode. We don't know the + * implications of enabling this in blocking mode as we haven't looked + * at the details. + */ + vfu_ctx->migr_trans_pending = true; + return 0; + } if (ret != in_ra->count) { vfu_log(vfu_ctx, LOG_ERR, "failed to %s %#lx-%#lx: %m", msg->hdr.cmd == VFIO_USER_REGION_WRITE ? "write" : "read", @@ -1209,6 +1220,29 @@ exec_command(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) return ret; } +static int +do_reply(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg, int reply_errno) +{ + assert(vfu_ctx != NULL); + assert(msg != NULL); + + int ret = vfu_ctx->tran->reply(vfu_ctx, msg, reply_errno); + + if (ret < 0) { + vfu_log(vfu_ctx, LOG_ERR, "failed to reply: %m"); + + if (errno == ECONNRESET) { + vfu_reset_ctx(vfu_ctx, "reset"); + errno = ENOTCONN; + } else if (errno == ENOMSG) { + vfu_reset_ctx(vfu_ctx, "closed"); + errno = ENOTCONN; + } + } + + return ret; +} + /* * Handle requests over the vfio-user socket. This can return immediately if we * are non-blocking, and there is no request from the client ready to read from @@ -1257,25 +1291,19 @@ process_request(vfu_ctx_t *vfu_ctx) } out: + if (vfu_ctx->migr_trans_pending) { + assert(ret == 0); + vfu_ctx->migr_trans_msg = msg; + /* NB the message is freed in vfu_migr_done */ + return 0; + } if (msg->hdr.flags.no_reply) { /* * A failed client request is not a failure of process_request() itself. */ ret = 0; } else { - ret = vfu_ctx->tran->reply(vfu_ctx, msg, ret == 0 ? 0 : errno); - - if (ret < 0) { - vfu_log(vfu_ctx, LOG_ERR, "failed to reply: %m"); - - if (errno == ECONNRESET) { - vfu_reset_ctx(vfu_ctx, "reset"); - errno = ENOTCONN; - } else if (errno == ENOMSG) { - vfu_reset_ctx(vfu_ctx, "closed"); - errno = ENOTCONN; - } - } + ret = do_reply(vfu_ctx, msg, ret == 0 ? 0 : errno); } free_msg(vfu_ctx, msg); @@ -1376,6 +1404,9 @@ vfu_run_ctx(vfu_ctx_t *vfu_ctx) blocking = !(vfu_ctx->flags & LIBVFIO_USER_FLAG_ATTACH_NB); do { + if (vfu_ctx->migr_trans_pending) { + return ERROR_INT(EBUSY); + } err = process_request(vfu_ctx); if (err == 0) { @@ -1928,12 +1959,14 @@ vfu_dma_transfer(vfu_ctx_t *vfu_ctx, enum vfio_user_command cmd, EXPORT int vfu_dma_read(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, void *data) { + assert(!vfu_ctx->migr_trans_pending); return vfu_dma_transfer(vfu_ctx, VFIO_USER_DMA_READ, sg, data); } EXPORT int vfu_dma_write(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, void *data) { + assert(!vfu_ctx->migr_trans_pending); return vfu_dma_transfer(vfu_ctx, VFIO_USER_DMA_WRITE, sg, data); } @@ -1943,4 +1976,19 @@ vfu_sg_is_mappable(vfu_ctx_t *vfu_ctx, dma_sg_t *sg) return dma_sg_is_mappable(vfu_ctx->dma, sg); } +EXPORT void +vfu_migr_done(vfu_ctx_t *vfu_ctx, int reply_errno) +{ + assert(vfu_ctx != NULL); + assert(vfu_ctx->migr_trans_pending); + + if (!vfu_ctx->migr_trans_msg->hdr.flags.no_reply) { + do_reply(vfu_ctx, vfu_ctx->migr_trans_msg, reply_errno); + } + free_msg(vfu_ctx, vfu_ctx->migr_trans_msg); + vfu_ctx->migr_trans_msg = NULL; + + vfu_ctx->migr_trans_pending = false; +} + /* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/lib/migration.c b/lib/migration.c index c1fe0b5..c8b97fa 100644 --- a/lib/migration.c +++ b/lib/migration.c @@ -423,6 +423,11 @@ MOCK_DEFINE(migration_region_access_registers)(vfu_ctx_t *vfu_ctx, char *buf, "migration: transition from state %s to state %s", migr_states[old_device_state].name, migr_states[*device_state].name); + } else if (errno == EBUSY) { + vfu_log(vfu_ctx, LOG_DEBUG, + "migration: transition from state %s to state %s deferred", + migr_states[old_device_state].name, + migr_states[*device_state].name); } else { vfu_log(vfu_ctx, LOG_ERR, "migration: failed to transition from state %s to state %s", diff --git a/lib/private.h b/lib/private.h index 93a354b..05d2fa4 100644 --- a/lib/private.h +++ b/lib/private.h @@ -169,6 +169,8 @@ struct vfu_ctx { size_t client_max_data_xfer_size; struct migration *migration; + bool migr_trans_pending; + vfu_msg_t *migr_trans_msg; uint32_t irq_count[VFU_DEV_NUM_IRQS]; vfu_irqs_t *irqs; -- cgit v1.1