| #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 <sys/stat.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 INQUIRY command. |
| It is mainly based on the SCSI-3 SPC-2 document. |
| |
| Acknowledgment: |
| - Martin Schwenke <martin@meltin.net> added the raw switch and other |
| improvements [20020814] |
| |
| */ |
| |
| static char * version_str = "0.23 20030506"; |
| |
| |
| /* #define SG_DEBUG */ |
| |
| #define SENSE_BUFF_LEN 32 /* Arbitrary, could be larger */ |
| #define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */ |
| |
| #define INQUIRY_CMD 0x12 |
| #define INQUIRY_CMDLEN 6 |
| #define MX_ALLOC_LEN 255 |
| |
| #define EBUFF_SZ 256 |
| |
| #ifndef SCSI_IOCTL_GET_PCI |
| #define SCSI_IOCTL_GET_PCI 0x5387 |
| #endif |
| |
| /* Returns 0 when successful, else -1 */ |
| static int do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op, |
| 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; |
| |
| if (cmddt) |
| inqCmdBlk[1] |= 2; |
| if (evpd) |
| inqCmdBlk[1] |= 1; |
| inqCmdBlk[2] = (unsigned char)pg_op; |
| 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, CmdDt=%d, " |
| "EVPD=%d, page_opcode=%x ", cmddt, evpd, pg_op); |
| sg_chk_n_print3(ebuff, &io_hdr); |
| } |
| return -1; |
| } |
| } |
| |
| static void usage() |
| { |
| fprintf(stderr, |
| "Usage: 'sg_inq [-c] [-cl] [-e] [-h|-r] [-o=<opcode_page>] [-p]" |
| " [-V] [-36]\n [-?] <sg_device>'\n" |
| " where -c set CmdDt mode (use -o for opcode)\n" |
| " -cl list supported commands using CmdDt mode\n" |
| " -e set EVPD mode (use -o for page code)\n" |
| " -h output in hex (ASCII to the right)\n" |
| " -o=<opcode_page> opcode or page code in hex\n" |
| " -p output SCSI adapter PCI information\n" |
| " -r output raw binary data\n" |
| " -V output version string\n" |
| " -36 only perform a 36 byte INQUIRY\n" |
| " -? output this usage message\n" |
| " If no optional switches given (or '-h') then does" |
| " a standard INQUIRY\n"); |
| } |
| |
| |
| static void dStrRaw(const char* str, int len) |
| { |
| int i; |
| |
| for (i = 0 ; i < len; i++) { |
| printf("%c", str[i]); |
| } |
| } |
| |
| static void dStrHex(const char* str, int len) |
| { |
| 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 ((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, j, num, len, act_len; |
| int support_num; |
| char * file_name = 0; |
| char ebuff[EBUFF_SZ]; |
| char buff[MX_ALLOC_LEN + 1]; |
| unsigned char rsp_buff[MX_ALLOC_LEN + 1]; |
| unsigned int num_opcode = 0; |
| int do_evpd = 0; |
| int do_cmddt = 0; |
| int do_cmdlst = 0; |
| int do_hex = 0; |
| int do_raw = 0; |
| int do_pci = 0; |
| int do_36 = 0; |
| int oflags = O_RDONLY | O_NONBLOCK; |
| int ansi_version = 0; |
| int ret = 0; |
| |
| for (k = 1; k < argc; ++k) { |
| if (0 == strncmp("-o=", argv[k], 3)) { |
| num = sscanf(argv[k] + 3, "%x", &num_opcode); |
| if ((1 != num) || (num_opcode > 255)) { |
| fprintf(stderr, "Bad number after '-o' switch\n"); |
| file_name = 0; |
| break; |
| } |
| } |
| else if (0 == strcmp("-e", argv[k])) |
| do_evpd = 1; |
| else if (0 == strcmp("-h", argv[k])) |
| do_hex = 1; |
| else if (0 == strcmp("-r", argv[k])) |
| do_raw = 1; |
| else if (0 == strcmp("-cl", argv[k])) { |
| do_cmdlst = 1; |
| do_cmddt = 1; |
| } |
| else if (0 == strcmp("-c", argv[k])) |
| do_cmddt = 1; |
| else if (0 == strcmp("-p", argv[k])) |
| do_pci = 1; |
| else if (0 == strcmp("-36", argv[k])) |
| do_36 = 1; |
| else if (0 == strcmp("-?", argv[k])) { |
| file_name = 0; |
| break; |
| } |
| else if (0 == strcmp("-V", argv[k])) { |
| fprintf(stderr, "Version string: %s\n", version_str); |
| exit(0); |
| } |
| else if (*argv[k] == '-') { |
| fprintf(stderr, "Unrecognized switch: %s\n", argv[k]); |
| file_name = 0; |
| break; |
| } |
| else if (0 == file_name) |
| file_name = argv[k]; |
| else { |
| fprintf(stderr, "too many arguments\n"); |
| file_name = 0; |
| break; |
| } |
| } |
| |
| if (do_raw && do_hex) { |
| fprintf(stderr, "Can't do hex and raw at the same time\n"); |
| file_name = 0; |
| } |
| |
| if (0 == file_name) { |
| usage(); |
| return 1; |
| } |
| |
| if (do_pci) |
| oflags = O_RDWR | O_NONBLOCK; |
| if ((sg_fd = open(file_name, oflags)) < 0) { |
| snprintf(ebuff, EBUFF_SZ, "sg_inq: 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)) { |
| fprintf(stderr, |
| "sg_inq: %s doesn't seem to be a version 3 sg device\n", |
| file_name); |
| close(sg_fd); |
| return 1; |
| } |
| memset(rsp_buff, 0, MX_ALLOC_LEN + 1); |
| |
| if (! (do_cmddt || do_evpd)) { |
| if (!do_raw) |
| printf("standard INQUIRY:\n"); |
| if (num_opcode > 0) |
| printf(" <<given opcode or page_code is being ignored>>\n"); |
| |
| if (0 == do_inq(sg_fd, 0, 0, 0, rsp_buff, 36, 1)) { |
| len = rsp_buff[4] + 5; |
| ansi_version = rsp_buff[2] & 0x7; |
| if ((len > 36) && (len < 256) && (! do_36)) { |
| if (do_inq(sg_fd, 0, 0, 0, rsp_buff, len, 1)) { |
| fprintf(stderr, "second INQUIRY (%d byte) failed\n", len); |
| return 1; |
| } |
| if (len != (rsp_buff[4] + 5)) { |
| fprintf(stderr, |
| "strange, twin INQUIRYs yield different " |
| "'additional length'\n"); |
| ret = 2; |
| } |
| } |
| if (do_36) { |
| act_len = len; |
| len = 36; |
| } |
| else |
| act_len = len; |
| if (do_hex) |
| dStrHex((const char *)rsp_buff, len); |
| else if (do_raw) |
| dStrRaw((const char *)rsp_buff, len); |
| else { |
| printf(" PQual=%d, Device type=%d, RMB=%d, ANSI version=%d, ", |
| (rsp_buff[0] & 0xe0) >> 5, rsp_buff[0] & 0x1f, |
| !!(rsp_buff[1] & 0x80), ansi_version); |
| printf("[full version=0x%02x]\n", (unsigned int)rsp_buff[2]); |
| printf(" AERC=%d, TrmTsk=%d, NormACA=%d, HiSUP=%d, " |
| "Resp data format=%d, SCCS=%d\n", |
| !!(rsp_buff[3] & 0x80), !!(rsp_buff[3] & 0x40), |
| !!(rsp_buff[3] & 0x20), !!(rsp_buff[3] & 0x10), |
| rsp_buff[3] & 0x0f, !!(rsp_buff[5] & 0x80)); |
| printf(" BQue=%d, EncServ=%d, MultiP=%d, MChngr=%d, " |
| "ACKREQQ=%d, ", |
| !!(rsp_buff[6] & 0x80), !!(rsp_buff[6] & 0x40), |
| !!(rsp_buff[6] & 0x10), !!(rsp_buff[6] & 0x08), |
| !!(rsp_buff[6] & 0x04)); |
| printf("Addr16=%d\n RelAdr=%d, ", |
| !!(rsp_buff[6] & 0x01), |
| !!(rsp_buff[7] & 0x80)); |
| printf("WBus16=%d, Sync=%d, Linked=%d, TranDis=%d, ", |
| !!(rsp_buff[7] & 0x20), !!(rsp_buff[7] & 0x10), |
| !!(rsp_buff[7] & 0x08), !!(rsp_buff[7] & 0x04)); |
| printf("CmdQue=%d\n", !!(rsp_buff[7] & 0x02)); |
| if (len > 56) |
| printf(" Clocking=0x%x, QAS=%d, IUS=%d\n", |
| (rsp_buff[56] & 0x0c) >> 2, !!(rsp_buff[56] & 0x2), |
| !!(rsp_buff[56] & 0x1)); |
| if (act_len == len) |
| printf(" length=%d (0x%x)", len, len); |
| else |
| printf(" length=%d (0x%x), but only read 36 bytes", |
| len, len); |
| if ((ansi_version >= 2) && (len < 36)) |
| printf(" [for SCSI>=2, len>=36 is expected]\n"); |
| else |
| printf("\n"); |
| |
| if (len <= 8) |
| printf(" Inquiry response length=%d\n, no vendor, " |
| "product or revision data\n", len); |
| else { |
| if (len < 36) |
| rsp_buff[len] = '\0'; |
| memcpy(buff, &rsp_buff[8], 8); |
| buff[8] = '\0'; |
| printf(" Vendor identification: %s\n", buff); |
| if (len <= 16) |
| printf(" Product identification: <none>\n"); |
| else { |
| memcpy(buff, &rsp_buff[16], 16); |
| buff[16] = '\0'; |
| printf(" Product identification: %s\n", buff); |
| } |
| if (len <= 32) |
| printf(" Product revision level: <none>\n"); |
| else { |
| memcpy(buff, &rsp_buff[32], 4); |
| buff[4] = '\0'; |
| printf(" Product revision level: %s\n", buff); |
| } |
| } |
| } |
| if (!do_raw && |
| (0 == do_inq(sg_fd, 0, 1, 0x80, rsp_buff, MX_ALLOC_LEN, 0))) { |
| len = rsp_buff[3]; |
| if (len > 0) { |
| memcpy(buff, rsp_buff + 4, len); |
| buff[len] = '\0'; |
| printf(" Product serial number: %s\n", buff); |
| } |
| } |
| } |
| else { |
| printf("36 byte INQUIRY failed\n"); |
| return 1; |
| } |
| } |
| else if (do_cmddt) { |
| int reserved_cmddt; |
| char op_name[128]; |
| |
| if (do_cmdlst) { |
| printf("Supported command list:\n"); |
| for (k = 0; k < 256; ++k) { |
| if (0 == do_inq(sg_fd, 1, 0, k, rsp_buff, MX_ALLOC_LEN, 1)) { |
| support_num = rsp_buff[1] & 7; |
| reserved_cmddt = rsp_buff[4]; |
| if ((3 == support_num) || (5 == support_num)) { |
| num = rsp_buff[5]; |
| for (j = 0; j < num; ++j) |
| printf(" %.2x", (int)rsp_buff[6 + j]); |
| if (5 == support_num) |
| printf(" [vendor specific manner (5)]"); |
| sg_get_command_name((unsigned char)k, |
| sizeof(op_name) - 1, op_name); |
| op_name[sizeof(op_name) - 1] = '\0'; |
| printf(" %s\n", op_name); |
| } |
| else if ((4 == support_num) || (6 == support_num)) |
| printf(" opcode=0x%.2x vendor specific (%d)\n", |
| k, support_num); |
| else if ((0 == support_num) && (reserved_cmddt > 0)) { |
| printf(" opcode=0x%.2x ignored cmddt bit, " |
| "given standard INQUIRY response, stop\n", k); |
| break; |
| } |
| } |
| else { |
| fprintf(stderr, |
| "CmdDt INQUIRY on opcode=0x%.2x: failed\n", k); |
| break; |
| } |
| } |
| } |
| else { |
| if (! do_raw) { |
| printf("CmdDt INQUIRY, opcode=0x%.2x: [", num_opcode); |
| sg_get_command_name((unsigned char)num_opcode, |
| sizeof(op_name) - 1, op_name); |
| op_name[sizeof(op_name) - 1] = '\0'; |
| printf("%s]\n", op_name); |
| } |
| if (0 == do_inq(sg_fd, 1, 0, num_opcode, rsp_buff, |
| MX_ALLOC_LEN, 1)) { |
| len = rsp_buff[5] + 6; |
| reserved_cmddt = rsp_buff[4]; |
| if (do_hex) |
| dStrHex((const char *)rsp_buff, len); |
| else if (do_raw) |
| dStrRaw((const char *)rsp_buff, len); |
| else { |
| const char * desc_p; |
| int prnt_cmd = 0; |
| |
| support_num = rsp_buff[1] & 7; |
| num = rsp_buff[5]; |
| switch (support_num) { |
| case 0: |
| if (0 == reserved_cmddt) |
| desc_p = "no data available"; |
| else |
| desc_p = "ignored cmddt bit, standard INQUIRY " |
| "response"; |
| break; |
| case 1: desc_p = "not supported"; break; |
| case 2: desc_p = "reserved (2)"; break; |
| case 3: desc_p = "supported as per standard"; |
| prnt_cmd = 1; |
| break; |
| case 4: desc_p = "vendor specific (4)"; break; |
| case 5: desc_p = "supported in vendor specific way"; |
| prnt_cmd = 1; |
| break; |
| case 6: desc_p = "vendor specific (6)"; break; |
| case 7: desc_p = "reserved (7)"; break; |
| default: desc_p = "impossible value > 7"; break; |
| } |
| if (prnt_cmd) { |
| printf(" Support field: %s [", desc_p); |
| for (j = 0; j < num; ++j) |
| printf(" %.2x", (int)rsp_buff[6 + j]); |
| printf(" ]\n"); |
| } else |
| printf(" Support field: %s\n", desc_p); |
| } |
| } |
| else { |
| fprintf(stderr, |
| "CmdDt INQUIRY on opcode=0x%.2x: failed\n", |
| num_opcode); |
| return 1; |
| } |
| |
| } |
| } |
| else if (do_evpd) { |
| if (!do_raw) |
| printf("EVPD INQUIRY, page code=0x%.2x:\n", num_opcode); |
| if (0 == do_inq(sg_fd, 0, 1, num_opcode, rsp_buff, MX_ALLOC_LEN, 1)) { |
| len = rsp_buff[3] + 4; |
| if (num_opcode != rsp_buff[1]) |
| printf("non evpd respone; probably a STANDARD INQUIRY " |
| "response\n"); |
| else if (do_raw) |
| dStrRaw((const char *)rsp_buff, len); |
| else { |
| if (! do_hex) |
| printf(" Only hex output supported\n"); |
| dStrHex((const char *)rsp_buff, len); |
| } |
| } |
| else { |
| fprintf(stderr, |
| "EVPD INQUIRY, page code=0x%.2x: failed\n", num_opcode); |
| return 1; |
| } |
| } |
| |
| if (do_pci) { |
| unsigned char slot_name[16]; |
| |
| printf("\n"); |
| memset(slot_name, '\0', sizeof(slot_name)); |
| if (ioctl(sg_fd, SCSI_IOCTL_GET_PCI, slot_name) < 0) { |
| if (EINVAL == errno) |
| printf("ioctl(SCSI_IOCTL_GET_PCI) not supported by this " |
| "kernel\n"); |
| else if (ENXIO == errno) |
| printf("associated adapter not a PCI device?\n"); |
| else |
| perror("ioctl(SCSI_IOCTL_GET_PCI) failed"); |
| } |
| else |
| printf("PCI:slot_name: %s\n", slot_name); |
| } |
| |
| close(sg_fd); |
| return ret; |
| } |