root/usr/src/lib/sun_sas/common/devtree_hba_disco.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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * Copyright 2019 Joyent, Inc.
 */

#include        <sun_sas.h>
#include        <sys/modctl.h>
#include        <sys/types.h>
#include        <netinet/in.h>
#include        <inttypes.h>
#include        <ctype.h>

struct sun_sas_hba *global_hba_head;

/* free hba port info for the given hba */
static void
free_hba_port(struct sun_sas_hba *hba_ptr)
{
        struct sun_sas_port     *hba_port = NULL;
        struct sun_sas_port     *last_hba_port = NULL;
        struct sun_sas_port     *tgt_port = NULL;
        struct sun_sas_port     *last_tgt_port = NULL;
        struct ScsiEntryList    *scsi_info = NULL;
        struct ScsiEntryList    *last_scsi_info = NULL;
        struct phy_info         *phy_ptr = NULL;
        struct phy_info         *last_phy = NULL;

        /* Free the nested structures (port and attached port) */
        hba_port = hba_ptr->first_port;
        while (hba_port != NULL) {
                /* Free discovered port structure list. */
                tgt_port = hba_port->first_attached_port;
                while (tgt_port != NULL) {
                        /* Free target mapping data list first. */
                        scsi_info = tgt_port->scsiInfo;
                        while (scsi_info != NULL) {
                                last_scsi_info = scsi_info;
                                scsi_info = scsi_info->next;
                                free(last_scsi_info);
                        }
                        last_tgt_port = tgt_port;
                        tgt_port = tgt_port->next;
                        free(last_tgt_port->port_attributes.\
                            PortSpecificAttribute.SASPort);
                        free(last_tgt_port);
                }
                hba_port->first_attached_port = NULL;

                phy_ptr = hba_port->first_phy;
                while (phy_ptr != NULL) {
                        last_phy = phy_ptr;
                        phy_ptr = phy_ptr->next;
                        free(last_phy);
                }
                hba_port->first_phy = NULL;

                last_hba_port = hba_port;
                hba_port = hba_port->next;
                free(last_hba_port->port_attributes.\
                    PortSpecificAttribute.SASPort);
                free(last_hba_port);
        }

        hba_ptr->first_port = NULL;
}

/*
 * Internal routine for adding an HBA port
 */
