--- /dev/null Fri Apr 4 13:31:05 2008 +++ new/src/sun_nws/comstar/port_providers/iscsit/src/iscsit_tgt.c Fri Apr 4 13:31:05 2008 @@ -0,0 +1,669 @@ +/* + * 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); + } +}