Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
config ISCSI_TARGET
tristate "SCSI Target Mode Stack"
depends on INET
select CRC32
select CRYPTO
select CRYPTO_HASH
help
Say M to enable the SCSI target mode stack. A SCSI target mode stack
is software that makes local storage available over a storage network
to a SCSI initiator system. The supported storage network technologies
include iSCSI, Fibre Channel and the SCSI RDMA Protocol (SRP).
Configuration of the SCSI target mode stack happens through configfs.
source "drivers/target/iscsi/cxgbit/Kconfig"
+22
View File
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0
iscsi_target_mod-y += iscsi_target_parameters.o \
iscsi_target_seq_pdu_list.o \
iscsi_target_auth.o \
iscsi_target_datain_values.o \
iscsi_target_device.o \
iscsi_target_erl0.o \
iscsi_target_erl1.o \
iscsi_target_erl2.o \
iscsi_target_login.o \
iscsi_target_nego.o \
iscsi_target_nodeattrib.o \
iscsi_target_tmr.o \
iscsi_target_tpg.o \
iscsi_target_util.o \
iscsi_target.o \
iscsi_target_configfs.o \
iscsi_target_stat.o \
iscsi_target_transport.o
obj-$(CONFIG_ISCSI_TARGET) += iscsi_target_mod.o
obj-$(CONFIG_ISCSI_TARGET_CXGB4) += cxgbit/
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
config ISCSI_TARGET_CXGB4
tristate "Chelsio iSCSI target offload driver"
depends on ISCSI_TARGET && CHELSIO_T4 && INET
select CHELSIO_LIB
help
To compile this driver as module, choose M here: the module
will be called cxgbit.
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
ccflags-y := -I $(srctree)/drivers/net/ethernet/chelsio/cxgb4
ccflags-y += -I $(srctree)/drivers/net/ethernet/chelsio/libcxgb
ccflags-y += -I $(srctree)/drivers/target/iscsi
obj-$(CONFIG_ISCSI_TARGET_CXGB4) += cxgbit.o
cxgbit-y := cxgbit_main.o cxgbit_cm.o cxgbit_target.o cxgbit_ddp.o
@@ -0,0 +1,351 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016 Chelsio Communications, Inc.
*/
#ifndef __CXGBIT_H__
#define __CXGBIT_H__
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/idr.h>
#include <linux/completion.h>
#include <linux/netdevice.h>
#include <linux/sched.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/inet.h>
#include <linux/wait.h>
#include <linux/kref.h>
#include <linux/timer.h>
#include <linux/io.h>
#include <asm/byteorder.h>
#include <net/net_namespace.h>
#include <target/iscsi/iscsi_transport.h>
#include <iscsi_target_parameters.h>
#include <iscsi_target_login.h>
#include "t4_regs.h"
#include "t4_msg.h"
#include "cxgb4.h"
#include "cxgb4_uld.h"
#include "l2t.h"
#include "libcxgb_ppm.h"
#include "cxgbit_lro.h"
extern struct mutex cdev_list_lock;
extern struct list_head cdev_list_head;
struct cxgbit_np;
struct cxgbit_sock;
struct cxgbit_cmd {
struct scatterlist sg;
struct cxgbi_task_tag_info ttinfo;
bool setup_ddp;
bool release;
};
#define CXGBIT_MAX_ISO_PAYLOAD \
min_t(u32, MAX_SKB_FRAGS * PAGE_SIZE, 65535)
struct cxgbit_iso_info {
u8 flags;
u32 mpdu;
u32 len;
u32 burst_len;
};
enum cxgbit_skcb_flags {
SKCBF_TX_NEED_HDR = (1 << 0), /* packet needs a header */
SKCBF_TX_FLAG_COMPL = (1 << 1), /* wr completion flag */
SKCBF_TX_ISO = (1 << 2), /* iso cpl in tx skb */
SKCBF_RX_LRO = (1 << 3), /* lro skb */
};
struct cxgbit_skb_rx_cb {
u8 opcode;
void *pdu_cb;
void (*backlog_fn)(struct cxgbit_sock *, struct sk_buff *);
};
struct cxgbit_skb_tx_cb {
u8 submode;
u32 extra_len;
};
union cxgbit_skb_cb {
struct {
u8 flags;
union {
struct cxgbit_skb_tx_cb tx;
struct cxgbit_skb_rx_cb rx;
};
};
struct {
/* This member must be first. */
struct l2t_skb_cb l2t;
struct sk_buff *wr_next;
};
};
#define CXGBIT_SKB_CB(skb) ((union cxgbit_skb_cb *)&((skb)->cb[0]))
#define cxgbit_skcb_flags(skb) (CXGBIT_SKB_CB(skb)->flags)
#define cxgbit_skcb_submode(skb) (CXGBIT_SKB_CB(skb)->tx.submode)
#define cxgbit_skcb_tx_wr_next(skb) (CXGBIT_SKB_CB(skb)->wr_next)
#define cxgbit_skcb_tx_extralen(skb) (CXGBIT_SKB_CB(skb)->tx.extra_len)
#define cxgbit_skcb_rx_opcode(skb) (CXGBIT_SKB_CB(skb)->rx.opcode)
#define cxgbit_skcb_rx_backlog_fn(skb) (CXGBIT_SKB_CB(skb)->rx.backlog_fn)
#define cxgbit_rx_pdu_cb(skb) (CXGBIT_SKB_CB(skb)->rx.pdu_cb)
static inline void *cplhdr(struct sk_buff *skb)
{
return skb->data;
}
enum cxgbit_cdev_flags {
CDEV_STATE_UP = 0,
CDEV_ISO_ENABLE,
CDEV_DDP_ENABLE,
};
#define NP_INFO_HASH_SIZE 32
struct np_info {
struct np_info *next;
struct cxgbit_np *cnp;
unsigned int stid;
};
struct cxgbit_list_head {
struct list_head list;
/* device lock */
spinlock_t lock;
};
struct cxgbit_device {
struct list_head list;
struct cxgb4_lld_info lldi;
struct np_info *np_hash_tab[NP_INFO_HASH_SIZE];
/* np lock */
spinlock_t np_lock;
u8 selectq[MAX_NPORTS][2];
struct cxgbit_list_head cskq;
u32 mdsl;
struct kref kref;
unsigned long flags;
};
struct cxgbit_wr_wait {
struct completion completion;
int ret;
};
enum cxgbit_csk_state {
CSK_STATE_IDLE = 0,
CSK_STATE_LISTEN,
CSK_STATE_CONNECTING,
CSK_STATE_ESTABLISHED,
CSK_STATE_ABORTING,
CSK_STATE_CLOSING,
CSK_STATE_MORIBUND,
CSK_STATE_DEAD,
};
enum cxgbit_csk_flags {
CSK_TX_DATA_SENT = 0,
CSK_LOGIN_PDU_DONE,
CSK_LOGIN_DONE,
CSK_DDP_ENABLE,
CSK_ABORT_RPL_WAIT,
};
struct cxgbit_sock_common {
struct cxgbit_device *cdev;
struct sockaddr_storage local_addr;
struct sockaddr_storage remote_addr;
struct cxgbit_wr_wait wr_wait;
enum cxgbit_csk_state state;
unsigned long flags;
};
struct cxgbit_np {
struct cxgbit_sock_common com;
wait_queue_head_t accept_wait;
struct iscsi_np *np;
struct completion accept_comp;
struct list_head np_accept_list;
/* np accept lock */
spinlock_t np_accept_lock;
struct kref kref;
unsigned int stid;
};
struct cxgbit_sock {
struct cxgbit_sock_common com;
struct cxgbit_np *cnp;
struct iscsit_conn *conn;
struct l2t_entry *l2t;
struct dst_entry *dst;
struct list_head list;
struct sk_buff_head rxq;
struct sk_buff_head txq;
struct sk_buff_head ppodq;
struct sk_buff_head backlogq;
struct sk_buff_head skbq;
struct sk_buff *wr_pending_head;
struct sk_buff *wr_pending_tail;
struct sk_buff *skb;
struct sk_buff *lro_skb;
struct sk_buff *lro_hskb;
struct list_head accept_node;
/* socket lock */
spinlock_t lock;
wait_queue_head_t waitq;
bool lock_owner;
struct kref kref;
u32 max_iso_npdu;
u32 wr_cred;
u32 wr_una_cred;
u32 wr_max_cred;
u32 snd_una;
u32 tid;
u32 snd_nxt;
u32 rcv_nxt;
u32 smac_idx;
u32 tx_chan;
u32 mtu;
u32 write_seq;
u32 rx_credits;
u32 snd_win;
u32 rcv_win;
u16 mss;
u16 emss;
u16 plen;
u16 rss_qid;
u16 txq_idx;
u16 ctrlq_idx;
u8 tos;
u8 port_id;
#define CXGBIT_SUBMODE_HCRC 0x1
#define CXGBIT_SUBMODE_DCRC 0x2
u8 submode;
#ifdef CONFIG_CHELSIO_T4_DCB
u8 dcb_priority;
#endif
u8 snd_wscale;
};
void _cxgbit_free_cdev(struct kref *kref);
void _cxgbit_free_csk(struct kref *kref);
void _cxgbit_free_cnp(struct kref *kref);
static inline void cxgbit_get_cdev(struct cxgbit_device *cdev)
{
kref_get(&cdev->kref);
}
static inline void cxgbit_put_cdev(struct cxgbit_device *cdev)
{
kref_put(&cdev->kref, _cxgbit_free_cdev);
}
static inline void cxgbit_get_csk(struct cxgbit_sock *csk)
{
kref_get(&csk->kref);
}
static inline void cxgbit_put_csk(struct cxgbit_sock *csk)
{
kref_put(&csk->kref, _cxgbit_free_csk);
}
static inline void cxgbit_get_cnp(struct cxgbit_np *cnp)
{
kref_get(&cnp->kref);
}
static inline void cxgbit_put_cnp(struct cxgbit_np *cnp)
{
kref_put(&cnp->kref, _cxgbit_free_cnp);
}
static inline void cxgbit_sock_reset_wr_list(struct cxgbit_sock *csk)
{
csk->wr_pending_tail = NULL;
csk->wr_pending_head = NULL;
}
static inline struct sk_buff *cxgbit_sock_peek_wr(const struct cxgbit_sock *csk)
{
return csk->wr_pending_head;
}
static inline void
cxgbit_sock_enqueue_wr(struct cxgbit_sock *csk, struct sk_buff *skb)
{
cxgbit_skcb_tx_wr_next(skb) = NULL;
skb_get(skb);
if (!csk->wr_pending_head)
csk->wr_pending_head = skb;
else
cxgbit_skcb_tx_wr_next(csk->wr_pending_tail) = skb;
csk->wr_pending_tail = skb;
}
static inline struct sk_buff *cxgbit_sock_dequeue_wr(struct cxgbit_sock *csk)
{
struct sk_buff *skb = csk->wr_pending_head;
if (likely(skb)) {
csk->wr_pending_head = cxgbit_skcb_tx_wr_next(skb);
cxgbit_skcb_tx_wr_next(skb) = NULL;
}
return skb;
}
typedef void (*cxgbit_cplhandler_func)(struct cxgbit_device *,
struct sk_buff *);
int cxgbit_setup_np(struct iscsi_np *, struct sockaddr_storage *);
int cxgbit_setup_conn_digest(struct cxgbit_sock *);
int cxgbit_accept_np(struct iscsi_np *, struct iscsit_conn *);
void cxgbit_free_np(struct iscsi_np *);
void cxgbit_abort_conn(struct cxgbit_sock *csk);
void cxgbit_free_conn(struct iscsit_conn *);
extern cxgbit_cplhandler_func cxgbit_cplhandlers[NUM_CPL_CMDS];
int cxgbit_get_login_rx(struct iscsit_conn *, struct iscsi_login *);
int cxgbit_rx_data_ack(struct cxgbit_sock *);
int cxgbit_l2t_send(struct cxgbit_device *, struct sk_buff *,
struct l2t_entry *);
void cxgbit_push_tx_frames(struct cxgbit_sock *);
int cxgbit_put_login_tx(struct iscsit_conn *, struct iscsi_login *, u32);
int cxgbit_xmit_pdu(struct iscsit_conn *, struct iscsit_cmd *,
struct iscsi_datain_req *, const void *, u32);
void cxgbit_get_r2t_ttt(struct iscsit_conn *, struct iscsit_cmd *,
struct iscsi_r2t *);
u32 cxgbit_send_tx_flowc_wr(struct cxgbit_sock *);
int cxgbit_ofld_send(struct cxgbit_device *, struct sk_buff *);
void cxgbit_get_rx_pdu(struct iscsit_conn *);
int cxgbit_validate_params(struct iscsit_conn *);
struct cxgbit_device *cxgbit_find_device(struct net_device *, u8 *);
/* DDP */
int cxgbit_ddp_init(struct cxgbit_device *);
int cxgbit_setup_conn_pgidx(struct cxgbit_sock *, u32);
int cxgbit_reserve_ttt(struct cxgbit_sock *, struct iscsit_cmd *);
void cxgbit_unmap_cmd(struct iscsit_conn *, struct iscsit_cmd *);
static inline
struct cxgbi_ppm *cdev2ppm(struct cxgbit_device *cdev)
{
return (struct cxgbi_ppm *)(*cdev->lldi.iscsi_ppm);
}
#endif /* __CXGBIT_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,330 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016 Chelsio Communications, Inc.
*/
#include "cxgbit.h"
static void
cxgbit_set_one_ppod(struct cxgbi_pagepod *ppod,
struct cxgbi_task_tag_info *ttinfo,
struct scatterlist **sg_pp, unsigned int *sg_off)
{
struct scatterlist *sg = sg_pp ? *sg_pp : NULL;
unsigned int offset = sg_off ? *sg_off : 0;
dma_addr_t addr = 0UL;
unsigned int len = 0;
int i;
memcpy(ppod, &ttinfo->hdr, sizeof(struct cxgbi_pagepod_hdr));
if (sg) {
addr = sg_dma_address(sg);
len = sg_dma_len(sg);
}
for (i = 0; i < PPOD_PAGES_MAX; i++) {
if (sg) {
ppod->addr[i] = cpu_to_be64(addr + offset);
offset += PAGE_SIZE;
if (offset == (len + sg->offset)) {
offset = 0;
sg = sg_next(sg);
if (sg) {
addr = sg_dma_address(sg);
len = sg_dma_len(sg);
}
}
} else {
ppod->addr[i] = 0ULL;
}
}
/*
* the fifth address needs to be repeated in the next ppod, so do
* not move sg
*/
if (sg_pp) {
*sg_pp = sg;
*sg_off = offset;
}
if (offset == len) {
offset = 0;
if (sg) {
sg = sg_next(sg);
if (sg)
addr = sg_dma_address(sg);
}
}
ppod->addr[i] = sg ? cpu_to_be64(addr + offset) : 0ULL;
}
static struct sk_buff *
cxgbit_ppod_init_idata(struct cxgbit_device *cdev, struct cxgbi_ppm *ppm,
unsigned int idx, unsigned int npods, unsigned int tid)
{
struct ulp_mem_io *req;
struct ulptx_idata *idata;
unsigned int pm_addr = (idx << PPOD_SIZE_SHIFT) + ppm->llimit;
unsigned int dlen = npods << PPOD_SIZE_SHIFT;
unsigned int wr_len = roundup(sizeof(struct ulp_mem_io) +
sizeof(struct ulptx_idata) + dlen, 16);
struct sk_buff *skb;
skb = alloc_skb(wr_len, GFP_KERNEL);
if (!skb)
return NULL;
req = __skb_put(skb, wr_len);
INIT_ULPTX_WR(req, wr_len, 0, tid);
req->wr.wr_hi = htonl(FW_WR_OP_V(FW_ULPTX_WR) |
FW_WR_ATOMIC_V(0));
req->cmd = htonl(ULPTX_CMD_V(ULP_TX_MEM_WRITE) |
ULP_MEMIO_ORDER_V(0) |
T5_ULP_MEMIO_IMM_V(1));
req->dlen = htonl(ULP_MEMIO_DATA_LEN_V(dlen >> 5));
req->lock_addr = htonl(ULP_MEMIO_ADDR_V(pm_addr >> 5));
req->len16 = htonl(DIV_ROUND_UP(wr_len - sizeof(req->wr), 16));
idata = (struct ulptx_idata *)(req + 1);
idata->cmd_more = htonl(ULPTX_CMD_V(ULP_TX_SC_IMM));
idata->len = htonl(dlen);
return skb;
}
static int
cxgbit_ppod_write_idata(struct cxgbi_ppm *ppm, struct cxgbit_sock *csk,
struct cxgbi_task_tag_info *ttinfo, unsigned int idx,
unsigned int npods, struct scatterlist **sg_pp,
unsigned int *sg_off)
{
struct cxgbit_device *cdev = csk->com.cdev;
struct sk_buff *skb;
struct ulp_mem_io *req;
struct ulptx_idata *idata;
struct cxgbi_pagepod *ppod;
unsigned int i;
skb = cxgbit_ppod_init_idata(cdev, ppm, idx, npods, csk->tid);
if (!skb)
return -ENOMEM;
req = (struct ulp_mem_io *)skb->data;
idata = (struct ulptx_idata *)(req + 1);
ppod = (struct cxgbi_pagepod *)(idata + 1);
for (i = 0; i < npods; i++, ppod++)
cxgbit_set_one_ppod(ppod, ttinfo, sg_pp, sg_off);
__skb_queue_tail(&csk->ppodq, skb);
return 0;
}
static int
cxgbit_ddp_set_map(struct cxgbi_ppm *ppm, struct cxgbit_sock *csk,
struct cxgbi_task_tag_info *ttinfo)
{
unsigned int pidx = ttinfo->idx;
unsigned int npods = ttinfo->npods;
unsigned int i, cnt;
struct scatterlist *sg = ttinfo->sgl;
unsigned int offset = 0;
int ret = 0;
for (i = 0; i < npods; i += cnt, pidx += cnt) {
cnt = npods - i;
if (cnt > ULPMEM_IDATA_MAX_NPPODS)
cnt = ULPMEM_IDATA_MAX_NPPODS;
ret = cxgbit_ppod_write_idata(ppm, csk, ttinfo, pidx, cnt,
&sg, &offset);
if (ret < 0)
break;
}
return ret;
}
static int cxgbit_ddp_sgl_check(struct scatterlist *sg,
unsigned int nents)
{
unsigned int last_sgidx = nents - 1;
unsigned int i;
for (i = 0; i < nents; i++, sg = sg_next(sg)) {
unsigned int len = sg->length + sg->offset;
if ((sg->offset & 0x3) || (i && sg->offset) ||
((i != last_sgidx) && (len != PAGE_SIZE))) {
return -EINVAL;
}
}
return 0;
}
static int
cxgbit_ddp_reserve(struct cxgbit_sock *csk, struct cxgbi_task_tag_info *ttinfo,
unsigned int xferlen)
{
struct cxgbit_device *cdev = csk->com.cdev;
struct cxgbi_ppm *ppm = cdev2ppm(cdev);
struct scatterlist *sgl = ttinfo->sgl;
unsigned int sgcnt = ttinfo->nents;
unsigned int sg_offset = sgl->offset;
int ret;
if ((xferlen < DDP_THRESHOLD) || (!sgcnt)) {
pr_debug("ppm 0x%p, pgidx %u, xfer %u, sgcnt %u, NO ddp.\n",
ppm, ppm->tformat.pgsz_idx_dflt,
xferlen, ttinfo->nents);
return -EINVAL;
}
if (cxgbit_ddp_sgl_check(sgl, sgcnt) < 0)
return -EINVAL;
ttinfo->nr_pages = (xferlen + sgl->offset +
(1 << PAGE_SHIFT) - 1) >> PAGE_SHIFT;
/*
* the ddp tag will be used for the ttt in the outgoing r2t pdu
*/
ret = cxgbi_ppm_ppods_reserve(ppm, ttinfo->nr_pages, 0, &ttinfo->idx,
&ttinfo->tag, 0);
if (ret < 0)
return ret;
ttinfo->npods = ret;
sgl->offset = 0;
ret = dma_map_sg(&ppm->pdev->dev, sgl, sgcnt, DMA_FROM_DEVICE);
sgl->offset = sg_offset;
if (!ret) {
pr_debug("%s: 0x%x, xfer %u, sgl %u dma mapping err.\n",
__func__, 0, xferlen, sgcnt);
goto rel_ppods;
}
cxgbi_ppm_make_ppod_hdr(ppm, ttinfo->tag, csk->tid, sgl->offset,
xferlen, &ttinfo->hdr);
ret = cxgbit_ddp_set_map(ppm, csk, ttinfo);
if (ret < 0) {
__skb_queue_purge(&csk->ppodq);
dma_unmap_sg(&ppm->pdev->dev, sgl, sgcnt, DMA_FROM_DEVICE);
goto rel_ppods;
}
return 0;
rel_ppods:
cxgbi_ppm_ppod_release(ppm, ttinfo->idx);
return -EINVAL;
}
void
cxgbit_get_r2t_ttt(struct iscsit_conn *conn, struct iscsit_cmd *cmd,
struct iscsi_r2t *r2t)
{
struct cxgbit_sock *csk = conn->context;
struct cxgbit_device *cdev = csk->com.cdev;
struct cxgbit_cmd *ccmd = iscsit_priv_cmd(cmd);
struct cxgbi_task_tag_info *ttinfo = &ccmd->ttinfo;
int ret;
if ((!ccmd->setup_ddp) ||
(!test_bit(CSK_DDP_ENABLE, &csk->com.flags)))
goto out;
ccmd->setup_ddp = false;
ttinfo->sgl = cmd->se_cmd.t_data_sg;
ttinfo->nents = cmd->se_cmd.t_data_nents;
ret = cxgbit_ddp_reserve(csk, ttinfo, cmd->se_cmd.data_length);
if (ret < 0) {
pr_debug("csk 0x%p, cmd 0x%p, xfer len %u, sgcnt %u no ddp.\n",
csk, cmd, cmd->se_cmd.data_length, ttinfo->nents);
ttinfo->sgl = NULL;
ttinfo->nents = 0;
} else {
ccmd->release = true;
}
out:
pr_debug("cdev 0x%p, cmd 0x%p, tag 0x%x\n", cdev, cmd, ttinfo->tag);
r2t->targ_xfer_tag = ttinfo->tag;
}
void cxgbit_unmap_cmd(struct iscsit_conn *conn, struct iscsit_cmd *cmd)
{
struct cxgbit_cmd *ccmd = iscsit_priv_cmd(cmd);
if (ccmd->release) {
if (cmd->se_cmd.se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC) {
put_page(sg_page(&ccmd->sg));
} else {
struct cxgbit_sock *csk = conn->context;
struct cxgbit_device *cdev = csk->com.cdev;
struct cxgbi_ppm *ppm = cdev2ppm(cdev);
struct cxgbi_task_tag_info *ttinfo = &ccmd->ttinfo;
/* Abort the TCP conn if DDP is not complete to
* avoid any possibility of DDP after freeing
* the cmd.
*/
if (unlikely(cmd->write_data_done !=
cmd->se_cmd.data_length))
cxgbit_abort_conn(csk);
if (unlikely(ttinfo->sgl)) {
dma_unmap_sg(&ppm->pdev->dev, ttinfo->sgl,
ttinfo->nents, DMA_FROM_DEVICE);
ttinfo->nents = 0;
ttinfo->sgl = NULL;
}
cxgbi_ppm_ppod_release(ppm, ttinfo->idx);
}
ccmd->release = false;
}
}
int cxgbit_ddp_init(struct cxgbit_device *cdev)
{
struct cxgb4_lld_info *lldi = &cdev->lldi;
struct net_device *ndev = cdev->lldi.ports[0];
struct cxgbi_tag_format tformat;
int ret, i;
if (!lldi->vr->iscsi.size) {
pr_warn("%s, iscsi NOT enabled, check config!\n", ndev->name);
return -EACCES;
}
memset(&tformat, 0, sizeof(struct cxgbi_tag_format));
for (i = 0; i < 4; i++)
tformat.pgsz_order[i] = (lldi->iscsi_pgsz_order >> (i << 3))
& 0xF;
cxgbi_tagmask_check(lldi->iscsi_tagmask, &tformat);
ret = cxgbi_ppm_init(lldi->iscsi_ppm, cdev->lldi.ports[0],
cdev->lldi.pdev, &cdev->lldi, &tformat,
lldi->vr->iscsi.size, lldi->iscsi_llimit,
lldi->vr->iscsi.start, 2,
lldi->vr->ppod_edram.start,
lldi->vr->ppod_edram.size);
if (ret >= 0) {
struct cxgbi_ppm *ppm = (struct cxgbi_ppm *)(*lldi->iscsi_ppm);
if ((ppm->tformat.pgsz_idx_dflt < DDP_PGIDX_MAX) &&
(ppm->ppmax >= 1024))
set_bit(CDEV_DDP_ENABLE, &cdev->flags);
ret = 0;
}
return ret;
}
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2016 Chelsio Communications, Inc.
*
* 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.
*
*/
#ifndef __CXGBIT_LRO_H__
#define __CXGBIT_LRO_H__
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#define LRO_FLUSH_LEN_MAX 65535
struct cxgbit_lro_cb {
struct cxgbit_sock *csk;
u32 pdu_totallen;
u32 offset;
u8 pdu_idx;
bool complete;
};
enum cxgbit_pducb_flags {
PDUCBF_RX_HDR = (1 << 0), /* received pdu header */
PDUCBF_RX_DATA = (1 << 1), /* received pdu payload */
PDUCBF_RX_STATUS = (1 << 2), /* received ddp status */
PDUCBF_RX_DATA_DDPD = (1 << 3), /* pdu payload ddp'd */
PDUCBF_RX_DDP_CMP = (1 << 4), /* ddp completion */
PDUCBF_RX_HCRC_ERR = (1 << 5), /* header digest error */
PDUCBF_RX_DCRC_ERR = (1 << 6), /* data digest error */
};
struct cxgbit_lro_pdu_cb {
u8 flags;
u8 frags;
u8 hfrag_idx;
u8 nr_dfrags;
u8 dfrag_idx;
bool complete;
u32 seq;
u32 pdulen;
u32 hlen;
u32 dlen;
u32 doffset;
u32 ddigest;
void *hdr;
};
#define LRO_SKB_MAX_HEADROOM \
(sizeof(struct cxgbit_lro_cb) + \
(MAX_SKB_FRAGS * sizeof(struct cxgbit_lro_pdu_cb)))
#define LRO_SKB_MIN_HEADROOM \
(sizeof(struct cxgbit_lro_cb) + \
sizeof(struct cxgbit_lro_pdu_cb))
#define cxgbit_skb_lro_cb(skb) ((struct cxgbit_lro_cb *)skb->data)
#define cxgbit_skb_lro_pdu_cb(skb, i) \
((struct cxgbit_lro_pdu_cb *)(skb->data + sizeof(struct cxgbit_lro_cb) \
+ (i * sizeof(struct cxgbit_lro_pdu_cb))))
#define CPL_RX_ISCSI_DDP_STATUS_DDP_SHIFT 16 /* ddp'able */
#define CPL_RX_ISCSI_DDP_STATUS_PAD_SHIFT 19 /* pad error */
#define CPL_RX_ISCSI_DDP_STATUS_HCRC_SHIFT 20 /* hcrc error */
#define CPL_RX_ISCSI_DDP_STATUS_DCRC_SHIFT 21 /* dcrc error */
#endif /*__CXGBIT_LRO_H_*/
@@ -0,0 +1,746 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016 Chelsio Communications, Inc.
*/
#define DRV_NAME "cxgbit"
#define DRV_VERSION "1.0.0-ko"
#define pr_fmt(fmt) DRV_NAME ": " fmt
#include "cxgbit.h"
#ifdef CONFIG_CHELSIO_T4_DCB
#include <net/dcbevent.h>
#include "cxgb4_dcb.h"
#endif
LIST_HEAD(cdev_list_head);
/* cdev list lock */
DEFINE_MUTEX(cdev_list_lock);
void _cxgbit_free_cdev(struct kref *kref)
{
struct cxgbit_device *cdev;
cdev = container_of(kref, struct cxgbit_device, kref);
cxgbi_ppm_release(cdev2ppm(cdev));
kfree(cdev);
}
static void cxgbit_set_mdsl(struct cxgbit_device *cdev)
{
struct cxgb4_lld_info *lldi = &cdev->lldi;
u32 mdsl;
#define CXGBIT_T5_MAX_PDU_LEN 16224
#define CXGBIT_PDU_NONPAYLOAD_LEN 312 /* 48(BHS) + 256(AHS) + 8(Digest) */
if (is_t5(lldi->adapter_type)) {
mdsl = min_t(u32, lldi->iscsi_iolen - CXGBIT_PDU_NONPAYLOAD_LEN,
CXGBIT_T5_MAX_PDU_LEN - CXGBIT_PDU_NONPAYLOAD_LEN);
} else {
mdsl = lldi->iscsi_iolen - CXGBIT_PDU_NONPAYLOAD_LEN;
mdsl = min(mdsl, 16384U);
}
mdsl = round_down(mdsl, 4);
mdsl = min_t(u32, mdsl, 4 * PAGE_SIZE);
mdsl = min_t(u32, mdsl, (MAX_SKB_FRAGS - 1) * PAGE_SIZE);
cdev->mdsl = mdsl;
}
static void *cxgbit_uld_add(const struct cxgb4_lld_info *lldi)
{
struct cxgbit_device *cdev;
if (is_t4(lldi->adapter_type))
return ERR_PTR(-ENODEV);
cdev = kzalloc_obj(*cdev);
if (!cdev)
return ERR_PTR(-ENOMEM);
kref_init(&cdev->kref);
spin_lock_init(&cdev->np_lock);
cdev->lldi = *lldi;
cxgbit_set_mdsl(cdev);
if (cxgbit_ddp_init(cdev) < 0) {
kfree(cdev);
return ERR_PTR(-EINVAL);
}
if (!test_bit(CDEV_DDP_ENABLE, &cdev->flags))
pr_info("cdev %s ddp init failed\n",
pci_name(lldi->pdev));
if (lldi->fw_vers >= 0x10d2b00)
set_bit(CDEV_ISO_ENABLE, &cdev->flags);
spin_lock_init(&cdev->cskq.lock);
INIT_LIST_HEAD(&cdev->cskq.list);
mutex_lock(&cdev_list_lock);
list_add_tail(&cdev->list, &cdev_list_head);
mutex_unlock(&cdev_list_lock);
pr_info("cdev %s added for iSCSI target transport\n",
pci_name(lldi->pdev));
return cdev;
}
static void cxgbit_close_conn(struct cxgbit_device *cdev)
{
struct cxgbit_sock *csk;
struct sk_buff *skb;
bool wakeup_thread = false;
spin_lock_bh(&cdev->cskq.lock);
list_for_each_entry(csk, &cdev->cskq.list, list) {
skb = alloc_skb(0, GFP_ATOMIC);
if (!skb)
continue;
spin_lock_bh(&csk->rxq.lock);
__skb_queue_tail(&csk->rxq, skb);
if (skb_queue_len(&csk->rxq) == 1)
wakeup_thread = true;
spin_unlock_bh(&csk->rxq.lock);
if (wakeup_thread) {
wake_up(&csk->waitq);
wakeup_thread = false;
}
}
spin_unlock_bh(&cdev->cskq.lock);
}
static void cxgbit_detach_cdev(struct cxgbit_device *cdev)
{
bool free_cdev = false;
spin_lock_bh(&cdev->cskq.lock);
if (list_empty(&cdev->cskq.list))
free_cdev = true;
spin_unlock_bh(&cdev->cskq.lock);
if (free_cdev) {
mutex_lock(&cdev_list_lock);
list_del(&cdev->list);
mutex_unlock(&cdev_list_lock);
cxgbit_put_cdev(cdev);
} else {
cxgbit_close_conn(cdev);
}
}
static int cxgbit_uld_state_change(void *handle, enum cxgb4_state state)
{
struct cxgbit_device *cdev = handle;
switch (state) {
case CXGB4_STATE_UP:
set_bit(CDEV_STATE_UP, &cdev->flags);
pr_info("cdev %s state UP.\n", pci_name(cdev->lldi.pdev));
break;
case CXGB4_STATE_START_RECOVERY:
clear_bit(CDEV_STATE_UP, &cdev->flags);
cxgbit_close_conn(cdev);
pr_info("cdev %s state RECOVERY.\n", pci_name(cdev->lldi.pdev));
break;
case CXGB4_STATE_DOWN:
pr_info("cdev %s state DOWN.\n", pci_name(cdev->lldi.pdev));
break;
case CXGB4_STATE_DETACH:
clear_bit(CDEV_STATE_UP, &cdev->flags);
pr_info("cdev %s state DETACH.\n", pci_name(cdev->lldi.pdev));
cxgbit_detach_cdev(cdev);
break;
default:
pr_info("cdev %s unknown state %d.\n",
pci_name(cdev->lldi.pdev), state);
break;
}
return 0;
}
static void
cxgbit_process_ddpvld(struct cxgbit_sock *csk, struct cxgbit_lro_pdu_cb *pdu_cb,
u32 ddpvld)
{
if (ddpvld & (1 << CPL_RX_ISCSI_DDP_STATUS_HCRC_SHIFT)) {
pr_info("tid 0x%x, status 0x%x, hcrc bad.\n", csk->tid, ddpvld);
pdu_cb->flags |= PDUCBF_RX_HCRC_ERR;
}
if (ddpvld & (1 << CPL_RX_ISCSI_DDP_STATUS_DCRC_SHIFT)) {
pr_info("tid 0x%x, status 0x%x, dcrc bad.\n", csk->tid, ddpvld);
pdu_cb->flags |= PDUCBF_RX_DCRC_ERR;
}
if (ddpvld & (1 << CPL_RX_ISCSI_DDP_STATUS_PAD_SHIFT))
pr_info("tid 0x%x, status 0x%x, pad bad.\n", csk->tid, ddpvld);
if ((ddpvld & (1 << CPL_RX_ISCSI_DDP_STATUS_DDP_SHIFT)) &&
(!(pdu_cb->flags & PDUCBF_RX_DATA))) {
pdu_cb->flags |= PDUCBF_RX_DATA_DDPD;
}
}
static void
cxgbit_lro_add_packet_rsp(struct sk_buff *skb, u8 op, const __be64 *rsp)
{
struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb);
struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb,
lro_cb->pdu_idx);
struct cpl_rx_iscsi_ddp *cpl = (struct cpl_rx_iscsi_ddp *)(rsp + 1);
cxgbit_process_ddpvld(lro_cb->csk, pdu_cb, be32_to_cpu(cpl->ddpvld));
pdu_cb->flags |= PDUCBF_RX_STATUS;
pdu_cb->ddigest = ntohl(cpl->ulp_crc);
pdu_cb->pdulen = ntohs(cpl->len);
if (pdu_cb->flags & PDUCBF_RX_HDR)
pdu_cb->complete = true;
lro_cb->pdu_totallen += pdu_cb->pdulen;
lro_cb->complete = true;
lro_cb->pdu_idx++;
}
static void
cxgbit_copy_frags(struct sk_buff *skb, const struct pkt_gl *gl,
unsigned int offset)
{
u8 skb_frag_idx = skb_shinfo(skb)->nr_frags;
u8 i;
/* usually there's just one frag */
__skb_fill_page_desc(skb, skb_frag_idx, gl->frags[0].page,
gl->frags[0].offset + offset,
gl->frags[0].size - offset);
for (i = 1; i < gl->nfrags; i++)
__skb_fill_page_desc(skb, skb_frag_idx + i,
gl->frags[i].page,
gl->frags[i].offset,
gl->frags[i].size);
skb_shinfo(skb)->nr_frags += gl->nfrags;
/* get a reference to the last page, we don't own it */
get_page(gl->frags[gl->nfrags - 1].page);
}
static void
cxgbit_lro_add_packet_gl(struct sk_buff *skb, u8 op, const struct pkt_gl *gl)
{
struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb);
struct cxgbit_lro_pdu_cb *pdu_cb = cxgbit_skb_lro_pdu_cb(skb,
lro_cb->pdu_idx);
u32 len, offset;
if (op == CPL_ISCSI_HDR) {
struct cpl_iscsi_hdr *cpl = (struct cpl_iscsi_hdr *)gl->va;
offset = sizeof(struct cpl_iscsi_hdr);
pdu_cb->flags |= PDUCBF_RX_HDR;
pdu_cb->seq = ntohl(cpl->seq);
len = ntohs(cpl->len);
pdu_cb->hdr = gl->va + offset;
pdu_cb->hlen = len;
pdu_cb->hfrag_idx = skb_shinfo(skb)->nr_frags;
if (unlikely(gl->nfrags > 1))
cxgbit_skcb_flags(skb) = 0;
lro_cb->complete = false;
} else if (op == CPL_ISCSI_DATA) {
struct cpl_iscsi_data *cpl = (struct cpl_iscsi_data *)gl->va;
offset = sizeof(struct cpl_iscsi_data);
pdu_cb->flags |= PDUCBF_RX_DATA;
len = ntohs(cpl->len);
pdu_cb->dlen = len;
pdu_cb->doffset = lro_cb->offset;
pdu_cb->nr_dfrags = gl->nfrags;
pdu_cb->dfrag_idx = skb_shinfo(skb)->nr_frags;
lro_cb->complete = false;
} else {
struct cpl_rx_iscsi_cmp *cpl;
cpl = (struct cpl_rx_iscsi_cmp *)gl->va;
offset = sizeof(struct cpl_rx_iscsi_cmp);
pdu_cb->flags |= (PDUCBF_RX_HDR | PDUCBF_RX_STATUS);
len = be16_to_cpu(cpl->len);
pdu_cb->hdr = gl->va + offset;
pdu_cb->hlen = len;
pdu_cb->hfrag_idx = skb_shinfo(skb)->nr_frags;
pdu_cb->ddigest = be32_to_cpu(cpl->ulp_crc);
pdu_cb->pdulen = ntohs(cpl->len);
if (unlikely(gl->nfrags > 1))
cxgbit_skcb_flags(skb) = 0;
cxgbit_process_ddpvld(lro_cb->csk, pdu_cb,
be32_to_cpu(cpl->ddpvld));
if (pdu_cb->flags & PDUCBF_RX_DATA_DDPD) {
pdu_cb->flags |= PDUCBF_RX_DDP_CMP;
pdu_cb->complete = true;
} else if (pdu_cb->flags & PDUCBF_RX_DATA) {
pdu_cb->complete = true;
}
lro_cb->pdu_totallen += pdu_cb->hlen + pdu_cb->dlen;
lro_cb->complete = true;
lro_cb->pdu_idx++;
}
cxgbit_copy_frags(skb, gl, offset);
pdu_cb->frags += gl->nfrags;
lro_cb->offset += len;
skb->len += len;
skb->data_len += len;
skb->truesize += len;
}
static struct sk_buff *
cxgbit_lro_init_skb(struct cxgbit_sock *csk, u8 op, const struct pkt_gl *gl,
const __be64 *rsp, struct napi_struct *napi)
{
struct sk_buff *skb;
struct cxgbit_lro_cb *lro_cb;
skb = napi_alloc_skb(napi, LRO_SKB_MAX_HEADROOM);
if (unlikely(!skb))
return NULL;
memset(skb->data, 0, LRO_SKB_MAX_HEADROOM);
cxgbit_skcb_flags(skb) |= SKCBF_RX_LRO;
lro_cb = cxgbit_skb_lro_cb(skb);
cxgbit_get_csk(csk);
lro_cb->csk = csk;
return skb;
}
static void cxgbit_queue_lro_skb(struct cxgbit_sock *csk, struct sk_buff *skb)
{
bool wakeup_thread = false;
spin_lock(&csk->rxq.lock);
__skb_queue_tail(&csk->rxq, skb);
if (skb_queue_len(&csk->rxq) == 1)
wakeup_thread = true;
spin_unlock(&csk->rxq.lock);
if (wakeup_thread)
wake_up(&csk->waitq);
}
static void cxgbit_lro_flush(struct t4_lro_mgr *lro_mgr, struct sk_buff *skb)
{
struct cxgbit_lro_cb *lro_cb = cxgbit_skb_lro_cb(skb);
struct cxgbit_sock *csk = lro_cb->csk;
csk->lro_skb = NULL;
__skb_unlink(skb, &lro_mgr->lroq);
cxgbit_queue_lro_skb(csk, skb);
cxgbit_put_csk(csk);
lro_mgr->lro_pkts++;
lro_mgr->lro_session_cnt--;
}
static void cxgbit_uld_lro_flush(struct t4_lro_mgr *lro_mgr)
{
struct sk_buff *skb;
while ((skb = skb_peek(&lro_mgr->lroq)))
cxgbit_lro_flush(lro_mgr, skb);
}
static int
cxgbit_lro_receive(struct cxgbit_sock *csk, u8 op, const __be64 *rsp,
const struct pkt_gl *gl, struct t4_lro_mgr *lro_mgr,
struct napi_struct *napi)
{
struct sk_buff *skb;
struct cxgbit_lro_cb *lro_cb;
if (!csk) {
pr_err("%s: csk NULL, op 0x%x.\n", __func__, op);
goto out;
}
if (csk->lro_skb)
goto add_packet;
start_lro:
if (lro_mgr->lro_session_cnt >= MAX_LRO_SESSIONS) {
cxgbit_uld_lro_flush(lro_mgr);
goto start_lro;
}
skb = cxgbit_lro_init_skb(csk, op, gl, rsp, napi);
if (unlikely(!skb))
goto out;
csk->lro_skb = skb;
__skb_queue_tail(&lro_mgr->lroq, skb);
lro_mgr->lro_session_cnt++;
add_packet:
skb = csk->lro_skb;
lro_cb = cxgbit_skb_lro_cb(skb);
if ((gl && (((skb_shinfo(skb)->nr_frags + gl->nfrags) >
MAX_SKB_FRAGS) || (lro_cb->pdu_totallen >= LRO_FLUSH_LEN_MAX))) ||
(lro_cb->pdu_idx >= MAX_SKB_FRAGS)) {
cxgbit_lro_flush(lro_mgr, skb);
goto start_lro;
}
if (gl)
cxgbit_lro_add_packet_gl(skb, op, gl);
else
cxgbit_lro_add_packet_rsp(skb, op, rsp);
lro_mgr->lro_merged++;
return 0;
out:
return -1;
}
static int
cxgbit_uld_lro_rx_handler(void *hndl, const __be64 *rsp,
const struct pkt_gl *gl, struct t4_lro_mgr *lro_mgr,
struct napi_struct *napi)
{
struct cxgbit_device *cdev = hndl;
struct cxgb4_lld_info *lldi = &cdev->lldi;
struct cpl_tx_data *rpl = NULL;
struct cxgbit_sock *csk = NULL;
unsigned int tid = 0;
struct sk_buff *skb;
unsigned int op = *(u8 *)rsp;
bool lro_flush = true;
switch (op) {
case CPL_ISCSI_HDR:
case CPL_ISCSI_DATA:
case CPL_RX_ISCSI_CMP:
case CPL_RX_ISCSI_DDP:
case CPL_FW4_ACK:
lro_flush = false;
fallthrough;
case CPL_ABORT_RPL_RSS:
case CPL_PASS_ESTABLISH:
case CPL_PEER_CLOSE:
case CPL_CLOSE_CON_RPL:
case CPL_ABORT_REQ_RSS:
case CPL_SET_TCB_RPL:
case CPL_RX_DATA:
rpl = gl ? (struct cpl_tx_data *)gl->va :
(struct cpl_tx_data *)(rsp + 1);
tid = GET_TID(rpl);
csk = lookup_tid(lldi->tids, tid);
break;
default:
break;
}
if (csk && csk->lro_skb && lro_flush)
cxgbit_lro_flush(lro_mgr, csk->lro_skb);
if (!gl) {
unsigned int len;
if (op == CPL_RX_ISCSI_DDP) {
if (!cxgbit_lro_receive(csk, op, rsp, NULL, lro_mgr,
napi))
return 0;
}
len = 64 - sizeof(struct rsp_ctrl) - 8;
skb = napi_alloc_skb(napi, len);
if (!skb)
goto nomem;
__skb_put(skb, len);
skb_copy_to_linear_data(skb, &rsp[1], len);
} else {
if (unlikely(op != *(u8 *)gl->va)) {
pr_info("? FL 0x%p,RSS%#llx,FL %#llx,len %u.\n",
gl->va, be64_to_cpu(*rsp),
get_unaligned_be64(gl->va),
gl->tot_len);
return 0;
}
if ((op == CPL_ISCSI_HDR) || (op == CPL_ISCSI_DATA) ||
(op == CPL_RX_ISCSI_CMP)) {
if (!cxgbit_lro_receive(csk, op, rsp, gl, lro_mgr,
napi))
return 0;
}
#define RX_PULL_LEN 128
skb = cxgb4_pktgl_to_skb(gl, RX_PULL_LEN, RX_PULL_LEN);
if (unlikely(!skb))
goto nomem;
}
rpl = (struct cpl_tx_data *)skb->data;
op = rpl->ot.opcode;
cxgbit_skcb_rx_opcode(skb) = op;
pr_debug("cdev %p, opcode 0x%x(0x%x,0x%x), skb %p.\n",
cdev, op, rpl->ot.opcode_tid,
ntohl(rpl->ot.opcode_tid), skb);
if (op < NUM_CPL_CMDS && cxgbit_cplhandlers[op]) {
cxgbit_cplhandlers[op](cdev, skb);
} else {
pr_err("No handler for opcode 0x%x.\n", op);
__kfree_skb(skb);
}
return 0;
nomem:
pr_err("%s OOM bailing out.\n", __func__);
return 1;
}
#ifdef CONFIG_CHELSIO_T4_DCB
struct cxgbit_dcb_work {
struct dcb_app_type dcb_app;
struct work_struct work;
};
static void
cxgbit_update_dcb_priority(struct cxgbit_device *cdev, u8 port_id,
u8 dcb_priority, u16 port_num)
{
struct cxgbit_sock *csk;
struct sk_buff *skb;
u16 local_port;
bool wakeup_thread = false;
spin_lock_bh(&cdev->cskq.lock);
list_for_each_entry(csk, &cdev->cskq.list, list) {
if (csk->port_id != port_id)
continue;
if (csk->com.local_addr.ss_family == AF_INET6) {
struct sockaddr_in6 *sock_in6;
sock_in6 = (struct sockaddr_in6 *)&csk->com.local_addr;
local_port = ntohs(sock_in6->sin6_port);
} else {
struct sockaddr_in *sock_in;
sock_in = (struct sockaddr_in *)&csk->com.local_addr;
local_port = ntohs(sock_in->sin_port);
}
if (local_port != port_num)
continue;
if (csk->dcb_priority == dcb_priority)
continue;
skb = alloc_skb(0, GFP_ATOMIC);
if (!skb)
continue;
spin_lock(&csk->rxq.lock);
__skb_queue_tail(&csk->rxq, skb);
if (skb_queue_len(&csk->rxq) == 1)
wakeup_thread = true;
spin_unlock(&csk->rxq.lock);
if (wakeup_thread) {
wake_up(&csk->waitq);
wakeup_thread = false;
}
}
spin_unlock_bh(&cdev->cskq.lock);
}
static void cxgbit_dcb_workfn(struct work_struct *work)
{
struct cxgbit_dcb_work *dcb_work;
struct net_device *ndev;
struct cxgbit_device *cdev = NULL;
struct dcb_app_type *iscsi_app;
u8 priority, port_id = 0xff;
dcb_work = container_of(work, struct cxgbit_dcb_work, work);
iscsi_app = &dcb_work->dcb_app;
if (iscsi_app->dcbx & DCB_CAP_DCBX_VER_IEEE) {
if ((iscsi_app->app.selector != IEEE_8021QAZ_APP_SEL_STREAM) &&
(iscsi_app->app.selector != IEEE_8021QAZ_APP_SEL_ANY))
goto out;
priority = iscsi_app->app.priority;
} else if (iscsi_app->dcbx & DCB_CAP_DCBX_VER_CEE) {
if (iscsi_app->app.selector != DCB_APP_IDTYPE_PORTNUM)
goto out;
if (!iscsi_app->app.priority)
goto out;
priority = ffs(iscsi_app->app.priority) - 1;
} else {
goto out;
}
pr_debug("priority for ifid %d is %u\n",
iscsi_app->ifindex, priority);
ndev = dev_get_by_index(&init_net, iscsi_app->ifindex);
if (!ndev)
goto out;
mutex_lock(&cdev_list_lock);
cdev = cxgbit_find_device(ndev, &port_id);
dev_put(ndev);
if (!cdev) {
mutex_unlock(&cdev_list_lock);
goto out;
}
cxgbit_update_dcb_priority(cdev, port_id, priority,
iscsi_app->app.protocol);
mutex_unlock(&cdev_list_lock);
out:
kfree(dcb_work);
}
static int
cxgbit_dcbevent_notify(struct notifier_block *nb, unsigned long action,
void *data)
{
struct cxgbit_dcb_work *dcb_work;
struct dcb_app_type *dcb_app = data;
dcb_work = kzalloc_obj(*dcb_work, GFP_ATOMIC);
if (!dcb_work)
return NOTIFY_DONE;
dcb_work->dcb_app = *dcb_app;
INIT_WORK(&dcb_work->work, cxgbit_dcb_workfn);
schedule_work(&dcb_work->work);
return NOTIFY_OK;
}
#endif
static enum target_prot_op cxgbit_get_sup_prot_ops(struct iscsit_conn *conn)
{
return TARGET_PROT_NORMAL;
}
static struct iscsit_transport cxgbit_transport = {
.name = DRV_NAME,
.transport_type = ISCSI_CXGBIT,
.rdma_shutdown = false,
.priv_size = sizeof(struct cxgbit_cmd),
.owner = THIS_MODULE,
.iscsit_setup_np = cxgbit_setup_np,
.iscsit_accept_np = cxgbit_accept_np,
.iscsit_free_np = cxgbit_free_np,
.iscsit_free_conn = cxgbit_free_conn,
.iscsit_get_login_rx = cxgbit_get_login_rx,
.iscsit_put_login_tx = cxgbit_put_login_tx,
.iscsit_immediate_queue = iscsit_immediate_queue,
.iscsit_response_queue = iscsit_response_queue,
.iscsit_get_dataout = iscsit_build_r2ts_for_cmd,
.iscsit_queue_data_in = iscsit_queue_rsp,
.iscsit_queue_status = iscsit_queue_rsp,
.iscsit_xmit_pdu = cxgbit_xmit_pdu,
.iscsit_get_r2t_ttt = cxgbit_get_r2t_ttt,
.iscsit_get_rx_pdu = cxgbit_get_rx_pdu,
.iscsit_validate_params = cxgbit_validate_params,
.iscsit_unmap_cmd = cxgbit_unmap_cmd,
.iscsit_aborted_task = iscsit_aborted_task,
.iscsit_get_sup_prot_ops = cxgbit_get_sup_prot_ops,
};
static struct cxgb4_uld_info cxgbit_uld_info = {
.name = DRV_NAME,
.nrxq = MAX_ULD_QSETS,
.ntxq = MAX_ULD_QSETS,
.rxq_size = 1024,
.lro = true,
.add = cxgbit_uld_add,
.state_change = cxgbit_uld_state_change,
.lro_rx_handler = cxgbit_uld_lro_rx_handler,
.lro_flush = cxgbit_uld_lro_flush,
};
#ifdef CONFIG_CHELSIO_T4_DCB
static struct notifier_block cxgbit_dcbevent_nb = {
.notifier_call = cxgbit_dcbevent_notify,
};
#endif
static int __init cxgbit_init(void)
{
cxgb4_register_uld(CXGB4_ULD_ISCSIT, &cxgbit_uld_info);
iscsit_register_transport(&cxgbit_transport);
#ifdef CONFIG_CHELSIO_T4_DCB
pr_info("%s dcb enabled.\n", DRV_NAME);
register_dcbevent_notifier(&cxgbit_dcbevent_nb);
#endif
BUILD_BUG_ON(sizeof_field(struct sk_buff, cb) <
sizeof(union cxgbit_skb_cb));
return 0;
}
static void __exit cxgbit_exit(void)
{
struct cxgbit_device *cdev, *tmp;
#ifdef CONFIG_CHELSIO_T4_DCB
unregister_dcbevent_notifier(&cxgbit_dcbevent_nb);
#endif
mutex_lock(&cdev_list_lock);
list_for_each_entry_safe(cdev, tmp, &cdev_list_head, list) {
list_del(&cdev->list);
cxgbit_put_cdev(cdev);
}
mutex_unlock(&cdev_list_lock);
iscsit_unregister_transport(&cxgbit_transport);
cxgb4_unregister_uld(CXGB4_ULD_ISCSIT);
}
module_init(cxgbit_init);
module_exit(cxgbit_exit);
MODULE_DESCRIPTION("Chelsio iSCSI target offload driver");
MODULE_AUTHOR("Chelsio Communications");
MODULE_VERSION(DRV_VERSION);
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_H
#define ISCSI_TARGET_H
#include <linux/types.h>
#include <linux/spinlock.h>
struct iscsit_cmd;
struct iscsit_conn;
struct iscsi_np;
struct iscsi_portal_group;
struct iscsit_session;
struct iscsi_tpg_np;
struct kref;
struct sockaddr_storage;
extern struct iscsi_tiqn *iscsit_get_tiqn_for_login(unsigned char *);
extern void iscsit_put_tiqn_for_login(struct iscsi_tiqn *);
extern struct iscsi_tiqn *iscsit_add_tiqn(unsigned char *);
extern void iscsit_del_tiqn(struct iscsi_tiqn *);
extern int iscsit_access_np(struct iscsi_np *, struct iscsi_portal_group *);
extern void iscsit_login_kref_put(struct kref *);
extern int iscsit_deaccess_np(struct iscsi_np *, struct iscsi_portal_group *,
struct iscsi_tpg_np *);
extern bool iscsit_check_np_match(struct sockaddr_storage *,
struct iscsi_np *, int);
extern struct iscsi_np *iscsit_add_np(struct sockaddr_storage *,
int);
extern int iscsit_reset_np_thread(struct iscsi_np *, struct iscsi_tpg_np *,
struct iscsi_portal_group *, bool);
extern int iscsit_del_np(struct iscsi_np *);
extern int iscsit_reject_cmd(struct iscsit_cmd *cmd, u8, unsigned char *);
extern void iscsit_set_unsolicited_dataout(struct iscsit_cmd *);
extern int iscsit_logout_closesession(struct iscsit_cmd *, struct iscsit_conn *);
extern int iscsit_logout_closeconnection(struct iscsit_cmd *, struct iscsit_conn *);
extern int iscsit_logout_removeconnforrecovery(struct iscsit_cmd *, struct iscsit_conn *);
extern int iscsit_build_r2ts_for_cmd(struct iscsit_conn *, struct iscsit_cmd *, bool recovery);
extern void iscsit_thread_get_cpumask(struct iscsit_conn *);
extern int iscsi_target_tx_thread(void *);
extern int iscsi_target_rx_thread(void *);
extern int iscsit_close_connection(struct iscsit_conn *);
extern int iscsit_close_session(struct iscsit_session *, bool can_sleep);
extern void iscsit_stop_session(struct iscsit_session *, int, int);
extern int iscsit_release_sessions_for_tpg(struct iscsi_portal_group *, int);
extern struct iscsit_global *iscsit_global;
extern const struct target_core_fabric_ops iscsi_ops;
extern struct kmem_cache *lio_dr_cache;
extern struct kmem_cache *lio_ooo_cache;
extern struct kmem_cache *lio_qr_cache;
extern struct kmem_cache *lio_r2t_cache;
extern struct ida sess_ida;
extern struct mutex auth_id_lock;
#endif /*** ISCSI_TARGET_H ***/
@@ -0,0 +1,624 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* This file houses the main functions for the iSCSI CHAP support
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <crypto/hash.h>
#include <crypto/utils.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/err.h>
#include <linux/hex.h>
#include <linux/random.h>
#include <linux/scatterlist.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_nego.h"
#include "iscsi_target_auth.h"
static char *chap_get_digest_name(const int digest_type)
{
switch (digest_type) {
case CHAP_DIGEST_MD5:
return "md5";
case CHAP_DIGEST_SHA1:
return "sha1";
case CHAP_DIGEST_SHA256:
return "sha256";
case CHAP_DIGEST_SHA3_256:
return "sha3-256";
default:
return NULL;
}
}
static int chap_gen_challenge(
struct iscsit_conn *conn,
int caller,
char *c_str,
unsigned int *c_len)
{
int ret;
unsigned char *challenge_asciihex;
struct iscsi_chap *chap = conn->auth_protocol;
challenge_asciihex = kzalloc(chap->challenge_len * 2 + 1, GFP_KERNEL);
if (!challenge_asciihex)
return -ENOMEM;
memset(chap->challenge, 0, MAX_CHAP_CHALLENGE_LEN);
ret = get_random_bytes_wait(chap->challenge, chap->challenge_len);
if (unlikely(ret))
goto out;
bin2hex(challenge_asciihex, chap->challenge,
chap->challenge_len);
/*
* Set CHAP_C, and copy the generated challenge into c_str.
*/
*c_len += sprintf(c_str + *c_len, "CHAP_C=0x%s", challenge_asciihex);
*c_len += 1;
pr_debug("[%s] Sending CHAP_C=0x%s\n\n", (caller) ? "server" : "client",
challenge_asciihex);
out:
kfree(challenge_asciihex);
return ret;
}
static int chap_test_algorithm(const char *name)
{
struct crypto_shash *tfm;
tfm = crypto_alloc_shash(name, 0, 0);
if (IS_ERR(tfm))
return -1;
crypto_free_shash(tfm);
return 0;
}
static int chap_check_algorithm(const char *a_str)
{
char *tmp, *orig, *token, *digest_name;
long digest_type;
int r = CHAP_DIGEST_UNKNOWN;
tmp = kstrdup(a_str, GFP_KERNEL);
if (!tmp) {
pr_err("Memory allocation failed for CHAP_A temporary buffer\n");
return CHAP_DIGEST_UNKNOWN;
}
orig = tmp;
token = strsep(&tmp, "=");
if (!token)
goto out;
if (strcmp(token, "CHAP_A")) {
pr_err("Unable to locate CHAP_A key\n");
goto out;
}
while (token) {
token = strsep(&tmp, ",");
if (!token)
goto out;
if (kstrtol(token, 10, &digest_type))
continue;
digest_name = chap_get_digest_name(digest_type);
if (!digest_name)
continue;
pr_debug("Selected %s Algorithm\n", digest_name);
if (chap_test_algorithm(digest_name) < 0) {
pr_err("failed to allocate %s algo\n", digest_name);
} else {
r = digest_type;
goto out;
}
}
out:
kfree(orig);
return r;
}
static void chap_close(struct iscsit_conn *conn)
{
kfree(conn->auth_protocol);
conn->auth_protocol = NULL;
}
static struct iscsi_chap *chap_server_open(
struct iscsit_conn *conn,
struct iscsi_node_auth *auth,
const char *a_str,
char *aic_str,
unsigned int *aic_len)
{
int digest_type;
struct iscsi_chap *chap;
if (!(auth->naf_flags & NAF_USERID_SET) ||
!(auth->naf_flags & NAF_PASSWORD_SET)) {
pr_err("CHAP user or password not set for"
" Initiator ACL\n");
return NULL;
}
conn->auth_protocol = kzalloc_obj(struct iscsi_chap);
if (!conn->auth_protocol)
return NULL;
chap = conn->auth_protocol;
digest_type = chap_check_algorithm(a_str);
switch (digest_type) {
case CHAP_DIGEST_MD5:
chap->digest_size = MD5_SIGNATURE_SIZE;
break;
case CHAP_DIGEST_SHA1:
chap->digest_size = SHA1_SIGNATURE_SIZE;
break;
case CHAP_DIGEST_SHA256:
chap->digest_size = SHA256_SIGNATURE_SIZE;
break;
case CHAP_DIGEST_SHA3_256:
chap->digest_size = SHA3_256_SIGNATURE_SIZE;
break;
case CHAP_DIGEST_UNKNOWN:
default:
pr_err("Unsupported CHAP_A value\n");
chap_close(conn);
return NULL;
}
chap->digest_name = chap_get_digest_name(digest_type);
/* Tie the challenge length to the digest size */
chap->challenge_len = chap->digest_size;
pr_debug("[server] Got CHAP_A=%d\n", digest_type);
*aic_len = sprintf(aic_str, "CHAP_A=%d", digest_type);
*aic_len += 1;
pr_debug("[server] Sending CHAP_A=%d\n", digest_type);
/*
* Set Identifier.
*/
chap->id = conn->tpg->tpg_chap_id++;
*aic_len += sprintf(aic_str + *aic_len, "CHAP_I=%d", chap->id);
*aic_len += 1;
pr_debug("[server] Sending CHAP_I=%d\n", chap->id);
/*
* Generate Challenge.
*/
if (chap_gen_challenge(conn, 1, aic_str, aic_len) < 0) {
chap_close(conn);
return NULL;
}
return chap;
}
static const char base64_lookup_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int chap_base64_decode(u8 *dst, const char *src, size_t len)
{
int i, bits = 0, ac = 0;
const char *p;
u8 *cp = dst;
for (i = 0; i < len; i++) {
if (src[i] == '=')
return cp - dst;
p = strchr(base64_lookup_table, src[i]);
if (p == NULL || src[i] == 0)
return -2;
ac <<= 6;
ac += (p - base64_lookup_table);
bits += 6;
if (bits >= 8) {
*cp++ = (ac >> (bits - 8)) & 0xff;
ac &= ~(BIT(16) - BIT(bits - 8));
bits -= 8;
}
}
if (ac)
return -1;
return cp - dst;
}
static int chap_server_compute_hash(
struct iscsit_conn *conn,
struct iscsi_node_auth *auth,
char *nr_in_ptr,
char *nr_out_ptr,
unsigned int *nr_out_len)
{
unsigned long id;
unsigned char id_as_uchar;
unsigned char type;
unsigned char identifier[10], *initiatorchg = NULL;
unsigned char *initiatorchg_binhex = NULL;
unsigned char *digest = NULL;
unsigned char *response = NULL;
unsigned char *client_digest = NULL;
unsigned char *server_digest = NULL;
unsigned char chap_n[MAX_CHAP_N_SIZE], chap_r[MAX_RESPONSE_LENGTH];
size_t compare_len;
struct iscsi_chap *chap = conn->auth_protocol;
struct crypto_shash *tfm = NULL;
struct shash_desc *desc = NULL;
int auth_ret = -1, ret, initiatorchg_len;
digest = kzalloc(chap->digest_size, GFP_KERNEL);
if (!digest) {
pr_err("Unable to allocate the digest buffer\n");
goto out;
}
response = kzalloc(chap->digest_size * 2 + 2, GFP_KERNEL);
if (!response) {
pr_err("Unable to allocate the response buffer\n");
goto out;
}
client_digest = kzalloc(chap->digest_size, GFP_KERNEL);
if (!client_digest) {
pr_err("Unable to allocate the client_digest buffer\n");
goto out;
}
server_digest = kzalloc(chap->digest_size, GFP_KERNEL);
if (!server_digest) {
pr_err("Unable to allocate the server_digest buffer\n");
goto out;
}
memset(identifier, 0, 10);
memset(chap_n, 0, MAX_CHAP_N_SIZE);
memset(chap_r, 0, MAX_RESPONSE_LENGTH);
initiatorchg = kzalloc(CHAP_CHALLENGE_STR_LEN, GFP_KERNEL);
if (!initiatorchg) {
pr_err("Unable to allocate challenge buffer\n");
goto out;
}
initiatorchg_binhex = kzalloc(CHAP_CHALLENGE_STR_LEN, GFP_KERNEL);
if (!initiatorchg_binhex) {
pr_err("Unable to allocate initiatorchg_binhex buffer\n");
goto out;
}
/*
* Extract CHAP_N.
*/
if (extract_param(nr_in_ptr, "CHAP_N", MAX_CHAP_N_SIZE, chap_n,
&type) < 0) {
pr_err("Could not find CHAP_N.\n");
goto out;
}
if (type == HEX) {
pr_err("Could not find CHAP_N.\n");
goto out;
}
/* Include the terminating NULL in the compare */
compare_len = strlen(auth->userid) + 1;
if (strncmp(chap_n, auth->userid, compare_len) != 0) {
pr_err("CHAP_N values do not match!\n");
goto out;
}
pr_debug("[server] Got CHAP_N=%s\n", chap_n);
/*
* Extract CHAP_R.
*/
if (extract_param(nr_in_ptr, "CHAP_R", MAX_RESPONSE_LENGTH, chap_r,
&type) < 0) {
pr_err("Could not find CHAP_R.\n");
goto out;
}
switch (type) {
case HEX:
if (strlen(chap_r) != chap->digest_size * 2) {
pr_err("Malformed CHAP_R\n");
goto out;
}
if (hex2bin(client_digest, chap_r, chap->digest_size) < 0) {
pr_err("Malformed CHAP_R: invalid HEX\n");
goto out;
}
break;
case BASE64: {
size_t r_len = strlen(chap_r);
while (r_len > 0 && chap_r[r_len - 1] == '=')
r_len--;
if (r_len > DIV_ROUND_UP(chap->digest_size * 4, 3)) {
pr_err("Malformed CHAP_R: base64 payload too long\n");
goto out;
}
if (chap_base64_decode(client_digest, chap_r, strlen(chap_r)) !=
chap->digest_size) {
pr_err("Malformed CHAP_R: invalid BASE64\n");
goto out;
}
break;
}
default:
pr_err("Could not find CHAP_R\n");
goto out;
}
pr_debug("[server] Got CHAP_R=%s\n", chap_r);
tfm = crypto_alloc_shash(chap->digest_name, 0, 0);
if (IS_ERR(tfm)) {
tfm = NULL;
pr_err("Unable to allocate struct crypto_shash\n");
goto out;
}
desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL);
if (!desc) {
pr_err("Unable to allocate struct shash_desc\n");
goto out;
}
desc->tfm = tfm;
ret = crypto_shash_init(desc);
if (ret < 0) {
pr_err("crypto_shash_init() failed\n");
goto out;
}
ret = crypto_shash_update(desc, &chap->id, 1);
if (ret < 0) {
pr_err("crypto_shash_update() failed for id\n");
goto out;
}
ret = crypto_shash_update(desc, (char *)&auth->password,
strlen(auth->password));
if (ret < 0) {
pr_err("crypto_shash_update() failed for password\n");
goto out;
}
ret = crypto_shash_finup(desc, chap->challenge,
chap->challenge_len, server_digest);
if (ret < 0) {
pr_err("crypto_shash_finup() failed for challenge\n");
goto out;
}
bin2hex(response, server_digest, chap->digest_size);
pr_debug("[server] %s Server Digest: %s\n",
chap->digest_name, response);
if (crypto_memneq(server_digest, client_digest, chap->digest_size)) {
pr_debug("[server] %s Digests do not match!\n\n",
chap->digest_name);
goto out;
} else
pr_debug("[server] %s Digests match, CHAP connection"
" successful.\n\n", chap->digest_name);
/*
* One way authentication has succeeded, return now if mutual
* authentication is not enabled.
*/
if (!auth->authenticate_target) {
auth_ret = 0;
goto out;
}
/*
* Get CHAP_I.
*/
ret = extract_param(nr_in_ptr, "CHAP_I", 10, identifier, &type);
if (ret == -ENOENT) {
pr_debug("Could not find CHAP_I. Initiator uses One way authentication.\n");
auth_ret = 0;
goto out;
}
if (ret < 0) {
pr_err("Could not find CHAP_I.\n");
goto out;
}
if (type == HEX)
ret = kstrtoul(identifier, 16, &id);
else if (type == DECIMAL)
ret = kstrtoul(identifier, 10, &id);
else
ret = -EINVAL;
if (ret < 0) {
pr_err("kstrtoul() failed for CHAP identifier: %d\n", ret);
goto out;
}
if (id > 255) {
pr_err("chap identifier: %lu greater than 255\n", id);
goto out;
}
/*
* RFC 1994 says Identifier is no more than octet (8 bits).
*/
pr_debug("[server] Got CHAP_I=%lu\n", id);
/*
* Get CHAP_C.
*/
if (extract_param(nr_in_ptr, "CHAP_C", CHAP_CHALLENGE_STR_LEN,
initiatorchg, &type) < 0) {
pr_err("Could not find CHAP_C.\n");
goto out;
}
switch (type) {
case HEX:
initiatorchg_len = DIV_ROUND_UP(strlen(initiatorchg), 2);
if (!initiatorchg_len) {
pr_err("Unable to convert incoming challenge\n");
goto out;
}
if (initiatorchg_len > 1024) {
pr_err("CHAP_C exceeds maximum binary size of 1024 bytes\n");
goto out;
}
if (hex2bin(initiatorchg_binhex, initiatorchg,
initiatorchg_len) < 0) {
pr_err("Malformed CHAP_C: invalid HEX\n");
goto out;
}
break;
case BASE64:
/*
* No overflow check needed: initiatorchg_binhex is
* CHAP_CHALLENGE_STR_LEN bytes and extract_param() caps
* initiatorchg at CHAP_CHALLENGE_STR_LEN characters, so
* the decoded output is at most DIV_ROUND_UP(
* (CHAP_CHALLENGE_STR_LEN - 1) * 3, 4) bytes, which is
* less than CHAP_CHALLENGE_STR_LEN.
*/
initiatorchg_len = chap_base64_decode(initiatorchg_binhex,
initiatorchg,
strlen(initiatorchg));
if (initiatorchg_len < 0) {
pr_err("Malformed CHAP_C: invalid BASE64\n");
goto out;
}
if (!initiatorchg_len) {
pr_err("Unable to convert incoming challenge\n");
goto out;
}
if (initiatorchg_len > 1024) {
pr_err("CHAP_C exceeds maximum binary size of 1024 bytes\n");
goto out;
}
break;
default:
pr_err("Could not find CHAP_C.\n");
goto out;
}
pr_debug("[server] Got CHAP_C=%s\n", initiatorchg);
/*
* During mutual authentication, the CHAP_C generated by the
* initiator must not match the original CHAP_C generated by
* the target.
*/
if (initiatorchg_len == chap->challenge_len &&
!memcmp(initiatorchg_binhex, chap->challenge,
initiatorchg_len)) {
pr_err("initiator CHAP_C matches target CHAP_C, failing"
" login attempt\n");
goto out;
}
/*
* Generate CHAP_N and CHAP_R for mutual authentication.
*/
ret = crypto_shash_init(desc);
if (ret < 0) {
pr_err("crypto_shash_init() failed\n");
goto out;
}
/* To handle both endiannesses */
id_as_uchar = id;
ret = crypto_shash_update(desc, &id_as_uchar, 1);
if (ret < 0) {
pr_err("crypto_shash_update() failed for id\n");
goto out;
}
ret = crypto_shash_update(desc, auth->password_mutual,
strlen(auth->password_mutual));
if (ret < 0) {
pr_err("crypto_shash_update() failed for"
" password_mutual\n");
goto out;
}
/*
* Convert received challenge to binary hex.
*/
ret = crypto_shash_finup(desc, initiatorchg_binhex, initiatorchg_len,
digest);
if (ret < 0) {
pr_err("crypto_shash_finup() failed for ma challenge\n");
goto out;
}
/*
* Generate CHAP_N and CHAP_R.
*/
*nr_out_len = sprintf(nr_out_ptr, "CHAP_N=%s", auth->userid_mutual);
*nr_out_len += 1;
pr_debug("[server] Sending CHAP_N=%s\n", auth->userid_mutual);
/*
* Convert response from binary hex to ascii hext.
*/
bin2hex(response, digest, chap->digest_size);
*nr_out_len += sprintf(nr_out_ptr + *nr_out_len, "CHAP_R=0x%s",
response);
*nr_out_len += 1;
pr_debug("[server] Sending CHAP_R=0x%s\n", response);
auth_ret = 0;
out:
kfree_sensitive(desc);
if (tfm)
crypto_free_shash(tfm);
kfree(initiatorchg);
kfree(initiatorchg_binhex);
kfree(digest);
kfree(response);
kfree(server_digest);
kfree(client_digest);
return auth_ret;
}
u32 chap_main_loop(
struct iscsit_conn *conn,
struct iscsi_node_auth *auth,
char *in_text,
char *out_text,
int *in_len,
int *out_len)
{
struct iscsi_chap *chap = conn->auth_protocol;
if (!chap) {
chap = chap_server_open(conn, auth, in_text, out_text, out_len);
if (!chap)
return 2;
chap->chap_state = CHAP_STAGE_SERVER_AIC;
return 0;
} else if (chap->chap_state == CHAP_STAGE_SERVER_AIC) {
convert_null_to_semi(in_text, *in_len);
if (chap_server_compute_hash(conn, auth, in_text, out_text,
out_len) < 0) {
chap_close(conn);
return 2;
}
if (auth->authenticate_target)
chap->chap_state = CHAP_STAGE_SERVER_NR;
else
*out_len = 0;
chap_close(conn);
return 1;
}
return 2;
}
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ISCSI_CHAP_H_
#define _ISCSI_CHAP_H_
#include <linux/types.h>
#define CHAP_DIGEST_UNKNOWN 0
#define CHAP_DIGEST_MD5 5
#define CHAP_DIGEST_SHA1 6
#define CHAP_DIGEST_SHA256 7
#define CHAP_DIGEST_SHA3_256 8
#define MAX_CHAP_CHALLENGE_LEN 32
#define CHAP_CHALLENGE_STR_LEN 4096
#define MAX_RESPONSE_LENGTH 128 /* sufficient for SHA3 256 */
#define MAX_CHAP_N_SIZE 512
#define MD5_SIGNATURE_SIZE 16 /* 16 bytes in a MD5 message digest */
#define SHA1_SIGNATURE_SIZE 20 /* 20 bytes in a SHA1 message digest */
#define SHA256_SIGNATURE_SIZE 32 /* 32 bytes in a SHA256 message digest */
#define SHA3_256_SIGNATURE_SIZE 32 /* 32 bytes in a SHA3 256 message digest */
#define CHAP_STAGE_CLIENT_A 1
#define CHAP_STAGE_SERVER_AIC 2
#define CHAP_STAGE_CLIENT_NR 3
#define CHAP_STAGE_CLIENT_NRIC 4
#define CHAP_STAGE_SERVER_NR 5
struct iscsi_node_auth;
struct iscsit_conn;
extern u32 chap_main_loop(struct iscsit_conn *, struct iscsi_node_auth *, char *, char *,
int *, int *);
struct iscsi_chap {
unsigned char id;
unsigned char challenge[MAX_CHAP_CHALLENGE_LEN];
unsigned int challenge_len;
unsigned char *digest_name;
unsigned int digest_size;
unsigned int authenticate_target;
unsigned int chap_state;
} ____cacheline_aligned;
#endif /*** _ISCSI_CHAP_H_ ***/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,519 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* This file contains the iSCSI Target DataIN value generation functions.
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <linux/slab.h>
#include <scsi/iscsi_proto.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
#include "iscsi_target_datain_values.h"
struct iscsi_datain_req *iscsit_allocate_datain_req(void)
{
struct iscsi_datain_req *dr;
dr = kmem_cache_zalloc(lio_dr_cache, GFP_ATOMIC);
if (!dr) {
pr_err("Unable to allocate memory for"
" struct iscsi_datain_req\n");
return NULL;
}
INIT_LIST_HEAD(&dr->cmd_datain_node);
return dr;
}
void iscsit_attach_datain_req(struct iscsit_cmd *cmd, struct iscsi_datain_req *dr)
{
spin_lock(&cmd->datain_lock);
list_add_tail(&dr->cmd_datain_node, &cmd->datain_list);
spin_unlock(&cmd->datain_lock);
}
void iscsit_free_datain_req(struct iscsit_cmd *cmd, struct iscsi_datain_req *dr)
{
spin_lock(&cmd->datain_lock);
list_del(&dr->cmd_datain_node);
spin_unlock(&cmd->datain_lock);
kmem_cache_free(lio_dr_cache, dr);
}
void iscsit_free_all_datain_reqs(struct iscsit_cmd *cmd)
{
struct iscsi_datain_req *dr, *dr_tmp;
spin_lock(&cmd->datain_lock);
list_for_each_entry_safe(dr, dr_tmp, &cmd->datain_list, cmd_datain_node) {
list_del(&dr->cmd_datain_node);
kmem_cache_free(lio_dr_cache, dr);
}
spin_unlock(&cmd->datain_lock);
}
struct iscsi_datain_req *iscsit_get_datain_req(struct iscsit_cmd *cmd)
{
if (list_empty(&cmd->datain_list)) {
pr_err("cmd->datain_list is empty for ITT:"
" 0x%08x\n", cmd->init_task_tag);
return NULL;
}
return list_first_entry(&cmd->datain_list, struct iscsi_datain_req,
cmd_datain_node);
}
/*
* For Normal and Recovery DataSequenceInOrder=Yes and DataPDUInOrder=Yes.
*/
static struct iscsi_datain_req *iscsit_set_datain_values_yes_and_yes(
struct iscsit_cmd *cmd,
struct iscsi_datain *datain)
{
u32 next_burst_len, read_data_done, read_data_left;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
dr = iscsit_get_datain_req(cmd);
if (!dr)
return NULL;
if (dr->recovery && dr->generate_recovery_values) {
if (iscsit_create_recovery_datain_values_datasequenceinorder_yes(
cmd, dr) < 0)
return NULL;
dr->generate_recovery_values = 0;
}
next_burst_len = (!dr->recovery) ?
cmd->next_burst_len : dr->next_burst_len;
read_data_done = (!dr->recovery) ?
cmd->read_data_done : dr->read_data_done;
read_data_left = (cmd->se_cmd.data_length - read_data_done);
if (!read_data_left) {
pr_err("ITT: 0x%08x read_data_left is zero!\n",
cmd->init_task_tag);
return NULL;
}
if ((read_data_left <= conn->conn_ops->MaxRecvDataSegmentLength) &&
(read_data_left <= (conn->sess->sess_ops->MaxBurstLength -
next_burst_len))) {
datain->length = read_data_left;
datain->flags |= (ISCSI_FLAG_CMD_FINAL | ISCSI_FLAG_DATA_STATUS);
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
datain->flags |= ISCSI_FLAG_DATA_ACK;
} else {
if ((next_burst_len +
conn->conn_ops->MaxRecvDataSegmentLength) <
conn->sess->sess_ops->MaxBurstLength) {
datain->length =
conn->conn_ops->MaxRecvDataSegmentLength;
next_burst_len += datain->length;
} else {
datain->length = (conn->sess->sess_ops->MaxBurstLength -
next_burst_len);
next_burst_len = 0;
datain->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
datain->flags |= ISCSI_FLAG_DATA_ACK;
}
}
datain->data_sn = (!dr->recovery) ? cmd->data_sn++ : dr->data_sn++;
datain->offset = read_data_done;
if (!dr->recovery) {
cmd->next_burst_len = next_burst_len;
cmd->read_data_done += datain->length;
} else {
dr->next_burst_len = next_burst_len;
dr->read_data_done += datain->length;
}
if (!dr->recovery) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS)
dr->dr_complete = DATAIN_COMPLETE_NORMAL;
return dr;
}
if (!dr->runlength) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
} else {
if ((dr->begrun + dr->runlength) == dr->data_sn) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
}
return dr;
}
/*
* For Normal and Recovery DataSequenceInOrder=No and DataPDUInOrder=Yes.
*/
static struct iscsi_datain_req *iscsit_set_datain_values_no_and_yes(
struct iscsit_cmd *cmd,
struct iscsi_datain *datain)
{
u32 offset, read_data_done, read_data_left, seq_send_order;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct iscsi_seq *seq;
dr = iscsit_get_datain_req(cmd);
if (!dr)
return NULL;
if (dr->recovery && dr->generate_recovery_values) {
if (iscsit_create_recovery_datain_values_datasequenceinorder_no(
cmd, dr) < 0)
return NULL;
dr->generate_recovery_values = 0;
}
read_data_done = (!dr->recovery) ?
cmd->read_data_done : dr->read_data_done;
seq_send_order = (!dr->recovery) ?
cmd->seq_send_order : dr->seq_send_order;
read_data_left = (cmd->se_cmd.data_length - read_data_done);
if (!read_data_left) {
pr_err("ITT: 0x%08x read_data_left is zero!\n",
cmd->init_task_tag);
return NULL;
}
seq = iscsit_get_seq_holder_for_datain(cmd, seq_send_order);
if (!seq)
return NULL;
seq->sent = 1;
if (!dr->recovery && !seq->next_burst_len)
seq->first_datasn = cmd->data_sn;
offset = (seq->offset + seq->next_burst_len);
if ((offset + conn->conn_ops->MaxRecvDataSegmentLength) >=
cmd->se_cmd.data_length) {
datain->length = (cmd->se_cmd.data_length - offset);
datain->offset = offset;
datain->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
datain->flags |= ISCSI_FLAG_DATA_ACK;
seq->next_burst_len = 0;
seq_send_order++;
} else {
if ((seq->next_burst_len +
conn->conn_ops->MaxRecvDataSegmentLength) <
conn->sess->sess_ops->MaxBurstLength) {
datain->length =
conn->conn_ops->MaxRecvDataSegmentLength;
datain->offset = (seq->offset + seq->next_burst_len);
seq->next_burst_len += datain->length;
} else {
datain->length = (conn->sess->sess_ops->MaxBurstLength -
seq->next_burst_len);
datain->offset = (seq->offset + seq->next_burst_len);
datain->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
datain->flags |= ISCSI_FLAG_DATA_ACK;
seq->next_burst_len = 0;
seq_send_order++;
}
}
if ((read_data_done + datain->length) == cmd->se_cmd.data_length)
datain->flags |= ISCSI_FLAG_DATA_STATUS;
datain->data_sn = (!dr->recovery) ? cmd->data_sn++ : dr->data_sn++;
if (!dr->recovery) {
cmd->seq_send_order = seq_send_order;
cmd->read_data_done += datain->length;
} else {
dr->seq_send_order = seq_send_order;
dr->read_data_done += datain->length;
}
if (!dr->recovery) {
if (datain->flags & ISCSI_FLAG_CMD_FINAL)
seq->last_datasn = datain->data_sn;
if (datain->flags & ISCSI_FLAG_DATA_STATUS)
dr->dr_complete = DATAIN_COMPLETE_NORMAL;
return dr;
}
if (!dr->runlength) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
} else {
if ((dr->begrun + dr->runlength) == dr->data_sn) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
}
return dr;
}
/*
* For Normal and Recovery DataSequenceInOrder=Yes and DataPDUInOrder=No.
*/
static struct iscsi_datain_req *iscsit_set_datain_values_yes_and_no(
struct iscsit_cmd *cmd,
struct iscsi_datain *datain)
{
u32 next_burst_len, read_data_done, read_data_left;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct iscsi_pdu *pdu;
dr = iscsit_get_datain_req(cmd);
if (!dr)
return NULL;
if (dr->recovery && dr->generate_recovery_values) {
if (iscsit_create_recovery_datain_values_datasequenceinorder_yes(
cmd, dr) < 0)
return NULL;
dr->generate_recovery_values = 0;
}
next_burst_len = (!dr->recovery) ?
cmd->next_burst_len : dr->next_burst_len;
read_data_done = (!dr->recovery) ?
cmd->read_data_done : dr->read_data_done;
read_data_left = (cmd->se_cmd.data_length - read_data_done);
if (!read_data_left) {
pr_err("ITT: 0x%08x read_data_left is zero!\n",
cmd->init_task_tag);
return dr;
}
pdu = iscsit_get_pdu_holder_for_seq(cmd, NULL);
if (!pdu)
return dr;
if ((read_data_done + pdu->length) == cmd->se_cmd.data_length) {
pdu->flags |= (ISCSI_FLAG_CMD_FINAL | ISCSI_FLAG_DATA_STATUS);
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
pdu->flags |= ISCSI_FLAG_DATA_ACK;
next_burst_len = 0;
} else {
if ((next_burst_len + conn->conn_ops->MaxRecvDataSegmentLength) <
conn->sess->sess_ops->MaxBurstLength)
next_burst_len += pdu->length;
else {
pdu->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
pdu->flags |= ISCSI_FLAG_DATA_ACK;
next_burst_len = 0;
}
}
pdu->data_sn = (!dr->recovery) ? cmd->data_sn++ : dr->data_sn++;
if (!dr->recovery) {
cmd->next_burst_len = next_burst_len;
cmd->read_data_done += pdu->length;
} else {
dr->next_burst_len = next_burst_len;
dr->read_data_done += pdu->length;
}
datain->flags = pdu->flags;
datain->length = pdu->length;
datain->offset = pdu->offset;
datain->data_sn = pdu->data_sn;
if (!dr->recovery) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS)
dr->dr_complete = DATAIN_COMPLETE_NORMAL;
return dr;
}
if (!dr->runlength) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
} else {
if ((dr->begrun + dr->runlength) == dr->data_sn) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
}
return dr;
}
/*
* For Normal and Recovery DataSequenceInOrder=No and DataPDUInOrder=No.
*/
static struct iscsi_datain_req *iscsit_set_datain_values_no_and_no(
struct iscsit_cmd *cmd,
struct iscsi_datain *datain)
{
u32 read_data_done, read_data_left, seq_send_order;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct iscsi_pdu *pdu;
struct iscsi_seq *seq = NULL;
dr = iscsit_get_datain_req(cmd);
if (!dr)
return NULL;
if (dr->recovery && dr->generate_recovery_values) {
if (iscsit_create_recovery_datain_values_datasequenceinorder_no(
cmd, dr) < 0)
return NULL;
dr->generate_recovery_values = 0;
}
read_data_done = (!dr->recovery) ?
cmd->read_data_done : dr->read_data_done;
seq_send_order = (!dr->recovery) ?
cmd->seq_send_order : dr->seq_send_order;
read_data_left = (cmd->se_cmd.data_length - read_data_done);
if (!read_data_left) {
pr_err("ITT: 0x%08x read_data_left is zero!\n",
cmd->init_task_tag);
return NULL;
}
seq = iscsit_get_seq_holder_for_datain(cmd, seq_send_order);
if (!seq)
return NULL;
seq->sent = 1;
if (!dr->recovery && !seq->next_burst_len)
seq->first_datasn = cmd->data_sn;
pdu = iscsit_get_pdu_holder_for_seq(cmd, seq);
if (!pdu)
return NULL;
if (seq->pdu_send_order == seq->pdu_count) {
pdu->flags |= ISCSI_FLAG_CMD_FINAL;
if (conn->sess->sess_ops->ErrorRecoveryLevel > 0)
pdu->flags |= ISCSI_FLAG_DATA_ACK;
seq->next_burst_len = 0;
seq_send_order++;
} else
seq->next_burst_len += pdu->length;
if ((read_data_done + pdu->length) == cmd->se_cmd.data_length)
pdu->flags |= ISCSI_FLAG_DATA_STATUS;
pdu->data_sn = (!dr->recovery) ? cmd->data_sn++ : dr->data_sn++;
if (!dr->recovery) {
cmd->seq_send_order = seq_send_order;
cmd->read_data_done += pdu->length;
} else {
dr->seq_send_order = seq_send_order;
dr->read_data_done += pdu->length;
}
datain->flags = pdu->flags;
datain->length = pdu->length;
datain->offset = pdu->offset;
datain->data_sn = pdu->data_sn;
if (!dr->recovery) {
if (datain->flags & ISCSI_FLAG_CMD_FINAL)
seq->last_datasn = datain->data_sn;
if (datain->flags & ISCSI_FLAG_DATA_STATUS)
dr->dr_complete = DATAIN_COMPLETE_NORMAL;
return dr;
}
if (!dr->runlength) {
if (datain->flags & ISCSI_FLAG_DATA_STATUS) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
} else {
if ((dr->begrun + dr->runlength) == dr->data_sn) {
dr->dr_complete =
(dr->recovery == DATAIN_WITHIN_COMMAND_RECOVERY) ?
DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY :
DATAIN_COMPLETE_CONNECTION_RECOVERY;
}
}
return dr;
}
struct iscsi_datain_req *iscsit_get_datain_values(
struct iscsit_cmd *cmd,
struct iscsi_datain *datain)
{
struct iscsit_conn *conn = cmd->conn;
if (conn->sess->sess_ops->DataSequenceInOrder &&
conn->sess->sess_ops->DataPDUInOrder)
return iscsit_set_datain_values_yes_and_yes(cmd, datain);
else if (!conn->sess->sess_ops->DataSequenceInOrder &&
conn->sess->sess_ops->DataPDUInOrder)
return iscsit_set_datain_values_no_and_yes(cmd, datain);
else if (conn->sess->sess_ops->DataSequenceInOrder &&
!conn->sess->sess_ops->DataPDUInOrder)
return iscsit_set_datain_values_yes_and_no(cmd, datain);
else if (!conn->sess->sess_ops->DataSequenceInOrder &&
!conn->sess->sess_ops->DataPDUInOrder)
return iscsit_set_datain_values_no_and_no(cmd, datain);
return NULL;
}
EXPORT_SYMBOL(iscsit_get_datain_values);
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_DATAIN_VALUES_H
#define ISCSI_TARGET_DATAIN_VALUES_H
struct iscsit_cmd;
struct iscsi_datain;
extern struct iscsi_datain_req *iscsit_allocate_datain_req(void);
extern void iscsit_attach_datain_req(struct iscsit_cmd *, struct iscsi_datain_req *);
extern void iscsit_free_datain_req(struct iscsit_cmd *, struct iscsi_datain_req *);
extern void iscsit_free_all_datain_reqs(struct iscsit_cmd *);
extern struct iscsi_datain_req *iscsit_get_datain_req(struct iscsit_cmd *);
extern struct iscsi_datain_req *iscsit_get_datain_values(struct iscsit_cmd *,
struct iscsi_datain *);
#endif /*** ISCSI_TARGET_DATAIN_VALUES_H ***/
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* This file contains the iSCSI Virtual Device and Disk Transport
* agnostic related functions.
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <target/target_core_base.h>
#include <target/target_core_fabric.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_device.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
void iscsit_determine_maxcmdsn(struct iscsit_session *sess)
{
struct se_node_acl *se_nacl;
/*
* This is a discovery session, the single queue slot was already
* assigned in iscsi_login_zero_tsih(). Since only Logout and
* Text Opcodes are allowed during discovery we do not have to worry
* about the HBA's queue depth here.
*/
if (sess->sess_ops->SessionType)
return;
se_nacl = sess->se_sess->se_node_acl;
/*
* This is a normal session, set the Session's CmdSN window to the
* struct se_node_acl->queue_depth. The value in struct se_node_acl->queue_depth
* has already been validated as a legal value in
* core_set_queue_depth_for_node().
*/
sess->cmdsn_window = se_nacl->queue_depth;
atomic_add(se_nacl->queue_depth - 1, &sess->max_cmd_sn);
}
void iscsit_increment_maxcmdsn(struct iscsit_cmd *cmd, struct iscsit_session *sess)
{
u32 max_cmd_sn;
if (cmd->immediate_cmd || cmd->maxcmdsn_inc)
return;
cmd->maxcmdsn_inc = 1;
max_cmd_sn = atomic_inc_return(&sess->max_cmd_sn);
pr_debug("Updated MaxCmdSN to 0x%08x\n", max_cmd_sn);
}
EXPORT_SYMBOL(iscsit_increment_maxcmdsn);
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_DEVICE_H
#define ISCSI_TARGET_DEVICE_H
struct iscsit_cmd;
struct iscsit_session;
extern void iscsit_determine_maxcmdsn(struct iscsit_session *);
extern void iscsit_increment_maxcmdsn(struct iscsit_cmd *, struct iscsit_session *);
#endif /* ISCSI_TARGET_DEVICE_H */
@@ -0,0 +1,937 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/******************************************************************************
* This file contains error recovery level zero functions used by
* the iSCSI Target driver.
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <linux/sched/signal.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_fabric.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
/*
* Used to set values in struct iscsit_cmd that iscsit_dataout_check_sequence()
* checks against to determine a PDU's Offset+Length is within the current
* DataOUT Sequence. Used for DataSequenceInOrder=Yes only.
*/
void iscsit_set_dataout_sequence_values(
struct iscsit_cmd *cmd)
{
struct iscsit_conn *conn = cmd->conn;
/*
* Still set seq_start_offset and seq_end_offset for Unsolicited
* DataOUT, even if DataSequenceInOrder=No.
*/
if (cmd->unsolicited_data) {
cmd->seq_start_offset = cmd->write_data_done;
cmd->seq_end_offset = min(cmd->se_cmd.data_length,
conn->sess->sess_ops->FirstBurstLength);
return;
}
if (!conn->sess->sess_ops->DataSequenceInOrder)
return;
if (!cmd->seq_start_offset && !cmd->seq_end_offset) {
cmd->seq_start_offset = cmd->write_data_done;
cmd->seq_end_offset = (cmd->se_cmd.data_length >
conn->sess->sess_ops->MaxBurstLength) ?
(cmd->write_data_done +
conn->sess->sess_ops->MaxBurstLength) : cmd->se_cmd.data_length;
} else {
cmd->seq_start_offset = cmd->seq_end_offset;
cmd->seq_end_offset = ((cmd->seq_end_offset +
conn->sess->sess_ops->MaxBurstLength) >=
cmd->se_cmd.data_length) ? cmd->se_cmd.data_length :
(cmd->seq_end_offset +
conn->sess->sess_ops->MaxBurstLength);
}
}
static int iscsit_dataout_within_command_recovery_check(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
struct iscsit_conn *conn = cmd->conn;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
/*
* We do the within-command recovery checks here as it is
* the first function called in iscsi_check_pre_dataout().
* Basically, if we are in within-command recovery and
* the PDU does not contain the offset the sequence needs,
* dump the payload.
*
* This only applies to DataPDUInOrder=Yes, for
* DataPDUInOrder=No we only re-request the failed PDU
* and check that all PDUs in a sequence are received
* upon end of sequence.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
if ((cmd->cmd_flags & ICF_WITHIN_COMMAND_RECOVERY) &&
cmd->write_data_done != be32_to_cpu(hdr->offset))
goto dump;
cmd->cmd_flags &= ~ICF_WITHIN_COMMAND_RECOVERY;
} else {
struct iscsi_seq *seq;
seq = iscsit_get_seq_holder(cmd, be32_to_cpu(hdr->offset),
payload_length);
if (!seq)
return DATAOUT_CANNOT_RECOVER;
/*
* Set the struct iscsi_seq pointer to reuse later.
*/
cmd->seq_ptr = seq;
if (conn->sess->sess_ops->DataPDUInOrder) {
if (seq->status ==
DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY &&
(seq->offset != be32_to_cpu(hdr->offset) ||
seq->data_sn != be32_to_cpu(hdr->datasn)))
goto dump;
} else {
if (seq->status ==
DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY &&
seq->data_sn != be32_to_cpu(hdr->datasn))
goto dump;
}
if (seq->status == DATAOUT_SEQUENCE_COMPLETE)
goto dump;
if (seq->status != DATAOUT_SEQUENCE_COMPLETE)
seq->status = 0;
}
return DATAOUT_NORMAL;
dump:
pr_err("Dumping DataOUT PDU Offset: %u Length: %d DataSN:"
" 0x%08x\n", hdr->offset, payload_length, hdr->datasn);
return iscsit_dump_data_payload(conn, payload_length, 1);
}
static int iscsit_dataout_check_unsolicited_sequence(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
u32 first_burst_len;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
if ((be32_to_cpu(hdr->offset) < cmd->seq_start_offset) ||
((be32_to_cpu(hdr->offset) + payload_length) > cmd->seq_end_offset)) {
pr_err("Command ITT: 0x%08x with Offset: %u,"
" Length: %u outside of Unsolicited Sequence %u:%u while"
" DataSequenceInOrder=Yes.\n", cmd->init_task_tag,
be32_to_cpu(hdr->offset), payload_length, cmd->seq_start_offset,
cmd->seq_end_offset);
return DATAOUT_CANNOT_RECOVER;
}
first_burst_len = (cmd->first_burst_len + payload_length);
if (first_burst_len > conn->sess->sess_ops->FirstBurstLength) {
pr_err("Total %u bytes exceeds FirstBurstLength: %u"
" for this Unsolicited DataOut Burst.\n",
first_burst_len, conn->sess->sess_ops->FirstBurstLength);
transport_send_check_condition_and_sense(&cmd->se_cmd,
TCM_INCORRECT_AMOUNT_OF_DATA, 0);
return DATAOUT_CANNOT_RECOVER;
}
/*
* Perform various MaxBurstLength and ISCSI_FLAG_CMD_FINAL sanity
* checks for the current Unsolicited DataOUT Sequence.
*/
if (hdr->flags & ISCSI_FLAG_CMD_FINAL) {
/*
* Ignore ISCSI_FLAG_CMD_FINAL checks while DataPDUInOrder=No, end of
* sequence checks are handled in
* iscsit_dataout_datapduinorder_no_fbit().
*/
if (!conn->sess->sess_ops->DataPDUInOrder)
goto out;
if ((first_burst_len != cmd->se_cmd.data_length) &&
(first_burst_len != conn->sess->sess_ops->FirstBurstLength)) {
pr_err("Unsolicited non-immediate data"
" received %u does not equal FirstBurstLength: %u, and"
" does not equal ExpXferLen %u.\n", first_burst_len,
conn->sess->sess_ops->FirstBurstLength,
cmd->se_cmd.data_length);
transport_send_check_condition_and_sense(&cmd->se_cmd,
TCM_INCORRECT_AMOUNT_OF_DATA, 0);
return DATAOUT_CANNOT_RECOVER;
}
} else {
if (first_burst_len == conn->sess->sess_ops->FirstBurstLength) {
pr_err("Command ITT: 0x%08x reached"
" FirstBurstLength: %u, but ISCSI_FLAG_CMD_FINAL is not set. protocol"
" error.\n", cmd->init_task_tag,
conn->sess->sess_ops->FirstBurstLength);
return DATAOUT_CANNOT_RECOVER;
}
if (first_burst_len == cmd->se_cmd.data_length) {
pr_err("Command ITT: 0x%08x reached"
" ExpXferLen: %u, but ISCSI_FLAG_CMD_FINAL is not set. protocol"
" error.\n", cmd->init_task_tag, cmd->se_cmd.data_length);
return DATAOUT_CANNOT_RECOVER;
}
}
out:
return DATAOUT_NORMAL;
}
static int iscsit_dataout_check_sequence(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
u32 next_burst_len;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_seq *seq = NULL;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
/*
* For DataSequenceInOrder=Yes: Check that the offset and offset+length
* is within range as defined by iscsi_set_dataout_sequence_values().
*
* For DataSequenceInOrder=No: Check that an struct iscsi_seq exists for
* offset+length tuple.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
/*
* Due to possibility of recovery DataOUT sent by the initiator
* fullfilling an Recovery R2T, it's best to just dump the
* payload here, instead of erroring out.
*/
if ((be32_to_cpu(hdr->offset) < cmd->seq_start_offset) ||
((be32_to_cpu(hdr->offset) + payload_length) > cmd->seq_end_offset)) {
pr_err("Command ITT: 0x%08x with Offset: %u,"
" Length: %u outside of Sequence %u:%u while"
" DataSequenceInOrder=Yes.\n", cmd->init_task_tag,
be32_to_cpu(hdr->offset), payload_length, cmd->seq_start_offset,
cmd->seq_end_offset);
if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
return DATAOUT_CANNOT_RECOVER;
return DATAOUT_WITHIN_COMMAND_RECOVERY;
}
next_burst_len = (cmd->next_burst_len + payload_length);
} else {
seq = iscsit_get_seq_holder(cmd, be32_to_cpu(hdr->offset),
payload_length);
if (!seq)
return DATAOUT_CANNOT_RECOVER;
/*
* Set the struct iscsi_seq pointer to reuse later.
*/
cmd->seq_ptr = seq;
if (seq->status == DATAOUT_SEQUENCE_COMPLETE) {
if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
return DATAOUT_CANNOT_RECOVER;
return DATAOUT_WITHIN_COMMAND_RECOVERY;
}
next_burst_len = (seq->next_burst_len + payload_length);
}
if (next_burst_len > conn->sess->sess_ops->MaxBurstLength) {
pr_err("Command ITT: 0x%08x, NextBurstLength: %u and"
" Length: %u exceeds MaxBurstLength: %u. protocol"
" error.\n", cmd->init_task_tag,
(next_burst_len - payload_length),
payload_length, conn->sess->sess_ops->MaxBurstLength);
return DATAOUT_CANNOT_RECOVER;
}
/*
* Perform various MaxBurstLength and ISCSI_FLAG_CMD_FINAL sanity
* checks for the current DataOUT Sequence.
*/
if (hdr->flags & ISCSI_FLAG_CMD_FINAL) {
/*
* Ignore ISCSI_FLAG_CMD_FINAL checks while DataPDUInOrder=No, end of
* sequence checks are handled in
* iscsit_dataout_datapduinorder_no_fbit().
*/
if (!conn->sess->sess_ops->DataPDUInOrder)
goto out;
if (conn->sess->sess_ops->DataSequenceInOrder) {
if ((next_burst_len <
conn->sess->sess_ops->MaxBurstLength) &&
((cmd->write_data_done + payload_length) <
cmd->se_cmd.data_length)) {
pr_err("Command ITT: 0x%08x set ISCSI_FLAG_CMD_FINAL"
" before end of DataOUT sequence, protocol"
" error.\n", cmd->init_task_tag);
return DATAOUT_CANNOT_RECOVER;
}
} else {
if (next_burst_len < seq->xfer_len) {
pr_err("Command ITT: 0x%08x set ISCSI_FLAG_CMD_FINAL"
" before end of DataOUT sequence, protocol"
" error.\n", cmd->init_task_tag);
return DATAOUT_CANNOT_RECOVER;
}
}
} else {
if (conn->sess->sess_ops->DataSequenceInOrder) {
if (next_burst_len ==
conn->sess->sess_ops->MaxBurstLength) {
pr_err("Command ITT: 0x%08x reached"
" MaxBurstLength: %u, but ISCSI_FLAG_CMD_FINAL is"
" not set, protocol error.", cmd->init_task_tag,
conn->sess->sess_ops->MaxBurstLength);
return DATAOUT_CANNOT_RECOVER;
}
if ((cmd->write_data_done + payload_length) ==
cmd->se_cmd.data_length) {
pr_err("Command ITT: 0x%08x reached"
" last DataOUT PDU in sequence but ISCSI_FLAG_"
"CMD_FINAL is not set, protocol error.\n",
cmd->init_task_tag);
return DATAOUT_CANNOT_RECOVER;
}
} else {
if (next_burst_len == seq->xfer_len) {
pr_err("Command ITT: 0x%08x reached"
" last DataOUT PDU in sequence but ISCSI_FLAG_"
"CMD_FINAL is not set, protocol error.\n",
cmd->init_task_tag);
return DATAOUT_CANNOT_RECOVER;
}
}
}
out:
return DATAOUT_NORMAL;
}
static int iscsit_dataout_check_datasn(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
u32 data_sn = 0;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
/*
* Considering the target has no method of re-requesting DataOUT
* by DataSN, if we receieve a greater DataSN than expected we
* assume the functions for DataPDUInOrder=[Yes,No] below will
* handle it.
*
* If the DataSN is less than expected, dump the payload.
*/
if (conn->sess->sess_ops->DataSequenceInOrder)
data_sn = cmd->data_sn;
else {
struct iscsi_seq *seq = cmd->seq_ptr;
data_sn = seq->data_sn;
}
if (be32_to_cpu(hdr->datasn) > data_sn) {
pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x"
" higher than expected 0x%08x.\n", cmd->init_task_tag,
be32_to_cpu(hdr->datasn), data_sn);
goto recover;
} else if (be32_to_cpu(hdr->datasn) < data_sn) {
pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x"
" lower than expected 0x%08x, discarding payload.\n",
cmd->init_task_tag, be32_to_cpu(hdr->datasn), data_sn);
goto dump;
}
return DATAOUT_NORMAL;
recover:
if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
pr_err("Unable to perform within-command recovery"
" while ERL=0.\n");
return DATAOUT_CANNOT_RECOVER;
}
dump:
if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
return DATAOUT_CANNOT_RECOVER;
return DATAOUT_WITHIN_COMMAND_RECOVERY;
}
static int iscsit_dataout_pre_datapduinorder_yes(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
int dump = 0, recovery = 0;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
/*
* For DataSequenceInOrder=Yes: If the offset is greater than the global
* DataPDUInOrder=Yes offset counter in struct iscsit_cmd a protcol error has
* occurred and fail the connection.
*
* For DataSequenceInOrder=No: If the offset is greater than the per
* sequence DataPDUInOrder=Yes offset counter in struct iscsi_seq a protocol
* error has occurred and fail the connection.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
if (be32_to_cpu(hdr->offset) != cmd->write_data_done) {
pr_err("Command ITT: 0x%08x, received offset"
" %u different than expected %u.\n", cmd->init_task_tag,
be32_to_cpu(hdr->offset), cmd->write_data_done);
recovery = 1;
goto recover;
}
} else {
struct iscsi_seq *seq = cmd->seq_ptr;
if (be32_to_cpu(hdr->offset) > seq->offset) {
pr_err("Command ITT: 0x%08x, received offset"
" %u greater than expected %u.\n", cmd->init_task_tag,
be32_to_cpu(hdr->offset), seq->offset);
recovery = 1;
goto recover;
} else if (be32_to_cpu(hdr->offset) < seq->offset) {
pr_err("Command ITT: 0x%08x, received offset"
" %u less than expected %u, discarding payload.\n",
cmd->init_task_tag, be32_to_cpu(hdr->offset),
seq->offset);
dump = 1;
goto dump;
}
}
return DATAOUT_NORMAL;
recover:
if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
pr_err("Unable to perform within-command recovery"
" while ERL=0.\n");
return DATAOUT_CANNOT_RECOVER;
}
dump:
if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
return DATAOUT_CANNOT_RECOVER;
return (recovery) ? iscsit_recover_dataout_sequence(cmd,
be32_to_cpu(hdr->offset), payload_length) :
(dump) ? DATAOUT_WITHIN_COMMAND_RECOVERY : DATAOUT_NORMAL;
}
static int iscsit_dataout_pre_datapduinorder_no(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
struct iscsi_pdu *pdu;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
pdu = iscsit_get_pdu_holder(cmd, be32_to_cpu(hdr->offset),
payload_length);
if (!pdu)
return DATAOUT_CANNOT_RECOVER;
cmd->pdu_ptr = pdu;
switch (pdu->status) {
case ISCSI_PDU_NOT_RECEIVED:
case ISCSI_PDU_CRC_FAILED:
case ISCSI_PDU_TIMED_OUT:
break;
case ISCSI_PDU_RECEIVED_OK:
pr_err("Command ITT: 0x%08x received already gotten"
" Offset: %u, Length: %u\n", cmd->init_task_tag,
be32_to_cpu(hdr->offset), payload_length);
return iscsit_dump_data_payload(cmd->conn, payload_length, 1);
default:
return DATAOUT_CANNOT_RECOVER;
}
return DATAOUT_NORMAL;
}
static int iscsit_dataout_update_r2t(struct iscsit_cmd *cmd, u32 offset, u32 length)
{
struct iscsi_r2t *r2t;
if (cmd->unsolicited_data)
return 0;
r2t = iscsit_get_r2t_for_eos(cmd, offset, length);
if (!r2t)
return -1;
spin_lock_bh(&cmd->r2t_lock);
r2t->seq_complete = 1;
cmd->outstanding_r2ts--;
spin_unlock_bh(&cmd->r2t_lock);
return 0;
}
static int iscsit_dataout_update_datapduinorder_no(
struct iscsit_cmd *cmd,
u32 data_sn,
int f_bit)
{
int ret = 0;
struct iscsi_pdu *pdu = cmd->pdu_ptr;
pdu->data_sn = data_sn;
switch (pdu->status) {
case ISCSI_PDU_NOT_RECEIVED:
pdu->status = ISCSI_PDU_RECEIVED_OK;
break;
case ISCSI_PDU_CRC_FAILED:
pdu->status = ISCSI_PDU_RECEIVED_OK;
break;
case ISCSI_PDU_TIMED_OUT:
pdu->status = ISCSI_PDU_RECEIVED_OK;
break;
default:
return DATAOUT_CANNOT_RECOVER;
}
if (f_bit) {
ret = iscsit_dataout_datapduinorder_no_fbit(cmd, pdu);
if (ret == DATAOUT_CANNOT_RECOVER)
return ret;
}
return DATAOUT_NORMAL;
}
static int iscsit_dataout_post_crc_passed(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
int ret, send_r2t = 0;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_seq *seq = NULL;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
if (cmd->unsolicited_data) {
if ((cmd->first_burst_len + payload_length) ==
conn->sess->sess_ops->FirstBurstLength) {
if (iscsit_dataout_update_r2t(cmd, be32_to_cpu(hdr->offset),
payload_length) < 0)
return DATAOUT_CANNOT_RECOVER;
send_r2t = 1;
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
ret = iscsit_dataout_update_datapduinorder_no(cmd,
be32_to_cpu(hdr->datasn),
(hdr->flags & ISCSI_FLAG_CMD_FINAL));
if (ret == DATAOUT_CANNOT_RECOVER)
return ret;
}
cmd->first_burst_len += payload_length;
if (conn->sess->sess_ops->DataSequenceInOrder)
cmd->data_sn++;
else {
seq = cmd->seq_ptr;
seq->data_sn++;
seq->offset += payload_length;
}
if (send_r2t) {
if (seq)
seq->status = DATAOUT_SEQUENCE_COMPLETE;
cmd->first_burst_len = 0;
cmd->unsolicited_data = 0;
}
} else {
if (conn->sess->sess_ops->DataSequenceInOrder) {
if ((cmd->next_burst_len + payload_length) ==
conn->sess->sess_ops->MaxBurstLength) {
if (iscsit_dataout_update_r2t(cmd,
be32_to_cpu(hdr->offset),
payload_length) < 0)
return DATAOUT_CANNOT_RECOVER;
send_r2t = 1;
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
ret = iscsit_dataout_update_datapduinorder_no(
cmd, be32_to_cpu(hdr->datasn),
(hdr->flags & ISCSI_FLAG_CMD_FINAL));
if (ret == DATAOUT_CANNOT_RECOVER)
return ret;
}
cmd->next_burst_len += payload_length;
cmd->data_sn++;
if (send_r2t)
cmd->next_burst_len = 0;
} else {
seq = cmd->seq_ptr;
if ((seq->next_burst_len + payload_length) ==
seq->xfer_len) {
if (iscsit_dataout_update_r2t(cmd,
be32_to_cpu(hdr->offset),
payload_length) < 0)
return DATAOUT_CANNOT_RECOVER;
send_r2t = 1;
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
ret = iscsit_dataout_update_datapduinorder_no(
cmd, be32_to_cpu(hdr->datasn),
(hdr->flags & ISCSI_FLAG_CMD_FINAL));
if (ret == DATAOUT_CANNOT_RECOVER)
return ret;
}
seq->data_sn++;
seq->offset += payload_length;
seq->next_burst_len += payload_length;
if (send_r2t) {
seq->next_burst_len = 0;
seq->status = DATAOUT_SEQUENCE_COMPLETE;
}
}
}
if (send_r2t && conn->sess->sess_ops->DataSequenceInOrder)
cmd->data_sn = 0;
cmd->write_data_done += payload_length;
if (cmd->write_data_done == cmd->se_cmd.data_length)
return DATAOUT_SEND_TO_TRANSPORT;
else if (send_r2t)
return DATAOUT_SEND_R2T;
else
return DATAOUT_NORMAL;
}
static int iscsit_dataout_post_crc_failed(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
struct iscsit_conn *conn = cmd->conn;
struct iscsi_pdu *pdu;
struct iscsi_data *hdr = (struct iscsi_data *) buf;
u32 payload_length = ntoh24(hdr->dlength);
if (conn->sess->sess_ops->DataPDUInOrder)
goto recover;
/*
* The rest of this function is only called when DataPDUInOrder=No.
*/
pdu = cmd->pdu_ptr;
switch (pdu->status) {
case ISCSI_PDU_NOT_RECEIVED:
pdu->status = ISCSI_PDU_CRC_FAILED;
break;
case ISCSI_PDU_CRC_FAILED:
break;
case ISCSI_PDU_TIMED_OUT:
pdu->status = ISCSI_PDU_CRC_FAILED;
break;
default:
return DATAOUT_CANNOT_RECOVER;
}
recover:
return iscsit_recover_dataout_sequence(cmd, be32_to_cpu(hdr->offset),
payload_length);
}
/*
* Called from iscsit_handle_data_out() before DataOUT Payload is received
* and CRC computed.
*/
int iscsit_check_pre_dataout(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
int ret;
struct iscsit_conn *conn = cmd->conn;
ret = iscsit_dataout_within_command_recovery_check(cmd, buf);
if ((ret == DATAOUT_WITHIN_COMMAND_RECOVERY) ||
(ret == DATAOUT_CANNOT_RECOVER))
return ret;
ret = iscsit_dataout_check_datasn(cmd, buf);
if ((ret == DATAOUT_WITHIN_COMMAND_RECOVERY) ||
(ret == DATAOUT_CANNOT_RECOVER))
return ret;
if (cmd->unsolicited_data) {
ret = iscsit_dataout_check_unsolicited_sequence(cmd, buf);
if ((ret == DATAOUT_WITHIN_COMMAND_RECOVERY) ||
(ret == DATAOUT_CANNOT_RECOVER))
return ret;
} else {
ret = iscsit_dataout_check_sequence(cmd, buf);
if ((ret == DATAOUT_WITHIN_COMMAND_RECOVERY) ||
(ret == DATAOUT_CANNOT_RECOVER))
return ret;
}
return (conn->sess->sess_ops->DataPDUInOrder) ?
iscsit_dataout_pre_datapduinorder_yes(cmd, buf) :
iscsit_dataout_pre_datapduinorder_no(cmd, buf);
}
/*
* Called from iscsit_handle_data_out() after DataOUT Payload is received
* and CRC computed.
*/
int iscsit_check_post_dataout(
struct iscsit_cmd *cmd,
unsigned char *buf,
u8 data_crc_failed)
{
struct iscsit_conn *conn = cmd->conn;
cmd->dataout_timeout_retries = 0;
if (!data_crc_failed)
return iscsit_dataout_post_crc_passed(cmd, buf);
else {
if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
pr_err("Unable to recover from DataOUT CRC"
" failure while ERL=0, closing session.\n");
iscsit_reject_cmd(cmd, ISCSI_REASON_DATA_DIGEST_ERROR,
buf);
return DATAOUT_CANNOT_RECOVER;
}
iscsit_reject_cmd(cmd, ISCSI_REASON_DATA_DIGEST_ERROR, buf);
return iscsit_dataout_post_crc_failed(cmd, buf);
}
}
void iscsit_handle_time2retain_timeout(struct timer_list *t)
{
struct iscsit_session *sess = timer_container_of(sess, t,
time2retain_timer);
struct iscsi_portal_group *tpg = sess->tpg;
struct se_portal_group *se_tpg = &tpg->tpg_se_tpg;
spin_lock_bh(&se_tpg->session_lock);
if (sess->time2retain_timer_flags & ISCSI_TF_STOP) {
spin_unlock_bh(&se_tpg->session_lock);
return;
}
if (atomic_read(&sess->session_reinstatement)) {
pr_err("Exiting Time2Retain handler because"
" session_reinstatement=1\n");
spin_unlock_bh(&se_tpg->session_lock);
return;
}
sess->time2retain_timer_flags |= ISCSI_TF_EXPIRED;
pr_err("Time2Retain timer expired for SID: %u, cleaning up"
" iSCSI session.\n", sess->sid);
iscsit_fill_cxn_timeout_err_stats(sess);
spin_unlock_bh(&se_tpg->session_lock);
iscsit_close_session(sess, false);
}
void iscsit_start_time2retain_handler(struct iscsit_session *sess)
{
int tpg_active;
/*
* Only start Time2Retain timer when the associated TPG is still in
* an ACTIVE (eg: not disabled or shutdown) state.
*/
spin_lock(&sess->tpg->tpg_state_lock);
tpg_active = (sess->tpg->tpg_state == TPG_STATE_ACTIVE);
spin_unlock(&sess->tpg->tpg_state_lock);
if (!tpg_active)
return;
if (sess->time2retain_timer_flags & ISCSI_TF_RUNNING)
return;
pr_debug("Starting Time2Retain timer for %u seconds on"
" SID: %u\n", sess->sess_ops->DefaultTime2Retain, sess->sid);
sess->time2retain_timer_flags &= ~ISCSI_TF_STOP;
sess->time2retain_timer_flags |= ISCSI_TF_RUNNING;
mod_timer(&sess->time2retain_timer,
jiffies + sess->sess_ops->DefaultTime2Retain * HZ);
}
int iscsit_stop_time2retain_timer(struct iscsit_session *sess)
{
struct iscsi_portal_group *tpg = sess->tpg;
struct se_portal_group *se_tpg = &tpg->tpg_se_tpg;
lockdep_assert_held(&se_tpg->session_lock);
if (sess->time2retain_timer_flags & ISCSI_TF_EXPIRED)
return -1;
if (!(sess->time2retain_timer_flags & ISCSI_TF_RUNNING))
return 0;
sess->time2retain_timer_flags |= ISCSI_TF_STOP;
spin_unlock(&se_tpg->session_lock);
timer_delete_sync(&sess->time2retain_timer);
spin_lock(&se_tpg->session_lock);
sess->time2retain_timer_flags &= ~ISCSI_TF_RUNNING;
pr_debug("Stopped Time2Retain Timer for SID: %u\n",
sess->sid);
return 0;
}
void iscsit_connection_reinstatement_rcfr(struct iscsit_conn *conn)
{
spin_lock_bh(&conn->state_lock);
if (atomic_read(&conn->connection_exit)) {
spin_unlock_bh(&conn->state_lock);
goto sleep;
}
if (atomic_read(&conn->transport_failed)) {
spin_unlock_bh(&conn->state_lock);
goto sleep;
}
spin_unlock_bh(&conn->state_lock);
if (conn->tx_thread && conn->tx_thread_active)
send_sig(SIGINT, conn->tx_thread, 1);
if (conn->rx_thread && conn->rx_thread_active)
send_sig(SIGINT, conn->rx_thread, 1);
sleep:
wait_for_completion(&conn->conn_wait_rcfr_comp);
complete(&conn->conn_post_wait_comp);
}
void iscsit_cause_connection_reinstatement(struct iscsit_conn *conn, int sleep)
{
spin_lock_bh(&conn->state_lock);
if (atomic_read(&conn->connection_exit)) {
spin_unlock_bh(&conn->state_lock);
return;
}
if (atomic_read(&conn->transport_failed)) {
spin_unlock_bh(&conn->state_lock);
return;
}
if (atomic_read(&conn->connection_reinstatement)) {
spin_unlock_bh(&conn->state_lock);
return;
}
if (conn->tx_thread && conn->tx_thread_active)
send_sig(SIGINT, conn->tx_thread, 1);
if (conn->rx_thread && conn->rx_thread_active)
send_sig(SIGINT, conn->rx_thread, 1);
atomic_set(&conn->connection_reinstatement, 1);
if (!sleep) {
spin_unlock_bh(&conn->state_lock);
return;
}
atomic_set(&conn->sleep_on_conn_wait_comp, 1);
spin_unlock_bh(&conn->state_lock);
wait_for_completion(&conn->conn_wait_comp);
complete(&conn->conn_post_wait_comp);
}
EXPORT_SYMBOL(iscsit_cause_connection_reinstatement);
void iscsit_fall_back_to_erl0(struct iscsit_session *sess)
{
pr_debug("Falling back to ErrorRecoveryLevel=0 for SID:"
" %u\n", sess->sid);
atomic_set(&sess->session_fall_back_to_erl0, 1);
}
static void iscsit_handle_connection_cleanup(struct iscsit_conn *conn)
{
struct iscsit_session *sess = conn->sess;
if ((sess->sess_ops->ErrorRecoveryLevel == 2) &&
!atomic_read(&sess->session_reinstatement) &&
!atomic_read(&sess->session_fall_back_to_erl0))
iscsit_connection_recovery_transport_reset(conn);
else {
pr_debug("Performing cleanup for failed iSCSI"
" Connection ID: %hu from %s\n", conn->cid,
sess->sess_ops->InitiatorName);
iscsit_close_connection(conn);
}
}
void iscsit_take_action_for_connection_exit(struct iscsit_conn *conn, bool *conn_freed)
{
*conn_freed = false;
spin_lock_bh(&conn->state_lock);
if (atomic_read(&conn->connection_exit)) {
spin_unlock_bh(&conn->state_lock);
return;
}
atomic_set(&conn->connection_exit, 1);
if (conn->conn_state == TARG_CONN_STATE_IN_LOGOUT) {
spin_unlock_bh(&conn->state_lock);
iscsit_close_connection(conn);
*conn_freed = true;
return;
}
if (conn->conn_state == TARG_CONN_STATE_CLEANUP_WAIT) {
spin_unlock_bh(&conn->state_lock);
return;
}
pr_debug("Moving to TARG_CONN_STATE_CLEANUP_WAIT.\n");
conn->conn_state = TARG_CONN_STATE_CLEANUP_WAIT;
spin_unlock_bh(&conn->state_lock);
iscsit_handle_connection_cleanup(conn);
*conn_freed = true;
}
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_ERL0_H
#define ISCSI_TARGET_ERL0_H
#include <linux/types.h>
struct iscsit_cmd;
struct iscsit_conn;
struct iscsit_session;
extern void iscsit_set_dataout_sequence_values(struct iscsit_cmd *);
extern int iscsit_check_pre_dataout(struct iscsit_cmd *, unsigned char *);
extern int iscsit_check_post_dataout(struct iscsit_cmd *, unsigned char *, u8);
extern void iscsit_start_time2retain_handler(struct iscsit_session *);
extern void iscsit_handle_time2retain_timeout(struct timer_list *t);
extern int iscsit_stop_time2retain_timer(struct iscsit_session *);
extern void iscsit_connection_reinstatement_rcfr(struct iscsit_conn *);
extern void iscsit_cause_connection_reinstatement(struct iscsit_conn *, int);
extern void iscsit_fall_back_to_erl0(struct iscsit_session *);
extern void iscsit_take_action_for_connection_exit(struct iscsit_conn *, bool *);
#endif /*** ISCSI_TARGET_ERL0_H ***/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_ERL1_H
#define ISCSI_TARGET_ERL1_H
#include <linux/types.h>
#include <scsi/iscsi_proto.h> /* itt_t */
struct iscsit_cmd;
struct iscsit_conn;
struct iscsi_datain_req;
struct iscsi_ooo_cmdsn;
struct iscsi_pdu;
struct iscsit_session;
extern int iscsit_dump_data_payload(struct iscsit_conn *, u32, int);
extern int iscsit_create_recovery_datain_values_datasequenceinorder_yes(
struct iscsit_cmd *, struct iscsi_datain_req *);
extern int iscsit_create_recovery_datain_values_datasequenceinorder_no(
struct iscsit_cmd *, struct iscsi_datain_req *);
extern int iscsit_handle_recovery_datain_or_r2t(struct iscsit_conn *, unsigned char *,
itt_t, u32, u32, u32);
extern int iscsit_handle_status_snack(struct iscsit_conn *, itt_t, u32,
u32, u32);
extern int iscsit_handle_data_ack(struct iscsit_conn *, u32, u32, u32);
extern int iscsit_dataout_datapduinorder_no_fbit(struct iscsit_cmd *, struct iscsi_pdu *);
extern int iscsit_recover_dataout_sequence(struct iscsit_cmd *, u32, u32);
extern void iscsit_clear_ooo_cmdsns_for_conn(struct iscsit_conn *);
extern void iscsit_free_all_ooo_cmdsns(struct iscsit_session *);
extern int iscsit_execute_ooo_cmdsns(struct iscsit_session *);
extern int iscsit_execute_cmd(struct iscsit_cmd *, int);
extern int iscsit_handle_ooo_cmdsn(struct iscsit_session *, struct iscsit_cmd *, u32);
extern void iscsit_remove_ooo_cmdsn(struct iscsit_session *, struct iscsi_ooo_cmdsn *);
extern void iscsit_handle_dataout_timeout(struct timer_list *t);
extern void iscsit_mod_dataout_timer(struct iscsit_cmd *);
extern void iscsit_start_dataout_timer(struct iscsit_cmd *, struct iscsit_conn *);
extern void iscsit_stop_dataout_timer(struct iscsit_cmd *);
#endif /* ISCSI_TARGET_ERL1_H */
@@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* This file contains error recovery level two functions used by
* the iSCSI Target driver.
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <linux/slab.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_fabric.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_datain_values.h"
#include "iscsi_target_util.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target.h"
/*
* FIXME: Does RData SNACK apply here as well?
*/
static int iscsit_attach_active_connection_recovery_entry(
struct iscsit_session *sess,
struct iscsi_conn_recovery *cr)
{
spin_lock(&sess->cr_a_lock);
list_add_tail(&cr->cr_list, &sess->cr_active_list);
spin_unlock(&sess->cr_a_lock);
return 0;
}
static int iscsit_attach_inactive_connection_recovery_entry(
struct iscsit_session *sess,
struct iscsi_conn_recovery *cr)
{
spin_lock(&sess->cr_i_lock);
list_add_tail(&cr->cr_list, &sess->cr_inactive_list);
sess->conn_recovery_count++;
pr_debug("Incremented connection recovery count to %u for"
" SID: %u\n", sess->conn_recovery_count, sess->sid);
spin_unlock(&sess->cr_i_lock);
return 0;
}
struct iscsi_conn_recovery *iscsit_get_inactive_connection_recovery_entry(
struct iscsit_session *sess,
u16 cid)
{
struct iscsi_conn_recovery *cr;
spin_lock(&sess->cr_i_lock);
list_for_each_entry(cr, &sess->cr_inactive_list, cr_list) {
if (cr->cid == cid) {
spin_unlock(&sess->cr_i_lock);
return cr;
}
}
spin_unlock(&sess->cr_i_lock);
return NULL;
}
void iscsit_free_connection_recovery_entries(struct iscsit_session *sess)
{
struct iscsit_cmd *cmd, *cmd_tmp;
struct iscsi_conn_recovery *cr, *cr_tmp;
spin_lock(&sess->cr_a_lock);
list_for_each_entry_safe(cr, cr_tmp, &sess->cr_active_list, cr_list) {
list_del(&cr->cr_list);
spin_unlock(&sess->cr_a_lock);
spin_lock(&cr->conn_recovery_cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp,
&cr->conn_recovery_cmd_list, i_conn_node) {
list_del_init(&cmd->i_conn_node);
cmd->conn = NULL;
spin_unlock(&cr->conn_recovery_cmd_lock);
iscsit_free_cmd(cmd, true);
spin_lock(&cr->conn_recovery_cmd_lock);
}
spin_unlock(&cr->conn_recovery_cmd_lock);
spin_lock(&sess->cr_a_lock);
kfree(cr);
}
spin_unlock(&sess->cr_a_lock);
spin_lock(&sess->cr_i_lock);
list_for_each_entry_safe(cr, cr_tmp, &sess->cr_inactive_list, cr_list) {
list_del(&cr->cr_list);
spin_unlock(&sess->cr_i_lock);
spin_lock(&cr->conn_recovery_cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp,
&cr->conn_recovery_cmd_list, i_conn_node) {
list_del_init(&cmd->i_conn_node);
cmd->conn = NULL;
spin_unlock(&cr->conn_recovery_cmd_lock);
iscsit_free_cmd(cmd, true);
spin_lock(&cr->conn_recovery_cmd_lock);
}
spin_unlock(&cr->conn_recovery_cmd_lock);
spin_lock(&sess->cr_i_lock);
kfree(cr);
}
spin_unlock(&sess->cr_i_lock);
}
int iscsit_remove_active_connection_recovery_entry(
struct iscsi_conn_recovery *cr,
struct iscsit_session *sess)
{
spin_lock(&sess->cr_a_lock);
list_del(&cr->cr_list);
sess->conn_recovery_count--;
pr_debug("Decremented connection recovery count to %u for"
" SID: %u\n", sess->conn_recovery_count, sess->sid);
spin_unlock(&sess->cr_a_lock);
kfree(cr);
return 0;
}
static void iscsit_remove_inactive_connection_recovery_entry(
struct iscsi_conn_recovery *cr,
struct iscsit_session *sess)
{
spin_lock(&sess->cr_i_lock);
list_del(&cr->cr_list);
spin_unlock(&sess->cr_i_lock);
}
/*
* Called with cr->conn_recovery_cmd_lock help.
*/
int iscsit_remove_cmd_from_connection_recovery(
struct iscsit_cmd *cmd,
struct iscsit_session *sess)
{
struct iscsi_conn_recovery *cr;
if (!cmd->cr) {
pr_err("struct iscsi_conn_recovery pointer for ITT: 0x%08x"
" is NULL!\n", cmd->init_task_tag);
BUG();
}
cr = cmd->cr;
list_del_init(&cmd->i_conn_node);
return --cr->cmd_count;
}
void iscsit_discard_cr_cmds_by_expstatsn(
struct iscsi_conn_recovery *cr,
u32 exp_statsn)
{
u32 dropped_count = 0;
struct iscsit_cmd *cmd, *cmd_tmp;
struct iscsit_session *sess = cr->sess;
spin_lock(&cr->conn_recovery_cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp,
&cr->conn_recovery_cmd_list, i_conn_node) {
if (((cmd->deferred_i_state != ISTATE_SENT_STATUS) &&
(cmd->deferred_i_state != ISTATE_REMOVE)) ||
(cmd->stat_sn >= exp_statsn)) {
continue;
}
dropped_count++;
pr_debug("Dropping Acknowledged ITT: 0x%08x, StatSN:"
" 0x%08x, CID: %hu.\n", cmd->init_task_tag,
cmd->stat_sn, cr->cid);
iscsit_remove_cmd_from_connection_recovery(cmd, sess);
spin_unlock(&cr->conn_recovery_cmd_lock);
iscsit_free_cmd(cmd, true);
spin_lock(&cr->conn_recovery_cmd_lock);
}
spin_unlock(&cr->conn_recovery_cmd_lock);
pr_debug("Dropped %u total acknowledged commands on"
" CID: %hu less than old ExpStatSN: 0x%08x\n",
dropped_count, cr->cid, exp_statsn);
if (!cr->cmd_count) {
pr_debug("No commands to be reassigned for failed"
" connection CID: %hu on SID: %u\n",
cr->cid, sess->sid);
iscsit_remove_inactive_connection_recovery_entry(cr, sess);
iscsit_attach_active_connection_recovery_entry(sess, cr);
pr_debug("iSCSI connection recovery successful for CID:"
" %hu on SID: %u\n", cr->cid, sess->sid);
iscsit_remove_active_connection_recovery_entry(cr, sess);
} else {
iscsit_remove_inactive_connection_recovery_entry(cr, sess);
iscsit_attach_active_connection_recovery_entry(sess, cr);
}
}
int iscsit_discard_unacknowledged_ooo_cmdsns_for_conn(struct iscsit_conn *conn)
{
u32 dropped_count = 0;
struct iscsit_cmd *cmd, *cmd_tmp;
struct iscsi_ooo_cmdsn *ooo_cmdsn, *ooo_cmdsn_tmp;
struct iscsit_session *sess = conn->sess;
mutex_lock(&sess->cmdsn_mutex);
list_for_each_entry_safe(ooo_cmdsn, ooo_cmdsn_tmp,
&sess->sess_ooo_cmdsn_list, ooo_list) {
if (ooo_cmdsn->cid != conn->cid)
continue;
dropped_count++;
pr_debug("Dropping unacknowledged CmdSN:"
" 0x%08x during connection recovery on CID: %hu\n",
ooo_cmdsn->cmdsn, conn->cid);
iscsit_remove_ooo_cmdsn(sess, ooo_cmdsn);
}
mutex_unlock(&sess->cmdsn_mutex);
spin_lock_bh(&conn->cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp, &conn->conn_cmd_list, i_conn_node) {
if (!(cmd->cmd_flags & ICF_OOO_CMDSN))
continue;
list_del_init(&cmd->i_conn_node);
spin_unlock_bh(&conn->cmd_lock);
iscsit_free_cmd(cmd, true);
spin_lock_bh(&conn->cmd_lock);
}
spin_unlock_bh(&conn->cmd_lock);
pr_debug("Dropped %u total unacknowledged commands on CID:"
" %hu for ExpCmdSN: 0x%08x.\n", dropped_count, conn->cid,
sess->exp_cmd_sn);
return 0;
}
int iscsit_prepare_cmds_for_reallegiance(struct iscsit_conn *conn)
{
u32 cmd_count = 0;
struct iscsit_cmd *cmd, *cmd_tmp;
struct iscsi_conn_recovery *cr;
/*
* Allocate an struct iscsi_conn_recovery for this connection.
* Each struct iscsit_cmd contains an struct iscsi_conn_recovery pointer
* (struct iscsit_cmd->cr) so we need to allocate this before preparing the
* connection's command list for connection recovery.
*/
cr = kzalloc_obj(struct iscsi_conn_recovery);
if (!cr) {
pr_err("Unable to allocate memory for"
" struct iscsi_conn_recovery.\n");
return -1;
}
INIT_LIST_HEAD(&cr->cr_list);
INIT_LIST_HEAD(&cr->conn_recovery_cmd_list);
spin_lock_init(&cr->conn_recovery_cmd_lock);
/*
* Only perform connection recovery on ISCSI_OP_SCSI_CMD or
* ISCSI_OP_NOOP_OUT opcodes. For all other opcodes call
* list_del_init(&cmd->i_conn_node); to release the command to the
* session pool and remove it from the connection's list.
*
* Also stop the DataOUT timer, which will be restarted after
* sending the TMR response.
*/
spin_lock_bh(&conn->cmd_lock);
list_for_each_entry_safe(cmd, cmd_tmp, &conn->conn_cmd_list, i_conn_node) {
if ((cmd->iscsi_opcode != ISCSI_OP_SCSI_CMD) &&
(cmd->iscsi_opcode != ISCSI_OP_NOOP_OUT)) {
pr_debug("Not performing reallegiance on"
" Opcode: 0x%02x, ITT: 0x%08x, CmdSN: 0x%08x,"
" CID: %hu\n", cmd->iscsi_opcode,
cmd->init_task_tag, cmd->cmd_sn, conn->cid);
list_del_init(&cmd->i_conn_node);
spin_unlock_bh(&conn->cmd_lock);
iscsit_free_cmd(cmd, true);
spin_lock_bh(&conn->cmd_lock);
continue;
}
/*
* Special case where commands greater than or equal to
* the session's ExpCmdSN are attached to the connection
* list but not to the out of order CmdSN list. The one
* obvious case is when a command with immediate data
* attached must only check the CmdSN against ExpCmdSN
* after the data is received. The special case below
* is when the connection fails before data is received,
* but also may apply to other PDUs, so it has been
* made generic here.
*/
if (!(cmd->cmd_flags & ICF_OOO_CMDSN) && !cmd->immediate_cmd &&
iscsi_sna_gte(cmd->cmd_sn, conn->sess->exp_cmd_sn)) {
list_del_init(&cmd->i_conn_node);
spin_unlock_bh(&conn->cmd_lock);
iscsit_free_cmd(cmd, true);
spin_lock_bh(&conn->cmd_lock);
continue;
}
cmd_count++;
pr_debug("Preparing Opcode: 0x%02x, ITT: 0x%08x,"
" CmdSN: 0x%08x, StatSN: 0x%08x, CID: %hu for"
" reallegiance.\n", cmd->iscsi_opcode,
cmd->init_task_tag, cmd->cmd_sn, cmd->stat_sn,
conn->cid);
cmd->deferred_i_state = cmd->i_state;
cmd->i_state = ISTATE_IN_CONNECTION_RECOVERY;
if (cmd->data_direction == DMA_TO_DEVICE)
iscsit_stop_dataout_timer(cmd);
cmd->sess = conn->sess;
list_del_init(&cmd->i_conn_node);
spin_unlock_bh(&conn->cmd_lock);
iscsit_free_all_datain_reqs(cmd);
transport_wait_for_tasks(&cmd->se_cmd);
/*
* Add the struct iscsit_cmd to the connection recovery cmd list
*/
spin_lock(&cr->conn_recovery_cmd_lock);
list_add_tail(&cmd->i_conn_node, &cr->conn_recovery_cmd_list);
spin_unlock(&cr->conn_recovery_cmd_lock);
spin_lock_bh(&conn->cmd_lock);
cmd->cr = cr;
cmd->conn = NULL;
}
spin_unlock_bh(&conn->cmd_lock);
/*
* Fill in the various values in the preallocated struct iscsi_conn_recovery.
*/
cr->cid = conn->cid;
cr->cmd_count = cmd_count;
cr->maxrecvdatasegmentlength = conn->conn_ops->MaxRecvDataSegmentLength;
cr->maxxmitdatasegmentlength = conn->conn_ops->MaxXmitDataSegmentLength;
cr->sess = conn->sess;
iscsit_attach_inactive_connection_recovery_entry(conn->sess, cr);
return 0;
}
int iscsit_connection_recovery_transport_reset(struct iscsit_conn *conn)
{
atomic_set(&conn->connection_recovery, 1);
if (iscsit_close_connection(conn) < 0)
return -1;
return 0;
}
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_ERL2_H
#define ISCSI_TARGET_ERL2_H
#include <linux/types.h>
struct iscsit_cmd;
struct iscsit_conn;
struct iscsi_conn_recovery;
struct iscsit_session;
extern struct iscsi_conn_recovery *iscsit_get_inactive_connection_recovery_entry(
struct iscsit_session *, u16);
extern void iscsit_free_connection_recovery_entries(struct iscsit_session *);
extern int iscsit_remove_active_connection_recovery_entry(
struct iscsi_conn_recovery *, struct iscsit_session *);
extern int iscsit_remove_cmd_from_connection_recovery(struct iscsit_cmd *,
struct iscsit_session *);
extern void iscsit_discard_cr_cmds_by_expstatsn(struct iscsi_conn_recovery *, u32);
extern int iscsit_discard_unacknowledged_ooo_cmdsns_for_conn(struct iscsit_conn *);
extern int iscsit_prepare_cmds_for_reallegiance(struct iscsit_conn *);
extern int iscsit_connection_recovery_transport_reset(struct iscsit_conn *);
#endif /*** ISCSI_TARGET_ERL2_H ***/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_LOGIN_H
#define ISCSI_TARGET_LOGIN_H
#include <linux/types.h>
struct iscsit_conn;
struct iscsi_login;
struct iscsi_np;
struct sockaddr_storage;
extern int iscsi_check_for_session_reinstatement(struct iscsit_conn *);
extern int iscsi_login_post_auth_non_zero_tsih(struct iscsit_conn *, u16, u32);
extern int iscsit_setup_np(struct iscsi_np *,
struct sockaddr_storage *);
extern int iscsi_target_setup_login_socket(struct iscsi_np *,
struct sockaddr_storage *);
extern int iscsit_accept_np(struct iscsi_np *, struct iscsit_conn *);
extern int iscsit_get_login_rx(struct iscsit_conn *, struct iscsi_login *);
extern int iscsit_put_login_tx(struct iscsit_conn *, struct iscsi_login *, u32);
extern void iscsit_free_conn(struct iscsit_conn *);
extern int iscsit_start_kthreads(struct iscsit_conn *);
extern void iscsi_post_login_handler(struct iscsi_np *, struct iscsit_conn *, u8);
extern void iscsi_target_login_sess_out(struct iscsit_conn *, bool, bool);
extern int iscsi_target_login_thread(void *);
#endif /*** ISCSI_TARGET_LOGIN_H ***/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_NEGO_H
#define ISCSI_TARGET_NEGO_H
#define DECIMAL 0
#define HEX 1
#define BASE64 2
struct iscsit_conn;
struct iscsi_login;
struct iscsi_np;
extern void convert_null_to_semi(char *, int);
extern int extract_param(const char *, const char *, unsigned int, char *,
unsigned char *);
extern int iscsi_target_check_login_request(struct iscsit_conn *,
struct iscsi_login *);
extern int iscsi_target_locate_portal(struct iscsi_np *, struct iscsit_conn *,
struct iscsi_login *);
extern int iscsi_target_start_negotiation(
struct iscsi_login *, struct iscsit_conn *);
extern void iscsi_target_nego_release(struct iscsit_conn *);
extern bool iscsi_conn_auth_required(struct iscsit_conn *conn);
#endif /* ISCSI_TARGET_NEGO_H */
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* This file contains the main functions related to Initiator Node Attributes.
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <target/target_core_base.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_device.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target_nodeattrib.h"
static inline char *iscsit_na_get_initiatorname(
struct iscsi_node_acl *nacl)
{
struct se_node_acl *se_nacl = &nacl->se_node_acl;
return &se_nacl->initiatorname[0];
}
void iscsit_set_default_node_attribues(
struct iscsi_node_acl *acl,
struct iscsi_portal_group *tpg)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
a->authentication = NA_AUTHENTICATION_INHERITED;
a->dataout_timeout = NA_DATAOUT_TIMEOUT;
a->dataout_timeout_retries = NA_DATAOUT_TIMEOUT_RETRIES;
a->nopin_timeout = NA_NOPIN_TIMEOUT;
a->nopin_response_timeout = NA_NOPIN_RESPONSE_TIMEOUT;
a->random_datain_pdu_offsets = NA_RANDOM_DATAIN_PDU_OFFSETS;
a->random_datain_seq_offsets = NA_RANDOM_DATAIN_SEQ_OFFSETS;
a->random_r2t_offsets = NA_RANDOM_R2T_OFFSETS;
a->default_erl = tpg->tpg_attrib.default_erl;
}
int iscsit_na_dataout_timeout(
struct iscsi_node_acl *acl,
u32 dataout_timeout)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (dataout_timeout > NA_DATAOUT_TIMEOUT_MAX) {
pr_err("Requested DataOut Timeout %u larger than"
" maximum %u\n", dataout_timeout,
NA_DATAOUT_TIMEOUT_MAX);
return -EINVAL;
} else if (dataout_timeout < NA_DATAOUT_TIMEOUT_MIX) {
pr_err("Requested DataOut Timeout %u smaller than"
" minimum %u\n", dataout_timeout,
NA_DATAOUT_TIMEOUT_MIX);
return -EINVAL;
}
a->dataout_timeout = dataout_timeout;
pr_debug("Set DataOut Timeout to %u for Initiator Node"
" %s\n", a->dataout_timeout, iscsit_na_get_initiatorname(acl));
return 0;
}
int iscsit_na_dataout_timeout_retries(
struct iscsi_node_acl *acl,
u32 dataout_timeout_retries)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (dataout_timeout_retries > NA_DATAOUT_TIMEOUT_RETRIES_MAX) {
pr_err("Requested DataOut Timeout Retries %u larger"
" than maximum %u", dataout_timeout_retries,
NA_DATAOUT_TIMEOUT_RETRIES_MAX);
return -EINVAL;
} else if (dataout_timeout_retries < NA_DATAOUT_TIMEOUT_RETRIES_MIN) {
pr_err("Requested DataOut Timeout Retries %u smaller"
" than minimum %u", dataout_timeout_retries,
NA_DATAOUT_TIMEOUT_RETRIES_MIN);
return -EINVAL;
}
a->dataout_timeout_retries = dataout_timeout_retries;
pr_debug("Set DataOut Timeout Retries to %u for"
" Initiator Node %s\n", a->dataout_timeout_retries,
iscsit_na_get_initiatorname(acl));
return 0;
}
int iscsit_na_nopin_timeout(
struct iscsi_node_acl *acl,
u32 nopin_timeout)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
struct iscsit_session *sess;
struct iscsit_conn *conn;
struct se_node_acl *se_nacl = &a->nacl->se_node_acl;
struct se_session *se_sess;
u32 orig_nopin_timeout = a->nopin_timeout;
if (nopin_timeout > NA_NOPIN_TIMEOUT_MAX) {
pr_err("Requested NopIn Timeout %u larger than maximum"
" %u\n", nopin_timeout, NA_NOPIN_TIMEOUT_MAX);
return -EINVAL;
} else if ((nopin_timeout < NA_NOPIN_TIMEOUT_MIN) &&
(nopin_timeout != 0)) {
pr_err("Requested NopIn Timeout %u smaller than"
" minimum %u and not 0\n", nopin_timeout,
NA_NOPIN_TIMEOUT_MIN);
return -EINVAL;
}
a->nopin_timeout = nopin_timeout;
pr_debug("Set NopIn Timeout to %u for Initiator"
" Node %s\n", a->nopin_timeout,
iscsit_na_get_initiatorname(acl));
/*
* Reenable disabled nopin_timeout timer for all iSCSI connections.
*/
if (!orig_nopin_timeout) {
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
spin_lock(&sess->conn_lock);
list_for_each_entry(conn, &sess->sess_conn_list,
conn_list) {
if (conn->conn_state !=
TARG_CONN_STATE_LOGGED_IN)
continue;
spin_lock(&conn->nopin_timer_lock);
__iscsit_start_nopin_timer(conn);
spin_unlock(&conn->nopin_timer_lock);
}
spin_unlock(&sess->conn_lock);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
}
return 0;
}
int iscsit_na_nopin_response_timeout(
struct iscsi_node_acl *acl,
u32 nopin_response_timeout)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (nopin_response_timeout > NA_NOPIN_RESPONSE_TIMEOUT_MAX) {
pr_err("Requested NopIn Response Timeout %u larger"
" than maximum %u\n", nopin_response_timeout,
NA_NOPIN_RESPONSE_TIMEOUT_MAX);
return -EINVAL;
} else if (nopin_response_timeout < NA_NOPIN_RESPONSE_TIMEOUT_MIN) {
pr_err("Requested NopIn Response Timeout %u smaller"
" than minimum %u\n", nopin_response_timeout,
NA_NOPIN_RESPONSE_TIMEOUT_MIN);
return -EINVAL;
}
a->nopin_response_timeout = nopin_response_timeout;
pr_debug("Set NopIn Response Timeout to %u for"
" Initiator Node %s\n", a->nopin_timeout,
iscsit_na_get_initiatorname(acl));
return 0;
}
int iscsit_na_random_datain_pdu_offsets(
struct iscsi_node_acl *acl,
u32 random_datain_pdu_offsets)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (random_datain_pdu_offsets != 0 && random_datain_pdu_offsets != 1) {
pr_err("Requested Random DataIN PDU Offsets: %u not"
" 0 or 1\n", random_datain_pdu_offsets);
return -EINVAL;
}
a->random_datain_pdu_offsets = random_datain_pdu_offsets;
pr_debug("Set Random DataIN PDU Offsets to %u for"
" Initiator Node %s\n", a->random_datain_pdu_offsets,
iscsit_na_get_initiatorname(acl));
return 0;
}
int iscsit_na_random_datain_seq_offsets(
struct iscsi_node_acl *acl,
u32 random_datain_seq_offsets)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (random_datain_seq_offsets != 0 && random_datain_seq_offsets != 1) {
pr_err("Requested Random DataIN Sequence Offsets: %u"
" not 0 or 1\n", random_datain_seq_offsets);
return -EINVAL;
}
a->random_datain_seq_offsets = random_datain_seq_offsets;
pr_debug("Set Random DataIN Sequence Offsets to %u for"
" Initiator Node %s\n", a->random_datain_seq_offsets,
iscsit_na_get_initiatorname(acl));
return 0;
}
int iscsit_na_random_r2t_offsets(
struct iscsi_node_acl *acl,
u32 random_r2t_offsets)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (random_r2t_offsets != 0 && random_r2t_offsets != 1) {
pr_err("Requested Random R2T Offsets: %u not"
" 0 or 1\n", random_r2t_offsets);
return -EINVAL;
}
a->random_r2t_offsets = random_r2t_offsets;
pr_debug("Set Random R2T Offsets to %u for"
" Initiator Node %s\n", a->random_r2t_offsets,
iscsit_na_get_initiatorname(acl));
return 0;
}
int iscsit_na_default_erl(
struct iscsi_node_acl *acl,
u32 default_erl)
{
struct iscsi_node_attrib *a = &acl->node_attrib;
if (default_erl != 0 && default_erl != 1 && default_erl != 2) {
pr_err("Requested default ERL: %u not 0, 1, or 2\n",
default_erl);
return -EINVAL;
}
a->default_erl = default_erl;
pr_debug("Set use ERL0 flag to %u for Initiator"
" Node %s\n", a->default_erl,
iscsit_na_get_initiatorname(acl));
return 0;
}
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_NODEATTRIB_H
#define ISCSI_TARGET_NODEATTRIB_H
#include <linux/types.h>
struct iscsi_node_acl;
struct iscsi_portal_group;
extern void iscsit_set_default_node_attribues(struct iscsi_node_acl *,
struct iscsi_portal_group *);
extern int iscsit_na_dataout_timeout(struct iscsi_node_acl *, u32);
extern int iscsit_na_dataout_timeout_retries(struct iscsi_node_acl *, u32);
extern int iscsit_na_nopin_timeout(struct iscsi_node_acl *, u32);
extern int iscsit_na_nopin_response_timeout(struct iscsi_node_acl *, u32);
extern int iscsit_na_random_datain_pdu_offsets(struct iscsi_node_acl *, u32);
extern int iscsit_na_random_datain_seq_offsets(struct iscsi_node_acl *, u32);
extern int iscsit_na_random_r2t_offsets(struct iscsi_node_acl *, u32);
extern int iscsit_na_default_erl(struct iscsi_node_acl *, u32);
#endif /* ISCSI_TARGET_NODEATTRIB_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,290 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_PARAMETERS_H
#define ISCSI_PARAMETERS_H
#include <linux/types.h>
#include <scsi/iscsi_proto.h>
struct iscsi_extra_response {
char key[KEY_MAXLEN];
char value[32];
struct list_head er_list;
} ____cacheline_aligned;
struct iscsi_param {
char *name;
char *value;
u8 set_param;
u8 phase;
u8 scope;
u8 sender;
u8 type;
u8 use;
u16 type_range;
u32 state;
struct list_head p_list;
} ____cacheline_aligned;
struct iscsit_conn;
struct iscsi_conn_ops;
struct iscsi_param_list;
struct iscsi_sess_ops;
extern int iscsi_login_rx_data(struct iscsit_conn *, char *, int);
extern int iscsi_login_tx_data(struct iscsit_conn *, char *, char *, int);
extern int iscsi_create_default_params(struct iscsi_param_list **);
extern int iscsi_set_keys_to_negotiate(struct iscsi_param_list *, bool);
extern int iscsi_set_keys_irrelevant_for_discovery(struct iscsi_param_list *);
extern int iscsi_copy_param_list(struct iscsi_param_list **,
struct iscsi_param_list *, int);
extern int iscsi_change_param_value(char *, struct iscsi_param_list *, int);
extern void iscsi_release_param_list(struct iscsi_param_list *);
extern struct iscsi_param *iscsi_find_param_from_key(char *, struct iscsi_param_list *);
extern int iscsi_extract_key_value(char *, char **, char **);
extern int iscsi_update_param_value(struct iscsi_param *, char *);
extern int iscsi_decode_text_input(u8, u8, char *, u32, struct iscsit_conn *);
extern int iscsi_encode_text_output(u8, u8, char *, u32 *, u32,
struct iscsi_param_list *, bool);
extern int iscsi_check_negotiated_keys(struct iscsi_param_list *);
extern void iscsi_set_connection_parameters(struct iscsi_conn_ops *,
struct iscsi_param_list *);
extern void iscsi_set_session_parameters(struct iscsi_sess_ops *,
struct iscsi_param_list *, int);
#define YES "Yes"
#define NO "No"
#define ALL "All"
#define IRRELEVANT "Irrelevant"
#define NONE "None"
#define NOTUNDERSTOOD "NotUnderstood"
#define REJECT "Reject"
/*
* The Parameter Names.
*/
#define AUTHMETHOD "AuthMethod"
#define HEADERDIGEST "HeaderDigest"
#define DATADIGEST "DataDigest"
#define MAXCONNECTIONS "MaxConnections"
#define SENDTARGETS "SendTargets"
#define TARGETNAME "TargetName"
#define INITIATORNAME "InitiatorName"
#define TARGETALIAS "TargetAlias"
#define INITIATORALIAS "InitiatorAlias"
#define TARGETADDRESS "TargetAddress"
#define TARGETPORTALGROUPTAG "TargetPortalGroupTag"
#define INITIALR2T "InitialR2T"
#define IMMEDIATEDATA "ImmediateData"
#define MAXRECVDATASEGMENTLENGTH "MaxRecvDataSegmentLength"
#define MAXXMITDATASEGMENTLENGTH "MaxXmitDataSegmentLength"
#define MAXBURSTLENGTH "MaxBurstLength"
#define FIRSTBURSTLENGTH "FirstBurstLength"
#define DEFAULTTIME2WAIT "DefaultTime2Wait"
#define DEFAULTTIME2RETAIN "DefaultTime2Retain"
#define MAXOUTSTANDINGR2T "MaxOutstandingR2T"
#define DATAPDUINORDER "DataPDUInOrder"
#define DATASEQUENCEINORDER "DataSequenceInOrder"
#define ERRORRECOVERYLEVEL "ErrorRecoveryLevel"
#define SESSIONTYPE "SessionType"
#define IFMARKER "IFMarker"
#define OFMARKER "OFMarker"
#define IFMARKINT "IFMarkInt"
#define OFMARKINT "OFMarkInt"
/*
* Parameter names of iSCSI Extentions for RDMA (iSER). See RFC-5046
*/
#define RDMAEXTENSIONS "RDMAExtensions"
#define INITIATORRECVDATASEGMENTLENGTH "InitiatorRecvDataSegmentLength"
#define TARGETRECVDATASEGMENTLENGTH "TargetRecvDataSegmentLength"
/*
* For AuthMethod.
*/
#define KRB5 "KRB5"
#define SPKM1 "SPKM1"
#define SPKM2 "SPKM2"
#define SRP "SRP"
#define CHAP "CHAP"
/*
* Initial values for Parameter Negotiation.
*/
#define INITIAL_AUTHMETHOD CHAP
#define INITIAL_HEADERDIGEST "CRC32C,None"
#define INITIAL_DATADIGEST "CRC32C,None"
#define INITIAL_MAXCONNECTIONS "1"
#define INITIAL_SENDTARGETS ALL
#define INITIAL_TARGETNAME "LIO.Target"
#define INITIAL_INITIATORNAME "LIO.Initiator"
#define INITIAL_TARGETALIAS "LIO Target"
#define INITIAL_INITIATORALIAS "LIO Initiator"
#define INITIAL_TARGETADDRESS "0.0.0.0:0000,0"
#define INITIAL_TARGETPORTALGROUPTAG "1"
#define INITIAL_INITIALR2T YES
#define INITIAL_IMMEDIATEDATA YES
#define INITIAL_MAXRECVDATASEGMENTLENGTH "8192"
/*
* Match outgoing MXDSL default to incoming Open-iSCSI default
*/
#define INITIAL_MAXXMITDATASEGMENTLENGTH "262144"
#define INITIAL_MAXBURSTLENGTH "262144"
#define INITIAL_FIRSTBURSTLENGTH "65536"
#define INITIAL_DEFAULTTIME2WAIT "2"
#define INITIAL_DEFAULTTIME2RETAIN "20"
#define INITIAL_MAXOUTSTANDINGR2T "1"
#define INITIAL_DATAPDUINORDER YES
#define INITIAL_DATASEQUENCEINORDER YES
#define INITIAL_ERRORRECOVERYLEVEL "0"
#define INITIAL_SESSIONTYPE NORMAL
#define INITIAL_IFMARKER NO
#define INITIAL_OFMARKER NO
#define INITIAL_IFMARKINT REJECT
#define INITIAL_OFMARKINT REJECT
/*
* Initial values for iSER parameters following RFC-5046 Section 6
*/
#define INITIAL_RDMAEXTENSIONS NO
#define INITIAL_INITIATORRECVDATASEGMENTLENGTH "262144"
#define INITIAL_TARGETRECVDATASEGMENTLENGTH "8192"
/*
* For [Header,Data]Digests.
*/
#define CRC32C "CRC32C"
/*
* For SessionType.
*/
#define DISCOVERY "Discovery"
#define NORMAL "Normal"
/*
* struct iscsi_param->use
*/
#define USE_LEADING_ONLY 0x01
#define USE_INITIAL_ONLY 0x02
#define USE_ALL 0x04
#define IS_USE_LEADING_ONLY(p) ((p)->use & USE_LEADING_ONLY)
#define IS_USE_INITIAL_ONLY(p) ((p)->use & USE_INITIAL_ONLY)
#define IS_USE_ALL(p) ((p)->use & USE_ALL)
#define SET_USE_INITIAL_ONLY(p) ((p)->use |= USE_INITIAL_ONLY)
/*
* struct iscsi_param->sender
*/
#define SENDER_INITIATOR 0x01
#define SENDER_TARGET 0x02
#define SENDER_BOTH 0x03
/* Used in iscsi_check_key() */
#define SENDER_RECEIVER 0x04
#define IS_SENDER_INITIATOR(p) ((p)->sender & SENDER_INITIATOR)
#define IS_SENDER_TARGET(p) ((p)->sender & SENDER_TARGET)
#define IS_SENDER_BOTH(p) ((p)->sender & SENDER_BOTH)
/*
* struct iscsi_param->scope
*/
#define SCOPE_CONNECTION_ONLY 0x01
#define SCOPE_SESSION_WIDE 0x02
#define IS_SCOPE_CONNECTION_ONLY(p) ((p)->scope & SCOPE_CONNECTION_ONLY)
#define IS_SCOPE_SESSION_WIDE(p) ((p)->scope & SCOPE_SESSION_WIDE)
/*
* struct iscsi_param->phase
*/
#define PHASE_SECURITY 0x01
#define PHASE_OPERATIONAL 0x02
#define PHASE_DECLARATIVE 0x04
#define PHASE_FFP0 0x08
#define IS_PHASE_SECURITY(p) ((p)->phase & PHASE_SECURITY)
#define IS_PHASE_OPERATIONAL(p) ((p)->phase & PHASE_OPERATIONAL)
#define IS_PHASE_DECLARATIVE(p) ((p)->phase & PHASE_DECLARATIVE)
#define IS_PHASE_FFP0(p) ((p)->phase & PHASE_FFP0)
/*
* struct iscsi_param->type
*/
#define TYPE_BOOL_AND 0x01
#define TYPE_BOOL_OR 0x02
#define TYPE_NUMBER 0x04
#define TYPE_NUMBER_RANGE 0x08
#define TYPE_STRING 0x10
#define TYPE_VALUE_LIST 0x20
#define IS_TYPE_BOOL_AND(p) ((p)->type & TYPE_BOOL_AND)
#define IS_TYPE_BOOL_OR(p) ((p)->type & TYPE_BOOL_OR)
#define IS_TYPE_NUMBER(p) ((p)->type & TYPE_NUMBER)
#define IS_TYPE_NUMBER_RANGE(p) ((p)->type & TYPE_NUMBER_RANGE)
#define IS_TYPE_STRING(p) ((p)->type & TYPE_STRING)
#define IS_TYPE_VALUE_LIST(p) ((p)->type & TYPE_VALUE_LIST)
/*
* struct iscsi_param->type_range
*/
#define TYPERANGE_BOOL_AND 0x0001
#define TYPERANGE_BOOL_OR 0x0002
#define TYPERANGE_0_TO_2 0x0004
#define TYPERANGE_0_TO_3600 0x0008
#define TYPERANGE_0_TO_32767 0x0010
#define TYPERANGE_0_TO_65535 0x0020
#define TYPERANGE_1_TO_65535 0x0040
#define TYPERANGE_2_TO_3600 0x0080
#define TYPERANGE_512_TO_16777215 0x0100
#define TYPERANGE_AUTH 0x0200
#define TYPERANGE_DIGEST 0x0400
#define TYPERANGE_ISCSINAME 0x0800
#define TYPERANGE_SESSIONTYPE 0x1000
#define TYPERANGE_TARGETADDRESS 0x2000
#define TYPERANGE_UTF8 0x4000
#define IS_TYPERANGE_0_TO_2(p) ((p)->type_range & TYPERANGE_0_TO_2)
#define IS_TYPERANGE_0_TO_3600(p) ((p)->type_range & TYPERANGE_0_TO_3600)
#define IS_TYPERANGE_0_TO_32767(p) ((p)->type_range & TYPERANGE_0_TO_32767)
#define IS_TYPERANGE_0_TO_65535(p) ((p)->type_range & TYPERANGE_0_TO_65535)
#define IS_TYPERANGE_1_TO_65535(p) ((p)->type_range & TYPERANGE_1_TO_65535)
#define IS_TYPERANGE_2_TO_3600(p) ((p)->type_range & TYPERANGE_2_TO_3600)
#define IS_TYPERANGE_512_TO_16777215(p) ((p)->type_range & \
TYPERANGE_512_TO_16777215)
#define IS_TYPERANGE_AUTH_PARAM(p) ((p)->type_range & TYPERANGE_AUTH)
#define IS_TYPERANGE_DIGEST_PARAM(p) ((p)->type_range & TYPERANGE_DIGEST)
#define IS_TYPERANGE_SESSIONTYPE(p) ((p)->type_range & \
TYPERANGE_SESSIONTYPE)
/*
* struct iscsi_param->state
*/
#define PSTATE_ACCEPTOR 0x01
#define PSTATE_NEGOTIATE 0x02
#define PSTATE_PROPOSER 0x04
#define PSTATE_IRRELEVANT 0x08
#define PSTATE_REJECT 0x10
#define PSTATE_REPLY_OPTIONAL 0x20
#define PSTATE_RESPONSE_GOT 0x40
#define PSTATE_RESPONSE_SENT 0x80
#define IS_PSTATE_ACCEPTOR(p) ((p)->state & PSTATE_ACCEPTOR)
#define IS_PSTATE_NEGOTIATE(p) ((p)->state & PSTATE_NEGOTIATE)
#define IS_PSTATE_PROPOSER(p) ((p)->state & PSTATE_PROPOSER)
#define IS_PSTATE_IRRELEVANT(p) ((p)->state & PSTATE_IRRELEVANT)
#define IS_PSTATE_REJECT(p) ((p)->state & PSTATE_REJECT)
#define IS_PSTATE_REPLY_OPTIONAL(p) ((p)->state & PSTATE_REPLY_OPTIONAL)
#define IS_PSTATE_RESPONSE_GOT(p) ((p)->state & PSTATE_RESPONSE_GOT)
#define IS_PSTATE_RESPONSE_SENT(p) ((p)->state & PSTATE_RESPONSE_SENT)
#define SET_PSTATE_ACCEPTOR(p) ((p)->state |= PSTATE_ACCEPTOR)
#define SET_PSTATE_NEGOTIATE(p) ((p)->state |= PSTATE_NEGOTIATE)
#define SET_PSTATE_PROPOSER(p) ((p)->state |= PSTATE_PROPOSER)
#define SET_PSTATE_IRRELEVANT(p) ((p)->state |= PSTATE_IRRELEVANT)
#define SET_PSTATE_REJECT(p) ((p)->state |= PSTATE_REJECT)
#define SET_PSTATE_REPLY_OPTIONAL(p) ((p)->state |= PSTATE_REPLY_OPTIONAL)
#define SET_PSTATE_RESPONSE_GOT(p) ((p)->state |= PSTATE_RESPONSE_GOT)
#define SET_PSTATE_RESPONSE_SENT(p) ((p)->state |= PSTATE_RESPONSE_SENT)
#endif /* ISCSI_PARAMETERS_H */
@@ -0,0 +1,690 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* This file contains main functions related to iSCSI DataSequenceInOrder=No
* and DataPDUInOrder=No.
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <linux/slab.h>
#include <linux/random.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_util.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_seq_pdu_list.h"
#ifdef DEBUG
static void iscsit_dump_seq_list(struct iscsit_cmd *cmd)
{
int i;
struct iscsi_seq *seq;
pr_debug("Dumping Sequence List for ITT: 0x%08x:\n",
cmd->init_task_tag);
for (i = 0; i < cmd->seq_count; i++) {
seq = &cmd->seq_list[i];
pr_debug("i: %d, pdu_start: %d, pdu_count: %d,"
" offset: %d, xfer_len: %d, seq_send_order: %d,"
" seq_no: %d\n", i, seq->pdu_start, seq->pdu_count,
seq->offset, seq->xfer_len, seq->seq_send_order,
seq->seq_no);
}
}
static void iscsit_dump_pdu_list(struct iscsit_cmd *cmd)
{
int i;
struct iscsi_pdu *pdu;
pr_debug("Dumping PDU List for ITT: 0x%08x:\n",
cmd->init_task_tag);
for (i = 0; i < cmd->pdu_count; i++) {
pdu = &cmd->pdu_list[i];
pr_debug("i: %d, offset: %d, length: %d,"
" pdu_send_order: %d, seq_no: %d\n", i, pdu->offset,
pdu->length, pdu->pdu_send_order, pdu->seq_no);
}
}
#else
static void iscsit_dump_seq_list(struct iscsit_cmd *cmd) {}
static void iscsit_dump_pdu_list(struct iscsit_cmd *cmd) {}
#endif
static void iscsit_ordered_seq_lists(
struct iscsit_cmd *cmd,
u8 type)
{
u32 i, seq_count = 0;
for (i = 0; i < cmd->seq_count; i++) {
if (cmd->seq_list[i].type != SEQTYPE_NORMAL)
continue;
cmd->seq_list[i].seq_send_order = seq_count++;
}
}
static void iscsit_ordered_pdu_lists(
struct iscsit_cmd *cmd,
u8 type)
{
u32 i, pdu_send_order = 0, seq_no = 0;
for (i = 0; i < cmd->pdu_count; i++) {
redo:
if (cmd->pdu_list[i].seq_no == seq_no) {
cmd->pdu_list[i].pdu_send_order = pdu_send_order++;
continue;
}
seq_no++;
pdu_send_order = 0;
goto redo;
}
}
/*
* Generate count random values into array.
* Use 0x80000000 to mark generates valued in array[].
*/
static void iscsit_create_random_array(u32 *array, u32 count)
{
int i, j, k;
if (count == 1) {
array[0] = 0;
return;
}
for (i = 0; i < count; i++) {
redo:
get_random_bytes(&j, sizeof(u32));
j = (1 + (int) (9999 + 1) - j) % count;
for (k = 0; k < i + 1; k++) {
j |= 0x80000000;
if ((array[k] & 0x80000000) && (array[k] == j))
goto redo;
}
array[i] = j;
}
for (i = 0; i < count; i++)
array[i] &= ~0x80000000;
}
static int iscsit_randomize_pdu_lists(
struct iscsit_cmd *cmd,
u8 type)
{
int i = 0;
u32 *array, pdu_count, seq_count = 0, seq_no = 0, seq_offset = 0;
for (pdu_count = 0; pdu_count < cmd->pdu_count; pdu_count++) {
redo:
if (cmd->pdu_list[pdu_count].seq_no == seq_no) {
seq_count++;
continue;
}
array = kcalloc(seq_count, sizeof(u32), GFP_KERNEL);
if (!array) {
pr_err("Unable to allocate memory"
" for random array.\n");
return -ENOMEM;
}
iscsit_create_random_array(array, seq_count);
for (i = 0; i < seq_count; i++)
cmd->pdu_list[seq_offset+i].pdu_send_order = array[i];
kfree(array);
seq_offset += seq_count;
seq_count = 0;
seq_no++;
goto redo;
}
if (seq_count) {
array = kcalloc(seq_count, sizeof(u32), GFP_KERNEL);
if (!array) {
pr_err("Unable to allocate memory for"
" random array.\n");
return -ENOMEM;
}
iscsit_create_random_array(array, seq_count);
for (i = 0; i < seq_count; i++)
cmd->pdu_list[seq_offset+i].pdu_send_order = array[i];
kfree(array);
}
return 0;
}
static int iscsit_randomize_seq_lists(
struct iscsit_cmd *cmd,
u8 type)
{
int i, j = 0;
u32 *array, seq_count = cmd->seq_count;
if ((type == PDULIST_IMMEDIATE) || (type == PDULIST_UNSOLICITED))
seq_count--;
else if (type == PDULIST_IMMEDIATE_AND_UNSOLICITED)
seq_count -= 2;
if (!seq_count)
return 0;
array = kcalloc(seq_count, sizeof(u32), GFP_KERNEL);
if (!array) {
pr_err("Unable to allocate memory for random array.\n");
return -ENOMEM;
}
iscsit_create_random_array(array, seq_count);
for (i = 0; i < cmd->seq_count; i++) {
if (cmd->seq_list[i].type != SEQTYPE_NORMAL)
continue;
cmd->seq_list[i].seq_send_order = array[j++];
}
kfree(array);
return 0;
}
static void iscsit_determine_counts_for_list(
struct iscsit_cmd *cmd,
struct iscsi_build_list *bl,
u32 *seq_count,
u32 *pdu_count)
{
int check_immediate = 0;
u32 burstlength = 0, offset = 0;
u32 unsolicited_data_length = 0;
u32 mdsl;
struct iscsit_conn *conn = cmd->conn;
if (cmd->se_cmd.data_direction == DMA_TO_DEVICE)
mdsl = cmd->conn->conn_ops->MaxXmitDataSegmentLength;
else
mdsl = cmd->conn->conn_ops->MaxRecvDataSegmentLength;
if ((bl->type == PDULIST_IMMEDIATE) ||
(bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED))
check_immediate = 1;
if ((bl->type == PDULIST_UNSOLICITED) ||
(bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED))
unsolicited_data_length = min(cmd->se_cmd.data_length,
conn->sess->sess_ops->FirstBurstLength);
while (offset < cmd->se_cmd.data_length) {
*pdu_count += 1;
if (check_immediate) {
check_immediate = 0;
offset += bl->immediate_data_length;
*seq_count += 1;
if (unsolicited_data_length)
unsolicited_data_length -=
bl->immediate_data_length;
continue;
}
if (unsolicited_data_length > 0) {
if ((offset + mdsl) >= cmd->se_cmd.data_length) {
unsolicited_data_length -=
(cmd->se_cmd.data_length - offset);
offset += (cmd->se_cmd.data_length - offset);
continue;
}
if ((offset + mdsl)
>= conn->sess->sess_ops->FirstBurstLength) {
unsolicited_data_length -=
(conn->sess->sess_ops->FirstBurstLength -
offset);
offset += (conn->sess->sess_ops->FirstBurstLength -
offset);
burstlength = 0;
*seq_count += 1;
continue;
}
offset += mdsl;
unsolicited_data_length -= mdsl;
continue;
}
if ((offset + mdsl) >= cmd->se_cmd.data_length) {
offset += (cmd->se_cmd.data_length - offset);
continue;
}
if ((burstlength + mdsl) >=
conn->sess->sess_ops->MaxBurstLength) {
offset += (conn->sess->sess_ops->MaxBurstLength -
burstlength);
burstlength = 0;
*seq_count += 1;
continue;
}
burstlength += mdsl;
offset += mdsl;
}
}
/*
* Builds PDU and/or Sequence list, called while DataSequenceInOrder=No
* or DataPDUInOrder=No.
*/
static int iscsit_do_build_pdu_and_seq_lists(
struct iscsit_cmd *cmd,
struct iscsi_build_list *bl)
{
int check_immediate = 0, datapduinorder, datasequenceinorder;
u32 burstlength = 0, offset = 0, i = 0, mdsl;
u32 pdu_count = 0, seq_no = 0, unsolicited_data_length = 0;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_pdu *pdu = cmd->pdu_list;
struct iscsi_seq *seq = cmd->seq_list;
if (cmd->se_cmd.data_direction == DMA_TO_DEVICE)
mdsl = cmd->conn->conn_ops->MaxXmitDataSegmentLength;
else
mdsl = cmd->conn->conn_ops->MaxRecvDataSegmentLength;
datapduinorder = conn->sess->sess_ops->DataPDUInOrder;
datasequenceinorder = conn->sess->sess_ops->DataSequenceInOrder;
if ((bl->type == PDULIST_IMMEDIATE) ||
(bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED))
check_immediate = 1;
if ((bl->type == PDULIST_UNSOLICITED) ||
(bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED))
unsolicited_data_length = min(cmd->se_cmd.data_length,
conn->sess->sess_ops->FirstBurstLength);
while (offset < cmd->se_cmd.data_length) {
pdu_count++;
if (!datapduinorder) {
pdu[i].offset = offset;
pdu[i].seq_no = seq_no;
}
if (!datasequenceinorder && (pdu_count == 1)) {
seq[seq_no].pdu_start = i;
seq[seq_no].seq_no = seq_no;
seq[seq_no].offset = offset;
seq[seq_no].orig_offset = offset;
}
if (check_immediate) {
check_immediate = 0;
if (!datapduinorder) {
pdu[i].type = PDUTYPE_IMMEDIATE;
pdu[i++].length = bl->immediate_data_length;
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_IMMEDIATE;
seq[seq_no].pdu_count = 1;
seq[seq_no].xfer_len =
bl->immediate_data_length;
}
offset += bl->immediate_data_length;
pdu_count = 0;
seq_no++;
if (unsolicited_data_length)
unsolicited_data_length -=
bl->immediate_data_length;
continue;
}
if (unsolicited_data_length > 0) {
if ((offset + mdsl) >= cmd->se_cmd.data_length) {
if (!datapduinorder) {
pdu[i].type = PDUTYPE_UNSOLICITED;
pdu[i].length =
(cmd->se_cmd.data_length - offset);
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_UNSOLICITED;
seq[seq_no].pdu_count = pdu_count;
seq[seq_no].xfer_len = (burstlength +
(cmd->se_cmd.data_length - offset));
}
unsolicited_data_length -=
(cmd->se_cmd.data_length - offset);
offset += (cmd->se_cmd.data_length - offset);
continue;
}
if ((offset + mdsl) >=
conn->sess->sess_ops->FirstBurstLength) {
if (!datapduinorder) {
pdu[i].type = PDUTYPE_UNSOLICITED;
pdu[i++].length =
(conn->sess->sess_ops->FirstBurstLength -
offset);
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_UNSOLICITED;
seq[seq_no].pdu_count = pdu_count;
seq[seq_no].xfer_len = (burstlength +
(conn->sess->sess_ops->FirstBurstLength -
offset));
}
unsolicited_data_length -=
(conn->sess->sess_ops->FirstBurstLength -
offset);
offset += (conn->sess->sess_ops->FirstBurstLength -
offset);
burstlength = 0;
pdu_count = 0;
seq_no++;
continue;
}
if (!datapduinorder) {
pdu[i].type = PDUTYPE_UNSOLICITED;
pdu[i++].length = mdsl;
}
burstlength += mdsl;
offset += mdsl;
unsolicited_data_length -= mdsl;
continue;
}
if ((offset + mdsl) >= cmd->se_cmd.data_length) {
if (!datapduinorder) {
pdu[i].type = PDUTYPE_NORMAL;
pdu[i].length = (cmd->se_cmd.data_length - offset);
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_NORMAL;
seq[seq_no].pdu_count = pdu_count;
seq[seq_no].xfer_len = (burstlength +
(cmd->se_cmd.data_length - offset));
}
offset += (cmd->se_cmd.data_length - offset);
continue;
}
if ((burstlength + mdsl) >=
conn->sess->sess_ops->MaxBurstLength) {
if (!datapduinorder) {
pdu[i].type = PDUTYPE_NORMAL;
pdu[i++].length =
(conn->sess->sess_ops->MaxBurstLength -
burstlength);
}
if (!datasequenceinorder) {
seq[seq_no].type = SEQTYPE_NORMAL;
seq[seq_no].pdu_count = pdu_count;
seq[seq_no].xfer_len = (burstlength +
(conn->sess->sess_ops->MaxBurstLength -
burstlength));
}
offset += (conn->sess->sess_ops->MaxBurstLength -
burstlength);
burstlength = 0;
pdu_count = 0;
seq_no++;
continue;
}
if (!datapduinorder) {
pdu[i].type = PDUTYPE_NORMAL;
pdu[i++].length = mdsl;
}
burstlength += mdsl;
offset += mdsl;
}
if (!datasequenceinorder) {
if (bl->data_direction & ISCSI_PDU_WRITE) {
if (bl->randomize & RANDOM_R2T_OFFSETS) {
if (iscsit_randomize_seq_lists(cmd, bl->type)
< 0)
return -1;
} else
iscsit_ordered_seq_lists(cmd, bl->type);
} else if (bl->data_direction & ISCSI_PDU_READ) {
if (bl->randomize & RANDOM_DATAIN_SEQ_OFFSETS) {
if (iscsit_randomize_seq_lists(cmd, bl->type)
< 0)
return -1;
} else
iscsit_ordered_seq_lists(cmd, bl->type);
}
iscsit_dump_seq_list(cmd);
}
if (!datapduinorder) {
if (bl->data_direction & ISCSI_PDU_WRITE) {
if (bl->randomize & RANDOM_DATAOUT_PDU_OFFSETS) {
if (iscsit_randomize_pdu_lists(cmd, bl->type)
< 0)
return -1;
} else
iscsit_ordered_pdu_lists(cmd, bl->type);
} else if (bl->data_direction & ISCSI_PDU_READ) {
if (bl->randomize & RANDOM_DATAIN_PDU_OFFSETS) {
if (iscsit_randomize_pdu_lists(cmd, bl->type)
< 0)
return -1;
} else
iscsit_ordered_pdu_lists(cmd, bl->type);
}
iscsit_dump_pdu_list(cmd);
}
return 0;
}
int iscsit_build_pdu_and_seq_lists(
struct iscsit_cmd *cmd,
u32 immediate_data_length)
{
struct iscsi_build_list bl;
u32 pdu_count = 0, seq_count = 1;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_pdu *pdu = NULL;
struct iscsi_seq *seq = NULL;
struct iscsit_session *sess = conn->sess;
struct iscsi_node_attrib *na;
/*
* Do nothing if no OOO shenanigans
*/
if (sess->sess_ops->DataSequenceInOrder &&
sess->sess_ops->DataPDUInOrder)
return 0;
if (cmd->data_direction == DMA_NONE)
return 0;
na = iscsit_tpg_get_node_attrib(sess);
memset(&bl, 0, sizeof(struct iscsi_build_list));
if (cmd->data_direction == DMA_FROM_DEVICE) {
bl.data_direction = ISCSI_PDU_READ;
bl.type = PDULIST_NORMAL;
if (na->random_datain_pdu_offsets)
bl.randomize |= RANDOM_DATAIN_PDU_OFFSETS;
if (na->random_datain_seq_offsets)
bl.randomize |= RANDOM_DATAIN_SEQ_OFFSETS;
} else {
bl.data_direction = ISCSI_PDU_WRITE;
bl.immediate_data_length = immediate_data_length;
if (na->random_r2t_offsets)
bl.randomize |= RANDOM_R2T_OFFSETS;
if (!cmd->immediate_data && !cmd->unsolicited_data)
bl.type = PDULIST_NORMAL;
else if (cmd->immediate_data && !cmd->unsolicited_data)
bl.type = PDULIST_IMMEDIATE;
else if (!cmd->immediate_data && cmd->unsolicited_data)
bl.type = PDULIST_UNSOLICITED;
else if (cmd->immediate_data && cmd->unsolicited_data)
bl.type = PDULIST_IMMEDIATE_AND_UNSOLICITED;
}
iscsit_determine_counts_for_list(cmd, &bl, &seq_count, &pdu_count);
if (!conn->sess->sess_ops->DataSequenceInOrder) {
seq = kzalloc_objs(struct iscsi_seq, seq_count, GFP_ATOMIC);
if (!seq) {
pr_err("Unable to allocate struct iscsi_seq list\n");
return -ENOMEM;
}
cmd->seq_list = seq;
cmd->seq_count = seq_count;
}
if (!conn->sess->sess_ops->DataPDUInOrder) {
pdu = kzalloc_objs(struct iscsi_pdu, pdu_count, GFP_ATOMIC);
if (!pdu) {
pr_err("Unable to allocate struct iscsi_pdu list.\n");
kfree(seq);
return -ENOMEM;
}
cmd->pdu_list = pdu;
cmd->pdu_count = pdu_count;
}
return iscsit_do_build_pdu_and_seq_lists(cmd, &bl);
}
struct iscsi_pdu *iscsit_get_pdu_holder(
struct iscsit_cmd *cmd,
u32 offset,
u32 length)
{
u32 i;
struct iscsi_pdu *pdu = NULL;
if (!cmd->pdu_list) {
pr_err("struct iscsit_cmd->pdu_list is NULL!\n");
return NULL;
}
pdu = &cmd->pdu_list[0];
for (i = 0; i < cmd->pdu_count; i++)
if ((pdu[i].offset == offset) && (pdu[i].length == length))
return &pdu[i];
pr_err("Unable to locate PDU holder for ITT: 0x%08x, Offset:"
" %u, Length: %u\n", cmd->init_task_tag, offset, length);
return NULL;
}
struct iscsi_pdu *iscsit_get_pdu_holder_for_seq(
struct iscsit_cmd *cmd,
struct iscsi_seq *seq)
{
u32 i;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_pdu *pdu = NULL;
if (!cmd->pdu_list) {
pr_err("struct iscsit_cmd->pdu_list is NULL!\n");
return NULL;
}
if (conn->sess->sess_ops->DataSequenceInOrder) {
redo:
pdu = &cmd->pdu_list[cmd->pdu_start];
for (i = 0; pdu[i].seq_no != cmd->seq_no; i++) {
pr_debug("pdu[i].seq_no: %d, pdu[i].pdu"
"_send_order: %d, pdu[i].offset: %d,"
" pdu[i].length: %d\n", pdu[i].seq_no,
pdu[i].pdu_send_order, pdu[i].offset,
pdu[i].length);
if (pdu[i].pdu_send_order == cmd->pdu_send_order) {
cmd->pdu_send_order++;
return &pdu[i];
}
}
cmd->pdu_start += cmd->pdu_send_order;
cmd->pdu_send_order = 0;
cmd->seq_no++;
if (cmd->pdu_start < cmd->pdu_count)
goto redo;
pr_err("Command ITT: 0x%08x unable to locate"
" struct iscsi_pdu for cmd->pdu_send_order: %u.\n",
cmd->init_task_tag, cmd->pdu_send_order);
return NULL;
} else {
if (!seq) {
pr_err("struct iscsi_seq is NULL!\n");
return NULL;
}
pr_debug("seq->pdu_start: %d, seq->pdu_count: %d,"
" seq->seq_no: %d\n", seq->pdu_start, seq->pdu_count,
seq->seq_no);
pdu = &cmd->pdu_list[seq->pdu_start];
if (seq->pdu_send_order == seq->pdu_count) {
pr_err("Command ITT: 0x%08x seq->pdu_send"
"_order: %u equals seq->pdu_count: %u\n",
cmd->init_task_tag, seq->pdu_send_order,
seq->pdu_count);
return NULL;
}
for (i = 0; i < seq->pdu_count; i++) {
if (pdu[i].pdu_send_order == seq->pdu_send_order) {
seq->pdu_send_order++;
return &pdu[i];
}
}
pr_err("Command ITT: 0x%08x unable to locate iscsi"
"_pdu_t for seq->pdu_send_order: %u.\n",
cmd->init_task_tag, seq->pdu_send_order);
return NULL;
}
return NULL;
}
struct iscsi_seq *iscsit_get_seq_holder(
struct iscsit_cmd *cmd,
u32 offset,
u32 length)
{
u32 i;
if (!cmd->seq_list) {
pr_err("struct iscsit_cmd->seq_list is NULL!\n");
return NULL;
}
for (i = 0; i < cmd->seq_count; i++) {
pr_debug("seq_list[i].orig_offset: %d, seq_list[i]."
"xfer_len: %d, seq_list[i].seq_no %u\n",
cmd->seq_list[i].orig_offset, cmd->seq_list[i].xfer_len,
cmd->seq_list[i].seq_no);
if ((cmd->seq_list[i].orig_offset +
cmd->seq_list[i].xfer_len) >=
(offset + length))
return &cmd->seq_list[i];
}
pr_err("Unable to locate Sequence holder for ITT: 0x%08x,"
" Offset: %u, Length: %u\n", cmd->init_task_tag, offset,
length);
return NULL;
}
@@ -0,0 +1,92 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_SEQ_AND_PDU_LIST_H
#define ISCSI_SEQ_AND_PDU_LIST_H
#include <linux/types.h>
#include <linux/cache.h>
/* struct iscsi_pdu->status */
#define DATAOUT_PDU_SENT 1
/* struct iscsi_seq->type */
#define SEQTYPE_IMMEDIATE 1
#define SEQTYPE_UNSOLICITED 2
#define SEQTYPE_NORMAL 3
/* struct iscsi_seq->status */
#define DATAOUT_SEQUENCE_GOT_R2T 1
#define DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY 2
#define DATAOUT_SEQUENCE_COMPLETE 3
/* iscsi_determine_counts_for_list() type */
#define PDULIST_NORMAL 1
#define PDULIST_IMMEDIATE 2
#define PDULIST_UNSOLICITED 3
#define PDULIST_IMMEDIATE_AND_UNSOLICITED 4
/* struct iscsi_pdu->type */
#define PDUTYPE_IMMEDIATE 1
#define PDUTYPE_UNSOLICITED 2
#define PDUTYPE_NORMAL 3
/* struct iscsi_pdu->status */
#define ISCSI_PDU_NOT_RECEIVED 0
#define ISCSI_PDU_RECEIVED_OK 1
#define ISCSI_PDU_CRC_FAILED 2
#define ISCSI_PDU_TIMED_OUT 3
/* struct iscsi_build_list->randomize */
#define RANDOM_DATAIN_PDU_OFFSETS 0x01
#define RANDOM_DATAIN_SEQ_OFFSETS 0x02
#define RANDOM_DATAOUT_PDU_OFFSETS 0x04
#define RANDOM_R2T_OFFSETS 0x08
/* struct iscsi_build_list->data_direction */
#define ISCSI_PDU_READ 0x01
#define ISCSI_PDU_WRITE 0x02
struct iscsi_build_list {
int data_direction;
int randomize;
int type;
int immediate_data_length;
};
struct iscsi_pdu {
int status;
int type;
u8 flags;
u32 data_sn;
u32 length;
u32 offset;
u32 pdu_send_order;
u32 seq_no;
} ____cacheline_aligned;
struct iscsi_seq {
int sent;
int status;
int type;
u32 data_sn;
u32 first_datasn;
u32 last_datasn;
u32 next_burst_len;
u32 pdu_start;
u32 pdu_count;
u32 offset;
u32 orig_offset;
u32 pdu_send_order;
u32 r2t_sn;
u32 seq_send_order;
u32 seq_no;
u32 xfer_len;
} ____cacheline_aligned;
struct iscsit_cmd;
extern int iscsit_build_pdu_and_seq_lists(struct iscsit_cmd *, u32);
extern struct iscsi_pdu *iscsit_get_pdu_holder(struct iscsit_cmd *, u32, u32);
extern struct iscsi_pdu *iscsit_get_pdu_holder_for_seq(struct iscsit_cmd *, struct iscsi_seq *);
extern struct iscsi_seq *iscsit_get_seq_holder(struct iscsit_cmd *, u32, u32);
#endif /* ISCSI_SEQ_AND_PDU_LIST_H */
@@ -0,0 +1,798 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* Modern ConfigFS group context specific iSCSI statistics based on original
* iscsi_target_mib.c code
*
* Copyright (c) 2011-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <linux/configfs.h>
#include <linux/export.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_parameters.h"
#include "iscsi_target_device.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include <target/iscsi/iscsi_target_stat.h>
#ifndef INITIAL_JIFFIES
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
#endif
/* Instance Attributes Table */
#define ISCSI_INST_NUM_NODES 1
#define ISCSI_INST_DESCR "Storage Engine Target"
#define ISCSI_DISCONTINUITY_TIME 0
#define ISCSI_NODE_INDEX 1
#define ISPRINT(a) ((a >= ' ') && (a <= '~'))
/****************************************************************************
* iSCSI MIB Tables
****************************************************************************/
/*
* Instance Attributes Table
*/
static struct iscsi_tiqn *iscsi_instance_tiqn(struct config_item *item)
{
struct iscsi_wwn_stat_grps *igrps = container_of(to_config_group(item),
struct iscsi_wwn_stat_grps, iscsi_instance_group);
return container_of(igrps, struct iscsi_tiqn, tiqn_stat_grps);
}
static ssize_t iscsi_stat_instance_inst_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n",
iscsi_instance_tiqn(item)->tiqn_index);
}
static ssize_t iscsi_stat_instance_min_ver_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_DRAFT20_VERSION);
}
static ssize_t iscsi_stat_instance_max_ver_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_DRAFT20_VERSION);
}
static ssize_t iscsi_stat_instance_portals_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n",
iscsi_instance_tiqn(item)->tiqn_num_tpg_nps);
}
static ssize_t iscsi_stat_instance_nodes_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_INST_NUM_NODES);
}
static ssize_t iscsi_stat_instance_sessions_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n",
iscsi_instance_tiqn(item)->tiqn_nsessions);
}
static ssize_t iscsi_stat_instance_fail_sess_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_instance_tiqn(item);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
u32 sess_err_count;
spin_lock_bh(&sess_err->lock);
sess_err_count = (sess_err->digest_errors +
sess_err->cxn_timeout_errors +
sess_err->pdu_format_errors);
spin_unlock_bh(&sess_err->lock);
return snprintf(page, PAGE_SIZE, "%u\n", sess_err_count);
}
static ssize_t iscsi_stat_instance_fail_type_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_instance_tiqn(item);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%u\n",
sess_err->last_sess_failure_type);
}
static ssize_t iscsi_stat_instance_fail_rem_name_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_instance_tiqn(item);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%s\n",
sess_err->last_sess_fail_rem_name[0] ?
sess_err->last_sess_fail_rem_name : NONE);
}
static ssize_t iscsi_stat_instance_disc_time_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_DISCONTINUITY_TIME);
}
static ssize_t iscsi_stat_instance_description_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%s\n", ISCSI_INST_DESCR);
}
static ssize_t iscsi_stat_instance_vendor_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "Datera, Inc. iSCSI-Target\n");
}
static ssize_t iscsi_stat_instance_version_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%s\n", ISCSIT_VERSION);
}
CONFIGFS_ATTR_RO(iscsi_stat_instance_, inst);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, min_ver);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, max_ver);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, portals);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, nodes);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, sessions);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, fail_sess);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, fail_type);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, fail_rem_name);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, disc_time);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, description);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, vendor);
CONFIGFS_ATTR_RO(iscsi_stat_instance_, version);
static struct configfs_attribute *iscsi_stat_instance_attrs[] = {
&iscsi_stat_instance_attr_inst,
&iscsi_stat_instance_attr_min_ver,
&iscsi_stat_instance_attr_max_ver,
&iscsi_stat_instance_attr_portals,
&iscsi_stat_instance_attr_nodes,
&iscsi_stat_instance_attr_sessions,
&iscsi_stat_instance_attr_fail_sess,
&iscsi_stat_instance_attr_fail_type,
&iscsi_stat_instance_attr_fail_rem_name,
&iscsi_stat_instance_attr_disc_time,
&iscsi_stat_instance_attr_description,
&iscsi_stat_instance_attr_vendor,
&iscsi_stat_instance_attr_version,
NULL,
};
const struct config_item_type iscsi_stat_instance_cit = {
.ct_attrs = iscsi_stat_instance_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Instance Session Failure Stats Table
*/
static struct iscsi_tiqn *iscsi_sess_err_tiqn(struct config_item *item)
{
struct iscsi_wwn_stat_grps *igrps = container_of(to_config_group(item),
struct iscsi_wwn_stat_grps, iscsi_sess_err_group);
return container_of(igrps, struct iscsi_tiqn, tiqn_stat_grps);
}
static ssize_t iscsi_stat_sess_err_inst_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n",
iscsi_sess_err_tiqn(item)->tiqn_index);
}
static ssize_t iscsi_stat_sess_err_digest_errors_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_sess_err_tiqn(item);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%u\n", sess_err->digest_errors);
}
static ssize_t iscsi_stat_sess_err_cxn_errors_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_sess_err_tiqn(item);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%u\n", sess_err->cxn_timeout_errors);
}
static ssize_t iscsi_stat_sess_err_format_errors_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_sess_err_tiqn(item);
struct iscsi_sess_err_stats *sess_err = &tiqn->sess_err_stats;
return snprintf(page, PAGE_SIZE, "%u\n", sess_err->pdu_format_errors);
}
CONFIGFS_ATTR_RO(iscsi_stat_sess_err_, inst);
CONFIGFS_ATTR_RO(iscsi_stat_sess_err_, digest_errors);
CONFIGFS_ATTR_RO(iscsi_stat_sess_err_, cxn_errors);
CONFIGFS_ATTR_RO(iscsi_stat_sess_err_, format_errors);
static struct configfs_attribute *iscsi_stat_sess_err_attrs[] = {
&iscsi_stat_sess_err_attr_inst,
&iscsi_stat_sess_err_attr_digest_errors,
&iscsi_stat_sess_err_attr_cxn_errors,
&iscsi_stat_sess_err_attr_format_errors,
NULL,
};
const struct config_item_type iscsi_stat_sess_err_cit = {
.ct_attrs = iscsi_stat_sess_err_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Target Attributes Table
*/
static struct iscsi_tiqn *iscsi_tgt_attr_tiqn(struct config_item *item)
{
struct iscsi_wwn_stat_grps *igrps = container_of(to_config_group(item),
struct iscsi_wwn_stat_grps, iscsi_tgt_attr_group);
return container_of(igrps, struct iscsi_tiqn, tiqn_stat_grps);
}
static ssize_t iscsi_stat_tgt_attr_inst_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n",
iscsi_tgt_attr_tiqn(item)->tiqn_index);
}
static ssize_t iscsi_stat_tgt_attr_indx_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_NODE_INDEX);
}
static ssize_t iscsi_stat_tgt_attr_login_fails_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_tgt_attr_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
u32 fail_count;
spin_lock(&lstat->lock);
fail_count = (lstat->redirects + lstat->authorize_fails +
lstat->authenticate_fails + lstat->negotiate_fails +
lstat->other_fails);
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%u\n", fail_count);
}
static ssize_t iscsi_stat_tgt_attr_last_fail_time_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_tgt_attr_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
u32 last_fail_time;
spin_lock(&lstat->lock);
last_fail_time = lstat->last_fail_time ?
(u32)(((u32)lstat->last_fail_time -
INITIAL_JIFFIES) * 100 / HZ) : 0;
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%u\n", last_fail_time);
}
static ssize_t iscsi_stat_tgt_attr_last_fail_type_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_tgt_attr_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
u32 last_fail_type;
spin_lock(&lstat->lock);
last_fail_type = lstat->last_fail_type;
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%u\n", last_fail_type);
}
static ssize_t iscsi_stat_tgt_attr_fail_intr_name_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_tgt_attr_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
unsigned char buf[ISCSI_IQN_LEN];
spin_lock(&lstat->lock);
snprintf(buf, ISCSI_IQN_LEN, "%s", lstat->last_intr_fail_name[0] ?
lstat->last_intr_fail_name : NONE);
spin_unlock(&lstat->lock);
return snprintf(page, PAGE_SIZE, "%s\n", buf);
}
static ssize_t iscsi_stat_tgt_attr_fail_intr_addr_type_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_tgt_attr_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
int ret;
spin_lock(&lstat->lock);
if (lstat->last_intr_fail_ip_family == AF_INET6)
ret = snprintf(page, PAGE_SIZE, "ipv6\n");
else
ret = snprintf(page, PAGE_SIZE, "ipv4\n");
spin_unlock(&lstat->lock);
return ret;
}
static ssize_t iscsi_stat_tgt_attr_fail_intr_addr_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_tgt_attr_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
int ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%pISc\n", &lstat->last_intr_fail_sockaddr);
spin_unlock(&lstat->lock);
return ret;
}
CONFIGFS_ATTR_RO(iscsi_stat_tgt_attr_, inst);
CONFIGFS_ATTR_RO(iscsi_stat_tgt_attr_, indx);
CONFIGFS_ATTR_RO(iscsi_stat_tgt_attr_, login_fails);
CONFIGFS_ATTR_RO(iscsi_stat_tgt_attr_, last_fail_time);
CONFIGFS_ATTR_RO(iscsi_stat_tgt_attr_, last_fail_type);
CONFIGFS_ATTR_RO(iscsi_stat_tgt_attr_, fail_intr_name);
CONFIGFS_ATTR_RO(iscsi_stat_tgt_attr_, fail_intr_addr_type);
CONFIGFS_ATTR_RO(iscsi_stat_tgt_attr_, fail_intr_addr);
static struct configfs_attribute *iscsi_stat_tgt_attr_attrs[] = {
&iscsi_stat_tgt_attr_attr_inst,
&iscsi_stat_tgt_attr_attr_indx,
&iscsi_stat_tgt_attr_attr_login_fails,
&iscsi_stat_tgt_attr_attr_last_fail_time,
&iscsi_stat_tgt_attr_attr_last_fail_type,
&iscsi_stat_tgt_attr_attr_fail_intr_name,
&iscsi_stat_tgt_attr_attr_fail_intr_addr_type,
&iscsi_stat_tgt_attr_attr_fail_intr_addr,
NULL,
};
const struct config_item_type iscsi_stat_tgt_attr_cit = {
.ct_attrs = iscsi_stat_tgt_attr_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Target Login Stats Table
*/
static struct iscsi_tiqn *iscsi_login_stat_tiqn(struct config_item *item)
{
struct iscsi_wwn_stat_grps *igrps = container_of(to_config_group(item),
struct iscsi_wwn_stat_grps, iscsi_login_stats_group);
return container_of(igrps, struct iscsi_tiqn, tiqn_stat_grps);
}
static ssize_t iscsi_stat_login_inst_show(struct config_item *item, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n",
iscsi_login_stat_tiqn(item)->tiqn_index);
}
static ssize_t iscsi_stat_login_indx_show(struct config_item *item,
char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_NODE_INDEX);
}
static ssize_t iscsi_stat_login_accepts_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_login_stat_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->accepts);
spin_unlock(&lstat->lock);
return ret;
}
static ssize_t iscsi_stat_login_other_fails_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_login_stat_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->other_fails);
spin_unlock(&lstat->lock);
return ret;
}
static ssize_t iscsi_stat_login_redirects_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_login_stat_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->redirects);
spin_unlock(&lstat->lock);
return ret;
}
static ssize_t iscsi_stat_login_authorize_fails_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_login_stat_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->authorize_fails);
spin_unlock(&lstat->lock);
return ret;
}
static ssize_t iscsi_stat_login_authenticate_fails_show(
struct config_item *item, char *page)
{
struct iscsi_tiqn *tiqn = iscsi_login_stat_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->authenticate_fails);
spin_unlock(&lstat->lock);
return ret;
}
static ssize_t iscsi_stat_login_negotiate_fails_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_login_stat_tiqn(item);
struct iscsi_login_stats *lstat = &tiqn->login_stats;
ssize_t ret;
spin_lock(&lstat->lock);
ret = snprintf(page, PAGE_SIZE, "%u\n", lstat->negotiate_fails);
spin_unlock(&lstat->lock);
return ret;
}
CONFIGFS_ATTR_RO(iscsi_stat_login_, inst);
CONFIGFS_ATTR_RO(iscsi_stat_login_, indx);
CONFIGFS_ATTR_RO(iscsi_stat_login_, accepts);
CONFIGFS_ATTR_RO(iscsi_stat_login_, other_fails);
CONFIGFS_ATTR_RO(iscsi_stat_login_, redirects);
CONFIGFS_ATTR_RO(iscsi_stat_login_, authorize_fails);
CONFIGFS_ATTR_RO(iscsi_stat_login_, authenticate_fails);
CONFIGFS_ATTR_RO(iscsi_stat_login_, negotiate_fails);
static struct configfs_attribute *iscsi_stat_login_stats_attrs[] = {
&iscsi_stat_login_attr_inst,
&iscsi_stat_login_attr_indx,
&iscsi_stat_login_attr_accepts,
&iscsi_stat_login_attr_other_fails,
&iscsi_stat_login_attr_redirects,
&iscsi_stat_login_attr_authorize_fails,
&iscsi_stat_login_attr_authenticate_fails,
&iscsi_stat_login_attr_negotiate_fails,
NULL,
};
const struct config_item_type iscsi_stat_login_cit = {
.ct_attrs = iscsi_stat_login_stats_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Target Logout Stats Table
*/
static struct iscsi_tiqn *iscsi_logout_stat_tiqn(struct config_item *item)
{
struct iscsi_wwn_stat_grps *igrps = container_of(to_config_group(item),
struct iscsi_wwn_stat_grps, iscsi_logout_stats_group);
return container_of(igrps, struct iscsi_tiqn, tiqn_stat_grps);
}
static ssize_t iscsi_stat_logout_inst_show(struct config_item *item, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n",
iscsi_logout_stat_tiqn(item)->tiqn_index);
}
static ssize_t iscsi_stat_logout_indx_show(struct config_item *item, char *page)
{
return snprintf(page, PAGE_SIZE, "%u\n", ISCSI_NODE_INDEX);
}
static ssize_t iscsi_stat_logout_normal_logouts_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_logout_stat_tiqn(item);
struct iscsi_logout_stats *lstats = &tiqn->logout_stats;
return snprintf(page, PAGE_SIZE, "%u\n", lstats->normal_logouts);
}
static ssize_t iscsi_stat_logout_abnormal_logouts_show(struct config_item *item,
char *page)
{
struct iscsi_tiqn *tiqn = iscsi_logout_stat_tiqn(item);
struct iscsi_logout_stats *lstats = &tiqn->logout_stats;
return snprintf(page, PAGE_SIZE, "%u\n", lstats->abnormal_logouts);
}
CONFIGFS_ATTR_RO(iscsi_stat_logout_, inst);
CONFIGFS_ATTR_RO(iscsi_stat_logout_, indx);
CONFIGFS_ATTR_RO(iscsi_stat_logout_, normal_logouts);
CONFIGFS_ATTR_RO(iscsi_stat_logout_, abnormal_logouts);
static struct configfs_attribute *iscsi_stat_logout_stats_attrs[] = {
&iscsi_stat_logout_attr_inst,
&iscsi_stat_logout_attr_indx,
&iscsi_stat_logout_attr_normal_logouts,
&iscsi_stat_logout_attr_abnormal_logouts,
NULL,
};
const struct config_item_type iscsi_stat_logout_cit = {
.ct_attrs = iscsi_stat_logout_stats_attrs,
.ct_owner = THIS_MODULE,
};
/*
* Session Stats Table
*/
static struct iscsi_node_acl *iscsi_stat_nacl(struct config_item *item)
{
struct iscsi_node_stat_grps *igrps = container_of(to_config_group(item),
struct iscsi_node_stat_grps, iscsi_sess_stats_group);
return container_of(igrps, struct iscsi_node_acl, node_stat_grps);
}
static ssize_t iscsi_stat_sess_inst_show(struct config_item *item, char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_wwn *wwn = acl->se_node_acl.se_tpg->se_tpg_wwn;
struct iscsi_tiqn *tiqn = container_of(wwn,
struct iscsi_tiqn, tiqn_wwn);
return snprintf(page, PAGE_SIZE, "%u\n", tiqn->tiqn_index);
}
static ssize_t iscsi_stat_sess_node_show(struct config_item *item, char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsit_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%u\n",
sess->sess_ops->SessionType ? 0 : ISCSI_NODE_INDEX);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
static ssize_t iscsi_stat_sess_indx_show(struct config_item *item, char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsit_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%u\n",
sess->session_index);
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
static ssize_t iscsi_stat_sess_cmd_pdus_show(struct config_item *item,
char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsit_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%lu\n",
atomic_long_read(&sess->cmd_pdus));
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
static ssize_t iscsi_stat_sess_rsp_pdus_show(struct config_item *item,
char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsit_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%lu\n",
atomic_long_read(&sess->rsp_pdus));
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
static ssize_t iscsi_stat_sess_txdata_octs_show(struct config_item *item,
char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsit_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%lu\n",
atomic_long_read(&sess->tx_data_octets));
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
static ssize_t iscsi_stat_sess_rxdata_octs_show(struct config_item *item,
char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsit_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%lu\n",
atomic_long_read(&sess->rx_data_octets));
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
static ssize_t iscsi_stat_sess_conn_digest_errors_show(struct config_item *item,
char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsit_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%lu\n",
atomic_long_read(&sess->conn_digest_errors));
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
static ssize_t iscsi_stat_sess_conn_timeout_errors_show(
struct config_item *item, char *page)
{
struct iscsi_node_acl *acl = iscsi_stat_nacl(item);
struct se_node_acl *se_nacl = &acl->se_node_acl;
struct iscsit_session *sess;
struct se_session *se_sess;
ssize_t ret = 0;
spin_lock_bh(&se_nacl->nacl_sess_lock);
se_sess = se_nacl->nacl_sess;
if (se_sess) {
sess = se_sess->fabric_sess_ptr;
if (sess)
ret = snprintf(page, PAGE_SIZE, "%lu\n",
atomic_long_read(&sess->conn_timeout_errors));
}
spin_unlock_bh(&se_nacl->nacl_sess_lock);
return ret;
}
CONFIGFS_ATTR_RO(iscsi_stat_sess_, inst);
CONFIGFS_ATTR_RO(iscsi_stat_sess_, node);
CONFIGFS_ATTR_RO(iscsi_stat_sess_, indx);
CONFIGFS_ATTR_RO(iscsi_stat_sess_, cmd_pdus);
CONFIGFS_ATTR_RO(iscsi_stat_sess_, rsp_pdus);
CONFIGFS_ATTR_RO(iscsi_stat_sess_, txdata_octs);
CONFIGFS_ATTR_RO(iscsi_stat_sess_, rxdata_octs);
CONFIGFS_ATTR_RO(iscsi_stat_sess_, conn_digest_errors);
CONFIGFS_ATTR_RO(iscsi_stat_sess_, conn_timeout_errors);
static struct configfs_attribute *iscsi_stat_sess_stats_attrs[] = {
&iscsi_stat_sess_attr_inst,
&iscsi_stat_sess_attr_node,
&iscsi_stat_sess_attr_indx,
&iscsi_stat_sess_attr_cmd_pdus,
&iscsi_stat_sess_attr_rsp_pdus,
&iscsi_stat_sess_attr_txdata_octs,
&iscsi_stat_sess_attr_rxdata_octs,
&iscsi_stat_sess_attr_conn_digest_errors,
&iscsi_stat_sess_attr_conn_timeout_errors,
NULL,
};
const struct config_item_type iscsi_stat_sess_cit = {
.ct_attrs = iscsi_stat_sess_stats_attrs,
.ct_owner = THIS_MODULE,
};
@@ -0,0 +1,842 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* This file contains the iSCSI Target specific Task Management functions.
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <linux/unaligned.h>
#include <scsi/scsi_proto.h>
#include <scsi/iscsi_proto.h>
#include <target/target_core_base.h>
#include <target/target_core_fabric.h>
#include <target/iscsi/iscsi_transport.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_seq_pdu_list.h"
#include "iscsi_target_datain_values.h"
#include "iscsi_target_device.h"
#include "iscsi_target_erl0.h"
#include "iscsi_target_erl1.h"
#include "iscsi_target_erl2.h"
#include "iscsi_target_tmr.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
u8 iscsit_tmr_abort_task(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
struct iscsit_cmd *ref_cmd;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_tmr_req *tmr_req = cmd->tmr_req;
struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req;
struct iscsi_tm *hdr = (struct iscsi_tm *) buf;
ref_cmd = iscsit_find_cmd_from_itt(conn, hdr->rtt);
if (!ref_cmd) {
pr_err("Unable to locate RefTaskTag: 0x%08x on CID:"
" %hu.\n", hdr->rtt, conn->cid);
return (iscsi_sna_gte(be32_to_cpu(hdr->refcmdsn), conn->sess->exp_cmd_sn) &&
iscsi_sna_lte(be32_to_cpu(hdr->refcmdsn), (u32) atomic_read(&conn->sess->max_cmd_sn))) ?
ISCSI_TMF_RSP_COMPLETE : ISCSI_TMF_RSP_NO_TASK;
}
if (ref_cmd->cmd_sn != be32_to_cpu(hdr->refcmdsn)) {
pr_err("RefCmdSN 0x%08x does not equal"
" task's CmdSN 0x%08x. Rejecting ABORT_TASK.\n",
hdr->refcmdsn, ref_cmd->cmd_sn);
return ISCSI_TMF_RSP_REJECTED;
}
se_tmr->ref_task_tag = (__force u32)hdr->rtt;
tmr_req->ref_cmd = ref_cmd;
tmr_req->exp_data_sn = be32_to_cpu(hdr->exp_datasn);
return ISCSI_TMF_RSP_COMPLETE;
}
/*
* Called from iscsit_handle_task_mgt_cmd().
*/
int iscsit_tmr_task_warm_reset(
struct iscsit_conn *conn,
struct iscsi_tmr_req *tmr_req,
unsigned char *buf)
{
struct iscsit_session *sess = conn->sess;
struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);
if (!na->tmr_warm_reset) {
pr_err("TMR Opcode TARGET_WARM_RESET authorization"
" failed for Initiator Node: %s\n",
sess->se_sess->se_node_acl->initiatorname);
return -1;
}
/*
* Do the real work in transport_generic_do_tmr().
*/
return 0;
}
int iscsit_tmr_task_cold_reset(
struct iscsit_conn *conn,
struct iscsi_tmr_req *tmr_req,
unsigned char *buf)
{
struct iscsit_session *sess = conn->sess;
struct iscsi_node_attrib *na = iscsit_tpg_get_node_attrib(sess);
if (!na->tmr_cold_reset) {
pr_err("TMR Opcode TARGET_COLD_RESET authorization"
" failed for Initiator Node: %s\n",
sess->se_sess->se_node_acl->initiatorname);
return -1;
}
/*
* Do the real work in transport_generic_do_tmr().
*/
return 0;
}
u8 iscsit_tmr_task_reassign(
struct iscsit_cmd *cmd,
unsigned char *buf)
{
struct iscsit_cmd *ref_cmd = NULL;
struct iscsit_conn *conn = cmd->conn;
struct iscsi_conn_recovery *cr = NULL;
struct iscsi_tmr_req *tmr_req = cmd->tmr_req;
struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req;
struct iscsi_tm *hdr = (struct iscsi_tm *) buf;
u64 ref_lun;
int ret;
pr_debug("Got TASK_REASSIGN TMR ITT: 0x%08x,"
" RefTaskTag: 0x%08x, ExpDataSN: 0x%08x, CID: %hu\n",
hdr->itt, hdr->rtt, hdr->exp_datasn, conn->cid);
if (conn->sess->sess_ops->ErrorRecoveryLevel != 2) {
pr_err("TMR TASK_REASSIGN not supported in ERL<2,"
" ignoring request.\n");
return ISCSI_TMF_RSP_NOT_SUPPORTED;
}
ret = iscsit_find_cmd_for_recovery(conn->sess, &ref_cmd, &cr, hdr->rtt);
if (ret == -2) {
pr_err("Command ITT: 0x%08x is still alligent to CID:"
" %hu\n", ref_cmd->init_task_tag, cr->cid);
return ISCSI_TMF_RSP_TASK_ALLEGIANT;
} else if (ret == -1) {
pr_err("Unable to locate RefTaskTag: 0x%08x in"
" connection recovery command list.\n", hdr->rtt);
return ISCSI_TMF_RSP_NO_TASK;
}
/*
* Temporary check to prevent connection recovery for
* connections with a differing Max*DataSegmentLength.
*/
if (cr->maxrecvdatasegmentlength !=
conn->conn_ops->MaxRecvDataSegmentLength) {
pr_err("Unable to perform connection recovery for"
" differing MaxRecvDataSegmentLength, rejecting"
" TMR TASK_REASSIGN.\n");
return ISCSI_TMF_RSP_REJECTED;
}
if (cr->maxxmitdatasegmentlength !=
conn->conn_ops->MaxXmitDataSegmentLength) {
pr_err("Unable to perform connection recovery for"
" differing MaxXmitDataSegmentLength, rejecting"
" TMR TASK_REASSIGN.\n");
return ISCSI_TMF_RSP_REJECTED;
}
ref_lun = scsilun_to_int(&hdr->lun);
if (ref_lun != ref_cmd->se_cmd.orig_fe_lun) {
pr_err("Unable to perform connection recovery for"
" differing ref_lun: %llu ref_cmd orig_fe_lun: %llu\n",
ref_lun, ref_cmd->se_cmd.orig_fe_lun);
return ISCSI_TMF_RSP_REJECTED;
}
se_tmr->ref_task_tag = (__force u32)hdr->rtt;
tmr_req->ref_cmd = ref_cmd;
tmr_req->exp_data_sn = be32_to_cpu(hdr->exp_datasn);
tmr_req->conn_recovery = cr;
tmr_req->task_reassign = 1;
/*
* Command can now be reassigned to a new connection.
* The task management response must be sent before the
* reassignment actually happens. See iscsi_tmr_post_handler().
*/
return ISCSI_TMF_RSP_COMPLETE;
}
static void iscsit_task_reassign_remove_cmd(
struct iscsit_cmd *cmd,
struct iscsi_conn_recovery *cr,
struct iscsit_session *sess)
{
int ret;
spin_lock(&cr->conn_recovery_cmd_lock);
ret = iscsit_remove_cmd_from_connection_recovery(cmd, sess);
spin_unlock(&cr->conn_recovery_cmd_lock);
if (!ret) {
pr_debug("iSCSI connection recovery successful for CID:"
" %hu on SID: %u\n", cr->cid, sess->sid);
iscsit_remove_active_connection_recovery_entry(cr, sess);
}
}
static int iscsit_task_reassign_complete_nop_out(
struct iscsi_tmr_req *tmr_req,
struct iscsit_conn *conn)
{
struct iscsit_cmd *cmd = tmr_req->ref_cmd;
struct iscsi_conn_recovery *cr;
if (!cmd->cr) {
pr_err("struct iscsi_conn_recovery pointer for ITT: 0x%08x"
" is NULL!\n", cmd->init_task_tag);
return -1;
}
cr = cmd->cr;
/*
* Reset the StatSN so a new one for this commands new connection
* will be assigned.
* Reset the ExpStatSN as well so we may receive Status SNACKs.
*/
cmd->stat_sn = cmd->exp_stat_sn = 0;
iscsit_task_reassign_remove_cmd(cmd, cr, conn->sess);
spin_lock_bh(&conn->cmd_lock);
list_add_tail(&cmd->i_conn_node, &conn->conn_cmd_list);
spin_unlock_bh(&conn->cmd_lock);
cmd->i_state = ISTATE_SEND_NOPIN;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
static int iscsit_task_reassign_complete_write(
struct iscsit_cmd *cmd,
struct iscsi_tmr_req *tmr_req)
{
int no_build_r2ts = 0;
u32 length = 0, offset = 0;
struct iscsit_conn *conn = cmd->conn;
struct se_cmd *se_cmd = &cmd->se_cmd;
/*
* The Initiator must not send a R2T SNACK with a Begrun less than
* the TMR TASK_REASSIGN's ExpDataSN.
*/
if (!tmr_req->exp_data_sn) {
cmd->cmd_flags &= ~ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = 0;
} else {
cmd->cmd_flags |= ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = (tmr_req->exp_data_sn - 1);
}
/*
* The TMR TASK_REASSIGN's ExpDataSN contains the next R2TSN the
* Initiator is expecting. The Target controls all WRITE operations
* so if we have received all DataOUT we can safety ignore Initiator.
*/
if (cmd->cmd_flags & ICF_GOT_LAST_DATAOUT) {
if (!(cmd->se_cmd.transport_state & CMD_T_SENT)) {
pr_debug("WRITE ITT: 0x%08x: t_state: %d"
" never sent to transport\n",
cmd->init_task_tag, cmd->se_cmd.t_state);
target_execute_cmd(se_cmd);
return 0;
}
cmd->i_state = ISTATE_SEND_STATUS;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
/*
* Special case to deal with DataSequenceInOrder=No and Non-Immeidate
* Unsolicited DataOut.
*/
if (cmd->unsolicited_data) {
cmd->unsolicited_data = 0;
offset = cmd->next_burst_len = cmd->write_data_done;
if ((conn->sess->sess_ops->FirstBurstLength - offset) >=
cmd->se_cmd.data_length) {
no_build_r2ts = 1;
length = (cmd->se_cmd.data_length - offset);
} else
length = (conn->sess->sess_ops->FirstBurstLength - offset);
spin_lock_bh(&cmd->r2t_lock);
if (iscsit_add_r2t_to_list(cmd, offset, length, 0, 0) < 0) {
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
cmd->outstanding_r2ts++;
spin_unlock_bh(&cmd->r2t_lock);
if (no_build_r2ts)
return 0;
}
/*
* iscsit_build_r2ts_for_cmd() can handle the rest from here.
*/
return conn->conn_transport->iscsit_get_dataout(conn, cmd, true);
}
static int iscsit_task_reassign_complete_read(
struct iscsit_cmd *cmd,
struct iscsi_tmr_req *tmr_req)
{
struct iscsit_conn *conn = cmd->conn;
struct iscsi_datain_req *dr;
struct se_cmd *se_cmd = &cmd->se_cmd;
/*
* The Initiator must not send a Data SNACK with a BegRun less than
* the TMR TASK_REASSIGN's ExpDataSN.
*/
if (!tmr_req->exp_data_sn) {
cmd->cmd_flags &= ~ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = 0;
} else {
cmd->cmd_flags |= ICF_GOT_DATACK_SNACK;
cmd->acked_data_sn = (tmr_req->exp_data_sn - 1);
}
if (!(cmd->se_cmd.transport_state & CMD_T_SENT)) {
pr_debug("READ ITT: 0x%08x: t_state: %d never sent to"
" transport\n", cmd->init_task_tag,
cmd->se_cmd.t_state);
target_submit(se_cmd);
return 0;
}
if (!(se_cmd->transport_state & CMD_T_COMPLETE)) {
pr_err("READ ITT: 0x%08x: t_state: %d, never returned"
" from transport\n", cmd->init_task_tag,
cmd->se_cmd.t_state);
return -1;
}
dr = iscsit_allocate_datain_req();
if (!dr)
return -1;
/*
* The TMR TASK_REASSIGN's ExpDataSN contains the next DataSN the
* Initiator is expecting.
*/
dr->data_sn = dr->begrun = tmr_req->exp_data_sn;
dr->runlength = 0;
dr->generate_recovery_values = 1;
dr->recovery = DATAIN_CONNECTION_RECOVERY;
iscsit_attach_datain_req(cmd, dr);
cmd->i_state = ISTATE_SEND_DATAIN;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
static int iscsit_task_reassign_complete_none(
struct iscsit_cmd *cmd,
struct iscsi_tmr_req *tmr_req)
{
struct iscsit_conn *conn = cmd->conn;
cmd->i_state = ISTATE_SEND_STATUS;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
static int iscsit_task_reassign_complete_scsi_cmnd(
struct iscsi_tmr_req *tmr_req,
struct iscsit_conn *conn)
{
struct iscsit_cmd *cmd = tmr_req->ref_cmd;
struct iscsi_conn_recovery *cr;
if (!cmd->cr) {
pr_err("struct iscsi_conn_recovery pointer for ITT: 0x%08x"
" is NULL!\n", cmd->init_task_tag);
return -1;
}
cr = cmd->cr;
/*
* Reset the StatSN so a new one for this commands new connection
* will be assigned.
* Reset the ExpStatSN as well so we may receive Status SNACKs.
*/
cmd->stat_sn = cmd->exp_stat_sn = 0;
iscsit_task_reassign_remove_cmd(cmd, cr, conn->sess);
spin_lock_bh(&conn->cmd_lock);
list_add_tail(&cmd->i_conn_node, &conn->conn_cmd_list);
spin_unlock_bh(&conn->cmd_lock);
if (cmd->se_cmd.se_cmd_flags & SCF_SENT_CHECK_CONDITION) {
cmd->i_state = ISTATE_SEND_STATUS;
iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state);
return 0;
}
switch (cmd->data_direction) {
case DMA_TO_DEVICE:
return iscsit_task_reassign_complete_write(cmd, tmr_req);
case DMA_FROM_DEVICE:
return iscsit_task_reassign_complete_read(cmd, tmr_req);
case DMA_NONE:
return iscsit_task_reassign_complete_none(cmd, tmr_req);
default:
pr_err("Unknown cmd->data_direction: 0x%02x\n",
cmd->data_direction);
return -1;
}
return 0;
}
static int iscsit_task_reassign_complete(
struct iscsi_tmr_req *tmr_req,
struct iscsit_conn *conn)
{
struct iscsit_cmd *cmd;
int ret = 0;
if (!tmr_req->ref_cmd) {
pr_err("TMR Request is missing a RefCmd struct iscsit_cmd.\n");
return -1;
}
cmd = tmr_req->ref_cmd;
cmd->conn = conn;
switch (cmd->iscsi_opcode) {
case ISCSI_OP_NOOP_OUT:
ret = iscsit_task_reassign_complete_nop_out(tmr_req, conn);
break;
case ISCSI_OP_SCSI_CMD:
ret = iscsit_task_reassign_complete_scsi_cmnd(tmr_req, conn);
break;
default:
pr_err("Illegal iSCSI Opcode 0x%02x during"
" command reallegiance\n", cmd->iscsi_opcode);
return -1;
}
if (ret != 0)
return ret;
pr_debug("Completed connection reallegiance for Opcode: 0x%02x,"
" ITT: 0x%08x to CID: %hu.\n", cmd->iscsi_opcode,
cmd->init_task_tag, conn->cid);
return 0;
}
/*
* Handles special after-the-fact actions related to TMRs.
* Right now the only one that its really needed for is
* connection recovery releated TASK_REASSIGN.
*/
int iscsit_tmr_post_handler(struct iscsit_cmd *cmd, struct iscsit_conn *conn)
{
struct iscsi_tmr_req *tmr_req = cmd->tmr_req;
struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req;
if (tmr_req->task_reassign &&
(se_tmr->response == ISCSI_TMF_RSP_COMPLETE))
return iscsit_task_reassign_complete(tmr_req, conn);
return 0;
}
EXPORT_SYMBOL(iscsit_tmr_post_handler);
/*
* Nothing to do here, but leave it for good measure. :-)
*/
static int iscsit_task_reassign_prepare_read(
struct iscsi_tmr_req *tmr_req,
struct iscsit_conn *conn)
{
return 0;
}
static void iscsit_task_reassign_prepare_unsolicited_dataout(
struct iscsit_cmd *cmd,
struct iscsit_conn *conn)
{
int i, j;
struct iscsi_pdu *pdu = NULL;
struct iscsi_seq *seq = NULL;
if (conn->sess->sess_ops->DataSequenceInOrder) {
cmd->data_sn = 0;
if (cmd->immediate_data)
cmd->r2t_offset += (cmd->first_burst_len -
cmd->seq_start_offset);
if (conn->sess->sess_ops->DataPDUInOrder) {
cmd->write_data_done -= (cmd->immediate_data) ?
(cmd->first_burst_len -
cmd->seq_start_offset) :
cmd->first_burst_len;
cmd->first_burst_len = 0;
return;
}
for (i = 0; i < cmd->pdu_count; i++) {
pdu = &cmd->pdu_list[i];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
if ((pdu->offset >= cmd->seq_start_offset) &&
((pdu->offset + pdu->length) <=
cmd->seq_end_offset)) {
cmd->first_burst_len -= pdu->length;
cmd->write_data_done -= pdu->length;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
} else {
for (i = 0; i < cmd->seq_count; i++) {
seq = &cmd->seq_list[i];
if (seq->type != SEQTYPE_UNSOLICITED)
continue;
cmd->write_data_done -=
(seq->offset - seq->orig_offset);
cmd->first_burst_len = 0;
seq->data_sn = 0;
seq->offset = seq->orig_offset;
seq->next_burst_len = 0;
seq->status = DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY;
if (conn->sess->sess_ops->DataPDUInOrder)
continue;
for (j = 0; j < seq->pdu_count; j++) {
pdu = &cmd->pdu_list[j+seq->pdu_start];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
}
}
static int iscsit_task_reassign_prepare_write(
struct iscsi_tmr_req *tmr_req,
struct iscsit_conn *conn)
{
struct iscsit_cmd *cmd = tmr_req->ref_cmd;
struct iscsi_pdu *pdu = NULL;
struct iscsi_r2t *r2t = NULL, *r2t_tmp;
int first_incomplete_r2t = 1, i = 0;
/*
* The command was in the process of receiving Unsolicited DataOUT when
* the connection failed.
*/
if (cmd->unsolicited_data)
iscsit_task_reassign_prepare_unsolicited_dataout(cmd, conn);
/*
* The Initiator is requesting R2Ts starting from zero, skip
* checking acknowledged R2Ts and start checking struct iscsi_r2ts
* greater than zero.
*/
if (!tmr_req->exp_data_sn)
goto drop_unacknowledged_r2ts;
/*
* We now check that the PDUs in DataOUT sequences below
* the TMR TASK_REASSIGN ExpDataSN (R2TSN the Initiator is
* expecting next) have all the DataOUT they require to complete
* the DataOUT sequence. First scan from R2TSN 0 to TMR
* TASK_REASSIGN ExpDataSN-1.
*
* If we have not received all DataOUT in question, we must
* make sure to make the appropriate changes to values in
* struct iscsit_cmd (and elsewhere depending on session parameters)
* so iscsit_build_r2ts_for_cmd() in iscsit_task_reassign_complete_write()
* will resend a new R2T for the DataOUT sequences in question.
*/
spin_lock_bh(&cmd->r2t_lock);
if (list_empty(&cmd->cmd_r2t_list)) {
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
list_for_each_entry(r2t, &cmd->cmd_r2t_list, r2t_list) {
if (r2t->r2t_sn >= tmr_req->exp_data_sn)
continue;
/*
* Safely ignore Recovery R2Ts and R2Ts that have completed
* DataOUT sequences.
*/
if (r2t->seq_complete)
continue;
if (r2t->recovery_r2t)
continue;
/*
* DataSequenceInOrder=Yes:
*
* Taking into account the iSCSI implementation requirement of
* MaxOutstandingR2T=1 while ErrorRecoveryLevel>0 and
* DataSequenceInOrder=Yes, we must take into consideration
* the following:
*
* DataSequenceInOrder=No:
*
* Taking into account that the Initiator controls the (possibly
* random) PDU Order in (possibly random) Sequence Order of
* DataOUT the target requests with R2Ts, we must take into
* consideration the following:
*
* DataPDUInOrder=Yes for DataSequenceInOrder=[Yes,No]:
*
* While processing non-complete R2T DataOUT sequence requests
* the Target will re-request only the total sequence length
* minus current received offset. This is because we must
* assume the initiator will continue sending DataOUT from the
* last PDU before the connection failed.
*
* DataPDUInOrder=No for DataSequenceInOrder=[Yes,No]:
*
* While processing non-complete R2T DataOUT sequence requests
* the Target will re-request the entire DataOUT sequence if
* any single PDU is missing from the sequence. This is because
* we have no logical method to determine the next PDU offset,
* and we must assume the Initiator will be sending any random
* PDU offset in the current sequence after TASK_REASSIGN
* has completed.
*/
if (conn->sess->sess_ops->DataSequenceInOrder) {
if (!first_incomplete_r2t) {
cmd->r2t_offset -= r2t->xfer_len;
goto next;
}
if (conn->sess->sess_ops->DataPDUInOrder) {
cmd->data_sn = 0;
cmd->r2t_offset -= (r2t->xfer_len -
cmd->next_burst_len);
first_incomplete_r2t = 0;
goto next;
}
cmd->data_sn = 0;
cmd->r2t_offset -= r2t->xfer_len;
for (i = 0; i < cmd->pdu_count; i++) {
pdu = &cmd->pdu_list[i];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
if ((pdu->offset >= r2t->offset) &&
(pdu->offset < (r2t->offset +
r2t->xfer_len))) {
cmd->next_burst_len -= pdu->length;
cmd->write_data_done -= pdu->length;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
first_incomplete_r2t = 0;
} else {
struct iscsi_seq *seq;
seq = iscsit_get_seq_holder(cmd, r2t->offset,
r2t->xfer_len);
if (!seq) {
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
cmd->write_data_done -=
(seq->offset - seq->orig_offset);
seq->data_sn = 0;
seq->offset = seq->orig_offset;
seq->next_burst_len = 0;
seq->status = DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY;
cmd->seq_send_order--;
if (conn->sess->sess_ops->DataPDUInOrder)
goto next;
for (i = 0; i < seq->pdu_count; i++) {
pdu = &cmd->pdu_list[i+seq->pdu_start];
if (pdu->status != ISCSI_PDU_RECEIVED_OK)
continue;
pdu->status = ISCSI_PDU_NOT_RECEIVED;
}
}
next:
cmd->outstanding_r2ts--;
}
spin_unlock_bh(&cmd->r2t_lock);
/*
* We now drop all unacknowledged R2Ts, ie: ExpDataSN from TMR
* TASK_REASSIGN to the last R2T in the list.. We are also careful
* to check that the Initiator is not requesting R2Ts for DataOUT
* sequences it has already completed.
*
* Free each R2T in question and adjust values in struct iscsit_cmd
* accordingly so iscsit_build_r2ts_for_cmd() do the rest of
* the work after the TMR TASK_REASSIGN Response is sent.
*/
drop_unacknowledged_r2ts:
cmd->cmd_flags &= ~ICF_SENT_LAST_R2T;
cmd->r2t_sn = tmr_req->exp_data_sn;
spin_lock_bh(&cmd->r2t_lock);
list_for_each_entry_safe(r2t, r2t_tmp, &cmd->cmd_r2t_list, r2t_list) {
/*
* Skip up to the R2T Sequence number provided by the
* iSCSI TASK_REASSIGN TMR
*/
if (r2t->r2t_sn < tmr_req->exp_data_sn)
continue;
if (r2t->seq_complete) {
pr_err("Initiator is requesting R2Ts from"
" R2TSN: 0x%08x, but R2TSN: 0x%08x, Offset: %u,"
" Length: %u is already complete."
" BAD INITIATOR ERL=2 IMPLEMENTATION!\n",
tmr_req->exp_data_sn, r2t->r2t_sn,
r2t->offset, r2t->xfer_len);
spin_unlock_bh(&cmd->r2t_lock);
return -1;
}
if (r2t->recovery_r2t) {
iscsit_free_r2t(r2t, cmd);
continue;
}
/* DataSequenceInOrder=Yes:
*
* Taking into account the iSCSI implementation requirement of
* MaxOutstandingR2T=1 while ErrorRecoveryLevel>0 and
* DataSequenceInOrder=Yes, it's safe to subtract the R2Ts
* entire transfer length from the commands R2T offset marker.
*
* DataSequenceInOrder=No:
*
* We subtract the difference from struct iscsi_seq between the
* current offset and original offset from cmd->write_data_done
* for account for DataOUT PDUs already received. Then reset
* the current offset to the original and zero out the current
* burst length, to make sure we re-request the entire DataOUT
* sequence.
*/
if (conn->sess->sess_ops->DataSequenceInOrder)
cmd->r2t_offset -= r2t->xfer_len;
else
cmd->seq_send_order--;
cmd->outstanding_r2ts--;
iscsit_free_r2t(r2t, cmd);
}
spin_unlock_bh(&cmd->r2t_lock);
return 0;
}
/*
* Performs sanity checks TMR TASK_REASSIGN's ExpDataSN for
* a given struct iscsit_cmd.
*/
int iscsit_check_task_reassign_expdatasn(
struct iscsi_tmr_req *tmr_req,
struct iscsit_conn *conn)
{
struct iscsit_cmd *ref_cmd = tmr_req->ref_cmd;
if (ref_cmd->iscsi_opcode != ISCSI_OP_SCSI_CMD)
return 0;
if (ref_cmd->se_cmd.se_cmd_flags & SCF_SENT_CHECK_CONDITION)
return 0;
if (ref_cmd->data_direction == DMA_NONE)
return 0;
/*
* For READs the TMR TASK_REASSIGNs ExpDataSN contains the next DataSN
* of DataIN the Initiator is expecting.
*
* Also check that the Initiator is not re-requesting DataIN that has
* already been acknowledged with a DataAck SNACK.
*/
if (ref_cmd->data_direction == DMA_FROM_DEVICE) {
if (tmr_req->exp_data_sn > ref_cmd->data_sn) {
pr_err("Received ExpDataSN: 0x%08x for READ"
" in TMR TASK_REASSIGN greater than command's"
" DataSN: 0x%08x.\n", tmr_req->exp_data_sn,
ref_cmd->data_sn);
return -1;
}
if ((ref_cmd->cmd_flags & ICF_GOT_DATACK_SNACK) &&
(tmr_req->exp_data_sn <= ref_cmd->acked_data_sn)) {
pr_err("Received ExpDataSN: 0x%08x for READ"
" in TMR TASK_REASSIGN for previously"
" acknowledged DataIN: 0x%08x,"
" protocol error\n", tmr_req->exp_data_sn,
ref_cmd->acked_data_sn);
return -1;
}
return iscsit_task_reassign_prepare_read(tmr_req, conn);
}
/*
* For WRITEs the TMR TASK_REASSIGNs ExpDataSN contains the next R2TSN
* for R2Ts the Initiator is expecting.
*
* Do the magic in iscsit_task_reassign_prepare_write().
*/
if (ref_cmd->data_direction == DMA_TO_DEVICE) {
if (tmr_req->exp_data_sn > ref_cmd->r2t_sn) {
pr_err("Received ExpDataSN: 0x%08x for WRITE"
" in TMR TASK_REASSIGN greater than command's"
" R2TSN: 0x%08x.\n", tmr_req->exp_data_sn,
ref_cmd->r2t_sn);
return -1;
}
return iscsit_task_reassign_prepare_write(tmr_req, conn);
}
pr_err("Unknown iSCSI data_direction: 0x%02x\n",
ref_cmd->data_direction);
return -1;
}
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_TMR_H
#define ISCSI_TARGET_TMR_H
#include <linux/types.h>
struct iscsit_cmd;
struct iscsit_conn;
struct iscsi_tmr_req;
extern u8 iscsit_tmr_abort_task(struct iscsit_cmd *, unsigned char *);
extern int iscsit_tmr_task_warm_reset(struct iscsit_conn *, struct iscsi_tmr_req *,
unsigned char *);
extern int iscsit_tmr_task_cold_reset(struct iscsit_conn *, struct iscsi_tmr_req *,
unsigned char *);
extern u8 iscsit_tmr_task_reassign(struct iscsit_cmd *, unsigned char *);
extern int iscsit_tmr_post_handler(struct iscsit_cmd *, struct iscsit_conn *);
extern int iscsit_check_task_reassign_expdatasn(struct iscsi_tmr_req *,
struct iscsit_conn *);
#endif /* ISCSI_TARGET_TMR_H */
@@ -0,0 +1,881 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
* This file contains iSCSI Target Portal Group related functions.
*
* (c) Copyright 2007-2013 Datera, Inc.
*
* Author: Nicholas A. Bellinger <nab@linux-iscsi.org>
*
******************************************************************************/
#include <linux/slab.h>
#include <target/target_core_base.h>
#include <target/target_core_fabric.h>
#include <target/iscsi/iscsi_target_core.h>
#include "iscsi_target_erl0.h"
#include "iscsi_target_login.h"
#include "iscsi_target_nodeattrib.h"
#include "iscsi_target_tpg.h"
#include "iscsi_target_util.h"
#include "iscsi_target.h"
#include "iscsi_target_parameters.h"
#include <target/iscsi/iscsi_transport.h>
struct iscsi_portal_group *iscsit_alloc_portal_group(struct iscsi_tiqn *tiqn, u16 tpgt)
{
struct iscsi_portal_group *tpg;
tpg = kzalloc_obj(struct iscsi_portal_group);
if (!tpg) {
pr_err("Unable to allocate struct iscsi_portal_group\n");
return NULL;
}
tpg->tpgt = tpgt;
tpg->tpg_state = TPG_STATE_FREE;
tpg->tpg_tiqn = tiqn;
INIT_LIST_HEAD(&tpg->tpg_gnp_list);
INIT_LIST_HEAD(&tpg->tpg_list);
mutex_init(&tpg->tpg_access_lock);
sema_init(&tpg->np_login_sem, 1);
spin_lock_init(&tpg->tpg_state_lock);
spin_lock_init(&tpg->tpg_np_lock);
return tpg;
}
static void iscsit_set_default_tpg_attribs(struct iscsi_portal_group *);
int iscsit_load_discovery_tpg(void)
{
struct iscsi_param *param;
struct iscsi_portal_group *tpg;
int ret;
tpg = iscsit_alloc_portal_group(NULL, 1);
if (!tpg) {
pr_err("Unable to allocate struct iscsi_portal_group\n");
return -1;
}
/*
* Save iscsi_ops pointer for special case discovery TPG that
* doesn't exist as se_wwn->wwn_group within configfs.
*/
tpg->tpg_se_tpg.se_tpg_tfo = &iscsi_ops;
ret = core_tpg_register(NULL, &tpg->tpg_se_tpg, -1);
if (ret < 0) {
kfree(tpg);
return -1;
}
tpg->sid = 1; /* First Assigned LIO Session ID */
iscsit_set_default_tpg_attribs(tpg);
if (iscsi_create_default_params(&tpg->param_list) < 0)
goto out;
/*
* By default we disable authentication for discovery sessions,
* this can be changed with:
*
* /sys/kernel/config/target/iscsi/discovery_auth/enforce_discovery_auth
*/
param = iscsi_find_param_from_key(AUTHMETHOD, tpg->param_list);
if (!param)
goto free_pl_out;
if (iscsi_update_param_value(param, "CHAP,None") < 0)
goto free_pl_out;
tpg->tpg_attrib.authentication = 0;
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_ACTIVE;
spin_unlock(&tpg->tpg_state_lock);
iscsit_global->discovery_tpg = tpg;
pr_debug("CORE[0] - Allocated Discovery TPG\n");
return 0;
free_pl_out:
iscsi_release_param_list(tpg->param_list);
out:
if (tpg->sid == 1)
core_tpg_deregister(&tpg->tpg_se_tpg);
kfree(tpg);
return -1;
}
void iscsit_release_discovery_tpg(void)
{
struct iscsi_portal_group *tpg = iscsit_global->discovery_tpg;
if (!tpg)
return;
iscsi_release_param_list(tpg->param_list);
core_tpg_deregister(&tpg->tpg_se_tpg);
kfree(tpg);
iscsit_global->discovery_tpg = NULL;
}
struct iscsi_portal_group *iscsit_get_tpg_from_np(
struct iscsi_tiqn *tiqn,
struct iscsi_np *np,
struct iscsi_tpg_np **tpg_np_out)
{
struct iscsi_portal_group *tpg = NULL;
struct iscsi_tpg_np *tpg_np;
spin_lock(&tiqn->tiqn_tpg_lock);
list_for_each_entry(tpg, &tiqn->tiqn_tpg_list, tpg_list) {
spin_lock(&tpg->tpg_state_lock);
if (tpg->tpg_state != TPG_STATE_ACTIVE) {
spin_unlock(&tpg->tpg_state_lock);
continue;
}
spin_unlock(&tpg->tpg_state_lock);
spin_lock(&tpg->tpg_np_lock);
list_for_each_entry(tpg_np, &tpg->tpg_gnp_list, tpg_np_list) {
if (tpg_np->tpg_np == np) {
*tpg_np_out = tpg_np;
kref_get(&tpg_np->tpg_np_kref);
spin_unlock(&tpg->tpg_np_lock);
spin_unlock(&tiqn->tiqn_tpg_lock);
return tpg;
}
}
spin_unlock(&tpg->tpg_np_lock);
}
spin_unlock(&tiqn->tiqn_tpg_lock);
return NULL;
}
int iscsit_get_tpg(
struct iscsi_portal_group *tpg)
{
return mutex_lock_interruptible(&tpg->tpg_access_lock);
}
void iscsit_put_tpg(struct iscsi_portal_group *tpg)
{
mutex_unlock(&tpg->tpg_access_lock);
}
static void iscsit_clear_tpg_np_login_thread(
struct iscsi_tpg_np *tpg_np,
struct iscsi_portal_group *tpg,
bool shutdown)
{
if (!tpg_np->tpg_np) {
pr_err("struct iscsi_tpg_np->tpg_np is NULL!\n");
return;
}
if (shutdown)
tpg_np->tpg_np->enabled = false;
iscsit_reset_np_thread(tpg_np->tpg_np, tpg_np, tpg, shutdown);
}
static void iscsit_clear_tpg_np_login_threads(
struct iscsi_portal_group *tpg,
bool shutdown)
{
struct iscsi_tpg_np *tpg_np;
spin_lock(&tpg->tpg_np_lock);
list_for_each_entry(tpg_np, &tpg->tpg_gnp_list, tpg_np_list) {
if (!tpg_np->tpg_np) {
pr_err("struct iscsi_tpg_np->tpg_np is NULL!\n");
continue;
}
spin_unlock(&tpg->tpg_np_lock);
iscsit_clear_tpg_np_login_thread(tpg_np, tpg, shutdown);
spin_lock(&tpg->tpg_np_lock);
}
spin_unlock(&tpg->tpg_np_lock);
}
static void iscsit_set_default_tpg_attribs(struct iscsi_portal_group *tpg)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
a->authentication = TA_AUTHENTICATION;
a->login_timeout = TA_LOGIN_TIMEOUT;
a->default_cmdsn_depth = TA_DEFAULT_CMDSN_DEPTH;
a->generate_node_acls = TA_GENERATE_NODE_ACLS;
a->cache_dynamic_acls = TA_CACHE_DYNAMIC_ACLS;
a->demo_mode_write_protect = TA_DEMO_MODE_WRITE_PROTECT;
a->prod_mode_write_protect = TA_PROD_MODE_WRITE_PROTECT;
a->demo_mode_discovery = TA_DEMO_MODE_DISCOVERY;
a->default_erl = TA_DEFAULT_ERL;
a->t10_pi = TA_DEFAULT_T10_PI;
a->fabric_prot_type = TA_DEFAULT_FABRIC_PROT_TYPE;
a->tpg_enabled_sendtargets = TA_DEFAULT_TPG_ENABLED_SENDTARGETS;
a->login_keys_workaround = TA_DEFAULT_LOGIN_KEYS_WORKAROUND;
}
int iscsit_tpg_add_portal_group(struct iscsi_tiqn *tiqn, struct iscsi_portal_group *tpg)
{
if (tpg->tpg_state != TPG_STATE_FREE) {
pr_err("Unable to add iSCSI Target Portal Group: %d"
" while not in TPG_STATE_FREE state.\n", tpg->tpgt);
return -EEXIST;
}
iscsit_set_default_tpg_attribs(tpg);
if (iscsi_create_default_params(&tpg->param_list) < 0)
goto err_out;
tpg->tpg_attrib.tpg = tpg;
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_INACTIVE;
spin_unlock(&tpg->tpg_state_lock);
spin_lock(&tiqn->tiqn_tpg_lock);
list_add_tail(&tpg->tpg_list, &tiqn->tiqn_tpg_list);
tiqn->tiqn_ntpgs++;
pr_debug("CORE[%s]_TPG[%hu] - Added iSCSI Target Portal Group\n",
tiqn->tiqn, tpg->tpgt);
spin_unlock(&tiqn->tiqn_tpg_lock);
return 0;
err_out:
if (tpg->param_list) {
iscsi_release_param_list(tpg->param_list);
tpg->param_list = NULL;
}
return -ENOMEM;
}
int iscsit_tpg_del_portal_group(
struct iscsi_tiqn *tiqn,
struct iscsi_portal_group *tpg,
int force)
{
u8 old_state = tpg->tpg_state;
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_INACTIVE;
spin_unlock(&tpg->tpg_state_lock);
if (iscsit_release_sessions_for_tpg(tpg, force) < 0) {
pr_err("Unable to delete iSCSI Target Portal Group:"
" %hu while active sessions exist, and force=0\n",
tpg->tpgt);
tpg->tpg_state = old_state;
return -EPERM;
}
if (tpg->param_list) {
iscsi_release_param_list(tpg->param_list);
tpg->param_list = NULL;
}
core_tpg_deregister(&tpg->tpg_se_tpg);
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_FREE;
spin_unlock(&tpg->tpg_state_lock);
spin_lock(&tiqn->tiqn_tpg_lock);
tiqn->tiqn_ntpgs--;
list_del(&tpg->tpg_list);
spin_unlock(&tiqn->tiqn_tpg_lock);
pr_debug("CORE[%s]_TPG[%hu] - Deleted iSCSI Target Portal Group\n",
tiqn->tiqn, tpg->tpgt);
kfree(tpg);
return 0;
}
int iscsit_tpg_enable_portal_group(struct iscsi_portal_group *tpg)
{
struct iscsi_param *param;
struct iscsi_tiqn *tiqn = tpg->tpg_tiqn;
int ret;
if (tpg->tpg_state == TPG_STATE_ACTIVE) {
pr_err("iSCSI target portal group: %hu is already"
" active, ignoring request.\n", tpg->tpgt);
return -EINVAL;
}
/*
* Make sure that AuthMethod does not contain None as an option
* unless explictly disabled. Set the default to CHAP if authentication
* is enforced (as per default), and remove the NONE option.
*/
param = iscsi_find_param_from_key(AUTHMETHOD, tpg->param_list);
if (!param)
return -EINVAL;
if (tpg->tpg_attrib.authentication) {
if (!strcmp(param->value, NONE)) {
ret = iscsi_update_param_value(param, CHAP);
if (ret)
goto err;
}
ret = iscsit_ta_authentication(tpg, 1);
if (ret < 0)
goto err;
}
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = TPG_STATE_ACTIVE;
spin_unlock(&tpg->tpg_state_lock);
spin_lock(&tiqn->tiqn_tpg_lock);
tiqn->tiqn_active_tpgs++;
pr_debug("iSCSI_TPG[%hu] - Enabled iSCSI Target Portal Group\n",
tpg->tpgt);
spin_unlock(&tiqn->tiqn_tpg_lock);
return 0;
err:
return ret;
}
int iscsit_tpg_disable_portal_group(struct iscsi_portal_group *tpg, int force)
{
struct iscsi_tiqn *tiqn;
u8 old_state = tpg->tpg_state;
spin_lock(&tpg->tpg_state_lock);
if (tpg->tpg_state == TPG_STATE_INACTIVE) {
pr_err("iSCSI Target Portal Group: %hu is already"
" inactive, ignoring request.\n", tpg->tpgt);
spin_unlock(&tpg->tpg_state_lock);
return -EINVAL;
}
tpg->tpg_state = TPG_STATE_INACTIVE;
spin_unlock(&tpg->tpg_state_lock);
iscsit_clear_tpg_np_login_threads(tpg, false);
if (iscsit_release_sessions_for_tpg(tpg, force) < 0) {
spin_lock(&tpg->tpg_state_lock);
tpg->tpg_state = old_state;
spin_unlock(&tpg->tpg_state_lock);
pr_err("Unable to disable iSCSI Target Portal Group:"
" %hu while active sessions exist, and force=0\n",
tpg->tpgt);
return -EPERM;
}
tiqn = tpg->tpg_tiqn;
if (!tiqn || (tpg == iscsit_global->discovery_tpg))
return 0;
spin_lock(&tiqn->tiqn_tpg_lock);
tiqn->tiqn_active_tpgs--;
pr_debug("iSCSI_TPG[%hu] - Disabled iSCSI Target Portal Group\n",
tpg->tpgt);
spin_unlock(&tiqn->tiqn_tpg_lock);
return 0;
}
struct iscsi_node_attrib *iscsit_tpg_get_node_attrib(
struct iscsit_session *sess)
{
struct se_session *se_sess = sess->se_sess;
struct se_node_acl *se_nacl = se_sess->se_node_acl;
struct iscsi_node_acl *acl = to_iscsi_nacl(se_nacl);
return &acl->node_attrib;
}
struct iscsi_tpg_np *iscsit_tpg_locate_child_np(
struct iscsi_tpg_np *tpg_np,
int network_transport)
{
struct iscsi_tpg_np *tpg_np_child, *tpg_np_child_tmp;
spin_lock(&tpg_np->tpg_np_parent_lock);
list_for_each_entry_safe(tpg_np_child, tpg_np_child_tmp,
&tpg_np->tpg_np_parent_list, tpg_np_child_list) {
if (tpg_np_child->tpg_np->np_network_transport ==
network_transport) {
spin_unlock(&tpg_np->tpg_np_parent_lock);
return tpg_np_child;
}
}
spin_unlock(&tpg_np->tpg_np_parent_lock);
return NULL;
}
static bool iscsit_tpg_check_network_portal(
struct iscsi_tiqn *tiqn,
struct sockaddr_storage *sockaddr,
int network_transport)
{
struct iscsi_portal_group *tpg;
struct iscsi_tpg_np *tpg_np;
struct iscsi_np *np;
bool match = false;
spin_lock(&tiqn->tiqn_tpg_lock);
list_for_each_entry(tpg, &tiqn->tiqn_tpg_list, tpg_list) {
spin_lock(&tpg->tpg_np_lock);
list_for_each_entry(tpg_np, &tpg->tpg_gnp_list, tpg_np_list) {
np = tpg_np->tpg_np;
match = iscsit_check_np_match(sockaddr, np,
network_transport);
if (match)
break;
}
spin_unlock(&tpg->tpg_np_lock);
if (match)
break;
}
spin_unlock(&tiqn->tiqn_tpg_lock);
return match;
}
struct iscsi_tpg_np *iscsit_tpg_add_network_portal(
struct iscsi_portal_group *tpg,
struct sockaddr_storage *sockaddr,
struct iscsi_tpg_np *tpg_np_parent,
int network_transport)
{
struct iscsi_np *np;
struct iscsi_tpg_np *tpg_np;
if (!tpg_np_parent) {
if (iscsit_tpg_check_network_portal(tpg->tpg_tiqn, sockaddr,
network_transport)) {
pr_err("Network Portal: %pISc already exists on a"
" different TPG on %s\n", sockaddr,
tpg->tpg_tiqn->tiqn);
return ERR_PTR(-EEXIST);
}
}
tpg_np = kzalloc_obj(struct iscsi_tpg_np);
if (!tpg_np) {
pr_err("Unable to allocate memory for"
" struct iscsi_tpg_np.\n");
return ERR_PTR(-ENOMEM);
}
np = iscsit_add_np(sockaddr, network_transport);
if (IS_ERR(np)) {
kfree(tpg_np);
return ERR_CAST(np);
}
INIT_LIST_HEAD(&tpg_np->tpg_np_list);
INIT_LIST_HEAD(&tpg_np->tpg_np_child_list);
INIT_LIST_HEAD(&tpg_np->tpg_np_parent_list);
spin_lock_init(&tpg_np->tpg_np_parent_lock);
init_completion(&tpg_np->tpg_np_comp);
kref_init(&tpg_np->tpg_np_kref);
tpg_np->tpg_np = np;
tpg_np->tpg = tpg;
spin_lock(&tpg->tpg_np_lock);
list_add_tail(&tpg_np->tpg_np_list, &tpg->tpg_gnp_list);
tpg->num_tpg_nps++;
if (tpg->tpg_tiqn)
tpg->tpg_tiqn->tiqn_num_tpg_nps++;
spin_unlock(&tpg->tpg_np_lock);
if (tpg_np_parent) {
tpg_np->tpg_np_parent = tpg_np_parent;
spin_lock(&tpg_np_parent->tpg_np_parent_lock);
list_add_tail(&tpg_np->tpg_np_child_list,
&tpg_np_parent->tpg_np_parent_list);
spin_unlock(&tpg_np_parent->tpg_np_parent_lock);
}
pr_debug("CORE[%s] - Added Network Portal: %pISpc,%hu on %s\n",
tpg->tpg_tiqn->tiqn, &np->np_sockaddr, tpg->tpgt,
np->np_transport->name);
return tpg_np;
}
static int iscsit_tpg_release_np(
struct iscsi_tpg_np *tpg_np,
struct iscsi_portal_group *tpg,
struct iscsi_np *np)
{
iscsit_clear_tpg_np_login_thread(tpg_np, tpg, true);
pr_debug("CORE[%s] - Removed Network Portal: %pISpc,%hu on %s\n",
tpg->tpg_tiqn->tiqn, &np->np_sockaddr, tpg->tpgt,
np->np_transport->name);
tpg_np->tpg_np = NULL;
tpg_np->tpg = NULL;
kfree(tpg_np);
/*
* iscsit_del_np() will shutdown struct iscsi_np when last TPG reference is released.
*/
return iscsit_del_np(np);
}
int iscsit_tpg_del_network_portal(
struct iscsi_portal_group *tpg,
struct iscsi_tpg_np *tpg_np)
{
struct iscsi_np *np;
struct iscsi_tpg_np *tpg_np_child, *tpg_np_child_tmp;
int ret = 0;
np = tpg_np->tpg_np;
if (!np) {
pr_err("Unable to locate struct iscsi_np from"
" struct iscsi_tpg_np\n");
return -EINVAL;
}
if (!tpg_np->tpg_np_parent) {
/*
* We are the parent tpg network portal. Release all of the
* child tpg_np's (eg: the non ISCSI_TCP ones) on our parent
* list first.
*/
list_for_each_entry_safe(tpg_np_child, tpg_np_child_tmp,
&tpg_np->tpg_np_parent_list,
tpg_np_child_list) {
ret = iscsit_tpg_del_network_portal(tpg, tpg_np_child);
if (ret < 0)
pr_err("iscsit_tpg_del_network_portal()"
" failed: %d\n", ret);
}
} else {
/*
* We are not the parent ISCSI_TCP tpg network portal. Release
* our own network portals from the child list.
*/
spin_lock(&tpg_np->tpg_np_parent->tpg_np_parent_lock);
list_del(&tpg_np->tpg_np_child_list);
spin_unlock(&tpg_np->tpg_np_parent->tpg_np_parent_lock);
}
spin_lock(&tpg->tpg_np_lock);
list_del(&tpg_np->tpg_np_list);
tpg->num_tpg_nps--;
if (tpg->tpg_tiqn)
tpg->tpg_tiqn->tiqn_num_tpg_nps--;
spin_unlock(&tpg->tpg_np_lock);
return iscsit_tpg_release_np(tpg_np, tpg, np);
}
int iscsit_ta_authentication(struct iscsi_portal_group *tpg, u32 authentication)
{
unsigned char buf1[256], buf2[256], *none = NULL;
int len;
struct iscsi_param *param;
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((authentication != 1) && (authentication != 0)) {
pr_err("Illegal value for authentication parameter:"
" %u, ignoring request.\n", authentication);
return -EINVAL;
}
memset(buf1, 0, sizeof(buf1));
memset(buf2, 0, sizeof(buf2));
param = iscsi_find_param_from_key(AUTHMETHOD, tpg->param_list);
if (!param)
return -EINVAL;
if (authentication) {
snprintf(buf1, sizeof(buf1), "%s", param->value);
none = strstr(buf1, NONE);
if (!none)
goto out;
if (!strncmp(none + 4, ",", 1)) {
if (!strcmp(buf1, none))
sprintf(buf2, "%s", none+5);
else {
none--;
*none = '\0';
len = sprintf(buf2, "%s", buf1);
none += 5;
sprintf(buf2 + len, "%s", none);
}
} else {
none--;
*none = '\0';
sprintf(buf2, "%s", buf1);
}
if (iscsi_update_param_value(param, buf2) < 0)
return -EINVAL;
} else {
snprintf(buf1, sizeof(buf1), "%s", param->value);
none = strstr(buf1, NONE);
if (none)
goto out;
strlcat(buf1, "," NONE, sizeof(buf1));
if (iscsi_update_param_value(param, buf1) < 0)
return -EINVAL;
}
out:
a->authentication = authentication;
pr_debug("%s iSCSI Authentication Methods for TPG: %hu.\n",
a->authentication ? "Enforcing" : "Disabling", tpg->tpgt);
return 0;
}
int iscsit_ta_login_timeout(
struct iscsi_portal_group *tpg,
u32 login_timeout)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if (login_timeout > TA_LOGIN_TIMEOUT_MAX) {
pr_err("Requested Login Timeout %u larger than maximum"
" %u\n", login_timeout, TA_LOGIN_TIMEOUT_MAX);
return -EINVAL;
} else if (login_timeout < TA_LOGIN_TIMEOUT_MIN) {
pr_err("Requested Logout Timeout %u smaller than"
" minimum %u\n", login_timeout, TA_LOGIN_TIMEOUT_MIN);
return -EINVAL;
}
a->login_timeout = login_timeout;
pr_debug("Set Logout Timeout to %u for Target Portal Group"
" %hu\n", a->login_timeout, tpg->tpgt);
return 0;
}
int iscsit_ta_generate_node_acls(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->generate_node_acls = flag;
pr_debug("iSCSI_TPG[%hu] - Generate Initiator Portal Group ACLs: %s\n",
tpg->tpgt, (a->generate_node_acls) ? "Enabled" : "Disabled");
if (flag == 1 && a->cache_dynamic_acls == 0) {
pr_debug("Explicitly setting cache_dynamic_acls=1 when "
"generate_node_acls=1\n");
a->cache_dynamic_acls = 1;
}
return 0;
}
int iscsit_ta_default_cmdsn_depth(
struct iscsi_portal_group *tpg,
u32 tcq_depth)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if (tcq_depth > TA_DEFAULT_CMDSN_DEPTH_MAX) {
pr_err("Requested Default Queue Depth: %u larger"
" than maximum %u\n", tcq_depth,
TA_DEFAULT_CMDSN_DEPTH_MAX);
return -EINVAL;
} else if (tcq_depth < TA_DEFAULT_CMDSN_DEPTH_MIN) {
pr_err("Requested Default Queue Depth: %u smaller"
" than minimum %u\n", tcq_depth,
TA_DEFAULT_CMDSN_DEPTH_MIN);
return -EINVAL;
}
a->default_cmdsn_depth = tcq_depth;
pr_debug("iSCSI_TPG[%hu] - Set Default CmdSN TCQ Depth to %u\n",
tpg->tpgt, a->default_cmdsn_depth);
return 0;
}
int iscsit_ta_cache_dynamic_acls(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
if (a->generate_node_acls == 1 && flag == 0) {
pr_debug("Skipping cache_dynamic_acls=0 when"
" generate_node_acls=1\n");
return 0;
}
a->cache_dynamic_acls = flag;
pr_debug("iSCSI_TPG[%hu] - Cache Dynamic Initiator Portal Group"
" ACLs %s\n", tpg->tpgt, (a->cache_dynamic_acls) ?
"Enabled" : "Disabled");
return 0;
}
int iscsit_ta_demo_mode_write_protect(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->demo_mode_write_protect = flag;
pr_debug("iSCSI_TPG[%hu] - Demo Mode Write Protect bit: %s\n",
tpg->tpgt, (a->demo_mode_write_protect) ? "ON" : "OFF");
return 0;
}
int iscsit_ta_prod_mode_write_protect(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->prod_mode_write_protect = flag;
pr_debug("iSCSI_TPG[%hu] - Production Mode Write Protect bit:"
" %s\n", tpg->tpgt, (a->prod_mode_write_protect) ?
"ON" : "OFF");
return 0;
}
int iscsit_ta_demo_mode_discovery(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->demo_mode_discovery = flag;
pr_debug("iSCSI_TPG[%hu] - Demo Mode Discovery bit:"
" %s\n", tpg->tpgt, (a->demo_mode_discovery) ?
"ON" : "OFF");
return 0;
}
int iscsit_ta_default_erl(
struct iscsi_portal_group *tpg,
u32 default_erl)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((default_erl != 0) && (default_erl != 1) && (default_erl != 2)) {
pr_err("Illegal value for default_erl: %u\n", default_erl);
return -EINVAL;
}
a->default_erl = default_erl;
pr_debug("iSCSI_TPG[%hu] - DefaultERL: %u\n", tpg->tpgt, a->default_erl);
return 0;
}
int iscsit_ta_t10_pi(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->t10_pi = flag;
pr_debug("iSCSI_TPG[%hu] - T10 Protection information bit:"
" %s\n", tpg->tpgt, (a->t10_pi) ?
"ON" : "OFF");
return 0;
}
int iscsit_ta_fabric_prot_type(
struct iscsi_portal_group *tpg,
u32 prot_type)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((prot_type != 0) && (prot_type != 1) && (prot_type != 3)) {
pr_err("Illegal value for fabric_prot_type: %u\n", prot_type);
return -EINVAL;
}
a->fabric_prot_type = prot_type;
pr_debug("iSCSI_TPG[%hu] - T10 Fabric Protection Type: %u\n",
tpg->tpgt, prot_type);
return 0;
}
int iscsit_ta_tpg_enabled_sendtargets(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->tpg_enabled_sendtargets = flag;
pr_debug("iSCSI_TPG[%hu] - TPG enabled bit required for SendTargets:"
" %s\n", tpg->tpgt, (a->tpg_enabled_sendtargets) ? "ON" : "OFF");
return 0;
}
int iscsit_ta_login_keys_workaround(
struct iscsi_portal_group *tpg,
u32 flag)
{
struct iscsi_tpg_attrib *a = &tpg->tpg_attrib;
if ((flag != 0) && (flag != 1)) {
pr_err("Illegal value %d\n", flag);
return -EINVAL;
}
a->login_keys_workaround = flag;
pr_debug("iSCSI_TPG[%hu] - TPG enabled bit for login keys workaround: %s ",
tpg->tpgt, (a->login_keys_workaround) ? "ON" : "OFF");
return 0;
}
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_TPG_H
#define ISCSI_TARGET_TPG_H
#include <linux/types.h>
struct iscsi_np;
struct iscsit_session;
struct iscsi_tiqn;
struct iscsi_tpg_np;
struct se_node_acl;
struct sockaddr_storage;
extern struct iscsi_portal_group *iscsit_alloc_portal_group(struct iscsi_tiqn *, u16);
extern int iscsit_load_discovery_tpg(void);
extern void iscsit_release_discovery_tpg(void);
extern struct iscsi_portal_group *iscsit_get_tpg_from_np(struct iscsi_tiqn *,
struct iscsi_np *, struct iscsi_tpg_np **);
extern int iscsit_get_tpg(struct iscsi_portal_group *);
extern void iscsit_put_tpg(struct iscsi_portal_group *);
extern int iscsit_tpg_add_portal_group(struct iscsi_tiqn *, struct iscsi_portal_group *);
extern int iscsit_tpg_del_portal_group(struct iscsi_tiqn *, struct iscsi_portal_group *,
int);
extern int iscsit_tpg_enable_portal_group(struct iscsi_portal_group *);
extern int iscsit_tpg_disable_portal_group(struct iscsi_portal_group *, int);
extern struct iscsi_node_attrib *iscsit_tpg_get_node_attrib(struct iscsit_session *);
extern struct iscsi_tpg_np *iscsit_tpg_locate_child_np(struct iscsi_tpg_np *, int);
extern struct iscsi_tpg_np *iscsit_tpg_add_network_portal(struct iscsi_portal_group *,
struct sockaddr_storage *, struct iscsi_tpg_np *,
int);
extern int iscsit_tpg_del_network_portal(struct iscsi_portal_group *,
struct iscsi_tpg_np *);
extern int iscsit_ta_authentication(struct iscsi_portal_group *, u32);
extern int iscsit_ta_login_timeout(struct iscsi_portal_group *, u32);
extern int iscsit_ta_generate_node_acls(struct iscsi_portal_group *, u32);
extern int iscsit_ta_default_cmdsn_depth(struct iscsi_portal_group *, u32);
extern int iscsit_ta_cache_dynamic_acls(struct iscsi_portal_group *, u32);
extern int iscsit_ta_demo_mode_write_protect(struct iscsi_portal_group *, u32);
extern int iscsit_ta_prod_mode_write_protect(struct iscsi_portal_group *, u32);
extern int iscsit_ta_demo_mode_discovery(struct iscsi_portal_group *, u32);
extern int iscsit_ta_default_erl(struct iscsi_portal_group *, u32);
extern int iscsit_ta_t10_pi(struct iscsi_portal_group *, u32);
extern int iscsit_ta_fabric_prot_type(struct iscsi_portal_group *, u32);
extern int iscsit_ta_tpg_enabled_sendtargets(struct iscsi_portal_group *, u32);
extern int iscsit_ta_login_keys_workaround(struct iscsi_portal_group *, u32);
#endif /* ISCSI_TARGET_TPG_H */
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/module.h>
#include <target/iscsi/iscsi_transport.h>
static LIST_HEAD(g_transport_list);
static DEFINE_MUTEX(transport_mutex);
struct iscsit_transport *iscsit_get_transport(int type)
{
struct iscsit_transport *t;
mutex_lock(&transport_mutex);
list_for_each_entry(t, &g_transport_list, t_node) {
if (t->transport_type == type) {
if (t->owner && !try_module_get(t->owner)) {
t = NULL;
}
mutex_unlock(&transport_mutex);
return t;
}
}
mutex_unlock(&transport_mutex);
return NULL;
}
void iscsit_put_transport(struct iscsit_transport *t)
{
module_put(t->owner);
}
void iscsit_register_transport(struct iscsit_transport *t)
{
INIT_LIST_HEAD(&t->t_node);
mutex_lock(&transport_mutex);
list_add_tail(&t->t_node, &g_transport_list);
mutex_unlock(&transport_mutex);
pr_debug("Registered iSCSI transport: %s\n", t->name);
}
EXPORT_SYMBOL(iscsit_register_transport);
void iscsit_unregister_transport(struct iscsit_transport *t)
{
mutex_lock(&transport_mutex);
list_del(&t->t_node);
mutex_unlock(&transport_mutex);
pr_debug("Unregistered iSCSI transport: %s\n", t->name);
}
EXPORT_SYMBOL(iscsit_unregister_transport);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef ISCSI_TARGET_UTIL_H
#define ISCSI_TARGET_UTIL_H
#include <linux/types.h>
#include <scsi/iscsi_proto.h> /* itt_t */
#define MARKER_SIZE 8
struct iscsit_cmd;
struct iscsit_conn;
struct iscsi_conn_recovery;
struct iscsit_session;
extern int iscsit_add_r2t_to_list(struct iscsit_cmd *, u32, u32, int, u32);
extern struct iscsi_r2t *iscsit_get_r2t_for_eos(struct iscsit_cmd *, u32, u32);
extern struct iscsi_r2t *iscsit_get_r2t_from_list(struct iscsit_cmd *);
extern void iscsit_free_r2t(struct iscsi_r2t *, struct iscsit_cmd *);
extern void iscsit_free_r2ts_from_list(struct iscsit_cmd *);
extern struct iscsit_cmd *iscsit_allocate_cmd(struct iscsit_conn *, int);
extern struct iscsi_seq *iscsit_get_seq_holder_for_datain(struct iscsit_cmd *, u32);
extern struct iscsi_seq *iscsit_get_seq_holder_for_r2t(struct iscsit_cmd *);
extern struct iscsi_r2t *iscsit_get_holder_for_r2tsn(struct iscsit_cmd *, u32);
extern int iscsit_sequence_cmd(struct iscsit_conn *conn, struct iscsit_cmd *cmd,
unsigned char * ,__be32 cmdsn);
extern struct iscsit_cmd *iscsit_find_cmd_from_itt_or_dump(struct iscsit_conn *,
itt_t, u32);
extern struct iscsit_cmd *iscsit_find_cmd_from_ttt(struct iscsit_conn *, u32);
extern int iscsit_find_cmd_for_recovery(struct iscsit_session *, struct iscsit_cmd **,
struct iscsi_conn_recovery **, itt_t);
extern void iscsit_add_cmd_to_immediate_queue(struct iscsit_cmd *, struct iscsit_conn *, u8);
extern struct iscsi_queue_req *iscsit_get_cmd_from_immediate_queue(struct iscsit_conn *);
extern int iscsit_add_cmd_to_response_queue(struct iscsit_cmd *, struct iscsit_conn *, u8);
extern struct iscsi_queue_req *iscsit_get_cmd_from_response_queue(struct iscsit_conn *);
extern bool iscsit_conn_all_queues_empty(struct iscsit_conn *);
extern void iscsit_free_queue_reqs_for_conn(struct iscsit_conn *);
extern void iscsit_release_cmd(struct iscsit_cmd *);
extern void __iscsit_free_cmd(struct iscsit_cmd *, bool);
extern void iscsit_free_cmd(struct iscsit_cmd *, bool);
extern bool iscsit_check_session_usage_count(struct iscsit_session *sess, bool can_sleep);
extern void iscsit_dec_session_usage_count(struct iscsit_session *);
extern void iscsit_inc_session_usage_count(struct iscsit_session *);
extern struct iscsit_conn *iscsit_get_conn_from_cid(struct iscsit_session *, u16);
extern struct iscsit_conn *iscsit_get_conn_from_cid_rcfr(struct iscsit_session *, u16);
extern void iscsit_check_conn_usage_count(struct iscsit_conn *);
extern void iscsit_dec_conn_usage_count(struct iscsit_conn *);
extern void iscsit_inc_conn_usage_count(struct iscsit_conn *);
extern void iscsit_handle_nopin_response_timeout(struct timer_list *t);
extern void iscsit_mod_nopin_response_timer(struct iscsit_conn *);
extern void iscsit_start_nopin_response_timer(struct iscsit_conn *);
extern void iscsit_stop_nopin_response_timer(struct iscsit_conn *);
extern void iscsit_handle_nopin_timeout(struct timer_list *t);
extern void __iscsit_start_nopin_timer(struct iscsit_conn *);
extern void iscsit_start_nopin_timer(struct iscsit_conn *);
extern void iscsit_stop_nopin_timer(struct iscsit_conn *);
extern void iscsit_login_timeout(struct timer_list *t);
extern void iscsit_start_login_timer(struct iscsit_conn *, struct task_struct *kthr);
extern void iscsit_stop_login_timer(struct iscsit_conn *);
extern int iscsit_set_login_timer_kworker(struct iscsit_conn *, struct task_struct *kthr);
extern int iscsit_send_tx_data(struct iscsit_cmd *, struct iscsit_conn *, int);
extern int iscsit_fe_sendpage_sg(struct iscsit_cmd *, struct iscsit_conn *);
extern int iscsit_tx_login_rsp(struct iscsit_conn *, u8, u8);
extern int rx_data(struct iscsit_conn *, struct kvec *, int, int);
extern int tx_data(struct iscsit_conn *, struct kvec *, int, int);
extern void iscsit_collect_login_stats(struct iscsit_conn *, u8, u8);
extern struct iscsi_tiqn *iscsit_snmp_get_tiqn(struct iscsit_conn *);
extern void iscsit_fill_cxn_timeout_err_stats(struct iscsit_session *);
#endif /*** ISCSI_TARGET_UTIL_H ***/