xref: /illumos-gate/usr/src/lib/fm/libdiskstatus/common/ds_scsi.c (revision 78801af7286cd73dbc996d470f789e75993cf15d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
25  */
26 
27 #include <assert.h>
28 #include <errno.h>
29 #include <libdiskstatus.h>
30 #include <limits.h>
31 #include <stdlib.h>
32 #include <strings.h>
33 #include <sys/fm/io/scsi.h>
34 
35 #include "ds_scsi.h"
36 #include "ds_scsi_sim.h"
37 #include "ds_scsi_uscsi.h"
38 
39 typedef struct ds_scsi_info {
40 	disk_status_t		*si_dsp;
41 	void			*si_sim;
42 	int			si_cdblen;
43 	int			si_supp_mode;
44 	int			si_supp_log;
45 	int			si_extensions;
46 	int			si_reftemp;
47 	scsi_ms_hdrs_t		si_hdrs;
48 	scsi_ie_page_t		si_iec_current;
49 	scsi_ie_page_t		si_iec_changeable;
50 	nvlist_t		*si_state_modepage;
51 	nvlist_t		*si_state_logpage;
52 	nvlist_t		*si_state_iec;
53 } ds_scsi_info_t;
54 
55 #define	scsi_set_errno(sip, errno)	(ds_set_errno((sip)->si_dsp, (errno)))
56 
57 /*
58  * Table to validate log pages
59  */
60 typedef int (*logpage_validation_fn_t)(ds_scsi_info_t *,
61     scsi_log_parameter_header_t *, int, nvlist_t *);
62 typedef int (*logpage_analyze_fn_t)(ds_scsi_info_t *,
63     scsi_log_parameter_header_t *, int);
64 
65 typedef struct logpage_validation_entry {
66 	uchar_t			ve_code;
67 	int			ve_supported;
68 	const char		*ve_desc;
69 	logpage_validation_fn_t	ve_validate;
70 	logpage_analyze_fn_t	ve_analyze;
71 } logpage_validation_entry_t;
72 
73 static int logpage_ie_verify(ds_scsi_info_t *,
74     scsi_log_parameter_header_t *, int, nvlist_t *);
75 static int logpage_temp_verify(ds_scsi_info_t *,
76     scsi_log_parameter_header_t *, int, nvlist_t *);
77 static int logpage_selftest_verify(ds_scsi_info_t *,
78     scsi_log_parameter_header_t *, int, nvlist_t *);
79 static int logpage_ssm_verify(ds_scsi_info_t *,
80     scsi_log_parameter_header_t *, int, nvlist_t *);
81 
82 static int logpage_ie_analyze(ds_scsi_info_t *,
83     scsi_log_parameter_header_t *, int);
84 static int logpage_temp_analyze(ds_scsi_info_t *,
85     scsi_log_parameter_header_t *, int);
86 static int logpage_selftest_analyze(ds_scsi_info_t *,
87     scsi_log_parameter_header_t *, int);
88 static int logpage_ssm_analyze(ds_scsi_info_t *,
89     scsi_log_parameter_header_t *, int);
90 
91 static struct logpage_validation_entry log_validation[] = {
92 	{ LOGPAGE_IE,		LOGPAGE_SUPP_IE,
93 	    "informational-exceptions",
94 	    logpage_ie_verify,	logpage_ie_analyze },
95 	{ LOGPAGE_TEMP,		LOGPAGE_SUPP_TEMP,
96 	    "temperature",
97 	    logpage_temp_verify, logpage_temp_analyze },
98 	{ LOGPAGE_SELFTEST,	LOGPAGE_SUPP_SELFTEST,
99 	    "self-test",
100 	    logpage_selftest_verify, logpage_selftest_analyze },
101 	{ LOGPAGE_SSM,		LOGPAGE_SUPP_SSM,
102 	    FM_EREPORT_SCSI_SSMWEAROUT,
103 	    logpage_ssm_verify, logpage_ssm_analyze }
104 };
105 
106 #define	NLOG_VALIDATION	(sizeof (log_validation) / sizeof (log_validation[0]))
107 
108 /*
109  * Given an extended sense page, retrieves the sense key, as well as the
110  * additional sense code information.
111  */
112 static void
113 scsi_translate_error(struct scsi_extended_sense *rq, uint_t *skeyp,
114     uint_t *ascp, uint_t *ascqp)
115 {
116 	struct scsi_descr_sense_hdr *sdsp =
117 	    (struct scsi_descr_sense_hdr *)rq;
118 
119 	*skeyp = rq->es_key;
120 
121 	/*
122 	 * Get asc, ascq and info field from sense data.  There are two
123 	 * possible formats (fixed sense data and descriptor sense data)
124 	 * depending on the value of es_code.
125 	 */
126 	switch (rq->es_code) {
127 	case CODE_FMT_DESCR_CURRENT:
128 	case CODE_FMT_DESCR_DEFERRED:
129 
130 		*ascp = sdsp->ds_add_code;
131 		*ascqp = sdsp->ds_qual_code;
132 		break;
133 
134 	case CODE_FMT_FIXED_CURRENT:
135 	case CODE_FMT_FIXED_DEFERRED:
136 	default:
137 
138 		if (rq->es_add_len >= 6) {
139 			*ascp = rq->es_add_code;
140 			*ascqp = rq->es_qual_code;
141 		} else {
142 			*ascp = 0xff;
143 			*ascqp = 0xff;
144 		}
145 		break;
146 	}
147 }
148 
149 /*
150  * Routines built atop the bare uscsi commands, which take into account the
151  * command length, automatically translate any scsi errors, and transparently
152  * call into the simulator if active.
153  */
154 static int
155 scsi_mode_select(ds_scsi_info_t *sip, uchar_t page_code, int options,
156     void *buf, uint_t buflen, scsi_ms_hdrs_t *headers, uint_t *skp,
157     uint_t *ascp, uint_t *ascqp)
158 {
159 	int result;
160 	struct scsi_extended_sense sense;
161 	int senselen = sizeof (struct scsi_extended_sense);
162 	struct mode_page *mp = (struct mode_page *)buf;
163 
164 	assert(sip->si_cdblen == MODE_CMD_LEN_6 ||
165 	    sip->si_cdblen == MODE_CMD_LEN_10);
166 	assert(headers->ms_length == sip->si_cdblen);
167 
168 	bzero(&sense, sizeof (struct scsi_extended_sense));
169 
170 	if (mp->ps) {
171 		options |= MODE_SELECT_SP;
172 		mp->ps = 0;
173 	} else {
174 		options &= ~MODE_SELECT_SP;
175 	}
176 
177 	if (sip->si_cdblen == MODE_CMD_LEN_6) {
178 		/* The following fields are reserved during mode select: */
179 		headers->ms_hdr.g0.ms_header.length = 0;
180 		headers->ms_hdr.g0.ms_header.device_specific = 0;
181 
182 		if (sip->si_sim)
183 			result = simscsi_mode_select(sip->si_sim,
184 			    page_code, options, buf, buflen,
185 			    &headers->ms_hdr.g0, &sense, &senselen);
186 		else
187 			result = uscsi_mode_select(sip->si_dsp->ds_fd,
188 			    page_code, options, buf, buflen,
189 			    &headers->ms_hdr.g0, &sense, &senselen);
190 	} else {
191 		/* The following fields are reserved during mode select: */
192 		headers->ms_hdr.g1.ms_header.length = 0;
193 		headers->ms_hdr.g1.ms_header.device_specific = 0;
194 
195 		if (sip->si_sim)
196 			result = simscsi_mode_select_10(sip->si_sim,
197 			    page_code, options, buf, buflen,
198 			    &headers->ms_hdr.g1, &sense, &senselen);
199 		else
200 			result = uscsi_mode_select_10(sip->si_dsp->ds_fd,
201 			    page_code, options, buf, buflen,
202 			    &headers->ms_hdr.g1, &sense, &senselen);
203 	}
204 
205 	if (result != 0)
206 		scsi_translate_error(&sense, skp, ascp, ascqp);
207 
208 	return (result);
209 }
210 
211 static int
212 scsi_mode_sense(ds_scsi_info_t *sip, uchar_t page_code, uchar_t pc,
213     void *buf, uint_t buflen, scsi_ms_hdrs_t *headers, uint_t *skp,
214     uint_t *ascp, uint_t *ascqp)
215 {
216 	int result;
217 	struct scsi_extended_sense sense;
218 	int senselen = sizeof (struct scsi_extended_sense);
219 
220 	assert(sip->si_cdblen == MODE_CMD_LEN_6 ||
221 	    sip->si_cdblen == MODE_CMD_LEN_10);
222 
223 	bzero(&sense, sizeof (struct scsi_extended_sense));
224 
225 	bzero(headers, sizeof (scsi_ms_hdrs_t));
226 	headers->ms_length = sip->si_cdblen;
227 
228 	if (sip->si_cdblen == MODE_CMD_LEN_6) {
229 		if (sip->si_sim)
230 			result = simscsi_mode_sense(sip->si_sim,
231 			    page_code, pc, buf, buflen, &headers->ms_hdr.g0,
232 			    &sense, &senselen);
233 		else
234 			result = uscsi_mode_sense(sip->si_dsp->ds_fd, page_code,
235 			    pc, buf, buflen, &headers->ms_hdr.g0, &sense,
236 			    &senselen);
237 	} else {
238 		if (sip->si_sim)
239 			result = simscsi_mode_sense_10(sip->si_sim,
240 			    page_code, pc, buf, buflen, &headers->ms_hdr.g1,
241 			    &sense, &senselen);
242 		else
243 			result = uscsi_mode_sense_10(sip->si_dsp->ds_fd,
244 			    page_code, pc, buf, buflen, &headers->ms_hdr.g1,
245 			    &sense, &senselen);
246 	}
247 
248 	if (result != 0)
249 		scsi_translate_error(&sense, skp, ascp, ascqp);
250 
251 	return (result);
252 }
253 
254 static int
255 scsi_request_sense(ds_scsi_info_t *sip, uint_t *skp, uint_t *ascp,
256     uint_t *ascqp)
257 {
258 	struct scsi_extended_sense sense, sensebuf;
259 	int senselen = sizeof (struct scsi_extended_sense);
260 	int sensebuflen = sizeof (struct scsi_extended_sense);
261 	int result;
262 
263 	bzero(&sense, sizeof (struct scsi_extended_sense));
264 	bzero(&sensebuf, sizeof (struct scsi_extended_sense));
265 
266 	if (sip->si_sim)
267 		result = simscsi_request_sense(sip->si_sim,
268 		    (caddr_t)&sensebuf, sensebuflen, &sense, &senselen);
269 	else
270 		result = uscsi_request_sense(sip->si_dsp->ds_fd,
271 		    (caddr_t)&sensebuf, sensebuflen, &sense, &senselen);
272 
273 	if (result == 0)
274 		scsi_translate_error(&sensebuf, skp, ascp, ascqp);
275 	else
276 		scsi_translate_error(&sense, skp, ascp, ascqp);
277 
278 	return (result);
279 }
280 
281 static int
282 scsi_log_sense(ds_scsi_info_t *sip, int page_code, int page_control,
283     caddr_t page_data, int page_size, uint_t *skp, uint_t *ascp, uint_t *ascqp)
284 {
285 	int result;
286 	struct scsi_extended_sense sense;
287 	int senselen = sizeof (struct scsi_extended_sense);
288 
289 	if (sip->si_sim)
290 		result = simscsi_log_sense(sip->si_sim,
291 		    page_code, page_control, page_data, page_size, &sense,
292 		    &senselen);
293 	else
294 		result = uscsi_log_sense(sip->si_dsp->ds_fd,
295 		    page_code, page_control, page_data, page_size, &sense,
296 		    &senselen);
297 
298 	if (result != 0)
299 		scsi_translate_error(&sense, skp, ascp, ascqp);
300 
301 	return (result);
302 }
303 
304 /*
305  * Given a list of supported mode pages, determine if the given page is present.
306  */
307 static boolean_t
308 mode_page_present(uchar_t *pgdata, uint_t pgdatalen, uchar_t pagecode)
309 {
310 	uint_t i = 0;
311 	struct mode_page *pg;
312 	boolean_t found = B_FALSE;
313 
314 	/*
315 	 * The mode page list contains all mode pages supported by the device,
316 	 * one after the other.
317 	 */
318 	while (i < pgdatalen) {
319 		pg = (struct mode_page *)&pgdata[i];
320 
321 		if (pg->code == pagecode) {
322 			found = B_TRUE;
323 			break;
324 		}
325 
326 		i += MODESENSE_PAGE_LEN(pg);
327 	}
328 
329 	return (found);
330 }
331 
332 /*
333  * Load mode pages and check that the appropriate pages are supported.
334  *
335  * As part of this process, we determine which form of the MODE SENSE / MODE
336  * SELECT command to use (the 6-byte or 10-byte version) by executing a MODE
337  * SENSE command for a page that should be implemented by the device.
338  */
339 static int
340 load_modepages(ds_scsi_info_t *sip)
341 {
342 	int allpages_buflen;
343 	uchar_t *allpages;
344 	scsi_ms_hdrs_t headers;
345 	int result;
346 	uint_t skey, asc, ascq;
347 	int datalength = 0;
348 	scsi_ms_header_t *smh = &headers.ms_hdr.g0;
349 	scsi_ms_header_g1_t *smh_g1 = &headers.ms_hdr.g1;
350 	nvlist_t *nvl;
351 
352 	allpages_buflen = MAX_BUFLEN(scsi_ms_header_g1_t);
353 	if ((allpages = calloc(allpages_buflen, 1)) == NULL)
354 		return (scsi_set_errno(sip, EDS_NOMEM));
355 
356 	bzero(&headers, sizeof (headers));
357 
358 	/*
359 	 * Attempt a mode sense(6).  If that fails, try a mode sense(10)
360 	 *
361 	 * allpages is allocated to be of the maximum size for either a mode
362 	 * sense(6) or mode sense(10) MODEPAGE_ALLPAGES response.
363 	 *
364 	 * Note that the length passed into uscsi_mode_sense should be set to
365 	 * the maximum size of the parameter response, which in this case is
366 	 * UCHAR_MAX - the size of the headers/block descriptors.
367 	 */
368 	sip->si_cdblen = MODE_CMD_LEN_6;
369 	if ((result = scsi_mode_sense(sip, MODEPAGE_ALLPAGES, PC_CURRENT,
370 	    (caddr_t)allpages, UCHAR_MAX - sizeof (scsi_ms_header_t),
371 	    &headers, &skey, &asc, &ascq)) == 0) {
372 		/*
373 		 * Compute the data length of the page that contains all mode
374 		 * sense pages.  This is a bit tricky because the format of the
375 		 * response from the lun is:
376 		 *
377 		 * header: <length> <medium type byte> <dev specific byte>
378 		 *	   <block descriptor length>
379 		 *	   [<optional block descriptor>]
380 		 * data:   [<mode page data> <mode page data> ...]
381 		 *
382 		 * Since the length field in the header describes the length of
383 		 * the entire response.  This includes the header, but NOT
384 		 * the length field itself, which is 1 or 2 bytes depending on
385 		 * which mode sense type (6- or 10- byte) is being executed.
386 		 *
387 		 * So, the data length equals the length value in the header
388 		 * plus 1 (because the length byte was not included in the
389 		 * length count), minus [[the sum of the length of the header
390 		 * and the length of the block descriptor]].
391 		 */
392 		datalength = (smh->ms_header.length +
393 		    sizeof (smh->ms_header.length)) -
394 		    (sizeof (struct mode_header) +
395 		    smh->ms_header.bdesc_length);
396 	} else if (SCSI_INVALID_OPCODE(skey, asc, ascq)) {
397 		/*
398 		 * Fallback and try the 10-byte version of the command.
399 		 */
400 		sip->si_cdblen = MODE_CMD_LEN_10;
401 		result = scsi_mode_sense(sip, MODEPAGE_ALLPAGES,
402 		    PC_CURRENT, (caddr_t)allpages, allpages_buflen,
403 		    &headers, &skey, &asc, &ascq);
404 
405 		if (result == 0) {
406 			datalength = (BE_16(smh_g1->ms_header.length) +
407 			    sizeof (smh_g1->ms_header.length)) -
408 			    (sizeof (struct mode_header_g1) +
409 			    BE_16(smh_g1->ms_header.bdesc_length));
410 
411 		}
412 	}
413 
414 	if (result == 0 && datalength >= 0) {
415 		if (nvlist_add_int8(sip->si_dsp->ds_state, "command-length",
416 		    sip->si_cdblen == MODE_CMD_LEN_6 ? 6 : 10) != 0) {
417 			free(allpages);
418 			return (scsi_set_errno(sip, EDS_NOMEM));
419 		}
420 
421 		nvl = NULL;
422 		if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
423 		    nvlist_add_nvlist(sip->si_dsp->ds_state, "modepages",
424 		    nvl) != 0) {
425 			free(allpages);
426 			nvlist_free(nvl);
427 			return (scsi_set_errno(sip, EDS_NOMEM));
428 		}
429 
430 		nvlist_free(nvl);
431 		result = nvlist_lookup_nvlist(sip->si_dsp->ds_state,
432 		    "modepages", &sip->si_state_modepage);
433 		assert(result == 0);
434 
435 		/*
436 		 * One of the sets of the commands (above) succeeded, so now
437 		 * look for the mode pages we need and record them appropriately
438 		 */
439 		if (mode_page_present(allpages, datalength,
440 		    MODEPAGE_INFO_EXCPT)) {
441 
442 			nvl = NULL;
443 			if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
444 			    nvlist_add_nvlist(sip->si_state_modepage,
445 			    "informational-exceptions", nvl) != 0) {
446 				free(allpages);
447 				nvlist_free(nvl);
448 				return (scsi_set_errno(sip, EDS_NOMEM));
449 			}
450 			nvlist_free(nvl);
451 			sip->si_supp_mode |= MODEPAGE_SUPP_IEC;
452 			result = nvlist_lookup_nvlist(sip->si_state_modepage,
453 			    "informational-exceptions", &sip->si_state_iec);
454 			assert(result == 0);
455 		}
456 
457 	} else {
458 		/*
459 		 * If the device failed to respond to one of the basic commands,
460 		 * then assume it's not a SCSI device or otherwise doesn't
461 		 * support the necessary transport.
462 		 */
463 		if (datalength < 0)
464 			dprintf("command returned invalid data length (%d)\n",
465 			    datalength);
466 		else
467 			dprintf("failed to load modepages (KEY=0x%x "
468 			    "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
469 
470 		result = scsi_set_errno(sip, EDS_NO_TRANSPORT);
471 	}
472 
473 	free(allpages);
474 	return (result);
475 }
476 
477 /*
478  * Verify a single logpage.  This will do some generic validation and then call
479  * the logpage-specific function for further verification.
480  */
481 static int
482 verify_logpage(ds_scsi_info_t *sip, logpage_validation_entry_t *lp)
483 {
484 	scsi_log_header_t *lhp;
485 	struct scsi_extended_sense sense;
486 	int buflen;
487 	int log_length;
488 	int result = 0;
489 	uint_t kp, asc, ascq;
490 	nvlist_t *nvl;
491 
492 	buflen = MAX_BUFLEN(scsi_log_header_t);
493 	if ((lhp = calloc(buflen, 1)) == NULL)
494 		return (scsi_set_errno(sip, EDS_NOMEM));
495 	bzero(&sense, sizeof (struct scsi_extended_sense));
496 
497 	nvl = NULL;
498 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
499 	    nvlist_add_nvlist(sip->si_state_logpage, lp->ve_desc, nvl) != 0) {
500 		nvlist_free(nvl);
501 		free(lhp);
502 		return (scsi_set_errno(sip, EDS_NOMEM));
503 	}
504 	nvlist_free(nvl);
505 	result = nvlist_lookup_nvlist(sip->si_state_logpage, lp->ve_desc, &nvl);
506 	assert(result == 0);
507 
508 	result = scsi_log_sense(sip, lp->ve_code,
509 	    PC_CUMULATIVE, (caddr_t)lhp, buflen, &kp, &asc, &ascq);
510 
511 	if (result == 0) {
512 		log_length = BE_16(lhp->lh_length);
513 		if (nvlist_add_uint16(nvl, "length", log_length) != 0) {
514 			free(lhp);
515 			return (scsi_set_errno(sip, EDS_NOMEM));
516 		}
517 
518 		if (lp->ve_validate(sip, (scsi_log_parameter_header_t *)
519 		    (((char *)lhp) + sizeof (scsi_log_header_t)),
520 		    log_length, nvl) != 0) {
521 			free(lhp);
522 			return (-1);
523 		}
524 	} else {
525 		dprintf("failed to load %s log page (KEY=0x%x "
526 		    "ASC=0x%x ASCQ=0x%x)\n", lp->ve_desc, kp, asc, ascq);
527 	}
528 
529 	free(lhp);
530 	return (0);
531 }
532 
533 /*
534  * Load log pages and determine which pages are supported.
535  */
536 static int
537 load_logpages(ds_scsi_info_t *sip)
538 {
539 	int buflen;
540 	scsi_supported_log_pages_t *sp;
541 	struct scsi_extended_sense sense;
542 	int result;
543 	uint_t sk, asc, ascq;
544 	int i, j;
545 	nvlist_t *nvl;
546 
547 	buflen = MAX_BUFLEN(scsi_log_header_t);
548 	if ((sp = calloc(buflen, 1)) == NULL)
549 		return (scsi_set_errno(sip, EDS_NOMEM));
550 
551 	bzero(&sense, sizeof (struct scsi_extended_sense));
552 
553 	if ((result = scsi_log_sense(sip, LOGPAGE_SUPP_LIST,
554 	    PC_CUMULATIVE, (caddr_t)sp, buflen, &sk, &asc, &ascq)) == 0) {
555 		int pagecount = BE_16(sp->slp_hdr.lh_length);
556 
557 		for (i = 0; i < pagecount; i++) {
558 			for (j = 0; j < NLOG_VALIDATION; j++) {
559 				if (log_validation[j].ve_code ==
560 				    sp->slp_pages[i])
561 					sip->si_supp_log |=
562 					    log_validation[j].ve_supported;
563 			}
564 		}
565 	}
566 
567 	free(sp);
568 	if (result == 0) {
569 		nvl = NULL;
570 		if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
571 		    nvlist_add_nvlist(sip->si_dsp->ds_state, "logpages",
572 		    nvl) != 0) {
573 			nvlist_free(nvl);
574 			return (scsi_set_errno(sip, EDS_NOMEM));
575 		}
576 
577 		nvlist_free(nvl);
578 		result = nvlist_lookup_nvlist(sip->si_dsp->ds_state,
579 		    "logpages", &sip->si_state_logpage);
580 		assert(result == 0);
581 
582 		/*
583 		 * Validate the logpage contents.
584 		 */
585 		for (i = 0; i < NLOG_VALIDATION; i++) {
586 			if ((sip->si_supp_log &
587 			    log_validation[i].ve_supported) == 0)
588 				continue;
589 
590 			/*
591 			 * verify_logpage will clear the supported bit if
592 			 * verification fails.
593 			 */
594 			if (verify_logpage(sip, &log_validation[i]) != 0)
595 				return (-1);
596 		}
597 
598 	} else {
599 		dprintf("failed to get log pages "
600 		    "(KEY=0x%x ASC=0x%x ASCq=0x%x)\n", sk, asc, ascq);
601 	}
602 
603 	/*
604 	 * We always return 0 here, even if the required log pages aren't
605 	 * supported.
606 	 */
607 	return (0);
608 }
609 
610 /*
611  * Verify that the IE log page is sane.  This log page is potentially chock-full
612  * of vendor specific information that we do not know how to access.  All we can
613  * do is check for the generic predictive failure bit.  If this log page is not
614  * well-formed, then bail out.
615  */
616 static int
617 logpage_ie_verify(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
618     int log_length, nvlist_t *nvl)
619 {
620 	int i, plen = 0;
621 	boolean_t seen = B_FALSE;
622 	scsi_ie_log_param_t *iep =
623 	    (scsi_ie_log_param_t *)lphp;
624 
625 	for (i = 0; i < log_length; i += plen) {
626 		iep = (scsi_ie_log_param_t *)((char *)iep + plen);
627 
628 		if (BE_16(iep->ie_hdr.lph_param) == LOGPARAM_IE) {
629 			if (nvlist_add_boolean_value(nvl, "general",
630 			    B_TRUE) != 0)
631 				return (scsi_set_errno(sip, EDS_NOMEM));
632 
633 			if (lphp->lph_length < LOGPARAM_IE_MIN_LEN) {
634 				if (nvlist_add_uint8(nvl,
635 				    "invalid-length", lphp->lph_length) != 0)
636 					return (scsi_set_errno(sip, EDS_NOMEM));
637 			} else {
638 				seen = B_TRUE;
639 			}
640 			break;
641 		}
642 
643 		plen = iep->ie_hdr.lph_length +
644 		    sizeof (scsi_log_parameter_header_t);
645 	}
646 
647 	if (!seen) {
648 		sip->si_supp_log &= ~LOGPAGE_SUPP_IE;
649 		dprintf("IE logpage validation failed\n");
650 	}
651 
652 	return (0);
653 }
654 
655 /*
656  * Verify the contents of the temperature log page.  The temperature log page
657  * contains two log parameters: the current temperature, and (optionally) the
658  * reference temperature.  For the verification phase, we check that the two
659  * parameters we care about are well-formed.  If there is no reference
660  * temperature, then we cannot use the page for monitoring purposes.
661  */
662 static int
663 logpage_temp_verify(ds_scsi_info_t *sip,
664     scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl)
665 {
666 	int i, plen = 0;
667 	boolean_t has_reftemp = B_FALSE;
668 	boolean_t bad_length = B_FALSE;
669 	ushort_t param_code;
670 
671 	for (i = 0; i < log_length; i += plen) {
672 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
673 		param_code = BE_16(lphp->lph_param);
674 
675 		switch (param_code) {
676 		case LOGPARAM_TEMP_CURTEMP:
677 			if (nvlist_add_boolean_value(nvl, "current-temperature",
678 			    B_TRUE) != 0)
679 				return (scsi_set_errno(sip, EDS_NOMEM));
680 			if (lphp->lph_length != LOGPARAM_TEMP_LEN) {
681 				if (nvlist_add_uint8(nvl,
682 				    "invalid-length", lphp->lph_length) != 0)
683 					return (scsi_set_errno(sip, EDS_NOMEM));
684 				bad_length = B_TRUE;
685 			}
686 			break;
687 
688 		case LOGPARAM_TEMP_REFTEMP:
689 			if (nvlist_add_boolean_value(nvl,
690 			    "reference-temperature", B_TRUE) != 0)
691 				return (scsi_set_errno(sip, EDS_NOMEM));
692 			if (lphp->lph_length != LOGPARAM_TEMP_LEN) {
693 				if (nvlist_add_uint8(nvl,
694 				    "invalid-length", lphp->lph_length) != 0)
695 					return (scsi_set_errno(sip, EDS_NOMEM));
696 				bad_length = B_TRUE;
697 			}
698 			has_reftemp = B_TRUE;
699 			break;
700 		}
701 
702 		plen = lphp->lph_length +
703 		    sizeof (scsi_log_parameter_header_t);
704 	}
705 
706 	if (bad_length || !has_reftemp) {
707 		sip->si_supp_log &= ~LOGPAGE_SUPP_TEMP;
708 		dprintf("temperature logpage validation failed\n");
709 	}
710 
711 	return (0);
712 }
713 
714 /*
715  * Verify the contents of the self test log page.  The log supports a maximum of
716  * 20 entries, where each entry's parameter code is its index in the log.  We
717  * check that the parameter codes fall within this range, and that the size of
718  * each page is what we expect.  It's perfectly acceptable for there to be no
719  * entries in this log, so we must also be sure to validate the contents as part
720  * of the analysis phase.
721  */
722 static int
723 logpage_selftest_verify(ds_scsi_info_t *sip,
724     scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl)
725 {
726 	int i, plen = 0;
727 	boolean_t bad = B_FALSE;
728 	int entries = 0;
729 	ushort_t param_code;
730 
731 	for (i = 0; i < log_length; i += plen, entries++) {
732 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
733 		param_code = BE_16(lphp->lph_param);
734 
735 		if (param_code < LOGPAGE_SELFTEST_MIN_PARAM_CODE ||
736 		    param_code > LOGPAGE_SELFTEST_MAX_PARAM_CODE) {
737 			if (nvlist_add_uint16(nvl, "invalid-param-code",
738 			    param_code) != 0)
739 				return (scsi_set_errno(sip, EDS_NOMEM));
740 			bad = B_TRUE;
741 			break;
742 		}
743 
744 		if (lphp->lph_length != LOGPAGE_SELFTEST_PARAM_LEN) {
745 			if (nvlist_add_uint8(nvl, "invalid-length",
746 			    lphp->lph_length) != 0)
747 				return (scsi_set_errno(sip, EDS_NOMEM));
748 			bad = B_TRUE;
749 			break;
750 
751 		}
752 
753 		plen = lphp->lph_length +
754 		    sizeof (scsi_log_parameter_header_t);
755 	}
756 
757 	if (bad) {
758 		sip->si_supp_log &= ~LOGPAGE_SUPP_SELFTEST;
759 		dprintf("selftest logpage validation failed\n");
760 	}
761 
762 	return (0);
763 }
764 
765 /*
766  * Verify the contents of the Solid State Media (SSM) log page.
767  * As of SBC3r36 SSM log page contains one log parameter:
768  * "Percentage Used Endurance Indicator" which is mandatory.
769  * For the verification phase, we sanity check this parameter
770  * by making sure it's present and it's length is set to 0x04.
771  */
772 static int
773 logpage_ssm_verify(ds_scsi_info_t *sip,
774     scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl)
775 {
776 	ushort_t param_code;
777 	int i, plen = 0;
778 
779 	for (i = 0; i < log_length; i += plen) {
780 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
781 		param_code = BE_16(lphp->lph_param);
782 
783 		switch (param_code) {
784 		case LOGPARAM_PRCNT_USED:
785 			if (nvlist_add_boolean_value(nvl,
786 			    FM_EREPORT_SCSI_SSMWEAROUT, B_TRUE) != 0)
787 				return (scsi_set_errno(sip, EDS_NOMEM));
788 			if (lphp->lph_length != LOGPARAM_PRCNT_USED_PARAM_LEN) {
789 				if (nvlist_add_uint8(nvl,
790 				    "invalid-length", lphp->lph_length) != 0)
791 					return (scsi_set_errno(sip, EDS_NOMEM));
792 
793 				dprintf("solid state media logpage bad len\n");
794 				break;
795 			}
796 
797 			/* verification succeded */
798 			return (0);
799 		}
800 
801 		plen = lphp->lph_length +
802 		    sizeof (scsi_log_parameter_header_t);
803 	}
804 
805 	/* verification failed */
806 	sip->si_supp_log &= ~LOGPAGE_SUPP_SSM;
807 	return (0);
808 }
809 
810 /*
811  * Load the current IE mode pages
812  */
813 static int
814 load_ie_modepage(ds_scsi_info_t *sip)
815 {
816 	struct scsi_ms_hdrs junk_hdrs;
817 	int result;
818 	uint_t skey, asc, ascq;
819 
820 	if (!(sip->si_supp_mode & MODEPAGE_SUPP_IEC))
821 		return (0);
822 
823 	bzero(&sip->si_iec_current, sizeof (sip->si_iec_current));
824 	bzero(&sip->si_iec_changeable, sizeof (sip->si_iec_changeable));
825 
826 	if ((result = scsi_mode_sense(sip,
827 	    MODEPAGE_INFO_EXCPT, PC_CURRENT, &sip->si_iec_current,
828 	    MODEPAGE_INFO_EXCPT_LEN, &sip->si_hdrs, &skey, &asc,
829 	    &ascq)) == 0) {
830 		result = scsi_mode_sense(sip,
831 		    MODEPAGE_INFO_EXCPT, PC_CHANGEABLE,
832 		    &sip->si_iec_changeable,
833 		    MODEPAGE_INFO_EXCPT_LEN, &junk_hdrs, &skey, &asc, &ascq);
834 	}
835 
836 	if (result != 0) {
837 		dprintf("failed to get IEC modepage (KEY=0x%x "
838 		    "ASC=0x%x ASCQ=0x%x)", skey, asc, ascq);
839 		sip->si_supp_mode &= ~MODEPAGE_SUPP_IEC;
840 	} else  {
841 		if (nvlist_add_boolean_value(sip->si_state_iec,
842 		    "dexcpt", sip->si_iec_current.ie_dexcpt) != 0 ||
843 		    nvlist_add_boolean_value(sip->si_state_iec,
844 		    "logerr", sip->si_iec_current.ie_logerr) != 0 ||
845 		    nvlist_add_uint8(sip->si_state_iec,
846 		    "mrie", sip->si_iec_current.ie_mrie) != 0 ||
847 		    nvlist_add_boolean_value(sip->si_state_iec,
848 		    "test", sip->si_iec_current.ie_test) != 0 ||
849 		    nvlist_add_boolean_value(sip->si_state_iec,
850 		    "ewasc", sip->si_iec_current.ie_ewasc) != 0 ||
851 		    nvlist_add_boolean_value(sip->si_state_iec,
852 		    "perf", sip->si_iec_current.ie_perf) != 0 ||
853 		    nvlist_add_boolean_value(sip->si_state_iec,
854 		    "ebf", sip->si_iec_current.ie_ebf) != 0 ||
855 		    nvlist_add_uint32(sip->si_state_iec,
856 		    "interval-timer",
857 		    BE_32(sip->si_iec_current.ie_interval_timer)) != 0 ||
858 		    nvlist_add_uint32(sip->si_state_iec,
859 		    "report-count",
860 		    BE_32(sip->si_iec_current.ie_report_count)) != 0)
861 			return (scsi_set_errno(sip, EDS_NOMEM));
862 	}
863 
864 	return (0);
865 }
866 
867 /*
868  * Enable IE reporting.  We prefer the following settings:
869  *
870  * (1) DEXCPT = 0
871  * (3) MRIE = 6 (IE_REPORT_ON_REQUEST)
872  * (4) EWASC = 1
873  * (6) REPORT COUNT = 0x00000001
874  * (7) LOGERR = 1
875  *
876  * However, not all drives support changing these values, and the current state
877  * may be useful enough as-is.  For example, some drives support IE logging, but
878  * don't support changing the MRIE.  In this case, we can still use the
879  * information provided by the log page.
880  */
881 static int
882 scsi_enable_ie(ds_scsi_info_t *sip, boolean_t *changed)
883 {
884 	scsi_ie_page_t new_iec_page;
885 	scsi_ms_hdrs_t hdrs;
886 	uint_t skey, asc, ascq;
887 
888 	if (!(sip->si_supp_mode & MODEPAGE_SUPP_IEC))
889 		return (0);
890 
891 	bzero(&new_iec_page, sizeof (new_iec_page));
892 	bzero(&hdrs, sizeof (hdrs));
893 
894 	(void) memcpy(&new_iec_page, &sip->si_iec_current,
895 	    sizeof (new_iec_page));
896 
897 	if (IEC_IE_CHANGEABLE(sip->si_iec_changeable))
898 		new_iec_page.ie_dexcpt = 0;
899 
900 	if (IEC_MRIE_CHANGEABLE(sip->si_iec_changeable))
901 		new_iec_page.ie_mrie = IE_REPORT_ON_REQUEST;
902 
903 	/*
904 	 * We only want to enable warning reporting if we are able to change the
905 	 * mrie to report on request.  Otherwise, we risk unnecessarily
906 	 * interrupting normal SCSI commands with a CHECK CONDITION code.
907 	 */
908 	if (IEC_EWASC_CHANGEABLE(sip->si_iec_changeable)) {
909 		if (new_iec_page.ie_mrie == IE_REPORT_ON_REQUEST)
910 			new_iec_page.ie_ewasc = 1;
911 		else
912 			new_iec_page.ie_ewasc = 0;
913 	}
914 
915 	if (IEC_RPTCNT_CHANGEABLE(sip->si_iec_changeable))
916 		new_iec_page.ie_report_count = BE_32(1);
917 
918 	if (IEC_LOGERR_CHANGEABLE(sip->si_iec_changeable))
919 		new_iec_page.ie_logerr = 1;
920 
921 	/*
922 	 * Now compare the new mode page with the existing one.
923 	 * if there's no difference, there's no need for a mode select
924 	 */
925 	if (memcmp(&new_iec_page, &sip->si_iec_current,
926 	    MODEPAGE_INFO_EXCPT_LEN) == 0) {
927 		*changed = B_FALSE;
928 	} else {
929 		(void) memcpy(&hdrs, &sip->si_hdrs, sizeof (sip->si_hdrs));
930 
931 		if (scsi_mode_select(sip,
932 		    MODEPAGE_INFO_EXCPT, MODE_SELECT_PF, &new_iec_page,
933 		    MODEPAGE_INFO_EXCPT_LEN, &hdrs, &skey, &asc, &ascq) == 0) {
934 			*changed = B_TRUE;
935 		} else {
936 			dprintf("failed to enable IE (KEY=0x%x "
937 			    "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
938 			*changed = B_FALSE;
939 		}
940 	}
941 
942 	if (nvlist_add_boolean_value(sip->si_state_iec, "changed",
943 	    *changed) != 0)
944 		return (scsi_set_errno(sip, EDS_NOMEM));
945 
946 	return (0);
947 }
948 
949 /*
950  * Clear the GLTSD bit, indicating log pages should be saved to non-volatile
951  * storage.
952  */
953 static int
954 clear_gltsd(ds_scsi_info_t *sip)
955 {
956 	scsi_ms_hdrs_t hdrs, junk_hdrs;
957 	struct mode_control_scsi3 control_pg_cur, control_pg_chg;
958 	int result;
959 	uint_t skey, asc, ascq;
960 
961 	bzero(&hdrs, sizeof (hdrs));
962 	bzero(&control_pg_cur, sizeof (control_pg_cur));
963 	bzero(&control_pg_chg, sizeof (control_pg_chg));
964 
965 	result = scsi_mode_sense(sip,
966 	    MODEPAGE_CTRL_MODE, PC_CURRENT, &control_pg_cur,
967 	    MODEPAGE_CTRL_MODE_LEN, &hdrs, &skey, &asc, &ascq);
968 
969 	if (result != 0) {
970 		dprintf("failed to read Control mode page (KEY=0x%x "
971 		    "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
972 	} else if (control_pg_cur.mode_page.length !=
973 	    PAGELENGTH_MODE_CONTROL_SCSI3) {
974 		dprintf("SCSI-3 control mode page not supported\n");
975 	} else if ((result = scsi_mode_sense(sip,
976 	    MODEPAGE_CTRL_MODE, PC_CHANGEABLE, &control_pg_chg,
977 	    MODEPAGE_CTRL_MODE_LEN, &junk_hdrs, &skey, &asc, &ascq))
978 	    != 0) {
979 		dprintf("failed to read changeable Control mode page (KEY=0x%x "
980 		    "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
981 	} else if (control_pg_cur.gltsd && !GLTSD_CHANGEABLE(control_pg_chg)) {
982 		dprintf("gltsd is set and not changeable\n");
983 		if (nvlist_add_boolean_value(sip->si_dsp->ds_state,
984 		    "gltsd", control_pg_cur.gltsd) != 0)
985 			return (scsi_set_errno(sip, EDS_NOMEM));
986 	} else if (control_pg_cur.gltsd) {
987 		control_pg_cur.gltsd = 0;
988 		result = scsi_mode_select(sip,
989 		    MODEPAGE_CTRL_MODE, MODE_SELECT_PF, &control_pg_cur,
990 		    MODEPAGE_CTRL_MODE_LEN, &hdrs, &skey, &asc, &ascq);
991 		if (result != 0)
992 			dprintf("failed to enable GLTSD (KEY=0x%x "
993 			    "ASC=0x%x ASCQ=0x%x\n", skey, asc, ascq);
994 		if (nvlist_add_boolean_value(sip->si_dsp->ds_state,
995 		    "gltsd", control_pg_cur.gltsd) != 0)
996 			return (scsi_set_errno(sip, EDS_NOMEM));
997 	}
998 
999 	return (0);
1000 }
1001 
1002 /*
1003  * Fetch the contents of the logpage, and then call the logpage-specific
1004  * analysis function.  The analysis function is responsible for detecting any
1005  * faults and filling in the details.
1006  */
1007 static int
1008 analyze_one_logpage(ds_scsi_info_t *sip, logpage_validation_entry_t *entry)
1009 {
1010 	scsi_log_header_t *lhp;
1011 	scsi_log_parameter_header_t *lphp;
1012 	int buflen;
1013 	int log_length;
1014 	uint_t skey, asc, ascq;
1015 	int result;
1016 
1017 	buflen = MAX_BUFLEN(scsi_log_header_t);
1018 	if ((lhp = calloc(buflen, 1)) == NULL)
1019 		return (scsi_set_errno(sip, EDS_NOMEM));
1020 
1021 	result = scsi_log_sense(sip, entry->ve_code,
1022 	    PC_CUMULATIVE, (caddr_t)lhp, buflen, &skey, &asc, &ascq);
1023 
1024 	if (result == 0) {
1025 		log_length = BE_16(lhp->lh_length);
1026 		lphp = (scsi_log_parameter_header_t *)(((uchar_t *)lhp) +
1027 		    sizeof (scsi_log_header_t));
1028 
1029 		result = entry->ve_analyze(sip, lphp, log_length);
1030 	} else {
1031 		result = scsi_set_errno(sip, EDS_IO);
1032 	}
1033 
1034 	free(lhp);
1035 	return (result);
1036 }
1037 
1038 /*
1039  * Analyze the IE logpage.  If we find an IE log record with a non-zero 'asc',
1040  * then we have a fault.
1041  */
1042 static int
1043 logpage_ie_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
1044     int log_length)
1045 {
1046 	int i, plen = 0;
1047 	scsi_ie_log_param_t *iep = (scsi_ie_log_param_t *)lphp;
1048 	nvlist_t *nvl;
1049 
1050 	assert(sip->si_dsp->ds_predfail == NULL);
1051 	if (nvlist_alloc(&sip->si_dsp->ds_predfail, NV_UNIQUE_NAME, 0) != 0)
1052 		return (scsi_set_errno(sip, EDS_NOMEM));
1053 	nvl = sip->si_dsp->ds_predfail;
1054 
1055 	for (i = 0; i < log_length; i += plen) {
1056 		iep = (scsi_ie_log_param_t *)((char *)iep + plen);
1057 
1058 		/*
1059 		 * Even though we validated the length during the initial phase,
1060 		 * never trust the device.
1061 		 */
1062 		if (BE_16(iep->ie_hdr.lph_param) == LOGPARAM_IE &&
1063 		    iep->ie_hdr.lph_length >= LOGPARAM_IE_MIN_LEN) {
1064 			if (nvlist_add_uint8(nvl, FM_EREPORT_PAYLOAD_SCSI_ASC,
1065 			    iep->ie_asc) != 0 ||
1066 			    nvlist_add_uint8(nvl, FM_EREPORT_PAYLOAD_SCSI_ASCQ,
1067 			    iep->ie_ascq) != 0)
1068 				return (scsi_set_errno(sip, EDS_NOMEM));
1069 
1070 			if (iep->ie_asc != 0)
1071 				sip->si_dsp->ds_faults |=
1072 				    DS_FAULT_PREDFAIL;
1073 			break;
1074 		}
1075 		plen = iep->ie_hdr.lph_length +
1076 		    sizeof (scsi_log_parameter_header_t);
1077 	}
1078 
1079 	return (0);
1080 }
1081 
1082 static int
1083 logpage_temp_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
1084     int log_length)
1085 {
1086 	int i, plen = 0;
1087 	uint8_t reftemp, curtemp;
1088 	ushort_t param_code;
1089 	scsi_temp_log_param_t *temp;
1090 	nvlist_t *nvl;
1091 
1092 	assert(sip->si_dsp->ds_overtemp == NULL);
1093 	if (nvlist_alloc(&sip->si_dsp->ds_overtemp, NV_UNIQUE_NAME, 0) != 0)
1094 		return (scsi_set_errno(sip, EDS_NOMEM));
1095 	nvl = sip->si_dsp->ds_overtemp;
1096 
1097 	reftemp = curtemp = INVALID_TEMPERATURE;
1098 	for (i = 0; i < log_length; i += plen) {
1099 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
1100 		param_code = BE_16(lphp->lph_param);
1101 		temp = (scsi_temp_log_param_t *)lphp;
1102 
1103 		switch (param_code) {
1104 		case LOGPARAM_TEMP_CURTEMP:
1105 			if (lphp->lph_length != LOGPARAM_TEMP_LEN)
1106 				break;
1107 
1108 			if (nvlist_add_uint8(nvl,
1109 			    FM_EREPORT_PAYLOAD_SCSI_CURTEMP,
1110 			    temp->t_temp) != 0)
1111 				return (scsi_set_errno(sip, EDS_NOMEM));
1112 			curtemp = temp->t_temp;
1113 			break;
1114 
1115 		case LOGPARAM_TEMP_REFTEMP:
1116 			if (lphp->lph_length != LOGPARAM_TEMP_LEN)
1117 				break;
1118 
1119 			if (nvlist_add_uint8(nvl,
1120 			    FM_EREPORT_PAYLOAD_SCSI_THRESHTEMP,
1121 			    temp->t_temp) != 0)
1122 				return (scsi_set_errno(sip, EDS_NOMEM));
1123 			reftemp = temp->t_temp;
1124 			break;
1125 		}
1126 
1127 		plen = lphp->lph_length +
1128 		    sizeof (scsi_log_parameter_header_t);
1129 	}
1130 
1131 	if (reftemp != INVALID_TEMPERATURE && curtemp != INVALID_TEMPERATURE &&
1132 	    curtemp > reftemp)
1133 		sip->si_dsp->ds_faults |= DS_FAULT_OVERTEMP;
1134 
1135 	return (0);
1136 }
1137 
1138 static int
1139 logpage_selftest_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
1140     int log_length)
1141 {
1142 	int i, plen = 0;
1143 	int entries = 0;
1144 	ushort_t param_code;
1145 	scsi_selftest_log_param_t *stp;
1146 	nvlist_t *nvl;
1147 
1148 	assert(sip->si_dsp->ds_testfail == NULL);
1149 	if (nvlist_alloc(&sip->si_dsp->ds_testfail, NV_UNIQUE_NAME, 0) != 0)
1150 		return (scsi_set_errno(sip, EDS_NOMEM));
1151 	nvl = sip->si_dsp->ds_testfail;
1152 
1153 	for (i = 0; i < log_length; i += plen, entries++) {
1154 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
1155 		param_code = BE_16(lphp->lph_param);
1156 		stp = (scsi_selftest_log_param_t *)lphp;
1157 
1158 		if (param_code >= LOGPAGE_SELFTEST_MIN_PARAM_CODE &&
1159 		    param_code <= LOGPAGE_SELFTEST_MAX_PARAM_CODE &&
1160 		    lphp->lph_length >= LOGPAGE_SELFTEST_PARAM_LEN) {
1161 			/*
1162 			 * We always log the last result, or the result of the
1163 			 * last completed test.
1164 			 */
1165 			if ((param_code == 1 ||
1166 			    SELFTEST_COMPLETE(stp->st_results))) {
1167 				if (nvlist_add_uint8(nvl,
1168 				    FM_EREPORT_PAYLOAD_SCSI_RESULTCODE,
1169 				    stp->st_results) != 0 ||
1170 				    nvlist_add_uint16(nvl,
1171 				    FM_EREPORT_PAYLOAD_SCSI_TIMESTAMP,
1172 				    BE_16(stp->st_timestamp)) != 0 ||
1173 				    nvlist_add_uint8(nvl,
1174 				    FM_EREPORT_PAYLOAD_SCSI_SEGMENT,
1175 				    stp->st_number) != 0 ||
1176 				    nvlist_add_uint64(nvl,
1177 				    FM_EREPORT_PAYLOAD_SCSI_ADDRESS,
1178 				    BE_64(stp->st_lba)) != 0)
1179 					return (scsi_set_errno(sip,
1180 					    EDS_NOMEM));
1181 
1182 				if (SELFTEST_COMPLETE(stp->st_results)) {
1183 					if (stp->st_results != SELFTEST_OK)
1184 						sip->si_dsp->ds_faults |=
1185 						    DS_FAULT_TESTFAIL;
1186 					return (0);
1187 				}
1188 			}
1189 		}
1190 
1191 		plen = lphp->lph_length +
1192 		    sizeof (scsi_log_parameter_header_t);
1193 	}
1194 
1195 	return (0);
1196 }
1197 
1198 /*
1199  * Analyze the contents of the Solid State Media (SSM) log page's
1200  * "Percentage Used Endurance Indicator" log parameter.
1201  * We generate a fault if the percentage used is equal to or over
1202  * PRCNT_USED_FAULT_THRSH
1203  */
1204 static int
1205 logpage_ssm_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
1206     int log_length)
1207 {
1208 	uint16_t param_code;
1209 	scsi_ssm_log_param_t *ssm;
1210 	nvlist_t *nvl;
1211 	int i, plen = 0;
1212 
1213 	assert(sip->si_dsp->ds_ssmwearout == NULL);
1214 	if (nvlist_alloc(&sip->si_dsp->ds_ssmwearout, NV_UNIQUE_NAME, 0) != 0)
1215 		return (scsi_set_errno(sip, EDS_NOMEM));
1216 	nvl = sip->si_dsp->ds_ssmwearout;
1217 
1218 	for (i = 0; i < log_length; i += plen) {
1219 		lphp = (scsi_log_parameter_header_t *)((uint8_t *)lphp + plen);
1220 		param_code = BE_16(lphp->lph_param);
1221 		ssm = (scsi_ssm_log_param_t *)lphp;
1222 
1223 		switch (param_code) {
1224 		case LOGPARAM_PRCNT_USED:
1225 			if (lphp->lph_length != LOGPARAM_PRCNT_USED_PARAM_LEN)
1226 				break;
1227 
1228 			if ((nvlist_add_uint8(nvl,
1229 			    FM_EREPORT_PAYLOAD_SCSI_CURSSMWEAROUT,
1230 			    ssm->ssm_prcnt_used) != 0) ||
1231 			    (nvlist_add_uint8(nvl,
1232 			    FM_EREPORT_PAYLOAD_SCSI_THRSHSSMWEAROUT,
1233 			    PRCNT_USED_FAULT_THRSH) != 0))
1234 				return (scsi_set_errno(sip, EDS_NOMEM));
1235 
1236 			if (ssm->ssm_prcnt_used >= PRCNT_USED_FAULT_THRSH)
1237 				sip->si_dsp->ds_faults |= DS_FAULT_SSMWEAROUT;
1238 
1239 			return (0);
1240 		}
1241 
1242 		plen = lphp->lph_length +
1243 		    sizeof (scsi_log_parameter_header_t);
1244 	}
1245 
1246 	/*
1247 	 * If we got this far we didn't see LOGPARAM_PRCNT_USED
1248 	 * which is strange since we verified that it's there
1249 	 */
1250 	dprintf("solid state media logpage analyze failed\n");
1251 #if DEBUG
1252 	abort();
1253 #endif
1254 	return (scsi_set_errno(sip, EDS_NOT_SUPPORTED));
1255 }
1256 
1257 /*
1258  * Analyze the IE mode sense page explicitly.  This is only needed if the IE log
1259  * page is not supported.
1260  */
1261 static int
1262 analyze_ie_sense(ds_scsi_info_t *sip)
1263 {
1264 	uint_t skey, asc, ascq;
1265 	nvlist_t *nvl;
1266 
1267 	/*
1268 	 * Don't bother checking if we weren't able to set our MRIE correctly.
1269 	 */
1270 	if (sip->si_iec_current.ie_mrie != IE_REPORT_ON_REQUEST)
1271 		return (0);
1272 
1273 	if (scsi_request_sense(sip, &skey, &asc, &ascq) != 0) {
1274 		dprintf("failed to request IE page (KEY=0x%x ASC=0x%x "
1275 		    "ASCQ=0x%x)\n", skey, asc, ascq);
1276 		return (scsi_set_errno(sip, EDS_IO));
1277 	} else if (skey == KEY_NO_SENSE) {
1278 		assert(sip->si_dsp->ds_predfail == NULL);
1279 		if (nvlist_alloc(&sip->si_dsp->ds_predfail,
1280 		    NV_UNIQUE_NAME, 0) != 0)
1281 			return (scsi_set_errno(sip, EDS_NOMEM));
1282 		nvl = sip->si_dsp->ds_predfail;
1283 
1284 		if (nvlist_add_uint8(nvl,
1285 		    FM_EREPORT_PAYLOAD_SCSI_ASC, asc) != 0 ||
1286 		    nvlist_add_uint8(nvl,
1287 		    FM_EREPORT_PAYLOAD_SCSI_ASCQ, ascq) != 0) {
1288 			nvlist_free(nvl);
1289 			return (scsi_set_errno(sip, EDS_NOMEM));
1290 		}
1291 
1292 		if (asc != 0)
1293 			sip->si_dsp->ds_faults |= DS_FAULT_PREDFAIL;
1294 	}
1295 
1296 	return (0);
1297 }
1298 
1299 /*
1300  * Clean up the scsi-specific information structure.
1301  */
1302 static void
1303 ds_scsi_close(void *arg)
1304 {
1305 	ds_scsi_info_t *sip = arg;
1306 	if (sip->si_sim)
1307 		(void) dlclose(sip->si_sim);
1308 
1309 	free(sip);
1310 }
1311 
1312 /*
1313  * Initialize a single disk.  Initialization consists of:
1314  *
1315  * 1. Check to see if the IE mechanism is enabled via MODE SENSE for the IE
1316  *    Control page (page 0x1C).
1317  *
1318  * 2. If the IE page is available, try to set the following parameters:
1319  *
1320  *    	DEXCPT		0	Enable exceptions
1321  *    	MRIE		6	Only report IE information on request
1322  *    	EWASC		1	Enable warning reporting
1323  *    	REPORT COUNT	1	Only report an IE exception once
1324  *    	LOGERR		1	Enable logging of errors
1325  *
1326  *    The remaining fields are left as-is, preserving the current values.  If we
1327  *    cannot set some of these fields, then we do our best.  Some drives may
1328  *    have a static configuration which still allows for some monitoring.
1329  *
1330  * 3. Check to see if the IE log page (page 0x2F) is supported by issuing a
1331  *    LOG SENSE command.
1332  *
1333  * 4. Check to see if the self-test log page (page 0x10) is supported.
1334  *
1335  * 5. Check to see if the temperature log page (page 0x0D) is supported, and
1336  *    contains a reference temperature.
1337  *
1338  * 6. Clear the GLTSD bit in control mode page 0xA.  This will allow the drive
1339  *    to save each of the log pages described above to nonvolatile storage.
1340  *    This is essential if the drive is to remember its failures across
1341  *    loss of power.
1342  */
1343 static void *
1344 ds_scsi_open_common(disk_status_t *dsp, ds_scsi_info_t *sip)
1345 {
1346 	boolean_t changed;
1347 
1348 	sip->si_dsp = dsp;
1349 
1350 	/* Load and validate mode pages */
1351 	if (load_modepages(sip) != 0) {
1352 		ds_scsi_close(sip);
1353 		return (NULL);
1354 	}
1355 
1356 	/* Load and validate log pages */
1357 	if (load_logpages(sip) != 0) {
1358 		ds_scsi_close(sip);
1359 		return (NULL);
1360 	}
1361 
1362 	/* Load IE state */
1363 	if (load_ie_modepage(sip) != 0 ||
1364 	    scsi_enable_ie(sip, &changed) != 0 ||
1365 	    (changed && load_ie_modepage(sip) != 0)) {
1366 		ds_scsi_close(sip);
1367 		return (NULL);
1368 	}
1369 
1370 	/* Clear the GLTSD bit in the control page */
1371 	if (sip->si_supp_log != 0 && clear_gltsd(sip) != 0) {
1372 		ds_scsi_close(sip);
1373 		return (NULL);
1374 	}
1375 
1376 	return (sip);
1377 }
1378 
1379 static void *
1380 ds_scsi_open_uscsi(disk_status_t *dsp)
1381 {
1382 	ds_scsi_info_t *sip;
1383 
1384 	if ((sip = calloc(sizeof (ds_scsi_info_t), 1)) == NULL) {
1385 		(void) ds_set_errno(dsp, EDS_NOMEM);
1386 		return (NULL);
1387 	}
1388 
1389 	return (ds_scsi_open_common(dsp, sip));
1390 }
1391 
1392 static void *
1393 ds_scsi_open_sim(disk_status_t *dsp)
1394 {
1395 	ds_scsi_info_t *sip;
1396 
1397 	if ((sip = calloc(sizeof (ds_scsi_info_t), 1)) == NULL) {
1398 		(void) ds_set_errno(dsp, EDS_NOMEM);
1399 		return (NULL);
1400 	}
1401 
1402 	if ((sip->si_sim = dlopen(dsp->ds_path, RTLD_LAZY)) == NULL) {
1403 		(void) ds_set_errno(dsp, EDS_NO_TRANSPORT);
1404 		free(sip);
1405 		return (NULL);
1406 	}
1407 
1408 	return (ds_scsi_open_common(dsp, sip));
1409 }
1410 
1411 
1412 /*
1413  * Scan for any faults.  The following steps are performed:
1414  *
1415  * 1. If the temperature log page is supported, check the current temperature
1416  *    and threshold.  If the current temperature exceeds the threshold, report
1417  *    and overtemp fault.
1418  *
1419  * 2. If the selftest log page is supported, check to the last completed self
1420  *    test.  If the last completed test resulted in failure, report a selftest
1421  *    fault.
1422  *
1423  * 3. If the IE log page is supported, check to see if failure is predicted.  If
1424  *    so, indicate a predictive failure fault.
1425  *
1426  * 4. If the IE log page is not supported, but the mode page supports report on
1427  *    request mode, then issue a REQUEST SENSE for the mode page.  Indicate a
1428  *    predictive failure fault if necessary.
1429  */
1430 static int
1431 ds_scsi_scan(void *arg)
1432 {
1433 	ds_scsi_info_t *sip = arg;
1434 	int i;
1435 
1436 	for (i = 0; i < NLOG_VALIDATION; i++) {
1437 		if ((sip->si_supp_log & log_validation[i].ve_supported) == 0)
1438 			continue;
1439 
1440 		if (analyze_one_logpage(sip, &log_validation[i]) != 0)
1441 			return (-1);
1442 	}
1443 
1444 	if (!(sip->si_supp_log & LOGPAGE_SUPP_IE) &&
1445 	    (sip->si_supp_mode & MODEPAGE_SUPP_IEC) &&
1446 	    analyze_ie_sense(sip) != 0)
1447 		return (-1);
1448 
1449 	return (0);
1450 }
1451 
1452 ds_transport_t ds_scsi_uscsi_transport = {
1453 	ds_scsi_open_uscsi,
1454 	ds_scsi_close,
1455 	ds_scsi_scan
1456 };
1457 
1458 ds_transport_t ds_scsi_sim_transport = {
1459 	ds_scsi_open_sim,
1460 	ds_scsi_close,
1461 	ds_scsi_scan
1462 };
1463