ses.c revision d91236fe104c7ea63142e053b22a39c8a30d304b
/*
* 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
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <dirent.h>
#include <devid.h>
#include <fm/libdiskstatus.h>
#include <inttypes.h>
#include <pthread.h>
#include <strings.h>
#include <unistd.h>
#include "disk.h"
#include "ses.h"
#define SES_VERSION 1
#define SES_STATUS_UNAVAIL(s) \
((s) == SES_ESC_UNSUPPORTED || (s) >= SES_ESC_UNKNOWN)
/*
* Because multiple SES targets can be part of a single chassis, we construct
* our own hierarchy that takes this into account. These SES targets may refer
* to the same devices (multiple paths) or to different devices (managing
* different portions of the space). We arrange things into a
* ses_enum_enclosure_t, which contains a set of ses targets, and a list of all
* nodes found so far.
*/
typedef struct ses_enum_target {
struct timeval set_snaptime;
char *set_devpath;
int set_refcount;
typedef struct ses_enum_node {
typedef struct ses_enum_chassis {
const char *sec_csn;
typedef struct ses_enum_data {
int sed_errno;
char *sed_name;
nvlist_t **);
nvlist_t **);
static const topo_method_t ses_component_methods[] = {
{ NULL }
};
static const topo_method_t ses_bay_methods[] = {
{ NULL }
};
static const topo_method_t ses_enclosure_methods[] = {
{ NULL }
};
static void
{
if (--stp->set_refcount == 0) {
}
}
static void
{
}
}
}
}
/*
* For enclosure nodes, we have a special contains method. By default, the hc
* walker will compare the node name and instance number to determine if an
* FMRI matches. For enclosures where the enumeration order is impossible to
* predict, we instead use the chassis-id as a unique identifier, and ignore
* the instance number.
*/
static int
{
int err, i;
int mindepth;
if (err != 0)
/*
* If the chassis-id doesn't match, then these FMRIs are not
* equivalent. If one of the FMRIs doesn't have a chassis ID, then we
* have no choice but to fall back to the instance ID.
*/
return (0);
mindepth = 1;
} else {
mindepth = 0;
}
return (0);
for (i = 0; i < nhcp1; i++) {
continue;
return (0);
}
return (1);
}
/*ARGSUSED*/
static int
{
int ret;
if (ret < 0)
return (-1);
ret) == 0)
return (0);
else
nvlist_free(*out);
}
return (-1);
}
/*
* Return a current instance of the node. This is somewhat complicated because
* we need to take a new snapshot in order to get the new data, but we don't
* want to be constantly taking SES snapshots if the consumer is going to do a
* series of queries. So we adopt the strategy of assuming that the SES state
* is not going to be rapidly changing, and limit our snapshot frequency to
* some defined bounds.
*/
{
int err;
ses_node_t *np;
return (NULL);
}
/*
* Determine if we need to take a new snapshot.
*/
}
if (ses_snap_generation(snap) !=
/*
* If we find ourselves in this situation, we're in
* trouble. The generation count has changed, which
* indicates that our current topology is out of date.
* But we need to consult the new topology in order to
* determine presence at this moment in time. We can't
* go back and change the topo snapshot in situ, so
* we'll just have to fail the call in this unlikely
* scenario.
*/
return (NULL);
} else {
}
}
return (np);
}
/*
* Determine if the element is present.
*/
/*ARGSUSED*/
static int
{
ses_node_t *np;
return (-1);
SES_PROP_STATUS_CODE, &status) == 0);
present) != 0) {
}
return (0);
}
/*
* Sets standard properties for a ses node (enclosure or bay). This includes
* setting the FRU to be the same as the resource, as well as setting the
* authority information.
*/
static int
{
int err;
/*
* Set the authority explicitly if specified.
*/
if (auth) {
&product) == 0);
&chassis) == 0);
&err) != 0 ||
&err) != 0 ||
&err) != 0) {
}
}
/*
* Copy the resource and set that as the FRU.
*/
"topo_node_resource() failed : %s\n",
topo_strerror(err));
}
"topo_node_fru_set() failed : %s\n",
topo_strerror(err));
}
/*
* Set the SES-specific properties so that consumers can query
* additional information about the particular SES element.
*/
return (-1);
}
"failed to create property %s: %s\n",
return (-1);
}
"failed to create property %s: %s\n",
return (-1);
}
return (0);
}
/*
* Callback to add a disk to a given bay. We first check the status-code to
* determine if a disk is present, ignoring those that aren't in an appropriate
* state. We then scan the sas-phys array to determine the attached SAS
* address. We create a disk node regardless of whether the SES target is SAS
* and supports the necessary pages. If we do find a SAS address, we correlate
* this to the corresponding Solaris device node to fill in the rest of the
* data.
*/
static int
{
char buf[17];
/*
* Skip devices that are not in a present (and possibly damaged) state.
*/
return (0);
if (status != SES_ESC_OK &&
status != SES_ESC_CRITICAL &&
status != SES_ESC_NONCRITICAL &&
status != SES_ESC_UNRECOVERABLE &&
return (0);
/*
* Create the disk range.
*/
"topo_node_create_range() failed: %s",
return (-1);
}
/*
* Look through all SAS addresses and attempt to correlate them to a
* known Solaris device. If we don't find a matching node, then we
* don't enumerate the disk node.
*/
return (0);
for (s = 0; s < nsas; s++) {
SES_SAS_PROP_ADDR, &addr) == 0);
if (addr == 0)
continue;
buf) != 0)
return (-1);
}
return (0);
}
/*
* Callback to create a basic node (bay, psu, fan, or controller).
*/
static int
{
char label[128];
int err;
char *desc;
/*
* Create the node. The interesting information is all copied from the
* parent enclosure node, so there is not much to do.
*/
goto error;
/*
* We want to report revision information for the controller nodes, but
* we do not get per-element revision information. However, we do have
* revision information for the entire enclosure, and we can use the
* 'reported-via' property to know that this controller corresponds to
* the given revision information. This means we cannot get revision
* information for targets we are not explicitly connected to, but
* there is little we can do about the situation.
*/
report) {
(void) nvlist_lookup_string(
break;
}
}
}
goto error;
}
goto error;
}
/*
* For the node label, we look for the following in order:
*
* <ses-description>
* <ses-class-description> <instance>
* <default-type-label> <instance>
*/
desc[0] == '\0') {
instance);
}
goto error;
goto error;
goto error;
"topo_method_register() failed: %s",
goto error;
}
} else {
/*
* Only fan, psu, and controller nodes have a 'present' method.
* Bay nodes are always present, and disk nodes are present by
* virtue of being enumerated.
*/
"topo_method_register() failed: %s",
goto error;
}
}
return (0);
return (-1);
}
/*
* Instantiate any children of a given type.
*/
static int
{
/*
* First go through and count how many matching nodes we have.
*/
max = 0;
}
}
/*
* No enclosure should export both DEVICE and ARRAY_DEVICE elements.
* Since we map both of these to 'disk', if an enclosure does this, we
* just ignore the array elements.
*/
if (!found ||
return (0);
"topo_node_create_range() failed: %s",
return (-1);
}
nodename, defaultlabel) != 0)
return (-1);
}
}
return (0);
}
/*
* Instantiate a new chassis instance in the topology.
*/
static int
{
char *serial;
int ret = -1;
/*
* Ignore any internal enclosures.
*/
if (cp->sec_internal)
return (0);
/*
* Check to see if there are any devices presennt in the chassis. If
* not, ignore the chassis alltogether. This is most useful for
* ignoring internal HBAs that present a SES target but don't actually
* manage any of the devices.
*/
break;
}
return (0);
/*
* We use the following property mappings:
*
* manufacturer vendor-id
* model product-id
* serial-number libses-chassis-serial
*/
&raw_manufacturer) == 0);
&raw_revision) == 0);
/*
* To construct the authority information, we 'clean' each string by
* removing any offensive characters and trimmming whitespace. For the
* 'product-id', we use a concatenation of 'manufacturer-model'. We
* also take the numerical serial number and convert it to a string.
*/
goto error;
}
goto error;
/*
* Construct the topo node and bind it to our parent.
*/
goto error;
goto error;
}
/*
* We pass NULL for the parent FMRI because there is no resource
* associated with it. For the toplevel enclosure, we leave the
* individual components within the chassis.
*/
goto error;
}
goto error;
}
"topo_method_register() failed: %s",
goto error;
}
goto error;
/*
* Create the nodes for power supplies, fans, and devices.
*/
goto error;
ret = 0;
return (ret);
}
/*
* Create a bay node explicitly enumerated via XML.
*/
static int
{
/*
* Iterate over chassis looking for an internal enclosure. This
* property is set via a vendor-specific plugin, and there should only
* ever be a single internal chassis in a system.
*/
if (cp->sec_internal)
break;
}
return (-1);
}
return (-1);
return (0);
}
/*
* Gather nodes from the current SES target into our chassis list, merging the
* results if necessary.
*/
static ses_walk_action_t
{
char *csn;
/*
* If we have already identified the chassis for this target,
* then this is a secondary enclosure and we should ignore it,
* along with the rest of the tree (since this is depth-first).
*/
return (SES_WALK_ACTION_TERMINATE);
/*
* Go through the list of chassis we have seen so far and see
* if this serial number matches one of the known values.
*/
&csn) != 0)
return (SES_WALK_ACTION_TERMINATE);
break;
}
}
sizeof (ses_enum_chassis_t))) == NULL)
goto error;
LIBSES_EN_PROP_INTERNAL, &internal) == 0)
}
/*
* If we haven't yet seen an enclosure node and identified the
* current chassis, something is very wrong; bail out.
*/
return (SES_WALK_ACTION_TERMINATE);
/*
* If this isn't one of the element types we care about, then
* ignore it.
*/
&type) == 0);
if (type != SES_ET_DEVICE &&
type != SES_ET_ARRAY_DEVICE &&
type != SES_ET_COOLING &&
type != SES_ET_POWER_SUPPLY &&
return (SES_WALK_ACTION_CONTINUE);
/*
* Get the current instance number and see if we already know
* about this element. If so, it means we have multiple paths
* to the same elements, and we should ignore the current path.
*/
&instance) == 0);
&instance);
break;
}
/*
* We prefer the new element under the following circumstances:
*
* - The currently known element's status is unknown or not
* available, but the new element has a known status. This
* occurs if a given element is only available through a
* particular target.
*
* - This is an ESC_ELECTRONICS element, and the 'reported-via'
* property is set. This allows us to get reliable firmware
* revision information from the enclosure node.
*/
if (nvlist_lookup_uint64(
SES_PROP_STATUS_CODE, &prevstatus) != 0)
if (nvlist_lookup_uint64(
if ((SES_STATUS_UNAVAIL(prevstatus) &&
!SES_STATUS_UNAVAIL(status)) ||
(type == SES_ET_ESC_ELECTRONICS &&
report)) {
}
return (SES_WALK_ACTION_CONTINUE);
}
sizeof (ses_enum_node_t))) == NULL)
goto error;
if (type == SES_ET_DEVICE)
}
return (SES_WALK_ACTION_CONTINUE);
return (SES_WALK_ACTION_TERMINATE);
}
static int
{
int err = -1;
/*
* Open the SES target directory and iterate over any available
* targets.
*/
/*
* If the SES target directory does not exist, then return as if
* there are no active targets.
*/
"directory '%s'", dirpath);
return (0);
}
continue;
/*
* Create a new target instance and take a snapshot.
*/
sizeof (ses_enum_target_t))) == NULL)
goto error;
/*
* We keep track of the SES device path and export it on a
* per-node basis to allow higher level software to get to the
* corresponding SES state.
*/
goto error;
}
if ((stp->set_target =
continue;
}
/*
* Enumerate over all SES elements and merge them into the
* correct ses_enum_chassis_t.
*/
goto error;
}
err = 0;
return (err);
}
static void
{
}
/*ARGSUSED*/
static int
{
/*
* Check to make sure we're being invoked sensibly, and that we're not
* being invoked as part of a post-processing step.
*/
return (0);
/*
* If this is the first time we've called our enumeration method, then
* gather information about any available enclosures.
*/
NULL)
return (-1);
goto error;
/*
* We search both the ses(7D) and sgen(7D) locations, so we are
* independent of any particular driver class bindings.
*/
goto error;
}
/*
* This is a request to enumerate external enclosures. Go
* through all the targets and create chassis nodes where
* necessary.
*/
goto error;
}
} else {
/*
* This is a request to enumerate a specific bay underneath the
* root chassis (for internal disks).
*/
goto error;
}
/*
* This is a bit of a kludge. In order to allow internal disks to be
* enumerated and share snapshot-specific information with the external
* enclosure enumeration, we rely on the fact that we will be invoked
* for the 'ses-enclosure' node last.
*/
}
return (0);
return (-1);
}
static const topo_modops_t ses_ops =
{ ses_enum, ses_release };
static topo_modinfo_t ses_info =
/*ARGSUSED*/
int
{
}
void
{
}