aboutsummaryrefslogtreecommitdiff
path: root/src/sg_rbuf.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_rbuf.c')
-rw-r--r--src/sg_rbuf.c688
1 files changed, 688 insertions, 0 deletions
diff --git a/src/sg_rbuf.c b/src/sg_rbuf.c
new file mode 100644
index 00000000..d37cc259
--- /dev/null
+++ b/src/sg_rbuf.c
@@ -0,0 +1,688 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 1999-2022 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.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program uses the SCSI command READ BUFFER on the given
+ * device, first to find out how big it is and then to read that
+ * buffer (data mode, buffer id 0).
+ */
+
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/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"
+
+#define RB_MODE_DESC 3
+#define RB_MODE_DATA 2
+#define RB_MODE_ECHO_DESC 0xb
+#define RB_MODE_ECHO_DATA 0xa
+#define RB_DESC_LEN 4
+#define RB_DEF_SIZE (200*1024*1024)
+#define RB_OPCODE 0x3C
+#define RB_CMD_LEN 10
+
+#ifndef SG_FLAG_MMAP_IO
+#define SG_FLAG_MMAP_IO 4
+#endif
+
+
+static const char * version_str = "5.09 20220425";
+
+static struct option long_options[] = {
+ {"buffer", required_argument, 0, 'b'},
+ {"dio", no_argument, 0, 'd'},
+ {"echo", no_argument, 0, 'e'},
+ {"help", no_argument, 0, 'h'},
+ {"mmap", no_argument, 0, 'm'},
+ {"new", no_argument, 0, 'N'},
+ {"old", no_argument, 0, 'O'},
+ {"quick", no_argument, 0, 'q'},
+ {"size", required_argument, 0, 's'},
+ {"time", no_argument, 0, 't'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+struct opts_t {
+ bool do_dio;
+ bool do_echo;
+ bool do_mmap;
+ bool do_quick;
+ bool do_time;
+ bool verbose_given;
+ bool version_given;
+ bool opt_new;
+ int do_buffer;
+ int do_help;
+ int verbose;
+ int64_t do_size;
+ const char * device_name;
+};
+
+
+static void
+usage()
+{
+ pr2serr("Usage: sg_rbuf [--buffer=EACH] [--dio] [--echo] "
+ "[--help] [--mmap]\n"
+ " [--quick] [--size=OVERALL] [--time] [--verbose] "
+ "[--version]\n"
+ " SG_DEVICE\n");
+ pr2serr(" where:\n"
+ " --buffer=EACH|-b EACH buffer size to use (in bytes)\n"
+ " --dio|-d requests dio ('-q' overrides it)\n"
+ " --echo|-e use echo buffer (def: use data mode)\n"
+ " --help|-h print usage message then exit\n"
+ " --mmap|-m requests mmap-ed IO (overrides -q, -d)\n"
+ " --quick|-q quick, don't xfer to user space\n");
+ pr2serr(" --size=OVERALL|-s OVERALL total size to read (in bytes)\n"
+ " default: 200 MiB\n"
+ " --time|-t time the data transfer\n"
+ " --verbose|-v increase verbosity (more debug)\n"
+ " --old|-O use old interface (use as first option)\n"
+ " --version|-V print version string then exit\n\n"
+ "Use SCSI READ BUFFER command (data or echo buffer mode, buffer "
+ "id 0)\nrepeatedly. This utility only works with Linux sg "
+ "devices.\n");
+}
+
+static void
+usage_old()
+{
+ printf("Usage: sg_rbuf [-b=EACH_KIB] [-d] [-m] [-q] [-s=OVERALL_MIB] "
+ "[-t] [-v] [-V]\n SG_DEVICE\n");
+ printf(" where:\n");
+ printf(" -b=EACH_KIB num is buffer size to use (in KiB)\n");
+ printf(" -d requests dio ('-q' overrides it)\n");
+ printf(" -e use echo buffer (def: use data mode)\n");
+ printf(" -m requests mmap-ed IO (overrides -q, -d)\n");
+ printf(" -q quick, don't xfer to user space\n");
+ printf(" -s=OVERALL_MIB num is total size to read (in MiB) "
+ "(default: 200 MiB)\n");
+ printf(" maximum total size is 4000 MiB\n");
+ printf(" -t time the data transfer\n");
+ printf(" -v increase verbosity (more debug)\n");
+ printf(" -N|--new use new interface\n");
+ printf(" -V print version string then exit\n\n");
+ printf("Use SCSI READ BUFFER command (data or echo buffer mode, buffer "
+ "id 0)\nrepeatedly. This utility only works with Linux sg "
+ "devices.\n");
+}
+
+static void
+usage_for(const struct opts_t * op)
+{
+ if (op->opt_new)
+ usage();
+ else
+ usage_old();
+}
+
+static int
+new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int c, n;
+ int64_t nn;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "b:dehmNOqs:tvV", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'b':
+ n = sg_get_num(optarg);
+ if (n < 0) {
+ pr2serr("bad argument to '--buffer'\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_buffer = n;
+ break;
+ case 'd':
+ op->do_dio = true;
+ break;
+ case 'e':
+ op->do_echo = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'm':
+ op->do_mmap = true;
+ break;
+ case 'N':
+ break; /* ignore */
+ case 'O':
+ op->opt_new = false;
+ return 0;
+ case 'q':
+ op->do_quick = true;
+ break;
+ case 's':
+ nn = sg_get_llnum(optarg);
+ if (nn < 0) {
+ pr2serr("bad argument to '--size'\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_size = nn;
+ break;
+ case 't':
+ op->do_time = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ pr2serr("unrecognised option code %c [0x%x]\n", c, c);
+ if (op->do_help)
+ break;
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == op->device_name) {
+ op->device_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ pr2serr("Unexpected extra argument: %s\n", argv[optind]);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ bool jmp_out;
+ int k, plen, num;
+ int64_t nn;
+ const char * cp;
+
+ for (k = 1; k < argc; ++k) {
+ cp = argv[k];
+ plen = strlen(cp);
+ if (plen <= 0)
+ continue;
+ if ('-' == *cp) {
+ for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case 'd':
+ op->do_dio = true;
+ break;
+ case 'e':
+ op->do_echo = true;
+ break;
+ case 'h':
+ case '?':
+ ++op->do_help;
+ break;
+ case 'm':
+ op->do_mmap = true;
+ break;
+ case 'N':
+ op->opt_new = true;
+ return 0;
+ case 'O':
+ break;
+ case 'q':
+ op->do_quick = true;
+ break;
+ case 't':
+ op->do_time = true;
+ break;
+ case 'v':
+ op->verbose_given = true;
+ ++op->verbose;
+ break;
+ case 'V':
+ op->version_given = true;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (0 == strncmp("b=", cp, 2)) {
+ num = sscanf(cp + 2, "%d", &op->do_buffer);
+ if ((1 != num) || (op->do_buffer <= 0)) {
+ printf("Couldn't decode number after 'b=' option\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_buffer *= 1024;
+ }
+ else if (0 == strncmp("s=", cp, 2)) {
+ nn = sg_get_llnum(optarg);
+ if (nn < 0) {
+ printf("Couldn't decode number after 's=' option\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ op->do_size = nn;
+ op->do_size *= 1024 * 1024;
+ } else if (0 == strncmp("-old", cp, 4))
+ ;
+ else if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (0 == op->device_name)
+ op->device_name = cp;
+ else {
+ pr2serr("too many arguments, got: %s, not expecting: %s\n",
+ op->device_name, cp);
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ return 0;
+}
+
+static int
+parse_cmd_line(struct opts_t * op, int argc, char * argv[])
+{
+ int res;
+ char * cp;
+
+ cp = getenv("SG3_UTILS_OLD_OPTS");
+ if (cp) {
+ op->opt_new = false;
+ res = old_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && op->opt_new)
+ res = new_parse_cmd_line(op, argc, argv);
+ } else {
+ op->opt_new = true;
+ res = new_parse_cmd_line(op, argc, argv);
+ if ((0 == res) && (! op->opt_new))
+ res = old_parse_cmd_line(op, argc, argv);
+ }
+ return res;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+#ifdef DEBUG
+ bool clear = true;
+#endif
+ bool dio_incomplete = false;
+ int sg_fd, res, err;
+ int buf_capacity = 0;
+ int buf_size = 0;
+ size_t psz;
+ unsigned int k, num;
+ int64_t total_size = RB_DEF_SIZE;
+ struct opts_t * op;
+ uint8_t * rbBuff = NULL;
+ void * rawp = NULL;
+ uint8_t sense_buffer[32] SG_C_CPP_ZERO_INIT;
+ uint8_t rb_cdb [RB_CMD_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+ struct timeval start_tm, end_tm;
+ struct opts_t opts;
+
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+ psz = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
+#else
+ psz = 4096; /* give up, pick likely figure */
+#endif
+ op = &opts;
+ memset(op, 0, sizeof(opts));
+ res = parse_cmd_line(op, argc, argv);
+ if (res)
+ return SG_LIB_SYNTAX_ERROR;
+ if (op->do_help) {
+ usage_for(op);
+ return 0;
+ }
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (op->verbose_given && op->version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ op->verbose_given = false;
+ op->version_given = false;
+ op->verbose = 0;
+ } else if (! op->verbose_given) {
+ pr2serr("set '-vv'\n");
+ op->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", op->verbose);
+#else
+ if (op->verbose_given && op->version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (op->version_given) {
+ pr2serr("Version string: %s\n", version_str);
+ return 0;
+ }
+
+ if (NULL == op->device_name) {
+ pr2serr("No DEVICE argument given\n\n");
+ usage_for(op);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ if (op->do_buffer > 0)
+ buf_size = op->do_buffer;
+ if (op->do_size > 0)
+ total_size = op->do_size;
+
+ sg_fd = open(op->device_name, O_RDONLY | O_NONBLOCK);
+ if (sg_fd < 0) {
+ err = errno;
+ perror("device open error");
+ return sg_convert_errno(err);
+ }
+ if (op->do_mmap) {
+ op->do_dio = false;
+ op->do_quick = false;
+ }
+ if (NULL == (rawp = malloc(512))) {
+ printf("out of memory (query)\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ rbBuff = (uint8_t *)rawp;
+
+ rb_cdb[0] = RB_OPCODE;
+ rb_cdb[1] = op->do_echo ? RB_MODE_ECHO_DESC : RB_MODE_DESC;
+ rb_cdb[8] = RB_DESC_LEN;
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rb_cdb);
+ 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 = rb_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 60000; /* 60000 millisecs == 60 seconds */
+ if (op->verbose) {
+ char b[128];
+
+ pr2serr(" Read buffer (%sdescriptor) cdb: %s\n",
+ (op->do_echo ? "echo " : ""),
+ sg_get_command_str(rb_cdb, RB_CMD_LEN, false, sizeof(b), b));
+ }
+
+ /* do normal IO to find RB size (not dio or mmap-ed at this stage) */
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ perror("SG_IO READ BUFFER descriptor error");
+ if (rawp)
+ free(rawp);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (op->verbose > 2)
+ pr2serr(" duration=%u ms\n", io_hdr.duration);
+ /* 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,
+ op->verbose > 1);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CLEAN:
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("READ BUFFER descriptor error", &io_hdr,
+ op->verbose > 1);
+ if (rawp) free(rawp);
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+ }
+
+ if (op->do_echo) {
+ buf_capacity = 0x1fff & sg_get_unaligned_be16(rbBuff + 2);
+ printf("READ BUFFER reports: echo buffer capacity=%d\n",
+ buf_capacity);
+ } else {
+ buf_capacity = sg_get_unaligned_be24(rbBuff + 1);
+ printf("READ BUFFER reports: buffer capacity=%d, offset "
+ "boundary=%d\n", buf_capacity, (int)rbBuff[0]);
+ }
+
+ if (0 == buf_size)
+ buf_size = buf_capacity;
+ else if (buf_size > buf_capacity) {
+ printf("Requested buffer size=%d exceeds reported capacity=%d\n",
+ buf_size, buf_capacity);
+ if (rawp) free(rawp);
+ return SG_LIB_CAT_MALFORMED;
+ }
+ if (rawp) {
+ free(rawp);
+ rawp = NULL;
+ }
+
+ if (! op->do_dio) {
+ k = buf_size;
+ if (op->do_mmap && (0 != (k % psz)))
+ k = ((k / psz) + 1) * psz; /* round up to page size */
+ res = ioctl(sg_fd, SG_SET_RESERVED_SIZE, &k);
+ if (res < 0)
+ perror("SG_SET_RESERVED_SIZE error");
+ }
+
+ if (op->do_mmap) {
+ rbBuff = (uint8_t *)mmap(NULL, buf_size, PROT_READ, MAP_SHARED,
+ sg_fd, 0);
+ if (MAP_FAILED == rbBuff) {
+ if (ENOMEM == errno) {
+ pr2serr("mmap() out of memory, try a smaller buffer size "
+ "than %d bytes\n", buf_size);
+ if (op->opt_new)
+ pr2serr(" [with '--buffer=EACH' where EACH is in "
+ "bytes]\n");
+ else
+ pr2serr(" [with '-b=EACH' where EACH is in KiB]\n");
+ } else
+ perror("error using mmap()");
+ return SG_LIB_CAT_OTHER;
+ }
+ }
+ else { /* non mmap-ed IO */
+ rawp = (uint8_t *)malloc(buf_size + (op->do_dio ? psz : 0));
+ if (NULL == rawp) {
+ printf("out of memory (data)\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ /* perhaps use posix_memalign() instead */
+ if (op->do_dio) /* align to page boundary */
+ rbBuff= (uint8_t *)(((sg_uintptr_t)rawp + psz - 1) &
+ (~(psz - 1)));
+ else
+ rbBuff = (uint8_t *)rawp;
+ }
+
+ num = total_size / buf_size;
+ if (op->do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ }
+ /* main data reading loop */
+ for (k = 0; k < num; ++k) {
+ memset(rb_cdb, 0, RB_CMD_LEN);
+ rb_cdb[0] = RB_OPCODE;
+ rb_cdb[1] = op->do_echo ? RB_MODE_ECHO_DATA : RB_MODE_DATA;
+ sg_put_unaligned_be24((uint32_t)buf_size, rb_cdb + 6);
+#ifdef DEBUG
+ memset(rbBuff, 0, buf_size);
+#endif
+
+ memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rb_cdb);
+ io_hdr.mx_sb_len = sizeof(sense_buffer);
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = buf_size;
+ if (! op->do_mmap)
+ io_hdr.dxferp = rbBuff;
+ io_hdr.cmdp = rb_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+ io_hdr.pack_id = k;
+ if (op->do_mmap)
+ io_hdr.flags |= SG_FLAG_MMAP_IO;
+ else if (op->do_dio)
+ io_hdr.flags |= SG_FLAG_DIRECT_IO;
+ else if (op->do_quick)
+ io_hdr.flags |= SG_FLAG_NO_DXFER;
+ if (op->verbose > 1) {
+ char b[128];
+
+ pr2serr(" Read buffer (%sdata) cdb: %s\n",
+ (op->do_echo ? "echo " : ""),
+ sg_get_command_str(rb_cdb, RB_CMD_LEN, false,
+ sizeof(b), b));
+ }
+ if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
+ if (ENOMEM == errno) {
+ pr2serr("SG_IO data: out of memory, try a smaller buffer "
+ "size than %d bytes\n", buf_size);
+ if (op->opt_new)
+ pr2serr(" [with '--buffer=EACH' where EACH is in "
+ "bytes]\n");
+ else
+ pr2serr(" [with '-b=EACH' where EACH is in KiB]\n");
+ } else
+ perror("SG_IO READ BUFFER data error");
+ if (rawp) free(rawp);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (op->verbose > 2)
+ pr2serr(" duration=%u ms\n", io_hdr.duration);
+ /* now for the error processing */
+ res = sg_err_category3(&io_hdr);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ sg_chk_n_print3("READ BUFFER data, continuing", &io_hdr,
+ op->verbose > 1);
+ break;
+ default: /* won't bother decoding other categories */
+ sg_chk_n_print3("READ BUFFER data error", &io_hdr,
+ op->verbose > 1);
+ if (rawp) free(rawp);
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+ }
+ if (op->do_dio &&
+ ((io_hdr.info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
+ dio_incomplete = true; /* flag that dio not done (completely) */
+
+#ifdef DEBUG
+ if (clear) {
+ int j;
+
+ for (j = 0; j < buf_size; ++j) {
+ if (rbBuff[j] != 0) {
+ clear = false;
+ break;
+ }
+ }
+ }
+#endif
+ }
+ if (op->do_time && (start_tm.tv_sec || start_tm.tv_usec)) {
+ struct timeval res_tm;
+ double a, b;
+
+ gettimeofday(&end_tm, NULL);
+ res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
+ res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec;
+ if (res_tm.tv_usec < 0) {
+ --res_tm.tv_sec;
+ res_tm.tv_usec += 1000000;
+ }
+ a = res_tm.tv_sec;
+ a += (0.000001 * res_tm.tv_usec);
+ b = (double)buf_size * num;
+ printf("time to read data from buffer was %d.%06d secs",
+ (int)res_tm.tv_sec, (int)res_tm.tv_usec);
+ if (a > 0.00001) {
+ if (b > 511)
+ printf(", %.2f MB/sec", b / (a * 1000000.0));
+ printf(", %.2f IOPS", num / a);
+ }
+ printf("\n");
+ }
+ if (dio_incomplete)
+ printf(">> direct IO requested but not done\n");
+ printf("Read %" PRId64 " MiB (actual: %" PRId64 " bytes), buffer "
+ "size=%d KiB (%d bytes)\n", (total_size / (1024 * 1024)),
+ (int64_t)num * buf_size, buf_size / 1024, buf_size);
+
+ if (rawp) free(rawp);
+ res = close(sg_fd);
+ if (res < 0) {
+ err = errno;
+ perror("close error");
+ return sg_convert_errno(err);
+ }
+#ifdef DEBUG
+ if (clear)
+ printf("read buffer always zero\n");
+ else
+ printf("read buffer non-zero\n");
+#endif
+ return res;
+}