static HBA_STATUS
add_hba_port_info(di_node_t portNode, struct sun_sas_hba *hba_ptr, int protocol)
{
        const char                  ROUTINE[] = "add_hba_port_info";
        struct sun_sas_port         *port_ptr;
        char                        *portDevpath;
        int                         *propIntData;
        char                        *propStringData;
        uint64_t                    tmpAddr;
        char                        *charptr, cntlLink[MAXPATHLEN] = {'\0'};
        int                         rval;
        di_node_t                   branchNode;
        uint_t                      state = HBA_PORTSTATE_UNKNOWN;

        if (hba_ptr == NULL) {
                log(LOG_DEBUG, ROUTINE,
                    "Sun_sas handle ptr set to NULL.");
                return (HBA_STATUS_ERROR_ARG);
        }

        if ((port_ptr = (struct sun_sas_port *)calloc(1,
            sizeof (struct sun_sas_port))) == NULL) {
                OUT_OF_MEMORY(ROUTINE);
                return (HBA_STATUS_ERROR);
        }

        if ((port_ptr->port_attributes.PortSpecificAttribute.SASPort =
            (struct SMHBA_SAS_Port *)calloc(1, sizeof (struct SMHBA_SAS_Port)))
            == NULL) {
                OUT_OF_MEMORY(ROUTINE);
                return (HBA_STATUS_ERROR);
        }

        if ((portDevpath = di_devfs_path(portNode)) == NULL) {
                log(LOG_DEBUG, ROUTINE,
                    "Unable to get device path from HBA Port Node.");
                S_FREE(port_ptr->port_attributes.PortSpecificAttribute.SASPort);
                S_FREE(port_ptr);
                return (HBA_STATUS_ERROR);
        }

        /*
         * Let's take a branch snap shot for pulling attributes.
         * The attribute change doesn't invalidate devinfo cache snapshot.
         * Phy info prop and num-phys can be obsolate when the same hba
         * connected to the same expander(SIM) thus phy numbers are increased.
         * Also the phy number may get decreased when a connection is removed
         * while the iport still exist through another connection.
         */
        branchNode = di_init(portDevpath, DINFOPROP);
        if (branchNode == DI_NODE_NIL) {
                /* something is wrong here. */
                di_fini(branchNode);
                log(LOG_DEBUG, ROUTINE,
                    "Unable to take devinfoi branch snapshot on HBA port \"%s\""
                    " due to %s", portDevpath, strerror(errno));
                S_FREE(port_ptr->port_attributes.PortSpecificAttribute.SASPort);
                S_FREE(port_ptr);
                return (HBA_STATUS_ERROR);
        }

        state = di_state(portNode);
        if (((state & DI_DRIVER_DETACHED) == DI_DRIVER_DETACHED) ||
            ((state & DI_DEVICE_OFFLINE) == DI_DEVICE_OFFLINE)) {
                log(LOG_DEBUG, ROUTINE,
                    "HBA port node %s is either OFFLINE or DETACHED",
                    portDevpath);
                port_ptr->port_attributes.PortState = HBA_PORTSTATE_OFFLINE;
        } else {
                port_ptr->port_attributes.PortState = HBA_PORTSTATE_ONLINE;
        }

        port_ptr->port_attributes.PortType = HBA_PORTTYPE_SASDEVICE;

        (void) strlcpy(port_ptr->device_path, portDevpath, MAXPATHLEN + 1);

        if (lookupControllerLink(portDevpath, (char *)cntlLink) ==
            HBA_STATUS_OK) {
                (void) strlcpy(port_ptr->port_attributes.OSDeviceName, cntlLink,
                    sizeof (port_ptr->port_attributes.OSDeviceName));
                if ((charptr = strrchr(cntlLink, '/')) != NULL) {
                        charptr++;
                }
                if (charptr[0] ==  'c') {
                        port_ptr->cntlNumber = atoi(++charptr);
                } else {
                        port_ptr->cntlNumber = -1;
                }
        } else {
                (void) snprintf(port_ptr->port_attributes.OSDeviceName,
                    sizeof (port_ptr->port_attributes.OSDeviceName),
                    "%s%s%s", DEVICES_DIR, portDevpath, SCSI_SUFFIX);
        }

        di_devfs_path_free(portDevpath);

        port_ptr->port_attributes.PortSpecificAttribute.
            SASPort->PortProtocol = protocol;

        rval = di_prop_lookup_strings(DDI_DEV_T_ANY, branchNode,
            "initiator-port", &propStringData);
        if (rval < 0) {
                log(LOG_DEBUG, ROUTINE,
                    "Unable to get initiator-port from HBA port node %s.",
                    port_ptr->port_attributes.OSDeviceName);
                di_fini(branchNode);
                S_FREE(port_ptr->port_attributes.PortSpecificAttribute.SASPort);
                S_FREE(port_ptr);
                return (HBA_STATUS_ERROR);
        } else {
                for (charptr = propStringData; *charptr != '\0'; charptr++) {
                        if (isxdigit(*charptr)) {
                                break;
                        }
                }
                if (*charptr != '\0') {
                        tmpAddr = htonll(strtoll(charptr, NULL, 16));
                        (void) memcpy(port_ptr->port_attributes.
                            PortSpecificAttribute.SASPort->LocalSASAddress.wwn,
                            &tmpAddr, 8);
                } else {
                        log(LOG_DEBUG, ROUTINE,
                            "No proper intiator-port prop value on HBA port %s",
                            port_ptr->port_attributes.OSDeviceName);
                }
        }

        rval = di_prop_lookup_strings(DDI_DEV_T_ANY, branchNode,
            "attached-port", &propStringData);
        if (rval < 0) {
                log(LOG_DEBUG, ROUTINE,
                    "Unable to get attached-port from HBA port node %s.",
                    port_ptr->port_attributes.OSDeviceName);
                di_fini(branchNode);
                S_FREE(port_ptr->port_attributes.PortSpecificAttribute.SASPort);
                S_FREE(port_ptr);
                return (HBA_STATUS_ERROR);
        } else {
                for (charptr = propStringData; *charptr != '\0'; charptr++) {
                        if (isxdigit(*charptr)) {
                                break;
                        }
                }
                if (*charptr != '\0') {
                        tmpAddr = htonll(strtoll(charptr, NULL, 16));
                        (void) memcpy(port_ptr->port_attributes.
                            PortSpecificAttribute.SASPort->
                            AttachedSASAddress.wwn, &tmpAddr, 8);
                } else {
                        /* continue even if the attached port is NULL. */
                        log(LOG_DEBUG, ROUTINE,
                            "No proper attached-port prop value: "
                            "HBA port Local SAS Address(%016llx)",
                            wwnConversion(port_ptr->port_attributes.
                            PortSpecificAttribute.
                            SASPort->LocalSASAddress.wwn));
                }
        }

        rval = di_prop_lookup_ints(DDI_DEV_T_ANY, branchNode,
            "num-phys", &propIntData);
        if (rval < 0) {
                log(LOG_DEBUG, ROUTINE,
                    "Unable to get NumberofPhys from HBA port %s.",
                    port_ptr->port_attributes.OSDeviceName);
                di_fini(branchNode);
                S_FREE(port_ptr->port_attributes.PortSpecificAttribute.SASPort);
                S_FREE(port_ptr);
                return (HBA_STATUS_ERROR);
        } else {
                port_ptr->port_attributes.PortSpecificAttribute.\
                    SASPort->NumberofPhys = *propIntData;
        }

        if (port_ptr->port_attributes.PortSpecificAttribute.\
            SASPort->NumberofPhys > 0) {
                if (get_phy_info(branchNode, port_ptr) != HBA_STATUS_OK) {
                        log(LOG_DEBUG, ROUTINE,
                            "Failed to get phy info on HBA port %s.",
                            port_ptr->port_attributes.OSDeviceName);
                        di_fini(branchNode);
                        S_FREE(port_ptr->port_attributes.
                            PortSpecificAttribute.SASPort);
                        S_FREE(port_ptr);
                        return (HBA_STATUS_ERROR);
                }
        }

        /* now done with prop checking. remove branchNode. */
        di_fini(branchNode);

        /* Construct discovered target port. */
        if (devtree_attached_devices(portNode, port_ptr) != HBA_STATUS_OK) {
                log(LOG_DEBUG, ROUTINE,
                    "Failed to get attached device info HBA port %s.",
                    port_ptr->port_attributes.OSDeviceName);
                S_FREE(port_ptr->port_attributes.PortSpecificAttribute.SASPort);
                S_FREE(port_ptr);
                return (HBA_STATUS_ERROR);
        }

        fillDomainPortWWN(port_ptr);

        /* add new port onto hba handle list */
        if (hba_ptr->first_port == NULL) {
                port_ptr->index = 0;
                hba_ptr->first_port = port_ptr;
        } else {
                port_ptr->index = hba_ptr->first_port->index + 1;
                port_ptr->next = hba_ptr->first_port;
                hba_ptr->first_port = port_ptr;
        }

        return (HBA_STATUS_OK);
}

