root/usr/src/lib/sun_sas/common/sun_sas.c
/*
 * 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 usr/src/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 usr/src/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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright 2019 Joyent, Inc.
 */

#include <sys/byteorder.h>
#include <sun_sas.h>

/*
 * creates a handle each time Sun_sas_OpenAdapter() is called.
 *
 * a open_handle_struct was created to keep track of which handles are currently
 * open.  This prevents a user from using an old handle that corresponds to
 * an hba that has already been closed.
 */
HBA_HANDLE
CreateHandle(int adapterIndex)
{
        const char              ROUTINE[] = "CreateHandle";
        struct open_handle      *new_open_handle;
        HBA_UINT32              new_handle_index;
        HBA_UINT8               max_handle_wrap = 0;

        if (global_hba_head == NULL) {
                log(LOG_DEBUG, ROUTINE,
                    "an error as occurred.  global_hba_head is "
                    "NULL.  Library may not be loaded yet.");
                return (HANDLE_ERROR);
        }

        while (RetrieveIndex(open_handle_index) != -1)  {
                open_handle_index = open_handle_index + 1;
                if (open_handle_index == 0) {
                        /*
                         * If open_handle_index wraps back to zero again,
                         * that means all handles are currently in use.
                         * Spec only allows for 16 bits of handles
                         */
                        if (max_handle_wrap == 1) {
                                log(LOG_DEBUG, ROUTINE,
                                    "Max number of handles reached.");
                                return (HANDLE_ERROR);
                        }
                        open_handle_index = 1;
                        max_handle_wrap = 1;
                }
        }

        new_handle_index = open_handle_index;
        if ((new_open_handle = (struct open_handle *)calloc(1,
            sizeof (struct open_handle))) == NULL) {
                OUT_OF_MEMORY(ROUTINE);
                return (HANDLE_ERROR);
        }
        (void) memset(new_open_handle, 0, sizeof (struct open_handle));
        new_open_handle->adapterIndex = adapterIndex;
        new_open_handle->handle = new_handle_index;

        lock(&open_handles_lock);

        /* add new open handle struct to the open_handles list */
        if (global_hba_head->open_handles == NULL) {
                global_hba_head->open_handles = new_open_handle;
        } else {
                new_open_handle->next = global_hba_head->open_handles;
                global_hba_head->open_handles = new_open_handle;
        }

        unlock(&open_handles_lock);
        open_handle_index = open_handle_index + 1;
        if (open_handle_index == 0) {
                open_handle_index = 1;
        }

        return (new_handle_index);
}

/*
 * given a handle, returns the adapterIndex number.
 *
 * This functions checkes to see if the given handle corresponds to an open
 * HBA.  If it does, the adapterIndex is returned.
 */
int
RetrieveIndex(HBA_HANDLE handle)
{

        struct open_handle      *open_handle_ptr;

        lock(&open_handles_lock);

        open_handle_ptr = RetrieveOpenHandle(handle);

        unlock(&open_handles_lock);
        if (open_handle_ptr == NULL) {
                return (-1);
        }

        return (open_handle_ptr->adapterIndex);
}
/*
 * Given a handle, returns the open_handle structure
 * The routine assumes that the open_handles_lock has already
 * been taken.
 */
struct open_handle *
RetrieveOpenHandle(HBA_HANDLE handle)
{

        const char              ROUTINE[] = "RetrieveOpenHandle";
        struct open_handle      *open_handle_ptr = NULL;

        if (global_hba_head == NULL) {
                log(LOG_DEBUG, ROUTINE, "No adapter is found.");
                return (NULL);
        }

        for (open_handle_ptr = global_hba_head->open_handles;
            open_handle_ptr != NULL;
            open_handle_ptr = open_handle_ptr->next) {
                if (open_handle_ptr->handle == handle) {
                        break;
                }
        }

        return (open_handle_ptr);
}

/*
 * Given an adapterIndex, this functions returns a pointer to the handle
 * structure.  This handle structure holds the hba's information
 * Caller must take all_hbas_lock first.
 */
