diff --git a/src/vrend_renderer.c b/src/vrend_renderer.c index be037a1..2533f63 100644 --- a/src/vrend_renderer.c +++ b/src/vrend_renderer.c @@ -6416,42 +6416,95 @@ static bool check_transfer_bounds(struct vrend_resource *res, return true; } +/* Calculate the size of the memory needed to hold all the data of a + * transfer for particular stride values. + */ +static uint64_t vrend_transfer_size(struct vrend_resource *vres, + const struct vrend_transfer_info *info, + uint32_t stride, uint32_t layer_stride) +{ + struct pipe_resource *pres = &vres->base; + struct pipe_box *box = info->box; + uint64_t size; + /* For purposes of size calculation, assume that invalid dimension values + * correspond to 1. + */ + int w = box->width > 0 ? box->width : 1; + int h = box->height > 0 ? box->height : 1; + int d = box->depth > 0 ? box->depth : 1; + int nblocksx = util_format_get_nblocksx(pres->format, w); + int nblocksy = util_format_get_nblocksy(pres->format, h); + + /* Calculate the box size, not including the last layer. The last layer + * is the only one which may be incomplete, and is the only layer for + * non 3d/2d-array formats. + */ + size = (d - 1) * layer_stride; + /* Calculate the size of the last (or only) layer, not including the last + * block row. The last block row is the only one which may be incomplete and + * is the only block row for non 2d/1d-array formats. + */ + size += (nblocksy - 1) * stride; + /* Calculate the size of the the last (or only) block row. */ + size += nblocksx * util_format_get_blocksize(pres->format); + + return size; +} + static bool check_iov_bounds(struct vrend_resource *res, const struct vrend_transfer_info *info, struct iovec *iov, int num_iovs) { - GLuint send_size; + GLuint transfer_size; GLuint iovsize = vrend_get_iovec_size(iov, num_iovs); GLuint valid_stride, valid_layer_stride; - /* validate the send size */ - valid_stride = util_format_get_stride(res->base.format, info->box->width); - if (info->stride) { - /* only validate passed in stride for boxes with height */ - if (info->box->height > 1) { - if (info->stride < valid_stride) - return false; - valid_stride = info->stride; - } + /* If the transfer specifies a stride, verify that it's at least as large as + * the minimum required for the transfer. If no stride is specified use the + * image stride for the specified level. For backward compatibility, we only + * use any guest-specified transfer stride for boxes with height. + */ + if (info->stride && info->box->height > 1) { + GLuint min_stride = util_format_get_stride(res->base.format, info->box->width); + if (info->stride < min_stride) + return false; + valid_stride = info->stride; + } else { + valid_stride = util_format_get_stride(res->base.format, + u_minify(res->base.width0, info->level)); } - valid_layer_stride = util_format_get_2d_size(res->base.format, valid_stride, - info->box->height); - if (info->layer_stride) { - /* only validate passed in layer_stride for boxes with depth */ - if (info->box->depth > 1) { - if (info->layer_stride < valid_layer_stride) - return false; - valid_layer_stride = info->layer_stride; - } + /* If the transfer specifies a layer_stride, verify that it's at least as + * large as the minimum required for the transfer. If no layer_stride is + * specified use the image layer_stride for the specified level. For + * backward compatibility, we only use any guest-specified transfer + * layer_stride for boxes with depth. + */ + if (info->layer_stride && info->box->depth > 1) { + GLuint min_layer_stride = util_format_get_2d_size(res->base.format, + valid_stride, + info->box->height); + if (info->layer_stride < min_layer_stride) + return false; + valid_layer_stride = info->layer_stride; + } else { + valid_layer_stride = + util_format_get_2d_size(res->base.format, valid_stride, + u_minify(res->base.height0, info->level)); } - send_size = valid_layer_stride * info->box->depth; + /* Calculate the size required for the transferred data, based on the + * calculated or provided strides, and ensure that the iov, starting at the + * specified offset, is able to hold at least that size. + */ + transfer_size = vrend_transfer_size(res, info, + valid_stride, + valid_layer_stride); if (iovsize < info->offset) return false; - if (iovsize < send_size) + if (iovsize < transfer_size) return false; - if (iovsize < info->offset + send_size) + if (iovsize < info->offset + transfer_size) return false; return true; diff --git a/tests/test_virgl_transfer.c b/tests/test_virgl_transfer.c index b1fcf2e..c8bf032 100644 --- a/tests/test_virgl_transfer.c +++ b/tests/test_virgl_transfer.c @@ -932,6 +932,35 @@ START_TEST(virgl_test_copy_transfer_to_staging_with_iov_succeeds) } END_TEST +START_TEST(virgl_test_transfer_near_res_bounds_with_stride_succeeds) +{ + struct virgl_context ctx = {0}; + struct virgl_resource res = {0}; + int res_width = 4; + int res_height = 3; + int res_stride = res_width * 4; + struct pipe_box box = {.x = 2, .y = 1, .z = 0, .width = 2, .height = 2, .depth = 1}; + int ret; + + ret = testvirgl_init_ctx_cmdbuf(&ctx); + ck_assert_int_eq(ret, 0); + + ret = testvirgl_create_backed_simple_2d_res(&res, 1, res_width, res_height); + ck_assert_int_eq(ret, 0); + virgl_renderer_ctx_attach_resource(ctx.ctx_id, res.handle); + + virgl_encoder_transfer_with_stride(&ctx, &res, 0, 0, &box, 6 * 4, VIRGL_TRANSFER_TO_HOST, + res_stride, 0); + + ret = virgl_renderer_submit_cmd(ctx.cbuf->buf, ctx.ctx_id, ctx.cbuf->cdw); + ck_assert_int_eq(ret, 0); + + virgl_renderer_ctx_detach_resource(ctx.ctx_id, res.handle); + testvirgl_destroy_backed_res(&res); + testvirgl_fini_ctx_cmdbuf(&ctx); +} +END_TEST + static Suite *virgl_init_suite(void) { Suite *s; @@ -986,6 +1015,11 @@ static Suite *virgl_init_suite(void) suite_add_tcase(s, tc_core); + tc_core = tcase_create("transfer_command_bounds"); + tcase_add_test(tc_core, virgl_test_transfer_near_res_bounds_with_stride_succeeds); + + suite_add_tcase(s, tc_core); + return s; } diff --git a/tests/testvirgl_encode.c b/tests/testvirgl_encode.c index 5707482..6f7192a 100644 --- a/tests/testvirgl_encode.c +++ b/tests/testvirgl_encode.c @@ -568,11 +568,22 @@ int virgl_encoder_transfer(struct virgl_context *ctx, unsigned level, unsigned usage, const struct pipe_box *box, unsigned offset, unsigned direction) +{ + return virgl_encoder_transfer_with_stride(ctx, res, level, usage, box, + offset, direction, 0, 0); +} + +int virgl_encoder_transfer_with_stride(struct virgl_context *ctx, + struct virgl_resource *res, + unsigned level, unsigned usage, + const struct pipe_box *box, + unsigned offset, unsigned direction, + unsigned stride, unsigned layer_stride) { uint32_t command; command = VIRGL_CMD0(VIRGL_CCMD_TRANSFER3D, 0, VIRGL_TRANSFER3D_SIZE); virgl_encoder_write_dword(ctx->cbuf, command); - virgl_encoder_transfer3d_common(ctx, res, level, usage, box, 0, 0); + virgl_encoder_transfer3d_common(ctx, res, level, usage, box, stride, layer_stride); virgl_encoder_write_dword(ctx->cbuf, offset); virgl_encoder_write_dword(ctx->cbuf, direction); return 0; diff --git a/tests/testvirgl_encode.h b/tests/testvirgl_encode.h index a41d4ce..c5bd9c1 100644 --- a/tests/testvirgl_encode.h +++ b/tests/testvirgl_encode.h @@ -149,6 +149,13 @@ int virgl_encoder_transfer(struct virgl_context *ctx, const struct pipe_box *box, unsigned offset, unsigned direction); +int virgl_encoder_transfer_with_stride(struct virgl_context *ctx, + struct virgl_resource *res, + unsigned level, unsigned usage, + const struct pipe_box *box, + unsigned offset, unsigned direction, + unsigned stride, unsigned layer_stride); + int virgl_encoder_copy_transfer(struct virgl_context *ctx, struct virgl_resource *res, unsigned level, unsigned usage,