This patch adds a new target module for SCST and libfc

that accepts FCP requests from libfc HBAs running Fibre Channel
over Ethernet (FCoE) and passes them to SCST.

Signed-off-by: Joe Eykholt <jeykholt@cisco.com>



git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@1514 d57e44dd-8a1f-0410-8b47-8ef2f437770f
This commit is contained in:
Vladislav Bolkhovitin
2010-02-20 20:02:02 +00:00
parent 21140e2a07
commit 6023f1ad34
23 changed files with 3310 additions and 0 deletions

5
fcst/Kconfig Normal file
View File

@@ -0,0 +1,5 @@
config FCST
tristate "SCST target module for Fibre Channel using libfc"
depends on LIBFC && SCST
---help---
Supports using libfc HBAs as target adapters with SCST

106
fcst/Makefile Normal file
View File

@@ -0,0 +1,106 @@
#
# FCST: libfc SCSI target makefile
# Based on ../mvsas_tgt/Makefile
#
# Copyright (C) 2006 - 2008 Jacky Feng <jfeng@marvell.com>
#
# 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, version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# Main targets:
# all (the default) : make all
# clean : clean files
# extraclean : clean + clean dependencies
# install : install
# uninstall : uninstall
#
# Notes :
# - install and uninstall must be made as root
#
ifeq ($(KVER),)
ifeq ($(KDIR),)
KVER = $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
endif
else
KDIR := /lib/modules/$(KVER)/build
endif
export PWD := $(shell pwd)
export CONFIG_FCST := m
#SCST_INC_DIR := /usr/local/include/scst
#SCST_DIR := $(SCST_INC_DIR)
SCST_INC_DIR := $(SUBDIRS)/../scst/include
SCST_DIR := $(shell pwd)/../scst/src
EXTRA_CFLAGS += -I$(SCST_INC_DIR)
EXTRA_CFLAGS += -DSUPPORT_TARGET
MODULE_NAME = fcst
EXTRA_CFLAGS += -DMV_DEBUG
INSTALL_DIR := /lib/modules/$(KVER)/extra
#EXTRA_CFLAGS += -DCONFIG_SCST_TRACING
#EXTRA_CFLAGS += -DDEBUG_WORK_IN_THREAD
#EXTRA_CFLAGS += -DCONFIG_SCST_DEBUG
ifneq ($(KERNELRELEASE),)
include $(SUBDIRS)/Makefile_in-tree
else
all: Modules.symvers Module.symvers
$(MAKE) -C $(KDIR) SUBDIRS=$(shell pwd) BUILD_INI=m
tgt: Modules.symvers Module.symvers
$(MAKE) -C $(KDIR) SUBDIRS=$(shell pwd) BUILD_INI=n
install: all
$(MAKE) -C $(KDIR) SUBDIRS=$(shell pwd) BUILD_INI=m \
modules_install
-depmod -a $(KVER)
ins:
./config
insmod fcst.ko
SCST_MOD_VERS := $(shell ls $(SCST_DIR)/Modules.symvers 2>/dev/null)
ifneq ($(SCST_MOD_VERS),)
Modules.symvers: $(SCST_DIR)/Modules.symvers
cp $(SCST_DIR)/Modules.symvers .
else
.PHONY: Modules.symvers
endif
# It's renamed in 2.6.18
SCST_MOD_VERS := $(shell ls $(SCST_DIR)/Module.symvers 2>/dev/null)
ifneq ($(SCST_MOD_VERS),)
Module.symvers: $(SCST_DIR)/Module.symvers
cp $(SCST_DIR)/Module.symvers .
else
.PHONY: Module.symvers
endif
uninstall:
rm -f $(INSTALL_DIR)/$(MODULE_NAME).ko
-/sbin/depmod -a $(KVER)
endif
clean:
rm -f *.o *.ko .*.cmd *.mod.c .*.d .depend *~ Modules.symvers \
Module.symvers Module.markers modules.order
rm -rf .tmp_versions
extraclean: clean
.PHONY: all tgt install uninstall clean extraclean

9
fcst/Makefile_in-tree Normal file
View File

@@ -0,0 +1,9 @@
ccflags-y += -Iinclude/scst
obj-$(CONFIG_FCST) += fcst.o
fcst-objs := \
ft_cmd.o \
ft_io.o \
ft_scst.o \
ft_sess.o

98
fcst/README Normal file
View File

