aboutsummaryrefslogtreecommitdiff
path: root/drivers/scsi/libfc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/libfc')
-rw-r--r--drivers/scsi/libfc/Makefile4
-rw-r--r--drivers/scsi/libfc/fc_disc.c746
-rw-r--r--drivers/scsi/libfc/fc_elsct.c115
-rw-r--r--drivers/scsi/libfc/fc_exch.c2089
-rw-r--r--drivers/scsi/libfc/fc_fcp.c1448
-rw-r--r--drivers/scsi/libfc/fc_frame.c16
-rw-r--r--drivers/scsi/libfc/fc_libfc.c331
-rw-r--r--drivers/scsi/libfc/fc_libfc.h139
-rw-r--r--drivers/scsi/libfc/fc_lport.c1768
-rw-r--r--drivers/scsi/libfc/fc_npiv.c159
-rw-r--r--drivers/scsi/libfc/fc_rport.c2150
11 files changed, 5865 insertions, 3100 deletions
diff --git a/drivers/scsi/libfc/Makefile b/drivers/scsi/libfc/Makefile
index 55f982de3a9..4bb23ac86a5 100644
--- a/drivers/scsi/libfc/Makefile
+++ b/drivers/scsi/libfc/Makefile
@@ -3,10 +3,12 @@
obj-$(CONFIG_LIBFC) += libfc.o
libfc-objs := \
+ fc_libfc.o \
fc_disc.o \
fc_exch.o \
fc_elsct.o \
fc_frame.o \
fc_lport.o \
fc_rport.o \
- fc_fcp.o
+ fc_fcp.o \
+ fc_npiv.o
diff --git a/drivers/scsi/libfc/fc_disc.c b/drivers/scsi/libfc/fc_disc.c
index dd1564c9e04..880a9068ca1 100644
--- a/drivers/scsi/libfc/fc_disc.c
+++ b/drivers/scsi/libfc/fc_disc.c
@@ -33,138 +33,58 @@
*/
#include <linux/timer.h>
+#include <linux/slab.h>
#include <linux/err.h>
+#include <linux/export.h>
#include <asm/unaligned.h>
#include <scsi/fc/fc_gs.h>
#include <scsi/libfc.h>
+#include "fc_libfc.h"
+
#define FC_DISC_RETRY_LIMIT 3 /* max retries */
#define FC_DISC_RETRY_DELAY 500UL /* (msecs) delay */
-#define FC_DISC_DELAY 3
-
-static int fc_disc_debug;
-
-#define FC_DEBUG_DISC(fmt...) \
- do { \
- if (fc_disc_debug) \
- FC_DBG(fmt); \
- } while (0)
-
static void fc_disc_gpn_ft_req(struct fc_disc *);
static void fc_disc_gpn_ft_resp(struct fc_seq *, struct fc_frame *, void *);
-static int fc_disc_new_target(struct fc_disc *, struct fc_rport *,
- struct fc_rport_identifiers *);
-static void fc_disc_del_target(struct fc_disc *, struct fc_rport *);
-static void fc_disc_done(struct fc_disc *);
+static void fc_disc_done(struct fc_disc *, enum fc_disc_event);
static void fc_disc_timeout(struct work_struct *);
-static void fc_disc_single(struct fc_disc *, struct fc_disc_port *);
+static int fc_disc_single(struct fc_lport *, struct fc_disc_port *);
static void fc_disc_restart(struct fc_disc *);
/**
- * fc_disc_lookup_rport - lookup a remote port by port_id
- * @lport: Fibre Channel host port instance
- * @port_id: remote port port_id to match
- */
-struct fc_rport *fc_disc_lookup_rport(const struct fc_lport *lport,
- u32 port_id)
-{
- const struct fc_disc *disc = &lport->disc;
- struct fc_rport *rport, *found = NULL;
- struct fc_rport_libfc_priv *rdata;
- int disc_found = 0;
-
- list_for_each_entry(rdata, &disc->rports, peers) {
- rport = PRIV_TO_RPORT(rdata);
- if (rport->port_id == port_id) {
- disc_found = 1;
- found = rport;
- break;
- }
- }
-
- if (!disc_found)
- found = NULL;
-
- return found;
-}
-
-/**
- * fc_disc_stop_rports - delete all the remote ports associated with the lport
- * @disc: The discovery job to stop rports on
+ * fc_disc_stop_rports() - Delete all the remote ports associated with the lport
+ * @disc: The discovery job to stop remote ports on
*
* Locking Note: This function expects that the lport mutex is locked before
* calling it.
*/
-void fc_disc_stop_rports(struct fc_disc *disc)
+static void fc_disc_stop_rports(struct fc_disc *disc)
{
struct fc_lport *lport;
- struct fc_rport *rport;
- struct fc_rport_libfc_priv *rdata, *next;
+ struct fc_rport_priv *rdata;
- lport = disc->lport;
+ lport = fc_disc_lport(disc);
mutex_lock(&disc->disc_mutex);
- list_for_each_entry_safe(rdata, next, &disc->rports, peers) {
- rport = PRIV_TO_RPORT(rdata);
- list_del(&rdata->peers);
- lport->tt.rport_logoff(rport);
- }
-
+ list_for_each_entry_rcu(rdata, &disc->rports, peers)
+ lport->tt.rport_logoff(rdata);
mutex_unlock(&disc->disc_mutex);
}
/**
- * fc_disc_rport_callback - Event handler for rport events
- * @lport: The lport which is receiving the event
- * @rport: The rport which the event has occured on
- * @event: The event that occured
- *
- * Locking Note: The rport lock should not be held when calling
- * this function.
- */
-static void fc_disc_rport_callback(struct fc_lport *lport,
- struct fc_rport *rport,
- enum fc_rport_event event)
-{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
- struct fc_disc *disc = &lport->disc;
- int found = 0;
-
- FC_DEBUG_DISC("Received a %d event for port (%6x)\n", event,
- rport->port_id);
-
- if (event == RPORT_EV_CREATED) {
- if (disc) {
- found = 1;
- mutex_lock(&disc->disc_mutex);
- list_add_tail(&rdata->peers, &disc->rports);
- mutex_unlock(&disc->disc_mutex);
- }
- }
-
- if (!found)
- FC_DEBUG_DISC("The rport (%6x) is not maintained "
- "by the discovery layer\n", rport->port_id);
-}
-
-/**
- * fc_disc_recv_rscn_req - Handle Registered State Change Notification (RSCN)
- * @sp: Current sequence of the RSCN exchange
- * @fp: RSCN Frame
- * @lport: Fibre Channel host port instance
+ * fc_disc_recv_rscn_req() - Handle Registered State Change Notification (RSCN)
+ * @disc: The discovery object to which the RSCN applies
+ * @fp: The RSCN frame
*
* Locking Note: This function expects that the disc_mutex is locked
* before it is called.
*/
-static void fc_disc_recv_rscn_req(struct fc_seq *sp, struct fc_frame *fp,
- struct fc_disc *disc)
+static void fc_disc_recv_rscn_req(struct fc_disc *disc, struct fc_frame *fp)
{
struct fc_lport *lport;
- struct fc_rport *rport;
- struct fc_rport_libfc_priv *rdata;
struct fc_els_rscn *rp;
struct fc_els_rscn_page *pp;
struct fc_seq_els_data rjt_data;
@@ -175,10 +95,9 @@ static void fc_disc_recv_rscn_req(struct fc_seq *sp, struct fc_frame *fp,
LIST_HEAD(disc_ports);
struct fc_disc_port *dp, *next;
- lport = disc->lport;
+ lport = fc_disc_lport(disc);
- FC_DEBUG_DISC("Received an RSCN event on port (%6x)\n",
- fc_host_port_id(lport->host));
+ FC_DISC_DBG(disc, "Received an RSCN event\n");
/* make sure the frame contains an RSCN message */
rp = fc_frame_payload_get(fp, sizeof(*rp));
@@ -211,71 +130,68 @@ static void fc_disc_recv_rscn_req(struct fc_seq *sp, struct fc_frame *fp,
*/
switch (fmt) {
case ELS_ADDR_FMT_PORT:
- FC_DEBUG_DISC("Port address format for port (%6x)\n",
- ntoh24(pp->rscn_fid));
+ FC_DISC_DBG(disc, "Port address format for port "
+ "(%6.6x)\n", ntoh24(pp->rscn_fid));
dp = kzalloc(sizeof(*dp), GFP_KERNEL);
if (!dp) {
redisc = 1;
break;
}
dp->lp = lport;
- dp->ids.port_id = ntoh24(pp->rscn_fid);
- dp->ids.port_name = -1;
- dp->ids.node_name = -1;
- dp->ids.roles = FC_RPORT_ROLE_UNKNOWN;
+ dp->port_id = ntoh24(pp->rscn_fid);
list_add_tail(&dp->peers, &disc_ports);
break;
case ELS_ADDR_FMT_AREA:
case ELS_ADDR_FMT_DOM:
case ELS_ADDR_FMT_FAB:
default:
- FC_DEBUG_DISC("Address format is (%d)\n", fmt);
+ FC_DISC_DBG(disc, "Address format is (%d)\n", fmt);
redisc = 1;
break;
}
}
- lport->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
+ lport->tt.seq_els_rsp_send(fp, ELS_LS_ACC, NULL);
+
+ /*
+ * If not doing a complete rediscovery, do GPN_ID on
+ * the individual ports mentioned in the list.
+ * If any of these get an error, do a full rediscovery.
+ * In any case, go through the list and free the entries.
+ */
+ list_for_each_entry_safe(dp, next, &disc_ports, peers) {
+ list_del(&dp->peers);
+ if (!redisc)
+ redisc = fc_disc_single(lport, dp);
+ kfree(dp);
+ }
if (redisc) {
- FC_DEBUG_DISC("RSCN received: rediscovering\n");
+ FC_DISC_DBG(disc, "RSCN received: rediscovering\n");
fc_disc_restart(disc);
} else {
- FC_DEBUG_DISC("RSCN received: not rediscovering. "
- "redisc %d state %d in_prog %d\n",
- redisc, lport->state, disc->pending);
- list_for_each_entry_safe(dp, next, &disc_ports, peers) {
- list_del(&dp->peers);
- rport = lport->tt.rport_lookup(lport, dp->ids.port_id);
- if (rport) {
- rdata = RPORT_TO_PRIV(rport);
- list_del(&rdata->peers);
- lport->tt.rport_logoff(rport);
- }
- fc_disc_single(disc, dp);
- }
+ FC_DISC_DBG(disc, "RSCN received: not rediscovering. "
+ "redisc %d state %d in_prog %d\n",
+ redisc, lport->state, disc->pending);
}
fc_frame_free(fp);
return;
reject:
- FC_DEBUG_DISC("Received a bad RSCN frame\n");
- rjt_data.fp = NULL;
+ FC_DISC_DBG(disc, "Received a bad RSCN frame\n");
rjt_data.reason = ELS_RJT_LOGIC;
rjt_data.explan = ELS_EXPL_NONE;
- lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
+ lport->tt.seq_els_rsp_send(fp, ELS_LS_RJT, &rjt_data);
fc_frame_free(fp);
}
/**
- * fc_disc_recv_req - Handle incoming requests
- * @sp: Current sequence of the request exchange
- * @fp: The frame
- * @lport: The FC local port
+ * fc_disc_recv_req() - Handle incoming requests
+ * @lport: The local port receiving the request
+ * @fp: The request frame
*
* Locking Note: This function is called from the EM and will lock
* the disc_mutex before calling the handler for the
* request.
*/
-static void fc_disc_recv_req(struct fc_seq *sp, struct fc_frame *fp,
- struct fc_lport *lport)
+static void fc_disc_recv_req(struct fc_lport *lport, struct fc_frame *fp)
{
u8 op;
struct fc_disc *disc = &lport->disc;
@@ -284,55 +200,54 @@ static void fc_disc_recv_req(struct fc_seq *sp, struct fc_frame *fp,
switch (op) {
case ELS_RSCN:
mutex_lock(&disc->disc_mutex);
- fc_disc_recv_rscn_req(sp, fp, disc);
+ fc_disc_recv_rscn_req(disc, fp);
mutex_unlock(&disc->disc_mutex);
break;
default:
- FC_DBG("Received an unsupported request. opcode (%x)\n", op);
+ FC_DISC_DBG(disc, "Received an unsupported request, "
+ "the opcode is (%x)\n", op);
+ fc_frame_free(fp);
break;
}
}
/**
- * fc_disc_restart - Restart discovery
- * @lport: FC discovery context
+ * fc_disc_restart() - Restart discovery
+ * @disc: The discovery object to be restarted
*
* Locking Note: This function expects that the disc mutex
* is already locked.
*/
static void fc_disc_restart(struct fc_disc *disc)
{
- struct fc_rport *rport;
- struct fc_rport_libfc_priv *rdata, *next;
- struct fc_lport *lport = disc->lport;
-
- FC_DEBUG_DISC("Restarting discovery for port (%6x)\n",
- fc_host_port_id(lport->host));
-
- list_for_each_entry_safe(rdata, next, &disc->rports, peers) {
- rport = PRIV_TO_RPORT(rdata);
- FC_DEBUG_DISC("list_del(%6x)\n", rport->port_id);
- list_del(&rdata->peers);
- lport->tt.rport_logoff(rport);
- }
+ if (!disc->disc_callback)
+ return;
+
+ FC_DISC_DBG(disc, "Restarting discovery\n");
disc->requested = 1;
- if (!disc->pending)
- fc_disc_gpn_ft_req(disc);
+ if (disc->pending)
+ return;
+
+ /*
+ * Advance disc_id. This is an arbitrary non-zero number that will
+ * match the value in the fc_rport_priv after discovery for all
+ * freshly-discovered remote ports. Avoid wrapping to zero.
+ */
+ disc->disc_id = (disc->disc_id + 2) | 1;
+ disc->retry_count = 0;
+ fc_disc_gpn_ft_req(disc);
}
/**
- * fc_disc_start - Fibre Channel Target discovery
- * @lport: FC local port
- *
- * Returns non-zero if discovery cannot be started.
+ * fc_disc_start() - Start discovery on a local port
+ * @lport: The local port to have discovery started on
+ * @disc_callback: Callback function to be called when discovery is complete
*/
static void fc_disc_start(void (*disc_callback)(struct fc_lport *,
enum fc_disc_event),
struct fc_lport *lport)
{
- struct fc_rport *rport;
- struct fc_rport_identifiers ids;
struct fc_disc *disc = &lport->disc;
/*
@@ -341,156 +256,66 @@ static void fc_disc_start(void (*disc_callback)(struct fc_lport *,
* and send the GPN_FT request.
*/
mutex_lock(&disc->disc_mutex);
-
disc->disc_callback = disc_callback;
-
- /*
- * If not ready, or already running discovery, just set request flag.
- */
- disc->requested = 1;
-
- if (disc->pending) {
- mutex_unlock(&disc->disc_mutex);
- return;
- }
-
- /*
- * Handle point-to-point mode as a simple discovery
- * of the remote port. Yucky, yucky, yuck, yuck!
- */
- rport = disc->lport->ptp_rp;
- if (rport) {
- ids.port_id = rport->port_id;
- ids.port_name = rport->port_name;
- ids.node_name = rport->node_name;
- ids.roles = FC_RPORT_ROLE_UNKNOWN;
- get_device(&rport->dev);
-
- if (!fc_disc_new_target(disc, rport, &ids)) {
- disc->event = DISC_EV_SUCCESS;
- fc_disc_done(disc);
- }
- put_device(&rport->dev);
- } else {
- fc_disc_gpn_ft_req(disc); /* get ports by FC-4 type */
- }
-
+ fc_disc_restart(disc);
mutex_unlock(&disc->disc_mutex);
}
-static struct fc_rport_operations fc_disc_rport_ops = {
- .event_callback = fc_disc_rport_callback,
-};
-
/**
- * fc_disc_new_target - Handle new target found by discovery
- * @lport: FC local port
- * @rport: The previous FC remote port (NULL if new remote port)
- * @ids: Identifiers for the new FC remote port
+ * fc_disc_done() - Discovery has been completed
+ * @disc: The discovery context
+ * @event: The discovery completion status
*
- * Locking Note: This function expects that the disc_mutex is locked
- * before it is called.
+ * Locking Note: This function expects that the disc mutex is locked before
+ * it is called. The discovery callback is then made with the lock released,
+ * and the lock is re-taken before returning from this function
*/
-static int fc_disc_new_target(struct fc_disc *disc,
- struct fc_rport *rport,
- struct fc_rport_identifiers *ids)
+static void fc_disc_done(struct fc_disc *disc, enum fc_disc_event event)
{
- struct fc_lport *lport = disc->lport;
- struct fc_rport_libfc_priv *rp;
- int error = 0;
-
- if (rport && ids->port_name) {
- if (rport->port_name == -1) {
- /*
- * Set WWN and fall through to notify of create.
- */
- fc_rport_set_name(rport, ids->port_name,
- rport->node_name);
- } else if (rport->port_name != ids->port_name) {
- /*
- * This is a new port with the same FCID as
- * a previously-discovered port. Presumably the old
- * port logged out and a new port logged in and was
- * assigned the same FCID. This should be rare.
- * Delete the old one and fall thru to re-create.
- */
- fc_disc_del_target(disc, rport);
- rport = NULL;
- }
- }
- if (((ids->port_name != -1) || (ids->port_id != -1)) &&
- ids->port_id != fc_host_port_id(lport->host) &&
- ids->port_name != lport->wwpn) {
- if (!rport) {
- rport = lport->tt.rport_lookup(lport, ids->port_id);
- if (!rport) {
- struct fc_disc_port dp;
- dp.lp = lport;
- dp.ids.port_id = ids->port_id;
- dp.ids.port_name = ids->port_name;
- dp.ids.node_name = ids->node_name;
- dp.ids.roles = ids->roles;
- rport = fc_rport_rogue_create(&dp);
- }
- if (!rport)
- error = -ENOMEM;
- }
- if (rport) {
- rp = rport->dd_data;
- rp->ops = &fc_disc_rport_ops;
- rp->rp_state = RPORT_ST_INIT;
- lport->tt.rport_login(rport);
- }
- }
- return error;
-}
-
-/**
- * fc_disc_del_target - Delete a target
- * @disc: FC discovery context
- * @rport: The remote port to be removed
- */
-static void fc_disc_del_target(struct fc_disc *disc, struct fc_rport *rport)
-{
- struct fc_lport *lport = disc->lport;
- struct fc_rport_libfc_priv *rdata = RPORT_TO_PRIV(rport);
- list_del(&rdata->peers);
- lport->tt.rport_logoff(rport);
-}
+ struct fc_lport *lport = fc_disc_lport(disc);
+ struct fc_rport_priv *rdata;
-/**
- * fc_disc_done - Discovery has been completed
- * @disc: FC discovery context
- */
-static void fc_disc_done(struct fc_disc *disc)
-{
- struct fc_lport *lport = disc->lport;
+ FC_DISC_DBG(disc, "Discovery complete\n");
- FC_DEBUG_DISC("Discovery complete for port (%6x)\n",
- fc_host_port_id(lport->host));
+ disc->pending = 0;
+ if (disc->requested) {
+ fc_disc_restart(disc);
+ return;
+ }
- disc->disc_callback(lport, disc->event);
- disc->event = DISC_EV_NONE;
+ /*
+ * Go through all remote ports. If they were found in the latest
+ * discovery, reverify or log them in. Otherwise, log them out.
+ * Skip ports which were never discovered. These are the dNS port
+ * and ports which were created by PLOGI.
+ */
+ list_for_each_entry_rcu(rdata, &disc->rports, peers) {
+ if (!rdata->disc_id)
+ continue;
+ if (rdata->disc_id == disc->disc_id)
+ lport->tt.rport_login(rdata);
+ else
+ lport->tt.rport_logoff(rdata);
+ }
- if (disc->requested)
- fc_disc_gpn_ft_req(disc);
- else
- disc->pending = 0;
+ mutex_unlock(&disc->disc_mutex);
+ disc->disc_callback(lport, event);
+ mutex_lock(&disc->disc_mutex);
}
/**
- * fc_disc_error - Handle error on dNS request
- * @disc: FC discovery context
- * @fp: The frame pointer
+ * fc_disc_error() - Handle error on dNS request
+ * @disc: The discovery context
+ * @fp: The error code encoded as a frame pointer
*/
static void fc_disc_error(struct fc_disc *disc, struct fc_frame *fp)
{
- struct fc_lport *lport = disc->lport;
+ struct fc_lport *lport = fc_disc_lport(disc);
unsigned long delay = 0;
- if (fc_disc_debug)
- FC_DBG("Error %ld, retries %d/%d\n",
- PTR_ERR(fp), disc->retry_count,
- FC_DISC_RETRY_LIMIT);
+
+ FC_DISC_DBG(disc, "Error %ld, retries %d/%d\n",
+ PTR_ERR(fp), disc->retry_count,
+ FC_DISC_RETRY_LIMIT);
if (!fp || PTR_ERR(fp) == -FC_EX_TIMEOUT) {
/*
@@ -510,17 +335,21 @@ static void fc_disc_error(struct fc_disc *disc, struct fc_frame *fp)
}
disc->retry_count++;
schedule_delayed_work(&disc->disc_work, delay);
- } else {
- /* exceeded retries */
- disc->event = DISC_EV_FAILED;
- fc_disc_done(disc);
- }
+ } else
+ fc_disc_done(disc, DISC_EV_FAILED);
+ } else if (PTR_ERR(fp) == -FC_EX_CLOSED) {
+ /*
+ * if discovery fails due to lport reset, clear
+ * pending flag so that subsequent discovery can
+ * continue
+ */
+ disc->pending = 0;
}
}
/**
- * fc_disc_gpn_ft_req - Send Get Port Names by FC-4 type (GPN_FT) request
- * @lport: FC discovery context
+ * fc_disc_gpn_ft_req() - Send Get Port Names by FC-4 type (GPN_FT) request
+ * @lport: The discovery context
*
* Locking Note: This function expects that the disc_mutex is locked
* before it is called.
@@ -528,7 +357,7 @@ static void fc_disc_error(struct fc_disc *disc, struct fc_frame *fp)
static void fc_disc_gpn_ft_req(struct fc_disc *disc)
{
struct fc_frame *fp;
- struct fc_lport *lport = disc->lport;
+ struct fc_lport *lport = fc_disc_lport(disc);
WARN_ON(!fc_lport_test_ready(lport));
@@ -543,20 +372,22 @@ static void fc_disc_gpn_ft_req(struct fc_disc *disc)
if (!fp)
goto err;
- if (lport->tt.elsct_send(lport, NULL, fp,
+ if (lport->tt.elsct_send(lport, 0, fp,
FC_NS_GPN_FT,
fc_disc_gpn_ft_resp,
- disc, lport->e_d_tov))
+ disc, 3 * lport->r_a_tov))
return;
err:
- fc_disc_error(disc, fp);
+ fc_disc_error(disc, NULL);
}
/**
- * fc_disc_gpn_ft_parse - Parse the list of IDs and names resulting from a request
- * @lport: Fibre Channel host port instance
- * @buf: GPN_FT response buffer
- * @len: size of response buffer
+ * fc_disc_gpn_ft_parse() - Parse the body of the dNS GPN_FT response.
+ * @lport: The local port the GPN_FT was received on
+ * @buf: The GPN_FT response buffer
+ * @len: The size of response buffer
+ *
+ * Goes through the list of IDs and names resulting from a request.
*/
static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len)
{
@@ -566,11 +397,11 @@ static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len)
size_t plen;
size_t tlen;
int error = 0;
- struct fc_disc_port dp;
- struct fc_rport *rport;
- struct fc_rport_libfc_priv *rdata;
+ struct fc_rport_identifiers ids;
+ struct fc_rport_priv *rdata;
- lport = disc->lport;
+ lport = fc_disc_lport(disc);
+ disc->seq_count++;
/*
* Handle partial name record left over from previous call.
@@ -579,6 +410,7 @@ static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len)
plen = len;
np = (struct fc_gpn_ft_resp *)bp;
tlen = disc->buf_len;
+ disc->buf_len = 0;
if (tlen) {
WARN_ON(tlen >= sizeof(*np));
plen = sizeof(*np) - tlen;
@@ -609,29 +441,25 @@ static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len)
* After the first time through the loop, things return to "normal".
*/
while (plen >= sizeof(*np)) {
- dp.lp = lport;
- dp.ids.port_id = ntoh24(np->fp_fid);
- dp.ids.port_name = ntohll(np->fp_wwpn);
- dp.ids.node_name = -1;
- dp.ids.roles = FC_RPORT_ROLE_UNKNOWN;
-
- if ((dp.ids.port_id != fc_host_port_id(lport->host)) &&
- (dp.ids.port_name != lport->wwpn)) {
- rport = fc_rport_rogue_create(&dp);
- if (rport) {
- rdata = rport->dd_data;
- rdata->ops = &fc_disc_rport_ops;
- rdata->local_port = lport;
- lport->tt.rport_login(rport);
- } else
- FC_DBG("Failed to allocate memory for "
- "the newly discovered port (%6x)\n",
- dp.ids.port_id);
+ ids.port_id = ntoh24(np->fp_fid);
+ ids.port_name = ntohll(np->fp_wwpn);
+
+ if (ids.port_id != lport->port_id &&
+ ids.port_name != lport->wwpn) {
+ rdata = lport->tt.rport_create(lport, ids.port_id);
+ if (rdata) {
+ rdata->ids.port_name = ids.port_name;
+ rdata->disc_id = disc->disc_id;
+ } else {
+ printk(KERN_WARNING "libfc: Failed to allocate "
+ "memory for the newly discovered port "
+ "(%6.6x)\n", ids.port_id);
+ error = -ENOMEM;
+ }
}
if (np->fp_flags & FC_NS_FID_LAST) {
- disc->event = DISC_EV_SUCCESS;
- fc_disc_done(disc);
+ fc_disc_done(disc, DISC_EV_SUCCESS);
len = 0;
break;
}
@@ -646,20 +474,18 @@ static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len)
*/
if (error == 0 && len > 0 && len < sizeof(*np)) {
if (np != &disc->partial_buf) {
- FC_DEBUG_DISC("Partial buffer remains "
- "for discovery by (%6x)\n",
- fc_host_port_id(lport->host));
+ FC_DISC_DBG(disc, "Partial buffer remains "
+ "for discovery\n");
memcpy(&disc->partial_buf, np, len);
}
disc->buf_len = (unsigned char) len;
- } else {
- disc->buf_len = 0;
}
return error;
}
-/*
- * Handle retry of memory allocation for remote ports.
+/**
+ * fc_disc_timeout() - Handler for discovery timeouts
+ * @work: Structure holding discovery context that needs to retry discovery
*/
static void fc_disc_timeout(struct work_struct *work)
{
@@ -667,19 +493,18 @@ static void fc_disc_timeout(struct work_struct *work)
struct fc_disc,
disc_work.work);
mutex_lock(&disc->disc_mutex);
- if (disc->requested && !disc->pending)
- fc_disc_gpn_ft_req(disc);
+ fc_disc_gpn_ft_req(disc);
mutex_unlock(&disc->disc_mutex);
}
/**
- * fc_disc_gpn_ft_resp - Handle a response frame from Get Port Names (GPN_FT)
- * @sp: Current sequence of GPN_FT exchange
- * @fp: response frame
- * @lp_arg: Fibre Channel host port instance
+ * fc_disc_gpn_ft_resp() - Handle a response frame from Get Port Names (GPN_FT)
+ * @sp: The sequence that the GPN_FT response was received on
+ * @fp: The GPN_FT response frame
+ * @lp_arg: The discovery context
*
- * Locking Note: This function expects that the disc_mutex is locked
- * before it is called.
+ * Locking Note: This function is called without disc mutex held, and
+ * should do all its processing with the mutex held
*/
static void fc_disc_gpn_ft_resp(struct fc_seq *sp, struct fc_frame *fp,
void *disc_arg)
@@ -687,16 +512,17 @@ static void fc_disc_gpn_ft_resp(struct fc_seq *sp, struct fc_frame *fp,
struct fc_disc *disc = disc_arg;
struct fc_ct_hdr *cp;
struct fc_frame_header *fh;
+ enum fc_disc_event event = DISC_EV_NONE;
unsigned int seq_cnt;
- void *buf = NULL;
unsigned int len;
- int error;
+ int error = 0;
- FC_DEBUG_DISC("Received a GPN_FT response on port (%6x)\n",
- fc_host_port_id(disc->lport->host));
+ mutex_lock(&disc->disc_mutex);
+ FC_DISC_DBG(disc, "Received a GPN_FT response\n");
if (IS_ERR(fp)) {
fc_disc_error(disc, fp);
+ mutex_unlock(&disc->disc_mutex);
return;
}
@@ -704,117 +530,195 @@ static void fc_disc_gpn_ft_resp(struct fc_seq *sp, struct fc_frame *fp,
fh = fc_frame_header_get(fp);
len = fr_len(fp) - sizeof(*fh);
seq_cnt = ntohs(fh->fh_seq_cnt);
- if (fr_sof(fp) == FC_SOF_I3 && seq_cnt == 0 &&
- disc->seq_count == 0) {
+ if (fr_sof(fp) == FC_SOF_I3 && seq_cnt == 0 && disc->seq_count == 0) {
cp = fc_frame_payload_get(fp, sizeof(*cp));
if (!cp) {
- FC_DBG("GPN_FT response too short, len %d\n",
- fr_len(fp));
+ FC_DISC_DBG(disc, "GPN_FT response too short, len %d\n",
+ fr_len(fp));
+ event = DISC_EV_FAILED;
} else if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
- /*
- * Accepted. Parse response.
- */
- buf = cp + 1;
+ /* Accepted, parse the response. */
len -= sizeof(*cp);
+ error = fc_disc_gpn_ft_parse(disc, cp + 1, len);
} else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
- FC_DBG("GPN_FT rejected reason %x exp %x "
- "(check zoning)\n", cp->ct_reason,
- cp->ct_explan);
- disc->event = DISC_EV_FAILED;
- fc_disc_done(disc);
+ FC_DISC_DBG(disc, "GPN_FT rejected reason %x exp %x "
+ "(check zoning)\n", cp->ct_reason,
+ cp->ct_explan);
+ event = DISC_EV_FAILED;
+ if (cp->ct_reason == FC_FS_RJT_UNABL &&
+ cp->ct_explan == FC_FS_EXP_FTNR)
+ event = DISC_EV_SUCCESS;
} else {
- FC_DBG("GPN_FT unexpected response code %x\n",
- ntohs(cp->ct_cmd));
+ FC_DISC_DBG(disc, "GPN_FT unexpected response code "
+ "%x\n", ntohs(cp->ct_cmd));
+ event = DISC_EV_FAILED;
}
- } else if (fr_sof(fp) == FC_SOF_N3 &&
- seq_cnt == disc->seq_count) {
- buf = fh + 1;
+ } else if (fr_sof(fp) == FC_SOF_N3 && seq_cnt == disc->seq_count) {
+ error = fc_disc_gpn_ft_parse(disc, fh + 1, len);
} else {
- FC_DBG("GPN_FT unexpected frame - out of sequence? "
- "seq_cnt %x expected %x sof %x eof %x\n",
- seq_cnt, disc->seq_count, fr_sof(fp), fr_eof(fp));
- }
- if (buf) {
- error = fc_disc_gpn_ft_parse(disc, buf, len);
- if (error)
- fc_disc_error(disc, fp);
- else
- disc->seq_count++;
+ FC_DISC_DBG(disc, "GPN_FT unexpected frame - out of sequence? "
+ "seq_cnt %x expected %x sof %x eof %x\n",
+ seq_cnt, disc->seq_count, fr_sof(fp), fr_eof(fp));
+ event = DISC_EV_FAILED;
}
+ if (error)
+ fc_disc_error(disc, fp);
+ else if (event != DISC_EV_NONE)
+ fc_disc_done(disc, event);
fc_frame_free(fp);
+ mutex_unlock(&disc->disc_mutex);
}
/**
- * fc_disc_single - Discover the directory information for a single target
- * @lport: FC local port
- * @dp: The port to rediscover
+ * fc_disc_gpn_id_resp() - Handle a response frame from Get Port Names (GPN_ID)
+ * @sp: The sequence the GPN_ID is on
+ * @fp: The response frame
+ * @rdata_arg: The remote port that sent the GPN_ID response
*
- * Locking Note: This function expects that the disc_mutex is locked
- * before it is called.
+ * Locking Note: This function is called without disc mutex held.
*/
-static void fc_disc_single(struct fc_disc *disc, struct fc_disc_port *dp)
+static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *rdata_arg)
{
+ struct fc_rport_priv *rdata = rdata_arg;
+ struct fc_rport_priv *new_rdata;
struct fc_lport *lport;
- struct fc_rport *rport;
- struct fc_rport *new_rport;
- struct fc_rport_libfc_priv *rdata;
+ struct fc_disc *disc;
+ struct fc_ct_hdr *cp;
+ struct fc_ns_gid_pn *pn;
+ u64 port_name;
- lport = disc->lport;
+ lport = rdata->local_port;
+ disc = &lport->disc;
- if (dp->ids.port_id == fc_host_port_id(lport->host))
+ mutex_lock(&disc->disc_mutex);
+ if (PTR_ERR(fp) == -FC_EX_CLOSED)
goto out;
-
- rport = lport->tt.rport_lookup(lport, dp->ids.port_id);
- if (rport)
- fc_disc_del_target(disc, rport);
-
- new_rport = fc_rport_rogue_create(dp);
- if (new_rport) {
- rdata = new_rport->dd_data;
- rdata->ops = &fc_disc_rport_ops;
- kfree(dp);
- lport->tt.rport_login(new_rport);
+ if (IS_ERR(fp))
+ goto redisc;
+
+ cp = fc_frame_payload_get(fp, sizeof(*cp));
+ if (!cp)
+ goto redisc;
+ if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
+ if (fr_len(fp) < sizeof(struct fc_frame_header) +
+ sizeof(*cp) + sizeof(*pn))
+ goto redisc;
+ pn = (struct fc_ns_gid_pn *)(cp + 1);
+ port_name = get_unaligned_be64(&pn->fn_wwpn);
+ if (rdata->ids.port_name == -1)
+ rdata->ids.port_name = port_name;
+ else if (rdata->ids.port_name != port_name) {
+ FC_DISC_DBG(disc, "GPN_ID accepted. WWPN changed. "
+ "Port-id %6.6x wwpn %16.16llx\n",
+ rdata->ids.port_id, port_name);
+ lport->tt.rport_logoff(rdata);
+
+ new_rdata = lport->tt.rport_create(lport,
+ rdata->ids.port_id);
+ if (new_rdata) {
+ new_rdata->disc_id = disc->disc_id;
+ lport->tt.rport_login(new_rdata);
+ }
+ goto out;
+ }
+ rdata->disc_id = disc->disc_id;
+ lport->tt.rport_login(rdata);
+ } else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
+ FC_DISC_DBG(disc, "GPN_ID rejected reason %x exp %x\n",
+ cp->ct_reason, cp->ct_explan);
+ lport->tt.rport_logoff(rdata);
+ } else {
+ FC_DISC_DBG(disc, "GPN_ID unexpected response code %x\n",
+ ntohs(cp->ct_cmd));
+redisc:
+ fc_disc_restart(disc);
}
- return;
out:
- kfree(dp);
+ mutex_unlock(&disc->disc_mutex);
+ kref_put(&rdata->kref, lport->tt.rport_destroy);
}
/**
- * fc_disc_stop - Stop discovery for a given lport
- * @lport: The lport that discovery should stop for
+ * fc_disc_gpn_id_req() - Send Get Port Names by ID (GPN_ID) request
+ * @lport: The local port to initiate discovery on
+ * @rdata: remote port private data
+ *
+ * Locking Note: This function expects that the disc_mutex is locked
+ * before it is called.
+ * On failure, an error code is returned.
*/
-void fc_disc_stop(struct fc_lport *lport)
+static int fc_disc_gpn_id_req(struct fc_lport *lport,
+ struct fc_rport_priv *rdata)
+{
+ struct fc_frame *fp;
+
+ fp = fc_frame_alloc(lport, sizeof(struct fc_ct_hdr) +
+ sizeof(struct fc_ns_fid));
+ if (!fp)
+ return -ENOMEM;
+ if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, FC_NS_GPN_ID,
+ fc_disc_gpn_id_resp, rdata,
+ 3 * lport->r_a_tov))
+ return -ENOMEM;
+ kref_get(&rdata->kref);
+ return 0;
+}
+
+/**
+ * fc_disc_single() - Discover the directory information for a single target
+ * @lport: The local port the remote port is associated with
+ * @dp: The port to rediscover
+ *
+ * Locking Note: This function expects that the disc_mutex is locked
+ * before it is called.
+ */
+static int fc_disc_single(struct fc_lport *lport, struct fc_disc_port *dp)
+{
+ struct fc_rport_priv *rdata;
+
+ rdata = lport->tt.rport_create(lport, dp->port_id);
+ if (!rdata)
+ return -ENOMEM;
+ rdata->disc_id = 0;
+ return fc_disc_gpn_id_req(lport, rdata);
+}
+
+/**
+ * fc_disc_stop() - Stop discovery for a given lport
+ * @lport: The local port that discovery should stop on
+ */
+static void fc_disc_stop(struct fc_lport *lport)
{
struct fc_disc *disc = &lport->disc;
- if (disc) {
+ if (disc->pending)
cancel_delayed_work_sync(&disc->disc_work);
- fc_disc_stop_rports(disc);
- }
+ fc_disc_stop_rports(disc);
}
/**
- * fc_disc_stop_final - Stop discovery for a given lport
- * @lport: The lport that discovery should stop for
+ * fc_disc_stop_final() - Stop discovery for a given lport
+ * @lport: The lport that discovery should stop on
*
* This function will block until discovery has been
* completely stopped and all rports have been deleted.
*/
-void fc_disc_stop_final(struct fc_lport *lport)
+static void fc_disc_stop_final(struct fc_lport *lport)
{
fc_disc_stop(lport);
lport->tt.rport_flush_queue();
}
/**
- * fc_disc_init - Initialize the discovery block
- * @lport: FC local port
+ * fc_disc_config() - Configure the discovery layer for a local port
+ * @lport: The local port that needs the discovery layer to be configured
+ * @priv: Private data structre for users of the discovery layer
*/
-int fc_disc_init(struct fc_lport *lport)
+void fc_disc_config(struct fc_lport *lport, void *priv)
{
- struct fc_disc *disc;
+ struct fc_disc *disc = &lport->disc;
if (!lport->tt.disc_start)
lport->tt.disc_start = fc_disc_start;
@@ -828,18 +732,22 @@ int fc_disc_init(struct fc_lport *lport)
if (!lport->tt.disc_recv_req)
lport->tt.disc_recv_req = fc_disc_recv_req;
- if (!lport->tt.rport_lookup)
- lport->tt.rport_lookup = fc_disc_lookup_rport;
-
disc = &lport->disc;
+
+ disc->priv = priv;
+}
+EXPORT_SYMBOL(fc_disc_config);
+
+/**
+ * fc_disc_init() - Initialize the discovery layer for a local port
+ * @lport: The local port that needs the discovery layer to be initialized
+ */
+void fc_disc_init(struct fc_lport *lport)
+{
+ struct fc_disc *disc = &lport->disc;
+
INIT_DELAYED_WORK(&disc->disc_work, fc_disc_timeout);
mutex_init(&disc->disc_mutex);
INIT_LIST_HEAD(&disc->rports);
-
- disc->lport = lport;
- disc->delay = FC_DISC_DELAY;
- disc->event = DISC_EV_NONE;
-
- return 0;
}
EXPORT_SYMBOL(fc_disc_init);
diff --git a/drivers/scsi/libfc/fc_elsct.c b/drivers/scsi/libfc/fc_elsct.c
index dd47fe619d1..c2384d50147 100644
--- a/drivers/scsi/libfc/fc_elsct.c
+++ b/drivers/scsi/libfc/fc_elsct.c
@@ -21,46 +21,60 @@
* Provide interface to send ELS/CT FC frames
*/
+#include <linux/export.h>
#include <asm/unaligned.h>
#include <scsi/fc/fc_gs.h>
#include <scsi/fc/fc_ns.h>
#include <scsi/fc/fc_els.h>
#include <scsi/libfc.h>
#include <scsi/fc_encode.h>
+#include "fc_libfc.h"
-/*
- * fc_elsct_send - sends ELS/CT frame
+/**
+ * fc_elsct_send() - Send an ELS or CT frame
+ * @lport: The local port to send the frame on
+ * @did: The destination ID for the frame
+ * @fp: The frame to be sent
+ * @op: The operational code
+ * @resp: The callback routine when the response is received
+ * @arg: The argument to pass to the response callback routine
+ * @timer_msec: The timeout period for the frame (in msecs)
*/
-static struct fc_seq *fc_elsct_send(struct fc_lport *lport,
- struct fc_rport *rport,
- struct fc_frame *fp,
- unsigned int op,
- void (*resp)(struct fc_seq *,
- struct fc_frame *fp,
- void *arg),
- void *arg, u32 timer_msec)
+struct fc_seq *fc_elsct_send(struct fc_lport *lport, u32 did,
+ struct fc_frame *fp, unsigned int op,
+ void (*resp)(struct fc_seq *,
+ struct fc_frame *,
+ void *),
+ void *arg, u32 timer_msec)
{
enum fc_rctl r_ctl;
- u32 did;
enum fc_fh_type fh_type;
int rc;
/* ELS requests */
if ((op >= ELS_LS_RJT) && (op <= ELS_AUTH_ELS))
- rc = fc_els_fill(lport, rport, fp, op, &r_ctl, &did, &fh_type);
- else
+ rc = fc_els_fill(lport, did, fp, op, &r_ctl, &fh_type);
+ else {
/* CT requests */
- rc = fc_ct_fill(lport, fp, op, &r_ctl, &did, &fh_type);
+ rc = fc_ct_fill(lport, did, fp, op, &r_ctl, &fh_type, &did);
+ }
- if (rc)
+ if (rc) {
+ fc_frame_free(fp);
return NULL;
+ }
- fc_fill_fc_hdr(fp, r_ctl, did, fc_host_port_id(lport->host), fh_type,
- FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
+ fc_fill_fc_hdr(fp, r_ctl, did, lport->port_id, fh_type,
+ FC_FCTL_REQ, 0);
return lport->tt.exch_seq_send(lport, fp, resp, NULL, arg, timer_msec);
}
+EXPORT_SYMBOL(fc_elsct_send);
+/**
+ * fc_elsct_init() - Initialize the ELS/CT layer
+ * @lport: The local port to initialize the ELS/CT layer for
+ */
int fc_elsct_init(struct fc_lport *lport)
{
if (!lport->tt.elsct_send)
@@ -69,3 +83,70 @@ int fc_elsct_init(struct fc_lport *lport)
return 0;
}
EXPORT_SYMBOL(fc_elsct_init);
+
+/**
+ * fc_els_resp_type() - Return a string describing the ELS response
+ * @fp: The frame pointer or possible error code
+ */
+const char *fc_els_resp_type(struct fc_frame *fp)
+{
+ const char *msg;
+ struct fc_frame_header *fh;
+ struct fc_ct_hdr *ct;
+
+ if (IS_ERR(fp)) {
+ switch (-PTR_ERR(fp)) {
+ case FC_NO_ERR:
+ msg = "response no error";
+ break;
+ case FC_EX_TIMEOUT:
+ msg = "response timeout";
+ break;
+ case FC_EX_CLOSED:
+ msg = "response closed";
+ break;
+ default:
+ msg = "response unknown error";
+ break;
+ }
+ } else {
+ fh = fc_frame_header_get(fp);
+ switch (fh->fh_type) {
+ case FC_TYPE_ELS:
+ switch (fc_frame_payload_op(fp)) {
+ case ELS_LS_ACC:
+ msg = "accept";
+ break;
+ case ELS_LS_RJT:
+ msg = "reject";
+ break;
+ default:
+ msg = "response unknown ELS";
+ break;
+ }
+ break;
+ case FC_TYPE_CT:
+ ct = fc_frame_payload_get(fp, sizeof(*ct));
+ if (ct) {
+ switch (ntohs(ct->ct_cmd)) {
+ case FC_FS_ACC:
+ msg = "CT accept";
+ break;
+ case FC_FS_RJT:
+ msg = "CT reject";
+ break;
+ default:
+ msg = "response unknown CT";
+ break;
+ }
+ } else {
+ msg = "short CT response";
+ }
+ break;
+ default:
+ msg = "response not ELS or CT";
+ break;
+ }
+ }
+ return msg;
+}
diff --git a/drivers/scsi/libfc/fc_exch.c b/drivers/scsi/libfc/fc_exch.c
index 66db08a5f27..1b3a0947345 100644
--- a/drivers/scsi/libfc/fc_exch.c
+++ b/drivers/scsi/libfc/fc_exch.c
@@ -24,28 +24,23 @@
*/
#include <linux/timer.h>
-#include <linux/gfp.h>
+#include <linux/slab.h>
#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/log2.h>
#include <scsi/fc/fc_fc2.h>
#include <scsi/libfc.h>
#include <scsi/fc_encode.h>
-#define FC_DEF_R_A_TOV (10 * 1000) /* resource allocation timeout */
+#include "fc_libfc.h"
-/*
- * fc_exch_debug can be set in debugger or at compile time to get more logs.
- */
-static int fc_exch_debug;
-
-#define FC_DEBUG_EXCH(fmt...) \
- do { \
- if (fc_exch_debug) \
- FC_DBG(fmt); \
- } while (0)
-
-static struct kmem_cache *fc_em_cachep; /* cache for exchanges */
+u16 fc_cpu_mask; /* cpu mask for possible cpus */
+EXPORT_SYMBOL(fc_cpu_mask);
+static u16 fc_cpu_order; /* 2's power to represent total possible cpus */
+static struct kmem_cache *fc_em_cachep; /* cache for exchanges */
+static struct workqueue_struct *fc_exch_workqueue;
/*
* Structure and function definitions for managing Fibre Channel Exchanges
@@ -60,31 +55,51 @@ static struct kmem_cache *fc_em_cachep; /* cache for exchanges */
* fc_seq holds the state for an individual sequence.
*/
-/*
- * Exchange manager.
+/**
+ * struct fc_exch_pool - Per cpu exchange pool
+ * @next_index: Next possible free exchange index
+ * @total_exches: Total allocated exchanges
+ * @lock: Exch pool lock
+ * @ex_list: List of exchanges
+ *
+ * This structure manages per cpu exchanges in array of exchange pointers.
+ * This array is allocated followed by struct fc_exch_pool memory for
+ * assigned range of exchanges to per cpu pool.
+ */
+struct fc_exch_pool {
+ spinlock_t lock;
+ struct list_head ex_list;
+ u16 next_index;
+ u16 total_exches;
+
+ /* two cache of free slot in exch array */
+ u16 left;
+ u16 right;
+} ____cacheline_aligned_in_smp;
+
+/**
+ * struct fc_exch_mgr - The Exchange Manager (EM).
+ * @class: Default class for new sequences
+ * @kref: Reference counter
+ * @min_xid: Minimum exchange ID
+ * @max_xid: Maximum exchange ID
+ * @ep_pool: Reserved exchange pointers
+ * @pool_max_index: Max exch array index in exch pool
+ * @pool: Per cpu exch pool
+ * @stats: Statistics structure
*
* This structure is the center for creating exchanges and sequences.
* It manages the allocation of exchange IDs.
*/
struct fc_exch_mgr {
- enum fc_class class; /* default class for sequences */
- spinlock_t em_lock; /* exchange manager lock,
- must be taken before ex_lock */
- u16 last_xid; /* last allocated exchange ID */
- u16 min_xid; /* min exchange ID */
- u16 max_xid; /* max exchange ID */
- u16 max_read; /* max exchange ID for read */
- u16 last_read; /* last xid allocated for read */
- u32 total_exches; /* total allocated exchanges */
- struct list_head ex_list; /* allocated exchanges list */
- struct fc_lport *lp; /* fc device instance */
- mempool_t *ep_pool; /* reserve ep's */
+ struct fc_exch_pool __percpu *pool;
+ mempool_t *ep_pool;
+ enum fc_class class;
+ struct kref kref;
+ u16 min_xid;
+ u16 max_xid;
+ u16 pool_max_index;
- /*
- * currently exchange mgr stats are updated but not used.
- * either stats can be expose via sysfs or remove them
- * all together if not used XXX
- */
struct {
atomic_t no_free_exch;
atomic_t no_free_exch_xid;
@@ -93,17 +108,32 @@ struct fc_exch_mgr {
atomic_t seq_not_found;
atomic_t non_bls_resp;
} stats;
- struct fc_exch **exches; /* for exch pointers indexed by xid */
};
-#define fc_seq_exch(sp) container_of(sp, struct fc_exch, seq)
+
+/**
+ * struct fc_exch_mgr_anchor - primary structure for list of EMs
+ * @ema_list: Exchange Manager Anchor list
+ * @mp: Exchange Manager associated with this anchor
+ * @match: Routine to determine if this anchor's EM should be used
+ *
+ * When walking the list of anchors the match routine will be called
+ * for each anchor to determine if that EM should be used. The last
+ * anchor in the list will always match to handle any exchanges not
+ * handled by other EMs. The non-default EMs would be added to the
+ * anchor list by HW that provides offloads.
+ */
+struct fc_exch_mgr_anchor {
+ struct list_head ema_list;
+ struct fc_exch_mgr *mp;
+ bool (*match)(struct fc_frame *);
+};
static void fc_exch_rrq(struct fc_exch *);
-static void fc_seq_ls_acc(struct fc_seq *);
-static void fc_seq_ls_rjt(struct fc_seq *, enum fc_els_rjt_reason,
+static void fc_seq_ls_acc(struct fc_frame *);
+static void fc_seq_ls_rjt(struct fc_frame *, enum fc_els_rjt_reason,
enum fc_els_rjt_explan);
-static void fc_exch_els_rec(struct fc_seq *, struct fc_frame *);
-static void fc_exch_els_rrq(struct fc_seq *, struct fc_frame *);
-static struct fc_seq *fc_seq_start_next_locked(struct fc_seq *sp);
+static void fc_exch_els_rec(struct fc_frame *);
+static void fc_exch_els_rrq(struct fc_frame *);
/*
* Internal implementation notes.
@@ -180,8 +210,8 @@ static struct fc_seq *fc_seq_start_next_locked(struct fc_seq *sp);
* sequence allocation and deallocation must be locked.
* - exchange refcnt can be done atomicly without locks.
* - sequence allocation must be locked by exch lock.
- * - If the em_lock and ex_lock must be taken at the same time, then the
- * em_lock must be taken before the ex_lock.
+ * - If the EM pool lock and ex_lock must be taken at the same time, then the
+ * EM pool lock must be taken before the ex_lock.
*/
/*
@@ -189,8 +219,15 @@ static struct fc_seq *fc_seq_start_next_locked(struct fc_seq *sp);
*/
static char *fc_exch_rctl_names[] = FC_RCTL_NAMES_INIT;
-#define FC_TABLE_SIZE(x) (sizeof(x) / sizeof(x[0]))
-
+/**
+ * fc_exch_name_lookup() - Lookup name by opcode
+ * @op: Opcode to be looked up
+ * @table: Opcode/name table
+ * @max_index: Index not to be exceeded
+ *
+ * This routine is used to determine a human-readable string identifying
+ * a R_CTL opcode.
+ */
static inline const char *fc_exch_name_lookup(unsigned int op, char **table,
unsigned int max_index)
{
@@ -203,25 +240,34 @@ static inline const char *fc_exch_name_lookup(unsigned int op, char **table,
return name;
}
+/**
+ * fc_exch_rctl_name() - Wrapper routine for fc_exch_name_lookup()
+ * @op: The opcode to be looked up
+ */
static const char *fc_exch_rctl_name(unsigned int op)
{
return fc_exch_name_lookup(op, fc_exch_rctl_names,
- FC_TABLE_SIZE(fc_exch_rctl_names));
+ ARRAY_SIZE(fc_exch_rctl_names));
}
-/*
- * Hold an exchange - keep it from being freed.
+/**
+ * fc_exch_hold() - Increment an exchange's reference count
+ * @ep: Echange to be held
*/
-static void fc_exch_hold(struct fc_exch *ep)
+static inline void fc_exch_hold(struct fc_exch *ep)
{
atomic_inc(&ep->ex_refcnt);
}
-/*
- * setup fc hdr by initializing few more FC header fields and sof/eof.
- * Initialized fields by this func:
- * - fh_ox_id, fh_rx_id, fh_seq_id, fh_seq_cnt
- * - sof and eof
+/**
+ * fc_exch_setup_hdr() - Initialize a FC header by initializing some fields
+ * and determine SOF and EOF.
+ * @ep: The exchange to that will use the header
+ * @fp: The frame whose header is to be modified
+ * @f_ctl: F_CTL bits that will be used for the frame header
+ *
+ * The fields initialized by this routine are: fh_ox_id, fh_rx_id,
+ * fh_seq_id, fh_seq_cnt and the SOF and EOF.
*/
static void fc_exch_setup_hdr(struct fc_exch *ep, struct fc_frame *fp,
u32 f_ctl)
@@ -238,7 +284,7 @@ static void fc_exch_setup_hdr(struct fc_exch *ep, struct fc_frame *fp,
if (fc_sof_needs_ack(ep->class))
fr_eof(fp) = FC_EOF_N;
/*
- * Form f_ctl.
+ * From F_CTL.
* The number of fill bytes to make the length a 4-byte
* multiple is the low order 2-bits of the f_ctl.
* The fill itself will have been cleared by the frame
@@ -258,20 +304,19 @@ static void fc_exch_setup_hdr(struct fc_exch *ep, struct fc_frame *fp,
fr_eof(fp) = FC_EOF_N;
}
- /*
- * Initialize remainig fh fields
- * from fc_fill_fc_hdr
- */
+ /* Initialize remaining fh fields from fc_fill_fc_hdr */
fh->fh_ox_id = htons(ep->oxid);
fh->fh_rx_id = htons(ep->rxid);
fh->fh_seq_id = ep->seq.id;
fh->fh_seq_cnt = htons(ep->seq.cnt);
}
-
-/*
- * Release a reference to an exchange.
- * If the refcnt goes to zero and the exchange is complete, it is freed.
+/**
+ * fc_exch_release() - Decrement an exchange's reference count
+ * @ep: Exchange to be released
+ *
+ * If the reference count reaches zero and the exchange is complete,
+ * it is freed.
*/
static void fc_exch_release(struct fc_exch *ep)
{
@@ -281,13 +326,64 @@ static void fc_exch_release(struct fc_exch *ep)
mp = ep->em;
if (ep->destructor)
ep->destructor(&ep->seq, ep->arg);
- if (ep->lp->tt.exch_put)
- ep->lp->tt.exch_put(ep->lp, mp, ep->xid);
- WARN_ON(!ep->esb_stat & ESB_ST_COMPLETE);
+ WARN_ON(!(ep->esb_stat & ESB_ST_COMPLETE));
mempool_free(ep, mp->ep_pool);
}
}
+/**
+ * fc_exch_timer_cancel() - cancel exch timer
+ * @ep: The exchange whose timer to be canceled
+ */
+static inline void fc_exch_timer_cancel(struct fc_exch *ep)
+{
+ if (cancel_delayed_work(&ep->timeout_work)) {
+ FC_EXCH_DBG(ep, "Exchange timer canceled\n");
+ atomic_dec(&ep->ex_refcnt); /* drop hold for timer */
+ }
+}
+
+/**
+ * fc_exch_timer_set_locked() - Start a timer for an exchange w/ the
+ * the exchange lock held
+ * @ep: The exchange whose timer will start
+ * @timer_msec: The timeout period
+ *
+ * Used for upper level protocols to time out the exchange.
+ * The timer is cancelled when it fires or when the exchange completes.
+ */
+static inline void fc_exch_timer_set_locked(struct fc_exch *ep,
+ unsigned int timer_msec)
+{
+ if (ep->state & (FC_EX_RST_CLEANUP | FC_EX_DONE))
+ return;
+
+ FC_EXCH_DBG(ep, "Exchange timer armed : %d msecs\n", timer_msec);
+
+ fc_exch_hold(ep); /* hold for timer */
+ if (!queue_delayed_work(fc_exch_workqueue, &ep->timeout_work,
+ msecs_to_jiffies(timer_msec)))
+ fc_exch_release(ep);
+}
+
+/**
+ * fc_exch_timer_set() - Lock the exchange and set the timer
+ * @ep: The exchange whose timer will start
+ * @timer_msec: The timeout period
+ */
+static void fc_exch_timer_set(struct fc_exch *ep, unsigned int timer_msec)
+{
+ spin_lock_bh(&ep->ex_lock);
+ fc_exch_timer_set_locked(ep, timer_msec);
+ spin_unlock_bh(&ep->ex_lock);
+}
+
+/**
+ * fc_exch_done_locked() - Complete an exchange with the exchange lock held
+ * @ep: The exchange that is complete
+ *
+ * Note: May sleep if invoked from outside a response handler.
+ */
static int fc_exch_done_locked(struct fc_exch *ep)
{
int rc = 1;
@@ -298,129 +394,374 @@ static int fc_exch_done_locked(struct fc_exch *ep)
* ep, and in that case we only clear the resp and set it as
* complete, so it can be reused by the timer to send the rrq.
*/
- ep->resp = NULL;
if (ep->state & FC_EX_DONE)
return rc;
ep->esb_stat |= ESB_ST_COMPLETE;
if (!(ep->esb_stat & ESB_ST_REC_QUAL)) {
ep->state |= FC_EX_DONE;
- if (cancel_delayed_work(&ep->timeout_work))
- atomic_dec(&ep->ex_refcnt); /* drop hold for timer */
+ fc_exch_timer_cancel(ep);
rc = 0;
}
return rc;
}
-static void fc_exch_mgr_delete_ep(struct fc_exch *ep)
+/**
+ * fc_exch_ptr_get() - Return an exchange from an exchange pool
+ * @pool: Exchange Pool to get an exchange from
+ * @index: Index of the exchange within the pool
+ *
+ * Use the index to get an exchange from within an exchange pool. exches
+ * will point to an array of exchange pointers. The index will select
+ * the exchange within the array.
+ */
+static inline struct fc_exch *fc_exch_ptr_get(struct fc_exch_pool *pool,
+ u16 index)
{
- struct fc_exch_mgr *mp;
+ struct fc_exch **exches = (struct fc_exch **)(pool + 1);
+ return exches[index];
+}
+
+/**
+ * fc_exch_ptr_set() - Assign an exchange to a slot in an exchange pool
+ * @pool: The pool to assign the exchange to
+ * @index: The index in the pool where the exchange will be assigned
+ * @ep: The exchange to assign to the pool
+ */
+static inline void fc_exch_ptr_set(struct fc_exch_pool *pool, u16 index,
+ struct fc_exch *ep)
+{
+ ((struct fc_exch **)(pool + 1))[index] = ep;
+}
- mp = ep->em;
- spin_lock_bh(&mp->em_lock);
- WARN_ON(mp->total_exches <= 0);
- mp->total_exches--;
- mp->exches[ep->xid - mp->min_xid] = NULL;
+/**
+ * fc_exch_delete() - Delete an exchange
+ * @ep: The exchange to be deleted
+ */
+static void fc_exch_delete(struct fc_exch *ep)
+{
+ struct fc_exch_pool *pool;
+ u16 index;
+
+ pool = ep->pool;
+ spin_lock_bh(&pool->lock);
+ WARN_ON(pool->total_exches <= 0);
+ pool->total_exches--;
+
+ /* update cache of free slot */
+ index = (ep->xid - ep->em->min_xid) >> fc_cpu_order;
+ if (pool->left == FC_XID_UNKNOWN)
+ pool->left = index;
+ else if (pool->right == FC_XID_UNKNOWN)
+ pool->right = index;
+ else
+ pool->next_index = index;
+
+ fc_exch_ptr_set(pool, index, NULL);
list_del(&ep->ex_list);
- spin_unlock_bh(&mp->em_lock);
+ spin_unlock_bh(&pool->lock);
fc_exch_release(ep); /* drop hold for exch in mp */
}
-/*
- * Internal version of fc_exch_timer_set - used with lock held.
+static int fc_seq_send_locked(struct fc_lport *lport, struct fc_seq *sp,
+ struct fc_frame *fp)
+{
+ struct fc_exch *ep;
+ struct fc_frame_header *fh = fc_frame_header_get(fp);
+ int error = -ENXIO;
+ u32 f_ctl;
+ u8 fh_type = fh->fh_type;
+
+ ep = fc_seq_exch(sp);
+
+ if (ep->esb_stat & (ESB_ST_COMPLETE | ESB_ST_ABNORMAL)) {
+ fc_frame_free(fp);
+ goto out;
+ }
+
+ WARN_ON(!(ep->esb_stat & ESB_ST_SEQ_INIT));
+
+ f_ctl = ntoh24(fh->fh_f_ctl);
+ fc_exch_setup_hdr(ep, fp, f_ctl);
+ fr_encaps(fp) = ep->encaps;
+
+ /*
+ * update sequence count if this frame is carrying
+ * multiple FC frames when sequence offload is enabled
+ * by LLD.
+ */
+ if (fr_max_payload(fp))
+ sp->cnt += DIV_ROUND_UP((fr_len(fp) - sizeof(*fh)),
+ fr_max_payload(fp));
+ else
+ sp->cnt++;
+
+ /*
+ * Send the frame.
+ */
+ error = lport->tt.frame_send(lport, fp);
+
+ if (fh_type == FC_TYPE_BLS)
+ goto out;
+
+ /*
+ * Update the exchange and sequence flags,
+ * assuming all frames for the sequence have been sent.
+ * We can only be called to send once for each sequence.
+ */
+ ep->f_ctl = f_ctl & ~FC_FC_FIRST_SEQ; /* not first seq */
+ if (f_ctl & FC_FC_SEQ_INIT)
+ ep->esb_stat &= ~ESB_ST_SEQ_INIT;
+out:
+ return error;
+}
+
+/**
+ * fc_seq_send() - Send a frame using existing sequence/exchange pair
+ * @lport: The local port that the exchange will be sent on
+ * @sp: The sequence to be sent
+ * @fp: The frame to be sent on the exchange
+ *
+ * Note: The frame will be freed either by a direct call to fc_frame_free(fp)
+ * or indirectly by calling libfc_function_template.frame_send().
*/
-static inline void fc_exch_timer_set_locked(struct fc_exch *ep,
- unsigned int timer_msec)
+static int fc_seq_send(struct fc_lport *lport, struct fc_seq *sp,
+ struct fc_frame *fp)
{
- if (ep->state & (FC_EX_RST_CLEANUP | FC_EX_DONE))
- return;
+ struct fc_exch *ep;
+ int error;
+ ep = fc_seq_exch(sp);
+ spin_lock_bh(&ep->ex_lock);
+ error = fc_seq_send_locked(lport, sp, fp);
+ spin_unlock_bh(&ep->ex_lock);
+ return error;
+}
+
+/**
+ * fc_seq_alloc() - Allocate a sequence for a given exchange
+ * @ep: The exchange to allocate a new sequence for
+ * @seq_id: The sequence ID to be used
+ *
+ * We don't support multiple originated sequences on the same exchange.
+ * By implication, any previously originated sequence on this exchange
+ * is complete, and we reallocate the same sequence.
+ */
+static struct fc_seq *fc_seq_alloc(struct fc_exch *ep, u8 seq_id)
+{
+ struct fc_seq *sp;
+
+ sp = &ep->seq;
+ sp->ssb_stat = 0;
+ sp->cnt = 0;
+ sp->id = seq_id;
+ return sp;
+}
+
+/**
+ * fc_seq_start_next_locked() - Allocate a new sequence on the same
+ * exchange as the supplied sequence
+ * @sp: The sequence/exchange to get a new sequence for
+ */
+static struct fc_seq *fc_seq_start_next_locked(struct fc_seq *sp)
+{
+ struct fc_exch *ep = fc_seq_exch(sp);
- FC_DEBUG_EXCH("Exchange (%4x) timed out, notifying the upper layer\n",
- ep->xid);
- if (schedule_delayed_work(&ep->timeout_work,
- msecs_to_jiffies(timer_msec)))
- fc_exch_hold(ep); /* hold for timer */
+ sp = fc_seq_alloc(ep, ep->seq_id++);
+ FC_EXCH_DBG(ep, "f_ctl %6x seq %2x\n",
+ ep->f_ctl, sp->id);
+ return sp;
+}
+
+/**
+ * fc_seq_start_next() - Lock the exchange and get a new sequence
+ * for a given sequence/exchange pair
+ * @sp: The sequence/exchange to get a new exchange for
+ */
+static struct fc_seq *fc_seq_start_next(struct fc_seq *sp)
+{
+ struct fc_exch *ep = fc_seq_exch(sp);
+
+ spin_lock_bh(&ep->ex_lock);
+ sp = fc_seq_start_next_locked(sp);
+ spin_unlock_bh(&ep->ex_lock);
+
+ return sp;
}
/*
- * Set timer for an exchange.
- * The time is a minimum delay in milliseconds until the timer fires.
- * Used for upper level protocols to time out the exchange.
- * The timer is cancelled when it fires or when the exchange completes.
- * Returns non-zero if a timer couldn't be allocated.
+ * Set the response handler for the exchange associated with a sequence.
+ *
+ * Note: May sleep if invoked from outside a response handler.
*/
-static void fc_exch_timer_set(struct fc_exch *ep, unsigned int timer_msec)
+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);
+ DEFINE_WAIT(wait);
+
spin_lock_bh(&ep->ex_lock);
- fc_exch_timer_set_locked(ep, timer_msec);
+ while (ep->resp_active && ep->resp_task != current) {
+ prepare_to_wait(&ep->resp_wq, &wait, TASK_UNINTERRUPTIBLE);
+ spin_unlock_bh(&ep->ex_lock);
+
+ schedule();
+
+ spin_lock_bh(&ep->ex_lock);
+ }
+ finish_wait(&ep->resp_wq, &wait);
+ ep->resp = resp;
+ ep->arg = arg;
spin_unlock_bh(&ep->ex_lock);
}
-int fc_seq_exch_abort(const struct fc_seq *req_sp, unsigned int timer_msec)
+/**
+ * fc_exch_abort_locked() - Abort an exchange
+ * @ep: The exchange to be aborted
+ * @timer_msec: The period of time to wait before aborting
+ *
+ * Locking notes: Called with exch lock held
+ *
+ * Return value: 0 on success else error code
+ */
+static int fc_exch_abort_locked(struct fc_exch *ep,
+ unsigned int timer_msec)
{
struct fc_seq *sp;
- struct fc_exch *ep;
struct fc_frame *fp;
int error;
- ep = fc_seq_exch(req_sp);
-
- spin_lock_bh(&ep->ex_lock);
if (ep->esb_stat & (ESB_ST_COMPLETE | ESB_ST_ABNORMAL) ||
- ep->state & (FC_EX_DONE | FC_EX_RST_CLEANUP)) {
- spin_unlock_bh(&ep->ex_lock);
+ ep->state & (FC_EX_DONE | FC_EX_RST_CLEANUP))
return -ENXIO;
- }
/*
* Send the abort on a new sequence if possible.
*/
sp = fc_seq_start_next_locked(&ep->seq);
- if (!sp) {
- spin_unlock_bh(&ep->ex_lock);
+ if (!sp)
return -ENOMEM;
- }
- ep->esb_stat |= ESB_ST_SEQ_INIT | ESB_ST_ABNORMAL;
if (timer_msec)
fc_exch_timer_set_locked(ep, timer_msec);
- spin_unlock_bh(&ep->ex_lock);
- /*
- * If not logged into the fabric, don't send ABTS but leave
- * sequence active until next timeout.
- */
- if (!ep->sid)
- return 0;
+ if (ep->sid) {
+ /*
+ * Send an abort for the sequence that timed out.
+ */
+ fp = fc_frame_alloc(ep->lp, 0);
+ if (fp) {
+ ep->esb_stat |= ESB_ST_SEQ_INIT;
+ fc_fill_fc_hdr(fp, FC_RCTL_BA_ABTS, ep->did, ep->sid,
+ FC_TYPE_BLS, FC_FC_END_SEQ |
+ FC_FC_SEQ_INIT, 0);
+ error = fc_seq_send_locked(ep->lp, sp, fp);
+ } else {
+ error = -ENOBUFS;
+ }
+ } else {
+ /*
+ * If not logged into the fabric, don't send ABTS but leave
+ * sequence active until next timeout.
+ */
+ error = 0;
+ }
+ ep->esb_stat |= ESB_ST_ABNORMAL;
+ return error;
+}
- /*
- * Send an abort for the sequence that timed out.
- */
- fp = fc_frame_alloc(ep->lp, 0);
- if (fp) {
- fc_fill_fc_hdr(fp, FC_RCTL_BA_ABTS, ep->did, ep->sid,
- FC_TYPE_BLS, FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
- error = fc_seq_send(ep->lp, sp, fp);
- } else
- error = -ENOBUFS;
+/**
+ * fc_seq_exch_abort() - Abort an exchange and sequence
+ * @req_sp: The sequence to be aborted
+ * @timer_msec: The period of time to wait before aborting
+ *
+ * Generally called because of a timeout or an abort from the upper layer.
+ *
+ * Return value: 0 on success else error code
+ */
+static int fc_seq_exch_abort(const struct fc_seq *req_sp,
+ unsigned int timer_msec)
+{
+ struct fc_exch *ep;
+ int error;
+
+ ep = fc_seq_exch(req_sp);
+ spin_lock_bh(&ep->ex_lock);
+ error = fc_exch_abort_locked(ep, timer_msec);
+ spin_unlock_bh(&ep->ex_lock);
return error;
}
-EXPORT_SYMBOL(fc_seq_exch_abort);
-/*
- * Exchange timeout - handle exchange timer expiration.
- * The timer will have been cancelled before this is called.
+/**
+ * fc_invoke_resp() - invoke ep->resp()
+ *
+ * Notes:
+ * It is assumed that after initialization finished (this means the
+ * first unlock of ex_lock after fc_exch_alloc()) ep->resp and ep->arg are
+ * modified only via fc_seq_set_resp(). This guarantees that none of these
+ * two variables changes if ep->resp_active > 0.
+ *
+ * If an fc_seq_set_resp() call is busy modifying ep->resp and ep->arg when
+ * this function is invoked, the first spin_lock_bh() call in this function
+ * will wait until fc_seq_set_resp() has finished modifying these variables.
+ *
+ * Since fc_exch_done() invokes fc_seq_set_resp() it is guaranteed that that
+ * ep->resp() won't be invoked after fc_exch_done() has returned.
+ *
+ * The response handler itself may invoke fc_exch_done(), which will clear the
+ * ep->resp pointer.
+ *
+ * Return value:
+ * Returns true if and only if ep->resp has been invoked.
+ */
+static bool fc_invoke_resp(struct fc_exch *ep, struct fc_seq *sp,
+ struct fc_frame *fp)
+{
+ void (*resp)(struct fc_seq *, struct fc_frame *fp, void *arg);
+ void *arg;
+ bool res = false;
+
+ spin_lock_bh(&ep->ex_lock);
+ ep->resp_active++;
+ if (ep->resp_task != current)
+ ep->resp_task = !ep->resp_task ? current : NULL;
+ resp = ep->resp;
+ arg = ep->arg;
+ spin_unlock_bh(&ep->ex_lock);
+
+ if (resp) {
+ resp(sp, fp, arg);
+ res = true;
+ } else if (!IS_ERR(fp)) {
+ fc_frame_free(fp);
+ }
+
+ spin_lock_bh(&ep->ex_lock);
+ if (--ep->resp_active == 0)
+ ep->resp_task = NULL;
+ spin_unlock_bh(&ep->ex_lock);
+
+ if (ep->resp_active == 0)
+ wake_up(&ep->resp_wq);
+
+ return res;
+}
+
+/**
+ * fc_exch_timeout() - Handle exchange timer expiration
+ * @work: The work_struct identifying the exchange that timed out
*/
static void fc_exch_timeout(struct work_struct *work)
{
struct fc_exch *ep = container_of(work, struct fc_exch,
timeout_work.work);
struct fc_seq *sp = &ep->seq;
- void (*resp)(struct fc_seq *, struct fc_frame *fp, void *arg);
- void *arg;
u32 e_stat;
int rc = 1;
+ FC_EXCH_DBG(ep, "Exchange timed out\n");
+
spin_lock_bh(&ep->ex_lock);
if (ep->state & (FC_EX_RST_CLEANUP | FC_EX_DONE))
goto unlock;
@@ -428,21 +769,18 @@ static void fc_exch_timeout(struct work_struct *work)
e_stat = ep->esb_stat;
if (e_stat & ESB_ST_COMPLETE) {
ep->esb_stat = e_stat & ~ESB_ST_REC_QUAL;
+ spin_unlock_bh(&ep->ex_lock);
if (e_stat & ESB_ST_REC_QUAL)
fc_exch_rrq(ep);
- spin_unlock_bh(&ep->ex_lock);
goto done;
} else {
- resp = ep->resp;
- arg = ep->arg;
- ep->resp = NULL;
if (e_stat & ESB_ST_ABNORMAL)
rc = fc_exch_done_locked(ep);
spin_unlock_bh(&ep->ex_lock);
if (!rc)
- fc_exch_mgr_delete_ep(ep);
- if (resp)
- resp(sp, ERR_PTR(-FC_EX_TIMEOUT), arg);
+ fc_exch_delete(ep);
+ fc_invoke_resp(ep, sp, ERR_PTR(-FC_EX_TIMEOUT));
+ fc_seq_set_resp(sp, NULL, ep->arg);
fc_seq_exch_abort(sp, 2 * ep->r_a_tov);
goto done;
}
@@ -455,83 +793,20 @@ done:
fc_exch_release(ep);
}
-/*
- * Allocate a sequence.
- *
- * We don't support multiple originated sequences on the same exchange.
- * By implication, any previously originated sequence on this exchange
- * is complete, and we reallocate the same sequence.
- */
-static struct fc_seq *fc_seq_alloc(struct fc_exch *ep, u8 seq_id)
-{
- struct fc_seq *sp;
-
- sp = &ep->seq;
- sp->ssb_stat = 0;
- sp->cnt = 0;
- sp->id = seq_id;
- return sp;
-}
-
-/*
- * fc_em_alloc_xid - returns an xid based on request type
- * @lp : ptr to associated lport
- * @fp : ptr to the assocated frame
- *
- * check the associated fc_fsp_pkt to get scsi command type and
- * command direction to decide from which range this exch id
- * will be allocated from.
- *
- * Returns : 0 or an valid xid
- */
-static u16 fc_em_alloc_xid(struct fc_exch_mgr *mp, const struct fc_frame *fp)
-{
- u16 xid, min, max;
- u16 *plast;
- struct fc_exch *ep = NULL;
-
- if (mp->max_read) {
- if (fc_frame_is_read(fp)) {
- min = mp->min_xid;
- max = mp->max_read;
- plast = &mp->last_read;
- } else {
- min = mp->max_read + 1;
- max = mp->max_xid;
- plast = &mp->last_xid;
- }
- } else {
- min = mp->min_xid;
- max = mp->max_xid;
- plast = &mp->last_xid;
- }
- xid = *plast;
- do {
- xid = (xid == max) ? min : xid + 1;
- ep = mp->exches[xid - mp->min_xid];
- } while ((ep != NULL) && (xid != *plast));
-
- if (unlikely(ep))
- xid = 0;
- else
- *plast = xid;
-
- return xid;
-}
-
-/*
- * fc_exch_alloc - allocate an exchange.
- * @mp : ptr to the exchange manager
- * @xid: input xid
+/**
+ * fc_exch_em_alloc() - Allocate an exchange from a specified EM.
+ * @lport: The local port that the exchange is for
+ * @mp: The exchange manager that will allocate the exchange
*
- * if xid is supplied zero then assign next free exchange ID
- * from exchange manager, otherwise use supplied xid.
- * Returns with exch lock held.
+ * Returns pointer to allocated fc_exch with exch lock held.
*/
-struct fc_exch *fc_exch_alloc(struct fc_exch_mgr *mp,
- struct fc_frame *fp, u16 xid)
+static struct fc_exch *fc_exch_em_alloc(struct fc_lport *lport,
+ struct fc_exch_mgr *mp)
{
struct fc_exch *ep;
+ unsigned int cpu;
+ u16 index;
+ struct fc_exch_pool *pool;
/* allocate memory for exchange */
ep = mempool_alloc(mp->ep_pool, GFP_ATOMIC);
@@ -541,17 +816,32 @@ struct fc_exch *fc_exch_alloc(struct fc_exch_mgr *mp,
}
memset(ep, 0, sizeof(*ep));
- spin_lock_bh(&mp->em_lock);
- /* alloc xid if input xid 0 */
- if (!xid) {
- /* alloc a new xid */
- xid = fc_em_alloc_xid(mp, fp);
- if (!xid) {
- printk(KERN_ERR "fc_em_alloc_xid() failed\n");
- goto err;
- }
+ cpu = get_cpu();
+ pool = per_cpu_ptr(mp->pool, cpu);
+ spin_lock_bh(&pool->lock);
+ put_cpu();
+
+ /* peek cache of free slot */
+ if (pool->left != FC_XID_UNKNOWN) {
+ index = pool->left;
+ pool->left = FC_XID_UNKNOWN;
+ goto hit;
+ }
+ if (pool->right != FC_XID_UNKNOWN) {
+ index = pool->right;
+ pool->right = FC_XID_UNKNOWN;
+ goto hit;
}
+ index = pool->next_index;
+ /* allocate new exch from pool */
+ while (fc_exch_ptr_get(pool, index)) {
+ index = index == mp->pool_max_index ? 0 : index + 1;
+ if (index == pool->next_index)
+ goto err;
+ }
+ pool->next_index = index == mp->pool_max_index ? 0 : index + 1;
+hit:
fc_exch_hold(ep); /* hold for exch in mp */
spin_lock_init(&ep->ex_lock);
/*
@@ -561,52 +851,88 @@ struct fc_exch *fc_exch_alloc(struct fc_exch_mgr *mp,
*/
spin_lock_bh(&ep->ex_lock);
- mp->exches[xid - mp->min_xid] = ep;
- list_add_tail(&ep->ex_list, &mp->ex_list);
+ fc_exch_ptr_set(pool, index, ep);
+ list_add_tail(&ep->ex_list, &pool->ex_list);
fc_seq_alloc(ep, ep->seq_id++);
- mp->total_exches++;
- spin_unlock_bh(&mp->em_lock);
+ pool->total_exches++;
+ spin_unlock_bh(&pool->lock);
/*
* update exchange
*/
- ep->oxid = ep->xid = xid;
+ ep->oxid = ep->xid = (index << fc_cpu_order | cpu) + mp->min_xid;
ep->em = mp;
- ep->lp = mp->lp;
+ ep->pool = pool;
+ ep->lp = lport;
ep->f_ctl = FC_FC_FIRST_SEQ; /* next seq is first seq */
ep->rxid = FC_XID_UNKNOWN;
ep->class = mp->class;
+ ep->resp_active = 0;
+ init_waitqueue_head(&ep->resp_wq);
INIT_DELAYED_WORK(&ep->timeout_work, fc_exch_timeout);
out:
return ep;
err:
- spin_unlock_bh(&mp->em_lock);
+ spin_unlock_bh(&pool->lock);
atomic_inc(&mp->stats.no_free_exch_xid);
mempool_free(ep, mp->ep_pool);
return NULL;
}
-EXPORT_SYMBOL(fc_exch_alloc);
-/*
- * Lookup and hold an exchange.
+/**
+ * fc_exch_alloc() - Allocate an exchange from an EM on a
+ * local port's list of EMs.
+ * @lport: The local port that will own the exchange
+ * @fp: The FC frame that the exchange will be for
+ *
+ * This function walks the list of exchange manager(EM)
+ * anchors to select an EM for a new exchange allocation. The
+ * EM is selected when a NULL match function pointer is encountered
+ * or when a call to a match function returns true.
+ */
+static inline struct fc_exch *fc_exch_alloc(struct fc_lport *lport,
+ struct fc_frame *fp)
+{
+ struct fc_exch_mgr_anchor *ema;
+
+ list_for_each_entry(ema, &lport->ema_list, ema_list)
+ if (!ema->match || ema->match(fp))
+ return fc_exch_em_alloc(lport, ema->mp);
+ return NULL;
+}
+
+/**
+ * fc_exch_find() - Lookup and hold an exchange
+ * @mp: The exchange manager to lookup the exchange from
+ * @xid: The XID of the exchange to look up
*/
static struct fc_exch *fc_exch_find(struct fc_exch_mgr *mp, u16 xid)
{
+ struct fc_exch_pool *pool;
struct fc_exch *ep = NULL;
if ((xid >= mp->min_xid) && (xid <= mp->max_xid)) {
- spin_lock_bh(&mp->em_lock);
- ep = mp->exches[xid - mp->min_xid];
+ pool = per_cpu_ptr(mp->pool, xid & fc_cpu_mask);
+ spin_lock_bh(&pool->lock);
+ ep = fc_exch_ptr_get(pool, (xid - mp->min_xid) >> fc_cpu_order);
if (ep) {
- fc_exch_hold(ep);
WARN_ON(ep->xid != xid);
+ fc_exch_hold(ep);
}
- spin_unlock_bh(&mp->em_lock);
+ spin_unlock_bh(&pool->lock);
}
return ep;
}
-void fc_exch_done(struct fc_seq *sp)
+
+/**
+ * fc_exch_done() - Indicate that an exchange/sequence tuple is complete and
+ * the memory allocated for the related objects may be freed.
+ * @sp: The sequence that has completed
+ *
+ * Note: May sleep if invoked from outside a response handler.
+ */
+static void fc_exch_done(struct fc_seq *sp)
{
struct fc_exch *ep = fc_seq_exch(sp);
int rc;
@@ -614,22 +940,28 @@ void fc_exch_done(struct fc_seq *sp)
spin_lock_bh(&ep->ex_lock);
rc = fc_exch_done_locked(ep);
spin_unlock_bh(&ep->ex_lock);
+
+ fc_seq_set_resp(sp, NULL, ep->arg);
if (!rc)
- fc_exch_mgr_delete_ep(ep);
+ fc_exch_delete(ep);
}
-EXPORT_SYMBOL(fc_exch_done);
-/*
- * Allocate a new exchange as responder.
+/**
+ * fc_exch_resp() - Allocate a new exchange for a response frame
+ * @lport: The local port that the exchange was for
+ * @mp: The exchange manager to allocate the exchange from
+ * @fp: The response frame
+ *
* Sets the responder ID in the frame header.
*/
-static struct fc_exch *fc_exch_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
+static struct fc_exch *fc_exch_resp(struct fc_lport *lport,
+ struct fc_exch_mgr *mp,
+ struct fc_frame *fp)
{
struct fc_exch *ep;
struct fc_frame_header *fh;
- u16 rxid;
- ep = mp->lp->tt.exch_get(mp->lp, fp);
+ ep = fc_exch_alloc(lport, fp);
if (ep) {
ep->class = fc_frame_class(fp);
@@ -654,31 +986,25 @@ static struct fc_exch *fc_exch_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
if ((ntoh24(fh->fh_f_ctl) & FC_FC_SEQ_INIT) == 0)
ep->esb_stat &= ~ESB_ST_SEQ_INIT;
- /*
- * Set the responder ID in the frame header.
- * The old one should've been 0xffff.
- * If it isn't, don't assign one.
- * Incoming basic link service frames may specify
- * a referenced RX_ID.
- */
- if (fh->fh_type != FC_TYPE_BLS) {
- rxid = ntohs(fh->fh_rx_id);
- WARN_ON(rxid != FC_XID_UNKNOWN);
- fh->fh_rx_id = htons(ep->rxid);
- }
fc_exch_hold(ep); /* hold for caller */
- spin_unlock_bh(&ep->ex_lock); /* lock from exch_get */
+ spin_unlock_bh(&ep->ex_lock); /* lock from fc_exch_alloc */
}
return ep;
}
-/*
- * Find a sequence for receive where the other end is originating the sequence.
+/**
+ * fc_seq_lookup_recip() - Find a sequence where the other end
+ * originated the sequence
+ * @lport: The local port that the frame was sent to
+ * @mp: The Exchange Manager to lookup the exchange from
+ * @fp: The frame associated with the sequence we're looking for
+ *
* If fc_pf_rjt_reason is FC_RJT_NONE then this function will have a hold
* on the ep that should be released by the caller.
*/
-static enum fc_pf_rjt_reason
-fc_seq_lookup_recip(struct fc_exch_mgr *mp, struct fc_frame *fp)
+static enum fc_pf_rjt_reason fc_seq_lookup_recip(struct fc_lport *lport,
+ struct fc_exch_mgr *mp,
+ struct fc_frame *fp)
{
struct fc_frame_header *fh = fc_frame_header_get(fp);
struct fc_exch *ep = NULL;
@@ -731,7 +1057,7 @@ fc_seq_lookup_recip(struct fc_exch_mgr *mp, struct fc_frame *fp)
reject = FC_RJT_RX_ID;
goto rel;
}
- ep = fc_exch_resp(mp, fp);
+ ep = fc_exch_resp(lport, mp, fp);
if (!ep) {
reject = FC_RJT_EXCH_EST; /* XXX */
goto out;
@@ -744,30 +1070,50 @@ fc_seq_lookup_recip(struct fc_exch_mgr *mp, struct fc_frame *fp)
}
}
+ spin_lock_bh(&ep->ex_lock);
/*
* At this point, we have the exchange held.
* Find or create the sequence.
*/
if (fc_sof_is_init(fr_sof(fp))) {
- sp = fc_seq_start_next(&ep->seq);
- if (!sp) {
- reject = FC_RJT_SEQ_XS; /* exchange shortage */
- goto rel;
- }
- sp->id = fh->fh_seq_id;
+ sp = &ep->seq;
sp->ssb_stat |= SSB_ST_RESP;
+ sp->id = fh->fh_seq_id;
} else {
sp = &ep->seq;
if (sp->id != fh->fh_seq_id) {
atomic_inc(&mp->stats.seq_not_found);
- reject = FC_RJT_SEQ_ID; /* sequence/exch should exist */
- goto rel;
+ if (f_ctl & FC_FC_END_SEQ) {
+ /*
+ * Update sequence_id based on incoming last
+ * frame of sequence exchange. This is needed
+ * for FC target where DDP has been used
+ * on target where, stack is indicated only
+ * about last frame's (payload _header) header.
+ * Whereas "seq_id" which is part of
+ * frame_header is allocated by initiator
+ * which is totally different from "seq_id"
+ * allocated when XFER_RDY was sent by target.
+ * To avoid false -ve which results into not
+ * sending RSP, hence write request on other
+ * end never finishes.
+ */
+ sp->ssb_stat |= SSB_ST_RESP;
+ sp->id = fh->fh_seq_id;
+ } else {
+ spin_unlock_bh(&ep->ex_lock);
+
+ /* sequence/exch should exist */
+ reject = FC_RJT_SEQ_ID;
+ goto rel;
+ }
}
}
WARN_ON(ep != fc_seq_exch(sp));
if (f_ctl & FC_FC_SEQ_INIT)
ep->esb_stat |= ESB_ST_SEQ_INIT;
+ spin_unlock_bh(&ep->ex_lock);
fr_seq(fp) = sp;
out:
@@ -778,10 +1124,12 @@ rel:
return reject;
}
-/*
- * Find the sequence for a frame being received.
- * We originated the sequence, so it should be found.
- * We may or may not have originated the exchange.
+/**
+ * fc_seq_lookup_orig() - Find a sequence where this end
+ * originated the sequence
+ * @mp: The Exchange Manager to lookup the exchange from
+ * @fp: The frame associated with the sequence we're looking for
+ *
* Does not hold the sequence for the caller.
*/
static struct fc_seq *fc_seq_lookup_orig(struct fc_exch_mgr *mp,
@@ -813,8 +1161,12 @@ static struct fc_seq *fc_seq_lookup_orig(struct fc_exch_mgr *mp,
return sp;
}
-/*
- * Set addresses for an exchange.
+/**
+ * fc_exch_set_addr() - Set the source and destination IDs for an exchange
+ * @ep: The exchange to set the addresses for
+ * @orig_id: The originator's ID
+ * @resp_id: The responder's ID
+ *
* Note this must be done before the first sequence of the exchange is sent.
*/
static void fc_exch_set_addr(struct fc_exch *ep,
@@ -830,99 +1182,42 @@ static void fc_exch_set_addr(struct fc_exch *ep,
}
}
-static struct fc_seq *fc_seq_start_next_locked(struct fc_seq *sp)
-{
- struct fc_exch *ep = fc_seq_exch(sp);
-
- sp = fc_seq_alloc(ep, ep->seq_id++);
- FC_DEBUG_EXCH("exch %4x f_ctl %6x seq %2x\n",
- ep->xid, ep->f_ctl, sp->id);
- return sp;
-}
-/*
- * Allocate a new sequence on the same exchange as the supplied sequence.
- * This will never return NULL.
+/**
+ * fc_seq_els_rsp_send() - Send an ELS response using information from
+ * the existing sequence/exchange.
+ * @fp: The received frame
+ * @els_cmd: The ELS command to be sent
+ * @els_data: The ELS data to be sent
+ *
+ * The received frame is not freed.
*/
-struct fc_seq *fc_seq_start_next(struct fc_seq *sp)
-{
- struct fc_exch *ep = fc_seq_exch(sp);
-
- spin_lock_bh(&ep->ex_lock);
- WARN_ON((ep->esb_stat & ESB_ST_COMPLETE) != 0);
- sp = fc_seq_start_next_locked(sp);
- spin_unlock_bh(&ep->ex_lock);
-
- return sp;
-}
-EXPORT_SYMBOL(fc_seq_start_next);
-
-int fc_seq_send(struct fc_lport *lp, struct fc_seq *sp, struct fc_frame *fp)
-{
- struct fc_exch *ep;
- struct fc_frame_header *fh = fc_frame_header_get(fp);
- int error;
- u32 f_ctl;
-
- ep = fc_seq_exch(sp);
- WARN_ON((ep->esb_stat & ESB_ST_SEQ_INIT) != ESB_ST_SEQ_INIT);
-
- f_ctl = ntoh24(fh->fh_f_ctl);
- fc_exch_setup_hdr(ep, fp, f_ctl);
-
- /*
- * update sequence count if this frame is carrying
- * multiple FC frames when sequence offload is enabled
- * by LLD.
- */
- if (fr_max_payload(fp))
- sp->cnt += DIV_ROUND_UP((fr_len(fp) - sizeof(*fh)),
- fr_max_payload(fp));
- else
- sp->cnt++;
-
- /*
- * Send the frame.
- */
- error = lp->tt.frame_send(lp, fp);
-
- /*
- * Update the exchange and sequence flags,
- * assuming all frames for the sequence have been sent.
- * We can only be called to send once for each sequence.
- */
- 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))
- ep->esb_stat &= ~ESB_ST_SEQ_INIT;
- spin_unlock_bh(&ep->ex_lock);
- return error;
-}
-EXPORT_SYMBOL(fc_seq_send);
-
-void fc_seq_els_rsp_send(struct fc_seq *sp, enum fc_els_cmd els_cmd,
- struct fc_seq_els_data *els_data)
+static void fc_seq_els_rsp_send(struct fc_frame *fp, enum fc_els_cmd els_cmd,
+ struct fc_seq_els_data *els_data)
{
switch (els_cmd) {
case ELS_LS_RJT:
- fc_seq_ls_rjt(sp, els_data->reason, els_data->explan);
+ fc_seq_ls_rjt(fp, els_data->reason, els_data->explan);
break;
case ELS_LS_ACC:
- fc_seq_ls_acc(sp);
+ fc_seq_ls_acc(fp);
break;
case ELS_RRQ:
- fc_exch_els_rrq(sp, els_data->fp);
+ fc_exch_els_rrq(fp);
break;
case ELS_REC:
- fc_exch_els_rec(sp, els_data->fp);
+ fc_exch_els_rec(fp);
break;
default:
- FC_DBG("Invalid ELS CMD:%x\n", els_cmd);
+ FC_LPORT_DBG(fr_dev(fp), "Invalid ELS CMD:%x\n", els_cmd);
}
}
-EXPORT_SYMBOL(fc_seq_els_rsp_send);
-/*
- * Send a sequence, which is also the last sequence in the exchange.
+/**
+ * fc_seq_send_last() - Send a sequence that is the last in the exchange
+ * @sp: The sequence that is to be sent
+ * @fp: The frame that will be sent on the sequence
+ * @rctl: The R_CTL information to be sent
+ * @fh_type: The frame header type
*/
static void fc_seq_send_last(struct fc_seq *sp, struct fc_frame *fp,
enum fc_rctl rctl, enum fc_fh_type fh_type)
@@ -933,12 +1228,15 @@ static void fc_seq_send_last(struct fc_seq *sp, struct fc_frame *fp,
f_ctl = FC_FC_LAST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT;
f_ctl |= ep->f_ctl;
fc_fill_fc_hdr(fp, rctl, ep->did, ep->sid, fh_type, f_ctl, 0);
- fc_seq_send(ep->lp, sp, fp);
+ fc_seq_send_locked(ep->lp, sp, fp);
}
-/*
+/**
+ * fc_seq_send_ack() - Send an acknowledgement that we've received a frame
+ * @sp: The sequence to send the ACK on
+ * @rx_fp: The received frame that is being acknoledged
+ *
* Send ACK_1 (or equiv.) indicating we received something.
- * The frame we're acking is supplied.
*/
static void fc_seq_send_ack(struct fc_seq *sp, const struct fc_frame *rx_fp)
{
@@ -946,14 +1244,14 @@ static void fc_seq_send_ack(struct fc_seq *sp, const struct fc_frame *rx_fp)
struct fc_frame_header *rx_fh;
struct fc_frame_header *fh;
struct fc_exch *ep = fc_seq_exch(sp);
- struct fc_lport *lp = ep->lp;
+ struct fc_lport *lport = ep->lp;
unsigned int f_ctl;
/*
* Don't send ACKs for class 3.
*/
if (fc_sof_needs_ack(fr_sof(rx_fp))) {
- fp = fc_frame_alloc(lp, 0);
+ fp = fc_frame_alloc(lport, 0);
if (!fp)
return;
@@ -988,27 +1286,31 @@ static void fc_seq_send_ack(struct fc_seq *sp, const struct fc_frame *rx_fp)
else
fr_eof(fp) = FC_EOF_N;
- (void) lp->tt.frame_send(lp, fp);
+ lport->tt.frame_send(lport, fp);
}
}
-/*
- * Send BLS Reject.
+/**
+ * fc_exch_send_ba_rjt() - Send BLS Reject
+ * @rx_fp: The frame being rejected
+ * @reason: The reason the frame is being rejected
+ * @explan: The explanation for the rejection
+ *
* This is for rejecting BA_ABTS only.
*/
-static void
-fc_exch_send_ba_rjt(struct fc_frame *rx_fp, enum fc_ba_rjt_reason reason,
- enum fc_ba_rjt_explan explan)
+static void fc_exch_send_ba_rjt(struct fc_frame *rx_fp,
+ enum fc_ba_rjt_reason reason,
+ enum fc_ba_rjt_explan explan)
{
struct fc_frame *fp;
struct fc_frame_header *rx_fh;
struct fc_frame_header *fh;
struct fc_ba_rjt *rp;
- struct fc_lport *lp;
+ struct fc_lport *lport;
unsigned int f_ctl;
- lp = fr_dev(rx_fp);
- fp = fc_frame_alloc(lp, sizeof(*rp));
+ lport = fr_dev(rx_fp);
+ fp = fc_frame_alloc(lport, sizeof(*rp));
if (!fp)
return;
fh = fc_frame_header_get(fp);
@@ -1025,8 +1327,8 @@ fc_exch_send_ba_rjt(struct fc_frame *rx_fp, enum fc_ba_rjt_reason reason,
*/
memcpy(fh->fh_s_id, rx_fh->fh_d_id, 3);
memcpy(fh->fh_d_id, rx_fh->fh_s_id, 3);
- fh->fh_ox_id = rx_fh->fh_rx_id;
- fh->fh_rx_id = rx_fh->fh_ox_id;
+ fh->fh_ox_id = rx_fh->fh_ox_id;
+ fh->fh_rx_id = rx_fh->fh_rx_id;
fh->fh_seq_cnt = rx_fh->fh_seq_cnt;
fh->fh_r_ctl = FC_RCTL_BA_RJT;
fh->fh_type = FC_TYPE_BLS;
@@ -1053,13 +1355,17 @@ fc_exch_send_ba_rjt(struct fc_frame *rx_fp, enum fc_ba_rjt_reason reason,
if (fc_sof_needs_ack(fr_sof(fp)))
fr_eof(fp) = FC_EOF_N;
- (void) lp->tt.frame_send(lp, fp);
+ lport->tt.frame_send(lport, fp);
}
-/*
- * Handle an incoming ABTS. This would be for target mode usually,
- * but could be due to lost FCP transfer ready, confirm or RRQ.
- * We always handle this as an exchange abort, ignoring the parameter.
+/**
+ * fc_exch_recv_abts() - Handle an incoming ABTS
+ * @ep: The exchange the abort was on
+ * @rx_fp: The ABTS frame
+ *
+ * This would be for target mode usually, but could be due to lost
+ * FCP transfer ready, confirm or RRQ. We always handle this as an
+ * exchange abort, ignoring the parameter.
*/
static void fc_exch_recv_abts(struct fc_exch *ep, struct fc_frame *rx_fp)
{
@@ -1070,21 +1376,23 @@ static void fc_exch_recv_abts(struct fc_exch *ep, struct fc_frame *rx_fp)
if (!ep)
goto reject;
+
+ fp = fc_frame_alloc(ep->lp, sizeof(*ap));
+ if (!fp)
+ goto free;
+
spin_lock_bh(&ep->ex_lock);
if (ep->esb_stat & ESB_ST_COMPLETE) {
spin_unlock_bh(&ep->ex_lock);
+
+ fc_frame_free(fp);
goto reject;
}
- if (!(ep->esb_stat & ESB_ST_REC_QUAL))
+ if (!(ep->esb_stat & ESB_ST_REC_QUAL)) {
+ ep->esb_stat |= ESB_ST_REC_QUAL;
fc_exch_hold(ep); /* hold for REC_QUAL */
- ep->esb_stat |= ESB_ST_ABNORMAL | ESB_ST_REC_QUAL;
- fc_exch_timer_set_locked(ep, ep->r_a_tov);
-
- fp = fc_frame_alloc(ep->lp, sizeof(*ap));
- if (!fp) {
- spin_unlock_bh(&ep->ex_lock);
- goto free;
}
+ fc_exch_timer_set_locked(ep, ep->r_a_tov);
fh = fc_frame_header_get(fp);
ap = fc_frame_payload_get(fp, sizeof(*ap));
memset(ap, 0, sizeof(*ap));
@@ -1096,41 +1404,95 @@ static void fc_exch_recv_abts(struct fc_exch *ep, struct fc_frame *rx_fp)
ap->ba_high_seq_cnt = fh->fh_seq_cnt;
ap->ba_low_seq_cnt = htons(sp->cnt);
}
- sp = fc_seq_start_next(sp);
- spin_unlock_bh(&ep->ex_lock);
+ sp = fc_seq_start_next_locked(sp);
fc_seq_send_last(sp, fp, FC_RCTL_BA_ACC, FC_TYPE_BLS);
+ ep->esb_stat |= ESB_ST_ABNORMAL;
+ spin_unlock_bh(&ep->ex_lock);
+
+free:
fc_frame_free(rx_fp);
return;
reject:
fc_exch_send_ba_rjt(rx_fp, FC_BA_RJT_UNABLE, FC_BA_RJT_INV_XID);
-free:
- fc_frame_free(rx_fp);
+ goto free;
}
-/*
- * Handle receive where the other end is originating the sequence.
+/**
+ * fc_seq_assign() - Assign exchange and sequence for incoming request
+ * @lport: The local port that received the request
+ * @fp: The request frame
+ *
+ * On success, the sequence pointer will be returned and also in fr_seq(@fp).
+ * A reference will be held on the exchange/sequence for the caller, which
+ * must call fc_seq_release().
+ */
+static struct fc_seq *fc_seq_assign(struct fc_lport *lport, struct fc_frame *fp)
+{
+ struct fc_exch_mgr_anchor *ema;
+
+ WARN_ON(lport != fr_dev(fp));
+ WARN_ON(fr_seq(fp));
+ fr_seq(fp) = NULL;
+
+ list_for_each_entry(ema, &lport->ema_list, ema_list)
+ if ((!ema->match || ema->match(fp)) &&
+ fc_seq_lookup_recip(lport, ema->mp, fp) == FC_RJT_NONE)
+ break;
+ return fr_seq(fp);
+}
+
+/**
+ * fc_seq_release() - Release the hold
+ * @sp: The sequence.
+ */
+static void fc_seq_release(struct fc_seq *sp)
+{
+ fc_exch_release(fc_seq_exch(sp));
+}
+
+/**
+ * fc_exch_recv_req() - Handler for an incoming request
+ * @lport: The local port that received the request
+ * @mp: The EM that the exchange is on
+ * @fp: The request frame
+ *
+ * This is used when the other end is originating the exchange
+ * and the sequence.
*/
-static void fc_exch_recv_req(struct fc_lport *lp, struct fc_exch_mgr *mp,
+static void fc_exch_recv_req(struct fc_lport *lport, struct fc_exch_mgr *mp,
struct fc_frame *fp)
{
struct fc_frame_header *fh = fc_frame_header_get(fp);
struct fc_seq *sp = NULL;
struct fc_exch *ep = NULL;
- enum fc_sof sof;
- enum fc_eof eof;
- u32 f_ctl;
enum fc_pf_rjt_reason reject;
- fr_seq(fp) = NULL;
- reject = fc_seq_lookup_recip(mp, fp);
+ /* We can have the wrong fc_lport at this point with NPIV, which is a
+ * problem now that we know a new exchange needs to be allocated
+ */
+ lport = fc_vport_id_lookup(lport, ntoh24(fh->fh_d_id));
+ if (!lport) {
+ fc_frame_free(fp);
+ return;
+ }
+ fr_dev(fp) = lport;
+
+ BUG_ON(fr_seq(fp)); /* XXX remove later */
+
+ /*
+ * If the RX_ID is 0xffff, don't allocate an exchange.
+ * The upper-level protocol may request one later, if needed.
+ */
+ if (fh->fh_rx_id == htons(FC_XID_UNKNOWN))
+ return lport->tt.lport_recv(lport, fp);
+
+ reject = fc_seq_lookup_recip(lport, mp, fp);
if (reject == FC_RJT_NONE) {
sp = fr_seq(fp); /* sequence will be held */
ep = fc_seq_exch(sp);
- sof = fr_sof(fp);
- eof = fr_eof(fp);
- f_ctl = ntoh24(fh->fh_f_ctl);
fc_seq_send_ack(sp, fp);
+ ep->encaps = fr_encaps(fp);
/*
* Call the receive function.
@@ -1143,20 +1505,22 @@ static void fc_exch_recv_req(struct fc_lport *lp, struct fc_exch_mgr *mp,
* If new exch resp handler is valid then call that
* first.
*/
- if (ep->resp)
- ep->resp(sp, fp, ep->arg);
- else
- lp->tt.lport_recv(lp, sp, fp);
+ if (!fc_invoke_resp(ep, sp, fp))
+ lport->tt.lport_recv(lport, fp);
fc_exch_release(ep); /* release from lookup */
} else {
- FC_DEBUG_EXCH("exch/seq lookup failed: reject %x\n", reject);
+ FC_LPORT_DBG(lport, "exch/seq lookup failed: reject %x\n",
+ reject);
fc_frame_free(fp);
}
}
-/*
- * Handle receive where the other end is originating the sequence in
- * response to our exchange.
+/**
+ * fc_exch_recv_seq_resp() - Handler for an incoming response where the other
+ * end is the originator of the sequence that is a
+ * response to our initial exchange
+ * @mp: The EM that the exchange is on
+ * @fp: The response frame
*/
static void fc_exch_recv_seq_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
{
@@ -1165,8 +1529,6 @@ static void fc_exch_recv_seq_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
struct fc_exch *ep;
enum fc_sof sof;
u32 f_ctl;
- void (*resp)(struct fc_seq *, struct fc_frame *fp, void *arg);
- void *ex_resp_arg;
int rc;
ep = fc_exch_find(mp, ntohs(fh->fh_ox_id));
@@ -1174,6 +1536,10 @@ static void fc_exch_recv_seq_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
atomic_inc(&mp->stats.xid_not_found);
goto out;
}
+ if (ep->esb_stat & ESB_ST_COMPLETE) {
+ atomic_inc(&mp->stats.xid_not_found);
+ goto rel;
+ }
if (ep->rxid == FC_XID_UNKNOWN)
ep->rxid = ntohs(fh->fh_rx_id);
if (ep->sid != 0 && ep->sid != ntoh24(fh->fh_d_id)) {
@@ -1186,26 +1552,25 @@ static void fc_exch_recv_seq_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
goto rel;
}
sof = fr_sof(fp);
+ sp = &ep->seq;
if (fc_sof_is_init(sof)) {
- sp = fc_seq_start_next(&ep->seq);
- sp->id = fh->fh_seq_id;
sp->ssb_stat |= SSB_ST_RESP;
- } else {
- sp = &ep->seq;
- if (sp->id != fh->fh_seq_id) {
- atomic_inc(&mp->stats.seq_not_found);
- goto rel;
- }
+ sp->id = fh->fh_seq_id;
+ } else if (sp->id != fh->fh_seq_id) {
+ atomic_inc(&mp->stats.seq_not_found);
+ goto rel;
}
+
f_ctl = ntoh24(fh->fh_f_ctl);
fr_seq(fp) = sp;
+
+ spin_lock_bh(&ep->ex_lock);
if (f_ctl & FC_FC_SEQ_INIT)
ep->esb_stat |= ESB_ST_SEQ_INIT;
+ spin_unlock_bh(&ep->ex_lock);
if (fc_sof_needs_ack(sof))
fc_seq_send_ack(sp, fp);
- resp = ep->resp;
- ex_resp_arg = ep->arg;
if (fh->fh_type != FC_TYPE_FCP && fr_eof(fp) == FC_EOF_T &&
(f_ctl & (FC_FC_LAST_SEQ | FC_FC_END_SEQ)) ==
@@ -1215,7 +1580,7 @@ static void fc_exch_recv_seq_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
WARN_ON(fc_seq_exch(sp) != ep);
spin_unlock_bh(&ep->ex_lock);
if (!rc)
- fc_exch_mgr_delete_ep(ep);
+ fc_exch_delete(ep);
}
/*
@@ -1231,10 +1596,8 @@ static void fc_exch_recv_seq_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
* If new exch resp handler is valid then call that
* first.
*/
- if (resp)
- resp(sp, fp, ex_resp_arg);
- else
- fc_frame_free(fp);
+ fc_invoke_resp(ep, sp, fp);
+
fc_exch_release(ep);
return;
rel:
@@ -1243,32 +1606,36 @@ out:
fc_frame_free(fp);
}
-/*
- * Handle receive for a sequence where other end is responding to our sequence.
+/**
+ * fc_exch_recv_resp() - Handler for a sequence where other end is
+ * responding to our sequence
+ * @mp: The EM that the exchange is on
+ * @fp: The response frame
*/
static void fc_exch_recv_resp(struct fc_exch_mgr *mp, struct fc_frame *fp)
{
struct fc_seq *sp;
sp = fc_seq_lookup_orig(mp, fp); /* doesn't hold sequence */
- if (!sp) {
+
+ if (!sp)
atomic_inc(&mp->stats.xid_not_found);
- FC_DEBUG_EXCH("seq lookup failed\n");
- } else {
+ else
atomic_inc(&mp->stats.non_bls_resp);
- FC_DEBUG_EXCH("non-BLS response to sequence");
- }
+
fc_frame_free(fp);
}
-/*
- * Handle the response to an ABTS for exchange or sequence.
- * This can be BA_ACC or BA_RJT.
+/**
+ * fc_exch_abts_resp() - Handler for a response to an ABT
+ * @ep: The exchange that the frame is on
+ * @fp: The response frame
+ *
+ * This response would be to an ABTS cancelling an exchange or sequence.
+ * The response can be either BA_ACC or BA_RJT
*/
static void fc_exch_abts_resp(struct fc_exch *ep, struct fc_frame *fp)
{
- void (*resp)(struct fc_seq *, struct fc_frame *fp, void *arg);
- void *ex_resp_arg;
struct fc_frame_header *fh;
struct fc_ba_acc *ap;
struct fc_seq *sp;
@@ -1277,11 +1644,13 @@ static void fc_exch_abts_resp(struct fc_exch *ep, struct fc_frame *fp)
int rc = 1, has_rec = 0;
fh = fc_frame_header_get(fp);
- FC_DEBUG_EXCH("exch: BLS rctl %x - %s\n",
- fh->fh_r_ctl, fc_exch_rctl_name(fh->fh_r_ctl));
+ FC_EXCH_DBG(ep, "exch: BLS rctl %x - %s\n", fh->fh_r_ctl,
+ fc_exch_rctl_name(fh->fh_r_ctl));
- if (cancel_delayed_work_sync(&ep->timeout_work))
+ if (cancel_delayed_work_sync(&ep->timeout_work)) {
+ FC_EXCH_DBG(ep, "Exchange timer canceled due to ABTS response\n");
fc_exch_release(ep); /* release from pending timer hold */
+ }
spin_lock_bh(&ep->ex_lock);
switch (fh->fh_r_ctl) {
@@ -1311,9 +1680,6 @@ static void fc_exch_abts_resp(struct fc_exch *ep, struct fc_frame *fp)
break;
}
- resp = ep->resp;
- ex_resp_arg = ep->arg;
-
/* do we need to do some other checks here. Can we reuse more of
* fc_exch_recv_seq_resp
*/
@@ -1325,22 +1691,22 @@ static void fc_exch_abts_resp(struct fc_exch *ep, struct fc_frame *fp)
ntoh24(fh->fh_f_ctl) & FC_FC_LAST_SEQ)
rc = fc_exch_done_locked(ep);
spin_unlock_bh(&ep->ex_lock);
- if (!rc)
- fc_exch_mgr_delete_ep(ep);
-
- if (resp)
- resp(sp, fp, ex_resp_arg);
- else
- fc_frame_free(fp);
+ fc_exch_hold(ep);
+ if (!rc)
+ fc_exch_delete(ep);
+ fc_invoke_resp(ep, sp, fp);
if (has_rec)
fc_exch_timer_set(ep, ep->r_a_tov);
-
+ fc_exch_release(ep);
}
-/*
- * Receive BLS sequence.
- * This is always a sequence initiated by the remote side.
+/**
+ * fc_exch_recv_bls() - Handler for a BLS sequence
+ * @mp: The EM that the exchange is on
+ * @fp: The request frame
+ *
+ * The BLS frame is always a sequence initiated by the remote side.
* We may be either the originator or recipient of the exchange.
*/
static void fc_exch_recv_bls(struct fc_exch_mgr *mp, struct fc_frame *fp)
@@ -1370,9 +1736,10 @@ static void fc_exch_recv_bls(struct fc_exch_mgr *mp, struct fc_frame *fp)
case FC_RCTL_ACK_0:
break;
default:
- FC_DEBUG_EXCH("BLS rctl %x - %s received",
- fh->fh_r_ctl,
- fc_exch_rctl_name(fh->fh_r_ctl));
+ if (ep)
+ FC_EXCH_DBG(ep, "BLS rctl %x - %s received\n",
+ fh->fh_r_ctl,
+ fc_exch_rctl_name(fh->fh_r_ctl));
break;
}
fc_frame_free(fp);
@@ -1397,127 +1764,190 @@ static void fc_exch_recv_bls(struct fc_exch_mgr *mp, struct fc_frame *fp)
fc_exch_release(ep); /* release hold taken by fc_exch_find */
}
-/*
- * Accept sequence with LS_ACC.
+/**
+ * fc_seq_ls_acc() - Accept sequence with LS_ACC
+ * @rx_fp: The received frame, not freed here.
+ *
* If this fails due to allocation or transmit congestion, assume the
* originator will repeat the sequence.
*/
-static void fc_seq_ls_acc(struct fc_seq *req_sp)
+static void fc_seq_ls_acc(struct fc_frame *rx_fp)
{
- struct fc_seq *sp;
+ struct fc_lport *lport;
struct fc_els_ls_acc *acc;
struct fc_frame *fp;
- sp = fc_seq_start_next(req_sp);
- fp = fc_frame_alloc(fc_seq_exch(sp)->lp, sizeof(*acc));
- if (fp) {
- acc = fc_frame_payload_get(fp, sizeof(*acc));
- memset(acc, 0, sizeof(*acc));
- acc->la_cmd = ELS_LS_ACC;
- fc_seq_send_last(sp, fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
- }
+ lport = fr_dev(rx_fp);
+ 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;
+ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
}
-/*
- * Reject sequence with ELS LS_RJT.
+/**
+ * fc_seq_ls_rjt() - Reject a sequence with ELS LS_RJT
+ * @rx_fp: The received frame, not freed here.
+ * @reason: The reason the sequence is being rejected
+ * @explan: The explanation for the rejection
+ *
* If this fails due to allocation or transmit congestion, assume the
* originator will repeat the sequence.
*/
-static void fc_seq_ls_rjt(struct fc_seq *req_sp, enum fc_els_rjt_reason reason,
+static void fc_seq_ls_rjt(struct fc_frame *rx_fp, enum fc_els_rjt_reason reason,
enum fc_els_rjt_explan explan)
{
- struct fc_seq *sp;
+ struct fc_lport *lport;
struct fc_els_ls_rjt *rjt;
struct fc_frame *fp;
- sp = fc_seq_start_next(req_sp);
- fp = fc_frame_alloc(fc_seq_exch(sp)->lp, sizeof(*rjt));
- if (fp) {
- 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;
- fc_seq_send_last(sp, fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
- }
+ lport = fr_dev(rx_fp);
+ 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;
+ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
}
+/**
+ * fc_exch_reset() - Reset an exchange
+ * @ep: The exchange to be reset
+ *
+ * Note: May sleep if invoked from outside a response handler.
+ */
static void fc_exch_reset(struct fc_exch *ep)
{
struct fc_seq *sp;
- void (*resp)(struct fc_seq *, struct fc_frame *, void *);
- void *arg;
int rc = 1;
spin_lock_bh(&ep->ex_lock);
+ fc_exch_abort_locked(ep, 0);
ep->state |= FC_EX_RST_CLEANUP;
- /*
- * we really want to call del_timer_sync, but cannot due
- * to the lport calling with the lport lock held (some resp
- * functions can also grab the lport lock which could cause
- * a deadlock).
- */
- if (cancel_delayed_work(&ep->timeout_work))
- atomic_dec(&ep->ex_refcnt); /* drop hold for timer */
- resp = ep->resp;
- ep->resp = NULL;
+ fc_exch_timer_cancel(ep);
if (ep->esb_stat & ESB_ST_REC_QUAL)
atomic_dec(&ep->ex_refcnt); /* drop hold for rec_qual */
ep->esb_stat &= ~ESB_ST_REC_QUAL;
- arg = ep->arg;
sp = &ep->seq;
rc = fc_exch_done_locked(ep);
spin_unlock_bh(&ep->ex_lock);
+
+ fc_exch_hold(ep);
+
if (!rc)
- fc_exch_mgr_delete_ep(ep);
+ fc_exch_delete(ep);
- if (resp)
- resp(sp, ERR_PTR(-FC_EX_CLOSED), arg);
+ fc_invoke_resp(ep, sp, ERR_PTR(-FC_EX_CLOSED));
+ fc_seq_set_resp(sp, NULL, ep->arg);
+ fc_exch_release(ep);
}
-/*
- * Reset an exchange manager, releasing all sequences and exchanges.
- * If sid is non-zero, reset only exchanges we source from that FID.
- * If did is non-zero, reset only exchanges destined to that FID.
+/**
+ * fc_exch_pool_reset() - Reset a per cpu exchange pool
+ * @lport: The local port that the exchange pool is on
+ * @pool: The exchange pool to be reset
+ * @sid: The source ID
+ * @did: The destination ID
+ *
+ * Resets a per cpu exches pool, releasing all of its sequences
+ * and exchanges. If sid is non-zero then reset only exchanges
+ * we sourced from the local port's FID. If did is non-zero then
+ * only reset exchanges destined for the local port's FID.
*/
-void fc_exch_mgr_reset(struct fc_exch_mgr *mp, u32 sid, u32 did)
+static void fc_exch_pool_reset(struct fc_lport *lport,
+ struct fc_exch_pool *pool,
+ u32 sid, u32 did)
{
struct fc_exch *ep;
struct fc_exch *next;
- spin_lock_bh(&mp->em_lock);
+ spin_lock_bh(&pool->lock);
restart:
- list_for_each_entry_safe(ep, next, &mp->ex_list, ex_list) {
- if ((sid == 0 || sid == ep->sid) &&
+ list_for_each_entry_safe(ep, next, &pool->ex_list, ex_list) {
+ if ((lport == ep->lp) &&
+ (sid == 0 || sid == ep->sid) &&
(did == 0 || did == ep->did)) {
fc_exch_hold(ep);
- spin_unlock_bh(&mp->em_lock);
+ spin_unlock_bh(&pool->lock);
fc_exch_reset(ep);
fc_exch_release(ep);
- spin_lock_bh(&mp->em_lock);
+ spin_lock_bh(&pool->lock);
/*
- * must restart loop incase while lock was down
- * multiple eps were released.
+ * must restart loop incase while lock
+ * was down multiple eps were released.
*/
goto restart;
}
}
- spin_unlock_bh(&mp->em_lock);
+ pool->next_index = 0;
+ pool->left = FC_XID_UNKNOWN;
+ pool->right = FC_XID_UNKNOWN;
+ spin_unlock_bh(&pool->lock);
+}
+
+/**
+ * fc_exch_mgr_reset() - Reset all EMs of a local port
+ * @lport: The local port whose EMs are to be reset
+ * @sid: The source ID
+ * @did: The destination ID
+ *
+ * Reset all EMs associated with a given local port. Release all
+ * sequences and exchanges. If sid is non-zero then reset only the
+ * exchanges sent from the local port's FID. If did is non-zero then
+ * reset only exchanges destined for the local port's FID.
+ */
+void fc_exch_mgr_reset(struct fc_lport *lport, u32 sid, u32 did)
+{
+ struct fc_exch_mgr_anchor *ema;
+ unsigned int cpu;
+
+ list_for_each_entry(ema, &lport->ema_list, ema_list) {
+ for_each_possible_cpu(cpu)
+ fc_exch_pool_reset(lport,
+ per_cpu_ptr(ema->mp->pool, cpu),
+ sid, did);
+ }
}
EXPORT_SYMBOL(fc_exch_mgr_reset);
-/*
- * Handle incoming ELS REC - Read Exchange Concise.
+/**
+ * fc_exch_lookup() - find an exchange
+ * @lport: The local port
+ * @xid: The exchange ID
+ *
+ * Returns exchange pointer with hold for caller, or NULL if not found.
+ */
+static struct fc_exch *fc_exch_lookup(struct fc_lport *lport, u32 xid)
+{
+ struct fc_exch_mgr_anchor *ema;
+
+ list_for_each_entry(ema, &lport->ema_list, ema_list)
+ if (ema->mp->min_xid <= xid && xid <= ema->mp->max_xid)
+ return fc_exch_find(ema->mp, xid);
+ return NULL;
+}
+
+/**
+ * fc_exch_els_rec() - Handler for ELS REC (Read Exchange Concise) requests
+ * @rfp: The REC frame, not freed here.
+ *
* Note that the requesting port may be different than the S_ID in the request.
*/
-static void fc_exch_els_rec(struct fc_seq *sp, struct fc_frame *rfp)
+static void fc_exch_els_rec(struct fc_frame *rfp)
{
+ struct fc_lport *lport;
struct fc_frame *fp;
struct fc_exch *ep;
- struct fc_exch_mgr *em;
struct fc_els_rec *rp;
struct fc_els_rec_acc *acc;
enum fc_els_rjt_reason reason = ELS_RJT_LOGIC;
@@ -1526,6 +1956,7 @@ static void fc_exch_els_rec(struct fc_seq *sp, struct fc_frame *rfp)
u16 rxid;
u16 oxid;
+ lport = fr_dev(rfp);
rp = fc_frame_payload_get(rfp, sizeof(*rp));
explan = ELS_EXPL_INV_LEN;
if (!rp)
@@ -1534,36 +1965,19 @@ static void fc_exch_els_rec(struct fc_seq *sp, struct fc_frame *rfp)
rxid = ntohs(rp->rec_rx_id);
oxid = ntohs(rp->rec_ox_id);
- /*
- * Currently it's hard to find the local S_ID from the exchange
- * manager. This will eventually be fixed, but for now it's easier
- * to lookup the subject exchange twice, once as if we were
- * the initiator, and then again if we weren't.
- */
- em = fc_seq_exch(sp)->em;
- ep = fc_exch_find(em, oxid);
+ ep = fc_exch_lookup(lport,
+ sid == fc_host_port_id(lport->host) ? oxid : rxid);
explan = ELS_EXPL_OXID_RXID;
- if (ep && ep->oid == sid) {
- if (ep->rxid != FC_XID_UNKNOWN &&
- rxid != FC_XID_UNKNOWN &&
- ep->rxid != rxid)
- goto rel;
- } else {
- if (ep)
- fc_exch_release(ep);
- ep = NULL;
- if (rxid != FC_XID_UNKNOWN)
- ep = fc_exch_find(em, rxid);
- if (!ep)
- goto reject;
- }
-
- fp = fc_frame_alloc(fc_seq_exch(sp)->lp, sizeof(*acc));
- if (!fp) {
- fc_exch_done(sp);
+ if (!ep)
+ goto reject;
+ if (ep->oid != sid || oxid != ep->oxid)
+ goto rel;
+ if (rxid != FC_XID_UNKNOWN && rxid != ep->rxid)
+ goto rel;
+ fp = fc_frame_alloc(lport, sizeof(*acc));
+ if (!fp)
goto out;
- }
- sp = fc_seq_start_next(sp);
+
acc = fc_frame_payload_get(fp, sizeof(*acc));
memset(acc, 0, sizeof(*acc));
acc->reca_cmd = ELS_LS_ACC;
@@ -1578,24 +1992,23 @@ static void fc_exch_els_rec(struct fc_seq *sp, struct fc_frame *rfp)
acc->reca_e_stat = htonl(ep->esb_stat & (ESB_ST_RESP |
ESB_ST_SEQ_INIT |
ESB_ST_COMPLETE));
- sp = fc_seq_start_next(sp);
- fc_seq_send_last(sp, fp, FC_RCTL_ELS_REP, FC_TYPE_ELS);
+ fc_fill_reply_hdr(fp, rfp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
out:
fc_exch_release(ep);
- fc_frame_free(rfp);
return;
rel:
fc_exch_release(ep);
reject:
- fc_seq_ls_rjt(sp, reason, explan);
- fc_frame_free(rfp);
+ fc_seq_ls_rjt(rfp, reason, explan);
}
-/*
- * Handle response from RRQ.
- * Not much to do here, really.
- * Should report errors.
+/**
+ * fc_exch_rrq_resp() - Handler for RRQ responses
+ * @sp: The sequence that the RRQ is on
+ * @fp: The RRQ frame
+ * @arg: The exchange that the RRQ is on
*
* TODO: fix error handler.
*/
@@ -1607,9 +2020,10 @@ static void fc_exch_rrq_resp(struct fc_seq *sp, struct fc_frame *fp, void *arg)
if (IS_ERR(fp)) {
int err = PTR_ERR(fp);
- if (err == -FC_EX_CLOSED)
+ if (err == -FC_EX_CLOSED || err == -FC_EX_TIMEOUT)
goto cleanup;
- FC_DBG("Cannot process RRQ, because of frame error %d\n", err);
+ FC_EXCH_DBG(aborted_ep, "Cannot process RRQ, "
+ "frame error %d\n", err);
return;
}
@@ -1618,12 +2032,13 @@ static void fc_exch_rrq_resp(struct fc_seq *sp, struct fc_frame *fp, void *arg)
switch (op) {
case ELS_LS_RJT:
- FC_DBG("LS_RJT for RRQ");
+ FC_EXCH_DBG(aborted_ep, "LS_RJT for RRQ\n");
/* fall through */
case ELS_LS_ACC:
goto cleanup;
default:
- FC_DBG("unexpected response op %x for RRQ", op);
+ FC_EXCH_DBG(aborted_ep, "unexpected response op %x for RRQ\n",
+ op);
return;
}
@@ -1633,24 +2048,107 @@ cleanup:
fc_exch_release(aborted_ep);
}
-/*
- * Send ELS RRQ - Reinstate Recovery Qualifier.
+
+/**
+ * fc_exch_seq_send() - Send a frame using a new exchange and sequence
+ * @lport: The local port to send the frame on
+ * @fp: The frame to be sent
+ * @resp: The response handler for this request
+ * @destructor: The destructor for the exchange
+ * @arg: The argument to be passed to the response handler
+ * @timer_msec: The timeout period for the exchange
+ *
+ * The frame pointer with some of the header's fields must be
+ * filled before calling this routine, those fields are:
+ *
+ * - routing control
+ * - FC port did
+ * - FC port sid
+ * - FC header type
+ * - frame control
+ * - parameter or relative offset
+ */
+static struct fc_seq *fc_exch_seq_send(struct fc_lport *lport,
+ struct fc_frame *fp,
+ void (*resp)(struct fc_seq *,
+ struct fc_frame *fp,
+ void *arg),
+ void (*destructor)(struct fc_seq *,
+ void *),
+ void *arg, u32 timer_msec)
+{
+ struct fc_exch *ep;
+ struct fc_seq *sp = NULL;
+ struct fc_frame_header *fh;
+ struct fc_fcp_pkt *fsp = NULL;
+ int rc = 1;
+
+ ep = fc_exch_alloc(lport, fp);
+ if (!ep) {
+ fc_frame_free(fp);
+ return NULL;
+ }
+ ep->esb_stat |= ESB_ST_SEQ_INIT;
+ fh = fc_frame_header_get(fp);
+ fc_exch_set_addr(ep, ntoh24(fh->fh_s_id), ntoh24(fh->fh_d_id));
+ ep->resp = resp;
+ ep->destructor = destructor;
+ ep->arg = arg;
+ ep->r_a_tov = FC_DEF_R_A_TOV;
+ ep->lp = lport;
+ sp = &ep->seq;
+
+ ep->fh_type = fh->fh_type; /* save for possbile timeout handling */
+ ep->f_ctl = ntoh24(fh->fh_f_ctl);
+ fc_exch_setup_hdr(ep, fp, ep->f_ctl);
+ sp->cnt++;
+
+ if (ep->xid <= lport->lro_xid && fh->fh_r_ctl == FC_RCTL_DD_UNSOL_CMD) {
+ fsp = fr_fsp(fp);
+ fc_fcp_ddp_setup(fr_fsp(fp), ep->xid);
+ }
+
+ if (unlikely(lport->tt.frame_send(lport, fp)))
+ goto err;
+
+ if (timer_msec)
+ fc_exch_timer_set_locked(ep, timer_msec);
+ ep->f_ctl &= ~FC_FC_FIRST_SEQ; /* not first seq */
+
+ if (ep->f_ctl & FC_FC_SEQ_INIT)
+ ep->esb_stat &= ~ESB_ST_SEQ_INIT;
+ spin_unlock_bh(&ep->ex_lock);
+ return sp;
+err:
+ if (fsp)
+ fc_fcp_ddp_done(fsp);
+ rc = fc_exch_done_locked(ep);
+ spin_unlock_bh(&ep->ex_lock);
+ if (!rc)
+ fc_exch_delete(ep);
+ return NULL;
+}
+
+/**
+ * fc_exch_rrq() - Send an ELS RRQ (Reinstate Recovery Qualifier) command
+ * @ep: The exchange to send the RRQ on
+ *
* This tells the remote port to stop blocking the use of
* the exchange and the seq_cnt range.
*/
static void fc_exch_rrq(struct fc_exch *ep)
{
- struct fc_lport *lp;
+ struct fc_lport *lport;
struct fc_els_rrq *rrq;
struct fc_frame *fp;
- struct fc_seq *rrq_sp;
u32 did;
- lp = ep->lp;
+ lport = ep->lp;
- fp = fc_frame_alloc(lp, sizeof(*rrq));
+ fp = fc_frame_alloc(lport, sizeof(*rrq));
if (!fp)
- return;
+ goto retry;
+
rrq = fc_frame_payload_get(fp, sizeof(*rrq));
memset(rrq, 0, sizeof(*rrq));
rrq->rrq_cmd = ELS_RRQ;
@@ -1663,30 +2161,40 @@ static void fc_exch_rrq(struct fc_exch *ep)
did = ep->sid;
fc_fill_fc_hdr(fp, FC_RCTL_ELS_REQ, did,
- fc_host_port_id(lp->host), FC_TYPE_ELS,
+ lport->port_id, FC_TYPE_ELS,
FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
- rrq_sp = fc_exch_seq_send(lp, fp, fc_exch_rrq_resp, NULL, ep,
- lp->e_d_tov);
- if (!rrq_sp) {
- ep->esb_stat |= ESB_ST_REC_QUAL;
- fc_exch_timer_set_locked(ep, ep->r_a_tov);
+ if (fc_exch_seq_send(lport, fp, fc_exch_rrq_resp, NULL, ep,
+ lport->e_d_tov))
+ return;
+
+retry:
+ spin_lock_bh(&ep->ex_lock);
+ if (ep->state & (FC_EX_RST_CLEANUP | FC_EX_DONE)) {
+ spin_unlock_bh(&ep->ex_lock);
+ /* drop hold for rec qual */
+ fc_exch_release(ep);
return;
}
+ ep->esb_stat |= ESB_ST_REC_QUAL;
+ fc_exch_timer_set_locked(ep, ep->r_a_tov);
+ spin_unlock_bh(&ep->ex_lock);
}
-
-/*
- * Handle incoming ELS RRQ - Reset Recovery Qualifier.
+/**
+ * fc_exch_els_rrq() - Handler for ELS RRQ (Reset Recovery Qualifier) requests
+ * @fp: The RRQ frame, not freed here.
*/
-static void fc_exch_els_rrq(struct fc_seq *sp, struct fc_frame *fp)
+static void fc_exch_els_rrq(struct fc_frame *fp)
{
- struct fc_exch *ep; /* request or subject exchange */
+ struct fc_lport *lport;
+ struct fc_exch *ep = NULL; /* request or subject exchange */
struct fc_els_rrq *rp;
u32 sid;
u16 xid;
enum fc_els_rjt_explan explan;
+ lport = fr_dev(fp);
rp = fc_frame_payload_get(fp, sizeof(*rp));
explan = ELS_EXPL_INV_LEN;
if (!rp)
@@ -1695,11 +2203,10 @@ static void fc_exch_els_rrq(struct fc_seq *sp, struct fc_frame *fp)
/*
* lookup subject exchange.
*/
- ep = fc_seq_exch(sp);
sid = ntoh24(rp->rrq_s_id); /* subject source */
- xid = ep->did == sid ? ntohs(rp->rrq_ox_id) : ntohs(rp->rrq_rx_id);
- ep = fc_exch_find(ep->em, xid);
-
+ xid = fc_host_port_id(lport->host) == sid ?
+ ntohs(rp->rrq_ox_id) : ntohs(rp->rrq_rx_id);
+ ep = fc_exch_lookup(lport, xid);
explan = ELS_EXPL_OXID_RXID;
if (!ep)
goto reject;
@@ -1720,173 +2227,293 @@ static void fc_exch_els_rrq(struct fc_seq *sp, struct fc_frame *fp)
ep->esb_stat &= ~ESB_ST_REC_QUAL;
atomic_dec(&ep->ex_refcnt); /* drop hold for rec qual */
}
- if (ep->esb_stat & ESB_ST_COMPLETE) {
- if (cancel_delayed_work(&ep->timeout_work))
- atomic_dec(&ep->ex_refcnt); /* drop timer hold */
- }
+ if (ep->esb_stat & ESB_ST_COMPLETE)
+ fc_exch_timer_cancel(ep);
spin_unlock_bh(&ep->ex_lock);
/*
* Send LS_ACC.
*/
- fc_seq_ls_acc(sp);
- fc_frame_free(fp);
- return;
+ fc_seq_ls_acc(fp);
+ goto out;
unlock_reject:
spin_unlock_bh(&ep->ex_lock);
- fc_exch_release(ep); /* drop hold from fc_exch_find */
reject:
- fc_seq_ls_rjt(sp, ELS_RJT_LOGIC, explan);
- fc_frame_free(fp);
+ fc_seq_ls_rjt(fp, ELS_RJT_LOGIC, explan);
+out:
+ if (ep)
+ fc_exch_release(ep); /* drop hold from fc_exch_find */
}
-struct fc_exch_mgr *fc_exch_mgr_alloc(struct fc_lport *lp,
- enum fc_class class,
- u16 min_xid, u16 max_xid)
+/**
+ * fc_exch_update_stats() - update exches stats to lport
+ * @lport: The local port to update exchange manager stats
+ */
+void fc_exch_update_stats(struct fc_lport *lport)
{
+ struct fc_host_statistics *st;
+ struct fc_exch_mgr_anchor *ema;
struct fc_exch_mgr *mp;
- size_t len;
- if (max_xid <= min_xid || min_xid == 0 || max_xid == FC_XID_UNKNOWN) {
- FC_DBG("Invalid min_xid 0x:%x and max_xid 0x:%x\n",
- min_xid, max_xid);
+ st = &lport->host_stats;
+
+ list_for_each_entry(ema, &lport->ema_list, ema_list) {
+ mp = ema->mp;
+ st->fc_no_free_exch += atomic_read(&mp->stats.no_free_exch);
+ st->fc_no_free_exch_xid +=
+ atomic_read(&mp->stats.no_free_exch_xid);
+ st->fc_xid_not_found += atomic_read(&mp->stats.xid_not_found);
+ st->fc_xid_busy += atomic_read(&mp->stats.xid_busy);
+ st->fc_seq_not_found += atomic_read(&mp->stats.seq_not_found);
+ st->fc_non_bls_resp += atomic_read(&mp->stats.non_bls_resp);
+ }
+}
+EXPORT_SYMBOL(fc_exch_update_stats);
+
+/**
+ * fc_exch_mgr_add() - Add an exchange manager to a local port's list of EMs
+ * @lport: The local port to add the exchange manager to
+ * @mp: The exchange manager to be added to the local port
+ * @match: The match routine that indicates when this EM should be used
+ */
+struct fc_exch_mgr_anchor *fc_exch_mgr_add(struct fc_lport *lport,
+ struct fc_exch_mgr *mp,
+ bool (*match)(struct fc_frame *))
+{
+ struct fc_exch_mgr_anchor *ema;
+
+ ema = kmalloc(sizeof(*ema), GFP_ATOMIC);
+ if (!ema)
+ return ema;
+
+ ema->mp = mp;
+ ema->match = match;
+ /* add EM anchor to EM anchors list */
+ list_add_tail(&ema->ema_list, &lport->ema_list);
+ kref_get(&mp->kref);
+ return ema;
+}
+EXPORT_SYMBOL(fc_exch_mgr_add);
+
+/**
+ * fc_exch_mgr_destroy() - Destroy an exchange manager
+ * @kref: The reference to the EM to be destroyed
+ */
+static void fc_exch_mgr_destroy(struct kref *kref)
+{
+ struct fc_exch_mgr *mp = container_of(kref, struct fc_exch_mgr, kref);
+
+ mempool_destroy(mp->ep_pool);
+ free_percpu(mp->pool);
+ kfree(mp);
+}
+
+/**
+ * fc_exch_mgr_del() - Delete an EM from a local port's list
+ * @ema: The exchange manager anchor identifying the EM to be deleted
+ */
+void fc_exch_mgr_del(struct fc_exch_mgr_anchor *ema)
+{
+ /* remove EM anchor from EM anchors list */
+ list_del(&ema->ema_list);
+ kref_put(&ema->mp->kref, fc_exch_mgr_destroy);
+ kfree(ema);
+}
+EXPORT_SYMBOL(fc_exch_mgr_del);
+
+/**
+ * fc_exch_mgr_list_clone() - Share all exchange manager objects
+ * @src: Source lport to clone exchange managers from
+ * @dst: New lport that takes references to all the exchange managers
+ */
+int fc_exch_mgr_list_clone(struct fc_lport *src, struct fc_lport *dst)
+{
+ struct fc_exch_mgr_anchor *ema, *tmp;
+
+ list_for_each_entry(ema, &src->ema_list, ema_list) {
+ if (!fc_exch_mgr_add(dst, ema->mp, ema->match))
+ goto err;
+ }
+ return 0;
+err:
+ list_for_each_entry_safe(ema, tmp, &dst->ema_list, ema_list)
+ fc_exch_mgr_del(ema);
+ return -ENOMEM;
+}
+EXPORT_SYMBOL(fc_exch_mgr_list_clone);
+
+/**
+ * fc_exch_mgr_alloc() - Allocate an exchange manager
+ * @lport: The local port that the new EM will be associated with
+ * @class: The default FC class for new exchanges
+ * @min_xid: The minimum XID for exchanges from the new EM
+ * @max_xid: The maximum XID for exchanges from the new EM
+ * @match: The match routine for the new EM
+ */
+struct fc_exch_mgr *fc_exch_mgr_alloc(struct fc_lport *lport,
+ enum fc_class class,
+ u16 min_xid, u16 max_xid,
+ bool (*match)(struct fc_frame *))
+{
+ struct fc_exch_mgr *mp;
+ u16 pool_exch_range;
+ size_t pool_size;
+ unsigned int cpu;
+ struct fc_exch_pool *pool;
+
+ if (max_xid <= min_xid || max_xid == FC_XID_UNKNOWN ||
+ (min_xid & fc_cpu_mask) != 0) {
+ FC_LPORT_DBG(lport, "Invalid min_xid 0x:%x and max_xid 0x:%x\n",
+ min_xid, max_xid);
return NULL;
}
/*
- * Memory need for EM
+ * allocate memory for EM
*/
-#define xid_ok(i, m1, m2) (((i) >= (m1)) && ((i) <= (m2)))
- len = (max_xid - min_xid + 1) * (sizeof(struct fc_exch *));
- len += sizeof(struct fc_exch_mgr);
-
- mp = kzalloc(len, GFP_ATOMIC);
+ mp = kzalloc(sizeof(struct fc_exch_mgr), GFP_ATOMIC);
if (!mp)
return NULL;
mp->class = class;
- mp->total_exches = 0;
- mp->exches = (struct fc_exch **)(mp + 1);
- mp->lp = lp;
/* adjust em exch xid range for offload */
mp->min_xid = min_xid;
- mp->max_xid = max_xid;
- mp->last_xid = min_xid - 1;
- mp->max_read = 0;
- mp->last_read = 0;
- if (lp->lro_enabled && xid_ok(lp->lro_xid, min_xid, max_xid)) {
- mp->max_read = lp->lro_xid;
- mp->last_read = min_xid - 1;
- mp->last_xid = mp->max_read;
+
+ /* reduce range so per cpu pool fits into PCPU_MIN_UNIT_SIZE pool */
+ pool_exch_range = (PCPU_MIN_UNIT_SIZE - sizeof(*pool)) /
+ sizeof(struct fc_exch *);
+ if ((max_xid - min_xid + 1) / (fc_cpu_mask + 1) > pool_exch_range) {
+ mp->max_xid = pool_exch_range * (fc_cpu_mask + 1) +
+ min_xid - 1;
} else {
- /* disable lro if no xid control over read */
- lp->lro_enabled = 0;
+ mp->max_xid = max_xid;
+ pool_exch_range = (mp->max_xid - mp->min_xid + 1) /
+ (fc_cpu_mask + 1);
}
- INIT_LIST_HEAD(&mp->ex_list);
- spin_lock_init(&mp->em_lock);
-
mp->ep_pool = mempool_create_slab_pool(2, fc_em_cachep);
if (!mp->ep_pool)
goto free_mp;
- return mp;
+ /*
+ * Setup per cpu exch pool with entire exchange id range equally
+ * divided across all cpus. The exch pointers array memory is
+ * allocated for exch range per pool.
+ */
+ mp->pool_max_index = pool_exch_range - 1;
-free_mp:
- kfree(mp);
- return NULL;
-}
-EXPORT_SYMBOL(fc_exch_mgr_alloc);
+ /*
+ * Allocate and initialize per cpu exch pool
+ */
+ pool_size = sizeof(*pool) + pool_exch_range * sizeof(struct fc_exch *);
+ mp->pool = __alloc_percpu(pool_size, __alignof__(struct fc_exch_pool));
+ if (!mp->pool)
+ goto free_mempool;
+ for_each_possible_cpu(cpu) {
+ pool = per_cpu_ptr(mp->pool, cpu);
+ pool->next_index = 0;
+ pool->left = FC_XID_UNKNOWN;
+ pool->right = FC_XID_UNKNOWN;
+ spin_lock_init(&pool->lock);
+ INIT_LIST_HEAD(&pool->ex_list);
+ }
+
+ kref_init(&mp->kref);
+ if (!fc_exch_mgr_add(lport, mp, match)) {
+ free_percpu(mp->pool);
+ goto free_mempool;
+ }
-void fc_exch_mgr_free(struct fc_exch_mgr *mp)
-{
- WARN_ON(!mp);
/*
- * The total exch count must be zero
- * before freeing exchange manager.
+ * Above kref_init() sets mp->kref to 1 and then
+ * call to fc_exch_mgr_add incremented mp->kref again,
+ * so adjust that extra increment.
*/
- WARN_ON(mp->total_exches != 0);
+ kref_put(&mp->kref, fc_exch_mgr_destroy);
+ return mp;
+
+free_mempool:
mempool_destroy(mp->ep_pool);
+free_mp:
kfree(mp);
+ return NULL;
}
-EXPORT_SYMBOL(fc_exch_mgr_free);
+EXPORT_SYMBOL(fc_exch_mgr_alloc);
-struct fc_exch *fc_exch_get(struct fc_lport *lp, struct fc_frame *fp)
+/**
+ * fc_exch_mgr_free() - Free all exchange managers on a local port
+ * @lport: The local port whose EMs are to be freed
+ */
+void fc_exch_mgr_free(struct fc_lport *lport)
{
- if (!lp || !lp->emp)
- return NULL;
+ struct fc_exch_mgr_anchor *ema, *next;
- return fc_exch_alloc(lp->emp, fp, 0);
+ flush_workqueue(fc_exch_workqueue);
+ list_for_each_entry_safe(ema, next, &lport->ema_list, ema_list)
+ fc_exch_mgr_del(ema);
}
-EXPORT_SYMBOL(fc_exch_get);
+EXPORT_SYMBOL(fc_exch_mgr_free);
-struct fc_seq *fc_exch_seq_send(struct fc_lport *lp,
- struct fc_frame *fp,
- void (*resp)(struct fc_seq *,
- struct fc_frame *fp,
- void *arg),
- void (*destructor)(struct fc_seq *, void *),
- void *arg, u32 timer_msec)
+/**
+ * fc_find_ema() - Lookup and return appropriate Exchange Manager Anchor depending
+ * upon 'xid'.
+ * @f_ctl: f_ctl
+ * @lport: The local port the frame was received on
+ * @fh: The received frame header
+ */
+static struct fc_exch_mgr_anchor *fc_find_ema(u32 f_ctl,
+ struct fc_lport *lport,
+ struct fc_frame_header *fh)
{
- struct fc_exch *ep;
- struct fc_seq *sp = NULL;
- struct fc_frame_header *fh;
- int rc = 1;
+ struct fc_exch_mgr_anchor *ema;
+ u16 xid;
- ep = lp->tt.exch_get(lp, fp);
- if (!ep) {
- fc_frame_free(fp);
- return NULL;
+ if (f_ctl & FC_FC_EX_CTX)
+ xid = ntohs(fh->fh_ox_id);
+ else {
+ xid = ntohs(fh->fh_rx_id);
+ if (xid == FC_XID_UNKNOWN)
+ return list_entry(lport->ema_list.prev,
+ typeof(*ema), ema_list);
}
- ep->esb_stat |= ESB_ST_SEQ_INIT;
- fh = fc_frame_header_get(fp);
- fc_exch_set_addr(ep, ntoh24(fh->fh_s_id), ntoh24(fh->fh_d_id));
- ep->resp = resp;
- ep->destructor = destructor;
- ep->arg = arg;
- ep->r_a_tov = FC_DEF_R_A_TOV;
- ep->lp = lp;
- sp = &ep->seq;
- ep->fh_type = fh->fh_type; /* save for possbile timeout handling */
- ep->f_ctl = ntoh24(fh->fh_f_ctl);
- fc_exch_setup_hdr(ep, fp, ep->f_ctl);
- sp->cnt++;
-
- if (unlikely(lp->tt.frame_send(lp, fp)))
- goto err;
-
- if (timer_msec)
- fc_exch_timer_set_locked(ep, timer_msec);
- ep->f_ctl &= ~FC_FC_FIRST_SEQ; /* not first seq */
-
- if (ep->f_ctl & FC_FC_SEQ_INIT)
- ep->esb_stat &= ~ESB_ST_SEQ_INIT;
- spin_unlock_bh(&ep->ex_lock);
- return sp;
-err:
- rc = fc_exch_done_locked(ep);
- spin_unlock_bh(&ep->ex_lock);
- if (!rc)
- fc_exch_mgr_delete_ep(ep);
+ list_for_each_entry(ema, &lport->ema_list, ema_list) {
+ if ((xid >= ema->mp->min_xid) &&
+ (xid <= ema->mp->max_xid))
+ return ema;
+ }
return NULL;
}
-EXPORT_SYMBOL(fc_exch_seq_send);
-
-/*
- * Receive a frame
+/**
+ * fc_exch_recv() - Handler for received frames
+ * @lport: The local port the frame was received on
+ * @fp: The received frame
*/
-void fc_exch_recv(struct fc_lport *lp, struct fc_exch_mgr *mp,
- struct fc_frame *fp)
+void fc_exch_recv(struct fc_lport *lport, struct fc_frame *fp)
{
struct fc_frame_header *fh = fc_frame_header_get(fp);
+ struct fc_exch_mgr_anchor *ema;
u32 f_ctl;
/* lport lock ? */
- if (!lp || !mp || (lp->state == LPORT_ST_NONE)) {
- FC_DBG("fc_lport or EM is not allocated and configured");
+ if (!lport || lport->state == LPORT_ST_DISABLED) {
+ FC_LPORT_DBG(lport, "Receiving frames for an lport that "
+ "has not been initialized correctly\n");
+ fc_frame_free(fp);
+ return;
+ }
+
+ f_ctl = ntoh24(fh->fh_f_ctl);
+ ema = fc_find_ema(f_ctl, lport, fh);
+ if (!ema) {
+ FC_LPORT_DBG(lport, "Unable to find Exchange Manager Anchor,"
+ "fc_ctl <0x%x>, xid <0x%x>\n",
+ f_ctl,
+ (f_ctl & FC_FC_EX_CTX) ?
+ ntohs(fh->fh_ox_id) :
+ ntohs(fh->fh_rx_id));
fc_frame_free(fp);
return;
}
@@ -1894,7 +2521,6 @@ void fc_exch_recv(struct fc_lport *lp, struct fc_exch_mgr *mp,
/*
* If frame is marked invalid, just drop it.
*/
- f_ctl = ntoh24(fh->fh_f_ctl);
switch (fr_eof(fp)) {
case FC_EOF_T:
if (f_ctl & FC_FC_END_SEQ)
@@ -1902,69 +2528,104 @@ void fc_exch_recv(struct fc_lport *lp, struct fc_exch_mgr *mp,
/* fall through */
case FC_EOF_N:
if (fh->fh_type == FC_TYPE_BLS)
- fc_exch_recv_bls(mp, fp);
+ fc_exch_recv_bls(ema->mp, fp);
else if ((f_ctl & (FC_FC_EX_CTX | FC_FC_SEQ_CTX)) ==
FC_FC_EX_CTX)
- fc_exch_recv_seq_resp(mp, fp);
+ fc_exch_recv_seq_resp(ema->mp, fp);
else if (f_ctl & FC_FC_SEQ_CTX)
- fc_exch_recv_resp(mp, fp);
- else
- fc_exch_recv_req(lp, mp, fp);
+ fc_exch_recv_resp(ema->mp, fp);
+ else /* no EX_CTX and no SEQ_CTX */
+ fc_exch_recv_req(lport, ema->mp, fp);
break;
default:
- FC_DBG("dropping invalid frame (eof %x)", fr_eof(fp));
+ FC_LPORT_DBG(lport, "dropping invalid frame (eof %x)",
+ fr_eof(fp));
fc_frame_free(fp);
- break;
}
}
EXPORT_SYMBOL(fc_exch_recv);
-int fc_exch_init(struct fc_lport *lp)
+/**
+ * fc_exch_init() - Initialize the exchange layer for a local port
+ * @lport: The local port to initialize the exchange layer for
+ */
+int fc_exch_init(struct fc_lport *lport)
{
- if (!lp->tt.exch_get) {
- /*
- * exch_put() should be NULL if
- * exch_get() is NULL
- */
- WARN_ON(lp->tt.exch_put);
- lp->tt.exch_get = fc_exch_get;
- }
+ 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 (!lp->tt.seq_start_next)
- lp->tt.seq_start_next = fc_seq_start_next;
+ if (!lport->tt.exch_seq_send)
+ lport->tt.exch_seq_send = fc_exch_seq_send;
- if (!lp->tt.exch_seq_send)
- lp->tt.exch_seq_send = fc_exch_seq_send;
+ if (!lport->tt.seq_send)
+ lport->tt.seq_send = fc_seq_send;
- if (!lp->tt.seq_send)
- lp->tt.seq_send = fc_seq_send;
+ if (!lport->tt.seq_els_rsp_send)
+ lport->tt.seq_els_rsp_send = fc_seq_els_rsp_send;
- if (!lp->tt.seq_els_rsp_send)
- lp->tt.seq_els_rsp_send = fc_seq_els_rsp_send;
+ if (!lport->tt.exch_done)
+ lport->tt.exch_done = fc_exch_done;
- if (!lp->tt.exch_done)
- lp->tt.exch_done = fc_exch_done;
+ if (!lport->tt.exch_mgr_reset)
+ lport->tt.exch_mgr_reset = fc_exch_mgr_reset;
- if (!lp->tt.exch_mgr_reset)
- lp->tt.exch_mgr_reset = fc_exch_mgr_reset;
+ if (!lport->tt.seq_exch_abort)
+ lport->tt.seq_exch_abort = fc_seq_exch_abort;
- if (!lp->tt.seq_exch_abort)
- lp->tt.seq_exch_abort = fc_seq_exch_abort;
+ if (!lport->tt.seq_assign)
+ lport->tt.seq_assign = fc_seq_assign;
+
+ if (!lport->tt.seq_release)
+ lport->tt.seq_release = fc_seq_release;
return 0;
}
EXPORT_SYMBOL(fc_exch_init);
+/**
+ * fc_setup_exch_mgr() - Setup an exchange manager
+ */
int fc_setup_exch_mgr(void)
{
fc_em_cachep = kmem_cache_create("libfc_em", sizeof(struct fc_exch),
0, SLAB_HWCACHE_ALIGN, NULL);
if (!fc_em_cachep)
return -ENOMEM;
+
+ /*
+ * Initialize fc_cpu_mask and fc_cpu_order. The
+ * fc_cpu_mask is set for nr_cpu_ids rounded up
+ * to order of 2's * power and order is stored
+ * in fc_cpu_order as this is later required in
+ * mapping between an exch id and exch array index
+ * in per cpu exch pool.
+ *
+ * This round up is required to align fc_cpu_mask
+ * to exchange id's lower bits such that all incoming
+ * frames of an exchange gets delivered to the same
+ * cpu on which exchange originated by simple bitwise
+ * AND operation between fc_cpu_mask and exchange id.
+ */
+ fc_cpu_order = ilog2(roundup_pow_of_two(nr_cpu_ids));
+ fc_cpu_mask = (1 << fc_cpu_order) - 1;
+
+ fc_exch_workqueue = create_singlethread_workqueue("fc_exch_workqueue");
+ if (!fc_exch_workqueue)
+ goto err;
return 0;
+err:
+ kmem_cache_destroy(fc_em_cachep);
+ return -ENOMEM;
}
+/**
+ * fc_destroy_exch_mgr() - Destroy an exchange manager
+ */
void fc_destroy_exch_mgr(void)
{
+ destroy_workqueue(fc_exch_workqueue);
kmem_cache_destroy(fc_em_cachep);
}
diff --git a/drivers/scsi/libfc/fc_fcp.c b/drivers/scsi/libfc/fc_fcp.c
index 404e63ff46b..1d7e76e8b44 100644
--- a/drivers/scsi/libfc/fc_fcp.c
+++ b/drivers/scsi/libfc/fc_fcp.c
@@ -27,6 +27,7 @@
#include <linux/scatterlist.h>
#include <linux/err.h>
#include <linux/crc32.h>
+#include <linux/slab.h>
#include <scsi/scsi_tcq.h>
#include <scsi/scsi.h>
@@ -39,17 +40,7 @@
#include <scsi/libfc.h>
#include <scsi/fc_encode.h>
-MODULE_AUTHOR("Open-FCoE.org");
-MODULE_DESCRIPTION("libfc");
-MODULE_LICENSE("GPL");
-
-static int fc_fcp_debug;
-
-#define FC_DEBUG_FCP(fmt...) \
- do { \
- if (fc_fcp_debug) \
- FC_DBG(fmt); \
- } while (0)
+#include "fc_libfc.h"
static struct kmem_cache *scsi_pkt_cachep;
@@ -58,18 +49,16 @@ static struct kmem_cache *scsi_pkt_cachep;
#define FC_SRB_CMD_SENT (1 << 0) /* cmd has been sent */
#define FC_SRB_RCV_STATUS (1 << 1) /* response has arrived */
#define FC_SRB_ABORT_PENDING (1 << 2) /* cmd abort sent to device */
-#define FC_SRB_ABORTED (1 << 3) /* abort acknowleged */
+#define FC_SRB_ABORTED (1 << 3) /* abort acknowledged */
#define FC_SRB_DISCONTIG (1 << 4) /* non-sequential data recvd */
#define FC_SRB_COMPL (1 << 5) /* fc_io_compl has been run */
#define FC_SRB_FCP_PROCESSING_TMO (1 << 6) /* timer function processing */
-#define FC_SRB_NOMEM (1 << 7) /* dropped to out of mem */
#define FC_SRB_READ (1 << 1)
#define FC_SRB_WRITE (1 << 0)
/*
- * The SCp.ptr should be tested and set under the host lock. NULL indicates
- * that the command has been retruned to the scsi layer.
+ * The SCp.ptr should be tested and set under the scsi_pkt_queue lock
*/
#define CMD_SP(Cmnd) ((struct fc_fcp_pkt *)(Cmnd)->SCp.ptr)
#define CMD_ENTRY_STATUS(Cmnd) ((Cmnd)->SCp.have_data_in)
@@ -77,10 +66,22 @@ static struct kmem_cache *scsi_pkt_cachep;
#define CMD_SCSI_STATUS(Cmnd) ((Cmnd)->SCp.Status)
#define CMD_RESID_LEN(Cmnd) ((Cmnd)->SCp.buffers_residual)
+/**
+ * struct fc_fcp_internal - FCP layer internal data
+ * @scsi_pkt_pool: Memory pool to draw FCP packets from
+ * @scsi_queue_lock: Protects the scsi_pkt_queue
+ * @scsi_pkt_queue: Current FCP packets
+ * @last_can_queue_ramp_down_time: ramp down time
+ * @last_can_queue_ramp_up_time: ramp up time
+ * @max_can_queue: max can_queue size
+ */
struct fc_fcp_internal {
- mempool_t *scsi_pkt_pool;
- struct list_head scsi_pkt_queue;
- u8 throttled;
+ mempool_t *scsi_pkt_pool;
+ spinlock_t scsi_queue_lock;
+ struct list_head scsi_pkt_queue;
+ unsigned long last_can_queue_ramp_down_time;
+ unsigned long last_can_queue_ramp_up_time;
+ int max_can_queue;
};
#define fc_get_scsi_internal(x) ((struct fc_fcp_internal *)(x)->scsi_priv)
@@ -94,9 +95,9 @@ static void fc_fcp_recv(struct fc_seq *, struct fc_frame *, void *);
static void fc_fcp_resp(struct fc_fcp_pkt *, struct fc_frame *);
static void fc_fcp_complete_locked(struct fc_fcp_pkt *);
static void fc_tm_done(struct fc_seq *, struct fc_frame *, void *);
-static void fc_fcp_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp);
-static void fc_timeout_error(struct fc_fcp_pkt *);
-static void fc_fcp_timeout(unsigned long data);
+static void fc_fcp_error(struct fc_fcp_pkt *, struct fc_frame *);
+static void fc_fcp_recovery(struct fc_fcp_pkt *, u8 code);
+static void fc_fcp_timeout(unsigned long);
static void fc_fcp_rec(struct fc_fcp_pkt *);
static void fc_fcp_rec_error(struct fc_fcp_pkt *, struct fc_frame *);
static void fc_fcp_rec_resp(struct fc_seq *, struct fc_frame *, void *);
@@ -119,15 +120,15 @@ static void fc_fcp_srr_error(struct fc_fcp_pkt *, struct fc_frame *);
#define FC_DATA_UNDRUN 7
#define FC_ERROR 8
#define FC_HRD_ERROR 9
-#define FC_CMD_TIME_OUT 10
+#define FC_CRC_ERROR 10
+#define FC_TIMED_OUT 11
/*
* Error recovery timeout values.
*/
-#define FC_SCSI_ER_TIMEOUT (10 * HZ)
#define FC_SCSI_TM_TOV (10 * HZ)
-#define FC_SCSI_REC_TOV (2 * HZ)
#define FC_HOST_RESET_TIMEOUT (30 * HZ)
+#define FC_CAN_QUEUE_PERIOD (60 * HZ)
#define FC_MAX_ERROR_CNT 5
#define FC_MAX_RECOV_RETRY 3
@@ -135,38 +136,41 @@ static void fc_fcp_srr_error(struct fc_fcp_pkt *, struct fc_frame *);
#define FC_FCP_DFLT_QUEUE_DEPTH 32
/**
- * fc_fcp_pkt_alloc - allocation routine for scsi_pkt packet
- * @lp: fc lport struct
- * @gfp: gfp flags for allocation
+ * fc_fcp_pkt_alloc() - Allocate a fcp_pkt
+ * @lport: The local port that the FCP packet is for
+ * @gfp: GFP flags for allocation
*
- * This is used by upper layer scsi driver.
- * Return Value : scsi_pkt structure or null on allocation failure.
- * Context : call from process context. no locking required.
+ * Return value: fcp_pkt structure or null on allocation failure.
+ * Context: Can be called from process context, no lock is required.
*/
-static struct fc_fcp_pkt *fc_fcp_pkt_alloc(struct fc_lport *lp, gfp_t gfp)
+static struct fc_fcp_pkt *fc_fcp_pkt_alloc(struct fc_lport *lport, gfp_t gfp)
{
- struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
+ struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
struct fc_fcp_pkt *fsp;
fsp = mempool_alloc(si->scsi_pkt_pool, gfp);
if (fsp) {
memset(fsp, 0, sizeof(*fsp));
- fsp->lp = lp;
+ fsp->lp = lport;
+ fsp->xfer_ddp = FC_XID_UNKNOWN;
atomic_set(&fsp->ref_cnt, 1);
init_timer(&fsp->timer);
+ fsp->timer.data = (unsigned long)fsp;
INIT_LIST_HEAD(&fsp->list);
spin_lock_init(&fsp->scsi_pkt_lock);
+ } else {
+ per_cpu_ptr(lport->stats, get_cpu())->FcpPktAllocFails++;
+ put_cpu();
}
return fsp;
}
/**
- * fc_fcp_pkt_release - release hold on scsi_pkt packet
- * @fsp: fcp packet struct
+ * fc_fcp_pkt_release() - Release hold on a fcp_pkt
+ * @fsp: The FCP packet to be released
*
- * This is used by upper layer scsi driver.
- * Context : call from process and interrupt context.
- * no locking required
+ * Context: Can be called from process or interrupt context,
+ * no lock is required.
*/
static void fc_fcp_pkt_release(struct fc_fcp_pkt *fsp)
{
@@ -177,21 +181,25 @@ static void fc_fcp_pkt_release(struct fc_fcp_pkt *fsp)
}
}
+/**
+ * fc_fcp_pkt_hold() - Hold a fcp_pkt
+ * @fsp: The FCP packet to be held
+ */
static void fc_fcp_pkt_hold(struct fc_fcp_pkt *fsp)
{
atomic_inc(&fsp->ref_cnt);
}
/**
- * fc_fcp_pkt_destory - release hold on scsi_pkt packet
+ * fc_fcp_pkt_destory() - Release hold on a fcp_pkt
+ * @seq: The sequence that the FCP packet is on (required by destructor API)
+ * @fsp: The FCP packet to be released
*
- * @seq: exchange sequence
- * @fsp: fcp packet struct
+ * This routine is called by a destructor callback in the exch_seq_send()
+ * routine of the libfc Transport Template. The 'struct fc_seq' is a required
+ * argument even though it is not used by this routine.
*
- * Release hold on scsi_pkt packet set to keep scsi_pkt
- * till EM layer exch resource is not freed.
- * Context : called from from EM layer.
- * no locking required
+ * Context: No locking required.
*/
static void fc_fcp_pkt_destroy(struct fc_seq *seq, void *fsp)
{
@@ -199,10 +207,10 @@ static void fc_fcp_pkt_destroy(struct fc_seq *seq, void *fsp)
}
/**
- * fc_fcp_lock_pkt - lock a packet and get a ref to it.
- * @fsp: fcp packet
+ * fc_fcp_lock_pkt() - Lock a fcp_pkt and increase its reference count
+ * @fsp: The FCP packet to be locked and incremented
*
- * We should only return error if we return a command to scsi-ml before
+ * We should only return error if we return a command to SCSI-ml before
* getting a response. This can happen in cases where we send a abort, but
* do not wait for the response and the abort and command can be passing
* each other on the wire/network-layer.
@@ -227,30 +235,53 @@ static inline int fc_fcp_lock_pkt(struct fc_fcp_pkt *fsp)
return 0;
}
+/**
+ * fc_fcp_unlock_pkt() - Release a fcp_pkt's lock and decrement its
+ * reference count
+ * @fsp: The FCP packet to be unlocked and decremented
+ */
static inline void fc_fcp_unlock_pkt(struct fc_fcp_pkt *fsp)
{
spin_unlock_bh(&fsp->scsi_pkt_lock);
fc_fcp_pkt_release(fsp);
}
+/**
+ * fc_fcp_timer_set() - Start a timer for a fcp_pkt
+ * @fsp: The FCP packet to start a timer for
+ * @delay: The timeout period in jiffies
+ */
static void fc_fcp_timer_set(struct fc_fcp_pkt *fsp, unsigned long delay)
{
if (!(fsp->state & FC_SRB_COMPL))
mod_timer(&fsp->timer, jiffies + delay);
}
+/**
+ * fc_fcp_send_abort() - Send an abort for exchanges associated with a
+ * fcp_pkt
+ * @fsp: The FCP packet to abort exchanges on
+ */
static int fc_fcp_send_abort(struct fc_fcp_pkt *fsp)
{
if (!fsp->seq_ptr)
return -EINVAL;
+ per_cpu_ptr(fsp->lp->stats, get_cpu())->FcpPktAborts++;
+ put_cpu();
+
fsp->state |= FC_SRB_ABORT_PENDING;
return fsp->lp->tt.seq_exch_abort(fsp->seq_ptr, 0);
}
-/*
- * Retry command.
- * An abort isn't needed.
+/**
+ * fc_fcp_retry_cmd() - Retry a fcp_pkt
+ * @fsp: The FCP packet to be retried
+ *
+ * Sets the status code to be FC_ERROR and then calls
+ * fc_fcp_complete_locked() which in turn calls fc_io_compl().
+ * fc_io_compl() will notify the SCSI-ml that the I/O is done.
+ * The SCSI-ml will retry the command.
*/
static void fc_fcp_retry_cmd(struct fc_fcp_pkt *fsp)
{
@@ -260,20 +291,158 @@ static void fc_fcp_retry_cmd(struct fc_fcp_pkt *fsp)
}
fsp->state &= ~FC_SRB_ABORT_PENDING;
- fsp->io_status = SUGGEST_RETRY << 24;
+ fsp->io_status = 0;
fsp->status_code = FC_ERROR;
fc_fcp_complete_locked(fsp);
}
+/**
+ * fc_fcp_ddp_setup() - Calls a LLD's ddp_setup routine to set up DDP context
+ * @fsp: The FCP packet that will manage the DDP frames
+ * @xid: The XID that will be used for the DDP exchange
+ */
+void fc_fcp_ddp_setup(struct fc_fcp_pkt *fsp, u16 xid)
+{
+ struct fc_lport *lport;
+
+ lport = fsp->lp;
+ if ((fsp->req_flags & FC_SRB_READ) &&
+ (lport->lro_enabled) && (lport->tt.ddp_setup)) {
+ if (lport->tt.ddp_setup(lport, xid, scsi_sglist(fsp->cmd),
+ scsi_sg_count(fsp->cmd)))
+ fsp->xfer_ddp = xid;
+ }
+}
+
+/**
+ * fc_fcp_ddp_done() - Calls a LLD's ddp_done routine to release any
+ * DDP related resources for a fcp_pkt
+ * @fsp: The FCP packet that DDP had been used on
+ */
+void fc_fcp_ddp_done(struct fc_fcp_pkt *fsp)
+{
+ struct fc_lport *lport;
+
+ if (!fsp)
+ return;
+
+ if (fsp->xfer_ddp == FC_XID_UNKNOWN)
+ return;
+
+ lport = fsp->lp;
+ if (lport->tt.ddp_done) {
+ fsp->xfer_len = lport->tt.ddp_done(lport, fsp->xfer_ddp);
+ fsp->xfer_ddp = FC_XID_UNKNOWN;
+ }
+}
+
+/**
+ * fc_fcp_can_queue_ramp_up() - increases can_queue
+ * @lport: lport to ramp up can_queue
+ */
+static void fc_fcp_can_queue_ramp_up(struct fc_lport *lport)
+{
+ struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
+ unsigned long flags;
+ int can_queue;
+
+ spin_lock_irqsave(lport->host->host_lock, flags);
+
+ if (si->last_can_queue_ramp_up_time &&
+ (time_before(jiffies, si->last_can_queue_ramp_up_time +
+ FC_CAN_QUEUE_PERIOD)))
+ goto unlock;
+
+ if (time_before(jiffies, si->last_can_queue_ramp_down_time +
+ FC_CAN_QUEUE_PERIOD))
+ goto unlock;
+
+ si->last_can_queue_ramp_up_time = jiffies;
+
+ can_queue = lport->host->can_queue << 1;
+ if (can_queue >= si->max_can_queue) {
+ can_queue = si->max_can_queue;
+ si->last_can_queue_ramp_down_time = 0;
+ }
+ lport->host->can_queue = can_queue;
+ shost_printk(KERN_ERR, lport->host, "libfc: increased "
+ "can_queue to %d.\n", can_queue);
+
+unlock:
+ spin_unlock_irqrestore(lport->host->host_lock, flags);
+}
+
+/**
+ * fc_fcp_can_queue_ramp_down() - reduces can_queue
+ * @lport: lport to reduce can_queue
+ *
+ * If we are getting memory allocation failures, then we may
+ * be trying to execute too many commands. We let the running
+ * commands complete or timeout, then try again with a reduced
+ * can_queue. Eventually we will hit the point where we run
+ * on all reserved structs.
+ */
+static void fc_fcp_can_queue_ramp_down(struct fc_lport *lport)
+{
+ struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
+ unsigned long flags;
+ int can_queue;
+
+ spin_lock_irqsave(lport->host->host_lock, flags);
+
+ if (si->last_can_queue_ramp_down_time &&
+ (time_before(jiffies, si->last_can_queue_ramp_down_time +
+ FC_CAN_QUEUE_PERIOD)))
+ goto unlock;
+
+ si->last_can_queue_ramp_down_time = jiffies;
+
+ can_queue = lport->host->can_queue;
+ can_queue >>= 1;
+ if (!can_queue)
+ can_queue = 1;
+ lport->host->can_queue = can_queue;
+ shost_printk(KERN_ERR, lport->host, "libfc: Could not allocate frame.\n"
+ "Reducing can_queue to %d.\n", can_queue);
+
+unlock:
+ spin_unlock_irqrestore(lport->host->host_lock, flags);
+}
+
/*
- * Receive SCSI data from target.
- * Called after receiving solicited data.
+ * fc_fcp_frame_alloc() - Allocates fc_frame structure and buffer.
+ * @lport: fc lport struct
+ * @len: payload length
+ *
+ * Allocates fc_frame structure and buffer but if fails to allocate
+ * then reduce can_queue.
+ */
+static inline struct fc_frame *fc_fcp_frame_alloc(struct fc_lport *lport,
+ size_t len)
+{
+ struct fc_frame *fp;
+
+ fp = fc_frame_alloc(lport, len);
+ if (likely(fp))
+ return fp;
+
+ per_cpu_ptr(lport->stats, get_cpu())->FcpFrameAllocFails++;
+ put_cpu();
+ /* error case */
+ fc_fcp_can_queue_ramp_down(lport);
+ return NULL;
+}
+
+/**
+ * fc_fcp_recv_data() - Handler for receiving SCSI-FCP data from a target
+ * @fsp: The FCP packet the data is on
+ * @fp: The data frame
*/
static void fc_fcp_recv_data(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
{
struct scsi_cmnd *sc = fsp->cmd;
- struct fc_lport *lp = fsp->lp;
- struct fcoe_dev_stats *stats;
+ struct fc_lport *lport = fsp->lp;
+ struct fc_stats *stats;
struct fc_frame_header *fh;
size_t start_offset;
size_t offset;
@@ -282,7 +451,8 @@ static void fc_fcp_recv_data(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
size_t len;
void *buf;
struct scatterlist *sg;
- size_t remaining;
+ u32 nents;
+ u8 host_bcode = FC_COMPLETE;
fh = fc_frame_header_get(fp);
offset = ntohl(fh->fh_parm_offset);
@@ -290,77 +460,58 @@ static void fc_fcp_recv_data(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
len = fr_len(fp) - sizeof(*fh);
buf = fc_frame_payload_get(fp, 0);
+ /*
+ * if this I/O is ddped then clear it and initiate recovery since data
+ * frames are expected to be placed directly in that case.
+ *
+ * Indicate error to scsi-ml because something went wrong with the
+ * ddp handling to get us here.
+ */
+ if (fsp->xfer_ddp != FC_XID_UNKNOWN) {
+ fc_fcp_ddp_done(fsp);
+ FC_FCP_DBG(fsp, "DDP I/O in fc_fcp_recv_data set ERROR\n");
+ host_bcode = FC_ERROR;
+ goto err;
+ }
if (offset + len > fsp->data_len) {
- /*
- * this should never happen
- */
+ /* this should never happen */
if ((fr_flags(fp) & FCPHF_CRC_UNCHECKED) &&
fc_frame_crc_check(fp))
goto crc_err;
- FC_DEBUG_FCP("data received past end. len %zx offset %zx "
- "data_len %x\n", len, offset, fsp->data_len);
- fc_fcp_retry_cmd(fsp);
- return;
+ FC_FCP_DBG(fsp, "data received past end. len %zx offset %zx "
+ "data_len %x\n", len, offset, fsp->data_len);
+
+ /* Data is corrupted indicate scsi-ml should retry */
+ host_bcode = FC_DATA_OVRRUN;
+ goto err;
}
if (offset != fsp->xfer_len)
fsp->state |= FC_SRB_DISCONTIG;
- crc = 0;
- if (fr_flags(fp) & FCPHF_CRC_UNCHECKED)
- crc = crc32(~0, (u8 *) fh, sizeof(*fh));
-
sg = scsi_sglist(sc);
- remaining = len;
-
- while (remaining > 0 && sg) {
- size_t off;
- void *page_addr;
- size_t sg_bytes;
-
- if (offset >= sg->length) {
- offset -= sg->length;
- sg = sg_next(sg);
- continue;
- }
- sg_bytes = min(remaining, sg->length - offset);
-
- /*
- * The scatterlist item may be bigger than PAGE_SIZE,
- * but we are limited to mapping PAGE_SIZE at a time.
- */
- off = offset + sg->offset;
- sg_bytes = min(sg_bytes, (size_t)
- (PAGE_SIZE - (off & ~PAGE_MASK)));
- page_addr = kmap_atomic(sg_page(sg) + (off >> PAGE_SHIFT),
- KM_SOFTIRQ0);
- if (!page_addr)
- break; /* XXX panic? */
-
- if (fr_flags(fp) & FCPHF_CRC_UNCHECKED)
- crc = crc32(crc, buf, sg_bytes);
- memcpy((char *)page_addr + (off & ~PAGE_MASK), buf,
- sg_bytes);
-
- kunmap_atomic(page_addr, KM_SOFTIRQ0);
- buf += sg_bytes;
- offset += sg_bytes;
- remaining -= sg_bytes;
- copy_len += sg_bytes;
- }
+ nents = scsi_sg_count(sc);
- if (fr_flags(fp) & FCPHF_CRC_UNCHECKED) {
+ if (!(fr_flags(fp) & FCPHF_CRC_UNCHECKED)) {
+ copy_len = fc_copy_buffer_to_sglist(buf, len, sg, &nents,
+ &offset, NULL);
+ } else {
+ crc = crc32(~0, (u8 *) fh, sizeof(*fh));
+ copy_len = fc_copy_buffer_to_sglist(buf, len, sg, &nents,
+ &offset, &crc);
buf = fc_frame_payload_get(fp, 0);
- if (len % 4) {
+ if (len % 4)
crc = crc32(crc, buf + len, 4 - (len % 4));
- len += 4 - (len % 4);
- }
if (~crc != le32_to_cpu(fr_crc(fp))) {
crc_err:
- stats = lp->dev_stats[smp_processor_id()];
+ stats = per_cpu_ptr(lport->stats, get_cpu());
stats->ErrorFrames++;
- if (stats->InvalidCRCCount++ < 5)
- FC_DBG("CRC error on data frame\n");
+ /* per cpu count, not total count, but OK for limit */
+ if (stats->InvalidCRCCount++ < FC_MAX_ERROR_CNT)
+ printk(KERN_WARNING "libfc: CRC error on data "
+ "frame for port (%6.6x)\n",
+ lport->port_id);
+ put_cpu();
/*
* Assume the frame is total garbage.
* We may have copied it over the good part
@@ -368,8 +519,10 @@ crc_err:
* If so, we need to retry the entire operation.
* Otherwise, ignore it.
*/
- if (fsp->state & FC_SRB_DISCONTIG)
- fc_fcp_retry_cmd(fsp);
+ if (fsp->state & FC_SRB_DISCONTIG) {
+ host_bcode = FC_CRC_ERROR;
+ goto err;
+ }
return;
}
}
@@ -385,21 +538,23 @@ crc_err:
if (unlikely(fsp->state & FC_SRB_RCV_STATUS) &&
fsp->xfer_len == fsp->data_len - fsp->scsi_resid)
fc_fcp_complete_locked(fsp);
+ return;
+err:
+ fc_fcp_recovery(fsp, host_bcode);
}
-/*
- * fc_fcp_send_data - Send SCSI data to target.
- * @fsp: ptr to fc_fcp_pkt
- * @sp: ptr to this sequence
- * @offset: starting offset for this data request
- * @seq_blen: the burst length for this data request
+/**
+ * fc_fcp_send_data() - Send SCSI data to a target
+ * @fsp: The FCP packet the data is on
+ * @sp: The sequence the data is to be sent on
+ * @offset: The starting offset for this data request
+ * @seq_blen: The burst length for this data request
*
* Called after receiving a Transfer Ready data descriptor.
- * if LLD is capable of seq offload then send down seq_blen
- * size of data in single frame, otherwise send multiple FC
- * frames of max FC frame payload supported by target port.
- *
- * Returns : 0 for success.
+ * If the LLD is capable of sequence offload then send down the
+ * seq_blen amount of data in single frame, otherwise send
+ * multiple frames of the maximum frame payload supported by
+ * the target port.
*/
static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
size_t offset, size_t seq_blen)
@@ -408,29 +563,31 @@ static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
struct scsi_cmnd *sc;
struct scatterlist *sg;
struct fc_frame *fp = NULL;
- struct fc_lport *lp = fsp->lp;
+ struct fc_lport *lport = fsp->lp;
+ struct page *page;
size_t remaining;
size_t t_blen;
size_t tlen;
size_t sg_bytes;
size_t frame_offset, fh_parm_offset;
+ size_t off;
int error;
void *data = NULL;
void *page_addr;
- int using_sg = lp->sg_supp;
+ int using_sg = lport->sg_supp;
u32 f_ctl;
WARN_ON(seq_blen <= 0);
if (unlikely(offset + seq_blen > fsp->data_len)) {
/* this should never happen */
- FC_DEBUG_FCP("xfer-ready past end. seq_blen %zx offset %zx\n",
- seq_blen, offset);
+ FC_FCP_DBG(fsp, "xfer-ready past end. seq_blen %zx "
+ "offset %zx\n", seq_blen, offset);
fc_fcp_send_abort(fsp);
return 0;
} else if (offset != fsp->xfer_len) {
/* Out of Order Data Request - no problem, but unexpected. */
- FC_DEBUG_FCP("xfer-ready non-contiguous. "
- "seq_blen %zx offset %zx\n", seq_blen, offset);
+ FC_FCP_DBG(fsp, "xfer-ready non-contiguous. "
+ "seq_blen %zx offset %zx\n", seq_blen, offset);
}
/*
@@ -438,47 +595,24 @@ static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
* burst length (t_blen) to seq_blen, otherwise set t_blen
* to max FC frame payload previously set in fsp->max_payload.
*/
- t_blen = lp->seq_offload ? seq_blen : fsp->max_payload;
- WARN_ON(t_blen < FC_MIN_MAX_PAYLOAD);
+ t_blen = fsp->max_payload;
+ if (lport->seq_offload) {
+ t_blen = min(seq_blen, (size_t)lport->lso_max);
+ FC_FCP_DBG(fsp, "fsp=%p:lso:blen=%zx lso_max=0x%x t_blen=%zx\n",
+ fsp, seq_blen, lport->lso_max, t_blen);
+ }
+
if (t_blen > 512)
t_blen &= ~(512 - 1); /* round down to block size */
- WARN_ON(t_blen < FC_MIN_MAX_PAYLOAD); /* won't go below 256 */
sc = fsp->cmd;
remaining = seq_blen;
fh_parm_offset = frame_offset = offset;
tlen = 0;
- seq = lp->tt.seq_start_next(seq);
+ seq = lport->tt.seq_start_next(seq);
f_ctl = FC_FC_REL_OFF;
WARN_ON(!seq);
- /*
- * If a get_page()/put_page() will fail, don't use sg lists
- * in the fc_frame structure.
- *
- * The put_page() may be long after the I/O has completed
- * in the case of FCoE, since the network driver does it
- * via free_skb(). See the test in free_pages_check().
- *
- * Test this case with 'dd </dev/zero >/dev/st0 bs=64k'.
- */
- if (using_sg) {
- for (sg = scsi_sglist(sc); sg; sg = sg_next(sg)) {
- if (page_count(sg_page(sg)) == 0 ||
- (sg_page(sg)->flags & (1 << PG_lru |
- 1 << PG_private |
- 1 << PG_locked |
- 1 << PG_active |
- 1 << PG_slab |
- 1 << PG_swapcache |
- 1 << PG_writeback |
- 1 << PG_reserved |
- 1 << PG_buddy))) {
- using_sg = 0;
- break;
- }
- }
- }
sg = scsi_sglist(sc);
while (remaining > 0 && sg) {
@@ -497,48 +631,37 @@ static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
*/
if (tlen % 4)
using_sg = 0;
- if (using_sg) {
- fp = _fc_frame_alloc(lp, 0);
- if (!fp)
- return -ENOMEM;
- } else {
- fp = fc_frame_alloc(lp, tlen);
- if (!fp)
- return -ENOMEM;
+ fp = fc_frame_alloc(lport, using_sg ? 0 : tlen);
+ if (!fp)
+ return -ENOMEM;
- data = (void *)(fr_hdr(fp)) +
- sizeof(struct fc_frame_header);
- }
+ data = fc_frame_header_get(fp) + 1;
fh_parm_offset = frame_offset;
fr_max_payload(fp) = fsp->max_payload;
}
+
+ off = offset + sg->offset;
sg_bytes = min(tlen, sg->length - offset);
+ sg_bytes = min(sg_bytes,
+ (size_t) (PAGE_SIZE - (off & ~PAGE_MASK)));
+ page = sg_page(sg) + (off >> PAGE_SHIFT);
if (using_sg) {
- WARN_ON(skb_shinfo(fp_skb(fp))->nr_frags >
- FC_FRAME_SG_LEN);
- get_page(sg_page(sg));
+ get_page(page);
skb_fill_page_desc(fp_skb(fp),
skb_shinfo(fp_skb(fp))->nr_frags,
- sg_page(sg), sg->offset + offset,
- sg_bytes);
+ page, off & ~PAGE_MASK, sg_bytes);
fp_skb(fp)->data_len += sg_bytes;
fr_len(fp) += sg_bytes;
fp_skb(fp)->truesize += PAGE_SIZE;
} else {
- size_t off = offset + sg->offset;
-
/*
* The scatterlist item may be bigger than PAGE_SIZE,
* but we must not cross pages inside the kmap.
*/
- sg_bytes = min(sg_bytes, (size_t) (PAGE_SIZE -
- (off & ~PAGE_MASK)));
- page_addr = kmap_atomic(sg_page(sg) +
- (off >> PAGE_SHIFT),
- KM_SOFTIRQ0);
+ page_addr = kmap_atomic(page);
memcpy(data, (char *)page_addr + (off & ~PAGE_MASK),
sg_bytes);
- kunmap_atomic(page_addr, KM_SOFTIRQ0);
+ kunmap_atomic(page_addr);
data += sg_bytes;
}
offset += sg_bytes;
@@ -546,7 +669,8 @@ static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
tlen -= sg_bytes;
remaining -= sg_bytes;
- if (tlen)
+ if ((skb_shinfo(fp_skb(fp))->nr_frags < FC_FRAME_SG_LEN) &&
+ (tlen))
continue;
/*
@@ -563,11 +687,10 @@ static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
/*
* send fragment using for a sequence.
*/
- error = lp->tt.seq_send(lp, seq, fp);
+ error = lport->tt.seq_send(lport, seq, fp);
if (error) {
WARN_ON(1); /* send error should be rare */
- fc_fcp_retry_cmd(fsp);
- return 0;
+ return error;
}
fp = NULL;
}
@@ -575,6 +698,11 @@ static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
return 0;
}
+/**
+ * fc_fcp_abts_resp() - Receive an ABTS response
+ * @fsp: The FCP packet that is being aborted
+ * @fp: The response frame
+ */
static void fc_fcp_abts_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
{
int ba_done = 1;
@@ -610,67 +738,36 @@ static void fc_fcp_abts_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
}
}
-/*
- * fc_fcp_reduce_can_queue - drop can_queue
- * @lp: lport to drop queueing for
- *
- * If we are getting memory allocation failures, then we may
- * be trying to execute too many commands. We let the running
- * commands complete or timeout, then try again with a reduced
- * can_queue. Eventually we will hit the point where we run
- * on all reserved structs.
- */
-static void fc_fcp_reduce_can_queue(struct fc_lport *lp)
-{
- struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
- unsigned long flags;
- int can_queue;
-
- spin_lock_irqsave(lp->host->host_lock, flags);
- if (si->throttled)
- goto done;
- si->throttled = 1;
-
- can_queue = lp->host->can_queue;
- can_queue >>= 1;
- if (!can_queue)
- can_queue = 1;
- lp->host->can_queue = can_queue;
- shost_printk(KERN_ERR, lp->host, "Could not allocate frame.\n"
- "Reducing can_queue to %d.\n", can_queue);
-done:
- spin_unlock_irqrestore(lp->host->host_lock, flags);
-}
-
-/*
- * exch mgr calls this routine to process scsi
- * exchanges.
+/**
+ * fc_fcp_recv() - Receive an FCP frame
+ * @seq: The sequence the frame is on
+ * @fp: The received frame
+ * @arg: The related FCP packet
*
- * Return : None
- * Context : called from Soft IRQ context
- * can not called holding list lock
+ * Context: Called from Soft IRQ context. Can not be called
+ * holding the FCP packet list lock.
*/
static void fc_fcp_recv(struct fc_seq *seq, struct fc_frame *fp, void *arg)
{
struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)arg;
- struct fc_lport *lp;
+ struct fc_lport *lport = fsp->lp;
struct fc_frame_header *fh;
struct fcp_txrdy *dd;
u8 r_ctl;
int rc = 0;
- if (IS_ERR(fp))
- goto errout;
+ if (IS_ERR(fp)) {
+ fc_fcp_error(fsp, fp);
+ return;
+ }
fh = fc_frame_header_get(fp);
r_ctl = fh->fh_r_ctl;
- lp = fsp->lp;
- if (!(lp->state & LPORT_ST_READY))
+ if (lport->state != LPORT_ST_READY)
goto out;
if (fc_fcp_lock_pkt(fsp))
goto out;
- fsp->last_pkt_time = jiffies;
if (fh->fh_type == FC_TYPE_BLS) {
fc_fcp_abts_resp(fsp, fp);
@@ -694,8 +791,6 @@ static void fc_fcp_recv(struct fc_seq *seq, struct fc_frame *fp, void *arg)
(size_t) ntohl(dd->ft_burst_len));
if (!rc)
seq->rec_data = fsp->xfer_len;
- else if (rc == -ENOMEM)
- fsp->state |= FC_SRB_NOMEM;
} else if (r_ctl == FC_RCTL_DD_SOL_DATA) {
/*
* received a DATA frame
@@ -709,19 +804,19 @@ static void fc_fcp_recv(struct fc_seq *seq, struct fc_frame *fp, void *arg)
fc_fcp_resp(fsp, fp);
} else {
- FC_DBG("unexpected frame. r_ctl %x\n", r_ctl);
+ FC_FCP_DBG(fsp, "unexpected frame. r_ctl %x\n", r_ctl);
}
unlock:
fc_fcp_unlock_pkt(fsp);
out:
fc_frame_free(fp);
-errout:
- if (IS_ERR(fp))
- fc_fcp_error(fsp, fp);
- else if (rc == -ENOMEM)
- fc_fcp_reduce_can_queue(lp);
}
+/**
+ * fc_fcp_resp() - Handler for FCP responses
+ * @fsp: The FCP packet the response is for
+ * @fp: The response frame
+ */
static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
{
struct fc_frame_header *fh;
@@ -745,6 +840,9 @@ static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
fsp->scsi_comp_flags = flags;
expected_len = fsp->data_len;
+ /* if ddp, update xfer len */
+ fc_fcp_ddp_done(fsp);
+
if (unlikely((flags & ~FCP_CONF_REQ) || fc_rp->fr_status)) {
rp_ex = (void *)(fc_rp + 1);
if (flags & (FCP_RSP_LEN_VAL | FCP_SNS_LEN_VAL)) {
@@ -753,7 +851,8 @@ static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
fc_rp_info = (struct fcp_resp_rsp_info *)(rp_ex + 1);
if (flags & FCP_RSP_LEN_VAL) {
respl = ntohl(rp_ex->fr_rsp_len);
- if (respl != sizeof(*fc_rp_info))
+ if ((respl != FCP_RESP_RSP_INFO_LEN4) &&
+ (respl != FCP_RESP_RSP_INFO_LEN8))
goto len_err;
if (fsp->wait_for_comp) {
/* Abuse cdb_status for rsp code */
@@ -764,8 +863,7 @@ static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
* exit here
*/
return;
- } else
- goto err;
+ }
}
if (flags & FCP_SNS_LEN_VAL) {
snsl = ntohl(rp_ex->fr_sns_len);
@@ -782,7 +880,7 @@ static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
fsp->scsi_resid = ntohl(rp_ex->fr_resid);
/*
* The cmnd->underflow is the minimum number of
- * bytes that must be transfered for this
+ * bytes that must be transferred for this
* command. Provided a sense condition is not
* present, make sure the actual amount
* transferred is at least the underflow value
@@ -804,7 +902,8 @@ static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
/*
* Check for missing or extra data frames.
*/
- if (unlikely(fsp->xfer_len != expected_len)) {
+ if (unlikely(fsp->cdb_status == SAM_STAT_GOOD &&
+ fsp->xfer_len != expected_len)) {
if (fsp->xfer_len < expected_len) {
/*
* Some data may be queued locally,
@@ -815,32 +914,33 @@ static void fc_fcp_resp(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
return;
}
fsp->status_code = FC_DATA_OVRRUN;
- FC_DBG("tgt %6x xfer len %zx greater than expected len %x. "
- "data len %x\n",
- fsp->rport->port_id,
- fsp->xfer_len, expected_len, fsp->data_len);
+ FC_FCP_DBG(fsp, "tgt %6.6x xfer len %zx greater than expected, "
+ "len %x, data len %x\n",
+ fsp->rport->port_id,
+ fsp->xfer_len, expected_len, fsp->data_len);
}
fc_fcp_complete_locked(fsp);
return;
len_err:
- FC_DBG("short FCP response. flags 0x%x len %u respl %u snsl %u\n",
- flags, fr_len(fp), respl, snsl);
+ FC_FCP_DBG(fsp, "short FCP response. flags 0x%x len %u respl %u "
+ "snsl %u\n", flags, fr_len(fp), respl, snsl);
err:
fsp->status_code = FC_ERROR;
fc_fcp_complete_locked(fsp);
}
/**
- * fc_fcp_complete_locked - complete processing of a fcp packet
- * @fsp: fcp packet
+ * fc_fcp_complete_locked() - Complete processing of a fcp_pkt with the
+ * fcp_pkt lock held
+ * @fsp: The FCP packet to be completed
*
* This function may sleep if a timer is pending. The packet lock must be
* held, and the host lock must not be held.
*/
static void fc_fcp_complete_locked(struct fc_fcp_pkt *fsp)
{
- struct fc_lport *lp = fsp->lp;
+ struct fc_lport *lport = fsp->lp;
struct fc_seq *seq;
struct fc_exch *ep;
u32 f_ctl;
@@ -856,12 +956,11 @@ static void fc_fcp_complete_locked(struct fc_fcp_pkt *fsp)
* Test for transport underrun, independent of response
* underrun status.
*/
- if (fsp->xfer_len < fsp->data_len && !fsp->io_status &&
+ if (fsp->cdb_status == SAM_STAT_GOOD &&
+ fsp->xfer_len < fsp->data_len && !fsp->io_status &&
(!(fsp->scsi_comp_flags & FCP_RESID_UNDER) ||
- fsp->xfer_len < fsp->data_len - fsp->scsi_resid)) {
+ fsp->xfer_len < fsp->data_len - fsp->scsi_resid))
fsp->status_code = FC_DATA_UNDRUN;
- fsp->io_status = SUGGEST_RETRY << 24;
- }
}
seq = fsp->seq_ptr;
@@ -871,8 +970,8 @@ static void fc_fcp_complete_locked(struct fc_fcp_pkt *fsp)
struct fc_frame *conf_frame;
struct fc_seq *csp;
- csp = lp->tt.seq_start_next(seq);
- conf_frame = fc_frame_alloc(fsp->lp, 0);
+ csp = lport->tt.seq_start_next(seq);
+ conf_frame = fc_fcp_frame_alloc(fsp->lp, 0);
if (conf_frame) {
f_ctl = FC_FC_SEQ_INIT;
f_ctl |= FC_FC_LAST_SEQ | FC_FC_END_SEQ;
@@ -880,43 +979,54 @@ static void fc_fcp_complete_locked(struct fc_fcp_pkt *fsp)
fc_fill_fc_hdr(conf_frame, FC_RCTL_DD_SOL_CTL,
ep->did, ep->sid,
FC_TYPE_FCP, f_ctl, 0);
- lp->tt.seq_send(lp, csp, conf_frame);
+ lport->tt.seq_send(lport, csp, conf_frame);
}
}
- lp->tt.exch_done(seq);
+ lport->tt.exch_done(seq);
}
- fc_io_compl(fsp);
+ /*
+ * Some resets driven by SCSI are not I/Os and do not have
+ * SCSI commands associated with the requests. We should not
+ * call I/O completion if we do not have a SCSI command.
+ */
+ if (fsp->cmd)
+ fc_io_compl(fsp);
}
+/**
+ * fc_fcp_cleanup_cmd() - Cancel the active exchange on a fcp_pkt
+ * @fsp: The FCP packet whose exchanges should be canceled
+ * @error: The reason for the cancellation
+ */
static void fc_fcp_cleanup_cmd(struct fc_fcp_pkt *fsp, int error)
{
- struct fc_lport *lp = fsp->lp;
+ struct fc_lport *lport = fsp->lp;
if (fsp->seq_ptr) {
- lp->tt.exch_done(fsp->seq_ptr);
+ lport->tt.exch_done(fsp->seq_ptr);
fsp->seq_ptr = NULL;
}
fsp->status_code = error;
}
/**
- * fc_fcp_cleanup_each_cmd - run fn on each active command
- * @lp: logical port
- * @id: target id
- * @lun: lun
- * @error: fsp status code
+ * fc_fcp_cleanup_each_cmd() - Cancel all exchanges on a local port
+ * @lport: The local port whose exchanges should be canceled
+ * @id: The target's ID
+ * @lun: The LUN
+ * @error: The reason for cancellation
*
* If lun or id is -1, they are ignored.
*/
-static void fc_fcp_cleanup_each_cmd(struct fc_lport *lp, unsigned int id,
+static void fc_fcp_cleanup_each_cmd(struct fc_lport *lport, unsigned int id,
unsigned int lun, int error)
{
- struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
+ struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
struct fc_fcp_pkt *fsp;
struct scsi_cmnd *sc_cmd;
unsigned long flags;
- spin_lock_irqsave(lp->host->host_lock, flags);
+ spin_lock_irqsave(&si->scsi_queue_lock, flags);
restart:
list_for_each_entry(fsp, &si->scsi_pkt_queue, list) {
sc_cmd = fsp->cmd;
@@ -927,7 +1037,7 @@ restart:
continue;
fc_fcp_pkt_hold(fsp);
- spin_unlock_irqrestore(lp->host->host_lock, flags);
+ spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
if (!fc_fcp_lock_pkt(fsp)) {
fc_fcp_cleanup_cmd(fsp, error);
@@ -936,56 +1046,80 @@ restart:
}
fc_fcp_pkt_release(fsp);
- spin_lock_irqsave(lp->host->host_lock, flags);
+ spin_lock_irqsave(&si->scsi_queue_lock, flags);
/*
* while we dropped the lock multiple pkts could
* have been released, so we have to start over.
*/
goto restart;
}
- spin_unlock_irqrestore(lp->host->host_lock, flags);
+ spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
}
-static void fc_fcp_abort_io(struct fc_lport *lp)
+/**
+ * fc_fcp_abort_io() - Abort all FCP-SCSI exchanges on a local port
+ * @lport: The local port whose exchanges are to be aborted
+ */
+static void fc_fcp_abort_io(struct fc_lport *lport)
{
- fc_fcp_cleanup_each_cmd(lp, -1, -1, FC_HRD_ERROR);
+ fc_fcp_cleanup_each_cmd(lport, -1, -1, FC_HRD_ERROR);
}
/**
- * fc_fcp_pkt_send - send a fcp packet to the lower level.
- * @lp: fc lport
- * @fsp: fc packet.
+ * fc_fcp_pkt_send() - Send a fcp_pkt
+ * @lport: The local port to send the FCP packet on
+ * @fsp: The FCP packet to send
*
- * This is called by upper layer protocol.
- * Return : zero for success and -1 for failure
- * Context : called from queuecommand which can be called from process
- * or scsi soft irq.
- * Locks : called with the host lock and irqs disabled.
+ * Return: Zero for success and -1 for failure
+ * Locks: Called without locks held
*/
-static int fc_fcp_pkt_send(struct fc_lport *lp, struct fc_fcp_pkt *fsp)
+static int fc_fcp_pkt_send(struct fc_lport *lport, struct fc_fcp_pkt *fsp)
{
- struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
+ struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
+ unsigned long flags;
int rc;
fsp->cmd->SCp.ptr = (char *)fsp;
fsp->cdb_cmd.fc_dl = htonl(fsp->data_len);
fsp->cdb_cmd.fc_flags = fsp->req_flags & ~FCP_CFL_LEN_MASK;
- int_to_scsilun(fsp->cmd->device->lun,
- (struct scsi_lun *)fsp->cdb_cmd.fc_lun);
+ int_to_scsilun(fsp->cmd->device->lun, &fsp->cdb_cmd.fc_lun);
memcpy(fsp->cdb_cmd.fc_cdb, fsp->cmd->cmnd, fsp->cmd->cmd_len);
- list_add_tail(&fsp->list, &si->scsi_pkt_queue);
- spin_unlock_irq(lp->host->host_lock);
- rc = lp->tt.fcp_cmd_send(lp, fsp, fc_fcp_recv);
- spin_lock_irq(lp->host->host_lock);
- if (rc)
+ spin_lock_irqsave(&si->scsi_queue_lock, flags);
+ list_add_tail(&fsp->list, &si->scsi_pkt_queue);
+ spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
+ rc = lport->tt.fcp_cmd_send(lport, fsp, fc_fcp_recv);
+ if (unlikely(rc)) {
+ spin_lock_irqsave(&si->scsi_queue_lock, flags);
+ fsp->cmd->SCp.ptr = NULL;
list_del(&fsp->list);
+ spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
+ }
return rc;
}
-static int fc_fcp_cmd_send(struct fc_lport *lp, struct fc_fcp_pkt *fsp,
+/**
+ * get_fsp_rec_tov() - Helper function to get REC_TOV
+ * @fsp: the FCP packet
+ *
+ * Returns rec tov in jiffies as rpriv->e_d_tov + 1 second
+ */
+static inline unsigned int get_fsp_rec_tov(struct fc_fcp_pkt *fsp)
+{
+ struct fc_rport_libfc_priv *rpriv = fsp->rport->dd_data;
+
+ return msecs_to_jiffies(rpriv->e_d_tov) + HZ;
+}
+
+/**
+ * fc_fcp_cmd_send() - Send a FCP command
+ * @lport: The local port to send the command on
+ * @fsp: The FCP packet the command is on
+ * @resp: The handler for the response
+ */
+static int fc_fcp_cmd_send(struct fc_lport *lport, struct fc_fcp_pkt *fsp,
void (*resp)(struct fc_seq *,
struct fc_frame *fp,
void *arg))
@@ -993,50 +1127,51 @@ static int fc_fcp_cmd_send(struct fc_lport *lp, struct fc_fcp_pkt *fsp,
struct fc_frame *fp;
struct fc_seq *seq;
struct fc_rport *rport;
- struct fc_rport_libfc_priv *rp;
+ struct fc_rport_libfc_priv *rpriv;
const size_t len = sizeof(fsp->cdb_cmd);
int rc = 0;
if (fc_fcp_lock_pkt(fsp))
return 0;
- fp = fc_frame_alloc(lp, sizeof(fsp->cdb_cmd));
+ fp = fc_fcp_frame_alloc(lport, sizeof(fsp->cdb_cmd));
if (!fp) {
rc = -1;
goto unlock;
}
memcpy(fc_frame_payload_get(fp, len), &fsp->cdb_cmd, len);
- fr_cmd(fp) = fsp->cmd;
+ fr_fsp(fp) = fsp;
rport = fsp->rport;
fsp->max_payload = rport->maxframe_size;
- rp = rport->dd_data;
+ rpriv = rport->dd_data;
fc_fill_fc_hdr(fp, FC_RCTL_DD_UNSOL_CMD, rport->port_id,
- fc_host_port_id(rp->local_port->host), FC_TYPE_FCP,
- FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
+ rpriv->local_port->port_id, FC_TYPE_FCP,
+ FC_FCTL_REQ, 0);
- seq = lp->tt.exch_seq_send(lp, fp, resp, fc_fcp_pkt_destroy, fsp, 0);
+ seq = lport->tt.exch_seq_send(lport, fp, resp, fc_fcp_pkt_destroy,
+ fsp, 0);
if (!seq) {
- fc_frame_free(fp);
rc = -1;
goto unlock;
}
- fsp->last_pkt_time = jiffies;
fsp->seq_ptr = seq;
fc_fcp_pkt_hold(fsp); /* hold for fc_fcp_pkt_destroy */
setup_timer(&fsp->timer, fc_fcp_timeout, (unsigned long)fsp);
- fc_fcp_timer_set(fsp,
- (fsp->tgt_flags & FC_RP_FLAGS_REC_SUPPORTED) ?
- FC_SCSI_REC_TOV : FC_SCSI_ER_TIMEOUT);
+ if (rpriv->flags & FC_RP_FLAGS_REC_SUPPORTED)
+ fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
+
unlock:
fc_fcp_unlock_pkt(fsp);
return rc;
}
-/*
- * transport error handler
+/**
+ * fc_fcp_error() - Handler for FCP layer errors
+ * @fsp: The FCP packet the error is on
+ * @fp: The frame that has errored
*/
static void fc_fcp_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
{
@@ -1045,13 +1180,11 @@ static void fc_fcp_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
if (fc_fcp_lock_pkt(fsp))
return;
- switch (error) {
- case -FC_EX_CLOSED:
+ if (error == -FC_EX_CLOSED) {
fc_fcp_retry_cmd(fsp);
goto unlock;
- default:
- FC_DBG("unknown error %ld\n", PTR_ERR(fp));
}
+
/*
* clear abort pending, because the lower layer
* decided to force completion.
@@ -1063,13 +1196,16 @@ unlock:
fc_fcp_unlock_pkt(fsp);
}
-/*
- * Scsi abort handler- calls to send an abort
- * and then wait for abort completion
+/**
+ * fc_fcp_pkt_abort() - Abort a fcp_pkt
+ * @fsp: The FCP packet to abort on
+ *
+ * Called to send an abort and then wait for abort completion
*/
-static int fc_fcp_pkt_abort(struct fc_lport *lp, struct fc_fcp_pkt *fsp)
+static int fc_fcp_pkt_abort(struct fc_fcp_pkt *fsp)
{
int rc = FAILED;
+ unsigned long ticks_left;
if (fc_fcp_send_abort(fsp))
return FAILED;
@@ -1078,15 +1214,15 @@ static int fc_fcp_pkt_abort(struct fc_lport *lp, struct fc_fcp_pkt *fsp)
fsp->wait_for_comp = 1;
spin_unlock_bh(&fsp->scsi_pkt_lock);
- rc = wait_for_completion_timeout(&fsp->tm_done, FC_SCSI_TM_TOV);
+ ticks_left = wait_for_completion_timeout(&fsp->tm_done,
+ FC_SCSI_TM_TOV);
spin_lock_bh(&fsp->scsi_pkt_lock);
fsp->wait_for_comp = 0;
- if (!rc) {
- FC_DBG("target abort cmd failed\n");
- rc = FAILED;
+ if (!ticks_left) {
+ FC_FCP_DBG(fsp, "target abort cmd failed\n");
} else if (fsp->state & FC_SRB_ABORTED) {
- FC_DBG("target abort cmd passed\n");
+ FC_FCP_DBG(fsp, "target abort cmd passed\n");
rc = SUCCESS;
fc_fcp_complete_locked(fsp);
}
@@ -1094,36 +1230,42 @@ static int fc_fcp_pkt_abort(struct fc_lport *lp, struct fc_fcp_pkt *fsp)
return rc;
}
-/*
- * Retry LUN reset after resource allocation failed.
+/**
+ * fc_lun_reset_send() - Send LUN reset command
+ * @data: The FCP packet that identifies the LUN to be reset
*/
static void fc_lun_reset_send(unsigned long data)
{
struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)data;
- struct fc_lport *lp = fsp->lp;
- if (lp->tt.fcp_cmd_send(lp, fsp, fc_tm_done)) {
+ struct fc_lport *lport = fsp->lp;
+
+ if (lport->tt.fcp_cmd_send(lport, fsp, fc_tm_done)) {
if (fsp->recov_retry++ >= FC_MAX_RECOV_RETRY)
return;
if (fc_fcp_lock_pkt(fsp))
return;
setup_timer(&fsp->timer, fc_lun_reset_send, (unsigned long)fsp);
- fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
+ fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
fc_fcp_unlock_pkt(fsp);
}
}
-/*
- * Scsi device reset handler- send a LUN RESET to the device
- * and wait for reset reply
+/**
+ * fc_lun_reset() - Send a LUN RESET command to a device
+ * and wait for the reply
+ * @lport: The local port to sent the command on
+ * @fsp: The FCP packet that identifies the LUN to be reset
+ * @id: The SCSI command ID
+ * @lun: The LUN ID to be reset
*/
-static int fc_lun_reset(struct fc_lport *lp, struct fc_fcp_pkt *fsp,
+static int fc_lun_reset(struct fc_lport *lport, struct fc_fcp_pkt *fsp,
unsigned int id, unsigned int lun)
{
int rc;
fsp->cdb_cmd.fc_dl = htonl(fsp->data_len);
fsp->cdb_cmd.fc_tm_flags = FCP_TMF_LUN_RESET;
- int_to_scsilun(lun, (struct scsi_lun *)fsp->cdb_cmd.fc_lun);
+ int_to_scsilun(lun, &fsp->cdb_cmd.fc_lun);
fsp->wait_for_comp = 1;
init_completion(&fsp->tm_done);
@@ -1144,14 +1286,14 @@ static int fc_lun_reset(struct fc_lport *lp, struct fc_fcp_pkt *fsp,
spin_lock_bh(&fsp->scsi_pkt_lock);
if (fsp->seq_ptr) {
- lp->tt.exch_done(fsp->seq_ptr);
+ lport->tt.exch_done(fsp->seq_ptr);
fsp->seq_ptr = NULL;
}
fsp->wait_for_comp = 0;
spin_unlock_bh(&fsp->scsi_pkt_lock);
if (!rc) {
- FC_DBG("lun reset failed\n");
+ FC_SCSI_DBG(lport, "lun reset failed\n");
return FAILED;
}
@@ -1159,13 +1301,16 @@ static int fc_lun_reset(struct fc_lport *lp, struct fc_fcp_pkt *fsp,
if (fsp->cdb_status != FCP_TMF_CMPL)
return FAILED;
- FC_DBG("lun reset to lun %u completed\n", lun);
- fc_fcp_cleanup_each_cmd(lp, id, lun, FC_CMD_ABORTED);
+ FC_SCSI_DBG(lport, "lun reset to lun %u completed\n", lun);
+ fc_fcp_cleanup_each_cmd(lport, id, lun, FC_CMD_ABORTED);
return SUCCESS;
}
-/*
- * Task Managment response handler
+/**
+ * fc_tm_done() - Task Management response handler
+ * @seq: The sequence that the response is on
+ * @fp: The response frame
+ * @arg: The FCP packet the response is for
*/
static void fc_tm_done(struct fc_seq *seq, struct fc_frame *fp, void *arg)
{
@@ -1183,53 +1328,50 @@ static void fc_tm_done(struct fc_seq *seq, struct fc_frame *fp, void *arg)
}
if (fc_fcp_lock_pkt(fsp))
- return;
+ goto out;
/*
* raced with eh timeout handler.
*/
- if (!fsp->seq_ptr || !fsp->wait_for_comp) {
- spin_unlock_bh(&fsp->scsi_pkt_lock);
- return;
- }
+ if (!fsp->seq_ptr || !fsp->wait_for_comp)
+ goto out_unlock;
fh = fc_frame_header_get(fp);
if (fh->fh_type != FC_TYPE_BLS)
fc_fcp_resp(fsp, fp);
fsp->seq_ptr = NULL;
fsp->lp->tt.exch_done(seq);
- fc_frame_free(fp);
+out_unlock:
fc_fcp_unlock_pkt(fsp);
+out:
+ fc_frame_free(fp);
}
-static void fc_fcp_cleanup(struct fc_lport *lp)
+/**
+ * fc_fcp_cleanup() - Cleanup all FCP exchanges on a local port
+ * @lport: The local port to be cleaned up
+ */
+static void fc_fcp_cleanup(struct fc_lport *lport)
{
- fc_fcp_cleanup_each_cmd(lp, -1, -1, FC_ERROR);
+ fc_fcp_cleanup_each_cmd(lport, -1, -1, FC_ERROR);
}
-/*
- * fc_fcp_timeout: called by OS timer function.
- *
- * The timer has been inactivated and must be reactivated if desired
- * using fc_fcp_timer_set().
- *
- * Algorithm:
- *
- * If REC is supported, just issue it, and return. The REC exchange will
- * complete or time out, and recovery can continue at that point.
- *
- * Otherwise, if the response has been received without all the data,
- * it has been ER_TIMEOUT since the response was received.
+/**
+ * fc_fcp_timeout() - Handler for fcp_pkt timeouts
+ * @data: The FCP packet that has timed out
*
- * If the response has not been received,
- * we see if data was received recently. If it has been, we continue waiting,
- * otherwise, we abort the command.
+ * If REC is supported then just issue it and return. The REC exchange will
+ * complete or time out and recovery can continue at that point. Otherwise,
+ * if the response has been received without all the data it has been
+ * ER_TIMEOUT since the response was received. If the response has not been
+ * received we see if data was received recently. If it has been then we
+ * continue waiting, otherwise, we abort the command.
*/
static void fc_fcp_timeout(unsigned long data)
{
struct fc_fcp_pkt *fsp = (struct fc_fcp_pkt *)data;
struct fc_rport *rport = fsp->rport;
- struct fc_rport_libfc_priv *rp = rport->dd_data;
+ struct fc_rport_libfc_priv *rpriv = rport->dd_data;
if (fc_fcp_lock_pkt(fsp))
return;
@@ -1239,66 +1381,69 @@ static void fc_fcp_timeout(unsigned long data)
fsp->state |= FC_SRB_FCP_PROCESSING_TMO;
- if (rp->flags & FC_RP_FLAGS_REC_SUPPORTED)
+ if (rpriv->flags & FC_RP_FLAGS_REC_SUPPORTED)
fc_fcp_rec(fsp);
- else if (time_after_eq(fsp->last_pkt_time + (FC_SCSI_ER_TIMEOUT / 2),
- jiffies))
- fc_fcp_timer_set(fsp, FC_SCSI_ER_TIMEOUT);
else if (fsp->state & FC_SRB_RCV_STATUS)
fc_fcp_complete_locked(fsp);
else
- fc_timeout_error(fsp);
+ fc_fcp_recovery(fsp, FC_TIMED_OUT);
fsp->state &= ~FC_SRB_FCP_PROCESSING_TMO;
unlock:
fc_fcp_unlock_pkt(fsp);
}
-/*
- * Send a REC ELS request
+/**
+ * fc_fcp_rec() - Send a REC ELS request
+ * @fsp: The FCP packet to send the REC request on
*/
static void fc_fcp_rec(struct fc_fcp_pkt *fsp)
{
- struct fc_lport *lp;
+ struct fc_lport *lport;
struct fc_frame *fp;
struct fc_rport *rport;
- struct fc_rport_libfc_priv *rp;
+ struct fc_rport_libfc_priv *rpriv;
- lp = fsp->lp;
+ lport = fsp->lp;
rport = fsp->rport;
- rp = rport->dd_data;
- if (!fsp->seq_ptr || rp->rp_state != RPORT_ST_READY) {
+ rpriv = rport->dd_data;
+ if (!fsp->seq_ptr || rpriv->rp_state != RPORT_ST_READY) {
fsp->status_code = FC_HRD_ERROR;
- fsp->io_status = SUGGEST_RETRY << 24;
+ fsp->io_status = 0;
fc_fcp_complete_locked(fsp);
return;
}
- fp = fc_frame_alloc(lp, sizeof(struct fc_els_rec));
+
+ fp = fc_fcp_frame_alloc(lport, sizeof(struct fc_els_rec));
if (!fp)
goto retry;
fr_seq(fp) = fsp->seq_ptr;
fc_fill_fc_hdr(fp, FC_RCTL_ELS_REQ, rport->port_id,
- fc_host_port_id(rp->local_port->host), FC_TYPE_ELS,
- FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
- if (lp->tt.elsct_send(lp, rport, fp, ELS_REC, fc_fcp_rec_resp,
- fsp, jiffies_to_msecs(FC_SCSI_REC_TOV))) {
+ rpriv->local_port->port_id, FC_TYPE_ELS,
+ FC_FCTL_REQ, 0);
+ if (lport->tt.elsct_send(lport, rport->port_id, fp, ELS_REC,
+ fc_fcp_rec_resp, fsp,
+ 2 * lport->r_a_tov)) {
fc_fcp_pkt_hold(fsp); /* hold while REC outstanding */
return;
}
- fc_frame_free(fp);
retry:
if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
- fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
+ fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
else
- fc_timeout_error(fsp);
+ fc_fcp_recovery(fsp, FC_TIMED_OUT);
}
-/*
- * Receive handler for REC ELS frame
- * if it is a reject then let the scsi layer to handle
- * the timeout. if it is a LS_ACC then if the io was not completed
- * then set the timeout and return otherwise complete the exchange
- * and tell the scsi layer to restart the I/O.
+/**
+ * fc_fcp_rec_resp() - Handler for REC ELS responses
+ * @seq: The sequence the response is on
+ * @fp: The response frame
+ * @arg: The FCP packet the response is on
+ *
+ * If the response is a reject then the scsi layer will handle
+ * the timeout. If the response is a LS_ACC then if the I/O was not completed
+ * set the timeout and return. If the I/O was completed then complete the
+ * exchange and tell the SCSI layer.
*/
static void fc_fcp_rec_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
{
@@ -1310,7 +1455,7 @@ static void fc_fcp_rec_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
u32 offset;
enum dma_data_direction data_dir;
enum fc_rctl r_ctl;
- struct fc_rport_libfc_priv *rp;
+ struct fc_rport_libfc_priv *rpriv;
if (IS_ERR(fp)) {
fc_fcp_rec_error(fsp, fp);
@@ -1326,21 +1471,20 @@ static void fc_fcp_rec_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
rjt = fc_frame_payload_get(fp, sizeof(*rjt));
switch (rjt->er_reason) {
default:
- FC_DEBUG_FCP("device %x unexpected REC reject "
- "reason %d expl %d\n",
- fsp->rport->port_id, rjt->er_reason,
- rjt->er_explan);
+ FC_FCP_DBG(fsp, "device %x unexpected REC reject "
+ "reason %d expl %d\n",
+ fsp->rport->port_id, rjt->er_reason,
+ rjt->er_explan);
/* fall through */
case ELS_RJT_UNSUP:
- FC_DEBUG_FCP("device does not support REC\n");
- rp = fsp->rport->dd_data;
+ FC_FCP_DBG(fsp, "device does not support REC\n");
+ rpriv = fsp->rport->dd_data;
/*
* if we do not spport RECs or got some bogus
* reason then resetup timer so we check for
* making progress.
*/
- rp->flags &= ~FC_RP_FLAGS_REC_SUPPORTED;
- fc_fcp_timer_set(fsp, FC_SCSI_ER_TIMEOUT);
+ rpriv->flags &= ~FC_RP_FLAGS_REC_SUPPORTED;
break;
case ELS_RJT_LOGIC:
case ELS_RJT_UNAB:
@@ -1355,7 +1499,7 @@ static void fc_fcp_rec_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
fc_fcp_retry_cmd(fsp);
break;
}
- fc_timeout_error(fsp);
+ fc_fcp_recovery(fsp, FC_ERROR);
break;
}
} else if (opcode == ELS_LS_ACC) {
@@ -1393,12 +1537,11 @@ static void fc_fcp_rec_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
}
fc_fcp_srr(fsp, r_ctl, offset);
} else if (e_stat & ESB_ST_SEQ_INIT) {
-
/*
* The remote port has the initiative, so just
* keep waiting for it to complete.
*/
- fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
+ fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
} else {
/*
@@ -1436,8 +1579,10 @@ out:
fc_frame_free(fp);
}
-/*
- * Handle error response or timeout for REC exchange.
+/**
+ * fc_fcp_rec_error() - Handler for REC errors
+ * @fsp: The FCP packet the error is on
+ * @fp: The REC frame
*/
static void fc_fcp_rec_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
{
@@ -1452,8 +1597,8 @@ static void fc_fcp_rec_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
break;
default:
- FC_DBG("REC %p fid %x error unexpected error %d\n",
- fsp, fsp->rport->port_id, error);
+ FC_FCP_DBG(fsp, "REC %p fid %6.6x error unexpected error %d\n",
+ fsp, fsp->rport->port_id, error);
fsp->status_code = FC_CMD_PLOGO;
/* fall through */
@@ -1462,13 +1607,13 @@ static void fc_fcp_rec_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
* Assume REC or LS_ACC was lost.
* The exchange manager will have aborted REC, so retry.
*/
- FC_DBG("REC fid %x error error %d retry %d/%d\n",
- fsp->rport->port_id, error, fsp->recov_retry,
- FC_MAX_RECOV_RETRY);
+ FC_FCP_DBG(fsp, "REC fid %6.6x error error %d retry %d/%d\n",
+ fsp->rport->port_id, error, fsp->recov_retry,
+ FC_MAX_RECOV_RETRY);
if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
fc_fcp_rec(fsp);
else
- fc_timeout_error(fsp);
+ fc_fcp_recovery(fsp, FC_ERROR);
break;
}
fc_fcp_unlock_pkt(fsp);
@@ -1476,14 +1621,13 @@ out:
fc_fcp_pkt_release(fsp); /* drop hold for outstanding REC */
}
-/*
- * Time out error routine:
- * abort's the I/O close the exchange and
- * send completion notification to scsi layer
+/**
+ * fc_fcp_recovery() - Handler for fcp_pkt recovery
+ * @fsp: The FCP pkt that needs to be aborted
*/
-static void fc_timeout_error(struct fc_fcp_pkt *fsp)
+static void fc_fcp_recovery(struct fc_fcp_pkt *fsp, u8 code)
{
- fsp->status_code = FC_CMD_TIME_OUT;
+ fsp->status_code = code;
fsp->cdb_status = 0;
fsp->io_status = 0;
/*
@@ -1493,29 +1637,31 @@ static void fc_timeout_error(struct fc_fcp_pkt *fsp)
fc_fcp_send_abort(fsp);
}
-/*
- * Sequence retransmission request.
+/**
+ * fc_fcp_srr() - Send a SRR request (Sequence Retransmission Request)
+ * @fsp: The FCP packet the SRR is to be sent on
+ * @r_ctl: The R_CTL field for the SRR request
* This is called after receiving status but insufficient data, or
* when expecting status but the request has timed out.
*/
static void fc_fcp_srr(struct fc_fcp_pkt *fsp, enum fc_rctl r_ctl, u32 offset)
{
- struct fc_lport *lp = fsp->lp;
+ struct fc_lport *lport = fsp->lp;
struct fc_rport *rport;
- struct fc_rport_libfc_priv *rp;
+ struct fc_rport_libfc_priv *rpriv;
struct fc_exch *ep = fc_seq_exch(fsp->seq_ptr);
struct fc_seq *seq;
struct fcp_srr *srr;
struct fc_frame *fp;
- u8 cdb_op;
+ unsigned int rec_tov;
rport = fsp->rport;
- rp = rport->dd_data;
- cdb_op = fsp->cdb_cmd.fc_cdb[0];
+ rpriv = rport->dd_data;
- if (!(rp->flags & FC_RP_FLAGS_RETRY) || rp->rp_state != RPORT_ST_READY)
+ if (!(rpriv->flags & FC_RP_FLAGS_RETRY) ||
+ rpriv->rp_state != RPORT_ST_READY)
goto retry; /* shouldn't happen */
- fp = fc_frame_alloc(lp, sizeof(*srr));
+ fp = fc_fcp_frame_alloc(lport, sizeof(*srr));
if (!fp)
goto retry;
@@ -1528,15 +1674,16 @@ static void fc_fcp_srr(struct fc_fcp_pkt *fsp, enum fc_rctl r_ctl, u32 offset)
srr->srr_rel_off = htonl(offset);
fc_fill_fc_hdr(fp, FC_RCTL_ELS4_REQ, rport->port_id,
- fc_host_port_id(rp->local_port->host), FC_TYPE_FCP,
- FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
-
- seq = lp->tt.exch_seq_send(lp, fp, fc_fcp_srr_resp, NULL,
- fsp, jiffies_to_msecs(FC_SCSI_REC_TOV));
- if (!seq) {
- fc_frame_free(fp);
+ rpriv->local_port->port_id, FC_TYPE_FCP,
+ FC_FCTL_REQ, 0);
+
+ rec_tov = get_fsp_rec_tov(fsp);
+ seq = lport->tt.exch_seq_send(lport, fp, fc_fcp_srr_resp,
+ fc_fcp_pkt_destroy,
+ fsp, jiffies_to_msecs(rec_tov));
+ if (!seq)
goto retry;
- }
+
fsp->recov_seq = seq;
fsp->xfer_len = offset;
fsp->xfer_contig_end = offset;
@@ -1547,8 +1694,11 @@ retry:
fc_fcp_retry_cmd(fsp);
}
-/*
- * Handle response from SRR.
+/**
+ * fc_fcp_srr_resp() - Handler for SRR response
+ * @seq: The sequence the SRR is on
+ * @fp: The SRR frame
+ * @arg: The FCP packet the SRR is on
*/
static void fc_fcp_srr_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
{
@@ -1576,36 +1726,37 @@ static void fc_fcp_srr_resp(struct fc_seq *seq, struct fc_frame *fp, void *arg)
return;
}
- fsp->recov_seq = NULL;
switch (fc_frame_payload_op(fp)) {
case ELS_LS_ACC:
fsp->recov_retry = 0;
- fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
+ fc_fcp_timer_set(fsp, get_fsp_rec_tov(fsp));
break;
case ELS_LS_RJT:
default:
- fc_timeout_error(fsp);
+ fc_fcp_recovery(fsp, FC_ERROR);
break;
}
fc_fcp_unlock_pkt(fsp);
- fsp->lp->tt.exch_done(seq);
out:
+ fsp->lp->tt.exch_done(seq);
fc_frame_free(fp);
- fc_fcp_pkt_release(fsp); /* drop hold for outstanding SRR */
}
+/**
+ * fc_fcp_srr_error() - Handler for SRR errors
+ * @fsp: The FCP packet that the SRR error is on
+ * @fp: The SRR frame
+ */
static void fc_fcp_srr_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
{
if (fc_fcp_lock_pkt(fsp))
goto out;
- fsp->lp->tt.exch_done(fsp->recov_seq);
- fsp->recov_seq = NULL;
switch (PTR_ERR(fp)) {
case -FC_EX_TIMEOUT:
if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
fc_fcp_rec(fsp);
else
- fc_timeout_error(fsp);
+ fc_fcp_recovery(fsp, FC_TIMED_OUT);
break;
case -FC_EX_CLOSED: /* e.g., link failure */
/* fall through */
@@ -1615,40 +1766,42 @@ static void fc_fcp_srr_error(struct fc_fcp_pkt *fsp, struct fc_frame *fp)
}
fc_fcp_unlock_pkt(fsp);
out:
- fc_fcp_pkt_release(fsp); /* drop hold for outstanding SRR */
+ fsp->lp->tt.exch_done(fsp->recov_seq);
}
-static inline int fc_fcp_lport_queue_ready(struct fc_lport *lp)
+/**
+ * fc_fcp_lport_queue_ready() - Determine if the lport and it's queue is ready
+ * @lport: The local port to be checked
+ */
+static inline int fc_fcp_lport_queue_ready(struct fc_lport *lport)
{
/* lock ? */
- return (lp->state == LPORT_ST_READY) && (lp->link_status & FC_LINK_UP);
+ return (lport->state == LPORT_ST_READY) &&
+ lport->link_up && !lport->qfull;
}
/**
- * fc_queuecommand - The queuecommand function of the scsi template
- * @cmd: struct scsi_cmnd to be executed
- * @done: Callback function to be called when cmd is completed
+ * fc_queuecommand() - The queuecommand function of the SCSI template
+ * @shost: The Scsi_Host that the command was issued to
+ * @cmd: The scsi_cmnd to be executed
*
- * this is the i/o strategy routine, called by the scsi layer
- * this routine is called with holding the host_lock.
+ * This is the i/o strategy routine, called by the SCSI layer.
*/
-int fc_queuecommand(struct scsi_cmnd *sc_cmd, void (*done)(struct scsi_cmnd *))
+int fc_queuecommand(struct Scsi_Host *shost, struct scsi_cmnd *sc_cmd)
{
- struct fc_lport *lp;
+ struct fc_lport *lport = shost_priv(shost);
struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device));
struct fc_fcp_pkt *fsp;
- struct fc_rport_libfc_priv *rp;
+ struct fc_rport_libfc_priv *rpriv;
int rval;
int rc = 0;
- struct fcoe_dev_stats *stats;
-
- lp = shost_priv(sc_cmd->device->host);
+ struct fc_stats *stats;
rval = fc_remote_port_chkready(rport);
if (rval) {
sc_cmd->result = rval;
- done(sc_cmd);
- goto out;
+ sc_cmd->scsi_done(sc_cmd);
+ return 0;
}
if (!*(struct fc_remote_port **)rport->dd_data) {
@@ -1657,18 +1810,20 @@ int fc_queuecommand(struct scsi_cmnd *sc_cmd, void (*done)(struct scsi_cmnd *))
* online
*/
sc_cmd->result = DID_IMM_RETRY << 16;
- done(sc_cmd);
+ sc_cmd->scsi_done(sc_cmd);
goto out;
}
- rp = rport->dd_data;
+ rpriv = rport->dd_data;
- if (!fc_fcp_lport_queue_ready(lp)) {
+ if (!fc_fcp_lport_queue_ready(lport)) {
+ if (lport->qfull)
+ fc_fcp_can_queue_ramp_down(lport);
rc = SCSI_MLQUEUE_HOST_BUSY;
goto out;
}
- fsp = fc_fcp_pkt_alloc(lp, GFP_ATOMIC);
+ fsp = fc_fcp_pkt_alloc(lport, GFP_ATOMIC);
if (fsp == NULL) {
rc = SCSI_MLQUEUE_HOST_BUSY;
goto out;
@@ -1678,9 +1833,7 @@ int fc_queuecommand(struct scsi_cmnd *sc_cmd, void (*done)(struct scsi_cmnd *))
* build the libfc request pkt
*/
fsp->cmd = sc_cmd; /* save the cmd */
- fsp->lp = lp; /* save the softc ptr */
fsp->rport = rport; /* set the remote port ptr */
- sc_cmd->scsi_done = done;
/*
* set up the transfer length
@@ -1691,31 +1844,27 @@ int fc_queuecommand(struct scsi_cmnd *sc_cmd, void (*done)(struct scsi_cmnd *))
/*
* setup the data direction
*/
- stats = lp->dev_stats[smp_processor_id()];
+ stats = per_cpu_ptr(lport->stats, get_cpu());
if (sc_cmd->sc_data_direction == DMA_FROM_DEVICE) {
fsp->req_flags = FC_SRB_READ;
stats->InputRequests++;
- stats->InputMegabytes = fsp->data_len;
+ stats->InputBytes += fsp->data_len;
} else if (sc_cmd->sc_data_direction == DMA_TO_DEVICE) {
fsp->req_flags = FC_SRB_WRITE;
stats->OutputRequests++;
- stats->OutputMegabytes = fsp->data_len;
+ stats->OutputBytes += fsp->data_len;
} else {
fsp->req_flags = 0;
stats->ControlRequests++;
}
-
- fsp->tgt_flags = rp->flags;
-
- init_timer(&fsp->timer);
- fsp->timer.data = (unsigned long)fsp;
+ put_cpu();
/*
* send it to the lower layer
* if we get -1 return then put the request in the pending
* queue.
*/
- rval = fc_fcp_pkt_send(lp, fsp);
+ rval = fc_fcp_pkt_send(lport, fsp);
if (rval != 0) {
fsp->state = FC_SRB_FREE;
fc_fcp_pkt_release(fsp);
@@ -1727,20 +1876,22 @@ out:
EXPORT_SYMBOL(fc_queuecommand);
/**
- * fc_io_compl - Handle responses for completed commands
- * @fsp: scsi packet
- *
- * Translates a error to a Linux SCSI error.
+ * fc_io_compl() - Handle responses for completed commands
+ * @fsp: The FCP packet that is complete
*
+ * Translates fcp_pkt errors to a Linux SCSI errors.
* The fcp packet lock must be held when calling.
*/
static void fc_io_compl(struct fc_fcp_pkt *fsp)
{
struct fc_fcp_internal *si;
struct scsi_cmnd *sc_cmd;
- struct fc_lport *lp;
+ struct fc_lport *lport;
unsigned long flags;
+ /* release outstanding ddp context */
+ fc_fcp_ddp_done(fsp);
+
fsp->state |= FC_SRB_COMPL;
if (!(fsp->state & FC_SRB_FCP_PROCESSING_TMO)) {
spin_unlock_bh(&fsp->scsi_pkt_lock);
@@ -1748,31 +1899,17 @@ static void fc_io_compl(struct fc_fcp_pkt *fsp)
spin_lock_bh(&fsp->scsi_pkt_lock);
}
- lp = fsp->lp;
- si = fc_get_scsi_internal(lp);
- spin_lock_irqsave(lp->host->host_lock, flags);
- if (!fsp->cmd) {
- spin_unlock_irqrestore(lp->host->host_lock, flags);
- return;
- }
+ lport = fsp->lp;
+ si = fc_get_scsi_internal(lport);
/*
- * if a command timed out while we had to try and throttle IO
- * and it is now getting cleaned up, then we are about to
- * try again so clear the throttled flag incase we get more
- * time outs.
+ * if can_queue ramp down is done then try can_queue ramp up
+ * since commands are completing now.
*/
- if (si->throttled && fsp->state & FC_SRB_NOMEM)
- si->throttled = 0;
+ if (si->last_can_queue_ramp_down_time)
+ fc_fcp_can_queue_ramp_up(lport);
sc_cmd = fsp->cmd;
- fsp->cmd = NULL;
-
- if (!sc_cmd->SCp.ptr) {
- spin_unlock_irqrestore(lp->host->host_lock, flags);
- return;
- }
-
CMD_SCSI_STATUS(sc_cmd) = fsp->cdb_status;
switch (fsp->status_code) {
case FC_COMPLETE:
@@ -1783,21 +1920,6 @@ static void fc_io_compl(struct fc_fcp_pkt *fsp)
sc_cmd->result = DID_OK << 16;
if (fsp->scsi_resid)
CMD_RESID_LEN(sc_cmd) = fsp->scsi_resid;
- } else if (fsp->cdb_status == QUEUE_FULL) {
- struct scsi_device *tmp_sdev;
- struct scsi_device *sdev = sc_cmd->device;
-
- shost_for_each_device(tmp_sdev, sdev->host) {
- if (tmp_sdev->id != sdev->id)
- continue;
-
- if (tmp_sdev->queue_depth > 1) {
- scsi_track_queue_full(tmp_sdev,
- tmp_sdev->
- queue_depth - 1);
- }
- }
- sc_cmd->result = (DID_OK << 16) | fsp->cdb_status;
} else {
/*
* transport level I/O was ok but scsi
@@ -1807,19 +1929,29 @@ static void fc_io_compl(struct fc_fcp_pkt *fsp)
}
break;
case FC_ERROR:
+ FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
+ "due to FC_ERROR\n");
sc_cmd->result = DID_ERROR << 16;
break;
case FC_DATA_UNDRUN:
- if (fsp->cdb_status == 0) {
+ if ((fsp->cdb_status == 0) && !(fsp->req_flags & FC_SRB_READ)) {
/*
* scsi status is good but transport level
- * underrun. for read it should be an error??
+ * underrun.
*/
- sc_cmd->result = (DID_OK << 16) | fsp->cdb_status;
+ if (fsp->state & FC_SRB_RCV_STATUS) {
+ sc_cmd->result = DID_OK << 16;
+ } else {
+ FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml"
+ " due to FC_DATA_UNDRUN (trans)\n");
+ sc_cmd->result = DID_ERROR << 16;
+ }
} else {
/*
* scsi got underrun, this is an error
*/
+ FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
+ "due to FC_DATA_UNDRUN (scsi)\n");
CMD_RESID_LEN(sc_cmd) = fsp->scsi_resid;
sc_cmd->result = (DID_ERROR << 16) | fsp->cdb_status;
}
@@ -1828,81 +1960,92 @@ static void fc_io_compl(struct fc_fcp_pkt *fsp)
/*
* overrun is an error
*/
+ FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
+ "due to FC_DATA_OVRRUN\n");
sc_cmd->result = (DID_ERROR << 16) | fsp->cdb_status;
break;
case FC_CMD_ABORTED:
- sc_cmd->result = (DID_ABORT << 16) | fsp->io_status;
- break;
- case FC_CMD_TIME_OUT:
- sc_cmd->result = (DID_BUS_BUSY << 16) | fsp->io_status;
+ FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
+ "due to FC_CMD_ABORTED\n");
+ sc_cmd->result = (DID_ERROR << 16) | fsp->io_status;
break;
case FC_CMD_RESET:
+ FC_FCP_DBG(fsp, "Returning DID_RESET to scsi-ml "
+ "due to FC_CMD_RESET\n");
sc_cmd->result = (DID_RESET << 16);
break;
case FC_HRD_ERROR:
+ FC_FCP_DBG(fsp, "Returning DID_NO_CONNECT to scsi-ml "
+ "due to FC_HRD_ERROR\n");
sc_cmd->result = (DID_NO_CONNECT << 16);
break;
+ case FC_CRC_ERROR:
+ FC_FCP_DBG(fsp, "Returning DID_PARITY to scsi-ml "
+ "due to FC_CRC_ERROR\n");
+ sc_cmd->result = (DID_PARITY << 16);
+ break;
+ case FC_TIMED_OUT:
+ FC_FCP_DBG(fsp, "Returning DID_BUS_BUSY to scsi-ml "
+ "due to FC_TIMED_OUT\n");
+ sc_cmd->result = (DID_BUS_BUSY << 16) | fsp->io_status;
+ break;
default:
+ FC_FCP_DBG(fsp, "Returning DID_ERROR to scsi-ml "
+ "due to unknown error\n");
sc_cmd->result = (DID_ERROR << 16);
break;
}
+ if (lport->state != LPORT_ST_READY && fsp->status_code != FC_COMPLETE)
+ sc_cmd->result = (DID_TRANSPORT_DISRUPTED << 16);
+
+ spin_lock_irqsave(&si->scsi_queue_lock, flags);
list_del(&fsp->list);
sc_cmd->SCp.ptr = NULL;
+ spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
sc_cmd->scsi_done(sc_cmd);
- spin_unlock_irqrestore(lp->host->host_lock, flags);
/* release ref from initial allocation in queue command */
fc_fcp_pkt_release(fsp);
}
/**
- * fc_fcp_complete - complete processing of a fcp packet
- * @fsp: fcp packet
- *
- * This function may sleep if a fsp timer is pending.
- * The host lock must not be held by caller.
- */
-void fc_fcp_complete(struct fc_fcp_pkt *fsp)
-{
- if (fc_fcp_lock_pkt(fsp))
- return;
-
- fc_fcp_complete_locked(fsp);
- fc_fcp_unlock_pkt(fsp);
-}
-EXPORT_SYMBOL(fc_fcp_complete);
-
-/**
- * fc_eh_abort - Abort a command...from scsi host template
- * @sc_cmd: scsi command to abort
+ * fc_eh_abort() - Abort a command
+ * @sc_cmd: The SCSI command to abort
*
- * send ABTS to the target device and wait for the response
- * sc_cmd is the pointer to the command to be aborted.
+ * From SCSI host template.
+ * Send an ABTS to the target device and wait for the response.
*/
int fc_eh_abort(struct scsi_cmnd *sc_cmd)
{
struct fc_fcp_pkt *fsp;
- struct fc_lport *lp;
+ struct fc_lport *lport;
+ struct fc_fcp_internal *si;
int rc = FAILED;
unsigned long flags;
+ int rval;
+
+ rval = fc_block_scsi_eh(sc_cmd);
+ if (rval)
+ return rval;
- lp = shost_priv(sc_cmd->device->host);
- if (lp->state != LPORT_ST_READY)
+ lport = shost_priv(sc_cmd->device->host);
+ if (lport->state != LPORT_ST_READY)
return rc;
- else if (!(lp->link_status & FC_LINK_UP))
+ else if (!lport->link_up)
return rc;
- spin_lock_irqsave(lp->host->host_lock, flags);
+ si = fc_get_scsi_internal(lport);
+ spin_lock_irqsave(&si->scsi_queue_lock, flags);
fsp = CMD_SP(sc_cmd);
if (!fsp) {
/* command completed while scsi eh was setting up */
- spin_unlock_irqrestore(lp->host->host_lock, flags);
+ spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
return SUCCESS;
}
- /* grab a ref so the fsp and sc_cmd cannot be relased from under us */
+ /* grab a ref so the fsp and sc_cmd cannot be released from under us */
fc_fcp_pkt_hold(fsp);
- spin_unlock_irqrestore(lp->host->host_lock, flags);
+ spin_unlock_irqrestore(&si->scsi_queue_lock, flags);
if (fc_fcp_lock_pkt(fsp)) {
/* completed while we were waiting for timer to be deleted */
@@ -1910,7 +2053,7 @@ int fc_eh_abort(struct scsi_cmnd *sc_cmd)
goto release_pkt;
}
- rc = fc_fcp_pkt_abort(lp, fsp);
+ rc = fc_fcp_pkt_abort(fsp);
fc_fcp_unlock_pkt(fsp);
release_pkt:
@@ -1920,35 +2063,34 @@ release_pkt:
EXPORT_SYMBOL(fc_eh_abort);
/**
- * fc_eh_device_reset: Reset a single LUN
- * @sc_cmd: scsi command
+ * fc_eh_device_reset() - Reset a single LUN
+ * @sc_cmd: The SCSI command which identifies the device whose
+ * LUN is to be reset
*
- * Set from scsi host template to send tm cmd to the target and wait for the
- * response.
+ * Set from SCSI host template.
*/
int fc_eh_device_reset(struct scsi_cmnd *sc_cmd)
{
- struct fc_lport *lp;
+ struct fc_lport *lport;
struct fc_fcp_pkt *fsp;
struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device));
int rc = FAILED;
- struct fc_rport_libfc_priv *rp;
int rval;
- rval = fc_remote_port_chkready(rport);
+ rval = fc_block_scsi_eh(sc_cmd);
if (rval)
- goto out;
+ return rval;
- rp = rport->dd_data;
- lp = shost_priv(sc_cmd->device->host);
+ lport = shost_priv(sc_cmd->device->host);
- if (lp->state != LPORT_ST_READY)
+ if (lport->state != LPORT_ST_READY)
return rc;
- fsp = fc_fcp_pkt_alloc(lp, GFP_NOIO);
+ FC_SCSI_DBG(lport, "Resetting rport (%6.6x)\n", rport->port_id);
+
+ fsp = fc_fcp_pkt_alloc(lport, GFP_NOIO);
if (fsp == NULL) {
- FC_DBG("could not allocate scsi_pkt\n");
- sc_cmd->result = DID_NO_CONNECT << 16;
+ printk(KERN_WARNING "libfc: could not allocate scsi_pkt\n");
goto out;
}
@@ -1957,13 +2099,12 @@ int fc_eh_device_reset(struct scsi_cmnd *sc_cmd)
* the sc passed in is not setup for execution like when sent
* through the queuecommand callout.
*/
- fsp->lp = lp; /* save the softc ptr */
fsp->rport = rport; /* set the remote port ptr */
/*
* flush outstanding commands
*/
- rc = fc_lun_reset(lp, fsp, scmd_id(sc_cmd), sc_cmd->device->lun);
+ rc = fc_lun_reset(lport, fsp, scmd_id(sc_cmd), sc_cmd->device->lun);
fsp->state = FC_SRB_FREE;
fc_fcp_pkt_release(fsp);
@@ -1973,34 +2114,41 @@ out:
EXPORT_SYMBOL(fc_eh_device_reset);
/**
- * fc_eh_host_reset - The reset function will reset the ports on the host.
- * @sc_cmd: scsi command
+ * fc_eh_host_reset() - Reset a Scsi_Host.
+ * @sc_cmd: The SCSI command that identifies the SCSI host to be reset
*/
int fc_eh_host_reset(struct scsi_cmnd *sc_cmd)
{
struct Scsi_Host *shost = sc_cmd->device->host;
- struct fc_lport *lp = shost_priv(shost);
+ struct fc_lport *lport = shost_priv(shost);
unsigned long wait_tmo;
- lp->tt.lport_reset(lp);
+ FC_SCSI_DBG(lport, "Resetting host\n");
+
+ fc_block_scsi_eh(sc_cmd);
+
+ lport->tt.lport_reset(lport);
wait_tmo = jiffies + FC_HOST_RESET_TIMEOUT;
- while (!fc_fcp_lport_queue_ready(lp) && time_before(jiffies, wait_tmo))
+ while (!fc_fcp_lport_queue_ready(lport) && time_before(jiffies,
+ wait_tmo))
msleep(1000);
- if (fc_fcp_lport_queue_ready(lp)) {
- shost_printk(KERN_INFO, shost, "Host reset succeeded.\n");
+ if (fc_fcp_lport_queue_ready(lport)) {
+ shost_printk(KERN_INFO, shost, "libfc: Host reset succeeded "
+ "on port (%6.6x)\n", lport->port_id);
return SUCCESS;
} else {
- shost_printk(KERN_INFO, shost, "Host reset failed. "
- "lport not ready.\n");
+ shost_printk(KERN_INFO, shost, "libfc: Host reset failed, "
+ "port (%6.6x) is not ready.\n",
+ lport->port_id);
return FAILED;
}
}
EXPORT_SYMBOL(fc_eh_host_reset);
/**
- * fc_slave_alloc - configure queue depth
- * @sdev: scsi device
+ * fc_slave_alloc() - Configure the queue depth of a Scsi_Host
+ * @sdev: The SCSI device that identifies the SCSI host
*
* Configures queue depth based on host's cmd_per_len. If not set
* then we use the libfc default.
@@ -2008,29 +2156,50 @@ EXPORT_SYMBOL(fc_eh_host_reset);
int fc_slave_alloc(struct scsi_device *sdev)
{
struct fc_rport *rport = starget_to_rport(scsi_target(sdev));
- int queue_depth;
if (!rport || fc_remote_port_chkready(rport))
return -ENXIO;
- if (sdev->tagged_supported) {
- if (sdev->host->hostt->cmd_per_lun)
- queue_depth = sdev->host->hostt->cmd_per_lun;
- else
- queue_depth = FC_FCP_DFLT_QUEUE_DEPTH;
- scsi_activate_tcq(sdev, queue_depth);
- }
+ if (sdev->tagged_supported)
+ scsi_activate_tcq(sdev, FC_FCP_DFLT_QUEUE_DEPTH);
+ else
+ scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev),
+ FC_FCP_DFLT_QUEUE_DEPTH);
+
return 0;
}
EXPORT_SYMBOL(fc_slave_alloc);
-int fc_change_queue_depth(struct scsi_device *sdev, int qdepth)
+/**
+ * fc_change_queue_depth() - Change a device's queue depth
+ * @sdev: The SCSI device whose queue depth is to change
+ * @qdepth: The new queue depth
+ * @reason: The resason for the change
+ */
+int fc_change_queue_depth(struct scsi_device *sdev, int qdepth, int reason)
{
- scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), qdepth);
+ switch (reason) {
+ case SCSI_QDEPTH_DEFAULT:
+ scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), qdepth);
+ break;
+ case SCSI_QDEPTH_QFULL:
+ scsi_track_queue_full(sdev, qdepth);
+ break;
+ case SCSI_QDEPTH_RAMP_UP:
+ scsi_adjust_queue_depth(sdev, scsi_get_tag_type(sdev), qdepth);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
return sdev->queue_depth;
}
EXPORT_SYMBOL(fc_change_queue_depth);
+/**
+ * fc_change_queue_type() - Change a device's queue type
+ * @sdev: The SCSI device whose queue depth is to change
+ * @tag_type: Identifier for queue type
+ */
int fc_change_queue_type(struct scsi_device *sdev, int tag_type)
{
if (sdev->tagged_supported) {
@@ -2046,38 +2215,71 @@ int fc_change_queue_type(struct scsi_device *sdev, int tag_type)
}
EXPORT_SYMBOL(fc_change_queue_type);
-void fc_fcp_destroy(struct fc_lport *lp)
+/**
+ * fc_fcp_destory() - Tear down the FCP layer for a given local port
+ * @lport: The local port that no longer needs the FCP layer
+ */
+void fc_fcp_destroy(struct fc_lport *lport)
{
- struct fc_fcp_internal *si = fc_get_scsi_internal(lp);
+ struct fc_fcp_internal *si = fc_get_scsi_internal(lport);
if (!list_empty(&si->scsi_pkt_queue))
- printk(KERN_ERR "Leaked scsi packets.\n");
+ printk(KERN_ERR "libfc: Leaked SCSI packets when destroying "
+ "port (%6.6x)\n", lport->port_id);
mempool_destroy(si->scsi_pkt_pool);
kfree(si);
- lp->scsi_priv = NULL;
+ lport->scsi_priv = NULL;
}
EXPORT_SYMBOL(fc_fcp_destroy);
-int fc_fcp_init(struct fc_lport *lp)
+int fc_setup_fcp(void)
+{
+ int rc = 0;
+
+ scsi_pkt_cachep = kmem_cache_create("libfc_fcp_pkt",
+ sizeof(struct fc_fcp_pkt),
+ 0, SLAB_HWCACHE_ALIGN, NULL);
+ if (!scsi_pkt_cachep) {
+ printk(KERN_ERR "libfc: Unable to allocate SRB cache, "
+ "module load failed!");
+ rc = -ENOMEM;
+ }
+
+ return rc;
+}
+
+void fc_destroy_fcp(void)
+{
+ if (scsi_pkt_cachep)
+ kmem_cache_destroy(scsi_pkt_cachep);
+}
+
+/**
+ * fc_fcp_init() - Initialize the FCP layer for a local port
+ * @lport: The local port to initialize the exchange layer for
+ */
+int fc_fcp_init(struct fc_lport *lport)
{
int rc;
struct fc_fcp_internal *si;
- if (!lp->tt.fcp_cmd_send)
- lp->tt.fcp_cmd_send = fc_fcp_cmd_send;
+ if (!lport->tt.fcp_cmd_send)
+ lport->tt.fcp_cmd_send = fc_fcp_cmd_send;
- if (!lp->tt.fcp_cleanup)
- lp->tt.fcp_cleanup = fc_fcp_cleanup;
+ if (!lport->tt.fcp_cleanup)
+ lport->tt.fcp_cleanup = fc_fcp_cleanup;
- if (!lp->tt.fcp_abort_io)
- lp->tt.fcp_abort_io = fc_fcp_abort_io;
+ if (!lport->tt.fcp_abort_io)
+ lport->tt.fcp_abort_io = fc_fcp_abort_io;
si = kzalloc(sizeof(struct fc_fcp_internal), GFP_KERNEL);
if (!si)
return -ENOMEM;
- lp->scsi_priv = si;
+ lport->scsi_priv = si;
+ si->max_can_queue = lport->host->can_queue;
INIT_LIST_HEAD(&si->scsi_pkt_queue);
+ spin_lock_init(&si->scsi_queue_lock);
si->scsi_pkt_pool = mempool_create_slab_pool(2, scsi_pkt_cachep);
if (!si->scsi_pkt_pool) {
@@ -2091,41 +2293,3 @@ free_internal:
return rc;
}
EXPORT_SYMBOL(fc_fcp_init);
-
-static int __init libfc_init(void)
-{
- int rc;
-
- scsi_pkt_cachep = kmem_cache_create("libfc_fcp_pkt",
- sizeof(struct fc_fcp_pkt),
- 0, SLAB_HWCACHE_ALIGN, NULL);
- if (scsi_pkt_cachep == NULL) {
- FC_DBG("Unable to allocate SRB cache...module load failed!");
- return -ENOMEM;
- }
-
- rc = fc_setup_exch_mgr();
- if (rc)
- goto destroy_pkt_cache;
-
- rc = fc_setup_rport();
- if (rc)
- goto destroy_em;
-
- return rc;
-destroy_em:
- fc_destroy_exch_mgr();
-destroy_pkt_cache:
- kmem_cache_destroy(scsi_pkt_cachep);
- return rc;
-}
-
-static void __exit libfc_exit(void)
-{
- kmem_cache_destroy(scsi_pkt_cachep);
- fc_destroy_exch_mgr();
- fc_destroy_rport();
-}
-
-module_init(libfc_init);
-module_exit(libfc_exit);
diff --git a/drivers/scsi/libfc/fc_frame.c b/drivers/scsi/libfc/fc_frame.c
index 63fe00cfe66..0382ac06906 100644
--- a/drivers/scsi/libfc/fc_frame.c
+++ b/drivers/scsi/libfc/fc_frame.c
@@ -24,6 +24,7 @@
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/crc32.h>
+#include <linux/gfp.h>
#include <scsi/fc_frame.h>
@@ -48,27 +49,27 @@ u32 fc_frame_crc_check(struct fc_frame *fp)
EXPORT_SYMBOL(fc_frame_crc_check);
/*
- * Allocate a frame intended to be sent via fcoe_xmit.
+ * Allocate a frame intended to be sent.
* Get an sk_buff for the frame and set the length.
*/
-struct fc_frame *__fc_frame_alloc(size_t len)
+struct fc_frame *_fc_frame_alloc(size_t len)
{
struct fc_frame *fp;
struct sk_buff *skb;
WARN_ON((len % sizeof(u32)) != 0);
len += sizeof(struct fc_frame_header);
- skb = dev_alloc_skb(len + FC_FRAME_HEADROOM + FC_FRAME_TAILROOM);
+ skb = alloc_skb_fclone(len + FC_FRAME_HEADROOM + FC_FRAME_TAILROOM +
+ NET_SKB_PAD, GFP_ATOMIC);
if (!skb)
return NULL;
+ skb_reserve(skb, NET_SKB_PAD + FC_FRAME_HEADROOM);
fp = (struct fc_frame *) skb;
fc_frame_init(fp);
- skb_reserve(skb, FC_FRAME_HEADROOM);
skb_put(skb, len);
return fp;
}
-EXPORT_SYMBOL(__fc_frame_alloc);
-
+EXPORT_SYMBOL(_fc_frame_alloc);
struct fc_frame *fc_frame_alloc_fill(struct fc_lport *lp, size_t payload_len)
{
@@ -78,7 +79,7 @@ struct fc_frame *fc_frame_alloc_fill(struct fc_lport *lp, size_t payload_len)
fill = payload_len % 4;
if (fill != 0)
fill = 4 - fill;
- fp = __fc_frame_alloc(payload_len + fill);
+ fp = _fc_frame_alloc(payload_len + fill);
if (fp) {
memset((char *) fr_hdr(fp) + payload_len, 0, fill);
/* trim is OK, we just allocated it so there are no fragments */
@@ -87,3 +88,4 @@ struct fc_frame *fc_frame_alloc_fill(struct fc_lport *lp, size_t payload_len)
}
return fp;
}
+EXPORT_SYMBOL(fc_frame_alloc_fill);
diff --git a/drivers/scsi/libfc/fc_libfc.c b/drivers/scsi/libfc/fc_libfc.c
new file mode 100644
index 00000000000..8d65a51a759
--- /dev/null
+++ b/drivers/scsi/libfc/fc_libfc.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright(c) 2009 Intel Corporation. All rights reserved.
+ *
+ * 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.
+ *
+ * Maintained at www.Open-FCoE.org
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/scatterlist.h>
+#include <linux/crc32.h>
+#include <linux/module.h>
+
+#include <scsi/libfc.h>
+#include <scsi/fc_encode.h>
+
+#include "fc_libfc.h"
+
+MODULE_AUTHOR("Open-FCoE.org");
+MODULE_DESCRIPTION("libfc");
+MODULE_LICENSE("GPL v2");
+
+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);
+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.
+ */
+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
+ */
+static int __init libfc_init(void)
+{
+ int rc = 0;
+
+ rc = fc_setup_fcp();
+ if (rc)
+ return rc;
+
+ rc = fc_setup_exch_mgr();
+ if (rc)
+ goto destroy_pkt_cache;
+
+ rc = fc_setup_rport();
+ if (rc)
+ goto destroy_em;
+
+ return rc;
+destroy_em:
+ fc_destroy_exch_mgr();
+destroy_pkt_cache:
+ fc_destroy_fcp();
+ return rc;
+}
+module_init(libfc_init);
+
+/**
+ * libfc_exit() - Tear down libfc.ko
+ */
+static void __exit libfc_exit(void)
+{
+ fc_destroy_fcp();
+ fc_destroy_exch_mgr();
+ fc_destroy_rport();
+}
+module_exit(libfc_exit);
+
+/**
+ * fc_copy_buffer_to_sglist() - This routine copies the data of a buffer
+ * into a scatter-gather list (SG list).
+ *
+ * @buf: pointer to the data buffer.
+ * @len: the byte-length of the data buffer.
+ * @sg: pointer to the pointer of the SG list.
+ * @nents: pointer to the remaining number of entries in the SG list.
+ * @offset: pointer to the current offset in the SG list.
+ * @crc: pointer to the 32-bit crc value.
+ * If crc is NULL, CRC is not calculated.
+ */
+u32 fc_copy_buffer_to_sglist(void *buf, size_t len,
+ struct scatterlist *sg,
+ u32 *nents, size_t *offset,
+ u32 *crc)
+{
+ size_t remaining = len;
+ u32 copy_len = 0;
+
+ while (remaining > 0 && sg) {
+ size_t off, sg_bytes;
+ void *page_addr;
+
+ if (*offset >= sg->length) {
+ /*
+ * Check for end and drop resources
+ * from the last iteration.
+ */
+ if (!(*nents))
+ break;
+ --(*nents);
+ *offset -= sg->length;
+ sg = sg_next(sg);
+ continue;
+ }
+ sg_bytes = min(remaining, sg->length - *offset);
+
+ /*
+ * The scatterlist item may be bigger than PAGE_SIZE,
+ * but we are limited to mapping PAGE_SIZE at a time.
+ */
+ off = *offset + sg->offset;
+ sg_bytes = min(sg_bytes,
+ (size_t)(PAGE_SIZE - (off & ~PAGE_MASK)));
+ page_addr = kmap_atomic(sg_page(sg) + (off >> PAGE_SHIFT));
+ if (crc)
+ *crc = crc32(*crc, buf, sg_bytes);
+ memcpy((char *)page_addr + (off & ~PAGE_MASK), buf, sg_bytes);
+ kunmap_atomic(page_addr);
+ buf += sg_bytes;
+ *offset += sg_bytes;
+ remaining -= sg_bytes;
+ copy_len += sg_bytes;
+ }
+ return copy_len;
+}
+
+/**
+ * fc_fill_hdr() - fill FC header fields based on request
+ * @fp: reply frame containing header to be filled in
+ * @in_fp: request frame containing header to use in filling in reply
+ * @r_ctl: R_CTL value for header
+ * @f_ctl: F_CTL value for header, with 0 pad
+ * @seq_cnt: sequence count for the header, ignored if frame has a sequence
+ * @parm_offset: parameter / offset value
+ */
+void fc_fill_hdr(struct fc_frame *fp, const struct fc_frame *in_fp,
+ enum fc_rctl r_ctl, u32 f_ctl, u16 seq_cnt, u32 parm_offset)
+{
+ struct fc_frame_header *fh;
+ struct fc_frame_header *in_fh;
+ struct fc_seq *sp;
+ u32 fill;
+
+ fh = __fc_frame_header_get(fp);
+ in_fh = __fc_frame_header_get(in_fp);
+
+ if (f_ctl & FC_FC_END_SEQ) {
+ fill = -fr_len(fp) & 3;
+ if (fill) {
+ /* TODO, this may be a problem with fragmented skb */
+ memset(skb_put(fp_skb(fp), fill), 0, fill);
+ f_ctl |= fill;
+ }
+ fr_eof(fp) = FC_EOF_T;
+ } else {
+ WARN_ON(fr_len(fp) % 4 != 0); /* no pad to non last frame */
+ fr_eof(fp) = FC_EOF_N;
+ }
+
+ fh->fh_r_ctl = r_ctl;
+ memcpy(fh->fh_d_id, in_fh->fh_s_id, sizeof(fh->fh_d_id));
+ memcpy(fh->fh_s_id, in_fh->fh_d_id, sizeof(fh->fh_s_id));
+ fh->fh_type = in_fh->fh_type;
+ hton24(fh->fh_f_ctl, f_ctl);
+ fh->fh_ox_id = in_fh->fh_ox_id;
+ fh->fh_rx_id = in_fh->fh_rx_id;
+ fh->fh_cs_ctl = 0;
+ fh->fh_df_ctl = 0;
+ fh->fh_parm_offset = htonl(parm_offset);
+
+ sp = fr_seq(in_fp);
+ if (sp) {
+ fr_seq(fp) = sp;
+ fh->fh_seq_id = sp->id;
+ seq_cnt = sp->cnt;
+ } else {
+ fh->fh_seq_id = 0;
+ }
+ fh->fh_seq_cnt = ntohs(seq_cnt);
+ fr_sof(fp) = seq_cnt ? FC_SOF_N3 : FC_SOF_I3;
+ fr_encaps(fp) = fr_encaps(in_fp);
+}
+EXPORT_SYMBOL(fc_fill_hdr);
+
+/**
+ * fc_fill_reply_hdr() - fill FC reply header fields based on request
+ * @fp: reply frame containing header to be filled in
+ * @in_fp: request frame containing header to use in filling in reply
+ * @r_ctl: R_CTL value for reply
+ * @parm_offset: parameter / offset value
+ */
+void fc_fill_reply_hdr(struct fc_frame *fp, const struct fc_frame *in_fp,
+ enum fc_rctl r_ctl, u32 parm_offset)
+{
+ struct fc_seq *sp;
+
+ sp = fr_seq(in_fp);
+ if (sp)
+ fr_seq(fp) = fr_dev(in_fp)->tt.seq_start_next(sp);
+ fc_fill_hdr(fp, in_fp, r_ctl, FC_FCTL_RESP, 0, parm_offset);
+}
+EXPORT_SYMBOL(fc_fill_reply_hdr);
+
+/**
+ * fc_fc4_conf_lport_params() - Modify "service_params" of specified lport
+ * if there is service provider (target provider) registered with libfc
+ * for specified "fc_ft_type"
+ * @lport: Local port which service_params needs to be modified
+ * @type: FC-4 type, such as FC_TYPE_FCP
+ */
+void fc_fc4_conf_lport_params(struct fc_lport *lport, enum fc_fh_type type)
+{
+ struct fc4_prov *prov_entry;
+ BUG_ON(type >= FC_FC4_PROV_SIZE);
+ BUG_ON(!lport);
+ prov_entry = fc_passive_prov[type];
+ if (type == FC_TYPE_FCP) {
+ if (prov_entry && prov_entry->recv)
+ lport->service_params |= FCP_SPPF_TARG_FCN;
+ }
+}
+
+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
+ * @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);
+
+/**
+ * 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
new file mode 100644
index 00000000000..b74189d8932
--- /dev/null
+++ b/drivers/scsi/libfc/fc_libfc.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright(c) 2009 Intel Corporation. All rights reserved.
+ *
+ * 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.
+ *
+ * Maintained at www.Open-FCoE.org
+ */
+
+#ifndef _FC_LIBFC_H_
+#define _FC_LIBFC_H_
+
+#define FC_LIBFC_LOGGING 0x01 /* General logging, not categorized */
+#define FC_LPORT_LOGGING 0x02 /* lport layer logging */
+#define FC_DISC_LOGGING 0x04 /* discovery layer logging */
+#define FC_RPORT_LOGGING 0x08 /* rport layer logging */
+#define FC_FCP_LOGGING 0x10 /* I/O path logging */
+#define FC_EM_LOGGING 0x20 /* Exchange Manager logging */
+#define FC_EXCH_LOGGING 0x40 /* Exchange/Sequence logging */
+#define FC_SCSI_LOGGING 0x80 /* SCSI logging (mostly error handling) */
+
+extern unsigned int fc_debug_logging;
+
+#define FC_CHECK_LOGGING(LEVEL, CMD) \
+ do { \
+ if (unlikely(fc_debug_logging & LEVEL)) \
+ do { \
+ CMD; \
+ } while (0); \
+ } while (0)
+
+#define FC_LIBFC_DBG(fmt, args...) \
+ FC_CHECK_LOGGING(FC_LIBFC_LOGGING, \
+ pr_info("libfc: " fmt, ##args))
+
+#define FC_LPORT_DBG(lport, fmt, args...) \
+ FC_CHECK_LOGGING(FC_LPORT_LOGGING, \
+ pr_info("host%u: lport %6.6x: " fmt, \
+ (lport)->host->host_no, \
+ (lport)->port_id, ##args))
+
+#define FC_DISC_DBG(disc, fmt, args...) \
+ FC_CHECK_LOGGING(FC_DISC_LOGGING, \
+ pr_info("host%u: disc: " fmt, \
+ fc_disc_lport(disc)->host->host_no, \
+ ##args))
+
+#define FC_RPORT_ID_DBG(lport, port_id, fmt, args...) \
+ FC_CHECK_LOGGING(FC_RPORT_LOGGING, \
+ pr_info("host%u: rport %6.6x: " fmt, \
+ (lport)->host->host_no, \
+ (port_id), ##args))
+
+#define FC_RPORT_DBG(rdata, fmt, args...) \
+ FC_RPORT_ID_DBG((rdata)->local_port, (rdata)->ids.port_id, fmt, ##args)
+
+#define FC_FCP_DBG(pkt, fmt, args...) \
+ FC_CHECK_LOGGING(FC_FCP_LOGGING, \
+ { \
+ if ((pkt)->seq_ptr) { \
+ struct fc_exch *_ep = NULL; \
+ _ep = fc_seq_exch((pkt)->seq_ptr); \
+ pr_info("host%u: fcp: %6.6x: " \
+ "xid %04x-%04x: " fmt, \
+ (pkt)->lp->host->host_no, \
+ (pkt)->rport->port_id, \
+ (_ep)->oxid, (_ep)->rxid, ##args); \
+ } else { \
+ pr_info("host%u: fcp: %6.6x: " fmt, \
+ (pkt)->lp->host->host_no, \
+ (pkt)->rport->port_id, ##args); \
+ } \
+ })
+
+#define FC_EXCH_DBG(exch, fmt, args...) \
+ FC_CHECK_LOGGING(FC_EXCH_LOGGING, \
+ pr_info("host%u: xid %4x: " fmt, \
+ (exch)->lp->host->host_no, \
+ exch->xid, ##args))
+
+#define FC_SCSI_DBG(lport, fmt, args...) \
+ FC_CHECK_LOGGING(FC_SCSI_LOGGING, \
+ pr_info("host%u: scsi: " fmt, \
+ (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);
+void fc_fcp_ddp_done(struct fc_fcp_pkt *fsp);
+
+/*
+ * Module setup functions
+ */
+int fc_setup_exch_mgr(void);
+void fc_destroy_exch_mgr(void);
+int fc_setup_rport(void);
+void fc_destroy_rport(void);
+int fc_setup_fcp(void);
+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 *);
+extern void fc_fc4_conf_lport_params(struct fc_lport *, enum fc_fh_type);
+
+/*
+ * Copies a buffer into an sg list
+ */
+u32 fc_copy_buffer_to_sglist(void *buf, size_t len,
+ struct scatterlist *sg,
+ u32 *nents, size_t *offset,
+ u32 *crc);
+
+#endif /* _FC_LIBFC_H_ */
diff --git a/drivers/scsi/libfc/fc_lport.c b/drivers/scsi/libfc/fc_lport.c
index 0b9bdb1fb80..e01a29863c3 100644
--- a/drivers/scsi/libfc/fc_lport.c
+++ b/drivers/scsi/libfc/fc_lport.c
@@ -32,11 +32,11 @@
* invalid SID. We also need to ensure that states don't change unexpectedly
* while processing another state.
*
- * HEIRARCHY
+ * HIERARCHY
*
- * The following heirarchy defines the locking rules. A greater lock
+ * The following hierarchy defines the locking rules. A greater lock
* may be held before acquiring a lesser lock, but a lesser lock should never
- * be held while attempting to acquire a greater lock. Here is the heirarchy-
+ * be held while attempting to acquire a greater lock. Here is the hierarchy-
*
* lport > disc, lport > rport, disc > rport
*
@@ -52,11 +52,11 @@
* while making the callback. To ensure that the rport is not free'd while
* processing the callback the rport callbacks are serialized through a
* single-threaded workqueue. An rport would never be free'd while in a
- * callback handler becuase no other rport work in this queue can be executed
+ * callback handler because no other rport work in this queue can be executed
* at the same time.
*
* When discovery succeeds or fails a callback is made to the lport as
- * notification. Currently, succesful discovery causes the lport to take no
+ * notification. Currently, successful discovery causes the lport to take no
* action. A failure will cause the lport to reset. There is likely a circular
* locking problem with this implementation.
*/
@@ -88,12 +88,18 @@
*/
#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/slab.h>
#include <asm/unaligned.h>
#include <scsi/fc/fc_gs.h>
#include <scsi/libfc.h>
#include <scsi/fc_encode.h>
+#include <linux/scatterlist.h>
+
+#include "fc_libfc.h"
/* Fabric IDs to use for point-to-point mode, chosen on whims. */
#define FC_LOCAL_PTP_FID_LO 0x010101
@@ -101,37 +107,61 @@
#define DNS_DELAY 3 /* Discovery delay after RSCN (in seconds)*/
-static int fc_lport_debug;
-
-#define FC_DEBUG_LPORT(fmt...) \
- do { \
- if (fc_lport_debug) \
- FC_DBG(fmt); \
- } while (0)
-
static void fc_lport_error(struct fc_lport *, struct fc_frame *);
static void fc_lport_enter_reset(struct fc_lport *);
static void fc_lport_enter_flogi(struct fc_lport *);
static void fc_lport_enter_dns(struct fc_lport *);
-static void fc_lport_enter_rpn_id(struct fc_lport *);
-static void fc_lport_enter_rft_id(struct fc_lport *);
+static void fc_lport_enter_ns(struct fc_lport *, enum fc_lport_state);
static void fc_lport_enter_scr(struct fc_lport *);
static void fc_lport_enter_ready(struct fc_lport *);
static void fc_lport_enter_logo(struct fc_lport *);
+static void fc_lport_enter_fdmi(struct fc_lport *lport);
+static void fc_lport_enter_ms(struct fc_lport *, enum fc_lport_state);
static const char *fc_lport_state_names[] = {
- [LPORT_ST_NONE] = "none",
+ [LPORT_ST_DISABLED] = "disabled",
[LPORT_ST_FLOGI] = "FLOGI",
[LPORT_ST_DNS] = "dNS",
- [LPORT_ST_RPN_ID] = "RPN_ID",
+ [LPORT_ST_RNN_ID] = "RNN_ID",
+ [LPORT_ST_RSNN_NN] = "RSNN_NN",
+ [LPORT_ST_RSPN_ID] = "RSPN_ID",
[LPORT_ST_RFT_ID] = "RFT_ID",
+ [LPORT_ST_RFF_ID] = "RFF_ID",
+ [LPORT_ST_FDMI] = "FDMI",
+ [LPORT_ST_RHBA] = "RHBA",
+ [LPORT_ST_RPA] = "RPA",
+ [LPORT_ST_DHBA] = "DHBA",
+ [LPORT_ST_DPRT] = "DPRT",
[LPORT_ST_SCR] = "SCR",
[LPORT_ST_READY] = "Ready",
[LPORT_ST_LOGO] = "LOGO",
[LPORT_ST_RESET] = "reset",
};
+/**
+ * struct fc_bsg_info - FC Passthrough managemet structure
+ * @job: The passthrough job
+ * @lport: The local port to pass through a command
+ * @rsp_code: The expected response code
+ * @sg: job->reply_payload.sg_list
+ * @nents: job->reply_payload.sg_cnt
+ * @offset: The offset into the response data
+ */
+struct fc_bsg_info {
+ struct fc_bsg_job *job;
+ struct fc_lport *lport;
+ u16 rsp_code;
+ struct scatterlist *sg;
+ u32 nents;
+ size_t offset;
+};
+
+/**
+ * fc_frame_drop() - Dummy frame handler
+ * @lport: The local port the frame was received on
+ * @fp: The received frame
+ */
static int fc_frame_drop(struct fc_lport *lport, struct fc_frame *fp)
{
fc_frame_free(fp);
@@ -139,63 +169,56 @@ static int fc_frame_drop(struct fc_lport *lport, struct fc_frame *fp)
}
/**
- * fc_lport_rport_callback - Event handler for rport events
+ * fc_lport_rport_callback() - Event handler for rport events
* @lport: The lport which is receiving the event
- * @rport: The rport which the event has occured on
- * @event: The event that occured
+ * @rdata: private remote port data
+ * @event: The event that occurred
*
* Locking Note: The rport lock should not be held when calling
* this function.
*/
static void fc_lport_rport_callback(struct fc_lport *lport,
- struct fc_rport *rport,
+ struct fc_rport_priv *rdata,
enum fc_rport_event event)
{
- FC_DEBUG_LPORT("Received a %d event for port (%6x)\n", event,
- rport->port_id);
+ FC_LPORT_DBG(lport, "Received a %d event for port (%6.6x)\n", event,
+ rdata->ids.port_id);
+ mutex_lock(&lport->lp_mutex);
switch (event) {
- case RPORT_EV_CREATED:
- if (rport->port_id == FC_FID_DIR_SERV) {
- mutex_lock(&lport->lp_mutex);
- if (lport->state == LPORT_ST_DNS) {
- lport->dns_rp = rport;
- fc_lport_enter_rpn_id(lport);
- } else {
- FC_DEBUG_LPORT("Received an CREATED event on "
- "port (%6x) for the directory "
- "server, but the lport is not "
- "in the DNS state, it's in the "
- "%d state", rport->port_id,
- lport->state);
- lport->tt.rport_logoff(rport);
- }
- mutex_unlock(&lport->lp_mutex);
- } else
- FC_DEBUG_LPORT("Received an event for port (%6x) "
- "which is not the directory server\n",
- rport->port_id);
+ case RPORT_EV_READY:
+ if (lport->state == LPORT_ST_DNS) {
+ lport->dns_rdata = rdata;
+ fc_lport_enter_ns(lport, LPORT_ST_RNN_ID);
+ } else if (lport->state == LPORT_ST_FDMI) {
+ lport->ms_rdata = rdata;
+ fc_lport_enter_ms(lport, LPORT_ST_DHBA);
+ } else {
+ FC_LPORT_DBG(lport, "Received an READY event "
+ "on port (%6.6x) for the directory "
+ "server, but the lport is not "
+ "in the DNS or FDMI state, it's in the "
+ "%d state", rdata->ids.port_id,
+ lport->state);
+ lport->tt.rport_logoff(rdata);
+ }
break;
case RPORT_EV_LOGO:
case RPORT_EV_FAILED:
case RPORT_EV_STOP:
- if (rport->port_id == FC_FID_DIR_SERV) {
- mutex_lock(&lport->lp_mutex);
- lport->dns_rp = NULL;
- mutex_unlock(&lport->lp_mutex);
-
- } else
- FC_DEBUG_LPORT("Received an event for port (%6x) "
- "which is not the directory server\n",
- rport->port_id);
+ if (rdata->ids.port_id == FC_FID_DIR_SERV)
+ lport->dns_rdata = NULL;
+ else if (rdata->ids.port_id == FC_FID_MGMT_SERV)
+ lport->ms_rdata = NULL;
break;
case RPORT_EV_NONE:
break;
}
+ mutex_unlock(&lport->lp_mutex);
}
/**
- * fc_lport_state - Return a string which represents the lport's state
+ * fc_lport_state() - Return a string which represents the lport's state
* @lport: The lport whose state is to converted to a string
*/
static const char *fc_lport_state(struct fc_lport *lport)
@@ -209,9 +232,9 @@ static const char *fc_lport_state(struct fc_lport *lport)
}
/**
- * fc_lport_ptp_setup - Create an rport for point-to-point mode
- * @lport: The lport to attach the ptp rport to
- * @fid: The FID of the ptp rport
+ * fc_lport_ptp_setup() - Create an rport for point-to-point mode
+ * @lport: The lport to attach the ptp rport to
+ * @remote_fid: The FID of the ptp rport
* @remote_wwpn: The WWPN of the ptp rport
* @remote_wwnn: The WWNN of the ptp rport
*/
@@ -219,44 +242,49 @@ static void fc_lport_ptp_setup(struct fc_lport *lport,
u32 remote_fid, u64 remote_wwpn,
u64 remote_wwnn)
{
- struct fc_disc_port dp;
-
- dp.lp = lport;
- dp.ids.port_id = remote_fid;
- dp.ids.port_name = remote_wwpn;
- dp.ids.node_name = remote_wwnn;
- dp.ids.roles = FC_RPORT_ROLE_UNKNOWN;
-
- if (lport->ptp_rp) {
- lport->tt.rport_logoff(lport->ptp_rp);
- lport->ptp_rp = NULL;
+ mutex_lock(&lport->disc.disc_mutex);
+ 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);
- lport->ptp_rp = fc_rport_rogue_create(&dp);
-
- lport->tt.rport_login(lport->ptp_rp);
+ lport->tt.rport_login(lport->ptp_rdata);
fc_lport_enter_ready(lport);
}
-void fc_get_host_port_type(struct Scsi_Host *shost)
-{
- /* TODO - currently just NPORT */
- fc_host_port_type(shost) = FC_PORTTYPE_NPORT;
-}
-EXPORT_SYMBOL(fc_get_host_port_type);
-
+/**
+ * fc_get_host_port_state() - Return the port state of the given Scsi_Host
+ * @shost: The SCSI host whose port state is to be determined
+ */
void fc_get_host_port_state(struct Scsi_Host *shost)
{
- struct fc_lport *lp = shost_priv(shost);
+ struct fc_lport *lport = shost_priv(shost);
- if ((lp->link_status & FC_LINK_UP) == FC_LINK_UP)
- fc_host_port_state(shost) = FC_PORTSTATE_ONLINE;
+ mutex_lock(&lport->lp_mutex);
+ if (!lport->link_up)
+ fc_host_port_state(shost) = FC_PORTSTATE_LINKDOWN;
else
- fc_host_port_state(shost) = FC_PORTSTATE_OFFLINE;
+ switch (lport->state) {
+ case LPORT_ST_READY:
+ fc_host_port_state(shost) = FC_PORTSTATE_ONLINE;
+ break;
+ default:
+ fc_host_port_state(shost) = FC_PORTSTATE_OFFLINE;
+ }
+ mutex_unlock(&lport->lp_mutex);
}
EXPORT_SYMBOL(fc_get_host_port_state);
+/**
+ * fc_get_host_speed() - Return the speed of the given Scsi_Host
+ * @shost: The SCSI host whose port speed is to be determined
+ */
void fc_get_host_speed(struct Scsi_Host *shost)
{
struct fc_lport *lport = shost_priv(shost);
@@ -265,53 +293,72 @@ void fc_get_host_speed(struct Scsi_Host *shost)
}
EXPORT_SYMBOL(fc_get_host_speed);
+/**
+ * fc_get_host_stats() - Return the Scsi_Host's statistics
+ * @shost: The SCSI host whose statistics are to be returned
+ */
struct fc_host_statistics *fc_get_host_stats(struct Scsi_Host *shost)
{
- int i;
- struct fc_host_statistics *fcoe_stats;
- struct fc_lport *lp = shost_priv(shost);
+ struct fc_host_statistics *fc_stats;
+ struct fc_lport *lport = shost_priv(shost);
struct timespec v0, v1;
+ unsigned int cpu;
+ u64 fcp_in_bytes = 0;
+ u64 fcp_out_bytes = 0;
- fcoe_stats = &lp->host_stats;
- memset(fcoe_stats, 0, sizeof(struct fc_host_statistics));
+ fc_stats = &lport->host_stats;
+ memset(fc_stats, 0, sizeof(struct fc_host_statistics));
jiffies_to_timespec(jiffies, &v0);
- jiffies_to_timespec(lp->boot_time, &v1);
- fcoe_stats->seconds_since_last_reset = (v0.tv_sec - v1.tv_sec);
-
- for_each_online_cpu(i) {
- struct fcoe_dev_stats *stats = lp->dev_stats[i];
- if (stats == NULL)
- continue;
- fcoe_stats->tx_frames += stats->TxFrames;
- fcoe_stats->tx_words += stats->TxWords;
- fcoe_stats->rx_frames += stats->RxFrames;
- fcoe_stats->rx_words += stats->RxWords;
- fcoe_stats->error_frames += stats->ErrorFrames;
- fcoe_stats->invalid_crc_count += stats->InvalidCRCCount;
- fcoe_stats->fcp_input_requests += stats->InputRequests;
- fcoe_stats->fcp_output_requests += stats->OutputRequests;
- fcoe_stats->fcp_control_requests += stats->ControlRequests;
- fcoe_stats->fcp_input_megabytes += stats->InputMegabytes;
- fcoe_stats->fcp_output_megabytes += stats->OutputMegabytes;
- fcoe_stats->link_failure_count += stats->LinkFailureCount;
+ jiffies_to_timespec(lport->boot_time, &v1);
+ fc_stats->seconds_since_last_reset = (v0.tv_sec - v1.tv_sec);
+
+ for_each_possible_cpu(cpu) {
+ struct fc_stats *stats;
+
+ stats = per_cpu_ptr(lport->stats, cpu);
+
+ fc_stats->tx_frames += stats->TxFrames;
+ fc_stats->tx_words += stats->TxWords;
+ fc_stats->rx_frames += stats->RxFrames;
+ fc_stats->rx_words += stats->RxWords;
+ fc_stats->error_frames += stats->ErrorFrames;
+ fc_stats->invalid_crc_count += stats->InvalidCRCCount;
+ fc_stats->fcp_input_requests += stats->InputRequests;
+ fc_stats->fcp_output_requests += stats->OutputRequests;
+ fc_stats->fcp_control_requests += stats->ControlRequests;
+ fcp_in_bytes += stats->InputBytes;
+ fcp_out_bytes += stats->OutputBytes;
+ fc_stats->fcp_packet_alloc_failures += stats->FcpPktAllocFails;
+ fc_stats->fcp_packet_aborts += stats->FcpPktAborts;
+ fc_stats->fcp_frame_alloc_failures += stats->FcpFrameAllocFails;
+ fc_stats->link_failure_count += stats->LinkFailureCount;
}
- fcoe_stats->lip_count = -1;
- fcoe_stats->nos_count = -1;
- fcoe_stats->loss_of_sync_count = -1;
- fcoe_stats->loss_of_signal_count = -1;
- fcoe_stats->prim_seq_protocol_err_count = -1;
- fcoe_stats->dumped_frames = -1;
- return fcoe_stats;
+ fc_stats->fcp_input_megabytes = div_u64(fcp_in_bytes, 1000000);
+ fc_stats->fcp_output_megabytes = div_u64(fcp_out_bytes, 1000000);
+ fc_stats->lip_count = -1;
+ fc_stats->nos_count = -1;
+ fc_stats->loss_of_sync_count = -1;
+ fc_stats->loss_of_signal_count = -1;
+ fc_stats->prim_seq_protocol_err_count = -1;
+ fc_stats->dumped_frames = -1;
+
+ /* update exches stats */
+ fc_exch_update_stats(lport);
+
+ return fc_stats;
}
EXPORT_SYMBOL(fc_get_host_stats);
-/*
- * Fill in FLOGI command for request.
+/**
+ * fc_lport_flogi_fill() - Fill in FLOGI command for request
+ * @lport: The local port the FLOGI is for
+ * @flogi: The FLOGI command
+ * @op: The opcode
*/
-static void
-fc_lport_flogi_fill(struct fc_lport *lport, struct fc_els_flogi *flogi,
- unsigned int op)
+static void fc_lport_flogi_fill(struct fc_lport *lport,
+ struct fc_els_flogi *flogi,
+ unsigned int op)
{
struct fc_els_csp *sp;
struct fc_els_cssp *cp;
@@ -339,8 +386,10 @@ fc_lport_flogi_fill(struct fc_lport *lport, struct fc_els_flogi *flogi,
}
}
-/*
- * Add a supported FC-4 type.
+/**
+ * fc_lport_add_fc4_type() - Add a supported FC-4 type to a local port
+ * @lport: The local port to add a new FC-4 type to
+ * @type: The new FC-4 type
*/
static void fc_lport_add_fc4_type(struct fc_lport *lport, enum fc_fh_type type)
{
@@ -351,45 +400,40 @@ static void fc_lport_add_fc4_type(struct fc_lport *lport, enum fc_fh_type type)
}
/**
- * fc_lport_recv_rlir_req - Handle received Registered Link Incident Report.
- * @lport: Fibre Channel local port recieving the RLIR
- * @sp: current sequence in the RLIR exchange
- * @fp: RLIR request frame
+ * fc_lport_recv_rlir_req() - Handle received Registered Link Incident Report.
+ * @lport: Fibre Channel local port receiving the RLIR
+ * @fp: The RLIR request frame
*
- * Locking Note: The lport lock is exected to be held before calling
+ * Locking Note: The lport lock is expected to be held before calling
* this function.
*/
-static void fc_lport_recv_rlir_req(struct fc_seq *sp, struct fc_frame *fp,
- struct fc_lport *lport)
+static void fc_lport_recv_rlir_req(struct fc_lport *lport, struct fc_frame *fp)
{
- FC_DEBUG_LPORT("Received RLIR request while in state %s\n",
- fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Received RLIR request while in state %s\n",
+ fc_lport_state(lport));
- lport->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
+ lport->tt.seq_els_rsp_send(fp, ELS_LS_ACC, NULL);
fc_frame_free(fp);
}
/**
- * fc_lport_recv_echo_req - Handle received ECHO request
- * @lport: Fibre Channel local port recieving the ECHO
- * @sp: current sequence in the ECHO exchange
- * @fp: ECHO request frame
+ * fc_lport_recv_echo_req() - Handle received ECHO request
+ * @lport: The local port receiving the ECHO
+ * @fp: ECHO request frame
*
- * Locking Note: The lport lock is exected to be held before calling
+ * Locking Note: The lport lock is expected to be held before calling
* this function.
*/
-static void fc_lport_recv_echo_req(struct fc_seq *sp, struct fc_frame *in_fp,
- struct fc_lport *lport)
+static void fc_lport_recv_echo_req(struct fc_lport *lport,
+ struct fc_frame *in_fp)
{
struct fc_frame *fp;
- struct fc_exch *ep = fc_seq_exch(sp);
unsigned int len;
void *pp;
void *dp;
- u32 f_ctl;
- FC_DEBUG_LPORT("Received RLIR request while in state %s\n",
- fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Received ECHO request while in state %s\n",
+ fc_lport_state(lport));
len = fr_len(in_fp) - sizeof(struct fc_frame_header);
pp = fc_frame_payload_get(in_fp, len);
@@ -401,30 +445,25 @@ static void fc_lport_recv_echo_req(struct fc_seq *sp, struct fc_frame *in_fp,
if (fp) {
dp = fc_frame_payload_get(fp, len);
memcpy(dp, pp, len);
- *((u32 *)dp) = htonl(ELS_LS_ACC << 24);
- sp = lport->tt.seq_start_next(sp);
- f_ctl = FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ;
- 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);
+ *((__be32 *)dp) = htonl(ELS_LS_ACC << 24);
+ fc_fill_reply_hdr(fp, in_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
}
fc_frame_free(in_fp);
}
/**
- * fc_lport_recv_echo_req - Handle received Request Node ID data request
- * @lport: Fibre Channel local port recieving the RNID
- * @sp: current sequence in the RNID exchange
- * @fp: RNID request frame
+ * fc_lport_recv_rnid_req() - Handle received Request Node ID data request
+ * @lport: The local port receiving the RNID
+ * @fp: The RNID request frame
*
- * Locking Note: The lport lock is exected to be held before calling
+ * Locking Note: The lport lock is expected to be held before calling
* this function.
*/
-static void fc_lport_recv_rnid_req(struct fc_seq *sp, struct fc_frame *in_fp,
- struct fc_lport *lport)
+static void fc_lport_recv_rnid_req(struct fc_lport *lport,
+ struct fc_frame *in_fp)
{
struct fc_frame *fp;
- struct fc_exch *ep = fc_seq_exch(sp);
struct fc_els_rnid *req;
struct {
struct fc_els_rnid_resp rnid;
@@ -434,17 +473,15 @@ static void fc_lport_recv_rnid_req(struct fc_seq *sp, struct fc_frame *in_fp,
struct fc_seq_els_data rjt_data;
u8 fmt;
size_t len;
- u32 f_ctl;
- FC_DEBUG_LPORT("Received RNID request while in state %s\n",
- fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Received RNID request while in state %s\n",
+ fc_lport_state(lport));
req = fc_frame_payload_get(in_fp, sizeof(*req));
if (!req) {
- rjt_data.fp = NULL;
rjt_data.reason = ELS_RJT_LOGIC;
rjt_data.explan = ELS_EXPL_NONE;
- lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
+ lport->tt.seq_els_rsp_send(in_fp, ELS_LS_RJT, &rjt_data);
} else {
fmt = req->rnid_fmt;
len = sizeof(*rp);
@@ -467,87 +504,31 @@ static void fc_lport_recv_rnid_req(struct fc_seq *sp, struct fc_frame *in_fp,
memcpy(&rp->gen, &lport->rnid_gen,
sizeof(rp->gen));
}
- sp = lport->tt.seq_start_next(sp);
- f_ctl = FC_FC_EX_CTX | FC_FC_LAST_SEQ;
- f_ctl |= FC_FC_END_SEQ | FC_FC_SEQ_INIT;
- 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);
+ fc_fill_reply_hdr(fp, in_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
}
}
fc_frame_free(in_fp);
}
/**
- * fc_lport_recv_adisc_req - Handle received Address Discovery Request
- * @lport: Fibre Channel local port recieving the ADISC
- * @sp: current sequence in the ADISC exchange
- * @fp: ADISC request frame
+ * fc_lport_recv_logo_req() - Handle received fabric LOGO request
+ * @lport: The local port receiving the LOGO
+ * @fp: The LOGO request frame
*
* Locking Note: The lport lock is expected to be held before calling
* this function.
*/
-static void fc_lport_recv_adisc_req(struct fc_seq *sp, struct fc_frame *in_fp,
- struct fc_lport *lport)
-{
- struct fc_frame *fp;
- struct fc_exch *ep = fc_seq_exch(sp);
- struct fc_els_adisc *req, *rp;
- struct fc_seq_els_data rjt_data;
- size_t len;
- u32 f_ctl;
-
- FC_DEBUG_LPORT("Received ADISC request while in state %s\n",
- fc_lport_state(lport));
-
- req = fc_frame_payload_get(in_fp, sizeof(*req));
- if (!req) {
- rjt_data.fp = NULL;
- rjt_data.reason = ELS_RJT_LOGIC;
- rjt_data.explan = ELS_EXPL_NONE;
- lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
- } else {
- len = sizeof(*rp);
- fp = fc_frame_alloc(lport, len);
- if (fp) {
- rp = fc_frame_payload_get(fp, len);
- memset(rp, 0, len);
- rp->adisc_cmd = ELS_LS_ACC;
- rp->adisc_wwpn = htonll(lport->wwpn);
- rp->adisc_wwnn = htonll(lport->wwnn);
- hton24(rp->adisc_port_id,
- fc_host_port_id(lport->host));
- sp = lport->tt.seq_start_next(sp);
- f_ctl = FC_FC_EX_CTX | FC_FC_LAST_SEQ;
- f_ctl |= FC_FC_END_SEQ | FC_FC_SEQ_INIT;
- 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);
- }
- }
- fc_frame_free(in_fp);
-}
-
-/**
- * fc_lport_recv_logo_req - Handle received fabric LOGO request
- * @lport: Fibre Channel local port recieving the LOGO
- * @sp: current sequence in the LOGO exchange
- * @fp: LOGO request frame
- *
- * Locking Note: The lport lock is exected to be held before calling
- * this function.
- */
-static void fc_lport_recv_logo_req(struct fc_seq *sp, struct fc_frame *fp,
- struct fc_lport *lport)
+static void fc_lport_recv_logo_req(struct fc_lport *lport, struct fc_frame *fp)
{
- lport->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
+ lport->tt.seq_els_rsp_send(fp, ELS_LS_ACC, NULL);
fc_lport_enter_reset(lport);
fc_frame_free(fp);
}
/**
- * fc_fabric_login - Start the lport state machine
- * @lport: The lport that should log into the fabric
+ * fc_fabric_login() - Start the lport state machine
+ * @lport: The local port that should log into the fabric
*
* Locking Note: This function should not be called
* with the lport lock held.
@@ -557,7 +538,9 @@ int fc_fabric_login(struct fc_lport *lport)
int rc = -1;
mutex_lock(&lport->lp_mutex);
- if (lport->state == LPORT_ST_NONE) {
+ if (lport->state == LPORT_ST_DISABLED ||
+ lport->state == LPORT_ST_LOGO) {
+ fc_lport_state_enter(lport, LPORT_ST_RESET);
fc_lport_enter_reset(lport);
rc = 0;
}
@@ -568,116 +551,121 @@ int fc_fabric_login(struct fc_lport *lport)
EXPORT_SYMBOL(fc_fabric_login);
/**
- * fc_linkup - Handler for transport linkup events
+ * __fc_linkup() - Handler for transport linkup events
* @lport: The lport whose link is up
+ *
+ * Locking: must be called with the lp_mutex held
*/
-void fc_linkup(struct fc_lport *lport)
+void __fc_linkup(struct fc_lport *lport)
{
- FC_DEBUG_LPORT("Link is up for port (%6x)\n",
- fc_host_port_id(lport->host));
-
- mutex_lock(&lport->lp_mutex);
- if ((lport->link_status & FC_LINK_UP) != FC_LINK_UP) {
- lport->link_status |= FC_LINK_UP;
+ if (!lport->link_up) {
+ lport->link_up = 1;
if (lport->state == LPORT_ST_RESET)
fc_lport_enter_flogi(lport);
}
- mutex_unlock(&lport->lp_mutex);
}
-EXPORT_SYMBOL(fc_linkup);
/**
- * fc_linkdown - Handler for transport linkdown events
- * @lport: The lport whose link is down
+ * fc_linkup() - Handler for transport linkup events
+ * @lport: The local port whose link is up
*/
-void fc_linkdown(struct fc_lport *lport)
+void fc_linkup(struct fc_lport *lport)
{
- mutex_lock(&lport->lp_mutex);
- FC_DEBUG_LPORT("Link is down for port (%6x)\n",
- fc_host_port_id(lport->host));
+ printk(KERN_INFO "host%d: libfc: Link up on port (%6.6x)\n",
+ lport->host->host_no, lport->port_id);
- if ((lport->link_status & FC_LINK_UP) == FC_LINK_UP) {
- lport->link_status &= ~(FC_LINK_UP);
- fc_lport_enter_reset(lport);
- lport->tt.fcp_cleanup(lport);
- }
+ mutex_lock(&lport->lp_mutex);
+ __fc_linkup(lport);
mutex_unlock(&lport->lp_mutex);
}
-EXPORT_SYMBOL(fc_linkdown);
+EXPORT_SYMBOL(fc_linkup);
/**
- * fc_pause - Pause the flow of frames
- * @lport: The lport to be paused
+ * __fc_linkdown() - Handler for transport linkdown events
+ * @lport: The lport whose link is down
+ *
+ * Locking: must be called with the lp_mutex held
*/
-void fc_pause(struct fc_lport *lport)
+void __fc_linkdown(struct fc_lport *lport)
{
- mutex_lock(&lport->lp_mutex);
- lport->link_status |= FC_PAUSE;
- mutex_unlock(&lport->lp_mutex);
+ if (lport->link_up) {
+ lport->link_up = 0;
+ fc_lport_enter_reset(lport);
+ lport->tt.fcp_cleanup(lport);
+ }
}
-EXPORT_SYMBOL(fc_pause);
/**
- * fc_unpause - Unpause the flow of frames
- * @lport: The lport to be unpaused
+ * fc_linkdown() - Handler for transport linkdown events
+ * @lport: The local port whose link is down
*/
-void fc_unpause(struct fc_lport *lport)
+void fc_linkdown(struct fc_lport *lport)
{
+ printk(KERN_INFO "host%d: libfc: Link down on port (%6.6x)\n",
+ lport->host->host_no, lport->port_id);
+
mutex_lock(&lport->lp_mutex);
- lport->link_status &= ~(FC_PAUSE);
+ __fc_linkdown(lport);
mutex_unlock(&lport->lp_mutex);
}
-EXPORT_SYMBOL(fc_unpause);
+EXPORT_SYMBOL(fc_linkdown);
/**
- * fc_fabric_logoff - Logout of the fabric
- * @lport: fc_lport pointer to logoff the fabric
+ * fc_fabric_logoff() - Logout of the fabric
+ * @lport: The local port to logoff the fabric
*
* Return value:
* 0 for success, -1 for failure
- **/
+ */
int fc_fabric_logoff(struct fc_lport *lport)
{
lport->tt.disc_stop_final(lport);
mutex_lock(&lport->lp_mutex);
+ if (lport->dns_rdata)
+ lport->tt.rport_logoff(lport->dns_rdata);
+ mutex_unlock(&lport->lp_mutex);
+ lport->tt.rport_flush_queue();
+ mutex_lock(&lport->lp_mutex);
fc_lport_enter_logo(lport);
mutex_unlock(&lport->lp_mutex);
+ cancel_delayed_work_sync(&lport->retry_work);
return 0;
}
EXPORT_SYMBOL(fc_fabric_logoff);
/**
- * fc_lport_destroy - unregister a fc_lport
- * @lport: fc_lport pointer to unregister
+ * fc_lport_destroy() - Unregister a fc_lport
+ * @lport: The local port to unregister
*
- * Return value:
- * None
* Note:
* exit routine for fc_lport instance
* clean-up all the allocated memory
* and free up other system resources.
*
- **/
+ */
int fc_lport_destroy(struct fc_lport *lport)
{
+ mutex_lock(&lport->lp_mutex);
+ lport->state = LPORT_ST_DISABLED;
+ lport->link_up = 0;
lport->tt.frame_send = fc_frame_drop;
+ mutex_unlock(&lport->lp_mutex);
+
lport->tt.fcp_abort_io(lport);
- lport->tt.exch_mgr_reset(lport->emp, 0, 0);
+ lport->tt.disc_stop_final(lport);
+ lport->tt.exch_mgr_reset(lport, 0, 0);
+ cancel_delayed_work_sync(&lport->retry_work);
+ fc_fc4_del_lport(lport);
return 0;
}
EXPORT_SYMBOL(fc_lport_destroy);
/**
- * fc_set_mfs - sets up the mfs for the corresponding fc_lport
- * @lport: fc_lport pointer to unregister
- * @mfs: the new mfs for fc_lport
- *
- * Set mfs for the given fc_lport to the new mfs.
- *
- * Return: 0 for success
- *
- **/
+ * fc_set_mfs() - Set the maximum frame size for a local port
+ * @lport: The local port to set the MFS for
+ * @mfs: The new MFS
+ */
int fc_set_mfs(struct fc_lport *lport, u32 mfs)
{
unsigned int old_mfs;
@@ -706,20 +694,21 @@ int fc_set_mfs(struct fc_lport *lport, u32 mfs)
EXPORT_SYMBOL(fc_set_mfs);
/**
- * fc_lport_disc_callback - Callback for discovery events
- * @lport: FC local port
+ * fc_lport_disc_callback() - Callback for discovery events
+ * @lport: The local port receiving the event
* @event: The discovery event
*/
-void fc_lport_disc_callback(struct fc_lport *lport, enum fc_disc_event event)
+static void fc_lport_disc_callback(struct fc_lport *lport,
+ enum fc_disc_event event)
{
switch (event) {
case DISC_EV_SUCCESS:
- FC_DEBUG_LPORT("Got a SUCCESS event for port (%6x)\n",
- fc_host_port_id(lport->host));
+ FC_LPORT_DBG(lport, "Discovery succeeded\n");
break;
case DISC_EV_FAILED:
- FC_DEBUG_LPORT("Got a FAILED event for port (%6x)\n",
- fc_host_port_id(lport->host));
+ printk(KERN_ERR "host%d: libfc: "
+ "Discovery failed for port (%6.6x)\n",
+ lport->host->host_no, lport->port_id);
mutex_lock(&lport->lp_mutex);
fc_lport_enter_reset(lport);
mutex_unlock(&lport->lp_mutex);
@@ -731,65 +720,117 @@ void fc_lport_disc_callback(struct fc_lport *lport, enum fc_disc_event event)
}
/**
- * fc_rport_enter_ready - Enter the ready state and start discovery
- * @lport: Fibre Channel local port that is ready
+ * fc_rport_enter_ready() - Enter the ready state and start discovery
+ * @lport: The local port that is ready
*
* Locking Note: The lport lock is expected to be held before calling
* this routine.
*/
static void fc_lport_enter_ready(struct fc_lport *lport)
{
- FC_DEBUG_LPORT("Port (%6x) entered Ready from state %s\n",
- fc_host_port_id(lport->host), fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Entered READY from state %s\n",
+ fc_lport_state(lport));
fc_lport_state_enter(lport, LPORT_ST_READY);
+ if (lport->vport)
+ fc_vport_set_state(lport->vport, FC_VPORT_ACTIVE);
+ fc_vports_linkchange(lport);
+
+ if (!lport->ptp_rdata)
+ lport->tt.disc_start(fc_lport_disc_callback, lport);
+}
+
+/**
+ * fc_lport_set_port_id() - set the local port Port ID
+ * @lport: The local port which will have its Port ID set.
+ * @port_id: The new port ID.
+ * @fp: The frame containing the incoming request, or NULL.
+ *
+ * Locking Note: The lport lock is expected to be held before calling
+ * this function.
+ */
+static void fc_lport_set_port_id(struct fc_lport *lport, u32 port_id,
+ struct fc_frame *fp)
+{
+ if (port_id)
+ printk(KERN_INFO "host%d: Assigned Port ID %6.6x\n",
+ lport->host->host_no, port_id);
+
+ lport->port_id = port_id;
- lport->tt.disc_start(fc_lport_disc_callback, lport);
+ /* Update the fc_host */
+ fc_host_port_id(lport->host) = port_id;
+
+ if (lport->tt.lport_set_port_id)
+ lport->tt.lport_set_port_id(lport, port_id, fp);
}
/**
- * fc_lport_recv_flogi_req - Receive a FLOGI request
- * @sp_in: The sequence the FLOGI is on
- * @rx_fp: The frame the FLOGI is in
- * @lport: The lport that recieved the request
+ * fc_lport_set_port_id() - set the local port Port ID for point-to-multipoint
+ * @lport: The local port which will have its Port ID set.
+ * @port_id: The new port ID.
+ *
+ * Called by the lower-level driver when transport sets the local port_id.
+ * This is used in VN_port to VN_port mode for FCoE, and causes FLOGI and
+ * discovery to be skipped.
+ */
+void fc_lport_set_local_id(struct fc_lport *lport, u32 port_id)
+{
+ mutex_lock(&lport->lp_mutex);
+
+ fc_lport_set_port_id(lport, port_id, NULL);
+
+ switch (lport->state) {
+ case LPORT_ST_RESET:
+ case LPORT_ST_FLOGI:
+ if (port_id)
+ fc_lport_enter_ready(lport);
+ break;
+ default:
+ break;
+ }
+ mutex_unlock(&lport->lp_mutex);
+}
+EXPORT_SYMBOL(fc_lport_set_local_id);
+
+/**
+ * fc_lport_recv_flogi_req() - Receive a FLOGI request
+ * @lport: The local port that received the request
+ * @rx_fp: The FLOGI frame
*
* A received FLOGI request indicates a point-to-point connection.
* Accept it with the common service parameters indicating our N port.
* Set up to do a PLOGI if we have the higher-number WWPN.
*
- * Locking Note: The lport lock is exected to be held before calling
+ * Locking Note: The lport lock is expected to be held before calling
* this function.
*/
-static void fc_lport_recv_flogi_req(struct fc_seq *sp_in,
- struct fc_frame *rx_fp,
- struct fc_lport *lport)
+static void fc_lport_recv_flogi_req(struct fc_lport *lport,
+ struct fc_frame *rx_fp)
{
struct fc_frame *fp;
struct fc_frame_header *fh;
- struct fc_seq *sp;
- struct fc_exch *ep;
struct fc_els_flogi *flp;
struct fc_els_flogi *new_flp;
u64 remote_wwpn;
u32 remote_fid;
u32 local_fid;
- u32 f_ctl;
- FC_DEBUG_LPORT("Received FLOGI request while in state %s\n",
- fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Received FLOGI request while in state %s\n",
+ fc_lport_state(lport));
- fh = fc_frame_header_get(rx_fp);
- remote_fid = ntoh24(fh->fh_s_id);
+ remote_fid = fc_frame_sid(rx_fp);
flp = fc_frame_payload_get(rx_fp, sizeof(*flp));
if (!flp)
goto out;
remote_wwpn = get_unaligned_be64(&flp->fl_wwpn);
if (remote_wwpn == lport->wwpn) {
- FC_DBG("FLOGI from port with same WWPN %llx "
- "possible configuration error\n", remote_wwpn);
+ printk(KERN_WARNING "host%d: libfc: Received FLOGI from port "
+ "with same WWPN %16.16llx\n",
+ lport->host->host_no, remote_wwpn);
goto out;
}
- FC_DBG("FLOGI from port WWPN %llx\n", remote_wwpn);
+ FC_LPORT_DBG(lport, "FLOGI from port WWPN %16.16llx\n", remote_wwpn);
/*
* XXX what is the right thing to do for FIDs?
@@ -805,11 +846,10 @@ static void fc_lport_recv_flogi_req(struct fc_seq *sp_in,
remote_fid = FC_LOCAL_PTP_FID_HI;
}
- fc_host_port_id(lport->host) = local_fid;
+ fc_lport_set_port_id(lport, local_fid, rx_fp);
fp = fc_frame_alloc(lport, sizeof(*flp));
if (fp) {
- sp = lport->tt.seq_start_next(fr_seq(rx_fp));
new_flp = fc_frame_payload_get(fp, sizeof(*flp));
fc_lport_flogi_fill(lport, new_flp, ELS_FLOGI);
new_flp->fl_cmd = (u8) ELS_LS_ACC;
@@ -818,46 +858,36 @@ static void fc_lport_recv_flogi_req(struct fc_seq *sp_in,
* Send the response. If this fails, the originator should
* repeat the sequence.
*/
- 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_TYPE_ELS, f_ctl, 0);
- lport->tt.seq_send(lport, sp, fp);
+ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_ELS_REP, 0);
+ fh = fc_frame_header_get(fp);
+ hton24(fh->fh_s_id, local_fid);
+ hton24(fh->fh_d_id, remote_fid);
+ lport->tt.frame_send(lport, fp);
} else {
fc_lport_error(lport, fp);
}
fc_lport_ptp_setup(lport, remote_fid, remote_wwpn,
get_unaligned_be64(&flp->fl_wwnn));
-
- lport->tt.disc_start(fc_lport_disc_callback, lport);
-
out:
- sp = fr_seq(rx_fp);
fc_frame_free(rx_fp);
}
/**
- * 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
+ * fc_lport_recv_els_req() - The generic lport ELS request handler
+ * @lport: The local port that received the request
+ * @fp: The request frame
*
* This function will see if the lport handles the request or
* if an rport should handle the request.
*
* Locking Note: This function should not be called with the lport
- * lock held becuase it will grab the lock.
+ * lock held because 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_frame *fp)
{
- struct fc_frame_header *fh = fc_frame_header_get(fp);
- void (*recv) (struct fc_seq *, struct fc_frame *, struct fc_lport *);
- struct fc_rport *rport;
- u32 s_id;
- u32 d_id;
- struct fc_seq_els_data rjt_data;
+ void (*recv)(struct fc_lport *, struct fc_frame *);
mutex_lock(&lport->lp_mutex);
@@ -866,18 +896,20 @@ static void fc_lport_recv_req(struct fc_lport *lport, struct fc_seq *sp,
* RSCN here. These don't require a session.
* Even if we had a session, it might not be ready.
*/
- if (fh->fh_type == FC_TYPE_ELS && fh->fh_r_ctl == FC_RCTL_ELS_REQ) {
+ if (!lport->link_up)
+ fc_frame_free(fp);
+ else {
/*
* Check opcode.
*/
- recv = NULL;
+ recv = lport->tt.rport_recv_req;
switch (fc_frame_payload_op(fp)) {
case ELS_FLOGI:
- recv = fc_lport_recv_flogi_req;
+ if (!lport->point_to_multipoint)
+ recv = fc_lport_recv_flogi_req;
break;
case ELS_LOGO:
- fh = fc_frame_header_get(fp);
- if (ntoh24(fh->fh_s_id) == FC_FID_FLOGI)
+ if (fc_frame_sid(fp) == FC_FID_FLOGI)
recv = fc_lport_recv_logo_req;
break;
case ELS_RSCN:
@@ -892,56 +924,76 @@ static void fc_lport_recv_req(struct fc_lport *lport, struct fc_seq *sp,
case ELS_RNID:
recv = fc_lport_recv_rnid_req;
break;
- case ELS_ADISC:
- recv = fc_lport_recv_adisc_req;
- break;
}
- if (recv)
- recv(sp, fp, lport);
- else {
- /*
- * Find session.
- * If this is a new incoming PLOGI, we won't find it.
- */
- s_id = ntoh24(fh->fh_s_id);
- d_id = ntoh24(fh->fh_d_id);
-
- rport = lport->tt.rport_lookup(lport, s_id);
- if (rport)
- lport->tt.rport_recv_req(sp, fp, rport);
- else {
- rjt_data.fp = NULL;
- rjt_data.reason = ELS_RJT_UNAB;
- rjt_data.explan = ELS_EXPL_NONE;
- lport->tt.seq_els_rsp_send(sp,
- ELS_LS_RJT,
- &rjt_data);
- fc_frame_free(fp);
- }
- }
- } else {
- FC_DBG("dropping invalid frame (eof %x)\n", fr_eof(fp));
- fc_frame_free(fp);
+ recv(lport, fp);
}
mutex_unlock(&lport->lp_mutex);
+}
+
+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
+ * @fp: The frame the request is in
+ *
+ * Locking Note: This function should not be called with the lport
+ * lock held because it may grab the lock.
+ */
+static void fc_lport_recv_req(struct fc_lport *lport,
+ struct fc_frame *fp)
+{
+ struct fc_frame_header *fh = fc_frame_header_get(fp);
+ struct fc_seq *sp = fr_seq(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.
*/
- lport->tt.exch_done(sp);
+
+ 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, 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);
+ if (sp)
+ lport->tt.exch_done(sp);
}
/**
- * fc_lport_reset - Reset an lport
- * @lport: The lport which should be reset
+ * fc_lport_reset() - Reset a local port
+ * @lport: The local port which should be reset
*
* Locking Note: This functions should not be called with the
* lport lock held.
*/
int fc_lport_reset(struct fc_lport *lport)
{
+ cancel_delayed_work_sync(&lport->retry_work);
mutex_lock(&lport->lp_mutex);
fc_lport_enter_reset(lport);
mutex_unlock(&lport->lp_mutex);
@@ -950,41 +1002,83 @@ int fc_lport_reset(struct fc_lport *lport)
EXPORT_SYMBOL(fc_lport_reset);
/**
- * fc_rport_enter_reset - Reset the local port
- * @lport: Fibre Channel local port to be reset
+ * fc_lport_reset_locked() - Reset the local port w/ the lport lock held
+ * @lport: The local port to be reset
*
* Locking Note: The lport lock is expected to be held before calling
* this routine.
*/
-static void fc_lport_enter_reset(struct fc_lport *lport)
+static void fc_lport_reset_locked(struct fc_lport *lport)
{
- FC_DEBUG_LPORT("Port (%6x) entered RESET state from %s state\n",
- fc_host_port_id(lport->host), fc_lport_state(lport));
-
- fc_lport_state_enter(lport, LPORT_ST_RESET);
-
- if (lport->dns_rp)
- lport->tt.rport_logoff(lport->dns_rp);
+ if (lport->dns_rdata)
+ lport->tt.rport_logoff(lport->dns_rdata);
- if (lport->ptp_rp) {
- lport->tt.rport_logoff(lport->ptp_rp);
- lport->ptp_rp = 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);
- lport->tt.exch_mgr_reset(lport->emp, 0, 0);
+ lport->tt.exch_mgr_reset(lport, 0, 0);
fc_host_fabric_name(lport->host) = 0;
- fc_host_port_id(lport->host) = 0;
- if ((lport->link_status & FC_LINK_UP) == FC_LINK_UP)
+ if (lport->port_id && (!lport->point_to_multipoint || !lport->link_up))
+ fc_lport_set_port_id(lport, 0, NULL);
+}
+
+/**
+ * fc_lport_enter_reset() - Reset the local port
+ * @lport: The local port to be reset
+ *
+ * Locking Note: The lport lock is expected to be held before calling
+ * this routine.
+ */
+static void fc_lport_enter_reset(struct fc_lport *lport)
+{
+ FC_LPORT_DBG(lport, "Entered RESET state from %s state\n",
+ fc_lport_state(lport));
+
+ if (lport->state == LPORT_ST_DISABLED || lport->state == LPORT_ST_LOGO)
+ return;
+
+ if (lport->vport) {
+ if (lport->link_up)
+ fc_vport_set_state(lport->vport, FC_VPORT_INITIALIZING);
+ else
+ fc_vport_set_state(lport->vport, FC_VPORT_LINKDOWN);
+ }
+ fc_lport_state_enter(lport, LPORT_ST_RESET);
+ fc_host_post_event(lport->host, fc_get_event_number(),
+ FCH_EVT_LIPRESET, 0);
+ fc_vports_linkchange(lport);
+ fc_lport_reset_locked(lport);
+ if (lport->link_up)
fc_lport_enter_flogi(lport);
}
/**
- * fc_lport_error - Handler for any errors
- * @lport: The fc_lport object
- * @fp: The frame pointer
+ * fc_lport_enter_disabled() - Disable the local port
+ * @lport: The local port to be reset
+ *
+ * Locking Note: The lport lock is expected to be held before calling
+ * this routine.
+ */
+static void fc_lport_enter_disabled(struct fc_lport *lport)
+{
+ FC_LPORT_DBG(lport, "Entered disabled state from %s state\n",
+ fc_lport_state(lport));
+
+ fc_lport_state_enter(lport, LPORT_ST_DISABLED);
+ fc_vports_linkchange(lport);
+ fc_lport_reset_locked(lport);
+}
+
+/**
+ * fc_lport_error() - Handler for any errors
+ * @lport: The local port that the error was on
+ * @fp: The error code encoded in a frame pointer
*
* If the error was caused by a resource allocation failure
* then wait for half a second and retry, otherwise retry
@@ -993,69 +1087,60 @@ static void fc_lport_enter_reset(struct fc_lport *lport)
static void fc_lport_error(struct fc_lport *lport, struct fc_frame *fp)
{
unsigned long delay = 0;
- FC_DEBUG_LPORT("Error %ld in state %s, retries %d\n",
- PTR_ERR(fp), fc_lport_state(lport),
- lport->retry_count);
+ FC_LPORT_DBG(lport, "Error %ld in state %s, retries %d\n",
+ IS_ERR(fp) ? -PTR_ERR(fp) : 0, fc_lport_state(lport),
+ lport->retry_count);
- if (!fp || PTR_ERR(fp) == -FC_EX_TIMEOUT) {
- /*
- * Memory allocation failure, or the exchange timed out.
- * Retry after delay
- */
- if (lport->retry_count < lport->max_retry_count) {
- lport->retry_count++;
- if (!fp)
- delay = msecs_to_jiffies(500);
- else
- delay = msecs_to_jiffies(lport->e_d_tov);
+ if (PTR_ERR(fp) == -FC_EX_CLOSED)
+ return;
- schedule_delayed_work(&lport->retry_work, delay);
- } else {
- switch (lport->state) {
- case LPORT_ST_NONE:
- case LPORT_ST_READY:
- case LPORT_ST_RESET:
- case LPORT_ST_RPN_ID:
- case LPORT_ST_RFT_ID:
- case LPORT_ST_SCR:
- case LPORT_ST_DNS:
- case LPORT_ST_FLOGI:
- case LPORT_ST_LOGO:
- fc_lport_enter_reset(lport);
- break;
- }
- }
- }
+ /*
+ * Memory allocation failure, or the exchange timed out
+ * or we received LS_RJT.
+ * Retry after delay
+ */
+ if (lport->retry_count < lport->max_retry_count) {
+ lport->retry_count++;
+ if (!fp)
+ delay = msecs_to_jiffies(500);
+ else
+ delay = msecs_to_jiffies(lport->e_d_tov);
+
+ schedule_delayed_work(&lport->retry_work, delay);
+ } else
+ fc_lport_enter_reset(lport);
}
/**
- * fc_lport_rft_id_resp - Handle response to Register Fibre
- * Channel Types by ID (RPN_ID) request
- * @sp: current sequence in RPN_ID exchange
- * @fp: response frame
+ * fc_lport_ns_resp() - Handle response to a name server
+ * registration exchange
+ * @sp: current sequence in exchange
+ * @fp: response frame
* @lp_arg: Fibre Channel host port instance
*
* Locking Note: This function will be called without the lport lock
- * held, but it will lock, call an _enter_* function or fc_lport_error
+ * held, but it will lock, call an _enter_* function or fc_lport_error()
* and then unlock the lport.
*/
-static void fc_lport_rft_id_resp(struct fc_seq *sp, struct fc_frame *fp,
- void *lp_arg)
+static void fc_lport_ns_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *lp_arg)
{
struct fc_lport *lport = lp_arg;
struct fc_frame_header *fh;
struct fc_ct_hdr *ct;
+ FC_LPORT_DBG(lport, "Received a ns %s\n", fc_els_resp_type(fp));
+
if (fp == ERR_PTR(-FC_EX_CLOSED))
return;
mutex_lock(&lport->lp_mutex);
- FC_DEBUG_LPORT("Received a RFT_ID response\n");
-
- if (lport->state != LPORT_ST_RFT_ID) {
- FC_DBG("Received a RFT_ID response, but in state %s\n",
- fc_lport_state(lport));
+ if (lport->state < LPORT_ST_RNN_ID || lport->state > LPORT_ST_RFF_ID) {
+ FC_LPORT_DBG(lport, "Received a name server response, "
+ "but in state %s\n", fc_lport_state(lport));
+ if (IS_ERR(fp))
+ goto err;
goto out;
}
@@ -1071,7 +1156,29 @@ static void fc_lport_rft_id_resp(struct fc_seq *sp, struct fc_frame *fp,
ct->ct_fs_type == FC_FST_DIR &&
ct->ct_fs_subtype == FC_NS_SUBTYPE &&
ntohs(ct->ct_cmd) == FC_FS_ACC)
- fc_lport_enter_scr(lport);
+ switch (lport->state) {
+ case LPORT_ST_RNN_ID:
+ fc_lport_enter_ns(lport, LPORT_ST_RSNN_NN);
+ break;
+ case LPORT_ST_RSNN_NN:
+ fc_lport_enter_ns(lport, LPORT_ST_RSPN_ID);
+ break;
+ case LPORT_ST_RSPN_ID:
+ fc_lport_enter_ns(lport, LPORT_ST_RFT_ID);
+ break;
+ case LPORT_ST_RFT_ID:
+ fc_lport_enter_ns(lport, LPORT_ST_RFF_ID);
+ break;
+ case LPORT_ST_RFF_ID:
+ if (lport->fdmi_enabled)
+ fc_lport_enter_fdmi(lport);
+ else
+ fc_lport_enter_scr(lport);
+ break;
+ default:
+ /* should have already been caught by state checks */
+ break;
+ }
else
fc_lport_error(lport, fp);
out:
@@ -1081,33 +1188,35 @@ err:
}
/**
- * fc_lport_rpn_id_resp - Handle response to Register Port
- * Name by ID (RPN_ID) request
- * @sp: current sequence in RPN_ID exchange
- * @fp: response frame
+ * fc_lport_ms_resp() - Handle response to a management server
+ * exchange
+ * @sp: current sequence in exchange
+ * @fp: response frame
* @lp_arg: Fibre Channel host port instance
*
* Locking Note: This function will be called without the lport lock
- * held, but it will lock, call an _enter_* function or fc_lport_error
+ * held, but it will lock, call an _enter_* function or fc_lport_error()
* and then unlock the lport.
*/
-static void fc_lport_rpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
- void *lp_arg)
+static void fc_lport_ms_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *lp_arg)
{
struct fc_lport *lport = lp_arg;
struct fc_frame_header *fh;
struct fc_ct_hdr *ct;
+ FC_LPORT_DBG(lport, "Received a ms %s\n", fc_els_resp_type(fp));
+
if (fp == ERR_PTR(-FC_EX_CLOSED))
return;
mutex_lock(&lport->lp_mutex);
- FC_DEBUG_LPORT("Received a RPN_ID response\n");
-
- if (lport->state != LPORT_ST_RPN_ID) {
- FC_DBG("Received a RPN_ID response, but in state %s\n",
- fc_lport_state(lport));
+ if (lport->state < LPORT_ST_RHBA || lport->state > LPORT_ST_DPRT) {
+ FC_LPORT_DBG(lport, "Received a management server response, "
+ "but in state %s\n", fc_lport_state(lport));
+ if (IS_ERR(fp))
+ goto err;
goto out;
}
@@ -1118,14 +1227,39 @@ static void fc_lport_rpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
fh = fc_frame_header_get(fp);
ct = fc_frame_payload_get(fp, sizeof(*ct));
+
if (fh && ct && fh->fh_type == FC_TYPE_CT &&
- ct->ct_fs_type == FC_FST_DIR &&
- ct->ct_fs_subtype == FC_NS_SUBTYPE &&
- ntohs(ct->ct_cmd) == FC_FS_ACC)
- fc_lport_enter_rft_id(lport);
- else
+ ct->ct_fs_type == FC_FST_MGMT &&
+ ct->ct_fs_subtype == FC_FDMI_SUBTYPE) {
+ FC_LPORT_DBG(lport, "Received a management server response, "
+ "reason=%d explain=%d\n",
+ ct->ct_reason,
+ ct->ct_explan);
+
+ switch (lport->state) {
+ case LPORT_ST_RHBA:
+ if (ntohs(ct->ct_cmd) == FC_FS_ACC)
+ fc_lport_enter_ms(lport, LPORT_ST_RPA);
+ else /* Error Skip RPA */
+ fc_lport_enter_scr(lport);
+ break;
+ case LPORT_ST_RPA:
+ fc_lport_enter_scr(lport);
+ break;
+ case LPORT_ST_DPRT:
+ fc_lport_enter_ms(lport, LPORT_ST_RHBA);
+ break;
+ case LPORT_ST_DHBA:
+ fc_lport_enter_ms(lport, LPORT_ST_DPRT);
+ break;
+ default:
+ /* should have already been caught by state checks */
+ break;
+ }
+ } else {
+ /* Invalid Frame? */
fc_lport_error(lport, fp);
-
+ }
out:
fc_frame_free(fp);
err:
@@ -1133,9 +1267,9 @@ err:
}
/**
- * fc_lport_scr_resp - Handle response to State Change Register (SCR) request
- * @sp: current sequence in SCR exchange
- * @fp: response frame
+ * fc_lport_scr_resp() - Handle response to State Change Register (SCR) request
+ * @sp: current sequence in SCR exchange
+ * @fp: response frame
* @lp_arg: Fibre Channel lport port instance that sent the registration request
*
* Locking Note: This function will be called without the lport lock
@@ -1148,16 +1282,18 @@ static void fc_lport_scr_resp(struct fc_seq *sp, struct fc_frame *fp,
struct fc_lport *lport = lp_arg;
u8 op;
+ FC_LPORT_DBG(lport, "Received a SCR %s\n", fc_els_resp_type(fp));
+
if (fp == ERR_PTR(-FC_EX_CLOSED))
return;
mutex_lock(&lport->lp_mutex);
- FC_DEBUG_LPORT("Received a SCR response\n");
-
if (lport->state != LPORT_ST_SCR) {
- FC_DBG("Received a SCR response, but in state %s\n",
- fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Received a SCR response, but in state "
+ "%s\n", fc_lport_state(lport));
+ if (IS_ERR(fp))
+ goto err;
goto out;
}
@@ -1179,8 +1315,8 @@ err:
}
/**
- * fc_lport_enter_scr - Send a State Change Register (SCR) request
- * @lport: Fibre Channel local port to register for state changes
+ * fc_lport_enter_scr() - Send a SCR (State Change Register) request
+ * @lport: The local port to register for state changes
*
* Locking Note: The lport lock is expected to be held before calling
* this routine.
@@ -1189,8 +1325,8 @@ static void fc_lport_enter_scr(struct fc_lport *lport)
{
struct fc_frame *fp;
- FC_DEBUG_LPORT("Port (%6x) entered SCR state from %s state\n",
- fc_host_port_id(lport->host), fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Entered SCR state from %s state\n",
+ fc_lport_state(lport));
fc_lport_state_enter(lport, LPORT_ST_SCR);
@@ -1200,117 +1336,223 @@ static void fc_lport_enter_scr(struct fc_lport *lport)
return;
}
- if (!lport->tt.elsct_send(lport, NULL, fp, ELS_SCR,
- fc_lport_scr_resp, lport, lport->e_d_tov))
- fc_lport_error(lport, fp);
+ if (!lport->tt.elsct_send(lport, FC_FID_FCTRL, fp, ELS_SCR,
+ fc_lport_scr_resp, lport,
+ 2 * lport->r_a_tov))
+ fc_lport_error(lport, NULL);
}
/**
- * fc_lport_enter_rft_id - Register FC4-types with the name server
+ * fc_lport_enter_ns() - register some object with the name server
* @lport: Fibre Channel local port to register
*
* Locking Note: The lport lock is expected to be held before calling
* this routine.
*/
-static void fc_lport_enter_rft_id(struct fc_lport *lport)
+static void fc_lport_enter_ns(struct fc_lport *lport, enum fc_lport_state state)
{
struct fc_frame *fp;
- struct fc_ns_fts *lps;
- int i;
+ enum fc_ns_req cmd;
+ int size = sizeof(struct fc_ct_hdr);
+ size_t len;
- FC_DEBUG_LPORT("Port (%6x) entered RFT_ID state from %s state\n",
- fc_host_port_id(lport->host), fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Entered %s state from %s state\n",
+ fc_lport_state_names[state],
+ fc_lport_state(lport));
- fc_lport_state_enter(lport, LPORT_ST_RFT_ID);
+ fc_lport_state_enter(lport, state);
- lps = &lport->fcts;
- i = sizeof(lps->ff_type_map) / sizeof(lps->ff_type_map[0]);
- while (--i >= 0)
- if (ntohl(lps->ff_type_map[i]) != 0)
- break;
- if (i < 0) {
- /* nothing to register, move on to SCR */
- fc_lport_enter_scr(lport);
+ switch (state) {
+ case LPORT_ST_RNN_ID:
+ cmd = FC_NS_RNN_ID;
+ size += sizeof(struct fc_ns_rn_id);
+ break;
+ case LPORT_ST_RSNN_NN:
+ len = strnlen(fc_host_symbolic_name(lport->host), 255);
+ /* if there is no symbolic name, skip to RFT_ID */
+ if (!len)
+ return fc_lport_enter_ns(lport, LPORT_ST_RFT_ID);
+ cmd = FC_NS_RSNN_NN;
+ size += sizeof(struct fc_ns_rsnn) + len;
+ break;
+ case LPORT_ST_RSPN_ID:
+ len = strnlen(fc_host_symbolic_name(lport->host), 255);
+ /* if there is no symbolic name, skip to RFT_ID */
+ if (!len)
+ return fc_lport_enter_ns(lport, LPORT_ST_RFT_ID);
+ cmd = FC_NS_RSPN_ID;
+ size += sizeof(struct fc_ns_rspn) + len;
+ break;
+ case LPORT_ST_RFT_ID:
+ cmd = FC_NS_RFT_ID;
+ size += sizeof(struct fc_ns_rft);
+ break;
+ case LPORT_ST_RFF_ID:
+ cmd = FC_NS_RFF_ID;
+ size += sizeof(struct fc_ns_rff_id);
+ break;
+ default:
+ fc_lport_error(lport, NULL);
return;
}
- fp = fc_frame_alloc(lport, sizeof(struct fc_ct_hdr) +
- sizeof(struct fc_ns_rft));
+ fp = fc_frame_alloc(lport, size);
if (!fp) {
fc_lport_error(lport, fp);
return;
}
- if (!lport->tt.elsct_send(lport, NULL, fp, FC_NS_RFT_ID,
- fc_lport_rft_id_resp,
- lport, lport->e_d_tov))
+ if (!lport->tt.elsct_send(lport, FC_FID_DIR_SERV, fp, cmd,
+ fc_lport_ns_resp,
+ lport, 3 * lport->r_a_tov))
fc_lport_error(lport, fp);
}
+static struct fc_rport_operations fc_lport_rport_ops = {
+ .event_callback = fc_lport_rport_callback,
+};
+
/**
- * fc_rport_enter_rft_id - Register port name with the name server
- * @lport: Fibre Channel local port to register
+ * fc_rport_enter_dns() - Create a fc_rport for the name server
+ * @lport: The local port requesting a remote port for the name server
*
* Locking Note: The lport lock is expected to be held before calling
* this routine.
*/
-static void fc_lport_enter_rpn_id(struct fc_lport *lport)
+static void fc_lport_enter_dns(struct fc_lport *lport)
{
- struct fc_frame *fp;
+ struct fc_rport_priv *rdata;
+
+ FC_LPORT_DBG(lport, "Entered DNS state from %s state\n",
+ fc_lport_state(lport));
- FC_DEBUG_LPORT("Port (%6x) entered RPN_ID state from %s state\n",
- fc_host_port_id(lport->host), fc_lport_state(lport));
+ fc_lport_state_enter(lport, LPORT_ST_DNS);
- fc_lport_state_enter(lport, LPORT_ST_RPN_ID);
+ mutex_lock(&lport->disc.disc_mutex);
+ rdata = lport->tt.rport_create(lport, FC_FID_DIR_SERV);
+ mutex_unlock(&lport->disc.disc_mutex);
+ if (!rdata)
+ goto err;
- fp = fc_frame_alloc(lport, sizeof(struct fc_ct_hdr) +
- sizeof(struct fc_ns_rn_id));
+ rdata->ops = &fc_lport_rport_ops;
+ lport->tt.rport_login(rdata);
+ return;
+
+err:
+ fc_lport_error(lport, NULL);
+}
+
+/**
+ * fc_lport_enter_ms() - management server commands
+ * @lport: Fibre Channel local port to register
+ *
+ * Locking Note: The lport lock is expected to be held before calling
+ * this routine.
+ */
+static void fc_lport_enter_ms(struct fc_lport *lport, enum fc_lport_state state)
+{
+ struct fc_frame *fp;
+ enum fc_fdmi_req cmd;
+ int size = sizeof(struct fc_ct_hdr);
+ size_t len;
+ int numattrs;
+
+ FC_LPORT_DBG(lport, "Entered %s state from %s state\n",
+ fc_lport_state_names[state],
+ fc_lport_state(lport));
+
+ fc_lport_state_enter(lport, state);
+
+ switch (state) {
+ case LPORT_ST_RHBA:
+ cmd = FC_FDMI_RHBA;
+ /* Number of HBA Attributes */
+ numattrs = 10;
+ len = sizeof(struct fc_fdmi_rhba);
+ len -= sizeof(struct fc_fdmi_attr_entry);
+ len += (numattrs * FC_FDMI_ATTR_ENTRY_HEADER_LEN);
+ len += FC_FDMI_HBA_ATTR_NODENAME_LEN;
+ len += FC_FDMI_HBA_ATTR_MANUFACTURER_LEN;
+ len += FC_FDMI_HBA_ATTR_SERIALNUMBER_LEN;
+ len += FC_FDMI_HBA_ATTR_MODEL_LEN;
+ len += FC_FDMI_HBA_ATTR_MODELDESCR_LEN;
+ len += FC_FDMI_HBA_ATTR_HARDWAREVERSION_LEN;
+ len += FC_FDMI_HBA_ATTR_DRIVERVERSION_LEN;
+ len += FC_FDMI_HBA_ATTR_OPTIONROMVERSION_LEN;
+ len += FC_FDMI_HBA_ATTR_FIRMWAREVERSION_LEN;
+ len += FC_FDMI_HBA_ATTR_OSNAMEVERSION_LEN;
+
+ size += len;
+ break;
+ case LPORT_ST_RPA:
+ cmd = FC_FDMI_RPA;
+ /* Number of Port Attributes */
+ numattrs = 6;
+ len = sizeof(struct fc_fdmi_rpa);
+ len -= sizeof(struct fc_fdmi_attr_entry);
+ len += (numattrs * FC_FDMI_ATTR_ENTRY_HEADER_LEN);
+ len += FC_FDMI_PORT_ATTR_FC4TYPES_LEN;
+ len += FC_FDMI_PORT_ATTR_SUPPORTEDSPEED_LEN;
+ len += FC_FDMI_PORT_ATTR_CURRENTPORTSPEED_LEN;
+ len += FC_FDMI_PORT_ATTR_MAXFRAMESIZE_LEN;
+ len += FC_FDMI_PORT_ATTR_OSDEVICENAME_LEN;
+ len += FC_FDMI_PORT_ATTR_HOSTNAME_LEN;
+
+ size += len;
+ break;
+ case LPORT_ST_DPRT:
+ cmd = FC_FDMI_DPRT;
+ len = sizeof(struct fc_fdmi_dprt);
+ size += len;
+ break;
+ case LPORT_ST_DHBA:
+ cmd = FC_FDMI_DHBA;
+ len = sizeof(struct fc_fdmi_dhba);
+ size += len;
+ break;
+ default:
+ fc_lport_error(lport, NULL);
+ return;
+ }
+
+ FC_LPORT_DBG(lport, "Cmd=0x%x Len %d size %d\n",
+ cmd, (int)len, size);
+ fp = fc_frame_alloc(lport, size);
if (!fp) {
fc_lport_error(lport, fp);
return;
}
- if (!lport->tt.elsct_send(lport, NULL, fp, FC_NS_RPN_ID,
- fc_lport_rpn_id_resp,
- lport, lport->e_d_tov))
+ if (!lport->tt.elsct_send(lport, FC_FID_MGMT_SERV, fp, cmd,
+ fc_lport_ms_resp,
+ lport, 3 * lport->r_a_tov))
fc_lport_error(lport, fp);
}
-static struct fc_rport_operations fc_lport_rport_ops = {
- .event_callback = fc_lport_rport_callback,
-};
-
/**
- * fc_rport_enter_dns - Create a rport to the name server
- * @lport: Fibre Channel local port requesting a rport for the name server
+ * fc_rport_enter_fdmi() - Create a fc_rport for the management server
+ * @lport: The local port requesting a remote port for the management server
*
* Locking Note: The lport lock is expected to be held before calling
* this routine.
*/
-static void fc_lport_enter_dns(struct fc_lport *lport)
+static void fc_lport_enter_fdmi(struct fc_lport *lport)
{
- struct fc_rport *rport;
- struct fc_rport_libfc_priv *rdata;
- struct fc_disc_port dp;
-
- dp.ids.port_id = FC_FID_DIR_SERV;
- dp.ids.port_name = -1;
- dp.ids.node_name = -1;
- dp.ids.roles = FC_RPORT_ROLE_UNKNOWN;
- dp.lp = lport;
+ struct fc_rport_priv *rdata;
- FC_DEBUG_LPORT("Port (%6x) entered DNS state from %s state\n",
- fc_host_port_id(lport->host), fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Entered FDMI state from %s state\n",
+ fc_lport_state(lport));
- fc_lport_state_enter(lport, LPORT_ST_DNS);
+ fc_lport_state_enter(lport, LPORT_ST_FDMI);
- rport = fc_rport_rogue_create(&dp);
- if (!rport)
+ mutex_lock(&lport->disc.disc_mutex);
+ rdata = lport->tt.rport_create(lport, FC_FID_MGMT_SERV);
+ mutex_unlock(&lport->disc.disc_mutex);
+ if (!rdata)
goto err;
- rdata = rport->dd_data;
rdata->ops = &fc_lport_rport_ops;
- lport->tt.rport_login(rport);
+ lport->tt.rport_login(rdata);
return;
err:
@@ -1318,8 +1560,8 @@ err:
}
/**
- * fc_lport_timeout - Handler for the retry_work timer.
- * @work: The work struct of the fc_lport
+ * fc_lport_timeout() - Handler for the retry_work timer
+ * @work: The work struct of the local port
*/
static void fc_lport_timeout(struct work_struct *work)
{
@@ -1330,10 +1572,11 @@ static void fc_lport_timeout(struct work_struct *work)
mutex_lock(&lport->lp_mutex);
switch (lport->state) {
- case LPORT_ST_NONE:
+ case LPORT_ST_DISABLED:
+ break;
case LPORT_ST_READY:
+ break;
case LPORT_ST_RESET:
- WARN_ON(1);
break;
case LPORT_ST_FLOGI:
fc_lport_enter_flogi(lport);
@@ -1341,12 +1584,23 @@ static void fc_lport_timeout(struct work_struct *work)
case LPORT_ST_DNS:
fc_lport_enter_dns(lport);
break;
- case LPORT_ST_RPN_ID:
- fc_lport_enter_rpn_id(lport);
- break;
+ case LPORT_ST_RNN_ID:
+ case LPORT_ST_RSNN_NN:
+ case LPORT_ST_RSPN_ID:
case LPORT_ST_RFT_ID:
- fc_lport_enter_rft_id(lport);
+ case LPORT_ST_RFF_ID:
+ fc_lport_enter_ns(lport, lport->state);
+ break;
+ case LPORT_ST_FDMI:
+ fc_lport_enter_fdmi(lport);
break;
+ case LPORT_ST_RHBA:
+ case LPORT_ST_RPA:
+ case LPORT_ST_DHBA:
+ case LPORT_ST_DPRT:
+ FC_LPORT_DBG(lport, "Skipping lport state %s to SCR\n",
+ fc_lport_state(lport));
+ /* fall thru */
case LPORT_ST_SCR:
fc_lport_enter_scr(lport);
break;
@@ -1359,31 +1613,33 @@ static void fc_lport_timeout(struct work_struct *work)
}
/**
- * fc_lport_logo_resp - Handle response to LOGO request
- * @sp: current sequence in LOGO exchange
- * @fp: response frame
- * @lp_arg: Fibre Channel lport port instance that sent the LOGO request
+ * fc_lport_logo_resp() - Handle response to LOGO request
+ * @sp: The sequence that the LOGO was on
+ * @fp: The LOGO frame
+ * @lp_arg: The lport port that received the LOGO request
*
* Locking Note: This function will be called without the lport lock
- * held, but it will lock, call an _enter_* function or fc_lport_error
+ * held, but it will lock, call an _enter_* function or fc_lport_error()
* and then unlock the lport.
*/
-static void fc_lport_logo_resp(struct fc_seq *sp, struct fc_frame *fp,
- void *lp_arg)
+void fc_lport_logo_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *lp_arg)
{
struct fc_lport *lport = lp_arg;
u8 op;
+ FC_LPORT_DBG(lport, "Received a LOGO %s\n", fc_els_resp_type(fp));
+
if (fp == ERR_PTR(-FC_EX_CLOSED))
return;
mutex_lock(&lport->lp_mutex);
- FC_DEBUG_LPORT("Received a LOGO response\n");
-
if (lport->state != LPORT_ST_LOGO) {
- FC_DBG("Received a LOGO response, but in state %s\n",
- fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Received a LOGO response, but in state "
+ "%s\n", fc_lport_state(lport));
+ if (IS_ERR(fp))
+ goto err;
goto out;
}
@@ -1394,7 +1650,7 @@ static void fc_lport_logo_resp(struct fc_seq *sp, struct fc_frame *fp,
op = fc_frame_payload_op(fp);
if (op == ELS_LS_ACC)
- fc_lport_enter_reset(lport);
+ fc_lport_enter_disabled(lport);
else
fc_lport_error(lport, fp);
@@ -1403,10 +1659,11 @@ out:
err:
mutex_unlock(&lport->lp_mutex);
}
+EXPORT_SYMBOL(fc_lport_logo_resp);
/**
- * fc_rport_enter_logo - Logout of the fabric
- * @lport: Fibre Channel local port to be logged out
+ * fc_rport_enter_logo() - Logout of the fabric
+ * @lport: The local port to be logged out
*
* Locking Note: The lport lock is expected to be held before calling
* this routine.
@@ -1416,14 +1673,11 @@ static void fc_lport_enter_logo(struct fc_lport *lport)
struct fc_frame *fp;
struct fc_els_logo *logo;
- FC_DEBUG_LPORT("Port (%6x) entered LOGO state from %s state\n",
- fc_host_port_id(lport->host), fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Entered LOGO state from %s state\n",
+ fc_lport_state(lport));
fc_lport_state_enter(lport, LPORT_ST_LOGO);
-
- /* DNS session should be closed so we can release it here */
- if (lport->dns_rp)
- lport->tt.rport_logoff(lport->dns_rp);
+ fc_vports_linkchange(lport);
fp = fc_frame_alloc(lport, sizeof(*logo));
if (!fp) {
@@ -1431,23 +1685,24 @@ static void fc_lport_enter_logo(struct fc_lport *lport)
return;
}
- if (!lport->tt.elsct_send(lport, NULL, fp, ELS_LOGO, fc_lport_logo_resp,
- lport, lport->e_d_tov))
- fc_lport_error(lport, fp);
+ if (!lport->tt.elsct_send(lport, FC_FID_FLOGI, fp, ELS_LOGO,
+ fc_lport_logo_resp, lport,
+ 2 * lport->r_a_tov))
+ fc_lport_error(lport, NULL);
}
/**
- * fc_lport_flogi_resp - Handle response to FLOGI request
- * @sp: current sequence in FLOGI exchange
- * @fp: response frame
- * @lp_arg: Fibre Channel lport port instance that sent the FLOGI request
+ * fc_lport_flogi_resp() - Handle response to FLOGI request
+ * @sp: The sequence that the FLOGI was on
+ * @fp: The FLOGI response frame
+ * @lp_arg: The lport port that received the FLOGI response
*
* Locking Note: This function will be called without the lport lock
- * held, but it will lock, call an _enter_* function or fc_lport_error
+ * held, but it will lock, call an _enter_* function or fc_lport_error()
* and then unlock the lport.
*/
-static void fc_lport_flogi_resp(struct fc_seq *sp, struct fc_frame *fp,
- void *lp_arg)
+void fc_lport_flogi_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *lp_arg)
{
struct fc_lport *lport = lp_arg;
struct fc_frame_header *fh;
@@ -1458,16 +1713,18 @@ static void fc_lport_flogi_resp(struct fc_seq *sp, struct fc_frame *fp,
unsigned int e_d_tov;
u16 mfs;
+ FC_LPORT_DBG(lport, "Received a FLOGI %s\n", fc_els_resp_type(fp));
+
if (fp == ERR_PTR(-FC_EX_CLOSED))
return;
mutex_lock(&lport->lp_mutex);
- FC_DEBUG_LPORT("Received a FLOGI response\n");
-
if (lport->state != LPORT_ST_FLOGI) {
- FC_DBG("Received a FLOGI response, but in state %s\n",
- fc_lport_state(lport));
+ FC_LPORT_DBG(lport, "Received a FLOGI response, but in state "
+ "%s\n", fc_lport_state(lport));
+ if (IS_ERR(fp))
+ goto err;
goto out;
}
@@ -1477,52 +1734,65 @@ static void fc_lport_flogi_resp(struct fc_seq *sp, struct fc_frame *fp,
}
fh = fc_frame_header_get(fp);
- did = ntoh24(fh->fh_d_id);
- if (fc_frame_payload_op(fp) == ELS_LS_ACC && did != 0) {
-
- FC_DEBUG_LPORT("Assigned fid %x\n", did);
- fc_host_port_id(lport->host) = did;
-
- flp = fc_frame_payload_get(fp, sizeof(*flp));
- if (flp) {
- mfs = ntohs(flp->fl_csp.sp_bb_data) &
- FC_SP_BB_DATA_MASK;
- if (mfs >= FC_SP_MIN_MAX_PAYLOAD &&
- mfs < lport->mfs)
- lport->mfs = mfs;
- csp_flags = ntohs(flp->fl_csp.sp_features);
- r_a_tov = ntohl(flp->fl_csp.sp_r_a_tov);
- e_d_tov = ntohl(flp->fl_csp.sp_e_d_tov);
- if (csp_flags & FC_SP_FT_EDTR)
- e_d_tov /= 1000000;
- if ((csp_flags & FC_SP_FT_FPORT) == 0) {
- if (e_d_tov > lport->e_d_tov)
- lport->e_d_tov = e_d_tov;
- lport->r_a_tov = 2 * e_d_tov;
- FC_DBG("Point-to-Point mode\n");
- fc_lport_ptp_setup(lport, ntoh24(fh->fh_s_id),
- get_unaligned_be64(
- &flp->fl_wwpn),
- get_unaligned_be64(
- &flp->fl_wwnn));
- } else {
- lport->e_d_tov = e_d_tov;
- lport->r_a_tov = r_a_tov;
- fc_host_fabric_name(lport->host) =
- get_unaligned_be64(&flp->fl_wwnn);
- fc_lport_enter_dns(lport);
- }
- }
+ did = fc_frame_did(fp);
+ if (fh->fh_r_ctl != FC_RCTL_ELS_REP || did == 0 ||
+ fc_frame_payload_op(fp) != ELS_LS_ACC) {
+ FC_LPORT_DBG(lport, "FLOGI not accepted or bad response\n");
+ fc_lport_error(lport, fp);
+ goto err;
+ }
- if (flp) {
- csp_flags = ntohs(flp->fl_csp.sp_features);
- if ((csp_flags & FC_SP_FT_FPORT) == 0) {
- lport->tt.disc_start(fc_lport_disc_callback,
- lport);
- }
- }
+ flp = fc_frame_payload_get(fp, sizeof(*flp));
+ if (!flp) {
+ FC_LPORT_DBG(lport, "FLOGI bad response\n");
+ fc_lport_error(lport, fp);
+ goto err;
+ }
+
+ mfs = ntohs(flp->fl_csp.sp_bb_data) &
+ FC_SP_BB_DATA_MASK;
+
+ if (mfs < FC_SP_MIN_MAX_PAYLOAD || mfs > FC_SP_MAX_MAX_PAYLOAD) {
+ FC_LPORT_DBG(lport, "FLOGI bad mfs:%hu response, "
+ "lport->mfs:%hu\n", mfs, lport->mfs);
+ fc_lport_error(lport, fp);
+ goto err;
+ }
+
+ if (mfs <= lport->mfs) {
+ lport->mfs = mfs;
+ fc_host_maxframe_size(lport->host) = mfs;
+ }
+
+ csp_flags = ntohs(flp->fl_csp.sp_features);
+ r_a_tov = ntohl(flp->fl_csp.sp_r_a_tov);
+ e_d_tov = ntohl(flp->fl_csp.sp_e_d_tov);
+ if (csp_flags & FC_SP_FT_EDTR)
+ e_d_tov /= 1000000;
+
+ lport->npiv_enabled = !!(csp_flags & FC_SP_FT_NPIV_ACC);
+
+ if ((csp_flags & FC_SP_FT_FPORT) == 0) {
+ if (e_d_tov > lport->e_d_tov)
+ lport->e_d_tov = e_d_tov;
+ lport->r_a_tov = 2 * e_d_tov;
+ fc_lport_set_port_id(lport, did, fp);
+ printk(KERN_INFO "host%d: libfc: "
+ "Port (%6.6x) entered "
+ "point-to-point mode\n",
+ lport->host->host_no, did);
+ fc_lport_ptp_setup(lport, fc_frame_sid(fp),
+ get_unaligned_be64(
+ &flp->fl_wwpn),
+ get_unaligned_be64(
+ &flp->fl_wwnn));
} else {
- FC_DBG("bad FLOGI response\n");
+ lport->e_d_tov = e_d_tov;
+ lport->r_a_tov = r_a_tov;
+ fc_host_fabric_name(lport->host) =
+ get_unaligned_be64(&flp->fl_wwnn);
+ fc_lport_set_port_id(lport, did, fp);
+ fc_lport_enter_dns(lport);
}
out:
@@ -1530,46 +1800,65 @@ out:
err:
mutex_unlock(&lport->lp_mutex);
}
+EXPORT_SYMBOL(fc_lport_flogi_resp);
/**
- * fc_rport_enter_flogi - Send a FLOGI request to the fabric manager
+ * fc_rport_enter_flogi() - Send a FLOGI request to the fabric manager
* @lport: Fibre Channel local port to be logged in to the fabric
*
* Locking Note: The lport lock is expected to be held before calling
* this routine.
*/
-void fc_lport_enter_flogi(struct fc_lport *lport)
+static void fc_lport_enter_flogi(struct fc_lport *lport)
{
struct fc_frame *fp;
- FC_DEBUG_LPORT("Processing FLOGI state\n");
+ FC_LPORT_DBG(lport, "Entered FLOGI state from %s state\n",
+ fc_lport_state(lport));
fc_lport_state_enter(lport, LPORT_ST_FLOGI);
+ if (lport->point_to_multipoint) {
+ if (lport->port_id)
+ fc_lport_enter_ready(lport);
+ return;
+ }
+
fp = fc_frame_alloc(lport, sizeof(struct fc_els_flogi));
if (!fp)
return fc_lport_error(lport, fp);
- if (!lport->tt.elsct_send(lport, NULL, fp, ELS_FLOGI,
- fc_lport_flogi_resp, lport, lport->e_d_tov))
- fc_lport_error(lport, fp);
+ if (!lport->tt.elsct_send(lport, FC_FID_FLOGI, fp,
+ lport->vport ? ELS_FDISC : ELS_FLOGI,
+ fc_lport_flogi_resp, lport,
+ lport->vport ? 2 * lport->r_a_tov :
+ lport->e_d_tov))
+ fc_lport_error(lport, NULL);
}
-/* Configure a fc_lport */
+/**
+ * fc_lport_config() - Configure a fc_lport
+ * @lport: The local port to be configured
+ */
int fc_lport_config(struct fc_lport *lport)
{
INIT_DELAYED_WORK(&lport->retry_work, fc_lport_timeout);
mutex_init(&lport->lp_mutex);
- fc_lport_state_enter(lport, LPORT_ST_NONE);
+ fc_lport_state_enter(lport, LPORT_ST_DISABLED);
fc_lport_add_fc4_type(lport, FC_TYPE_FCP);
fc_lport_add_fc4_type(lport, FC_TYPE_CT);
+ fc_fc4_conf_lport_params(lport, FC_TYPE_FCP);
return 0;
}
EXPORT_SYMBOL(fc_lport_config);
+/**
+ * fc_lport_init() - Initialize the lport layer for a local port
+ * @lport: The local port to initialize the exchange layer for
+ */
int fc_lport_init(struct fc_lport *lport)
{
if (!lport->tt.lport_recv)
@@ -1598,7 +1887,258 @@ 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;
}
EXPORT_SYMBOL(fc_lport_init);
+
+/**
+ * fc_lport_bsg_resp() - The common response handler for FC Passthrough requests
+ * @sp: The sequence for the FC Passthrough response
+ * @fp: The response frame
+ * @info_arg: The BSG info that the response is for
+ */
+static void fc_lport_bsg_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *info_arg)
+{
+ struct fc_bsg_info *info = info_arg;
+ struct fc_bsg_job *job = info->job;
+ struct fc_lport *lport = info->lport;
+ struct fc_frame_header *fh;
+ size_t len;
+ void *buf;
+
+ if (IS_ERR(fp)) {
+ job->reply->result = (PTR_ERR(fp) == -FC_EX_CLOSED) ?
+ -ECONNABORTED : -ETIMEDOUT;
+ job->reply_len = sizeof(uint32_t);
+ job->state_flags |= FC_RQST_STATE_DONE;
+ job->job_done(job);
+ kfree(info);
+ return;
+ }
+
+ mutex_lock(&lport->lp_mutex);
+ fh = fc_frame_header_get(fp);
+ len = fr_len(fp) - sizeof(*fh);
+ buf = fc_frame_payload_get(fp, 0);
+
+ if (fr_sof(fp) == FC_SOF_I3 && !ntohs(fh->fh_seq_cnt)) {
+ /* Get the response code from the first frame payload */
+ unsigned short cmd = (info->rsp_code == FC_FS_ACC) ?
+ ntohs(((struct fc_ct_hdr *)buf)->ct_cmd) :
+ (unsigned short)fc_frame_payload_op(fp);
+
+ /* Save the reply status of the job */
+ job->reply->reply_data.ctels_reply.status =
+ (cmd == info->rsp_code) ?
+ FC_CTELS_STATUS_OK : FC_CTELS_STATUS_REJECT;
+ }
+
+ job->reply->reply_payload_rcv_len +=
+ fc_copy_buffer_to_sglist(buf, len, info->sg, &info->nents,
+ &info->offset, NULL);
+
+ if (fr_eof(fp) == FC_EOF_T &&
+ (ntoh24(fh->fh_f_ctl) & (FC_FC_LAST_SEQ | FC_FC_END_SEQ)) ==
+ (FC_FC_LAST_SEQ | FC_FC_END_SEQ)) {
+ if (job->reply->reply_payload_rcv_len >
+ job->reply_payload.payload_len)
+ job->reply->reply_payload_rcv_len =
+ job->reply_payload.payload_len;
+ job->reply->result = 0;
+ job->state_flags |= FC_RQST_STATE_DONE;
+ job->job_done(job);
+ kfree(info);
+ }
+ fc_frame_free(fp);
+ mutex_unlock(&lport->lp_mutex);
+}
+
+/**
+ * fc_lport_els_request() - Send ELS passthrough request
+ * @job: The BSG Passthrough job
+ * @lport: The local port sending the request
+ * @did: The destination port id
+ *
+ * Locking Note: The lport lock is expected to be held before calling
+ * this routine.
+ */
+static int fc_lport_els_request(struct fc_bsg_job *job,
+ struct fc_lport *lport,
+ u32 did, u32 tov)
+{
+ struct fc_bsg_info *info;
+ struct fc_frame *fp;
+ struct fc_frame_header *fh;
+ char *pp;
+ int len;
+
+ fp = fc_frame_alloc(lport, job->request_payload.payload_len);
+ if (!fp)
+ return -ENOMEM;
+
+ len = job->request_payload.payload_len;
+ pp = fc_frame_payload_get(fp, len);
+
+ sg_copy_to_buffer(job->request_payload.sg_list,
+ job->request_payload.sg_cnt,
+ pp, len);
+
+ fh = fc_frame_header_get(fp);
+ fh->fh_r_ctl = FC_RCTL_ELS_REQ;
+ hton24(fh->fh_d_id, did);
+ hton24(fh->fh_s_id, lport->port_id);
+ fh->fh_type = FC_TYPE_ELS;
+ hton24(fh->fh_f_ctl, FC_FCTL_REQ);
+ fh->fh_cs_ctl = 0;
+ fh->fh_df_ctl = 0;
+ fh->fh_parm_offset = 0;
+
+ info = kzalloc(sizeof(struct fc_bsg_info), GFP_KERNEL);
+ if (!info) {
+ fc_frame_free(fp);
+ return -ENOMEM;
+ }
+
+ info->job = job;
+ info->lport = lport;
+ info->rsp_code = ELS_LS_ACC;
+ info->nents = job->reply_payload.sg_cnt;
+ info->sg = job->reply_payload.sg_list;
+
+ if (!lport->tt.exch_seq_send(lport, fp, fc_lport_bsg_resp,
+ NULL, info, tov)) {
+ kfree(info);
+ return -ECOMM;
+ }
+ return 0;
+}
+
+/**
+ * fc_lport_ct_request() - Send CT Passthrough request
+ * @job: The BSG Passthrough job
+ * @lport: The local port sending the request
+ * @did: The destination FC-ID
+ * @tov: The timeout period to wait for the response
+ *
+ * Locking Note: The lport lock is expected to be held before calling
+ * this routine.
+ */
+static int fc_lport_ct_request(struct fc_bsg_job *job,
+ struct fc_lport *lport, u32 did, u32 tov)
+{
+ struct fc_bsg_info *info;
+ struct fc_frame *fp;
+ struct fc_frame_header *fh;
+ struct fc_ct_req *ct;
+ size_t len;
+
+ fp = fc_frame_alloc(lport, sizeof(struct fc_ct_hdr) +
+ job->request_payload.payload_len);
+ if (!fp)
+ return -ENOMEM;
+
+ len = job->request_payload.payload_len;
+ ct = fc_frame_payload_get(fp, len);
+
+ sg_copy_to_buffer(job->request_payload.sg_list,
+ job->request_payload.sg_cnt,
+ ct, len);
+
+ fh = fc_frame_header_get(fp);
+ fh->fh_r_ctl = FC_RCTL_DD_UNSOL_CTL;
+ hton24(fh->fh_d_id, did);
+ hton24(fh->fh_s_id, lport->port_id);
+ fh->fh_type = FC_TYPE_CT;
+ hton24(fh->fh_f_ctl, FC_FCTL_REQ);
+ fh->fh_cs_ctl = 0;
+ fh->fh_df_ctl = 0;
+ fh->fh_parm_offset = 0;
+
+ info = kzalloc(sizeof(struct fc_bsg_info), GFP_KERNEL);
+ if (!info) {
+ fc_frame_free(fp);
+ return -ENOMEM;
+ }
+
+ info->job = job;
+ info->lport = lport;
+ info->rsp_code = FC_FS_ACC;
+ info->nents = job->reply_payload.sg_cnt;
+ info->sg = job->reply_payload.sg_list;
+
+ if (!lport->tt.exch_seq_send(lport, fp, fc_lport_bsg_resp,
+ NULL, info, tov)) {
+ kfree(info);
+ return -ECOMM;
+ }
+ return 0;
+}
+
+/**
+ * fc_lport_bsg_request() - The common entry point for sending
+ * FC Passthrough requests
+ * @job: The BSG passthrough job
+ */
+int fc_lport_bsg_request(struct fc_bsg_job *job)
+{
+ struct request *rsp = job->req->next_rq;
+ struct Scsi_Host *shost = job->shost;
+ struct fc_lport *lport = shost_priv(shost);
+ struct fc_rport *rport;
+ struct fc_rport_priv *rdata;
+ int rc = -EINVAL;
+ u32 did;
+
+ job->reply->reply_payload_rcv_len = 0;
+ if (rsp)
+ rsp->resid_len = job->reply_payload.payload_len;
+
+ mutex_lock(&lport->lp_mutex);
+
+ switch (job->request->msgcode) {
+ case FC_BSG_RPT_ELS:
+ rport = job->rport;
+ if (!rport)
+ break;
+
+ rdata = rport->dd_data;
+ rc = fc_lport_els_request(job, lport, rport->port_id,
+ rdata->e_d_tov);
+ break;
+
+ case FC_BSG_RPT_CT:
+ rport = job->rport;
+ if (!rport)
+ break;
+
+ rdata = rport->dd_data;
+ rc = fc_lport_ct_request(job, lport, rport->port_id,
+ rdata->e_d_tov);
+ break;
+
+ case FC_BSG_HST_CT:
+ did = ntoh24(job->request->rqst_data.h_ct.port_id);
+ if (did == FC_FID_DIR_SERV)
+ rdata = lport->dns_rdata;
+ else
+ rdata = lport->tt.rport_lookup(lport, did);
+
+ if (!rdata)
+ break;
+
+ rc = fc_lport_ct_request(job, lport, did, rdata->e_d_tov);
+ break;
+
+ case FC_BSG_HST_ELS_NOLOGIN:
+ did = ntoh24(job->request->rqst_data.h_els.port_id);
+ rc = fc_lport_els_request(job, lport, did, lport->e_d_tov);
+ break;
+ }
+
+ mutex_unlock(&lport->lp_mutex);
+ return rc;
+}
+EXPORT_SYMBOL(fc_lport_bsg_request);
diff --git a/drivers/scsi/libfc/fc_npiv.c b/drivers/scsi/libfc/fc_npiv.c
new file mode 100644
index 00000000000..9fbf78ed821
--- /dev/null
+++ b/drivers/scsi/libfc/fc_npiv.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright(c) 2009 Intel Corporation. All rights reserved.
+ *
+ * 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.
+ *
+ * Maintained at www.Open-FCoE.org
+ */
+
+/*
+ * NPIV VN_Port helper functions for libfc
+ */
+
+#include <scsi/libfc.h>
+#include <linux/export.h>
+
+/**
+ * fc_vport_create() - Create a new NPIV vport instance
+ * @vport: fc_vport structure from scsi_transport_fc
+ * @privsize: driver private data size to allocate along with the Scsi_Host
+ */
+
+struct fc_lport *libfc_vport_create(struct fc_vport *vport, int privsize)
+{
+ struct Scsi_Host *shost = vport_to_shost(vport);
+ struct fc_lport *n_port = shost_priv(shost);
+ struct fc_lport *vn_port;
+
+ vn_port = libfc_host_alloc(shost->hostt, privsize);
+ if (!vn_port)
+ return vn_port;
+
+ vn_port->vport = vport;
+ vport->dd_data = vn_port;
+
+ mutex_lock(&n_port->lp_mutex);
+ list_add_tail(&vn_port->list, &n_port->vports);
+ mutex_unlock(&n_port->lp_mutex);
+
+ return vn_port;
+}
+EXPORT_SYMBOL(libfc_vport_create);
+
+/**
+ * fc_vport_id_lookup() - find NPIV lport that matches a given fabric ID
+ * @n_port: Top level N_Port which may have multiple NPIV VN_Ports
+ * @port_id: Fabric ID to find a match for
+ *
+ * Returns: matching lport pointer or NULL if there is no match
+ */
+struct fc_lport *fc_vport_id_lookup(struct fc_lport *n_port, u32 port_id)
+{
+ struct fc_lport *lport = NULL;
+ struct fc_lport *vn_port;
+
+ if (n_port->port_id == 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 (vn_port->port_id == port_id) {
+ lport = vn_port;
+ break;
+ }
+ }
+ mutex_unlock(&n_port->lp_mutex);
+
+ return lport;
+}
+EXPORT_SYMBOL(fc_vport_id_lookup);
+
+/*
+ * When setting the link state of vports during an lport state change, it's
+ * necessary to hold the lp_mutex of both the N_Port and the VN_Port.
+ * This tells the lockdep engine to treat the nested locking of the VN_Port
+ * as a different lock class.
+ */
+enum libfc_lport_mutex_class {
+ LPORT_MUTEX_NORMAL = 0,
+ LPORT_MUTEX_VN_PORT = 1,
+};
+
+/**
+ * __fc_vport_setlink() - update link and status on a VN_Port
+ * @n_port: parent N_Port
+ * @vn_port: VN_Port to update
+ *
+ * Locking: must be called with both the N_Port and VN_Port lp_mutex held
+ */
+static void __fc_vport_setlink(struct fc_lport *n_port,
+ struct fc_lport *vn_port)
+{
+ struct fc_vport *vport = vn_port->vport;
+
+ if (vn_port->state == LPORT_ST_DISABLED)
+ return;
+
+ if (n_port->state == LPORT_ST_READY) {
+ if (n_port->npiv_enabled) {
+ fc_vport_set_state(vport, FC_VPORT_INITIALIZING);
+ __fc_linkup(vn_port);
+ } else {
+ fc_vport_set_state(vport, FC_VPORT_NO_FABRIC_SUPP);
+ __fc_linkdown(vn_port);
+ }
+ } else {
+ fc_vport_set_state(vport, FC_VPORT_LINKDOWN);
+ __fc_linkdown(vn_port);
+ }
+}
+
+/**
+ * fc_vport_setlink() - update link and status on a VN_Port
+ * @vn_port: virtual port to update
+ */
+void fc_vport_setlink(struct fc_lport *vn_port)
+{
+ struct fc_vport *vport = vn_port->vport;
+ struct Scsi_Host *shost = vport_to_shost(vport);
+ struct fc_lport *n_port = shost_priv(shost);
+
+ mutex_lock(&n_port->lp_mutex);
+ mutex_lock_nested(&vn_port->lp_mutex, LPORT_MUTEX_VN_PORT);
+ __fc_vport_setlink(n_port, vn_port);
+ mutex_unlock(&vn_port->lp_mutex);
+ mutex_unlock(&n_port->lp_mutex);
+}
+EXPORT_SYMBOL(fc_vport_setlink);
+
+/**
+ * fc_vports_linkchange() - change the link state of all vports
+ * @n_port: Parent N_Port that has changed state
+ *
+ * Locking: called with the n_port lp_mutex held
+ */
+void fc_vports_linkchange(struct fc_lport *n_port)
+{
+ struct fc_lport *vn_port;
+
+ list_for_each_entry(vn_port, &n_port->vports, list) {
+ mutex_lock_nested(&vn_port->lp_mutex, LPORT_MUTEX_VN_PORT);
+ __fc_vport_setlink(n_port, vn_port);
+ mutex_unlock(&vn_port->lp_mutex);
+ }
+}
+
diff --git a/drivers/scsi/libfc/fc_rport.c b/drivers/scsi/libfc/fc_rport.c
index e780d8caf70..589ff9aedd3 100644
--- a/drivers/scsi/libfc/fc_rport.c
+++ b/drivers/scsi/libfc/fc_rport.c
@@ -34,7 +34,7 @@
* The rport should never hold the rport mutex and then attempt to acquire
* either the lport or disc mutexes. The rport's mutex is considered lesser
* than both the lport's mutex and the disc mutex. Refer to fc_lport.c for
- * more comments on the heirarchy.
+ * more comments on the hierarchy.
*
* The locking strategy is similar to the lport's strategy. The lock protects
* the rport's states and is held and released by the entry points to the rport
@@ -47,111 +47,131 @@
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
+#include <linux/slab.h>
#include <linux/rcupdate.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
+#include <linux/export.h>
#include <asm/unaligned.h>
#include <scsi/libfc.h>
#include <scsi/fc_encode.h>
-static int fc_rport_debug;
-
-#define FC_DEBUG_RPORT(fmt...) \
- do { \
- if (fc_rport_debug) \
- FC_DBG(fmt); \
- } while (0)
-
-struct workqueue_struct *rport_event_queue;
-
-static void fc_rport_enter_plogi(struct fc_rport *);
-static void fc_rport_enter_prli(struct fc_rport *);
-static void fc_rport_enter_rtv(struct fc_rport *);
-static void fc_rport_enter_ready(struct fc_rport *);
-static void fc_rport_enter_logo(struct fc_rport *);
-
-static void fc_rport_recv_plogi_req(struct fc_rport *,
- struct fc_seq *, struct fc_frame *);
-static void fc_rport_recv_prli_req(struct fc_rport *,
- struct fc_seq *, struct fc_frame *);
-static void fc_rport_recv_prlo_req(struct fc_rport *,
- struct fc_seq *, struct fc_frame *);
-static void fc_rport_recv_logo_req(struct fc_rport *,
- struct fc_seq *, struct fc_frame *);
+#include "fc_libfc.h"
+
+static struct workqueue_struct *rport_event_queue;
+
+static void fc_rport_enter_flogi(struct fc_rport_priv *);
+static void fc_rport_enter_plogi(struct fc_rport_priv *);
+static void fc_rport_enter_prli(struct fc_rport_priv *);
+static void fc_rport_enter_rtv(struct fc_rport_priv *);
+static void fc_rport_enter_ready(struct fc_rport_priv *);
+static void fc_rport_enter_logo(struct fc_rport_priv *);
+static void fc_rport_enter_adisc(struct fc_rport_priv *);
+
+static void fc_rport_recv_plogi_req(struct fc_lport *, struct fc_frame *);
+static void fc_rport_recv_prli_req(struct fc_rport_priv *, struct fc_frame *);
+static void fc_rport_recv_prlo_req(struct fc_rport_priv *, struct fc_frame *);
+static void fc_rport_recv_logo_req(struct fc_lport *, struct fc_frame *);
static void fc_rport_timeout(struct work_struct *);
-static void fc_rport_error(struct fc_rport *, struct fc_frame *);
+static void fc_rport_error(struct fc_rport_priv *, struct fc_frame *);
+static void fc_rport_error_retry(struct fc_rport_priv *, struct fc_frame *);
static void fc_rport_work(struct work_struct *);
static const char *fc_rport_state_names[] = {
- [RPORT_ST_NONE] = "None",
[RPORT_ST_INIT] = "Init",
+ [RPORT_ST_FLOGI] = "FLOGI",
+ [RPORT_ST_PLOGI_WAIT] = "PLOGI_WAIT",
[RPORT_ST_PLOGI] = "PLOGI",
[RPORT_ST_PRLI] = "PRLI",
[RPORT_ST_RTV] = "RTV",
[RPORT_ST_READY] = "Ready",
- [RPORT_ST_LOGO] = "LOGO",
+ [RPORT_ST_ADISC] = "ADISC",
+ [RPORT_ST_DELETE] = "Delete",
};
-static void fc_rport_rogue_destroy(struct device *dev)
+/**
+ * fc_rport_lookup() - Lookup a remote port by port_id
+ * @lport: The local port to lookup the remote port on
+ * @port_id: The remote port ID to look up
+ *
+ * The caller must hold either disc_mutex or rcu_read_lock().
+ */
+static struct fc_rport_priv *fc_rport_lookup(const struct fc_lport *lport,
+ u32 port_id)
{
- struct fc_rport *rport = dev_to_rport(dev);
- FC_DEBUG_RPORT("Destroying rogue rport (%6x)\n", rport->port_id);
- kfree(rport);
+ struct fc_rport_priv *rdata;
+
+ list_for_each_entry_rcu(rdata, &lport->disc.rports, peers)
+ if (rdata->ids.port_id == port_id)
+ return rdata;
+ return NULL;
}
-struct fc_rport *fc_rport_rogue_create(struct fc_disc_port *dp)
+/**
+ * fc_rport_create() - Create a new remote port
+ * @lport: The local port this remote port will be associated with
+ * @ids: The identifiers for the new remote port
+ *
+ * The remote port will start in the INIT state.
+ *
+ * Locking note: must be called with the disc_mutex held.
+ */
+static struct fc_rport_priv *fc_rport_create(struct fc_lport *lport,
+ u32 port_id)
{
- struct fc_rport *rport;
- struct fc_rport_libfc_priv *rdata;
- rport = kzalloc(sizeof(*rport) + sizeof(*rdata), GFP_KERNEL);
+ struct fc_rport_priv *rdata;
+
+ rdata = lport->tt.rport_lookup(lport, port_id);
+ if (rdata)
+ return rdata;
- if (!rport)
+ rdata = kzalloc(sizeof(*rdata) + lport->rport_priv_size, GFP_KERNEL);
+ if (!rdata)
return NULL;
- rdata = RPORT_TO_PRIV(rport);
-
- rport->dd_data = rdata;
- rport->port_id = dp->ids.port_id;
- rport->port_name = dp->ids.port_name;
- rport->node_name = dp->ids.node_name;
- rport->roles = dp->ids.roles;
- rport->maxframe_size = FC_MIN_MAX_PAYLOAD;
- /*
- * Note: all this libfc rogue rport code will be removed for
- * upstream so it fine that this is really ugly and hacky right now.
- */
- device_initialize(&rport->dev);
- rport->dev.release = fc_rport_rogue_destroy;
+ rdata->ids.node_name = -1;
+ rdata->ids.port_name = -1;
+ rdata->ids.port_id = port_id;
+ rdata->ids.roles = FC_RPORT_ROLE_UNKNOWN;
+ kref_init(&rdata->kref);
mutex_init(&rdata->rp_mutex);
- rdata->local_port = dp->lp;
- rdata->trans_state = FC_PORTSTATE_ROGUE;
+ rdata->local_port = lport;
rdata->rp_state = RPORT_ST_INIT;
rdata->event = RPORT_EV_NONE;
rdata->flags = FC_RP_FLAGS_REC_SUPPORTED;
- rdata->ops = NULL;
- rdata->e_d_tov = dp->lp->e_d_tov;
- rdata->r_a_tov = dp->lp->r_a_tov;
+ rdata->e_d_tov = lport->e_d_tov;
+ rdata->r_a_tov = lport->r_a_tov;
+ rdata->maxframe_size = FC_MIN_MAX_PAYLOAD;
INIT_DELAYED_WORK(&rdata->retry_work, fc_rport_timeout);
INIT_WORK(&rdata->event_work, fc_rport_work);
- /*
- * For good measure, but not necessary as we should only
- * add REAL rport to the lport list.
- */
- INIT_LIST_HEAD(&rdata->peers);
+ if (port_id != FC_FID_DIR_SERV) {
+ rdata->lld_event_callback = lport->tt.rport_event_callback;
+ list_add_rcu(&rdata->peers, &lport->disc.rports);
+ }
+ return rdata;
+}
- return rport;
+/**
+ * fc_rport_destroy() - Free a remote port after last reference is released
+ * @kref: The remote port's kref
+ */
+static void fc_rport_destroy(struct kref *kref)
+{
+ struct fc_rport_priv *rdata;
+
+ rdata = container_of(kref, struct fc_rport_priv, kref);
+ kfree_rcu(rdata, rcu);
}
/**
- * fc_rport_state - return a string for the state the rport is in
- * @rport: The rport whose state we want to get a string for
+ * fc_rport_state() - Return a string identifying the remote port's state
+ * @rdata: The remote port
*/
-static const char *fc_rport_state(struct fc_rport *rport)
+static const char *fc_rport_state(struct fc_rport_priv *rdata)
{
const char *cp;
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
cp = fc_rport_state_names[rdata->rp_state];
if (!cp)
@@ -160,26 +180,28 @@ static const char *fc_rport_state(struct fc_rport *rport)
}
/**
- * fc_set_rport_loss_tmo - Set the remote port loss timeout in seconds.
- * @rport: Pointer to Fibre Channel remote port structure
- * @timeout: timeout in seconds
+ * fc_set_rport_loss_tmo() - Set the remote port loss timeout
+ * @rport: The remote port that gets a new timeout value
+ * @timeout: The new timeout value (in seconds)
*/
void fc_set_rport_loss_tmo(struct fc_rport *rport, u32 timeout)
{
if (timeout)
- rport->dev_loss_tmo = timeout + 5;
+ rport->dev_loss_tmo = timeout;
else
- rport->dev_loss_tmo = 30;
+ rport->dev_loss_tmo = 1;
}
EXPORT_SYMBOL(fc_set_rport_loss_tmo);
/**
- * fc_plogi_get_maxframe - Get max payload from the common service parameters
- * @flp: FLOGI payload structure
- * @maxval: upper limit, may be less than what is in the service parameters
+ * fc_plogi_get_maxframe() - Get the maximum payload from the common service
+ * parameters in a FLOGI frame
+ * @flp: The FLOGI or PLOGI payload
+ * @maxval: The maximum frame size upper limit; this may be less than what
+ * is in the service parameters
*/
-static unsigned int
-fc_plogi_get_maxframe(struct fc_els_flogi *flp, unsigned int maxval)
+static unsigned int fc_plogi_get_maxframe(struct fc_els_flogi *flp,
+ unsigned int maxval)
{
unsigned int mfs;
@@ -197,177 +219,282 @@ fc_plogi_get_maxframe(struct fc_els_flogi *flp, unsigned int maxval)
}
/**
- * fc_rport_state_enter - Change the rport's state
- * @rport: The rport whose state should change
- * @new: The new state of the rport
+ * fc_rport_state_enter() - Change the state of a remote port
+ * @rdata: The remote port whose state should change
+ * @new: The new state
*
* Locking Note: Called with the rport lock held
*/
-static void fc_rport_state_enter(struct fc_rport *rport,
+static void fc_rport_state_enter(struct fc_rport_priv *rdata,
enum fc_rport_state new)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
if (rdata->rp_state != new)
rdata->retries = 0;
rdata->rp_state = new;
}
+/**
+ * fc_rport_work() - Handler for remote port events in the rport_event_queue
+ * @work: Handle to the remote port being dequeued
+ */
static void fc_rport_work(struct work_struct *work)
{
- struct fc_rport_libfc_priv *rdata =
- container_of(work, struct fc_rport_libfc_priv, event_work);
+ u32 port_id;
+ struct fc_rport_priv *rdata =
+ container_of(work, struct fc_rport_priv, event_work);
+ struct fc_rport_libfc_priv *rpriv;
enum fc_rport_event event;
- enum fc_rport_trans_state trans_state;
struct fc_lport *lport = rdata->local_port;
struct fc_rport_operations *rport_ops;
- struct fc_rport *rport = PRIV_TO_RPORT(rdata);
+ struct fc_rport_identifiers ids;
+ struct fc_rport *rport;
+ struct fc4_prov *prov;
+ u8 type;
mutex_lock(&rdata->rp_mutex);
event = rdata->event;
rport_ops = rdata->ops;
+ rport = rdata->rport;
- if (event == RPORT_EV_CREATED) {
- struct fc_rport *new_rport;
- struct fc_rport_libfc_priv *new_rdata;
- struct fc_rport_identifiers ids;
+ FC_RPORT_DBG(rdata, "work event %u\n", event);
- ids.port_id = rport->port_id;
- ids.roles = rport->roles;
- ids.port_name = rport->port_name;
- ids.node_name = rport->node_name;
+ switch (event) {
+ case RPORT_EV_READY:
+ ids = rdata->ids;
+ rdata->event = RPORT_EV_NONE;
+ rdata->major_retries = 0;
+ kref_get(&rdata->kref);
+ mutex_unlock(&rdata->rp_mutex);
+ if (!rport)
+ rport = fc_remote_port_add(lport->host, 0, &ids);
+ if (!rport) {
+ FC_RPORT_DBG(rdata, "Failed to add the rport\n");
+ lport->tt.rport_logoff(rdata);
+ kref_put(&rdata->kref, lport->tt.rport_destroy);
+ return;
+ }
+ mutex_lock(&rdata->rp_mutex);
+ if (rdata->rport)
+ FC_RPORT_DBG(rdata, "rport already allocated\n");
+ rdata->rport = rport;
+ rport->maxframe_size = rdata->maxframe_size;
+ rport->supported_classes = rdata->supported_classes;
+
+ rpriv = rport->dd_data;
+ rpriv->local_port = lport;
+ rpriv->rp_state = rdata->rp_state;
+ rpriv->flags = rdata->flags;
+ rpriv->e_d_tov = rdata->e_d_tov;
+ rpriv->r_a_tov = rdata->r_a_tov;
mutex_unlock(&rdata->rp_mutex);
- new_rport = fc_remote_port_add(lport->host, 0, &ids);
- if (new_rport) {
- /*
- * Switch from the rogue rport to the rport
- * returned by the FC class.
- */
- new_rport->maxframe_size = rport->maxframe_size;
-
- new_rdata = new_rport->dd_data;
- new_rdata->e_d_tov = rdata->e_d_tov;
- new_rdata->r_a_tov = rdata->r_a_tov;
- new_rdata->ops = rdata->ops;
- new_rdata->local_port = rdata->local_port;
- new_rdata->flags = FC_RP_FLAGS_REC_SUPPORTED;
- new_rdata->trans_state = FC_PORTSTATE_REAL;
- mutex_init(&new_rdata->rp_mutex);
- INIT_DELAYED_WORK(&new_rdata->retry_work,
- fc_rport_timeout);
- INIT_LIST_HEAD(&new_rdata->peers);
- INIT_WORK(&new_rdata->event_work, fc_rport_work);
-
- fc_rport_state_enter(new_rport, RPORT_ST_READY);
- } else {
- FC_DBG("Failed to create the rport for port "
- "(%6x).\n", ids.port_id);
- event = RPORT_EV_FAILED;
+ if (rport_ops && rport_ops->event_callback) {
+ FC_RPORT_DBG(rdata, "callback ev %d\n", event);
+ rport_ops->event_callback(lport, rdata, event);
}
- put_device(&rport->dev);
- rport = new_rport;
- rdata = new_rport->dd_data;
- if (rport_ops->event_callback)
- rport_ops->event_callback(lport, rport, event);
- } else if ((event == RPORT_EV_FAILED) ||
- (event == RPORT_EV_LOGO) ||
- (event == RPORT_EV_STOP)) {
- trans_state = rdata->trans_state;
+ if (rdata->lld_event_callback) {
+ FC_RPORT_DBG(rdata, "lld callback ev %d\n", event);
+ rdata->lld_event_callback(lport, rdata, event);
+ }
+ kref_put(&rdata->kref, lport->tt.rport_destroy);
+ break;
+
+ 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);
- if (rport_ops->event_callback)
- rport_ops->event_callback(lport, rport, event);
- if (trans_state == FC_PORTSTATE_ROGUE)
- put_device(&rport->dev);
- else
+
+ if (rport_ops && rport_ops->event_callback) {
+ FC_RPORT_DBG(rdata, "callback ev %d\n", event);
+ rport_ops->event_callback(lport, rdata, event);
+ }
+ if (rdata->lld_event_callback) {
+ FC_RPORT_DBG(rdata, "lld callback ev %d\n", event);
+ rdata->lld_event_callback(lport, rdata, event);
+ }
+ cancel_delayed_work_sync(&rdata->retry_work);
+
+ /*
+ * Reset any outstanding exchanges before freeing rport.
+ */
+ lport->tt.exch_mgr_reset(lport, 0, port_id);
+ lport->tt.exch_mgr_reset(lport, port_id, 0);
+
+ if (rport) {
+ rpriv = rport->dd_data;
+ rpriv->rp_state = RPORT_ST_DELETE;
+ mutex_lock(&rdata->rp_mutex);
+ rdata->rport = NULL;
+ mutex_unlock(&rdata->rp_mutex);
fc_remote_port_delete(rport);
- } else
+ }
+
+ mutex_lock(&lport->disc.disc_mutex);
+ mutex_lock(&rdata->rp_mutex);
+ if (rdata->rp_state == RPORT_ST_DELETE) {
+ if (port_id == FC_FID_DIR_SERV) {
+ rdata->event = RPORT_EV_NONE;
+ mutex_unlock(&rdata->rp_mutex);
+ kref_put(&rdata->kref, lport->tt.rport_destroy);
+ } else if ((rdata->flags & FC_RP_STARTED) &&
+ rdata->major_retries <
+ lport->max_rport_retry_count) {
+ rdata->major_retries++;
+ rdata->event = RPORT_EV_NONE;
+ FC_RPORT_DBG(rdata, "work restart\n");
+ fc_rport_enter_flogi(rdata);
+ mutex_unlock(&rdata->rp_mutex);
+ } else {
+ FC_RPORT_DBG(rdata, "work delete\n");
+ list_del_rcu(&rdata->peers);
+ mutex_unlock(&rdata->rp_mutex);
+ kref_put(&rdata->kref, lport->tt.rport_destroy);
+ }
+ } else {
+ /*
+ * Re-open for events. Reissue READY event if ready.
+ */
+ rdata->event = RPORT_EV_NONE;
+ if (rdata->rp_state == RPORT_ST_READY)
+ fc_rport_enter_ready(rdata);
+ mutex_unlock(&rdata->rp_mutex);
+ }
+ mutex_unlock(&lport->disc.disc_mutex);
+ break;
+
+ default:
mutex_unlock(&rdata->rp_mutex);
+ break;
+ }
}
/**
- * fc_rport_login - Start the remote port login state machine
- * @rport: Fibre Channel remote port
+ * fc_rport_login() - Start the remote port login state machine
+ * @rdata: The remote port to be logged in to
*
* Locking Note: Called without the rport lock held. This
* function will hold the rport lock, call an _enter_*
* function and then unlock the rport.
+ *
+ * This indicates the intent to be logged into the remote port.
+ * If it appears we are already logged in, ADISC is used to verify
+ * the setup.
*/
-int fc_rport_login(struct fc_rport *rport)
+static int fc_rport_login(struct fc_rport_priv *rdata)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
-
mutex_lock(&rdata->rp_mutex);
- FC_DEBUG_RPORT("Login to port (%6x)\n", rport->port_id);
-
- fc_rport_enter_plogi(rport);
-
+ rdata->flags |= FC_RP_STARTED;
+ switch (rdata->rp_state) {
+ case RPORT_ST_READY:
+ FC_RPORT_DBG(rdata, "ADISC port\n");
+ fc_rport_enter_adisc(rdata);
+ break;
+ case RPORT_ST_DELETE:
+ FC_RPORT_DBG(rdata, "Restart deleted port\n");
+ break;
+ default:
+ FC_RPORT_DBG(rdata, "Login to port\n");
+ fc_rport_enter_flogi(rdata);
+ break;
+ }
mutex_unlock(&rdata->rp_mutex);
return 0;
}
/**
- * fc_rport_logoff - Logoff and remove an rport
- * @rport: Fibre Channel remote port to be removed
+ * fc_rport_enter_delete() - Schedule a remote port to be deleted
+ * @rdata: The remote port to be deleted
+ * @event: The event to report as the reason for deletion
+ *
+ * Locking Note: Called with the rport lock held.
+ *
+ * Allow state change into DELETE only once.
+ *
+ * Call queue_work only if there's no event already pending.
+ * Set the new event so that the old pending event will not occur.
+ * Since we have the mutex, even if fc_rport_work() is already started,
+ * it'll see the new event.
+ */
+static void fc_rport_enter_delete(struct fc_rport_priv *rdata,
+ enum fc_rport_event event)
+{
+ if (rdata->rp_state == RPORT_ST_DELETE)
+ return;
+
+ FC_RPORT_DBG(rdata, "Delete port\n");
+
+ fc_rport_state_enter(rdata, RPORT_ST_DELETE);
+
+ if (rdata->event == RPORT_EV_NONE)
+ queue_work(rport_event_queue, &rdata->event_work);
+ rdata->event = event;
+}
+
+/**
+ * fc_rport_logoff() - Logoff and remove a remote port
+ * @rdata: The remote port to be logged off of
*
* Locking Note: Called without the rport lock held. This
* function will hold the rport lock, call an _enter_*
* function and then unlock the rport.
*/
-int fc_rport_logoff(struct fc_rport *rport)
+static int fc_rport_logoff(struct fc_rport_priv *rdata)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
-
mutex_lock(&rdata->rp_mutex);
- FC_DEBUG_RPORT("Remove port (%6x)\n", rport->port_id);
+ FC_RPORT_DBG(rdata, "Remove port\n");
- fc_rport_enter_logo(rport);
+ rdata->flags &= ~FC_RP_STARTED;
+ if (rdata->rp_state == RPORT_ST_DELETE) {
+ FC_RPORT_DBG(rdata, "Port in Delete state, not removing\n");
+ goto out;
+ }
+ fc_rport_enter_logo(rdata);
/*
- * Change the state to NONE so that we discard
+ * Change the state to Delete so that we discard
* the response.
*/
- fc_rport_state_enter(rport, RPORT_ST_NONE);
-
- mutex_unlock(&rdata->rp_mutex);
-
- cancel_delayed_work_sync(&rdata->retry_work);
-
- mutex_lock(&rdata->rp_mutex);
-
- rdata->event = RPORT_EV_STOP;
- queue_work(rport_event_queue, &rdata->event_work);
-
+ fc_rport_enter_delete(rdata, RPORT_EV_STOP);
+out:
mutex_unlock(&rdata->rp_mutex);
-
return 0;
}
/**
- * fc_rport_enter_ready - The rport is ready
- * @rport: Fibre Channel remote port that is ready
+ * fc_rport_enter_ready() - Transition to the RPORT_ST_READY state
+ * @rdata: The remote port that is ready
*
* Locking Note: The rport lock is expected to be held before calling
* this routine.
*/
-static void fc_rport_enter_ready(struct fc_rport *rport)
+static void fc_rport_enter_ready(struct fc_rport_priv *rdata)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
-
- fc_rport_state_enter(rport, RPORT_ST_READY);
+ fc_rport_state_enter(rdata, RPORT_ST_READY);
- FC_DEBUG_RPORT("Port (%6x) is Ready\n", rport->port_id);
+ FC_RPORT_DBG(rdata, "Port is Ready\n");
- rdata->event = RPORT_EV_CREATED;
- queue_work(rport_event_queue, &rdata->event_work);
+ if (rdata->event == RPORT_EV_NONE)
+ queue_work(rport_event_queue, &rdata->event_work);
+ rdata->event = RPORT_EV_READY;
}
/**
- * fc_rport_timeout - Handler for the retry_work timer.
- * @work: The work struct of the fc_rport_libfc_priv
+ * fc_rport_timeout() - Handler for the retry_work timer
+ * @work: Handle to the remote port that has timed out
*
* Locking Note: Called without the rport lock held. This
* function will hold the rport lock, call an _enter_*
@@ -375,354 +502,644 @@ static void fc_rport_enter_ready(struct fc_rport *rport)
*/
static void fc_rport_timeout(struct work_struct *work)
{
- struct fc_rport_libfc_priv *rdata =
- container_of(work, struct fc_rport_libfc_priv, retry_work.work);
- struct fc_rport *rport = PRIV_TO_RPORT(rdata);
+ struct fc_rport_priv *rdata =
+ container_of(work, struct fc_rport_priv, retry_work.work);
mutex_lock(&rdata->rp_mutex);
switch (rdata->rp_state) {
+ case RPORT_ST_FLOGI:
+ fc_rport_enter_flogi(rdata);
+ break;
case RPORT_ST_PLOGI:
- fc_rport_enter_plogi(rport);
+ fc_rport_enter_plogi(rdata);
break;
case RPORT_ST_PRLI:
- fc_rport_enter_prli(rport);
+ fc_rport_enter_prli(rdata);
break;
case RPORT_ST_RTV:
- fc_rport_enter_rtv(rport);
+ fc_rport_enter_rtv(rdata);
break;
- case RPORT_ST_LOGO:
- fc_rport_enter_logo(rport);
+ case RPORT_ST_ADISC:
+ fc_rport_enter_adisc(rdata);
break;
+ case RPORT_ST_PLOGI_WAIT:
case RPORT_ST_READY:
case RPORT_ST_INIT:
- case RPORT_ST_NONE:
+ case RPORT_ST_DELETE:
break;
}
mutex_unlock(&rdata->rp_mutex);
- put_device(&rport->dev);
}
/**
- * fc_rport_error - Handler for any errors
- * @rport: The fc_rport object
- * @fp: The frame pointer
+ * fc_rport_error() - Error handler, called once retries have been exhausted
+ * @rdata: The remote port the error is happened on
+ * @fp: The error code encapsulated in a frame pointer
+ *
+ * Locking Note: The rport lock is expected to be held before
+ * calling this routine
+ */
+static void fc_rport_error(struct fc_rport_priv *rdata, struct fc_frame *fp)
+{
+ FC_RPORT_DBG(rdata, "Error %ld in state %s, retries %d\n",
+ IS_ERR(fp) ? -PTR_ERR(fp) : 0,
+ fc_rport_state(rdata), rdata->retries);
+
+ switch (rdata->rp_state) {
+ case RPORT_ST_FLOGI:
+ case RPORT_ST_PLOGI:
+ rdata->flags &= ~FC_RP_STARTED;
+ fc_rport_enter_delete(rdata, RPORT_EV_FAILED);
+ break;
+ case RPORT_ST_RTV:
+ fc_rport_enter_ready(rdata);
+ break;
+ case RPORT_ST_PRLI:
+ case RPORT_ST_ADISC:
+ fc_rport_enter_logo(rdata);
+ break;
+ case RPORT_ST_PLOGI_WAIT:
+ case RPORT_ST_DELETE:
+ case RPORT_ST_READY:
+ case RPORT_ST_INIT:
+ break;
+ }
+}
+
+/**
+ * fc_rport_error_retry() - Handler for remote port state retries
+ * @rdata: The remote port whose state is to be retried
+ * @fp: The error code encapsulated in a frame pointer
*
- * If the error was caused by a resource allocation failure
- * then wait for half a second and retry, otherwise retry
- * immediately.
+ * If the error was an exchange timeout retry immediately,
+ * otherwise wait for E_D_TOV.
*
* Locking Note: The rport lock is expected to be held before
* calling this routine
*/
-static void fc_rport_error(struct fc_rport *rport, struct fc_frame *fp)
+static void fc_rport_error_retry(struct fc_rport_priv *rdata,
+ struct fc_frame *fp)
+{
+ unsigned long delay = msecs_to_jiffies(FC_DEF_E_D_TOV);
+
+ /* make sure this isn't an FC_EX_CLOSED error, never retry those */
+ if (PTR_ERR(fp) == -FC_EX_CLOSED)
+ goto out;
+
+ if (rdata->retries < rdata->local_port->max_rport_retry_count) {
+ FC_RPORT_DBG(rdata, "Error %ld in state %s, retrying\n",
+ PTR_ERR(fp), fc_rport_state(rdata));
+ rdata->retries++;
+ /* no additional delay on exchange timeouts */
+ if (PTR_ERR(fp) == -FC_EX_TIMEOUT)
+ delay = 0;
+ schedule_delayed_work(&rdata->retry_work, delay);
+ return;
+ }
+
+out:
+ fc_rport_error(rdata, fp);
+}
+
+/**
+ * fc_rport_login_complete() - Handle parameters and completion of p-mp login.
+ * @rdata: The remote port which we logged into or which logged into us.
+ * @fp: The FLOGI or PLOGI request or response frame
+ *
+ * Returns non-zero error if a problem is detected with the frame.
+ * Does not free the frame.
+ *
+ * This is only used in point-to-multipoint mode for FIP currently.
+ */
+static int fc_rport_login_complete(struct fc_rport_priv *rdata,
+ struct fc_frame *fp)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
- unsigned long delay = 0;
+ struct fc_lport *lport = rdata->local_port;
+ struct fc_els_flogi *flogi;
+ unsigned int e_d_tov;
+ u16 csp_flags;
- FC_DEBUG_RPORT("Error %ld in state %s, retries %d\n",
- PTR_ERR(fp), fc_rport_state(rport), rdata->retries);
+ flogi = fc_frame_payload_get(fp, sizeof(*flogi));
+ if (!flogi)
+ return -EINVAL;
+
+ csp_flags = ntohs(flogi->fl_csp.sp_features);
+
+ if (fc_frame_payload_op(fp) == ELS_FLOGI) {
+ if (csp_flags & FC_SP_FT_FPORT) {
+ FC_RPORT_DBG(rdata, "Fabric bit set in FLOGI\n");
+ return -EINVAL;
+ }
+ } else {
- if (!fp || PTR_ERR(fp) == -FC_EX_TIMEOUT) {
/*
- * Memory allocation failure, or the exchange timed out.
- * Retry after delay
+ * E_D_TOV is not valid on an incoming FLOGI request.
*/
- if (rdata->retries < rdata->local_port->max_retry_count) {
- rdata->retries++;
- if (!fp)
- delay = msecs_to_jiffies(500);
- get_device(&rport->dev);
- schedule_delayed_work(&rdata->retry_work, delay);
- } else {
- switch (rdata->rp_state) {
- case RPORT_ST_PLOGI:
- case RPORT_ST_PRLI:
- case RPORT_ST_LOGO:
- rdata->event = RPORT_EV_FAILED;
- queue_work(rport_event_queue,
- &rdata->event_work);
- break;
- case RPORT_ST_RTV:
- fc_rport_enter_ready(rport);
- break;
- case RPORT_ST_NONE:
- case RPORT_ST_READY:
- case RPORT_ST_INIT:
- break;
- }
- }
+ e_d_tov = ntohl(flogi->fl_csp.sp_e_d_tov);
+ if (csp_flags & FC_SP_FT_EDTR)
+ e_d_tov /= 1000000;
+ if (e_d_tov > rdata->e_d_tov)
+ rdata->e_d_tov = e_d_tov;
}
+ rdata->maxframe_size = fc_plogi_get_maxframe(flogi, lport->mfs);
+ return 0;
}
/**
- * fc_rport_plogi_recv_resp - Handle incoming ELS PLOGI response
- * @sp: current sequence in the PLOGI exchange
- * @fp: response frame
- * @rp_arg: Fibre Channel remote port
+ * fc_rport_flogi_resp() - Handle response to FLOGI request for p-mp mode
+ * @sp: The sequence that the FLOGI was on
+ * @fp: The FLOGI response frame
+ * @rp_arg: The remote port that received the FLOGI response
+ */
+static void fc_rport_flogi_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *rp_arg)
+{
+ struct fc_rport_priv *rdata = rp_arg;
+ struct fc_lport *lport = rdata->local_port;
+ struct fc_els_flogi *flogi;
+ unsigned int r_a_tov;
+
+ FC_RPORT_DBG(rdata, "Received a FLOGI %s\n", fc_els_resp_type(fp));
+
+ if (fp == ERR_PTR(-FC_EX_CLOSED))
+ goto put;
+
+ mutex_lock(&rdata->rp_mutex);
+
+ if (rdata->rp_state != RPORT_ST_FLOGI) {
+ FC_RPORT_DBG(rdata, "Received a FLOGI response, but in state "
+ "%s\n", fc_rport_state(rdata));
+ if (IS_ERR(fp))
+ goto err;
+ goto out;
+ }
+
+ if (IS_ERR(fp)) {
+ fc_rport_error(rdata, fp);
+ goto err;
+ }
+
+ if (fc_frame_payload_op(fp) != ELS_LS_ACC)
+ goto bad;
+ if (fc_rport_login_complete(rdata, fp))
+ goto bad;
+
+ flogi = fc_frame_payload_get(fp, sizeof(*flogi));
+ if (!flogi)
+ goto bad;
+ r_a_tov = ntohl(flogi->fl_csp.sp_r_a_tov);
+ if (r_a_tov > rdata->r_a_tov)
+ rdata->r_a_tov = r_a_tov;
+
+ if (rdata->ids.port_name < lport->wwpn)
+ fc_rport_enter_plogi(rdata);
+ else
+ fc_rport_state_enter(rdata, RPORT_ST_PLOGI_WAIT);
+out:
+ fc_frame_free(fp);
+err:
+ mutex_unlock(&rdata->rp_mutex);
+put:
+ kref_put(&rdata->kref, rdata->local_port->tt.rport_destroy);
+ return;
+bad:
+ FC_RPORT_DBG(rdata, "Bad FLOGI response\n");
+ fc_rport_error_retry(rdata, fp);
+ goto out;
+}
+
+/**
+ * fc_rport_enter_flogi() - Send a FLOGI request to the remote port for p-mp
+ * @rdata: The remote port to send a FLOGI to
+ *
+ * Locking Note: The rport lock is expected to be held before calling
+ * this routine.
+ */
+static void fc_rport_enter_flogi(struct fc_rport_priv *rdata)
+{
+ struct fc_lport *lport = rdata->local_port;
+ struct fc_frame *fp;
+
+ if (!lport->point_to_multipoint)
+ return fc_rport_enter_plogi(rdata);
+
+ FC_RPORT_DBG(rdata, "Entered FLOGI state from %s state\n",
+ fc_rport_state(rdata));
+
+ fc_rport_state_enter(rdata, RPORT_ST_FLOGI);
+
+ fp = fc_frame_alloc(lport, sizeof(struct fc_els_flogi));
+ if (!fp)
+ return fc_rport_error_retry(rdata, fp);
+
+ if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_FLOGI,
+ fc_rport_flogi_resp, rdata,
+ 2 * lport->r_a_tov))
+ fc_rport_error_retry(rdata, NULL);
+ else
+ kref_get(&rdata->kref);
+}
+
+/**
+ * fc_rport_recv_flogi_req() - Handle Fabric Login (FLOGI) request in p-mp mode
+ * @lport: The local port that received the PLOGI request
+ * @rx_fp: The PLOGI request frame
+ */
+static void fc_rport_recv_flogi_req(struct fc_lport *lport,
+ struct fc_frame *rx_fp)
+{
+ struct fc_disc *disc;
+ struct fc_els_flogi *flp;
+ struct fc_rport_priv *rdata;
+ struct fc_frame *fp = rx_fp;
+ struct fc_seq_els_data rjt_data;
+ u32 sid;
+
+ sid = fc_frame_sid(fp);
+
+ FC_RPORT_ID_DBG(lport, sid, "Received FLOGI request\n");
+
+ disc = &lport->disc;
+ mutex_lock(&disc->disc_mutex);
+
+ if (!lport->point_to_multipoint) {
+ rjt_data.reason = ELS_RJT_UNSUP;
+ rjt_data.explan = ELS_EXPL_NONE;
+ goto reject;
+ }
+
+ flp = fc_frame_payload_get(fp, sizeof(*flp));
+ if (!flp) {
+ rjt_data.reason = ELS_RJT_LOGIC;
+ rjt_data.explan = ELS_EXPL_INV_LEN;
+ goto reject;
+ }
+
+ rdata = lport->tt.rport_lookup(lport, sid);
+ if (!rdata) {
+ rjt_data.reason = ELS_RJT_FIP;
+ rjt_data.explan = ELS_EXPL_NOT_NEIGHBOR;
+ goto reject;
+ }
+ mutex_lock(&rdata->rp_mutex);
+
+ FC_RPORT_DBG(rdata, "Received FLOGI in %s state\n",
+ fc_rport_state(rdata));
+
+ switch (rdata->rp_state) {
+ case RPORT_ST_INIT:
+ /*
+ * If received the FLOGI request on RPORT which is INIT state
+ * (means not transition to FLOGI either fc_rport timeout
+ * function didn;t trigger or this end hasn;t received
+ * beacon yet from other end. In that case only, allow RPORT
+ * state machine to continue, otherwise fall through which
+ * causes the code to send reject response.
+ * NOTE; Not checking for FIP->state such as VNMP_UP or
+ * VNMP_CLAIM because if FIP state is not one of those,
+ * RPORT wouldn;t have created and 'rport_lookup' would have
+ * failed anyway in that case.
+ */
+ if (lport->point_to_multipoint)
+ break;
+ case RPORT_ST_DELETE:
+ mutex_unlock(&rdata->rp_mutex);
+ rjt_data.reason = ELS_RJT_FIP;
+ rjt_data.explan = ELS_EXPL_NOT_NEIGHBOR;
+ goto reject;
+ case RPORT_ST_FLOGI:
+ case RPORT_ST_PLOGI_WAIT:
+ case RPORT_ST_PLOGI:
+ break;
+ case RPORT_ST_PRLI:
+ case RPORT_ST_RTV:
+ case RPORT_ST_READY:
+ case RPORT_ST_ADISC:
+ /*
+ * Set the remote port to be deleted and to then restart.
+ * This queues work to be sure exchanges are reset.
+ */
+ fc_rport_enter_delete(rdata, RPORT_EV_LOGO);
+ mutex_unlock(&rdata->rp_mutex);
+ rjt_data.reason = ELS_RJT_BUSY;
+ rjt_data.explan = ELS_EXPL_NONE;
+ goto reject;
+ }
+ if (fc_rport_login_complete(rdata, fp)) {
+ mutex_unlock(&rdata->rp_mutex);
+ rjt_data.reason = ELS_RJT_LOGIC;
+ rjt_data.explan = ELS_EXPL_NONE;
+ goto reject;
+ }
+
+ fp = fc_frame_alloc(lport, sizeof(*flp));
+ if (!fp)
+ goto out;
+
+ fc_flogi_fill(lport, fp);
+ flp = fc_frame_payload_get(fp, sizeof(*flp));
+ flp->fl_cmd = ELS_LS_ACC;
+
+ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
+
+ if (rdata->ids.port_name < lport->wwpn)
+ fc_rport_enter_plogi(rdata);
+ else
+ fc_rport_state_enter(rdata, RPORT_ST_PLOGI_WAIT);
+out:
+ mutex_unlock(&rdata->rp_mutex);
+ mutex_unlock(&disc->disc_mutex);
+ fc_frame_free(rx_fp);
+ return;
+
+reject:
+ mutex_unlock(&disc->disc_mutex);
+ lport->tt.seq_els_rsp_send(rx_fp, ELS_LS_RJT, &rjt_data);
+ fc_frame_free(rx_fp);
+}
+
+/**
+ * fc_rport_plogi_resp() - Handler for ELS PLOGI responses
+ * @sp: The sequence the PLOGI is on
+ * @fp: The PLOGI response frame
+ * @rdata_arg: The remote port that sent the PLOGI response
*
* Locking Note: This function will be called without the rport lock
* held, but it will lock, call an _enter_* function or fc_rport_error
* and then unlock the rport.
*/
static void fc_rport_plogi_resp(struct fc_seq *sp, struct fc_frame *fp,
- void *rp_arg)
+ void *rdata_arg)
{
- struct fc_rport *rport = rp_arg;
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
+ struct fc_rport_priv *rdata = rdata_arg;
struct fc_lport *lport = rdata->local_port;
- struct fc_els_flogi *plp;
- unsigned int tov;
+ struct fc_els_flogi *plp = NULL;
u16 csp_seq;
u16 cssp_seq;
u8 op;
mutex_lock(&rdata->rp_mutex);
- FC_DEBUG_RPORT("Received a PLOGI response from port (%6x)\n",
- rport->port_id);
+ FC_RPORT_DBG(rdata, "Received a PLOGI %s\n", fc_els_resp_type(fp));
if (rdata->rp_state != RPORT_ST_PLOGI) {
- FC_DBG("Received a PLOGI response, but in state %s\n",
- fc_rport_state(rport));
+ FC_RPORT_DBG(rdata, "Received a PLOGI response, but in state "
+ "%s\n", fc_rport_state(rdata));
+ if (IS_ERR(fp))
+ goto err;
goto out;
}
if (IS_ERR(fp)) {
- fc_rport_error(rport, fp);
+ fc_rport_error_retry(rdata, fp);
goto err;
}
op = fc_frame_payload_op(fp);
if (op == ELS_LS_ACC &&
(plp = fc_frame_payload_get(fp, sizeof(*plp))) != NULL) {
- rport->port_name = get_unaligned_be64(&plp->fl_wwpn);
- rport->node_name = get_unaligned_be64(&plp->fl_wwnn);
+ rdata->ids.port_name = get_unaligned_be64(&plp->fl_wwpn);
+ rdata->ids.node_name = get_unaligned_be64(&plp->fl_wwnn);
- tov = ntohl(plp->fl_csp.sp_e_d_tov);
- if (ntohs(plp->fl_csp.sp_features) & FC_SP_FT_EDTR)
- tov /= 1000;
- if (tov > rdata->e_d_tov)
- rdata->e_d_tov = tov;
+ /* save plogi response sp_features for further reference */
+ rdata->sp_features = ntohs(plp->fl_csp.sp_features);
+
+ if (lport->point_to_multipoint)
+ fc_rport_login_complete(rdata, fp);
csp_seq = ntohs(plp->fl_csp.sp_tot_seq);
cssp_seq = ntohs(plp->fl_cssp[3 - 1].cp_con_seq);
if (cssp_seq < csp_seq)
csp_seq = cssp_seq;
rdata->max_seq = csp_seq;
- rport->maxframe_size =
- fc_plogi_get_maxframe(plp, lport->mfs);
-
- /*
- * If the rport is one of the well known addresses
- * we skip PRLI and RTV and go straight to READY.
- */
- if (rport->port_id >= FC_FID_DOM_MGR)
- fc_rport_enter_ready(rport);
- else
- fc_rport_enter_prli(rport);
+ rdata->maxframe_size = fc_plogi_get_maxframe(plp, lport->mfs);
+ fc_rport_enter_prli(rdata);
} else
- fc_rport_error(rport, fp);
+ fc_rport_error_retry(rdata, fp);
out:
fc_frame_free(fp);
err:
mutex_unlock(&rdata->rp_mutex);
- put_device(&rport->dev);
+ kref_put(&rdata->kref, rdata->local_port->tt.rport_destroy);
+}
+
+static bool
+fc_rport_compatible_roles(struct fc_lport *lport, struct fc_rport_priv *rdata)
+{
+ if (rdata->ids.roles == FC_PORT_ROLE_UNKNOWN)
+ return true;
+ if ((rdata->ids.roles & FC_PORT_ROLE_FCP_TARGET) &&
+ (lport->service_params & FCP_SPPF_INIT_FCN))
+ return true;
+ if ((rdata->ids.roles & FC_PORT_ROLE_FCP_INITIATOR) &&
+ (lport->service_params & FCP_SPPF_TARG_FCN))
+ return true;
+ return false;
}
/**
- * fc_rport_enter_plogi - Send Port Login (PLOGI) request to peer
- * @rport: Fibre Channel remote port to send PLOGI to
+ * fc_rport_enter_plogi() - Send Port Login (PLOGI) request
+ * @rdata: The remote port to send a PLOGI to
*
* Locking Note: The rport lock is expected to be held before calling
* this routine.
*/
-static void fc_rport_enter_plogi(struct fc_rport *rport)
+static void fc_rport_enter_plogi(struct fc_rport_priv *rdata)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
struct fc_lport *lport = rdata->local_port;
struct fc_frame *fp;
- FC_DEBUG_RPORT("Port (%6x) entered PLOGI state from %s state\n",
- rport->port_id, fc_rport_state(rport));
+ if (!fc_rport_compatible_roles(lport, rdata)) {
+ FC_RPORT_DBG(rdata, "PLOGI suppressed for incompatible role\n");
+ fc_rport_state_enter(rdata, RPORT_ST_PLOGI_WAIT);
+ return;
+ }
+
+ FC_RPORT_DBG(rdata, "Port entered PLOGI state from %s state\n",
+ fc_rport_state(rdata));
- fc_rport_state_enter(rport, RPORT_ST_PLOGI);
+ fc_rport_state_enter(rdata, RPORT_ST_PLOGI);
- rport->maxframe_size = FC_MIN_MAX_PAYLOAD;
+ rdata->maxframe_size = FC_MIN_MAX_PAYLOAD;
fp = fc_frame_alloc(lport, sizeof(struct fc_els_flogi));
if (!fp) {
- fc_rport_error(rport, fp);
+ FC_RPORT_DBG(rdata, "%s frame alloc failed\n", __func__);
+ fc_rport_error_retry(rdata, fp);
return;
}
rdata->e_d_tov = lport->e_d_tov;
- if (!lport->tt.elsct_send(lport, rport, fp, ELS_PLOGI,
- fc_rport_plogi_resp, rport, lport->e_d_tov))
- fc_rport_error(rport, fp);
+ if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_PLOGI,
+ fc_rport_plogi_resp, rdata,
+ 2 * lport->r_a_tov))
+ fc_rport_error_retry(rdata, NULL);
else
- get_device(&rport->dev);
+ kref_get(&rdata->kref);
}
/**
- * fc_rport_prli_resp - Process Login (PRLI) response handler
- * @sp: current sequence in the PRLI exchange
- * @fp: response frame
- * @rp_arg: Fibre Channel remote port
+ * fc_rport_prli_resp() - Process Login (PRLI) response handler
+ * @sp: The sequence the PRLI response was on
+ * @fp: The PRLI response frame
+ * @rdata_arg: The remote port that sent the PRLI response
*
* Locking Note: This function will be called without the rport lock
* held, but it will lock, call an _enter_* function or fc_rport_error
* and then unlock the rport.
*/
static void fc_rport_prli_resp(struct fc_seq *sp, struct fc_frame *fp,
- void *rp_arg)
+ void *rdata_arg)
{
- struct fc_rport *rport = rp_arg;
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
+ struct fc_rport_priv *rdata = rdata_arg;
struct {
struct fc_els_prli prli;
struct fc_els_spp spp;
} *pp;
+ struct fc_els_spp temp_spp;
+ struct fc4_prov *prov;
u32 roles = FC_RPORT_ROLE_UNKNOWN;
u32 fcp_parm = 0;
u8 op;
+ u8 resp_code = 0;
mutex_lock(&rdata->rp_mutex);
- FC_DEBUG_RPORT("Received a PRLI response from port (%6x)\n",
- rport->port_id);
+ FC_RPORT_DBG(rdata, "Received a PRLI %s\n", fc_els_resp_type(fp));
if (rdata->rp_state != RPORT_ST_PRLI) {
- FC_DBG("Received a PRLI response, but in state %s\n",
- fc_rport_state(rport));
+ FC_RPORT_DBG(rdata, "Received a PRLI response, but in state "
+ "%s\n", fc_rport_state(rdata));
+ if (IS_ERR(fp))
+ goto err;
goto out;
}
if (IS_ERR(fp)) {
- fc_rport_error(rport, fp);
+ fc_rport_error_retry(rdata, fp);
goto err;
}
+ /* reinitialize remote port roles */
+ rdata->ids.roles = FC_RPORT_ROLE_UNKNOWN;
+
op = fc_frame_payload_op(fp);
if (op == ELS_LS_ACC) {
pp = fc_frame_payload_get(fp, sizeof(*pp));
- if (pp && pp->prli.prli_spp_len >= sizeof(pp->spp)) {
- fcp_parm = ntohl(pp->spp.spp_params);
- if (fcp_parm & FCP_SPPF_RETRY)
- rdata->flags |= FC_RP_FLAGS_RETRY;
+ if (!pp)
+ goto out;
+
+ resp_code = (pp->spp.spp_flags & FC_SPP_RESP_MASK);
+ FC_RPORT_DBG(rdata, "PRLI spp_flags = 0x%x\n",
+ pp->spp.spp_flags);
+ rdata->spp_type = pp->spp.spp_type;
+ if (resp_code != FC_SPP_RESP_ACK) {
+ if (resp_code == FC_SPP_RESP_CONF)
+ fc_rport_error(rdata, fp);
+ else
+ fc_rport_error_retry(rdata, fp);
+ goto out;
+ }
+ if (pp->prli.prli_spp_len < sizeof(pp->spp))
+ goto out;
+
+ fcp_parm = ntohl(pp->spp.spp_params);
+ if (fcp_parm & FCP_SPPF_RETRY)
+ rdata->flags |= FC_RP_FLAGS_RETRY;
+ if (fcp_parm & FCP_SPPF_CONF_COMPL)
+ rdata->flags |= FC_RP_FLAGS_CONF_REQ;
+
+ prov = fc_passive_prov[FC_TYPE_FCP];
+ if (prov) {
+ memset(&temp_spp, 0, sizeof(temp_spp));
+ prov->prli(rdata, pp->prli.prli_spp_len,
+ &pp->spp, &temp_spp);
}
- rport->supported_classes = FC_COS_CLASS3;
+ 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;
- rport->roles = roles;
- fc_rport_enter_rtv(rport);
-
- } else {
- FC_DBG("Bad ELS response\n");
- rdata->event = RPORT_EV_FAILED;
- queue_work(rport_event_queue, &rdata->event_work);
- }
-
-out:
- fc_frame_free(fp);
-err:
- mutex_unlock(&rdata->rp_mutex);
- put_device(&rport->dev);
-}
-
-/**
- * fc_rport_logo_resp - Logout (LOGO) response handler
- * @sp: current sequence in the LOGO exchange
- * @fp: response frame
- * @rp_arg: Fibre Channel remote port
- *
- * Locking Note: This function will be called without the rport lock
- * held, but it will lock, call an _enter_* function or fc_rport_error
- * and then unlock the rport.
- */
-static void fc_rport_logo_resp(struct fc_seq *sp, struct fc_frame *fp,
- void *rp_arg)
-{
- struct fc_rport *rport = rp_arg;
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
- u8 op;
-
- mutex_lock(&rdata->rp_mutex);
-
- FC_DEBUG_RPORT("Received a LOGO response from port (%6x)\n",
- rport->port_id);
+ rdata->ids.roles = roles;
+ fc_rport_enter_rtv(rdata);
- if (IS_ERR(fp)) {
- fc_rport_error(rport, fp);
- goto err;
- }
-
- if (rdata->rp_state != RPORT_ST_LOGO) {
- FC_DEBUG_RPORT("Received a LOGO response, but in state %s\n",
- fc_rport_state(rport));
- goto out;
- }
-
- op = fc_frame_payload_op(fp);
- if (op == ELS_LS_ACC) {
- fc_rport_enter_rtv(rport);
} else {
- FC_DBG("Bad ELS response\n");
- rdata->event = RPORT_EV_LOGO;
- queue_work(rport_event_queue, &rdata->event_work);
+ FC_RPORT_DBG(rdata, "Bad ELS response for PRLI command\n");
+ fc_rport_error_retry(rdata, fp);
}
out:
fc_frame_free(fp);
err:
mutex_unlock(&rdata->rp_mutex);
- put_device(&rport->dev);
+ kref_put(&rdata->kref, rdata->local_port->tt.rport_destroy);
}
/**
- * fc_rport_enter_prli - Send Process Login (PRLI) request to peer
- * @rport: Fibre Channel remote port to send PRLI to
+ * fc_rport_enter_prli() - Send Process Login (PRLI) request
+ * @rdata: The remote port to send the PRLI request to
*
* Locking Note: The rport lock is expected to be held before calling
* this routine.
*/
-static void fc_rport_enter_prli(struct fc_rport *rport)
+static void fc_rport_enter_prli(struct fc_rport_priv *rdata)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
struct fc_lport *lport = rdata->local_port;
struct {
struct fc_els_prli prli;
struct fc_els_spp spp;
} *pp;
struct fc_frame *fp;
+ struct fc4_prov *prov;
- FC_DEBUG_RPORT("Port (%6x) entered PRLI state from %s state\n",
- rport->port_id, fc_rport_state(rport));
+ /*
+ * If the rport is one of the well known addresses
+ * we skip PRLI and RTV and go straight to READY.
+ */
+ if (rdata->ids.port_id >= FC_FID_DOM_MGR) {
+ fc_rport_enter_ready(rdata);
+ return;
+ }
- fc_rport_state_enter(rport, RPORT_ST_PRLI);
+ FC_RPORT_DBG(rdata, "Port entered PRLI state from %s state\n",
+ fc_rport_state(rdata));
+
+ fc_rport_state_enter(rdata, RPORT_ST_PRLI);
fp = fc_frame_alloc(lport, sizeof(*pp));
if (!fp) {
- fc_rport_error(rport, fp);
+ fc_rport_error_retry(rdata, fp);
return;
}
- if (!lport->tt.elsct_send(lport, rport, fp, ELS_PRLI,
- fc_rport_prli_resp, rport, lport->e_d_tov))
- fc_rport_error(rport, fp);
+ fc_prli_fill(lport, fp);
+
+ prov = fc_passive_prov[FC_TYPE_FCP];
+ if (prov) {
+ pp = fc_frame_payload_get(fp, sizeof(*pp));
+ prov->prli(rdata, sizeof(pp->spp), NULL, &pp->spp);
+ }
+
+ fc_fill_fc_hdr(fp, FC_RCTL_ELS_REQ, rdata->ids.port_id,
+ fc_host_port_id(lport->host), FC_TYPE_ELS,
+ FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
+
+ if (!lport->tt.exch_seq_send(lport, fp, fc_rport_prli_resp,
+ NULL, rdata, 2 * lport->r_a_tov))
+ fc_rport_error_retry(rdata, NULL);
else
- get_device(&rport->dev);
+ kref_get(&rdata->kref);
}
/**
- * fc_rport_els_rtv_resp - Request Timeout Value response handler
- * @sp: current sequence in the RTV exchange
- * @fp: response frame
- * @rp_arg: Fibre Channel remote port
+ * fc_rport_els_rtv_resp() - Handler for Request Timeout Value (RTV) responses
+ * @sp: The sequence the RTV was on
+ * @fp: The RTV response frame
+ * @rdata_arg: The remote port that sent the RTV response
*
* Many targets don't seem to support this.
*
@@ -731,25 +1148,25 @@ static void fc_rport_enter_prli(struct fc_rport *rport)
* and then unlock the rport.
*/
static void fc_rport_rtv_resp(struct fc_seq *sp, struct fc_frame *fp,
- void *rp_arg)
+ void *rdata_arg)
{
- struct fc_rport *rport = rp_arg;
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
+ struct fc_rport_priv *rdata = rdata_arg;
u8 op;
mutex_lock(&rdata->rp_mutex);
- FC_DEBUG_RPORT("Received a RTV response from port (%6x)\n",
- rport->port_id);
+ FC_RPORT_DBG(rdata, "Received a RTV %s\n", fc_els_resp_type(fp));
if (rdata->rp_state != RPORT_ST_RTV) {
- FC_DBG("Received a RTV response, but in state %s\n",
- fc_rport_state(rport));
+ FC_RPORT_DBG(rdata, "Received a RTV response, but in state "
+ "%s\n", fc_rport_state(rdata));
+ if (IS_ERR(fp))
+ goto err;
goto out;
}
if (IS_ERR(fp)) {
- fc_rport_error(rport, fp);
+ fc_rport_error(rdata, fp);
goto err;
}
@@ -775,185 +1192,438 @@ static void fc_rport_rtv_resp(struct fc_seq *sp, struct fc_frame *fp,
}
}
- fc_rport_enter_ready(rport);
+ fc_rport_enter_ready(rdata);
out:
fc_frame_free(fp);
err:
mutex_unlock(&rdata->rp_mutex);
- put_device(&rport->dev);
+ kref_put(&rdata->kref, rdata->local_port->tt.rport_destroy);
}
/**
- * fc_rport_enter_rtv - Send Request Timeout Value (RTV) request to peer
- * @rport: Fibre Channel remote port to send RTV to
+ * fc_rport_enter_rtv() - Send Request Timeout Value (RTV) request
+ * @rdata: The remote port to send the RTV request to
*
* Locking Note: The rport lock is expected to be held before calling
* this routine.
*/
-static void fc_rport_enter_rtv(struct fc_rport *rport)
+static void fc_rport_enter_rtv(struct fc_rport_priv *rdata)
{
struct fc_frame *fp;
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
struct fc_lport *lport = rdata->local_port;
- FC_DEBUG_RPORT("Port (%6x) entered RTV state from %s state\n",
- rport->port_id, fc_rport_state(rport));
+ FC_RPORT_DBG(rdata, "Port entered RTV state from %s state\n",
+ fc_rport_state(rdata));
- fc_rport_state_enter(rport, RPORT_ST_RTV);
+ fc_rport_state_enter(rdata, RPORT_ST_RTV);
fp = fc_frame_alloc(lport, sizeof(struct fc_els_rtv));
if (!fp) {
- fc_rport_error(rport, fp);
+ fc_rport_error_retry(rdata, fp);
return;
}
- if (!lport->tt.elsct_send(lport, rport, fp, ELS_RTV,
- fc_rport_rtv_resp, rport, lport->e_d_tov))
- fc_rport_error(rport, fp);
+ if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_RTV,
+ fc_rport_rtv_resp, rdata,
+ 2 * lport->r_a_tov))
+ fc_rport_error_retry(rdata, NULL);
else
- get_device(&rport->dev);
+ kref_get(&rdata->kref);
}
/**
- * fc_rport_enter_logo - Send Logout (LOGO) request to peer
- * @rport: Fibre Channel remote port to send LOGO to
+ * fc_rport_logo_resp() - Handler for logout (LOGO) responses
+ * @sp: The sequence the LOGO was on
+ * @fp: The LOGO response frame
+ * @lport_arg: The local port
+ */
+static void fc_rport_logo_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *lport_arg)
+{
+ struct fc_lport *lport = lport_arg;
+
+ FC_RPORT_ID_DBG(lport, fc_seq_exch(sp)->did,
+ "Received a LOGO %s\n", fc_els_resp_type(fp));
+ if (IS_ERR(fp))
+ return;
+ fc_frame_free(fp);
+}
+
+/**
+ * fc_rport_enter_logo() - Send a logout (LOGO) request
+ * @rdata: The remote port to send the LOGO request to
*
* Locking Note: The rport lock is expected to be held before calling
* this routine.
*/
-static void fc_rport_enter_logo(struct fc_rport *rport)
+static void fc_rport_enter_logo(struct fc_rport_priv *rdata)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
struct fc_lport *lport = rdata->local_port;
struct fc_frame *fp;
- FC_DEBUG_RPORT("Port (%6x) entered LOGO state from %s state\n",
- rport->port_id, fc_rport_state(rport));
-
- fc_rport_state_enter(rport, RPORT_ST_LOGO);
+ FC_RPORT_DBG(rdata, "Port sending LOGO from %s state\n",
+ fc_rport_state(rdata));
fp = fc_frame_alloc(lport, sizeof(struct fc_els_logo));
- if (!fp) {
- fc_rport_error(rport, fp);
+ if (!fp)
return;
+ (void)lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_LOGO,
+ fc_rport_logo_resp, lport, 0);
+}
+
+/**
+ * fc_rport_els_adisc_resp() - Handler for Address Discovery (ADISC) responses
+ * @sp: The sequence the ADISC response was on
+ * @fp: The ADISC response frame
+ * @rdata_arg: The remote port that sent the ADISC response
+ *
+ * Locking Note: This function will be called without the rport lock
+ * held, but it will lock, call an _enter_* function or fc_rport_error
+ * and then unlock the rport.
+ */
+static void fc_rport_adisc_resp(struct fc_seq *sp, struct fc_frame *fp,
+ void *rdata_arg)
+{
+ struct fc_rport_priv *rdata = rdata_arg;
+ struct fc_els_adisc *adisc;
+ u8 op;
+
+ mutex_lock(&rdata->rp_mutex);
+
+ FC_RPORT_DBG(rdata, "Received a ADISC response\n");
+
+ if (rdata->rp_state != RPORT_ST_ADISC) {
+ FC_RPORT_DBG(rdata, "Received a ADISC resp but in state %s\n",
+ fc_rport_state(rdata));
+ if (IS_ERR(fp))
+ goto err;
+ goto out;
}
- if (!lport->tt.elsct_send(lport, rport, fp, ELS_LOGO,
- fc_rport_logo_resp, rport, lport->e_d_tov))
- fc_rport_error(rport, fp);
+ if (IS_ERR(fp)) {
+ fc_rport_error(rdata, fp);
+ goto err;
+ }
+
+ /*
+ * If address verification failed. Consider us logged out of the rport.
+ * Since the rport is still in discovery, we want to be
+ * logged in, so go to PLOGI state. Otherwise, go back to READY.
+ */
+ op = fc_frame_payload_op(fp);
+ adisc = fc_frame_payload_get(fp, sizeof(*adisc));
+ if (op != ELS_LS_ACC || !adisc ||
+ ntoh24(adisc->adisc_port_id) != rdata->ids.port_id ||
+ get_unaligned_be64(&adisc->adisc_wwpn) != rdata->ids.port_name ||
+ get_unaligned_be64(&adisc->adisc_wwnn) != rdata->ids.node_name) {
+ FC_RPORT_DBG(rdata, "ADISC error or mismatch\n");
+ fc_rport_enter_flogi(rdata);
+ } else {
+ FC_RPORT_DBG(rdata, "ADISC OK\n");
+ fc_rport_enter_ready(rdata);
+ }
+out:
+ fc_frame_free(fp);
+err:
+ mutex_unlock(&rdata->rp_mutex);
+ kref_put(&rdata->kref, rdata->local_port->tt.rport_destroy);
+}
+
+/**
+ * fc_rport_enter_adisc() - Send Address Discover (ADISC) request
+ * @rdata: The remote port to send the ADISC request to
+ *
+ * Locking Note: The rport lock is expected to be held before calling
+ * this routine.
+ */
+static void fc_rport_enter_adisc(struct fc_rport_priv *rdata)
+{
+ struct fc_lport *lport = rdata->local_port;
+ struct fc_frame *fp;
+
+ FC_RPORT_DBG(rdata, "sending ADISC from %s state\n",
+ fc_rport_state(rdata));
+
+ fc_rport_state_enter(rdata, RPORT_ST_ADISC);
+
+ fp = fc_frame_alloc(lport, sizeof(struct fc_els_adisc));
+ if (!fp) {
+ fc_rport_error_retry(rdata, fp);
+ return;
+ }
+ if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_ADISC,
+ fc_rport_adisc_resp, rdata,
+ 2 * lport->r_a_tov))
+ fc_rport_error_retry(rdata, NULL);
else
- get_device(&rport->dev);
+ kref_get(&rdata->kref);
}
+/**
+ * fc_rport_recv_adisc_req() - Handler for Address Discovery (ADISC) requests
+ * @rdata: The remote port that sent the ADISC request
+ * @in_fp: The ADISC request frame
+ *
+ * Locking Note: Called with the lport and rport locks held.
+ */
+static void fc_rport_recv_adisc_req(struct fc_rport_priv *rdata,
+ struct fc_frame *in_fp)
+{
+ struct fc_lport *lport = rdata->local_port;
+ struct fc_frame *fp;
+ struct fc_els_adisc *adisc;
+ struct fc_seq_els_data rjt_data;
+
+ FC_RPORT_DBG(rdata, "Received ADISC request\n");
+
+ adisc = fc_frame_payload_get(in_fp, sizeof(*adisc));
+ if (!adisc) {
+ rjt_data.reason = ELS_RJT_PROT;
+ rjt_data.explan = ELS_EXPL_INV_LEN;
+ lport->tt.seq_els_rsp_send(in_fp, ELS_LS_RJT, &rjt_data);
+ goto drop;
+ }
+
+ fp = fc_frame_alloc(lport, sizeof(*adisc));
+ if (!fp)
+ goto drop;
+ fc_adisc_fill(lport, fp);
+ adisc = fc_frame_payload_get(fp, sizeof(*adisc));
+ adisc->adisc_cmd = ELS_LS_ACC;
+ fc_fill_reply_hdr(fp, in_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
+drop:
+ fc_frame_free(in_fp);
+}
/**
- * fc_rport_recv_req - Receive a request from a rport
- * @sp: current sequence in the PLOGI exchange
- * @fp: response frame
- * @rp_arg: Fibre Channel remote port
+ * fc_rport_recv_rls_req() - Handle received Read Link Status request
+ * @rdata: The remote port that sent the RLS request
+ * @rx_fp: The PRLI request frame
*
- * Locking Note: Called without the rport lock held. This
- * function will hold the rport lock, call an _enter_*
- * function and then unlock the rport.
+ * Locking Note: The rport lock is expected to be held before calling
+ * this function.
*/
-void fc_rport_recv_req(struct fc_seq *sp, struct fc_frame *fp,
- struct fc_rport *rport)
+static void fc_rport_recv_rls_req(struct fc_rport_priv *rdata,
+ struct fc_frame *rx_fp)
+
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
struct fc_lport *lport = rdata->local_port;
+ struct fc_frame *fp;
+ struct fc_els_rls *rls;
+ struct fc_els_rls_resp *rsp;
+ struct fc_els_lesb *lesb;
+ struct fc_seq_els_data rjt_data;
+ struct fc_host_statistics *hst;
+
+ FC_RPORT_DBG(rdata, "Received RLS request while in state %s\n",
+ fc_rport_state(rdata));
+
+ rls = fc_frame_payload_get(rx_fp, sizeof(*rls));
+ if (!rls) {
+ rjt_data.reason = ELS_RJT_PROT;
+ rjt_data.explan = ELS_EXPL_INV_LEN;
+ goto out_rjt;
+ }
+
+ fp = fc_frame_alloc(lport, sizeof(*rsp));
+ if (!fp) {
+ rjt_data.reason = ELS_RJT_UNAB;
+ rjt_data.explan = ELS_EXPL_INSUF_RES;
+ goto out_rjt;
+ }
+
+ rsp = fc_frame_payload_get(fp, sizeof(*rsp));
+ memset(rsp, 0, sizeof(*rsp));
+ rsp->rls_cmd = ELS_LS_ACC;
+ lesb = &rsp->rls_lesb;
+ if (lport->tt.get_lesb) {
+ /* get LESB from LLD if it supports it */
+ lport->tt.get_lesb(lport, lesb);
+ } else {
+ fc_get_host_stats(lport->host);
+ hst = &lport->host_stats;
+ lesb->lesb_link_fail = htonl(hst->link_failure_count);
+ lesb->lesb_sync_loss = htonl(hst->loss_of_sync_count);
+ lesb->lesb_sig_loss = htonl(hst->loss_of_signal_count);
+ lesb->lesb_prim_err = htonl(hst->prim_seq_protocol_err_count);
+ lesb->lesb_inv_word = htonl(hst->invalid_tx_word_count);
+ lesb->lesb_inv_crc = htonl(hst->invalid_crc_count);
+ }
+
+ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
+ goto out;
+
+out_rjt:
+ lport->tt.seq_els_rsp_send(rx_fp, ELS_LS_RJT, &rjt_data);
+out:
+ fc_frame_free(rx_fp);
+}
- struct fc_frame_header *fh;
+/**
+ * fc_rport_recv_els_req() - Handler for validated ELS requests
+ * @lport: The local port that received the ELS request
+ * @fp: The ELS request frame
+ *
+ * Handle incoming ELS requests that require port login.
+ * The ELS opcode has already been validated by the caller.
+ *
+ * Locking Note: Called with the lport lock held.
+ */
+static void fc_rport_recv_els_req(struct fc_lport *lport, struct fc_frame *fp)
+{
+ struct fc_rport_priv *rdata;
struct fc_seq_els_data els_data;
- u8 op;
+ mutex_lock(&lport->disc.disc_mutex);
+ rdata = lport->tt.rport_lookup(lport, fc_frame_sid(fp));
+ if (!rdata) {
+ mutex_unlock(&lport->disc.disc_mutex);
+ goto reject;
+ }
mutex_lock(&rdata->rp_mutex);
+ mutex_unlock(&lport->disc.disc_mutex);
- els_data.fp = NULL;
- els_data.explan = ELS_EXPL_NONE;
- els_data.reason = ELS_RJT_NONE;
-
- fh = fc_frame_header_get(fp);
+ switch (rdata->rp_state) {
+ case RPORT_ST_PRLI:
+ case RPORT_ST_RTV:
+ case RPORT_ST_READY:
+ case RPORT_ST_ADISC:
+ break;
+ default:
+ mutex_unlock(&rdata->rp_mutex);
+ goto reject;
+ }
- if (fh->fh_r_ctl == FC_RCTL_ELS_REQ && fh->fh_type == FC_TYPE_ELS) {
- op = fc_frame_payload_op(fp);
- switch (op) {
- case ELS_PLOGI:
- fc_rport_recv_plogi_req(rport, sp, fp);
- break;
- case ELS_PRLI:
- fc_rport_recv_prli_req(rport, sp, fp);
- break;
- case ELS_PRLO:
- fc_rport_recv_prlo_req(rport, sp, fp);
- break;
- case ELS_LOGO:
- fc_rport_recv_logo_req(rport, sp, fp);
- break;
- case ELS_RRQ:
- els_data.fp = fp;
- lport->tt.seq_els_rsp_send(sp, ELS_RRQ, &els_data);
- break;
- case ELS_REC:
- els_data.fp = fp;
- lport->tt.seq_els_rsp_send(sp, ELS_REC, &els_data);
- break;
- default:
- els_data.reason = ELS_RJT_UNSUP;
- lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &els_data);
- break;
- }
+ switch (fc_frame_payload_op(fp)) {
+ case ELS_PRLI:
+ fc_rport_recv_prli_req(rdata, fp);
+ break;
+ case ELS_PRLO:
+ fc_rport_recv_prlo_req(rdata, fp);
+ break;
+ case ELS_ADISC:
+ fc_rport_recv_adisc_req(rdata, fp);
+ break;
+ case ELS_RRQ:
+ lport->tt.seq_els_rsp_send(fp, ELS_RRQ, NULL);
+ fc_frame_free(fp);
+ break;
+ case ELS_REC:
+ lport->tt.seq_els_rsp_send(fp, ELS_REC, NULL);
+ fc_frame_free(fp);
+ break;
+ case ELS_RLS:
+ fc_rport_recv_rls_req(rdata, fp);
+ break;
+ default:
+ fc_frame_free(fp); /* can't happen */
+ break;
}
mutex_unlock(&rdata->rp_mutex);
+ return;
+
+reject:
+ els_data.reason = ELS_RJT_UNAB;
+ els_data.explan = ELS_EXPL_PLOGI_REQD;
+ lport->tt.seq_els_rsp_send(fp, ELS_LS_RJT, &els_data);
+ fc_frame_free(fp);
}
/**
- * fc_rport_recv_plogi_req - Handle incoming Port Login (PLOGI) request
- * @rport: Fibre Channel remote port that initiated PLOGI
- * @sp: current sequence in the PLOGI exchange
- * @fp: PLOGI request frame
+ * fc_rport_recv_req() - Handler for requests
+ * @lport: The local port that received the request
+ * @fp: The request frame
*
- * Locking Note: The rport lock is exected to be held before calling
- * this function.
+ * Locking Note: Called with the lport lock held.
*/
-static void fc_rport_recv_plogi_req(struct fc_rport *rport,
- struct fc_seq *sp, struct fc_frame *rx_fp)
+static void fc_rport_recv_req(struct fc_lport *lport, struct fc_frame *fp)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
- struct fc_lport *lport = rdata->local_port;
+ struct fc_seq_els_data els_data;
+
+ /*
+ * Handle FLOGI, PLOGI and LOGO requests separately, since they
+ * don't require prior login.
+ * Check for unsupported opcodes first and reject them.
+ * For some ops, it would be incorrect to reject with "PLOGI required".
+ */
+ switch (fc_frame_payload_op(fp)) {
+ case ELS_FLOGI:
+ fc_rport_recv_flogi_req(lport, fp);
+ break;
+ case ELS_PLOGI:
+ fc_rport_recv_plogi_req(lport, fp);
+ break;
+ case ELS_LOGO:
+ fc_rport_recv_logo_req(lport, fp);
+ break;
+ case ELS_PRLI:
+ case ELS_PRLO:
+ case ELS_ADISC:
+ case ELS_RRQ:
+ case ELS_REC:
+ case ELS_RLS:
+ fc_rport_recv_els_req(lport, fp);
+ break;
+ default:
+ els_data.reason = ELS_RJT_UNSUP;
+ els_data.explan = ELS_EXPL_NONE;
+ lport->tt.seq_els_rsp_send(fp, ELS_LS_RJT, &els_data);
+ fc_frame_free(fp);
+ break;
+ }
+}
+
+/**
+ * fc_rport_recv_plogi_req() - Handler for Port Login (PLOGI) requests
+ * @lport: The local port that received the PLOGI request
+ * @rx_fp: The PLOGI request frame
+ *
+ * Locking Note: The rport lock is held before calling this function.
+ */
+static void fc_rport_recv_plogi_req(struct fc_lport *lport,
+ struct fc_frame *rx_fp)
+{
+ struct fc_disc *disc;
+ struct fc_rport_priv *rdata;
struct fc_frame *fp = rx_fp;
- struct fc_exch *ep;
- struct fc_frame_header *fh;
struct fc_els_flogi *pl;
struct fc_seq_els_data rjt_data;
u32 sid;
- u64 wwpn;
- u64 wwnn;
- enum fc_els_rjt_reason reject = 0;
- u32 f_ctl;
- rjt_data.fp = NULL;
- fh = fc_frame_header_get(fp);
+ sid = fc_frame_sid(fp);
- FC_DEBUG_RPORT("Received PLOGI request from port (%6x) "
- "while in state %s\n", ntoh24(fh->fh_s_id),
- fc_rport_state(rport));
+ FC_RPORT_ID_DBG(lport, sid, "Received PLOGI request\n");
- sid = ntoh24(fh->fh_s_id);
pl = fc_frame_payload_get(fp, sizeof(*pl));
if (!pl) {
- FC_DBG("incoming PLOGI from %x too short\n", sid);
- WARN_ON(1);
- /* XXX TBD: send reject? */
- fc_frame_free(fp);
- return;
+ FC_RPORT_ID_DBG(lport, sid, "Received PLOGI too short\n");
+ rjt_data.reason = ELS_RJT_PROT;
+ rjt_data.explan = ELS_EXPL_INV_LEN;
+ goto reject;
}
- wwpn = get_unaligned_be64(&pl->fl_wwpn);
- wwnn = get_unaligned_be64(&pl->fl_wwnn);
+
+ disc = &lport->disc;
+ mutex_lock(&disc->disc_mutex);
+ rdata = lport->tt.rport_create(lport, sid);
+ if (!rdata) {
+ mutex_unlock(&disc->disc_mutex);
+ rjt_data.reason = ELS_RJT_UNAB;
+ rjt_data.explan = ELS_EXPL_INSUF_RES;
+ goto reject;
+ }
+
+ mutex_lock(&rdata->rp_mutex);
+ mutex_unlock(&disc->disc_mutex);
+
+ rdata->ids.port_name = get_unaligned_be64(&pl->fl_wwpn);
+ rdata->ids.node_name = get_unaligned_be64(&pl->fl_wwnn);
/*
- * If the session was just created, possibly due to the incoming PLOGI,
+ * If the rport was just created, possibly due to the incoming PLOGI,
* set the state appropriately and accept the PLOGI.
*
* If we had also sent a PLOGI, and if the received PLOGI is from a
@@ -965,88 +1635,84 @@ static void fc_rport_recv_plogi_req(struct fc_rport *rport,
*/
switch (rdata->rp_state) {
case RPORT_ST_INIT:
- FC_DEBUG_RPORT("incoming PLOGI from %6x wwpn %llx state INIT "
- "- reject\n", sid, wwpn);
- reject = ELS_RJT_UNSUP;
+ FC_RPORT_DBG(rdata, "Received PLOGI in INIT state\n");
+ break;
+ case RPORT_ST_PLOGI_WAIT:
+ FC_RPORT_DBG(rdata, "Received PLOGI in PLOGI_WAIT state\n");
break;
case RPORT_ST_PLOGI:
- FC_DEBUG_RPORT("incoming PLOGI from %x in PLOGI state %d\n",
- sid, rdata->rp_state);
- if (wwpn < lport->wwpn)
- reject = ELS_RJT_INPROG;
+ FC_RPORT_DBG(rdata, "Received PLOGI in PLOGI state\n");
+ if (rdata->ids.port_name < lport->wwpn) {
+ mutex_unlock(&rdata->rp_mutex);
+ rjt_data.reason = ELS_RJT_INPROG;
+ rjt_data.explan = ELS_EXPL_NONE;
+ goto reject;
+ }
break;
case RPORT_ST_PRLI:
+ case RPORT_ST_RTV:
case RPORT_ST_READY:
- FC_DEBUG_RPORT("incoming PLOGI from %x in logged-in state %d "
- "- ignored for now\n", sid, rdata->rp_state);
+ case RPORT_ST_ADISC:
+ FC_RPORT_DBG(rdata, "Received PLOGI in logged-in state %d "
+ "- ignored for now\n", rdata->rp_state);
/* XXX TBD - should reset */
break;
- case RPORT_ST_NONE:
- default:
- FC_DEBUG_RPORT("incoming PLOGI from %x in unexpected "
- "state %d\n", sid, rdata->rp_state);
- break;
+ case RPORT_ST_FLOGI:
+ case RPORT_ST_DELETE:
+ FC_RPORT_DBG(rdata, "Received PLOGI in state %s - send busy\n",
+ fc_rport_state(rdata));
+ mutex_unlock(&rdata->rp_mutex);
+ rjt_data.reason = ELS_RJT_BUSY;
+ rjt_data.explan = ELS_EXPL_NONE;
+ goto reject;
}
-
- if (reject) {
- rjt_data.reason = reject;
+ if (!fc_rport_compatible_roles(lport, rdata)) {
+ FC_RPORT_DBG(rdata, "Received PLOGI for incompatible role\n");
+ mutex_unlock(&rdata->rp_mutex);
+ rjt_data.reason = ELS_RJT_LOGIC;
rjt_data.explan = ELS_EXPL_NONE;
- lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
- fc_frame_free(fp);
- } else {
- fp = fc_frame_alloc(lport, sizeof(*pl));
- if (fp == NULL) {
- fp = rx_fp;
- rjt_data.reason = ELS_RJT_UNAB;
- rjt_data.explan = ELS_EXPL_NONE;
- lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
- fc_frame_free(fp);
- } else {
- sp = lport->tt.seq_start_next(sp);
- WARN_ON(!sp);
- fc_rport_set_name(rport, wwpn, wwnn);
+ goto reject;
+ }
- /*
- * Get session payload size from incoming PLOGI.
- */
- rport->maxframe_size =
- fc_plogi_get_maxframe(pl, lport->mfs);
- fc_frame_free(rx_fp);
- fc_plogi_fill(lport, fp, ELS_LS_ACC);
+ /*
+ * Get session payload size from incoming PLOGI.
+ */
+ rdata->maxframe_size = fc_plogi_get_maxframe(pl, lport->mfs);
- /*
- * 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);
- if (rdata->rp_state == RPORT_ST_PLOGI)
- fc_rport_enter_prli(rport);
- }
- }
+ /*
+ * Send LS_ACC. If this fails, the originator should retry.
+ */
+ fp = fc_frame_alloc(lport, sizeof(*pl));
+ if (!fp)
+ goto out;
+
+ fc_plogi_fill(lport, fp, ELS_LS_ACC);
+ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
+ fc_rport_enter_prli(rdata);
+out:
+ mutex_unlock(&rdata->rp_mutex);
+ fc_frame_free(rx_fp);
+ return;
+
+reject:
+ lport->tt.seq_els_rsp_send(fp, ELS_LS_RJT, &rjt_data);
+ fc_frame_free(fp);
}
/**
- * fc_rport_recv_prli_req - Handle incoming Process Login (PRLI) request
- * @rport: Fibre Channel remote port that initiated PRLI
- * @sp: current sequence in the PRLI exchange
- * @fp: PRLI request frame
+ * fc_rport_recv_prli_req() - Handler for process login (PRLI) requests
+ * @rdata: The remote port that sent the PRLI request
+ * @rx_fp: The PRLI request frame
*
- * Locking Note: The rport lock is exected to be held before calling
+ * Locking Note: The rport lock is expected to be held before calling
* this function.
*/
-static void fc_rport_recv_prli_req(struct fc_rport *rport,
- struct fc_seq *sp, struct fc_frame *rx_fp)
+static void fc_rport_recv_prli_req(struct fc_rport_priv *rdata,
+ struct fc_frame *rx_fp)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
struct fc_lport *lport = rdata->local_port;
- struct fc_exch *ep;
struct fc_frame *fp;
- struct fc_frame_header *fh;
struct {
struct fc_els_prli prli;
struct fc_els_spp spp;
@@ -1055,200 +1721,228 @@ static void fc_rport_recv_prli_req(struct fc_rport *rport,
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;
+ 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);
+ struct fc4_prov *prov;
- FC_DEBUG_RPORT("Received PRLI request from port (%6x) "
- "while in state %s\n", ntoh24(fh->fh_s_id),
- fc_rport_state(rport));
+ 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_READY:
- reason = ELS_RJT_NONE;
- break;
- default:
- break;
- }
- len = fr_len(rx_fp) - sizeof(*fh);
+ len = fr_len(rx_fp) - sizeof(struct fc_frame_header);
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);
+ 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);
- /*
- * 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;
- rport->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;
- rport->roles = roles;
-
- spp->spp_params =
- htonl(lport->service_params);
- break;
- default:
- resp = FC_SPP_RESP_INVL;
- break;
+ /*
+ * 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) {
+ rdata->spp_type = rspp->spp_type;
+ spp->spp_type = rspp->spp_type;
+ spp->spp_type_ext = rspp->spp_type_ext;
+ 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;
}
- spp->spp_flags |= resp;
- len -= plen;
- rspp = (struct fc_els_spp *)((char *)rspp + plen);
- spp = (struct fc_els_spp *)((char *)spp + plen);
}
+ 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.
- */
- 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);
+ /*
+ * Send LS_ACC. If this fails, the originator should retry.
+ */
+ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
- /*
- * Get lock and re-check state.
- */
- switch (rdata->rp_state) {
- case RPORT_ST_PRLI:
- fc_rport_enter_ready(rport);
- break;
- case RPORT_ST_READY:
- break;
- default:
- break;
- }
+ 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(rx_fp, ELS_LS_RJT, &rjt_data);
+drop:
fc_frame_free(rx_fp);
}
/**
- * fc_rport_recv_prlo_req - Handle incoming Process Logout (PRLO) request
- * @rport: Fibre Channel remote port that initiated PRLO
- * @sp: current sequence in the PRLO exchange
- * @fp: PRLO request frame
+ * fc_rport_recv_prlo_req() - Handler for process logout (PRLO) requests
+ * @rdata: The remote port that sent the PRLO request
+ * @rx_fp: The PRLO request frame
*
- * Locking Note: The rport lock is exected to be held before calling
+ * Locking Note: The rport lock is expected to be held before calling
* this function.
*/
-static void fc_rport_recv_prlo_req(struct fc_rport *rport, struct fc_seq *sp,
- struct fc_frame *fp)
+static void fc_rport_recv_prlo_req(struct fc_rport_priv *rdata,
+ struct fc_frame *rx_fp)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
struct fc_lport *lport = rdata->local_port;
-
- struct fc_frame_header *fh;
+ struct fc_frame *fp;
+ struct {
+ struct fc_els_prlo prlo;
+ struct fc_els_spp spp;
+ } *pp;
+ struct fc_els_spp *rspp; /* request service param page */
+ struct fc_els_spp *spp; /* response spp */
+ unsigned int len;
+ unsigned int plen;
struct fc_seq_els_data rjt_data;
- fh = fc_frame_header_get(fp);
+ FC_RPORT_DBG(rdata, "Received PRLO request while in state %s\n",
+ fc_rport_state(rdata));
- FC_DEBUG_RPORT("Received PRLO request from port (%6x) "
- "while in state %s\n", ntoh24(fh->fh_s_id),
- fc_rport_state(rport));
+ len = fr_len(rx_fp) - sizeof(struct fc_frame_header);
+ pp = fc_frame_payload_get(rx_fp, sizeof(*pp));
+ if (!pp)
+ goto reject_len;
+ plen = ntohs(pp->prlo.prlo_len);
+ if (plen != 20)
+ goto reject_len;
+ if (plen < len)
+ len = plen;
- rjt_data.fp = NULL;
- rjt_data.reason = ELS_RJT_UNAB;
- rjt_data.explan = ELS_EXPL_NONE;
- lport->tt.seq_els_rsp_send(sp, ELS_LS_RJT, &rjt_data);
- fc_frame_free(fp);
+ 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;
+ }
+
+ pp = fc_frame_payload_get(fp, len);
+ WARN_ON(!pp);
+ memset(pp, 0, len);
+ pp->prlo.prlo_cmd = ELS_LS_ACC;
+ pp->prlo.prlo_obs = 0x10;
+ pp->prlo.prlo_len = htons(len);
+ spp = &pp->spp;
+ spp->spp_type = rspp->spp_type;
+ spp->spp_type_ext = rspp->spp_type_ext;
+ spp->spp_flags = FC_SPP_RESP_ACK;
+
+ fc_rport_enter_delete(rdata, RPORT_EV_LOGO);
+
+ fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_ELS_REP, 0);
+ lport->tt.frame_send(lport, fp);
+ goto drop;
+
+reject_len:
+ rjt_data.reason = ELS_RJT_PROT;
+ rjt_data.explan = ELS_EXPL_INV_LEN;
+reject:
+ lport->tt.seq_els_rsp_send(rx_fp, ELS_LS_RJT, &rjt_data);
+drop:
+ fc_frame_free(rx_fp);
}
/**
- * fc_rport_recv_logo_req - Handle incoming Logout (LOGO) request
- * @rport: Fibre Channel remote port that initiated LOGO
- * @sp: current sequence in the LOGO exchange
- * @fp: LOGO request frame
+ * fc_rport_recv_logo_req() - Handler for logout (LOGO) requests
+ * @lport: The local port that received the LOGO request
+ * @fp: The LOGO request frame
*
- * Locking Note: The rport lock is exected to be held before calling
+ * Locking Note: The rport lock is expected to be held before calling
* this function.
*/
-static void fc_rport_recv_logo_req(struct fc_rport *rport, struct fc_seq *sp,
- struct fc_frame *fp)
+static void fc_rport_recv_logo_req(struct fc_lport *lport, struct fc_frame *fp)
{
- struct fc_frame_header *fh;
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
- struct fc_lport *lport = rdata->local_port;
+ struct fc_rport_priv *rdata;
+ u32 sid;
- fh = fc_frame_header_get(fp);
+ lport->tt.seq_els_rsp_send(fp, ELS_LS_ACC, NULL);
- FC_DEBUG_RPORT("Received LOGO request from port (%6x) "
- "while in state %s\n", ntoh24(fh->fh_s_id),
- fc_rport_state(rport));
+ sid = fc_frame_sid(fp);
- rdata->event = RPORT_EV_LOGO;
- queue_work(rport_event_queue, &rdata->event_work);
+ mutex_lock(&lport->disc.disc_mutex);
+ rdata = lport->tt.rport_lookup(lport, sid);
+ if (rdata) {
+ mutex_lock(&rdata->rp_mutex);
+ FC_RPORT_DBG(rdata, "Received LOGO request while in state %s\n",
+ fc_rport_state(rdata));
- lport->tt.seq_els_rsp_send(sp, ELS_LS_ACC, NULL);
+ fc_rport_enter_delete(rdata, RPORT_EV_LOGO);
+ mutex_unlock(&rdata->rp_mutex);
+ } else
+ FC_RPORT_ID_DBG(lport, sid,
+ "Received LOGO from non-logged-in port\n");
+ mutex_unlock(&lport->disc.disc_mutex);
fc_frame_free(fp);
}
+/**
+ * fc_rport_flush_queue() - Flush the rport_event_queue
+ */
static void fc_rport_flush_queue(void)
{
flush_workqueue(rport_event_queue);
}
-
+/**
+ * fc_rport_init() - Initialize the remote port layer for a local port
+ * @lport: The local port to initialize the remote port layer for
+ */
int fc_rport_init(struct fc_lport *lport)
{
+ if (!lport->tt.rport_lookup)
+ lport->tt.rport_lookup = fc_rport_lookup;
+
+ if (!lport->tt.rport_create)
+ lport->tt.rport_create = fc_rport_create;
+
if (!lport->tt.rport_login)
lport->tt.rport_login = fc_rport_login;
@@ -1261,31 +1955,115 @@ int fc_rport_init(struct fc_lport *lport)
if (!lport->tt.rport_flush_queue)
lport->tt.rport_flush_queue = fc_rport_flush_queue;
+ if (!lport->tt.rport_destroy)
+ lport->tt.rport_destroy = fc_rport_destroy;
+
return 0;
}
EXPORT_SYMBOL(fc_rport_init);
-int fc_setup_rport()
+/**
+ * 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 & FCP_SPPF_INIT_FCN))
+ 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(void)
{
rport_event_queue = create_singlethread_workqueue("fc_rport_eq");
if (!rport_event_queue)
return -ENOMEM;
return 0;
}
-EXPORT_SYMBOL(fc_setup_rport);
-void fc_destroy_rport()
+/**
+ * fc_destroy_rport() - Destroy the rport_event_queue
+ */
+void fc_destroy_rport(void)
{
destroy_workqueue(rport_event_queue);
}
-EXPORT_SYMBOL(fc_destroy_rport);
+/**
+ * fc_rport_terminate_io() - Stop all outstanding I/O on a remote port
+ * @rport: The remote port whose I/O should be terminated
+ */
void fc_rport_terminate_io(struct fc_rport *rport)
{
- struct fc_rport_libfc_priv *rdata = rport->dd_data;
- struct fc_lport *lport = rdata->local_port;
+ struct fc_rport_libfc_priv *rpriv = rport->dd_data;
+ struct fc_lport *lport = rpriv->local_port;
- lport->tt.exch_mgr_reset(lport->emp, 0, rport->port_id);
- lport->tt.exch_mgr_reset(lport->emp, rport->port_id, 0);
+ lport->tt.exch_mgr_reset(lport, 0, rport->port_id);
+ lport->tt.exch_mgr_reset(lport, rport->port_id, 0);
}
EXPORT_SYMBOL(fc_rport_terminate_io);