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. I'm hoping that
this can be included in the SCST tree eventually.
This is mostly to gather comments. It isn't at all well tested.
It works with simple read/write tests with vdisk targets.
It doesn't use any libfc offloads as of yet. Task management
and error cases haven't been tested.
It relies on a set of patches just submitted to open-fcoe.org
to add hooks for target providers like this.
It has only been used under the 2.6.33-rc4 kernel.
The known issues are:
* task management
* could offload or allocate buffers to avoid copies
* doesn't handle target timeouts.
* completion handling not done (no tape support yet)
* exchanges not held around for REC/SRR after response sent
* SRR not tested
* error handling could be better
* need way of sending LOGO when disabling I/T nexus.
* Asynchronous Event Notification (AEN) not handled.
Management is similar to the qla2x00t target. I tested this
with /sys only, but management uses the common SCST code, so
/proc may work as well.
Signed-off-by: Joe Eykholt <[email protected]>
---
drivers/scst/fcst/Kconfig | 5
drivers/scst/fcst/Makefile | 9
drivers/scst/fcst/fcst.h | 143 +++++++
drivers/scst/fcst/ft_cmd.c | 831 +++++++++++++++++++++++++++++++++++++++++++
drivers/scst/fcst/ft_io.c | 281 +++++++++++++++
drivers/scst/fcst/ft_scst.c | 97 +++++
drivers/scst/fcst/ft_sess.c | 537 ++++++++++++++++++++++++++++
7 files changed, 1903 insertions(+), 0 deletions(-)
create mode 100644 drivers/scst/fcst/Kconfig
create mode 100644 drivers/scst/fcst/Makefile
create mode 100644 drivers/scst/fcst/fcst.h
create mode 100644 drivers/scst/fcst/ft_cmd.c
create mode 100644 drivers/scst/fcst/ft_io.c
create mode 100644 drivers/scst/fcst/ft_scst.c
create mode 100644 drivers/scst/fcst/ft_sess.c
diff --git a/drivers/scst/fcst/Kconfig b/drivers/scst/fcst/Kconfig
new file mode 100644
index 0000000..dcf5f4c
--- /dev/null
+++ b/drivers/scst/fcst/Kconfig
@@ -0,0 +1,5 @@
+config FCST
+ tristate "Target module for Fibre Channel using libfc"
+ depends on LIBFC && SCST
+ ---help---
+ Supports using libfc HBAs as target adapters with SCST
diff --git a/drivers/scst/fcst/Makefile b/drivers/scst/fcst/Makefile
new file mode 100644
index 0000000..a9c003e
--- /dev/null
+++ b/drivers/scst/fcst/Makefile
@@ -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
diff --git a/drivers/scst/fcst/fcst.h b/drivers/scst/fcst/fcst.h
new file mode 100644
index 0000000..63a42db
--- /dev/null
+++ b/drivers/scst/fcst/fcst.h
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+#ifndef __SCSI_FCST_H__
+#define __SCSI_FCST_H__
+
+#define FT_VERSION "0.2"
+#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_frame; /* maximum frame 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 */
+ struct ft_sess *sess;
+ 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.
+ */
+void ft_sess_put(struct ft_sess *);
+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__ */
diff --git a/drivers/scst/fcst/ft_cmd.c b/drivers/scst/fcst/ft_cmd.c
new file mode 100644
index 0000000..fcb873e
--- /dev/null
+++ b/drivers/scst/fcst/ft_cmd.c
@@ -0,0 +1,831 @@
+/*
+ * 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 <scst/scst.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);
+ ft_sess_put(fcmd->sess);
+ 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__);
+
+ 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;
+ }
+
+ fcmd = scst_cmd_get_tgt_priv(cmd);
+ status = scst_cmd_get_status(cmd);
+ dir = scst_cmd_get_data_direction(cmd);
+
+ ep = fc_seq_exch(fcmd->seq);
+ lport = ep->lp;
+ 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, cmd->data_len);
+}
+
+/*
+ * 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)
+{
+ struct fc_frame *fp;
+ struct {
+ struct fcp_resp resp;
+ struct fcp_resp_ext ext;
+ struct fcp_resp_rsp_info info;
+ } __attribute__((packed)) *fcp;
+ struct fc_lport *lport;
+ struct fc_exch *ep;
+
+ ep = fc_seq_exch(fcmd->seq);
+
+ FT_IO_DBG("FCP error response: did %x oxid %x code %d\n",
+ ep->did, ep->oxid, code);
+ lport = ep->lp;
+ fp = fc_frame_alloc(lport, sizeof(*fcp));
+ if (!fp)
+ goto out;
+ fcp = fc_frame_payload_get(fp, sizeof(*fcp));
+ memset(fcp, 0, sizeof(*fcp));
+ fcp->ext.fr_rsp_len = htonl(sizeof(fcp->info));
+ fcp->resp.fr_flags |= FCP_RSP_LEN_VAL;
+ fcp->info.rsp_code = code;
+
+ 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);
+out:
+ lport->tt.exch_done(fcmd->seq);
+ fc_frame_free(fcmd->req_frame);
+ ft_sess_put(fcmd->sess);
+ 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 ft_cmd *fcmd, struct fcp_cmnd *fcp)
+{
+ struct scst_rx_mgmt_params params;
+ int ret;
+
+ memset(¶ms, 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(fcmd->sess->scst_sess, ¶ms);
+ 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 drop;
+ fcmd->serial = atomic_inc_return(&serial); /* debug only */
+ fcmd->seq = sp;
+ fcmd->sess = sess;
+ fcmd->req_frame = fp;
+
+ fcp = fc_frame_payload_get(fp, sizeof(*fcp));
+ if (!fcp)
+ goto err;
+ if (fcp->fc_tm_flags) {
+ ft_recv_tm(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 drop;
+ }
+ 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;
+drop:
+ FT_IO_DBG("cmd error - dropped\n");
+ lport->tt.exch_done(sp);
+ fc_frame_free(fp);
+ ft_sess_put(sess); /* undo get from lookup */
+ kfree(fcmd); /* OK if NULL */
+}
+
+/*
+ * Send FCP ELS-4 Accept.
+ */
+static void ft_cmd_ls_acc(struct fc_seq *sp)
+{
+ struct fc_frame *fp;
+ struct fc_els_ls_acc *acc;
+ struct fc_lport *lport;
+ struct fc_exch *ep;
+
+ ep = fc_seq_exch(sp);
+ lport = ep->lp;
+ fp = fc_frame_alloc(lport, sizeof(*acc));
+ if (!fp)
+ return;
+
+ acc = fc_frame_payload_get(fp, sizeof(*acc));
+ memset(acc, 0, sizeof(*acc));
+ acc->la_cmd = ELS_LS_ACC;
+
+ 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);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Compare a command to the one specified by an SRR request.
+ * Return non-zero if the command matches.
+ */
+static int ft_cmd_match_srr(struct scst_cmd *cmd, void *data)
+{
+ struct fc_frame *fp = data;
+ struct fc_frame_header *fh;
+ struct fc_frame_header *rfh;
+ struct fcp_srr *srr;
+ struct ft_cmd *fcmd;
+ struct fc_exch *ep;
+ u16 rx_id;
+
+ fcmd = scst_cmd_get_tgt_priv(cmd);
+ ep = fc_seq_exch(fcmd->seq);
+ srr = fc_frame_payload_get(fp, sizeof(*srr));
+
+ if (ep->oxid != ntohs(srr->srr_ox_id))
+ return 0;
+ rx_id = ntohs(srr->srr_rx_id);
+ if (rx_id != FC_XID_UNKNOWN && ep->rxid != rx_id)
+ return 0;
+ fh = fc_frame_header_get(fp);
+ rfh = fc_frame_header_get(fcmd->req_frame);
+ if (fh->fh_parm_offset && fh->fh_parm_offset != rfh->fh_parm_offset)
+ return 0;
+ return 1;
+}
+
+/*
+ * Find command, even if it is done.
+ * This is a temporary workaround for scst_find_cmd().
+ * A reference is held for the caller.
+ */
+struct scst_cmd *ft_find_cmd(struct scst_session *sess, void *data,
+ int (*match)(struct scst_cmd *, void *))
+{
+ struct scst_cmd *cmd;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sess->sess_list_lock, flags);
+ list_for_each_entry(cmd, &sess->sess_cmd_list, sess_cmd_list_entry) {
+ if (match(cmd, data)) {
+ scst_cmd_get(cmd);
+ goto found;
+ }
+ }
+ cmd = NULL;
+found:
+ spin_unlock_irqrestore(&sess->sess_list_lock, flags);
+ return cmd;
+}
+
+/*
+ * Handle an incoming SRR (Sequence Retransmission request).
+ * Note that this may be called directly from the softirq context.
+ */
+static void ft_recv_srr(struct ft_sess *sess, struct fc_seq *sp,
+ struct fc_frame *fp)
+{
+ struct fc_frame_header *fh = fc_frame_header_get(fp);
+ struct fcp_srr *srr;
+ struct scst_cmd *cmd;
+ struct ft_cmd *fcmd;
+ u32 offset;
+
+ srr = fc_frame_payload_get(fp, sizeof(*srr));
+ if (!srr) {
+ ft_cmd_ls_rjt(sp, ELS_RJT_LOGIC, ELS_EXPL_INV_LEN);
+ goto drop;
+ }
+ FT_IO_DBG("SRR sid %x oxid %x r_ctl %x\n", ntoh24(fh->fh_s_id),
+ ntohs(srr->srr_ox_id), srr->srr_r_ctl);
+ cmd = ft_find_cmd(sess->scst_sess, fp, ft_cmd_match_srr);
+ if (!cmd) {
+ FT_IO_DBG("SRR ox_id not found\n");
+ ft_cmd_ls_rjt(sp, ELS_RJT_UNAB, ELS_EXPL_OXID_RXID);
+ goto drop;
+ }
+ fcmd = scst_cmd_get_tgt_priv(cmd);
+ offset = ntohl(srr->srr_rel_off);
+
+ switch (srr->srr_r_ctl) {
+ case FC_RCTL_DD_SOL_DATA:
+ case FC_RCTL_DD_CMD_STATUS:
+ /*
+ * XXX TBD. Since we close the exchange and cmd as soon as we
+ * send status, we can't resend anything after that point.
+ * This should be fixed later for tape targets by holding
+ * the command for a while.
+ */
+ ft_cmd_ls_rjt(sp, ELS_RJT_UNAB, ELS_EXPL_UNAB_DATA);
+ break;
+ case FC_RCTL_DD_DATA_DESC:
+ if (cmd->state != SCST_CMD_STATE_RDY_TO_XFER) {
+ ft_cmd_ls_rjt(sp, ELS_RJT_UNAB, ELS_EXPL_UNAB_DATA);
+ break;
+ }
+ fcmd->xfer_rdy_len = 0;
+ fcmd->write_data_len = 0;
+ ft_cmd_ls_acc(sp);
+ ft_send_xfer_rdy_off(cmd, 0, cmd->data_len);
+ break;
+ default:
+ ft_cmd_ls_rjt(sp, ELS_RJT_LOGIC, ELS_EXPL_UNAB_DATA);
+ break;
+ }
+ scst_cmd_put(cmd);
+drop:
+ fc_frame_free(fp);
+ ft_sess_put(sess); /* undo get from lookup */
+}
+
+/*
+ * 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:
+ ft_recv_srr(sess, sp, fp);
+ break;
+ 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);
+ ft_sess_put(sess); /* undo get from lookup */
+ 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);
+ ft_sess_put(sess); /* undo get from lookup */
+ break;
+ }
+}
diff --git a/drivers/scst/fcst/ft_io.c b/drivers/scst/fcst/ft_io.c
new file mode 100644
index 0000000..51cde44
--- /dev/null
+++ b/drivers/scst/fcst/ft_io.c
@@ -0,0 +1,281 @@
+/*
+ * 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 <scst/scst.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;
+ size_t max_frame_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;
+
+ /* Use larger frame length if LLD is capable of seq_offload */
+ max_frame_len = fcmd->sess->max_frame;
+ if (lport->seq_offload)
+ max_frame_len = lport->lso_max;
+ if (!max_frame_len) {
+ FT_IO_DBG("sess->max_frame 0\n");
+ return SCST_TGT_RES_FATAL_ERROR;
+ }
+
+ 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 = min(max_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->sess->max_frame;
+ 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(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;
+}
diff --git a/drivers/scst/fcst/ft_scst.c b/drivers/scst/fcst/ft_scst.c
new file mode 100644
index 0000000..10f90c3
--- /dev/null
+++ b/drivers/scst/fcst/ft_scst.c
@@ -0,0 +1,97 @@
+/*
+ * 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 <scst/scst.h>
+#include "fcst.h"
+
+MODULE_AUTHOR("Joe Eykholt <[email protected]>");
+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 = 0, /* XXX possibly OK to set to 1 */
+ .rdy_to_xfer_atomic = 0, /* XXX possibly OK to set to 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_tgt = ft_tgt_enable,
+ .is_tgt_enabled = ft_tgt_enabled,
+ .name = FT_MODULE,
+ .threads_num = 4, /* XXX */
+ .default_trace_flags = ~0, /* XXX */
+};
+
+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);
diff --git a/drivers/scst/fcst/ft_sess.c b/drivers/scst/fcst/ft_sess.c
new file mode 100644
index 0000000..445926e
--- /dev/null
+++ b/drivers/scst/fcst/ft_sess.c
@@ -0,0 +1,537 @@
+/*
+ * 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 <scst/scst.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_set(&sess->kref, 1); /* 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_frame = rdata->maxframe_size;
+ 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);
+ ft_sess_put(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);
+
+ call_rcu(&sess->rcu, ft_sess_rcu_free);
+}
+
+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);
+}
+
+/*
+ * Handle PRLO.
+ */
+void ft_prlo(struct fc_rport_priv *rdata)
+{
+ struct ft_sess *sess;
+ struct ft_tport *tport;
+ struct scst_session *scst_sess;
+
+ mutex_lock(&ft_lport_lock);
+ tport = rcu_dereference(rdata->local_port->prov[FC_TYPE_FCP]);
+ if (!tport) {
+ mutex_unlock(&ft_lport_lock);
+ return;
+ }
+ sess = ft_sess_delete(tport, rdata->ids.port_id);
+ if (!sess) {
+ mutex_unlock(&ft_lport_lock);
+ return;
+ }
+ sess->params = 0;
+ scst_sess = sess->scst_sess;
+ if (scst_sess) {
+ sess->scst_sess = NULL;
+ FT_SESS_DBG("unregister %s\n", scst_sess->initiator_name);
+ /* XXX TBD: unregister should be done in RCU to avoid
+ * conflict with incoming commands.
+ */
+ scst_unregister_session(scst_sess, 0, NULL);
+ }
+ mutex_unlock(&ft_lport_lock);
+ ft_sess_put(sess); /* release from table */
+ rdata->prli_count--;
+ /* XXX TBD - clearing actions. unit attn, see 4.10 */
+}
+
+/*
+ * 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); /* must do ft_sess_put() */
+}
+
+/*
+ * 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 */
+}
_______________________________________________
devel mailing list
[email protected]
http://www.open-fcoe.org/mailman/listinfo/devel