root/usr/src/lib/storage/libg_fc/common/hot.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*LINTLIBRARY*/


/*
 *      This module is part of fibre channel interface library.
 */

/*
 * I18N message number ranges
 *  This file: 11000 - 11499
 *  Shared common messages: 1 - 1999
 */

/* #define              _POSIX_SOURCE 1 */

/*      Includes        */
#include        <stdlib.h>
#include        <stdio.h>
#include        <sys/file.h>
#include        <sys/errno.h>
#include        <sys/types.h>
#include        <sys/param.h>
#include        <sys/stat.h>
#include        <fcntl.h>
#include        <unistd.h>
#include        <errno.h>
#include        <string.h>
#include        <strings.h>
#include        <sys/sunddi.h>
#include        <sys/scsi/scsi.h>
#include        <nl_types.h>
#include        <l_common.h>
#include        <stgcom.h>
#include        <l_error.h>
#include        <g_state.h>

/* Forward declarations */
static int issue_lip(char *, int);


/*      Global variables        */
extern  uchar_t         g_switch_to_alpa[];
extern  uchar_t         g_sf_alpa_to_switch[];

/*
 * starts a device.
 *
 * RETURNS:
 *      0        if O.K.
 *      non-zero otherwise
 */
int
g_dev_start(char *drv_path, int verbose)
{
int status;

        if ((drv_path != NULL) && (*drv_path != '\0')) {
                if (status = g_start(drv_path)) {
                        return (status);
                }
        }
        return (L_INVALID_PATH);
}



/*
 * stops a device. If the device was
 * reserved by a host, it gets multiple
 * paths to the device and try to stop the
 * device using a different path.
 *
 * Returns:
 *      0 if OK
 *      -1 otherwise
 */

int
g_dev_stop(char *drv_path, struct wwn_list_struct *wwn_list,
                                                int verbose)
{
int             status, err;
char            *phys_path;
struct dlist    *ml = NULL;


        /* stop the device */
        /* Make the stop NOT immediate, so we wait. */
        if ((drv_path == NULL) || (*drv_path == '\0')) {
                return (L_INVALID_PATH);
        }
        if ((status = g_stop(drv_path, 0)) != 0) {
                /*
                 * In case of reservation conflict,
                 * get the multiple paths and try to
                 * stop the device through the path
                 * which held the reservations.
                 */
                if ((status & ~L_SCSI_ERROR) == STATUS_RESERVATION_CONFLICT) {
                        if ((phys_path = g_get_physical_name(drv_path))
                                                                == NULL) {
                                return (L_INVALID_PATH);
                        }
                        if ((err = g_get_multipath(phys_path, &ml,
                                                wwn_list, verbose)) != 0) {
                                return (err);
                        }
                        while (ml != NULL) {
                                if (g_stop(ml->logical_path, 0) == 0) {
                                        (void) g_free_multipath(ml);
                                        goto done;
                                }
                                ml = ml->next;
                        }
                        (void) g_free_multipath(ml);
                }
                return (status);
        }
done:
        return (0);
}

/*
 * This function is for Leadville devices only
 * It takes as input the actual path on which to issue the LIP and issues it
 *
 * INPUT :
 * Path to the FCA devctl node.
 *
 * For example,
 * /devices/pci@6,2000/pci@2/SUNW,qlc@4/fp@0,0:devctl
 *
 * No SCSI_VHCI paths will work. No checks are done and we'll let the ioctl
 * handle any failures if it is passed in.
 *
 * RETURNS:
 * 0 on Success
 * non-zero otherwise
 */
