diff options
Diffstat (limited to 'src/spdk/lib/util/dif.c')
-rw-r--r-- | src/spdk/lib/util/dif.c | 1999 |
1 files changed, 1999 insertions, 0 deletions
diff --git a/src/spdk/lib/util/dif.c b/src/spdk/lib/util/dif.c new file mode 100644 index 000000000..64bce1487 --- /dev/null +++ b/src/spdk/lib/util/dif.c @@ -0,0 +1,1999 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/dif.h" +#include "spdk/crc16.h" +#include "spdk/crc32.h" +#include "spdk/endian.h" +#include "spdk/log.h" +#include "spdk/util.h" + +/* Context to iterate or create a iovec array. + * Each sgl is either iterated or created at a time. + */ +struct _dif_sgl { + /* Current iovec in the iteration or creation */ + struct iovec *iov; + + /* Remaining count of iovecs in the iteration or creation. */ + int iovcnt; + + /* Current offset in the iovec */ + uint32_t iov_offset; + + /* Size of the created iovec array in bytes */ + uint32_t total_size; +}; + +static inline void +_dif_sgl_init(struct _dif_sgl *s, struct iovec *iovs, int iovcnt) +{ + s->iov = iovs; + s->iovcnt = iovcnt; + s->iov_offset = 0; + s->total_size = 0; +} + +static void +_dif_sgl_advance(struct _dif_sgl *s, uint32_t step) +{ + s->iov_offset += step; + while (s->iovcnt != 0) { + if (s->iov_offset < s->iov->iov_len) { + break; + } + + s->iov_offset -= s->iov->iov_len; + s->iov++; + s->iovcnt--; + } +} + +static inline void +_dif_sgl_get_buf(struct _dif_sgl *s, void **_buf, uint32_t *_buf_len) +{ + if (_buf != NULL) { + *_buf = s->iov->iov_base + s->iov_offset; + } + if (_buf_len != NULL) { + *_buf_len = s->iov->iov_len - s->iov_offset; + } +} + +static inline bool +_dif_sgl_append(struct _dif_sgl *s, uint8_t *data, uint32_t data_len) +{ + assert(s->iovcnt > 0); + s->iov->iov_base = data; + s->iov->iov_len = data_len; + s->total_size += data_len; + s->iov++; + s->iovcnt--; + + if (s->iovcnt > 0) { + return true; + } else { + return false; + } +} + +static inline bool +_dif_sgl_append_split(struct _dif_sgl *dst, struct _dif_sgl *src, uint32_t data_len) +{ + uint8_t *buf; + uint32_t buf_len; + + while (data_len != 0) { + _dif_sgl_get_buf(src, (void *)&buf, &buf_len); + buf_len = spdk_min(buf_len, data_len); + + if (!_dif_sgl_append(dst, buf, buf_len)) { + return false; + } + + _dif_sgl_advance(src, buf_len); + data_len -= buf_len; + } + + return true; +} + +/* This function must be used before starting iteration. */ +static bool +_dif_sgl_is_bytes_multiple(struct _dif_sgl *s, uint32_t bytes) +{ + int i; + + for (i = 0; i < s->iovcnt; i++) { + if (s->iov[i].iov_len % bytes) { + return false; + } + } + + return true; +} + +/* This function must be used before starting iteration. */ +static bool +_dif_sgl_is_valid(struct _dif_sgl *s, uint32_t bytes) +{ + uint64_t total = 0; + int i; + + for (i = 0; i < s->iovcnt; i++) { + total += s->iov[i].iov_len; + } + + return total >= bytes; +} + +static void +_dif_sgl_copy(struct _dif_sgl *to, struct _dif_sgl *from) +{ + memcpy(to, from, sizeof(struct _dif_sgl)); +} + +static bool +_dif_type_is_valid(enum spdk_dif_type dif_type, uint32_t dif_flags) +{ + switch (dif_type) { + case SPDK_DIF_TYPE1: + case SPDK_DIF_TYPE2: + case SPDK_DIF_DISABLE: + break; + case SPDK_DIF_TYPE3: + if (dif_flags & SPDK_DIF_FLAGS_REFTAG_CHECK) { + SPDK_ERRLOG("Reference Tag should not be checked for Type 3\n"); + return false; + } + break; + default: + SPDK_ERRLOG("Unknown DIF Type: %d\n", dif_type); + return false; + } + + return true; +} + +static bool +_dif_is_disabled(enum spdk_dif_type dif_type) +{ + if (dif_type == SPDK_DIF_DISABLE) { + return true; + } else { + return false; + } +} + + +static uint32_t +_get_guard_interval(uint32_t block_size, uint32_t md_size, bool dif_loc, bool md_interleave) +{ + if (!dif_loc) { + /* For metadata formats with more than 8 bytes, if the DIF is + * contained in the last 8 bytes of metadata, then the CRC + * covers all metadata up to but excluding these last 8 bytes. + */ + if (md_interleave) { + return block_size - sizeof(struct spdk_dif); + } else { + return md_size - sizeof(struct spdk_dif); + } + } else { + /* For metadata formats with more than 8 bytes, if the DIF is + * contained in the first 8 bytes of metadata, then the CRC + * does not cover any metadata. + */ + if (md_interleave) { + return block_size - md_size; + } else { + return 0; + } + } +} + +int +spdk_dif_ctx_init(struct spdk_dif_ctx *ctx, uint32_t block_size, uint32_t md_size, + bool md_interleave, bool dif_loc, enum spdk_dif_type dif_type, uint32_t dif_flags, + uint32_t init_ref_tag, uint16_t apptag_mask, uint16_t app_tag, + uint32_t data_offset, uint16_t guard_seed) +{ + uint32_t data_block_size; + + if (md_size < sizeof(struct spdk_dif)) { + SPDK_ERRLOG("Metadata size is smaller than DIF size.\n"); + return -EINVAL; + } + + if (md_interleave) { + if (block_size < md_size) { + SPDK_ERRLOG("Block size is smaller than DIF size.\n"); + return -EINVAL; + } + data_block_size = block_size - md_size; + } else { + if (block_size == 0 || (block_size % 512) != 0) { + SPDK_ERRLOG("Zero block size is not allowed\n"); + return -EINVAL; + } + data_block_size = block_size; + } + + if (!_dif_type_is_valid(dif_type, dif_flags)) { + SPDK_ERRLOG("DIF type is invalid.\n"); + return -EINVAL; + } + + ctx->block_size = block_size; + ctx->md_size = md_size; + ctx->md_interleave = md_interleave; + ctx->guard_interval = _get_guard_interval(block_size, md_size, dif_loc, md_interleave); + ctx->dif_type = dif_type; + ctx->dif_flags = dif_flags; + ctx->init_ref_tag = init_ref_tag; + ctx->apptag_mask = apptag_mask; + ctx->app_tag = app_tag; + ctx->data_offset = data_offset; + ctx->ref_tag_offset = data_offset / data_block_size; + ctx->last_guard = guard_seed; + ctx->guard_seed = guard_seed; + ctx->remapped_init_ref_tag = 0; + + return 0; +} + +void +spdk_dif_ctx_set_data_offset(struct spdk_dif_ctx *ctx, uint32_t data_offset) +{ + uint32_t data_block_size; + + if (ctx->md_interleave) { + data_block_size = ctx->block_size - ctx->md_size; + } else { + data_block_size = ctx->block_size; + } + + ctx->data_offset = data_offset; + ctx->ref_tag_offset = data_offset / data_block_size; +} + +void +spdk_dif_ctx_set_remapped_init_ref_tag(struct spdk_dif_ctx *ctx, + uint32_t remapped_init_ref_tag) +{ + ctx->remapped_init_ref_tag = remapped_init_ref_tag; +} + +static void +_dif_generate(void *_dif, uint16_t guard, uint32_t offset_blocks, + const struct spdk_dif_ctx *ctx) +{ + struct spdk_dif *dif = _dif; + uint32_t ref_tag; + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + to_be16(&dif->guard, guard); + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_APPTAG_CHECK) { + to_be16(&dif->app_tag, ctx->app_tag); + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_REFTAG_CHECK) { + /* For type 1 and 2, the reference tag is incremented for each + * subsequent logical block. For type 3, the reference tag + * remains the same as the initial reference tag. + */ + if (ctx->dif_type != SPDK_DIF_TYPE3) { + ref_tag = ctx->init_ref_tag + ctx->ref_tag_offset + offset_blocks; + } else { + ref_tag = ctx->init_ref_tag + ctx->ref_tag_offset; + } + + to_be32(&dif->ref_tag, ref_tag); + } +} + +static void +dif_generate(struct _dif_sgl *sgl, uint32_t num_blocks, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_blocks = 0; + void *buf; + uint16_t guard = 0; + + while (offset_blocks < num_blocks) { + _dif_sgl_get_buf(sgl, &buf, NULL); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(ctx->guard_seed, buf, ctx->guard_interval); + } + + _dif_generate(buf + ctx->guard_interval, guard, offset_blocks, ctx); + + _dif_sgl_advance(sgl, ctx->block_size); + offset_blocks++; + } +} + +static uint16_t +_dif_generate_split(struct _dif_sgl *sgl, uint32_t offset_in_block, uint32_t data_len, + uint16_t guard, uint32_t offset_blocks, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_in_dif, buf_len; + void *buf; + struct spdk_dif dif = {}; + + assert(offset_in_block < ctx->guard_interval); + assert(offset_in_block + data_len < ctx->guard_interval || + offset_in_block + data_len == ctx->block_size); + + /* Compute CRC over split logical block data. */ + while (data_len != 0 && offset_in_block < ctx->guard_interval) { + _dif_sgl_get_buf(sgl, &buf, &buf_len); + buf_len = spdk_min(buf_len, data_len); + buf_len = spdk_min(buf_len, ctx->guard_interval - offset_in_block); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(guard, buf, buf_len); + } + + _dif_sgl_advance(sgl, buf_len); + offset_in_block += buf_len; + data_len -= buf_len; + } + + if (offset_in_block < ctx->guard_interval) { + return guard; + } + + /* If a whole logical block data is parsed, generate DIF + * and save it to the temporary DIF area. + */ + _dif_generate(&dif, guard, offset_blocks, ctx); + + /* Copy generated DIF field to the split DIF field, and then + * skip metadata field after DIF field (if any). + */ + while (offset_in_block < ctx->block_size) { + _dif_sgl_get_buf(sgl, &buf, &buf_len); + + if (offset_in_block < ctx->guard_interval + sizeof(struct spdk_dif)) { + offset_in_dif = offset_in_block - ctx->guard_interval; + buf_len = spdk_min(buf_len, sizeof(struct spdk_dif) - offset_in_dif); + + memcpy(buf, ((uint8_t *)&dif) + offset_in_dif, buf_len); + } else { + buf_len = spdk_min(buf_len, ctx->block_size - offset_in_block); + } + + _dif_sgl_advance(sgl, buf_len); + offset_in_block += buf_len; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->guard_seed; + } + + return guard; +} + +static void +dif_generate_split(struct _dif_sgl *sgl, uint32_t num_blocks, + const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_blocks; + uint16_t guard = 0; + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->guard_seed; + } + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + _dif_generate_split(sgl, 0, ctx->block_size, guard, offset_blocks, ctx); + } +} + +int +spdk_dif_generate(struct iovec *iovs, int iovcnt, uint32_t num_blocks, + const struct spdk_dif_ctx *ctx) +{ + struct _dif_sgl sgl; + + _dif_sgl_init(&sgl, iovs, iovcnt); + + if (!_dif_sgl_is_valid(&sgl, ctx->block_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec array is not valid.\n"); + return -EINVAL; + } + + if (_dif_is_disabled(ctx->dif_type)) { + return 0; + } + + if (_dif_sgl_is_bytes_multiple(&sgl, ctx->block_size)) { + dif_generate(&sgl, num_blocks, ctx); + } else { + dif_generate_split(&sgl, num_blocks, ctx); + } + + return 0; +} + +static void +_dif_error_set(struct spdk_dif_error *err_blk, uint8_t err_type, + uint32_t expected, uint32_t actual, uint32_t err_offset) +{ + if (err_blk) { + err_blk->err_type = err_type; + err_blk->expected = expected; + err_blk->actual = actual; + err_blk->err_offset = err_offset; + } +} + +static int +_dif_verify(void *_dif, uint16_t guard, uint32_t offset_blocks, + const struct spdk_dif_ctx *ctx, struct spdk_dif_error *err_blk) +{ + struct spdk_dif *dif = _dif; + uint16_t _guard; + uint16_t _app_tag; + uint32_t ref_tag, _ref_tag; + + switch (ctx->dif_type) { + case SPDK_DIF_TYPE1: + case SPDK_DIF_TYPE2: + /* If Type 1 or 2 is used, then all DIF checks are disabled when + * the Application Tag is 0xFFFF. + */ + if (dif->app_tag == 0xFFFF) { + return 0; + } + break; + case SPDK_DIF_TYPE3: + /* If Type 3 is used, then all DIF checks are disabled when the + * Application Tag is 0xFFFF and the Reference Tag is 0xFFFFFFFF. + */ + if (dif->app_tag == 0xFFFF && dif->ref_tag == 0xFFFFFFFF) { + return 0; + } + break; + default: + break; + } + + /* For type 1 and 2, the reference tag is incremented for each + * subsequent logical block. For type 3, the reference tag + * remains the same as the initial reference tag. + */ + if (ctx->dif_type != SPDK_DIF_TYPE3) { + ref_tag = ctx->init_ref_tag + ctx->ref_tag_offset + offset_blocks; + } else { + ref_tag = ctx->init_ref_tag + ctx->ref_tag_offset; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + /* Compare the DIF Guard field to the CRC computed over the logical + * block data. + */ + _guard = from_be16(&dif->guard); + if (_guard != guard) { + _dif_error_set(err_blk, SPDK_DIF_GUARD_ERROR, _guard, guard, + offset_blocks); + SPDK_ERRLOG("Failed to compare Guard: LBA=%" PRIu32 "," \ + " Expected=%x, Actual=%x\n", + ref_tag, _guard, guard); + return -1; + } + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_APPTAG_CHECK) { + /* Compare unmasked bits in the DIF Application Tag field to the + * passed Application Tag. + */ + _app_tag = from_be16(&dif->app_tag); + if ((_app_tag & ctx->apptag_mask) != ctx->app_tag) { + _dif_error_set(err_blk, SPDK_DIF_APPTAG_ERROR, ctx->app_tag, + (_app_tag & ctx->apptag_mask), offset_blocks); + SPDK_ERRLOG("Failed to compare App Tag: LBA=%" PRIu32 "," \ + " Expected=%x, Actual=%x\n", + ref_tag, ctx->app_tag, (_app_tag & ctx->apptag_mask)); + return -1; + } + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_REFTAG_CHECK) { + switch (ctx->dif_type) { + case SPDK_DIF_TYPE1: + case SPDK_DIF_TYPE2: + /* Compare the DIF Reference Tag field to the passed Reference Tag. + * The passed Reference Tag will be the least significant 4 bytes + * of the LBA when Type 1 is used, and application specific value + * if Type 2 is used, + */ + _ref_tag = from_be32(&dif->ref_tag); + if (_ref_tag != ref_tag) { + _dif_error_set(err_blk, SPDK_DIF_REFTAG_ERROR, ref_tag, + _ref_tag, offset_blocks); + SPDK_ERRLOG("Failed to compare Ref Tag: LBA=%" PRIu32 "," \ + " Expected=%x, Actual=%x\n", + ref_tag, ref_tag, _ref_tag); + return -1; + } + break; + case SPDK_DIF_TYPE3: + /* For Type 3, computed Reference Tag remains unchanged. + * Hence ignore the Reference Tag field. + */ + break; + default: + break; + } + } + + return 0; +} + +static int +dif_verify(struct _dif_sgl *sgl, uint32_t num_blocks, + const struct spdk_dif_ctx *ctx, struct spdk_dif_error *err_blk) +{ + uint32_t offset_blocks = 0; + int rc; + void *buf; + uint16_t guard = 0; + + while (offset_blocks < num_blocks) { + _dif_sgl_get_buf(sgl, &buf, NULL); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(ctx->guard_seed, buf, ctx->guard_interval); + } + + rc = _dif_verify(buf + ctx->guard_interval, guard, offset_blocks, ctx, err_blk); + if (rc != 0) { + return rc; + } + + _dif_sgl_advance(sgl, ctx->block_size); + offset_blocks++; + } + + return 0; +} + +static int +_dif_verify_split(struct _dif_sgl *sgl, uint32_t offset_in_block, uint32_t data_len, + uint16_t *_guard, uint32_t offset_blocks, + const struct spdk_dif_ctx *ctx, struct spdk_dif_error *err_blk) +{ + uint32_t offset_in_dif, buf_len; + void *buf; + uint16_t guard; + struct spdk_dif dif = {}; + int rc; + + assert(_guard != NULL); + assert(offset_in_block < ctx->guard_interval); + assert(offset_in_block + data_len < ctx->guard_interval || + offset_in_block + data_len == ctx->block_size); + + guard = *_guard; + + /* Compute CRC over split logical block data. */ + while (data_len != 0 && offset_in_block < ctx->guard_interval) { + _dif_sgl_get_buf(sgl, &buf, &buf_len); + buf_len = spdk_min(buf_len, data_len); + buf_len = spdk_min(buf_len, ctx->guard_interval - offset_in_block); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(guard, buf, buf_len); + } + + _dif_sgl_advance(sgl, buf_len); + offset_in_block += buf_len; + data_len -= buf_len; + } + + if (offset_in_block < ctx->guard_interval) { + *_guard = guard; + return 0; + } + + /* Copy the split DIF field to the temporary DIF buffer, and then + * skip metadata field after DIF field (if any). */ + while (offset_in_block < ctx->block_size) { + _dif_sgl_get_buf(sgl, &buf, &buf_len); + + if (offset_in_block < ctx->guard_interval + sizeof(struct spdk_dif)) { + offset_in_dif = offset_in_block - ctx->guard_interval; + buf_len = spdk_min(buf_len, sizeof(struct spdk_dif) - offset_in_dif); + + memcpy((uint8_t *)&dif + offset_in_dif, buf, buf_len); + } else { + buf_len = spdk_min(buf_len, ctx->block_size - offset_in_block); + } + _dif_sgl_advance(sgl, buf_len); + offset_in_block += buf_len; + } + + rc = _dif_verify(&dif, guard, offset_blocks, ctx, err_blk); + if (rc != 0) { + return rc; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->guard_seed; + } + + *_guard = guard; + return 0; +} + +static int +dif_verify_split(struct _dif_sgl *sgl, uint32_t num_blocks, + const struct spdk_dif_ctx *ctx, struct spdk_dif_error *err_blk) +{ + uint32_t offset_blocks; + uint16_t guard = 0; + int rc; + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->guard_seed; + } + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + rc = _dif_verify_split(sgl, 0, ctx->block_size, &guard, offset_blocks, + ctx, err_blk); + if (rc != 0) { + return rc; + } + } + + return 0; +} + +int +spdk_dif_verify(struct iovec *iovs, int iovcnt, uint32_t num_blocks, + const struct spdk_dif_ctx *ctx, struct spdk_dif_error *err_blk) +{ + struct _dif_sgl sgl; + + _dif_sgl_init(&sgl, iovs, iovcnt); + + if (!_dif_sgl_is_valid(&sgl, ctx->block_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec array is not valid.\n"); + return -EINVAL; + } + + if (_dif_is_disabled(ctx->dif_type)) { + return 0; + } + + if (_dif_sgl_is_bytes_multiple(&sgl, ctx->block_size)) { + return dif_verify(&sgl, num_blocks, ctx, err_blk); + } else { + return dif_verify_split(&sgl, num_blocks, ctx, err_blk); + } +} + +static uint32_t +dif_update_crc32c(struct _dif_sgl *sgl, uint32_t num_blocks, + uint32_t crc32c, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_blocks; + void *buf; + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + _dif_sgl_get_buf(sgl, &buf, NULL); + + crc32c = spdk_crc32c_update(buf, ctx->block_size - ctx->md_size, crc32c); + + _dif_sgl_advance(sgl, ctx->block_size); + } + + return crc32c; +} + +static uint32_t +_dif_update_crc32c_split(struct _dif_sgl *sgl, uint32_t offset_in_block, uint32_t data_len, + uint32_t crc32c, const struct spdk_dif_ctx *ctx) +{ + uint32_t data_block_size, buf_len; + void *buf; + + data_block_size = ctx->block_size - ctx->md_size; + + assert(offset_in_block + data_len <= ctx->block_size); + + while (data_len != 0) { + _dif_sgl_get_buf(sgl, &buf, &buf_len); + buf_len = spdk_min(buf_len, data_len); + + if (offset_in_block < data_block_size) { + buf_len = spdk_min(buf_len, data_block_size - offset_in_block); + crc32c = spdk_crc32c_update(buf, buf_len, crc32c); + } + + _dif_sgl_advance(sgl, buf_len); + offset_in_block += buf_len; + data_len -= buf_len; + } + + return crc32c; +} + +static uint32_t +dif_update_crc32c_split(struct _dif_sgl *sgl, uint32_t num_blocks, + uint32_t crc32c, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_blocks; + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + crc32c = _dif_update_crc32c_split(sgl, 0, ctx->block_size, crc32c, ctx); + } + + return crc32c; +} + +int +spdk_dif_update_crc32c(struct iovec *iovs, int iovcnt, uint32_t num_blocks, + uint32_t *_crc32c, const struct spdk_dif_ctx *ctx) +{ + struct _dif_sgl sgl; + + if (_crc32c == NULL) { + return -EINVAL; + } + + _dif_sgl_init(&sgl, iovs, iovcnt); + + if (!_dif_sgl_is_valid(&sgl, ctx->block_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec array is not valid.\n"); + return -EINVAL; + } + + if (_dif_sgl_is_bytes_multiple(&sgl, ctx->block_size)) { + *_crc32c = dif_update_crc32c(&sgl, num_blocks, *_crc32c, ctx); + } else { + *_crc32c = dif_update_crc32c_split(&sgl, num_blocks, *_crc32c, ctx); + } + + return 0; +} + +static void +dif_generate_copy(struct _dif_sgl *src_sgl, struct _dif_sgl *dst_sgl, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_blocks = 0, data_block_size; + void *src, *dst; + uint16_t guard; + + data_block_size = ctx->block_size - ctx->md_size; + + while (offset_blocks < num_blocks) { + _dif_sgl_get_buf(src_sgl, &src, NULL); + _dif_sgl_get_buf(dst_sgl, &dst, NULL); + + guard = 0; + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif_copy(ctx->guard_seed, dst, src, data_block_size); + guard = spdk_crc16_t10dif(guard, dst + data_block_size, + ctx->guard_interval - data_block_size); + } else { + memcpy(dst, src, data_block_size); + } + + _dif_generate(dst + ctx->guard_interval, guard, offset_blocks, ctx); + + _dif_sgl_advance(src_sgl, data_block_size); + _dif_sgl_advance(dst_sgl, ctx->block_size); + offset_blocks++; + } +} + +static void +_dif_generate_copy_split(struct _dif_sgl *src_sgl, struct _dif_sgl *dst_sgl, + uint32_t offset_blocks, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_in_block, src_len, data_block_size; + uint16_t guard = 0; + void *src, *dst; + + _dif_sgl_get_buf(dst_sgl, &dst, NULL); + + data_block_size = ctx->block_size - ctx->md_size; + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->guard_seed; + } + offset_in_block = 0; + + while (offset_in_block < data_block_size) { + /* Compute CRC over split logical block data and copy + * data to bounce buffer. + */ + _dif_sgl_get_buf(src_sgl, &src, &src_len); + src_len = spdk_min(src_len, data_block_size - offset_in_block); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif_copy(guard, dst + offset_in_block, + src, src_len); + } else { + memcpy(dst + offset_in_block, src, src_len); + } + + _dif_sgl_advance(src_sgl, src_len); + offset_in_block += src_len; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(guard, dst + data_block_size, + ctx->guard_interval - data_block_size); + } + + _dif_sgl_advance(dst_sgl, ctx->block_size); + + _dif_generate(dst + ctx->guard_interval, guard, offset_blocks, ctx); +} + +static void +dif_generate_copy_split(struct _dif_sgl *src_sgl, struct _dif_sgl *dst_sgl, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_blocks; + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + _dif_generate_copy_split(src_sgl, dst_sgl, offset_blocks, ctx); + } +} + +int +spdk_dif_generate_copy(struct iovec *iovs, int iovcnt, struct iovec *bounce_iov, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx) +{ + struct _dif_sgl src_sgl, dst_sgl; + uint32_t data_block_size; + + _dif_sgl_init(&src_sgl, iovs, iovcnt); + _dif_sgl_init(&dst_sgl, bounce_iov, 1); + + data_block_size = ctx->block_size - ctx->md_size; + + if (!_dif_sgl_is_valid(&src_sgl, data_block_size * num_blocks) || + !_dif_sgl_is_valid(&dst_sgl, ctx->block_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec arrays are not valid.\n"); + return -EINVAL; + } + + if (_dif_is_disabled(ctx->dif_type)) { + return 0; + } + + if (_dif_sgl_is_bytes_multiple(&src_sgl, data_block_size)) { + dif_generate_copy(&src_sgl, &dst_sgl, num_blocks, ctx); + } else { + dif_generate_copy_split(&src_sgl, &dst_sgl, num_blocks, ctx); + } + + return 0; +} + +static int +dif_verify_copy(struct _dif_sgl *src_sgl, struct _dif_sgl *dst_sgl, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + uint32_t offset_blocks = 0, data_block_size; + void *src, *dst; + int rc; + uint16_t guard; + + data_block_size = ctx->block_size - ctx->md_size; + + while (offset_blocks < num_blocks) { + _dif_sgl_get_buf(src_sgl, &src, NULL); + _dif_sgl_get_buf(dst_sgl, &dst, NULL); + + guard = 0; + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif_copy(ctx->guard_seed, dst, src, data_block_size); + guard = spdk_crc16_t10dif(guard, src + data_block_size, + ctx->guard_interval - data_block_size); + } else { + memcpy(dst, src, data_block_size); + } + + rc = _dif_verify(src + ctx->guard_interval, guard, offset_blocks, ctx, err_blk); + if (rc != 0) { + return rc; + } + + _dif_sgl_advance(src_sgl, ctx->block_size); + _dif_sgl_advance(dst_sgl, data_block_size); + offset_blocks++; + } + + return 0; +} + +static int +_dif_verify_copy_split(struct _dif_sgl *src_sgl, struct _dif_sgl *dst_sgl, + uint32_t offset_blocks, const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + uint32_t offset_in_block, dst_len, data_block_size; + uint16_t guard = 0; + void *src, *dst; + + _dif_sgl_get_buf(src_sgl, &src, NULL); + + data_block_size = ctx->block_size - ctx->md_size; + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->guard_seed; + } + offset_in_block = 0; + + while (offset_in_block < data_block_size) { + /* Compute CRC over split logical block data and copy + * data to bounce buffer. + */ + _dif_sgl_get_buf(dst_sgl, &dst, &dst_len); + dst_len = spdk_min(dst_len, data_block_size - offset_in_block); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif_copy(guard, dst, + src + offset_in_block, dst_len); + } else { + memcpy(dst, src + offset_in_block, dst_len); + } + + _dif_sgl_advance(dst_sgl, dst_len); + offset_in_block += dst_len; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(guard, src + data_block_size, + ctx->guard_interval - data_block_size); + } + + _dif_sgl_advance(src_sgl, ctx->block_size); + + return _dif_verify(src + ctx->guard_interval, guard, offset_blocks, ctx, err_blk); +} + +static int +dif_verify_copy_split(struct _dif_sgl *src_sgl, struct _dif_sgl *dst_sgl, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + uint32_t offset_blocks; + int rc; + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + rc = _dif_verify_copy_split(src_sgl, dst_sgl, offset_blocks, ctx, err_blk); + if (rc != 0) { + return rc; + } + } + + return 0; +} + +int +spdk_dif_verify_copy(struct iovec *iovs, int iovcnt, struct iovec *bounce_iov, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + struct _dif_sgl src_sgl, dst_sgl; + uint32_t data_block_size; + + _dif_sgl_init(&src_sgl, bounce_iov, 1); + _dif_sgl_init(&dst_sgl, iovs, iovcnt); + + data_block_size = ctx->block_size - ctx->md_size; + + if (!_dif_sgl_is_valid(&dst_sgl, data_block_size * num_blocks) || + !_dif_sgl_is_valid(&src_sgl, ctx->block_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec arrays are not valid\n"); + return -EINVAL; + } + + if (_dif_is_disabled(ctx->dif_type)) { + return 0; + } + + if (_dif_sgl_is_bytes_multiple(&dst_sgl, data_block_size)) { + return dif_verify_copy(&src_sgl, &dst_sgl, num_blocks, ctx, err_blk); + } else { + return dif_verify_copy_split(&src_sgl, &dst_sgl, num_blocks, ctx, err_blk); + } +} + +static void +_bit_flip(uint8_t *buf, uint32_t flip_bit) +{ + uint8_t byte; + + byte = *buf; + byte ^= 1 << flip_bit; + *buf = byte; +} + +static int +_dif_inject_error(struct _dif_sgl *sgl, + uint32_t block_size, uint32_t num_blocks, + uint32_t inject_offset_blocks, + uint32_t inject_offset_bytes, + uint32_t inject_offset_bits) +{ + uint32_t offset_in_block, buf_len; + void *buf; + + _dif_sgl_advance(sgl, block_size * inject_offset_blocks); + + offset_in_block = 0; + + while (offset_in_block < block_size) { + _dif_sgl_get_buf(sgl, &buf, &buf_len); + buf_len = spdk_min(buf_len, block_size - offset_in_block); + + if (inject_offset_bytes >= offset_in_block && + inject_offset_bytes < offset_in_block + buf_len) { + buf += inject_offset_bytes - offset_in_block; + _bit_flip(buf, inject_offset_bits); + return 0; + } + + _dif_sgl_advance(sgl, buf_len); + offset_in_block += buf_len; + } + + return -1; +} + +static int +dif_inject_error(struct _dif_sgl *sgl, uint32_t block_size, uint32_t num_blocks, + uint32_t start_inject_bytes, uint32_t inject_range_bytes, + uint32_t *inject_offset) +{ + uint32_t inject_offset_blocks, inject_offset_bytes, inject_offset_bits; + uint32_t offset_blocks; + int rc; + + srand(time(0)); + + inject_offset_blocks = rand() % num_blocks; + inject_offset_bytes = start_inject_bytes + (rand() % inject_range_bytes); + inject_offset_bits = rand() % 8; + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + if (offset_blocks == inject_offset_blocks) { + rc = _dif_inject_error(sgl, block_size, num_blocks, + inject_offset_blocks, + inject_offset_bytes, + inject_offset_bits); + if (rc == 0) { + *inject_offset = inject_offset_blocks; + } + return rc; + } + } + + return -1; +} + +#define _member_size(type, member) sizeof(((type *)0)->member) + +int +spdk_dif_inject_error(struct iovec *iovs, int iovcnt, uint32_t num_blocks, + const struct spdk_dif_ctx *ctx, uint32_t inject_flags, + uint32_t *inject_offset) +{ + struct _dif_sgl sgl; + int rc; + + _dif_sgl_init(&sgl, iovs, iovcnt); + + if (!_dif_sgl_is_valid(&sgl, ctx->block_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec array is not valid.\n"); + return -EINVAL; + } + + if (inject_flags & SPDK_DIF_REFTAG_ERROR) { + rc = dif_inject_error(&sgl, ctx->block_size, num_blocks, + ctx->guard_interval + offsetof(struct spdk_dif, ref_tag), + _member_size(struct spdk_dif, ref_tag), + inject_offset); + if (rc != 0) { + SPDK_ERRLOG("Failed to inject error to Reference Tag.\n"); + return rc; + } + } + + if (inject_flags & SPDK_DIF_APPTAG_ERROR) { + rc = dif_inject_error(&sgl, ctx->block_size, num_blocks, + ctx->guard_interval + offsetof(struct spdk_dif, app_tag), + _member_size(struct spdk_dif, app_tag), + inject_offset); + if (rc != 0) { + SPDK_ERRLOG("Failed to inject error to Application Tag.\n"); + return rc; + } + } + if (inject_flags & SPDK_DIF_GUARD_ERROR) { + rc = dif_inject_error(&sgl, ctx->block_size, num_blocks, + ctx->guard_interval, + _member_size(struct spdk_dif, guard), + inject_offset); + if (rc != 0) { + SPDK_ERRLOG("Failed to inject error to Guard.\n"); + return rc; + } + } + + if (inject_flags & SPDK_DIF_DATA_ERROR) { + /* If the DIF information is contained within the last 8 bytes of + * metadata, then the CRC covers all metadata bytes up to but excluding + * the last 8 bytes. But error injection does not cover these metadata + * because classification is not determined yet. + * + * Note: Error injection to data block is expected to be detected as + * guard error. + */ + rc = dif_inject_error(&sgl, ctx->block_size, num_blocks, + 0, + ctx->block_size - ctx->md_size, + inject_offset); + if (rc != 0) { + SPDK_ERRLOG("Failed to inject error to data block.\n"); + return rc; + } + } + + return 0; +} + +static void +dix_generate(struct _dif_sgl *data_sgl, struct _dif_sgl *md_sgl, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_blocks = 0; + uint16_t guard; + void *data_buf, *md_buf; + + while (offset_blocks < num_blocks) { + _dif_sgl_get_buf(data_sgl, &data_buf, NULL); + _dif_sgl_get_buf(md_sgl, &md_buf, NULL); + + guard = 0; + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(ctx->guard_seed, data_buf, ctx->block_size); + guard = spdk_crc16_t10dif(guard, md_buf, ctx->guard_interval); + } + + _dif_generate(md_buf + ctx->guard_interval, guard, offset_blocks, ctx); + + _dif_sgl_advance(data_sgl, ctx->block_size); + _dif_sgl_advance(md_sgl, ctx->md_size); + offset_blocks++; + } +} + +static void +_dix_generate_split(struct _dif_sgl *data_sgl, struct _dif_sgl *md_sgl, + uint32_t offset_blocks, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_in_block, data_buf_len; + uint16_t guard = 0; + void *data_buf, *md_buf; + + _dif_sgl_get_buf(md_sgl, &md_buf, NULL); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->guard_seed; + } + offset_in_block = 0; + + while (offset_in_block < ctx->block_size) { + _dif_sgl_get_buf(data_sgl, &data_buf, &data_buf_len); + data_buf_len = spdk_min(data_buf_len, ctx->block_size - offset_in_block); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(guard, data_buf, data_buf_len); + } + + _dif_sgl_advance(data_sgl, data_buf_len); + offset_in_block += data_buf_len; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(guard, md_buf, ctx->guard_interval); + } + + _dif_sgl_advance(md_sgl, ctx->md_size); + + _dif_generate(md_buf + ctx->guard_interval, guard, offset_blocks, ctx); +} + +static void +dix_generate_split(struct _dif_sgl *data_sgl, struct _dif_sgl *md_sgl, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx) +{ + uint32_t offset_blocks; + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + _dix_generate_split(data_sgl, md_sgl, offset_blocks, ctx); + } +} + +int +spdk_dix_generate(struct iovec *iovs, int iovcnt, struct iovec *md_iov, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx) +{ + struct _dif_sgl data_sgl, md_sgl; + + _dif_sgl_init(&data_sgl, iovs, iovcnt); + _dif_sgl_init(&md_sgl, md_iov, 1); + + if (!_dif_sgl_is_valid(&data_sgl, ctx->block_size * num_blocks) || + !_dif_sgl_is_valid(&md_sgl, ctx->md_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec array is not valid.\n"); + return -EINVAL; + } + + if (_dif_is_disabled(ctx->dif_type)) { + return 0; + } + + if (_dif_sgl_is_bytes_multiple(&data_sgl, ctx->block_size)) { + dix_generate(&data_sgl, &md_sgl, num_blocks, ctx); + } else { + dix_generate_split(&data_sgl, &md_sgl, num_blocks, ctx); + } + + return 0; +} + +static int +dix_verify(struct _dif_sgl *data_sgl, struct _dif_sgl *md_sgl, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + uint32_t offset_blocks = 0; + uint16_t guard; + void *data_buf, *md_buf; + int rc; + + while (offset_blocks < num_blocks) { + _dif_sgl_get_buf(data_sgl, &data_buf, NULL); + _dif_sgl_get_buf(md_sgl, &md_buf, NULL); + + guard = 0; + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(ctx->guard_seed, data_buf, ctx->block_size); + guard = spdk_crc16_t10dif(guard, md_buf, ctx->guard_interval); + } + + rc = _dif_verify(md_buf + ctx->guard_interval, guard, offset_blocks, ctx, err_blk); + if (rc != 0) { + return rc; + } + + _dif_sgl_advance(data_sgl, ctx->block_size); + _dif_sgl_advance(md_sgl, ctx->md_size); + offset_blocks++; + } + + return 0; +} + +static int +_dix_verify_split(struct _dif_sgl *data_sgl, struct _dif_sgl *md_sgl, + uint32_t offset_blocks, const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + uint32_t offset_in_block, data_buf_len; + uint16_t guard = 0; + void *data_buf, *md_buf; + + _dif_sgl_get_buf(md_sgl, &md_buf, NULL); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->guard_seed; + } + offset_in_block = 0; + + while (offset_in_block < ctx->block_size) { + _dif_sgl_get_buf(data_sgl, &data_buf, &data_buf_len); + data_buf_len = spdk_min(data_buf_len, ctx->block_size - offset_in_block); + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(guard, data_buf, data_buf_len); + } + + _dif_sgl_advance(data_sgl, data_buf_len); + offset_in_block += data_buf_len; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = spdk_crc16_t10dif(guard, md_buf, ctx->guard_interval); + } + + _dif_sgl_advance(md_sgl, ctx->md_size); + + return _dif_verify(md_buf + ctx->guard_interval, guard, offset_blocks, ctx, err_blk); +} + +static int +dix_verify_split(struct _dif_sgl *data_sgl, struct _dif_sgl *md_sgl, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + uint32_t offset_blocks; + int rc; + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + rc = _dix_verify_split(data_sgl, md_sgl, offset_blocks, ctx, err_blk); + if (rc != 0) { + return rc; + } + } + + return 0; +} + +int +spdk_dix_verify(struct iovec *iovs, int iovcnt, struct iovec *md_iov, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + struct _dif_sgl data_sgl, md_sgl; + + _dif_sgl_init(&data_sgl, iovs, iovcnt); + _dif_sgl_init(&md_sgl, md_iov, 1); + + if (!_dif_sgl_is_valid(&data_sgl, ctx->block_size * num_blocks) || + !_dif_sgl_is_valid(&md_sgl, ctx->md_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec array is not valid.\n"); + return -EINVAL; + } + + if (_dif_is_disabled(ctx->dif_type)) { + return 0; + } + + if (_dif_sgl_is_bytes_multiple(&data_sgl, ctx->block_size)) { + return dix_verify(&data_sgl, &md_sgl, num_blocks, ctx, err_blk); + } else { + return dix_verify_split(&data_sgl, &md_sgl, num_blocks, ctx, err_blk); + } +} + +int +spdk_dix_inject_error(struct iovec *iovs, int iovcnt, struct iovec *md_iov, + uint32_t num_blocks, const struct spdk_dif_ctx *ctx, + uint32_t inject_flags, uint32_t *inject_offset) +{ + struct _dif_sgl data_sgl, md_sgl; + int rc; + + _dif_sgl_init(&data_sgl, iovs, iovcnt); + _dif_sgl_init(&md_sgl, md_iov, 1); + + if (!_dif_sgl_is_valid(&data_sgl, ctx->block_size * num_blocks) || + !_dif_sgl_is_valid(&md_sgl, ctx->md_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec array is not valid.\n"); + return -EINVAL; + } + + if (inject_flags & SPDK_DIF_REFTAG_ERROR) { + rc = dif_inject_error(&md_sgl, ctx->md_size, num_blocks, + ctx->guard_interval + offsetof(struct spdk_dif, ref_tag), + _member_size(struct spdk_dif, ref_tag), + inject_offset); + if (rc != 0) { + SPDK_ERRLOG("Failed to inject error to Reference Tag.\n"); + return rc; + } + } + + if (inject_flags & SPDK_DIF_APPTAG_ERROR) { + rc = dif_inject_error(&md_sgl, ctx->md_size, num_blocks, + ctx->guard_interval + offsetof(struct spdk_dif, app_tag), + _member_size(struct spdk_dif, app_tag), + inject_offset); + if (rc != 0) { + SPDK_ERRLOG("Failed to inject error to Application Tag.\n"); + return rc; + } + } + + if (inject_flags & SPDK_DIF_GUARD_ERROR) { + rc = dif_inject_error(&md_sgl, ctx->md_size, num_blocks, + ctx->guard_interval, + _member_size(struct spdk_dif, guard), + inject_offset); + if (rc != 0) { + SPDK_ERRLOG("Failed to inject error to Guard.\n"); + return rc; + } + } + + if (inject_flags & SPDK_DIF_DATA_ERROR) { + /* Note: Error injection to data block is expected to be detected + * as guard error. + */ + rc = dif_inject_error(&data_sgl, ctx->block_size, num_blocks, + 0, + ctx->block_size, + inject_offset); + if (rc != 0) { + SPDK_ERRLOG("Failed to inject error to Guard.\n"); + return rc; + } + } + + return 0; +} + +static uint32_t +_to_next_boundary(uint32_t offset, uint32_t boundary) +{ + return boundary - (offset % boundary); +} + +static uint32_t +_to_size_with_md(uint32_t size, uint32_t data_block_size, uint32_t block_size) +{ + return (size / data_block_size) * block_size + (size % data_block_size); +} + +int +spdk_dif_set_md_interleave_iovs(struct iovec *iovs, int iovcnt, + struct iovec *buf_iovs, int buf_iovcnt, + uint32_t data_offset, uint32_t data_len, + uint32_t *_mapped_len, + const struct spdk_dif_ctx *ctx) +{ + uint32_t data_block_size, data_unalign, buf_len, buf_offset, len; + struct _dif_sgl dif_sgl; + struct _dif_sgl buf_sgl; + + if (iovs == NULL || iovcnt == 0 || buf_iovs == NULL || buf_iovcnt == 0) { + return -EINVAL; + } + + data_block_size = ctx->block_size - ctx->md_size; + + data_unalign = ctx->data_offset % data_block_size; + + buf_len = _to_size_with_md(data_unalign + data_offset + data_len, data_block_size, + ctx->block_size); + buf_len -= data_unalign; + + _dif_sgl_init(&dif_sgl, iovs, iovcnt); + _dif_sgl_init(&buf_sgl, buf_iovs, buf_iovcnt); + + if (!_dif_sgl_is_valid(&buf_sgl, buf_len)) { + SPDK_ERRLOG("Buffer overflow will occur.\n"); + return -ERANGE; + } + + buf_offset = _to_size_with_md(data_unalign + data_offset, data_block_size, ctx->block_size); + buf_offset -= data_unalign; + + _dif_sgl_advance(&buf_sgl, buf_offset); + + while (data_len != 0) { + len = spdk_min(data_len, _to_next_boundary(ctx->data_offset + data_offset, data_block_size)); + if (!_dif_sgl_append_split(&dif_sgl, &buf_sgl, len)) { + break; + } + _dif_sgl_advance(&buf_sgl, ctx->md_size); + data_offset += len; + data_len -= len; + } + + if (_mapped_len != NULL) { + *_mapped_len = dif_sgl.total_size; + } + + return iovcnt - dif_sgl.iovcnt; +} + +static int +_dif_sgl_setup_stream(struct _dif_sgl *sgl, uint32_t *_buf_offset, uint32_t *_buf_len, + uint32_t data_offset, uint32_t data_len, + const struct spdk_dif_ctx *ctx) +{ + uint32_t data_block_size, data_unalign, buf_len, buf_offset; + + data_block_size = ctx->block_size - ctx->md_size; + + data_unalign = ctx->data_offset % data_block_size; + + /* If the last data block is complete, DIF of the data block is + * inserted or verified in this turn. + */ + buf_len = _to_size_with_md(data_unalign + data_offset + data_len, data_block_size, + ctx->block_size); + buf_len -= data_unalign; + + if (!_dif_sgl_is_valid(sgl, buf_len)) { + return -ERANGE; + } + + buf_offset = _to_size_with_md(data_unalign + data_offset, data_block_size, ctx->block_size); + buf_offset -= data_unalign; + + _dif_sgl_advance(sgl, buf_offset); + buf_len -= buf_offset; + + buf_offset += data_unalign; + + *_buf_offset = buf_offset; + *_buf_len = buf_len; + + return 0; +} + +int +spdk_dif_generate_stream(struct iovec *iovs, int iovcnt, + uint32_t data_offset, uint32_t data_len, + struct spdk_dif_ctx *ctx) +{ + uint32_t buf_len = 0, buf_offset = 0; + uint32_t len, offset_in_block, offset_blocks; + uint16_t guard = 0; + struct _dif_sgl sgl; + int rc; + + if (iovs == NULL || iovcnt == 0) { + return -EINVAL; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->last_guard; + } + + _dif_sgl_init(&sgl, iovs, iovcnt); + + rc = _dif_sgl_setup_stream(&sgl, &buf_offset, &buf_len, data_offset, data_len, ctx); + if (rc != 0) { + return rc; + } + + while (buf_len != 0) { + len = spdk_min(buf_len, _to_next_boundary(buf_offset, ctx->block_size)); + offset_in_block = buf_offset % ctx->block_size; + offset_blocks = buf_offset / ctx->block_size; + + guard = _dif_generate_split(&sgl, offset_in_block, len, guard, offset_blocks, ctx); + + buf_len -= len; + buf_offset += len; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + ctx->last_guard = guard; + } + + return 0; +} + +int +spdk_dif_verify_stream(struct iovec *iovs, int iovcnt, + uint32_t data_offset, uint32_t data_len, + struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + uint32_t buf_len = 0, buf_offset = 0; + uint32_t len, offset_in_block, offset_blocks; + uint16_t guard = 0; + struct _dif_sgl sgl; + int rc = 0; + + if (iovs == NULL || iovcnt == 0) { + return -EINVAL; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + guard = ctx->last_guard; + } + + _dif_sgl_init(&sgl, iovs, iovcnt); + + rc = _dif_sgl_setup_stream(&sgl, &buf_offset, &buf_len, data_offset, data_len, ctx); + if (rc != 0) { + return rc; + } + + while (buf_len != 0) { + len = spdk_min(buf_len, _to_next_boundary(buf_offset, ctx->block_size)); + offset_in_block = buf_offset % ctx->block_size; + offset_blocks = buf_offset / ctx->block_size; + + rc = _dif_verify_split(&sgl, offset_in_block, len, &guard, offset_blocks, + ctx, err_blk); + if (rc != 0) { + goto error; + } + + buf_len -= len; + buf_offset += len; + } + + if (ctx->dif_flags & SPDK_DIF_FLAGS_GUARD_CHECK) { + ctx->last_guard = guard; + } +error: + return rc; +} + +int +spdk_dif_update_crc32c_stream(struct iovec *iovs, int iovcnt, + uint32_t data_offset, uint32_t data_len, + uint32_t *_crc32c, const struct spdk_dif_ctx *ctx) +{ + uint32_t buf_len = 0, buf_offset = 0, len, offset_in_block; + uint32_t crc32c; + struct _dif_sgl sgl; + int rc; + + if (iovs == NULL || iovcnt == 0) { + return -EINVAL; + } + + crc32c = *_crc32c; + _dif_sgl_init(&sgl, iovs, iovcnt); + + rc = _dif_sgl_setup_stream(&sgl, &buf_offset, &buf_len, data_offset, data_len, ctx); + if (rc != 0) { + return rc; + } + + while (buf_len != 0) { + len = spdk_min(buf_len, _to_next_boundary(buf_offset, ctx->block_size)); + offset_in_block = buf_offset % ctx->block_size; + + crc32c = _dif_update_crc32c_split(&sgl, offset_in_block, len, crc32c, ctx); + + buf_len -= len; + buf_offset += len; + } + + *_crc32c = crc32c; + + return 0; +} + +void +spdk_dif_get_range_with_md(uint32_t data_offset, uint32_t data_len, + uint32_t *_buf_offset, uint32_t *_buf_len, + const struct spdk_dif_ctx *ctx) +{ + uint32_t data_block_size, data_unalign, buf_offset, buf_len; + + if (!ctx->md_interleave) { + buf_offset = data_offset; + buf_len = data_len; + } else { + data_block_size = ctx->block_size - ctx->md_size; + + data_unalign = data_offset % data_block_size; + + buf_offset = _to_size_with_md(data_offset, data_block_size, ctx->block_size); + buf_len = _to_size_with_md(data_unalign + data_len, data_block_size, ctx->block_size) - + data_unalign; + } + + if (_buf_offset != NULL) { + *_buf_offset = buf_offset; + } + + if (_buf_len != NULL) { + *_buf_len = buf_len; + } +} + +uint32_t +spdk_dif_get_length_with_md(uint32_t data_len, const struct spdk_dif_ctx *ctx) +{ + uint32_t data_block_size; + + if (!ctx->md_interleave) { + return data_len; + } else { + data_block_size = ctx->block_size - ctx->md_size; + + return _to_size_with_md(data_len, data_block_size, ctx->block_size); + } +} + +static int +_dif_remap_ref_tag(struct _dif_sgl *sgl, uint32_t offset_blocks, + const struct spdk_dif_ctx *ctx, struct spdk_dif_error *err_blk) +{ + uint32_t offset, buf_len, expected = 0, _actual, remapped; + void *buf; + struct _dif_sgl tmp_sgl; + struct spdk_dif dif; + + /* Fast forward to DIF field. */ + _dif_sgl_advance(sgl, ctx->guard_interval); + _dif_sgl_copy(&tmp_sgl, sgl); + + /* Copy the split DIF field to the temporary DIF buffer */ + offset = 0; + while (offset < sizeof(struct spdk_dif)) { + _dif_sgl_get_buf(sgl, &buf, &buf_len); + buf_len = spdk_min(buf_len, sizeof(struct spdk_dif) - offset); + + memcpy((uint8_t *)&dif + offset, buf, buf_len); + + _dif_sgl_advance(sgl, buf_len); + offset += buf_len; + } + + switch (ctx->dif_type) { + case SPDK_DIF_TYPE1: + case SPDK_DIF_TYPE2: + /* If Type 1 or 2 is used, then all DIF checks are disabled when + * the Application Tag is 0xFFFF. + */ + if (dif.app_tag == 0xFFFF) { + goto end; + } + break; + case SPDK_DIF_TYPE3: + /* If Type 3 is used, then all DIF checks are disabled when the + * Application Tag is 0xFFFF and the Reference Tag is 0xFFFFFFFF. + */ + if (dif.app_tag == 0xFFFF && dif.ref_tag == 0xFFFFFFFF) { + goto end; + } + break; + default: + break; + } + + /* For type 1 and 2, the Reference Tag is incremented for each + * subsequent logical block. For type 3, the Reference Tag + * remains the same as the initial Reference Tag. + */ + if (ctx->dif_type != SPDK_DIF_TYPE3) { + expected = ctx->init_ref_tag + ctx->ref_tag_offset + offset_blocks; + remapped = ctx->remapped_init_ref_tag + ctx->ref_tag_offset + offset_blocks; + } else { + remapped = ctx->remapped_init_ref_tag; + } + + /* Verify the stored Reference Tag. */ + switch (ctx->dif_type) { + case SPDK_DIF_TYPE1: + case SPDK_DIF_TYPE2: + /* Compare the DIF Reference Tag field to the computed Reference Tag. + * The computed Reference Tag will be the least significant 4 bytes + * of the LBA when Type 1 is used, and application specific value + * if Type 2 is used. + */ + _actual = from_be32(&dif.ref_tag); + if (_actual != expected) { + _dif_error_set(err_blk, SPDK_DIF_REFTAG_ERROR, expected, + _actual, offset_blocks); + SPDK_ERRLOG("Failed to compare Ref Tag: LBA=%" PRIu32 "," \ + " Expected=%x, Actual=%x\n", + expected, expected, _actual); + return -1; + } + break; + case SPDK_DIF_TYPE3: + /* For type 3, the computed Reference Tag remains unchanged. + * Hence ignore the Reference Tag field. + */ + break; + default: + break; + } + + /* Update the stored Reference Tag to the remapped one. */ + to_be32(&dif.ref_tag, remapped); + + offset = 0; + while (offset < sizeof(struct spdk_dif)) { + _dif_sgl_get_buf(&tmp_sgl, &buf, &buf_len); + buf_len = spdk_min(buf_len, sizeof(struct spdk_dif) - offset); + + memcpy(buf, (uint8_t *)&dif + offset, buf_len); + + _dif_sgl_advance(&tmp_sgl, buf_len); + offset += buf_len; + } + +end: + _dif_sgl_advance(sgl, ctx->block_size - ctx->guard_interval - sizeof(struct spdk_dif)); + + return 0; +} + +int +spdk_dif_remap_ref_tag(struct iovec *iovs, int iovcnt, uint32_t num_blocks, + const struct spdk_dif_ctx *ctx, struct spdk_dif_error *err_blk) +{ + struct _dif_sgl sgl; + uint32_t offset_blocks; + int rc; + + _dif_sgl_init(&sgl, iovs, iovcnt); + + if (!_dif_sgl_is_valid(&sgl, ctx->block_size * num_blocks)) { + SPDK_ERRLOG("Size of iovec array is not valid.\n"); + return -EINVAL; + } + + if (_dif_is_disabled(ctx->dif_type)) { + return 0; + } + + if (!(ctx->dif_flags & SPDK_DIF_FLAGS_REFTAG_CHECK)) { + return 0; + } + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + rc = _dif_remap_ref_tag(&sgl, offset_blocks, ctx, err_blk); + if (rc != 0) { + return rc; + } + } + + return 0; +} + +static int +_dix_remap_ref_tag(struct _dif_sgl *md_sgl, uint32_t offset_blocks, + const struct spdk_dif_ctx *ctx, struct spdk_dif_error *err_blk) +{ + uint32_t expected = 0, _actual, remapped; + uint8_t *md_buf; + struct spdk_dif *dif; + + _dif_sgl_get_buf(md_sgl, (void *)&md_buf, NULL); + + dif = (struct spdk_dif *)(md_buf + ctx->guard_interval); + + switch (ctx->dif_type) { + case SPDK_DIF_TYPE1: + case SPDK_DIF_TYPE2: + /* If Type 1 or 2 is used, then all DIF checks are disabled when + * the Application Tag is 0xFFFF. + */ + if (dif->app_tag == 0xFFFF) { + goto end; + } + break; + case SPDK_DIF_TYPE3: + /* If Type 3 is used, then all DIF checks are disabled when the + * Application Tag is 0xFFFF and the Reference Tag is 0xFFFFFFFF. + */ + if (dif->app_tag == 0xFFFF && dif->ref_tag == 0xFFFFFFFF) { + goto end; + } + break; + default: + break; + } + + /* For type 1 and 2, the Reference Tag is incremented for each + * subsequent logical block. For type 3, the Reference Tag + * remains the same as the initialReference Tag. + */ + if (ctx->dif_type != SPDK_DIF_TYPE3) { + expected = ctx->init_ref_tag + ctx->ref_tag_offset + offset_blocks; + remapped = ctx->remapped_init_ref_tag + ctx->ref_tag_offset + offset_blocks; + } else { + remapped = ctx->remapped_init_ref_tag; + } + + /* Verify the stored Reference Tag. */ + switch (ctx->dif_type) { + case SPDK_DIF_TYPE1: + case SPDK_DIF_TYPE2: + /* Compare the DIF Reference Tag field to the computed Reference Tag. + * The computed Reference Tag will be the least significant 4 bytes + * of the LBA when Type 1 is used, and application specific value + * if Type 2 is used. + */ + _actual = from_be32(&dif->ref_tag); + if (_actual != expected) { + _dif_error_set(err_blk, SPDK_DIF_REFTAG_ERROR, expected, + _actual, offset_blocks); + SPDK_ERRLOG("Failed to compare Ref Tag: LBA=%" PRIu32 "," \ + " Expected=%x, Actual=%x\n", + expected, expected, _actual); + return -1; + } + break; + case SPDK_DIF_TYPE3: + /* For type 3, the computed Reference Tag remains unchanged. + * Hence ignore the Reference Tag field. + */ + break; + default: + break; + } + + /* Update the stored Reference Tag to the remapped one. */ + to_be32(&dif->ref_tag, remapped); + +end: + _dif_sgl_advance(md_sgl, ctx->md_size); + + return 0; +} + +int +spdk_dix_remap_ref_tag(struct iovec *md_iov, uint32_t num_blocks, + const struct spdk_dif_ctx *ctx, + struct spdk_dif_error *err_blk) +{ + struct _dif_sgl md_sgl; + uint32_t offset_blocks; + int rc; + + _dif_sgl_init(&md_sgl, md_iov, 1); + + if (!_dif_sgl_is_valid(&md_sgl, ctx->md_size * num_blocks)) { + SPDK_ERRLOG("Size of metadata iovec array is not valid.\n"); + return -EINVAL; + } + + if (_dif_is_disabled(ctx->dif_type)) { + return 0; + } + + if (!(ctx->dif_flags & SPDK_DIF_FLAGS_REFTAG_CHECK)) { + return 0; + } + + for (offset_blocks = 0; offset_blocks < num_blocks; offset_blocks++) { + rc = _dix_remap_ref_tag(&md_sgl, offset_blocks, ctx, err_blk); + if (rc != 0) { + return rc; + } + } + + return 0; +} |