1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at src/sun_nws/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at src/sun_nws/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 #pragma ident "@(#)iscsit_tgt.c 1.3 08/03/28 SMI"
27
28 #include <sys/cpuvar.h>
29 #include <sys/types.h>
30 #include <sys/conf.h>
31 #include <sys/stat.h>
32 #include <sys/file.h>
33 #include <sys/ddi.h>
34 #include <sys/sunddi.h>
35 #include <sys/modctl.h>
36 #include <sys/sysmacros.h>
37
38 #include <sys/socket.h> /* networking stuff */
39 #include <sys/strsubr.h> /* networking stuff */
40
41 #include <stmf.h>
42 #include <stmf_ioctl.h>
43 #include <portif.h>
44 #include <idm.h>
45 #include <iscsit.h>
46
47 static int
48 iscsit_tpgt_avl_compare(const void *void_tpgt1, const void *void_tpgt2);
49
50 iscsit_tgt_t *
51 iscsit_tgt_lookup(char *target_name)
52 {
53 iscsit_tgt_t tmp_tgt;
54
55 /*
56 * Use a dummy target for lookup, filling in all fields used in AVL
57 * comparison.
58 */
59 tmp_tgt.target_name = target_name;
60 return (avl_find(&iscsit_global.global_target_list, &tmp_tgt, NULL));
61 }
62
63 iscsit_tgt_t *
64 iscsit_tgt_create(char *target_name)
65 {
66 iscsit_tgt_t *result;
67 stmf_local_port_t *lport;
68 idm_svc_req_t sr;
69 idm_svc_t *svc;
70
71 /*
72 * Each target is an STMF local port.
73 */
74
75 lport = stmf_alloc(STMF_STRUCT_STMF_LOCAL_PORT,
76 sizeof (iscsit_tgt_t) + sizeof (scsi_devid_desc_t) +
77 strlen(target_name) + 1, 0);
78 if (lport == NULL) {
79 return (NULL);
80 }
81
82 result = lport->lport_port_private;
83 result->target_stmf_lport = lport;
84 result->target_stmf_state = STMF_STATE_OFFLINE;
85 result->target_stmf_state_not_acked = 0;
86 /* Use pointer arithmetic to find scsi_devid_desc_t */
87 result->target_devid = (scsi_devid_desc_t *)(result + 1);
88 strcpy((char *)result->target_devid->ident, target_name);
89 result->target_devid->ident_length = strlen(target_name);
90 result->target_devid->protocol_id = PROTOCOL_iSCSI;
91 result->target_devid->piv = 1;
92 result->target_devid->code_set = CODE_SET_ASCII;
93 result->target_devid->association = ID_IS_TARGET_PORT;
94
95 /* Store a shortcut to the target name */
96 result->target_name = (char *)result->target_devid->ident;
97 mutex_init(&result->target_mutex, NULL, MUTEX_DEFAULT, NULL);
98 avl_create(&result->target_sess_list, iscsit_sess_avl_compare,
99 sizeof (iscsit_sess_t), offsetof(iscsit_sess_t, ist_tgt_ln));
100 avl_create(&result->target_tpgt_list, iscsit_tpgt_avl_compare,
101 sizeof (iscsit_tpgt_t), offsetof(iscsit_tpgt_t, tpgt_tgt_ln));
102
103 /* Finish initializing local port */
104 lport->lport_id = result->target_devid;
105 lport->lport_pp = iscsit_global.global_pp;
106 /*
107 * XXX Confirm that multiple local ports can share a single dbuf
108 * store. Also confirm that there is no penalty for using a
109 * global dbuf store assuming none of the buffers will be cached.
110 */
111 lport->lport_ds = iscsit_global.global_dbuf_store;
112 lport->lport_xfer_data = &iscsit_xfer_scsi_data;
113 lport->lport_send_status = &iscsit_send_scsi_status;
114 lport->lport_task_free = &iscsit_lport_task_free;
115 lport->lport_abort = &iscsit_abort;
116 lport->lport_ctl = &iscsit_ctl;
117
118 /* Start with default TPG. */
119 iscsit_tgt_bind_tpgt(result, iscsit_global.global_default_tpg,
120 ISCSIT_DEFAULT_TPGT);
121
122 /*
123 * Don't register the target with STMF until we have all the
124 * TPGT bindings an any other additional config setup. STMF
125 * may immediately ask us to go online.
126 *
127 * Make sure the target doesn't already exist. It might make
128 * more sense to do this above but we'd like to hold the
129 * lock across the lookup and insert operations.
130 */
131 mutex_enter(&iscsit_global.global_mutex);
132 if (iscsit_tgt_lookup(target_name) == NULL) {
133 avl_add(&iscsit_global.global_target_list, result);
134 } else {
135 /* Target exists */
136 stmf_free(lport);
137 result = NULL;
138 }
139 mutex_exit(&iscsit_global.global_mutex);
140
141 return (result);
142 }
143
144 void
145 iscsit_tgt_destroy(iscsit_tgt_t *tgt)
146 {
147 ASSERT(tgt->target_stmf_state != STMF_STATE_ONLINE);
148 avl_destroy(&tgt->target_tpgt_list);
149 avl_destroy(&tgt->target_sess_list);
150 mutex_destroy(&tgt->target_mutex);
151 stmf_free(tgt->target_stmf_lport); /* Also frees "tgt' */
152 }
153
154 int
155 iscsit_tgt_avl_compare(const void *void_tgt1, const void *void_tgt2)
156 {
157 const iscsit_tgt_t *tgt1 = void_tgt1;
158 const iscsit_tgt_t *tgt2 = void_tgt2;
159 int result;
160
161 /*
162 * Sort by ISID first then TSIH
163 */
164 result = strcmp(tgt1->target_name, tgt2->target_name);
165 if (result < 0) {
166 return (-1);
167 } else if (result > 0) {
168 return (1);
169 }
170
171 return (0);
172 }
173
174
175 iscsit_tpgt_t *
176 iscsit_tgt_lookup_tpgt(iscsit_tgt_t *tgt, uint16_t tag)
177 {
178 iscsit_tpgt_t tmp_tpgt;
179
180 /* Caller holds tgt->target_mutex */
181 tmp_tpgt.tpgt_tag = tag;
182 return (avl_find(&tgt->target_tpgt_list, &tmp_tpgt, NULL));
183 }
184
185 iscsit_portal_t *
186 iscsit_tgt_lookup_portal(iscsit_tgt_t *tgt, struct sockaddr_storage *sa,
187 uint16_t port)
188 {
189 iscsit_tpgt_t *tpgt;
190 iscsit_portal_t *portal;
191
192 /* Caller holds tgt->target_mutex */
193 for (tpgt = avl_first(&tgt->target_tpgt_list);
194 tpgt != NULL;
195 tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) {
196 portal = iscsit_portal_lookup(tpgt->tpgt_tpg, sa, port);
197 if (portal) {
198 return (portal);
199 }
200 }
201
202 return (NULL);
203 }
204
205
206 void
207 iscsit_tgt_bind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess)
208 {
209 if (tgt) {
210 sess->ist_lport = tgt->target_stmf_lport;
211 mutex_enter(&tgt->target_mutex);
212 avl_add(&tgt->target_sess_list, sess);
213 mutex_exit(&tgt->target_mutex);
214 } else {
215 /* Discovery session */
216 sess->ist_lport = NULL;
217 ISCSIT_GLOBAL_LOCK();
218 avl_add(&iscsit_global.global_discovery_sessions, sess);
219 ISCSIT_GLOBAL_UNLOCK();
220 }
221 }
222
223 void
224 iscsit_tgt_unbind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess)
225 {
226 if (tgt) {
227 mutex_enter(&tgt->target_mutex);
228 avl_remove(&tgt->target_sess_list, sess);
229 mutex_exit(&tgt->target_mutex);
230 } else {
231 /* Discovery session */
232 ISCSIT_GLOBAL_LOCK();
233 avl_remove(&iscsit_global.global_discovery_sessions, sess);
234 ISCSIT_GLOBAL_UNLOCK();
235 }
236 }
237
238 iscsit_sess_t *
239 iscsit_tgt_lookup_sess(iscsit_tgt_t *tgt, char *initiator_name,
240 uint8_t *isid, uint16_t tsih)
241 {
242 iscsit_sess_t tmp_sess;
243 avl_tree_t *sess_avl;
244 avl_index_t where;
245 iscsit_sess_t *result;
246
247 /*
248 * If tgt is NULL then we are looking for a discovery session
249 */
250 if (tgt == NULL) {
251 sess_avl = &iscsit_global.global_discovery_sessions;
252 } else {
253 sess_avl = &tgt->target_sess_list;
254 }
255
256 if (avl_numnodes(sess_avl) == NULL) {
257 return (NULL);
258 }
259
260 /*
261 * We'll try to find a session matching ISID + TSIH first. If we
262 * can't find one then we will return the closest match. If the
263 * caller needs an exact match it must compare the TSIH after
264 * the session is returned.
265 */
266 bcopy(isid, tmp_sess.ist_isid, ISCSI_ISID_LEN);
267 tmp_sess.ist_initiator_name = initiator_name;
268 tmp_sess.ist_tsih = tsih;
269
270 result = avl_find(sess_avl, &tmp_sess, &where);
271 if (result != NULL) {
272 return (result);
273 }
274
275 /*
276 * avl_find_nearest() may return a result with a different ISID so
277 * we should only return a result if the name and ISID match
278 */
279 result = avl_nearest(sess_avl, where, AVL_BEFORE);
280 if ((result != NULL) &&
281 (strcmp(result->ist_initiator_name, initiator_name) == 0) &&
282 (memcmp(result->ist_isid, isid, ISCSI_ISID_LEN) == 0)) {
283 return (result);
284 }
285
286 result = avl_nearest(sess_avl, where, AVL_AFTER);
287 if ((result != NULL) &&
288 (strcmp(result->ist_initiator_name, initiator_name) == 0) &&
289 (memcmp(result->ist_isid, isid, ISCSI_ISID_LEN) == 0)) {
290 return (result);
291 }
292
293 return (NULL);
294 }
295
296 idm_status_t
297 iscsit_tgt_bind_tpgt(iscsit_tgt_t *tgt, iscsit_tpg_t *tpg, uint16_t tag)
298 {
299 iscsit_tpgt_t *tpgt, *first_tpgt;
300
301 tpgt = kmem_zalloc(sizeof (*tpgt), KM_SLEEP);
302
303 tpgt->tpgt_tpg = tpg;
304 tpgt->tpgt_tag = tag;
305
306 mutex_enter(&tgt->target_mutex);
307
308 first_tpgt = avl_first(&tgt->target_tpgt_list);
309 if (first_tpgt != NULL) {
310 /*
311 * Compare the requested TPG against the global default TPG.
312 * The global default TPG can only be bound if the TPGT list
313 * is empty
314 */
315 if (tpg == iscsit_global.global_default_tpg) {
316 mutex_exit(&tgt->target_mutex);
317 kmem_free(tpgt, sizeof (*tpgt));
318 return (IDM_STATUS_FAIL);
319 }
320
321 /*
322 * Otherwise if the TPGT list contains the default TPG
323 * we need to remove it. The default TPG is only for targets
324 * with no TPGTs configured.
325 */
326 if (first_tpgt->tpgt_tpg == iscsit_global.global_default_tpg) {
327 iscsit_tgt_unbind_tpgt(tgt, tpgt);
328 ASSERT(avl_numnodes(&tgt->target_tpgt_list) == 0);
329 }
330 }
331
332 /*
333 * Fail request if this is a duplicate tag
334 */
335 if (iscsit_tgt_lookup_tpgt(tgt, tag) == NULL) {
336 /* New tag */
337 avl_add(&tgt->target_tpgt_list, tpgt);
338 } else {
339 mutex_exit(&tgt->target_mutex);
340 kmem_free(tpgt, sizeof (*tpgt));
341 return (IDM_STATUS_FAIL);
342 }
343
344 if (tgt->target_stmf_state == STMF_STATE_ONLINE) {
345 iscsit_tpg_online(tpg); /* XXX OK to hold target lock? */
346 }
347 mutex_exit(&tgt->target_mutex);
348
349 return (IDM_STATUS_SUCCESS);
350 }
351
352 void
353 iscsit_tgt_unbind_tpgt(iscsit_tgt_t *tgt, iscsit_tpgt_t *tpgt)
354 {
355 /* Caller holds target mutex */
356 if (tgt->target_stmf_state == STMF_STATE_ONLINE) {
357 /* XXX OK to hold target lock? */
358 iscsit_tpg_offline(tpgt->tpgt_tpg);
359 }
360 avl_remove(&tgt->target_tpgt_list, tpgt);
361 }
362
363 static int
364 iscsit_tpgt_avl_compare(const void *void_tpgt1, const void *void_tpgt2)
365 {
366 const iscsit_tpgt_t *tpgt1 = void_tpgt1;
367 const iscsit_tpgt_t *tpgt2 = void_tpgt2;
368
369 if (tpgt1->tpgt_tag < tpgt2->tpgt_tag)
370 return (-1);
371 else if (tpgt1->tpgt_tag > tpgt2->tpgt_tag)
372 return (1);
373
374 return (0);
375 }
376
377
378
379 idm_status_t
380 iscsit_tgt_online(iscsit_tgt_t *tgt)
381 {
382 iscsit_tpgt_t *tpgt, *tpgt_fail;
383 idm_status_t rc;
384
385 mutex_enter(&tgt->target_mutex);
386 for (tpgt = avl_first(&tgt->target_tpgt_list);
387 tpgt != NULL;
388 tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) {
389 rc = iscsit_tpg_online(tpgt->tpgt_tpg);
390 if (rc != IDM_STATUS_SUCCESS) {
391 tpgt_fail = tpgt;
392 goto tgt_online_fail;
393 }
394 }
395
396 tgt->target_stmf_state = STMF_STATE_ONLINE;
397 mutex_exit(&tgt->target_mutex);
398
399 return (IDM_STATUS_SUCCESS);
400
401 tgt_online_fail:
402 /* Offline all the tpgs we successfully onlined up to the failure */
403 for (tpgt = avl_first(&tgt->target_tpgt_list);
404 tpgt != tpgt_fail;
405 tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) {
406 iscsit_tpg_offline(tpgt->tpgt_tpg);
407 }
408 mutex_exit(&tgt->target_mutex);
409 return (rc);
410 }
411
412 void
413 iscsit_tgt_offline(iscsit_tgt_t *tgt)
414 {
415 iscsit_tpgt_t *tpgt;
416
417 mutex_enter(&tgt->target_mutex);
418 tgt->target_stmf_state = STMF_STATE_OFFLINE;
419 for (tpgt = avl_first(&tgt->target_tpgt_list);
420 tpgt != NULL;
421 tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) {
422 iscsit_tpg_offline(tpgt->tpgt_tpg);
423 }
424 mutex_exit(&tgt->target_mutex);
425 }
426
427 iscsit_tpg_t *
428 iscsit_tpg_lookup(char *tpg_name)
429 {
430 iscsit_tpg_t tmp_tpg;
431
432 tmp_tpg.tpg_name = tpg_name;
433 return (avl_find(&iscsit_global.global_tpg_list, &tmp_tpg, NULL));
434 }
435
436
437 iscsit_tpg_t *
438 iscsit_tpg_create(char *tpg_name)
439 {
440 iscsit_tpg_t *tpg;
441
442 tpg = kmem_zalloc(sizeof (*tpg), KM_SLEEP);
443
444 tpg->tpg_name = kmem_alloc(strlen(tpg_name) + 1, KM_SLEEP);
445 strcpy(tpg->tpg_name, tpg_name);
446 avl_create(&tpg->tpg_portal_list, iscsit_tpg_avl_compare,
447 sizeof (iscsit_portal_t), offsetof(iscsit_portal_t, portal_tpg_ln));
448
449 if (strcmp(tpg_name, ISCSIT_DEFAULT_TPG) != 0) {
450 /* Not default TPG, add to global TPG list */
451 ISCSIT_GLOBAL_LOCK();
452 avl_add(&iscsit_global.global_tpg_list, tpg);
453 ISCSIT_GLOBAL_UNLOCK();
454 }
455
456 return (tpg);
457 }
458
459 void
460 iscsit_tpg_destroy(iscsit_tpg_t *tpg)
461 {
462 /* Caller hold global mutex */
463 if (strcmp(tpg->tpg_name, ISCSIT_DEFAULT_TPG) != 0) {
464 avl_remove(&iscsit_global.global_tpg_list, tpg);
465 }
466
467 avl_destroy(&tpg->tpg_portal_list);
468 kmem_free(tpg->tpg_name, strlen(tpg->tpg_name) + 1);
469 kmem_free(tpg, sizeof (*tpg));
470 }
471
472 iscsit_tpg_t *
473 iscsit_tpg_createdefault()
474 {
475 iscsit_tpg_t *tpg;
476
477 tpg = iscsit_tpg_create(ISCSIT_DEFAULT_TPG);
478 if (tpg != NULL) {
479 /* Now create default portal */
480 if (iscsit_portal_create(tpg, NULL, ISCSI_LISTEN_PORT) ==
481 NULL) {
482 iscsit_tpg_destroy(tpg);
483 }
484 }
485
486 return (tpg);
487 }
488
489 int
490 iscsit_tpg_avl_compare(const void *void_tpg1, const void *void_tpg2)
491 {
492 const iscsit_tpg_t *tpg1 = void_tpg1;
493 const iscsit_tpg_t *tpg2 = void_tpg2;
494 int result;
495
496 /*
497 * Sort by ISID first then TSIH
498 */
499 result = strcmp(tpg1->tpg_name, tpg2->tpg_name);
500 if (result < 0) {
501 return (-1);
502 } else if (result > 0) {
503 return (1);
504 }
505
506 return (0);
507 }
508
509 idm_status_t
510 iscsit_tpg_online(iscsit_tpg_t *tpg)
511 {
512 iscsit_portal_t *portal, *portal_fail;
513 idm_status_t rc;
514
515 mutex_enter(&tpg->tpg_mutex);
516 if (tpg->tpg_online == 0) {
517 for (portal = avl_first(&tpg->tpg_portal_list);
518 portal != NULL;
519 portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) {
520 rc = iscsit_portal_online(portal);
521 if (rc != IDM_STATUS_SUCCESS) {
522 portal_fail = portal;
523 goto tpg_online_fail;
524 }
525 }
526 }
527 tpg->tpg_online++;
528
529 mutex_exit(&tpg->tpg_mutex);
530 return (IDM_STATUS_SUCCESS);
531
532 tpg_online_fail:
533 /* Offline all the portals we successfully onlined up to the failure */
534 for (portal = avl_first(&tpg->tpg_portal_list);
535 portal != portal_fail;
536 portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) {
537 iscsit_portal_offline(portal);
538 }
539 mutex_exit(&tpg->tpg_mutex);
540 return (rc);
541 }
542
543 void
544 iscsit_tpg_offline(iscsit_tpg_t *tpg)
545 {
546 iscsit_portal_t *portal;
547
548 mutex_enter(&tpg->tpg_mutex);
549 tpg->tpg_online--;
550 if (tpg->tpg_online == 0) {
551 for (portal = avl_first(&tpg->tpg_portal_list);
552 portal != NULL;
553 portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) {
554 iscsit_portal_offline(portal);
555 }
556 }
557 mutex_exit(&tpg->tpg_mutex);
558 }
559
560
561 iscsit_portal_t *
562 iscsit_portal_lookup(iscsit_tpg_t *tpg, struct sockaddr_storage *sa,
563 uint16_t port)
564 {
565 iscsit_portal_t tmp_portal;
566
567 /* Caller holds tpg->tpg_mutex */
568 bcopy(sa, &tmp_portal.portal_addr, sizeof (*sa));
569 tmp_portal.portal_port = port;
570
571 return (avl_find(&tpg->tpg_portal_list, &tmp_portal, NULL));
572 }
573
574
575 iscsit_portal_t *
576 iscsit_portal_create(iscsit_tpg_t *tpg, struct sockaddr_storage *sa,
577 uint16_t port)
578 {
579 iscsit_portal_t *portal;
580 idm_svc_t *svc;
581 idm_svc_req_t sr;
582 idm_status_t rc;
583
584 portal = kmem_zalloc(sizeof (*portal), KM_SLEEP);
585 /*
586 * If (sa == NULL) && port == ISCSI_LISTEN_PORT then we are being
587 * asked to create the default portal -- targets will use this
588 * portal when no portals are explicitly configured.
589 */
590 if (sa != NULL || port != ISCSI_LISTEN_PORT) {
591 bcopy(sa, &portal->portal_addr,
592 sizeof (struct sockaddr_storage));
593 }
594 portal->portal_port = port;
595
596 /*
597 * If there is no existing IDM service instance for this port, create
598 * one.
599 */
600 if ((svc = idm_tgt_svc_lookup(port)) == NULL) {
601 sr.sr_port = port;
602 sr.sr_conn_ops.icb_rx_scsi_cmd = &iscsit_op_scsi_cmd;
603 sr.sr_conn_ops.icb_rx_scsi_rsp = NULL;
604 sr.sr_conn_ops.icb_rx_misc = &iscsit_rx_pdu;
605 sr.sr_conn_ops.icb_rx_error = &iscsit_rx_pdu_error;
606 sr.sr_conn_ops.icb_client_notify =
607 &iscsit_client_notify;
608 sr.sr_conn_ops.icb_build_hdr = &iscsit_build_hdr;
609
610 if ((rc = idm_tgt_svc_create(&sr, &svc)) !=
611 IDM_STATUS_SUCCESS) {
612 kmem_free(portal, sizeof (*portal));
613 return (NULL);
614 }
615 }
616 portal->portal_svc = svc;
617
618 /*
619 * If this is not the default portal (sa != NULL) then make sure
620 * this portal isn't already defined on this TPG.
621 */
622 mutex_enter(&tpg->tpg_mutex);
623 if ((sa != NULL) && (iscsit_portal_lookup(tpg, sa, port) != NULL)) {
624 kmem_free(portal, sizeof (*portal));
625 idm_tgt_svc_destroy(svc);
626 return (NULL);
627 } else {
628 avl_add(&tpg->tpg_portal_list, portal);
629 }
630 mutex_exit(&tpg->tpg_mutex);
631
632 return (portal);
633 }
634
635 void
636 iscsit_portal_destroy(iscsit_portal_t *portal)
637 {
638 /* Caller holds parent TPG mutex */
639 avl_remove(&portal->portal_tpg->tpg_portal_list, portal);
640 kmem_free(portal, sizeof (*portal));
641 }
642
643 idm_status_t
644 iscsit_portal_online(iscsit_portal_t *portal)
645 {
646 idm_status_t rc;
647
648 /* Caller holds parent TPG mutex */
649 if (portal->portal_online == 0) {
650 if ((rc = idm_tgt_svc_online(portal->portal_svc)) !=
651 IDM_STATUS_SUCCESS) {
652 return (IDM_STATUS_FAIL);
653 }
654 }
655
656 portal->portal_online++;
657
658 return (rc);
659 }
660
661 void
662 iscsit_portal_offline(iscsit_portal_t *portal)
663 {
664 portal->portal_online--;
665
666 if (portal->portal_online == 0) {
667 idm_tgt_svc_offline(portal->portal_svc);
668 }
669 }