xref: /illumos-gate/usr/src/lib/fm/libdiskstatus/common/ds_scsi.c (revision f6d7161b32abcd9432e9ae2bf4ca99c7a207558c)
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
scsi_translate_error(struct scsi_extended_sense * rq,uint_t * skeyp,uint_t * ascp,uint_t * ascqp)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
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)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
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)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
scsi_request_sense(ds_scsi_info_t * sip,uint_t * skp,uint_t * ascp,uint_t * ascqp)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
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)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
mode_page_present(uchar_t * pgdata,uint_t pgdatalen,uchar_t pagecode)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
load_modepages(ds_scsi_info_t * sip)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 			ds_dprintf(
465 			    "command returned invalid data length (%d)\n",
466 			    datalength);
467 		else
468 			ds_dprintf("failed to load modepages (KEY=0x%x "
469 			    "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
470 
471 		result = scsi_set_errno(sip, EDS_NO_TRANSPORT);
472 	}
473 
474 	free(allpages);
475 	return (result);
476 }
477 
478 /*
479  * Verify a single logpage.  This will do some generic validation and then call
480  * the logpage-specific function for further verification.
481  */
482 static int
verify_logpage(ds_scsi_info_t * sip,logpage_validation_entry_t * lp)483 verify_logpage(ds_scsi_info_t *sip, logpage_validation_entry_t *lp)
484 {
485 	scsi_log_header_t *lhp;
486 	struct scsi_extended_sense sense;
487 	int buflen;
488 	int log_length;
489 	int result = 0;
490 	uint_t kp, asc, ascq;
491 	nvlist_t *nvl;
492 
493 	buflen = MAX_BUFLEN(scsi_log_header_t);
494 	if ((lhp = calloc(buflen, 1)) == NULL)
495 		return (scsi_set_errno(sip, EDS_NOMEM));
496 	bzero(&sense, sizeof (struct scsi_extended_sense));
497 
498 	nvl = NULL;
499 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
500 	    nvlist_add_nvlist(sip->si_state_logpage, lp->ve_desc, nvl) != 0) {
501 		nvlist_free(nvl);
502 		free(lhp);
503 		return (scsi_set_errno(sip, EDS_NOMEM));
504 	}
505 	nvlist_free(nvl);
506 	result = nvlist_lookup_nvlist(sip->si_state_logpage, lp->ve_desc, &nvl);
507 	assert(result == 0);
508 
509 	result = scsi_log_sense(sip, lp->ve_code,
510 	    PC_CUMULATIVE, (caddr_t)lhp, buflen, &kp, &asc, &ascq);
511 
512 	if (result == 0) {
513 		log_length = BE_16(lhp->lh_length);
514 		if (nvlist_add_uint16(nvl, "length", log_length) != 0) {
515 			free(lhp);
516 			return (scsi_set_errno(sip, EDS_NOMEM));
517 		}
518 
519 		if (lp->ve_validate(sip, (scsi_log_parameter_header_t *)
520 		    (((char *)lhp) + sizeof (scsi_log_header_t)),
521 		    log_length, nvl) != 0) {
522 			free(lhp);
523 			return (-1);
524 		}
525 	} else {
526 		ds_dprintf("failed to load %s log page (KEY=0x%x "
527 		    "ASC=0x%x ASCQ=0x%x)\n", lp->ve_desc, kp, asc, ascq);
528 	}
529 
530 	free(lhp);
531 	return (0);
532 }
533 
534 /*
535  * Load log pages and determine which pages are supported.
536  */
537 static int
load_logpages(ds_scsi_info_t * sip)538 load_logpages(ds_scsi_info_t *sip)
539 {
540 	int buflen;
541 	scsi_supported_log_pages_t *sp;
542 	struct scsi_extended_sense sense;
543 	int result;
544 	uint_t sk, asc, ascq;
545 	int i, j;
546 	nvlist_t *nvl;
547 
548 	buflen = MAX_BUFLEN(scsi_log_header_t);
549 	if ((sp = calloc(buflen, 1)) == NULL)
550 		return (scsi_set_errno(sip, EDS_NOMEM));
551 
552 	bzero(&sense, sizeof (struct scsi_extended_sense));
553 
554 	if ((result = scsi_log_sense(sip, LOGPAGE_SUPP_LIST,
555 	    PC_CUMULATIVE, (caddr_t)sp, buflen, &sk, &asc, &ascq)) == 0) {
556 		int pagecount = BE_16(sp->slp_hdr.lh_length);
557 
558 		for (i = 0; i < pagecount; i++) {
559 			for (j = 0; j < NLOG_VALIDATION; j++) {
560 				if (log_validation[j].ve_code ==
561 				    sp->slp_pages[i])
562 					sip->si_supp_log |=
563 					    log_validation[j].ve_supported;
564 			}
565 		}
566 	}
567 
568 	free(sp);
569 	if (result == 0) {
570 		nvl = NULL;
571 		if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
572 		    nvlist_add_nvlist(sip->si_dsp->ds_state, "logpages",
573 		    nvl) != 0) {
574 			nvlist_free(nvl);
575 			return (scsi_set_errno(sip, EDS_NOMEM));
576 		}
577 
578 		nvlist_free(nvl);
579 		result = nvlist_lookup_nvlist(sip->si_dsp->ds_state,
580 		    "logpages", &sip->si_state_logpage);
581 		assert(result == 0);
582 
583 		/*
584 		 * Validate the logpage contents.
585 		 */
586 		for (i = 0; i < NLOG_VALIDATION; i++) {
587 			if ((sip->si_supp_log &
588 			    log_validation[i].ve_supported) == 0)
589 				continue;
590 
591 			/*
592 			 * verify_logpage will clear the supported bit if
593 			 * verification fails.
594 			 */
595 			if (verify_logpage(sip, &log_validation[i]) != 0)
596 				return (-1);
597 		}
598 
599 	} else {
600 		ds_dprintf("failed to get log pages "
601 		    "(KEY=0x%x ASC=0x%x ASCq=0x%x)\n", sk, asc, ascq);
602 	}
603 
604 	/*
605 	 * We always return 0 here, even if the required log pages aren't
606 	 * supported.
607 	 */
608 	return (0);
609 }
610 
611 /*
612  * Verify that the IE log page is sane.  This log page is potentially chock-full
613  * of vendor specific information that we do not know how to access.  All we can
614  * do is check for the generic predictive failure bit.  If this log page is not
615  * well-formed, then bail out.
616  */
617 static int
logpage_ie_verify(ds_scsi_info_t * sip,scsi_log_parameter_header_t * lphp,int log_length,nvlist_t * nvl)618 logpage_ie_verify(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
619     int log_length, nvlist_t *nvl)
620 {
621 	int i, plen = 0;
622 	boolean_t seen = B_FALSE;
623 	scsi_ie_log_param_t *iep =
624 	    (scsi_ie_log_param_t *)lphp;
625 
626 	for (i = 0; i < log_length; i += plen) {
627 		iep = (scsi_ie_log_param_t *)((char *)iep + plen);
628 
629 		if (BE_16(iep->ie_hdr.lph_param) == LOGPARAM_IE) {
630 			if (nvlist_add_boolean_value(nvl, "general",
631 			    B_TRUE) != 0)
632 				return (scsi_set_errno(sip, EDS_NOMEM));
633 
634 			if (lphp->lph_length < LOGPARAM_IE_MIN_LEN) {
635 				if (nvlist_add_uint8(nvl,
636 				    "invalid-length", lphp->lph_length) != 0)
637 					return (scsi_set_errno(sip, EDS_NOMEM));
638 			} else {
639 				seen = B_TRUE;
640 			}
641 			break;
642 		}
643 
644 		plen = iep->ie_hdr.lph_length +
645 		    sizeof (scsi_log_parameter_header_t);
646 	}
647 
648 	if (!seen) {
649 		sip->si_supp_log &= ~LOGPAGE_SUPP_IE;
650 		ds_dprintf("IE logpage validation failed\n");
651 	}
652 
653 	return (0);
654 }
655 
656 /*
657  * Verify the contents of the temperature log page.  The temperature log page
658  * contains two log parameters: the current temperature, and (optionally) the
659  * reference temperature.  For the verification phase, we check that the two
660  * parameters we care about are well-formed.  If there is no reference
661  * temperature, then we cannot use the page for monitoring purposes.
662  */
663 static int
logpage_temp_verify(ds_scsi_info_t * sip,scsi_log_parameter_header_t * lphp,int log_length,nvlist_t * nvl)664 logpage_temp_verify(ds_scsi_info_t *sip,
665     scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl)
666 {
667 	int i, plen = 0;
668 	boolean_t has_reftemp = B_FALSE;
669 	boolean_t bad_length = B_FALSE;
670 	ushort_t param_code;
671 
672 	for (i = 0; i < log_length; i += plen) {
673 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
674 		param_code = BE_16(lphp->lph_param);
675 
676 		switch (param_code) {
677 		case LOGPARAM_TEMP_CURTEMP:
678 			if (nvlist_add_boolean_value(nvl, "current-temperature",
679 			    B_TRUE) != 0)
680 				return (scsi_set_errno(sip, EDS_NOMEM));
681 			if (lphp->lph_length != LOGPARAM_TEMP_LEN) {
682 				if (nvlist_add_uint8(nvl,
683 				    "invalid-length", lphp->lph_length) != 0)
684 					return (scsi_set_errno(sip, EDS_NOMEM));
685 				bad_length = B_TRUE;
686 			}
687 			break;
688 
689 		case LOGPARAM_TEMP_REFTEMP:
690 			if (nvlist_add_boolean_value(nvl,
691 			    "reference-temperature", B_TRUE) != 0)
692 				return (scsi_set_errno(sip, EDS_NOMEM));
693 			if (lphp->lph_length != LOGPARAM_TEMP_LEN) {
694 				if (nvlist_add_uint8(nvl,
695 				    "invalid-length", lphp->lph_length) != 0)
696 					return (scsi_set_errno(sip, EDS_NOMEM));
697 				bad_length = B_TRUE;
698 			}
699 			has_reftemp = B_TRUE;
700 			break;
701 		}
702 
703 		plen = lphp->lph_length +
704 		    sizeof (scsi_log_parameter_header_t);
705 	}
706 
707 	if (bad_length || !has_reftemp) {
708 		sip->si_supp_log &= ~LOGPAGE_SUPP_TEMP;
709 		ds_dprintf("temperature logpage validation failed\n");
710 	}
711 
712 	return (0);
713 }
714 
715 /*
716  * Verify the contents of the self test log page.  The log supports a maximum of
717  * 20 entries, where each entry's parameter code is its index in the log.  We
718  * check that the parameter codes fall within this range, and that the size of
719  * each page is what we expect.  It's perfectly acceptable for there to be no
720  * entries in this log, so we must also be sure to validate the contents as part
721  * of the analysis phase.
722  */
723 static int
logpage_selftest_verify(ds_scsi_info_t * sip,scsi_log_parameter_header_t * lphp,int log_length,nvlist_t * nvl)724 logpage_selftest_verify(ds_scsi_info_t *sip,
725     scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl)
726 {
727 	int i, plen = 0;
728 	boolean_t bad = B_FALSE;
729 	int entries = 0;
730 	ushort_t param_code;
731 
732 	for (i = 0; i < log_length; i += plen, entries++) {
733 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
734 		param_code = BE_16(lphp->lph_param);
735 
736 		if (param_code < LOGPAGE_SELFTEST_MIN_PARAM_CODE ||
737 		    param_code > LOGPAGE_SELFTEST_MAX_PARAM_CODE) {
738 			if (nvlist_add_uint16(nvl, "invalid-param-code",
739 			    param_code) != 0)
740 				return (scsi_set_errno(sip, EDS_NOMEM));
741 			bad = B_TRUE;
742 			break;
743 		}
744 
745 		if (lphp->lph_length != LOGPAGE_SELFTEST_PARAM_LEN) {
746 			if (nvlist_add_uint8(nvl, "invalid-length",
747 			    lphp->lph_length) != 0)
748 				return (scsi_set_errno(sip, EDS_NOMEM));
749 			bad = B_TRUE;
750 			break;
751 
752 		}
753 
754 		plen = lphp->lph_length +
755 		    sizeof (scsi_log_parameter_header_t);
756 	}
757 
758 	if (bad) {
759 		sip->si_supp_log &= ~LOGPAGE_SUPP_SELFTEST;
760 		ds_dprintf("selftest logpage validation failed\n");
761 	}
762 
763 	return (0);
764 }
765 
766 /*
767  * Verify the contents of the Solid State Media (SSM) log page.
768  * As of SBC3r36 SSM log page contains one log parameter:
769  * "Percentage Used Endurance Indicator" which is mandatory.
770  * For the verification phase, we sanity check this parameter
771  * by making sure it's present and it's length is set to 0x04.
772  */
773 static int
logpage_ssm_verify(ds_scsi_info_t * sip,scsi_log_parameter_header_t * lphp,int log_length,nvlist_t * nvl)774 logpage_ssm_verify(ds_scsi_info_t *sip,
775     scsi_log_parameter_header_t *lphp, int log_length, nvlist_t *nvl)
776 {
777 	ushort_t param_code;
778 	int i, plen = 0;
779 
780 	for (i = 0; i < log_length; i += plen) {
781 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
782 		param_code = BE_16(lphp->lph_param);
783 
784 		switch (param_code) {
785 		case LOGPARAM_PRCNT_USED:
786 			if (nvlist_add_boolean_value(nvl,
787 			    FM_EREPORT_SCSI_SSMWEAROUT, B_TRUE) != 0)
788 				return (scsi_set_errno(sip, EDS_NOMEM));
789 			if (lphp->lph_length != LOGPARAM_PRCNT_USED_PARAM_LEN) {
790 				if (nvlist_add_uint8(nvl,
791 				    "invalid-length", lphp->lph_length) != 0)
792 					return (scsi_set_errno(sip, EDS_NOMEM));
793 
794 				ds_dprintf(
795 				    "solid state media logpage bad len\n");
796 				break;
797 			}
798 
799 			/* verification succeded */
800 			return (0);
801 		}
802 
803 		plen = lphp->lph_length +
804 		    sizeof (scsi_log_parameter_header_t);
805 	}
806 
807 	/* verification failed */
808 	sip->si_supp_log &= ~LOGPAGE_SUPP_SSM;
809 	return (0);
810 }
811 
812 /*
813  * Load the current IE mode pages
814  */
815 static int
load_ie_modepage(ds_scsi_info_t * sip)816 load_ie_modepage(ds_scsi_info_t *sip)
817 {
818 	struct scsi_ms_hdrs junk_hdrs;
819 	int result;
820 	uint_t skey, asc, ascq;
821 
822 	if (!(sip->si_supp_mode & MODEPAGE_SUPP_IEC))
823 		return (0);
824 
825 	bzero(&sip->si_iec_current, sizeof (sip->si_iec_current));
826 	bzero(&sip->si_iec_changeable, sizeof (sip->si_iec_changeable));
827 
828 	if ((result = scsi_mode_sense(sip,
829 	    MODEPAGE_INFO_EXCPT, PC_CURRENT, &sip->si_iec_current,
830 	    MODEPAGE_INFO_EXCPT_LEN, &sip->si_hdrs, &skey, &asc,
831 	    &ascq)) == 0) {
832 		result = scsi_mode_sense(sip,
833 		    MODEPAGE_INFO_EXCPT, PC_CHANGEABLE,
834 		    &sip->si_iec_changeable,
835 		    MODEPAGE_INFO_EXCPT_LEN, &junk_hdrs, &skey, &asc, &ascq);
836 	}
837 
838 	if (result != 0) {
839 		ds_dprintf("failed to get IEC modepage (KEY=0x%x "
840 		    "ASC=0x%x ASCQ=0x%x)", skey, asc, ascq);
841 		sip->si_supp_mode &= ~MODEPAGE_SUPP_IEC;
842 	} else  {
843 		if (nvlist_add_boolean_value(sip->si_state_iec,
844 		    "dexcpt", sip->si_iec_current.ie_dexcpt) != 0 ||
845 		    nvlist_add_boolean_value(sip->si_state_iec,
846 		    "logerr", sip->si_iec_current.ie_logerr) != 0 ||
847 		    nvlist_add_uint8(sip->si_state_iec,
848 		    "mrie", sip->si_iec_current.ie_mrie) != 0 ||
849 		    nvlist_add_boolean_value(sip->si_state_iec,
850 		    "test", sip->si_iec_current.ie_test) != 0 ||
851 		    nvlist_add_boolean_value(sip->si_state_iec,
852 		    "ewasc", sip->si_iec_current.ie_ewasc) != 0 ||
853 		    nvlist_add_boolean_value(sip->si_state_iec,
854 		    "perf", sip->si_iec_current.ie_perf) != 0 ||
855 		    nvlist_add_boolean_value(sip->si_state_iec,
856 		    "ebf", sip->si_iec_current.ie_ebf) != 0 ||
857 		    nvlist_add_uint32(sip->si_state_iec,
858 		    "interval-timer",
859 		    BE_32(sip->si_iec_current.ie_interval_timer)) != 0 ||
860 		    nvlist_add_uint32(sip->si_state_iec,
861 		    "report-count",
862 		    BE_32(sip->si_iec_current.ie_report_count)) != 0)
863 			return (scsi_set_errno(sip, EDS_NOMEM));
864 	}
865 
866 	return (0);
867 }
868 
869 /*
870  * Enable IE reporting.  We prefer the following settings:
871  *
872  * (1) DEXCPT = 0
873  * (3) MRIE = 6 (IE_REPORT_ON_REQUEST)
874  * (4) EWASC = 1
875  * (6) REPORT COUNT = 0x00000001
876  * (7) LOGERR = 1
877  *
878  * However, not all drives support changing these values, and the current state
879  * may be useful enough as-is.  For example, some drives support IE logging, but
880  * don't support changing the MRIE.  In this case, we can still use the
881  * information provided by the log page.
882  */
883 static int
scsi_enable_ie(ds_scsi_info_t * sip,boolean_t * changed)884 scsi_enable_ie(ds_scsi_info_t *sip, boolean_t *changed)
885 {
886 	scsi_ie_page_t new_iec_page;
887 	scsi_ms_hdrs_t hdrs;
888 	uint_t skey, asc, ascq;
889 
890 	if (!(sip->si_supp_mode & MODEPAGE_SUPP_IEC))
891 		return (0);
892 
893 	bzero(&new_iec_page, sizeof (new_iec_page));
894 	bzero(&hdrs, sizeof (hdrs));
895 
896 	(void) memcpy(&new_iec_page, &sip->si_iec_current,
897 	    sizeof (new_iec_page));
898 
899 	if (IEC_IE_CHANGEABLE(sip->si_iec_changeable))
900 		new_iec_page.ie_dexcpt = 0;
901 
902 	if (IEC_MRIE_CHANGEABLE(sip->si_iec_changeable))
903 		new_iec_page.ie_mrie = IE_REPORT_ON_REQUEST;
904 
905 	/*
906 	 * We only want to enable warning reporting if we are able to change the
907 	 * mrie to report on request.  Otherwise, we risk unnecessarily
908 	 * interrupting normal SCSI commands with a CHECK CONDITION code.
909 	 */
910 	if (IEC_EWASC_CHANGEABLE(sip->si_iec_changeable)) {
911 		if (new_iec_page.ie_mrie == IE_REPORT_ON_REQUEST)
912 			new_iec_page.ie_ewasc = 1;
913 		else
914 			new_iec_page.ie_ewasc = 0;
915 	}
916 
917 	if (IEC_RPTCNT_CHANGEABLE(sip->si_iec_changeable))
918 		new_iec_page.ie_report_count = BE_32(1);
919 
920 	if (IEC_LOGERR_CHANGEABLE(sip->si_iec_changeable))
921 		new_iec_page.ie_logerr = 1;
922 
923 	/*
924 	 * Now compare the new mode page with the existing one.
925 	 * if there's no difference, there's no need for a mode select
926 	 */
927 	if (memcmp(&new_iec_page, &sip->si_iec_current,
928 	    MODEPAGE_INFO_EXCPT_LEN) == 0) {
929 		*changed = B_FALSE;
930 	} else {
931 		(void) memcpy(&hdrs, &sip->si_hdrs, sizeof (sip->si_hdrs));
932 
933 		if (scsi_mode_select(sip,
934 		    MODEPAGE_INFO_EXCPT, MODE_SELECT_PF, &new_iec_page,
935 		    MODEPAGE_INFO_EXCPT_LEN, &hdrs, &skey, &asc, &ascq) == 0) {
936 			*changed = B_TRUE;
937 		} else {
938 			ds_dprintf("failed to enable IE (KEY=0x%x "
939 			    "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
940 			*changed = B_FALSE;
941 		}
942 	}
943 
944 	if (nvlist_add_boolean_value(sip->si_state_iec, "changed",
945 	    *changed) != 0)
946 		return (scsi_set_errno(sip, EDS_NOMEM));
947 
948 	return (0);
949 }
950 
951 /*
952  * Clear the GLTSD bit, indicating log pages should be saved to non-volatile
953  * storage.
954  */
955 static int
clear_gltsd(ds_scsi_info_t * sip)956 clear_gltsd(ds_scsi_info_t *sip)
957 {
958 	scsi_ms_hdrs_t hdrs, junk_hdrs;
959 	struct mode_control_scsi3 control_pg_cur, control_pg_chg;
960 	int result;
961 	uint_t skey, asc, ascq;
962 
963 	bzero(&hdrs, sizeof (hdrs));
964 	bzero(&control_pg_cur, sizeof (control_pg_cur));
965 	bzero(&control_pg_chg, sizeof (control_pg_chg));
966 
967 	result = scsi_mode_sense(sip,
968 	    MODEPAGE_CTRL_MODE, PC_CURRENT, &control_pg_cur,
969 	    MODEPAGE_CTRL_MODE_LEN, &hdrs, &skey, &asc, &ascq);
970 
971 	if (result != 0) {
972 		ds_dprintf("failed to read Control mode page (KEY=0x%x "
973 		    "ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
974 	} else if (control_pg_cur.mode_page.length !=
975 	    PAGELENGTH_MODE_CONTROL_SCSI3) {
976 		ds_dprintf("SCSI-3 control mode page not supported\n");
977 	} else if ((result = scsi_mode_sense(sip,
978 	    MODEPAGE_CTRL_MODE, PC_CHANGEABLE, &control_pg_chg,
979 	    MODEPAGE_CTRL_MODE_LEN, &junk_hdrs, &skey, &asc, &ascq))
980 	    != 0) {
981 		ds_dprintf("failed to read changeable Control mode page "
982 		    "(KEY=0x%x ASC=0x%x ASCQ=0x%x)\n", skey, asc, ascq);
983 	} else if (control_pg_cur.gltsd && !GLTSD_CHANGEABLE(control_pg_chg)) {
984 		ds_dprintf("gltsd is set and not changeable\n");
985 		if (nvlist_add_boolean_value(sip->si_dsp->ds_state,
986 		    "gltsd", control_pg_cur.gltsd) != 0)
987 			return (scsi_set_errno(sip, EDS_NOMEM));
988 	} else if (control_pg_cur.gltsd) {
989 		control_pg_cur.gltsd = 0;
990 		result = scsi_mode_select(sip,
991 		    MODEPAGE_CTRL_MODE, MODE_SELECT_PF, &control_pg_cur,
992 		    MODEPAGE_CTRL_MODE_LEN, &hdrs, &skey, &asc, &ascq);
993 		if (result != 0)
994 			ds_dprintf("failed to enable GLTSD (KEY=0x%x "
995 			    "ASC=0x%x ASCQ=0x%x\n", skey, asc, ascq);
996 		if (nvlist_add_boolean_value(sip->si_dsp->ds_state,
997 		    "gltsd", control_pg_cur.gltsd) != 0)
998 			return (scsi_set_errno(sip, EDS_NOMEM));
999 	}
1000 
1001 	return (0);
1002 }
1003 
1004 /*
1005  * Fetch the contents of the logpage, and then call the logpage-specific
1006  * analysis function.  The analysis function is responsible for detecting any
1007  * faults and filling in the details.
1008  */
1009 static int
analyze_one_logpage(ds_scsi_info_t * sip,logpage_validation_entry_t * entry)1010 analyze_one_logpage(ds_scsi_info_t *sip, logpage_validation_entry_t *entry)
1011 {
1012 	scsi_log_header_t *lhp;
1013 	scsi_log_parameter_header_t *lphp;
1014 	int buflen;
1015 	int log_length;
1016 	uint_t skey, asc, ascq;
1017 	int result;
1018 
1019 	buflen = MAX_BUFLEN(scsi_log_header_t);
1020 	if ((lhp = calloc(buflen, 1)) == NULL)
1021 		return (scsi_set_errno(sip, EDS_NOMEM));
1022 
1023 	result = scsi_log_sense(sip, entry->ve_code,
1024 	    PC_CUMULATIVE, (caddr_t)lhp, buflen, &skey, &asc, &ascq);
1025 
1026 	if (result == 0) {
1027 		log_length = BE_16(lhp->lh_length);
1028 		lphp = (scsi_log_parameter_header_t *)(((uchar_t *)lhp) +
1029 		    sizeof (scsi_log_header_t));
1030 
1031 		result = entry->ve_analyze(sip, lphp, log_length);
1032 	} else {
1033 		result = scsi_set_errno(sip, EDS_IO);
1034 	}
1035 
1036 	free(lhp);
1037 	return (result);
1038 }
1039 
1040 /*
1041  * Analyze the IE logpage.  If we find an IE log record with a non-zero 'asc',
1042  * then we have a fault.
1043  */
1044 static int
logpage_ie_analyze(ds_scsi_info_t * sip,scsi_log_parameter_header_t * lphp,int log_length)1045 logpage_ie_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
1046     int log_length)
1047 {
1048 	int i, plen = 0;
1049 	scsi_ie_log_param_t *iep = (scsi_ie_log_param_t *)lphp;
1050 	nvlist_t *nvl;
1051 
1052 	assert(sip->si_dsp->ds_predfail == NULL);
1053 	if (nvlist_alloc(&sip->si_dsp->ds_predfail, NV_UNIQUE_NAME, 0) != 0)
1054 		return (scsi_set_errno(sip, EDS_NOMEM));
1055 	nvl = sip->si_dsp->ds_predfail;
1056 
1057 	for (i = 0; i < log_length; i += plen) {
1058 		iep = (scsi_ie_log_param_t *)((char *)iep + plen);
1059 
1060 		/*
1061 		 * Even though we validated the length during the initial phase,
1062 		 * never trust the device.
1063 		 */
1064 		if (BE_16(iep->ie_hdr.lph_param) == LOGPARAM_IE &&
1065 		    iep->ie_hdr.lph_length >= LOGPARAM_IE_MIN_LEN) {
1066 			if (nvlist_add_uint8(nvl, FM_EREPORT_PAYLOAD_SCSI_ASC,
1067 			    iep->ie_asc) != 0 ||
1068 			    nvlist_add_uint8(nvl, FM_EREPORT_PAYLOAD_SCSI_ASCQ,
1069 			    iep->ie_ascq) != 0)
1070 				return (scsi_set_errno(sip, EDS_NOMEM));
1071 
1072 			if (iep->ie_asc != 0)
1073 				sip->si_dsp->ds_faults |=
1074 				    DS_FAULT_PREDFAIL;
1075 			break;
1076 		}
1077 		plen = iep->ie_hdr.lph_length +
1078 		    sizeof (scsi_log_parameter_header_t);
1079 	}
1080 
1081 	return (0);
1082 }
1083 
1084 static int
logpage_temp_analyze(ds_scsi_info_t * sip,scsi_log_parameter_header_t * lphp,int log_length)1085 logpage_temp_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
1086     int log_length)
1087 {
1088 	int i, plen = 0;
1089 	uint8_t reftemp, curtemp;
1090 	ushort_t param_code;
1091 	scsi_temp_log_param_t *temp;
1092 	nvlist_t *nvl;
1093 
1094 	assert(sip->si_dsp->ds_overtemp == NULL);
1095 	if (nvlist_alloc(&sip->si_dsp->ds_overtemp, NV_UNIQUE_NAME, 0) != 0)
1096 		return (scsi_set_errno(sip, EDS_NOMEM));
1097 	nvl = sip->si_dsp->ds_overtemp;
1098 
1099 	reftemp = curtemp = INVALID_TEMPERATURE;
1100 	for (i = 0; i < log_length; i += plen) {
1101 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
1102 		param_code = BE_16(lphp->lph_param);
1103 		temp = (scsi_temp_log_param_t *)lphp;
1104 
1105 		switch (param_code) {
1106 		case LOGPARAM_TEMP_CURTEMP:
1107 			if (lphp->lph_length != LOGPARAM_TEMP_LEN)
1108 				break;
1109 
1110 			if (nvlist_add_uint8(nvl,
1111 			    FM_EREPORT_PAYLOAD_SCSI_CURTEMP,
1112 			    temp->t_temp) != 0)
1113 				return (scsi_set_errno(sip, EDS_NOMEM));
1114 			curtemp = temp->t_temp;
1115 			break;
1116 
1117 		case LOGPARAM_TEMP_REFTEMP:
1118 			if (lphp->lph_length != LOGPARAM_TEMP_LEN)
1119 				break;
1120 
1121 			if (nvlist_add_uint8(nvl,
1122 			    FM_EREPORT_PAYLOAD_SCSI_THRESHTEMP,
1123 			    temp->t_temp) != 0)
1124 				return (scsi_set_errno(sip, EDS_NOMEM));
1125 			reftemp = temp->t_temp;
1126 			break;
1127 		}
1128 
1129 		plen = lphp->lph_length +
1130 		    sizeof (scsi_log_parameter_header_t);
1131 	}
1132 
1133 	if (reftemp != INVALID_TEMPERATURE && curtemp != INVALID_TEMPERATURE &&
1134 	    curtemp > reftemp)
1135 		sip->si_dsp->ds_faults |= DS_FAULT_OVERTEMP;
1136 
1137 	return (0);
1138 }
1139 
1140 static int
logpage_selftest_analyze(ds_scsi_info_t * sip,scsi_log_parameter_header_t * lphp,int log_length)1141 logpage_selftest_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
1142     int log_length)
1143 {
1144 	int i, plen = 0;
1145 	int entries = 0;
1146 	ushort_t param_code;
1147 	scsi_selftest_log_param_t *stp;
1148 	nvlist_t *nvl;
1149 
1150 	assert(sip->si_dsp->ds_testfail == NULL);
1151 	if (nvlist_alloc(&sip->si_dsp->ds_testfail, NV_UNIQUE_NAME, 0) != 0)
1152 		return (scsi_set_errno(sip, EDS_NOMEM));
1153 	nvl = sip->si_dsp->ds_testfail;
1154 
1155 	for (i = 0; i < log_length; i += plen, entries++) {
1156 		lphp = (scsi_log_parameter_header_t *)((char *)lphp + plen);
1157 		param_code = BE_16(lphp->lph_param);
1158 		stp = (scsi_selftest_log_param_t *)lphp;
1159 
1160 		if (param_code >= LOGPAGE_SELFTEST_MIN_PARAM_CODE &&
1161 		    param_code <= LOGPAGE_SELFTEST_MAX_PARAM_CODE &&
1162 		    lphp->lph_length >= LOGPAGE_SELFTEST_PARAM_LEN) {
1163 			/*
1164 			 * We always log the last result, or the result of the
1165 			 * last completed test.
1166 			 */
1167 			if ((param_code == 1 ||
1168 			    SELFTEST_COMPLETE(stp->st_results))) {
1169 				if (nvlist_add_uint8(nvl,
1170 				    FM_EREPORT_PAYLOAD_SCSI_RESULTCODE,
1171 				    stp->st_results) != 0 ||
1172 				    nvlist_add_uint16(nvl,
1173 				    FM_EREPORT_PAYLOAD_SCSI_TIMESTAMP,
1174 				    BE_16(stp->st_timestamp)) != 0 ||
1175 				    nvlist_add_uint8(nvl,
1176 				    FM_EREPORT_PAYLOAD_SCSI_SEGMENT,
1177 				    stp->st_number) != 0 ||
1178 				    nvlist_add_uint64(nvl,
1179 				    FM_EREPORT_PAYLOAD_SCSI_ADDRESS,
1180 				    BE_64(stp->st_lba)) != 0)
1181 					return (scsi_set_errno(sip,
1182 					    EDS_NOMEM));
1183 
1184 				if (SELFTEST_COMPLETE(stp->st_results)) {
1185 					if (stp->st_results != SELFTEST_OK)
1186 						sip->si_dsp->ds_faults |=
1187 						    DS_FAULT_TESTFAIL;
1188 					return (0);
1189 				}
1190 			}
1191 		}
1192 
1193 		plen = lphp->lph_length +
1194 		    sizeof (scsi_log_parameter_header_t);
1195 	}
1196 
1197 	return (0);
1198 }
1199 
1200 /*
1201  * Analyze the contents of the Solid State Media (SSM) log page's
1202  * "Percentage Used Endurance Indicator" log parameter.
1203  * We generate a fault if the percentage used is equal to or over
1204  * PRCNT_USED_FAULT_THRSH
1205  */
1206 static int
logpage_ssm_analyze(ds_scsi_info_t * sip,scsi_log_parameter_header_t * lphp,int log_length)1207 logpage_ssm_analyze(ds_scsi_info_t *sip, scsi_log_parameter_header_t *lphp,
1208     int log_length)
1209 {
1210 	uint16_t param_code;
1211 	scsi_ssm_log_param_t *ssm;
1212 	nvlist_t *nvl;
1213 	int i, plen = 0;
1214 
1215 	assert(sip->si_dsp->ds_ssmwearout == NULL);
1216 	if (nvlist_alloc(&sip->si_dsp->ds_ssmwearout, NV_UNIQUE_NAME, 0) != 0)
1217 		return (scsi_set_errno(sip, EDS_NOMEM));
1218 	nvl = sip->si_dsp->ds_ssmwearout;
1219 
1220 	for (i = 0; i < log_length; i += plen) {
1221 		lphp = (scsi_log_parameter_header_t *)((uint8_t *)lphp + plen);
1222 		param_code = BE_16(lphp->lph_param);
1223 		ssm = (scsi_ssm_log_param_t *)lphp;
1224 
1225 		switch (param_code) {
1226 		case LOGPARAM_PRCNT_USED:
1227 			if (lphp->lph_length != LOGPARAM_PRCNT_USED_PARAM_LEN)
1228 				break;
1229 
1230 			if ((nvlist_add_uint8(nvl,
1231 			    FM_EREPORT_PAYLOAD_SCSI_CURSSMWEAROUT,
1232 			    ssm->ssm_prcnt_used) != 0) ||
1233 			    (nvlist_add_uint8(nvl,
1234 			    FM_EREPORT_PAYLOAD_SCSI_THRSHSSMWEAROUT,
1235 			    PRCNT_USED_FAULT_THRSH) != 0))
1236 				return (scsi_set_errno(sip, EDS_NOMEM));
1237 
1238 			if (ssm->ssm_prcnt_used >= PRCNT_USED_FAULT_THRSH)
1239 				sip->si_dsp->ds_faults |= DS_FAULT_SSMWEAROUT;
1240 
1241 			return (0);
1242 		}
1243 
1244 		plen = lphp->lph_length +
1245 		    sizeof (scsi_log_parameter_header_t);
1246 	}
1247 
1248 	/*
1249 	 * If we got this far we didn't see LOGPARAM_PRCNT_USED
1250 	 * which is strange since we verified that it's there
1251 	 */
1252 	ds_dprintf("solid state media logpage analyze failed\n");
1253 #if DEBUG
1254 	abort();
1255 #endif
1256 	return (scsi_set_errno(sip, EDS_NOT_SUPPORTED));
1257 }
1258 
1259 /*
1260  * Analyze the IE mode sense page explicitly.  This is only needed if the IE log
1261  * page is not supported.
1262  */
1263 static int
analyze_ie_sense(ds_scsi_info_t * sip)1264 analyze_ie_sense(ds_scsi_info_t *sip)
1265 {
1266 	uint_t skey, asc, ascq;
1267 	nvlist_t *nvl;
1268 
1269 	/*
1270 	 * Don't bother checking if we weren't able to set our MRIE correctly.
1271 	 */
1272 	if (sip->si_iec_current.ie_mrie != IE_REPORT_ON_REQUEST)
1273 		return (0);
1274 
1275 	if (scsi_request_sense(sip, &skey, &asc, &ascq) != 0) {
1276 		ds_dprintf("failed to request IE page (KEY=0x%x ASC=0x%x "
1277 		    "ASCQ=0x%x)\n", skey, asc, ascq);
1278 		return (scsi_set_errno(sip, EDS_IO));
1279 	} else if (skey == KEY_NO_SENSE) {
1280 		assert(sip->si_dsp->ds_predfail == NULL);
1281 		if (nvlist_alloc(&sip->si_dsp->ds_predfail,
1282 		    NV_UNIQUE_NAME, 0) != 0)
1283 			return (scsi_set_errno(sip, EDS_NOMEM));
1284 		nvl = sip->si_dsp->ds_predfail;
1285 
1286 		if (nvlist_add_uint8(nvl,
1287 		    FM_EREPORT_PAYLOAD_SCSI_ASC, asc) != 0 ||
1288 		    nvlist_add_uint8(nvl,
1289 		    FM_EREPORT_PAYLOAD_SCSI_ASCQ, ascq) != 0) {
1290 			nvlist_free(nvl);
1291 			return (scsi_set_errno(sip, EDS_NOMEM));
1292 		}
1293 
1294 		if (asc != 0)
1295 			sip->si_dsp->ds_faults |= DS_FAULT_PREDFAIL;
1296 	}
1297 
1298 	return (0);
1299 }
1300 
1301 /*
1302  * Clean up the scsi-specific information structure.
1303  */
1304 static void
ds_scsi_close(void * arg)1305 ds_scsi_close(void *arg)
1306 {
1307 	ds_scsi_info_t *sip = arg;
1308 	if (sip->si_sim)
1309 		(void) dlclose(sip->si_sim);
1310 
1311 	free(sip);
1312 }
1313 
1314 /*
1315  * Initialize a single disk.  Initialization consists of:
1316  *
1317  * 1. Check to see if the IE mechanism is enabled via MODE SENSE for the IE
1318  *    Control page (page 0x1C).
1319  *
1320  * 2. If the IE page is available, try to set the following parameters:
1321  *
1322  *	DEXCPT		0	Enable exceptions
1323  *	MRIE		6	Only report IE information on request
1324  *	EWASC		1	Enable warning reporting
1325  *	REPORT COUNT	1	Only report an IE exception once
1326  *	LOGERR		1	Enable logging of errors
1327  *
1328  *    The remaining fields are left as-is, preserving the current values.  If we
1329  *    cannot set some of these fields, then we do our best.  Some drives may
1330  *    have a static configuration which still allows for some monitoring.
1331  *
1332  * 3. Check to see if the IE log page (page 0x2F) is supported by issuing a
1333  *    LOG SENSE command.
1334  *
1335  * 4. Check to see if the self-test log page (page 0x10) is supported.
1336  *
1337  * 5. Check to see if the temperature log page (page 0x0D) is supported, and
1338  *    contains a reference temperature.
1339  *
1340  * 6. Clear the GLTSD bit in control mode page 0xA.  This will allow the drive
1341  *    to save each of the log pages described above to nonvolatile storage.
1342  *    This is essential if the drive is to remember its failures across
1343  *    loss of power.
1344  */
1345 static void *
ds_scsi_open_common(disk_status_t * dsp,ds_scsi_info_t * sip)1346 ds_scsi_open_common(disk_status_t *dsp, ds_scsi_info_t *sip)
1347 {
1348 	boolean_t changed;
1349 
1350 	sip->si_dsp = dsp;
1351 
1352 	/* Load and validate mode pages */
1353 	if (load_modepages(sip) != 0) {
1354 		ds_scsi_close(sip);
1355 		return (NULL);
1356 	}
1357 
1358 	/* Load and validate log pages */
1359 	if (load_logpages(sip) != 0) {
1360 		ds_scsi_close(sip);
1361 		return (NULL);
1362 	}
1363 
1364 	/* Load IE state */
1365 	if (load_ie_modepage(sip) != 0 ||
1366 	    scsi_enable_ie(sip, &changed) != 0 ||
1367 	    (changed && load_ie_modepage(sip) != 0)) {
1368 		ds_scsi_close(sip);
1369 		return (NULL);
1370 	}
1371 
1372 	/* Clear the GLTSD bit in the control page */
1373 	if (sip->si_supp_log != 0 && clear_gltsd(sip) != 0) {
1374 		ds_scsi_close(sip);
1375 		return (NULL);
1376 	}
1377 
1378 	return (sip);
1379 }
1380 
1381 static void *
ds_scsi_open_uscsi(disk_status_t * dsp)1382 ds_scsi_open_uscsi(disk_status_t *dsp)
1383 {
1384 	ds_scsi_info_t *sip;
1385 
1386 	if ((sip = calloc(sizeof (ds_scsi_info_t), 1)) == NULL) {
1387 		(void) ds_set_errno(dsp, EDS_NOMEM);
1388 		return (NULL);
1389 	}
1390 
1391 	return (ds_scsi_open_common(dsp, sip));
1392 }
1393 
1394 static void *
ds_scsi_open_sim(disk_status_t * dsp)1395 ds_scsi_open_sim(disk_status_t *dsp)
1396 {
1397 	ds_scsi_info_t *sip;
1398 
1399 	if ((sip = calloc(sizeof (ds_scsi_info_t), 1)) == NULL) {
1400 		(void) ds_set_errno(dsp, EDS_NOMEM);
1401 		return (NULL);
1402 	}
1403 
1404 	if ((sip->si_sim = dlopen(dsp->ds_path, RTLD_LAZY)) == NULL) {
1405 		(void) ds_set_errno(dsp, EDS_NO_TRANSPORT);
1406 		free(sip);
1407 		return (NULL);
1408 	}
1409 
1410 	return (ds_scsi_open_common(dsp, sip));
1411 }
1412 
1413 
1414 /*
1415  * Scan for any faults.  The following steps are performed:
1416  *
1417  * 1. If the temperature log page is supported, check the current temperature
1418  *    and threshold.  If the current temperature exceeds the threshold, report
1419  *    and overtemp fault.
1420  *
1421  * 2. If the selftest log page is supported, check to the last completed self
1422  *    test.  If the last completed test resulted in failure, report a selftest
1423  *    fault.
1424  *
1425  * 3. If the IE log page is supported, check to see if failure is predicted.  If
1426  *    so, indicate a predictive failure fault.
1427  *
1428  * 4. If the IE log page is not supported, but the mode page supports report on
1429  *    request mode, then issue a REQUEST SENSE for the mode page.  Indicate a
1430  *    predictive failure fault if necessary.
1431  */
1432 static int
ds_scsi_scan(void * arg)1433 ds_scsi_scan(void *arg)
1434 {
1435 	ds_scsi_info_t *sip = arg;
1436 	int i;
1437 
1438 	for (i = 0; i < NLOG_VALIDATION; i++) {
1439 		if ((sip->si_supp_log & log_validation[i].ve_supported) == 0)
1440 			continue;
1441 
1442 		if (analyze_one_logpage(sip, &log_validation[i]) != 0)
1443 			return (-1);
1444 	}
1445 
1446 	if (!(sip->si_supp_log & LOGPAGE_SUPP_IE) &&
1447 	    (sip->si_supp_mode & MODEPAGE_SUPP_IEC) &&
1448 	    analyze_ie_sense(sip) != 0)
1449 		return (-1);
1450 
1451 	return (0);
1452 }
1453 
1454 ds_transport_t ds_scsi_uscsi_transport = {
1455 	ds_scsi_open_uscsi,
1456 	ds_scsi_close,
1457 	ds_scsi_scan
1458 };
1459 
1460 ds_transport_t ds_scsi_sim_transport = {
1461 	ds_scsi_open_sim,
1462 	ds_scsi_close,
1463 	ds_scsi_scan
1464 };
1465