struct sun_sas_hba *
RetrieveHandle(int index)
{
        struct sun_sas_hba *hba_ptr = NULL;

        for (hba_ptr = global_hba_head; hba_ptr != NULL;
            hba_ptr = hba_ptr->next) {
                if (hba_ptr->index == index)
                        break;
        }

        return (hba_ptr);
}

/*
 * Given an adapterIndex, this functions returns a pointer to the handle
 * structure and extracts it from the global list.
 *
 * all_hbas_lock must be taken already.
 */
struct sun_sas_hba *
ExtractHandle(int index)
{
        struct sun_sas_hba *last = NULL;
        struct sun_sas_hba *hba_ptr = NULL;

        for (hba_ptr = global_hba_head;
            hba_ptr != NULL;
            last = hba_ptr, hba_ptr = hba_ptr->next) {
                if (hba_ptr->index == index) {
                        if (last) {
                                last->next = hba_ptr->next;
                        } else {
                                /* Hmm, must be the head of the list. */
                                global_hba_head = hba_ptr->next;
                        }
                        hba_ptr->next = NULL; /* Zap it to be safe */
                        break;
                }
        }

        return (hba_ptr);
}


/*
 * Given an handle, this functions returns a pointer to the handle structure
 * for that hba
 *
 * Caller must take all_hbas_lock first.
 */
struct sun_sas_hba *
Retrieve_Sun_sasHandle(HBA_HANDLE handle)
{
        const char                  ROUTINE[] = "Retrieve_Sun_sasHandle";
        struct  sun_sas_hba         *handle_struct = NULL;
        int                         index;

        /* Retrieve fp device path from handle */
        index = RetrieveIndex(handle);
        if (index == -1) {
                log(LOG_DEBUG, ROUTINE,
                    "handle could not be found.");
                return (handle_struct);
        }
        lock(&open_handles_lock);
        handle_struct = RetrieveHandle(index);
        if (handle_struct == NULL) {
                log(LOG_DEBUG, ROUTINE,
                    "could not find index in the handle list.");
                unlock(&open_handles_lock);
                return (handle_struct);
        }
        unlock(&open_handles_lock);

        return (handle_struct);
}

/*
 * Take a mutex lock.  The routine will try, and if it fails,
 * it will loop for a while and retry.  If it fails many times,
 * it will start writing to the log file.
 */
void
lock(mutex_t *mp)
{
        int status;
        int loop = 0;
        const char ROUTINE[] = "lock";

        do {
                loop++;
                status = mutex_trylock(mp);
                switch (status) {
                        case 0:
                                break;
                        case EFAULT:
                                log(LOG_DEBUG, ROUTINE,
                                    "Lock failed: fault 0x%x", mp);
                                break;
                        case EINVAL:
                                log(LOG_DEBUG, ROUTINE,
                                    "Lock failed: invalid 0x%x", mp);
                                break;
                        case EBUSY:
                                if (loop > DEADLOCK_WARNING) {
                                        log(LOG_DEBUG, ROUTINE,
                                            "Lock busy, possible deadlock:0x%x",
                                            mp);
                                }
                                break;
                        case EOWNERDEAD:
                                log(LOG_DEBUG, ROUTINE,
                                    "Lock failed: owner dead 0x%x",
                                    mp);
                                break;
                        case ELOCKUNMAPPED:
                                log(LOG_DEBUG, ROUTINE,
                                    "Lock failed: unmapped 0x%x",
                                    mp);
                                break;
                        case ENOTRECOVERABLE:
                                log(LOG_DEBUG, ROUTINE,
                                    "Lock failed: not recoverable 0x%x", mp);
                                break;
                        default:
                                if (loop > DEADLOCK_WARNING) {
                                        log(LOG_DEBUG, ROUTINE,
                                            "Lock failed: %s 0x%x",
                                            strerror(status), mp);
                                        break;
                                }
                }

                if (status) {
                        (void) sleep(LOCK_SLEEP);
                }

        } while (status);
}

/*
 * Unlock a mutex lock.
 */
void
unlock(mutex_t *mp)
{
        (void) mutex_unlock(mp);
}


/*
 * Get the Port WWN of the first adapter port.  This routine
 * is used by the old V1 interfaces so that they can call
 * the new V2 interfaces and exhibit the same behavior.
 * In the event of error the WWN will be zero.
 *
 * This function will transition to PAA state but it will not
 * verfiy whether data is stale or not
 */