HBA_STATUS
refresh_hba(di_node_t hbaNode, struct sun_sas_hba *hba_ptr)
{
        const char      ROUTINE[] = "refresh_hba";
        di_node_t       portNode;
        int             protocol = 0;
        int             *propIntData;

        /*
         * clean up existing hba port, discovered target, phy info.
         * leave open handles intact.
         */
        free_hba_port(hba_ptr);

        if ((portNode = di_child_node(hbaNode)) == NULL) {
                log(LOG_DEBUG, ROUTINE,
                    "HBA node doesn't have iport child.");
                return (HBA_STATUS_ERROR);
        }

        if ((di_prop_lookup_ints(DDI_DEV_T_ANY, hbaNode,
            "supported-protocol", &propIntData)) == -1) {
                log(LOG_DEBUG, ROUTINE,
                    "Unable to get supported-protocol from HBA node.");
        } else {
                protocol = *propIntData;
        }

        while (portNode != DI_NODE_NIL) {
                if (di_prop_lookup_ints(DDI_DEV_T_ANY, portNode,
                    "virtual-port", &propIntData) >= 0) {
                        if (*propIntData) {
                                /* ignore a virtual port. */
                                portNode = di_sibling_node(portNode);
                                continue;
                        }
                }
                if (add_hba_port_info(portNode, hba_ptr, protocol)
                    == HBA_STATUS_ERROR) {
                        S_FREE(hba_ptr->first_port);
                        S_FREE(hba_ptr);
                        return (HBA_STATUS_ERROR);
                }
                portNode = di_sibling_node(portNode);
        }

        return (HBA_STATUS_OK);
}

