1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright(c) 2007 - 2008 Intel Corporation. All rights reserved. |
4 | * |
5 | * Maintained at www.Open-FCoE.org |
6 | */ |
7 | |
8 | /* |
9 | * Target Discovery |
10 | * |
11 | * This block discovers all FC-4 remote ports, including FCP initiators. It |
12 | * also handles RSCN events and re-discovery if necessary. |
13 | */ |
14 | |
15 | /* |
16 | * DISC LOCKING |
17 | * |
18 | * The disc mutex is can be locked when acquiring rport locks, but may not |
19 | * be held when acquiring the lport lock. Refer to fc_lport.c for more |
20 | * details. |
21 | */ |
22 | |
23 | #include <linux/timer.h> |
24 | #include <linux/slab.h> |
25 | #include <linux/err.h> |
26 | #include <linux/export.h> |
27 | #include <linux/list.h> |
28 | |
29 | #include <asm/unaligned.h> |
30 | |
31 | #include <scsi/fc/fc_gs.h> |
32 | |
33 | #include <scsi/libfc.h> |
34 | |
35 | #include "fc_libfc.h" |
36 | |
37 | #define FC_DISC_RETRY_LIMIT 3 /* max retries */ |
38 | #define FC_DISC_RETRY_DELAY 500UL /* (msecs) delay */ |
39 | |
40 | static void fc_disc_gpn_ft_req(struct fc_disc *); |
41 | static void fc_disc_gpn_ft_resp(struct fc_seq *, struct fc_frame *, void *); |
42 | static void fc_disc_done(struct fc_disc *, enum fc_disc_event); |
43 | static void fc_disc_timeout(struct work_struct *); |
44 | static int fc_disc_single(struct fc_lport *, struct fc_disc_port *); |
45 | static void fc_disc_restart(struct fc_disc *); |
46 | |
47 | /** |
48 | * fc_disc_stop_rports() - Delete all the remote ports associated with the lport |
49 | * @disc: The discovery job to stop remote ports on |
50 | */ |
51 | static void fc_disc_stop_rports(struct fc_disc *disc) |
52 | { |
53 | struct fc_rport_priv *rdata; |
54 | |
55 | lockdep_assert_held(&disc->disc_mutex); |
56 | |
57 | list_for_each_entry(rdata, &disc->rports, peers) { |
58 | if (kref_get_unless_zero(kref: &rdata->kref)) { |
59 | fc_rport_logoff(rdata); |
60 | kref_put(kref: &rdata->kref, release: fc_rport_destroy); |
61 | } |
62 | } |
63 | } |
64 | |
65 | /** |
66 | * fc_disc_recv_rscn_req() - Handle Registered State Change Notification (RSCN) |
67 | * @disc: The discovery object to which the RSCN applies |
68 | * @fp: The RSCN frame |
69 | */ |
70 | static void fc_disc_recv_rscn_req(struct fc_disc *disc, struct fc_frame *fp) |
71 | { |
72 | struct fc_lport *lport; |
73 | struct fc_els_rscn *rp; |
74 | struct fc_els_rscn_page *pp; |
75 | struct fc_seq_els_data rjt_data; |
76 | unsigned int len; |
77 | int redisc = 0; |
78 | enum fc_els_rscn_addr_fmt fmt; |
79 | LIST_HEAD(disc_ports); |
80 | struct fc_disc_port *dp, *next; |
81 | |
82 | lockdep_assert_held(&disc->disc_mutex); |
83 | |
84 | lport = fc_disc_lport(disc); |
85 | |
86 | FC_DISC_DBG(disc, "Received an RSCN event\n" ); |
87 | |
88 | /* make sure the frame contains an RSCN message */ |
89 | rp = fc_frame_payload_get(fp, len: sizeof(*rp)); |
90 | if (!rp) |
91 | goto reject; |
92 | /* make sure the page length is as expected (4 bytes) */ |
93 | if (rp->rscn_page_len != sizeof(*pp)) |
94 | goto reject; |
95 | /* get the RSCN payload length */ |
96 | len = ntohs(rp->rscn_plen); |
97 | if (len < sizeof(*rp)) |
98 | goto reject; |
99 | /* make sure the frame contains the expected payload */ |
100 | rp = fc_frame_payload_get(fp, len); |
101 | if (!rp) |
102 | goto reject; |
103 | /* payload must be a multiple of the RSCN page size */ |
104 | len -= sizeof(*rp); |
105 | if (len % sizeof(*pp)) |
106 | goto reject; |
107 | |
108 | for (pp = (void *)(rp + 1); len > 0; len -= sizeof(*pp), pp++) { |
109 | fmt = pp->rscn_page_flags >> ELS_RSCN_ADDR_FMT_BIT; |
110 | fmt &= ELS_RSCN_ADDR_FMT_MASK; |
111 | /* |
112 | * if we get an address format other than port |
113 | * (area, domain, fabric), then do a full discovery |
114 | */ |
115 | switch (fmt) { |
116 | case ELS_ADDR_FMT_PORT: |
117 | FC_DISC_DBG(disc, "Port address format for port " |
118 | "(%6.6x)\n" , ntoh24(pp->rscn_fid)); |
119 | dp = kzalloc(size: sizeof(*dp), GFP_KERNEL); |
120 | if (!dp) { |
121 | redisc = 1; |
122 | break; |
123 | } |
124 | dp->lp = lport; |
125 | dp->port_id = ntoh24(p: pp->rscn_fid); |
126 | list_add_tail(new: &dp->peers, head: &disc_ports); |
127 | break; |
128 | case ELS_ADDR_FMT_AREA: |
129 | case ELS_ADDR_FMT_DOM: |
130 | case ELS_ADDR_FMT_FAB: |
131 | default: |
132 | FC_DISC_DBG(disc, "Address format is (%d)\n" , fmt); |
133 | redisc = 1; |
134 | break; |
135 | } |
136 | } |
137 | fc_seq_els_rsp_send(fp, ELS_LS_ACC, NULL); |
138 | |
139 | /* |
140 | * If not doing a complete rediscovery, do GPN_ID on |
141 | * the individual ports mentioned in the list. |
142 | * If any of these get an error, do a full rediscovery. |
143 | * In any case, go through the list and free the entries. |
144 | */ |
145 | list_for_each_entry_safe(dp, next, &disc_ports, peers) { |
146 | list_del(entry: &dp->peers); |
147 | if (!redisc) |
148 | redisc = fc_disc_single(lport, dp); |
149 | kfree(objp: dp); |
150 | } |
151 | if (redisc) { |
152 | FC_DISC_DBG(disc, "RSCN received: rediscovering\n" ); |
153 | fc_disc_restart(disc); |
154 | } else { |
155 | FC_DISC_DBG(disc, "RSCN received: not rediscovering. " |
156 | "redisc %d state %d in_prog %d\n" , |
157 | redisc, lport->state, disc->pending); |
158 | } |
159 | fc_frame_free(fp); |
160 | return; |
161 | reject: |
162 | FC_DISC_DBG(disc, "Received a bad RSCN frame\n" ); |
163 | rjt_data.reason = ELS_RJT_LOGIC; |
164 | rjt_data.explan = ELS_EXPL_NONE; |
165 | fc_seq_els_rsp_send(fp, ELS_LS_RJT, &rjt_data); |
166 | fc_frame_free(fp); |
167 | } |
168 | |
169 | /** |
170 | * fc_disc_recv_req() - Handle incoming requests |
171 | * @lport: The local port receiving the request |
172 | * @fp: The request frame |
173 | * |
174 | * Locking Note: This function is called from the EM and will lock |
175 | * the disc_mutex before calling the handler for the |
176 | * request. |
177 | */ |
178 | static void fc_disc_recv_req(struct fc_lport *lport, struct fc_frame *fp) |
179 | { |
180 | u8 op; |
181 | struct fc_disc *disc = &lport->disc; |
182 | |
183 | op = fc_frame_payload_op(fp); |
184 | switch (op) { |
185 | case ELS_RSCN: |
186 | mutex_lock(&disc->disc_mutex); |
187 | fc_disc_recv_rscn_req(disc, fp); |
188 | mutex_unlock(lock: &disc->disc_mutex); |
189 | break; |
190 | default: |
191 | FC_DISC_DBG(disc, "Received an unsupported request, " |
192 | "the opcode is (%x)\n" , op); |
193 | fc_frame_free(fp); |
194 | break; |
195 | } |
196 | } |
197 | |
198 | /** |
199 | * fc_disc_restart() - Restart discovery |
200 | * @disc: The discovery object to be restarted |
201 | */ |
202 | static void fc_disc_restart(struct fc_disc *disc) |
203 | { |
204 | lockdep_assert_held(&disc->disc_mutex); |
205 | |
206 | if (!disc->disc_callback) |
207 | return; |
208 | |
209 | FC_DISC_DBG(disc, "Restarting discovery\n" ); |
210 | |
211 | disc->requested = 1; |
212 | if (disc->pending) |
213 | return; |
214 | |
215 | /* |
216 | * Advance disc_id. This is an arbitrary non-zero number that will |
217 | * match the value in the fc_rport_priv after discovery for all |
218 | * freshly-discovered remote ports. Avoid wrapping to zero. |
219 | */ |
220 | disc->disc_id = (disc->disc_id + 2) | 1; |
221 | disc->retry_count = 0; |
222 | fc_disc_gpn_ft_req(disc); |
223 | } |
224 | |
225 | /** |
226 | * fc_disc_start() - Start discovery on a local port |
227 | * @lport: The local port to have discovery started on |
228 | * @disc_callback: Callback function to be called when discovery is complete |
229 | */ |
230 | static void fc_disc_start(void (*disc_callback)(struct fc_lport *, |
231 | enum fc_disc_event), |
232 | struct fc_lport *lport) |
233 | { |
234 | struct fc_disc *disc = &lport->disc; |
235 | |
236 | /* |
237 | * At this point we may have a new disc job or an existing |
238 | * one. Either way, let's lock when we make changes to it |
239 | * and send the GPN_FT request. |
240 | */ |
241 | mutex_lock(&disc->disc_mutex); |
242 | disc->disc_callback = disc_callback; |
243 | fc_disc_restart(disc); |
244 | mutex_unlock(lock: &disc->disc_mutex); |
245 | } |
246 | |
247 | /** |
248 | * fc_disc_done() - Discovery has been completed |
249 | * @disc: The discovery context |
250 | * @event: The discovery completion status |
251 | */ |
252 | static void fc_disc_done(struct fc_disc *disc, enum fc_disc_event event) |
253 | { |
254 | struct fc_lport *lport = fc_disc_lport(disc); |
255 | struct fc_rport_priv *rdata; |
256 | |
257 | lockdep_assert_held(&disc->disc_mutex); |
258 | FC_DISC_DBG(disc, "Discovery complete\n" ); |
259 | |
260 | disc->pending = 0; |
261 | if (disc->requested) { |
262 | fc_disc_restart(disc); |
263 | return; |
264 | } |
265 | |
266 | /* |
267 | * Go through all remote ports. If they were found in the latest |
268 | * discovery, reverify or log them in. Otherwise, log them out. |
269 | * Skip ports which were never discovered. These are the dNS port |
270 | * and ports which were created by PLOGI. |
271 | * |
272 | * We don't need to use the _rcu variant here as the rport list |
273 | * is protected by the disc mutex which is already held on entry. |
274 | */ |
275 | list_for_each_entry(rdata, &disc->rports, peers) { |
276 | if (!kref_get_unless_zero(kref: &rdata->kref)) |
277 | continue; |
278 | if (rdata->disc_id) { |
279 | if (rdata->disc_id == disc->disc_id) |
280 | fc_rport_login(rdata); |
281 | else |
282 | fc_rport_logoff(rdata); |
283 | } |
284 | kref_put(kref: &rdata->kref, release: fc_rport_destroy); |
285 | } |
286 | mutex_unlock(lock: &disc->disc_mutex); |
287 | disc->disc_callback(lport, event); |
288 | mutex_lock(&disc->disc_mutex); |
289 | } |
290 | |
291 | /** |
292 | * fc_disc_error() - Handle error on dNS request |
293 | * @disc: The discovery context |
294 | * @fp: The error code encoded as a frame pointer |
295 | */ |
296 | static void fc_disc_error(struct fc_disc *disc, struct fc_frame *fp) |
297 | { |
298 | struct fc_lport *lport = fc_disc_lport(disc); |
299 | unsigned long delay = 0; |
300 | |
301 | FC_DISC_DBG(disc, "Error %d, retries %d/%d\n" , |
302 | PTR_ERR_OR_ZERO(fp), disc->retry_count, |
303 | FC_DISC_RETRY_LIMIT); |
304 | |
305 | if (!fp || PTR_ERR(ptr: fp) == -FC_EX_TIMEOUT) { |
306 | /* |
307 | * Memory allocation failure, or the exchange timed out, |
308 | * retry after delay. |
309 | */ |
310 | if (disc->retry_count < FC_DISC_RETRY_LIMIT) { |
311 | /* go ahead and retry */ |
312 | if (!fp) |
313 | delay = msecs_to_jiffies(FC_DISC_RETRY_DELAY); |
314 | else { |
315 | delay = msecs_to_jiffies(m: lport->e_d_tov); |
316 | |
317 | /* timeout faster first time */ |
318 | if (!disc->retry_count) |
319 | delay /= 4; |
320 | } |
321 | disc->retry_count++; |
322 | schedule_delayed_work(dwork: &disc->disc_work, delay); |
323 | } else |
324 | fc_disc_done(disc, event: DISC_EV_FAILED); |
325 | } else if (PTR_ERR(ptr: fp) == -FC_EX_CLOSED) { |
326 | /* |
327 | * if discovery fails due to lport reset, clear |
328 | * pending flag so that subsequent discovery can |
329 | * continue |
330 | */ |
331 | disc->pending = 0; |
332 | } |
333 | } |
334 | |
335 | /** |
336 | * fc_disc_gpn_ft_req() - Send Get Port Names by FC-4 type (GPN_FT) request |
337 | * @disc: The discovery context |
338 | */ |
339 | static void fc_disc_gpn_ft_req(struct fc_disc *disc) |
340 | { |
341 | struct fc_frame *fp; |
342 | struct fc_lport *lport = fc_disc_lport(disc); |
343 | |
344 | lockdep_assert_held(&disc->disc_mutex); |
345 | |
346 | WARN_ON(!fc_lport_test_ready(lport)); |
347 | |
348 | disc->pending = 1; |
349 | disc->requested = 0; |
350 | |
351 | disc->buf_len = 0; |
352 | disc->seq_count = 0; |
353 | fp = fc_frame_alloc(dev: lport, |
354 | len: sizeof(struct fc_ct_hdr) + |
355 | sizeof(struct fc_ns_gid_ft)); |
356 | if (!fp) |
357 | goto err; |
358 | |
359 | if (lport->tt.elsct_send(lport, 0, fp, |
360 | FC_NS_GPN_FT, |
361 | fc_disc_gpn_ft_resp, |
362 | disc, 3 * lport->r_a_tov)) |
363 | return; |
364 | err: |
365 | fc_disc_error(disc, NULL); |
366 | } |
367 | |
368 | /** |
369 | * fc_disc_gpn_ft_parse() - Parse the body of the dNS GPN_FT response. |
370 | * @disc: The discovery context |
371 | * @buf: The GPN_FT response buffer |
372 | * @len: The size of response buffer |
373 | * |
374 | * Goes through the list of IDs and names resulting from a request. |
375 | */ |
376 | static int fc_disc_gpn_ft_parse(struct fc_disc *disc, void *buf, size_t len) |
377 | { |
378 | struct fc_lport *lport; |
379 | struct fc_gpn_ft_resp *np; |
380 | char *bp; |
381 | size_t plen; |
382 | size_t tlen; |
383 | int error = 0; |
384 | struct fc_rport_identifiers ids; |
385 | struct fc_rport_priv *rdata; |
386 | |
387 | lport = fc_disc_lport(disc); |
388 | disc->seq_count++; |
389 | |
390 | /* |
391 | * Handle partial name record left over from previous call. |
392 | */ |
393 | bp = buf; |
394 | plen = len; |
395 | np = (struct fc_gpn_ft_resp *)bp; |
396 | tlen = disc->buf_len; |
397 | disc->buf_len = 0; |
398 | if (tlen) { |
399 | WARN_ON(tlen >= sizeof(*np)); |
400 | plen = sizeof(*np) - tlen; |
401 | WARN_ON(plen <= 0); |
402 | WARN_ON(plen >= sizeof(*np)); |
403 | if (plen > len) |
404 | plen = len; |
405 | np = &disc->partial_buf; |
406 | memcpy((char *)np + tlen, bp, plen); |
407 | |
408 | /* |
409 | * Set bp so that the loop below will advance it to the |
410 | * first valid full name element. |
411 | */ |
412 | bp -= tlen; |
413 | len += tlen; |
414 | plen += tlen; |
415 | disc->buf_len = (unsigned char) plen; |
416 | if (plen == sizeof(*np)) |
417 | disc->buf_len = 0; |
418 | } |
419 | |
420 | /* |
421 | * Handle full name records, including the one filled from above. |
422 | * Normally, np == bp and plen == len, but from the partial case above, |
423 | * bp, len describe the overall buffer, and np, plen describe the |
424 | * partial buffer, which if would usually be full now. |
425 | * After the first time through the loop, things return to "normal". |
426 | */ |
427 | while (plen >= sizeof(*np)) { |
428 | ids.port_id = ntoh24(p: np->fp_fid); |
429 | ids.port_name = ntohll(np->fp_wwpn); |
430 | |
431 | if (ids.port_id != lport->port_id && |
432 | ids.port_name != lport->wwpn) { |
433 | rdata = fc_rport_create(lport, ids.port_id); |
434 | if (rdata) { |
435 | rdata->ids.port_name = ids.port_name; |
436 | rdata->disc_id = disc->disc_id; |
437 | } else { |
438 | printk(KERN_WARNING "libfc: Failed to allocate " |
439 | "memory for the newly discovered port " |
440 | "(%6.6x)\n" , ids.port_id); |
441 | error = -ENOMEM; |
442 | } |
443 | } |
444 | |
445 | if (np->fp_flags & FC_NS_FID_LAST) { |
446 | fc_disc_done(disc, event: DISC_EV_SUCCESS); |
447 | len = 0; |
448 | break; |
449 | } |
450 | len -= sizeof(*np); |
451 | bp += sizeof(*np); |
452 | np = (struct fc_gpn_ft_resp *)bp; |
453 | plen = len; |
454 | } |
455 | |
456 | /* |
457 | * Save any partial record at the end of the buffer for next time. |
458 | */ |
459 | if (error == 0 && len > 0 && len < sizeof(*np)) { |
460 | if (np != &disc->partial_buf) { |
461 | FC_DISC_DBG(disc, "Partial buffer remains " |
462 | "for discovery\n" ); |
463 | memcpy(&disc->partial_buf, np, len); |
464 | } |
465 | disc->buf_len = (unsigned char) len; |
466 | } |
467 | return error; |
468 | } |
469 | |
470 | /** |
471 | * fc_disc_timeout() - Handler for discovery timeouts |
472 | * @work: Structure holding discovery context that needs to retry discovery |
473 | */ |
474 | static void fc_disc_timeout(struct work_struct *work) |
475 | { |
476 | struct fc_disc *disc = container_of(work, |
477 | struct fc_disc, |
478 | disc_work.work); |
479 | mutex_lock(&disc->disc_mutex); |
480 | fc_disc_gpn_ft_req(disc); |
481 | mutex_unlock(lock: &disc->disc_mutex); |
482 | } |
483 | |
484 | /** |
485 | * fc_disc_gpn_ft_resp() - Handle a response frame from Get Port Names (GPN_FT) |
486 | * @sp: The sequence that the GPN_FT response was received on |
487 | * @fp: The GPN_FT response frame |
488 | * @disc_arg: The discovery context |
489 | * |
490 | * Locking Note: This function is called without disc mutex held, and |
491 | * should do all its processing with the mutex held |
492 | */ |
493 | static void fc_disc_gpn_ft_resp(struct fc_seq *sp, struct fc_frame *fp, |
494 | void *disc_arg) |
495 | { |
496 | struct fc_disc *disc = disc_arg; |
497 | struct fc_ct_hdr *cp; |
498 | struct fc_frame_header *fh; |
499 | enum fc_disc_event event = DISC_EV_NONE; |
500 | unsigned int seq_cnt; |
501 | unsigned int len; |
502 | int error = 0; |
503 | |
504 | mutex_lock(&disc->disc_mutex); |
505 | FC_DISC_DBG(disc, "Received a GPN_FT response\n" ); |
506 | |
507 | if (IS_ERR(ptr: fp)) { |
508 | fc_disc_error(disc, fp); |
509 | mutex_unlock(lock: &disc->disc_mutex); |
510 | return; |
511 | } |
512 | |
513 | WARN_ON(!fc_frame_is_linear(fp)); /* buffer must be contiguous */ |
514 | fh = fc_frame_header_get(fp); |
515 | len = fr_len(fp) - sizeof(*fh); |
516 | seq_cnt = ntohs(fh->fh_seq_cnt); |
517 | if (fr_sof(fp) == FC_SOF_I3 && seq_cnt == 0 && disc->seq_count == 0) { |
518 | cp = fc_frame_payload_get(fp, len: sizeof(*cp)); |
519 | if (!cp) { |
520 | FC_DISC_DBG(disc, "GPN_FT response too short, len %d\n" , |
521 | fr_len(fp)); |
522 | event = DISC_EV_FAILED; |
523 | } else if (ntohs(cp->ct_cmd) == FC_FS_ACC) { |
524 | |
525 | /* Accepted, parse the response. */ |
526 | len -= sizeof(*cp); |
527 | error = fc_disc_gpn_ft_parse(disc, buf: cp + 1, len); |
528 | } else if (ntohs(cp->ct_cmd) == FC_FS_RJT) { |
529 | FC_DISC_DBG(disc, "GPN_FT rejected reason %x exp %x " |
530 | "(check zoning)\n" , cp->ct_reason, |
531 | cp->ct_explan); |
532 | event = DISC_EV_FAILED; |
533 | if (cp->ct_reason == FC_FS_RJT_UNABL && |
534 | cp->ct_explan == FC_FS_EXP_FTNR) |
535 | event = DISC_EV_SUCCESS; |
536 | } else { |
537 | FC_DISC_DBG(disc, "GPN_FT unexpected response code " |
538 | "%x\n" , ntohs(cp->ct_cmd)); |
539 | event = DISC_EV_FAILED; |
540 | } |
541 | } else if (fr_sof(fp) == FC_SOF_N3 && seq_cnt == disc->seq_count) { |
542 | error = fc_disc_gpn_ft_parse(disc, buf: fh + 1, len); |
543 | } else { |
544 | FC_DISC_DBG(disc, "GPN_FT unexpected frame - out of sequence? " |
545 | "seq_cnt %x expected %x sof %x eof %x\n" , |
546 | seq_cnt, disc->seq_count, fr_sof(fp), fr_eof(fp)); |
547 | event = DISC_EV_FAILED; |
548 | } |
549 | if (error) |
550 | fc_disc_error(disc, fp: ERR_PTR(error)); |
551 | else if (event != DISC_EV_NONE) |
552 | fc_disc_done(disc, event); |
553 | fc_frame_free(fp); |
554 | mutex_unlock(lock: &disc->disc_mutex); |
555 | } |
556 | |
557 | /** |
558 | * fc_disc_gpn_id_resp() - Handle a response frame from Get Port Names (GPN_ID) |
559 | * @sp: The sequence the GPN_ID is on |
560 | * @fp: The response frame |
561 | * @rdata_arg: The remote port that sent the GPN_ID response |
562 | * |
563 | * Locking Note: This function is called without disc mutex held. |
564 | */ |
565 | static void fc_disc_gpn_id_resp(struct fc_seq *sp, struct fc_frame *fp, |
566 | void *rdata_arg) |
567 | { |
568 | struct fc_rport_priv *rdata = rdata_arg; |
569 | struct fc_rport_priv *new_rdata; |
570 | struct fc_lport *lport; |
571 | struct fc_disc *disc; |
572 | struct fc_ct_hdr *cp; |
573 | struct fc_ns_gid_pn *pn; |
574 | u64 port_name; |
575 | |
576 | lport = rdata->local_port; |
577 | disc = &lport->disc; |
578 | |
579 | if (PTR_ERR(ptr: fp) == -FC_EX_CLOSED) |
580 | goto out; |
581 | if (IS_ERR(ptr: fp)) { |
582 | mutex_lock(&disc->disc_mutex); |
583 | fc_disc_restart(disc); |
584 | mutex_unlock(lock: &disc->disc_mutex); |
585 | goto out; |
586 | } |
587 | |
588 | cp = fc_frame_payload_get(fp, len: sizeof(*cp)); |
589 | if (!cp) |
590 | goto redisc; |
591 | if (ntohs(cp->ct_cmd) == FC_FS_ACC) { |
592 | if (fr_len(fp) < sizeof(struct fc_frame_header) + |
593 | sizeof(*cp) + sizeof(*pn)) |
594 | goto redisc; |
595 | pn = (struct fc_ns_gid_pn *)(cp + 1); |
596 | port_name = get_unaligned_be64(p: &pn->fn_wwpn); |
597 | mutex_lock(&rdata->rp_mutex); |
598 | if (rdata->ids.port_name == -1) |
599 | rdata->ids.port_name = port_name; |
600 | else if (rdata->ids.port_name != port_name) { |
601 | FC_DISC_DBG(disc, "GPN_ID accepted. WWPN changed. " |
602 | "Port-id %6.6x wwpn %16.16llx\n" , |
603 | rdata->ids.port_id, port_name); |
604 | mutex_unlock(lock: &rdata->rp_mutex); |
605 | fc_rport_logoff(rdata); |
606 | mutex_lock(&lport->disc.disc_mutex); |
607 | new_rdata = fc_rport_create(lport, rdata->ids.port_id); |
608 | mutex_unlock(lock: &lport->disc.disc_mutex); |
609 | if (new_rdata) { |
610 | new_rdata->disc_id = disc->disc_id; |
611 | fc_rport_login(rdata: new_rdata); |
612 | } |
613 | goto free_fp; |
614 | } |
615 | rdata->disc_id = disc->disc_id; |
616 | mutex_unlock(lock: &rdata->rp_mutex); |
617 | fc_rport_login(rdata); |
618 | } else if (ntohs(cp->ct_cmd) == FC_FS_RJT) { |
619 | FC_DISC_DBG(disc, "GPN_ID rejected reason %x exp %x\n" , |
620 | cp->ct_reason, cp->ct_explan); |
621 | fc_rport_logoff(rdata); |
622 | } else { |
623 | FC_DISC_DBG(disc, "GPN_ID unexpected response code %x\n" , |
624 | ntohs(cp->ct_cmd)); |
625 | redisc: |
626 | mutex_lock(&disc->disc_mutex); |
627 | fc_disc_restart(disc); |
628 | mutex_unlock(lock: &disc->disc_mutex); |
629 | } |
630 | free_fp: |
631 | fc_frame_free(fp); |
632 | out: |
633 | kref_put(kref: &rdata->kref, release: fc_rport_destroy); |
634 | } |
635 | |
636 | /** |
637 | * fc_disc_gpn_id_req() - Send Get Port Names by ID (GPN_ID) request |
638 | * @lport: The local port to initiate discovery on |
639 | * @rdata: remote port private data |
640 | * |
641 | * On failure, an error code is returned. |
642 | */ |
643 | static int fc_disc_gpn_id_req(struct fc_lport *lport, |
644 | struct fc_rport_priv *rdata) |
645 | { |
646 | struct fc_frame *fp; |
647 | |
648 | lockdep_assert_held(&lport->disc.disc_mutex); |
649 | fp = fc_frame_alloc(dev: lport, len: sizeof(struct fc_ct_hdr) + |
650 | sizeof(struct fc_ns_fid)); |
651 | if (!fp) |
652 | return -ENOMEM; |
653 | if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, FC_NS_GPN_ID, |
654 | fc_disc_gpn_id_resp, rdata, |
655 | 3 * lport->r_a_tov)) |
656 | return -ENOMEM; |
657 | kref_get(kref: &rdata->kref); |
658 | return 0; |
659 | } |
660 | |
661 | /** |
662 | * fc_disc_single() - Discover the directory information for a single target |
663 | * @lport: The local port the remote port is associated with |
664 | * @dp: The port to rediscover |
665 | */ |
666 | static int fc_disc_single(struct fc_lport *lport, struct fc_disc_port *dp) |
667 | { |
668 | struct fc_rport_priv *rdata; |
669 | |
670 | lockdep_assert_held(&lport->disc.disc_mutex); |
671 | |
672 | rdata = fc_rport_create(lport, dp->port_id); |
673 | if (!rdata) |
674 | return -ENOMEM; |
675 | rdata->disc_id = 0; |
676 | return fc_disc_gpn_id_req(lport, rdata); |
677 | } |
678 | |
679 | /** |
680 | * fc_disc_stop() - Stop discovery for a given lport |
681 | * @lport: The local port that discovery should stop on |
682 | */ |
683 | static void fc_disc_stop(struct fc_lport *lport) |
684 | { |
685 | struct fc_disc *disc = &lport->disc; |
686 | |
687 | if (disc->pending) |
688 | cancel_delayed_work_sync(dwork: &disc->disc_work); |
689 | mutex_lock(&disc->disc_mutex); |
690 | fc_disc_stop_rports(disc); |
691 | mutex_unlock(lock: &disc->disc_mutex); |
692 | } |
693 | |
694 | /** |
695 | * fc_disc_stop_final() - Stop discovery for a given lport |
696 | * @lport: The lport that discovery should stop on |
697 | * |
698 | * This function will block until discovery has been |
699 | * completely stopped and all rports have been deleted. |
700 | */ |
701 | static void fc_disc_stop_final(struct fc_lport *lport) |
702 | { |
703 | fc_disc_stop(lport); |
704 | fc_rport_flush_queue(); |
705 | } |
706 | |
707 | /** |
708 | * fc_disc_config() - Configure the discovery layer for a local port |
709 | * @lport: The local port that needs the discovery layer to be configured |
710 | * @priv: Private data structre for users of the discovery layer |
711 | */ |
712 | void fc_disc_config(struct fc_lport *lport, void *priv) |
713 | { |
714 | struct fc_disc *disc; |
715 | |
716 | if (!lport->tt.disc_start) |
717 | lport->tt.disc_start = fc_disc_start; |
718 | |
719 | if (!lport->tt.disc_stop) |
720 | lport->tt.disc_stop = fc_disc_stop; |
721 | |
722 | if (!lport->tt.disc_stop_final) |
723 | lport->tt.disc_stop_final = fc_disc_stop_final; |
724 | |
725 | if (!lport->tt.disc_recv_req) |
726 | lport->tt.disc_recv_req = fc_disc_recv_req; |
727 | |
728 | disc = &lport->disc; |
729 | |
730 | disc->priv = priv; |
731 | } |
732 | EXPORT_SYMBOL(fc_disc_config); |
733 | |
734 | /** |
735 | * fc_disc_init() - Initialize the discovery layer for a local port |
736 | * @lport: The local port that needs the discovery layer to be initialized |
737 | */ |
738 | void fc_disc_init(struct fc_lport *lport) |
739 | { |
740 | struct fc_disc *disc = &lport->disc; |
741 | |
742 | INIT_DELAYED_WORK(&disc->disc_work, fc_disc_timeout); |
743 | mutex_init(&disc->disc_mutex); |
744 | INIT_LIST_HEAD(list: &disc->rports); |
745 | } |
746 | EXPORT_SYMBOL(fc_disc_init); |
747 | |