diff options
Diffstat (limited to 'drivers/scsi/libfc')
| -rw-r--r-- | drivers/scsi/libfc/Makefile | 4 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_disc.c | 746 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_elsct.c | 115 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_exch.c | 2089 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_fcp.c | 1448 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_frame.c | 16 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_libfc.c | 331 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_libfc.h | 139 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_lport.c | 1768 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_npiv.c | 159 | ||||
| -rw-r--r-- | drivers/scsi/libfc/fc_rport.c | 2150 |
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); |