/*
 * Discover information for one HBA in the device tree.
 * The di_node_t argument should be a node with smhba-supported prop set
 * to true.
 * Without iport support, the devinfo node will represent one port hba.
 * This routine assumes the locks have been taken.
 */
HBA_STATUS
devtree_get_one_hba(di_node_t hbaNode)
{
        const char              ROUTINE[] = "devtree_get_one_hba";
        char                    *propdata = NULL;
        int                     *propIntData = NULL;
        struct sun_sas_hba      *new_hba, *hba_ptr;
        char                    *hbaDevpath, *hba_driver;
        int                     protocol = 0;
        di_node_t               portNode;
        int                     hba_instance = -1;

        hba_instance = di_instance(hbaNode);
        if (hba_instance == -1) {
                log(LOG_DEBUG, ROUTINE,
                    "portNode has instance of -1");
                return (DI_WALK_CONTINUE);
        }

        if ((hbaDevpath = di_devfs_path(hbaNode)) == NULL) {
                log(LOG_DEBUG, ROUTINE, "Unable to get "
                    "device path from hbaNode");
                return (HBA_STATUS_ERROR);
        }

        /* check to see if this is a repeat HBA */
        if (global_hba_head) {
                for (hba_ptr = global_hba_head;
                    hba_ptr != NULL;
                    hba_ptr = hba_ptr->next) {
                        if ((strncmp(hba_ptr->device_path, hbaDevpath,
                            strlen(hbaDevpath))) == 0) {
                                if (refresh_hba(hbaNode, hba_ptr) !=
                                    HBA_STATUS_OK) {
                                        log(LOG_DEBUG, ROUTINE, "Refresh failed"
                                            " on hbaNode %s", hbaDevpath);
                                }
                                di_devfs_path_free(hbaDevpath);
                                return (HBA_STATUS_OK);
                        }
                }
        }

        /* this is a new hba */
        if ((new_hba = (struct sun_sas_hba *)calloc(1,
            sizeof (struct sun_sas_hba))) == NULL) {
                OUT_OF_MEMORY(ROUTINE);
                di_devfs_path_free(hbaDevpath);
                return (HBA_STATUS_ERROR);
        }

        (void) strlcpy(new_hba->device_path, hbaDevpath,
            sizeof (new_hba->device_path));
        di_devfs_path_free(hbaDevpath);

        (void) snprintf(new_hba->adapter_attributes.HBASymbolicName,
            sizeof (new_hba->adapter_attributes.HBASymbolicName),
            "%s%s", DEVICES_DIR, new_hba->device_path);

        /* Manufacturer */
        if ((di_prop_lookup_strings(DDI_DEV_T_ANY, hbaNode,
            "Manufacturer", (char **)&propdata)) == -1) {
                (void) strlcpy(new_hba->adapter_attributes.Manufacturer,
                    SUN_MICROSYSTEMS,
                    sizeof (new_hba->adapter_attributes.Manufacturer));
        } else {
                (void) strlcpy(new_hba->adapter_attributes.Manufacturer,
                    propdata,
                    sizeof (new_hba->adapter_attributes.Manufacturer));
        }

        /* SerialNumber */
        if ((di_prop_lookup_strings(DDI_DEV_T_ANY, hbaNode,
            "SerialNumber", (char **)&propdata)) == -1) {
                new_hba->adapter_attributes.SerialNumber[0] = '\0';
        } else {
                (void) strlcpy(new_hba->adapter_attributes.SerialNumber,
                    propdata,
                    sizeof (new_hba->adapter_attributes.SerialNumber));
        }

        /* Model */
        if ((di_prop_lookup_strings(DDI_DEV_T_ANY, hbaNode,
            "ModelName", (char **)&propdata)) == -1) {
                new_hba->adapter_attributes.Model[0] = '\0';
        } else {
                (void) strlcpy(new_hba->adapter_attributes.Model,
                    propdata,
                    sizeof (new_hba->adapter_attributes.Model));
        }

        /* FirmwareVersion */
        if ((di_prop_lookup_strings(DDI_DEV_T_ANY, hbaNode,
            "firmware-version", (char **)&propdata)) == -1) {
                log(LOG_DEBUG, ROUTINE,
                    "Property \"%s\" not found for device \"%s\"",
                    "firmware-version", new_hba->device_path);
        } else {
                (void) strlcpy(new_hba->adapter_attributes.FirmwareVersion,
                    propdata,
                    sizeof (new_hba->adapter_attributes.FirmwareVersion));
        }

        /* HardwareVersion */
        if ((di_prop_lookup_strings(DDI_DEV_T_ANY, hbaNode,
            "hardware-version", (char **)&propdata)) == -1) {
                log(LOG_DEBUG, ROUTINE,
                    "Property \"%s\" not found for device \"%s\"",
                    "hardware-version", new_hba->device_path);
        } else {
                (void) strlcpy(new_hba->adapter_attributes.HardwareVersion,
                    propdata,
                    sizeof (new_hba->adapter_attributes.HardwareVersion));
        }

        /* DriverVersion */
        if ((di_prop_lookup_strings(DDI_DEV_T_ANY, hbaNode,
            "driver-version", (char **)&propdata)) == -1) {
                log(LOG_DEBUG, ROUTINE,
                    "Property \"%s\" not found for device \"%s\"",
                    "driver-version", new_hba->device_path);
        } else {
                (void) strlcpy(new_hba->adapter_attributes.DriverVersion,
                    propdata,
                    sizeof (new_hba->adapter_attributes.DriverVersion));
        }

        if ((di_prop_lookup_ints(DDI_DEV_T_ANY, hbaNode,
            "supported-protocol", &propIntData)) == -1) {
                log(LOG_DEBUG, ROUTINE,
                    "Unable to get supported-protocol from HBA node.");
        } else {
                protocol = *propIntData;
        }

        /* We don't use these */
        new_hba->adapter_attributes.OptionROMVersion[0] = '\0';
        new_hba->adapter_attributes.RedundantOptionROMVersion[0] = '\0';
        new_hba->adapter_attributes.RedundantFirmwareVersion[0] = '\0';
        new_hba->adapter_attributes.VendorSpecificID = 0;

        if ((hba_driver = di_driver_name(hbaNode)) != NULL) {
                (void) strlcpy(new_hba->adapter_attributes.DriverName,
                    hba_driver,
                    sizeof (new_hba->adapter_attributes.DriverName));
        } else {
                log(LOG_DEBUG, ROUTINE,
                    "HBA driver name not found for device \"%s\"",
                    new_hba->device_path);
        }

        /*
         * Name the adapter: like SUNW-pmcs-1
         * Using di_instance number as the suffix for the name for persistent
         * among rebooting.
         */
        (void) snprintf(new_hba->handle_name, HANDLE_NAME_LENGTH, "%s-%s-%d",
            "SUNW", new_hba->adapter_attributes.DriverName, hba_instance);

        if ((portNode = di_child_node(hbaNode)) == NULL) {
                log(LOG_DEBUG, ROUTINE,
                    "HBA driver doesn't have iport child. \"%s\"",
                    new_hba->device_path);
                /* continue on with an hba without any port. */
                new_hba->index = hba_count++;

                /*
                 * add newly created handle into global_hba_head list
                 */
                if (global_hba_head != NULL) {
                        /*
                         * Make sure to move the open_handles list to back to
                         * the head if it's there (for refresh scenario)
                         */
                        if (global_hba_head->open_handles) {
                                new_hba->open_handles =
                                    global_hba_head->open_handles;
                                global_hba_head->open_handles = NULL;
                        }
                        /* Now bump the new one to the head of the list */
                        new_hba->next = global_hba_head;
                        global_hba_head = new_hba;
                } else {
                        global_hba_head = new_hba;
                }
                return (HBA_STATUS_OK);
        }

        while (portNode != DI_NODE_NIL) {
                if (di_prop_lookup_ints(DDI_DEV_T_ANY, portNode,
                    "virtual-port", &propIntData) >= 0) {
                        if (*propIntData) {
                                /* ignore a virtual port. */
                                portNode = di_sibling_node(portNode);
                                continue;
                        }
                }
                if (add_hba_port_info(portNode, new_hba, protocol)
                    == HBA_STATUS_ERROR) {
                        S_FREE(new_hba->first_port);
                        S_FREE(new_hba);
                        return (HBA_STATUS_ERROR);
                }
                portNode = di_sibling_node(portNode);
        }

        new_hba->index = hba_count++;

        /*
         * add newly created handle into global_hba_head list
         */
        if (global_hba_head != NULL) {
                /*
                 * Make sure to move the open_handles list to back to the
                 * head if it's there (for refresh scenario)
                 */
                if (global_hba_head->open_handles) {
                        new_hba->open_handles = global_hba_head->open_handles;
                        global_hba_head->open_handles = NULL;
                }
                /* Now bump the new one to the head of the list */
                new_hba->next = global_hba_head;
                global_hba_head = new_hba;
        } else {
                global_hba_head = new_hba;
        }

        return (HBA_STATUS_OK);
}

