| /* |
| * (c) 2000 Kurt Garloff <garloff at suse dot de> |
| * heavily based on Douglas Gilbert's sg_rbuf program. |
| * (c) 1999-2015 Douglas Gilbert |
| * |
| * Program to test the SCSI host adapter by issueing |
| * write and read operations on a device's buffer |
| * and calculating checksums. |
| * NOTE: If you can not reserve the buffer of the device |
| * for this purpose (SG_GET_RESERVED_SIZE), you risk |
| * serious data corruption, if the device is accessed by |
| * somebody else in the meantime. |
| * |
| * 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. |
| * |
| * $Id: sg_test_rwbuf.c,v 1.1 2000/03/02 13:50:03 garloff Exp $ |
| * |
| * 2003/11/11 switch sg3_utils version to use SG_IO ioctl [dpg] |
| * 2004/06/08 remove SG_GET_VERSION_NUM check [dpg] |
| */ |
| |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <getopt.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <time.h> |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| #include "sg_lib.h" |
| #include "sg_io_linux.h" |
| #include "sg_unaligned.h" |
| #include "sg_pr2serr.h" |
| |
| |
| static const char * version_str = "1.09 20151220"; |
| |
| #define BPI (signed)(sizeof(int)) |
| |
| #define RB_MODE_DESC 3 |
| #define RWB_MODE_DATA 2 |
| #define RB_DESC_LEN 4 |
| |
| /* The microcode in a SCSI device is _not_ modified by doing a WRITE BUFFER |
| * with mode set to "data" (0x2) as done by this utility. Therefore this |
| * utility is safe in that respect. [Mode values 0x4, 0x5, 0x6 and 0x7 are |
| * the dangerous ones :-)] |
| */ |
| |
| #define ME "sg_test_rwbuf: " |
| |
| static int base = 0x12345678; |
| static int buf_capacity = 0; |
| static int buf_granul = 255; |
| static unsigned char *cmpbuf = NULL; |
| |
| |
| /* Options */ |
| static int size = -1; |
| static char do_quick = 0; |
| static int addwrite = 0; |
| static int addread = 0; |
| static int verbose = 0; |
| |
| static struct option long_options[] = { |
| {"help", 0, 0, 'h'}, |
| {"quick", 0, 0, 'q'}, |
| {"addrd", 1, 0, 'r'}, |
| {"size", 1, 0, 's'}, |
| {"times", 1, 0, 't'}, |
| {"verbose", 0, 0, 'v'}, |
| {"version", 0, 0, 'V'}, |
| {"addwr", 1, 0, 'w'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| int find_out_about_buffer (int sg_fd) |
| { |
| unsigned char rbCmdBlk[] = {READ_BUFFER, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
| unsigned char rbBuff[RB_DESC_LEN]; |
| unsigned char sense_buffer[32]; |
| struct sg_io_hdr io_hdr; |
| int k, res; |
| |
| rbCmdBlk[1] = RB_MODE_DESC; |
| rbCmdBlk[8] = RB_DESC_LEN; |
| memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); |
| io_hdr.interface_id = 'S'; |
| io_hdr.cmd_len = sizeof(rbCmdBlk); |
| io_hdr.mx_sb_len = sizeof(sense_buffer); |
| io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; |
| io_hdr.dxfer_len = RB_DESC_LEN; |
| io_hdr.dxferp = rbBuff; |
| io_hdr.cmdp = rbCmdBlk; |
| io_hdr.sbp = sense_buffer; |
| io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */ |
| |
| if (verbose) { |
| pr2serr(" read buffer [mode desc] cdb: "); |
| for (k = 0; k < (int)sizeof(rbCmdBlk); ++k) |
| pr2serr("%02x ", rbCmdBlk[k]); |
| pr2serr("\n"); |
| } |
| if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { |
| perror(ME "SG_IO READ BUFFER descriptor error"); |
| return -1; |
| } |
| /* now for the error processing */ |
| res = sg_err_category3(&io_hdr); |
| switch (res) { |
| case SG_LIB_CAT_RECOVERED: |
| sg_chk_n_print3("READ BUFFER descriptor, continuing", |
| &io_hdr, 1); |
| /* fall through */ |
| case SG_LIB_CAT_CLEAN: |
| break; |
| default: /* won't bother decoding other categories */ |
| sg_chk_n_print3("READ BUFFER descriptor error", &io_hdr, 1); |
| return res; |
| } |
| |
| buf_capacity = sg_get_unaligned_be24(rbBuff + 1); |
| buf_granul = (unsigned char)rbBuff[0]; |
| #if 0 |
| printf("READ BUFFER reports: %02x %02x %02x %02x\n", |
| rbBuff[0], rbBuff[1], rbBuff[2], rbBuff[3]); |
| #endif |
| if (verbose) |
| printf("READ BUFFER reports: buffer capacity=%d, offset " |
| "boundary=%d\n", buf_capacity, buf_granul); |
| return 0; |
| } |
| |
| int mymemcmp (unsigned char *bf1, unsigned char *bf2, int len) |
| { |
| int df; |
| for (df = 0; df < len; df++) |
| if (bf1[df] != bf2[df]) return df; |
| return 0; |
| } |
| |
| /* return 0 if good, else 2222 */ |
| int do_checksum (int *buf, int len, int quiet) |
| { |
| int sum = base; |
| int i; int rln = len; |
| for (i = 0; i < len/BPI; i++) |
| sum += buf[i]; |
| while (rln%BPI) sum += ((char*)buf)[--rln]; |
| if (sum != 0x12345678) { |
| if (!quiet) printf ("sg_test_rwbuf: Checksum error (sz=%i):" |
| " %08x\n", len, sum); |
| if (cmpbuf && !quiet) { |
| int diff = mymemcmp (cmpbuf, (unsigned char*)buf, |
| len); |
| printf ("Differ at pos %i/%i:\n", diff, len); |
| for (i = 0; i < 24 && i+diff < len; i++) |
| printf (" %02x", cmpbuf[i+diff]); |
| printf ("\n"); |
| for (i = 0; i < 24 && i+diff < len; i++) |
| printf (" %02x", |
| ((unsigned char*)buf)[i+diff]); |
| printf ("\n"); |
| } |
| return 2222; |
| } |
| else { |
| if (verbose > 1) |
| printf("Checksum value: 0x%x\n", sum); |
| return 0; |
| } |
| } |
| |
| void do_fill_buffer (int *buf, int len) |
| { |
| int sum; |
| int i; int rln = len; |
| srand (time (0)); |
| retry: |
| if (len >= BPI) |
| base = 0x12345678 + rand (); |
| else |
| base = 0x12345678 + (char) rand (); |
| sum = base; |
| for (i = 0; i < len/BPI - 1; i++) |
| { |
| /* we rely on rand() giving full range of int */ |
| buf[i] = rand (); |
| sum += buf[i]; |
| } |
| while (rln%BPI) |
| { |
| ((char*)buf)[--rln] = rand (); |
| sum += ((char*)buf)[rln]; |
| } |
| if (len >= BPI) buf[len/BPI - 1] = 0x12345678 - sum; |
| else ((char*)buf)[0] = 0x12345678 + ((char*)buf)[0] - sum; |
| if (do_checksum (buf, len, 1)) { |
| if (len < BPI) goto retry; |
| printf ("sg_test_rwbuf: Memory corruption?\n"); |
| exit (1); |
| } |
| if (cmpbuf) memcpy (cmpbuf, (char*)buf, len); |
| } |
| |
| |
| int read_buffer (int sg_fd, unsigned size) |
| { |
| int res, k; |
| unsigned char rbCmdBlk[] = {READ_BUFFER, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
| int bufSize = size + addread; |
| unsigned char * rbBuff = (unsigned char *)malloc(bufSize); |
| unsigned char sense_buffer[32]; |
| struct sg_io_hdr io_hdr; |
| |
| if (NULL == rbBuff) |
| return -1; |
| rbCmdBlk[1] = RWB_MODE_DATA; |
| sg_put_unaligned_be24((uint32_t)bufSize, rbCmdBlk + 6); |
| memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); |
| io_hdr.interface_id = 'S'; |
| io_hdr.cmd_len = sizeof(rbCmdBlk); |
| io_hdr.mx_sb_len = sizeof(sense_buffer); |
| io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; |
| io_hdr.dxfer_len = bufSize; |
| io_hdr.dxferp = rbBuff; |
| io_hdr.cmdp = rbCmdBlk; |
| io_hdr.sbp = sense_buffer; |
| io_hdr.pack_id = 2; |
| io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */ |
| if (verbose) { |
| pr2serr(" read buffer [mode data] cdb: "); |
| for (k = 0; k < (int)sizeof(rbCmdBlk); ++k) |
| pr2serr("%02x ", rbCmdBlk[k]); |
| pr2serr("\n"); |
| } |
| |
| if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { |
| perror(ME "SG_IO READ BUFFER data error"); |
| free(rbBuff); |
| return -1; |
| } |
| /* now for the error processing */ |
| res = sg_err_category3(&io_hdr); |
| switch (res) { |
| case SG_LIB_CAT_RECOVERED: |
| sg_chk_n_print3("READ BUFFER data, continuing", &io_hdr, 1); |
| /* fall through */ |
| case SG_LIB_CAT_CLEAN: |
| break; |
| default: /* won't bother decoding other categories */ |
| sg_chk_n_print3("READ BUFFER data error", &io_hdr, 1); |
| free(rbBuff); |
| return res; |
| } |
| |
| res = do_checksum ((int*)rbBuff, size, 0); |
| free(rbBuff); |
| return res; |
| } |
| |
| int write_buffer (int sg_fd, unsigned size) |
| { |
| unsigned char wbCmdBlk[] = {WRITE_BUFFER, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
| int bufSize = size + addwrite; |
| unsigned char * wbBuff = (unsigned char *)malloc(bufSize); |
| unsigned char sense_buffer[32]; |
| struct sg_io_hdr io_hdr; |
| int k, res; |
| |
| if (NULL == wbBuff) |
| return -1; |
| memset(wbBuff, 0, bufSize); |
| do_fill_buffer ((int*)wbBuff, size); |
| wbCmdBlk[1] = RWB_MODE_DATA; |
| sg_put_unaligned_be24((uint32_t)bufSize, wbCmdBlk + 6); |
| memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); |
| io_hdr.interface_id = 'S'; |
| io_hdr.cmd_len = sizeof(wbCmdBlk); |
| io_hdr.mx_sb_len = sizeof(sense_buffer); |
| io_hdr.dxfer_direction = SG_DXFER_TO_DEV; |
| io_hdr.dxfer_len = bufSize; |
| io_hdr.dxferp = wbBuff; |
| io_hdr.cmdp = wbCmdBlk; |
| io_hdr.sbp = sense_buffer; |
| io_hdr.pack_id = 1; |
| io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */ |
| if (verbose) { |
| pr2serr(" write buffer [mode data] cdb: "); |
| for (k = 0; k < (int)sizeof(wbCmdBlk); ++k) |
| pr2serr("%02x ", wbCmdBlk[k]); |
| pr2serr("\n"); |
| } |
| |
| if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { |
| perror(ME "SG_IO WRITE BUFFER data error"); |
| free(wbBuff); |
| return -1; |
| } |
| /* now for the error processing */ |
| res = sg_err_category3(&io_hdr); |
| switch (res) { |
| case SG_LIB_CAT_RECOVERED: |
| sg_chk_n_print3("WRITE BUFFER data, continuing", &io_hdr, 1); |
| /* fall through */ |
| case SG_LIB_CAT_CLEAN: |
| break; |
| default: /* won't bother decoding other categories */ |
| sg_chk_n_print3("WRITE BUFFER data error", &io_hdr, 1); |
| free(wbBuff); |
| return res; |
| } |
| free(wbBuff); |
| return res; |
| } |
| |
| void usage () |
| { |
| printf ("Usage: sg_test_rwbuf [--addrd=AR] [--addwr=AW] [--help] " |
| "[--quick]\n"); |
| printf (" --size=SZ [--times=NUM] [--verbose] " |
| "[--version]\n" |
| " DEVICE\n" |
| " or\n" |
| " sg_test_rwbuf DEVICE SZ [AW] [AR]\n"); |
| printf (" where:\n" |
| " --addrd=AR extra bytes to fetch during READ " |
| "BUFFER\n" |
| " --addwr=AW extra bytes to send to WRITE BUFFER\n" |
| " --help output this usage message then exit\n" |
| " --quick output read buffer size then exit\n" |
| " --size=SZ size of buffer (in bytes) to write " |
| "then read back\n" |
| " --times=NUM number of times to run test " |
| "(default 1)\n" |
| " --verbose increase verbosity of output\n" |
| " --version output version then exit\n"); |
| printf ("\nWARNING: If you access the device at the same time, e.g. " |
| "because it's a\n"); |
| printf (" mounted hard disk, the device's buffer may be used by the " |
| "device itself\n"); |
| printf (" for other data at the same time, and overwriting it may or " |
| "may not\n"); |
| printf (" cause data corruption!\n"); |
| printf ("(c) Douglas Gilbert, Kurt Garloff, 2000-2007, GNU GPL\n"); |
| } |
| |
| |
| int main (int argc, char * argv[]) |
| { |
| int sg_fd, res; |
| const char * device_name = NULL; |
| int times = 1; |
| int ret = 0; |
| int k = 0; |
| |
| while (1) { |
| int option_index = 0; |
| int c; |
| |
| c = getopt_long(argc, argv, "hqr:s:t:w:vV", |
| long_options, &option_index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'h': |
| usage(); |
| return 0; |
| case 'q': |
| do_quick = 1; |
| break; |
| case 'r': |
| addread = sg_get_num(optarg); |
| if (-1 == addread) { |
| pr2serr("bad argument to '--addrd'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 's': |
| size = sg_get_num(optarg); |
| if (-1 == size) { |
| pr2serr("bad argument to '--size'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 't': |
| times = sg_get_num(optarg); |
| if (-1 == times) { |
| pr2serr("bad argument to '--times'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| case 'v': |
| verbose++; |
| break; |
| case 'V': |
| pr2serr(ME "version: %s\n", version_str); |
| return 0; |
| case 'w': |
| addwrite = sg_get_num(optarg); |
| if (-1 == addwrite) { |
| pr2serr("bad argument to '--addwr'\n"); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| break; |
| default: |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| if (optind < argc) { |
| if (NULL == device_name) { |
| device_name = argv[optind]; |
| ++optind; |
| } |
| } |
| if (optind < argc) { |
| if (-1 == size) { |
| size = sg_get_num(argv[optind]); |
| if (-1 == size) { |
| pr2serr("bad <sz>\n"); |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (++optind < argc) { |
| addwrite = sg_get_num(argv[optind]); |
| if (-1 == addwrite) { |
| pr2serr("bad [addwr]\n"); |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if (++optind < argc) { |
| addread = sg_get_num(argv[optind]); |
| if (-1 == addread) { |
| pr2serr("bad [addrd]\n"); |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| } |
| |
| } |
| if (optind < argc) { |
| for (; optind < argc; ++optind) |
| pr2serr("Unexpected extra argument" ": %s\n", |
| argv[optind]); |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| } |
| if (NULL == device_name) { |
| pr2serr("no device name given\n"); |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| if ((size <= 0) && (! do_quick)) { |
| pr2serr("must give '--size' or '--quick' options or <sz> " |
| "argument\n"); |
| usage(); |
| return SG_LIB_SYNTAX_ERROR; |
| } |
| |
| sg_fd = open(device_name, O_RDWR | O_NONBLOCK); |
| if (sg_fd < 0) { |
| perror("sg_test_rwbuf: open error"); |
| return SG_LIB_FILE_ERROR; |
| } |
| ret = find_out_about_buffer(sg_fd); |
| if (ret) |
| goto err_out; |
| if (do_quick) { |
| printf ("READ BUFFER read descriptor reports a buffer " |
| "of %d bytes [%d KiB]\n", buf_capacity, |
| buf_capacity / 1024); |
| goto err_out; |
| } |
| if (size > buf_capacity) { |
| pr2serr (ME "sz=%i > buf_capacity=%i\n", size, buf_capacity); |
| ret = SG_LIB_CAT_OTHER; |
| goto err_out; |
| } |
| |
| cmpbuf = (unsigned char *)malloc(size); |
| for (k = 0; k < times; ++k) { |
| ret = write_buffer (sg_fd, size); |
| if (ret) { |
| goto err_out; |
| } |
| ret = read_buffer (sg_fd, size); |
| if (ret) { |
| if (2222 == ret) |
| ret = SG_LIB_CAT_MALFORMED; |
| goto err_out; |
| } |
| } |
| |
| err_out: |
| if (cmpbuf) |
| free(cmpbuf); |
| res = close(sg_fd); |
| if (res < 0) { |
| perror(ME "close error"); |
| if (0 == ret) |
| ret = SG_LIB_FILE_ERROR; |
| } |
| if ((0 == ret) && (! do_quick)) |
| printf ("Success\n"); |
| else if (times > 1) |
| printf ("Failed after %d successful cycles\n", k); |
| return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; |
| } |