/*
* 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
*/
/*
*/
#include <assert.h>
#include <errno.h>
#include <libdiskstatus.h>
#include <limits.h>
#include <stdlib.h>
#include <strings.h>
#include "ds_scsi.h"
#include "ds_scsi_sim.h"
#include "ds_scsi_uscsi.h"
typedef struct ds_scsi_info {
void *si_sim;
int si_cdblen;
int si_supp_mode;
int si_supp_log;
int si_extensions;
int si_reftemp;
/*
* Table to validate log pages
*/
scsi_log_parameter_header_t *, int, nvlist_t *);
scsi_log_parameter_header_t *, int);
typedef struct logpage_validation_entry {
int ve_supported;
const char *ve_desc;
static int logpage_ie_verify(ds_scsi_info_t *,
scsi_log_parameter_header_t *, int, nvlist_t *);
static int logpage_temp_verify(ds_scsi_info_t *,
scsi_log_parameter_header_t *, int, nvlist_t *);
static int logpage_selftest_verify(ds_scsi_info_t *,
scsi_log_parameter_header_t *, int, nvlist_t *);
static int logpage_ie_analyze(ds_scsi_info_t *,
scsi_log_parameter_header_t *, int);
static int logpage_temp_analyze(ds_scsi_info_t *,
scsi_log_parameter_header_t *, int);
static int logpage_selftest_analyze(ds_scsi_info_t *,
scsi_log_parameter_header_t *, int);
"informational-exceptions",
"temperature",
"self-test",
};
/*
* Given an extended sense page, retrieves the sense key, as well as the
* additional sense code information.
*/
static void
{
(struct scsi_descr_sense_hdr *)rq;
/*
* Get asc, ascq and info field from sense data. There are two
* possible formats (fixed sense data and descriptor sense data)
* depending on the value of es_code.
*/
case CODE_FMT_DESCR_CURRENT:
case CODE_FMT_DESCR_DEFERRED:
break;
case CODE_FMT_FIXED_CURRENT:
case CODE_FMT_FIXED_DEFERRED:
default:
} else {
*ascp = 0xff;
*ascqp = 0xff;
}
break;
}
}
/*
* Routines built atop the bare uscsi commands, which take into account the
* command length, automatically translate any scsi errors, and transparently
* call into the simulator if active.
*/
static int
{
int result;
} else {
options &= ~MODE_SELECT_SP;
}
/* The following fields are reserved during mode select: */
else
} else {
/* The following fields are reserved during mode select: */
else
}
if (result != 0)
return (result);
}
static int
{
int result;
else
&senselen);
} else {
else
}
if (result != 0)
return (result);
}
static int
{
int result;
else
if (result == 0)
else
return (result);
}
static int
{
int result;
&senselen);
else
&senselen);
if (result != 0)
return (result);
}
/*
* Given a list of supported mode pages, determine if the given page is present.
*/
static boolean_t
{
uint_t i = 0;
/*
* The mode page list contains all mode pages supported by the device,
* one after the other.
*/
while (i < pgdatalen) {
break;
}
i += MODESENSE_PAGE_LEN(pg);
}
return (found);
}
/*
* Load mode pages and check that the appropriate pages are supported.
*
* As part of this process, we determine which form of the MODE SENSE / MODE
* SELECT command to use (the 6-byte or 10-byte version) by executing a MODE
* SENSE command for a page that should be implemented by the device.
*/
static int
{
int allpages_buflen;
int result;
int datalength = 0;
/*
* Attempt a mode sense(6). If that fails, try a mode sense(10)
*
* allpages is allocated to be of the maximum size for either a mode
* sense(6) or mode sense(10) MODEPAGE_ALLPAGES response.
*
* Note that the length passed into uscsi_mode_sense should be set to
* the maximum size of the parameter response, which in this case is
*/
/*
* Compute the data length of the page that contains all mode
* sense pages. This is a bit tricky because the format of the
* response from the lun is:
*
* header: <length> <medium type byte> <dev specific byte>
* <block descriptor length>
* [<optional block descriptor>]
* data: [<mode page data> <mode page data> ...]
*
* Since the length field in the header describes the length of
* the entire response. This includes the header, but NOT
* the length field itself, which is 1 or 2 bytes depending on
* which mode sense type (6- or 10- byte) is being executed.
*
* So, the data length equals the length value in the header
* plus 1 (because the length byte was not included in the
* length count), minus [[the sum of the length of the header
* and the length of the block descriptor]].
*/
(sizeof (struct mode_header) +
/*
* Fallback and try the 10-byte version of the command.
*/
if (result == 0) {
(sizeof (struct mode_header_g1) +
}
}
if (result == 0 && datalength >= 0) {
}
nvl) != 0) {
}
/*
* One of the sets of the commands (above) succeeded, so now
* look for the mode pages we need and record them appropriately
* Specifically, we are looking to see if the disk supports
* MODEPAGE_INFO_EXCPT. If so, we will mark it as supported.
* Note that examining this mode page for data will occur only
* if the LOGPAGE for IE (SMART) data is not supported.
*/
"informational-exceptions", nvl) != 0) {
}
}
} else {
/*
* If the device failed to respond to one of the basic commands,
* then assume it's not a SCSI device or otherwise doesn't
* support the necessary transport.
*/
if (datalength < 0)
dprintf("command returned invalid data length (%d)\n",
else
dprintf("failed to load modepages (KEY=0x%x "
}
return (result);
}
/*
* Verify a single logpage. This will do some generic validation and then call
* the logpage-specific function for further verification.
*/
static int
{
int buflen;
int log_length;
int result = 0;
}
if (result == 0) {
}
(((char *)lhp) + sizeof (scsi_log_header_t)),
log_length, nvl) != 0) {
return (-1);
}
} else {
dprintf("failed to load %s log page (KEY=0x%x "
}
return (0);
}
/*
* Load log pages and determine which pages are supported.
*/
static int
{
int buflen;
int result;
int i, j;
/*
* Only add a page to the supported mask if it is
* returned from the disk, is one of the three supported
* by this code, and the test mask (tmask) calls for it
*/
for (i = 0; i < pagecount; i++) {
for (j = 0; j < NLOG_VALIDATION; j++) {
if ((log_validation[j].ve_code ==
sip->si_supp_log |=
}
}
}
if (result == 0) {
nvl) != 0) {
}
/*
* Validate the logpage contents.
*/
for (i = 0; i < NLOG_VALIDATION; i++) {
if ((sip->si_supp_log &
log_validation[i].ve_supported) == 0)
continue;
/*
* verify_logpage will clear the supported bit if
* verification fails.
*/
return (-1);
}
} else {
dprintf("failed to get log pages "
}
/*
* We always return 0 here, even if the required log pages aren't
* supported.
*/
return (0);
}
/*
* Verify that the IE log page is sane. This log page is potentially chock-full
* of vendor specific information that we do not know how to access. All we can
* do is check for the generic predictive failure bit. If this log page is not
* well-formed, then bail out.
*/
static int
{
int i, plen = 0;
for (i = 0; i < log_length; i += plen) {
B_TRUE) != 0)
if (nvlist_add_uint8(nvl,
} else {
}
break;
}
sizeof (scsi_log_parameter_header_t);
}
if (!seen) {
dprintf("IE logpage validation failed\n");
}
return (0);
}
/*
* Verify the contents of the temperature log page. The temperature log page
* contains two log parameters: the current temperature, and (optionally) the
* reference temperature. For the verification phase, we check that the two
* parameters we care about are well-formed. If there is no reference
* temperature, then we cannot use the page for monitoring purposes.
*/
static int
{
int i, plen = 0;
for (i = 0; i < log_length; i += plen) {
switch (param_code) {
case LOGPARAM_TEMP_CURTEMP:
B_TRUE) != 0)
if (nvlist_add_uint8(nvl,
bad_length = B_TRUE;
}
break;
case LOGPARAM_TEMP_REFTEMP:
"reference-temperature", B_TRUE) != 0)
if (nvlist_add_uint8(nvl,
bad_length = B_TRUE;
}
break;
}
sizeof (scsi_log_parameter_header_t);
}
if (bad_length || !has_reftemp) {
dprintf("temperature logpage validation failed\n");
}
return (0);
}
/*
* Verify the contents of the self test log page. The log supports a maximum of
* 20 entries, where each entry's parameter code is its index in the log. We
* check that the parameter codes fall within this range, and that the size of
* each page is what we expect. It's perfectly acceptable for there to be no
* entries in this log, so we must also be sure to validate the contents as part
* of the analysis phase.
*/
static int
{
int i, plen = 0;
int entries = 0;
param_code) != 0)
break;
}
lphp->lph_length) != 0)
break;
}
sizeof (scsi_log_parameter_header_t);
}
if (bad) {
dprintf("selftest logpage validation failed\n");
}
return (0);
}
/*
* Load the current IE mode pages
*/
static int
{
int result;
return (0);
&ascq)) == 0) {
}
if (result != 0) {
dprintf("failed to get IEC modepage (KEY=0x%x "
} else {
"interval-timer",
"report-count",
}
return (0);
}
/*
* Enable IE reporting. We prefer the following settings:
*
* (1) DEXCPT = 0
* (3) MRIE = 6 (IE_REPORT_ON_REQUEST)
* (4) EWASC = 1
* (6) REPORT COUNT = 0x00000001
* (7) LOGERR = 1
*
* However, not all drives support changing these values, and the current state
* may be useful enough as-is. For example, some drives support IE logging, but
* don't support changing the MRIE. In this case, we can still use the
* information provided by the log page.
*/
static int
{
return (0);
/* A previous enable attempt failed on this drive, don't retry. */
return (0);
}
sizeof (new_iec_page));
new_iec_page.ie_dexcpt = 0;
/*
* We only want to enable warning reporting if we are able to change the
* mrie to report on request. Otherwise, we risk unnecessarily
* interrupting normal SCSI commands with a CHECK CONDITION code.
*/
else
new_iec_page.ie_ewasc = 0;
}
/*
* Now compare the new mode page with the existing one.
* if there's no difference, there's no need for a mode select
*/
MODEPAGE_INFO_EXCPT_LEN) == 0) {
} else {
if (scsi_mode_select(sip,
} else {
dprintf("failed to enable IE (KEY=0x%x "
/* This drive can be skipped in the future. */
}
}
*changed) != 0)
return (0);
}
/*
* Clear the GLTSD bit, indicating log pages should be saved to non-volatile
* storage.
*/
static int
{
int result;
if (result != 0) {
dprintf("failed to read Control mode page (KEY=0x%x "
dprintf("SCSI-3 control mode page not supported\n");
!= 0) {
dprintf("failed to read changeable Control mode page (KEY=0x%x "
dprintf("gltsd is set and not changeable\n");
} else if (control_pg_cur.gltsd) {
control_pg_cur.gltsd = 0;
if (result != 0)
dprintf("failed to enable GLTSD (KEY=0x%x "
}
return (0);
}
/*
* Fetch the contents of the logpage, and then call the logpage-specific
* analysis function. The analysis function is responsible for detecting any
* faults and filling in the details.
*/
static int
{
int buflen;
int log_length;
int result;
if (result == 0) {
sizeof (scsi_log_header_t));
} else {
}
return (result);
}
/*
* Analyze the IE logpage. If we find an IE log record with a non-zero 'asc',
* then we have a fault.
*/
static int
int log_length)
{
int i, plen = 0;
for (i = 0; i < log_length; i += plen) {
/*
* Even though we validated the length during the initial phase,
* never trust the device.
*/
break;
}
sizeof (scsi_log_parameter_header_t);
}
return (0);
}
static int
int log_length)
{
int i, plen = 0;
for (i = 0; i < log_length; i += plen) {
switch (param_code) {
case LOGPARAM_TEMP_CURTEMP:
break;
if (nvlist_add_uint8(nvl,
break;
case LOGPARAM_TEMP_REFTEMP:
break;
if (nvlist_add_uint8(nvl,
break;
}
sizeof (scsi_log_parameter_header_t);
}
return (0);
}
static int
int log_length)
{
int i, plen = 0;
int entries = 0;
if (param_code >= LOGPAGE_SELFTEST_MIN_PARAM_CODE &&
/*
* We always log the last result, or the result of the
* last completed test.
*/
if ((param_code == 1 ||
if (nvlist_add_uint8(nvl,
stp->st_results) != 0 ||
return (scsi_set_errno(sip,
EDS_NOMEM));
return (0);
}
}
}
sizeof (scsi_log_parameter_header_t);
}
return (0);
}
/*
* Analyze the IE mode sense page explicitly. This is only needed if the IE log
* page is not supported.
*/
static int
{
/*
* Don't bother checking if we weren't able to set our MRIE correctly.
*/
return (0);
dprintf("failed to request IE page (KEY=0x%x ASC=0x%x "
} else if (skey == KEY_NO_SENSE) {
NV_UNIQUE_NAME, 0) != 0)
if (nvlist_add_uint8(nvl,
FM_EREPORT_PAYLOAD_SCSI_ASC, asc) != 0 ||
FM_EREPORT_PAYLOAD_SCSI_ASCQ, ascq) != 0) {
}
if (asc != 0)
}
return (0);
}
/*
* Clean up the scsi-specific information structure.
*/
static void
{
}
/*
* Initialize a single disk. Initialization consists of:
*
* 1. Check to see if the IE mechanism is enabled via MODE SENSE for the IE
* Control page (page 0x1C).
*
* 2. If the IE page is available, try to set the following parameters:
*
* DEXCPT 0 Enable exceptions
* MRIE 6 Only report IE information on request
* EWASC 1 Enable warning reporting
* REPORT COUNT 1 Only report an IE exception once
* LOGERR 1 Enable logging of errors
*
* The remaining fields are left as-is, preserving the current values. If we
* cannot set some of these fields, then we do our best. Some drives may
* have a static configuration which still allows for some monitoring.
*
* 3. Check to see if the IE log page (page 0x2F) is supported by issuing a
* LOG SENSE command.
*
* 4. Check to see if the self-test log page (page 0x10) is supported.
*
* 5. Check to see if the temperature log page (page 0x0D) is supported, and
* contains a reference temperature.
*
* 6. Clear the GLTSD bit in control mode page 0xA. This will allow the drive
* to save each of the log pages described above to nonvolatile storage.
* This is essential if the drive is to remember its failures across
* loss of power.
*/
static void *
{
/* Load and validate mode pages */
if (load_modepages(sip) != 0) {
return (NULL);
}
/* Load and validate log pages */
if (load_logpages(sip) != 0) {
return (NULL);
}
/*
* Load IE state, but only if the IE logpage is not supported.
* The ie_modepage is only used in the read phase if the logpage
* is not supported so save the effort here.
*/
if (load_ie_modepage(sip) != 0 ||
return (NULL);
}
}
/* Clear the GLTSD bit in the control page */
return (NULL);
}
return (sip);
}
static void *
{
return (NULL);
}
}
static void *
{
return (NULL);
}
return (NULL);
}
}
/*
* Scan for any faults. The following steps are performed:
*
* 1. If the temperature log page is supported, check the current temperature
* and threshold. If the current temperature exceeds the threshold, report
* and overtemp fault.
*
* 2. If the selftest log page is supported, check to the last completed self
* test. If the last completed test resulted in failure, report a selftest
* fault.
*
* 3. If the IE log page is supported, check to see if failure is predicted. If
* so, indicate a predictive failure fault.
*
* 4. If the IE log page is not supported, but the mode page supports report on
* request mode, then issue a REQUEST SENSE for the mode page. Indicate a
* predictive failure fault if necessary.
*/
static int
{
int i;
for (i = 0; i < NLOG_VALIDATION; i++) {
continue;
return (-1);
}
analyze_ie_sense(sip) != 0)
return (-1);
return (0);
}
};
};