mirror of
https://github.com/SCST-project/scst.git
synced 2026-05-14 01:01:27 +00:00
Kernel v6.15+ removed LIBCRC32C and switched in-tree users to CRC32. Keep older kernels working by accepting either CRC32 or LIBCRC32C in Kconfig and preprocessor guards, so crc32c() usage compiles and links on both old and new kernels.
245 lines
6.3 KiB
C
245 lines
6.3 KiB
C
/*
|
|
* iSCSI digest handling.
|
|
*
|
|
* Copyright (C) 2004 - 2006 Xiranet Communications GmbH
|
|
* <arne.redlich@xiranet.com>
|
|
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin
|
|
* Copyright (C) 2007 - 2018 Western Digital Corporation
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/scatterlist.h>
|
|
|
|
#include "iscsi_trace_flag.h"
|
|
#include "iscsi.h"
|
|
#include "digest.h"
|
|
#include <linux/crc32c.h>
|
|
|
|
void digest_alg_available(int *val)
|
|
{
|
|
#if defined(CONFIG_LIBCRC32C_MODULE) || defined(CONFIG_LIBCRC32C) || \
|
|
defined(CONFIG_CRC32_MODULE) || defined(CONFIG_CRC32)
|
|
int crc32c = 1;
|
|
#else
|
|
int crc32c = 0;
|
|
#endif
|
|
|
|
if ((*val & DIGEST_CRC32C) && !crc32c) {
|
|
PRINT_ERROR("CRC32C digest algorithm not available in kernel");
|
|
*val &= ~DIGEST_CRC32C;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* digest_init - initialize support for digest calculation.
|
|
* @conn: ptr to connection to make use of digests
|
|
* Returns: 0 on success, < 0 on error
|
|
*/
|
|
int digest_init(struct iscsi_conn *conn)
|
|
{
|
|
if (!(conn->hdigest_type & DIGEST_ALL))
|
|
conn->hdigest_type = DIGEST_NONE;
|
|
|
|
if (!(conn->ddigest_type & DIGEST_ALL))
|
|
conn->ddigest_type = DIGEST_NONE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __be32 evaluate_crc32_from_sg(struct scatterlist *sg, int nbytes, uint32_t padding)
|
|
{
|
|
u32 crc = ~0;
|
|
|
|
#ifdef CONFIG_SCST_ISCSI_DEBUG_DIGEST_FAILURES
|
|
if (((scst_random() % 100000) == 752)) {
|
|
PRINT_INFO("%s", "Simulating digest failure");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_LIBCRC32C_MODULE) || defined(CONFIG_LIBCRC32C) || \
|
|
defined(CONFIG_CRC32_MODULE) || defined(CONFIG_CRC32)
|
|
{
|
|
int pad_bytes = ((nbytes + 3) & -4) - nbytes;
|
|
|
|
while (nbytes > 0) {
|
|
int d = min(nbytes, (int)(sg->length));
|
|
|
|
crc = crc32c(crc, sg_virt(sg), d);
|
|
nbytes -= d;
|
|
sg++;
|
|
}
|
|
|
|
if (pad_bytes)
|
|
crc = crc32c(crc, (u8 *)&padding, pad_bytes);
|
|
}
|
|
#endif
|
|
|
|
return (__force __be32)~cpu_to_le32(crc);
|
|
}
|
|
|
|
static __be32 digest_header(struct iscsi_pdu *pdu)
|
|
{
|
|
struct scatterlist sg[2];
|
|
unsigned int nbytes = sizeof(struct iscsi_hdr);
|
|
int asize = (pdu->ahssize + 3) & -4;
|
|
|
|
sg_init_table(sg, 2);
|
|
|
|
sg_set_buf(&sg[0], &pdu->bhs, nbytes);
|
|
if (pdu->ahssize) {
|
|
sg_set_buf(&sg[1], pdu->ahs, asize);
|
|
nbytes += asize;
|
|
}
|
|
EXTRACHECKS_BUG_ON((nbytes & 3) != 0);
|
|
return evaluate_crc32_from_sg(sg, nbytes, 0);
|
|
}
|
|
|
|
static __be32 digest_data(struct iscsi_cmnd *cmd, u32 size, u32 offset, uint32_t padding)
|
|
{
|
|
struct scatterlist *sg = cmd->sg;
|
|
int idx, count;
|
|
struct scatterlist saved_sg;
|
|
__be32 crc;
|
|
|
|
offset += sg[0].offset;
|
|
idx = offset >> PAGE_SHIFT;
|
|
offset &= ~PAGE_MASK;
|
|
|
|
count = get_pgcnt(size, offset);
|
|
|
|
TRACE_DBG("req %p, idx %d, count %d, sg_cnt %d, size %d, offset %d",
|
|
cmd, idx, count, cmd->sg_cnt, size, offset);
|
|
sBUG_ON(idx + count > cmd->sg_cnt);
|
|
|
|
saved_sg = sg[idx];
|
|
sg[idx].offset = offset;
|
|
sg[idx].length -= offset - saved_sg.offset;
|
|
|
|
crc = evaluate_crc32_from_sg(sg + idx, size, padding);
|
|
|
|
sg[idx] = saved_sg;
|
|
return crc;
|
|
}
|
|
|
|
int digest_rx_header(struct iscsi_cmnd *cmnd)
|
|
{
|
|
__be32 crc;
|
|
|
|
crc = digest_header(&cmnd->pdu);
|
|
if (unlikely(crc != cmnd->hdigest)) {
|
|
PRINT_ERROR("%s", "RX header digest failed");
|
|
return -EIO;
|
|
}
|
|
|
|
TRACE_DBG("RX header digest OK for cmd %p", cmnd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void digest_tx_header(struct iscsi_cmnd *cmnd)
|
|
{
|
|
cmnd->hdigest = digest_header(&cmnd->pdu);
|
|
TRACE_DBG("TX header digest for cmd %p: %x", cmnd, cmnd->hdigest);
|
|
}
|
|
|
|
int digest_rx_data(struct iscsi_cmnd *cmnd)
|
|
{
|
|
struct iscsi_cmnd *req;
|
|
struct iscsi_data_out_hdr *req_hdr;
|
|
u32 offset;
|
|
__be32 crc;
|
|
int res = 0;
|
|
|
|
switch (cmnd_opcode(cmnd)) {
|
|
case ISCSI_OP_SCSI_DATA_OUT:
|
|
req = cmnd->cmd_req;
|
|
if (unlikely(!req)) {
|
|
/* It can be for prelim completed commands */
|
|
req = cmnd;
|
|
goto out;
|
|
}
|
|
req_hdr = (struct iscsi_data_out_hdr *)&cmnd->pdu.bhs;
|
|
offset = be32_to_cpu(req_hdr->buffer_offset);
|
|
break;
|
|
|
|
default:
|
|
req = cmnd;
|
|
offset = 0;
|
|
}
|
|
|
|
/*
|
|
* We need to skip the digest check for prelim completed commands,
|
|
* because we use shared data buffer for them, so, most likely, the
|
|
* check will fail. Plus, for such commands we sometimes don't have
|
|
* sg_cnt set correctly (cmnd_prepare_get_rejected_cmd_data() doesn't
|
|
* do it).
|
|
*/
|
|
if (unlikely(req->prelim_compl_flags != 0))
|
|
goto out;
|
|
|
|
/*
|
|
* Temporary to not crash with write residual overflows. ToDo. Until
|
|
* that let's always have succeeded data digests for such overflows.
|
|
* In ideal, we should allocate additional one or more sg's for the
|
|
* overflowed data and free them here or on req release. It's quite
|
|
* not trivial for such virtually never used case, so let's do it,
|
|
* when it gets needed.
|
|
*/
|
|
if (unlikely(offset + cmnd->pdu.datasize > req->bufflen)) {
|
|
PRINT_WARNING("Skipping RX data digest check for residual overflow command op %x (data size %d, buffer size %d)",
|
|
cmnd_hdr(req)->scb[0], offset + cmnd->pdu.datasize,
|
|
req->bufflen);
|
|
goto out;
|
|
}
|
|
|
|
crc = digest_data(req, cmnd->pdu.datasize, offset, cmnd->conn->rpadding);
|
|
|
|
if (unlikely(crc != cmnd->ddigest)) {
|
|
PRINT_ERROR("RX data digest failed, stable pages disabled?");
|
|
TRACE_MGMT_DBG("Calculated crc %x, ddigest %x, offset %d", crc,
|
|
cmnd->ddigest, offset);
|
|
iscsi_dump_pdu(&cmnd->pdu);
|
|
res = -EIO;
|
|
} else {
|
|
TRACE_DBG("RX data digest OK for cmd %p", cmnd);
|
|
}
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
void digest_tx_data(struct iscsi_cmnd *cmnd)
|
|
{
|
|
struct iscsi_data_in_hdr *hdr;
|
|
u32 offset;
|
|
|
|
TRACE_DBG("%s:%d req %p, own_sg %d, sg %p, sgcnt %d cmnd %p, own_sg %d, sg %p, sgcnt %d",
|
|
__func__, __LINE__,
|
|
cmnd->parent_req, cmnd->parent_req->own_sg,
|
|
cmnd->parent_req->sg, cmnd->parent_req->sg_cnt,
|
|
cmnd, cmnd->own_sg, cmnd->sg, cmnd->sg_cnt);
|
|
|
|
switch (cmnd_opcode(cmnd)) {
|
|
case ISCSI_OP_SCSI_DATA_IN:
|
|
hdr = (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs;
|
|
offset = be32_to_cpu(hdr->buffer_offset);
|
|
break;
|
|
default:
|
|
offset = 0;
|
|
}
|
|
|
|
cmnd->ddigest = digest_data(cmnd, cmnd->pdu.datasize, offset, 0);
|
|
TRACE_DBG("TX data digest for cmd %p: %x (offset %d, opcode %x)", cmnd,
|
|
cmnd->ddigest, offset, cmnd_opcode(cmnd));
|
|
}
|