@@ -0,0 +1,98 @@
fcst README v0.1 02/17/2010
$Id$
FCST is a module that depends on libfc and SCST to provide FC target support.
To build for linux-2.6.33-rc8, do:
1. Get the kernel:
KERNEL=linux-2.6.33-rc8
cd /usr/src/kernels
URL_DIR=http://www.kernel.org/pub/linux/kernel/v2.6/testing/
TARFILE=$VER.tar.bz2
wget -o $TARFILE $URL_DIR/$TARFILE
tar xfj $TARFILE
cd $KERNEL
2. Apply patches needed for libfc target hooks and point-to-point fixes:
KDIR=/usr/src/kernels/$KERNEL
PDIR=/usr/src/scst/trunk/fcst/linux-patches # use your dir here
cd $PDIR
for patch in `grep -v '^#' series-$KERNEL`
do
(cd $KDIR; patch -p1) < $patch
done
3. Apply SCST patches to the kernel
See trunk/scst/README
The readahead patches are not needed in 2.6.33.
The io_context patch needs to be adjusted for 2.6.33. Skip it for now.
4. Configure and make your kernel
5. Install SCST
See trunk/scst/README. Make sure you are building sysfs SCST build, because
FCST supports only it.
6. Make FCST
In this directory, just do
make
make install
7. Install the FCoE admin tools, including dcbd and fcoeadm.
Some distros may have these.
You should be able to use the source at
http://www.open-fcoe.org/openfc/downloads/2.6.32/open-fcoe-2.6.32.tar.gz
8. Bring up SCST and configure the devices.
9. Bring up an FCoE initiator (we'll enable target mode on it later):
fcoeadm -c eth3
The other end can be an initiator as well, in point-to-point mode
over a full-duplex loss-less link (enable pause on both sides),
or a link to an FCoE switch.
10. Use fcc (part of the open-fcoe debug tools in step 7) to see the
initiator setup. To get the FCoE port name for eth3
# fcc
FC HBAs:
HBA Port Name Port ID State Device
host4 20:00:00:1b:21:06:58:21 01:01:02 Online eth3
host4 Remote Ports:
Path Port Name Port ID State Roles
4:0-0 10:00:50:41:4c:4f:3b:00 01:01:01 Online FCP Initiator
In the above example, there's one local host on eth3, and it's in
a point-to-point connection with the remote initiator with Port_id 010101.
11. Load fcst and create an ini-group for the remote initiator. Do:
LPORT=20:00:00:1b:21:06:58:21 # the local Port_Name
INIT_PORT=10:00:50:41:4c:4f:3b:00 # remote Port Name
GROUP=test # your choice ini_group name
GDIR=$LPORT/ini_group/$GROUP
modprobe fcst
cd /sys/kernel/scst_tgt/targets/fcst
echo create $GROUP > $LPORT/ini_group/mgmt
echo add $INIT_PORT > $GDIR/initiators/mgmt
12. Add any disks you want to the LUNs in that init group.
echo add disk-name 0 > $GDIR/luns/mgmt
13. Enable the initiator:
echo 1 > $LPORT/enabled
14. As a temporary workaround, you may need to reset the interface
on the initiator side so it sees the SCST device as a target and
discovers LUNs. You can avoid this by bringing up the initiator last.

148
fcst/fcst.h Normal file
View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2010 Cisco Systems, Inc.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* $Id$
*/
#ifndef __SCSI_FCST_H__
#define __SCSI_FCST_H__
#include "scst.h"
#define FT_VERSION "0.3"
#define FT_MODULE "fcst"
/*
* Debug options.
*/
#define FT_DEBUG_CONF 0x01 /* configuration messages */
#define FT_DEBUG_SESS 0x02 /* session messages */
#define FT_DEBUG_IO 0x04 /* I/O operations */
extern unsigned int ft_debug_logging; /* debug options */
#define FT_ERR(fmt, args...) \
printk(KERN_ERR FT_MODULE ": %s: " fmt, __func__, ##args)
#define FT_DEBUG(mask, fmt, args...) \
do { \
if (ft_debug_logging & (mask)) \
printk(KERN_INFO FT_MODULE ": %s: " fmt, \
__func__, ##args); \
} while (0)
#define FT_CONF_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_CONF, fmt, ##args)
#define FT_SESS_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_SESS, fmt, ##args)
#define FT_IO_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_IO, fmt, ##args)
#define FT_NAMELEN 32 /* length of ASCI WWPNs including pad */
/*
* Session (remote port).
*/
struct ft_sess {
u32 port_id; /* for hash lookup use only */
u32 params;
u16 max_payload; /* max transmitted payload size */
u32 max_lso_payload; /* max offloaded payload size */
u64 port_name; /* port name for transport ID */
struct ft_tport *tport;
struct hlist_node hash; /* linkage in ft_sess_hash table */
struct rcu_head rcu;
struct kref kref; /* ref for hash and outstanding I/Os */
struct scst_session *scst_sess;
};
/*
* Hash table of sessions per local port.
* Hash lookup by remote port FC_ID.
*/
#define FT_SESS_HASH_BITS 6
#define FT_SESS_HASH_SIZE (1 << FT_SESS_HASH_BITS)
/*
* Per local port data.
* This is created when the first session logs into the local port.
* Deleted when tpg is deleted or last session is logged off.
*/
struct ft_tport {
u32 sess_count; /* number of sessions in hash */
u8 enabled:1;
struct rcu_head rcu;
struct hlist_head hash[FT_SESS_HASH_SIZE]; /* list of sessions */
struct fc_lport *lport;
struct scst_tgt *tgt;
};
/*
* Commands
*/
struct ft_cmd {
int serial; /* order received, for debugging */
struct fc_seq *seq; /* sequence in exchange mgr */
struct fc_frame *req_frame; /* original request frame */
u32 write_data_len; /* data received from initiator */
u32 read_data_len; /* data sent to initiator */
u32 xfer_rdy_len; /* max xfer ready offset */
u32 max_lso_payload; /* max offloaded (LSO) data payload */
u16 max_payload; /* max transmitted data payload */
struct scst_cmd *scst_cmd;
};
extern struct list_head ft_lport_list;
extern struct mutex ft_lport_lock;
extern struct scst_tgt_template ft_scst_template;
/*
* libfc interface.
*/
int ft_prli(struct fc_rport_priv *, u32 spp_len,
const struct fc_els_spp *, struct fc_els_spp *);
void ft_prlo(struct fc_rport_priv *);
void ft_recv(struct fc_lport *, struct fc_seq *, struct fc_frame *);
/*
* SCST interface.
*/
int ft_send_response(struct scst_cmd *);
int ft_send_xfer_rdy(struct scst_cmd *);
void ft_cmd_timeout(struct scst_cmd *);
void ft_cmd_free(struct scst_cmd *);
void ft_cmd_tm_done(struct scst_mgmt_cmd *);
int ft_tgt_detect(struct scst_tgt_template *);
int ft_tgt_release(struct scst_tgt *);
ssize_t ft_tgt_enable(struct scst_tgt *, const char *, size_t);
bool ft_tgt_enabled(struct scst_tgt *);
int ft_report_aen(struct scst_aen *);
/*
* Session interface.
*/
int ft_lport_notify(struct notifier_block *, unsigned long, void *);
void ft_lport_add(struct fc_lport *, void *);
void ft_lport_del(struct fc_lport *, void *);
/*
* other internal functions.
*/
int ft_thread(void *);
void ft_recv_req(struct ft_sess *, struct fc_seq *, struct fc_frame *);
void ft_recv_write_data(struct scst_cmd *, struct fc_frame *);
int ft_send_read_data(struct scst_cmd *);
struct ft_tpg *ft_lport_find_tpg(struct fc_lport *);
struct ft_node_acl *ft_acl_get(struct ft_tpg *, struct fc_rport_priv *);
void ft_cmd_dump(struct scst_cmd *, const char *);
#endif /* __SCSI_FCST_H__ */

707
fcst/ft_cmd.c Normal file
View File

@@ -0,0 +1,707 @@
/*
* Copyright (c) 2010 Cisco Systems, Inc.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <scsi/libfc.h>
#include <scsi/fc_encode.h>
#include "fcst.h"
/*
* Append string to buffer safely.
* Also prepends a space if there's already something the buf.
*/
static void ft_cmd_flag(char *buf, size_t len, const char *desc)
{
if (buf[0])
strlcat(buf, " ", len);
strlcat(buf, desc, len);
}
/*
* Debug: dump command.
*/
void ft_cmd_dump(struct scst_cmd *cmd, const char *caller)
{
static atomic_t serial;
struct ft_cmd *fcmd;
struct fc_exch *ep;
char prefix[30];
char buf[150];
if (!(ft_debug_logging & FT_DEBUG_IO))
return;
fcmd = scst_cmd_get_tgt_priv(cmd);
ep = fc_seq_exch(fcmd->seq);
snprintf(prefix, sizeof(prefix), FT_MODULE ": cmd %2x",
atomic_inc_return(&serial) & 0xff);
printk(KERN_INFO "%s %s oid %x oxid %x resp_len %u\n",
prefix, caller, ep->oid, ep->oxid,
scst_cmd_get_resp_data_len(cmd));
printk(KERN_INFO "%s scst_cmd %p wlen %u rlen %u\n",
prefix, cmd, fcmd->write_data_len, fcmd->read_data_len);
printk(KERN_INFO "%s exp_dir %x exp_xfer_len %d exp_in_len %d\n",
prefix, cmd->expected_data_direction,
cmd->expected_transfer_len, cmd->expected_in_transfer_len);
printk(KERN_INFO "%s dir %x data_len %d bufflen %d in_bufflen %d\n",
prefix, cmd->data_direction, cmd->data_len,
cmd->bufflen, cmd->in_bufflen);
printk(KERN_INFO "%s sg_cnt reg %d in %d tgt %d tgt_in %d\n",
prefix, cmd->sg_cnt, cmd->in_sg_cnt,
cmd->tgt_sg_cnt, cmd->tgt_in_sg_cnt);
buf[0] = '\0';
if (cmd->sent_for_exec)
ft_cmd_flag(buf, sizeof(buf), "sent");
if (cmd->completed)
ft_cmd_flag(buf, sizeof(buf), "comp");
if (cmd->ua_ignore)
ft_cmd_flag(buf, sizeof(buf), "ua_ign");
if (cmd->atomic)
ft_cmd_flag(buf, sizeof(buf), "atom");
if (cmd->double_ua_possible)
ft_cmd_flag(buf, sizeof(buf), "dbl_ua_poss");
if (cmd->is_send_status)
ft_cmd_flag(buf, sizeof(buf), "send_stat");
if (cmd->retry)
ft_cmd_flag(buf, sizeof(buf), "retry");
if (cmd->internal)
ft_cmd_flag(buf, sizeof(buf), "internal");
if (cmd->inc_blocking)
ft_cmd_flag(buf, sizeof(buf), "inc_blk");
if (cmd->needs_unblocking)
ft_cmd_flag(buf, sizeof(buf), "needs_unblk");
if (cmd->dec_on_dev_needed)
ft_cmd_flag(buf, sizeof(buf), "dec_on_dev");
if (cmd->cmd_hw_pending)
ft_cmd_flag(buf, sizeof(buf), "hw_pend");
if (cmd->tgt_need_alloc_data_buf)
ft_cmd_flag(buf, sizeof(buf), "tgt_need_alloc");
if (cmd->tgt_data_buf_alloced)
ft_cmd_flag(buf, sizeof(buf), "tgt_alloced");
if (cmd->dh_data_buf_alloced)
ft_cmd_flag(buf, sizeof(buf), "dh_alloced");
if (cmd->expected_values_set)
ft_cmd_flag(buf, sizeof(buf), "exp_val");
if (cmd->sg_buff_modified)
ft_cmd_flag(buf, sizeof(buf), "sg_buf_mod");
if (cmd->preprocessing_only)
ft_cmd_flag(buf, sizeof(buf), "pre_only");
if (cmd->sn_set)
ft_cmd_flag(buf, sizeof(buf), "sn_set");
if (cmd->hq_cmd_inced)
ft_cmd_flag(buf, sizeof(buf), "hq_cmd_inc");
if (cmd->set_sn_on_restart_cmd)
ft_cmd_flag(buf, sizeof(buf), "set_sn_on_restart");
if (cmd->no_sgv)
ft_cmd_flag(buf, sizeof(buf), "no_sgv");
if (cmd->may_need_dma_sync)
ft_cmd_flag(buf, sizeof(buf), "dma_sync");
if (cmd->out_of_sn)
ft_cmd_flag(buf, sizeof(buf), "oo_sn");
if (cmd->inc_expected_sn_on_done)
ft_cmd_flag(buf, sizeof(buf), "inc_sn_exp");
if (cmd->done)
ft_cmd_flag(buf, sizeof(buf), "done");
if (cmd->finished)
ft_cmd_flag(buf, sizeof(buf), "fin");
if (cmd->tm_dbg_delayed)
ft_cmd_flag(buf, sizeof(buf), "tm_dbg_del");
if (cmd->tm_dbg_immut)
ft_cmd_flag(buf, sizeof(buf), "tm_dbg_immut");
printk(KERN_INFO "%s flags %s\n", prefix, buf);
printk(KERN_INFO "%s lun %lld sn %d tag %lld cmd_flags %lx\n",
prefix, cmd->lun, cmd->sn, cmd->tag, cmd->cmd_flags);
printk(KERN_INFO "%s tgt_sn %d op_flags %x op %s\n",
prefix, cmd->tgt_sn, cmd->op_flags, cmd->op_name);
printk(KERN_INFO "%s status %x msg_status %x "
"host_status %x driver_status %x\n",
prefix, cmd->status, cmd->msg_status,
cmd->host_status, cmd->driver_status);
printk(KERN_INFO "%s cdb_len %d ext_cdb_len %u\n",
prefix, cmd->cdb_len, cmd->ext_cdb_len);
snprintf(buf, sizeof(buf), "%s cdb ", prefix);
print_hex_dump(KERN_INFO, buf, DUMP_PREFIX_NONE,
16, 4, cmd->cdb, SCST_MAX_CDB_SIZE, 0);
}
/*
* Debug: dump mgmt command.
*/
void ft_cmd_tm_dump(struct scst_mgmt_cmd *mcmd, const char *caller)
{
struct ft_cmd *fcmd;
struct fc_exch *ep;
char prefix[30];
char buf[150];
if (!(ft_debug_logging & FT_DEBUG_IO))
return;
fcmd = scst_mgmt_cmd_get_tgt_priv(mcmd);
ep = fc_seq_exch(fcmd->seq);
snprintf(prefix, sizeof(prefix), FT_MODULE ": mcmd");
printk(KERN_INFO "%s %s oid %x oxid %x lun %lld\n",
prefix, caller, ep->oid, ep->oxid,
(unsigned long long)mcmd->lun);
printk(KERN_INFO "%s state %d fn %d fin_wait %d done_wait %d comp %d\n",
prefix, mcmd->state, mcmd->fn,
mcmd->cmd_finish_wait_count, mcmd->cmd_done_wait_count,
mcmd->completed_cmd_count);
buf[0] = '\0';
if (mcmd->completed)
ft_cmd_flag(buf, sizeof(buf), "comp");
if (mcmd->needs_unblocking)
ft_cmd_flag(buf, sizeof(buf), "needs_unblock");
if (mcmd->lun_set)
ft_cmd_flag(buf, sizeof(buf), "lun_set");
if (mcmd->cmd_sn_set)
ft_cmd_flag(buf, sizeof(buf), "cmd_sn_set");
if (mcmd->affected_cmds_done_called)
ft_cmd_flag(buf, sizeof(buf), "cmds_done");
printk(KERN_INFO "%s flags %s\n", prefix, buf);
if (mcmd->cmd_to_abort)
ft_cmd_dump(mcmd->cmd_to_abort, caller);
}
/*
* Free command.
*/
void ft_cmd_free(struct scst_cmd *cmd)
{
struct ft_cmd *fcmd;
fcmd = scst_cmd_get_tgt_priv(cmd);
if (fcmd) {
scst_cmd_set_tgt_priv(cmd, NULL);
fc_frame_free(fcmd->req_frame);
kfree(fcmd);
}
}
/*
* Send response, after data if applicable.
*/
int ft_send_response(struct scst_cmd *cmd)
{
struct ft_cmd *fcmd;
struct fc_frame *fp;
struct fcp_resp_with_ext *fcp;
struct fc_lport *lport;
struct fc_exch *ep;
unsigned int slen;
size_t len;
int resid = 0;
int bi_resid = 0;
int error;
int dir;
u32 status;
ft_cmd_dump(cmd, __func__);
fcmd = scst_cmd_get_tgt_priv(cmd);
ep = fc_seq_exch(fcmd->seq);
lport = ep->lp;
if (scst_cmd_aborted(cmd)) {
scst_set_delivery_status(cmd, SCST_CMD_DELIVERY_ABORTED);
lport->tt.exch_done(fcmd->seq);
return SCST_TGT_RES_SUCCESS;
}
if (!scst_cmd_get_is_send_status(cmd)) {
FT_IO_DBG("send status not set. feature not implemented\n");
return SCST_TGT_RES_FATAL_ERROR;
}
status = scst_cmd_get_status(cmd);
dir = scst_cmd_get_data_direction(cmd);
slen = scst_cmd_get_sense_buffer_len(cmd);
len = sizeof(*fcp) + slen;
/*
* Send read data and set underflow/overflow residual count.
* For bi-directional comands, the bi_resid is for the read direction.
*/
if (dir & SCST_DATA_WRITE)
resid = (signed)scst_cmd_get_bufflen(cmd) -
fcmd->write_data_len;
if (dir & SCST_DATA_READ) {
error = ft_send_read_data(cmd);
if (error) {
FT_ERR("ft_send_read_data returned %d\n", error);
return error;
}
if (dir == SCST_DATA_BIDI) {
bi_resid = (signed)scst_cmd_get_in_bufflen(cmd) -
scst_cmd_get_resp_data_len(cmd);
if (bi_resid)
len += sizeof(__be32);
} else
resid = (signed)scst_cmd_get_bufflen(cmd) -
scst_cmd_get_resp_data_len(cmd);
}
fp = fc_frame_alloc(lport, len);
if (!fp)
return SCST_TGT_RES_QUEUE_FULL;
fcp = fc_frame_payload_get(fp, len);
memset(fcp, 0, sizeof(*fcp));
fcp->resp.fr_status = status;
if (slen) {
fcp->resp.fr_flags |= FCP_SNS_LEN_VAL;
fcp->ext.fr_sns_len = htonl(slen);
memcpy(fcp + 1, scst_cmd_get_sense_buffer(cmd), slen);
}
if (bi_resid) {
if (bi_resid < 0) {
fcp->resp.fr_flags |= FCP_BIDI_READ_OVER;
bi_resid = -bi_resid;
} else
fcp->resp.fr_flags |= FCP_BIDI_READ_UNDER;
*(__be32 *)((u8 *)(fcp + 1) + slen) = htonl(bi_resid);
}
if (resid) {
if (resid < 0) {
resid = -resid;
fcp->resp.fr_flags |= FCP_RESID_OVER;
} else
fcp->resp.fr_flags |= FCP_RESID_UNDER;
fcp->ext.fr_resid = htonl(resid);
}
FT_IO_DBG("response did %x oxid %x\n", ep->did, ep->oxid);
/*
* Send response.
*/
fcmd->seq = lport->tt.seq_start_next(fcmd->seq);
fc_fill_fc_hdr(fp, FC_RCTL_DD_CMD_STATUS, ep->did, ep->sid, FC_TYPE_FCP,
FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ, 0);
lport->tt.seq_send(lport, fcmd->seq, fp);
lport->tt.exch_done(fcmd->seq);
scst_tgt_cmd_done(cmd, SCST_CONTEXT_SAME);
return SCST_TGT_RES_SUCCESS;
}
/*
* FC sequence response handler for follow-on sequences (data) and aborts.
*/
static void ft_recv_seq(struct fc_seq *sp, struct fc_frame *fp, void *arg)
{
struct scst_cmd *cmd = arg;
struct fc_frame_header *fh;
/*
* If an error is being reported, it must be FC_EX_CLOSED.
* Timeouts don't occur on incoming requests, and there are
* currently no other errors.
* The PRLO handler will be also called by libfc to delete
* the session and all pending commands, so we ignore this response.
*/
if (IS_ERR(fp)) {
FT_IO_DBG("exchange error %ld - not handled\n", -PTR_ERR(fp));
return;
}
fh = fc_frame_header_get(fp);
switch (fh->fh_r_ctl) {
case FC_RCTL_DD_SOL_DATA: /* write data */
ft_recv_write_data(cmd, fp);
break;
case FC_RCTL_DD_UNSOL_CTL: /* command */
case FC_RCTL_DD_SOL_CTL: /* transfer ready */
case FC_RCTL_DD_DATA_DESC: /* transfer ready */
default:
printk(KERN_INFO "%s: unhandled frame r_ctl %x\n",
__func__, fh->fh_r_ctl);
fc_frame_free(fp);
break;
}
}
/*
* Command is about to be sent to device.
*/
int ft_pre_exec(struct scst_cmd *cmd)
{
scst_restart_cmd(cmd, SCST_PREPROCESS_STATUS_SUCCESS,
SCST_CONTEXT_SAME);
return SCST_PREPROCESS_STATUS_SUCCESS;
}
/*
* Command timeout.
* SCST calls this when the command has taken too long in the device handler.
*/
void ft_cmd_timeout(struct scst_cmd *cmd)
{
FT_IO_DBG("timeout not implemented\n"); /* XXX TBD */
}
/*
* Send TX_RDY (transfer ready).
*/
static int ft_send_xfer_rdy_off(struct scst_cmd *cmd, u32 offset, u32 len)
{
struct ft_cmd *fcmd;
struct fc_frame *fp;
struct fcp_txrdy *txrdy;
struct fc_lport *lport;
struct fc_exch *ep;
fcmd = scst_cmd_get_tgt_priv(cmd);
if (fcmd->xfer_rdy_len < len + offset)
fcmd->xfer_rdy_len = len + offset;
ep = fc_seq_exch(fcmd->seq);
lport = ep->lp;
fp = fc_frame_alloc(lport, sizeof(*txrdy));
if (!fp)
return SCST_TGT_RES_QUEUE_FULL;
txrdy = fc_frame_payload_get(fp, sizeof(*txrdy));
memset(txrdy, 0, sizeof(*txrdy));
txrdy->ft_data_ro = htonl(offset);
txrdy->ft_burst_len = htonl(len);
fcmd->seq = lport->tt.seq_start_next(fcmd->seq);
fc_fill_fc_hdr(fp, FC_RCTL_DD_DATA_DESC, ep->did, ep->sid, FC_TYPE_FCP,
FC_FC_EX_CTX | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
lport->tt.seq_send(lport, fcmd->seq, fp);
return SCST_TGT_RES_SUCCESS;
}
/*
* Send TX_RDY (transfer ready).
*/
int ft_send_xfer_rdy(struct scst_cmd *cmd)
{
return ft_send_xfer_rdy_off(cmd, 0, scst_cmd_get_bufflen(cmd));
}
/*
* Send a FCP response including SCSI status and optional FCP rsp_code.
* status is SAM_STAT_GOOD (zero) if code is valid.
* This is used in error cases, such as allocation failures.
*/
static void ft_send_resp_status(struct fc_seq *sp, u32 status,
enum fcp_resp_rsp_codes code)
{
struct fc_frame *fp;
size_t len;
struct fcp_resp_with_ext *fcp;
struct fcp_resp_rsp_info *info;
struct fc_lport *lport;
struct fc_exch *ep;
ep = fc_seq_exch(sp);
FT_IO_DBG("FCP error response: did %x oxid %x status %x code %x\n",
ep->did, ep->oxid, status, code);
lport = ep->lp;
len = sizeof(*fcp);
if (status == SAM_STAT_GOOD)
len += sizeof(*info);
fp = fc_frame_alloc(lport, len);
if (!fp)
goto out;
fcp = fc_frame_payload_get(fp, len);
memset(fcp, 0, len);
fcp->resp.fr_status = status;
if (status == SAM_STAT_GOOD) {
fcp->ext.fr_rsp_len = htonl(sizeof(*info));
fcp->resp.fr_flags |= FCP_RSP_LEN_VAL;
info = (struct fcp_resp_rsp_info *)(fcp + 1);
info->rsp_code = code;
}
sp = lport->tt.seq_start_next(sp);
fc_fill_fc_hdr(fp, FC_RCTL_DD_CMD_STATUS, ep->did, ep->sid, FC_TYPE_FCP,
FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ, 0);
lport->tt.seq_send(lport, sp, fp);
out:
lport->tt.exch_done(sp);
}
/*
* Send error or task management response.
* Always frees the fcmd and associated state.
*/
static void ft_send_resp_code(struct ft_cmd *fcmd, enum fcp_resp_rsp_codes code)
{
ft_send_resp_status(fcmd->seq, SAM_STAT_GOOD, code);
fc_frame_free(fcmd->req_frame);
kfree(fcmd);
}
void ft_cmd_tm_done(struct scst_mgmt_cmd *mcmd)
{
struct ft_cmd *fcmd;
enum fcp_resp_rsp_codes code;
ft_cmd_tm_dump(mcmd, __func__);
fcmd = scst_mgmt_cmd_get_tgt_priv(mcmd);
switch (scst_mgmt_cmd_get_status(mcmd)) {
case SCST_MGMT_STATUS_SUCCESS:
code = FCP_TMF_CMPL;
break;
case SCST_MGMT_STATUS_REJECTED:
code = FCP_TMF_REJECTED;
break;
case SCST_MGMT_STATUS_LUN_NOT_EXIST:
code = FCP_TMF_INVALID_LUN;
break;
case SCST_MGMT_STATUS_TASK_NOT_EXIST:
case SCST_MGMT_STATUS_FN_NOT_SUPPORTED:
case SCST_MGMT_STATUS_FAILED:
default:
code = FCP_TMF_FAILED;
break;
}
FT_IO_DBG("tm cmd done fn %d code %d\n", mcmd->fn, code);
ft_send_resp_code(fcmd, code);
}
/*
* Handle an incoming FCP task management command frame.
* Note that this may be called directly from the softirq context.
*/
static void ft_recv_tm(struct scst_session *scst_sess,
struct ft_cmd *fcmd, struct fcp_cmnd *fcp)
{
struct scst_rx_mgmt_params params;
int ret;
memset(&params, 0, sizeof(params));
params.lun = fcp->fc_lun;
params.lun_len = sizeof(fcp->fc_lun);
params.lun_set = 1;
params.atomic = SCST_ATOMIC;
params.tgt_priv = fcmd;
switch (fcp->fc_tm_flags) {
case FCP_TMF_LUN_RESET:
params.fn = SCST_LUN_RESET;
break;
case FCP_TMF_TGT_RESET:
params.fn = SCST_TARGET_RESET;
params.lun_set = 0;
break;
case FCP_TMF_CLR_TASK_SET:
params.fn = SCST_CLEAR_TASK_SET;
break;
case FCP_TMF_ABT_TASK_SET:
params.fn = SCST_ABORT_TASK_SET;
break;
case FCP_TMF_CLR_ACA:
params.fn = SCST_CLEAR_ACA;
break;
default:
/*
* FCP4r01 indicates having a combination of
* tm_flags set is invalid.
*/
FT_IO_DBG("invalid FCP tm_flags %x\n", fcp->fc_tm_flags);
ft_send_resp_code(fcmd, FCP_CMND_FIELDS_INVALID);
return;
}
FT_IO_DBG("submit tm cmd fn %d\n", params.fn);
ret = scst_rx_mgmt_fn(scst_sess, &params);
FT_IO_DBG("scst_rx_mgmt_fn ret %d\n", ret);
if (ret)
ft_send_resp_code(fcmd, FCP_TMF_FAILED);
}
/*
* Handle an incoming FCP command frame.
* Note that this may be called directly from the softirq context.
*/
static void ft_recv_cmd(struct ft_sess *sess, struct fc_seq *sp,
struct fc_frame *fp)
{
static atomic_t serial;
struct scst_cmd *cmd;
struct ft_cmd *fcmd;
struct fcp_cmnd *fcp;
struct fc_lport *lport;
int data_dir;
u32 data_len;
int cdb_len;
lport = fc_seq_exch(sp)->lp;
fcmd = kzalloc(sizeof(*fcmd), GFP_ATOMIC);
if (!fcmd)
goto busy;
fcmd->serial = atomic_inc_return(&serial); /* debug only */
fcmd->seq = sp;
fcmd->max_payload = sess->max_payload;
fcmd->max_lso_payload = sess->max_lso_payload;
fcmd->req_frame = fp;
fcp = fc_frame_payload_get(fp, sizeof(*fcp));
if (!fcp)
goto err;
if (fcp->fc_tm_flags) {
ft_recv_tm(sess->scst_sess, fcmd, fcp);
return;
}
/*
* re-check length including specified CDB length.
* data_len is just after the CDB.
*/
cdb_len = fcp->fc_flags & FCP_CFL_LEN_MASK;
fcp = fc_frame_payload_get(fp, sizeof(*fcp) + cdb_len);
if (!fcp)
goto err;
cdb_len += sizeof(fcp->fc_cdb);
data_len = ntohl(*(__be32 *)(fcp->fc_cdb + cdb_len));
cmd = scst_rx_cmd(sess->scst_sess, fcp->fc_lun, sizeof(fcp->fc_lun),
fcp->fc_cdb, cdb_len, SCST_ATOMIC);
if (!cmd) {
kfree(fcmd);
goto busy;
}
fcmd->scst_cmd = cmd;
scst_cmd_set_tgt_priv(cmd, fcmd);
switch (fcp->fc_flags & (FCP_CFL_RDDATA | FCP_CFL_WRDATA)) {
case 0:
data_dir = SCST_DATA_NONE;
break;
case FCP_CFL_RDDATA:
data_dir = SCST_DATA_READ;
break;
case FCP_CFL_WRDATA:
data_dir = SCST_DATA_WRITE;
break;
case FCP_CFL_RDDATA | FCP_CFL_WRDATA:
data_dir = SCST_DATA_BIDI;
break;
}
scst_cmd_set_expected(cmd, data_dir, data_len);
switch (fcp->fc_pri_ta & FCP_PTA_MASK) {
case FCP_PTA_SIMPLE:
cmd->queue_type = SCST_CMD_QUEUE_SIMPLE;
break;
case FCP_PTA_HEADQ:
cmd->queue_type = SCST_CMD_QUEUE_HEAD_OF_QUEUE;
break;
case FCP_PTA_ACA:
cmd->queue_type = SCST_CMD_QUEUE_ACA;
break;
case FCP_PTA_ORDERED:
default:
cmd->queue_type = SCST_CMD_QUEUE_ORDERED;
break;
}
lport->tt.seq_set_resp(sp, ft_recv_seq, cmd);
scst_cmd_init_done(cmd, SCST_CONTEXT_THREAD);
return;
err:
ft_send_resp_code(fcmd, FCP_CMND_FIELDS_INVALID);
return;
busy:
FT_IO_DBG("cmd allocation failure - sending BUSY\n");
ft_send_resp_status(sp, SAM_STAT_BUSY, 0);
fc_frame_free(fp);
}
/*
* Send FCP ELS-4 Reject.
*/
static void ft_cmd_ls_rjt(struct fc_seq *sp, enum fc_els_rjt_reason reason,
enum fc_els_rjt_explan explan)
{
struct fc_frame *fp;
struct fc_els_ls_rjt *rjt;
struct fc_lport *lport;
struct fc_exch *ep;
ep = fc_seq_exch(sp);
lport = ep->lp;
fp = fc_frame_alloc(lport, sizeof(*rjt));
if (!fp)
return;
rjt = fc_frame_payload_get(fp, sizeof(*rjt));
memset(rjt, 0, sizeof(*rjt));
rjt->er_cmd = ELS_LS_RJT;
rjt->er_reason = reason;
rjt->er_explan = explan;
sp = lport->tt.seq_start_next(sp);
fc_fill_fc_hdr(fp, FC_RCTL_ELS_REP, ep->did, ep->sid, FC_TYPE_FCP,
FC_FC_EX_CTX | FC_FC_END_SEQ | FC_FC_LAST_SEQ, 0);
lport->tt.seq_send(lport, sp, fp);
}
/*
* Handle an incoming FCP ELS-4 command frame.
* Note that this may be called directly from the softirq context.
*/
static void ft_recv_els4(struct ft_sess *sess, struct fc_seq *sp,
struct fc_frame *fp)
{
u8 op = fc_frame_payload_op(fp);
switch (op) {
case ELS_SRR: /* TBD */
default:
FT_IO_DBG("unsupported ELS-4 op %x\n", op);
ft_cmd_ls_rjt(sp, ELS_RJT_INVAL, ELS_EXPL_NONE);
fc_frame_free(fp);
break;
}
}
/*
* Handle an incoming FCP frame.
* Note that this may be called directly from the softirq context.
*/
void ft_recv_req(struct ft_sess *sess, struct fc_seq *sp, struct fc_frame *fp)
{
struct fc_frame_header *fh = fc_frame_header_get(fp);
switch (fh->fh_r_ctl) {
case FC_RCTL_DD_UNSOL_CMD:
ft_recv_cmd(sess, sp, fp);
break;
case FC_RCTL_ELS4_REQ:
ft_recv_els4(sess, sp, fp);
break;
default:
printk(KERN_INFO "%s: unhandled frame r_ctl %x\n",
__func__, fh->fh_r_ctl);
fc_frame_free(fp);
sess->tport->lport->tt.exch_done(sp);
break;
}
}

272
fcst/ft_io.c Normal file
View File

@@ -0,0 +1,272 @@
/*
* Copyright (c) 2010 Cisco Systems, Inc.
*
* Portions based on drivers/scsi/libfc/fc_fcp.c and subject to the following:
*
* Copyright (c) 2007 Intel Corporation. All rights reserved.
* Copyright (c) 2008 Red Hat, Inc. All rights reserved.
* Copyright (c) 2008 Mike Christie
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <scsi/libfc.h>
#include <scsi/fc_encode.h>
#include "fcst.h"
/*
* Receive write data frame.
*/
void ft_recv_write_data(struct scst_cmd *cmd, struct fc_frame *fp)
{
struct ft_cmd *fcmd;
struct fc_frame_header *fh;
unsigned int bufflen;
u32 rel_off;
size_t frame_len;
size_t mem_len;
size_t tlen;
void *from;
void *to;
int dir;
u8 *buf;
dir = scst_cmd_get_data_direction(cmd);
if (dir == SCST_DATA_BIDI) {
mem_len = scst_get_in_buf_first(cmd, &buf);
bufflen = scst_cmd_get_in_bufflen(cmd);
} else {
mem_len = scst_get_buf_first(cmd, &buf);
bufflen = scst_cmd_get_bufflen(cmd);
}
to = buf;
fcmd = scst_cmd_get_tgt_priv(cmd);
fh = fc_frame_header_get(fp);
frame_len = fr_len(fp);
rel_off = ntohl(fh->fh_parm_offset);
FT_IO_DBG("sid %x oxid %x payload_len %zd rel_off %x\n",
ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id),
frame_len - sizeof(*fh), rel_off);
if (!(ntoh24(fh->fh_f_ctl) & FC_FC_REL_OFF))
goto drop;
if (frame_len <= sizeof(*fh))
goto drop;
frame_len -= sizeof(*fh);
from = fc_frame_payload_get(fp, 0);
if (rel_off >= bufflen)
goto drop;
if (frame_len + rel_off > bufflen)
frame_len = bufflen - rel_off;
while (frame_len) {
if (!mem_len) {
if (dir == SCST_DATA_BIDI) {
scst_put_in_buf(cmd, buf);
mem_len = scst_get_in_buf_next(cmd, &buf);
} else {
scst_put_buf(cmd, buf);
mem_len = scst_get_buf_next(cmd, &buf);
}
to = buf;
if (!mem_len)
break;
}
if (rel_off) {
if (rel_off >= mem_len) {
rel_off -= mem_len;
mem_len = 0;
continue;
}
mem_len -= rel_off;
to += rel_off;
rel_off = 0;
}
tlen = min(mem_len, frame_len);
memcpy(to, from, tlen);
from += tlen;
frame_len -= tlen;
mem_len -= tlen;
to += tlen;
fcmd->write_data_len += tlen;
}
if (mem_len) {
if (dir == SCST_DATA_BIDI)
scst_put_in_buf(cmd, buf);
else
scst_put_buf(cmd, buf);
}
if (fcmd->write_data_len == cmd->data_len)
scst_rx_data(cmd, SCST_RX_STATUS_SUCCESS, SCST_CONTEXT_THREAD);
drop:
fc_frame_free(fp);
}
/*
* Send read data back to initiator.
*/
int ft_send_read_data(struct scst_cmd *cmd)
{
struct ft_cmd *fcmd;
struct fc_frame *fp = NULL;
struct fc_exch *ep;
struct fc_lport *lport;
size_t remaining;
u32 fh_off = 0;
u32 frame_off;
size_t frame_len = 0;
size_t mem_len;
u32 mem_off;
size_t tlen;
struct page *page;
int use_sg;
int error;
void *to = NULL;
u8 *from = NULL;
int loop_limit = 10000;
fcmd = scst_cmd_get_tgt_priv(cmd);
ep = fc_seq_exch(fcmd->seq);
lport = ep->lp;
frame_off = fcmd->read_data_len;
tlen = scst_cmd_get_resp_data_len(cmd);
FT_IO_DBG("oid %x oxid %x resp_len %zd frame_off %u\n",
ep->oid, ep->oxid, tlen, frame_off);
if (tlen <= frame_off)
return SCST_TGT_RES_SUCCESS;
remaining = tlen - frame_off;
if (remaining > UINT_MAX)
FT_ERR("oid %x oxid %x resp_len %zd frame_off %u\n",
ep->oid, ep->oxid, tlen, frame_off);
mem_len = scst_get_buf_first(cmd, &from);
mem_off = 0;
if (!mem_len) {
FT_IO_DBG("mem_len 0\n");
return SCST_TGT_RES_SUCCESS;
}
FT_IO_DBG("sid %x oxid %x mem_len %zd frame_off %u remaining %zd\n",
ep->sid, ep->oxid, mem_len, frame_off, remaining);
/*
* If we've already transferred some of the data, skip through
* the buffer over the data already sent and continue with the
* same sequence. Otherwise, get a new sequence for the data.
*/
if (frame_off) {
tlen = frame_off;
while (mem_len <= tlen) {
tlen -= mem_len;
scst_put_buf(cmd, from);
mem_len = scst_get_buf_next(cmd, &from);
if (!mem_len)
return SCST_TGT_RES_SUCCESS;
}
mem_len -= tlen;
mem_off = tlen;
} else
fcmd->seq = lport->tt.seq_start_next(fcmd->seq);
/* no scatter/gather in skb for odd word length due to fc_seq_send() */
use_sg = !(remaining % 4) && lport->sg_supp;
while (remaining) {
if (!loop_limit) {
FT_ERR("hit loop limit. remaining %zx mem_len %zx "
"frame_len %zx tlen %zx\n",
remaining, mem_len, frame_len, tlen);
break;
}
loop_limit--;
if (!mem_len) {
scst_put_buf(cmd, from);
mem_len = scst_get_buf_next(cmd, &from);
mem_off = 0;
if (!mem_len) {
FT_ERR("mem_len 0 from get_buf_next\n");
break;
}
}
if (!frame_len) {
frame_len = fcmd->max_lso_payload;
frame_len = min(frame_len, remaining);
fp = fc_frame_alloc(lport, use_sg ? 0 : frame_len);
if (!fp) {
FT_IO_DBG("frame_alloc failed. "
"use_sg %d frame_len %zd\n",
use_sg, frame_len);
break;
}
fr_max_payload(fp) = fcmd->max_payload;
to = fc_frame_payload_get(fp, 0);
fh_off = frame_off;
frame_off += frame_len;
}
tlen = min(mem_len, frame_len);
BUG_ON(!tlen);
BUG_ON(tlen > remaining);
BUG_ON(tlen > mem_len);
BUG_ON(tlen > frame_len);
if (use_sg) {
page = virt_to_page(from + mem_off);
get_page(page);
tlen = min_t(size_t, tlen,
PAGE_SIZE - (mem_off & ~PAGE_MASK));
skb_fill_page_desc(fp_skb(fp),
skb_shinfo(fp_skb(fp))->nr_frags,
page, mem_off, tlen);
fr_len(fp) += tlen;
fp_skb(fp)->data_len += tlen;
fp_skb(fp)->truesize +=
PAGE_SIZE << compound_order(page);
} else {
memcpy(to, from + mem_off, tlen);
to += tlen;
}
mem_len -= tlen;
mem_off += tlen;
frame_len -= tlen;
remaining -= tlen;
if (frame_len)
continue;
fc_fill_fc_hdr(fp, FC_RCTL_DD_SOL_DATA, ep->did, ep->sid,
FC_TYPE_FCP,
remaining ? (FC_FC_EX_CTX | FC_FC_REL_OFF) :
(FC_FC_EX_CTX | FC_FC_REL_OFF | FC_FC_END_SEQ),
fh_off);
error = lport->tt.seq_send(lport, fcmd->seq, fp);
if (error) {
WARN_ON(1);
/* XXX For now, initiator will retry */
} else
fcmd->read_data_len = frame_off;
}
if (mem_len)
scst_put_buf(cmd, from);
if (remaining) {
FT_IO_DBG("remaining read data %zd\n", remaining);
return SCST_TGT_RES_QUEUE_FULL;
}
return SCST_TGT_RES_SUCCESS;
}

94
fcst/ft_scst.c Normal file
View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2010 Cisco Systems, Inc.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <scsi/libfc.h>
#include "fcst.h"
MODULE_AUTHOR("Joe Eykholt <jeykholt@cisco.com>");
MODULE_DESCRIPTION("Fibre-Channel SCST target");
MODULE_LICENSE("GPL v2");
unsigned int ft_debug_logging;
module_param_named(debug_logging, ft_debug_logging, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug_logging, "log levels bigmask");
DEFINE_MUTEX(ft_lport_lock);
/*
* Provider ops for libfc.
*/
static struct fc4_prov ft_prov = {
.prli = ft_prli,
.prlo = ft_prlo,
.recv = ft_recv,
.module = THIS_MODULE,
};
static struct notifier_block ft_notifier = {
.notifier_call = ft_lport_notify
};
/*
* SCST target ops and configuration.
* XXX - re-check uninitialized fields
*/
struct scst_tgt_template ft_scst_template = {
.sg_tablesize = 128, /* XXX get true limit from libfc */
.xmit_response_atomic = 1,
.rdy_to_xfer_atomic = 1,
.xmit_response = ft_send_response,
.rdy_to_xfer = ft_send_xfer_rdy,
.on_hw_pending_cmd_timeout = ft_cmd_timeout,
.on_free_cmd = ft_cmd_free,
.task_mgmt_fn_done = ft_cmd_tm_done,
.detect = ft_tgt_detect,
.release = ft_tgt_release,
.report_aen = ft_report_aen,
.enable_target = ft_tgt_enable,
.is_target_enabled = ft_tgt_enabled,
.name = FT_MODULE,
};
static int __init ft_module_init(void)
{
int err;
err = scst_register_target_template(&ft_scst_template);
if (err)
return err;
err = fc_fc4_register_provider(FC_TYPE_FCP, &ft_prov);
if (err) {
scst_unregister_target_template(&ft_scst_template);
return err;
}
blocking_notifier_chain_register(&fc_lport_notifier_head, &ft_notifier);
fc_lport_iterate(ft_lport_add, NULL);
return 0;
}
module_init(ft_module_init);
static void __exit ft_module_exit(void)
{
blocking_notifier_chain_unregister(&fc_lport_notifier_head,
&ft_notifier);
fc_fc4_deregister_provider(FC_TYPE_FCP, &ft_prov);
fc_lport_iterate(ft_lport_del, NULL);
scst_unregister_target_template(&ft_scst_template);
synchronize_rcu();
}
module_exit(ft_module_exit);

549
fcst/ft_sess.c Normal file
View File

@@ -0,0 +1,549 @@
/*
* Copyright (c) 2010 Cisco Systems, Inc.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/hash.h>
#include <asm/unaligned.h>
#include <scsi/libfc.h>
#include <scsi/fc/fc_els.h>
#include "fcst.h"
static int ft_tport_count;
static ssize_t ft_format_wwn(char *buf, size_t len, u64 wwn)
{
u8 b[8];
put_unaligned_be64(wwn, b);
return snprintf(buf, len,
"%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x",
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
}
/*
* Lookup or allocate target local port.
* Caller holds ft_lport_lock.
*/
static struct ft_tport *ft_tport_create(struct fc_lport *lport)
{
struct ft_tport *tport;
char name[FT_NAMELEN];
int i;
ft_format_wwn(name, sizeof(name), lport->wwpn);
FT_SESS_DBG("create %s\n", name);
tport = rcu_dereference(lport->prov[FC_TYPE_FCP]);
if (tport)
return tport;
tport = kzalloc(sizeof(*tport), GFP_KERNEL);
if (!tport)
return NULL;
tport->tgt = scst_register(&ft_scst_template, name);
if (!tport->tgt) {
kfree(tport);
return NULL;
}
scst_tgt_set_tgt_priv(tport->tgt, tport);
ft_tport_count++;
tport->lport = lport;
for (i = 0; i < FT_SESS_HASH_SIZE; i++)
INIT_HLIST_HEAD(&tport->hash[i]);
rcu_assign_pointer(lport->prov[FC_TYPE_FCP], tport);
return tport;
}
/*
* Free tport via RCU.
*/
static void ft_tport_rcu_free(struct rcu_head *rcu)
{
struct ft_tport *tport = container_of(rcu, struct ft_tport, rcu);
kfree(tport);
}
/*
* Delete target local port, if any, associated with the local port.
* Caller holds ft_lport_lock.
*/
static void ft_tport_delete(struct ft_tport *tport)
{
struct fc_lport *lport;
struct scst_tgt *tgt;
tgt = tport->tgt;
BUG_ON(!tgt);
FT_SESS_DBG("delete %s\n", scst_get_tgt_name(tgt));
scst_unregister(tgt);
lport = tport->lport;
BUG_ON(tport != lport->prov[FC_TYPE_FCP]);
rcu_assign_pointer(lport->prov[FC_TYPE_FCP], NULL);
tport->lport = NULL;
call_rcu(&tport->rcu, ft_tport_rcu_free);
ft_tport_count--;
}
/*
* Add local port.
* Called thru fc_lport_iterate().
*/
void ft_lport_add(struct fc_lport *lport, void *arg)
{
mutex_lock(&ft_lport_lock);
ft_tport_create(lport);
mutex_unlock(&ft_lport_lock);
}
/*
* Delete local port.
* Called thru fc_lport_iterate().
*/
void ft_lport_del(struct fc_lport *lport, void *arg)
{
struct ft_tport *tport;
mutex_lock(&ft_lport_lock);
tport = lport->prov[FC_TYPE_FCP];
if (tport)
ft_tport_delete(tport);
mutex_unlock(&ft_lport_lock);
}
/*
* Notification of local port change from libfc.
* Create or delete local port and associated tport.
*/
int ft_lport_notify(struct notifier_block *nb, unsigned long event, void *arg)
{
struct fc_lport *lport = arg;
switch (event) {
case FC_LPORT_EV_ADD:
ft_lport_add(lport, NULL);
break;
case FC_LPORT_EV_DEL:
ft_lport_del(lport, NULL);
break;
}
return NOTIFY_DONE;
}
/*
* Find session in local port.
* Sessions and hash lists are RCU-protected.
* A reference is taken which must be eventually freed.
*/
static struct ft_sess *ft_sess_get(struct fc_lport *lport, u32 port_id)
{
struct ft_tport *tport;
struct hlist_head *head;
struct hlist_node *pos;
struct ft_sess *sess = NULL;
rcu_read_lock();
tport = rcu_dereference(lport->prov[FC_TYPE_FCP]);
if (!tport)
goto out;
head = &tport->hash[hash_32(port_id, FT_SESS_HASH_BITS)];
hlist_for_each_entry_rcu(sess, pos, head, hash) {
if (sess->port_id == port_id) {
kref_get(&sess->kref);
rcu_read_unlock();
FT_SESS_DBG("port_id %x found %p\n", port_id, sess);
return sess;
}
}
out:
rcu_read_unlock();
FT_SESS_DBG("port_id %x not found\n", port_id);
return NULL;
}
/*
* Allocate session and enter it in the hash for the local port.
* Caller holds ft_lport_lock.
*/
static int ft_sess_create(struct ft_tport *tport, struct fc_rport_priv *rdata,
u32 fcp_parm)
{
struct ft_sess *sess;
struct scst_session *scst_sess;
struct hlist_head *head;
struct hlist_node *pos;
u32 port_id;
char name[FT_NAMELEN];
port_id = rdata->ids.port_id;
if (!rdata->maxframe_size) {
FT_SESS_DBG("port_id %x maxframe_size 0\n", port_id);
return FC_SPP_RESP_CONF;
}
head = &tport->hash[hash_32(port_id, FT_SESS_HASH_BITS)];
hlist_for_each_entry_rcu(sess, pos, head, hash) {
if (sess->port_id == port_id) {
sess->params = fcp_parm;
return 0;
}
}
sess = kzalloc(sizeof(*sess), GFP_KERNEL);
if (!sess)
return FC_SPP_RESP_RES; /* out of resources */
ft_format_wwn(name, sizeof(name), rdata->ids.port_name);
FT_SESS_DBG("register %s\n", name);
scst_sess = scst_register_session(tport->tgt, 0, name, NULL, NULL);
if (!scst_sess) {
kfree(sess);
return FC_SPP_RESP_RES; /* out of resources */
}
sess->scst_sess = scst_sess;
sess->tport = tport;
sess->port_id = port_id;
kref_init(&sess->kref); /* ref for table entry */
hlist_add_head_rcu(&sess->hash, head);
tport->sess_count++;
FT_SESS_DBG("port_id %x sess %p\n", port_id, sess);
sess->port_name = rdata->ids.port_name;
sess->max_payload = rdata->maxframe_size;
sess->max_lso_payload = rdata->maxframe_size;
if (tport->lport->seq_offload)
sess->max_lso_payload = tport->lport->lso_max;
sess->params = fcp_parm;
rdata->prli_count++;
scst_sess_set_tgt_priv(scst_sess, sess);
return 0;
}
/*
* Unhash the session.
* Caller holds ft_lport_lock.
*/
static void ft_sess_unhash(struct ft_sess *sess)
{
struct ft_tport *tport = sess->tport;
hlist_del_rcu(&sess->hash);
BUG_ON(!tport->sess_count);
tport->sess_count--;
sess->port_id = -1;
sess->params = 0;
}
/*
* Delete session from hash.
* Caller holds ft_lport_lock.
*/
static struct ft_sess *ft_sess_delete(struct ft_tport *tport, u32 port_id)
{
struct hlist_head *head;
struct hlist_node *pos;
struct ft_sess *sess;
head = &tport->hash[hash_32(port_id, FT_SESS_HASH_BITS)];
hlist_for_each_entry_rcu(sess, pos, head, hash) {
if (sess->port_id == port_id) {
ft_sess_unhash(sess);
return sess;
}
}
return NULL;
}
/*
* Remove session and send PRLO.
* This is called when the target is being deleted.
* Caller holds ft_lport_lock.
*/
static void ft_sess_close(struct ft_sess *sess)
{
struct fc_lport *lport;
u32 port_id;
lport = sess->tport->lport;
port_id = sess->port_id;
if (port_id == -1)
return;
FT_SESS_DBG("port_id %x\n", port_id);
ft_sess_unhash(sess);
/* XXX should send LOGO or PRLO to rport */
}
/*
* libfc ops involving sessions.
*/
/*
* Handle PRLI (process login) request.
* This could be a PRLI we're sending or receiving.
* Caller holds ft_lport_lock.
*/
static int ft_prli_locked(struct fc_rport_priv *rdata, u32 spp_len,
const struct fc_els_spp *rspp, struct fc_els_spp *spp)
{
struct ft_tport *tport;
u32 fcp_parm;
int ret;
if (rspp->spp_flags & (FC_SPP_OPA_VAL | FC_SPP_RPA_VAL))
return FC_SPP_RESP_NO_PA;
/*
* If both target and initiator bits are off, the SPP is invalid.
*/
fcp_parm = ntohl(rspp->spp_params); /* requested parameters */
if (!(fcp_parm & (FCP_SPPF_INIT_FCN | FCP_SPPF_TARG_FCN)))
return FC_SPP_RESP_INVL;
/*
* Create session (image pair) only if requested by
* EST_IMG_PAIR flag and if the requestor is an initiator.
*/
if (rspp->spp_flags & FC_SPP_EST_IMG_PAIR) {
spp->spp_flags |= FC_SPP_EST_IMG_PAIR;
if (!(fcp_parm & FCP_SPPF_INIT_FCN))
return FC_SPP_RESP_CONF;
tport = rcu_dereference(rdata->local_port->prov[FC_TYPE_FCP]);
if (!tport || !tport->enabled)
return 0; /* not a target for this local port */
ret = ft_sess_create(tport, rdata, fcp_parm);
if (ret)
return ret;
}
/*
* OR in our service parameters with other provider (initiator), if any.
* If the initiator indicates RETRY, we must support that, too.
* Don't force RETRY on the initiator, though.
*/
fcp_parm = ntohl(spp->spp_params); /* response parameters */
spp->spp_params = htonl(fcp_parm | FCP_SPPF_TARG_FCN);
return FC_SPP_RESP_ACK;
}
/**
* tcm_fcp_prli() - Handle incoming or outgoing PRLI for the FCP target
* @rdata: remote port private
* @spp_len: service parameter page length
* @rspp: received service parameter page (NULL for outgoing PRLI)
* @spp: response service parameter page
*
* Returns spp response code.
*/
int ft_prli(struct fc_rport_priv *rdata, u32 spp_len,
const struct fc_els_spp *rspp, struct fc_els_spp *spp)
{
int ret;
FT_SESS_DBG("starting PRLI port_id %x\n", rdata->ids.port_id);
mutex_lock(&ft_lport_lock);
ret = ft_prli_locked(rdata, spp_len, rspp, spp);
mutex_unlock(&ft_lport_lock);
FT_SESS_DBG("port_id %x flags %x parms %x ret %x\n",
rdata->ids.port_id,
rspp->spp_flags,
ntohl(spp->spp_params), ret);
return ret;
}
static void ft_sess_rcu_free(struct rcu_head *rcu)
{
struct ft_sess *sess = container_of(rcu, struct ft_sess, rcu);
kfree(sess);
}
static void ft_sess_free(struct kref *kref)
{
struct ft_sess *sess = container_of(kref, struct ft_sess, kref);
struct scst_session *scst_sess;
scst_sess = sess->scst_sess;
FT_SESS_DBG("unregister %s\n", scst_sess->initiator_name);
scst_unregister_session(scst_sess, 0, NULL);
call_rcu(&sess->rcu, ft_sess_rcu_free);
}
static void ft_sess_put(struct ft_sess *sess)
{
int sess_held = atomic_read(&sess->kref.refcount);
BUG_ON(!sess_held);
kref_put(&sess->kref, ft_sess_free);
}
/*
* Delete ft_sess for PRLO.
* Called with ft_lport_lock held.
*/
static struct ft_sess *ft_sess_lookup_delete(struct fc_rport_priv *rdata)
{
struct ft_sess *sess;
struct ft_tport *tport;
tport = rcu_dereference(rdata->local_port->prov[FC_TYPE_FCP]);
if (!tport)
return NULL;
sess = ft_sess_delete(tport, rdata->ids.port_id);
if (sess)
sess->params = 0;
return sess;
}
/*
* Handle PRLO.
*/
void ft_prlo(struct fc_rport_priv *rdata)
{
struct ft_sess *sess;
mutex_lock(&ft_lport_lock);
sess = ft_sess_lookup_delete(rdata);
mutex_unlock(&ft_lport_lock);
if (!sess)
return;
/*
* Release the session hold from the table.
* When all command-starting threads have returned,
* kref will call ft_sess_free which will unregister
* the session.
* fcmds referencing the session are safe.
*/
ft_sess_put(sess); /* release from table */
rdata->prli_count--;
}
/*
* Handle incoming FCP request.
*
* Caller has verified that the frame is type FCP.
* Note that this may be called directly from the softirq context.
*/
void ft_recv(struct fc_lport *lport, struct fc_seq *sp, struct fc_frame *fp)
{
struct ft_sess *sess;
struct fc_frame_header *fh;
u32 sid;
fh = fc_frame_header_get(fp);
sid = ntoh24(fh->fh_s_id);
FT_SESS_DBG("sid %x preempt %x\n", sid, preempt_count());
sess = ft_sess_get(lport, sid);
if (!sess) {
FT_SESS_DBG("sid %x sess lookup failed\n", sid);
lport->tt.exch_done(sp);
/* TBD XXX - if FCP_CMND, send LOGO */
fc_frame_free(fp);
return;
}
FT_SESS_DBG("sid %x sess lookup returned %p preempt %x\n",
sid, sess, preempt_count());
ft_recv_req(sess, sp, fp);
ft_sess_put(sess);
}
/*
* Release all sessions for a target.
* Called through scst_unregister() as well as directly.
* Caller holds ft_lport_lock.
*/
int ft_tgt_release(struct scst_tgt *tgt)
{
struct ft_tport *tport;
struct hlist_head *head;
struct hlist_node *pos;
struct ft_sess *sess;
tport = scst_tgt_get_tgt_priv(tgt);
tport->enabled = 0;
tport->lport->service_params &= ~FCP_SPPF_TARG_FCN;
for (head = tport->hash; head < &tport->hash[FT_SESS_HASH_SIZE]; head++)
hlist_for_each_entry_rcu(sess, pos, head, hash)
ft_sess_close(sess);
synchronize_rcu();
return 0;
}
ssize_t ft_tgt_enable(struct scst_tgt *tgt, const char *buf, size_t len)
{
struct ft_tport *tport;
ssize_t ret = len;
mutex_lock(&ft_lport_lock);
switch (buf[0]) {
case '0':
FT_SESS_DBG("disable tgt %s\n", tgt->tgt_name);
ft_tgt_release(tgt);
break;
case '1':
FT_SESS_DBG("enable tgt %s\n", tgt->tgt_name);
tport = scst_tgt_get_tgt_priv(tgt);
tport->enabled = 1;
tport->lport->service_params |= FCP_SPPF_TARG_FCN;
break;
default:
ret = -EINVAL;
break;
}
mutex_unlock(&ft_lport_lock);
return ret;
}
bool ft_tgt_enabled(struct scst_tgt *tgt)
{
struct ft_tport *tport;
tport = scst_tgt_get_tgt_priv(tgt);
return tport->enabled;
}
int ft_tgt_detect(struct scst_tgt_template *tt)
{
return ft_tport_count;
}
/*
* Report AEN (Asynchronous Event Notification) from device to initiator.
* See notes in scst.h.
*/
int ft_report_aen(struct scst_aen *aen)
{
struct ft_sess *sess;
sess = scst_sess_get_tgt_priv(scst_aen_get_sess(aen));
FT_SESS_DBG("AEN event %d sess to %x lun %lld\n",
aen->event_fn, sess->port_id, scst_aen_get_lun(aen));
return SCST_AEN_RES_FAILED; /* XXX TBD */
}

View File

@@ -0,0 +1,241 @@
libfc: recode incoming PRLI handling
Reduce indentation in fc_rport_recv_prli_req() using gotos.
Also add payload length checks.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/libfc/fc_rport.c | 195 ++++++++++++++++++-----------------------
1 files changed, 87 insertions(+), 108 deletions(-)
---
diff --git a/drivers/scsi/libfc/fc_rport.c b/drivers/scsi/libfc/fc_rport.c
index 97923bb..09ec635 100644
--- a/drivers/scsi/libfc/fc_rport.c
+++ b/drivers/scsi/libfc/fc_rport.c
@@ -1441,136 +1441,115 @@ static void fc_rport_recv_prli_req(struct fc_rport_priv *rdata,
struct fc_els_spp *spp; /* response spp */
unsigned int len;
unsigned int plen;
- enum fc_els_rjt_reason reason = ELS_RJT_UNAB;
- enum fc_els_rjt_explan explan = ELS_EXPL_NONE;
enum fc_els_spp_resp resp;
struct fc_seq_els_data rjt_data;
u32 f_ctl;
u32 fcp_parm;
u32 roles = FC_RPORT_ROLE_UNKNOWN;
- rjt_data.fp = NULL;
+ rjt_data.fp = NULL;
fh = fc_frame_header_get(rx_fp);
FC_RPORT_DBG(rdata, "Received PRLI request while in state %s\n",
fc_rport_state(rdata));
- switch (rdata->rp_state) {
- case RPORT_ST_PRLI:
- case RPORT_ST_RTV:
- case RPORT_ST_READY:
- case RPORT_ST_ADISC:
- reason = ELS_RJT_NONE;
- break;
- default:
- fc_frame_free(rx_fp);
- return;
- break;
- }
len = fr_len(rx_fp) - sizeof(*fh);
pp = fc_frame_payload_get(rx_fp, sizeof(*pp));
- if (pp == NULL) {
- reason = ELS_RJT_PROT;
- explan = ELS_EXPL_INV_LEN;
- } else {
- plen = ntohs(pp->prli.prli_len);
- if ((plen % 4) != 0 || plen > len) {
- reason = ELS_RJT_PROT;
- explan = ELS_EXPL_INV_LEN;
- } else if (plen < len) {
- len = plen;
- }
- plen = pp->prli.prli_spp_len;
- if ((plen % 4) != 0 || plen < sizeof(*spp) ||
- plen > len || len < sizeof(*pp)) {
- reason = ELS_RJT_PROT;
- explan = ELS_EXPL_INV_LEN;
- }
- rspp = &pp->spp;
+ if (!pp)
+ goto reject_len;
+ plen = ntohs(pp->prli.prli_len);
+ if ((plen % 4) != 0 || plen > len || plen < 16)
+ goto reject_len;
+ if (plen < len)
+ len = plen;
+ plen = pp->prli.prli_spp_len;
+ if ((plen % 4) != 0 || plen < sizeof(*spp) ||
+ plen > len || len < sizeof(*pp) || plen < 12)
+ goto reject_len;
+ rspp = &pp->spp;
+
+ fp = fc_frame_alloc(lport, len);
+ if (!fp) {
+ rjt_data.reason = ELS_RJT_UNAB;
+ rjt_data.explan = ELS_EXPL_INSUF_RES;
+ goto reject;
}
- if (reason != ELS_RJT_NONE ||
- (fp = fc_frame_alloc(lport, len)) == NULL) {
- rjt_data.reason = reason;
- rjt_data.explan = explan;
- lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
- } else {
- sp = lport->tt.seq_start_next(sp);
- WARN_ON(!sp);
- pp = fc_frame_payload_get(fp, len);
- WARN_ON(!pp);
- memset(pp, 0, len);
- pp->prli.prli_cmd = ELS_LS_ACC;
- pp->prli.prli_spp_len = plen;
- pp->prli.prli_len = htons(len);
- len -= sizeof(struct fc_els_prli);
-
- /* reinitialize remote port roles */
- rdata->ids.roles = FC_RPORT_ROLE_UNKNOWN;
-
- /*
- * Go through all the service parameter pages and build
- * response. If plen indicates longer SPP than standard,
- * use that. The entire response has been pre-cleared above.
- */
- spp = &pp->spp;
- while (len >= plen) {
- spp->spp_type = rspp->spp_type;
- spp->spp_type_ext = rspp->spp_type_ext;
- spp->spp_flags = rspp->spp_flags & FC_SPP_EST_IMG_PAIR;
- resp = FC_SPP_RESP_ACK;
- if (rspp->spp_flags & FC_SPP_RPA_VAL)
- resp = FC_SPP_RESP_NO_PA;
- switch (rspp->spp_type) {
- case 0: /* common to all FC-4 types */
- break;
- case FC_TYPE_FCP:
- fcp_parm = ntohl(rspp->spp_params);
- if (fcp_parm & FCP_SPPF_RETRY)
- rdata->flags |= FC_RP_FLAGS_RETRY;
- rdata->supported_classes = FC_COS_CLASS3;
- if (fcp_parm & FCP_SPPF_INIT_FCN)
- roles |= FC_RPORT_ROLE_FCP_INITIATOR;
- if (fcp_parm & FCP_SPPF_TARG_FCN)
- roles |= FC_RPORT_ROLE_FCP_TARGET;
- rdata->ids.roles = roles;
-
- spp->spp_params =
- htonl(lport->service_params);
- break;
- default:
- resp = FC_SPP_RESP_INVL;
- break;
- }
- spp->spp_flags |= resp;
- len -= plen;
- rspp = (struct fc_els_spp *)((char *)rspp + plen);
- spp = (struct fc_els_spp *)((char *)spp + plen);
- }
+ sp = lport->tt.seq_start_next(sp);
+ WARN_ON(!sp);
+ pp = fc_frame_payload_get(fp, len);
+ WARN_ON(!pp);
+ memset(pp, 0, len);
+ pp->prli.prli_cmd = ELS_LS_ACC;
+ pp->prli.prli_spp_len = plen;
+ pp->prli.prli_len = htons(len);
+ len -= sizeof(struct fc_els_prli);
- /*
- * Send LS_ACC. If this fails, the originator should retry.
- */
- f_ctl = FC_FC_EX_CTX | FC_FC_LAST_SEQ;
- f_ctl |= FC_FC_END_SEQ | FC_FC_SEQ_INIT;
- ep = fc_seq_exch(sp);
- fc_fill_fc_hdr(fp, FC_RCTL_ELS_REP, ep->did, ep->sid,
- FC_TYPE_ELS, f_ctl, 0);
- lport->tt.seq_send(lport, sp, fp);
+ /* reinitialize remote port roles */
+ rdata->ids.roles = FC_RPORT_ROLE_UNKNOWN;
- /*
- * Get lock and re-check state.
- */
- switch (rdata->rp_state) {
- case RPORT_ST_PRLI:
- fc_rport_enter_ready(rdata);
+ /*
+ * Go through all the service parameter pages and build
+ * response. If plen indicates longer SPP than standard,
+ * use that. The entire response has been pre-cleared above.
+ */
+ spp = &pp->spp;
+ while (len >= plen) {
+ spp->spp_type = rspp->spp_type;
+ spp->spp_type_ext = rspp->spp_type_ext;
+ spp->spp_flags = rspp->spp_flags & FC_SPP_EST_IMG_PAIR;
+ resp = FC_SPP_RESP_ACK;
+
+ switch (rspp->spp_type) {
+ case 0: /* common to all FC-4 types */
break;
- case RPORT_ST_READY:
- case RPORT_ST_ADISC:
+ case FC_TYPE_FCP:
+ fcp_parm = ntohl(rspp->spp_params);
+ if (fcp_parm & FCP_SPPF_RETRY)
+ rdata->flags |= FC_RP_FLAGS_RETRY;
+ rdata->supported_classes = FC_COS_CLASS3;
+ if (fcp_parm & FCP_SPPF_INIT_FCN)
+ roles |= FC_RPORT_ROLE_FCP_INITIATOR;
+ if (fcp_parm & FCP_SPPF_TARG_FCN)
+ roles |= FC_RPORT_ROLE_FCP_TARGET;
+ rdata->ids.roles = roles;
+
+ spp->spp_params = htonl(lport->service_params);
break;
default:
+ resp = FC_SPP_RESP_INVL;
break;
}
+ spp->spp_flags |= resp;
+ len -= plen;
+ rspp = (struct fc_els_spp *)((char *)rspp + plen);
+ spp = (struct fc_els_spp *)((char *)spp + plen);
+ }
+
+ /*
+ * Send LS_ACC. If this fails, the originator should retry.
+ */
+ f_ctl = FC_FC_EX_CTX | FC_FC_LAST_SEQ;
+ f_ctl |= FC_FC_END_SEQ | FC_FC_SEQ_INIT;
+ ep = fc_seq_exch(sp);
+ fc_fill_fc_hdr(fp, FC_RCTL_ELS_REP, ep->did, ep->sid,
+ FC_TYPE_ELS, f_ctl, 0);
+ lport->tt.seq_send(lport, sp, fp);
+
+ switch (rdata->rp_state) {
+ case RPORT_ST_PRLI:
+ fc_rport_enter_ready(rdata);
+ break;
+ default:
+ break;
}
+ goto drop;
+
+reject_len:
+ rjt_data.reason = ELS_RJT_PROT;
+ rjt_data.explan = ELS_EXPL_INV_LEN;
+reject:
+ lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
+drop:
fc_frame_free(rx_fp);
}

487
fcst/linux-patches/02-fc4 Normal file
View File

@@ -0,0 +1,487 @@
libfc: add hook for FC-4 provider registration
Allow FC-4 provider modules to hook into libfc, mostly for targets.
This should allow any FC-4 module to handle PRLI requests and maintain
process-association states.
Each provider registers its ops with libfc and then will be called for
any incoming PRLI for that FC-4 type on any instance. The provider
can decide whether to handle that particular instance using any method
it likes, such as ACLs or other configuration information.
A count is kept of the number of successful PRLIs from the remote port.
Providers are called back with an implicit PRLO when the remote port
is about to be deleted or has been reset.
fc_lport_recv_req() now sends incoming FC-4 requests to FC-4 providers,
and there is a built-in provider always registered for handling
incoming ELS requests.
The call to provider recv() routines uses rcu_read_lock()
so that providers aren't removed during the call. That lock is very
cheap and shouldn't affect any performance on ELS requests.
Providers can rely on the RCU lock to protect a session lookup as well.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/libfc/fc_libfc.c | 60 ++++++++++++++++++
drivers/scsi/libfc/fc_libfc.h | 11 +++
drivers/scsi/libfc/fc_lport.c | 63 ++++++++++++++++---
drivers/scsi/libfc/fc_rport.c | 133 +++++++++++++++++++++++++++++++++--------
include/scsi/libfc.h | 26 ++++++++
5 files changed, 255 insertions(+), 38 deletions(-)
---
diff --git a/drivers/scsi/libfc/fc_libfc.c b/drivers/scsi/libfc/fc_libfc.c
index 39f4b6a..ce0de44 100644
--- a/drivers/scsi/libfc/fc_libfc.c
+++ b/drivers/scsi/libfc/fc_libfc.c
@@ -34,6 +34,23 @@ unsigned int fc_debug_logging;
module_param_named(debug_logging, fc_debug_logging, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels");
+DEFINE_MUTEX(fc_prov_mutex);
+
+/*
+ * Providers which primarily send requests and PRLIs.
+ */
+struct fc4_prov *fc_active_prov[FC_FC4_PROV_SIZE] = {
+ [0] = &fc_rport_t0_prov,
+ [FC_TYPE_FCP] = &fc_rport_fcp_init,
+};
+
+/*
+ * Providers which receive requests.
+ */
+struct fc4_prov *fc_passive_prov[FC_FC4_PROV_SIZE] = {
+ [FC_TYPE_ELS] = &fc_lport_els_prov,
+};
+
/**
* libfc_init() - Initialize libfc.ko
*/
@@ -132,3 +149,46 @@ u32 fc_copy_buffer_to_sglist(void *buf, size_t len,
}
return copy_len;
}
+
+/**
+ * fc_fc4_register_provider() - register FC-4 upper-level provider.
+ * @type: FC-4 type, such as FC_TYPE_FCP
+ * @prov: structure describing provider including ops vector.
+ *
+ * Returns 0 on success, negative error otherwise.
+ */
+int fc_fc4_register_provider(enum fc_fh_type type, struct fc4_prov *prov)
+{
+ struct fc4_prov **prov_entry;
+ int ret = 0;
+
+ if (type >= FC_FC4_PROV_SIZE)
+ return -EINVAL;
+ mutex_lock(&fc_prov_mutex);
+ prov_entry = (prov->recv ? fc_passive_prov : fc_active_prov) + type;
+ if (*prov_entry)
+ ret = -EBUSY;
+ else
+ *prov_entry = prov;
+ mutex_unlock(&fc_prov_mutex);
+ return ret;
+}
+EXPORT_SYMBOL(fc_fc4_register_provider);
+
+/**
+ * fc_fc4_deregister_provider() - deregister FC-4 upper-level provider.
+ * @type: FC-4 type, such as FC_TYPE_FCP
+ * @prov: structure describing provider including ops vector.
+ */
+void fc_fc4_deregister_provider(enum fc_fh_type type, struct fc4_prov *prov)
+{
+ BUG_ON(type >= FC_FC4_PROV_SIZE);
+ mutex_lock(&fc_prov_mutex);
+ if (prov->recv)
+ rcu_assign_pointer(fc_passive_prov[type], NULL);
+ else
+ rcu_assign_pointer(fc_active_prov[type], NULL);
+ mutex_unlock(&fc_prov_mutex);
+ synchronize_rcu();
+}
+EXPORT_SYMBOL(fc_fc4_deregister_provider);
diff --git a/drivers/scsi/libfc/fc_libfc.h b/drivers/scsi/libfc/fc_libfc.h
index 741fd5c..c3d740c 100644
--- a/drivers/scsi/libfc/fc_libfc.h
+++ b/drivers/scsi/libfc/fc_libfc.h
@@ -82,6 +82,17 @@ extern unsigned int fc_debug_logging;
(lport)->host->host_no, ##args))
/*
+ * FC-4 Providers.
+ */
+extern struct fc4_prov *fc_active_prov[]; /* providers without recv */
+extern struct fc4_prov *fc_passive_prov[]; /* providers with recv */
+extern struct mutex fc_prov_mutex; /* lock over table changes */
+
+extern struct fc4_prov fc_rport_t0_prov; /* type 0 provider */
+extern struct fc4_prov fc_lport_els_prov; /* ELS provider */
+extern struct fc4_prov fc_rport_fcp_init; /* FCP initiator provider */
+
+/*
* Set up direct-data placement for this I/O request
*/
void fc_fcp_ddp_setup(struct fc_fcp_pkt *fsp, u16 xid);
diff --git a/drivers/scsi/libfc/fc_lport.c b/drivers/scsi/libfc/fc_lport.c
index 7ec8ce7..35149f0 100644
--- a/drivers/scsi/libfc/fc_lport.c
+++ b/drivers/scsi/libfc/fc_lport.c
@@ -847,7 +847,7 @@ out:
}
/**
- * fc_lport_recv_req() - The generic lport request handler
+ * fc_lport_recv_els_req() - The generic lport ELS request handler
* @lport: The local port that received the request
* @sp: The sequence the request is on
* @fp: The request frame
@@ -858,8 +858,8 @@ out:
* Locking Note: This function should not be called with the lport
* lock held becuase it will grab the lock.
*/
-static void fc_lport_recv_req(struct fc_lport *lport, struct fc_seq *sp,
- struct fc_frame *fp)
+static void fc_lport_recv_els_req(struct fc_lport *lport, struct fc_seq *sp,
+ struct fc_frame *fp)
{
struct fc_frame_header *fh = fc_frame_header_get(fp);
void (*recv) (struct fc_seq *, struct fc_frame *, struct fc_lport *);
@@ -873,8 +873,7 @@ static void fc_lport_recv_req(struct fc_lport *lport, struct fc_seq *sp,
*/
if (!lport->link_up)
fc_frame_free(fp);
- else if (fh->fh_type == FC_TYPE_ELS &&
- fh->fh_r_ctl == FC_RCTL_ELS_REQ) {
+ else {
/*
* Check opcode.
*/
@@ -903,17 +902,59 @@ static void fc_lport_recv_req(struct fc_lport *lport, struct fc_seq *sp,
}
recv(sp, fp, lport);
- } else {
- FC_LPORT_DBG(lport, "dropping invalid frame (eof %x)\n",
- fr_eof(fp));
- fc_frame_free(fp);
}
mutex_unlock(&lport->lp_mutex);
+ lport->tt.exch_done(sp);
+}
+
+static int fc_lport_els_prli(struct fc_rport_priv *rdata, u32 spp_len,
+ const struct fc_els_spp *spp_in,
+ struct fc_els_spp *spp_out)
+{
+ return FC_SPP_RESP_INVL;
+}
+
+struct fc4_prov fc_lport_els_prov = {
+ .prli = fc_lport_els_prli,
+ .recv = fc_lport_recv_els_req,
+};
+
+/**
+ * fc_lport_recv_req() - The generic lport request handler
+ * @lport: The lport that received the request
+ * @sp: The sequence the request is on
+ * @fp: The frame the request is in
+ *
+ * Locking Note: This function should not be called with the lport
+ * lock held becuase it may grab the lock.
+ */
+static void fc_lport_recv_req(struct fc_lport *lport, struct fc_seq *sp,
+ struct fc_frame *fp)
+{
+ struct fc_frame_header *fh = fc_frame_header_get(fp);
+ struct fc4_prov *prov;
/*
- * The common exch_done for all request may not be good
- * if any request requires longer hold on exhange. XXX
+ * Use RCU read lock and module_lock to be sure module doesn't
+ * deregister and get unloaded while we're calling it.
+ * try_module_get() is inlined and accepts a NULL parameter.
+ * Only ELSes and FCP target ops should come through here.
+ * The locking is unfortunate, and a better scheme is being sought.
*/
+ rcu_read_lock();
+ if (fh->fh_type >= FC_FC4_PROV_SIZE)
+ goto drop;
+ prov = rcu_dereference(fc_passive_prov[fh->fh_type]);
+ if (!prov || !try_module_get(prov->module))
+ goto drop;
+ rcu_read_unlock();
+ prov->recv(lport, sp, fp);
+ module_put(prov->module);
+ return;
+drop:
+ rcu_read_unlock();
+ FC_LPORT_DBG(lport, "dropping unexpected frame type %x\n", fh->fh_type);
+ fc_frame_free(fp);
lport->tt.exch_done(sp);
}
diff --git a/drivers/scsi/libfc/fc_rport.c b/drivers/scsi/libfc/fc_rport.c
index 09ec635..3713060 100644
--- a/drivers/scsi/libfc/fc_rport.c
+++ b/drivers/scsi/libfc/fc_rport.c
@@ -245,6 +245,8 @@ static void fc_rport_work(struct work_struct *work)
struct fc_rport_operations *rport_ops;
struct fc_rport_identifiers ids;
struct fc_rport *rport;
+ struct fc4_prov *prov;
+ u8 type;
int restart = 0;
mutex_lock(&rdata->rp_mutex);
@@ -294,6 +296,15 @@ static void fc_rport_work(struct work_struct *work)
case RPORT_EV_FAILED:
case RPORT_EV_LOGO:
case RPORT_EV_STOP:
+ if (rdata->prli_count) {
+ mutex_lock(&fc_prov_mutex);
+ for (type = 1; type < FC_FC4_PROV_SIZE; type++) {
+ prov = fc_passive_prov[type];
+ if (prov && prov->prlo)
+ prov->prlo(rdata);
+ }
+ mutex_unlock(&fc_prov_mutex);
+ }
port_id = rdata->ids.port_id;
mutex_unlock(&rdata->rp_mutex);
@@ -1433,6 +1444,7 @@ static void fc_rport_recv_prli_req(struct fc_rport_priv *rdata,
struct fc_exch *ep;
struct fc_frame *fp;
struct fc_frame_header *fh;
+ struct fc4_prov *prov;
struct {
struct fc_els_prli prli;
struct fc_els_spp spp;
@@ -1442,10 +1454,9 @@ static void fc_rport_recv_prli_req(struct fc_rport_priv *rdata,
unsigned int len;
unsigned int plen;
enum fc_els_spp_resp resp;
+ enum fc_els_spp_resp passive;
struct fc_seq_els_data rjt_data;
u32 f_ctl;
- u32 fcp_parm;
- u32 roles = FC_RPORT_ROLE_UNKNOWN;
rjt_data.fp = NULL;
fh = fc_frame_header_get(rx_fp);
@@ -1484,46 +1495,41 @@ static void fc_rport_recv_prli_req(struct fc_rport_priv *rdata,
pp->prli.prli_len = htons(len);
len -= sizeof(struct fc_els_prli);
- /* reinitialize remote port roles */
- rdata->ids.roles = FC_RPORT_ROLE_UNKNOWN;
-
/*
* Go through all the service parameter pages and build
* response. If plen indicates longer SPP than standard,
* use that. The entire response has been pre-cleared above.
*/
spp = &pp->spp;
+ mutex_lock(&fc_prov_mutex);
while (len >= plen) {
spp->spp_type = rspp->spp_type;
spp->spp_type_ext = rspp->spp_type_ext;
- spp->spp_flags = rspp->spp_flags & FC_SPP_EST_IMG_PAIR;
- resp = FC_SPP_RESP_ACK;
-
- switch (rspp->spp_type) {
- case 0: /* common to all FC-4 types */
- break;
- case FC_TYPE_FCP:
- fcp_parm = ntohl(rspp->spp_params);
- if (fcp_parm & FCP_SPPF_RETRY)
- rdata->flags |= FC_RP_FLAGS_RETRY;
- rdata->supported_classes = FC_COS_CLASS3;
- if (fcp_parm & FCP_SPPF_INIT_FCN)
- roles |= FC_RPORT_ROLE_FCP_INITIATOR;
- if (fcp_parm & FCP_SPPF_TARG_FCN)
- roles |= FC_RPORT_ROLE_FCP_TARGET;
- rdata->ids.roles = roles;
-
- spp->spp_params = htonl(lport->service_params);
- break;
- default:
- resp = FC_SPP_RESP_INVL;
- break;
+ resp = 0;
+
+ if (rspp->spp_type < FC_FC4_PROV_SIZE) {
+ prov = fc_active_prov[rspp->spp_type];
+ if (prov)
+ resp = prov->prli(rdata, plen, rspp, spp);
+ prov = fc_passive_prov[rspp->spp_type];
+ if (prov) {
+ passive = prov->prli(rdata, plen, rspp, spp);
+ if (!resp || passive == FC_SPP_RESP_ACK)
+ resp = passive;
+ }
+ }
+ if (!resp) {
+ if (spp->spp_flags & FC_SPP_EST_IMG_PAIR)
+ resp |= FC_SPP_RESP_CONF;
+ else
+ resp |= FC_SPP_RESP_INVL;
}
spp->spp_flags |= resp;
len -= plen;
rspp = (struct fc_els_spp *)((char *)rspp + plen);
spp = (struct fc_els_spp *)((char *)spp + plen);
}
+ mutex_unlock(&fc_prov_mutex);
/*
* Send LS_ACC. If this fails, the originator should retry.
@@ -1668,6 +1674,79 @@ int fc_rport_init(struct fc_lport *lport)
EXPORT_SYMBOL(fc_rport_init);
/**
+ * fc_rport_fcp_prli() - Handle incoming PRLI for the FCP initiator.
+ * @rdata: remote port private
+ * @spp_len: service parameter page length
+ * @rspp: received service parameter page
+ * @spp: response service parameter page
+ *
+ * Returns the value for the response code to be placed in spp_flags;
+ * Returns 0 if not an initiator.
+ */
+static int fc_rport_fcp_prli(struct fc_rport_priv *rdata, u32 spp_len,
+ const struct fc_els_spp *rspp,
+ struct fc_els_spp *spp)
+{
+ struct fc_lport *lport = rdata->local_port;
+ u32 fcp_parm;
+
+ fcp_parm = ntohl(rspp->spp_params);
+ rdata->ids.roles = FC_RPORT_ROLE_UNKNOWN;
+ if (fcp_parm & FCP_SPPF_INIT_FCN)
+ rdata->ids.roles |= FC_RPORT_ROLE_FCP_INITIATOR;
+ if (fcp_parm & FCP_SPPF_TARG_FCN)
+ rdata->ids.roles |= FC_RPORT_ROLE_FCP_TARGET;
+ if (fcp_parm & FCP_SPPF_RETRY)
+ rdata->flags |= FC_RP_FLAGS_RETRY;
+ rdata->supported_classes = FC_COS_CLASS3;
+
+ if (!(lport->service_params & FC_RPORT_ROLE_FCP_INITIATOR))
+ return 0;
+
+ spp->spp_flags |= rspp->spp_flags & FC_SPP_EST_IMG_PAIR;
+
+ /*
+ * OR in our service parameters with other providers (target), if any.
+ */
+ fcp_parm = ntohl(spp->spp_params);
+ spp->spp_params = htonl(fcp_parm | lport->service_params);
+ return FC_SPP_RESP_ACK;
+}
+
+/*
+ * FC-4 provider ops for FCP initiator.
+ */
+struct fc4_prov fc_rport_fcp_init = {
+ .prli = fc_rport_fcp_prli,
+};
+
+/**
+ * fc_rport_t0_prli() - Handle incoming PRLI parameters for type 0
+ * @rdata: remote port private
+ * @spp_len: service parameter page length
+ * @rspp: received service parameter page
+ * @spp: response service parameter page
+ */
+static int fc_rport_t0_prli(struct fc_rport_priv *rdata, u32 spp_len,
+ const struct fc_els_spp *rspp,
+ struct fc_els_spp *spp)
+{
+ if (rspp->spp_flags & FC_SPP_EST_IMG_PAIR)
+ return FC_SPP_RESP_INVL;
+ return FC_SPP_RESP_ACK;
+}
+
+/*
+ * FC-4 provider ops for type 0 service parameters.
+ *
+ * This handles the special case of type 0 which is always successful
+ * but doesn't do anything otherwise.
+ */
+struct fc4_prov fc_rport_t0_prov = {
+ .prli = fc_rport_t0_prli,
+};
+
+/**
* fc_setup_rport() - Initialize the rport_event_queue
*/
int fc_setup_rport()
diff --git a/include/scsi/libfc.h b/include/scsi/libfc.h
index 4b912ee..9403d4e 100644
--- a/include/scsi/libfc.h
+++ b/include/scsi/libfc.h
@@ -35,6 +35,8 @@
#include <scsi/fc_frame.h>
+#define FC_FC4_PROV_SIZE (FC_TYPE_FCP + 1) /* size of tables */
+
/*
* libfc error codes
*/
@@ -190,6 +192,7 @@ struct fc_rport_libfc_priv {
* @rp_mutex: The mutex that protects the remote port
* @retry_work: Handle for retries
* @event_callback: Callback when READY, FAILED or LOGO states complete
+ * @prli_count: Count of open PRLI sessions in providers
*/
struct fc_rport_priv {
struct fc_lport *local_port;
@@ -211,6 +214,7 @@ struct fc_rport_priv {
struct list_head peers;
struct work_struct event_work;
u32 supported_classes;
+ u16 prli_count;
};
/**
@@ -850,6 +854,28 @@ struct fc_lport {
struct delayed_work retry_work;
};
+/**
+ * struct fc4_prov - FC-4 provider registration
+ * @prli: Handler for incoming PRLI
+ * @prlo: Handler for session reset
+ * @recv: Handler for incoming request
+ * @module: Pointer to module. May be NULL.
+ */
+struct fc4_prov {
+ int (*prli)(struct fc_rport_priv *, u32 spp_len,
+ const struct fc_els_spp *spp_in,
+ struct fc_els_spp *spp_out);
+ void (*prlo)(struct fc_rport_priv *);
+ void (*recv)(struct fc_lport *, struct fc_seq *, struct fc_frame *);
+ struct module *module;
+};
+
+/*
+ * Register FC-4 provider with libfc.
+ */
+int fc_fc4_register_provider(enum fc_fh_type type, struct fc4_prov *);
+void fc_fc4_deregister_provider(enum fc_fh_type type, struct fc4_prov *);
+
/*
* FC_LPORT HELPER FUNCTIONS
*****************************/

View File

@@ -0,0 +1,31 @@
libfc: fix sequence-initiative WARN in fc_seq_start_next
The target code was getting a warning that sequence initiative
wasn't held when starting the response sequence after sending
the data sequence.
The bug was that sequence initiative was cleared due to the
END_SEQ flag being on. The intent may have been to check LAST_SEQ.
Change just to check SEQ_INIT.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/libfc/fc_exch.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
---
diff --git a/drivers/scsi/libfc/fc_exch.c b/drivers/scsi/libfc/fc_exch.c
index 7f43647..1ad1ded 100644
--- a/drivers/scsi/libfc/fc_exch.c
+++ b/drivers/scsi/libfc/fc_exch.c
@@ -488,7 +488,7 @@ static int fc_seq_send(struct fc_lport *lport, struct fc_seq *sp,
*/
spin_lock_bh(&ep->ex_lock);
ep->f_ctl = f_ctl & ~FC_FC_FIRST_SEQ; /* not first seq */
- if (f_ctl & (FC_FC_END_SEQ | FC_FC_SEQ_INIT))
+ if (f_ctl & FC_FC_SEQ_INIT)
ep->esb_stat &= ~ESB_ST_SEQ_INIT;
spin_unlock_bh(&ep->ex_lock);
return error;

View File

@@ -0,0 +1,84 @@
libfc: add method for setting handler for incoming exchange
Add a method for setting handler for incoming exchange.
For multi-sequence exchanges, this allows the target driver
to add a response handler for handling subsequent sequences,
and exchange manager resets.
The new function is called fc_seq_set_resp().
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/libfc/fc_exch.c | 19 +++++++++++++++++++
include/scsi/libfc.h | 11 ++++++++++-
2 files changed, 29 insertions(+), 1 deletions(-)
---
diff --git a/drivers/scsi/libfc/fc_exch.c b/drivers/scsi/libfc/fc_exch.c
index 1ad1ded..aee3c59 100644
--- a/drivers/scsi/libfc/fc_exch.c
+++ b/drivers/scsi/libfc/fc_exch.c
@@ -545,6 +545,22 @@ static struct fc_seq *fc_seq_start_next(struct fc_seq *sp)
return sp;
}
+/*
+ * Set the response handler for the exchange associated with a sequence.
+ */
+static void fc_seq_set_resp(struct fc_seq *sp,
+ void (*resp)(struct fc_seq *, struct fc_frame *,
+ void *),
+ void *arg)
+{
+ struct fc_exch *ep = fc_seq_exch(sp);
+
+ spin_lock_bh(&ep->ex_lock);
+ ep->resp = resp;
+ ep->arg = arg;
+ spin_unlock_bh(&ep->ex_lock);
+}
+
/**
* fc_seq_exch_abort() - Abort an exchange and sequence
* @req_sp: The sequence to be aborted
@@ -2280,6 +2296,9 @@ int fc_exch_init(struct fc_lport *lport)
if (!lport->tt.seq_start_next)
lport->tt.seq_start_next = fc_seq_start_next;
+ if (!lport->tt.seq_set_resp)
+ lport->tt.seq_set_resp = fc_seq_set_resp;
+
if (!lport->tt.exch_seq_send)
lport->tt.exch_seq_send = fc_exch_seq_send;
diff --git a/include/scsi/libfc.h b/include/scsi/libfc.h
index 9403d4e..87204da 100644
--- a/include/scsi/libfc.h
+++ b/include/scsi/libfc.h
@@ -566,6 +566,16 @@ struct libfc_function_template {
struct fc_seq *(*seq_start_next)(struct fc_seq *);
/*
+ * Set a response handler for the exchange of the sequence.
+ *
+ * STATUS: OPTIONAL
+ */
+ void (*seq_set_resp)(struct fc_seq *sp,
+ void (*resp)(struct fc_seq *, struct fc_frame *,
+ void *),
+ void *arg);
+
+ /*
* Reset an exchange manager, completing all sequences and exchanges.
* If s_id is non-zero, reset only exchanges originating from that FID.
* If d_id is non-zero, reset only exchanges sending to that FID.
@@ -1058,7 +1068,6 @@ struct fc_seq *fc_elsct_send(struct fc_lport *, u32 did,
void fc_lport_flogi_resp(struct fc_seq *, struct fc_frame *, void *);
void fc_lport_logo_resp(struct fc_seq *, struct fc_frame *, void *);
-
/*
* EXCHANGE MANAGER LAYER
*****************************/

View File

@@ -0,0 +1,36 @@
libfc: add local port hook for provider session lookup
The target provider needs a per-instance lookup table
or other way to lookup sessions quickly without going through
a linear list or serializing too much.
Add a simple void * array indexed by FC-4 type to the fc_lport.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
include/scsi/libfc.h | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
---
diff --git a/include/scsi/libfc.h b/include/scsi/libfc.h
index 87204da..6817fe3 100644
--- a/include/scsi/libfc.h
+++ b/include/scsi/libfc.h
@@ -810,6 +810,7 @@ struct fc_disc {
* @lp_mutex: Mutex to protect the local port
* @list: Handle for list of local ports
* @retry_work: Handle to local port for delayed retry context
+ * @prov: Pointers available for use by passive FC-4 providers
*/
struct fc_lport {
/* Associations */
@@ -862,6 +863,7 @@ struct fc_lport {
struct mutex lp_mutex;
struct list_head list;
struct delayed_work retry_work;
+ void *prov[FC_FC4_PROV_SIZE];
};
/**

View File

@@ -0,0 +1,167 @@
libfc: add hook to notify providers of local port changes
When an SCST provider is registered, it needs to know what
local ports are available for configuration as targets.
Add a notifier chain that is invoked when any local port
that is added or deleted.
Maintain a global list of local ports and add an
interator function that calls a given function for
every existing local port. This is used when first
loading a provider.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/libfc/fc_libfc.c | 41 +++++++++++++++++++++++++++++++++++++++++
drivers/scsi/libfc/fc_libfc.h | 2 ++
drivers/scsi/libfc/fc_lport.c | 2 ++
include/scsi/libfc.h | 14 +++++++++++++-
4 files changed, 58 insertions(+), 1 deletions(-)
---
diff --git a/drivers/scsi/libfc/fc_libfc.c b/drivers/scsi/libfc/fc_libfc.c
index ce0de44..abd108a 100644
--- a/drivers/scsi/libfc/fc_libfc.c
+++ b/drivers/scsi/libfc/fc_libfc.c
@@ -35,6 +35,10 @@ module_param_named(debug_logging, fc_debug_logging, int, S_IRUGO|S_IWUSR);
MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels");
DEFINE_MUTEX(fc_prov_mutex);
+static LIST_HEAD(fc_local_ports);
+struct blocking_notifier_head fc_lport_notifier_head =
+ BLOCKING_NOTIFIER_INIT(fc_lport_notifier_head);
+EXPORT_SYMBOL(fc_lport_notifier_head);
/*
* Providers which primarily send requests and PRLIs.
@@ -150,6 +154,17 @@ u32 fc_copy_buffer_to_sglist(void *buf, size_t len,
return copy_len;
}
+void fc_lport_iterate(void (*notify)(struct fc_lport *, void *), void *arg)
+{
+ struct fc_lport *lport;
+
+ mutex_lock(&fc_prov_mutex);
+ list_for_each_entry(lport, &fc_local_ports, lport_list)
+ notify(lport, arg);
+ mutex_unlock(&fc_prov_mutex);
+}
+EXPORT_SYMBOL(fc_lport_iterate);
+
/**
* fc_fc4_register_provider() - register FC-4 upper-level provider.
* @type: FC-4 type, such as FC_TYPE_FCP
@@ -192,3 +207,29 @@ void fc_fc4_deregister_provider(enum fc_fh_type type, struct fc4_prov *prov)
synchronize_rcu();
}
EXPORT_SYMBOL(fc_fc4_deregister_provider);
+
+/**
+ * fc_fc4_add_lport() - add new local port to list and run notifiers.
+ * @lport: The new local port.
+ */
+void fc_fc4_add_lport(struct fc_lport *lport)
+{
+ mutex_lock(&fc_prov_mutex);
+ list_add_tail(&lport->lport_list, &fc_local_ports);
+ blocking_notifier_call_chain(&fc_lport_notifier_head,
+ FC_LPORT_EV_ADD, lport);
+ mutex_unlock(&fc_prov_mutex);
+}
+
+/**
+ * fc_fc4_del_lport() - remove local port from list and run notifiers.
+ * @lport: The new local port.
+ */
+void fc_fc4_del_lport(struct fc_lport *lport)
+{
+ mutex_lock(&fc_prov_mutex);
+ list_del(&lport->lport_list);
+ blocking_notifier_call_chain(&fc_lport_notifier_head,
+ FC_LPORT_EV_DEL, lport);
+ mutex_unlock(&fc_prov_mutex);
+}
diff --git a/drivers/scsi/libfc/fc_libfc.h b/drivers/scsi/libfc/fc_libfc.h
index c3d740c..1bde8e2 100644
--- a/drivers/scsi/libfc/fc_libfc.h
+++ b/drivers/scsi/libfc/fc_libfc.h
@@ -111,6 +111,8 @@ void fc_destroy_fcp(void);
* Internal libfc functions
*/
const char *fc_els_resp_type(struct fc_frame *);
+extern void fc_fc4_add_lport(struct fc_lport *);
+extern void fc_fc4_del_lport(struct fc_lport *);
/*
* Copies a buffer into an sg list
diff --git a/drivers/scsi/libfc/fc_lport.c b/drivers/scsi/libfc/fc_lport.c
index 35149f0..507b6c5 100644
--- a/drivers/scsi/libfc/fc_lport.c
+++ b/drivers/scsi/libfc/fc_lport.c
@@ -654,6 +654,7 @@ int fc_lport_destroy(struct fc_lport *lport)
lport->tt.fcp_abort_io(lport);
lport->tt.disc_stop_final(lport);
lport->tt.exch_mgr_reset(lport, 0, 0);
+ fc_fc4_del_lport(lport);
return 0;
}
EXPORT_SYMBOL(fc_lport_destroy);
@@ -1638,6 +1639,7 @@ int fc_lport_init(struct fc_lport *lport)
fc_host_supported_speeds(lport->host) |= FC_PORTSPEED_1GBIT;
if (lport->link_supported_speeds & FC_PORTSPEED_10GBIT)
fc_host_supported_speeds(lport->host) |= FC_PORTSPEED_10GBIT;
+ fc_fc4_add_lport(lport);
return 0;
}
diff --git a/include/scsi/libfc.h b/include/scsi/libfc.h
index 6817fe3..12fe787 100644
--- a/include/scsi/libfc.h
+++ b/include/scsi/libfc.h
@@ -770,6 +770,15 @@ struct fc_disc {
enum fc_disc_event);
};
+/*
+ * Local port notifier and events.
+ */
+extern struct blocking_notifier_head fc_lport_notifier_head;
+enum fc_lport_event {
+ FC_LPORT_EV_ADD,
+ FC_LPORT_EV_DEL,
+};
+
/**
* struct fc_lport - Local port
* @host: The SCSI host associated with a local port
@@ -808,8 +817,9 @@ struct fc_disc {
* @lso_max: The maximum large offload send size
* @fcts: FC-4 type mask
* @lp_mutex: Mutex to protect the local port
- * @list: Handle for list of local ports
+ * @list: Linkage on list of vport peers
* @retry_work: Handle to local port for delayed retry context
+ * @lport_list: Linkage on module-wide list of local ports
* @prov: Pointers available for use by passive FC-4 providers
*/
struct fc_lport {
@@ -863,6 +873,7 @@ struct fc_lport {
struct mutex lp_mutex;
struct list_head list;
struct delayed_work retry_work;
+ struct list_head lport_list;
void *prov[FC_FC4_PROV_SIZE];
};
@@ -1026,6 +1037,7 @@ int fc_set_mfs(struct fc_lport *, u32 mfs);
struct fc_lport *libfc_vport_create(struct fc_vport *, int privsize);
struct fc_lport *fc_vport_id_lookup(struct fc_lport *, u32 port_id);
int fc_lport_bsg_request(struct fc_bsg_job *);
+void fc_lport_iterate(void (*func)(struct fc_lport *, void *), void *);
/*
* REMOTE PORT LAYER

View File

@@ -0,0 +1,25 @@
libfc: add definition for task attribute mask
The FCP command header definition should define a mask for
the task attribute field. This adds that #define.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
include/scsi/fc/fc_fcp.h | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
---
diff --git a/include/scsi/fc/fc_fcp.h b/include/scsi/fc/fc_fcp.h
index 747e2c7..8e9b222 100644
--- a/include/scsi/fc/fc_fcp.h
+++ b/include/scsi/fc/fc_fcp.h
@@ -76,6 +76,7 @@ struct fcp_cmnd32 {
#define FCP_PTA_HEADQ 1 /* head of queue task attribute */
#define FCP_PTA_ORDERED 2 /* ordered task attribute */
#define FCP_PTA_ACA 4 /* auto. contigent allegiance */
+#define FCP_PTA_MASK 7 /* mask for task attribute field */
#define FCP_PRI_SHIFT 3 /* priority field starts in bit 3 */
#define FCP_PRI_RESVD_MASK 0x80 /* reserved bits in priority field */

View File

@@ -0,0 +1,49 @@
libfc: fix oops in point-to-point mode
In point-to-point mode, if the PLOGI to the remote port times
out, it can get deleted by the remote port module. Since there's
no reference by the local port, lport->ptp_data points to a freed
rport, and when the local port is reset and tries to logout again,
an oops occurs in mutex_lock_nested().
Hold a reference count on the point-to-point rdata.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/libfc/fc_lport.c | 11 +++++++++--
1 files changed, 9 insertions(+), 2 deletions(-)
---
diff --git a/drivers/scsi/libfc/fc_lport.c b/drivers/scsi/libfc/fc_lport.c
index 507b6c5..a728e99 100644
--- a/drivers/scsi/libfc/fc_lport.c
+++ b/drivers/scsi/libfc/fc_lport.c
@@ -227,9 +227,12 @@ static void fc_lport_ptp_setup(struct fc_lport *lport,
u64 remote_wwnn)
{
mutex_lock(&lport->disc.disc_mutex);
- if (lport->ptp_rdata)
+ if (lport->ptp_rdata) {
lport->tt.rport_logoff(lport->ptp_rdata);
+ kref_put(&lport->ptp_rdata->kref, lport->tt.rport_destroy);
+ }
lport->ptp_rdata = lport->tt.rport_create(lport, remote_fid);
+ kref_get(&lport->ptp_rdata->kref);
lport->ptp_rdata->ids.port_name = remote_wwpn;
lport->ptp_rdata->ids.node_name = remote_wwnn;
mutex_unlock(&lport->disc.disc_mutex);
@@ -988,7 +991,11 @@ static void fc_lport_reset_locked(struct fc_lport *lport)
if (lport->dns_rdata)
lport->tt.rport_logoff(lport->dns_rdata);
- lport->ptp_rdata = NULL;
+ if (lport->ptp_rdata) {
+ lport->tt.rport_logoff(lport->ptp_rdata);
+ kref_put(&lport->ptp_rdata->kref, lport->tt.rport_destroy);
+ lport->ptp_rdata = NULL;
+ }
lport->tt.disc_stop(lport);

View File

@@ -0,0 +1,30 @@
fcoe: call fcoe_ctlr_els_send even for ELS responses
In point-to-point mode, the destination MAC address for
the FLOGI response was zero because the LS_ACC for the FLOGI
wasn't getting intercepted by FIP.
Change to call fcoe_ctlr_els_send when sending any ELS,
not just requests.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/fcoe/fcoe.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
---
diff --git a/drivers/scsi/fcoe/fcoe.c b/drivers/scsi/fcoe/fcoe.c
index 2f47ae7..2f3b73c 100644
--- a/drivers/scsi/fcoe/fcoe.c
+++ b/drivers/scsi/fcoe/fcoe.c
@@ -1443,7 +1443,7 @@ int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp)
return 0;
}
- if (unlikely(fh->fh_r_ctl == FC_RCTL_ELS_REQ) &&
+ if (unlikely(fh->fh_type == FC_TYPE_ELS) &&
fcoe_ctlr_els_send(&fcoe->ctlr, lport, skb))
return 0;

View File

@@ -0,0 +1,33 @@
libfcoe: fix debug message entering non-FIP mode
The debug message that indicated we are using non-FIP mode was
being printed only if we were already in non-FIP mode.
Also changed the message text to make it more clear the mode
is being set, not that the message is indicating how FLOGI
was received.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/fcoe/libfcoe.c | 6 +++---
1 files changed, 3 insertions(+), 3 deletions(-)
---
diff --git a/drivers/scsi/fcoe/libfcoe.c b/drivers/scsi/fcoe/libfcoe.c
index 511cb6b..b2af53f 100644
--- a/drivers/scsi/fcoe/libfcoe.c
+++ b/drivers/scsi/fcoe/libfcoe.c
@@ -1333,9 +1333,9 @@ int fcoe_ctlr_recv_flogi(struct fcoe_ctlr *fip, struct fc_lport *lport,
if (fip->state == FIP_ST_AUTO || fip->state == FIP_ST_NON_FIP) {
memcpy(fip->dest_addr, sa, ETH_ALEN);
fip->map_dest = 0;
- if (fip->state == FIP_ST_NON_FIP)
- LIBFCOE_FIP_DBG(fip, "received FLOGI REQ, "
- "using non-FIP mode\n");
+ if (fip->state == FIP_ST_AUTO)
+ LIBFCOE_FIP_DBG(fip, "received non-FIP FLOGI. "
+ "Setting non-FIP mode\n");
fip->state = FIP_ST_NON_FIP;
}
spin_unlock_bh(&fip->lock);

View File

@@ -0,0 +1,63 @@
fcoe: save gateway address when receiving FLOGI request
In point-to-point mode, we need to save the source MAC
from received FLOGI requests to use as the destination MAC
for all outgoing frames. We stopped doing that at some point.
Use the lport_set_port_id method to catch incoming FLOGI frames
and pass them to fcoe_ctlr_recv_flogi() so it can save the source MAC.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/fcoe/fcoe.c | 24 ++++++++++++++++++++++++
1 files changed, 24 insertions(+), 0 deletions(-)
---
diff --git a/drivers/scsi/fcoe/fcoe.c b/drivers/scsi/fcoe/fcoe.c
index 2f3b73c..546e69a 100644
--- a/drivers/scsi/fcoe/fcoe.c
+++ b/drivers/scsi/fcoe/fcoe.c
@@ -145,6 +145,7 @@ static int fcoe_vport_destroy(struct fc_vport *);
static int fcoe_vport_create(struct fc_vport *, bool disabled);
static int fcoe_vport_disable(struct fc_vport *, bool disable);
static void fcoe_set_vport_symbolic_name(struct fc_vport *);
+static void fcoe_set_port_id(struct fc_lport *, u32, struct fc_frame *);
static struct libfc_function_template fcoe_libfc_fcn_templ = {
.frame_send = fcoe_xmit,
@@ -152,6 +153,7 @@ static struct libfc_function_template fcoe_libfc_fcn_templ = {
.ddp_done = fcoe_ddp_done,
.elsct_send = fcoe_elsct_send,
.get_lesb = fcoe_get_lesb,
+ .lport_set_port_id = fcoe_set_port_id,
};
struct fc_function_template fcoe_transport_function = {
@@ -2630,3 +2632,25 @@ static void fcoe_get_lesb(struct fc_lport *lport,
lesb->lesb_miss_fka = htonl(mdac);
lesb->lesb_fcs_error = htonl(dev_get_stats(netdev)->rx_crc_errors);
}
+
+/**
+ * fcoe_set_port_id() - Callback from libfc when Port_ID is set.
+ * @lport: the local port
+ * @port_id: the port ID
+ * @fp: the received frame, if any, that caused the port_id to be set.
+ *
+ * This routine handles the case where we received a FLOGI and are
+ * entering point-to-point mode. We need to call fcoe_ctlr_recv_flogi()
+ * so it can set the non-mapped mode and gateway address.
+ *
+ * The FLOGI LS_ACC is handled by fcoe_flogi_resp().
+ */
+static void fcoe_set_port_id(struct fc_lport *lport,
+ u32 port_id, struct fc_frame *fp)
+{
+ struct fcoe_port *port = lport_priv(lport);
+ struct fcoe_interface *fcoe = port->fcoe;
+
+ if (fp && fc_frame_payload_op(fp) == ELS_FLOGI)
+ fcoe_ctlr_recv_flogi(&fcoe->ctlr, lport, fp);
+}

View File

@@ -0,0 +1,30 @@
libfc: recognize incoming FLOGI for point-to-point mode
When receiving a FLOGI request from a point-to-point peer,
the D_ID of 0xfffffe was not recognized as belonging to one
of the lports, so it was dropped.
Change fc_vport_id_lookup() to treat d_id 0xfffffe as a match.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/libfc/fc_npiv.c | 3 +++
1 files changed, 3 insertions(+), 0 deletions(-)
---
diff --git a/drivers/scsi/libfc/fc_npiv.c b/drivers/scsi/libfc/fc_npiv.c
index c68f6c7..45b6f1e 100644
--- a/drivers/scsi/libfc/fc_npiv.c
+++ b/drivers/scsi/libfc/fc_npiv.c
@@ -72,6 +72,9 @@ struct fc_lport *fc_vport_id_lookup(struct fc_lport *n_port, u32 port_id)
if (fc_host_port_id(n_port->host) == port_id)
return n_port;
+ if (port_id == FC_FID_FLOGI)
+ return n_port; /* for point-to-point */
+
mutex_lock(&n_port->lp_mutex);
list_for_each_entry(vn_port, &n_port->vports, list) {
if (fc_host_port_id(vn_port->host) == port_id) {

View File

@@ -0,0 +1,32 @@
libfc: point-to-point: send FLOGI LS_ACC to assigned D_DID
The method we've been using for point-to-point mode requires
that the LS_ACC for the FLOGI uses the D_ID and S_ID assigned
to the remote port and local port, not those in the exchange.
This is not the correct method, but for now, it's what works
with the old target, as well as with new targets based on libfc.
This patch changes the addresses used accordingly.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
---
drivers/scsi/libfc/fc_lport.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
---
diff --git a/drivers/scsi/libfc/fc_lport.c b/drivers/scsi/libfc/fc_lport.c
index a728e99..a2a49a2 100644
--- a/drivers/scsi/libfc/fc_lport.c
+++ b/drivers/scsi/libfc/fc_lport.c
@@ -835,7 +835,7 @@ static void fc_lport_recv_flogi_req(struct fc_seq *sp_in,
*/
f_ctl = FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ;
ep = fc_seq_exch(sp);
- fc_fill_fc_hdr(fp, FC_RCTL_ELS_REP, ep->did, ep->sid,
+ fc_fill_fc_hdr(fp, FC_RCTL_ELS_REP, remote_fid, local_fid,
FC_TYPE_ELS, f_ctl, 0);
lport->tt.seq_send(lport, sp, fp);

View File

@@ -0,0 +1,14 @@
# This series applies on GIT commit ec7b3b3aaf3b3b208140ac83dc19a754eebf648f
01-prli-clean
02-fc4
03-seq-init
04-seq-set-resp
05-lport-hook
06-lport-notify
07-pta-mask
08-ptp-ref
09-ptp-type
10-non-fip
11-ptp-flogi-recv
12-ptp-npiv
13-ptp-acc