/*
 * Discover information for all HBAs found on the system.
 * The di_node_t argument should be the root of the device tree.
 * This routine assumes the locks have been taken
 */
static int
lookup_smhba_sas_hba(di_node_t node, void *arg)
{
        const char ROUTINE[] = "lookup_smhba_sas_hba";
        int *propData, rval;
        walkarg_t *wa = (walkarg_t *)arg;

        /* Skip stub(instance -1) nodes */
        if (IS_STUB_NODE(node)) {
                log(LOG_DEBUG, ROUTINE, "Walk continue");
                return (DI_WALK_CONTINUE);
        }

        rval = di_prop_lookup_ints(DDI_DEV_T_ANY, node,
            "sm-hba-supported", &propData);
        if (rval >= 0) {
                if (*propData) {
                        /* add the hba to the hba list */
                        if (devtree_get_one_hba(node) != HBA_STATUS_OK) {
                                *(wa->flag) = B_TRUE;
                        }
                        /* Found a node. No need to walk the child. */
                        log(LOG_DEBUG, ROUTINE, "Walk prunechild");
                        return (DI_WALK_PRUNECHILD);
                }
        }

        return (DI_WALK_CONTINUE);
}

/*
 * Discover information for all HBAs found on the system.
 * The di_node_t argument should be the root of the device tree.
 * This routine assumes the locks have been taken
 */
