510 lines
14 KiB
C
510 lines
14 KiB
C
|
/*-
|
||
|
* 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/endian.h"
|
||
|
#include "spdk/log.h"
|
||
|
#include "spdk/util.h"
|
||
|
|
||
|
static bool
|
||
|
_are_iovs_bytes_multiple(struct iovec *iovs, int iovcnt, uint32_t bytes)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < iovcnt; i++) {
|
||
|
if (iovs[i].iov_len % bytes) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool
|
||
|
_are_iovs_valid(struct iovec *iovs, int iovcnt, uint32_t bytes)
|
||
|
{
|
||
|
uint64_t total = 0;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < iovcnt; i++) {
|
||
|
total += iovs[i].iov_len;
|
||
|
}
|
||
|
|
||
|
return total >= bytes;
|
||
|
}
|
||
|
|
||
|
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:
|
||
|
break;
|
||
|
case SPDK_DIF_TYPE3:
|
||
|
if (dif_flags & SPDK_DIF_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 uint32_t
|
||
|
_get_dif_guard_interval(uint32_t block_size, uint32_t md_size, bool dif_loc)
|
||
|
{
|
||
|
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.
|
||
|
*/
|
||
|
return block_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.
|
||
|
*/
|
||
|
return block_size - md_size;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
_dif_generate(void *_dif, uint32_t dif_flags,
|
||
|
uint16_t guard, uint32_t ref_tag, uint16_t app_tag)
|
||
|
{
|
||
|
struct spdk_dif *dif = _dif;
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_GUARD_CHECK) {
|
||
|
to_be16(&dif->guard, guard);
|
||
|
}
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_APPTAG_CHECK) {
|
||
|
to_be16(&dif->app_tag, app_tag);
|
||
|
}
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_REFTAG_CHECK) {
|
||
|
to_be32(&dif->ref_tag, ref_tag);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
dif_generate(struct iovec *iovs, int iovcnt,
|
||
|
uint32_t block_size, uint32_t guard_interval, uint32_t num_blocks,
|
||
|
enum spdk_dif_type dif_type, uint32_t dif_flags,
|
||
|
uint32_t init_ref_tag, uint16_t app_tag)
|
||
|
{
|
||
|
uint32_t offset_blocks, iov_offset, ref_tag;
|
||
|
int iovpos;
|
||
|
void *buf;
|
||
|
uint16_t guard = 0;
|
||
|
|
||
|
offset_blocks = 0;
|
||
|
iov_offset = 0;
|
||
|
iovpos = 0;
|
||
|
|
||
|
while (offset_blocks < num_blocks && iovpos < iovcnt) {
|
||
|
/* 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 (dif_type != SPDK_DIF_TYPE3) {
|
||
|
ref_tag = init_ref_tag + offset_blocks;
|
||
|
} else {
|
||
|
ref_tag = init_ref_tag;
|
||
|
}
|
||
|
|
||
|
buf = iovs[iovpos].iov_base + iov_offset;
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_GUARD_CHECK) {
|
||
|
guard = spdk_crc16_t10dif(0, buf, guard_interval);
|
||
|
}
|
||
|
|
||
|
_dif_generate(buf + guard_interval, dif_flags, guard, ref_tag,
|
||
|
app_tag);
|
||
|
|
||
|
iov_offset += block_size;
|
||
|
if (iov_offset == iovs[iovpos].iov_len) {
|
||
|
iovpos++;
|
||
|
iov_offset = 0;
|
||
|
}
|
||
|
offset_blocks++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
dif_generate_split(struct iovec *iovs, int iovcnt,
|
||
|
uint32_t block_size, uint32_t guard_interval, uint32_t num_blocks,
|
||
|
enum spdk_dif_type dif_type, uint32_t dif_flags,
|
||
|
uint32_t init_ref_tag, uint16_t app_tag)
|
||
|
{
|
||
|
uint32_t offset_blocks, offset_in_block, offset_in_dif;
|
||
|
uint32_t iov_offset, buf_len, ref_tag;
|
||
|
int iovpos;
|
||
|
void *buf;
|
||
|
uint16_t guard;
|
||
|
struct spdk_dif dif = {};
|
||
|
|
||
|
offset_blocks = 0;
|
||
|
iov_offset = 0;
|
||
|
iovpos = 0;
|
||
|
|
||
|
while (offset_blocks < num_blocks && iovpos < iovcnt) {
|
||
|
/* 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 (dif_type != SPDK_DIF_TYPE3) {
|
||
|
ref_tag = init_ref_tag + offset_blocks;
|
||
|
} else {
|
||
|
ref_tag = init_ref_tag;
|
||
|
}
|
||
|
|
||
|
guard = 0;
|
||
|
offset_in_block = 0;
|
||
|
|
||
|
while (offset_in_block < block_size && iovpos < iovcnt) {
|
||
|
buf = iovs[iovpos].iov_base + iov_offset;
|
||
|
buf_len = iovs[iovpos].iov_len - iov_offset;
|
||
|
|
||
|
if (offset_in_block < guard_interval) {
|
||
|
buf_len = spdk_min(buf_len, guard_interval - offset_in_block);
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_GUARD_CHECK) {
|
||
|
/* Compute CRC over split logical block data. */
|
||
|
guard = spdk_crc16_t10dif(guard, buf, buf_len);
|
||
|
}
|
||
|
|
||
|
if (offset_in_block + buf_len == guard_interval) {
|
||
|
/* If a whole logical block data is parsed, generate DIF
|
||
|
* and save it to the temporary DIF area.
|
||
|
*/
|
||
|
_dif_generate(&dif, dif_flags, guard, ref_tag, app_tag);
|
||
|
}
|
||
|
} else if (offset_in_block < guard_interval + sizeof(struct spdk_dif)) {
|
||
|
/* Copy generated DIF to the split DIF field. */
|
||
|
offset_in_dif = offset_in_block - 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 {
|
||
|
/* Skip metadata field after DIF field. */
|
||
|
buf_len = spdk_min(buf_len, block_size - offset_in_block);
|
||
|
}
|
||
|
|
||
|
iov_offset += buf_len;
|
||
|
if (iov_offset == iovs[iovpos].iov_len) {
|
||
|
iovpos++;
|
||
|
iov_offset = 0;
|
||
|
}
|
||
|
offset_in_block += buf_len;
|
||
|
}
|
||
|
offset_blocks++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int
|
||
|
spdk_dif_generate(struct iovec *iovs, int iovcnt,
|
||
|
uint32_t block_size, uint32_t md_size, uint32_t num_blocks,
|
||
|
bool dif_loc, enum spdk_dif_type dif_type, uint32_t dif_flags,
|
||
|
uint32_t init_ref_tag, uint16_t app_tag)
|
||
|
{
|
||
|
uint32_t guard_interval;
|
||
|
|
||
|
if (md_size == 0) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!_are_iovs_valid(iovs, iovcnt, block_size * num_blocks)) {
|
||
|
SPDK_ERRLOG("Size of iovec array is not valid.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!_dif_type_is_valid(dif_type, dif_flags)) {
|
||
|
SPDK_ERRLOG("DIF type is invalid.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
guard_interval = _get_dif_guard_interval(block_size, md_size, dif_loc);
|
||
|
|
||
|
if (_are_iovs_bytes_multiple(iovs, iovcnt, block_size)) {
|
||
|
dif_generate(iovs, iovcnt, block_size, guard_interval, num_blocks,
|
||
|
dif_type, dif_flags, init_ref_tag, app_tag);
|
||
|
} else {
|
||
|
dif_generate_split(iovs, iovcnt, block_size, guard_interval, num_blocks,
|
||
|
dif_type, dif_flags, init_ref_tag, app_tag);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
_dif_verify(void *_dif, enum spdk_dif_type dif_type, uint32_t dif_flags,
|
||
|
uint16_t guard, uint32_t ref_tag, uint16_t apptag_mask, uint16_t app_tag)
|
||
|
{
|
||
|
struct spdk_dif *dif = _dif;
|
||
|
uint16_t _guard;
|
||
|
uint16_t _app_tag;
|
||
|
uint32_t _ref_tag;
|
||
|
|
||
|
switch (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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_GUARD_CHECK) {
|
||
|
/* Compare the DIF Guard field to the CRC computed over the logical
|
||
|
* block data.
|
||
|
*/
|
||
|
_guard = from_be16(&dif->guard);
|
||
|
if (_guard != guard) {
|
||
|
SPDK_ERRLOG("Failed to compare Guard: LBA=%" PRIu32 "," \
|
||
|
" Expected=%x, Actual=%x\n",
|
||
|
ref_tag, _guard, guard);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_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 & apptag_mask) != app_tag) {
|
||
|
SPDK_ERRLOG("Failed to compare App Tag: LBA=%" PRIu32 "," \
|
||
|
" Expected=%x, Actual=%x\n",
|
||
|
ref_tag, app_tag, (_app_tag & apptag_mask));
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_REFTAG_CHECK) {
|
||
|
switch (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) {
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
dif_verify(struct iovec *iovs, int iovcnt,
|
||
|
uint32_t block_size, uint32_t guard_interval, uint32_t num_blocks,
|
||
|
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 offset_blocks, iov_offset, ref_tag;
|
||
|
int iovpos, rc;
|
||
|
void *buf;
|
||
|
uint16_t guard = 0;
|
||
|
|
||
|
offset_blocks = 0;
|
||
|
iov_offset = 0;
|
||
|
iovpos = 0;
|
||
|
|
||
|
while (offset_blocks < num_blocks && iovpos < iovcnt) {
|
||
|
/* 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 (dif_type != SPDK_DIF_TYPE3) {
|
||
|
ref_tag = init_ref_tag + offset_blocks;
|
||
|
} else {
|
||
|
ref_tag = init_ref_tag;
|
||
|
}
|
||
|
|
||
|
buf = iovs[iovpos].iov_base + iov_offset;
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_GUARD_CHECK) {
|
||
|
guard = spdk_crc16_t10dif(0, buf, guard_interval);
|
||
|
}
|
||
|
|
||
|
rc = _dif_verify(buf + guard_interval, dif_type, dif_flags,
|
||
|
guard, ref_tag, apptag_mask, app_tag);
|
||
|
if (rc != 0) {
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
iov_offset += block_size;
|
||
|
if (iov_offset == iovs[iovpos].iov_len) {
|
||
|
iovpos++;
|
||
|
iov_offset = 0;
|
||
|
}
|
||
|
offset_blocks++;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
dif_verify_split(struct iovec *iovs, int iovcnt,
|
||
|
uint32_t block_size, uint32_t guard_interval, uint32_t num_blocks,
|
||
|
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 offset_blocks, offset_in_block, offset_in_dif;
|
||
|
uint32_t iov_offset, buf_len, ref_tag = 0;
|
||
|
int iovpos, rc;
|
||
|
void *buf;
|
||
|
uint16_t guard;
|
||
|
struct spdk_dif dif = {};
|
||
|
|
||
|
offset_blocks = 0;
|
||
|
iov_offset = 0;
|
||
|
iovpos = 0;
|
||
|
|
||
|
while (offset_blocks < num_blocks && iovpos < iovcnt) {
|
||
|
/* 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 (dif_type != SPDK_DIF_TYPE3) {
|
||
|
ref_tag = init_ref_tag + offset_blocks;
|
||
|
} else {
|
||
|
ref_tag = init_ref_tag;
|
||
|
}
|
||
|
|
||
|
guard = 0;
|
||
|
offset_in_block = 0;
|
||
|
|
||
|
while (offset_in_block < block_size && iovpos < iovcnt) {
|
||
|
buf = iovs[iovpos].iov_base + iov_offset;
|
||
|
buf_len = iovs[iovpos].iov_len - iov_offset;
|
||
|
|
||
|
if (offset_in_block < guard_interval) {
|
||
|
buf_len = spdk_min(buf_len, guard_interval - offset_in_block);
|
||
|
|
||
|
if (dif_flags & SPDK_DIF_GUARD_CHECK) {
|
||
|
/* Compute CRC over split logical block data. */
|
||
|
guard = spdk_crc16_t10dif(guard, buf, buf_len);
|
||
|
}
|
||
|
} else if (offset_in_block < guard_interval + sizeof(struct spdk_dif)) {
|
||
|
/* Copy the split DIF field to the temporary DIF buffer. */
|
||
|
offset_in_dif = offset_in_block - 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 {
|
||
|
/* Skip metadata field after DIF field. */
|
||
|
buf_len = spdk_min(buf_len, block_size - offset_in_block);
|
||
|
}
|
||
|
|
||
|
iov_offset += buf_len;
|
||
|
if (iov_offset == iovs[iovpos].iov_len) {
|
||
|
iovpos++;
|
||
|
iov_offset = 0;
|
||
|
}
|
||
|
offset_in_block += buf_len;
|
||
|
}
|
||
|
|
||
|
rc = _dif_verify(&dif, dif_type, dif_flags, guard, ref_tag, apptag_mask, app_tag);
|
||
|
if (rc != 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
offset_blocks++;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
spdk_dif_verify(struct iovec *iovs, int iovcnt,
|
||
|
uint32_t block_size, uint32_t md_size, uint32_t num_blocks,
|
||
|
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 guard_interval;
|
||
|
|
||
|
if (md_size == 0) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!_are_iovs_valid(iovs, iovcnt, block_size * num_blocks)) {
|
||
|
SPDK_ERRLOG("Size of iovec array is not valid.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!_dif_type_is_valid(dif_type, dif_flags)) {
|
||
|
SPDK_ERRLOG("DIF type is invalid.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
guard_interval = _get_dif_guard_interval(block_size, md_size, dif_loc);
|
||
|
|
||
|
if (_are_iovs_bytes_multiple(iovs, iovcnt, block_size)) {
|
||
|
return dif_verify(iovs, iovcnt, block_size, guard_interval, num_blocks,
|
||
|
dif_type, dif_flags, init_ref_tag, apptag_mask, app_tag);
|
||
|
} else {
|
||
|
return dif_verify_split(iovs, iovcnt, block_size, guard_interval, num_blocks,
|
||
|
dif_type, dif_flags, init_ref_tag, apptag_mask, app_tag);
|
||
|
}
|
||
|
}
|