blob: bb8cefa25dbbe45207802982a41fe5f02c57fafc [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) 2000-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 MODE SENSE command.
Does 10 byte MODE SENSE commands by default, Trent Piepho added a "-6"
switch for force 6 byte mode sense commands.
*/
static char * version_str = "0.18 20030507";
#define ME "sg_modes: "
/* #define SG_DEBUG */
#define SENSE_BUFF_LEN 32 /* Arbitrary, could be larger */
#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
#define MODE_SENSE6_CMD 0x1a
#define MODE_SENSE6_CMDLEN 6
#define MODE_SENSE10_CMD 0x5a
#define MODE_SENSE10_CMDLEN 10
#define INQUIRY_CMD 0x12
#define INQUIRY_CMDLEN 6
#define MX_ALLOC_LEN (1024 * 4)
#define PG_CODE_ALL 0x3f
#define EBUFF_SZ 256
/* Returns 0 when successful, else -1 */
static int do_simple_inq(int sg_fd, void * resp, int mx_resp_len, int noisy)
{
int res;
unsigned char inqCmdBlk[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
unsigned char sense_b[SENSE_BUFF_LEN];
sg_io_hdr_t io_hdr;
inqCmdBlk[4] = (unsigned char)mx_resp_len;
memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
io_hdr.interface_id = 'S';
io_hdr.cmd_len = sizeof(inqCmdBlk);
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 = inqCmdBlk;
io_hdr.sbp = sense_b;
io_hdr.timeout = DEF_TIMEOUT;
if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
perror("SG_IO (inquiry) 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, "Inquiry error ");
sg_chk_n_print3(ebuff, &io_hdr);
}
return -1;
}
}
static int do_modes(int sg_fd, int dbd, int pc, int pg_code, int sub_pg_code,
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;
modesCmdBlk[1] = (unsigned char)(dbd ? 0x8 : 0);
modesCmdBlk[2] = (unsigned char)(((pc << 6) & 0xc0) | (pg_code & 0x3f));
modesCmdBlk[3] = (unsigned char)(sub_pg_code & 0xff);
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));
memset(sense_b, 0, sizeof(sense_b));
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 sub_page_code=%x\n ", dbd, pc,
pg_code, sub_pg_code);
sg_chk_n_print3(ebuff, &io_hdr);
}
if ((0x70 == (0x7f & sense_b[0])) && (0x20 == sense_b[12]) &&
(0x0 == sense_b[13])) {
if (mode6)
fprintf(stderr, ">>>>>> drop '-6' switch and try again with "
"a 10 byte MODE SENSE\n");
else
fprintf(stderr, ">>>>>> add '-6' switch and try again with "
"a 6 byte MODE SENSE\n");
}
return -1;
}
}
const char * scsi_ptype_strs[] = {
"disk",
"tape",
"printer",
"processor",
"write once optical disk",
"cd/dvd",
"scanner",
"optical memory device",
"medium changer",
"communications",
"graphics",
"graphics",
"storage array controller",
"enclosure services device",
"simplified direct access device",
"optical card reader/writer device",
};
const char * get_ptype_str(int scsi_ptype)
{
int num = sizeof(scsi_ptype_strs) / sizeof(scsi_ptype_strs[0]);
return (scsi_ptype < num) ? scsi_ptype_strs[scsi_ptype] : "";
}
struct page_code_desc {
int page_code;
const char * desc;
};
static struct page_code_desc pc_desc_all[] = {
{0x0, "Unit Attention condition [vendor: page format not required]"},
{0x2, "Disconnect-Reconnect"},
{0xa, "Control"},
{0x15, "Extended"},
{0x16, "Extended device-type specific"},
{0x18, "Protocol specific LUN"},
{0x19, "Protocol specific port"},
{0x1a, "Power condition"},
{0x1c, "Informational exceptions control"},
{0x3f, "[yields all supported pages]"},
};
static struct page_code_desc pc_desc_disk[] = {
{0x1, "Read-Write error recovery"},
{0x3, "Format"},
{0x4, "Rigid disk geometry"},
{0x5, "Flexible geometry"},
{0x7, "Verify error recovery"},
{0x8, "Caching"},
{0x9, "Peripheral device (spc-2 ?)"},
{0xb, "Medium types supported"},
{0xc, "Notch and partition"},
{0xd, "Power condition (obsolete)"},
{0x10, "XOR control"},
};
static struct page_code_desc pc_desc_tape[] = {
{0xf, "Data Compression"},
{0x10, "Device config"},
{0x11, "Medium Partition [1]"},
{0x12, "Medium Partition [2]"},
{0x13, "Medium Partition [3]"},
{0x14, "Medium Partition [4]"},
{0x1c, "Informational exceptions control (tape version)"},
};
static struct page_code_desc pc_desc_cddvd[] = {
{0x1, "Read-Write error recovery"},
{0x3, "MRW"},
{0x5, "Write parameters"},
{0xd, "CD device parameters (obsolete)"},
{0xe, "CD audio"},
{0x1a, "Power condition"},
{0x1c, "Fault/failure reporting control"},
{0x1d, "Timeout and protect"},
{0x2a, "MM capabilities and mechanical status (obsolete)"},
};
static struct page_code_desc pc_desc_smc[] = {
{0x1d, "Element address assignment"},
{0x1e, "Transport geometry parameters"},
{0x1f, "Device capabilities"},
};
static struct page_code_desc pc_desc_scc[] = {
{0x1b, "LUN mapping"},
};
static struct page_code_desc pc_desc_ses[] = {
{0x14, "Enclosure services management"},
};
struct page_code_desc * find_mode_page_table(int scsi_ptype, int * size)
{
switch (scsi_ptype)
{
case 0: /* disk (direct access) type devices */
case 4:
case 7:
case 0xe:
*size = sizeof(pc_desc_disk) / sizeof(pc_desc_disk[0]);
return &pc_desc_disk[0];
case 1: /* tape devices */
case 2:
*size = sizeof(pc_desc_tape) / sizeof(pc_desc_tape[0]);
return &pc_desc_tape[0];
case 5: /* cd/dvd devices */
*size = sizeof(pc_desc_cddvd) / sizeof(pc_desc_cddvd[0]);
return &pc_desc_cddvd[0];
case 8: /* medium changer devices */
*size = sizeof(pc_desc_smc) / sizeof(pc_desc_smc[0]);
return &pc_desc_smc[0];
case 0xc: /* storage array devices */
*size = sizeof(pc_desc_scc) / sizeof(pc_desc_scc[0]);
return &pc_desc_scc[0];
case 0xd: /* enclosure services devices */
*size = sizeof(pc_desc_ses) / sizeof(pc_desc_ses[0]);
return &pc_desc_ses[0];
}
*size = 0;
return NULL;
}
const char * find_page_code_desc(int page_num, int scsi_ptype)
{
int k;
int num;
const struct page_code_desc * pcdp;
pcdp = find_mode_page_table(scsi_ptype, &num);
if (pcdp) {
for (k = 0; k < num; ++k, ++pcdp) {
if (page_num == pcdp->page_code)
return pcdp->desc;
else if (page_num < pcdp->page_code)
break;
}
}
pcdp = &pc_desc_all[0];
num = sizeof(pc_desc_all) / sizeof(pc_desc_all[0]);
for (k = 0; k < num; ++k, ++pcdp) {
if (page_num == pcdp->page_code)
return pcdp->desc;
else if (page_num < pcdp->page_code)
break;
}
return NULL;
}
static void list_page_codes(int scsi_ptype)
{
int k;
int num = sizeof(pc_desc_all) / sizeof(pc_desc_all[0]);
const struct page_code_desc * pcdp = &pc_desc_all[0];
int num_ptype;
const struct page_code_desc * pcd_ptypep;
pcd_ptypep = find_mode_page_table(scsi_ptype, &num_ptype);
printf("Page_Code Description\n");
for (k = 0; k < 0x3f; ++k) {
if (pcd_ptypep && (num_ptype > 0)) {
if (k == pcd_ptypep->page_code) {
printf(" 0x%02x %s\n", pcd_ptypep->page_code,
pcd_ptypep->desc);
++pcd_ptypep;
--num_ptype;
continue;
} else if (k > pcd_ptypep->page_code) {
pcd_ptypep++;
--num_ptype;
}
}
if (pcdp && (num > 0)) {
if (k == pcdp->page_code) {
printf(" 0x%02x %s\n", pcdp->page_code, pcdp->desc);
++pcdp;
--num;
continue;
} else if (k > pcdp->page_code) {
pcdp++;
--num;
}
}
}
}
static const char * pg_control_str_arr[] = {
"current",
"changeable",
"default",
"saved"};
static void usage()
{
printf("Usage: 'sg_modes [-a] [-c=<page_control] [-d] [-h]"
" [-l] [-p=<page_number>]\n\t\t [-subp=<sub_page_code>] [-V] "
"[-6] [<sg_device>]'\n"
" where -a get all mode pages\n"
" -c=<page_control> page control (def: 0 [current],"
" 1 [changeable],\n 2 [default], 3 [saved])\n"
" -d disable block descriptors\n"
" -h output in hex\n"
" -l list common page codes\n"
" -p=<page_code> page code (in hex, def: 0)\n"
" -subp=<sub_page_code> (in hex, def: 0)\n"
" -V output version string\n"
" -6 Use MODE SENSE(6) instead of MODE SENSE(10)\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, len, md_len, bd_len, longlba, page_num;
char * file_name = 0;
char ebuff[EBUFF_SZ];
const char * descp;
unsigned char rsp_buff[MX_ALLOC_LEN];
int rsp_buff_size = MX_ALLOC_LEN;
unsigned int u;
int pg_code = 0;
int sub_pg_code = 0;
int pc = 0;
int do_all = 0;
int do_dbd = 0;
int do_hex = 0;
int do_mode6 = 0; /* Use MODE SENSE(6) instead of MODE SENSE(10) */
int do_list = 0;
int oflags = O_RDONLY | O_NONBLOCK;
struct sg_scsi_id a_sid;
int scsi_ptype, density_code_off;
unsigned char * ucp;
unsigned char uc;
for (k = 1; k < argc; ++k) {
if (0 == strncmp("-p=", argv[k], 3)) {
num = sscanf(argv[k] + 3, "%x", &u);
if ((1 != num) || (u > 63)) {
printf("Bad page code after '-p' switch\n");
file_name = 0;
break;
}
pg_code = u;
}
else if (0 == strncmp("-subp=", argv[k], 6)) {
num = sscanf(argv[k] + 6, "%x", &u);
if ((1 != num) || (u > 255)) {
printf("Bad sub page code after '-subp' switch\n");
file_name = 0;
break;
}
sub_pg_code = u;
}
else if (0 == strncmp("-c=", argv[k], 3)) {
num = sscanf(argv[k] + 3, "%x", &u);
if ((1 != num) || (u > 3)) {
printf("Bad page control after '-c' switch\n");
file_name = 0;
break;
}
pc = u;
}
else if (0 == strcmp("-d", argv[k]))
do_dbd = 1;
else if (0 == strcmp("-a", argv[k]))
do_all = 1;
else if (0 == strcmp("-h", argv[k]))
do_hex = 1;
else if (0 == strcmp("-6", argv[k]))
do_mode6 = 1;
else if (0 == strcmp("-l", argv[k]))
do_list = 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 (0 == file_name) {
if (do_list) {
printf("Assume 'disk' device type\n");
list_page_codes(0);
return 0;
}
usage();
return 1;
}
/* The 6 bytes command only allows up to 255 bytes of response data */
if(do_mode6) rsp_buff_size = 255;
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 (ioctl(sg_fd, SG_GET_SCSI_ID, &a_sid) < 0) {
unsigned char inqBuff[36];
if (do_simple_inq(sg_fd, inqBuff, sizeof(inqBuff), 1)) {
printf(ME "%s doesn't respond to a SCSI INQUIRY\n", file_name);
close(sg_fd);
return 1;
}
scsi_ptype = inqBuff[0] & 0x1f; /* fetch peripheral device type */
}
else
scsi_ptype = a_sid.scsi_type;
printf(" SCSI peripheral type: %s [0x%x] (from INQUIRY)\n",
get_ptype_str(scsi_ptype), scsi_ptype);
if (do_list) {
list_page_codes(scsi_ptype);
return 0;
}
if (do_all)
pg_code = PG_CODE_ALL;
if (0 == do_modes(sg_fd, do_dbd, pc, pg_code, sub_pg_code,
rsp_buff, rsp_buff_size, 1, do_mode6))
{
int medium_type, specific, headerlen;
printf("Mode parameter header from %s byte MODE SENSE:\n",
(do_mode6 ? "6" : "10"));
if(do_mode6) {
headerlen = 4;
if (do_hex)
dStrHex((const char *)rsp_buff, headerlen, 1);
md_len = rsp_buff[0]+1;
bd_len = rsp_buff[3];
medium_type = rsp_buff[1];
specific = rsp_buff[2];
longlba = 0; /* what is this field? */
} else {
headerlen = 8;
md_len = (rsp_buff[0] << 8) + rsp_buff[1] + 2;
bd_len = (rsp_buff[6] << 8) + rsp_buff[7];
medium_type = rsp_buff[2];
specific = rsp_buff[3];
longlba = rsp_buff[4] & 1;
}
if (do_hex)
dStrHex((const char *)rsp_buff, headerlen, 1);
printf(" Mode data length=%d, medium type=0x%.2x, specific"
" param=0x%.2x, longlba=%d\n", md_len, medium_type,
specific, longlba);
if (md_len > rsp_buff_size) {
printf("Only fetched %d bytes of response, truncate output\n",
rsp_buff_size);
md_len = rsp_buff_size;
if (bd_len + headerlen > rsp_buff_size)
bd_len = rsp_buff_size - headerlen;
}
printf(" Block descriptor length=%d\n", bd_len);
if (bd_len > 0) {
len = 8;
density_code_off = 0;
num = bd_len;
if (longlba) {
printf("> longlba block descriptors:\n");
len = 16;
density_code_off = 8;
}
else if (0 == scsi_ptype) {
printf("> Direct access device block descriptors:\n");
density_code_off = 4;
}
else
printf("> General mode parameter block descriptors:\n");
ucp = rsp_buff + headerlen;
while (num > 0) {
printf(" Density code=0x%x\n", *(ucp + density_code_off));
dStrHex((const char *)ucp, len, 1);
ucp += len;
num -= len;
}
printf("\n");
}
ucp = rsp_buff + bd_len + headerlen; /* start of mode page(s) */
md_len -= bd_len + headerlen; /* length of mode page(s) */
while (md_len > 0) { /* got mode page(s) */
uc = *ucp;
page_num = ucp[0] & 0x3f;
if (do_hex)
descp = NULL;
else {
descp = find_page_code_desc(page_num, scsi_ptype);
if (NULL == descp)
snprintf(ebuff, EBUFF_SZ, "vendor[0x%x]", page_num);
}
if (uc & 0x40) {
len = (ucp[2] << 8) + ucp[3] + 4;
if (do_hex)
printf(">> page_code=0x%x, subpage_code=0x%x, "
"page_control=%d\n", page_num, ucp[1], pc);
else
printf(">> page_code: %s, subpage_code=0x%x, "
"page_control: %s\n",
(descp ? descp: ebuff), ucp[1],
pg_control_str_arr[pc]);
}
else {
len = ucp[1] + 2;
if (do_hex)
printf(">> page_code=0x%x, page_control=%d\n", page_num,
pc);
else
printf(">> page_code: %s, page_control: %s\n",
(descp ? descp: ebuff), pg_control_str_arr[pc]);
}
dStrHex((const char *)ucp, len, 1);
ucp += len;
md_len -= len;
}
}
close(sg_fd);
return 0;
}