2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include <devid.h>
2N/A#include <strings.h>
2N/A#include <stddef.h>
2N/A#include <fcntl.h>
2N/A#include <unistd.h>
2N/A#include <stropts.h>
2N/A#include <pthread.h>
2N/A#include <inttypes.h>
2N/A#include <fm/topo_mod.h>
2N/A#include <fm/topo_list.h>
2N/A#include <sys/scsi/scsi_address.h>
2N/A#include <sys/scsi/impl/scsi_sas.h>
2N/A#include <sys/fm/protocol.h>
2N/A#include <sys/utsname.h>
2N/A#include <sys/systeminfo.h>
2N/A#include <sys/pci.h>
2N/A#include <sys/pcie.h>
2N/A#include <sys/pci_tools.h>
2N/A#include <libdevinfo.h>
2N/A#include <bay.h>
2N/A
2N/Aextern int hba_node_cnt;
2N/A
2N/A/*
2N/A * These are routines that are used by both the bay.so topo enumerator and the
2N/A * $SRC/cmd/fm/fmti topo interactive utility.
2N/A */
2N/A
2N/A
2N/A/*
2N/A * Compare two strings.
2N/A */
2N/Aboolean_t
2N/Acmp_str(const char *s1, const char *s2)
2N/A{
2N/A if (strlen(s1) == strlen(s2) &&
2N/A strncmp(s1, s2, strlen(s2)) == 0) {
2N/A return (B_TRUE);
2N/A }
2N/A return (B_FALSE);
2N/A}
2N/A
2N/A/*
2N/A * Clear trailing blanks from product name/serial numbers.
2N/A */
2N/Achar *
2N/Actbl(char *s)
2N/A{
2N/A int i;
2N/A
2N/A /* remove any trailing blank spaces */
2N/A for (i = strlen(s) - 1; i > 0; i--) {
2N/A if (s[i] == ' ') {
2N/A s[i] = '\0';
2N/A } else {
2N/A /* stop at the first non-blank */
2N/A break;
2N/A }
2N/A }
2N/A
2N/A return (s);
2N/A}
2N/A
2N/A/*
2N/A * Callback for devtree walk.
2N/A *
2N/A * Look for SAS HBA nodes.
2N/A */
2N/Aint
2N/Agather_sas_hba(di_node_t dnode, void *arg)
2N/A{
2N/A int rv;
2N/A di_node_t parent_node = DI_NODE_NIL;
2N/A di_node_t *hba_nodes = (di_node_t *)arg;
2N/A char strp[MAXNAMELEN];
2N/A char model[MAXNAMELEN];
2N/A
2N/A rv = get_str_prop(dnode, DI_PATH_NIL, "model", model);
2N/A if (rv == 0) {
2N/A if (cmp_str(model, MODEL_SAS) ||
2N/A cmp_str(model, MODEL_SCSI)) {
2N/A /* make sure it's SAS direct attached */
2N/A if (sas_direct(dnode)) {
2N/A /* This is a SAS HBA node */
2N/A hba_nodes[hba_node_cnt] = dnode;
2N/A hba_node_cnt++;
2N/A }
2N/A }
2N/A } else {
2N/A /* look to see if we already captured it's parent */
2N/A parent_node = di_parent_node(dnode);
2N/A if (parent_node != DI_NODE_NIL) {
2N/A rv = get_str_prop(parent_node, DI_PATH_NIL, "model",
2N/A model);
2N/A if (rv == 0) {
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A rv = get_str_prop(parent_node, DI_PATH_NIL,
2N/A "ddi-vhci-class", model);
2N/A if (rv == 0) {
2N/A return (DI_WALK_CONTINUE);
2N/A }
2N/A }
2N/A
2N/A /* check for 'ddi-vhci-class' */
2N/A rv = get_str_prop(dnode, DI_PATH_NIL, "ddi-vhci-class", strp);
2N/A if (rv == 0 && cmp_str(strp, "scsi_vhci")) {
2N/A /* make sure it's SAS direct attached */
2N/A if (sas_direct(dnode)) {
2N/A /* This is a SAS HBA node */
2N/A hba_nodes[hba_node_cnt] = dnode;
2N/A hba_node_cnt++;
2N/A }
2N/A }
2N/A }
2N/A
2N/A if (hba_node_cnt == MAX_HBAS) {
2N/A return (DI_WALK_TERMINATE);
2N/A }
2N/A
2N/A return (DI_WALK_CONTINUE);
2N/A}
2N/A
2N/A/*
2N/A * Create the configuration file path/name. Attempt to create in this order:
2N/A * 1. ../<product>-<chassis-sn>,bay_labels
2N/A * 2. ../<product>,bay_labels
2N/A * 3. ../<platform>,bay_labels
2N/A */
2N/Avoid
2N/Agen_ofile_name(char *prod, char *csn, char *filenm)
2N/A{
2N/A char platform[MAXNAMELEN];
2N/A char *prod_name = malloc(MAXNAMELEN);
2N/A char *ch_sn = malloc(MAXNAMELEN);
2N/A int prod_len = 0;
2N/A int sn_len = 0;
2N/A
2N/A /* retain raw name/sn */
2N/A bcopy(prod, prod_name, strlen(prod) + 1);
2N/A bcopy(csn, ch_sn, strlen(csn) + 1);
2N/A
2N/A /* clean strings */
2N/A prod_name = di_cro_strclean(prod_name, 1, 1);
2N/A ch_sn = di_cro_strclean(ch_sn, 1, 1);
2N/A
2N/A /* get platform name */
2N/A platform[0] = '\0';
2N/A (void) sysinfo(SI_PLATFORM, platform, sizeof (platform));
2N/A
2N/A if (!cmp_str(prod_name, BAY_PROP_UNKNOWN)) {
2N/A prod_len = strlen(prod_name);
2N/A }
2N/A if (!cmp_str(ch_sn, BAY_PROP_UNKNOWN)) {
2N/A sn_len = strlen(ch_sn);
2N/A }
2N/A
2N/A if (prod_len > 0 && sn_len > 0) {
2N/A /* ../<product>-<product-sn>,bay_labels */
2N/A (void) snprintf(filenm, MAXNAMELEN, BAY_CONFIG,
2N/A platform, prod_name, ch_sn);
2N/A } else if (prod_len > 0 && sn_len == 0) {
2N/A /* ../<product>,bay_labels */
2N/A (void) snprintf(filenm, MAXNAMELEN, BAY_CONF_FILE_1,
2N/A platform, prod_name);
2N/A } else {
2N/A /* ../<platform>,bay_labels */
2N/A (void) snprintf(filenm, MAXNAMELEN, BAY_CONF_FILE_1,
2N/A platform, platform);
2N/A }
2N/A
2N/A /* clean up */
2N/A free(prod_name);
2N/A free(ch_sn);
2N/A}
2N/A
2N/A/*
2N/A * Get integer property.
2N/A */
2N/Aint
2N/Aget_int_prop(di_node_t dnode, di_path_t pnode, char *prop_name)
2N/A{
2N/A int prop_val;
2N/A int *buf;
2N/A unsigned char *chbuf;
2N/A di_path_t path_node = DI_PATH_NIL;
2N/A di_prop_t di_prop = DI_PROP_NIL;
2N/A di_path_prop_t di_path_prop = DI_PROP_NIL;
2N/A
2N/A if (prop_name == NULL ||
2N/A (dnode == DI_NODE_NIL && pnode == DI_PATH_NIL)) {
2N/A return (-1);
2N/A }
2N/A
2N/A /* look for pathinfo property */
2N/A if (pnode != DI_PATH_NIL) {
2N/A while ((di_path_prop =
2N/A di_path_prop_next(pnode, di_path_prop)) != DI_PROP_NIL) {
2N/A if (strcmp(prop_name,
2N/A di_path_prop_name(di_path_prop)) == 0) {
2N/A (void) di_path_prop_ints(di_path_prop, &buf);
2N/A bcopy(buf, &prop_val, sizeof (int));
2N/A goto found;
2N/A }
2N/A }
2N/A }
2N/A
2N/A /* look for devinfo property */
2N/A if (dnode != DI_NODE_NIL) {
2N/A for (di_prop = di_prop_next(dnode, DI_PROP_NIL);
2N/A di_prop != DI_PROP_NIL;
2N/A di_prop = di_prop_next(dnode, di_prop)) {
2N/A if (cmp_str(prop_name, di_prop_name(di_prop))) {
2N/A if (di_prop_bytes(di_prop, &chbuf) <
2N/A sizeof (uint_t)) {
2N/A continue;
2N/A }
2N/A bcopy(chbuf, &prop_val, sizeof (uint_t));
2N/A goto found;
2N/A }
2N/A }
2N/A
2N/A /* dig some more */
2N/A while ((path_node = di_path_phci_next_path(dnode, path_node)) !=
2N/A DI_PATH_NIL) {
2N/A while ((di_path_prop = di_path_prop_next(path_node,
2N/A di_path_prop)) != DI_PROP_NIL) {
2N/A if (strcmp(prop_name,
2N/A di_path_prop_name(di_path_prop)) == 0) {
2N/A (void) di_path_prop_ints(di_path_prop,
2N/A &buf);
2N/A bcopy(buf, &prop_val, sizeof (int));
2N/A goto found;
2N/A }
2N/A }
2N/A }
2N/A }
2N/A
2N/A /* property not found */
2N/A return (-1);
2N/Afound:
2N/A return (prop_val);
2N/A}
2N/A
2N/A/*
2N/A * Get the phy from the "attached-port-pm" property which is
2N/A * stored as a bit mask (1 << phy-num) string.
2N/A * If there is no phy mask property look for 'phy-num' property which
2N/A * is the PHY (potentially deprecated at some point). SATA mpt drives
2N/A * PHY is the target value.
2N/A */
2N/Aint
2N/Aget_phy(di_node_t dnode, di_path_t pnode)
2N/A{
2N/A int rv;
2N/A int phy = -1;
2N/A char p[MAXNAMELEN];
2N/A
2N/A /* first look for (SAS) 'attached-port-pm' */
2N/A rv = get_str_prop(dnode, pnode, SCSI_ADDR_PROP_ATTACHED_PORT_PM, p);
2N/A if (rv != 0 && dnode != DI_NODE_NIL) {
2N/A /* look at the child devinfo node */
2N/A rv = get_str_prop(di_child_node(dnode), DI_PATH_NIL,
2N/A SCSI_ADDR_PROP_ATTACHED_PORT_PM, p);
2N/A }
2N/A if (rv == 0) {
2N/A int i, phy_mask;
2N/A char *endp;
2N/A phy_mask = (int)strtol(p, &endp, 16);
2N/A for (i = 1; i < MAX_PM_SHIFT; i++) {
2N/A if ((phy_mask >> i) == 0x0) {
2N/A phy = (int)(i - 1);
2N/A break;
2N/A }
2N/A }
2N/A goto found;
2N/A }
2N/A
2N/A /* next look for (SAS) 'phy-num' */
2N/A phy = get_int_prop(dnode, pnode, PHY_NUM);
2N/A if (phy == -1 && dnode != DI_NODE_NIL) {
2N/A /* look at the child devinfo node */
2N/A phy = get_int_prop(di_child_node(dnode), DI_PATH_NIL, PHY_NUM);
2N/A }
2N/A if (phy != -1) {
2N/A goto found;
2N/A }
2N/A
2N/A /* must be a SATA drive, first look for 'sata-phy' */
2N/A phy = get_int_prop(dnode, pnode, SCSI_ADDR_PROP_SATA_PHY);
2N/A if (phy != -1) {
2N/A goto found;
2N/A }
2N/A
2N/A /* lastly for SATA drive, look for 'target' */
2N/A phy = get_int_prop(dnode, pnode, SCSI_ADDR_PROP_TARGET);
2N/A if (phy != -1) {
2N/A goto found;
2N/A }
2N/A
2N/A return (-1);
2N/Afound:
2N/A return (phy);
2N/A}
2N/A
2N/A/*
2N/A * Get string property.
2N/A */
2N/Aint
2N/Aget_str_prop(di_node_t dnode, di_path_t pnode, char *prop_name, char *prop_out)
2N/A{
2N/A char *prop_val = NULL;
2N/A di_path_t path_node = DI_PATH_NIL;
2N/A di_prop_t di_prop = DI_PROP_NIL;
2N/A di_path_prop_t di_path_prop = DI_PROP_NIL;
2N/A
2N/A if (prop_name == NULL ||
2N/A (dnode == DI_NODE_NIL && pnode == DI_PATH_NIL)) {
2N/A return (-1);
2N/A }
2N/A
2N/A /* look for pathinfo property */
2N/A if (pnode != DI_PATH_NIL) {
2N/A while ((di_path_prop =
2N/A di_path_prop_next(pnode, di_path_prop)) != DI_PROP_NIL) {
2N/A if (strcmp(prop_name,
2N/A di_path_prop_name(di_path_prop)) == 0) {
2N/A (void) di_path_prop_strings(di_path_prop,
2N/A &prop_val);
2N/A goto found;
2N/A }
2N/A }
2N/A }
2N/A
2N/A /* look for devinfo property */
2N/A if (dnode != DI_NODE_NIL) {
2N/A for (di_prop = di_prop_next(dnode, DI_PROP_NIL);
2N/A di_prop != DI_PROP_NIL;
2N/A di_prop = di_prop_next(dnode, di_prop)) {
2N/A if (cmp_str(prop_name, di_prop_name(di_prop))) {
2N/A if (di_prop_strings(di_prop, &prop_val) < 0) {
2N/A continue;
2N/A }
2N/A goto found;
2N/A }
2N/A }
2N/A
2N/A /* dig some more */
2N/A while ((path_node = di_path_phci_next_path(dnode, path_node)) !=
2N/A DI_PATH_NIL) {
2N/A while ((di_path_prop = di_path_prop_next(path_node,
2N/A di_path_prop)) != DI_PROP_NIL) {
2N/A if (strcmp(prop_name,
2N/A di_path_prop_name(di_path_prop)) == 0) {
2N/A (void)
2N/A di_path_prop_strings(di_path_prop,
2N/A &prop_val);
2N/A goto found;
2N/A }
2N/A }
2N/A }
2N/A }
2N/A
2N/A /* property not found */
2N/A return (-1);
2N/Afound:
2N/A bcopy(prop_val, prop_out, strlen(prop_val) + 1);
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * All iport devi nodes should have the 'initiator-port' property. Look for
2N/A * a matching 'attached-port' property. But that's not always the case so we
2N/A * use node1 and node2 notation.
2N/A */
2N/Aboolean_t
2N/Ai_direct(di_node_t dnode, char *node1_prop_str, char *node2_prop_str)
2N/A{
2N/A int rv;
2N/A di_node_t cnode = DI_NODE_NIL;
2N/A di_path_t pnode = DI_PATH_NIL;
2N/A char ap[MAXNAMELEN];
2N/A char ip[MAXNAMELEN];
2N/A
2N/A /* look for devi 'initiator-port' (or equivalent) */
2N/A rv = get_str_prop(dnode, DI_PATH_NIL, node1_prop_str, ip);
2N/A if (rv != 0) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A /*
2N/A * look for either a child devinfo or a pathinfo node that
2N/A * contains a 'attached-port' (or equivalent) property to match.
2N/A */
2N/A cnode = di_child_node(dnode);
2N/A if (cnode != DI_NODE_NIL) {
2N/A rv = get_str_prop(cnode, DI_PATH_NIL, node2_prop_str, ap);
2N/A if (rv == 0) {
2N/A if (cmp_str(ip, ap)) {
2N/A /* make sure it's not smp */
2N/A if (cmp_str("sd", di_node_name(cnode)) ||
2N/A cmp_str("disk", di_node_name(cnode)))
2N/A return (B_TRUE);
2N/A }
2N/A }
2N/A }
2N/A
2N/A pnode = di_path_phci_next_path(dnode, DI_PATH_NIL);
2N/A if (pnode != DI_PATH_NIL) {
2N/A rv = get_str_prop(DI_NODE_NIL, pnode, node2_prop_str, ap);
2N/A if (rv == 0) {
2N/A if (cmp_str(ip, ap)) {
2N/A /* make sure it's not smp */
2N/A if (cmp_str("sd", di_path_node_name(pnode)) ||
2N/A cmp_str("disk", di_path_node_name(pnode)))
2N/A return (B_TRUE);
2N/A }
2N/A }
2N/A }
2N/A return (B_FALSE);
2N/A}
2N/A
2N/A/*
2N/A * For HBAs "other" than ones that support iport:
2N/A * - first check that it's not attached to an expander
2N/A * - then see if it's direct attached
2N/A */
2N/Aboolean_t
2N/Ao_direct(di_node_t dnode)
2N/A{
2N/A int cnt, nphys;
2N/A boolean_t ret = B_FALSE;
2N/A di_node_t cnode = DI_NODE_NIL;
2N/A di_path_t pnode = DI_PATH_NIL;
2N/A
2N/A /*
2N/A * Unfortunately some HBA drivers don't differentiate between direct
2N/A * attached and expander attached storage via properties. Need to
2N/A * determine if this HBA is connected to an expander.
2N/A */
2N/A nphys = get_int_prop(dnode, DI_PATH_NIL, "num-phys");
2N/A
2N/A /* count devinfo child nodes */
2N/A if ((cnode = di_child_node(dnode)) != DI_NODE_NIL) {
2N/A /* count devinfo children */
2N/A cnt = 1;
2N/A while ((cnode = di_sibling_node(cnode)) != DI_NODE_NIL) {
2N/A ++cnt;
2N/A }
2N/A if (cnt > nphys) {
2N/A /* expander */
2N/A goto out;
2N/A }
2N/A }
2N/A
2N/A /* count pathinfo nodes */
2N/A if ((pnode = di_path_phci_next_path(dnode, DI_PATH_NIL)) !=
2N/A DI_PATH_NIL) {
2N/A /* count pathinfo nodes */
2N/A cnt = 1;
2N/A while ((pnode = di_path_phci_next_path(dnode, pnode)) !=
2N/A DI_PATH_NIL) {
2N/A ++cnt;
2N/A }
2N/A if (cnt > nphys) {
2N/A /* expander */
2N/A goto out;
2N/A }
2N/A }
2N/A
2N/A /* check for 'sd' or 'disk' devinfo nodes */
2N/A cnode = di_child_node(dnode);
2N/A if (cnode != DI_NODE_NIL &&
2N/A (cmp_str("sd", di_node_name(cnode)) ||
2N/A cmp_str("disk", di_node_name(cnode)))) {
2N/A ret = B_TRUE;
2N/A goto out;
2N/A }
2N/A
2N/A /* final check for 'sd' or 'disk' pathinfo nodes */
2N/A pnode = di_path_phci_next_path(dnode, DI_PATH_NIL);
2N/A if (pnode != DI_PATH_NIL &&
2N/A (cmp_str("sd", di_path_node_name(pnode)) ||
2N/A cmp_str("disk", di_path_node_name(pnode)))) {
2N/A ret = B_TRUE;
2N/A }
2N/Aout:
2N/A return (ret);
2N/A}
2N/A
2N/A/*
2N/A * Different SAS HBA driver (children) report they're direct attached
2N/A * differently. MPxIO also determines how to decide if this is sas direct.
2N/A *
2N/A * For HBAs that support iport, each will contain the '*-port' properties which
2N/A * determine direct attached.
2N/A *
2N/A * For HBAs that don't support iport, the direct attached hba will have a
2N/A * "disk"/"sd" devinfo child or pathinfo node.
2N/A *
2N/A * Direct Attached:
2N/A * mpt : !expander && child/path node name == "sd" || "disk"
2N/A * mpt_sas/scu : iport 'initiator-port' ==> device 'attached-port'
2N/A * pmcs : iport 'attached-port' ==> device 'target-port'
2N/A *
2N/A */
2N/Aboolean_t
2N/Asas_direct(di_node_t dnode)
2N/A{
2N/A di_node_t iport_dnode = DI_NODE_NIL;
2N/A
2N/A if (dnode == DI_NODE_NIL) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A /* look for child iport node */
2N/A iport_dnode = di_child_node(dnode);
2N/A while (iport_dnode != DI_NODE_NIL) {
2N/A if (cmp_str("iport", di_node_name(iport_dnode))) {
2N/A /* 'initiator-port' == 'attached-port' */
2N/A if (i_direct(iport_dnode, SCSI_ADDR_PROP_INITIATOR_PORT,
2N/A SCSI_ADDR_PROP_ATTACHED_PORT) == B_TRUE)
2N/A return (B_TRUE);
2N/A /* 'attached-port' == 'target-port' */
2N/A if (i_direct(iport_dnode, SCSI_ADDR_PROP_ATTACHED_PORT,
2N/A SCSI_ADDR_PROP_TARGET_PORT) == B_TRUE)
2N/A return (B_TRUE);
2N/A }
2N/A iport_dnode = di_sibling_node(iport_dnode);
2N/A }
2N/A
2N/A /* no iport; check other indicators */
2N/A return (o_direct(dnode));
2N/A}
2N/A
2N/A/*
2N/A * Callback for topo walk looking for hba label by matching the devfs path
2N/A * passed in with the 'dev' property of the topo node.
2N/A */
2N/A/* ARGSUSED */
2N/Aint
2N/Ath_hba_l(topo_hdl_t *thp, tnode_t *tnp, void *arg)
2N/A{
2N/A int rv;
2N/A int err;
2N/A int ret;
2N/A char *dpath = NULL;
2N/A char *label = NULL;
2N/A char *nodename = NULL;
2N/A tw_pcie_cbs_t *cbp = (tw_pcie_cbs_t *)arg;
2N/A char *hba_path = cbp->devfs_path;
2N/A
2N/A /* see if we're as PCIe function node */
2N/A nodename = topo_node_name(tnp);
2N/A if (!cmp_str(nodename, PCIEX_FUNCTION)) {
2N/A ret = TOPO_WALK_NEXT;
2N/A goto out;
2N/A }
2N/A
2N/A /* look for dev (TOPO_IO_DEV) path in (TOPO_PGROUP_IO) property */
2N/A rv = topo_prop_get_string(tnp, TOPO_PGROUP_IO, TOPO_IO_DEV,
2N/A &dpath, &err);
2N/A if (rv != 0) {
2N/A ret = TOPO_WALK_NEXT;
2N/A goto out;
2N/A }
2N/A
2N/A /* check for matching paths */
2N/A if (!cmp_str(dpath, hba_path)) {
2N/A ret = TOPO_WALK_NEXT;
2N/A goto out;
2N/A }
2N/A
2N/A /* path matches, get the label */
2N/A rv = topo_prop_get_string(tnp, TOPO_PGROUP_PROTOCOL,
2N/A TOPO_PROP_LABEL, &label, &err);
2N/A if (rv != 0 || label == NULL) {
2N/A ret = TOPO_WALK_NEXT;
2N/A goto out;
2N/A }
2N/A
2N/A /* copy the label */
2N/A bcopy(label, cbp->label, strlen(label) + 1);
2N/Aout:
2N/A /* clean up */
2N/A if (cbp->mod != NULL) {
2N/A if (dpath != NULL) {
2N/A topo_mod_strfree(cbp->mod, dpath);
2N/A }
2N/A if (label != NULL) {
2N/A topo_mod_strfree(cbp->mod, label);
2N/A }
2N/A } else if (cbp->hdl != NULL) {
2N/A if (dpath != NULL) {
2N/A topo_hdl_strfree(cbp->hdl, dpath);
2N/A }
2N/A if (label != NULL) {
2N/A topo_hdl_strfree(cbp->hdl, label);
2N/A }
2N/A }
2N/A return (ret);
2N/A}