diff options
Diffstat (limited to 'usr/src/lib')
32 files changed, 4956 insertions, 271 deletions
diff --git a/usr/src/lib/fm/Makefile b/usr/src/lib/fm/Makefile index 925e471c74..046d9346cb 100644 --- a/usr/src/lib/fm/Makefile +++ b/usr/src/lib/fm/Makefile @@ -36,6 +36,7 @@ i386_SUBDIRS = SUBDIRS = \ libdiagcode \ + libdiskstatus \ libfmd_adm \ libfmd_log \ libfmd_msg \ diff --git a/usr/src/lib/fm/libdiskstatus/Makefile b/usr/src/lib/fm/libdiskstatus/Makefile new file mode 100644 index 0000000000..b051273f20 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/Makefile @@ -0,0 +1,56 @@ +# +# 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. +# +#ident "%Z%%M% %I% %E% SMI" + +include ../../Makefile.lib +include ../Makefile.lib + +HDRDIR= common +FMHDRS= libdiskstatus.h + +SUBDIRS= $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +all := TARGET= all +clean := TARGET= clean +clobber := TARGET= clobber +install := TARGET= install +lint := TARGET= lint + +.KEEP_STATE: + +all clean clobber install lint: $(SUBDIRS) + +install_h: $(ROOTFMHDRS) + +check: $(CHECKHDRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include ../../Makefile.targ +include ../Makefile.targ diff --git a/usr/src/lib/fm/libdiskstatus/Makefile.com b/usr/src/lib/fm/libdiskstatus/Makefile.com new file mode 100644 index 0000000000..ee2fa3725a --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/Makefile.com @@ -0,0 +1,56 @@ +# +# 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. +# +#ident "%Z%%M% %I% %E% SMI" + +LIBRARY= libdiskstatus.a +VERS= .1 + +OBJECTS= libdiskstatus.o \ + ds_scsi.o \ + ds_scsi_sim.o \ + ds_scsi_uscsi.o \ + ds_util.o + +include ../../../Makefile.lib +include ../../Makefile.lib + +LIBS= $(DYNLIB) $(LINTLIB) + +SRCDIR= ../common + +INCS += -I$(SRCDIR) +LDLIBS += -lc -lnvpair +CPPFLAGS += $(INCS) + +$(LINTLIB) := SRCS= $(SRCDIR)/$(LINTSRC) + +.KEEP_STATE: + +all: $(LIBS) + +lint: lintcheck + +include ../../../Makefile.targ +include ../../Makefile.targ diff --git a/usr/src/lib/fm/libdiskstatus/amd64/Makefile b/usr/src/lib/fm/libdiskstatus/amd64/Makefile new file mode 100644 index 0000000000..5375e35120 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/amd64/Makefile @@ -0,0 +1,30 @@ +# +# 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. +# +#ident "%Z%%M% %I% %E% SMI" + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) diff --git a/usr/src/lib/fm/libdiskstatus/common/ds_impl.h b/usr/src/lib/fm/libdiskstatus/common/ds_impl.h new file mode 100644 index 0000000000..34f8b15d75 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/ds_impl.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef _DS_IMPL_H +#define _DS_IMPL_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <dlfcn.h> +#include <libnvpair.h> +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct disk_status; + +typedef struct ds_transport { + void *(*dt_open)(struct disk_status *); + void (*dt_close)(void *); + int (*dt_scan)(void *); +} ds_transport_t; + +struct disk_status { + char *ds_path; /* path to device */ + int ds_fd; /* device file descriptor */ + ds_transport_t *ds_transport; /* associated transport */ + void *ds_data; /* transport-specific data */ + int ds_faults; /* mask of current faults */ + nvlist_t *ds_overtemp; /* overtemp */ + nvlist_t *ds_predfail; /* predict fail */ + nvlist_t *ds_testfail; /* self test fail */ + int ds_error; /* last error */ + nvlist_t *ds_state; /* protocol state */ +}; + +#define DS_FAULT_OVERTEMP 0x1 +#define DS_FAULT_PREDFAIL 0x2 +#define DS_FAULT_TESTFAIL 0x4 + +extern void dprintf(const char *, ...); +extern void ddump(const char *, const void *, size_t); +extern boolean_t ds_debug; + +extern int ds_set_errno(struct disk_status *, int); + +#ifdef __cplusplus +} +#endif + +#endif /* _DS_IMPL_H */ 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 +}; diff --git a/usr/src/lib/fm/libdiskstatus/common/ds_scsi.h b/usr/src/lib/fm/libdiskstatus/common/ds_scsi.h new file mode 100644 index 0000000000..6d2648f06b --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/ds_scsi.h @@ -0,0 +1,327 @@ +/* + * 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. + */ + +#ifndef _DS_SCSI_H +#define _DS_SCSI_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/types.h> +#include <sys/byteorder.h> +#include <sys/scsi/scsi.h> + +#include "ds_impl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(_BIT_FIELDS_LTOH) && !defined(_BIT_FIELDS_HTOL) +#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined +#endif + +/* + * Log page structures + */ +#pragma pack(1) + +typedef struct scsi_log_header { +#if defined(_BIT_FIELDS_LTOH) + uint8_t lh_code : 6, + __reserved : 2; +#else + uint8_t __reserved : 2, + lh_code : 6; +#endif + uint8_t __reserved2; + uint16_t lh_length; +} scsi_log_header_t; + +typedef struct scsi_log_parameter_header { + uint16_t lph_param; +#if defined(_BIT_FIELDS_LTOH) + uint8_t lph_lp : 1, + lph_lbin : 1, + lph_tmc : 2, + lph_etc : 1, + lph_tsd : 1, + lph_ds : 1, + lph_du : 1; +#else + uint8_t lph_du : 1, + lph_ds : 1, + lph_tsd : 1, + lph_etc : 1, + lph_tmc : 2, + lph_lbin : 1, + lph_lp : 1; +#endif + uint8_t lph_length; +} scsi_log_parameter_header_t; + +typedef struct scsi_supported_log_pages { + scsi_log_header_t slp_hdr; + uchar_t slp_pages[1]; +} scsi_supported_log_pages_t; + +typedef struct scsi_ie_log_param { + scsi_log_parameter_header_t ie_hdr; + uchar_t ie_asc; + uchar_t ie_ascq; +} scsi_ie_log_param_t; + +/* + * The SCSI-3 SPC document states that IE log page (0x2F) parameter 0 + * must have a length of at least 4 (including the length byte). + */ +#define LOGPARAM_IE_MIN_LEN 2 /* the asc and ascq fields */ + +#define INVALID_TEMPERATURE 0xff + +#define LOGPARAM_IE 0x0000 + +typedef struct scsi_temp_log_param { + scsi_log_parameter_header_t t_hdr; + uchar_t __reserved; + uchar_t t_temp; +} scsi_temp_log_param_t; + +typedef struct scsi_selftest_log_param { + scsi_log_parameter_header_t st_hdr; +#if defined(_BIT_FIELDS_LTOH) + uint8_t st_results : 4, + __reserved1 : 1, + st_testcode : 3; +#else + uint8_t st_testcode : 3, + __reserved1 : 1, + st_results : 4; +#endif + uint8_t st_number; + uint16_t st_timestamp; + uint64_t st_lba; +#if defined(_BIT_FIELDS_LTOH) + uint8_t st_sensekey : 4, + __reserved2 : 4; +#else + uint8_t __reserved2 : 4, + st_sensekey : 4; +#endif + uint8_t st_asc; + uint8_t st_ascq; + uint8_t st_vendor; +} scsi_selftest_log_param_t; + +/* The results field of the self-test log parameter */ +#define SELFTEST_OK 0x0 +#define SELFTEST_ABORT_REQUEST 0x1 +#define SELFTEST_ABORT_OTHER 0x2 +#define SELFTEST_FAILURE_INCOMPLETE 0x3 +#define SELFTEST_FAILURE_SEG_UNKNOWN 0x4 +#define SELFTEST_FAILURE_SEG_FIRST 0x5 +#define SELFTEST_FAILURE_SEG_SECOND 0x6 +#define SELFTEST_FAILURE_SEG_OTHER 0x7 +#define SELFTEST_INPROGRESS 0xf + +#define SELFTEST_COMPLETE(code) \ + ((code) == SELFTEST_OK || \ + ((code) >= SELFTEST_FAILURE_INCOMPLETE && \ + ((code) <= SELFTEST_FAILURE_SEG_OTHER))) + +#define LOGPARAM_TEMP_CURTEMP 0x0000 +#define LOGPARAM_TEMP_REFTEMP 0x0001 + +#define LOGPARAM_TEMP_LEN \ + (sizeof (scsi_temp_log_param_t) - \ + sizeof (scsi_log_parameter_header_t)) + +/* + * Mode sense/select page header information + */ +typedef struct scsi_ms_header { + struct mode_header ms_header; + struct block_descriptor ms_descriptor; +} scsi_ms_header_t; + +typedef struct scsi_ms_header_g1 { + struct mode_header_g1 ms_header; + struct block_descriptor ms_descriptor; +} scsi_ms_header_g1_t; + +typedef struct scsi_ms_hdrs { + int ms_length; + union { + scsi_ms_header_t g0; + scsi_ms_header_g1_t g1; + } ms_hdr; +} scsi_ms_hdrs_t; + +typedef struct scsi_ie_page { + struct mode_page ie_mp; +#if defined(_BIT_FIELDS_LTOH) + uint8_t ie_logerr : 1, /* Errors should be logged */ + __reserved1 : 1, + ie_test : 1, /* Enable test gen of IEs */ + ie_dexcpt : 1, /* Disable exceptions */ + ie_ewasc : 1, /* Enable warning generation */ + ie_ebf : 1, /* enable backgrnd functions */ + __reserved2 : 1, + ie_perf : 1; /* No delays during excptns */ + uint8_t ie_mrie : 4, /* Method/reporting excptons */ + __reserved3 : 4; +#else + uint8_t ie_perf : 1, /* No delays during excptons */ + __reserved2 : 1, + ie_ebf : 1, /* enable background funcs */ + ie_ewasc : 1, /* Enable warning generation */ + ie_dexcpt : 1, /* Disable exceptions */ + ie_test : 1, /* Enable test gen of IEs */ + __reserved1 : 1, + ie_logerr : 1; /* Errors should be logged */ + uint8_t __reserved3 : 4, + ie_mrie : 4; /* Method of report excptns */ +#endif + uint32_t ie_interval_timer; /* reporting interval for IEs */ + uint32_t ie_report_count; /* # of times to report an IE */ +} scsi_ie_page_t; + +#pragma pack() + +#define MODEPAGE_INFO_EXCPT_LEN (sizeof (scsi_ie_page_t)) + +#define IEC_IE_ENABLED(ies) ((ies).ie_dexcpt == 0) +#define IEC_IE_CHANGEABLE(ies) ((ies).ie_dexcpt == 1) +#define IEC_MRIE_CHANGEABLE(ies) ((ies).ie_mrie == 0xf) +#define IEC_PERF_CHANGEABLE(ies) ((ies).ie_perf == 1) +#define IEC_EWASC_CHANGEABLE(ies) ((ies).ie_ewasc == 1) +#define IEC_TEST_CHANGEABLE(ies) ((ies).ie_test == 1) +#define IEC_RPTCNT_CHANGEABLE(ies) ((ies).ie_report_count == BE_32(0xffffffff)) +#define IEC_LOGERR_CHANGEABLE(ies) ((ies).ie_logerr == 1) + +/* + * Values for the MRIE field of the informational exceptions control mode page + */ +#define IE_REPORT_NONE 0 +#define IE_REPORT_ASYNCH 1 +#define IE_REPORT_UNIT_ATTN 2 +#define IE_REPORT_RECOV_ERR_COND 3 +#define IE_REPORT_RECOV_ERR_ALWAYS 4 +#define IE_REPORT_NO_SENSE 5 +#define IE_REPORT_ON_REQUEST 6 + +/* + * Constants in support of the CONTROL MODE mode page (page 0xA) + */ +#define MODEPAGE_CTRL_MODE_LEN (sizeof (struct mode_control_scsi3)) +#define GLTSD_CHANGEABLE(chg) ((chg).gltsd == 1) + +#define LOGPAGE_SELFTEST_MIN_PARAM_CODE 0x0001 +#define LOGPAGE_SELFTEST_MAX_PARAM_CODE 0x0014 + +#define LOGPAGE_SELFTEST_PARAM_LEN \ + ((sizeof (scsi_selftest_log_param_t)) - \ + (sizeof (scsi_log_parameter_header_t))) + +/* + * Macro to extract the length of a mode sense page + * as returned by a target. + */ +#define MODESENSE_PAGE_LEN(p) (((int)((struct mode_page *)p)->length) + \ + sizeof (struct mode_page)) + +/* + * Mode Select options + */ +#define MODE_SELECT_SP 0x01 +#define MODE_SELECT_PF 0x10 + + +/* + * Mode Sense Page Control + */ +#define PC_CURRENT (0 << 6) +#define PC_CHANGEABLE (1 << 6) +#define PC_DEFAULT (2 << 6) +#define PC_SAVED (3 << 6) + +/* + * Log Sense Page Control + */ +#define PC_CUMULATIVE (1 << 6) + +/* + * LOG page codes + */ +#define LOGPAGE_SUPP_LIST 0x00 +#define LOGPAGE_TEMP 0x0d +#define LOGPAGE_SELFTEST 0x10 +#define LOGPAGE_IE 0x2f + +/* ASC constants */ +#define ASC_INVALID_OPCODE 0x20 +#define ASC_INVALID_CDB_FIELD 0x24 +#define ASC_FAILURE_PREDICTION_THRESHOLD_EXCEEDED 0x5d + +/* ASCQ constants */ +#define ASCQ_INVALID_OPCODE 0 + +/* Error tests */ +#define SCSI_INVALID_OPCODE(s, a, aq) \ + (((s) == KEY_ILLEGAL_REQUEST) && ((a) == ASC_INVALID_OPCODE) && \ + ((aq) == ASCQ_INVALID_OPCODE)) + +#define MODE_PAGE_UNSUPPORTED(s, a, aq) \ + (((s) == KEY_ILLEGAL_REQUEST) && ((a) == ASC_INVALID_CDB_FIELD)) + +/* command length to use */ +#define MODE_CMD_LEN_UNKNOWN 0 +#define MODE_CMD_LEN_6 1 +#define MODE_CMD_LEN_10 2 + +/* supported modepages bitmask */ +#define MODEPAGE_SUPP_IEC 0x1 + +/* supported logpages bitmask */ +#define LOGPAGE_SUPP_IE 0x1 +#define LOGPAGE_SUPP_TEMP 0x2 +#define LOGPAGE_SUPP_SELFTEST 0x4 + +#define MSG_BUFLEN 256 + +/* + * For SCSI commands which want to accept arbitrary length responses, we need to + * allocate an appropriate sized buffer. The maximum length is USHRT_MAX, + * because some devices return nothing if the buffer length is too big. + */ +#define MAX_BUFLEN(type) (USHRT_MAX - sizeof (type)) + +extern ds_transport_t ds_scsi_uscsi_transport; +extern ds_transport_t ds_scsi_sim_transport; + +#ifdef __cplusplus +} +#endif + +#endif /* _DS_SCSI_H */ diff --git a/usr/src/lib/fm/libdiskstatus/common/ds_scsi_sim.c b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_sim.c new file mode 100644 index 0000000000..1750f67c27 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_sim.c @@ -0,0 +1,179 @@ +/* + * 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" + +/* + * SCSI simulator. + * + * For testing purposes, we need a way to simulate arbitrary SCSI responses. A + * completely flexible SCSI simulation language would be a large undertaking, + * given the number of possible outcomes. Instead, we opt for the simpler route + * of using a shared object which implements versions of these functions. + * + * If a shared object doesn't implement a given function, or if the function + * returns non-zero, then the simulator will provide a suitable response + * indicating the functionality isn't supported. + */ + +#include <libdiskstatus.h> + +#include "ds_scsi.h" +#include "ds_scsi_sim.h" + +static int +check_invalid_code(int ret, void *rqbuf) +{ + if (ret != 0) { + struct scsi_extended_sense *sensep = rqbuf; + + sensep->es_key = KEY_ILLEGAL_REQUEST; + sensep->es_add_len = 6; + sensep->es_code = CODE_FMT_FIXED_CURRENT; + sensep->es_add_code = ASC_INVALID_OPCODE; + sensep->es_qual_code = ASCQ_INVALID_OPCODE; + ret = -1; + } + + return (ret); +} + +typedef int (*scsi_mode_sense_f)(int, int, caddr_t, int, scsi_ms_header_t *, + void *, int *); + +int +simscsi_mode_sense(void *hdl, int page_code, int page_control, + caddr_t page_data, int page_size, scsi_ms_header_t *header, + void *rqbuf, int *rqblen) +{ + scsi_mode_sense_f dscsi_mode_sense; + int ret = -1; + + dscsi_mode_sense = (scsi_mode_sense_f)dlsym(hdl, "scsi_mode_sense"); + + if (dscsi_mode_sense != NULL) + ret = (*dscsi_mode_sense)(page_code, page_control, page_data, + page_size, header, rqbuf, rqblen); + + return (check_invalid_code(ret, rqbuf)); +} + +typedef int (*scsi_mode_sense_10_f)(int, int, caddr_t, int, + scsi_ms_header_g1_t *, void *, int *); + +int +simscsi_mode_sense_10(void *hdl, int page_code, int page_control, + caddr_t page_data, int page_size, scsi_ms_header_g1_t *header, + void *rqbuf, int *rqblen) +{ + scsi_mode_sense_10_f dscsi_mode_sense_10; + int ret = -1; + + dscsi_mode_sense_10 = (scsi_mode_sense_10_f)dlsym(hdl, + "scsi_mode_sense_10"); + + if (dscsi_mode_sense_10 != NULL) + ret = (*dscsi_mode_sense_10)(page_code, page_control, page_data, + page_size, header, rqbuf, rqblen); + + return (check_invalid_code(ret, rqbuf)); +} + +typedef int (*scsi_mode_select_f)(int, int, caddr_t, int, scsi_ms_header_t *, + void *, int *); + +int +simscsi_mode_select(void *hdl, int page_code, int options, caddr_t page_data, + int page_size, scsi_ms_header_t *header, void *rqbuf, int *rqblen) +{ + scsi_mode_select_f dscsi_mode_select; + int ret = -1; + + dscsi_mode_select = (scsi_mode_select_f)(dlsym(hdl, + "scsi_mode_select")); + + if (dscsi_mode_select != NULL) + ret = (*dscsi_mode_select)(page_code, options, page_data, + page_size, header, rqbuf, rqblen); + + return (check_invalid_code(ret, rqbuf)); +} + +typedef int (*scsi_mode_select_10_f)(int, int, caddr_t, int, + scsi_ms_header_g1_t *, void *, int *); + +int +simscsi_mode_select_10(void *hdl, int page_code, int options, + caddr_t page_data, int page_size, scsi_ms_header_g1_t *header, + void *rqbuf, int *rqblen) +{ + scsi_mode_select_10_f dscsi_mode_select_10; + int ret = -1; + + dscsi_mode_select_10 = (scsi_mode_select_10_f)dlsym(hdl, + "scsi_mode_select_10"); + + if (dscsi_mode_select_10 != NULL) + ret = (*dscsi_mode_select_10)(page_code, options, page_data, + page_size, header, rqbuf, rqblen); + + return (check_invalid_code(ret, rqbuf)); +} + +typedef int (*scsi_log_sense_f)(int, int, caddr_t, int, void *, int *); + +int +simscsi_log_sense(void *hdl, int page_code, int page_control, + caddr_t page_data, int page_size, void *rqbuf, int *rqblen) +{ + scsi_log_sense_f dscsi_log_sense; + int ret = -1; + + dscsi_log_sense = (scsi_log_sense_f)dlsym(hdl, "scsi_log_sense"); + + if (dscsi_log_sense != NULL) + ret = (*dscsi_log_sense)(page_code, page_control, page_data, + page_size, rqbuf, rqblen); + + return (check_invalid_code(ret, rqbuf)); +} + +typedef int (*scsi_request_sense_f)(caddr_t, int, void *, int *); + +int +simscsi_request_sense(void *hdl, caddr_t buf, int buflen, + void *rqbuf, int *rqblen) +{ + scsi_request_sense_f dscsi_request_sense; + int ret = -1; + + dscsi_request_sense = (scsi_request_sense_f)dlsym(hdl, + "scsi_request_sense"); + + if (dscsi_request_sense != NULL) + ret = (*dscsi_request_sense)(buf, buflen, rqbuf, rqblen); + + return (check_invalid_code(ret, rqbuf)); +} diff --git a/usr/src/lib/fm/libdiskstatus/common/ds_scsi_sim.h b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_sim.h new file mode 100644 index 0000000000..7190dd97ff --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_sim.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#ifndef _DS_SCSI_SIM_H +#define _DS_SCSI_SIM_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +int simscsi_mode_sense(void *, int, int, caddr_t, int, scsi_ms_header_t *, + void *, int *); +int simscsi_mode_sense_10(void *, int, int, caddr_t, int, + scsi_ms_header_g1_t *, void *, int *); +int simscsi_mode_select(void *, int, int, caddr_t, int, + scsi_ms_header_t *, void *, int *); +int simscsi_mode_select_10(void *, int, int, caddr_t, int, + scsi_ms_header_g1_t *, void *, int *); +int simscsi_log_sense(void *, int, int, caddr_t, int, void *, int *); +int simscsi_request_sense(void *, caddr_t, int, void *, int *); + +#ifdef __cplusplus +} +#endif + +#endif /* _DS_SCSI_SIM_H */ diff --git a/usr/src/lib/fm/libdiskstatus/common/ds_scsi_uscsi.c b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_uscsi.c new file mode 100644 index 0000000000..81f62ad0cf --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_uscsi.c @@ -0,0 +1,1658 @@ +/* + * 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" + +/* + * This file contains routines for sending and receiving SCSI commands. The + * higher level logic is contained in ds_scsi.c. + */ + +#include <assert.h> +#include <sys/types.h> +#include <sys/param.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <limits.h> +#include <utility.h> +#include <unistd.h> +#include <stropts.h> +#include <alloca.h> + +#include "ds_scsi.h" +#include "ds_scsi_uscsi.h" + +#define MSGBUFLEN 64 +#define USCSI_DEFAULT_TIMEOUT 45 +#define USCSI_TIMEOUT_MAX INT_MAX + +static diskaddr_t scsi_extract_sense_info_descr( + struct scsi_descr_sense_hdr *sdsp, int rqlen); +static void scsi_print_extended_sense(struct scsi_extended_sense *rq, + int rqlen); +static void scsi_print_descr_sense(struct scsi_descr_sense_hdr *rq, int rqlen); + +typedef struct slist { + char *str; + int value; +} slist_t; + +static char * +find_string(slist_t *slist, int match_value) +{ + for (; slist->str != NULL; slist++) { + if (slist->value == match_value) { + return (slist->str); + } + } + + return ((char *)NULL); +} + +/* + * Strings for printing mode sense page control values + */ +static slist_t page_control_strings[] = { + { "current", PC_CURRENT }, + { "changeable", PC_CHANGEABLE }, + { "default", PC_DEFAULT }, + { "saved", PC_SAVED }, + { NULL, 0 } +}; + +/* + * Strings for printing the mode select options + */ +static slist_t mode_select_strings[] = { + { "", 0 }, + { "(pf)", MODE_SELECT_PF }, + { "(sp)", MODE_SELECT_SP }, + { "(pf,sp)", MODE_SELECT_PF|MODE_SELECT_SP }, + { NULL, 0 } +}; + +static slist_t sensekey_strings[] = { + { "No sense error", KEY_NO_SENSE }, + { "Recoverable error", KEY_RECOVERABLE_ERROR }, + { "Not ready error", KEY_NOT_READY }, + { "Medium error", KEY_MEDIUM_ERROR }, + { "Hardware error", KEY_HARDWARE_ERROR }, + { "Illegal request", KEY_ILLEGAL_REQUEST }, + { "Unit attention error", KEY_UNIT_ATTENTION }, + { "Write protect error", KEY_WRITE_PROTECT }, + { "Blank check error", KEY_BLANK_CHECK }, + { "Vendor unique error", KEY_VENDOR_UNIQUE }, + { "Copy aborted error", KEY_COPY_ABORTED }, + { "Aborted command", KEY_ABORTED_COMMAND }, + { "Equal error", KEY_EQUAL }, + { "Volume overflow", KEY_VOLUME_OVERFLOW }, + { "Miscompare error", KEY_MISCOMPARE }, + { "Reserved error", KEY_RESERVED }, + { NULL, 0 } +}; + +static slist_t scsi_cmdname_strings[] = { + { "mode select", SCMD_MODE_SELECT }, + { "mode sense", SCMD_MODE_SENSE }, + { "mode select(10)", SCMD_MODE_SELECT_G1 }, + { "mode sense(10)", SCMD_MODE_SENSE_G1 }, + { "log sense", SCMD_LOG_SENSE_G1 }, + { "request sense", SCMD_REQUEST_SENSE }, + { NULL, 0 } +}; + +static struct _scsi_asq_key_strings { + uint_t asc; + uint_t ascq; + const char *message; +} extended_sense_list[] = { + { 0x00, 0x00, "no additional sense info" }, + { 0x00, 0x01, "filemark detected" }, + { 0x00, 0x02, "end of partition/medium detected" }, + { 0x00, 0x03, "setmark detected" }, + { 0x00, 0x04, "begining of partition/medium detected" }, + { 0x00, 0x05, "end of data detected" }, + { 0x00, 0x06, "i/o process terminated" }, + { 0x00, 0x11, "audio play operation in progress" }, + { 0x00, 0x12, "audio play operation paused" }, + { 0x00, 0x13, "audio play operation successfully completed" }, + { 0x00, 0x14, "audio play operation stopped due to error" }, + { 0x00, 0x15, "no current audio status to return" }, + { 0x00, 0x16, "operation in progress" }, + { 0x00, 0x17, "cleaning requested" }, + { 0x00, 0x18, "erase operation in progress" }, + { 0x00, 0x19, "locate operation in progress" }, + { 0x00, 0x1A, "rewind operation in progress" }, + { 0x00, 0x1B, "set capacity operation in progress" }, + { 0x00, 0x1C, "verify operation in progress" }, + { 0x01, 0x00, "no index/sector signal" }, + { 0x02, 0x00, "no seek complete" }, + { 0x03, 0x00, "peripheral device write fault" }, + { 0x03, 0x01, "no write current" }, + { 0x03, 0x02, "excessive write errors" }, + { 0x04, 0x00, "LUN not ready" }, + { 0x04, 0x01, "LUN is becoming ready" }, + { 0x04, 0x02, "LUN initializing command required" }, + { 0x04, 0x03, "LUN not ready intervention required" }, + { 0x04, 0x04, "LUN not ready format in progress" }, + { 0x04, 0x05, "LUN not ready, rebuild in progress" }, + { 0x04, 0x06, "LUN not ready, recalculation in progress" }, + { 0x04, 0x07, "LUN not ready, operation in progress" }, + { 0x04, 0x08, "LUN not ready, long write in progress" }, + { 0x04, 0x09, "LUN not ready, self-test in progress" }, + { 0x04, 0x0A, "LUN not accessible, asymmetric access state " + "transition" }, + { 0x04, 0x0B, "LUN not accessible, target port in standby state" }, + { 0x04, 0x0C, "LUN not accessible, target port in unavailable state" }, + { 0x04, 0x10, "LUN not ready, auxiliary memory not accessible" }, + { 0x05, 0x00, "LUN does not respond to selection" }, + { 0x06, 0x00, "reference position found" }, + { 0x07, 0x00, "multiple peripheral devices selected" }, + { 0x08, 0x00, "LUN communication failure" }, + { 0x08, 0x01, "LUN communication time-out" }, + { 0x08, 0x02, "LUN communication parity error" }, + { 0x08, 0x03, "LUN communication crc error (ultra-DMA/32)" }, + { 0x08, 0x04, "unreachable copy target" }, + { 0x09, 0x00, "track following error" }, + { 0x09, 0x01, "tracking servo failure" }, + { 0x09, 0x02, "focus servo failure" }, + { 0x09, 0x03, "spindle servo failure" }, + { 0x09, 0x04, "head select fault" }, + { 0x0a, 0x00, "error log overflow" }, + { 0x0b, 0x00, "warning" }, + { 0x0b, 0x01, "warning - specified temperature exceeded" }, + { 0x0b, 0x02, "warning - enclosure degraded" }, + { 0x0c, 0x00, "write error" }, + { 0x0c, 0x01, "write error - recovered with auto reallocation" }, + { 0x0c, 0x02, "write error - auto reallocation failed" }, + { 0x0c, 0x03, "write error - recommend reassignment" }, + { 0x0c, 0x04, "compression check miscompare error" }, + { 0x0c, 0x05, "data expansion occurred during compression" }, + { 0x0c, 0x06, "block not compressible" }, + { 0x0c, 0x07, "write error - recovery needed" }, + { 0x0c, 0x08, "write error - recovery failed" }, + { 0x0c, 0x09, "write error - loss of streaming" }, + { 0x0c, 0x0a, "write error - padding blocks added" }, + { 0x0c, 0x0b, "auxiliary memory write error" }, + { 0x0c, 0x0c, "write error - unexpected unsolicited data" }, + { 0x0c, 0x0d, "write error - not enough unsolicited data" }, + { 0x0d, 0x00, "error detected by third party temporary initiator" }, + { 0x0d, 0x01, "third party device failure" }, + { 0x0d, 0x02, "copy target device not reachable" }, + { 0x0d, 0x03, "incorrect copy target device type" }, + { 0x0d, 0x04, "copy target device data underrun" }, + { 0x0d, 0x05, "copy target device data overrun" }, + { 0x0e, 0x00, "invalid information unit" }, + { 0x0e, 0x01, "information unit too short" }, + { 0x0e, 0x02, "information unit too long" }, + { 0x10, 0x00, "ID CRC or ECC error" }, + { 0x11, 0x00, "unrecovered read error" }, + { 0x11, 0x01, "read retries exhausted" }, + { 0x11, 0x02, "error too long to correct" }, + { 0x11, 0x03, "multiple read errors" }, + { 0x11, 0x04, "unrecovered read error - auto reallocate failed" }, + { 0x11, 0x05, "L-EC uncorrectable error" }, + { 0x11, 0x06, "CIRC unrecovered error" }, + { 0x11, 0x07, "data re-synchronization error" }, + { 0x11, 0x08, "incomplete block read" }, + { 0x11, 0x09, "no gap found" }, + { 0x11, 0x0a, "miscorrected error" }, + { 0x11, 0x0b, "unrecovered read error - recommend reassignment" }, + { 0x11, 0x0c, "unrecovered read error - recommend rewrite the data" }, + { 0x11, 0x0d, "de-compression crc error" }, + { 0x11, 0x0e, "cannot decompress using declared algorithm" }, + { 0x11, 0x0f, "error reading UPC/EAN number" }, + { 0x11, 0x10, "error reading ISRC number" }, + { 0x11, 0x11, "read error - loss of streaming" }, + { 0x11, 0x12, "auxiliary memory read error" }, + { 0x11, 0x13, "read error - failed retransmission request" }, + { 0x12, 0x00, "address mark not found for ID field" }, + { 0x13, 0x00, "address mark not found for data field" }, + { 0x14, 0x00, "recorded entity not found" }, + { 0x14, 0x01, "record not found" }, + { 0x14, 0x02, "filemark or setmark not found" }, + { 0x14, 0x03, "end-of-data not found" }, + { 0x14, 0x04, "block sequence error" }, + { 0x14, 0x05, "record not found - recommend reassignment" }, + { 0x14, 0x06, "record not found - data auto-reallocated" }, + { 0x14, 0x07, "locate operation failure" }, + { 0x15, 0x00, "random positioning error" }, + { 0x15, 0x01, "mechanical positioning error" }, + { 0x15, 0x02, "positioning error detected by read of medium" }, + { 0x16, 0x00, "data sync mark error" }, + { 0x16, 0x01, "data sync error - data rewritten" }, + { 0x16, 0x02, "data sync error - recommend rewrite" }, + { 0x16, 0x03, "data sync error - data auto-reallocated" }, + { 0x16, 0x04, "data sync error - recommend reassignment" }, + { 0x17, 0x00, "recovered data with no error correction" }, + { 0x17, 0x01, "recovered data with retries" }, + { 0x17, 0x02, "recovered data with positive head offset" }, + { 0x17, 0x03, "recovered data with negative head offset" }, + { 0x17, 0x04, "recovered data with retries and/or CIRC applied" }, + { 0x17, 0x05, "recovered data using previous sector id" }, + { 0x17, 0x06, "recovered data without ECC - data auto-reallocated" }, + { 0x17, 0x07, "recovered data without ECC - recommend reassignment" }, + { 0x17, 0x08, "recovered data without ECC - recommend rewrite" }, + { 0x17, 0x09, "recovered data without ECC - data rewritten" }, + { 0x18, 0x00, "recovered data with error correction" }, + { 0x18, 0x01, "recovered data with error corr. & retries applied" }, + { 0x18, 0x02, "recovered data - data auto-reallocated" }, + { 0x18, 0x03, "recovered data with CIRC" }, + { 0x18, 0x04, "recovered data with L-EC" }, + { 0x18, 0x05, "recovered data - recommend reassignment" }, + { 0x18, 0x06, "recovered data - recommend rewrite" }, + { 0x18, 0x07, "recovered data with ECC - data rewritten" }, + { 0x18, 0x08, "recovered data with linking" }, + { 0x19, 0x00, "defect list error" }, + { 0x1a, 0x00, "parameter list length error" }, + { 0x1b, 0x00, "synchronous data xfer error" }, + { 0x1c, 0x00, "defect list not found" }, + { 0x1c, 0x01, "primary defect list not found" }, + { 0x1c, 0x02, "grown defect list not found" }, + { 0x1d, 0x00, "miscompare during verify" }, + { 0x1e, 0x00, "recovered ID with ECC" }, + { 0x1f, 0x00, "partial defect list transfer" }, + { 0x20, 0x00, "invalid command operation code" }, + { 0x20, 0x01, "access denied - initiator pending-enrolled" }, + { 0x20, 0x02, "access denied - no access rights" }, + { 0x20, 0x03, "access denied - invalid mgmt id key" }, + { 0x20, 0x04, "illegal command while in write capable state" }, + { 0x20, 0x06, "illegal command while in explicit address mode" }, + { 0x20, 0x07, "illegal command while in implicit address mode" }, + { 0x20, 0x08, "access denied - enrollment conflict" }, + { 0x20, 0x09, "access denied - invalid lu identifier" }, + { 0x20, 0x0a, "access denied - invalid proxy token" }, + { 0x20, 0x0b, "access denied - ACL LUN conflict" }, + { 0x21, 0x00, "logical block address out of range" }, + { 0x21, 0x01, "invalid element address" }, + { 0x21, 0x02, "invalid address for write" }, + { 0x22, 0x00, "illegal function" }, + { 0x24, 0x00, "invalid field in cdb" }, + { 0x24, 0x01, "cdb decryption error" }, + { 0x25, 0x00, "LUN not supported" }, + { 0x26, 0x00, "invalid field in param list" }, + { 0x26, 0x01, "parameter not supported" }, + { 0x26, 0x02, "parameter value invalid" }, + { 0x26, 0x03, "threshold parameters not supported" }, + { 0x26, 0x04, "invalid release of persistent reservation" }, + { 0x26, 0x05, "data decryption error" }, + { 0x26, 0x06, "too many target descriptors" }, + { 0x26, 0x07, "unsupported target descriptor type code" }, + { 0x26, 0x08, "too many segment descriptors" }, + { 0x26, 0x09, "unsupported segment descriptor type code" }, + { 0x26, 0x0a, "unexpected inexact segment" }, + { 0x26, 0x0b, "inline data length exceeded" }, + { 0x26, 0x0c, "invalid operation for copy source or destination" }, + { 0x26, 0x0d, "copy segment granularity violation" }, + { 0x27, 0x00, "write protected" }, + { 0x27, 0x01, "hardware write protected" }, + { 0x27, 0x02, "LUN software write protected" }, + { 0x27, 0x03, "associated write protect" }, + { 0x27, 0x04, "persistent write protect" }, + { 0x27, 0x05, "permanent write protect" }, + { 0x27, 0x06, "conditional write protect" }, + { 0x28, 0x00, "medium may have changed" }, + { 0x28, 0x01, "import or export element accessed" }, + { 0x29, 0x00, "power on, reset, or bus reset occurred" }, + { 0x29, 0x01, "power on occurred" }, + { 0x29, 0x02, "scsi bus reset occurred" }, + { 0x29, 0x03, "bus device reset message occurred" }, + { 0x29, 0x04, "device internal reset" }, + { 0x29, 0x05, "transceiver mode changed to single-ended" }, + { 0x29, 0x06, "transceiver mode changed to LVD" }, + { 0x29, 0x07, "i_t nexus loss occurred" }, + { 0x2a, 0x00, "parameters changed" }, + { 0x2a, 0x01, "mode parameters changed" }, + { 0x2a, 0x02, "log parameters changed" }, + { 0x2a, 0x03, "reservations preempted" }, + { 0x2a, 0x04, "reservations released" }, + { 0x2a, 0x05, "registrations preempted" }, + { 0x2a, 0x06, "asymmetric access state changed" }, + { 0x2a, 0x07, "implicit asymmetric access state transition failed" }, + { 0x2b, 0x00, "copy cannot execute since host cannot disconnect" }, + { 0x2c, 0x00, "command sequence error" }, + { 0x2c, 0x03, "current program area is not empty" }, + { 0x2c, 0x04, "current program area is empty" }, + { 0x2c, 0x06, "persistent prevent conflict" }, + { 0x2c, 0x07, "previous busy status" }, + { 0x2c, 0x08, "previous task set full status" }, + { 0x2c, 0x09, "previous reservation conflict status" }, + { 0x2d, 0x00, "overwrite error on update in place" }, + { 0x2e, 0x00, "insufficient time for operation" }, + { 0x2f, 0x00, "commands cleared by another initiator" }, + { 0x30, 0x00, "incompatible medium installed" }, + { 0x30, 0x01, "cannot read medium - unknown format" }, + { 0x30, 0x02, "cannot read medium - incompatible format" }, + { 0x30, 0x03, "cleaning cartridge installed" }, + { 0x30, 0x04, "cannot write medium - unknown format" }, + { 0x30, 0x05, "cannot write medium - incompatible format" }, + { 0x30, 0x06, "cannot format medium - incompatible medium" }, + { 0x30, 0x07, "cleaning failure" }, + { 0x30, 0x08, "cannot write - application code mismatch" }, + { 0x30, 0x09, "current session not fixated for append" }, + { 0x30, 0x10, "medium not formatted" }, + { 0x31, 0x00, "medium format corrupted" }, + { 0x31, 0x01, "format command failed" }, + { 0x31, 0x02, "zoned formatting failed due to spare linking" }, + { 0x32, 0x00, "no defect spare location available" }, + { 0x32, 0x01, "defect list update failure" }, + { 0x33, 0x00, "tape length error" }, + { 0x34, 0x00, "enclosure failure" }, + { 0x35, 0x00, "enclosure services failure" }, + { 0x35, 0x01, "unsupported enclosure function" }, + { 0x35, 0x02, "enclosure services unavailable" }, + { 0x35, 0x03, "enclosure services transfer failure" }, + { 0x35, 0x04, "enclosure services transfer refused" }, + { 0x36, 0x00, "ribbon, ink, or toner failure" }, + { 0x37, 0x00, "rounded parameter" }, + { 0x39, 0x00, "saving parameters not supported" }, + { 0x3a, 0x00, "medium not present" }, + { 0x3a, 0x01, "medium not present - tray closed" }, + { 0x3a, 0x02, "medium not present - tray open" }, + { 0x3a, 0x03, "medium not present - loadable" }, + { 0x3a, 0x04, "medium not present - medium auxiliary memory " + "accessible" }, + { 0x3b, 0x00, "sequential positioning error" }, + { 0x3b, 0x01, "tape position error at beginning-of-medium" }, + { 0x3b, 0x02, "tape position error at end-of-medium" }, + { 0x3b, 0x08, "reposition error" }, + { 0x3b, 0x0c, "position past beginning of medium" }, + { 0x3b, 0x0d, "medium destination element full" }, + { 0x3b, 0x0e, "medium source element empty" }, + { 0x3b, 0x0f, "end of medium reached" }, + { 0x3b, 0x11, "medium magazine not accessible" }, + { 0x3b, 0x12, "medium magazine removed" }, + { 0x3b, 0x13, "medium magazine inserted" }, + { 0x3b, 0x14, "medium magazine locked" }, + { 0x3b, 0x15, "medium magazine unlocked" }, + { 0x3b, 0x16, "mechanical positioning or changer error" }, + { 0x3d, 0x00, "invalid bits in indentify message" }, + { 0x3e, 0x00, "LUN has not self-configured yet" }, + { 0x3e, 0x01, "LUN failure" }, + { 0x3e, 0x02, "timeout on LUN" }, + { 0x3e, 0x03, "LUN failed self-test" }, + { 0x3e, 0x04, "LUN unable to update self-test log" }, + { 0x3f, 0x00, "target operating conditions have changed" }, + { 0x3f, 0x01, "microcode has been changed" }, + { 0x3f, 0x02, "changed operating definition" }, + { 0x3f, 0x03, "inquiry data has changed" }, + { 0x3f, 0x04, "component device attached" }, + { 0x3f, 0x05, "device identifier changed" }, + { 0x3f, 0x06, "redundancy group created or modified" }, + { 0x3f, 0x07, "redundancy group deleted" }, + { 0x3f, 0x08, "spare created or modified" }, + { 0x3f, 0x09, "spare deleted" }, + { 0x3f, 0x0a, "volume set created or modified" }, + { 0x3f, 0x0b, "volume set deleted" }, + { 0x3f, 0x0c, "volume set deassigned" }, + { 0x3f, 0x0d, "volume set reassigned" }, + { 0x3f, 0x0e, "reported LUNs data has changed" }, + { 0x3f, 0x0f, "echo buffer overwritten" }, + { 0x3f, 0x10, "medium loadable" }, + { 0x3f, 0x11, "medium auxiliary memory accessible" }, + { 0x40, 0x00, "ram failure" }, + { 0x41, 0x00, "data path failure" }, + { 0x42, 0x00, "power-on or self-test failure" }, + { 0x43, 0x00, "message error" }, + { 0x44, 0x00, "internal target failure" }, + { 0x45, 0x00, "select or reselect failure" }, + { 0x46, 0x00, "unsuccessful soft reset" }, + { 0x47, 0x00, "scsi parity error" }, + { 0x47, 0x01, "data phase crc error detected" }, + { 0x47, 0x02, "scsi parity error detected during st data phase" }, + { 0x47, 0x03, "information unit iucrc error detected" }, + { 0x47, 0x04, "asynchronous information protection error detected" }, + { 0x47, 0x05, "protocol service crc error" }, + { 0x47, 0x7f, "some commands cleared by iscsi protocol event" }, + { 0x48, 0x00, "initiator detected error message received" }, + { 0x49, 0x00, "invalid message error" }, + { 0x4a, 0x00, "command phase error" }, + { 0x4b, 0x00, "data phase error" }, + { 0x4b, 0x01, "invalid target port transfer tag received" }, + { 0x4b, 0x02, "too much write data" }, + { 0x4b, 0x03, "ack/nak timeout" }, + { 0x4b, 0x04, "nak received" }, + { 0x4b, 0x05, "data offset error" }, + { 0x4c, 0x00, "logical unit failed self-configuration" }, + { 0x4d, 0x00, "tagged overlapped commands (ASCQ = queue tag)" }, + { 0x4e, 0x00, "overlapped commands attempted" }, + { 0x50, 0x00, "write append error" }, + { 0x51, 0x00, "erase failure" }, + { 0x52, 0x00, "cartridge fault" }, + { 0x53, 0x00, "media load or eject failed" }, + { 0x53, 0x01, "unload tape failure" }, + { 0x53, 0x02, "medium removal prevented" }, + { 0x54, 0x00, "scsi to host system interface failure" }, + { 0x55, 0x00, "system resource failure" }, + { 0x55, 0x01, "system buffer full" }, + { 0x55, 0x02, "insufficient reservation resources" }, + { 0x55, 0x03, "insufficient resources" }, + { 0x55, 0x04, "insufficient registration resources" }, + { 0x55, 0x05, "insufficient access control resources" }, + { 0x55, 0x06, "auxiliary memory out of space" }, + { 0x57, 0x00, "unable to recover TOC" }, + { 0x58, 0x00, "generation does not exist" }, + { 0x59, 0x00, "updated block read" }, + { 0x5a, 0x00, "operator request or state change input" }, + { 0x5a, 0x01, "operator medium removal request" }, + { 0x5a, 0x02, "operator selected write protect" }, + { 0x5a, 0x03, "operator selected write permit" }, + { 0x5b, 0x00, "log exception" }, + { 0x5b, 0x01, "threshold condition met" }, + { 0x5b, 0x02, "log counter at maximum" }, + { 0x5b, 0x03, "log list codes exhausted" }, + { 0x5c, 0x00, "RPL status change" }, + { 0x5c, 0x01, "spindles synchronized" }, + { 0x5c, 0x02, "spindles not synchronized" }, + { 0x5d, 0x00, "drive operation marginal, service immediately" + " (failure prediction threshold exceeded)" }, + { 0x5d, 0x01, "media failure prediction threshold exceeded" }, + { 0x5d, 0x02, "LUN failure prediction threshold exceeded" }, + { 0x5d, 0x03, "spare area exhaustion prediction threshold exceeded" }, + { 0x5d, 0x10, "hardware impending failure general hard drive failure" }, + { 0x5d, 0x11, "hardware impending failure drive error rate too high" }, + { 0x5d, 0x12, "hardware impending failure data error rate too high" }, + { 0x5d, 0x13, "hardware impending failure seek error rate too high" }, + { 0x5d, 0x14, "hardware impending failure too many block reassigns" }, + { 0x5d, 0x15, "hardware impending failure access times too high" }, + { 0x5d, 0x16, "hardware impending failure start unit times too high" }, + { 0x5d, 0x17, "hardware impending failure channel parametrics" }, + { 0x5d, 0x18, "hardware impending failure controller detected" }, + { 0x5d, 0x19, "hardware impending failure throughput performance" }, + { 0x5d, 0x1a, "hardware impending failure seek time performance" }, + { 0x5d, 0x1b, "hardware impending failure spin-up retry count" }, + { 0x5d, 0x1c, "hardware impending failure drive calibration retry " + "count" }, + { 0x5d, 0x20, "controller impending failure general hard drive " + "failure" }, + { 0x5d, 0x21, "controller impending failure drive error rate too " + "high" }, + { 0x5d, 0x22, "controller impending failure data error rate too high" }, + { 0x5d, 0x23, "controller impending failure seek error rate too high" }, + { 0x5d, 0x24, "controller impending failure too many block reassigns" }, + { 0x5d, 0x25, "controller impending failure access times too high" }, + { 0x5d, 0x26, "controller impending failure start unit times too " + "high" }, + { 0x5d, 0x27, "controller impending failure channel parametrics" }, + { 0x5d, 0x28, "controller impending failure controller detected" }, + { 0x5d, 0x29, "controller impending failure throughput performance" }, + { 0x5d, 0x2a, "controller impending failure seek time performance" }, + { 0x5d, 0x2b, "controller impending failure spin-up retry count" }, + { 0x5d, 0x2c, "controller impending failure drive calibration retry " + "cnt" }, + { 0x5d, 0x30, "data channel impending failure general hard drive " + "failure" }, + { 0x5d, 0x31, "data channel impending failure drive error rate too " + "high" }, + { 0x5d, 0x32, "data channel impending failure data error rate too " + "high" }, + { 0x5d, 0x33, "data channel impending failure seek error rate too " + "high" }, + { 0x5d, 0x34, "data channel impending failure too many block " + "reassigns" }, + { 0x5d, 0x35, "data channel impending failure access times too high" }, + { 0x5d, 0x36, "data channel impending failure start unit times too " + "high" }, + { 0x5d, 0x37, "data channel impending failure channel parametrics" }, + { 0x5d, 0x38, "data channel impending failure controller detected" }, + { 0x5d, 0x39, "data channel impending failure throughput performance" }, + { 0x5d, 0x3a, "data channel impending failure seek time performance" }, + { 0x5d, 0x3b, "data channel impending failure spin-up retry count" }, + { 0x5d, 0x3c, "data channel impending failure drive calibrate retry " + "cnt" }, + { 0x5d, 0x40, "servo impending failure general hard drive failure" }, + { 0x5d, 0x41, "servo impending failure drive error rate too high" }, + { 0x5d, 0x42, "servo impending failure data error rate too high" }, + { 0x5d, 0x43, "servo impending failure seek error rate too high" }, + { 0x5d, 0x44, "servo impending failure too many block reassigns" }, + { 0x5d, 0x45, "servo impending failure access times too high" }, + { 0x5d, 0x46, "servo impending failure start unit times too high" }, + { 0x5d, 0x47, "servo impending failure channel parametrics" }, + { 0x5d, 0x48, "servo impending failure controller detected" }, + { 0x5d, 0x49, "servo impending failure throughput performance" }, + { 0x5d, 0x4a, "servo impending failure seek time performance" }, + { 0x5d, 0x4b, "servo impending failure spin-up retry count" }, + { 0x5d, 0x4c, "servo impending failure drive calibration retry count" }, + { 0x5d, 0x50, "spindle impending failure general hard drive failure" }, + { 0x5d, 0x51, "spindle impending failure drive error rate too high" }, + { 0x5d, 0x52, "spindle impending failure data error rate too high" }, + { 0x5d, 0x53, "spindle impending failure seek error rate too high" }, + { 0x5d, 0x54, "spindle impending failure too many block reassigns" }, + { 0x5d, 0x55, "spindle impending failure access times too high" }, + { 0x5d, 0x56, "spindle impending failure start unit times too high" }, + { 0x5d, 0x57, "spindle impending failure channel parametrics" }, + { 0x5d, 0x58, "spindle impending failure controller detected" }, + { 0x5d, 0x59, "spindle impending failure throughput performance" }, + { 0x5d, 0x5a, "spindle impending failure seek time performance" }, + { 0x5d, 0x5b, "spindle impending failure spin-up retry count" }, + { 0x5d, 0x5c, "spindle impending failure drive calibration retry " + "count" }, + { 0x5d, 0x60, "firmware impending failure general hard drive failure" }, + { 0x5d, 0x61, "firmware impending failure drive error rate too high" }, + { 0x5d, 0x62, "firmware impending failure data error rate too high" }, + { 0x5d, 0x63, "firmware impending failure seek error rate too high" }, + { 0x5d, 0x64, "firmware impending failure too many block reassigns" }, + { 0x5d, 0x65, "firmware impending failure access times too high" }, + { 0x5d, 0x66, "firmware impending failure start unit times too high" }, + { 0x5d, 0x67, "firmware impending failure channel parametrics" }, + { 0x5d, 0x68, "firmware impending failure controller detected" }, + { 0x5d, 0x69, "firmware impending failure throughput performance" }, + { 0x5d, 0x6a, "firmware impending failure seek time performance" }, + { 0x5d, 0x6b, "firmware impending failure spin-up retry count" }, + { 0x5d, 0x6c, "firmware impending failure drive calibration retry " + "count" }, + { 0x5d, 0xff, "failure prediction threshold exceeded (false)" }, + { 0x5e, 0x00, "low power condition active" }, + { 0x5e, 0x01, "idle condition activated by timer" }, + { 0x5e, 0x02, "standby condition activated by timer" }, + { 0x5e, 0x03, "idle condition activated by command" }, + { 0x5e, 0x04, "standby condition activated by command" }, + { 0x60, 0x00, "lamp failure" }, + { 0x61, 0x00, "video aquisition error" }, + { 0x62, 0x00, "scan head positioning error" }, + { 0x63, 0x00, "end of user area encountered on this track" }, + { 0x63, 0x01, "packet does not fit in available space" }, + { 0x64, 0x00, "illegal mode for this track" }, + { 0x64, 0x01, "invalid packet size" }, + { 0x65, 0x00, "voltage fault" }, + { 0x66, 0x00, "automatic document feeder cover up" }, + { 0x67, 0x00, "configuration failure" }, + { 0x67, 0x01, "configuration of incapable LUNs failed" }, + { 0x67, 0x02, "add LUN failed" }, + { 0x67, 0x03, "modification of LUN failed" }, + { 0x67, 0x04, "exchange of LUN failed" }, + { 0x67, 0x05, "remove of LUN failed" }, + { 0x67, 0x06, "attachment of LUN failed" }, + { 0x67, 0x07, "creation of LUN failed" }, + { 0x67, 0x08, "assign failure occurred" }, + { 0x67, 0x09, "multiply assigned LUN" }, + { 0x67, 0x0a, "set target port groups command failed" }, + { 0x68, 0x00, "logical unit not configured" }, + { 0x69, 0x00, "data loss on logical unit" }, + { 0x69, 0x01, "multiple LUN failures" }, + { 0x69, 0x02, "parity/data mismatch" }, + { 0x6a, 0x00, "informational, refer to log" }, + { 0x6b, 0x00, "state change has occured" }, + { 0x6b, 0x01, "redundancy level got better" }, + { 0x6b, 0x02, "redundancy level got worse" }, + { 0x6c, 0x00, "rebuild failure occured" }, + { 0x6d, 0x00, "recalculate failure occured" }, + { 0x6e, 0x00, "command to logical unit failed" }, + { 0x6f, 0x00, "copy protect key exchange failure authentication " + "failure" }, + { 0x6f, 0x01, "copy protect key exchange failure key not present" }, + { 0x6f, 0x02, "copy protect key exchange failure key not established" }, + { 0x6f, 0x03, "read of scrambled sector without authentication" }, + { 0x6f, 0x04, "media region code is mismatched to LUN region" }, + { 0x6f, 0x05, "drive region must be permanent/region reset count " + "error" }, + { 0x70, 0xffff, "decompression exception short algorithm id of ASCQ" }, + { 0x71, 0x00, "decompression exception long algorithm id" }, + { 0x72, 0x00, "session fixation error" }, + { 0x72, 0x01, "session fixation error writing lead-in" }, + { 0x72, 0x02, "session fixation error writing lead-out" }, + { 0x72, 0x03, "session fixation error - incomplete track in session" }, + { 0x72, 0x04, "empty or partially written reserved track" }, + { 0x72, 0x05, "no more track reservations allowed" }, + { 0x73, 0x00, "cd control error" }, + { 0x73, 0x01, "power calibration area almost full" }, + { 0x73, 0x02, "power calibration area is full" }, + { 0x73, 0x03, "power calibration area error" }, + { 0x73, 0x04, "program memory area update failure" }, + { 0x73, 0x05, "program memory area is full" }, + { 0x73, 0x06, "rma/pma is almost full" }, + { 0xffff, 0xffff, NULL } +}; + +/* + * Given an asc (Additional Sense Code) and ascq (Additional Sense Code + * Qualifier), return a string describing the error information. + */ +static char * +scsi_util_asc_ascq_name(uint_t asc, uint_t ascq, char *buf, int buflen) +{ + int i = 0; + + while (extended_sense_list[i].asc != 0xffff) { + if ((asc == extended_sense_list[i].asc) && + ((ascq == extended_sense_list[i].ascq) || + (extended_sense_list[i].ascq == 0xffff))) { + return ((char *)extended_sense_list[i].message); + } + i++; + } + (void) snprintf(buf, buflen, "<vendor unique code 0x%x>", asc); + return (buf); +} + +/* + * Dumps detailed information about a particular SCSI error condition. + */ +static void +scsi_printerr(struct uscsi_cmd *ucmd, struct scsi_extended_sense *rq, int rqlen) +{ + diskaddr_t blkno; + struct scsi_descr_sense_hdr *sdsp = (struct scsi_descr_sense_hdr *)rq; + char msgbuf[MSGBUFLEN]; + + if (find_string(sensekey_strings, rq->es_key) == NULL) + dprintf("unknown error"); + + dprintf("during %s:", + find_string(scsi_cmdname_strings, ucmd->uscsi_cdb[0])); + + /* + * 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: + blkno = (diskaddr_t)scsi_extract_sense_info_descr(sdsp, rqlen); + if (blkno != (diskaddr_t)-1) + dprintf(": block %lld (0x%llx)", blkno, blkno); + dprintf("\n"); + dprintf("ASC: 0x%x ASCQ: 0x%x (%s)\n", + sdsp->ds_add_code, sdsp->ds_qual_code, + scsi_util_asc_ascq_name(sdsp->ds_add_code, + sdsp->ds_qual_code, msgbuf, MSGBUFLEN)); + + break; + + case CODE_FMT_FIXED_CURRENT: + case CODE_FMT_FIXED_DEFERRED: + default: + if (rq->es_valid) { + blkno = (rq->es_info_1 << 24) | + (rq->es_info_2 << 16) | + (rq->es_info_3 << 8) | rq->es_info_4; + dprintf(": block %lld (0x%llx)", blkno, blkno); + } + dprintf("\n"); + if (rq->es_add_len >= 6) { + dprintf("ASC: 0x%x ASCQ: 0x%x (%s)\n", + rq->es_add_code, + rq->es_qual_code, + scsi_util_asc_ascq_name(rq->es_add_code, + rq->es_qual_code, msgbuf, MSGBUFLEN)); + } + break; + } + + if (rq->es_key == KEY_ILLEGAL_REQUEST) { + ddump("cmd:", (caddr_t)ucmd, + sizeof (struct uscsi_cmd)); + ddump("cdb:", (caddr_t)ucmd->uscsi_cdb, + ucmd->uscsi_cdblen); + } + ddump("sense:", (caddr_t)rq, rqlen); + + switch (rq->es_code) { + case CODE_FMT_DESCR_CURRENT: + case CODE_FMT_DESCR_DEFERRED: + scsi_print_descr_sense(sdsp, rqlen); + break; + case CODE_FMT_FIXED_CURRENT: + case CODE_FMT_FIXED_DEFERRED: + default: + scsi_print_extended_sense(rq, rqlen); + break; + } +} + +/* + * Retrieve "information" field from descriptor format sense data. Iterates + * through each sense descriptor looking for the information descriptor and + * returns the information field from that descriptor. + */ +static diskaddr_t +scsi_extract_sense_info_descr(struct scsi_descr_sense_hdr *sdsp, int rqlen) +{ + diskaddr_t result; + uint8_t *descr_offset; + int valid_sense_length; + struct scsi_information_sense_descr *isd; + + /* + * Initialize result to -1 indicating there is no information + * descriptor + */ + result = (diskaddr_t)-1; + + /* + * The first descriptor will immediately follow the header + */ + descr_offset = (uint8_t *)(sdsp+1); + + /* + * Calculate the amount of valid sense data + */ + valid_sense_length = + MIN((sizeof (struct scsi_descr_sense_hdr) + + sdsp->ds_addl_sense_length), rqlen); + + /* + * Iterate through the list of descriptors, stopping when we run out of + * sense data + */ + while ((descr_offset + sizeof (struct scsi_information_sense_descr)) <= + (uint8_t *)sdsp + valid_sense_length) { + /* + * Check if this is an information descriptor. We can use the + * scsi_information_sense_descr structure as a template since + * the first two fields are always the same + */ + isd = (struct scsi_information_sense_descr *)descr_offset; + if (isd->isd_descr_type == DESCR_INFORMATION) { + /* + * Found an information descriptor. Copy the + * information field. There will only be one + * information descriptor so we can stop looking. + */ + result = + (((diskaddr_t)isd->isd_information[0] << 56) | + ((diskaddr_t)isd->isd_information[1] << 48) | + ((diskaddr_t)isd->isd_information[2] << 40) | + ((diskaddr_t)isd->isd_information[3] << 32) | + ((diskaddr_t)isd->isd_information[4] << 24) | + ((diskaddr_t)isd->isd_information[5] << 16) | + ((diskaddr_t)isd->isd_information[6] << 8) | + ((diskaddr_t)isd->isd_information[7])); + break; + } + + /* + * Get pointer to the next descriptor. The "additional length" + * field holds the length of the descriptor except for the + * "type" and "additional length" fields, so we need to add 2 to + * get the total length. + */ + descr_offset += (isd->isd_addl_length + 2); + } + + return (result); +} + +/* + * Display the full scsi_extended_sense as returned by the device + */ +static void +scsi_print_extended_sense(struct scsi_extended_sense *rq, int rqlen) +{ + static char *scsi_extended_sense_labels[] = { + "Request sense valid: ", + "Error class and code: ", + "Segment number: ", + "Filemark: ", + "End-of-medium: ", + "Incorrect length indicator: ", + "Sense key: ", + "Information field: ", + "Additional sense length: ", + "Command-specific information: ", + "Additional sense code: ", + "Additional sense code qualifier: ", + "Field replaceable unit code: ", + "Sense-key specific: ", + "Additional sense bytes: " + }; + + char **p = scsi_extended_sense_labels; + + if (rqlen < (sizeof (*rq) - 2) || !rq->es_valid) { + /* + * target should be capable of returning at least 18 + * bytes of data, i.e upto rq->es_skey_specific field. + * The additional sense bytes (2 or more ...) are optional. + */ + return; + } + + dprintf("\n%s%s\n", *p++, rq->es_valid ? "yes" : "no"); + dprintf("%s0x%02x\n", *p++, (rq->es_class << 4) + rq->es_code); + dprintf("%s%d\n", *p++, rq->es_segnum); + dprintf("%s%s\n", *p++, rq->es_filmk ? "yes" : "no"); + dprintf("%s%s\n", *p++, rq->es_eom ? "yes" : "no"); + dprintf("%s%s\n", *p++, rq->es_ili ? "yes" : "no"); + dprintf("%s%d\n", *p++, rq->es_key); + + dprintf("%s0x%02x 0x%02x 0x%02x 0x%02x\n", *p++, rq->es_info_1, + rq->es_info_2, rq->es_info_3, rq->es_info_4); + dprintf("%s%d\n", *p++, rq->es_add_len); + dprintf("%s0x%02x 0x%02x 0x%02x 0x%02x\n", *p++, + rq->es_cmd_info[0], rq->es_cmd_info[1], rq->es_cmd_info[2], + rq->es_cmd_info[3]); + dprintf("%s0x%02x = %d\n", *p++, rq->es_add_code, + rq->es_add_code); + dprintf("%s0x%02x = %d\n", *p++, rq->es_qual_code, + rq->es_qual_code); + dprintf("%s%d\n", *p++, rq->es_fru_code); + dprintf("%s0x%02x 0x%02x 0x%02x\n", *p++, + rq->es_skey_specific[0], rq->es_skey_specific[1], + rq->es_skey_specific[2]); + if (rqlen >= sizeof (*rq)) { + dprintf("%s0x%02x 0x%02x%s\n", *p, rq->es_add_info[0], + rq->es_add_info[1], (rqlen > sizeof (*rq)) ? " ..." : ""); + } + + dprintf("\n"); +} + +/* + * Display the full descriptor sense data as returned by the device + */ +static void +scsi_print_descr_sense(struct scsi_descr_sense_hdr *rq, int rqlen) +{ + /* + * Labels for the various fields of the scsi_descr_sense_hdr structure + */ + static char *scsi_descr_sense_labels[] = { + "Error class and code: ", + "Sense key: ", + "Additional sense length: ", + "Additional sense code: ", + "Additional sense code qualifier: ", + "Additional sense bytes: " + }; + + struct scsi_information_sense_descr *isd; + uint8_t *descr_offset; + int valid_sense_length; + char **p = scsi_descr_sense_labels; + + /* Target must return at least 8 bytes of data */ + if (rqlen < sizeof (struct scsi_descr_sense_hdr)) + return; + + /* Print descriptor sense header */ + dprintf("%s0x%02x\n", *p++, (rq->ds_class << 4) + rq->ds_code); + dprintf("%s%d\n", *p++, rq->ds_key); + + dprintf("%s%d\n", *p++, rq->ds_addl_sense_length); + dprintf("%s0x%02x = %d\n", *p++, rq->ds_add_code, + rq->ds_add_code); + dprintf("%s0x%02x = %d\n", *p++, rq->ds_qual_code, + rq->ds_qual_code); + dprintf("\n"); + + /* + * Now print any sense descriptors. The first descriptor will + * immediately follow the header + */ + descr_offset = (uint8_t *)(rq+1); /* Pointer arithmetic */ + + /* + * Calculate the amount of valid sense data + */ + valid_sense_length = + MIN((sizeof (struct scsi_descr_sense_hdr) + + rq->ds_addl_sense_length), rqlen); + + /* + * Iterate through the list of descriptors, stopping when we + * run out of sense data. Descriptor format is: + * + * <Descriptor type> <Descriptor length> <Descriptor data> ... + */ + while ((descr_offset + *(descr_offset + 1)) <= + (uint8_t *)rq + valid_sense_length) { + /* + * Determine descriptor type. We can use the + * scsi_information_sense_descr structure as a + * template since the first two fields are always the + * same. + */ + isd = (struct scsi_information_sense_descr *)descr_offset; + switch (isd->isd_descr_type) { + case DESCR_INFORMATION: { + uint64_t information; + + information = + (((uint64_t)isd->isd_information[0] << 56) | + ((uint64_t)isd->isd_information[1] << 48) | + ((uint64_t)isd->isd_information[2] << 40) | + ((uint64_t)isd->isd_information[3] << 32) | + ((uint64_t)isd->isd_information[4] << 24) | + ((uint64_t)isd->isd_information[5] << 16) | + ((uint64_t)isd->isd_information[6] << 8) | + ((uint64_t)isd->isd_information[7])); + dprintf("Information field: " + "%0" PRIx64 "\n", information); + break; + } + case DESCR_COMMAND_SPECIFIC: { + struct scsi_cmd_specific_sense_descr *c = + (struct scsi_cmd_specific_sense_descr *)isd; + uint64_t cmd_specific; + + cmd_specific = + (((uint64_t)c->css_cmd_specific_info[0] << 56) | + ((uint64_t)c->css_cmd_specific_info[1] << 48) | + ((uint64_t)c->css_cmd_specific_info[2] << 40) | + ((uint64_t)c->css_cmd_specific_info[3] << 32) | + ((uint64_t)c->css_cmd_specific_info[4] << 24) | + ((uint64_t)c->css_cmd_specific_info[5] << 16) | + ((uint64_t)c->css_cmd_specific_info[6] << 8) | + ((uint64_t)c->css_cmd_specific_info[7])); + dprintf("Command-specific information: " + "%0" PRIx64 "\n", cmd_specific); + break; + } + case DESCR_SENSE_KEY_SPECIFIC: { + struct scsi_sk_specific_sense_descr *ssd = + (struct scsi_sk_specific_sense_descr *)isd; + uint8_t *sk_spec_ptr = (uint8_t *)&ssd->sss_data; + dprintf("Sense-key specific: " + "0x%02x 0x%02x 0x%02x\n", sk_spec_ptr[0], + sk_spec_ptr[1], sk_spec_ptr[2]); + break; + } + case DESCR_FRU: { + struct scsi_fru_sense_descr *fsd = + (struct scsi_fru_sense_descr *)isd; + dprintf("Field replaceable unit code: " + "%d\n", fsd->fs_fru_code); + break; + } + case DESCR_BLOCK_COMMANDS: { + struct scsi_block_cmd_sense_descr *bsd = + (struct scsi_block_cmd_sense_descr *)isd; + dprintf("Incorrect length indicator: " + "%s\n", bsd->bcs_ili ? "yes" : "no"); + break; + } + default: + /* Ignore */ + break; + } + + /* + * Get pointer to the next descriptor. The "additional + * length" field holds the length of the descriptor except + * for the "type" and "additional length" fields, so + * we need to add 2 to get the total length. + */ + descr_offset += (isd->isd_addl_length + 2); + } + + dprintf("\n"); +} + +static int +uscsi_timeout(void) +{ + const char *env = getenv("USCSI_TIMEOUT"); + static int timeo = -1; + int i; + + if (timeo > 0) + return (timeo); + + if (env != NULL) { + i = atoi(env); + if (i > USCSI_TIMEOUT_MAX) + i = USCSI_TIMEOUT_MAX; + else if (i < 0) + i = USCSI_DEFAULT_TIMEOUT; + } else + i = USCSI_DEFAULT_TIMEOUT; + + timeo = i; + return (i); +} + +/* + * Execute a command and determine the result. Uses the "uscsi" ioctl + * interface, which is fully supported. + * + * If the user wants request sense data to be returned in case of error then , + * the "uscsi_cmd" structure should have the request sense buffer allocated in + * uscsi_rqbuf. + */ +static int +uscsi_cmd(int fd, struct uscsi_cmd *ucmd, void *rqbuf, int *rqlen) +{ + struct scsi_extended_sense *rq; + int status; + + /* + * Set function flags for driver. + */ + ucmd->uscsi_flags = USCSI_ISOLATE; + if (!ds_debug) + ucmd->uscsi_flags |= USCSI_SILENT; + + /* + * If this command will perform a read, set the USCSI_READ flag + */ + if (ucmd->uscsi_buflen > 0) { + /* + * uscsi_cdb is declared as a caddr_t, so any CDB + * command byte with the MSB set will result in a + * compiler error unless we cast to an unsigned value. + */ + switch ((uint8_t)ucmd->uscsi_cdb[0]) { + case SCMD_MODE_SENSE: + case SCMD_MODE_SENSE_G1: + case SCMD_LOG_SENSE_G1: + case SCMD_REQUEST_SENSE: + ucmd->uscsi_flags |= USCSI_READ; + break; + + case SCMD_MODE_SELECT: + case SCMD_MODE_SELECT_G1: + /* LINTED */ + ucmd->uscsi_flags |= USCSI_WRITE; + break; + default: + assert(0); + break; + } + } + + /* Set timeout */ + ucmd->uscsi_timeout = uscsi_timeout(); + + /* + * Set up Request Sense buffer + */ + + if (ucmd->uscsi_rqbuf == NULL) { + ucmd->uscsi_rqbuf = rqbuf; + ucmd->uscsi_rqlen = *rqlen; + ucmd->uscsi_rqresid = *rqlen; + } + if (ucmd->uscsi_rqbuf) + ucmd->uscsi_flags |= USCSI_RQENABLE; + ucmd->uscsi_rqstatus = IMPOSSIBLE_SCSI_STATUS; + + if (ucmd->uscsi_rqbuf != NULL && ucmd->uscsi_rqlen > 0) + (void) memset(ucmd->uscsi_rqbuf, 0, ucmd->uscsi_rqlen); + + /* + * Execute the ioctl + */ + status = ioctl(fd, USCSICMD, ucmd); + if (status == 0 && ucmd->uscsi_status == 0) + return (status); + + /* + * If an automatic Request Sense gave us valid info about the error, we + * may be able to use that to print a reasonable error msg. + */ + if (ucmd->uscsi_rqstatus == IMPOSSIBLE_SCSI_STATUS) { + dprintf("No request sense for command %s\n", + find_string(scsi_cmdname_strings, + ucmd->uscsi_cdb[0])); + return (-1); + } + if (ucmd->uscsi_rqstatus != STATUS_GOOD) { + dprintf("Request sense status for command %s: 0x%x\n", + find_string(scsi_cmdname_strings, + ucmd->uscsi_cdb[0]), + ucmd->uscsi_rqstatus); + return (-1); + } + + rq = (struct scsi_extended_sense *)ucmd->uscsi_rqbuf; + *rqlen = ucmd->uscsi_rqlen - ucmd->uscsi_rqresid; + + if ((((int)rq->es_add_len) + 8) < MIN_REQUEST_SENSE_LEN || + rq->es_class != CLASS_EXTENDED_SENSE || + *rqlen < MIN_REQUEST_SENSE_LEN) { + dprintf("Request sense for command %s failed\n", + find_string(scsi_cmdname_strings, + ucmd->uscsi_cdb[0])); + + dprintf("Sense data:\n"); + ddump(NULL, (caddr_t)rqbuf, *rqlen); + + return (-1); + } + + /* + * If the failed command is a Mode Select, and the + * target is indicating that it has rounded one of + * the mode select parameters, as defined in the SCSI-2 + * specification, then we should accept the command + * as successful. + */ + if (ucmd->uscsi_cdb[0] == SCMD_MODE_SELECT || + ucmd->uscsi_cdb[0] == SCMD_MODE_SELECT_G1) { + if (rq->es_key == KEY_RECOVERABLE_ERROR && + rq->es_add_code == ROUNDED_PARAMETER && + rq->es_qual_code == 0) { + return (0); + } + } + + if (ds_debug) + scsi_printerr(ucmd, rq, *rqlen); + if (rq->es_key != KEY_RECOVERABLE_ERROR) + return (-1); + return (0); +} + +int +uscsi_request_sense(int fd, caddr_t buf, int buflen, void *rqbuf, int *rqblen) +{ + struct uscsi_cmd ucmd; + union scsi_cdb cdb; + int status; + + (void) memset(buf, 0, buflen); + (void) memset(&ucmd, 0, sizeof (ucmd)); + (void) memset(&cdb, 0, sizeof (union scsi_cdb)); + cdb.scc_cmd = SCMD_REQUEST_SENSE; + FORMG0COUNT(&cdb, (uchar_t)buflen); + ucmd.uscsi_cdb = (caddr_t)&cdb; + ucmd.uscsi_cdblen = CDB_GROUP0; + ucmd.uscsi_bufaddr = buf; + ucmd.uscsi_buflen = buflen; + status = uscsi_cmd(fd, &ucmd, rqbuf, rqblen); + if (status) + dprintf("Request sense failed\n"); + if (status == 0) + ddump("Request Sense data:", buf, buflen); + + return (status); +} + +/* + * Execute a uscsi mode sense command. This can only be used to return one page + * at a time. Return the mode header/block descriptor and the actual page data + * separately - this allows us to support devices which return either 0 or 1 + * block descriptors. Whatever a device gives us in the mode header/block + * descriptor will be returned to it upon subsequent mode selects. + */ +int +uscsi_mode_sense(int fd, int page_code, int page_control, caddr_t page_data, + int page_size, struct scsi_ms_header *header, void *rqbuf, int *rqblen) +{ + caddr_t mode_sense_buf; + struct mode_header *hdr; + struct mode_page *pg; + int nbytes; + struct uscsi_cmd ucmd; + union scsi_cdb cdb; + int status; + int maximum; + char *pc; + + assert(page_size >= 0 && page_size < 256); + assert(page_control == PC_CURRENT || page_control == PC_CHANGEABLE || + page_control == PC_DEFAULT || page_control == PC_SAVED); + + nbytes = sizeof (struct scsi_ms_header) + page_size; + mode_sense_buf = alloca((uint_t)nbytes); + + /* + * Build and execute the uscsi ioctl + */ + (void) memset(mode_sense_buf, 0, nbytes); + (void) memset(&ucmd, 0, sizeof (ucmd)); + (void) memset(&cdb, 0, sizeof (union scsi_cdb)); + cdb.scc_cmd = SCMD_MODE_SENSE; + FORMG0COUNT(&cdb, (uchar_t)nbytes); + cdb.cdb_opaque[2] = page_control | page_code; + ucmd.uscsi_cdb = (caddr_t)&cdb; + ucmd.uscsi_cdblen = CDB_GROUP0; + ucmd.uscsi_bufaddr = mode_sense_buf; + ucmd.uscsi_buflen = nbytes; + status = uscsi_cmd(fd, &ucmd, rqbuf, rqblen); + if (status) { + dprintf("Mode sense page 0x%x failed\n", page_code); + return (-1); + } + + ddump("RAW MODE SENSE BUFFER", mode_sense_buf, nbytes); + + /* + * Verify that the returned data looks reasonable, find the actual page + * data, and copy it into the user's buffer. Copy the mode_header and + * block_descriptor into the header structure, which can then be used to + * return the same data to the drive when issuing a mode select. + */ + hdr = (struct mode_header *)mode_sense_buf; + (void) memset((caddr_t)header, 0, sizeof (struct scsi_ms_header)); + if (hdr->bdesc_length != sizeof (struct block_descriptor) && + hdr->bdesc_length != 0) { + dprintf("\nMode sense page 0x%x: block descriptor " + "length %d incorrect\n", page_code, hdr->bdesc_length); + ddump("Mode sense:", mode_sense_buf, nbytes); + return (-1); + } + (void) memcpy((caddr_t)header, mode_sense_buf, + (int)(MODE_HEADER_LENGTH + hdr->bdesc_length)); + pg = (struct mode_page *)((ulong_t)mode_sense_buf + + MODE_HEADER_LENGTH + hdr->bdesc_length); + + if (page_code == MODEPAGE_ALLPAGES) { + /* special case */ + + (void) memcpy(page_data, (caddr_t)pg, + (hdr->length + sizeof (header->ms_header.length)) - + (MODE_HEADER_LENGTH + hdr->bdesc_length)); + + pc = find_string(page_control_strings, page_control); + dprintf("\nMode sense page 0x%x (%s):\n", page_code, + pc != NULL ? pc : ""); + ddump("header:", (caddr_t)header, + sizeof (struct scsi_ms_header)); + ddump("data:", page_data, + (hdr->length + + sizeof (header->ms_header.length)) - + (MODE_HEADER_LENGTH + hdr->bdesc_length)); + + return (0); + } + + if (pg->code != page_code) { + dprintf("\nMode sense page 0x%x: incorrect page code 0x%x\n", + page_code, pg->code); + ddump("Mode sense:", mode_sense_buf, nbytes); + return (-1); + } + + /* + * Accept up to "page_size" bytes of mode sense data. This allows us to + * accept both CCS and SCSI-2 structures, as long as we request the + * greater of the two. + */ + maximum = page_size - sizeof (struct mode_page); + if (((int)pg->length) > maximum) { + dprintf("Mode sense page 0x%x: incorrect page " + "length %d - expected max %d\n", + page_code, pg->length, maximum); + ddump("Mode sense:", mode_sense_buf, nbytes); + return (-1); + } + + (void) memcpy(page_data, (caddr_t)pg, MODESENSE_PAGE_LEN(pg)); + + pc = find_string(page_control_strings, page_control); + dprintf("\nMode sense page 0x%x (%s):\n", page_code, + pc != NULL ? pc : ""); + ddump("header:", (caddr_t)header, sizeof (struct scsi_ms_header)); + ddump("data:", page_data, MODESENSE_PAGE_LEN(pg)); + + return (0); +} + +/* + * Execute a uscsi MODE SENSE(10) command. This can only be used to return one + * page at a time. Return the mode header/block descriptor and the actual page + * data separately - this allows us to support devices which return either 0 or + * 1 block descriptors. Whatever a device gives us in the mode header/block + * descriptor will be returned to it upon subsequent mode selects. + */ +int +uscsi_mode_sense_10(int fd, int page_code, int page_control, + caddr_t page_data, int page_size, struct scsi_ms_header_g1 *header, + void *rqbuf, int *rqblen) +{ + caddr_t mode_sense_buf; + struct mode_header_g1 *hdr; + struct mode_page *pg; + int nbytes; + struct uscsi_cmd ucmd; + union scsi_cdb cdb; + int status; + int maximum; + ushort_t length, bdesc_length; + char *pc; + + assert(page_size >= 0 && page_size < UINT16_MAX); + assert(page_control == PC_CURRENT || page_control == PC_CHANGEABLE || + page_control == PC_DEFAULT || page_control == PC_SAVED); + + nbytes = sizeof (struct scsi_ms_header_g1) + page_size; + mode_sense_buf = alloca((uint_t)nbytes); + + (void) memset(mode_sense_buf, 0, nbytes); + (void) memset((char *)&ucmd, 0, sizeof (ucmd)); + (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb)); + cdb.scc_cmd = SCMD_MODE_SENSE_G1; + FORMG1COUNT(&cdb, (uint16_t)nbytes); + cdb.cdb_opaque[2] = page_control | page_code; + ucmd.uscsi_cdb = (caddr_t)&cdb; + ucmd.uscsi_cdblen = CDB_GROUP1; + ucmd.uscsi_bufaddr = mode_sense_buf; + ucmd.uscsi_buflen = nbytes; + + status = uscsi_cmd(fd, &ucmd, rqbuf, rqblen); + if (status) { + dprintf("Mode sense(10) page 0x%x failed\n", + page_code); + return (-1); + } + + ddump("RAW MODE SENSE(10) BUFFER", mode_sense_buf, nbytes); + + /* + * Verify that the returned data looks reasonable, find the actual page + * data, and copy it into the user's buffer. Copy the mode_header and + * block_descriptor into the header structure, which can then be used to + * return the same data to the drive when issuing a mode select. + */ + /* LINTED */ + hdr = (struct mode_header_g1 *)mode_sense_buf; + + length = BE_16(hdr->length); + bdesc_length = BE_16(hdr->bdesc_length); + + (void) memset((caddr_t)header, 0, sizeof (struct scsi_ms_header_g1)); + if (bdesc_length != sizeof (struct block_descriptor) && + bdesc_length != 0) { + dprintf("\nMode sense(10) page 0x%x: block descriptor " + "length %d incorrect\n", page_code, bdesc_length); + ddump("Mode sense(10):", mode_sense_buf, nbytes); + return (-1); + } + (void) memcpy((caddr_t)header, mode_sense_buf, + (int)(MODE_HEADER_LENGTH_G1 + bdesc_length)); + pg = (struct mode_page *)((ulong_t)mode_sense_buf + + MODE_HEADER_LENGTH_G1 + bdesc_length); + + if (page_code == MODEPAGE_ALLPAGES) { + /* special case */ + + (void) memcpy(page_data, (caddr_t)pg, + (length + sizeof (header->ms_header.length)) - + (MODE_HEADER_LENGTH_G1 + bdesc_length)); + + pc = find_string(page_control_strings, page_control); + dprintf("\nMode sense(10) page 0x%x (%s):\n", + page_code, pc != NULL ? pc : ""); + ddump("header:", (caddr_t)header, + MODE_HEADER_LENGTH_G1 + bdesc_length); + + ddump("data:", page_data, + (length + sizeof (header->ms_header.length)) - + (MODE_HEADER_LENGTH_G1 + bdesc_length)); + + return (0); + } + + if (pg->code != page_code) { + dprintf("\nMode sense(10) page 0x%x: incorrect page " + "code 0x%x\n", page_code, pg->code); + ddump("Mode sense(10):", mode_sense_buf, nbytes); + return (-1); + } + + /* + * Accept up to "page_size" bytes of mode sense data. This allows us to + * accept both CCS and SCSI-2 structures, as long as we request the + * greater of the two. + */ + maximum = page_size - sizeof (struct mode_page); + if (((int)pg->length) > maximum) { + dprintf("Mode sense(10) page 0x%x: incorrect page " + "length %d - expected max %d\n", + page_code, pg->length, maximum); + ddump("Mode sense(10):", mode_sense_buf, + nbytes); + return (-1); + } + + (void) memcpy(page_data, (caddr_t)pg, MODESENSE_PAGE_LEN(pg)); + + pc = find_string(page_control_strings, page_control); + dprintf("\nMode sense(10) page 0x%x (%s):\n", page_code, + pc != NULL ? pc : ""); + ddump("header:", (caddr_t)header, + sizeof (struct scsi_ms_header_g1)); + ddump("data:", page_data, MODESENSE_PAGE_LEN(pg)); + + return (0); +} + +/* + * Execute a uscsi mode select command. + */ +int +uscsi_mode_select(int fd, int page_code, int options, caddr_t page_data, + int page_size, struct scsi_ms_header *header, void *rqbuf, int *rqblen) +{ + caddr_t mode_select_buf; + int nbytes; + struct uscsi_cmd ucmd; + union scsi_cdb cdb; + int status; + char *s; + + assert(((struct mode_page *)page_data)->ps == 0); + assert(header->ms_header.length == 0); + assert(header->ms_header.device_specific == 0); + assert((options & ~(MODE_SELECT_SP|MODE_SELECT_PF)) == 0); + + nbytes = sizeof (struct scsi_ms_header) + page_size; + mode_select_buf = alloca((uint_t)nbytes); + + /* + * Build the mode select data out of the header and page data This + * allows us to support devices which return either 0 or 1 block + * descriptors. + */ + (void) memset(mode_select_buf, 0, nbytes); + nbytes = MODE_HEADER_LENGTH; + if (header->ms_header.bdesc_length == + sizeof (struct block_descriptor)) { + nbytes += sizeof (struct block_descriptor); + } + + s = find_string(mode_select_strings, + options & (MODE_SELECT_SP|MODE_SELECT_PF)); + dprintf("\nMode select page 0x%x%s:\n", page_code, + s != NULL ? s : ""); + ddump("header:", (caddr_t)header, nbytes); + ddump("data:", (caddr_t)page_data, page_size); + + /* + * Put the header and data together + */ + (void) memcpy(mode_select_buf, (caddr_t)header, nbytes); + (void) memcpy(mode_select_buf + nbytes, page_data, page_size); + nbytes += page_size; + + /* + * Build and execute the uscsi ioctl + */ + (void) memset((char *)&ucmd, 0, sizeof (ucmd)); + (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb)); + cdb.scc_cmd = SCMD_MODE_SELECT; + FORMG0COUNT(&cdb, (uchar_t)nbytes); + cdb.cdb_opaque[1] = (uchar_t)options; + ucmd.uscsi_cdb = (caddr_t)&cdb; + ucmd.uscsi_cdblen = CDB_GROUP0; + ucmd.uscsi_bufaddr = mode_select_buf; + ucmd.uscsi_buflen = nbytes; + status = uscsi_cmd(fd, &ucmd, rqbuf, rqblen); + + if (status) + dprintf("Mode select page 0x%x failed\n", page_code); + + return (status); +} + +/* + * Execute a uscsi mode select(10) command. + */ +int +uscsi_mode_select_10(int fd, int page_code, int options, + caddr_t page_data, int page_size, struct scsi_ms_header_g1 *header, + void *rqbuf, int *rqblen) +{ + caddr_t mode_select_buf; + int nbytes; + struct uscsi_cmd ucmd; + union scsi_cdb cdb; + int status; + char *s; + + assert(((struct mode_page *)page_data)->ps == 0); + assert(header->ms_header.length == 0); + assert(header->ms_header.device_specific == 0); + assert((options & ~(MODE_SELECT_SP|MODE_SELECT_PF)) == 0); + + nbytes = sizeof (struct scsi_ms_header_g1) + page_size; + mode_select_buf = alloca((uint_t)nbytes); + + /* + * Build the mode select data out of the header and page data + * This allows us to support devices which return either + * 0 or 1 block descriptors. + */ + (void) memset(mode_select_buf, 0, nbytes); + nbytes = sizeof (struct mode_header_g1); + if (BE_16(header->ms_header.bdesc_length) == + sizeof (struct block_descriptor)) { + nbytes += sizeof (struct block_descriptor); + } + + /* + * Dump the structures + */ + s = find_string(mode_select_strings, + options & (MODE_SELECT_SP|MODE_SELECT_PF)); + dprintf("\nMode select(10) page 0x%x%s:\n", page_code, + s != NULL ? s : ""); + ddump("header:", (caddr_t)header, nbytes); + ddump("data:", (caddr_t)page_data, page_size); + + /* + * Put the header and data together + */ + (void) memcpy(mode_select_buf, (caddr_t)header, nbytes); + (void) memcpy(mode_select_buf + nbytes, page_data, page_size); + nbytes += page_size; + + /* + * Build and execute the uscsi ioctl + */ + (void) memset((char *)&ucmd, 0, sizeof (ucmd)); + (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb)); + cdb.scc_cmd = SCMD_MODE_SELECT_G1; + FORMG1COUNT(&cdb, (uint16_t)nbytes); + cdb.cdb_opaque[1] = (uchar_t)options; + ucmd.uscsi_cdb = (caddr_t)&cdb; + ucmd.uscsi_cdblen = CDB_GROUP1; + ucmd.uscsi_bufaddr = mode_select_buf; + ucmd.uscsi_buflen = nbytes; + status = uscsi_cmd(fd, &ucmd, rqbuf, rqblen); + + if (status) + dprintf("Mode select(10) page 0x%x failed\n", page_code); + + return (status); +} + +int +uscsi_log_sense(int fd, int page_code, int page_control, caddr_t page_data, + int page_size, void *rqbuf, int *rqblen) +{ + caddr_t log_sense_buf; + scsi_log_header_t *hdr; + struct uscsi_cmd ucmd; + union scsi_cdb cdb; + int status; + ushort_t len; + char *pc; + + assert(page_size >= 0 && page_size < UINT16_MAX); + assert(page_control == PC_CURRENT || page_control == PC_CHANGEABLE || + page_control == PC_DEFAULT || page_control == PC_SAVED); + + if (page_size < sizeof (scsi_log_header_t)) + return (-1); + + log_sense_buf = alloca((uint_t)page_size); + + /* + * Build and execute the uscsi ioctl + */ + (void) memset(log_sense_buf, 0, page_size); + (void) memset((char *)&ucmd, 0, sizeof (ucmd)); + (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb)); + cdb.scc_cmd = SCMD_LOG_SENSE_G1; + FORMG1COUNT(&cdb, (uint16_t)page_size); + cdb.cdb_opaque[2] = page_control | page_code; + ucmd.uscsi_cdb = (caddr_t)&cdb; + ucmd.uscsi_cdblen = CDB_GROUP1; + ucmd.uscsi_bufaddr = log_sense_buf; + ucmd.uscsi_buflen = page_size; + status = uscsi_cmd(fd, &ucmd, rqbuf, rqblen); + if (status) { + dprintf("Log sense page 0x%x failed\n", page_code); + return (-1); + } + + /* + * Verify that the returned data looks reasonable, then copy it into the + * user's buffer. + */ + hdr = (scsi_log_header_t *)log_sense_buf; + + /* + * Ensure we have a host-understandable length field + */ + len = BE_16(hdr->lh_length); + + if (hdr->lh_code != page_code) { + dprintf("\nLog sense page 0x%x: incorrect page code 0x%x\n", + page_code, hdr->lh_code); + ddump("Log sense:", log_sense_buf, page_size); + return (-1); + } + + ddump("LOG SENSE RAW OUTPUT", log_sense_buf, + sizeof (scsi_log_header_t) + len); + + /* + * Accept up to "page_size" bytes of mode sense data. This allows us to + * accept both CCS and SCSI-2 structures, as long as we request the + * greater of the two. + */ + (void) memcpy(page_data, (caddr_t)hdr, len + + sizeof (scsi_log_header_t)); + + pc = find_string(page_control_strings, page_control); + dprintf("\nLog sense page 0x%x (%s):\n", page_code, + pc != NULL ? pc : ""); + ddump("header:", (caddr_t)hdr, + sizeof (scsi_log_header_t)); + ddump("data:", (caddr_t)hdr + + sizeof (scsi_log_header_t), len); + + return (0); +} diff --git a/usr/src/lib/fm/libdiskstatus/common/ds_scsi_uscsi.h b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_uscsi.h new file mode 100644 index 0000000000..e46caa4766 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/ds_scsi_uscsi.h @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#ifndef _DS_SCSI_USCSI_H +#define _DS_SCSI_USCSI_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * "impossible" status value + */ +#define IMPOSSIBLE_SCSI_STATUS 0xff + +/* + * Minimum length of Request Sense data that we can accept + */ +#define MIN_REQUEST_SENSE_LEN 18 + +/* + * Rounded parameter, as returned in Extended Sense information + */ +#define ROUNDED_PARAMETER 0x37 + +int uscsi_mode_sense(int, int, int, caddr_t, int, scsi_ms_header_t *, + void *, int *); +int uscsi_mode_sense_10(int, int, int, caddr_t, int, scsi_ms_header_g1_t *, + void *, int *); +int uscsi_mode_select(int, int, int, caddr_t, int, scsi_ms_header_t *, + void *, int *); +int uscsi_mode_select_10(int, int, int, caddr_t, int, scsi_ms_header_g1_t *, + void *, int *); +int uscsi_log_sense(int, int, int, caddr_t, int, void *, int *); +int uscsi_request_sense(int, caddr_t, int, void *, int *); + +#ifdef __cplusplus +} +#endif + +#endif /* _DS_SCSI_USCSI_H */ diff --git a/usr/src/lib/fm/libdiskstatus/common/ds_util.c b/usr/src/lib/fm/libdiskstatus/common/ds_util.c new file mode 100644 index 0000000000..fe59b12801 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/ds_util.c @@ -0,0 +1,172 @@ +/* + * 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 <ctype.h> +#include <libdiskstatus.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include "ds_impl.h" + +boolean_t ds_debug; + +/*PRINTFLIKE1*/ +void +dprintf(const char *fmt, ...) +{ + va_list ap; + + if (!ds_debug) + return; + + va_start(ap, fmt); + (void) vprintf(fmt, ap); + va_end(ap); +} + +void +ddump(const char *label, const void *data, size_t length) +{ + int byte_count; + int i; +#define LINEBUFLEN 128 + char linebuf[LINEBUFLEN]; + char *linep; + int bufleft, len; + const char *start = data; + + if (!ds_debug) + return; + + if (label != NULL) + dprintf("%s\n", label); + + linep = linebuf; + bufleft = LINEBUFLEN; + + for (byte_count = 0; byte_count < length; byte_count += i) { + + (void) snprintf(linep, bufleft, "0x%08x ", byte_count); + len = strlen(linep); + bufleft -= len; + linep += len; + + /* + * Inner loop processes 16 bytes at a time, or less + * if we have less than 16 bytes to go + */ + for (i = 0; (i < 16) && ((byte_count + i) < length); i++) { + (void) snprintf(linep, bufleft, "%02X", (unsigned int) + (unsigned char) start[byte_count + i]); + + len = strlen(linep); + bufleft -= len; + linep += len; + + if (bufleft >= 2) { + if (i == 7) + *linep = '-'; + else + *linep = ' '; + + --bufleft; + ++linep; + } + } + + /* + * If i is less than 16, then we had less than 16 bytes + * written to the output. We need to fixup the alignment + * to allow the "text" output to be aligned + */ + if (i < 16) { + int numspaces = (16 - i) * 3; + while (numspaces-- > 0) { + if (bufleft >= 2) { + *linep = ' '; + --bufleft; + linep++; + } + } + } + + if (bufleft >= 2) { + *linep = ' '; + --bufleft; + ++linep; + } + + for (i = 0; (i < 16) && ((byte_count + i) < length); i++) { + int subscript = byte_count + i; + char ch = (isprint(start[subscript]) ? + start[subscript] : '.'); + + if (bufleft >= 2) { + *linep = ch; + --bufleft; + ++linep; + } + } + + linebuf[LINEBUFLEN - bufleft] = 0; + + dprintf("%s\n", linebuf); + + linep = linebuf; + bufleft = LINEBUFLEN; + } + +} + +const char * +disk_status_errmsg(int error) +{ + switch (error) { + case EDS_NOMEM: + return ("memory allocation failure"); + case EDS_CANT_OPEN: + return ("failed to open device"); + case EDS_NO_TRANSPORT: + return ("no supported communication protocol"); + case EDS_NOT_SUPPORTED: + return ("disk status information not supported"); + case EDS_NOT_SIMULATOR: + return ("not a valid simulator file"); + case EDS_IO: + return ("I/O error from device"); + default: + return ("unknown error"); + } +} + +int +ds_set_errno(disk_status_t *dsp, int error) +{ + dsp->ds_error = error; + return (-1); +} diff --git a/usr/src/lib/fm/libdiskstatus/common/libdiskstatus.c b/usr/src/lib/fm/libdiskstatus/common/libdiskstatus.c new file mode 100644 index 0000000000..e2cf5ad7a2 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/libdiskstatus.c @@ -0,0 +1,238 @@ +/* + * 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" + +/* + * Disk status library + * + * This library is responsible for querying health and other status information + * from disk drives. It is intended to be a generic interface, however only + * SCSI (and therefore SATA) disks are currently supported. The library is + * capable of detecting the following status conditions: + * + * - Predictive failure + * - Overtemp + * - Self-test failure + */ + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <libdevinfo.h> +#include <libdiskstatus.h> +#include <stdlib.h> +#include <string.h> +#include <sys/fm/io/scsi.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "ds_impl.h" +#include "ds_scsi.h" + +static ds_transport_t *ds_transports[] = { + &ds_scsi_sim_transport, + &ds_scsi_uscsi_transport +}; + +#define NTRANSPORTS (sizeof (ds_transports) / sizeof (ds_transports[0])) + +/* + * Open a handle to a disk. This will fail if the device cannot be opened, or + * if no suitable transport exists for communicating with the device. + */ +disk_status_t * +disk_status_open(const char *path, int *error) +{ + disk_status_t *dsp; + ds_transport_t *t; + int i; + + if ((dsp = calloc(sizeof (disk_status_t), 1)) == NULL) { + *error = EDS_NOMEM; + return (NULL); + } + + if ((dsp->ds_fd = open(path, O_RDWR)) < 0) { + *error = EDS_CANT_OPEN; + free(dsp); + return (NULL); + } + + if ((dsp->ds_path = strdup(path)) == NULL) { + *error = EDS_NOMEM; + disk_status_close(dsp); + return (NULL); + } + + for (i = 0; i < NTRANSPORTS; i++) { + t = ds_transports[i]; + + dsp->ds_transport = t; + + nvlist_free(dsp->ds_state); + dsp->ds_state = NULL; + if (nvlist_alloc(&dsp->ds_state, NV_UNIQUE_NAME, 0) != 0) { + *error = EDS_NOMEM; + disk_status_close(dsp); + return (NULL); + } + + if ((dsp->ds_data = t->dt_open(dsp)) == NULL) { + if (dsp->ds_error != EDS_NO_TRANSPORT) { + *error = dsp->ds_error; + disk_status_close(dsp); + return (NULL); + } + } else { + dsp->ds_error = 0; + break; + } + } + + if (dsp->ds_error == EDS_NO_TRANSPORT) { + *error = dsp->ds_error; + disk_status_close(dsp); + return (NULL); + } + + return (dsp); +} + +/* + * Close a handle to a disk. + */ +void +disk_status_close(disk_status_t *dsp) +{ + nvlist_free(dsp->ds_state); + nvlist_free(dsp->ds_predfail); + nvlist_free(dsp->ds_overtemp); + nvlist_free(dsp->ds_testfail); + if (dsp->ds_data) + dsp->ds_transport->dt_close(dsp->ds_data); + (void) close(dsp->ds_fd); + free(dsp->ds_path); + free(dsp); +} + +void +disk_status_set_debug(boolean_t value) +{ + ds_debug = value; +} + +/* + * Query basic information + */ +const char * +disk_status_path(disk_status_t *dsp) +{ + return (dsp->ds_path); +} + +int +disk_status_errno(disk_status_t *dsp) +{ + return (dsp->ds_error); +} + +nvlist_t * +disk_status_get(disk_status_t *dsp) +{ + nvlist_t *nvl = NULL; + nvlist_t *faults = NULL; + int err; + + /* + * Scan (or rescan) the current device. + */ + nvlist_free(dsp->ds_testfail); + nvlist_free(dsp->ds_predfail); + nvlist_free(dsp->ds_overtemp); + dsp->ds_testfail = dsp->ds_overtemp = dsp->ds_predfail = NULL; + dsp->ds_faults = 0; + + /* + * Even if there is an I/O failure when trying to scan the device, we + * can still return the current state. + */ + if (dsp->ds_transport->dt_scan(dsp->ds_data) != 0 && + dsp->ds_error != EDS_IO) + return (NULL); + + if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0) + goto nverror; + + if ((err = nvlist_add_string(nvl, "protocol", "scsi")) != 0 || + (err = nvlist_add_nvlist(nvl, "status", dsp->ds_state)) != 0) + goto nverror; + + /* + * Construct the list of faults. + */ + if ((err = nvlist_alloc(&faults, NV_UNIQUE_NAME, 0)) != 0) + goto nverror; + + if (dsp->ds_predfail != NULL) { + if ((err = nvlist_add_boolean_value(faults, + FM_EREPORT_SCSI_PREDFAIL, + (dsp->ds_faults & DS_FAULT_PREDFAIL) != 0)) != 0 || + (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_PREDFAIL, + dsp->ds_predfail)) != 0) + goto nverror; + } + + if (dsp->ds_testfail != NULL) { + if ((err = nvlist_add_boolean_value(faults, + FM_EREPORT_SCSI_TESTFAIL, + (dsp->ds_faults & DS_FAULT_TESTFAIL) != 0)) != 0 || + (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_TESTFAIL, + dsp->ds_testfail)) != 0) + goto nverror; + } + + if (dsp->ds_overtemp != NULL) { + if ((err = nvlist_add_boolean_value(faults, + FM_EREPORT_SCSI_OVERTEMP, + (dsp->ds_faults & DS_FAULT_OVERTEMP) != 0)) != 0 || + (err = nvlist_add_nvlist(nvl, FM_EREPORT_SCSI_OVERTEMP, + dsp->ds_overtemp)) != 0) + goto nverror; + } + + if ((err = nvlist_add_nvlist(nvl, "faults", faults)) != 0) + goto nverror; + + nvlist_free(faults); + return (nvl); + +nverror: + assert(err == ENOMEM); + nvlist_free(nvl); + nvlist_free(faults); + (void) ds_set_errno(dsp, EDS_NOMEM); + return (NULL); +} diff --git a/usr/src/lib/fm/libdiskstatus/common/libdiskstatus.h b/usr/src/lib/fm/libdiskstatus/common/libdiskstatus.h new file mode 100644 index 0000000000..b659de316e --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/libdiskstatus.h @@ -0,0 +1,82 @@ +/* + * 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. + */ + +#ifndef _LIBDISKSTATUS_H +#define _LIBDISKSTATUS_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <libnvpair.h> +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct disk_status disk_status_t; + +/* + * Error definitions + */ +#define EDS_BASE 2000 + +enum { + EDS_NOMEM = EDS_BASE, /* memory allocation failure */ + EDS_CANT_OPEN, /* failed to open device */ + EDS_NO_TRANSPORT, /* no supported transport */ + EDS_NOT_SUPPORTED, /* status information not supported */ + EDS_NOT_SIMULATOR, /* not a valid simulator file */ + EDS_IO /* I/O error */ +}; + +/* + * Basic library functions + */ +extern disk_status_t *disk_status_open(const char *, int *); +extern void disk_status_close(disk_status_t *); +extern const char *disk_status_errmsg(int); +extern void disk_status_set_debug(boolean_t); +extern int disk_status_errno(disk_status_t *); + +/* + * Miscellaneous functions. + */ +extern const char *disk_status_path(disk_status_t *); + +/* + * Main entry point. + */ +extern nvlist_t *disk_status_get(disk_status_t *); + +/* + * Utility function to simulate predictive failure (device-specific). + */ +extern int disk_status_test_predfail(disk_status_t *); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBDISKSTATUS_H */ diff --git a/usr/src/lib/fm/libdiskstatus/common/llib-ldiskstatus b/usr/src/lib/fm/libdiskstatus/common/llib-ldiskstatus new file mode 100644 index 0000000000..a20222355f --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/llib-ldiskstatus @@ -0,0 +1,31 @@ +/* + * 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" + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <libdiskstatus.h> diff --git a/usr/src/lib/fm/libdiskstatus/common/mapfile-vers b/usr/src/lib/fm/libdiskstatus/common/mapfile-vers new file mode 100644 index 0000000000..912475031e --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/common/mapfile-vers @@ -0,0 +1,38 @@ +# +# 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. +# +#ident "%Z%%M% %I% %E% SMI" + +SUNWprivate_1.1 { + global: + disk_status_close; + disk_status_errmsg; + disk_status_errno; + disk_status_get; + disk_status_open; + disk_status_path; + disk_status_set_debug; + local: + *; +}; diff --git a/usr/src/lib/fm/libdiskstatus/i386/Makefile b/usr/src/lib/fm/libdiskstatus/i386/Makefile new file mode 100644 index 0000000000..741cbcaecd --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/i386/Makefile @@ -0,0 +1,29 @@ +# +# 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. +# +#ident "%Z%%M% %I% %E% SMI" + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/fm/libdiskstatus/sparc/Makefile b/usr/src/lib/fm/libdiskstatus/sparc/Makefile new file mode 100644 index 0000000000..741cbcaecd --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/sparc/Makefile @@ -0,0 +1,29 @@ +# +# 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. +# +#ident "%Z%%M% %I% %E% SMI" + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/fm/libdiskstatus/sparcv9/Makefile b/usr/src/lib/fm/libdiskstatus/sparcv9/Makefile new file mode 100644 index 0000000000..5375e35120 --- /dev/null +++ b/usr/src/lib/fm/libdiskstatus/sparcv9/Makefile @@ -0,0 +1,30 @@ +# +# 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. +# +#ident "%Z%%M% %I% %E% SMI" + +include ../Makefile.com +include ../../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) diff --git a/usr/src/lib/fm/libfmd_snmp/common/mapfile-vers b/usr/src/lib/fm/libfmd_snmp/common/mapfile-vers index 4af3e953f4..c2d380e684 100644 --- a/usr/src/lib/fm/libfmd_snmp/common/mapfile-vers +++ b/usr/src/lib/fm/libfmd_snmp/common/mapfile-vers @@ -38,7 +38,8 @@ SUNWprivate { fmd_fmri_strdup; fmd_fmri_strescape; fmd_fmri_strfree; - fmd_fmri_topology; + fmd_fmri_topo_hold; + fmd_fmri_topo_rele; fmd_fmri_warn; fmd_fmri_zalloc; init_sunFM; diff --git a/usr/src/lib/fm/libfmd_snmp/common/scheme.c b/usr/src/lib/fm/libfmd_snmp/common/scheme.c index a9b776b6bf..55c45a7ae3 100644 --- a/usr/src/lib/fm/libfmd_snmp/common/scheme.c +++ b/usr/src/lib/fm/libfmd_snmp/common/scheme.c @@ -299,12 +299,14 @@ fmd_fmri_warn(const char *format, ...) { } -/*ARGSUSED*/ struct topo_hdl * -fmd_fmri_topology(int version) +fmd_fmri_topo_hold(int version) { int err; + if (version != TOPO_VERSION) + return (NULL); + if (g_thp == NULL) { if ((g_thp = topo_open(TOPO_VERSION, "/", &err)) == NULL) { DEBUGMSGTL((MODNAME_STR, "topo_open failed: %s\n", @@ -315,3 +317,10 @@ fmd_fmri_topology(int version) return (g_thp); } + +/*ARGSUSED*/ +void +fmd_fmri_topo_rele(struct topo_hdl *thp) +{ + /* nothing to do */ +} diff --git a/usr/src/lib/fm/topo/libtopo/Makefile.com b/usr/src/lib/fm/topo/libtopo/Makefile.com index 9725caf851..d9d09adf4b 100644 --- a/usr/src/lib/fm/topo/libtopo/Makefile.com +++ b/usr/src/lib/fm/topo/libtopo/Makefile.com @@ -80,7 +80,8 @@ LINTFLAGS = -msux LINTFLAGS64 = -msux -Xarch=$(MACH64:sparcv9=v9) $(DYNLIB) := LDLIBS += \ - -lnvpair -lelf -lumem -lxml2 -lkstat -luuid -ldevinfo -lsmbios -lc + -lnvpair -lelf -lumem -lxml2 -lkstat -luuid -ldevinfo \ + -lsmbios -lc -ldevid $(LINTLIB) := SRCS = $(SRCDIR)/$(LINTSRC) $(LINTLIB) := LINTFLAGS = -nsvx diff --git a/usr/src/lib/fm/topo/libtopo/common/dev.c b/usr/src/lib/fm/topo/libtopo/common/dev.c index 18defd40d3..abbf934245 100644 --- a/usr/src/lib/fm/topo/libtopo/common/dev.c +++ b/usr/src/lib/fm/topo/libtopo/common/dev.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -32,6 +32,7 @@ #include <unistd.h> #include <stdio.h> #include <alloca.h> +#include <devid.h> #include <libnvpair.h> #include <fm/topo_mod.h> #include <sys/fm/protocol.h> @@ -49,6 +50,10 @@ static int dev_fmri_str2nvl(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *, nvlist_t **); static int dev_fmri_create_meth(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *, nvlist_t **); +static int dev_fmri_present(topo_mod_t *, tnode_t *, topo_version_t, + nvlist_t *, nvlist_t **); +static int dev_fmri_unusable(topo_mod_t *, tnode_t *, topo_version_t, + nvlist_t *, nvlist_t **); static const topo_method_t dev_methods[] = { { TOPO_METH_NVL2STR, TOPO_METH_NVL2STR_DESC, TOPO_METH_NVL2STR_VERSION, @@ -57,6 +62,11 @@ static const topo_method_t dev_methods[] = { TOPO_STABILITY_INTERNAL, dev_fmri_str2nvl }, { TOPO_METH_FMRI, TOPO_METH_FMRI_DESC, TOPO_METH_FMRI_VERSION, TOPO_STABILITY_INTERNAL, dev_fmri_create_meth }, + { TOPO_METH_PRESENT, TOPO_METH_PRESENT_DESC, TOPO_METH_PRESENT_VERSION, + TOPO_STABILITY_INTERNAL, dev_fmri_present }, + { TOPO_METH_UNUSABLE, TOPO_METH_UNUSABLE_DESC, + TOPO_METH_UNUSABLE_VERSION, TOPO_STABILITY_INTERNAL, + dev_fmri_unusable }, { NULL } }; @@ -290,6 +300,170 @@ dev_fmri_str2nvl(topo_mod_t *mod, tnode_t *node, topo_version_t version, return (0); } +/* + * callback routine for di_walk_minor() + */ +struct walkinfo { + int matched; + const char *path; + const char *devid; + int len; +}; + +static int +dev_match(di_node_t node, void *arg) +{ + struct walkinfo *wip = (struct walkinfo *)arg; + char *path = di_devfs_path(node); + ddi_devid_t devid; + char *devidstr = NULL; + + if (path != NULL && strncmp(path, wip->path, wip->len) == 0) { + /* + * If we found the match we were looking for, check to see if a + * devid was specified. If so, then this must also match. + */ + if (wip->devid) { + if ((devid = di_devid(node)) == NULL || + (devidstr = devid_str_encode(devid, NULL)) == NULL) + goto out; + + if (strcmp(devidstr, wip->devid) != 0) + goto out; + } + + /* + * Either the devid wasn't specified, or it correctly matched. + * In this case, indicate a successful match and terminate the + * walk. + */ + wip->matched = 1; + di_devfs_path_free(path); + devid_str_free(devidstr); + return (DI_WALK_TERMINATE); + } + +out: + if (path != NULL) + di_devfs_path_free(path); + devid_str_free(devidstr); + return (DI_WALK_CONTINUE); +} + +/*ARGSUSED*/ +static int +dev_fmri_present(topo_mod_t *mod, tnode_t *node, topo_version_t version, + nvlist_t *in, nvlist_t **out) +{ + di_node_t parent; + uint8_t fmversion; + char *devpath = NULL; + char *parentpath; + char *cp; + struct walkinfo walkinfo; + uint32_t present; + char *devid = NULL; + + if (version > TOPO_METH_PRESENT_VERSION) + return (topo_mod_seterrno(mod, EMOD_VER_NEW)); + + if (nvlist_lookup_uint8(in, FM_VERSION, &fmversion) != 0 || + fmversion > FM_DEV_SCHEME_VERSION || + nvlist_lookup_string(in, FM_FMRI_DEV_PATH, &devpath) != 0) + return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM)); + + (void) nvlist_lookup_string(in, FM_FMRI_DEV_ID, &devid); + + if (devpath == NULL || (walkinfo.len = strlen(devpath)) == 0) + return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM)); + + /* strip off last component of path */ + parentpath = alloca(walkinfo.len + 1); + (void) strcpy(parentpath, devpath); + if ((cp = strrchr(parentpath, '/')) == NULL) + parentpath = "/"; + else + *cp = '\0'; + + /* if the result is an empty path, start walk at "/" */ + if (*parentpath == '\0') + parentpath = "/"; + + /* + * DINFOFORCE is required for the devid to be present. + */ + if ((parent = di_init(parentpath, + DINFOSUBTREE | DINFOFORCE)) == DI_NODE_NIL) { + if (errno != ENXIO) + return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM)); + present = 0; + } else { + walkinfo.matched = 0; + walkinfo.path = devpath; + walkinfo.devid = devid; + (void) di_walk_node(parent, + DI_WALK_SIBFIRST, (void *)&walkinfo, dev_match); + di_fini(parent); + + present = walkinfo.matched; + } + + if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) != 0) + return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); + if (nvlist_add_uint32(*out, TOPO_METH_PRESENT_RET, present) != 0) { + nvlist_free(*out); + return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); + } + + return (0); +} + +/*ARGSUSED*/ +static int +dev_fmri_unusable(topo_mod_t *mod, tnode_t *node, topo_version_t version, + nvlist_t *in, nvlist_t **out) +{ + di_node_t dnode; + uint8_t fmversion; + char *devpath = NULL; + uint32_t unusable; + uint_t state; + + if (version > TOPO_METH_PRESENT_VERSION) + return (topo_mod_seterrno(mod, EMOD_VER_NEW)); + + if (nvlist_lookup_uint8(in, FM_VERSION, &fmversion) != 0 || + fmversion > FM_DEV_SCHEME_VERSION || + nvlist_lookup_string(in, FM_FMRI_DEV_PATH, &devpath) != 0) + return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM)); + + if (devpath == NULL) + return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM)); + + if ((dnode = di_init(devpath, DINFOCPYONE)) == DI_NODE_NIL) { + if (errno != ENXIO) + return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM)); + unusable = 1; + } else { + state = di_state(dnode); + if (state & (DI_DEVICE_OFFLINE | DI_DEVICE_DOWN | + DI_BUS_QUIESCED | DI_BUS_DOWN)) + unusable = 1; + else + unusable = 0; + di_fini(dnode); + } + + if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) != 0) + return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); + if (nvlist_add_uint32(*out, TOPO_METH_PRESENT_RET, unusable) != 0) { + nvlist_free(*out); + return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); + } + + return (0); +} + static nvlist_t * dev_fmri_create(topo_mod_t *mp, const char *id, const char *path) { diff --git a/usr/src/lib/fm/topo/libtopo/common/hc.c b/usr/src/lib/fm/topo/libtopo/common/hc.c index 1df1fa04f0..4567fd80b7 100644 --- a/usr/src/lib/fm/topo/libtopo/common/hc.c +++ b/usr/src/lib/fm/topo/libtopo/common/hc.c @@ -751,7 +751,8 @@ char **rev, nvlist_t **auth) if (na == NULL) { if (topo_mod_nvalloc(mod, &na, NV_UNIQUE_NAME) == 0) { - (void) nvlist_add_string(na, aname, aid); + (void) nvlist_add_string(na, aname, + aid); } } else { (void) nvlist_add_string(na, aname, aid); @@ -1375,7 +1376,7 @@ hc_is_present(topo_mod_t *mod, tnode_t *node, void *pdata) return (ETOPO_PROP_NVL); } - return (err); + return (0); } static int diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_fmri.c b/usr/src/lib/fm/topo/libtopo/common/topo_fmri.c index 7fc51be9c8..1847757626 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_fmri.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_fmri.c @@ -359,7 +359,7 @@ topo_fmri_fru(topo_hdl_t *thp, nvlist_t *nvl, nvlist_t **fru, int *err) nvlist_t *fp, *prop = NULL; if (fmri_prop(thp, nvl, TOPO_PGROUP_PROTOCOL, TOPO_PROP_FRU, - nvl, fru, err) < 0) + nvl, &prop, err) < 0) return (set_error(thp, *err, err, "topo_fmri_fru", NULL)); if (nvlist_lookup_nvlist(prop, TOPO_PROP_VAL_VAL, &fp) != 0) diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_mod.c b/usr/src/lib/fm/topo/libtopo/common/topo_mod.c index 5318bd1c12..04b6043648 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_mod.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_mod.c @@ -41,14 +41,14 @@ * * Module Plugin API * - * Enumerators must provide entry points for intialization and clean-up + * Enumerators must provide entry points for initialization and clean-up * (_topo_init() and _topo_fini()). In their _topo_init() function, an * enumerator should register (topo_mod_register()) its enumeration callback * and allocate resources required for a subsequent call to the callback. * Optionally, methods may also be registered with topo_method_register(). * * In its enumeration callback routine, the module should search for resources - * within its realm of resposibility and create any node ranges, + * within its realm of responsibility and create any node ranges, * topo_node_range_create() and nodes, topo_node_bind(). The Enumerator * module is handed a node to which it may begin attaching additional * topology nodes. The enumerator may only access those nodes within its @@ -306,7 +306,7 @@ topo_mod_hcfmri(topo_mod_t *mod, tnode_t *pnode, int version, const char *name, if (pnode != NULL || auth != NULL || part != NULL || rev != NULL || serial != NULL || hc_specific != NULL) { if (topo_mod_nvalloc(mod, &args, NV_UNIQUE_NAME) != 0) - return (set_fmri_err(mod, EMOD_FMRI_NVL)); + return (set_fmri_err(mod, EMOD_FMRI_NVL)); } if (pnode != NULL) { @@ -686,7 +686,8 @@ topo_mod_csn(topo_mod_t *mod) return (NULL); } - return (topo_mod_strdup(mod, csn)); + + return (topo_cleanup_auth_str(mod->tm_hdl, csn)); } nvlist_t * diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_mod.h b/usr/src/lib/fm/topo/libtopo/common/topo_mod.h index 43d217330a..d3f46d1b51 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_mod.h +++ b/usr/src/lib/fm/topo/libtopo/common/topo_mod.h @@ -153,6 +153,10 @@ extern nvlist_t *topo_mod_auth(topo_mod_t *, tnode_t *); #define TOPO_METH_FRU_COMPUTE_VERSION 0 #define TOPO_METH_FRU_COMPUTE_DESC "Dynamic FRU constructor" +#define TOPO_METH_DISK_STATUS "topo_disk_status" +#define TOPO_METH_DISK_STATUS_VERSION 0 +#define TOPO_METH_DISK_STATUS_DESC "Disk status" + extern void *topo_mod_alloc(topo_mod_t *, size_t); extern void *topo_mod_zalloc(topo_mod_t *, size_t); extern void topo_mod_free(topo_mod_t *, void *, size_t); diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_snap.c b/usr/src/lib/fm/topo/libtopo/common/topo_snap.c index ec333ee7c3..1645fb5a50 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_snap.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_snap.c @@ -102,37 +102,6 @@ set_open_errno(topo_hdl_t *thp, int *errp, int err) return (NULL); } -static char * -smbios_fix(topo_hdl_t *thp, char *begin) -{ - char buf[MAXNAMELEN]; - size_t count; - char *str, *end, *pp; - - end = begin + strlen(begin); - - while (begin < end && isspace(*begin)) - begin++; - while (begin < end && isspace(*(end - 1))) - end--; - - if (begin >= end) - return (NULL); - - count = end - begin; - count += 1; - - if (count > sizeof (buf)) - return (NULL); - - (void) snprintf(buf, count, "%s", begin); - while ((str = strchr(buf, ' ')) != NULL) - *str = '-'; - - pp = topo_hdl_strdup(thp, buf); - return (pp); -} - topo_hdl_t * topo_open(int version, const char *rootdir, int *errp) { @@ -218,7 +187,7 @@ topo_open(int version, const char *rootdir, int *errp) if (strcmp(s2.smbi_product, SMB_DEFAULT1) != 0 && strcmp(s2.smbi_product, SMB_DEFAULT2) != 0) { - thp->th_product = smbios_fix(thp, + thp->th_product = topo_cleanup_auth_str(thp, (char *)s2.smbi_product); } } diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_subr.c b/usr/src/lib/fm/topo/libtopo/common/topo_subr.c index 50f69156c4..d89ecbb1fa 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_subr.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_subr.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -313,3 +313,40 @@ topo_search_path(topo_mod_t *mod, const char *rootdir, const char *file) return (pp); } + +/* + * SMBIOS serial numbers can contain characters (particularly ':' and ' ') + * that are invalid for the authority and can break FMRI parsing. We translate + * any invalid characters to a safe '-', as well as trimming any leading or + * trailing whitespace. + */ +char * +topo_cleanup_auth_str(topo_hdl_t *thp, char *begin) +{ + char buf[MAXNAMELEN]; + size_t count; + char *str, *end, *pp; + + end = begin + strlen(begin); + + while (begin < end && isspace(*begin)) + begin++; + while (begin < end && isspace(*(end - 1))) + end--; + + if (begin >= end) + return (NULL); + + count = end - begin; + count += 1; + + if (count > sizeof (buf)) + return (NULL); + + (void) snprintf(buf, count, "%s", begin); + while ((str = strpbrk(buf, " :=")) != NULL) + *str = '-'; + + pp = topo_hdl_strdup(thp, buf); + return (pp); +} diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_subr.h b/usr/src/lib/fm/topo/libtopo/common/topo_subr.h index e3df346dc2..d3e56e5921 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_subr.h +++ b/usr/src/lib/fm/topo/libtopo/common/topo_subr.h @@ -90,6 +90,8 @@ extern void topo_fmristr_build(ssize_t *, char *, size_t, char *, char *, extern int topo_walk_byid(topo_walk_t *wp, const char *name, topo_instance_t); +extern char *topo_cleanup_auth_str(topo_hdl_t *, char *); + #ifdef __cplusplus } #endif diff --git a/usr/src/lib/fm/topo/modules/i86pc/sata/Makefile b/usr/src/lib/fm/topo/modules/i86pc/sata/Makefile index bd57a80a5f..cce64c561c 100644 --- a/usr/src/lib/fm/topo/modules/i86pc/sata/Makefile +++ b/usr/src/lib/fm/topo/modules/i86pc/sata/Makefile @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" @@ -31,4 +31,4 @@ MODULESRCS = sata.c sfx4500_props.c include ../../Makefile.plugin -LDLIBS += -lcfgadm -lsmbios -ldevinfo +LDLIBS += -lcfgadm -lsmbios -ldevinfo -ldiskstatus -ldevid diff --git a/usr/src/lib/fm/topo/modules/i86pc/sata/sata.c b/usr/src/lib/fm/topo/modules/i86pc/sata/sata.c index 5a3ea47846..a1329eac7b 100644 --- a/usr/src/lib/fm/topo/modules/i86pc/sata/sata.c +++ b/usr/src/lib/fm/topo/modules/i86pc/sata/sata.c @@ -20,7 +20,7 @@ */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -54,6 +54,9 @@ #include <assert.h> #include <sys/dkio.h> #include <pthread.h> +#include <fm/libdiskstatus.h> +#include <sys/fm/io/scsi.h> +#include <devid.h> #include "sata.h" #include "sfx4500_props.h" @@ -63,6 +66,18 @@ #define MAX_MACHNAMELEN 256 +/* + * Given a /devices path for a whole disk, appending this extension gives the + * path to a raw device that can be opened. + */ +#if defined(__i386) || defined(__amd64) +#define PHYS_EXTN ":q,raw" +#elif defined(__sparc) || defined(__sparcv9) +#define PHYS_EXTN ":c,raw" +#else +#error Unknown architecture +#endif + struct sata_machine_specific_properties *machprops[] = { &SFX4500_machprops, NULL }; @@ -86,13 +101,12 @@ static tnode_t *node_create(topo_mod_t *mod, tnode_t *pnode, const char *name, char *label, cfga_list_data_t *, int *err); static char *trimdup(const char *s, int *slen, topo_mod_t *mod); static boolean_t get_machine_name(char **name, int *namelen, topo_mod_t *mod); -static int make_legacyhc_fmri(topo_mod_t *mod, const char *str, - nvlist_t **fmri); static int sata_minorname_to_ap(char *minorname, char **ap, int *apbuflen, cfga_list_data_t **list_array, topo_mod_t *mod); static sata_dev_prop_t *lookup_sdp_by_minor(char *minorpath); static boolean_t find_physical_disk_node(char *physpath, int pathbuflen, int portnum, topo_mod_t *mod); +static char *devpath_to_devid(char *devpath); static int sata_maximum_port(char *dpath, topo_mod_t *mod); static void sata_add_port_props(tnode_t *cnode, sata_dev_prop_t *sdp, int *err); static void sata_port_add_private_props(tnode_t *cnode, char *minorpath, @@ -100,34 +114,32 @@ static void sata_port_add_private_props(tnode_t *cnode, char *minorpath, static void sata_info_to_fru(char *info, char **model, int *modlen, char **manuf, int *manulen, char **serial, int *serlen, char **firm, int *firmlen, topo_mod_t *mod); -static void sata_add_disk_props(tnode_t *cnode, int portnum, +static void sata_add_disk_props(tnode_t *cnode, const char *physpath, cfga_list_data_t *cfgap, int *err, topo_mod_t *mod); static boolean_t is_sata_controller(char *dpath); -static int sata_disks_create(topo_mod_t *mod, tnode_t *pnode, nvlist_t *pasru, - const char *name, int portnum, cfga_list_data_t *cfglist, int ndisks, - int *err); +static int sata_disks_create(topo_mod_t *mod, tnode_t *pnode, const char *name, + int portnum, cfga_list_data_t *cfglist, int ndisks, int *err); static int sata_port_create(topo_mod_t *mod, tnode_t *pnode, const char *name, topo_instance_t min, topo_instance_t max); static int sata_enum(topo_mod_t *mod, tnode_t *pnode, const char *name, topo_instance_t min, topo_instance_t max, void *notused1, void *notused2); -static int sata_present(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *, +static int sata_disk_status(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *, nvlist_t **); static void sata_release(topo_mod_t *mod, tnode_t *nodep); /* - * The topo method is bound to the sata-port nodes because we want - * the hc scheme's lookup to find the method and execute it. If it - * were bound to the disk nodes, and if a disk were not inserted, - * the present method would never execute, and the hc scheme plugin - * would return PRESENT (because other hc-schemed FMRIs do not implement - * a presence method, and in those cases, the correct default is to - * assert presence). + * Methods for SATA disks. This is used by the disk-transport module to + * generate ereports based off SCSI disk status. Unlike the present method, + * this can only function when we have a /devices path (i.e. the disk is + * configured) and so must be a function of the disk itself. */ -static const topo_method_t SATA_METHODS[] = { - { TOPO_METH_PRESENT, TOPO_METH_PRESENT_DESC, TOPO_METH_PRESENT_VERSION, - TOPO_STABILITY_INTERNAL, sata_present }, +static const topo_method_t sata_disk_methods[] = { + { TOPO_METH_DISK_STATUS, TOPO_METH_DISK_STATUS_DESC, + TOPO_METH_DISK_STATUS_VERSION, TOPO_STABILITY_INTERNAL, + sata_disk_status }, { NULL } }; + /* * Since the global data is only initialized ONCE in _topo_init * and multiple threads that use libtopo can cause _topo_init to @@ -156,10 +168,7 @@ static const topo_modinfo_t sata_info = static void sata_release(topo_mod_t *mod, tnode_t *nodep) { - /* Only need to unregister methods for the sata port nodes */ - if (strcmp(topo_node_name(nodep), SATA_PORT) == 0) - topo_method_unregister_all(mod, nodep); - + topo_method_unregister_all(mod, nodep); topo_node_unbind(nodep); } @@ -373,17 +382,6 @@ _topo_fini(topo_mod_t *mod) } static int -make_legacyhc_fmri(topo_mod_t *mod, const char *str, nvlist_t **fmri) -{ - char buf[PATH_MAX]; - - (void) snprintf(buf, PATH_MAX, "hc:///component=%s", str); - - return (topo_mod_str2nvl(mod, buf, fmri)); -} - - -static int config_list_ext_poll(int num, char * const *path, cfga_list_data_t **list_array, int *nlist) { @@ -428,113 +426,6 @@ config_list_ext_poll(int num, char * const *path, return (e); } -static boolean_t -sata_serial_match(topo_mod_t *mod, char *sn, char *ap_lname, char *info) -{ - char *p, *apsn; - boolean_t rv = B_FALSE; - int buflen; - - /* Some cfgadm plugins provide serial number information (e.g. sata) */ - if (strncmp(ap_lname, "sata", 4) == 0 && isdigit(ap_lname[4])) { - /* The sata plugin provides the serial number after "SN: " */ - if ((p = strstr(info, "SN: ")) != NULL) { - - p += 4 /* strlen("SN: ") */; - - /* - * Terminate the string at any spaces (the serial - * number is a string with no spaces) - */ - buflen = strlen(p) + 1; - apsn = topo_mod_alloc(mod, buflen); - (void) strcpy(apsn, p); - if ((p = strpbrk(apsn, " \t")) != NULL) - *p = 0; - - rv = (strcasecmp(apsn, sn) == 0); - - topo_mod_free(mod, apsn, buflen); - } - } else /* Unknown cfgadm plugin ap type -- assume sn matches */ - rv = B_TRUE; - - return (rv); -} - -/*ARGSUSED*/ -static int -sata_present(topo_mod_t *mod, tnode_t *nodep, topo_version_t vers, - nvlist_t *nvl, nvlist_t **out_nvl) -{ - nvlist_t **hcprs = NULL; - uint8_t version; - char *nm = NULL; - char *id = NULL; - uint_t hcnprs; - int err; - cfga_list_data_t *list_array = NULL; - int nlist; - char *ap_path[1]; - int present = 1; - char *sn; - - /* - * If this FMRI is a legacy hc FMRI with a component - * looking like an attachment point, use libcfgadm to - * detect its state: - */ - - if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 || - version > FM_HC_SCHEME_VERSION) { - - return (topo_mod_seterrno(mod, EINVAL)); - } - - err = nvlist_lookup_nvlist_array(nvl, FM_FMRI_HC_LIST, &hcprs, &hcnprs); - if (err != 0 || hcprs == NULL) { - - return (topo_mod_seterrno(mod, EINVAL)); - } - - (void) nvlist_lookup_string(hcprs[0], FM_FMRI_HC_NAME, &nm); - (void) nvlist_lookup_string(hcprs[0], FM_FMRI_HC_ID, &id); - /* The hc-list must have 1 entry and that entry must be a component */ - if (hcnprs == 1 && nm != NULL && id != NULL && - strcmp(nm, FM_FMRI_LEGACY_HC) == 0 && strchr(id, '/') != NULL) { - ap_path[0] = id; - - if (config_list_ext_poll(1, ap_path, &list_array, &nlist) - == CFGA_OK) { - - if (nvlist_lookup_string(nvl, FM_FMRI_HC_SERIAL_ID, &sn) - != 0) - sn = NULL; - - /* - * If a serial number is included in the FMRI, use it - * to verify presence - */ - present = (list_array[0].ap_r_state - == CFGA_STAT_CONNECTED && (sn == NULL || - (sn != NULL && - sata_serial_match(mod, sn, list_array[0].ap_log_id, - list_array[0].ap_info)))); - - free(list_array); - } - } - - if (topo_mod_nvalloc(mod, out_nvl, NV_UNIQUE_NAME) != 0) - return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); - if (nvlist_add_uint32(*out_nvl, TOPO_METH_PRESENT_RET, present) != 0) { - nvlist_free(*out_nvl); - return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); - } - - return (0); -} - /* * Translates a device minor name into an attachment point (`sataX/Y`) * Allocates memory and returns the attachment point name in *ap. @@ -635,6 +526,27 @@ find_physical_disk_node(char *physpath, int pathbuflen, int portnum, return (found); } +static char * +devpath_to_devid(char *devpath) +{ + di_node_t node; + ddi_devid_t devid; + char *devidstr = NULL; + + if ((node = di_init(devpath, DINFOCPYONE)) == DI_NODE_NIL) + return (NULL); + + if ((devid = di_devid(node)) == NULL) { + di_fini(node); + return (NULL); + } + + devidstr = devid_str_encode(devid, NULL); + di_fini(node); + + return (devidstr); +} + static int sata_maximum_port(char *dpath, topo_mod_t *mod) { @@ -837,39 +749,25 @@ sata_info_to_fru(char *info, char **model, int *modlen, char **manuf, } static void -sata_add_disk_props(tnode_t *cnode, int portnum, cfga_list_data_t *cfgap, - int *err, topo_mod_t *mod) +sata_add_disk_props(tnode_t *cnode, const char *physpath, + cfga_list_data_t *cfgap, int *err, topo_mod_t *mod) { - char physpath[PATH_MAX]; char *ldev, *p; char *model = NULL, *manuf = NULL, *serial = NULL, *firm = NULL; - boolean_t physpath_found = B_FALSE; int manuf_len, model_len, serial_len, firm_len; - /* - * The physical path to the drive can be derived by - * taking the attachment point physical path, chopping off the - * minor portion, and looking for the target node of the form - * <nodename>@<X>,0 (where <X> is the port number) - */ - strncpy(physpath, cfgap->ap_phys_id, PATH_MAX); - - /* The AP phys path MUST have a colon: */ - *strchr(physpath, ':') = 0; - - if (find_physical_disk_node(physpath, PATH_MAX, portnum, mod)) { + (void) topo_pgroup_create(cnode, &storage_pgroup, err); - (void) topo_pgroup_create(cnode, &io_pgroup, err); + if (physpath) { + (void) topo_pgroup_create(cnode, &io_pgroup, + err); (void) topo_prop_set_string(cnode, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH, TOPO_PROP_IMMUTABLE, physpath + 8 /* strlen("/devices") */, err); - physpath_found = B_TRUE; } - (void) topo_pgroup_create(cnode, &storage_pgroup, err); - if ((ldev = strstr(cfgap->ap_log_id, "::")) != NULL) { ldev += 2 /* strlen("::") */; if ((p = strchr(ldev, '/')) != NULL) { @@ -903,22 +801,21 @@ sata_add_disk_props(tnode_t *cnode, int portnum, cfga_list_data_t *cfgap, topo_mod_free(mod, firm, firm_len); } -#if defined(__i386) || defined(__amd64) /* * Try to get the disk capacity and store it in a property if the * device is accessible */ - if (physpath_found) { + if (physpath) { int fd; struct dk_minfo dkmi; uint64_t capacity = 0; char capstr[32]; - /* - * On x86, for disk target drivers, `:q,raw' means "access the - * character device that corresponds to the whole disk". - */ - strncat(physpath, ":q,raw", PATH_MAX); - if ((fd = open(physpath, O_RDONLY)) >= 0) { + char buf[PATH_MAX]; + + (void) snprintf(buf, sizeof (buf), "%s%s", physpath, + PHYS_EXTN); + + if ((fd = open(buf, O_RDONLY)) >= 0) { if (ioctl(fd, DKIOCGMEDIAINFO, &dkmi) == 0) { capacity = dkmi.dki_capacity * (uint64_t)dkmi.dki_lbsize; @@ -933,7 +830,6 @@ sata_add_disk_props(tnode_t *cnode, int portnum, cfga_list_data_t *cfgap, err); } } -#endif } static boolean_t @@ -957,14 +853,17 @@ is_sata_controller(char *dpath) } static int -sata_disks_create(topo_mod_t *mod, tnode_t *pnode, nvlist_t *pasru, - const char *name, int portnum, cfga_list_data_t *cfglist, - int ndisks, int *err) +sata_disks_create(topo_mod_t *mod, tnode_t *pnode, const char *name, + int portnum, cfga_list_data_t *cfglist, int ndisks, int *err) { tnode_t *cnode; sata_dev_prop_t *sdp; nvlist_t *fru = NULL; int i, nerrs = 0; + char physpath_buf[PATH_MAX]; + char *physpath, *devpath; + char *devid; + nvlist_t *asru; if (topo_node_range_create(mod, pnode, name, 0, ndisks - 1) < 0) { topo_mod_dprintf(mod, "Unable to create " @@ -981,33 +880,57 @@ sata_disks_create(topo_mod_t *mod, tnode_t *pnode, nvlist_t *pasru, (cfglist[i].ap_o_state == CFGA_STAT_CONFIGURED || cfglist[i].ap_o_state == CFGA_STAT_UNCONFIGURED)) { - if ((sdp = lookup_sdp_by_minor(cfglist[i].ap_phys_id)) - != NULL) { - if (make_legacyhc_fmri(mod, sdp->label, &fru) - != 0) { - topo_mod_dprintf(mod, "Error creating " - "FRU while creating " SATA_DISK - " nodes: %s\n", - topo_mod_errmsg(mod)); - } - } + sdp = lookup_sdp_by_minor(cfglist[i].ap_phys_id); /* - * If this disk is one which we're decorating with - * machine-specific properties, set the FRU to - * a legacy-hc FMRI with the label as the component, - * the ASRU as the parent's ASRU (the ASRU of the - * sata port, e.g. the attachment point); otherwise, - * use the node's FMRI as its FRU (fru will be NULL - * in that case, causing node_create to use the FMRI). + * The physical path to the drive can be derived by + * taking the attachment point physical path, chopping + * off the minor portion, and looking for the target + * node of the form <nodename>@<X>,0 (where <X> is the + * port number) */ + strncpy(physpath_buf, cfglist[i].ap_phys_id, PATH_MAX); + + /* The AP phys path MUST have a colon: */ + *strchr(physpath_buf, ':') = 0; + + /* + * Create the ASRU. If the device is attached to the + * system but unconfigured, then it will have no + * associated ASRU. + */ + if (find_physical_disk_node(physpath_buf, PATH_MAX, + portnum, mod)) { + physpath = physpath_buf; + devpath = physpath_buf + + sizeof ("/devices") - 1; + /* + * Given the /devices path, attempt to find an + * associated devid. + */ + devid = devpath_to_devid(devpath); + asru = topo_mod_devfmri(mod, + FM_DEV_SCHEME_VERSION, devpath, devid); + devid_str_free(devid); + } else { + physpath = NULL; + asru = NULL; + } + if ((cnode = node_create(mod, pnode, name, i, - B_TRUE, fru, pasru, sdp ? (char *)sdp->label : NULL, + B_TRUE, fru, asru, sdp ? (char *)sdp->label : NULL, &cfglist[i], err)) != NULL) { - sata_add_disk_props(cnode, portnum, &cfglist[i], - err, mod); + sata_add_disk_props(cnode, physpath, + &cfglist[i], err, mod); + if (topo_method_register(mod, cnode, + sata_disk_methods) < 0) { + topo_mod_dprintf(mod, + "topo_method_register failed: %s\n", + topo_strerror(topo_mod_errno(mod))); + ++nerrs; + } } else { topo_mod_dprintf(mod, "Error creating disk " "node for port %d: %s\n", portnum, @@ -1015,8 +938,8 @@ sata_disks_create(topo_mod_t *mod, tnode_t *pnode, nvlist_t *pasru, ++nerrs; } - if (fru) - nvlist_free(fru); + nvlist_free(fru); + nvlist_free(asru); } } @@ -1032,7 +955,7 @@ static int sata_port_create(topo_mod_t *mod, tnode_t *pnode, const char *name, topo_instance_t min, topo_instance_t max) { - nvlist_t *asru, *fru; + nvlist_t *asru; tnode_t *cnode; int err = 0, nerr = 0; int i; @@ -1066,15 +989,6 @@ sata_port_create(topo_mod_t *mod, tnode_t *pnode, const char *name, return (-1); } - /* Create the FRU - a legacy component FMRI */ - if (make_legacyhc_fmri(mod, "motherboard", &fru) != 0) { - topo_mod_dprintf(mod, "Unable to create legacy FRU FMRI for " - "node " SATA_PORT ": %s\n", - topo_strerror(err)); - topo_mod_free(mod, dpath, dpathlen); - return (-1); - } - for (i = min; i <= max; i++) { /* The minor node name = "/devices" + dpath + ":" + i */ @@ -1088,8 +1002,9 @@ sata_port_create(topo_mod_t *mod, tnode_t *pnode, const char *name, continue; } - /* Create the ASRU - a legacy component FMRI */ - if (make_legacyhc_fmri(mod, ap, &asru) != 0) { + /* Create the ASRU - a dev:// FMRI */ + if ((asru = topo_mod_devfmri(mod, FM_DEV_SCHEME_VERSION, + dpath, NULL)) == NULL) { free(cfglist); topo_mod_free(mod, ap, apbuflen); topo_mod_dprintf(mod, "failed to make ASRU FMRI: " @@ -1099,12 +1014,10 @@ sata_port_create(topo_mod_t *mod, tnode_t *pnode, const char *name, } /* - * The ASRU is a legacy-hc component FMRI with the - * component being the attachment point - * The FRU is a legacy-hc component FMRI with the - * component being "MB". + * The ASRU is the devices path + * The FRU is a the component FRMI */ - if ((cnode = node_create(mod, pnode, name, i, B_TRUE, fru, + if ((cnode = node_create(mod, pnode, name, i, B_TRUE, NULL, asru, ap, NULL, &err)) == NULL) { nvlist_free(asru); free(cfglist); @@ -1115,24 +1028,13 @@ sata_port_create(topo_mod_t *mod, tnode_t *pnode, const char *name, continue; } - if (topo_method_register(mod, cnode, SATA_METHODS) < 0) { - topo_mod_dprintf(mod, "topo_method_register failed: " - "%s\n", topo_strerror(topo_mod_errno(mod))); - topo_node_unbind(cnode); - nvlist_free(asru); - free(cfglist); - topo_mod_free(mod, ap, apbuflen); - ++nerr; - continue; - } - topo_mod_free(mod, ap, apbuflen); /* For now, ignore errors from private property creation */ sata_port_add_private_props(cnode, minorname, &err); /* Create the disk node(s) under this sata-port: */ - if (sata_disks_create(mod, cnode, asru, SATA_DISK, i, cfglist, + if (sata_disks_create(mod, cnode, SATA_DISK, i, cfglist, 1 /* one disk for now */, &err) != 0) { topo_mod_dprintf(mod, "Error while creating " SATA_DISK " node(s): %s\n", topo_strerror(err)); @@ -1144,7 +1046,6 @@ sata_port_create(topo_mod_t *mod, tnode_t *pnode, const char *name, nvlist_free(asru); } - nvlist_free(fru); topo_mod_free(mod, dpath, dpathlen); if (nerr != 0) { @@ -1164,3 +1065,91 @@ sata_enum(topo_mod_t *mod, tnode_t *pnode, const char *name, return (0); } + +/* + * Query the current disk status. If successful, the disk status is returned as + * an nvlist consisting of at least the following members: + * + * protocol string Supported protocol (currently "scsi") + * + * status nvlist Arbitrary protocol-specific information + * about the current state of the disk. + * + * faults nvlist A list of supported faults. Each + * element of this list is a boolean value. + * An element's existence indicates that + * the drive supports detecting this fault, + * and the value indicates the current + * state of the fault. + * + * <fault-name> nvlist For each fault named in 'faults', a + * nvlist describing protocol-specific + * attributes of the fault. + * + * This method relies on the libdiskstatus library to query this information. + */ +static int +sata_disk_status(topo_mod_t *mod, tnode_t *nodep, topo_version_t vers, + nvlist_t *in_nvl, nvlist_t **out_nvl) +{ + disk_status_t *dsp; + char *devpath, *fullpath; + size_t pathlen; + int err; + nvlist_t *status; + *out_nvl = NULL; + + if (vers != TOPO_METH_DISK_STATUS_VERSION) + return (topo_mod_seterrno(mod, EMOD_VER_NEW)); + + /* + * If the caller specifies the "path" parameter, then this indicates + * that we should use this instead of deriving it from the topo node + * itself. + */ + if (nvlist_lookup_string(in_nvl, "path", &fullpath) == 0) { + devpath = NULL; + } else { + /* + * Get the /devices path and attempt to open the disk status + * handle. + */ + if (topo_prop_get_string(nodep, TOPO_PGROUP_IO, + TOPO_IO_DEV_PATH, &devpath, &err) != 0) + return (topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP)); + + /* + * Note that sizeof(string) includes the terminating NULL byte + */ + pathlen = strlen(devpath) + sizeof ("/devices") + + sizeof (PHYS_EXTN) - 1; + + if ((fullpath = topo_mod_alloc(mod, pathlen)) == NULL) + return (topo_mod_seterrno(mod, EMOD_NOMEM)); + + (void) snprintf(fullpath, pathlen, "/devices%s%s", devpath, + PHYS_EXTN); + + topo_mod_strfree(mod, devpath); + } + + if ((dsp = disk_status_open(fullpath, &err)) == NULL) { + topo_mod_free(mod, fullpath, pathlen); + return (topo_mod_seterrno(mod, err == EDS_NOMEM ? + EMOD_NOMEM : EMOD_METHOD_NOTSUP)); + } + + if (devpath) + topo_mod_free(mod, fullpath, pathlen); + + if ((status = disk_status_get(dsp)) == NULL) { + err = (disk_status_errno(dsp) == EDS_NOMEM ? + EMOD_NOMEM : EMOD_METHOD_NOTSUP); + disk_status_close(dsp); + return (topo_mod_seterrno(mod, err)); + } + + *out_nvl = status; + disk_status_close(dsp); + return (0); +} |
