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.
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.
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]
22 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
26 * SES Log reader library
28 * This library is responsible for accessing the SES log at the target address,
29 * formatting and returning any log entries found.
31 * The data will be returned in an nvlist_t structure allocated here.
37 #include <sys/param.h>
38 #include <libseslog.h>
44 #include <sys/scsi/generic/commands.h>
45 #include <sys/scsi/generic/status.h>
46 #include <sys/scsi/impl/commands.h>
49 * open the device with given device name
52 open_device(const char *device_name
)
54 int oflags
= O_NONBLOCK
| O_RDWR
;
57 fd
= open(device_name
, oflags
);
64 * Initialize scsi struct
67 construct_scsi_pt_obj(struct uscsi_cmd
*uscsi
)
69 (void) memset(uscsi
, 0, sizeof (struct uscsi_cmd
));
70 uscsi
->uscsi_timeout
= DEF_PT_TIMEOUT
;
71 uscsi
->uscsi_flags
= USCSI_READ
| USCSI_ISOLATE
| USCSI_RQENABLE
;
75 * set control cdb of scsi structure
78 set_scsi_pt_cdb(struct uscsi_cmd
*uscsi
, const unsigned char *cdb
,
81 uscsi
->uscsi_cdb
= (char *)cdb
;
82 uscsi
->uscsi_cdblen
= cdb_len
;
86 * initialize sense data
89 set_scsi_pt_sense(struct uscsi_cmd
*uscsi
, unsigned char *sense
,
92 (void) memset(sense
, 0, max_sense_len
);
93 uscsi
->uscsi_rqbuf
= (char *)sense
;
94 uscsi
->uscsi_rqlen
= max_sense_len
;
98 * Initialize data going to device
101 set_scsi_pt_data_in(struct uscsi_cmd
*uscsi
, unsigned char *dxferp
,
105 uscsi
->uscsi_bufaddr
= (char *)dxferp
;
106 uscsi
->uscsi_buflen
= dxfer_len
;
107 uscsi
->uscsi_flags
= USCSI_READ
| USCSI_ISOLATE
|
113 * Executes SCSI command(or at least forwards it to lower layers).
116 do_scsi_pt(struct uscsi_cmd
*uscsi
, int fd
, int time_secs
)
119 uscsi
->uscsi_timeout
= time_secs
;
121 if (ioctl(fd
, USCSICMD
, uscsi
)) {
130 * Read log from device
131 * Invokes a SCSI LOG SENSE command.
134 * SG_LIB_CAT_INVALID_OP -> Log Sense not supported,
135 * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
136 * SG_LIB_CAT_NOT_READY -> device not ready,
137 * -1 -> other failure
141 read_log(int sg_fd
, unsigned char *resp
, int mx_resp_len
)
144 unsigned char logsCmdBlk
[CDB_GROUP1
] =
145 {SCMD_LOG_SENSE_G1
, 0, 0, 0, 0, 0, 0, 0, 0, 0};
146 unsigned char sense_b
[SENSE_BUFF_LEN
];
147 struct uscsi_cmd uscsi
;
149 if (mx_resp_len
> 0xffff) {
153 /* pc = 1, pg_code = 0x7 (logs page) */
154 /* (((pc << 6) & 0xc0) | (pg_code & 0x3f)) = 0x47; */
155 logsCmdBlk
[2] = 0x47;
156 /* pc = 1 current values */
157 logsCmdBlk
[3] = 0; /* No subpage code */
158 logsCmdBlk
[5] = 0; /* Want all logs starting from 0 */
160 logsCmdBlk
[7] = (unsigned char) ((mx_resp_len
>> 8) & 0xff);
161 logsCmdBlk
[8] = (unsigned char) (mx_resp_len
& 0xff);
163 construct_scsi_pt_obj(&uscsi
);
165 set_scsi_pt_cdb(&uscsi
, logsCmdBlk
, sizeof (logsCmdBlk
));
166 set_scsi_pt_sense(&uscsi
, sense_b
, sizeof (sense_b
));
167 set_scsi_pt_data_in(&uscsi
, resp
, mx_resp_len
);
168 res
= do_scsi_pt(&uscsi
, sg_fd
, DEF_PT_TIMEOUT
);
172 ret
= uscsi
.uscsi_status
;
178 * Save the logs by walking through the entries in the response buffer.
180 * resp buffer looks like:
182 * +=====-========-========-========-========-========-========-========-=====+
183 * | Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
184 * |Byte | | | | | | | | |
185 * |=====+====================================================================|
186 * | 0 | reserved | page code |
187 * |-----+--------------------------------------------------------------------|
189 * |-----+--------------------------------------------------------------------|
190 * | 2 |(MSB) Page Length(n-3) |
193 * |-----+--------------------------------------------------------------------|
194 * | 4 | Log Parameter (First)(Length X) |
197 * |-----+--------------------------------------------------------------------|
198 * |n-y+1| Log Parameter (Last)(Length y) |
201 * +==========================================================================+
203 * Log parameter field looks like:
205 * +=====-========-========-========-========-========-========-========-=====+
206 * | Bit| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
207 * |Byte | | | | | | | | |
208 * |=====+====================================================================|
209 * | 0 |(MSB) Parameter Code |
212 * |-----+--------------------------------------------------------------------|
213 * | 2 | DU | DS | TSD | ETC | TMC | LBIN | LP |
214 * |-----+--------------------------------------------------------------------|
215 * | 3 | Parameter Length(n-3) |
216 * |-----+--------------------------------------------------------------------|
217 * | 4 | Parameter Values |
220 * |-----+--------------------------------------------------------------------|
224 save_logs(unsigned char *resp
, ses_log_call_t
*data
)
227 int param_code
; /* Parameter code */
228 int param_len
= 0; /* Paramter length */
229 unsigned char *log_param_ptr
; /* Log parameter pointer */
230 unsigned char *log_str_ptr
; /* ptr to ascii str returend by expander */
232 char log_code
[ENTRY_MAX_SIZE
];
233 char log_level
[ENTRY_MAX_SIZE
];
237 char save_buffer
[MAX_LOG_ENTRY_SZ
];
238 char entry_added
= 0;
239 int all_log_data_len
;
242 * Bytes 2 and 3 of response buffer contain the page length of
243 * the log entries returned.
245 all_log_data_len
= SCSI_READ16(&resp
[2]);
248 * Initialize log parameter pointer to point to first log entry.
249 * The resp includes 4 bytes of header info and then log entries
251 log_param_ptr
= &resp
[0] + 4;
254 * If multiple heads are reading the logs, it is possible that we
255 * could be re-reading some of the same log entries plus some
256 * new additional entries. Check to see if any entries in this read
257 * contain the same log entry as the last entry we read last time.
259 if (data
->last_log_entry
!= NULL
&&
260 (strlen(data
->last_log_entry
) == SES_LOG_VALID_LOG_SIZE
)) {
262 * We have a valid log entry from a previous read log
268 * Start walking each log entry in response buffer looking for
271 for (k
= 0; k
< all_log_data_len
; k
+= param_len
) {
273 * Calculate log entry length
274 * Log param ptr [3] contains the log length minus the
275 * header info which is 4 bytes so add that in.
277 param_len
= log_param_ptr
[3] + 4;
279 if (param_len
<= 4) {
281 * Only header information in this entry
282 * process next log entry
284 log_param_ptr
+= param_len
;
290 * initialize log_str_ptr to point to string info
291 * returned by expander
292 * first 4 bytes of log parameter contains
293 * 2 bytes of parameter code, 1 byte of Control data
294 * and 1 byte for parameter length. Log string begins
295 * after that so add 4 to log param ptr.
297 log_str_ptr
= log_param_ptr
+ 4;
300 * Check to see if this is the
303 if (strncmp((char *)log_str_ptr
, data
->last_log_entry
,
304 SES_LOG_VALID_LOG_SIZE
) == 0) {
305 /* Found an exact match */
306 log_param_ptr
+= param_len
;
311 log_param_ptr
+= param_len
;
315 log_param_ptr
= &resp
[0] + 4;
318 if (k
== all_log_data_len
) {
320 * Either there was no log data or we have
321 * already read these log entries.
327 /* Grab memory to return logs with */
328 if (nvlist_alloc(&data
->log_data
, NV_UNIQUE_NAME
, 0) != 0) {
329 /* Couldn't alloc memory for nvlist */
330 return (SES_LOG_FAILED_NVLIST_CREATE
);
333 (void) memset(log_code
, 0, sizeof (log_code
));
334 (void) memset(save_buffer
, 0, sizeof (save_buffer
));
335 (void) memset(log_level
, 0, sizeof (log_level
));
338 * Start saving new log entries
339 * Walk the log data adding any new entries
342 for (; k
< all_log_data_len
; k
+= param_len
) {
344 * Calculate log entry length
345 * Log ptr [3] contains the log length minus the header info
346 * which is 4 bytes so add that in
348 param_len
= log_param_ptr
[3] + 4;
350 if (param_len
<= 4) {
351 /* Only header information in this entry */
352 /* process next log entry */
353 log_param_ptr
+= param_len
;
358 * initialize log_str_ptr to point to string info of the log
359 * entry. First 4 bytes of log entry contains param code,
360 * control byte, and length. Log string starts after that.
362 log_str_ptr
= log_param_ptr
+ 4;
365 * Format of log str is as follows
366 * "%8x %8x %8x %8x %8x %8x %8x %8x",
367 * log_entry.log_word0, log_entry.ts_u, log_entry.ts_l,
368 * log_entry.seq_num, log_entry.log_code, log_entry.log_word2,
369 * log_entry.log_word3, log_entry.log_word4
370 * following example has extra spaces removed to fit in 80 char
371 * 40004 0 42d5f5fe 185b 630002 fd0800 50800207 e482813
374 (void) strncpy(save_buffer
,
375 (const char *)log_str_ptr
,
376 SES_LOG_VALID_LOG_SIZE
);
378 (void) strncpy(log_code
,
379 (const char *)log_str_ptr
+SES_LOG_CODE_START
,
380 SES_LOG_SPECIFIC_ENTRY_SIZE
);
382 (void) strncpy(log_level
,
383 (const char *) log_str_ptr
+
384 SES_LOG_LEVEL_START
, 1);
387 /* Add this entry to the nvlist log data */
388 if (nvlist_alloc(&entry
, NV_UNIQUE_NAME
, 0) != 0) {
389 /* Couldn't alloc space, return error */
390 return (SES_LOG_FAILED_NV_UNIQUE
);
394 if (nvlist_add_string(entry
, ENTRY_LOG
, save_buffer
) != 0) {
395 /* Error adding string, return error */
397 return (SES_LOG_FAILED_NV_LOG
);
400 if (nvlist_add_string(entry
, ENTRY_CODE
, log_code
) != 0) {
401 /* Error adding string, return error */
403 return (SES_LOG_FAILED_NV_CODE
);
405 if (nvlist_add_string(entry
, ENTRY_SEVERITY
, log_level
) != 0) {
406 /* Error adding srtring, return error */
408 return (SES_LOG_FAILED_NV_SEV
);
411 param_code
= SCSI_READ16(&log_param_ptr
[0]);
413 (void) snprintf(entry_num
, sizeof (entry_num
),
414 "%s%d", ENTRY_PREFIX
, param_code
);
416 if (nvlist_add_nvlist(data
->log_data
, entry_num
, entry
) != 0) {
417 /* Error adding nvlist, return error */
419 return (SES_LOG_FAILED_NV_ENTRY
);
424 (data
->number_log_entries
)++;
426 log_param_ptr
+= param_len
;
430 /* Update the last log entry string with last one read */
431 (void) strncpy(data
->last_log_entry
, save_buffer
, MAXNAMELEN
);
438 /* Setup struct to send command to device */
440 set_scsi_pt_data_out(struct uscsi_cmd
*uscsi
, const unsigned char *dxferp
,
444 uscsi
->uscsi_bufaddr
= (char *)dxferp
;
445 uscsi
->uscsi_buflen
= dxfer_len
;
446 uscsi
->uscsi_flags
= USCSI_WRITE
| USCSI_ISOLATE
|
452 * Invokes a SCSI MODE SENSE(10) command.
455 * SG_LIB_CAT_INVALID_OP -> invalid opcode
456 * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb
457 * SG_LIB_CAT_NOT_READY -> device not ready
458 * -1 -> other failure
462 sg_ll_mode_sense10(int sg_fd
, void * resp
, int mx_resp_len
)
465 unsigned char modesCmdBlk
[MODE_SENSE10_CMDLEN
] =
466 {SCMD_MODE_SENSE_G1
, 0, 0, 0, 0, 0, 0, 0, 0, 0};
467 unsigned char sense_b
[SENSE_BUFF_LEN
];
468 struct uscsi_cmd uscsi
;
471 modesCmdBlk
[2] = 0; /* page code 0 vendor specific */
473 modesCmdBlk
[7] = (unsigned char) ((mx_resp_len
>> 8) & 0xff);
474 modesCmdBlk
[8] = (unsigned char) (mx_resp_len
& 0xff);
476 construct_scsi_pt_obj(&uscsi
);
477 set_scsi_pt_cdb(&uscsi
, modesCmdBlk
, sizeof (modesCmdBlk
));
478 set_scsi_pt_sense(&uscsi
, sense_b
, sizeof (sense_b
));
479 set_scsi_pt_data_in(&uscsi
, (unsigned char *) resp
, mx_resp_len
);
480 res
= do_scsi_pt(&uscsi
, sg_fd
, DEF_PT_TIMEOUT
);
484 ret
= uscsi
.uscsi_status
;
490 * Invokes a SCSI MODE SELECT(10) command.
493 * SG_LIB_CAT_INVALID_OP for invalid opcode
494 * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb,
495 * SG_LIB_CAT_NOT_READY -> device not ready,
496 * -1 -> other failure
499 sg_ll_mode_select10(int sg_fd
, void * paramp
, int param_len
)
502 unsigned char modesCmdBlk
[MODE_SELECT10_CMDLEN
] =
503 {SCMD_MODE_SELECT_G1
, 0, 0, 0, 0, 0, 0, 0, 0, 0};
504 unsigned char sense_b
[SENSE_BUFF_LEN
];
505 struct uscsi_cmd uscsi
;
510 * modesCmdBlk 2 equal 0 PC 0 return current page code 0 return
514 modesCmdBlk
[7] = (unsigned char)((param_len
>> 8) & 0xff);
515 modesCmdBlk
[8] = (unsigned char)(param_len
& 0xff);
517 construct_scsi_pt_obj(&uscsi
);
519 set_scsi_pt_cdb(&uscsi
, modesCmdBlk
, sizeof (modesCmdBlk
));
520 set_scsi_pt_sense(&uscsi
, sense_b
, sizeof (sense_b
));
521 set_scsi_pt_data_out(&uscsi
, (unsigned char *) paramp
, param_len
);
522 res
= do_scsi_pt(&uscsi
, sg_fd
, DEF_PT_TIMEOUT
);
526 ret
= uscsi
.uscsi_status
;
534 * MODE SENSE 10 commands yield a response that has block descriptors followed
535 * by mode pages. In most cases users are interested in the first mode page.
536 * This function returns the(byte) offset of the start of the first mode page.
537 * Returns >= 0 is successful or -1 if failure. If there is a failure
538 * a message is written to err_buff.
542 * return data looks like:
543 * Table 92 - Mode parameter header(10)
547 * ----------------------------------------------------------
550 * ----------------------------------------------------------
552 * ----------------------------------------------------------
553 * 3 Device-specific parameter
554 * ----------------------------------------------------------
556 * ----------------------------------------------------------
558 * ----------------------------------------------------------
559 * 6 MSB block descriptor length
560 * 7 LSB block descriptor length
561 * ----------------------------------------------------------
562 * block desciptors....
563 * -----------------------
565 * 0 : ps Reserved : page Code
566 * 1 : Page Length(n-1)
567 * 2-N Mode parameters
570 sg_mode_page_offset(const unsigned char *resp
, int resp_len
)
576 if ((NULL
== resp
) || (resp_len
< 8)) {
577 /* Too short of a response buffer */
581 calc_len
= (resp
[0] << 8) + resp
[1] + 2;
582 bd_len
= (resp
[6] << 8) + resp
[7];
584 /* LongLBA doesn't change this calculation */
585 offset
= bd_len
+ MODE10_RESP_HDR_LEN
;
587 if ((offset
+ 2) > resp_len
) {
588 /* Given response length to small */
590 } else if ((offset
+ 2) > calc_len
) {
591 /* Calculated response length too small */
601 clear_log(int sg_fd
, ses_log_call_t
*data
)
604 int res
, alloc_len
, off
;
607 unsigned char ref_md
[MAX_ALLOC_LEN
];
608 struct log_clear_control_struct clear_data
;
612 char seq_num_str
[10];
613 unsigned long seq_num
= 0;
615 (void) memset(&clear_data
, 0, sizeof (clear_data
));
617 clear_data
.pageControls
= 0x40;
618 clear_data
.subpage_code
= 0;
619 clear_data
.page_lengthLower
= 0x16;
621 myhostid
= gethostid();
622 /* 0 -> 11 are memset to 0 */
623 clear_data
.host_id
[12] = (myhostid
& 0xff000000) >> 24;
624 clear_data
.host_id
[13] = (myhostid
& 0xff0000) >> 16;
625 clear_data
.host_id
[14] = (myhostid
& 0xff00) >> 8;
626 clear_data
.host_id
[15] = myhostid
& 0xff;
629 * convert nanosecond time to seconds
631 poll_time
= data
->poll_time
/ 1000000000;
632 /* Add 5 minutes to poll time to allow for data retrieval time */
633 poll_time
= poll_time
+ 300;
634 clear_data
.timeout
[0] = (poll_time
& 0xff00) >> 8;
635 clear_data
.timeout
[1] = poll_time
& 0xff;
638 * retrieve the last read sequence number from the last
641 if (data
->last_log_entry
!= NULL
&&
642 (strlen(data
->last_log_entry
) == SES_LOG_VALID_LOG_SIZE
)) {
644 * We have a valid log entry from a previous read log
647 (void) strncpy(seq_num_str
,
648 (const char *) data
->last_log_entry
+
649 SES_LOG_SEQ_NUM_START
, 8);
650 seq_num
= strtoul(seq_num_str
, 0, 16);
652 clear_data
.seq_clear
[0] = (seq_num
& 0xff000000) >> 24;
653 clear_data
.seq_clear
[1] = (seq_num
& 0xff0000) >> 16;
654 clear_data
.seq_clear
[2] = (seq_num
& 0xff00) >> 8;
655 clear_data
.seq_clear
[3] = (seq_num
& 0xff);
657 read_in_len
= sizeof (clear_data
);
660 /* do MODE SENSE to fetch current values */
661 (void) memset(ref_md
, 0, MAX_ALLOC_LEN
);
662 alloc_len
= MAX_ALLOC_LEN
;
665 res
= sg_ll_mode_sense10(sg_fd
, ref_md
, alloc_len
);
667 /* Error during mode sense */
668 error
= SES_LOG_FAILED_MODE_SENSE
;
672 /* Setup mode Select to clear logs */
673 off
= sg_mode_page_offset(ref_md
, alloc_len
);
675 /* Mode page offset error */
676 error
= SES_LOG_FAILED_MODE_SENSE_OFFSET
;
679 md_len
= (ref_md
[0] << 8) + ref_md
[1] + 2;
684 if (md_len
> alloc_len
) {
685 /* Data length to large */
686 error
= SES_LOG_FAILED_BAD_DATA_LEN
;
690 if ((md_len
- off
) != read_in_len
) {
691 /* Content length not correct */
692 error
= SES_LOG_FAILED_BAD_CONTENT_LEN
;
696 if ((clear_data
.pageControls
& 0x40) != (ref_md
[off
] & 0x40)) {
697 /* reference model doesn't have use subpage format bit set */
698 /* Even though it should have */
699 /* don't send the command */
700 error
= SES_LOG_FAILED_FORMAT_PAGE_ERR
;
704 (void) memcpy(ref_md
+ off
, (const void *) &clear_data
,
705 sizeof (clear_data
));
707 res
= sg_ll_mode_select10(sg_fd
, ref_md
, md_len
);
709 error
= SES_LOG_FAILED_MODE_SELECT
;
716 * Gather data from given device.
719 gather_data(char *device_name
, ses_log_call_t
*data
)
723 unsigned char rsp_buff
[MAX_ALLOC_LEN
];
727 if ((sg_fd
= open_device(device_name
)) < 0) {
728 /* Failed to open device */
729 return (SES_LOG_FAILED_TO_OPEN_DEVICE
);
733 (void) memset(rsp_buff
, 0, sizeof (rsp_buff
));
734 resp_len
= 0x8000; /* Maximum size available to read */
735 res
= read_log(sg_fd
, rsp_buff
, resp_len
);
738 /* Some sort of Error during read of logs */
740 return (SES_LOG_FAILED_TO_READ_DEVICE
);
744 error
= save_logs(rsp_buff
, data
);
750 error
= clear_log(sg_fd
, data
);
758 * Access the SES target identified by the indicated path. Read the logs
759 * and return them in a nvlist.
762 access_ses_log(ses_log_call_t
*data
)
764 char real_path
[MAXPATHLEN
];
768 /* Initialize return data */
769 data
->log_data
= NULL
;
770 data
->number_log_entries
= 0;
772 if (data
->target_path
== NULL
) {
773 /* NULL Target path, return error */
774 return (SES_LOG_FAILED_NULL_TARGET_PATH
);
777 /* Try to find a valid path */
778 (void) snprintf(real_path
, sizeof (real_path
), "/devices%s:ses",
781 if (stat(real_path
, &buffer
) != 0) {
783 (void) snprintf(real_path
, sizeof (real_path
), "/devices%s:0",
785 if (stat(real_path
, &buffer
) != 0) {
786 /* Couldn't find a path that exists */
787 return (SES_LOG_FAILED_BAD_TARGET_PATH
);
791 error
= gather_data(real_path
, data
);
793 /* Update the size of log entries being returned */
794 data
->size_of_log_entries
=
795 data
->number_log_entries
* SES_LOG_VALID_LOG_SIZE
;