xref: /openbmc/linux/drivers/scsi/libfc/fc_disc.c (revision ee35624e)
142e9a92fSRobert Love /*
242e9a92fSRobert Love  * Copyright(c) 2007 - 2008 Intel Corporation. All rights reserved.
342e9a92fSRobert Love  *
442e9a92fSRobert Love  * This program is free software; you can redistribute it and/or modify it
542e9a92fSRobert Love  * under the terms and conditions of the GNU General Public License,
642e9a92fSRobert Love  * version 2, as published by the Free Software Foundation.
742e9a92fSRobert Love  *
842e9a92fSRobert Love  * This program is distributed in the hope it will be useful, but WITHOUT
942e9a92fSRobert Love  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1042e9a92fSRobert Love  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
1142e9a92fSRobert Love  * more details.
1242e9a92fSRobert Love  *
1342e9a92fSRobert Love  * You should have received a copy of the GNU General Public License along with
1442e9a92fSRobert Love  * this program; if not, write to the Free Software Foundation, Inc.,
1542e9a92fSRobert Love  * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
1642e9a92fSRobert Love  *
1742e9a92fSRobert Love  * Maintained at www.Open-FCoE.org
1842e9a92fSRobert Love  */
1942e9a92fSRobert Love 
2042e9a92fSRobert Love /*
2142e9a92fSRobert Love  * Target Discovery
2242e9a92fSRobert Love  *
2342e9a92fSRobert Love  * This block discovers all FC-4 remote ports, including FCP initiators. It
2442e9a92fSRobert Love  * also handles RSCN events and re-discovery if necessary.
2542e9a92fSRobert Love  */
2642e9a92fSRobert Love 
2742e9a92fSRobert Love /*
2842e9a92fSRobert Love  * DISC LOCKING
2942e9a92fSRobert Love  *
3042e9a92fSRobert Love  * The disc mutex is can be locked when acquiring rport locks, but may not
3142e9a92fSRobert Love  * be held when acquiring the lport lock. Refer to fc_lport.c for more
3242e9a92fSRobert Love  * details.
3342e9a92fSRobert Love  */
3442e9a92fSRobert Love 
3542e9a92fSRobert Love #include <linux/timer.h>
365a0e3ad6STejun Heo #include <linux/slab.h>
3742e9a92fSRobert Love #include <linux/err.h>
3809703660SPaul Gortmaker #include <linux/export.h>
39b2d09103SIngo Molnar #include <linux/rculist.h>
40b2d09103SIngo Molnar 
4142e9a92fSRobert Love #include <asm/unaligned.h>
4242e9a92fSRobert Love 
4342e9a92fSRobert Love #include <scsi/fc/fc_gs.h>
4442e9a92fSRobert Love 
4542e9a92fSRobert Love #include <scsi/libfc.h>
4642e9a92fSRobert Love 
478866a5d9SRobert Love #include "fc_libfc.h"
488866a5d9SRobert Love 
4942e9a92fSRobert Love #define FC_DISC_RETRY_LIMIT	3	/* max retries */
5042e9a92fSRobert Love #define FC_DISC_RETRY_DELAY	500UL	/* (msecs) delay */
5142e9a92fSRobert Love 
5242e9a92fSRobert Love static void fc_disc_gpn_ft_req(struct fc_disc *);
5342e9a92fSRobert Love static void fc_disc_gpn_ft_resp(struct fc_seq *, struct fc_frame *, void *);
54786681b9SJoe Eykholt static void fc_disc_done(struct fc_disc *, enum fc_disc_event);
5542e9a92fSRobert Love static void fc_disc_timeout(struct work_struct *);
562ab7e1ecSJoe Eykholt static int fc_disc_single(struct fc_lport *, struct fc_disc_port *);
5742e9a92fSRobert Love static void fc_disc_restart(struct fc_disc *);
5842e9a92fSRobert Love 
5942e9a92fSRobert Love /**
603a3b42bfSRobert Love  * fc_disc_stop_rports() - Delete all the remote ports associated with the lport
613a3b42bfSRobert Love  * @disc: The discovery job to stop remote ports on
6242e9a92fSRobert Love  */
63c6b21c93SBart Van Assche static void fc_disc_stop_rports(struct fc_disc *disc)
6442e9a92fSRobert Love {
6542e9a92fSRobert Love 	struct fc_lport *lport;
6642e90414SJoe Eykholt 	struct fc_rport_priv *rdata;
6742e9a92fSRobert Love 
680685230cSJoe Eykholt 	lport = fc_disc_lport(disc);
69ee35624eSHannes Reinecke 	lockdep_assert_held(&lport->lp_mutex);
7042e9a92fSRobert Love 
71a407c593SHannes Reinecke 	rcu_read_lock();
72a407c593SHannes Reinecke 	list_for_each_entry_rcu(rdata, &disc->rports, peers) {
73a407c593SHannes Reinecke 		if (kref_get_unless_zero(&rdata->kref)) {
74c96c792aSHannes Reinecke 			fc_rport_logoff(rdata);
75944ef968SHannes Reinecke 			kref_put(&rdata->kref, fc_rport_destroy);
76a407c593SHannes Reinecke 		}
77a407c593SHannes Reinecke 	}
78a407c593SHannes Reinecke 	rcu_read_unlock();
7942e9a92fSRobert Love }
8042e9a92fSRobert Love 
8142e9a92fSRobert Love /**
8234f42a07SRobert Love  * fc_disc_recv_rscn_req() - Handle Registered State Change Notification (RSCN)
8392261156SJoe Eykholt  * @disc:  The discovery object to which the RSCN applies
843a3b42bfSRobert Love  * @fp:	   The RSCN frame
8542e9a92fSRobert Love  */
8692261156SJoe Eykholt static void fc_disc_recv_rscn_req(struct fc_disc *disc, struct fc_frame *fp)
8742e9a92fSRobert Love {
8842e9a92fSRobert Love 	struct fc_lport *lport;
8942e9a92fSRobert Love 	struct fc_els_rscn *rp;
9042e9a92fSRobert Love 	struct fc_els_rscn_page *pp;
9142e9a92fSRobert Love 	struct fc_seq_els_data rjt_data;
9242e9a92fSRobert Love 	unsigned int len;
9342e9a92fSRobert Love 	int redisc = 0;
9442e9a92fSRobert Love 	enum fc_els_rscn_ev_qual ev_qual;
9542e9a92fSRobert Love 	enum fc_els_rscn_addr_fmt fmt;
9642e9a92fSRobert Love 	LIST_HEAD(disc_ports);
9742e9a92fSRobert Love 	struct fc_disc_port *dp, *next;
9842e9a92fSRobert Love 
99ee35624eSHannes Reinecke 	lockdep_assert_held(&disc->disc_mutex);
100ee35624eSHannes Reinecke 
1010685230cSJoe Eykholt 	lport = fc_disc_lport(disc);
10242e9a92fSRobert Love 
1037414705eSRobert Love 	FC_DISC_DBG(disc, "Received an RSCN event\n");
10442e9a92fSRobert Love 
10542e9a92fSRobert Love 	/* make sure the frame contains an RSCN message */
10642e9a92fSRobert Love 	rp = fc_frame_payload_get(fp, sizeof(*rp));
10742e9a92fSRobert Love 	if (!rp)
10842e9a92fSRobert Love 		goto reject;
10942e9a92fSRobert Love 	/* make sure the page length is as expected (4 bytes) */
11042e9a92fSRobert Love 	if (rp->rscn_page_len != sizeof(*pp))
11142e9a92fSRobert Love 		goto reject;
11242e9a92fSRobert Love 	/* get the RSCN payload length */
11342e9a92fSRobert Love 	len = ntohs(rp->rscn_plen);
11442e9a92fSRobert Love 	if (len < sizeof(*rp))
11542e9a92fSRobert Love 		goto reject;
11642e9a92fSRobert Love 	/* make sure the frame contains the expected payload */
11742e9a92fSRobert Love 	rp = fc_frame_payload_get(fp, len);
11842e9a92fSRobert Love 	if (!rp)
11942e9a92fSRobert Love 		goto reject;
12042e9a92fSRobert Love 	/* payload must be a multiple of the RSCN page size */
12142e9a92fSRobert Love 	len -= sizeof(*rp);
12242e9a92fSRobert Love 	if (len % sizeof(*pp))
12342e9a92fSRobert Love 		goto reject;
12442e9a92fSRobert Love 
12542e9a92fSRobert Love 	for (pp = (void *)(rp + 1); len > 0; len -= sizeof(*pp), pp++) {
12642e9a92fSRobert Love 		ev_qual = pp->rscn_page_flags >> ELS_RSCN_EV_QUAL_BIT;
12742e9a92fSRobert Love 		ev_qual &= ELS_RSCN_EV_QUAL_MASK;
12842e9a92fSRobert Love 		fmt = pp->rscn_page_flags >> ELS_RSCN_ADDR_FMT_BIT;
12942e9a92fSRobert Love 		fmt &= ELS_RSCN_ADDR_FMT_MASK;
13042e9a92fSRobert Love 		/*
13142e9a92fSRobert Love 		 * if we get an address format other than port
13242e9a92fSRobert Love 		 * (area, domain, fabric), then do a full discovery
13342e9a92fSRobert Love 		 */
13442e9a92fSRobert Love 		switch (fmt) {
13542e9a92fSRobert Love 		case ELS_ADDR_FMT_PORT:
1367414705eSRobert Love 			FC_DISC_DBG(disc, "Port address format for port "
137ce8b5df0SChris Leech 				    "(%6.6x)\n", ntoh24(pp->rscn_fid));
13842e9a92fSRobert Love 			dp = kzalloc(sizeof(*dp), GFP_KERNEL);
13942e9a92fSRobert Love 			if (!dp) {
14042e9a92fSRobert Love 				redisc = 1;
14142e9a92fSRobert Love 				break;
14242e9a92fSRobert Love 			}
14342e9a92fSRobert Love 			dp->lp = lport;
1449737e6a7SRobert Love 			dp->port_id = ntoh24(pp->rscn_fid);
14542e9a92fSRobert Love 			list_add_tail(&dp->peers, &disc_ports);
14642e9a92fSRobert Love 			break;
14742e9a92fSRobert Love 		case ELS_ADDR_FMT_AREA:
14842e9a92fSRobert Love 		case ELS_ADDR_FMT_DOM:
14942e9a92fSRobert Love 		case ELS_ADDR_FMT_FAB:
15042e9a92fSRobert Love 		default:
1517414705eSRobert Love 			FC_DISC_DBG(disc, "Address format is (%d)\n", fmt);
15242e9a92fSRobert Love 			redisc = 1;
15342e9a92fSRobert Love 			break;
15442e9a92fSRobert Love 		}
15542e9a92fSRobert Love 	}
1567ab24dd1SHannes Reinecke 	fc_seq_els_rsp_send(fp, ELS_LS_ACC, NULL);
1572ab7e1ecSJoe Eykholt 
1582ab7e1ecSJoe Eykholt 	/*
1592ab7e1ecSJoe Eykholt 	 * If not doing a complete rediscovery, do GPN_ID on
1602ab7e1ecSJoe Eykholt 	 * the individual ports mentioned in the list.
1612ab7e1ecSJoe Eykholt 	 * If any of these get an error, do a full rediscovery.
1622ab7e1ecSJoe Eykholt 	 * In any case, go through the list and free the entries.
1632ab7e1ecSJoe Eykholt 	 */
1642ab7e1ecSJoe Eykholt 	list_for_each_entry_safe(dp, next, &disc_ports, peers) {
1652ab7e1ecSJoe Eykholt 		list_del(&dp->peers);
1662ab7e1ecSJoe Eykholt 		if (!redisc)
1672ab7e1ecSJoe Eykholt 			redisc = fc_disc_single(lport, dp);
1682ab7e1ecSJoe Eykholt 		kfree(dp);
1692ab7e1ecSJoe Eykholt 	}
17042e9a92fSRobert Love 	if (redisc) {
1717414705eSRobert Love 		FC_DISC_DBG(disc, "RSCN received: rediscovering\n");
17242e9a92fSRobert Love 		fc_disc_restart(disc);
17342e9a92fSRobert Love 	} else {
1747414705eSRobert Love 		FC_DISC_DBG(disc, "RSCN received: not rediscovering. "
17542e9a92fSRobert Love 			    "redisc %d state %d in_prog %d\n",
17642e9a92fSRobert Love 			    redisc, lport->state, disc->pending);
17742e9a92fSRobert Love 	}
17842e9a92fSRobert Love 	fc_frame_free(fp);
17942e9a92fSRobert Love 	return;
18042e9a92fSRobert Love reject:
1817414705eSRobert Love 	FC_DISC_DBG(disc, "Received a bad RSCN frame\n");
18242e9a92fSRobert Love 	rjt_data.reason = ELS_RJT_LOGIC;
18342e9a92fSRobert Love 	rjt_data.explan = ELS_EXPL_NONE;
1847ab24dd1SHannes Reinecke 	fc_seq_els_rsp_send(fp, ELS_LS_RJT, &rjt_data);
18542e9a92fSRobert Love 	fc_frame_free(fp);
18642e9a92fSRobert Love }
18742e9a92fSRobert Love 
18842e9a92fSRobert Love /**
18934f42a07SRobert Love  * fc_disc_recv_req() - Handle incoming requests
1903a3b42bfSRobert Love  * @lport: The local port receiving the request
19192261156SJoe Eykholt  * @fp:	   The request frame
19242e9a92fSRobert Love  *
19342e9a92fSRobert Love  * Locking Note: This function is called from the EM and will lock
19442e9a92fSRobert Love  *		 the disc_mutex before calling the handler for the
19542e9a92fSRobert Love  *		 request.
19642e9a92fSRobert Love  */
19792261156SJoe Eykholt static void fc_disc_recv_req(struct fc_lport *lport, struct fc_frame *fp)
19842e9a92fSRobert Love {
19942e9a92fSRobert Love 	u8 op;
20042e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
20142e9a92fSRobert Love 
20242e9a92fSRobert Love 	op = fc_frame_payload_op(fp);
20342e9a92fSRobert Love 	switch (op) {
20442e9a92fSRobert Love 	case ELS_RSCN:
20542e9a92fSRobert Love 		mutex_lock(&disc->disc_mutex);
20692261156SJoe Eykholt 		fc_disc_recv_rscn_req(disc, fp);
20742e9a92fSRobert Love 		mutex_unlock(&disc->disc_mutex);
20842e9a92fSRobert Love 		break;
20942e9a92fSRobert Love 	default:
2107414705eSRobert Love 		FC_DISC_DBG(disc, "Received an unsupported request, "
2117414705eSRobert Love 			    "the opcode is (%x)\n", op);
21283383dd1SHillf Danton 		fc_frame_free(fp);
21342e9a92fSRobert Love 		break;
21442e9a92fSRobert Love 	}
21542e9a92fSRobert Love }
21642e9a92fSRobert Love 
21742e9a92fSRobert Love /**
21834f42a07SRobert Love  * fc_disc_restart() - Restart discovery
2193a3b42bfSRobert Love  * @disc: The discovery object to be restarted
22042e9a92fSRobert Love  */
22142e9a92fSRobert Love static void fc_disc_restart(struct fc_disc *disc)
22242e9a92fSRobert Love {
223ee35624eSHannes Reinecke 	lockdep_assert_held(&disc->disc_mutex);
224ee35624eSHannes Reinecke 
225935d0fceSJoe Eykholt 	if (!disc->disc_callback)
226935d0fceSJoe Eykholt 		return;
227935d0fceSJoe Eykholt 
2287414705eSRobert Love 	FC_DISC_DBG(disc, "Restarting discovery\n");
22942e9a92fSRobert Love 
23042e9a92fSRobert Love 	disc->requested = 1;
2310f6c6149SJoe Eykholt 	if (disc->pending)
2320f6c6149SJoe Eykholt 		return;
2330f6c6149SJoe Eykholt 
2340f6c6149SJoe Eykholt 	/*
2350f6c6149SJoe Eykholt 	 * Advance disc_id.  This is an arbitrary non-zero number that will
2360f6c6149SJoe Eykholt 	 * match the value in the fc_rport_priv after discovery for all
2370f6c6149SJoe Eykholt 	 * freshly-discovered remote ports.  Avoid wrapping to zero.
2380f6c6149SJoe Eykholt 	 */
2390f6c6149SJoe Eykholt 	disc->disc_id = (disc->disc_id + 2) | 1;
2403667d7e7SJoe Eykholt 	disc->retry_count = 0;
24142e9a92fSRobert Love 	fc_disc_gpn_ft_req(disc);
24242e9a92fSRobert Love }
24342e9a92fSRobert Love 
24442e9a92fSRobert Love /**
2453a3b42bfSRobert Love  * fc_disc_start() - Start discovery on a local port
2463a3b42bfSRobert Love  * @lport:	   The local port to have discovery started on
2473a3b42bfSRobert Love  * @disc_callback: Callback function to be called when discovery is complete
24842e9a92fSRobert Love  */
24942e9a92fSRobert Love static void fc_disc_start(void (*disc_callback)(struct fc_lport *,
25042e9a92fSRobert Love 						enum fc_disc_event),
25142e9a92fSRobert Love 			  struct fc_lport *lport)
25242e9a92fSRobert Love {
25342e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
25442e9a92fSRobert Love 
25542e9a92fSRobert Love 	/*
25642e9a92fSRobert Love 	 * At this point we may have a new disc job or an existing
25742e9a92fSRobert Love 	 * one. Either way, let's lock when we make changes to it
25842e9a92fSRobert Love 	 * and send the GPN_FT request.
25942e9a92fSRobert Love 	 */
26042e9a92fSRobert Love 	mutex_lock(&disc->disc_mutex);
26142e9a92fSRobert Love 	disc->disc_callback = disc_callback;
26229d898e9SJoe Eykholt 	fc_disc_restart(disc);
26342e9a92fSRobert Love 	mutex_unlock(&disc->disc_mutex);
26442e9a92fSRobert Love }
26542e9a92fSRobert Love 
26642e9a92fSRobert Love /**
26734f42a07SRobert Love  * fc_disc_done() - Discovery has been completed
2683a3b42bfSRobert Love  * @disc:  The discovery context
2693a3b42bfSRobert Love  * @event: The discovery completion status
27042e9a92fSRobert Love  */
271786681b9SJoe Eykholt static void fc_disc_done(struct fc_disc *disc, enum fc_disc_event event)
27242e9a92fSRobert Love {
2730685230cSJoe Eykholt 	struct fc_lport *lport = fc_disc_lport(disc);
2740f6c6149SJoe Eykholt 	struct fc_rport_priv *rdata;
27542e9a92fSRobert Love 
276ee35624eSHannes Reinecke 	lockdep_assert_held(&disc->disc_mutex);
2777414705eSRobert Love 	FC_DISC_DBG(disc, "Discovery complete\n");
27842e9a92fSRobert Love 
27942e9a92fSRobert Love 	disc->pending = 0;
2800f6c6149SJoe Eykholt 	if (disc->requested) {
2810f6c6149SJoe Eykholt 		fc_disc_restart(disc);
2820f6c6149SJoe Eykholt 		return;
2830f6c6149SJoe Eykholt 	}
2840f6c6149SJoe Eykholt 
2850f6c6149SJoe Eykholt 	/*
2860f6c6149SJoe Eykholt 	 * Go through all remote ports.	 If they were found in the latest
2870f6c6149SJoe Eykholt 	 * discovery, reverify or log them in.	Otherwise, log them out.
2880f6c6149SJoe Eykholt 	 * Skip ports which were never discovered.  These are the dNS port
2890f6c6149SJoe Eykholt 	 * and ports which were created by PLOGI.
2900f6c6149SJoe Eykholt 	 */
291a407c593SHannes Reinecke 	rcu_read_lock();
29242e90414SJoe Eykholt 	list_for_each_entry_rcu(rdata, &disc->rports, peers) {
293a407c593SHannes Reinecke 		if (!kref_get_unless_zero(&rdata->kref))
2940f6c6149SJoe Eykholt 			continue;
295a407c593SHannes Reinecke 		if (rdata->disc_id) {
2960f6c6149SJoe Eykholt 			if (rdata->disc_id == disc->disc_id)
29705d7d3b0SHannes Reinecke 				fc_rport_login(rdata);
2980f6c6149SJoe Eykholt 			else
299c96c792aSHannes Reinecke 				fc_rport_logoff(rdata);
3000f6c6149SJoe Eykholt 		}
301944ef968SHannes Reinecke 		kref_put(&rdata->kref, fc_rport_destroy);
302a407c593SHannes Reinecke 	}
303a407c593SHannes Reinecke 	rcu_read_unlock();
3040d228c0fSAbhijeet Joglekar 	mutex_unlock(&disc->disc_mutex);
3050d228c0fSAbhijeet Joglekar 	disc->disc_callback(lport, event);
3060d228c0fSAbhijeet Joglekar 	mutex_lock(&disc->disc_mutex);
30742e9a92fSRobert Love }
30842e9a92fSRobert Love 
30942e9a92fSRobert Love /**
31034f42a07SRobert Love  * fc_disc_error() - Handle error on dNS request
3113a3b42bfSRobert Love  * @disc: The discovery context
3123a3b42bfSRobert Love  * @fp:	  The error code encoded as a frame pointer
31342e9a92fSRobert Love  */
31442e9a92fSRobert Love static void fc_disc_error(struct fc_disc *disc, struct fc_frame *fp)
31542e9a92fSRobert Love {
3160685230cSJoe Eykholt 	struct fc_lport *lport = fc_disc_lport(disc);
31742e9a92fSRobert Love 	unsigned long delay = 0;
3187414705eSRobert Love 
3197414705eSRobert Love 	FC_DISC_DBG(disc, "Error %ld, retries %d/%d\n",
32042e9a92fSRobert Love 		    PTR_ERR(fp), disc->retry_count,
32142e9a92fSRobert Love 		    FC_DISC_RETRY_LIMIT);
32242e9a92fSRobert Love 
32342e9a92fSRobert Love 	if (!fp || PTR_ERR(fp) == -FC_EX_TIMEOUT) {
32442e9a92fSRobert Love 		/*
32542e9a92fSRobert Love 		 * Memory allocation failure, or the exchange timed out,
32642e9a92fSRobert Love 		 * retry after delay.
32742e9a92fSRobert Love 		 */
32842e9a92fSRobert Love 		if (disc->retry_count < FC_DISC_RETRY_LIMIT) {
32942e9a92fSRobert Love 			/* go ahead and retry */
33042e9a92fSRobert Love 			if (!fp)
33142e9a92fSRobert Love 				delay = msecs_to_jiffies(FC_DISC_RETRY_DELAY);
33242e9a92fSRobert Love 			else {
33342e9a92fSRobert Love 				delay = msecs_to_jiffies(lport->e_d_tov);
33442e9a92fSRobert Love 
33542e9a92fSRobert Love 				/* timeout faster first time */
33642e9a92fSRobert Love 				if (!disc->retry_count)
33742e9a92fSRobert Love 					delay /= 4;
33842e9a92fSRobert Love 			}
33942e9a92fSRobert Love 			disc->retry_count++;
34042e9a92fSRobert Love 			schedule_delayed_work(&disc->disc_work, delay);
341786681b9SJoe Eykholt 		} else
342786681b9SJoe Eykholt 			fc_disc_done(disc, DISC_EV_FAILED);
34300832084SBhanu Prakash Gollapudi 	} else if (PTR_ERR(fp) == -FC_EX_CLOSED) {
34400832084SBhanu Prakash Gollapudi 		/*
34500832084SBhanu Prakash Gollapudi 		 * if discovery fails due to lport reset, clear
34600832084SBhanu Prakash Gollapudi 		 * pending flag so that subsequent discovery can
34700832084SBhanu Prakash Gollapudi 		 * continue
34800832084SBhanu Prakash Gollapudi 		 */
34900832084SBhanu Prakash Gollapudi 		disc->pending = 0;
35042e9a92fSRobert Love 	}
35142e9a92fSRobert Love }
35242e9a92fSRobert Love 
35342e9a92fSRobert Love /**
35434f42a07SRobert Love  * fc_disc_gpn_ft_req() - Send Get Port Names by FC-4 type (GPN_FT) request
3553a3b42bfSRobert Love  * @lport: The discovery context
35642e9a92fSRobert Love  */
35742e9a92fSRobert Love static void fc_disc_gpn_ft_req(struct fc_disc *disc)
35842e9a92fSRobert Love {
35942e9a92fSRobert Love 	struct fc_frame *fp;
3600685230cSJoe Eykholt 	struct fc_lport *lport = fc_disc_lport(disc);
36142e9a92fSRobert Love 
362ee35624eSHannes Reinecke 	lockdep_assert_held(&disc->disc_mutex);
363ee35624eSHannes Reinecke 
36442e9a92fSRobert Love 	WARN_ON(!fc_lport_test_ready(lport));
36542e9a92fSRobert Love 
36642e9a92fSRobert Love 	disc->pending = 1;
36742e9a92fSRobert Love 	disc->requested = 0;
36842e9a92fSRobert Love 
36942e9a92fSRobert Love 	disc->buf_len = 0;
37042e9a92fSRobert Love 	disc->seq_count = 0;
37142e9a92fSRobert Love 	fp = fc_frame_alloc(lport,
37242e9a92fSRobert Love 			    sizeof(struct fc_ct_hdr) +
37342e9a92fSRobert Love 			    sizeof(struct fc_ns_gid_ft));
37442e9a92fSRobert Love 	if (!fp)
37542e9a92fSRobert Love 		goto err;
37642e9a92fSRobert Love 
377a46f327aSJoe Eykholt 	if (lport->tt.elsct_send(lport, 0, fp,
37842e9a92fSRobert Love 				 FC_NS_GPN_FT,
37942e9a92fSRobert Love 				 fc_disc_gpn_ft_resp,
380b94f8951SJoe Eykholt 				 disc, 3 * lport->r_a_tov))
38142e9a92fSRobert Love 		return;
38242e9a92fSRobert Love err:
3838f550f93SChris Leech 	fc_disc_error(disc, NULL);
38442e9a92fSRobert Love }
38542e9a92fSRobert Love 
38642e9a92fSRobert Love /**
387786681b9SJoe Eykholt  * fc_disc_gpn_ft_parse() - Parse the body of the dNS GPN_FT response.
3883a3b42bfSRobert Love  * @lport: The local port the GPN_FT was received on
3893a3b42bfSRobert Love  * @buf:   The GPN_FT response buffer
3903a3b42bfSRobert Love  * @len:   The size of response buffer
391786681b9SJoe Eykholt  *
392786681b9SJoe Eykholt  * Goes through the list of IDs and names resulting from a request.
39342e9a92fSRobert Love  */
39442e9a92fSRobert Love static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len)
39542e9a92fSRobert Love {
39642e9a92fSRobert Love 	struct fc_lport *lport;
39742e9a92fSRobert Love 	struct fc_gpn_ft_resp *np;
39842e9a92fSRobert Love 	char *bp;
39942e9a92fSRobert Love 	size_t plen;
40042e9a92fSRobert Love 	size_t tlen;
40142e9a92fSRobert Love 	int error = 0;
402795d86f5SJoe Eykholt 	struct fc_rport_identifiers ids;
403ab28f1fdSJoe Eykholt 	struct fc_rport_priv *rdata;
40442e9a92fSRobert Love 
4050685230cSJoe Eykholt 	lport = fc_disc_lport(disc);
406a1c1e4e7SJoe Eykholt 	disc->seq_count++;
40742e9a92fSRobert Love 
40842e9a92fSRobert Love 	/*
40942e9a92fSRobert Love 	 * Handle partial name record left over from previous call.
41042e9a92fSRobert Love 	 */
41142e9a92fSRobert Love 	bp = buf;
41242e9a92fSRobert Love 	plen = len;
41342e9a92fSRobert Love 	np = (struct fc_gpn_ft_resp *)bp;
41442e9a92fSRobert Love 	tlen = disc->buf_len;
41581a67b97SJoe Eykholt 	disc->buf_len = 0;
41642e9a92fSRobert Love 	if (tlen) {
41742e9a92fSRobert Love 		WARN_ON(tlen >= sizeof(*np));
41842e9a92fSRobert Love 		plen = sizeof(*np) - tlen;
41942e9a92fSRobert Love 		WARN_ON(plen <= 0);
42042e9a92fSRobert Love 		WARN_ON(plen >= sizeof(*np));
42142e9a92fSRobert Love 		if (plen > len)
42242e9a92fSRobert Love 			plen = len;
42342e9a92fSRobert Love 		np = &disc->partial_buf;
42442e9a92fSRobert Love 		memcpy((char *)np + tlen, bp, plen);
42542e9a92fSRobert Love 
42642e9a92fSRobert Love 		/*
42742e9a92fSRobert Love 		 * Set bp so that the loop below will advance it to the
42842e9a92fSRobert Love 		 * first valid full name element.
42942e9a92fSRobert Love 		 */
43042e9a92fSRobert Love 		bp -= tlen;
43142e9a92fSRobert Love 		len += tlen;
43242e9a92fSRobert Love 		plen += tlen;
43342e9a92fSRobert Love 		disc->buf_len = (unsigned char) plen;
43442e9a92fSRobert Love 		if (plen == sizeof(*np))
43542e9a92fSRobert Love 			disc->buf_len = 0;
43642e9a92fSRobert Love 	}
43742e9a92fSRobert Love 
43842e9a92fSRobert Love 	/*
43942e9a92fSRobert Love 	 * Handle full name records, including the one filled from above.
44042e9a92fSRobert Love 	 * Normally, np == bp and plen == len, but from the partial case above,
44142e9a92fSRobert Love 	 * bp, len describe the overall buffer, and np, plen describe the
44242e9a92fSRobert Love 	 * partial buffer, which if would usually be full now.
44342e9a92fSRobert Love 	 * After the first time through the loop, things return to "normal".
44442e9a92fSRobert Love 	 */
44542e9a92fSRobert Love 	while (plen >= sizeof(*np)) {
446795d86f5SJoe Eykholt 		ids.port_id = ntoh24(np->fp_fid);
447795d86f5SJoe Eykholt 		ids.port_name = ntohll(np->fp_wwpn);
44842e9a92fSRobert Love 
4497b2787ecSRobert Love 		if (ids.port_id != lport->port_id &&
450795d86f5SJoe Eykholt 		    ids.port_name != lport->wwpn) {
4512580064bSHannes Reinecke 			rdata = fc_rport_create(lport, ids.port_id);
4529737e6a7SRobert Love 			if (rdata) {
4539737e6a7SRobert Love 				rdata->ids.port_name = ids.port_name;
4540f6c6149SJoe Eykholt 				rdata->disc_id = disc->disc_id;
4559737e6a7SRobert Love 			} else {
4567414705eSRobert Love 				printk(KERN_WARNING "libfc: Failed to allocate "
4577414705eSRobert Love 				       "memory for the newly discovered port "
458ce8b5df0SChris Leech 				       "(%6.6x)\n", ids.port_id);
45981a67b97SJoe Eykholt 				error = -ENOMEM;
46081a67b97SJoe Eykholt 			}
46142e9a92fSRobert Love 		}
46242e9a92fSRobert Love 
46342e9a92fSRobert Love 		if (np->fp_flags & FC_NS_FID_LAST) {
464786681b9SJoe Eykholt 			fc_disc_done(disc, DISC_EV_SUCCESS);
46542e9a92fSRobert Love 			len = 0;
46642e9a92fSRobert Love 			break;
46742e9a92fSRobert Love 		}
46842e9a92fSRobert Love 		len -= sizeof(*np);
46942e9a92fSRobert Love 		bp += sizeof(*np);
47042e9a92fSRobert Love 		np = (struct fc_gpn_ft_resp *)bp;
47142e9a92fSRobert Love 		plen = len;
47242e9a92fSRobert Love 	}
47342e9a92fSRobert Love 
47442e9a92fSRobert Love 	/*
47542e9a92fSRobert Love 	 * Save any partial record at the end of the buffer for next time.
47642e9a92fSRobert Love 	 */
47742e9a92fSRobert Love 	if (error == 0 && len > 0 && len < sizeof(*np)) {
47842e9a92fSRobert Love 		if (np != &disc->partial_buf) {
4797414705eSRobert Love 			FC_DISC_DBG(disc, "Partial buffer remains "
4807414705eSRobert Love 				    "for discovery\n");
48142e9a92fSRobert Love 			memcpy(&disc->partial_buf, np, len);
48242e9a92fSRobert Love 		}
48342e9a92fSRobert Love 		disc->buf_len = (unsigned char) len;
48442e9a92fSRobert Love 	}
48542e9a92fSRobert Love 	return error;
48642e9a92fSRobert Love }
48742e9a92fSRobert Love 
48834f42a07SRobert Love /**
4893a3b42bfSRobert Love  * fc_disc_timeout() - Handler for discovery timeouts
4903a3b42bfSRobert Love  * @work: Structure holding discovery context that needs to retry discovery
49142e9a92fSRobert Love  */
49242e9a92fSRobert Love static void fc_disc_timeout(struct work_struct *work)
49342e9a92fSRobert Love {
49442e9a92fSRobert Love 	struct fc_disc *disc = container_of(work,
49542e9a92fSRobert Love 					    struct fc_disc,
49642e9a92fSRobert Love 					    disc_work.work);
49742e9a92fSRobert Love 	mutex_lock(&disc->disc_mutex);
49842e9a92fSRobert Love 	fc_disc_gpn_ft_req(disc);
49942e9a92fSRobert Love 	mutex_unlock(&disc->disc_mutex);
50042e9a92fSRobert Love }
50142e9a92fSRobert Love 
50242e9a92fSRobert Love /**
50334f42a07SRobert Love  * fc_disc_gpn_ft_resp() - Handle a response frame from Get Port Names (GPN_FT)
5043a3b42bfSRobert Love  * @sp:	    The sequence that the GPN_FT response was received on
5053a3b42bfSRobert Love  * @fp:	    The GPN_FT response frame
5063a3b42bfSRobert Love  * @lp_arg: The discovery context
50742e9a92fSRobert Love  *
5080d228c0fSAbhijeet Joglekar  * Locking Note: This function is called without disc mutex held, and
5090d228c0fSAbhijeet Joglekar  *		 should do all its processing with the mutex held
51042e9a92fSRobert Love  */
51142e9a92fSRobert Love static void fc_disc_gpn_ft_resp(struct fc_seq *sp, struct fc_frame *fp,
51242e9a92fSRobert Love 				void *disc_arg)
51342e9a92fSRobert Love {
51442e9a92fSRobert Love 	struct fc_disc *disc = disc_arg;
51542e9a92fSRobert Love 	struct fc_ct_hdr *cp;
51642e9a92fSRobert Love 	struct fc_frame_header *fh;
517a1c1e4e7SJoe Eykholt 	enum fc_disc_event event = DISC_EV_NONE;
51842e9a92fSRobert Love 	unsigned int seq_cnt;
51942e9a92fSRobert Love 	unsigned int len;
520a1c1e4e7SJoe Eykholt 	int error = 0;
52142e9a92fSRobert Love 
5220d228c0fSAbhijeet Joglekar 	mutex_lock(&disc->disc_mutex);
5237414705eSRobert Love 	FC_DISC_DBG(disc, "Received a GPN_FT response\n");
52442e9a92fSRobert Love 
52542e9a92fSRobert Love 	if (IS_ERR(fp)) {
52642e9a92fSRobert Love 		fc_disc_error(disc, fp);
5270d228c0fSAbhijeet Joglekar 		mutex_unlock(&disc->disc_mutex);
52842e9a92fSRobert Love 		return;
52942e9a92fSRobert Love 	}
53042e9a92fSRobert Love 
53142e9a92fSRobert Love 	WARN_ON(!fc_frame_is_linear(fp));	/* buffer must be contiguous */
53242e9a92fSRobert Love 	fh = fc_frame_header_get(fp);
53342e9a92fSRobert Love 	len = fr_len(fp) - sizeof(*fh);
53442e9a92fSRobert Love 	seq_cnt = ntohs(fh->fh_seq_cnt);
535a1c1e4e7SJoe Eykholt 	if (fr_sof(fp) == FC_SOF_I3 && seq_cnt == 0 && disc->seq_count == 0) {
53642e9a92fSRobert Love 		cp = fc_frame_payload_get(fp, sizeof(*cp));
53742e9a92fSRobert Love 		if (!cp) {
5387414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT response too short, len %d\n",
53942e9a92fSRobert Love 				    fr_len(fp));
540883a337cSJoe Eykholt 			event = DISC_EV_FAILED;
54142e9a92fSRobert Love 		} else if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
54242e9a92fSRobert Love 
54334f42a07SRobert Love 			/* Accepted, parse the response. */
54442e9a92fSRobert Love 			len -= sizeof(*cp);
545a1c1e4e7SJoe Eykholt 			error = fc_disc_gpn_ft_parse(disc, cp + 1, len);
54642e9a92fSRobert Love 		} else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
5477414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT rejected reason %x exp %x "
54842e9a92fSRobert Love 				    "(check zoning)\n", cp->ct_reason,
54942e9a92fSRobert Love 				    cp->ct_explan);
550a1c1e4e7SJoe Eykholt 			event = DISC_EV_FAILED;
551c762608bSJoe Eykholt 			if (cp->ct_reason == FC_FS_RJT_UNABL &&
552c762608bSJoe Eykholt 			    cp->ct_explan == FC_FS_EXP_FTNR)
553c762608bSJoe Eykholt 				event = DISC_EV_SUCCESS;
55442e9a92fSRobert Love 		} else {
5557414705eSRobert Love 			FC_DISC_DBG(disc, "GPN_FT unexpected response code "
5567414705eSRobert Love 				    "%x\n", ntohs(cp->ct_cmd));
557883a337cSJoe Eykholt 			event = DISC_EV_FAILED;
55842e9a92fSRobert Love 		}
559a1c1e4e7SJoe Eykholt 	} else if (fr_sof(fp) == FC_SOF_N3 && seq_cnt == disc->seq_count) {
560a1c1e4e7SJoe Eykholt 		error = fc_disc_gpn_ft_parse(disc, fh + 1, len);
56142e9a92fSRobert Love 	} else {
5627414705eSRobert Love 		FC_DISC_DBG(disc, "GPN_FT unexpected frame - out of sequence? "
56342e9a92fSRobert Love 			    "seq_cnt %x expected %x sof %x eof %x\n",
56442e9a92fSRobert Love 			    seq_cnt, disc->seq_count, fr_sof(fp), fr_eof(fp));
565883a337cSJoe Eykholt 		event = DISC_EV_FAILED;
56642e9a92fSRobert Love 	}
56742e9a92fSRobert Love 	if (error)
5686f37e210SDan Carpenter 		fc_disc_error(disc, ERR_PTR(error));
569a1c1e4e7SJoe Eykholt 	else if (event != DISC_EV_NONE)
570a1c1e4e7SJoe Eykholt 		fc_disc_done(disc, event);
57142e9a92fSRobert Love 	fc_frame_free(fp);
5720d228c0fSAbhijeet Joglekar 	mutex_unlock(&disc->disc_mutex);
57342e9a92fSRobert Love }
57442e9a92fSRobert Love 
57542e9a92fSRobert Love /**
5762ab7e1ecSJoe Eykholt  * fc_disc_gpn_id_resp() - Handle a response frame from Get Port Names (GPN_ID)
5773a3b42bfSRobert Love  * @sp:	       The sequence the GPN_ID is on
5783a3b42bfSRobert Love  * @fp:	       The response frame
5793a3b42bfSRobert Love  * @rdata_arg: The remote port that sent the GPN_ID response
5802ab7e1ecSJoe Eykholt  *
5812ab7e1ecSJoe Eykholt  * Locking Note: This function is called without disc mutex held.
5822ab7e1ecSJoe Eykholt  */
5832ab7e1ecSJoe Eykholt static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp,
5842ab7e1ecSJoe Eykholt 				void *rdata_arg)
5852ab7e1ecSJoe Eykholt {
5862ab7e1ecSJoe Eykholt 	struct fc_rport_priv *rdata = rdata_arg;
5872ab7e1ecSJoe Eykholt 	struct fc_rport_priv *new_rdata;
5882ab7e1ecSJoe Eykholt 	struct fc_lport *lport;
5892ab7e1ecSJoe Eykholt 	struct fc_disc *disc;
5902ab7e1ecSJoe Eykholt 	struct fc_ct_hdr *cp;
5912ab7e1ecSJoe Eykholt 	struct fc_ns_gid_pn *pn;
5922ab7e1ecSJoe Eykholt 	u64 port_name;
5932ab7e1ecSJoe Eykholt 
5942ab7e1ecSJoe Eykholt 	lport = rdata->local_port;
5952ab7e1ecSJoe Eykholt 	disc = &lport->disc;
5962ab7e1ecSJoe Eykholt 
5972ab7e1ecSJoe Eykholt 	if (PTR_ERR(fp) == -FC_EX_CLOSED)
5982ab7e1ecSJoe Eykholt 		goto out;
5992ab7e1ecSJoe Eykholt 	if (IS_ERR(fp))
6002ab7e1ecSJoe Eykholt 		goto redisc;
6012ab7e1ecSJoe Eykholt 
6022ab7e1ecSJoe Eykholt 	cp = fc_frame_payload_get(fp, sizeof(*cp));
6032ab7e1ecSJoe Eykholt 	if (!cp)
6042ab7e1ecSJoe Eykholt 		goto redisc;
6052ab7e1ecSJoe Eykholt 	if (ntohs(cp->ct_cmd) == FC_FS_ACC) {
6062ab7e1ecSJoe Eykholt 		if (fr_len(fp) < sizeof(struct fc_frame_header) +
6072ab7e1ecSJoe Eykholt 		    sizeof(*cp) + sizeof(*pn))
6082ab7e1ecSJoe Eykholt 			goto redisc;
6092ab7e1ecSJoe Eykholt 		pn = (struct fc_ns_gid_pn *)(cp + 1);
6102ab7e1ecSJoe Eykholt 		port_name = get_unaligned_be64(&pn->fn_wwpn);
611a407c593SHannes Reinecke 		mutex_lock(&rdata->rp_mutex);
6122ab7e1ecSJoe Eykholt 		if (rdata->ids.port_name == -1)
6132ab7e1ecSJoe Eykholt 			rdata->ids.port_name = port_name;
6142ab7e1ecSJoe Eykholt 		else if (rdata->ids.port_name != port_name) {
6152ab7e1ecSJoe Eykholt 			FC_DISC_DBG(disc, "GPN_ID accepted.  WWPN changed. "
6169f8f3aa6SChris Leech 				    "Port-id %6.6x wwpn %16.16llx\n",
6172ab7e1ecSJoe Eykholt 				    rdata->ids.port_id, port_name);
618a407c593SHannes Reinecke 			mutex_unlock(&rdata->rp_mutex);
619c96c792aSHannes Reinecke 			fc_rport_logoff(rdata);
620a407c593SHannes Reinecke 			mutex_lock(&lport->disc.disc_mutex);
6212580064bSHannes Reinecke 			new_rdata = fc_rport_create(lport, rdata->ids.port_id);
622a407c593SHannes Reinecke 			mutex_unlock(&lport->disc.disc_mutex);
6232ab7e1ecSJoe Eykholt 			if (new_rdata) {
6242ab7e1ecSJoe Eykholt 				new_rdata->disc_id = disc->disc_id;
62505d7d3b0SHannes Reinecke 				fc_rport_login(new_rdata);
6262ab7e1ecSJoe Eykholt 			}
6272ab7e1ecSJoe Eykholt 			goto out;
6282ab7e1ecSJoe Eykholt 		}
6292ab7e1ecSJoe Eykholt 		rdata->disc_id = disc->disc_id;
630a407c593SHannes Reinecke 		mutex_unlock(&rdata->rp_mutex);
63105d7d3b0SHannes Reinecke 		fc_rport_login(rdata);
6322ab7e1ecSJoe Eykholt 	} else if (ntohs(cp->ct_cmd) == FC_FS_RJT) {
6332ab7e1ecSJoe Eykholt 		FC_DISC_DBG(disc, "GPN_ID rejected reason %x exp %x\n",
6342ab7e1ecSJoe Eykholt 			    cp->ct_reason, cp->ct_explan);
635c96c792aSHannes Reinecke 		fc_rport_logoff(rdata);
6362ab7e1ecSJoe Eykholt 	} else {
6372ab7e1ecSJoe Eykholt 		FC_DISC_DBG(disc, "GPN_ID unexpected response code %x\n",
6382ab7e1ecSJoe Eykholt 			    ntohs(cp->ct_cmd));
6392ab7e1ecSJoe Eykholt redisc:
640a407c593SHannes Reinecke 		mutex_lock(&disc->disc_mutex);
6412ab7e1ecSJoe Eykholt 		fc_disc_restart(disc);
642a407c593SHannes Reinecke 		mutex_unlock(&disc->disc_mutex);
6432ab7e1ecSJoe Eykholt 	}
6442ab7e1ecSJoe Eykholt out:
645944ef968SHannes Reinecke 	kref_put(&rdata->kref, fc_rport_destroy);
6462ab7e1ecSJoe Eykholt }
6472ab7e1ecSJoe Eykholt 
6482ab7e1ecSJoe Eykholt /**
6492ab7e1ecSJoe Eykholt  * fc_disc_gpn_id_req() - Send Get Port Names by ID (GPN_ID) request
6503a3b42bfSRobert Love  * @lport: The local port to initiate discovery on
6512ab7e1ecSJoe Eykholt  * @rdata: remote port private data
6522ab7e1ecSJoe Eykholt  *
6532ab7e1ecSJoe Eykholt  * On failure, an error code is returned.
6542ab7e1ecSJoe Eykholt  */
6552ab7e1ecSJoe Eykholt static int fc_disc_gpn_id_req(struct fc_lport *lport,
6562ab7e1ecSJoe Eykholt 			      struct fc_rport_priv *rdata)
6572ab7e1ecSJoe Eykholt {
6582ab7e1ecSJoe Eykholt 	struct fc_frame *fp;
6592ab7e1ecSJoe Eykholt 
660ee35624eSHannes Reinecke 	lockdep_assert_held(&lport->disc.disc_mutex);
6612ab7e1ecSJoe Eykholt 	fp = fc_frame_alloc(lport, sizeof(struct fc_ct_hdr) +
6622ab7e1ecSJoe Eykholt 			    sizeof(struct fc_ns_fid));
6632ab7e1ecSJoe Eykholt 	if (!fp)
6642ab7e1ecSJoe Eykholt 		return -ENOMEM;
6652ab7e1ecSJoe Eykholt 	if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, FC_NS_GPN_ID,
666b94f8951SJoe Eykholt 				  fc_disc_gpn_id_resp, rdata,
667b94f8951SJoe Eykholt 				  3 * lport->r_a_tov))
6682ab7e1ecSJoe Eykholt 		return -ENOMEM;
6692ab7e1ecSJoe Eykholt 	kref_get(&rdata->kref);
6702ab7e1ecSJoe Eykholt 	return 0;
6712ab7e1ecSJoe Eykholt }
6722ab7e1ecSJoe Eykholt 
6732ab7e1ecSJoe Eykholt /**
67434f42a07SRobert Love  * fc_disc_single() - Discover the directory information for a single target
6753a3b42bfSRobert Love  * @lport: The local port the remote port is associated with
67642e9a92fSRobert Love  * @dp:	   The port to rediscover
67742e9a92fSRobert Love  */
6782ab7e1ecSJoe Eykholt static int fc_disc_single(struct fc_lport *lport, struct fc_disc_port *dp)
67942e9a92fSRobert Love {
680ab28f1fdSJoe Eykholt 	struct fc_rport_priv *rdata;
68142e9a92fSRobert Love 
682ee35624eSHannes Reinecke 	lockdep_assert_held(&lport->disc.disc_mutex);
683ee35624eSHannes Reinecke 
6842580064bSHannes Reinecke 	rdata = fc_rport_create(lport, dp->port_id);
6852ab7e1ecSJoe Eykholt 	if (!rdata)
6862ab7e1ecSJoe Eykholt 		return -ENOMEM;
6872ab7e1ecSJoe Eykholt 	rdata->disc_id = 0;
6882ab7e1ecSJoe Eykholt 	return fc_disc_gpn_id_req(lport, rdata);
68942e9a92fSRobert Love }
69042e9a92fSRobert Love 
69142e9a92fSRobert Love /**
69234f42a07SRobert Love  * fc_disc_stop() - Stop discovery for a given lport
6933a3b42bfSRobert Love  * @lport: The local port that discovery should stop on
69442e9a92fSRobert Love  */
695c6b21c93SBart Van Assche static void fc_disc_stop(struct fc_lport *lport)
69642e9a92fSRobert Love {
69742e9a92fSRobert Love 	struct fc_disc *disc = &lport->disc;
69842e9a92fSRobert Love 
699c531b9b4SBhanu Prakash Gollapudi 	if (disc->pending)
70042e9a92fSRobert Love 		cancel_delayed_work_sync(&disc->disc_work);
70142e9a92fSRobert Love 	fc_disc_stop_rports(disc);
70242e9a92fSRobert Love }
70342e9a92fSRobert Love 
70442e9a92fSRobert Love /**
70534f42a07SRobert Love  * fc_disc_stop_final() - Stop discovery for a given lport
7063a3b42bfSRobert Love  * @lport: The lport that discovery should stop on
70742e9a92fSRobert Love  *
70842e9a92fSRobert Love  * This function will block until discovery has been
70942e9a92fSRobert Love  * completely stopped and all rports have been deleted.
71042e9a92fSRobert Love  */
711c6b21c93SBart Van Assche static void fc_disc_stop_final(struct fc_lport *lport)
71242e9a92fSRobert Love {
71342e9a92fSRobert Love 	fc_disc_stop(lport);
7145922a957SHannes Reinecke 	fc_rport_flush_queue();
71542e9a92fSRobert Love }
71642e9a92fSRobert Love 
71742e9a92fSRobert Love /**
7180807619dSRobert Love  * fc_disc_config() - Configure the discovery layer for a local port
7190807619dSRobert Love  * @lport: The local port that needs the discovery layer to be configured
7208a9a7138SRobert Love  * @priv: Private data structre for users of the discovery layer
72142e9a92fSRobert Love  */
7220807619dSRobert Love void fc_disc_config(struct fc_lport *lport, void *priv)
72342e9a92fSRobert Love {
724bc2e1299SColin Ian King 	struct fc_disc *disc;
72542e9a92fSRobert Love 
72642e9a92fSRobert Love 	if (!lport->tt.disc_start)
72742e9a92fSRobert Love 		lport->tt.disc_start = fc_disc_start;
72842e9a92fSRobert Love 
72942e9a92fSRobert Love 	if (!lport->tt.disc_stop)
73042e9a92fSRobert Love 		lport->tt.disc_stop = fc_disc_stop;
73142e9a92fSRobert Love 
73242e9a92fSRobert Love 	if (!lport->tt.disc_stop_final)
73342e9a92fSRobert Love 		lport->tt.disc_stop_final = fc_disc_stop_final;
73442e9a92fSRobert Love 
73542e9a92fSRobert Love 	if (!lport->tt.disc_recv_req)
73642e9a92fSRobert Love 		lport->tt.disc_recv_req = fc_disc_recv_req;
73742e9a92fSRobert Love 
73842e9a92fSRobert Love 	disc = &lport->disc;
7390807619dSRobert Love 
7400807619dSRobert Love 	disc->priv = priv;
7410807619dSRobert Love }
7420807619dSRobert Love EXPORT_SYMBOL(fc_disc_config);
7430807619dSRobert Love 
7440807619dSRobert Love /**
7450807619dSRobert Love  * fc_disc_init() - Initialize the discovery layer for a local port
7460807619dSRobert Love  * @lport: The local port that needs the discovery layer to be initialized
7470807619dSRobert Love  */
7480807619dSRobert Love void fc_disc_init(struct fc_lport *lport)
7490807619dSRobert Love {
7500807619dSRobert Love 	struct fc_disc *disc = &lport->disc;
7510807619dSRobert Love 
75242e9a92fSRobert Love 	INIT_DELAYED_WORK(&disc->disc_work, fc_disc_timeout);
75342e9a92fSRobert Love 	mutex_init(&disc->disc_mutex);
75442e9a92fSRobert Love 	INIT_LIST_HEAD(&disc->rports);
75542e9a92fSRobert Love }
75642e9a92fSRobert Love EXPORT_SYMBOL(fc_disc_init);
757