static int
issue_lip(char *fp_path, int verbose)
{
        int             fp_fd;
        la_wwn_t        wwn;
        fcio_t          fcio;

        /*
         * open fp path with exclusive path, otherwise,
         * FCIO_RESET_LINK ioctl will fail with permission
         * denied error.
         */
        if ((fp_fd = g_object_open(fp_path, O_RDONLY | O_EXCL)) < 0) {
                return (L_OPEN_PATH_FAIL);
        }

        if (verbose) {
                (void) fprintf(stdout, MSGSTR(11001,
                        " Reinitializing the loop at:  %s\n"), fp_path);
        }

        fcio.fcio_cmd = FCIO_RESET_LINK;
        fcio.fcio_xfer = FCIO_XFER_WRITE;
        /*
         * Reset the local loop here (fcio_ibuf = 0).
         * Reset a remote loop on the Fabric by
         * passing its node wwn (fcio_len = sizeof(nwwn)
         * and fcio_ibuf = (caddr_t)&nwwn) to the port driver.
         */
        (void) bzero((caddr_t)&wwn, sizeof (wwn));
        fcio.fcio_ilen = sizeof (wwn);
        fcio.fcio_ibuf = (caddr_t)&wwn;
        if (g_issue_fcio_ioctl(fp_fd, &fcio, verbose) != 0) {
                I_DPRINTF(" issue_lip: FCIO_RESET_LINK"
                        " ioctl failed: %s\n", fp_path);
                (void) close(fp_fd);
                return (L_FCIO_RESET_LINK_FAIL);
        }
        (void) close(fp_fd);
        return (0);
}

/*
 * Issues the LIP (Loop Intialization Protocol)
 * on a nexus path (in case of socal) or on an
 * fp path (in case of fabric).
 *
 * RETURNS:
 *      0        O.K.
 *      non-zero otherwise
 */
int
g_force_lip(char *path_phys, int verbose)
{
int             fd, err = 0, i = 0, pathcnt = 0;
char            nexus_path[MAXPATHLEN], *nexus_path_ptr;
char            *charPtr, fp_path[MAXPATHLEN];
struct stat     stbuf;
uint_t          dev_type;
mp_pathlist_t   pathlist;
mp_pathinfo_t   *pinfop;

        /* return invalid path if path_phys NULL */
        if (path_phys == NULL) {
                return (L_INVALID_PATH);
        }

        /* Make a copy of the arg passed in ... we'll need it down */
        (void) strcpy(fp_path, path_phys);

        if (strstr(path_phys, SCSI_VHCI) != NULL) {

                /*
                 * Its an MPXIO device path
                 *
                 * First, Get a list of all the pHCI for the given vHCI
                 * Then issue a LIP on all the pHCI FCAs that are in the
                 * MDI_PATHINFO_STATE_ONLINE or MDI_PATHINFO_STATE_STANDBY
                 * states.
                 */
                if (g_get_pathlist(fp_path, &pathlist)) {
                        return (L_INVALID_PATH);
                }
                for (i = 0; i < pathlist.path_count; i++) {
                        pinfop = &pathlist.path_info[i];
                        if ((pinfop->path_state ==
                                MDI_PATHINFO_STATE_ONLINE) ||
                                    (pinfop->path_state ==
                                        MDI_PATHINFO_STATE_STANDBY)) {
                                pathcnt++;
                                sprintf(fp_path, "%s%s",
                                                pinfop->path_hba, FC_CTLR);
                                if (issue_lip(fp_path, verbose) != 0) {
                                        err++;
                                }
                        }
                }
                free(pathlist.path_info);
                if (err == 0)
                        return (0);
                if (err == pathcnt)
                        return (L_FCIO_FORCE_LIP_FAIL);
                return (L_FCIO_FORCE_LIP_PARTIAL_FAIL);
        }

        /* Non-MPXIO case */

        if ((dev_type = g_get_path_type(fp_path)) == 0) {
                return (L_INVALID_PATH);
        }

        if (dev_type & FC_FCA_MASK) {
                if (strstr(fp_path, DRV_NAME_SSD) ||
                        strstr(fp_path, SES_NAME) ||
                        strstr(fp_path, DRV_NAME_ST)) {
                        if ((charPtr = strrchr(fp_path, '/')) == NULL) {
                                return (L_INVALID_PATH);
                        }
                        *charPtr = '\0';
                        /* append devctl to the path */
                        (void) strcat(fp_path, FC_CTLR);
                } else {
                        /* should have fp transport node to continue. */
                        if (!(dev_type & FC_XPORT_MASK)) {
                                return (L_INVALID_PATH_TYPE);
                        }
                        if (stat(fp_path, &stbuf) < 0) {
                                return (L_LSTAT_ERROR);
                        }
                        if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
                                /* append devctl to the path */
                                (void) strcat(fp_path, FC_CTLR);
                        }
                }
                return (issue_lip(fp_path, verbose));

        } else {        /* for fc4 devices */
                if ((err = g_get_nexus_path(path_phys,
                                        &nexus_path_ptr)) != 0)
                        return (err);

                (void) strcpy(nexus_path, nexus_path_ptr);
                (void) g_destroy_data(nexus_path_ptr);
                P_DPRINTF("  g_force_lip: Force lip on:"
                        " Path %s\n", nexus_path);

                /* open driver */
                if ((fd = g_object_open(nexus_path,
                                O_NDELAY | O_RDONLY)) == -1)
                        return (L_OPEN_PATH_FAIL);

                if (verbose) {
                        (void) fprintf(stdout,
                                        MSGSTR(11000,
                                        "  Forcing lip (Loop Initialization "
                                        "Protocol)"
                                        "\n  on loop at: %s\n"), nexus_path);
                }
                if (ioctl(fd, FCIO_FORCE_LIP) != 0) {
                        I_DPRINTF("  FCIO_FORCE_LIP ioctl failed.\n");
                        (void) close(fd);
                        return (L_FCIO_FORCE_LIP_FAIL);
                }
                (void) close(fd);
        }
        return (0);
}



