blob: 40870afb2acb55a333964c6456841422f1e97c0f [file] [log] [blame]
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include "sg_include.h"
#include "sg_err.h"
/* A utility program for the Linux OS SCSI generic ("sg") device driver.
* Copyright (C) 2003 D. Gilbert
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
This program outputs information provided by a SCSI RECEIVE DIAGNOSTIC
command.
*/
static char * version_str = "0.15 20030422";
#define ME "sg_senddiag: "
/* #define SG_DEBUG */
#define SENSE_BUFF_LEN 32 /* Arbitrary, could be larger */
#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
#define LONG_TIMEOUT 2400000 /* 2,400,000 millisecs == 40 minutes */
#define SEND_DIAGNOSTIC_CMD 0x1d
#define SEND_DIAGNOSTIC_CMDLEN 6
#define RECEIVE_DIAGNOSTIC_CMD 0x1c
#define RECEIVE_DIAGNOSTIC_CMDLEN 6
#define MODE_SENSE6_CMD 0x1a
#define MODE_SENSE6_CMDLEN 6
#define MODE_SENSE10_CMD 0x5a
#define MODE_SENSE10_CMDLEN 10
#define MX_ALLOC_LEN (1024 * 4)
#define PG_CODE_ALL 0x0
#define EBUFF_SZ 256
static int do_senddiag(int sg_fd, int sf_code, int pf_bit, int sf_bit,
int devofl_bit, int unitofl_bit, void * outgoing_pg,
int outgoing_len, int noisy)
{
int res;
unsigned char senddiagCmdBlk[SEND_DIAGNOSTIC_CMDLEN] =
{SEND_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0};
unsigned char sense_b[SENSE_BUFF_LEN];
sg_io_hdr_t io_hdr;
senddiagCmdBlk[1] = (unsigned char)((sf_code << 5) | (pf_bit << 4) |
(sf_bit << 2) | (devofl_bit << 1) | unitofl_bit);
senddiagCmdBlk[3] = (unsigned char)((outgoing_len >> 8) & 0xff);
senddiagCmdBlk[4] = (unsigned char)(outgoing_len & 0xff);
memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = SEND_DIAGNOSTIC_CMDLEN;
io_hdr.mx_sb_len = sizeof(sense_b);
io_hdr.dxfer_direction = outgoing_len ? SG_DXFER_TO_DEV : SG_DXFER_NONE;
io_hdr.dxfer_len = outgoing_len;
io_hdr.dxferp = outgoing_pg;
io_hdr.cmdp = senddiagCmdBlk;
io_hdr.sbp = sense_b;
io_hdr.timeout = LONG_TIMEOUT;
if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
perror("SG_IO (send diagnostic) error");
return -1;
}
res = sg_err_category3(&io_hdr);
switch (res) {
case SG_ERR_CAT_CLEAN:
case SG_ERR_CAT_RECOVERED:
return 0;
default:
if (noisy) {
char ebuff[EBUFF_SZ];
snprintf(ebuff, EBUFF_SZ, "Send diagnostic error, sf_code=0x%x, "
"pf_bit=%d, sf_bit=%d ", sf_code, pf_bit, sf_bit);
sg_chk_n_print3(ebuff, &io_hdr);
}
return -1;
}
}
static int do_rcvdiag(int sg_fd, int pcv, int pg_code, void * resp,
int mx_resp_len, int noisy)
{
int res;
unsigned char rcvdiagCmdBlk[RECEIVE_DIAGNOSTIC_CMDLEN] =
{RECEIVE_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0};
unsigned char sense_b[SENSE_BUFF_LEN];
sg_io_hdr_t io_hdr;
rcvdiagCmdBlk[1] = (unsigned char)(pcv ? 0x1 : 0);
rcvdiagCmdBlk[2] = (unsigned char)(pg_code);
rcvdiagCmdBlk[3] = (unsigned char)((mx_resp_len >> 8) & 0xff);
rcvdiagCmdBlk[4] = (unsigned char)(mx_resp_len & 0xff);
memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = RECEIVE_DIAGNOSTIC_CMDLEN;
io_hdr.mx_sb_len = sizeof(sense_b);
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.dxfer_len = mx_resp_len;
io_hdr.dxferp = resp;
io_hdr.cmdp = rcvdiagCmdBlk;
io_hdr.sbp = sense_b;
io_hdr.timeout = DEF_TIMEOUT;
if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
perror("SG_IO (receive diagnostic) error");
return -1;
}
res = sg_err_category3(&io_hdr);
switch (res) {
case SG_ERR_CAT_CLEAN:
case SG_ERR_CAT_RECOVERED:
return 0;
default:
if (noisy) {
char ebuff[EBUFF_SZ];
snprintf(ebuff, EBUFF_SZ, "Receive diagnostic error, pcv=%d, "
"page_code=%x ", pcv, pg_code);
sg_chk_n_print3(ebuff, &io_hdr);
}
return -1;
}
}
/* Get last extended self-test time from mode page 0xa (for '-e' option) */
static int do_modes_0a(int sg_fd, void * resp, int mx_resp_len, int noisy,
int mode6)
{
int res;
unsigned char modesCmdBlk[MODE_SENSE10_CMDLEN] =
{MODE_SENSE10_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned char sense_b[SENSE_BUFF_LEN];
sg_io_hdr_t io_hdr;
int dbd = 1;
int pc = 0;
int pg_code = 0xa;
modesCmdBlk[1] = (unsigned char)(dbd ? 0x8 : 0);
modesCmdBlk[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
if (mx_resp_len > (mode6 ? 0xff : 0xffff)) {
printf( ME "mx_resp_len too big\n");
return -1;
}
if(mode6) {
modesCmdBlk[0] = MODE_SENSE6_CMD;
modesCmdBlk[4] = (unsigned char)(mx_resp_len & 0xff);
} else {
modesCmdBlk[7] = (unsigned char)((mx_resp_len >> 8) & 0xff);
modesCmdBlk[8] = (unsigned char)(mx_resp_len & 0xff);
}
memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = mode6 ? MODE_SENSE6_CMDLEN : MODE_SENSE10_CMDLEN;
io_hdr.mx_sb_len = sizeof(sense_b);
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.dxfer_len = mx_resp_len;
io_hdr.dxferp = resp;
io_hdr.cmdp = modesCmdBlk;
io_hdr.sbp = sense_b;
io_hdr.timeout = DEF_TIMEOUT;
if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
perror("SG_IO (mode sense) error");
return -1;
}
res = sg_err_category3(&io_hdr);
switch (res) {
case SG_ERR_CAT_CLEAN:
case SG_ERR_CAT_RECOVERED:
return 0;
default:
if (noisy) {
char ebuff[EBUFF_SZ];
snprintf(ebuff, EBUFF_SZ, "Mode sense error, dbd=%d, "
"pc=%d, page_code=%x ", dbd, pc, pg_code);
sg_chk_n_print3(ebuff, &io_hdr);
}
return -1;
}
}
struct page_code_desc {
int page_code;
const char * desc;
};
static struct page_code_desc pc_desc_arr[] = {
{0x0, "Supported diagnostic pages"},
{0x1, "Configuration (SES)"},
{0x2, "Enclosure status/control (SES)"},
{0x3, "Help text (SES)"},
{0x4, "String In/Out (SES)"},
{0x5, "Threshold In/Out (SES)"},
{0x6, "Array Status/Control (SES)"},
{0x7, "Element descriptor (SES)"},
{0x8, "Short enclosure status (SES)"},
{0x9, "Enclosure busy (SES-2)"},
{0xa, "Device element status (SES-2)"},
{0x40, "Translate address (direct access)"},
{0x41, "Device status (direct access)"},
};
const char * find_page_code_desc(int page_num)
{
int k;
int num = sizeof(pc_desc_arr) / sizeof(pc_desc_arr[0]);
const struct page_code_desc * pcdp = &pc_desc_arr[0];
for (k = 0; k < num; ++k, ++pcdp) {
if (page_num == pcdp->page_code)
return pcdp->desc;
else if (page_num < pcdp->page_code)
return NULL;
}
return NULL;
}
static void list_page_codes()
{
int k;
int num = sizeof(pc_desc_arr) / sizeof(pc_desc_arr[0]);
const struct page_code_desc * pcdp = &pc_desc_arr[0];
printf("Page_Code Description\n");
for (k = 0; k < num; ++k, ++pcdp)
printf(" 0x%02x %s\n", pcdp->page_code, pcdp->desc);
}
static void usage()
{
printf("Usage: 'sg_senddiag [-doff] [-e] [-h] [-l] [-pf]"
" [-s=<self_test_code>]\n"
" [-t] [-uoff] [-V] [<sg_device>]'\n"
" where -doff device online (def: 0, only with '-t')\n"
" -e duration of last extended test (from mode page 0xa)\n"
" -h output in hex\n"
" -l list supported page codes\n"
" -pf set PF bit (def: 0)\n"
" -s=<self_test_code> (def: 0)\n"
" 1->background short, 2->background extended,"
" 4->abort test\n"
" 5->foreground short, 6->foreground extended\n"
" -t default self test\n"
" -uoff unit online (def: 0, only with '-t')\n"
" -V output version string\n"
" -? output this usage message\n");
}
static void dStrHex(const char* str, int len, int no_ascii)
{
const char* p = str;
unsigned char c;
char buff[82];
int a = 0;
const int bpstart = 5;
const int cpstart = 60;
int cpos = cpstart;
int bpos = bpstart;
int i, k;
if (len <= 0) return;
memset(buff,' ',80);
buff[80]='\0';
k = sprintf(buff + 1, "%.2x", a);
buff[k + 1] = ' ';
if (bpos >= ((bpstart + (9 * 3))))
bpos++;
for(i = 0; i < len; i++)
{
c = *p++;
bpos += 3;
if (bpos == (bpstart + (9 * 3)))
bpos++;
sprintf(&buff[bpos], "%.2x", (int)(unsigned char)c);
buff[bpos + 2] = ' ';
if (no_ascii)
buff[cpos++] = ' ';
else {
if ((c < ' ') || (c >= 0x7f))
c='.';
buff[cpos++] = c;
}
if (cpos > (cpstart+15))
{
printf("%s\n", buff);
bpos = bpstart;
cpos = cpstart;
a += 16;
memset(buff,' ',80);
k = sprintf(buff + 1, "%.2x", a);
buff[k + 1] = ' ';
}
}
if (cpos > cpstart)
{
printf("%s\n", buff);
}
}
int main(int argc, char * argv[])
{
int sg_fd, k, num, rsp_len;
char * file_name = 0;
char ebuff[EBUFF_SZ];
unsigned char rsp_buff[MX_ALLOC_LEN];
int rsp_buff_size = MX_ALLOC_LEN;
unsigned int u;
int self_test_code = 0;
int do_pf = 0;
int do_doff = 0;
int do_hex = 0;
int do_list = 0;
int do_def_test = 0;
int do_uoff = 0;
int do_ext_time = 0;
int oflags = O_RDWR;
for (k = 1; k < argc; ++k) {
if (0 == strncmp("-s=", argv[k], 3)) {
num = sscanf(argv[k] + 3, "%x", &u);
if ((1 != num) || (u > 7)) {
printf("Bad page code after '-s' switch\n");
file_name = 0;
break;
}
self_test_code = u;
}
else if (0 == strcmp("-pf", argv[k]))
do_pf = 1;
else if (0 == strcmp("-doff", argv[k]))
do_doff = 1;
else if (0 == strcmp("-h", argv[k]))
do_hex = 1;
else if (0 == strcmp("-l", argv[k]))
do_list = 1;
else if (0 == strcmp("-t", argv[k]))
do_def_test = 1;
else if (0 == strcmp("-uoff", argv[k]))
do_uoff = 1;
else if (0 == strcmp("-e", argv[k]))
do_ext_time = 1;
else if (0 == strcmp("-?", argv[k])) {
usage();
return 0;
}
else if (0 == strcmp("-V", argv[k])) {
printf("Version string: %s\n", version_str);
exit(0);
}
else if (*argv[k] == '-') {
printf("Unrecognized switch: %s\n", argv[k]);
file_name = 0;
break;
}
else if (0 == file_name)
file_name = argv[k];
else {
printf("too many arguments\n");
file_name = 0;
break;
}
}
if ((do_doff || do_uoff) && (! do_def_test)) {
printf("setting -doff or -uoff only useful when -t is set\n");
usage();
return 1;
}
if ((self_test_code > 0) && do_def_test) {
printf("either set -s=<num> or -t (not both)\n");
usage();
return 1;
}
if (0 == file_name) {
if (do_list) {
list_page_codes();
return 0;
}
usage();
return 1;
}
if ((sg_fd = open(file_name, oflags)) < 0) {
snprintf(ebuff, EBUFF_SZ, ME "error opening file: %s", file_name);
perror(ebuff);
return 1;
}
/* Just to be safe, check we have a new sg device by trying an ioctl */
if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) {
printf( ME "%s doesn't seem to be a version 3 sg device\n",
file_name);
close(sg_fd);
return 1;
}
if (do_ext_time) {
if (0 == do_modes_0a(sg_fd, rsp_buff, 32, 1, 0)) {
/* Assume mode sense(10) response without block descriptors */
num = (rsp_buff[0] << 8) + rsp_buff[1] - 6;
if (num >= 0xc) {
int secs;
secs = (rsp_buff[18] << 8) + rsp_buff[19];
printf("Previous extended self-test duration=%d seconds "
"(%.2f minutes)\n", secs, secs / 60.0);
} else
printf("Extended self-test duration not available\n");
} else
printf("Extended self-test duration (mode page 0xa) failed\n");
return 0;
}
if (do_list) {
memset(rsp_buff, 0, sizeof(rsp_buff));
if (0 == do_senddiag(sg_fd, 0, do_pf, 0, 0, 0, rsp_buff, 4, 1)) {
if (0 == do_rcvdiag(sg_fd, 0, 0, rsp_buff, rsp_buff_size, 1)) {
printf("Supported diagnostic pages response:\n");
rsp_len = (rsp_buff[2] << 8) + rsp_buff[3] + 4;
if (do_hex)
dStrHex((const char *)rsp_buff, rsp_len, 1);
else {
for (k = 0; k < (rsp_len - 4); ++k)
printf(" %s\n", find_page_code_desc(rsp_buff[k + 4]));
}
}
}
}
else if (0 == do_senddiag(sg_fd, self_test_code, do_pf, do_def_test,
do_doff, do_uoff, NULL, 0, 1)) {
if ((5 == self_test_code) || (6 == self_test_code))
printf("Foreground self test returned GOOD status\n");
else if (do_def_test && (! do_doff) && (! do_uoff))
printf("Default self test returned GOOD status\n");
}
close(sg_fd);
return 0;
}