diff options
Diffstat (limited to 'usr/src/lib/fm/libdiskstatus/common/ds_scsi.c')
| -rw-r--r-- | usr/src/lib/fm/libdiskstatus/common/ds_scsi.c | 1352 |
1 files changed, 1352 insertions, 0 deletions
diff --git a/usr/src/lib/fm/libdiskstatus/common/ds_scsi.c b/usr/src/lib/fm/libdiskstatus/common/ds_scsi.c new file mode 100644 index 0000000000..0b80f4d4c2 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/ds_scsi.c @@ -0,0 +1,1352 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <assert.h> +#include <errno.h> +#include <libdiskstatus.h> +#include <limits.h> +#include <stdlib.h> +#include <strings.h> +#include <sys/fm/io/scsi.h> + +#include "ds_scsi.h" +#include "ds_scsi_sim.h" +#include "ds_scsi_uscsi.h" + +typedef struct ds_scsi_info { + disk_status_t *si_dsp; + void *si_sim; + int si_cdblen; + int si_supp_mode; + int si_supp_log; + int si_extensions; + int si_reftemp; + scsi_ms_hdrs_t si_hdrs; + scsi_ie_page_t si_iec_current; + scsi_ie_page_t si_iec_changeable; + nvlist_t *si_state_modepage; + nvlist_t *si_state_logpage; + nvlist_t *si_state_iec; +} ds_scsi_info_t; + +#define scsi_set_errno(sip, errno) (ds_set_errno((sip)->si_dsp, (errno))) + +/* + * Table to validate log pages + */ +typedef int (*logpage_validation_fn_t)(ds_scsi_info_t *, + scsi_log_parameter_header_t *, int, nvlist_t *); +typedef int (*logpage_analyze_fn_t)(ds_scsi_info_t *, + scsi_log_parameter_header_t *, int); + +typedef struct logpage_validation_entry { + uchar_t ve_code; + int ve_supported; + const char *ve_desc; + logpage_validation_fn_t ve_validate; + logpage_analyze_fn_t ve_analyze; +} logpage_validation_entry_t; + +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); + +static struct logpage_validation_entry log_validation[] = { + { LOGPAGE_IE, LOGPAGE_SUPP_IE, + "informational-exceptions", + logpage_ie_verify, logpage_ie_analyze }, + { LOGPAGE_TEMP, LOGPAGE_SUPP_TEMP, + "temperature", + logpage_temp_verify, logpage_temp_analyze }, + { LOGPAGE_SELFTEST, LOGPAGE_SUPP_SELFTEST, + "self-test", + logpage_selftest_verify, logpage_selftest_analyze } +}; + +#define NLOG_VALIDATION (sizeof (log_validation) / sizeof (log_validation[0])) + +/* + * Given an extended sense page, retrieves the sense key, as well as the + * additional sense code information. + */ +static void +scsi_translate_error(struct scsi_extended_sense *rq, uint_t *skeyp, + uint_t *ascp, uint_t *ascqp) +{ + struct scsi_descr_sense_hdr *sdsp = + (struct scsi_descr_sense_hdr *)rq; + + *skeyp = rq->es_key; + + /* + * 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. + */ + switch (rq->es_code) { + case CODE_FMT_DESCR_CURRENT: + case CODE_FMT_DESCR_DEFERRED: + + *ascp = sdsp->ds_add_code; + *ascqp = sdsp->ds_qual_code; + break; + + case CODE_FMT_FIXED_CURRENT: + case CODE_FMT_FIXED_DEFERRED: + default: + + if (rq->es_add_len >= 6) { + *ascp = rq->es_add_code; + *ascqp = rq->es_qual_code; + } 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 +scsi_mode_select(ds_scsi_info_t *sip, uchar_t page_code, int options, + void *buf, uint_t buflen, scsi_ms_hdrs_t *headers, uint_t *skp, + uint_t *ascp, uint_t *ascqp) +{ + int result; + struct scsi_extended_sense sense; + int senselen = sizeof (struct scsi_extended_sense); + struct mode_page *mp = (struct mode_page *)buf; + + assert(sip->si_cdblen == MODE_CMD_LEN_6 || + sip->si_cdblen == MODE_CMD_LEN_10); + assert(headers->ms_length == sip->si_cdblen); + + bzero(&sense, sizeof (struct scsi_extended_sense)); + + if (mp->ps) { + options |= MODE_SELECT_SP; + mp->ps = 0; + } else { + options &= ~MODE_SELECT_SP; + } + + if (sip->si_cdblen == MODE_CMD_LEN_6) { + /* The following fields are reserved during mode select: */ + headers->ms_hdr.g0.ms_header.length = 0; + headers->ms_hdr.g0.ms_header.device_specific = 0; + + if (sip->si_sim) + result = simscsi_mode_select(sip->si_sim, + page_code, options, buf, buflen, + &headers->ms_hdr.g0, &sense, &senselen); + else + result = uscsi_mode_select(sip->si_dsp->ds_fd, + page_code, options, buf, buflen, + &headers->ms_hdr.g0, &sense, &senselen); + } else { + /* The following fields are reserved during mode select: */ + headers->ms_hdr.g1.ms_header.length = 0; + headers->ms_hdr.g1.ms_header.device_specific = 0; + + if (sip->si_sim) + result = simscsi_mode_select_10(sip->si_sim, + page_code, options, buf, buflen, + &headers->ms_hdr.g1, &sense, &senselen); + else + result = uscsi_mode_select_10(sip->si_dsp->ds_fd, + page_code, options, buf, buflen, + &headers->ms_hdr.g1, &sense, &senselen); + } + + if (result != 0) + scsi_translate_error(&sense, skp, ascp, ascqp); + + return (result); +} + +static int +scsi_mode_sense(ds_scsi_info_t *sip, uchar_t page_code, uchar_t pc, + void *buf, uint_t buflen, scsi_ms_hdrs_t *headers, uint_t *skp, + uint_t *ascp, uint_t *ascqp) +{ + int result; + struct scsi_extended_sense sense; + int senselen = sizeof (struct scsi_extended_sense); + + assert(sip->si_cdblen == MODE_CMD_LEN_6 || + sip->si_cdblen == MODE_CMD_LEN_10); + + bzero(&sense, sizeof (struct scsi_extended_sense)); + + bzero(headers, sizeof (scsi_ms_hdrs_t)); + headers->ms_length = sip->si_cdblen; + + if (sip->si_cdblen == MODE_CMD_LEN_6) { + if (sip->si_sim) + result = simscsi_mode_sense(sip->si_sim, + page_code, pc, buf, buflen, &headers->ms_hdr.g0, + &sense, &senselen); + else + result = uscsi_mode_sense(sip->si_dsp->ds_fd, page_code, + pc, buf, buflen, &headers->ms_hdr.g0, &sense, + &senselen); + } else { + if (sip->si_sim) + result = simscsi_mode_sense_10(sip->si_sim, + page_code, pc, buf, buflen, &headers->ms_hdr.g1, + &sense, &senselen); + else + result = uscsi_mode_sense_10(sip->si_dsp->ds_fd, + page_code, pc, buf, buflen, &headers->ms_hdr.g1, + &sense, &senselen); + } + + if (result != 0) + scsi_translate_error(&sense, skp, ascp, ascqp); + + return (result); +} + +static int +scsi_request_sense(ds_scsi_info_t *sip, uint_t *skp, uint_t *ascp, + uint_t *ascqp) +{ + struct scsi_extended_sense sense, sensebuf; + int senselen = sizeof (struct scsi_extended_sense); + int sensebuflen = sizeof (struct scsi_extended_sense); + int result; + + bzero(&sense, sizeof (struct scsi_extended_sense)); + bzero(&sensebuf, sizeof (struct scsi_extended_sense)); + + if (sip->si_sim) + result = simscsi_request_sense(sip->si_sim, + (caddr_t)&sensebuf, sensebuflen, &sense, &senselen); + else + result = uscsi_request_sense(sip->si_dsp->ds_fd, + (caddr_t)&sensebuf, sensebuflen, &sense, &senselen); + + if (result == 0) + scsi_translate_error(&sensebuf, skp, ascp, ascqp); + else + scsi_translate_error(&sense, skp, ascp, ascqp); + + return (result); +} + +static int +scsi_log_sense(ds_scsi_info_t *sip, int page_code, int page_control, + caddr_t page_data, int page_size, uint_t *skp, uint_t *ascp, uint_t *ascqp) +{ + int result; + struct scsi_extended_sense sense; + int senselen = sizeof (struct scsi_extended_sense); + + if (sip->si_sim) + result = simscsi_log_sense(sip->si_sim, + page_code, page_control, page_data, page_size, &sense, + &senselen); + else + result = uscsi_log_sense(sip->si_dsp->ds_fd, + page_code, page_control, page_data, page_size, &sense, + &senselen); + + if (result != 0) + scsi_translate_error(&sense, skp, ascp, ascqp); + + return (result); +} + +/* + * Given a list of supported mode pages, determine if the given page is present. + */ +static boolean_t +mode_page_present(uchar_t *pgdata, uint_t pgdatalen, uchar_t pagecode) +{ + uint_t i = 0; + struct mode_page *pg; + boolean_t found = B_FALSE; + + /* + * The mode page list contains all mode pages supported by the device, + * one after the other. + */ + while (i < pgdatalen) { + pg = (struct mode_page *)&pgdata[i]; + + if (pg->code == pagecode) { + found = B_TRUE; + 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 +load_modepages(ds_scsi_info_t *sip) +{ + int allpages_buflen; + uchar_t *allpages; + scsi_ms_hdrs_t headers; + int result; + uint_t skey, asc, ascq; + int datalength = 0; + scsi_ms_header_t *smh = &headers.ms_hdr.g0; + scsi_ms_header_g1_t *smh_g1 = &headers.ms_hdr.g1; + nvlist_t *nvl; + + allpages_buflen = MAX_BUFLEN(scsi_ms_header_g1_t); + if ((allpages = calloc(allpages_buflen, 1)) == NULL) + return (scsi_set_errno(sip, EDS_NOMEM)); + + bzero(&headers, sizeof (headers)); + + /* + * 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 + * UCHAR_MAX - the size of the headers/block descriptors. + */ + sip->si_cdblen = MODE_CMD_LEN_6; + if ((result = scsi_mode_sense(sip, MODEPAGE_ALLPAGES, PC_CURRENT, + (caddr_t)allpages, UCHAR_MAX - sizeof (scsi_ms_header_t), + &headers, &skey, &asc, &ascq)) == 0) { + /* + * 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]]. + */ + datalength = (smh->ms_header.length + + sizeof (smh->ms_header.length)) - + (sizeof (struct mode_header) + + smh->ms_header.bdesc_length); + } else if (SCSI_INVALID_OPCODE(skey, asc, ascq)) { + /* + * Fallback and try the 10-byte version of the command. + */ + sip->si_cdblen = MODE_CMD_LEN_10; + result = scsi_mode_sense(sip, MODEPAGE_ALLPAGES, + PC_CURRENT, (caddr_t)allpages, allpages_buflen, + &headers, &skey, &asc, &ascq); + + if (result == 0) { + datalength = (BE_16(smh_g1->ms_header.length) + + sizeof (smh_g1->ms_header.length)) - + (sizeof (struct mode_header_g1) + + BE_16(smh_g1->ms_header.bdesc_length)); + + } + } + + if (result == 0 && datalength >= 0) { + if (nvlist_add_int8(sip->si_dsp->ds_state, "command-length", + sip->si_cdblen == MODE_CMD_LEN_6 ? 6 : 10) != 0) { + free(allpages); + return (scsi_set_errno(sip, EDS_NOMEM)); + } + + nvl = NULL; + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 || + nvlist_add_nvlist(sip->si_dsp->ds_state, "modepages", + nvl) != 0) { + free(allpages); + nvlist_free(nvl); + return (scsi_set_errno(sip, EDS_NOMEM)); + } + + nvlist_free(nvl); + result = nvlist_lookup_nvlist(sip->si_dsp->ds_state, + "modepages", &sip->si_state_modepage); + assert(result == 0); + + /* + * One of the sets of the commands (above) succeeded, so now + * look for the mode pages we need and record them appropriately + */ + if (mode_page_present(allpages, datalength, + MODEPAGE_INFO_EXCPT)) { + + nvl = NULL; + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 || + nvlist_add_nvlist(sip->si_state_modepage, + "informational-exceptions", nvl) != 0) { + free(allpages); + nvlist_free(nvl); + return (scsi_set_errno(sip, EDS_NOMEM)); + } + nvlist_free(nvl); + sip->si_supp_mode |= MODEPAGE_SUPP_IEC; + result = nvlist_lookup_nvlist(sip->si_state_modepage, + "informational-exceptions", &sip->si_state_iec); + assert(result == 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", + datalength); + else + dprintf("failed to load modepages (KEY=0x%x " + "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq); + + result = scsi_set_errno(sip, EDS_NO_TRANSPORT); + } + + free(allpages); + return (result); +} + +/* + * Verify a single logpage. This will do some generic validation and then call + * the logpage-specific function for further verification. + */ +static int +verify_logpage(ds_scsi_info_t *sip, logpage_validation_entry_t *lp) +{ + scsi_log_header_t *lhp; + struct scsi_extended_sense sense; + int buflen; + int log_length; + int result = 0; + uint_t kp, asc, ascq; + nvlist_t *nvl; + + buflen = MAX_BUFLEN(scsi_log_header_t); + if ((lhp = calloc(buflen, 1)) == NULL) + return (scsi_set_errno(sip, EDS_NOMEM)); + bzero(&sense, sizeof (struct scsi_extended_sense)); + + nvl = NULL; + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 || + nvlist_add_nvlist(sip->si_state_logpage, lp->ve_desc, nvl) != 0) { + nvlist_free(nvl); + free(lhp); + return (scsi_set_errno(sip, EDS_NOMEM)); + } + nvlist_free(nvl); + result = nvlist_lookup_nvlist(sip->si_state_logpage, lp->ve_desc, &nvl); + assert(result == 0); + + result = scsi_log_sense(sip, lp->ve_code, + PC_CUMULATIVE, (caddr_t)lhp, buflen, &kp, &asc, &ascq); + + if (result == 0) { + log_length = BE_16(lhp->lh_length); + if (nvlist_add_uint16(nvl, "length", log_length) != 0) { + free(lhp); + return (scsi_set_errno(sip, EDS_NOMEM)); + } + + if (lp->ve_validate(sip, (scsi_log_parameter_header_t *) + (((char *)lhp) + sizeof (scsi_log_header_t)), + log_length, nvl) != 0) { + free(lhp); + return (-1); + } + } else { + dprintf("failed to load %s log page (KEY=0x%x " + "ASC=0x%x ASCQ=0x%x)\n", lp->ve_desc, kp, asc, ascq); + } + + free(lhp); + return (0); +} + +/* + * Load log pages and determine which pages are supported. + */ +static int +load_logpages(ds_scsi_info_t *sip) +{ + int buflen; + scsi_supported_log_pages_t *sp; + struct scsi_extended_sense sense; + int result; + uint_t sk, asc, ascq; + int i, j; + nvlist_t *nvl; + + buflen = MAX_BUFLEN(scsi_log_header_t); + if ((sp = calloc(buflen, 1)) == NULL) + return (scsi_set_errno(sip, EDS_NOMEM)); + + bzero(&sense, sizeof (struct scsi_extended_sense)); + + if ((result = scsi_log_sense(sip, LOGPAGE_SUPP_LIST, + PC_CUMULATIVE, (caddr_t)sp, buflen, &sk, &asc, &ascq)) == 0) { + int pagecount = BE_16(sp->slp_hdr.lh_length); + + for (i = 0; i < pagecount; i++) { + for (j = 0; j < NLOG_VALIDATION; j++) { + if (log_validation[j].ve_code == + sp->slp_pages[i]) + sip->si_supp_log |= + log_validation[j].ve_supported; + } + } + } + + free(sp); + if (result == 0) { + nvl = NULL; + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 || + nvlist_add_nvlist(sip->si_dsp->ds_state, "logpages", + nvl) != 0) { + nvlist_free(nvl); + return (scsi_set_errno(sip, EDS_NOMEM)); + } + + nvlist_free(nvl); + result = nvlist_lookup_nvlist(sip->si_dsp->ds_state, + "logpages", &sip->si_state_logpage); + assert(result == 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. + */ + if (verify_logpage(sip, &log_validation[i]) != 0) + return (-1); + } + + } else { + dprintf("failed to get log pages " + "(KEY=0x%x ASC=0x%x ASCq=0x%x)\n", sk, asc, ascq); + } + + /* + * 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 +logpage_ie_verify(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp, + int log_length, nvlist_t *nvl) +{ + int i, plen = 0; + boolean_t seen = B_FALSE; + scsi_ie_log_param_t *iep = + (scsi_ie_log_param_t *)lphp; + + for (i = 0; i < log_length; i += plen) { + iep = (scsi_ie_log_param_t *)((char *)iep + plen); + + if (BE_16(iep->ie_hdr.lph_param) == LOGPARAM_IE) { + if (nvlist_add_boolean_value(nvl, "general", + B_TRUE) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + + if (lphp->lph_length < LOGPARAM_IE_MIN_LEN) { + if (nvlist_add_uint8(nvl, + "invalid-length", lphp->lph_length) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + } else { + seen = B_TRUE; + } + break; + } + + plen = iep->ie_hdr.lph_length + + sizeof (scsi_log_parameter_header_t); + } + + if (!seen) { + sip->si_supp_log &= ~LOGPAGE_SUPP_IE; + 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 +logpage_temp_verify(ds_scsi_info_t *sip, + scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl) +{ + int i, plen = 0; + boolean_t has_reftemp = B_FALSE; + boolean_t bad_length = B_FALSE; + ushort_t param_code; + + for (i = 0; i < log_length; i += plen) { + lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen); + param_code = BE_16(lphp->lph_param); + + switch (param_code) { + case LOGPARAM_TEMP_CURTEMP: + if (nvlist_add_boolean_value(nvl, "current-temperature", + B_TRUE) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + if (lphp->lph_length != LOGPARAM_TEMP_LEN) { + if (nvlist_add_uint8(nvl, + "invalid-length", lphp->lph_length) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + bad_length = B_TRUE; + } + break; + + case LOGPARAM_TEMP_REFTEMP: + if (nvlist_add_boolean_value(nvl, + "reference-temperature", B_TRUE) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + if (lphp->lph_length != LOGPARAM_TEMP_LEN) { + if (nvlist_add_uint8(nvl, + "invalid-length", lphp->lph_length) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + bad_length = B_TRUE; + } + has_reftemp = B_TRUE; + break; + } + + plen = lphp->lph_length + + sizeof (scsi_log_parameter_header_t); + } + + if (bad_length || !has_reftemp) { + sip->si_supp_log &= ~LOGPAGE_SUPP_TEMP; + 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 +logpage_selftest_verify(ds_scsi_info_t *sip, + scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl) +{ + int i, plen = 0; + boolean_t bad = B_FALSE; + int entries = 0; + ushort_t param_code; + + for (i = 0; i < log_length; i += plen, entries++) { + lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen); + param_code = BE_16(lphp->lph_param); + + if (param_code < LOGPAGE_SELFTEST_MIN_PARAM_CODE || + param_code > LOGPAGE_SELFTEST_MAX_PARAM_CODE) { + if (nvlist_add_uint16(nvl, "invalid-param-code", + param_code) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + bad = B_TRUE; + break; + } + + if (lphp->lph_length != LOGPAGE_SELFTEST_PARAM_LEN) { + if (nvlist_add_uint8(nvl, "invalid-length", + lphp->lph_length) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + bad = B_TRUE; + break; + + } + + plen = lphp->lph_length + + sizeof (scsi_log_parameter_header_t); + } + + if (bad) { + sip->si_supp_log &= ~LOGPAGE_SUPP_SELFTEST; + dprintf("selftest logpage validation failed\n"); + } + + return (0); +} + +/* + * Load the current IE mode pages + */ +static int +load_ie_modepage(ds_scsi_info_t *sip) +{ + struct scsi_ms_hdrs junk_hdrs; + int result; + uint_t skey, asc, ascq; + + if (!(sip->si_supp_mode & MODEPAGE_SUPP_IEC)) + return (0); + + bzero(&sip->si_iec_current, sizeof (sip->si_iec_current)); + bzero(&sip->si_iec_changeable, sizeof (sip->si_iec_changeable)); + + if ((result = scsi_mode_sense(sip, + MODEPAGE_INFO_EXCPT, PC_CURRENT, &sip->si_iec_current, + MODEPAGE_INFO_EXCPT_LEN, &sip->si_hdrs, &skey, &asc, + &ascq)) == 0) { + result = scsi_mode_sense(sip, + MODEPAGE_INFO_EXCPT, PC_CHANGEABLE, + &sip->si_iec_changeable, + MODEPAGE_INFO_EXCPT_LEN, &junk_hdrs, &skey, &asc, &ascq); + } + + if (result != 0) { + dprintf("failed to get IEC modepage (KEY=0x%x " + "ASC=0x%x ASCQ=0x%x)", skey, asc, ascq); + sip->si_supp_mode &= ~MODEPAGE_SUPP_IEC; + } else { + if (nvlist_add_boolean_value(sip->si_state_iec, + "dexcpt", sip->si_iec_current.ie_dexcpt) != 0 || + nvlist_add_boolean_value(sip->si_state_iec, + "logerr", sip->si_iec_current.ie_logerr) != 0 || + nvlist_add_uint8(sip->si_state_iec, + "mrie", sip->si_iec_current.ie_mrie) != 0 || + nvlist_add_boolean_value(sip->si_state_iec, + "test", sip->si_iec_current.ie_test) != 0 || + nvlist_add_boolean_value(sip->si_state_iec, + "ewasc", sip->si_iec_current.ie_ewasc) != 0 || + nvlist_add_boolean_value(sip->si_state_iec, + "perf", sip->si_iec_current.ie_perf) != 0 || + nvlist_add_boolean_value(sip->si_state_iec, + "ebf", sip->si_iec_current.ie_ebf) != 0 || + nvlist_add_uint32(sip->si_state_iec, + "interval-timer", + BE_32(sip->si_iec_current.ie_interval_timer)) != 0 || + nvlist_add_uint32(sip->si_state_iec, + "report-count", + BE_32(sip->si_iec_current.ie_report_count)) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + } + + 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 +scsi_enable_ie(ds_scsi_info_t *sip, boolean_t *changed) +{ + scsi_ie_page_t new_iec_page; + scsi_ms_hdrs_t hdrs; + uint_t skey, asc, ascq; + + if (!(sip->si_supp_mode & MODEPAGE_SUPP_IEC)) + return (0); + + bzero(&new_iec_page, sizeof (new_iec_page)); + bzero(&hdrs, sizeof (hdrs)); + + (void) memcpy(&new_iec_page, &sip->si_iec_current, + sizeof (new_iec_page)); + + if (IEC_IE_CHANGEABLE(sip->si_iec_changeable)) + new_iec_page.ie_dexcpt = 0; + + if (IEC_MRIE_CHANGEABLE(sip->si_iec_changeable)) + new_iec_page.ie_mrie = IE_REPORT_ON_REQUEST; + + /* + * 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. + */ + if (IEC_EWASC_CHANGEABLE(sip->si_iec_changeable)) { + if (new_iec_page.ie_mrie == IE_REPORT_ON_REQUEST) + new_iec_page.ie_ewasc = 1; + else + new_iec_page.ie_ewasc = 0; + } + + if (IEC_RPTCNT_CHANGEABLE(sip->si_iec_changeable)) + new_iec_page.ie_report_count = BE_32(1); + + if (IEC_LOGERR_CHANGEABLE(sip->si_iec_changeable)) + new_iec_page.ie_logerr = 1; + + /* + * Now compare the new mode page with the existing one. + * if there's no difference, there's no need for a mode select + */ + if (memcmp(&new_iec_page, &sip->si_iec_current, + MODEPAGE_INFO_EXCPT_LEN) == 0) { + *changed = B_FALSE; + } else { + (void) memcpy(&hdrs, &sip->si_hdrs, sizeof (sip->si_hdrs)); + + if (scsi_mode_select(sip, + MODEPAGE_INFO_EXCPT, MODE_SELECT_PF, &new_iec_page, + MODEPAGE_INFO_EXCPT_LEN, &hdrs, &skey, &asc, &ascq) == 0) { + *changed = B_TRUE; + } else { + dprintf("failed to enable IE (KEY=0x%x " + "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq); + *changed = B_FALSE; + } + } + + if (nvlist_add_boolean_value(sip->si_state_iec, "changed", + *changed) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + + return (0); +} + +/* + * Clear the GLTSD bit, indicating log pages should be saved to non-volatile + * storage. + */ +static int +clear_gltsd(ds_scsi_info_t *sip) +{ + scsi_ms_hdrs_t hdrs, junk_hdrs; + struct mode_control_scsi3 control_pg_cur, control_pg_chg; + int result; + uint_t skey, asc, ascq; + + bzero(&hdrs, sizeof (hdrs)); + bzero(&control_pg_cur, sizeof (control_pg_cur)); + bzero(&control_pg_chg, sizeof (control_pg_chg)); + + result = scsi_mode_sense(sip, + MODEPAGE_CTRL_MODE, PC_CURRENT, &control_pg_cur, + MODEPAGE_CTRL_MODE_LEN, &hdrs, &skey, &asc, &ascq); + + if (result != 0) { + dprintf("failed to read Control mode page (KEY=0x%x " + "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq); + } else if (control_pg_cur.mode_page.length != + PAGELENGTH_MODE_CONTROL_SCSI3) { + dprintf("SCSI-3 control mode page not supported\n"); + } else if ((result = scsi_mode_sense(sip, + MODEPAGE_CTRL_MODE, PC_CHANGEABLE, &control_pg_chg, + MODEPAGE_CTRL_MODE_LEN, &junk_hdrs, &skey, &asc, &ascq)) + != 0) { + dprintf("failed to read changeable Control mode page (KEY=0x%x " + "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq); + } else if (control_pg_cur.gltsd && !GLTSD_CHANGEABLE(control_pg_chg)) { + dprintf("gltsd is set and not changeable\n"); + if (nvlist_add_boolean_value(sip->si_dsp->ds_state, + "gltsd", control_pg_cur.gltsd) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + } else if (control_pg_cur.gltsd) { + control_pg_cur.gltsd = 0; + result = scsi_mode_select(sip, + MODEPAGE_CTRL_MODE, MODE_SELECT_PF, &control_pg_cur, + MODEPAGE_CTRL_MODE_LEN, &hdrs, &skey, &asc, &ascq); + if (result != 0) + dprintf("failed to enable GLTSD (KEY=0x%x " + "ASC=0x%x ASCQ=0x%x\n", skey, asc, ascq); + if (nvlist_add_boolean_value(sip->si_dsp->ds_state, + "gltsd", control_pg_cur.gltsd) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + } + + 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 +analyze_one_logpage(ds_scsi_info_t *sip, logpage_validation_entry_t *entry) +{ + scsi_log_header_t *lhp; + scsi_log_parameter_header_t *lphp; + int buflen; + int log_length; + uint_t skey, asc, ascq; + int result; + + buflen = MAX_BUFLEN(scsi_log_header_t); + if ((lhp = calloc(buflen, 1)) == NULL) + return (scsi_set_errno(sip, EDS_NOMEM)); + + result = scsi_log_sense(sip, entry->ve_code, + PC_CUMULATIVE, (caddr_t)lhp, buflen, &skey, &asc, &ascq); + + if (result == 0) { + log_length = BE_16(lhp->lh_length); + lphp = (scsi_log_parameter_header_t *)(((uchar_t *)lhp) + + sizeof (scsi_log_header_t)); + + result = entry->ve_analyze(sip, lphp, log_length); + } else { + result = scsi_set_errno(sip, EDS_IO); + } + + free(lhp); + 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 +logpage_ie_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp, + int log_length) +{ + int i, plen = 0; + scsi_ie_log_param_t *iep = (scsi_ie_log_param_t *)lphp; + nvlist_t *nvl; + + assert(sip->si_dsp->ds_predfail == NULL); + if (nvlist_alloc(&sip->si_dsp->ds_predfail, NV_UNIQUE_NAME, 0) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + nvl = sip->si_dsp->ds_predfail; + + for (i = 0; i < log_length; i += plen) { + iep = (scsi_ie_log_param_t *)((char *)iep + plen); + + /* + * Even though we validated the length during the initial phase, + * never trust the device. + */ + if (BE_16(iep->ie_hdr.lph_param) == LOGPARAM_IE && + iep->ie_hdr.lph_length >= LOGPARAM_IE_MIN_LEN) { + if (nvlist_add_uint8(nvl, FM_EREPORT_PAYLOAD_SCSI_ASC, + iep->ie_asc) != 0 || + nvlist_add_uint8(nvl, FM_EREPORT_PAYLOAD_SCSI_ASCQ, + iep->ie_ascq) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + + if (iep->ie_asc != 0) + sip->si_dsp->ds_faults |= + DS_FAULT_PREDFAIL; + break; + } + plen = iep->ie_hdr.lph_length + + sizeof (scsi_log_parameter_header_t); + } + + return (0); +} + +static int +logpage_temp_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp, + int log_length) +{ + int i, plen = 0; + uint8_t reftemp, curtemp; + ushort_t param_code; + scsi_temp_log_param_t *temp; + nvlist_t *nvl; + + assert(sip->si_dsp->ds_overtemp == NULL); + if (nvlist_alloc(&sip->si_dsp->ds_overtemp, NV_UNIQUE_NAME, 0) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + nvl = sip->si_dsp->ds_overtemp; + + reftemp = curtemp = INVALID_TEMPERATURE; + for (i = 0; i < log_length; i += plen) { + lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen); + param_code = BE_16(lphp->lph_param); + temp = (scsi_temp_log_param_t *)lphp; + + switch (param_code) { + case LOGPARAM_TEMP_CURTEMP: + if (lphp->lph_length != LOGPARAM_TEMP_LEN) + break; + + if (nvlist_add_uint8(nvl, + FM_EREPORT_PAYLOAD_SCSI_CURTEMP, + temp->t_temp) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + curtemp = temp->t_temp; + break; + + case LOGPARAM_TEMP_REFTEMP: + if (lphp->lph_length != LOGPARAM_TEMP_LEN) + break; + + if (nvlist_add_uint8(nvl, + FM_EREPORT_PAYLOAD_SCSI_THRESHTEMP, + temp->t_temp) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + reftemp = temp->t_temp; + break; + } + + plen = lphp->lph_length + + sizeof (scsi_log_parameter_header_t); + } + + if (reftemp != INVALID_TEMPERATURE && curtemp != INVALID_TEMPERATURE && + curtemp > reftemp) + sip->si_dsp->ds_faults |= DS_FAULT_OVERTEMP; + + return (0); +} + +static int +logpage_selftest_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp, + int log_length) +{ + int i, plen = 0; + int entries = 0; + ushort_t param_code; + scsi_selftest_log_param_t *stp; + nvlist_t *nvl; + + assert(sip->si_dsp->ds_testfail == NULL); + if (nvlist_alloc(&sip->si_dsp->ds_testfail, NV_UNIQUE_NAME, 0) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + nvl = sip->si_dsp->ds_testfail; + + for (i = 0; i < log_length; i += plen, entries++) { + lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen); + param_code = BE_16(lphp->lph_param); + stp = (scsi_selftest_log_param_t *)lphp; + + if (param_code >= LOGPAGE_SELFTEST_MIN_PARAM_CODE && + param_code <= LOGPAGE_SELFTEST_MAX_PARAM_CODE && + lphp->lph_length >= LOGPAGE_SELFTEST_PARAM_LEN) { + /* + * We always log the last result, or the result of the + * last completed test. + */ + if ((param_code == 1 || + SELFTEST_COMPLETE(stp->st_results))) { + if (nvlist_add_uint8(nvl, + FM_EREPORT_PAYLOAD_SCSI_RESULTCODE, + stp->st_results) != 0 || + nvlist_add_uint16(nvl, + FM_EREPORT_PAYLOAD_SCSI_TIMESTAMP, + BE_16(stp->st_timestamp)) != 0 || + nvlist_add_uint8(nvl, + FM_EREPORT_PAYLOAD_SCSI_SEGMENT, + stp->st_number) != 0 || + nvlist_add_uint64(nvl, + FM_EREPORT_PAYLOAD_SCSI_ADDRESS, + BE_64(stp->st_lba)) != 0) + return (scsi_set_errno(sip, + EDS_NOMEM)); + + if (SELFTEST_COMPLETE(stp->st_results)) { + if (stp->st_results != SELFTEST_OK) + sip->si_dsp->ds_faults |= + DS_FAULT_TESTFAIL; + return (0); + } + } + } + + plen = lphp->lph_length + + 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 +analyze_ie_sense(ds_scsi_info_t *sip) +{ + uint_t skey, asc, ascq; + nvlist_t *nvl; + + /* + * Don't bother checking if we weren't able to set our MRIE correctly. + */ + if (sip->si_iec_current.ie_mrie != IE_REPORT_ON_REQUEST) + return (0); + + if (scsi_request_sense(sip, &skey, &asc, &ascq) != 0) { + dprintf("failed to request IE page (KEY=0x%x ASC=0x%x " + "ASCQ=0x%x)\n", skey, asc, ascq); + return (scsi_set_errno(sip, EDS_IO)); + } else if (skey == KEY_NO_SENSE) { + assert(sip->si_dsp->ds_predfail == NULL); + if (nvlist_alloc(&sip->si_dsp->ds_predfail, + NV_UNIQUE_NAME, 0) != 0) + return (scsi_set_errno(sip, EDS_NOMEM)); + nvl = sip->si_dsp->ds_predfail; + + if (nvlist_add_uint8(nvl, + FM_EREPORT_PAYLOAD_SCSI_ASC, asc) != 0 || + nvlist_add_uint8(nvl, + FM_EREPORT_PAYLOAD_SCSI_ASCQ, ascq) != 0) { + nvlist_free(nvl); + return (scsi_set_errno(sip, EDS_NOMEM)); + } + + if (asc != 0) + sip->si_dsp->ds_faults |= DS_FAULT_PREDFAIL; + } + + return (0); +} + +/* + * Clean up the scsi-specific information structure. + */ +static void +ds_scsi_close(void *arg) +{ + ds_scsi_info_t *sip = arg; + if (sip->si_sim) + (void) dlclose(sip->si_sim); + + free(sip); +} + +/* + * 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 * +ds_scsi_open_common(disk_status_t *dsp, ds_scsi_info_t *sip) +{ + boolean_t changed; + + sip->si_dsp = dsp; + + /* Load and validate mode pages */ + if (load_modepages(sip) != 0) { + ds_scsi_close(sip); + return (NULL); + } + + /* Load and validate log pages */ + if (load_logpages(sip) != 0) { + ds_scsi_close(sip); + return (NULL); + } + + /* Load IE state */ + if (load_ie_modepage(sip) != 0 || + scsi_enable_ie(sip, &changed) != 0 || + (changed && load_ie_modepage(sip) != 0)) { + ds_scsi_close(sip); + return (NULL); + } + + /* Clear the GLTSD bit in the control page */ + if (sip->si_supp_log != 0 && clear_gltsd(sip) != 0) { + ds_scsi_close(sip); + return (NULL); + } + + return (sip); +} + +static void * +ds_scsi_open_uscsi(disk_status_t *dsp) +{ + ds_scsi_info_t *sip; + + if ((sip = calloc(sizeof (ds_scsi_info_t), 1)) == NULL) { + (void) ds_set_errno(dsp, EDS_NOMEM); + return (NULL); + } + + return (ds_scsi_open_common(dsp, sip)); +} + +static void * +ds_scsi_open_sim(disk_status_t *dsp) +{ + ds_scsi_info_t *sip; + + if ((sip = calloc(sizeof (ds_scsi_info_t), 1)) == NULL) { + (void) ds_set_errno(dsp, EDS_NOMEM); + return (NULL); + } + + if ((sip->si_sim = dlopen(dsp->ds_path, RTLD_LAZY)) == NULL) { + (void) ds_set_errno(dsp, EDS_NO_TRANSPORT); + free(sip); + return (NULL); + } + + return (ds_scsi_open_common(dsp, sip)); +} + + +/* + * 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 +ds_scsi_scan(void *arg) +{ + ds_scsi_info_t *sip = arg; + int i; + + for (i = 0; i < NLOG_VALIDATION; i++) { + if ((sip->si_supp_log & log_validation[i].ve_supported) == 0) + continue; + + if (analyze_one_logpage(sip, &log_validation[i]) != 0) + return (-1); + } + + if (!(sip->si_supp_log & LOGPAGE_SUPP_IE) && + (sip->si_supp_mode & MODEPAGE_SUPP_IEC) && + analyze_ie_sense(sip) != 0) + return (-1); + + return (0); +} + +ds_transport_t ds_scsi_uscsi_transport = { + ds_scsi_open_uscsi, + ds_scsi_close, + ds_scsi_scan +}; + +ds_transport_t ds_scsi_sim_transport = { + ds_scsi_open_sim, + ds_scsi_close, + ds_scsi_scan +}; |