/*
 * Takes one or more drives offline.
 * If the force flag is supplied then: (1) don't pass the exclusive flag
 * to the acquire routine and (2) allow the offline to fail
 * If any acquire fails, print an error message and continue.
 *
 * RETURNS:
 *      0               iff each offline succeeds
 *      non-zero        otherwise
 */
int
g_offline_drive(struct dlist *dl, int force_flag)
{
devctl_hdl_t            devhdl;


        /* for each drive attempt to take it offline */
        for (; dl != NULL; dl = dl->next) {

                /* attempt to acquire the device */
                if ((devhdl = devctl_device_acquire(dl->dev_path,
                                force_flag ? 0 : DC_EXCL)) == NULL) {
                        if (errno != EBUSY) {
                                P_DPRINTF("%s: Could not acquire"
                                        " the device: %s\n\n",
                                        strerror(errno), dl->dev_path);
                                continue;
                        }
                }
                /* attempt to offline the drive */
                if ((devctl_device_offline(devhdl) != 0) && !force_flag) {
                        (void) devctl_release(devhdl);
                        return (L_DEV_BUSY);
                }

                /* offline succeeded -- release handle acquired above */
                (void) devctl_release(devhdl);
        }

        return (0);
}



/*
 * Brings one or more drives online.
 * If the force flag is supplied then: (1) don't pass the exclusive
 * flag to the acquire routine and (2) allow the offline to fail
 * If any acquire fails, continue with the next device.
 *
 * RETURNS:
 *      None.
 */
void
g_online_drive(struct dlist *dl, int force_flag)
{
devctl_hdl_t            devhdl;


        while (dl != NULL) {
                if ((devhdl = devctl_device_acquire(dl->dev_path,
                                        force_flag ? 0 : DC_EXCL)) != NULL) {
                        (void) devctl_device_online(devhdl);
                        (void) devctl_release(devhdl);
                }
                dl = dl->next;
        }
}



