diff --git a/include/spdk/bdev.h b/include/spdk/bdev.h index 1225e4e6d1..f09f34c7e6 100644 --- a/include/spdk/bdev.h +++ b/include/spdk/bdev.h @@ -137,6 +137,7 @@ enum spdk_bdev_io_type { SPDK_BDEV_IO_TYPE_GET_ZONE_INFO, SPDK_BDEV_IO_TYPE_ZONE_MANAGEMENT, SPDK_BDEV_IO_TYPE_ZONE_APPEND, + SPDK_BDEV_IO_TYPE_COMPARE, SPDK_BDEV_NUM_IO_TYPES /* Keep last */ }; @@ -1013,6 +1014,117 @@ int spdk_bdev_writev_blocks_with_md(struct spdk_bdev_desc *desc, struct spdk_io_ uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb, void *cb_arg); +/** + * Submit a compare request to the bdev on the given channel. + * + * \ingroup bdev_io_submit_functions + * + * \param desc Block device descriptor. + * \param ch I/O channel. Obtained by calling spdk_bdev_get_io_channel(). + * \param buf Data buffer to compare to. + * \param offset_blocks The offset, in blocks, from the start of the block device. + * \param num_blocks The number of blocks to compare. buf must be greater than or equal to this size. + * \param cb Called when the request is complete. + * \param cb_arg Argument passed to cb. + * + * \return 0 on success. On success, the callback will always + * be called (even if the request ultimately failed). Return + * negated errno on failure, in which case the callback will not be called. + * * -EINVAL - offset_blocks and/or num_blocks are out of range + * * -ENOMEM - spdk_bdev_io buffer cannot be allocated + */ +int spdk_bdev_compare_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + void *buf, uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg); + +/** + * Submit a compare request to the bdev on the given channel. This function uses + * separate buffer for metadata transfer (valid only if bdev supports this + * mode). + * + * \ingroup bdev_io_submit_functions + * + * \param desc Block device descriptor. + * \param ch I/O channel. Obtained by calling spdk_bdev_get_io_channel(). + * \param buf Data buffer to compare to. + * \param md Metadata buffer. + * \param offset_blocks The offset, in blocks, from the start of the block device. + * \param num_blocks The number of blocks to compare. buf must be greater than or equal to this size. + * \param cb Called when the request is complete. + * \param cb_arg Argument passed to cb. + * + * \return 0 on success. On success, the callback will always + * be called (even if the request ultimately failed). Return + * negated errno on failure, in which case the callback will not be called. + * * -EINVAL - offset_blocks and/or num_blocks are out of range or separate + * metadata is not supported + * * -ENOMEM - spdk_bdev_io buffer cannot be allocated + */ +int spdk_bdev_compare_blocks_with_md(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + void *buf, void *md, uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg); + +/** + * Submit a compare request to the bdev on the given channel. This differs from + * spdk_bdev_compare by allowing the data buffer to be described in a scatter + * gather list. Some physical devices place memory alignment requirements on + * data and may not be able to directly transfer out of the buffers provided. In + * this case, the request may fail. + * + * \ingroup bdev_io_submit_functions + * + * \param desc Block device descriptor. + * \param ch I/O channel. Obtained by calling spdk_bdev_get_io_channel(). + * \param iov A scatter gather list of buffers to be compared to. + * \param iovcnt The number of elements in iov. + * \param offset_blocks The offset, in blocks, from the start of the block device. + * \param num_blocks The number of blocks to compare. + * \param cb Called when the request is complete. + * \param cb_arg Argument passed to cb. + * + * \return 0 on success. On success, the callback will always + * be called (even if the request ultimately failed). Return + * negated errno on failure, in which case the callback will not be called. + * * -EINVAL - offset_blocks and/or num_blocks are out of range + * * -ENOMEM - spdk_bdev_io buffer cannot be allocated + */ +int spdk_bdev_comparev_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg); + +/** + * Submit a compare request to the bdev on the given channel. This differs from + * spdk_bdev_compare by allowing the data buffer to be described in a scatter + * gather list. Some physical devices place memory alignment requirements on + * data or metadata and may not be able to directly transfer out of the buffers + * provided. In this case, the request may fail. This function uses separate + * buffer for metadata transfer (valid only if bdev supports this mode). + * + * \ingroup bdev_io_submit_functions + * + * \param desc Block device descriptor. + * \param ch I/O channel. Obtained by calling spdk_bdev_get_io_channel(). + * \param iov A scatter gather list of buffers to be compared to. + * \param iovcnt The number of elements in iov. + * \param md Metadata buffer. + * \param offset_blocks The offset, in blocks, from the start of the block device. + * \param num_blocks The number of blocks to compare. + * \param cb Called when the request is complete. + * \param cb_arg Argument passed to cb. + * + * \return 0 on success. On success, the callback will always + * be called (even if the request ultimately failed). Return + * negated errno on failure, in which case the callback will not be called. + * * -EINVAL - offset_blocks and/or num_blocks are out of range or separate + * metadata is not supported + * * -ENOMEM - spdk_bdev_io buffer cannot be allocated + */ +int spdk_bdev_comparev_blocks_with_md(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, void *md, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg); + /** * Submit a request to acquire a data buffer that represents the given * range of blocks. The data buffer is placed in the spdk_bdev_io structure diff --git a/include/spdk/bdev_module.h b/include/spdk/bdev_module.h index 4abaac8373..32cafe071b 100644 --- a/include/spdk/bdev_module.h +++ b/include/spdk/bdev_module.h @@ -222,6 +222,7 @@ struct spdk_bdev_fn_table { /** bdev I/O completion status */ enum spdk_bdev_io_status { + SPDK_BDEV_IO_STATUS_MISCOMPARE = -5, /* * NOMEM should be returned when a bdev module cannot start an I/O because of * some lack of resources. It may not be returned for RESET I/O. I/O completed diff --git a/lib/bdev/bdev.c b/lib/bdev/bdev.c index 0ceeb32010..251972d5f1 100644 --- a/lib/bdev/bdev.c +++ b/lib/bdev/bdev.c @@ -3263,6 +3263,197 @@ spdk_bdev_writev_blocks_with_md(struct spdk_bdev_desc *desc, struct spdk_io_chan num_blocks, cb, cb_arg); } +static void +bdev_compare_do_read_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg) +{ + struct spdk_bdev_io *parent_io = cb_arg; + uint8_t *read_buf = bdev_io->u.bdev.iovs[0].iov_base; + int i, rc = 0; + + if (!success) { + parent_io->internal.status = SPDK_BDEV_IO_STATUS_FAILED; + parent_io->internal.cb(parent_io, false, parent_io->internal.caller_ctx); + spdk_bdev_free_io(bdev_io); + return; + } + + for (i = 0; i < parent_io->u.bdev.iovcnt; i++) { + rc = memcmp(read_buf, + parent_io->u.bdev.iovs[i].iov_base, + parent_io->u.bdev.iovs[i].iov_len); + if (rc) { + break; + } + read_buf += parent_io->u.bdev.iovs[i].iov_len; + } + + spdk_bdev_free_io(bdev_io); + + if (rc == 0) { + parent_io->internal.status = SPDK_BDEV_IO_STATUS_SUCCESS; + parent_io->internal.cb(parent_io, true, parent_io->internal.caller_ctx); + } else { + parent_io->internal.status = SPDK_BDEV_IO_STATUS_MISCOMPARE; + parent_io->internal.cb(parent_io, false, parent_io->internal.caller_ctx); + } +} + +static void +bdev_compare_do_read(void *_bdev_io) +{ + struct spdk_bdev_io *bdev_io = _bdev_io; + int rc; + + rc = spdk_bdev_read_blocks(bdev_io->internal.desc, + spdk_io_channel_from_ctx(bdev_io->internal.ch), NULL, + bdev_io->u.bdev.offset_blocks, bdev_io->u.bdev.num_blocks, + bdev_compare_do_read_done, bdev_io); + + if (rc == -ENOMEM) { + bdev_queue_io_wait_with_cb(bdev_io, bdev_compare_do_read); + } else if (rc != 0) { + bdev_io->internal.status = SPDK_BDEV_IO_STATUS_FAILED; + bdev_io->internal.cb(bdev_io, false, bdev_io->internal.caller_ctx); + } +} + +static int +bdev_comparev_blocks_with_md(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, void *md_buf, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + struct spdk_bdev *bdev = spdk_bdev_desc_get_bdev(desc); + struct spdk_bdev_io *bdev_io; + struct spdk_bdev_channel *channel = spdk_io_channel_get_ctx(ch); + + if (!bdev_io_valid_blocks(bdev, offset_blocks, num_blocks)) { + return -EINVAL; + } + + bdev_io = bdev_channel_get_io(channel); + if (!bdev_io) { + return -ENOMEM; + } + + bdev_io->internal.ch = channel; + bdev_io->internal.desc = desc; + bdev_io->type = SPDK_BDEV_IO_TYPE_COMPARE; + bdev_io->u.bdev.iovs = iov; + bdev_io->u.bdev.iovcnt = iovcnt; + bdev_io->u.bdev.md_buf = md_buf; + bdev_io->u.bdev.num_blocks = num_blocks; + bdev_io->u.bdev.offset_blocks = offset_blocks; + bdev_io_init(bdev_io, bdev, cb_arg, cb); + + if (bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_COMPARE)) { + bdev_io_submit(bdev_io); + return 0; + } + + bdev_compare_do_read(bdev_io); + + return 0; +} + +int +spdk_bdev_comparev_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return bdev_comparev_blocks_with_md(desc, ch, iov, iovcnt, NULL, offset_blocks, + num_blocks, cb, cb_arg); +} + +int +spdk_bdev_comparev_blocks_with_md(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + struct iovec *iov, int iovcnt, void *md_buf, + uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + if (!spdk_bdev_is_md_separate(spdk_bdev_desc_get_bdev(desc))) { + return -EINVAL; + } + + if (!_bdev_io_check_md_buf(iov, md_buf)) { + return -EINVAL; + } + + return bdev_comparev_blocks_with_md(desc, ch, iov, iovcnt, md_buf, offset_blocks, + num_blocks, cb, cb_arg); +} + +static int +bdev_compare_blocks_with_md(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + void *buf, void *md_buf, uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + struct spdk_bdev *bdev = spdk_bdev_desc_get_bdev(desc); + struct spdk_bdev_io *bdev_io; + struct spdk_bdev_channel *channel = spdk_io_channel_get_ctx(ch); + + if (!bdev_io_valid_blocks(bdev, offset_blocks, num_blocks)) { + return -EINVAL; + } + + bdev_io = bdev_channel_get_io(channel); + if (!bdev_io) { + return -ENOMEM; + } + + bdev_io->internal.ch = channel; + bdev_io->internal.desc = desc; + bdev_io->type = SPDK_BDEV_IO_TYPE_COMPARE; + bdev_io->u.bdev.iovs = &bdev_io->iov; + bdev_io->u.bdev.iovs[0].iov_base = buf; + bdev_io->u.bdev.iovs[0].iov_len = num_blocks * bdev->blocklen; + bdev_io->u.bdev.iovcnt = 1; + bdev_io->u.bdev.md_buf = md_buf; + bdev_io->u.bdev.num_blocks = num_blocks; + bdev_io->u.bdev.offset_blocks = offset_blocks; + bdev_io_init(bdev_io, bdev, cb_arg, cb); + + if (bdev_io_type_supported(bdev, SPDK_BDEV_IO_TYPE_COMPARE)) { + bdev_io_submit(bdev_io); + return 0; + } + + bdev_compare_do_read(bdev_io); + + return 0; +} + +int +spdk_bdev_compare_blocks(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + void *buf, uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + return bdev_compare_blocks_with_md(desc, ch, buf, NULL, offset_blocks, num_blocks, + cb, cb_arg); +} + +int +spdk_bdev_compare_blocks_with_md(struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, + void *buf, void *md_buf, uint64_t offset_blocks, uint64_t num_blocks, + spdk_bdev_io_completion_cb cb, void *cb_arg) +{ + struct iovec iov = { + .iov_base = buf, + }; + + if (!spdk_bdev_is_md_separate(spdk_bdev_desc_get_bdev(desc))) { + return -EINVAL; + } + + if (!_bdev_io_check_md_buf(&iov, md_buf)) { + return -EINVAL; + } + + return bdev_compare_blocks_with_md(desc, ch, buf, md_buf, offset_blocks, num_blocks, + cb, cb_arg); +} + static void bdev_zcopy_get_buf(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io, bool success) {