diff --git a/include/vfio-user.h b/include/vfio-user.h index 7439484b..17de4c37 100644 --- a/include/vfio-user.h +++ b/include/vfio-user.h @@ -67,6 +67,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, }; @@ -226,6 +227,21 @@ struct vfio_user_bitmap_range { #endif /* VFIO_REGION_TYPE_MIGRATION */ +#define VFIO_USER_MULTI_DATA 8 +#define VFIO_USER_MULTI_MAX 200 + +struct vfio_user_write_multi_data { + uint64_t offset; + uint32_t region; + uint32_t count; + char data[VFIO_USER_MULTI_DATA]; +} __attribute__((packed)); + +struct vfio_user_write_multi { + uint64_t wr_cnt; + struct vfio_user_write_multi_data wrs[VFIO_USER_MULTI_MAX]; +} __attribute__((packed)); + #ifdef __cplusplus } #endif diff --git a/lib/libvfio-user.c b/lib/libvfio-user.c index 47e3572f..3c7062a5 100644 --- a/lib/libvfio-user.c +++ b/lib/libvfio-user.c @@ -264,10 +264,27 @@ is_valid_region_access(vfu_ctx_t *vfu_ctx, size_t size, uint16_t cmd, return false; } - if (cmd == VFIO_USER_REGION_WRITE && size - sizeof(*ra) != ra->count) { - vfu_log(vfu_ctx, LOG_ERR, "region write count too small: " - "expected %lu, got %u", size - sizeof(*ra), ra->count); - return false; + switch (cmd) { + case VFIO_USER_REGION_WRITE: + if (size - sizeof(*ra) != ra->count) { + vfu_log(vfu_ctx, LOG_ERR, "region write count too small: " + "expected %lu, got %u", size - sizeof(*ra), ra->count); + return false; + } + + break; + + case VFIO_USER_REGION_WRITE_MULTI: + if (ra->count > VFIO_USER_MULTI_DATA) { + vfu_log(vfu_ctx, LOG_ERR, "region write count too large: " + "expected %lu, got %u", size - sizeof(*ra), ra->count); + return false; + } + + break; + + default: + break; } index = ra->region; @@ -350,6 +367,61 @@ handle_region_access(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) return 0; } +static int +handle_region_write_multi(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) +{ + struct vfio_user_write_multi *wm = msg->in.iov.iov_base; + ssize_t ret; + char *buf; + + assert(vfu_ctx != NULL); + assert(msg != NULL); + + // FIXME: validate properly (?) + + if (wm->wr_cnt > VFIO_USER_MULTI_MAX) { + return ERROR_INT(EINVAL); + } + + if (msg->in.iov.iov_len < sizeof(*wm)) { + return ERROR_INT(EINVAL); + } + + for (size_t i = 0; i < wm->wr_cnt; i++) { + struct vfio_user_region_access *in_ra; + + /* Re-use the very similar type. */ + in_ra = (struct vfio_user_region_access *)&wm->wrs[i]; + + /* + * We already checked total length so can be sure each entry is at least + * big enough. + */ + if (!is_valid_region_access(vfu_ctx, sizeof(wm->wrs[i]), + msg->hdr.cmd, in_ra)) { + return ERROR_INT(EINVAL); + } + + if (in_ra->count == 0) { + continue; + } + + buf = (char *)(&in_ra->data); + + ret = region_access(vfu_ctx, in_ra->region, buf, in_ra->count, + in_ra->offset, true); + if (ret != in_ra->count) { + /* FIXME we should return whatever has been accessed, not an error */ + if (ret >= 0) { + ret = ERROR_INT(EINVAL); + } + return ret; + } + } + + return 0; +} + static int handle_device_get_info(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) { @@ -1146,6 +1218,11 @@ handle_request(vfu_ctx_t *vfu_ctx, vfu_msg_t *msg) ret = handle_region_access(vfu_ctx, msg); break; + case VFIO_USER_REGION_WRITE_MULTI: + ret = handle_region_write_multi(vfu_ctx, msg); + break; + + case VFIO_USER_DEVICE_RESET: vfu_log(vfu_ctx, LOG_INFO, "device reset by client"); ret = handle_device_reset(vfu_ctx, VFU_RESET_DEVICE); @@ -1334,6 +1411,10 @@ command_needs_quiesce(vfu_ctx_t *vfu_ctx, const vfu_msg_t *msg) return true; } break; + + case VFIO_USER_REGION_WRITE_MULTI: + /* FIXME */ + return false; } return false;