void
g_ll_to_str(uchar_t *wwn_ll, char       *wwn_str)
{
int     j, k, fnib, snib;
uchar_t c;

        for (j = 0, k = 0; j < 8; j++) {
                c = wwn_ll[j];
                fnib = ((int)(c & 0xf0) >> 4);
                snib = (c & 0x0f);
                if (fnib >= 0 && fnib <= 9)
                        wwn_str[k++] = '0' + fnib;
                else if (fnib >= 10 && fnib <= 15)
                        wwn_str[k++] = 'a' + fnib - 10;
                if (snib >= 0 && snib <= 9)
                        wwn_str[k++] = '0' + snib;
                else if (snib >= 10 && snib <= 15)
                        wwn_str[k++] = 'a' + snib - 10;
        }
        wwn_str[k] = '\0';
}



/*
 * Creates a list of nexus paths for each
 * hotpluggable device and sends the list to g_force_lip(),
 * which forces the LIP on each nexus path in the list.
 *
 * RETURNS:
 *      None.
 */
int
g_forcelip_all(struct hotplug_disk_list *disk_list)
{
char            *p;
int             len, ndevs = 0, err = 0;
struct  dlist   *dl;
struct loop_list {      /* adp_name holds full dev path for MPXIO devices */
                char adp_name[MAXPATHLEN];
                struct loop_list *next;
                struct loop_list *prev;
        } *llist_head, *llist_tail, *llist, *llist1;

        llist_head = llist_tail = NULL;

        while (disk_list) {
                if (disk_list->dev_location == SENA) {
                        dl = disk_list->seslist;
                } else {
                        dl = disk_list->dlhead;
                }
                while (dl != NULL) {
                        if (strstr(dl->dev_path, SCSI_VHCI) == NULL) {
                                /* non-MPXIO device path */
                                if (disk_list->dev_location == SENA) {
                                    p = strstr(dl->dev_path, SLASH_SES);
                                } else {
                                    p = strstr(dl->dev_path, SLSH_DRV_NAME_SSD);
                                    if (p == NULL) {
                                        p = strstr(dl->dev_path,
                                                        SLSH_DRV_NAME_ST);
                                    }
                                }
                                if (p == NULL) {
                                        P_DPRINTF(
                                        "  g_forcelip_all: Not able to do"
                                        " LIP on this path because path "
                                        "invalid.\n  Path: %s\n", dl->dev_path);
                                        dl = dl->next;
                                        continue;
                                }
                                len = strlen(dl->dev_path) - strlen(p);
                        } else {
                                /* MPXIO path */
                                len = strlen(dl->dev_path);
                        }

                        /*
                         * Avoid issuing forcelip
                         * on the same HA more than once
                         */
                        if (llist_head != NULL) {
                                for (llist1 = llist_head; llist1 != NULL;
                                                llist1 = llist1->next) {
                                        if (strncmp(llist1->adp_name,
                                                dl->dev_path, len) == 0) {
                                                break;
                                        }
                                }
                                if (llist1 != NULL) {
                                        dl = dl->next;
                                        continue;
                                }
                        }
                        if ((llist = (struct loop_list *)
                                g_zalloc(sizeof (struct loop_list))) == NULL)
                                return (L_MALLOC_FAILED);
                        (void) strncpy(llist->adp_name, dl->dev_path, len);
                        llist->adp_name[len] = '\0';
                        ndevs++;

                        if (llist_head == NULL) {
                                llist_head = llist_tail = llist;
                        } else {
                                llist->prev = llist_tail;
                                llist_tail = llist_tail->next = llist;
                        }
                        dl = dl->next;
                }
                disk_list = disk_list->next;
        }

        while (llist_head) {
                if ((err = g_force_lip(llist_head->adp_name, 0)) != 0) {
                        (void) g_destroy_data(llist);
                        (void) g_destroy_data(llist_head);
                        return (err);
                }
                llist = llist_head;
                llist_head = llist_head->next;
                (void) g_destroy_data((char *)llist);
        }
        (void) sleep(ndevs*10);
        return (0);
}