HBA_WWN
getFirstAdapterPortWWN(HBA_HANDLE handle)
{
        const char      ROUTINE[] = "getFirstAdapterPortWWN";
        HBA_WWN                 pwwn = {0, 0, 0, 0, 0, 0, 0, 0};
        struct sun_sas_hba      *hba_ptr = NULL;
        int                     index = 0;
        HBA_STATUS              status;

        lock(&all_hbas_lock);
        index = RetrieveIndex(handle);
        lock(&open_handles_lock);
        hba_ptr = RetrieveHandle(index);
        if (hba_ptr == NULL) {
                log(LOG_DEBUG, ROUTINE, "Invalid handle %08lx", handle);
                unlock(&open_handles_lock);
                unlock(&all_hbas_lock);
                return (pwwn); /* zero WWN */
        }

        /* Check for stale data */
        status = verifyAdapter(hba_ptr);
        if (status != HBA_STATUS_OK) {
                log(LOG_DEBUG, ROUTINE, "Verify adapter failed");
                unlock(&open_handles_lock);
                unlock(&all_hbas_lock);
                return (pwwn);
        }

        if (hba_ptr->first_port == NULL) {
                /* This is probably an internal failure of the library */
                if (hba_ptr->device_path[0] != '\0') {
                        log(LOG_DEBUG, ROUTINE,
                            "Internal failure:  Adapter %s contains no "
                            "port data", hba_ptr->device_path);
                } else {
                        log(LOG_DEBUG, ROUTINE,
                            "Internal failure:  Adapter at index %d contains "
                            " no support data", hba_ptr->index);
                }
                unlock(&open_handles_lock);
                unlock(&all_hbas_lock);
                return (pwwn); /* zero WWN */
        }
        /* Set the WWN now and return it */
        pwwn = hba_ptr->first_port->port_attributes.PortSpecificAttribute.\
            SASPort->LocalSASAddress;
        unlock(&open_handles_lock);
        unlock(&all_hbas_lock);

        return (pwwn);
}

u_longlong_t
wwnConversion(uchar_t *wwn)
{
        u_longlong_t tmp;
        (void) memcpy(&tmp, wwn, sizeof (u_longlong_t));
        tmp = ntohll(tmp);
        return (tmp);
}

/*
 * Using ioctl to send uscsi command out
 */
HBA_STATUS
send_uscsi_cmd(const char *devpath, struct uscsi_cmd *ucmd)
{
        const char      ROUTINE[] = "send_uscsi_cmd";
        int             fd;
        HBA_STATUS      ret;

        /* set default timeout to 200 */
        ucmd->uscsi_timeout = 200;

        /* reset errno. */
        errno = 0;
        if ((fd = open(devpath, O_RDONLY | O_NDELAY)) == -1) {
                log(LOG_DEBUG, ROUTINE,
                    "open devpath %s failed: %s", devpath, strerror(errno));
                return (HBA_STATUS_ERROR);
        }

        if (ioctl(fd, USCSICMD, ucmd) == -1) {
                if (errno == EBUSY) {
                        ret = HBA_STATUS_ERROR_BUSY;
                } else if (errno == EAGAIN) {
                        ret = HBA_STATUS_ERROR_TRY_AGAIN;
                } else {
                        ret = HBA_STATUS_ERROR;
                }
                log(LOG_DEBUG, ROUTINE,
                    "ioctl send uscsi to devpath: %s failed: %s",
                    devpath, strerror(errno));
                (void) close(fd);
                return (ret);
        }

        (void) close(fd);

        return (HBA_STATUS_OK);
}

/*
 * Check whether the given Domain Address is valid.
 */
HBA_STATUS
validateDomainAddress(struct sun_sas_port *hba_port_ptr, HBA_WWN DomainAddr)
{
        if (hba_port_ptr->first_phy != NULL &&
            wwnConversion(hba_port_ptr->first_phy->
            phy.domainPortWWN.wwn) ==
            wwnConversion(DomainAddr.wwn)) {
                return (HBA_STATUS_OK);
        }
        return (HBA_STATUS_ERROR);
}