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 * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include <assert.h>
2N/A#include <errno.h>
2N/A#include <libdiskstatus.h>
2N/A#include <limits.h>
2N/A#include <stdlib.h>
2N/A#include <strings.h>
2N/A#include <sys/fm/io/scsi.h>
2N/A
2N/A#include "ds_scsi.h"
2N/A#include "ds_scsi_sim.h"
2N/A#include "ds_scsi_uscsi.h"
2N/A
2N/Atypedef struct ds_scsi_info {
2N/A disk_status_t *si_dsp;
2N/A void *si_sim;
2N/A int si_cdblen;
2N/A int si_supp_mode;
2N/A int si_supp_log;
2N/A int si_extensions;
2N/A int si_reftemp;
2N/A scsi_ms_hdrs_t si_hdrs;
2N/A scsi_ie_page_t si_iec_current;
2N/A scsi_ie_page_t si_iec_changeable;
2N/A nvlist_t *si_state_modepage;
2N/A nvlist_t *si_state_logpage;
2N/A nvlist_t *si_state_iec;
2N/A} ds_scsi_info_t;
2N/A
2N/A#define scsi_set_errno(sip, errno) (ds_set_errno((sip)->si_dsp, (errno)))
2N/A
2N/A/*
2N/A * Table to validate log pages
2N/A */
2N/Atypedef int (*logpage_validation_fn_t)(ds_scsi_info_t *,
2N/A scsi_log_parameter_header_t *, int, nvlist_t *);
2N/Atypedef int (*logpage_analyze_fn_t)(ds_scsi_info_t *,
2N/A scsi_log_parameter_header_t *, int);
2N/A
2N/Atypedef struct logpage_validation_entry {
2N/A uchar_t ve_code;
2N/A int ve_supported;
2N/A const char *ve_desc;
2N/A logpage_validation_fn_t ve_validate;
2N/A logpage_analyze_fn_t ve_analyze;
2N/A} logpage_validation_entry_t;
2N/A
2N/Astatic int logpage_ie_verify(ds_scsi_info_t *,
2N/A scsi_log_parameter_header_t *, int, nvlist_t *);
2N/Astatic int logpage_temp_verify(ds_scsi_info_t *,
2N/A scsi_log_parameter_header_t *, int, nvlist_t *);
2N/Astatic int logpage_selftest_verify(ds_scsi_info_t *,
2N/A scsi_log_parameter_header_t *, int, nvlist_t *);
2N/A
2N/Astatic int logpage_ie_analyze(ds_scsi_info_t *,
2N/A scsi_log_parameter_header_t *, int);
2N/Astatic int logpage_temp_analyze(ds_scsi_info_t *,
2N/A scsi_log_parameter_header_t *, int);
2N/Astatic int logpage_selftest_analyze(ds_scsi_info_t *,
2N/A scsi_log_parameter_header_t *, int);
2N/A
2N/Astatic struct logpage_validation_entry log_validation[] = {
2N/A { LOGPAGE_IE, LOGPAGE_SUPP_IE,
2N/A "informational-exceptions",
2N/A logpage_ie_verify, logpage_ie_analyze },
2N/A { LOGPAGE_TEMP, LOGPAGE_SUPP_TEMP,
2N/A "temperature",
2N/A logpage_temp_verify, logpage_temp_analyze },
2N/A { LOGPAGE_SELFTEST, LOGPAGE_SUPP_SELFTEST,
2N/A "self-test",
2N/A logpage_selftest_verify, logpage_selftest_analyze }
2N/A};
2N/A
2N/A#define NLOG_VALIDATION (sizeof (log_validation) / sizeof (log_validation[0]))
2N/A
2N/A/*
2N/A * Given an extended sense page, retrieves the sense key, as well as the
2N/A * additional sense code information.
2N/A */
2N/Astatic void
2N/Ascsi_translate_error(struct scsi_extended_sense *rq, uint_t *skeyp,
2N/A uint_t *ascp, uint_t *ascqp)
2N/A{
2N/A struct scsi_descr_sense_hdr *sdsp =
2N/A (struct scsi_descr_sense_hdr *)rq;
2N/A
2N/A *skeyp = rq->es_key;
2N/A
2N/A /*
2N/A * Get asc, ascq and info field from sense data. There are two
2N/A * possible formats (fixed sense data and descriptor sense data)
2N/A * depending on the value of es_code.
2N/A */
2N/A switch (rq->es_code) {
2N/A case CODE_FMT_DESCR_CURRENT:
2N/A case CODE_FMT_DESCR_DEFERRED:
2N/A
2N/A *ascp = sdsp->ds_add_code;
2N/A *ascqp = sdsp->ds_qual_code;
2N/A break;
2N/A
2N/A case CODE_FMT_FIXED_CURRENT:
2N/A case CODE_FMT_FIXED_DEFERRED:
2N/A default:
2N/A
2N/A if (rq->es_add_len >= 6) {
2N/A *ascp = rq->es_add_code;
2N/A *ascqp = rq->es_qual_code;
2N/A } else {
2N/A *ascp = 0xff;
2N/A *ascqp = 0xff;
2N/A }
2N/A break;
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Routines built atop the bare uscsi commands, which take into account the
2N/A * command length, automatically translate any scsi errors, and transparently
2N/A * call into the simulator if active.
2N/A */
2N/Astatic int
2N/Ascsi_mode_select(ds_scsi_info_t *sip, uchar_t page_code, int options,
2N/A void *buf, uint_t buflen, scsi_ms_hdrs_t *headers, uint_t *skp,
2N/A uint_t *ascp, uint_t *ascqp)
2N/A{
2N/A int result;
2N/A struct scsi_extended_sense sense;
2N/A int senselen = sizeof (struct scsi_extended_sense);
2N/A struct mode_page *mp = (struct mode_page *)buf;
2N/A
2N/A assert(sip->si_cdblen == MODE_CMD_LEN_6 ||
2N/A sip->si_cdblen == MODE_CMD_LEN_10);
2N/A assert(headers->ms_length == sip->si_cdblen);
2N/A
2N/A bzero(&sense, sizeof (struct scsi_extended_sense));
2N/A
2N/A if (mp->ps) {
2N/A options |= MODE_SELECT_SP;
2N/A mp->ps = 0;
2N/A } else {
2N/A options &= ~MODE_SELECT_SP;
2N/A }
2N/A
2N/A if (sip->si_cdblen == MODE_CMD_LEN_6) {
2N/A /* The following fields are reserved during mode select: */
2N/A headers->ms_hdr.g0.ms_header.length = 0;
2N/A headers->ms_hdr.g0.ms_header.device_specific = 0;
2N/A
2N/A if (sip->si_sim)
2N/A result = simscsi_mode_select(sip->si_sim,
2N/A page_code, options, buf, buflen,
2N/A &headers->ms_hdr.g0, &sense, &senselen);
2N/A else
2N/A result = uscsi_mode_select(sip->si_dsp->ds_fd,
2N/A page_code, options, buf, buflen,
2N/A &headers->ms_hdr.g0, &sense, &senselen);
2N/A } else {
2N/A /* The following fields are reserved during mode select: */
2N/A headers->ms_hdr.g1.ms_header.length = 0;
2N/A headers->ms_hdr.g1.ms_header.device_specific = 0;
2N/A
2N/A if (sip->si_sim)
2N/A result = simscsi_mode_select_10(sip->si_sim,
2N/A page_code, options, buf, buflen,
2N/A &headers->ms_hdr.g1, &sense, &senselen);
2N/A else
2N/A result = uscsi_mode_select_10(sip->si_dsp->ds_fd,
2N/A page_code, options, buf, buflen,
2N/A &headers->ms_hdr.g1, &sense, &senselen);
2N/A }
2N/A
2N/A if (result != 0)
2N/A scsi_translate_error(&sense, skp, ascp, ascqp);
2N/A
2N/A return (result);
2N/A}
2N/A
2N/Astatic int
2N/Ascsi_mode_sense(ds_scsi_info_t *sip, uchar_t page_code, uchar_t pc,
2N/A void *buf, uint_t buflen, scsi_ms_hdrs_t *headers, uint_t *skp,
2N/A uint_t *ascp, uint_t *ascqp)
2N/A{
2N/A int result;
2N/A struct scsi_extended_sense sense;
2N/A int senselen = sizeof (struct scsi_extended_sense);
2N/A
2N/A assert(sip->si_cdblen == MODE_CMD_LEN_6 ||
2N/A sip->si_cdblen == MODE_CMD_LEN_10);
2N/A
2N/A bzero(&sense, sizeof (struct scsi_extended_sense));
2N/A
2N/A bzero(headers, sizeof (scsi_ms_hdrs_t));
2N/A headers->ms_length = sip->si_cdblen;
2N/A
2N/A if (sip->si_cdblen == MODE_CMD_LEN_6) {
2N/A if (sip->si_sim)
2N/A result = simscsi_mode_sense(sip->si_sim,
2N/A page_code, pc, buf, buflen, &headers->ms_hdr.g0,
2N/A &sense, &senselen);
2N/A else
2N/A result = uscsi_mode_sense(sip->si_dsp->ds_fd, page_code,
2N/A pc, buf, buflen, &headers->ms_hdr.g0, &sense,
2N/A &senselen);
2N/A } else {
2N/A if (sip->si_sim)
2N/A result = simscsi_mode_sense_10(sip->si_sim,
2N/A page_code, pc, buf, buflen, &headers->ms_hdr.g1,
2N/A &sense, &senselen);
2N/A else
2N/A result = uscsi_mode_sense_10(sip->si_dsp->ds_fd,
2N/A page_code, pc, buf, buflen, &headers->ms_hdr.g1,
2N/A &sense, &senselen);
2N/A }
2N/A
2N/A if (result != 0)
2N/A scsi_translate_error(&sense, skp, ascp, ascqp);
2N/A
2N/A return (result);
2N/A}
2N/A
2N/Astatic int
2N/Ascsi_request_sense(ds_scsi_info_t *sip, uint_t *skp, uint_t *ascp,
2N/A uint_t *ascqp)
2N/A{
2N/A struct scsi_extended_sense sense, sensebuf;
2N/A int senselen = sizeof (struct scsi_extended_sense);
2N/A int sensebuflen = sizeof (struct scsi_extended_sense);
2N/A int result;
2N/A
2N/A bzero(&sense, sizeof (struct scsi_extended_sense));
2N/A bzero(&sensebuf, sizeof (struct scsi_extended_sense));
2N/A
2N/A if (sip->si_sim)
2N/A result = simscsi_request_sense(sip->si_sim,
2N/A (caddr_t)&sensebuf, sensebuflen, &sense, &senselen);
2N/A else
2N/A result = uscsi_request_sense(sip->si_dsp->ds_fd,
2N/A (caddr_t)&sensebuf, sensebuflen, &sense, &senselen);
2N/A
2N/A if (result == 0)
2N/A scsi_translate_error(&sensebuf, skp, ascp, ascqp);
2N/A else
2N/A scsi_translate_error(&sense, skp, ascp, ascqp);
2N/A
2N/A return (result);
2N/A}
2N/A
2N/Astatic int
2N/Ascsi_log_sense(ds_scsi_info_t *sip, int page_code, int page_control,
2N/A caddr_t page_data, int page_size, uint_t *skp, uint_t *ascp, uint_t *ascqp)
2N/A{
2N/A int result;
2N/A struct scsi_extended_sense sense;
2N/A int senselen = sizeof (struct scsi_extended_sense);
2N/A
2N/A if (sip->si_sim)
2N/A result = simscsi_log_sense(sip->si_sim,
2N/A page_code, page_control, page_data, page_size, &sense,
2N/A &senselen);
2N/A else
2N/A result = uscsi_log_sense(sip->si_dsp->ds_fd,
2N/A page_code, page_control, page_data, page_size, &sense,
2N/A &senselen);
2N/A
2N/A if (result != 0)
2N/A scsi_translate_error(&sense, skp, ascp, ascqp);
2N/A
2N/A return (result);
2N/A}
2N/A
2N/A/*
2N/A * Given a list of supported mode pages, determine if the given page is present.
2N/A */
2N/Astatic boolean_t
2N/Amode_page_present(uchar_t *pgdata, uint_t pgdatalen, uchar_t pagecode)
2N/A{
2N/A uint_t i = 0;
2N/A struct mode_page *pg;
2N/A boolean_t found = B_FALSE;
2N/A
2N/A /*
2N/A * The mode page list contains all mode pages supported by the device,
2N/A * one after the other.
2N/A */
2N/A while (i < pgdatalen) {
2N/A pg = (struct mode_page *)&pgdata[i];
2N/A
2N/A if (pg->code == pagecode) {
2N/A found = B_TRUE;
2N/A break;
2N/A }
2N/A
2N/A i += MODESENSE_PAGE_LEN(pg);
2N/A }
2N/A
2N/A return (found);
2N/A}
2N/A
2N/A/*
2N/A * Load mode pages and check that the appropriate pages are supported.
2N/A *
2N/A * As part of this process, we determine which form of the MODE SENSE / MODE
2N/A * SELECT command to use (the 6-byte or 10-byte version) by executing a MODE
2N/A * SENSE command for a page that should be implemented by the device.
2N/A */
2N/Astatic int
2N/Aload_modepages(ds_scsi_info_t *sip)
2N/A{
2N/A int allpages_buflen;
2N/A uchar_t *allpages;
2N/A scsi_ms_hdrs_t headers;
2N/A int result;
2N/A uint_t skey, asc, ascq;
2N/A int datalength = 0;
2N/A scsi_ms_header_t *smh = &headers.ms_hdr.g0;
2N/A scsi_ms_header_g1_t *smh_g1 = &headers.ms_hdr.g1;
2N/A nvlist_t *nvl;
2N/A
2N/A allpages_buflen = MAX_BUFLEN(scsi_ms_header_g1_t);
2N/A if ((allpages = calloc(allpages_buflen, 1)) == NULL)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A
2N/A bzero(&headers, sizeof (headers));
2N/A
2N/A /*
2N/A * Attempt a mode sense(6). If that fails, try a mode sense(10)
2N/A *
2N/A * allpages is allocated to be of the maximum size for either a mode
2N/A * sense(6) or mode sense(10) MODEPAGE_ALLPAGES response.
2N/A *
2N/A * Note that the length passed into uscsi_mode_sense should be set to
2N/A * the maximum size of the parameter response, which in this case is
2N/A * UCHAR_MAX - the size of the headers/block descriptors.
2N/A */
2N/A sip->si_cdblen = MODE_CMD_LEN_6;
2N/A if ((result = scsi_mode_sense(sip, MODEPAGE_ALLPAGES, PC_CURRENT,
2N/A (caddr_t)allpages, UCHAR_MAX - sizeof (scsi_ms_header_t),
2N/A &headers, &skey, &asc, &ascq)) == 0) {
2N/A /*
2N/A * Compute the data length of the page that contains all mode
2N/A * sense pages. This is a bit tricky because the format of the
2N/A * response from the lun is:
2N/A *
2N/A * header: <length> <medium type byte> <dev specific byte>
2N/A * <block descriptor length>
2N/A * [<optional block descriptor>]
2N/A * data: [<mode page data> <mode page data> ...]
2N/A *
2N/A * Since the length field in the header describes the length of
2N/A * the entire response. This includes the header, but NOT
2N/A * the length field itself, which is 1 or 2 bytes depending on
2N/A * which mode sense type (6- or 10- byte) is being executed.
2N/A *
2N/A * So, the data length equals the length value in the header
2N/A * plus 1 (because the length byte was not included in the
2N/A * length count), minus [[the sum of the length of the header
2N/A * and the length of the block descriptor]].
2N/A */
2N/A datalength = (smh->ms_header.length +
2N/A sizeof (smh->ms_header.length)) -
2N/A (sizeof (struct mode_header) +
2N/A smh->ms_header.bdesc_length);
2N/A } else if (SCSI_INVALID_OPCODE(skey, asc, ascq)) {
2N/A /*
2N/A * Fallback and try the 10-byte version of the command.
2N/A */
2N/A sip->si_cdblen = MODE_CMD_LEN_10;
2N/A result = scsi_mode_sense(sip, MODEPAGE_ALLPAGES,
2N/A PC_CURRENT, (caddr_t)allpages, allpages_buflen,
2N/A &headers, &skey, &asc, &ascq);
2N/A
2N/A if (result == 0) {
2N/A datalength = (BE_16(smh_g1->ms_header.length) +
2N/A sizeof (smh_g1->ms_header.length)) -
2N/A (sizeof (struct mode_header_g1) +
2N/A BE_16(smh_g1->ms_header.bdesc_length));
2N/A
2N/A }
2N/A }
2N/A
2N/A if (result == 0 && datalength >= 0) {
2N/A if (nvlist_add_int8(sip->si_dsp->ds_state, "command-length",
2N/A sip->si_cdblen == MODE_CMD_LEN_6 ? 6 : 10) != 0) {
2N/A free(allpages);
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A
2N/A nvl = NULL;
2N/A if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
2N/A nvlist_add_nvlist(sip->si_dsp->ds_state, "modepages",
2N/A nvl) != 0) {
2N/A free(allpages);
2N/A nvlist_free(nvl);
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A
2N/A nvlist_free(nvl);
2N/A result = nvlist_lookup_nvlist(sip->si_dsp->ds_state,
2N/A "modepages", &sip->si_state_modepage);
2N/A assert(result == 0);
2N/A
2N/A /*
2N/A * One of the sets of the commands (above) succeeded, so now
2N/A * look for the mode pages we need and record them appropriately
2N/A * Specifically, we are looking to see if the disk supports
2N/A * MODEPAGE_INFO_EXCPT. If so, we will mark it as supported.
2N/A * Note that examining this mode page for data will occur only
2N/A * if the LOGPAGE for IE (SMART) data is not supported.
2N/A */
2N/A if (mode_page_present(allpages, datalength,
2N/A MODEPAGE_INFO_EXCPT) &&
2N/A (sip->si_dsp->ds_test_mask & SMART_TEST)) {
2N/A nvl = NULL;
2N/A if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
2N/A nvlist_add_nvlist(sip->si_state_modepage,
2N/A "informational-exceptions", nvl) != 0) {
2N/A free(allpages);
2N/A nvlist_free(nvl);
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A nvlist_free(nvl);
2N/A sip->si_supp_mode |= MODEPAGE_SUPP_IEC;
2N/A result = nvlist_lookup_nvlist(sip->si_state_modepage,
2N/A "informational-exceptions", &sip->si_state_iec);
2N/A assert(result == 0);
2N/A }
2N/A
2N/A } else {
2N/A /*
2N/A * If the device failed to respond to one of the basic commands,
2N/A * then assume it's not a SCSI device or otherwise doesn't
2N/A * support the necessary transport.
2N/A */
2N/A if (datalength < 0)
2N/A dprintf("command returned invalid data length (%d)\n",
2N/A datalength);
2N/A else
2N/A dprintf("failed to load modepages (KEY=0x%x "
2N/A "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
2N/A
2N/A result = scsi_set_errno(sip, EDS_NO_TRANSPORT);
2N/A }
2N/A
2N/A free(allpages);
2N/A return (result);
2N/A}
2N/A
2N/A/*
2N/A * Verify a single logpage. This will do some generic validation and then call
2N/A * the logpage-specific function for further verification.
2N/A */
2N/Astatic int
2N/Averify_logpage(ds_scsi_info_t *sip, logpage_validation_entry_t *lp)
2N/A{
2N/A scsi_log_header_t *lhp;
2N/A struct scsi_extended_sense sense;
2N/A int buflen;
2N/A int log_length;
2N/A int result = 0;
2N/A uint_t kp, asc, ascq;
2N/A nvlist_t *nvl;
2N/A
2N/A buflen = MAX_BUFLEN(scsi_log_header_t);
2N/A if ((lhp = calloc(buflen, 1)) == NULL)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A bzero(&sense, sizeof (struct scsi_extended_sense));
2N/A
2N/A nvl = NULL;
2N/A if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
2N/A nvlist_add_nvlist(sip->si_state_logpage, lp->ve_desc, nvl) != 0) {
2N/A nvlist_free(nvl);
2N/A free(lhp);
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A nvlist_free(nvl);
2N/A result = nvlist_lookup_nvlist(sip->si_state_logpage, lp->ve_desc, &nvl);
2N/A assert(result == 0);
2N/A
2N/A result = scsi_log_sense(sip, lp->ve_code,
2N/A PC_CUMULATIVE, (caddr_t)lhp, buflen, &kp, &asc, &ascq);
2N/A
2N/A if (result == 0) {
2N/A log_length = BE_16(lhp->lh_length);
2N/A if (nvlist_add_uint16(nvl, "length", log_length) != 0) {
2N/A free(lhp);
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A
2N/A if (lp->ve_validate(sip, (scsi_log_parameter_header_t *)
2N/A (((char *)lhp) + sizeof (scsi_log_header_t)),
2N/A log_length, nvl) != 0) {
2N/A free(lhp);
2N/A return (-1);
2N/A }
2N/A } else {
2N/A dprintf("failed to load %s log page (KEY=0x%x "
2N/A "ASC=0x%x ASCQ=0x%x)\n", lp->ve_desc, kp, asc, ascq);
2N/A }
2N/A
2N/A free(lhp);
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Load log pages and determine which pages are supported.
2N/A */
2N/Astatic int
2N/Aload_logpages(ds_scsi_info_t *sip)
2N/A{
2N/A int buflen;
2N/A scsi_supported_log_pages_t *sp;
2N/A struct scsi_extended_sense sense;
2N/A int result;
2N/A uint_t sk, asc, ascq;
2N/A int i, j;
2N/A nvlist_t *nvl;
2N/A int tmask = sip->si_dsp->ds_test_mask;
2N/A
2N/A
2N/A buflen = MAX_BUFLEN(scsi_log_header_t);
2N/A if ((sp = calloc(buflen, 1)) == NULL)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A
2N/A bzero(&sense, sizeof (struct scsi_extended_sense));
2N/A
2N/A if ((result = scsi_log_sense(sip, LOGPAGE_SUPP_LIST,
2N/A PC_CUMULATIVE, (caddr_t)sp, buflen, &sk, &asc, &ascq)) == 0) {
2N/A int pagecount = BE_16(sp->slp_hdr.lh_length);
2N/A
2N/A
2N/A /*
2N/A * Only add a page to the supported mask if it is
2N/A * returned from the disk, is one of the three supported
2N/A * by this code, and the test mask (tmask) calls for it
2N/A */
2N/A for (i = 0; i < pagecount; i++) {
2N/A for (j = 0; j < NLOG_VALIDATION; j++) {
2N/A
2N/A if ((log_validation[j].ve_code ==
2N/A sp->slp_pages[i]) &&
2N/A (tmask & log_validation[j].ve_supported))
2N/A sip->si_supp_log |=
2N/A log_validation[j].ve_supported;
2N/A }
2N/A }
2N/A }
2N/A
2N/A free(sp);
2N/A if (result == 0) {
2N/A nvl = NULL;
2N/A if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
2N/A nvlist_add_nvlist(sip->si_dsp->ds_state, "logpages",
2N/A nvl) != 0) {
2N/A nvlist_free(nvl);
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A
2N/A nvlist_free(nvl);
2N/A result = nvlist_lookup_nvlist(sip->si_dsp->ds_state,
2N/A "logpages", &sip->si_state_logpage);
2N/A assert(result == 0);
2N/A
2N/A /*
2N/A * Validate the logpage contents.
2N/A */
2N/A for (i = 0; i < NLOG_VALIDATION; i++) {
2N/A if ((sip->si_supp_log &
2N/A log_validation[i].ve_supported) == 0)
2N/A continue;
2N/A
2N/A /*
2N/A * verify_logpage will clear the supported bit if
2N/A * verification fails.
2N/A */
2N/A if (verify_logpage(sip, &log_validation[i]) != 0)
2N/A return (-1);
2N/A }
2N/A
2N/A } else {
2N/A dprintf("failed to get log pages "
2N/A "(KEY=0x%x ASC=0x%x ASCq=0x%x)\n", sk, asc, ascq);
2N/A }
2N/A
2N/A /*
2N/A * We always return 0 here, even if the required log pages aren't
2N/A * supported.
2N/A */
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Verify that the IE log page is sane. This log page is potentially chock-full
2N/A * of vendor specific information that we do not know how to access. All we can
2N/A * do is check for the generic predictive failure bit. If this log page is not
2N/A * well-formed, then bail out.
2N/A */
2N/Astatic int
2N/Alogpage_ie_verify(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
2N/A int log_length, nvlist_t *nvl)
2N/A{
2N/A int i, plen = 0;
2N/A boolean_t seen = B_FALSE;
2N/A scsi_ie_log_param_t *iep =
2N/A (scsi_ie_log_param_t *)lphp;
2N/A
2N/A for (i = 0; i < log_length; i += plen) {
2N/A iep = (scsi_ie_log_param_t *)((char *)iep + plen);
2N/A
2N/A if (BE_16(iep->ie_hdr.lph_param) == LOGPARAM_IE) {
2N/A if (nvlist_add_boolean_value(nvl, "general",
2N/A B_TRUE) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A
2N/A if (lphp->lph_length < LOGPARAM_IE_MIN_LEN) {
2N/A if (nvlist_add_uint8(nvl,
2N/A "invalid-length", lphp->lph_length) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A } else {
2N/A seen = B_TRUE;
2N/A }
2N/A break;
2N/A }
2N/A
2N/A plen = iep->ie_hdr.lph_length +
2N/A sizeof (scsi_log_parameter_header_t);
2N/A }
2N/A
2N/A if (!seen) {
2N/A sip->si_supp_log &= ~LOGPAGE_SUPP_IE;
2N/A dprintf("IE logpage validation failed\n");
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Verify the contents of the temperature log page. The temperature log page
2N/A * contains two log parameters: the current temperature, and (optionally) the
2N/A * reference temperature. For the verification phase, we check that the two
2N/A * parameters we care about are well-formed. If there is no reference
2N/A * temperature, then we cannot use the page for monitoring purposes.
2N/A */
2N/Astatic int
2N/Alogpage_temp_verify(ds_scsi_info_t *sip,
2N/A scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl)
2N/A{
2N/A int i, plen = 0;
2N/A boolean_t has_reftemp = B_FALSE;
2N/A boolean_t bad_length = B_FALSE;
2N/A ushort_t param_code;
2N/A
2N/A for (i = 0; i < log_length; i += plen) {
2N/A lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
2N/A param_code = BE_16(lphp->lph_param);
2N/A
2N/A switch (param_code) {
2N/A case LOGPARAM_TEMP_CURTEMP:
2N/A if (nvlist_add_boolean_value(nvl, "current-temperature",
2N/A B_TRUE) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A if (lphp->lph_length != LOGPARAM_TEMP_LEN) {
2N/A if (nvlist_add_uint8(nvl,
2N/A "invalid-length", lphp->lph_length) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A bad_length = B_TRUE;
2N/A }
2N/A break;
2N/A
2N/A case LOGPARAM_TEMP_REFTEMP:
2N/A if (nvlist_add_boolean_value(nvl,
2N/A "reference-temperature", B_TRUE) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A if (lphp->lph_length != LOGPARAM_TEMP_LEN) {
2N/A if (nvlist_add_uint8(nvl,
2N/A "invalid-length", lphp->lph_length) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A bad_length = B_TRUE;
2N/A }
2N/A has_reftemp = B_TRUE;
2N/A break;
2N/A }
2N/A
2N/A plen = lphp->lph_length +
2N/A sizeof (scsi_log_parameter_header_t);
2N/A }
2N/A
2N/A if (bad_length || !has_reftemp) {
2N/A sip->si_supp_log &= ~LOGPAGE_SUPP_TEMP;
2N/A dprintf("temperature logpage validation failed\n");
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Verify the contents of the self test log page. The log supports a maximum of
2N/A * 20 entries, where each entry's parameter code is its index in the log. We
2N/A * check that the parameter codes fall within this range, and that the size of
2N/A * each page is what we expect. It's perfectly acceptable for there to be no
2N/A * entries in this log, so we must also be sure to validate the contents as part
2N/A * of the analysis phase.
2N/A */
2N/Astatic int
2N/Alogpage_selftest_verify(ds_scsi_info_t *sip,
2N/A scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl)
2N/A{
2N/A int i, plen = 0;
2N/A boolean_t bad = B_FALSE;
2N/A int entries = 0;
2N/A ushort_t param_code;
2N/A
2N/A for (i = 0; i < log_length; i += plen, entries++) {
2N/A lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
2N/A param_code = BE_16(lphp->lph_param);
2N/A
2N/A if (param_code < LOGPAGE_SELFTEST_MIN_PARAM_CODE ||
2N/A param_code > LOGPAGE_SELFTEST_MAX_PARAM_CODE) {
2N/A if (nvlist_add_uint16(nvl, "invalid-param-code",
2N/A param_code) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A bad = B_TRUE;
2N/A break;
2N/A }
2N/A
2N/A if (lphp->lph_length != LOGPAGE_SELFTEST_PARAM_LEN) {
2N/A if (nvlist_add_uint8(nvl, "invalid-length",
2N/A lphp->lph_length) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A bad = B_TRUE;
2N/A break;
2N/A
2N/A }
2N/A
2N/A plen = lphp->lph_length +
2N/A sizeof (scsi_log_parameter_header_t);
2N/A }
2N/A
2N/A if (bad) {
2N/A sip->si_supp_log &= ~LOGPAGE_SUPP_SELFTEST;
2N/A dprintf("selftest logpage validation failed\n");
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Load the current IE mode pages
2N/A */
2N/Astatic int
2N/Aload_ie_modepage(ds_scsi_info_t *sip)
2N/A{
2N/A struct scsi_ms_hdrs junk_hdrs;
2N/A int result;
2N/A uint_t skey, asc, ascq;
2N/A
2N/A if (!(sip->si_supp_mode & MODEPAGE_SUPP_IEC))
2N/A return (0);
2N/A
2N/A bzero(&sip->si_iec_current, sizeof (sip->si_iec_current));
2N/A bzero(&sip->si_iec_changeable, sizeof (sip->si_iec_changeable));
2N/A
2N/A if ((result = scsi_mode_sense(sip,
2N/A MODEPAGE_INFO_EXCPT, PC_CURRENT, &sip->si_iec_current,
2N/A MODEPAGE_INFO_EXCPT_LEN, &sip->si_hdrs, &skey, &asc,
2N/A &ascq)) == 0) {
2N/A result = scsi_mode_sense(sip,
2N/A MODEPAGE_INFO_EXCPT, PC_CHANGEABLE,
2N/A &sip->si_iec_changeable,
2N/A MODEPAGE_INFO_EXCPT_LEN, &junk_hdrs, &skey, &asc, &ascq);
2N/A }
2N/A
2N/A if (result != 0) {
2N/A dprintf("failed to get IEC modepage (KEY=0x%x "
2N/A "ASC=0x%x ASCQ=0x%x)", skey, asc, ascq);
2N/A sip->si_supp_mode &= ~MODEPAGE_SUPP_IEC;
2N/A } else {
2N/A if (nvlist_add_boolean_value(sip->si_state_iec,
2N/A "dexcpt", sip->si_iec_current.ie_dexcpt) != 0 ||
2N/A nvlist_add_boolean_value(sip->si_state_iec,
2N/A "logerr", sip->si_iec_current.ie_logerr) != 0 ||
2N/A nvlist_add_uint8(sip->si_state_iec,
2N/A "mrie", sip->si_iec_current.ie_mrie) != 0 ||
2N/A nvlist_add_boolean_value(sip->si_state_iec,
2N/A "test", sip->si_iec_current.ie_test) != 0 ||
2N/A nvlist_add_boolean_value(sip->si_state_iec,
2N/A "ewasc", sip->si_iec_current.ie_ewasc) != 0 ||
2N/A nvlist_add_boolean_value(sip->si_state_iec,
2N/A "perf", sip->si_iec_current.ie_perf) != 0 ||
2N/A nvlist_add_boolean_value(sip->si_state_iec,
2N/A "ebf", sip->si_iec_current.ie_ebf) != 0 ||
2N/A nvlist_add_uint32(sip->si_state_iec,
2N/A "interval-timer",
2N/A BE_32(sip->si_iec_current.ie_interval_timer)) != 0 ||
2N/A nvlist_add_uint32(sip->si_state_iec,
2N/A "report-count",
2N/A BE_32(sip->si_iec_current.ie_report_count)) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Enable IE reporting. We prefer the following settings:
2N/A *
2N/A * (1) DEXCPT = 0
2N/A * (3) MRIE = 6 (IE_REPORT_ON_REQUEST)
2N/A * (4) EWASC = 1
2N/A * (6) REPORT COUNT = 0x00000001
2N/A * (7) LOGERR = 1
2N/A *
2N/A * However, not all drives support changing these values, and the current state
2N/A * may be useful enough as-is. For example, some drives support IE logging, but
2N/A * don't support changing the MRIE. In this case, we can still use the
2N/A * information provided by the log page.
2N/A */
2N/Astatic int
2N/Ascsi_enable_ie(ds_scsi_info_t *sip, boolean_t *changed)
2N/A{
2N/A scsi_ie_page_t new_iec_page;
2N/A scsi_ms_hdrs_t hdrs;
2N/A uint_t skey, asc, ascq;
2N/A
2N/A if (!(sip->si_supp_mode & MODEPAGE_SUPP_IEC))
2N/A return (0);
2N/A
2N/A /* A previous enable attempt failed on this drive, don't retry. */
2N/A if (sip->si_dsp->ds_skip_update == B_TRUE) {
2N/A return (0);
2N/A }
2N/A
2N/A bzero(&new_iec_page, sizeof (new_iec_page));
2N/A bzero(&hdrs, sizeof (hdrs));
2N/A
2N/A (void) memcpy(&new_iec_page, &sip->si_iec_current,
2N/A sizeof (new_iec_page));
2N/A
2N/A if (IEC_IE_CHANGEABLE(sip->si_iec_changeable))
2N/A new_iec_page.ie_dexcpt = 0;
2N/A
2N/A if (IEC_MRIE_CHANGEABLE(sip->si_iec_changeable))
2N/A new_iec_page.ie_mrie = IE_REPORT_ON_REQUEST;
2N/A
2N/A /*
2N/A * We only want to enable warning reporting if we are able to change the
2N/A * mrie to report on request. Otherwise, we risk unnecessarily
2N/A * interrupting normal SCSI commands with a CHECK CONDITION code.
2N/A */
2N/A if (IEC_EWASC_CHANGEABLE(sip->si_iec_changeable)) {
2N/A if (new_iec_page.ie_mrie == IE_REPORT_ON_REQUEST)
2N/A new_iec_page.ie_ewasc = 1;
2N/A else
2N/A new_iec_page.ie_ewasc = 0;
2N/A }
2N/A
2N/A if (IEC_RPTCNT_CHANGEABLE(sip->si_iec_changeable))
2N/A new_iec_page.ie_report_count = BE_32(1);
2N/A
2N/A if (IEC_LOGERR_CHANGEABLE(sip->si_iec_changeable))
2N/A new_iec_page.ie_logerr = 1;
2N/A
2N/A /*
2N/A * Now compare the new mode page with the existing one.
2N/A * if there's no difference, there's no need for a mode select
2N/A */
2N/A if (memcmp(&new_iec_page, &sip->si_iec_current,
2N/A MODEPAGE_INFO_EXCPT_LEN) == 0) {
2N/A *changed = B_FALSE;
2N/A } else {
2N/A (void) memcpy(&hdrs, &sip->si_hdrs, sizeof (sip->si_hdrs));
2N/A
2N/A if (scsi_mode_select(sip,
2N/A MODEPAGE_INFO_EXCPT, MODE_SELECT_PF, &new_iec_page,
2N/A MODEPAGE_INFO_EXCPT_LEN, &hdrs, &skey, &asc, &ascq) == 0) {
2N/A *changed = B_TRUE;
2N/A } else {
2N/A dprintf("failed to enable IE (KEY=0x%x "
2N/A "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
2N/A *changed = B_FALSE;
2N/A /* This drive can be skipped in the future. */
2N/A sip->si_dsp->ds_skip_update = B_TRUE;
2N/A }
2N/A }
2N/A
2N/A if (nvlist_add_boolean_value(sip->si_state_iec, "changed",
2N/A *changed) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Clear the GLTSD bit, indicating log pages should be saved to non-volatile
2N/A * storage.
2N/A */
2N/Astatic int
2N/Aclear_gltsd(ds_scsi_info_t *sip)
2N/A{
2N/A scsi_ms_hdrs_t hdrs, junk_hdrs;
2N/A struct mode_control_scsi3 control_pg_cur, control_pg_chg;
2N/A int result;
2N/A uint_t skey, asc, ascq;
2N/A
2N/A bzero(&hdrs, sizeof (hdrs));
2N/A bzero(&control_pg_cur, sizeof (control_pg_cur));
2N/A bzero(&control_pg_chg, sizeof (control_pg_chg));
2N/A
2N/A result = scsi_mode_sense(sip,
2N/A MODEPAGE_CTRL_MODE, PC_CURRENT, &control_pg_cur,
2N/A MODEPAGE_CTRL_MODE_LEN, &hdrs, &skey, &asc, &ascq);
2N/A
2N/A if (result != 0) {
2N/A dprintf("failed to read Control mode page (KEY=0x%x "
2N/A "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
2N/A } else if (control_pg_cur.mode_page.length !=
2N/A PAGELENGTH_MODE_CONTROL_SCSI3) {
2N/A dprintf("SCSI-3 control mode page not supported\n");
2N/A } else if ((result = scsi_mode_sense(sip,
2N/A MODEPAGE_CTRL_MODE, PC_CHANGEABLE, &control_pg_chg,
2N/A MODEPAGE_CTRL_MODE_LEN, &junk_hdrs, &skey, &asc, &ascq))
2N/A != 0) {
2N/A dprintf("failed to read changeable Control mode page (KEY=0x%x "
2N/A "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
2N/A } else if (control_pg_cur.gltsd && !GLTSD_CHANGEABLE(control_pg_chg)) {
2N/A dprintf("gltsd is set and not changeable\n");
2N/A if (nvlist_add_boolean_value(sip->si_dsp->ds_state,
2N/A "gltsd", control_pg_cur.gltsd) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A } else if (control_pg_cur.gltsd) {
2N/A control_pg_cur.gltsd = 0;
2N/A result = scsi_mode_select(sip,
2N/A MODEPAGE_CTRL_MODE, MODE_SELECT_PF, &control_pg_cur,
2N/A MODEPAGE_CTRL_MODE_LEN, &hdrs, &skey, &asc, &ascq);
2N/A if (result != 0)
2N/A dprintf("failed to enable GLTSD (KEY=0x%x "
2N/A "ASC=0x%x ASCQ=0x%x\n", skey, asc, ascq);
2N/A if (nvlist_add_boolean_value(sip->si_dsp->ds_state,
2N/A "gltsd", control_pg_cur.gltsd) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Fetch the contents of the logpage, and then call the logpage-specific
2N/A * analysis function. The analysis function is responsible for detecting any
2N/A * faults and filling in the details.
2N/A */
2N/Astatic int
2N/Aanalyze_one_logpage(ds_scsi_info_t *sip, logpage_validation_entry_t *entry)
2N/A{
2N/A scsi_log_header_t *lhp;
2N/A scsi_log_parameter_header_t *lphp;
2N/A int buflen;
2N/A int log_length;
2N/A uint_t skey, asc, ascq;
2N/A int result;
2N/A
2N/A buflen = MAX_BUFLEN(scsi_log_header_t);
2N/A if ((lhp = calloc(buflen, 1)) == NULL)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A
2N/A result = scsi_log_sense(sip, entry->ve_code,
2N/A PC_CUMULATIVE, (caddr_t)lhp, buflen, &skey, &asc, &ascq);
2N/A
2N/A if (result == 0) {
2N/A log_length = BE_16(lhp->lh_length);
2N/A lphp = (scsi_log_parameter_header_t *)(((uchar_t *)lhp) +
2N/A sizeof (scsi_log_header_t));
2N/A
2N/A result = entry->ve_analyze(sip, lphp, log_length);
2N/A } else {
2N/A result = scsi_set_errno(sip, EDS_IO);
2N/A }
2N/A
2N/A free(lhp);
2N/A return (result);
2N/A}
2N/A
2N/A/*
2N/A * Analyze the IE logpage. If we find an IE log record with a non-zero 'asc',
2N/A * then we have a fault.
2N/A */
2N/Astatic int
2N/Alogpage_ie_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
2N/A int log_length)
2N/A{
2N/A int i, plen = 0;
2N/A scsi_ie_log_param_t *iep = (scsi_ie_log_param_t *)lphp;
2N/A nvlist_t *nvl;
2N/A
2N/A assert(sip->si_dsp->ds_predfail == NULL);
2N/A if (nvlist_alloc(&sip->si_dsp->ds_predfail, NV_UNIQUE_NAME, 0) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A nvl = sip->si_dsp->ds_predfail;
2N/A
2N/A for (i = 0; i < log_length; i += plen) {
2N/A iep = (scsi_ie_log_param_t *)((char *)iep + plen);
2N/A
2N/A /*
2N/A * Even though we validated the length during the initial phase,
2N/A * never trust the device.
2N/A */
2N/A if (BE_16(iep->ie_hdr.lph_param) == LOGPARAM_IE &&
2N/A iep->ie_hdr.lph_length >= LOGPARAM_IE_MIN_LEN) {
2N/A if (nvlist_add_uint8(nvl, FM_EREPORT_PAYLOAD_SCSI_ASC,
2N/A iep->ie_asc) != 0 ||
2N/A nvlist_add_uint8(nvl, FM_EREPORT_PAYLOAD_SCSI_ASCQ,
2N/A iep->ie_ascq) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A
2N/A if (iep->ie_asc != 0)
2N/A sip->si_dsp->ds_faults |=
2N/A DS_FAULT_PREDFAIL;
2N/A break;
2N/A }
2N/A plen = iep->ie_hdr.lph_length +
2N/A sizeof (scsi_log_parameter_header_t);
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Alogpage_temp_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
2N/A int log_length)
2N/A{
2N/A int i, plen = 0;
2N/A uint8_t reftemp, curtemp;
2N/A ushort_t param_code;
2N/A scsi_temp_log_param_t *temp;
2N/A nvlist_t *nvl;
2N/A
2N/A assert(sip->si_dsp->ds_overtemp == NULL);
2N/A if (nvlist_alloc(&sip->si_dsp->ds_overtemp, NV_UNIQUE_NAME, 0) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A nvl = sip->si_dsp->ds_overtemp;
2N/A
2N/A reftemp = curtemp = INVALID_TEMPERATURE;
2N/A for (i = 0; i < log_length; i += plen) {
2N/A lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
2N/A param_code = BE_16(lphp->lph_param);
2N/A temp = (scsi_temp_log_param_t *)lphp;
2N/A
2N/A switch (param_code) {
2N/A case LOGPARAM_TEMP_CURTEMP:
2N/A if (lphp->lph_length != LOGPARAM_TEMP_LEN)
2N/A break;
2N/A
2N/A if (nvlist_add_uint8(nvl,
2N/A FM_EREPORT_PAYLOAD_SCSI_CURTEMP,
2N/A temp->t_temp) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A curtemp = temp->t_temp;
2N/A break;
2N/A
2N/A case LOGPARAM_TEMP_REFTEMP:
2N/A if (lphp->lph_length != LOGPARAM_TEMP_LEN)
2N/A break;
2N/A
2N/A if (nvlist_add_uint8(nvl,
2N/A FM_EREPORT_PAYLOAD_SCSI_THRESHTEMP,
2N/A temp->t_temp) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A reftemp = temp->t_temp;
2N/A break;
2N/A }
2N/A
2N/A plen = lphp->lph_length +
2N/A sizeof (scsi_log_parameter_header_t);
2N/A }
2N/A
2N/A if (reftemp != INVALID_TEMPERATURE && curtemp != INVALID_TEMPERATURE &&
2N/A curtemp > reftemp)
2N/A sip->si_dsp->ds_faults |= DS_FAULT_OVERTEMP;
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Alogpage_selftest_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
2N/A int log_length)
2N/A{
2N/A int i, plen = 0;
2N/A int entries = 0;
2N/A ushort_t param_code;
2N/A scsi_selftest_log_param_t *stp;
2N/A nvlist_t *nvl;
2N/A
2N/A assert(sip->si_dsp->ds_testfail == NULL);
2N/A if (nvlist_alloc(&sip->si_dsp->ds_testfail, NV_UNIQUE_NAME, 0) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A nvl = sip->si_dsp->ds_testfail;
2N/A
2N/A for (i = 0; i < log_length; i += plen, entries++) {
2N/A lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
2N/A param_code = BE_16(lphp->lph_param);
2N/A stp = (scsi_selftest_log_param_t *)lphp;
2N/A
2N/A if (param_code >= LOGPAGE_SELFTEST_MIN_PARAM_CODE &&
2N/A param_code <= LOGPAGE_SELFTEST_MAX_PARAM_CODE &&
2N/A lphp->lph_length >= LOGPAGE_SELFTEST_PARAM_LEN) {
2N/A /*
2N/A * We always log the last result, or the result of the
2N/A * last completed test.
2N/A */
2N/A if ((param_code == 1 ||
2N/A SELFTEST_COMPLETE(stp->st_results))) {
2N/A if (nvlist_add_uint8(nvl,
2N/A FM_EREPORT_PAYLOAD_SCSI_RESULTCODE,
2N/A stp->st_results) != 0 ||
2N/A nvlist_add_uint16(nvl,
2N/A FM_EREPORT_PAYLOAD_SCSI_TIMESTAMP,
2N/A BE_16(stp->st_timestamp)) != 0 ||
2N/A nvlist_add_uint8(nvl,
2N/A FM_EREPORT_PAYLOAD_SCSI_SEGMENT,
2N/A stp->st_number) != 0 ||
2N/A nvlist_add_uint64(nvl,
2N/A FM_EREPORT_PAYLOAD_SCSI_ADDRESS,
2N/A BE_64(stp->st_lba)) != 0)
2N/A return (scsi_set_errno(sip,
2N/A EDS_NOMEM));
2N/A
2N/A if (SELFTEST_COMPLETE(stp->st_results)) {
2N/A if (stp->st_results != SELFTEST_OK)
2N/A sip->si_dsp->ds_faults |=
2N/A DS_FAULT_TESTFAIL;
2N/A return (0);
2N/A }
2N/A }
2N/A }
2N/A
2N/A plen = lphp->lph_length +
2N/A sizeof (scsi_log_parameter_header_t);
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Analyze the IE mode sense page explicitly. This is only needed if the IE log
2N/A * page is not supported.
2N/A */
2N/Astatic int
2N/Aanalyze_ie_sense(ds_scsi_info_t *sip)
2N/A{
2N/A uint_t skey, asc, ascq;
2N/A nvlist_t *nvl;
2N/A
2N/A /*
2N/A * Don't bother checking if we weren't able to set our MRIE correctly.
2N/A */
2N/A if (sip->si_iec_current.ie_mrie != IE_REPORT_ON_REQUEST)
2N/A return (0);
2N/A
2N/A if (scsi_request_sense(sip, &skey, &asc, &ascq) != 0) {
2N/A dprintf("failed to request IE page (KEY=0x%x ASC=0x%x "
2N/A "ASCQ=0x%x)\n", skey, asc, ascq);
2N/A return (scsi_set_errno(sip, EDS_IO));
2N/A } else if (skey == KEY_NO_SENSE) {
2N/A assert(sip->si_dsp->ds_predfail == NULL);
2N/A if (nvlist_alloc(&sip->si_dsp->ds_predfail,
2N/A NV_UNIQUE_NAME, 0) != 0)
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A nvl = sip->si_dsp->ds_predfail;
2N/A
2N/A if (nvlist_add_uint8(nvl,
2N/A FM_EREPORT_PAYLOAD_SCSI_ASC, asc) != 0 ||
2N/A nvlist_add_uint8(nvl,
2N/A FM_EREPORT_PAYLOAD_SCSI_ASCQ, ascq) != 0) {
2N/A nvlist_free(nvl);
2N/A return (scsi_set_errno(sip, EDS_NOMEM));
2N/A }
2N/A
2N/A if (asc != 0)
2N/A sip->si_dsp->ds_faults |= DS_FAULT_PREDFAIL;
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Clean up the scsi-specific information structure.
2N/A */
2N/Astatic void
2N/Ads_scsi_close(void *arg)
2N/A{
2N/A ds_scsi_info_t *sip = arg;
2N/A if (sip->si_sim)
2N/A (void) dlclose(sip->si_sim);
2N/A
2N/A free(sip);
2N/A}
2N/A
2N/A/*
2N/A * Initialize a single disk. Initialization consists of:
2N/A *
2N/A * 1. Check to see if the IE mechanism is enabled via MODE SENSE for the IE
2N/A * Control page (page 0x1C).
2N/A *
2N/A * 2. If the IE page is available, try to set the following parameters:
2N/A *
2N/A * DEXCPT 0 Enable exceptions
2N/A * MRIE 6 Only report IE information on request
2N/A * EWASC 1 Enable warning reporting
2N/A * REPORT COUNT 1 Only report an IE exception once
2N/A * LOGERR 1 Enable logging of errors
2N/A *
2N/A * The remaining fields are left as-is, preserving the current values. If we
2N/A * cannot set some of these fields, then we do our best. Some drives may
2N/A * have a static configuration which still allows for some monitoring.
2N/A *
2N/A * 3. Check to see if the IE log page (page 0x2F) is supported by issuing a
2N/A * LOG SENSE command.
2N/A *
2N/A * 4. Check to see if the self-test log page (page 0x10) is supported.
2N/A *
2N/A * 5. Check to see if the temperature log page (page 0x0D) is supported, and
2N/A * contains a reference temperature.
2N/A *
2N/A * 6. Clear the GLTSD bit in control mode page 0xA. This will allow the drive
2N/A * to save each of the log pages described above to nonvolatile storage.
2N/A * This is essential if the drive is to remember its failures across
2N/A * loss of power.
2N/A */
2N/Astatic void *
2N/Ads_scsi_open_common(disk_status_t *dsp, ds_scsi_info_t *sip)
2N/A{
2N/A boolean_t changed;
2N/A
2N/A sip->si_dsp = dsp;
2N/A
2N/A /* Load and validate mode pages */
2N/A if (load_modepages(sip) != 0) {
2N/A ds_scsi_close(sip);
2N/A return (NULL);
2N/A }
2N/A
2N/A /* Load and validate log pages */
2N/A if (load_logpages(sip) != 0) {
2N/A ds_scsi_close(sip);
2N/A return (NULL);
2N/A }
2N/A
2N/A /*
2N/A * Load IE state, but only if the IE logpage is not supported.
2N/A * The ie_modepage is only used in the read phase if the logpage
2N/A * is not supported so save the effort here.
2N/A */
2N/A if (!(sip->si_supp_log & LOGPAGE_SUPP_IE)) {
2N/A if (load_ie_modepage(sip) != 0 ||
2N/A scsi_enable_ie(sip, &changed) != 0 ||
2N/A (changed && load_ie_modepage(sip) != 0)) {
2N/A ds_scsi_close(sip);
2N/A return (NULL);
2N/A }
2N/A }
2N/A
2N/A /* Clear the GLTSD bit in the control page */
2N/A if (sip->si_supp_log != 0 && clear_gltsd(sip) != 0) {
2N/A ds_scsi_close(sip);
2N/A return (NULL);
2N/A }
2N/A
2N/A return (sip);
2N/A}
2N/A
2N/Astatic void *
2N/Ads_scsi_open_uscsi(disk_status_t *dsp)
2N/A{
2N/A ds_scsi_info_t *sip;
2N/A
2N/A if ((sip = calloc(sizeof (ds_scsi_info_t), 1)) == NULL) {
2N/A (void) ds_set_errno(dsp, EDS_NOMEM);
2N/A return (NULL);
2N/A }
2N/A
2N/A return (ds_scsi_open_common(dsp, sip));
2N/A}
2N/A
2N/Astatic void *
2N/Ads_scsi_open_sim(disk_status_t *dsp)
2N/A{
2N/A ds_scsi_info_t *sip;
2N/A
2N/A if ((sip = calloc(sizeof (ds_scsi_info_t), 1)) == NULL) {
2N/A (void) ds_set_errno(dsp, EDS_NOMEM);
2N/A return (NULL);
2N/A }
2N/A
2N/A if ((sip->si_sim = dlopen(dsp->ds_path, RTLD_LAZY)) == NULL) {
2N/A (void) ds_set_errno(dsp, EDS_NO_TRANSPORT);
2N/A free(sip);
2N/A return (NULL);
2N/A }
2N/A
2N/A return (ds_scsi_open_common(dsp, sip));
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Scan for any faults. The following steps are performed:
2N/A *
2N/A * 1. If the temperature log page is supported, check the current temperature
2N/A * and threshold. If the current temperature exceeds the threshold, report
2N/A * and overtemp fault.
2N/A *
2N/A * 2. If the selftest log page is supported, check to the last completed self
2N/A * test. If the last completed test resulted in failure, report a selftest
2N/A * fault.
2N/A *
2N/A * 3. If the IE log page is supported, check to see if failure is predicted. If
2N/A * so, indicate a predictive failure fault.
2N/A *
2N/A * 4. If the IE log page is not supported, but the mode page supports report on
2N/A * request mode, then issue a REQUEST SENSE for the mode page. Indicate a
2N/A * predictive failure fault if necessary.
2N/A */
2N/Astatic int
2N/Ads_scsi_scan(void *arg)
2N/A{
2N/A ds_scsi_info_t *sip = arg;
2N/A int i;
2N/A
2N/A for (i = 0; i < NLOG_VALIDATION; i++) {
2N/A if ((sip->si_supp_log & log_validation[i].ve_supported) == 0)
2N/A continue;
2N/A
2N/A if (analyze_one_logpage(sip, &log_validation[i]) != 0)
2N/A return (-1);
2N/A }
2N/A
2N/A if (!(sip->si_supp_log & LOGPAGE_SUPP_IE) &&
2N/A (sip->si_supp_mode & MODEPAGE_SUPP_IEC) &&
2N/A analyze_ie_sense(sip) != 0)
2N/A return (-1);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Ads_transport_t ds_scsi_uscsi_transport = {
2N/A ds_scsi_open_uscsi,
2N/A ds_scsi_close,
2N/A ds_scsi_scan
2N/A};
2N/A
2N/Ads_transport_t ds_scsi_sim_transport = {
2N/A ds_scsi_open_sim,
2N/A ds_scsi_close,
2N/A ds_scsi_scan
2N/A};