HBA_STATUS
devtree_get_all_hbas(di_node_t root)
{
        const char      ROUTINE[] = "devtree_get_all_hbas";
        int             rv, ret = HBA_STATUS_ERROR;
        walkarg_t       wa;

        wa.devpath = NULL;
        if ((wa.flag = (boolean_t *)calloc(1,
            sizeof (boolean_t))) == NULL) {
                OUT_OF_MEMORY(ROUTINE);
                return (HBA_STATUS_ERROR);
        }
        *wa.flag = B_FALSE;
        rv = di_walk_node(root, DI_WALK_SIBFIRST, &wa, lookup_smhba_sas_hba);

        if (rv == 0) {
                /*
                 * Now determine what status code to return, taking
                 * partial failure scenarios into consideration.
                 *
                 * If we have at least one working HBA, then we return an
                 * OK status.  If we have no good HBAs, but at least one
                 * failed HBA, we return an ERROR status.  If we have
                 * no HBAs and no failures, we return OK.
                 */
                if (global_hba_head) {
                        /*
                         * We've got at least one HBA and possibly some
                         * failures.
                         */
                        ret = HBA_STATUS_OK;
                } else if (*(wa.flag)) {
                        /* We have no HBAs but have failures */
                        ret = HBA_STATUS_ERROR;
                } else {
                        /* We have no HBAs and no failures */
                        ret = HBA_STATUS_OK;
                }
        }


        S_FREE(wa.flag);

        if (ret == HBA_STATUS_OK)
                (void) registerSysevent();

        return (ret);
}