/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at src/sun_nws/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at src/sun_nws/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "@(#)iscsit_tgt.c 1.3 08/03/28 SMI" #include #include #include #include #include #include #include #include #include #include /* networking stuff */ #include /* networking stuff */ #include #include #include #include #include static int iscsit_tpgt_avl_compare(const void *void_tpgt1, const void *void_tpgt2); iscsit_tgt_t * iscsit_tgt_lookup(char *target_name) { iscsit_tgt_t tmp_tgt; /* * Use a dummy target for lookup, filling in all fields used in AVL * comparison. */ tmp_tgt.target_name = target_name; return (avl_find(&iscsit_global.global_target_list, &tmp_tgt, NULL)); } iscsit_tgt_t * iscsit_tgt_create(char *target_name) { iscsit_tgt_t *result; stmf_local_port_t *lport; idm_svc_req_t sr; idm_svc_t *svc; /* * Each target is an STMF local port. */ lport = stmf_alloc(STMF_STRUCT_STMF_LOCAL_PORT, sizeof (iscsit_tgt_t) + sizeof (scsi_devid_desc_t) + strlen(target_name) + 1, 0); if (lport == NULL) { return (NULL); } result = lport->lport_port_private; result->target_stmf_lport = lport; result->target_stmf_state = STMF_STATE_OFFLINE; result->target_stmf_state_not_acked = 0; /* Use pointer arithmetic to find scsi_devid_desc_t */ result->target_devid = (scsi_devid_desc_t *)(result + 1); strcpy((char *)result->target_devid->ident, target_name); result->target_devid->ident_length = strlen(target_name); result->target_devid->protocol_id = PROTOCOL_iSCSI; result->target_devid->piv = 1; result->target_devid->code_set = CODE_SET_ASCII; result->target_devid->association = ID_IS_TARGET_PORT; /* Store a shortcut to the target name */ result->target_name = (char *)result->target_devid->ident; mutex_init(&result->target_mutex, NULL, MUTEX_DEFAULT, NULL); avl_create(&result->target_sess_list, iscsit_sess_avl_compare, sizeof (iscsit_sess_t), offsetof(iscsit_sess_t, ist_tgt_ln)); avl_create(&result->target_tpgt_list, iscsit_tpgt_avl_compare, sizeof (iscsit_tpgt_t), offsetof(iscsit_tpgt_t, tpgt_tgt_ln)); /* Finish initializing local port */ lport->lport_id = result->target_devid; lport->lport_pp = iscsit_global.global_pp; /* * XXX Confirm that multiple local ports can share a single dbuf * store. Also confirm that there is no penalty for using a * global dbuf store assuming none of the buffers will be cached. */ lport->lport_ds = iscsit_global.global_dbuf_store; lport->lport_xfer_data = &iscsit_xfer_scsi_data; lport->lport_send_status = &iscsit_send_scsi_status; lport->lport_task_free = &iscsit_lport_task_free; lport->lport_abort = &iscsit_abort; lport->lport_ctl = &iscsit_ctl; /* Start with default TPG. */ iscsit_tgt_bind_tpgt(result, iscsit_global.global_default_tpg, ISCSIT_DEFAULT_TPGT); /* * Don't register the target with STMF until we have all the * TPGT bindings an any other additional config setup. STMF * may immediately ask us to go online. * * Make sure the target doesn't already exist. It might make * more sense to do this above but we'd like to hold the * lock across the lookup and insert operations. */ mutex_enter(&iscsit_global.global_mutex); if (iscsit_tgt_lookup(target_name) == NULL) { avl_add(&iscsit_global.global_target_list, result); } else { /* Target exists */ stmf_free(lport); result = NULL; } mutex_exit(&iscsit_global.global_mutex); return (result); } void iscsit_tgt_destroy(iscsit_tgt_t *tgt) { ASSERT(tgt->target_stmf_state != STMF_STATE_ONLINE); avl_destroy(&tgt->target_tpgt_list); avl_destroy(&tgt->target_sess_list); mutex_destroy(&tgt->target_mutex); stmf_free(tgt->target_stmf_lport); /* Also frees "tgt' */ } int iscsit_tgt_avl_compare(const void *void_tgt1, const void *void_tgt2) { const iscsit_tgt_t *tgt1 = void_tgt1; const iscsit_tgt_t *tgt2 = void_tgt2; int result; /* * Sort by ISID first then TSIH */ result = strcmp(tgt1->target_name, tgt2->target_name); if (result < 0) { return (-1); } else if (result > 0) { return (1); } return (0); } iscsit_tpgt_t * iscsit_tgt_lookup_tpgt(iscsit_tgt_t *tgt, uint16_t tag) { iscsit_tpgt_t tmp_tpgt; /* Caller holds tgt->target_mutex */ tmp_tpgt.tpgt_tag = tag; return (avl_find(&tgt->target_tpgt_list, &tmp_tpgt, NULL)); } iscsit_portal_t * iscsit_tgt_lookup_portal(iscsit_tgt_t *tgt, struct sockaddr_storage *sa, uint16_t port) { iscsit_tpgt_t *tpgt; iscsit_portal_t *portal; /* Caller holds tgt->target_mutex */ for (tpgt = avl_first(&tgt->target_tpgt_list); tpgt != NULL; tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) { portal = iscsit_portal_lookup(tpgt->tpgt_tpg, sa, port); if (portal) { return (portal); } } return (NULL); } void iscsit_tgt_bind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess) { if (tgt) { sess->ist_lport = tgt->target_stmf_lport; mutex_enter(&tgt->target_mutex); avl_add(&tgt->target_sess_list, sess); mutex_exit(&tgt->target_mutex); } else { /* Discovery session */ sess->ist_lport = NULL; ISCSIT_GLOBAL_LOCK(); avl_add(&iscsit_global.global_discovery_sessions, sess); ISCSIT_GLOBAL_UNLOCK(); } } void iscsit_tgt_unbind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess) { if (tgt) { mutex_enter(&tgt->target_mutex); avl_remove(&tgt->target_sess_list, sess); mutex_exit(&tgt->target_mutex); } else { /* Discovery session */ ISCSIT_GLOBAL_LOCK(); avl_remove(&iscsit_global.global_discovery_sessions, sess); ISCSIT_GLOBAL_UNLOCK(); } } iscsit_sess_t * iscsit_tgt_lookup_sess(iscsit_tgt_t *tgt, char *initiator_name, uint8_t *isid, uint16_t tsih) { iscsit_sess_t tmp_sess; avl_tree_t *sess_avl; avl_index_t where; iscsit_sess_t *result; /* * If tgt is NULL then we are looking for a discovery session */ if (tgt == NULL) { sess_avl = &iscsit_global.global_discovery_sessions; } else { sess_avl = &tgt->target_sess_list; } if (avl_numnodes(sess_avl) == NULL) { return (NULL); } /* * We'll try to find a session matching ISID + TSIH first. If we * can't find one then we will return the closest match. If the * caller needs an exact match it must compare the TSIH after * the session is returned. */ bcopy(isid, tmp_sess.ist_isid, ISCSI_ISID_LEN); tmp_sess.ist_initiator_name = initiator_name; tmp_sess.ist_tsih = tsih; result = avl_find(sess_avl, &tmp_sess, &where); if (result != NULL) { return (result); } /* * avl_find_nearest() may return a result with a different ISID so * we should only return a result if the name and ISID match */ result = avl_nearest(sess_avl, where, AVL_BEFORE); if ((result != NULL) && (strcmp(result->ist_initiator_name, initiator_name) == 0) && (memcmp(result->ist_isid, isid, ISCSI_ISID_LEN) == 0)) { return (result); } result = avl_nearest(sess_avl, where, AVL_AFTER); if ((result != NULL) && (strcmp(result->ist_initiator_name, initiator_name) == 0) && (memcmp(result->ist_isid, isid, ISCSI_ISID_LEN) == 0)) { return (result); } return (NULL); } idm_status_t iscsit_tgt_bind_tpgt(iscsit_tgt_t *tgt, iscsit_tpg_t *tpg, uint16_t tag) { iscsit_tpgt_t *tpgt, *first_tpgt; tpgt = kmem_zalloc(sizeof (*tpgt), KM_SLEEP); tpgt->tpgt_tpg = tpg; tpgt->tpgt_tag = tag; mutex_enter(&tgt->target_mutex); first_tpgt = avl_first(&tgt->target_tpgt_list); if (first_tpgt != NULL) { /* * Compare the requested TPG against the global default TPG. * The global default TPG can only be bound if the TPGT list * is empty */ if (tpg == iscsit_global.global_default_tpg) { mutex_exit(&tgt->target_mutex); kmem_free(tpgt, sizeof (*tpgt)); return (IDM_STATUS_FAIL); } /* * Otherwise if the TPGT list contains the default TPG * we need to remove it. The default TPG is only for targets * with no TPGTs configured. */ if (first_tpgt->tpgt_tpg == iscsit_global.global_default_tpg) { iscsit_tgt_unbind_tpgt(tgt, tpgt); ASSERT(avl_numnodes(&tgt->target_tpgt_list) == 0); } } /* * Fail request if this is a duplicate tag */ if (iscsit_tgt_lookup_tpgt(tgt, tag) == NULL) { /* New tag */ avl_add(&tgt->target_tpgt_list, tpgt); } else { mutex_exit(&tgt->target_mutex); kmem_free(tpgt, sizeof (*tpgt)); return (IDM_STATUS_FAIL); } if (tgt->target_stmf_state == STMF_STATE_ONLINE) { iscsit_tpg_online(tpg); /* XXX OK to hold target lock? */ } mutex_exit(&tgt->target_mutex); return (IDM_STATUS_SUCCESS); } void iscsit_tgt_unbind_tpgt(iscsit_tgt_t *tgt, iscsit_tpgt_t *tpgt) { /* Caller holds target mutex */ if (tgt->target_stmf_state == STMF_STATE_ONLINE) { /* XXX OK to hold target lock? */ iscsit_tpg_offline(tpgt->tpgt_tpg); } avl_remove(&tgt->target_tpgt_list, tpgt); } static int iscsit_tpgt_avl_compare(const void *void_tpgt1, const void *void_tpgt2) { const iscsit_tpgt_t *tpgt1 = void_tpgt1; const iscsit_tpgt_t *tpgt2 = void_tpgt2; if (tpgt1->tpgt_tag < tpgt2->tpgt_tag) return (-1); else if (tpgt1->tpgt_tag > tpgt2->tpgt_tag) return (1); return (0); } idm_status_t iscsit_tgt_online(iscsit_tgt_t *tgt) { iscsit_tpgt_t *tpgt, *tpgt_fail; idm_status_t rc; mutex_enter(&tgt->target_mutex); for (tpgt = avl_first(&tgt->target_tpgt_list); tpgt != NULL; tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) { rc = iscsit_tpg_online(tpgt->tpgt_tpg); if (rc != IDM_STATUS_SUCCESS) { tpgt_fail = tpgt; goto tgt_online_fail; } } tgt->target_stmf_state = STMF_STATE_ONLINE; mutex_exit(&tgt->target_mutex); return (IDM_STATUS_SUCCESS); tgt_online_fail: /* Offline all the tpgs we successfully onlined up to the failure */ for (tpgt = avl_first(&tgt->target_tpgt_list); tpgt != tpgt_fail; tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) { iscsit_tpg_offline(tpgt->tpgt_tpg); } mutex_exit(&tgt->target_mutex); return (rc); } void iscsit_tgt_offline(iscsit_tgt_t *tgt) { iscsit_tpgt_t *tpgt; mutex_enter(&tgt->target_mutex); tgt->target_stmf_state = STMF_STATE_OFFLINE; for (tpgt = avl_first(&tgt->target_tpgt_list); tpgt != NULL; tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) { iscsit_tpg_offline(tpgt->tpgt_tpg); } mutex_exit(&tgt->target_mutex); } iscsit_tpg_t * iscsit_tpg_lookup(char *tpg_name) { iscsit_tpg_t tmp_tpg; tmp_tpg.tpg_name = tpg_name; return (avl_find(&iscsit_global.global_tpg_list, &tmp_tpg, NULL)); } iscsit_tpg_t * iscsit_tpg_create(char *tpg_name) { iscsit_tpg_t *tpg; tpg = kmem_zalloc(sizeof (*tpg), KM_SLEEP); tpg->tpg_name = kmem_alloc(strlen(tpg_name) + 1, KM_SLEEP); strcpy(tpg->tpg_name, tpg_name); avl_create(&tpg->tpg_portal_list, iscsit_tpg_avl_compare, sizeof (iscsit_portal_t), offsetof(iscsit_portal_t, portal_tpg_ln)); if (strcmp(tpg_name, ISCSIT_DEFAULT_TPG) != 0) { /* Not default TPG, add to global TPG list */ ISCSIT_GLOBAL_LOCK(); avl_add(&iscsit_global.global_tpg_list, tpg); ISCSIT_GLOBAL_UNLOCK(); } return (tpg); } void iscsit_tpg_destroy(iscsit_tpg_t *tpg) { /* Caller hold global mutex */ if (strcmp(tpg->tpg_name, ISCSIT_DEFAULT_TPG) != 0) { avl_remove(&iscsit_global.global_tpg_list, tpg); } avl_destroy(&tpg->tpg_portal_list); kmem_free(tpg->tpg_name, strlen(tpg->tpg_name) + 1); kmem_free(tpg, sizeof (*tpg)); } iscsit_tpg_t * iscsit_tpg_createdefault() { iscsit_tpg_t *tpg; tpg = iscsit_tpg_create(ISCSIT_DEFAULT_TPG); if (tpg != NULL) { /* Now create default portal */ if (iscsit_portal_create(tpg, NULL, ISCSI_LISTEN_PORT) == NULL) { iscsit_tpg_destroy(tpg); } } return (tpg); } int iscsit_tpg_avl_compare(const void *void_tpg1, const void *void_tpg2) { const iscsit_tpg_t *tpg1 = void_tpg1; const iscsit_tpg_t *tpg2 = void_tpg2; int result; /* * Sort by ISID first then TSIH */ result = strcmp(tpg1->tpg_name, tpg2->tpg_name); if (result < 0) { return (-1); } else if (result > 0) { return (1); } return (0); } idm_status_t iscsit_tpg_online(iscsit_tpg_t *tpg) { iscsit_portal_t *portal, *portal_fail; idm_status_t rc; mutex_enter(&tpg->tpg_mutex); if (tpg->tpg_online == 0) { for (portal = avl_first(&tpg->tpg_portal_list); portal != NULL; portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) { rc = iscsit_portal_online(portal); if (rc != IDM_STATUS_SUCCESS) { portal_fail = portal; goto tpg_online_fail; } } } tpg->tpg_online++; mutex_exit(&tpg->tpg_mutex); return (IDM_STATUS_SUCCESS); tpg_online_fail: /* Offline all the portals we successfully onlined up to the failure */ for (portal = avl_first(&tpg->tpg_portal_list); portal != portal_fail; portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) { iscsit_portal_offline(portal); } mutex_exit(&tpg->tpg_mutex); return (rc); } void iscsit_tpg_offline(iscsit_tpg_t *tpg) { iscsit_portal_t *portal; mutex_enter(&tpg->tpg_mutex); tpg->tpg_online--; if (tpg->tpg_online == 0) { for (portal = avl_first(&tpg->tpg_portal_list); portal != NULL; portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) { iscsit_portal_offline(portal); } } mutex_exit(&tpg->tpg_mutex); } iscsit_portal_t * iscsit_portal_lookup(iscsit_tpg_t *tpg, struct sockaddr_storage *sa, uint16_t port) { iscsit_portal_t tmp_portal; /* Caller holds tpg->tpg_mutex */ bcopy(sa, &tmp_portal.portal_addr, sizeof (*sa)); tmp_portal.portal_port = port; return (avl_find(&tpg->tpg_portal_list, &tmp_portal, NULL)); } iscsit_portal_t * iscsit_portal_create(iscsit_tpg_t *tpg, struct sockaddr_storage *sa, uint16_t port) { iscsit_portal_t *portal; idm_svc_t *svc; idm_svc_req_t sr; idm_status_t rc; portal = kmem_zalloc(sizeof (*portal), KM_SLEEP); /* * If (sa == NULL) && port == ISCSI_LISTEN_PORT then we are being * asked to create the default portal -- targets will use this * portal when no portals are explicitly configured. */ if (sa != NULL || port != ISCSI_LISTEN_PORT) { bcopy(sa, &portal->portal_addr, sizeof (struct sockaddr_storage)); } portal->portal_port = port; /* * If there is no existing IDM service instance for this port, create * one. */ if ((svc = idm_tgt_svc_lookup(port)) == NULL) { sr.sr_port = port; sr.sr_conn_ops.icb_rx_scsi_cmd = &iscsit_op_scsi_cmd; sr.sr_conn_ops.icb_rx_scsi_rsp = NULL; sr.sr_conn_ops.icb_rx_misc = &iscsit_rx_pdu; sr.sr_conn_ops.icb_rx_error = &iscsit_rx_pdu_error; sr.sr_conn_ops.icb_client_notify = &iscsit_client_notify; sr.sr_conn_ops.icb_build_hdr = &iscsit_build_hdr; if ((rc = idm_tgt_svc_create(&sr, &svc)) != IDM_STATUS_SUCCESS) { kmem_free(portal, sizeof (*portal)); return (NULL); } } portal->portal_svc = svc; /* * If this is not the default portal (sa != NULL) then make sure * this portal isn't already defined on this TPG. */ mutex_enter(&tpg->tpg_mutex); if ((sa != NULL) && (iscsit_portal_lookup(tpg, sa, port) != NULL)) { kmem_free(portal, sizeof (*portal)); idm_tgt_svc_destroy(svc); return (NULL); } else { avl_add(&tpg->tpg_portal_list, portal); } mutex_exit(&tpg->tpg_mutex); return (portal); } void iscsit_portal_destroy(iscsit_portal_t *portal) { /* Caller holds parent TPG mutex */ avl_remove(&portal->portal_tpg->tpg_portal_list, portal); kmem_free(portal, sizeof (*portal)); } idm_status_t iscsit_portal_online(iscsit_portal_t *portal) { idm_status_t rc; /* Caller holds parent TPG mutex */ if (portal->portal_online == 0) { if ((rc = idm_tgt_svc_online(portal->portal_svc)) != IDM_STATUS_SUCCESS) { return (IDM_STATUS_FAIL); } } portal->portal_online++; return (rc); } void iscsit_portal_offline(iscsit_portal_t *portal) { portal->portal_online--; if (portal->portal_online == 0) { idm_tgt_svc_offline(portal->portal_svc); } }