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 }