aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--doc/sg_ses.812
-rw-r--r--doc/sg_turs.817
-rw-r--r--include/sg_pt.h44
-rw-r--r--include/sg_unaligned.h2
-rw-r--r--src/sg_seek.c1
-rw-r--r--src/sg_turs.c245
-rw-r--r--testing/Makefile.cplus5
-rw-r--r--testing/sg_tst_async.cpp220
-rw-r--r--testing/tst_sg_lib.c2
10 files changed, 396 insertions, 157 deletions
diff --git a/ChangeLog b/ChangeLog
index 3c592631..4ea416ba 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,7 +2,7 @@ Each utility has its own version number, date of last change and
some description at the top of its ".c" file. All utilities in the main
directory have their own "man" pages. There is also a sg3_utils man page.
-Changelog for sg3_utils-1.43 [20180316] [svn: r762]
+Changelog for sg3_utils-1.43 [20180321] [svn: r763]
- sg_write_x: where x can be normal, atomic, or(write),
same, scattered, or stream writes with 16 or 32 byte
cdbs (sbc4r04 for atomic, sbc4r11 for scattered)
@@ -19,7 +19,7 @@ Changelog for sg3_utils-1.43 [20180316] [svn: r762]
- expand join handling of SAS connectors and others
- expand join debug code
- allow multiple --clear=, --get= and --set= options
- - allow individual index ranges (e.g. --index=3,5)
+ - allow individual index ranges (e.g. --index=3-5)
- allow --index=IIA with -ee to enumerate only fields
belonging to element type IIA
- --data=@FN with --status now decodes dpage(s) in FN
@@ -79,6 +79,7 @@ Changelog for sg3_utils-1.43 [20180316] [svn: r762]
- sg_rdac: add sanity checks for -f=lun value
- sg_turs+sg_requests: make both accept '--num=NUM'
and '--number=NUM' for mutual compatibility
+ - sg_turs: add --low option
- sg_zone: fix debug cdb naming
- sg_persist: add --maxlen-LEN option, LEN defaults to
decimal, similar to --alloc-length= which takes hex
diff --git a/doc/sg_ses.8 b/doc/sg_ses.8
index 10e660ad..b838267b 100644
--- a/doc/sg_ses.8
+++ b/doc/sg_ses.8
@@ -314,7 +314,7 @@ later invocation using the \fI\-\-data=\fR option. A dpage less its first
the full dpage contents is output in binary to stdout.
.br
when \fI\-rr\fR is used together with the \fI\-\-data=\-\fR or
-\fI\-\-data=FN\fR then stdin or file FN is decoded as a binary stream that
+\fI\-\-data=@FN\fR then stdin or file FN is decoded as a binary stream that
continues to be read until an end of file (EOF). Once that data is read then
the internal raw option is cleared to 0 so the output is not effected. So
the \fI\-rr\fR option either changes how the input or output is treated,
@@ -498,11 +498,11 @@ The \fI\-\-clear=STR\fR, \fI\-\-get=STR\fR and \fI\-\-set=STR\fR options can
be used up to 8 times in the same invocation. Any <acronym>s used in the
\fISTR\fR operands must refer to the same dpage.
.PP
-When multiple of these options are used, they are applied in the order in
-which they appear on the command line. So if options contradict each other,
-the last one appearing on the command line will be enforced. When there
-are multiple \fI\-\-clear=STR\fR and \fI\-\-set=STR\fR options, then the
-dpage they refer to is only written after the last one.
+When multiple of these options are used (maximum: 8), they are applied in the
+order in which they appear on the command line. So if options contradict each
+other, the last one appearing on the command line will be enforced. When
+there are multiple \fI\-\-clear=STR\fR and \fI\-\-set=STR\fR options, then
+the dpage they refer to is only written after the last one.
.SH DATA SUPPLIED
This section describes the two scenarios that can occur when the
\fI\-\-data=\fR option is given. These scenarios are the same irrespective
diff --git a/doc/sg_turs.8 b/doc/sg_turs.8
index d37f8fd4..66e6ad3a 100644
--- a/doc/sg_turs.8
+++ b/doc/sg_turs.8
@@ -1,9 +1,9 @@
-.TH SG_TURS "8" "October 2017" "sg3_utils\-1.43" SG3_UTILS
+.TH SG_TURS "8" "March 2018" "sg3_utils\-1.43" SG3_UTILS
.SH NAME
sg_turs \- send one or more SCSI TEST UNIT READY commands
.SH SYNOPSIS
.B sg_turs
-[\fI\-\-help\fR] [\fI\-\-number=NUM\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-help\fR] [\fI\-\-low\fR] [\fI\-\-number=NUM\fR] [\fI\-\-num=NUM\fR]
[\fI\-\-progress\fR] [\fI\-\-time\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
\fIDEVICE\fR
.PP
@@ -15,8 +15,8 @@ sg_turs \- send one or more SCSI TEST UNIT READY commands
.PP
This utility sends one or more SCSI TEST UNIT READY commands to the
\fIDEVICE\fR. This may be useful for timing the per command overhead.
-Note that TEST UNIT READY has no associated data, just a 6 byte command
-and a returned SCSI status value.
+Note that TEST UNIT READY has no associated data, just a 6 byte
+command (with each byte a zero) and a returned SCSI status value.
.PP
This utility supports two command line syntaxes, the preferred one is
shown first in the synopsis and explained in this section. A later section
@@ -27,6 +27,13 @@ Arguments to long options are mandatory for short options as well.
\fB\-h\fR, \fB\-\-help\fR
print out the usage message then exit.
.TP
+\fB\-l\fR, \fB\-\-low\fR
+when [\fI\-\-progress\fR] is not being used, this utility tries to complete
+the SCSI TEST UNIT READY command(s) as quickly as possible. Usually it
+calls a library function to do each TUR (sg_ll_test_unit_ready). With this
+option it uses the lower level sg_pt interface (see sg_pt.h) to save a
+little time on each TUR.
+.TP
\fB\-n\fR, \fB\-\-number\fR=\fINUM\fR
performs TEST UNIT READY \fINUM\fR times. If not given defaults to 1.
These suffix multipliers are permitted: c C *1; w W *2; b B *512;
@@ -121,7 +128,7 @@ using \fI\-\-old\fR (or \fI\-O\fR) as the first command line option.
.SH AUTHORS
Written by D. Gilbert
.SH COPYRIGHT
-Copyright \(co 2000\-2017 Douglas Gilbert
+Copyright \(co 2000\-2018 Douglas Gilbert
.br
This software is distributed under the GPL version 2. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/include/sg_pt.h b/include/sg_pt.h
index c2e1aad5..b9804757 100644
--- a/include/sg_pt.h
+++ b/include/sg_pt.h
@@ -63,14 +63,16 @@ int check_pt_file_handle(int dev_fd, const char * device_name, int verbose);
* destruct_scsi_pt_obj() when it is no longer needed. */
struct sg_pt_base * construct_scsi_pt_obj(void);
-/* An alternate way to create an object that can be used to issue one or
- * more SCSI commands (or task management functions). This variant
- * associates a device file descriptor (handle) with the object and a
- * verbose argument that causes messages to be written to stderr if errors
- * occur. The reason for this is to optionally allow the detection of NVMe
- * devices that will cause pt_device_is_nvme() to return true. Set dev_fd
- * to -1 if no open device file descriptor is available. Caller should
- * additionally call get_scsi_pt_os_err() after this call. */
+/* An alternate and preferred way to create an object that can be used to
+ * issue one or more SCSI (or NVMe) commands (or task management functions).
+ * This variant associates a device file descriptor (handle) with the object
+ * and a verbose argument that causes messages to be written to stderr if
+ * errors occur. The reason for this is to optionally allow the detection of
+ * NVMe devices that will cause pt_device_is_nvme() to return true. Set
+ * dev_fd to -1 if no open device file descriptor is available. Caller
+ * should additionally call get_scsi_pt_os_err() after this call to check
+ * for errors. The dev_fd argument may be -1 to indicate no device file
+ * descriptor. */
struct sg_pt_base *
construct_scsi_pt_obj_with_fd(int dev_fd, int verbose);
@@ -89,22 +91,34 @@ int get_pt_file_handle(const struct sg_pt_base * objp);
* Use set_pt_file_handle() to change dev_fd. */
void clear_scsi_pt_obj(struct sg_pt_base * objp);
-/* Set the CDB (command descriptor block) */
+/* Set the CDB (command descriptor block). May also be a NVMe Admin command
+ * which will be 64 bytes long.
+ *
+ * Note that the sg_cmds_is_nvme() function found in sg_cmds_basic.h can be
+ * called after this function to "guess" which command set the given command
+ * belongs to. */
void set_scsi_pt_cdb(struct sg_pt_base * objp, const uint8_t * cdb,
int cdb_len);
-/* Set the sense buffer and the maximum length that it can handle */
+
+/* Set the sense buffer and the maximum length of that buffer. For NVMe
+ * commands this "sense" buffer will receive the 4 DWORDs of from the
+ * completion queue. */
void set_scsi_pt_sense(struct sg_pt_base * objp, uint8_t * sense,
int max_sense_len);
+
/* Set a pointer and length to be used for data transferred from device */
void set_scsi_pt_data_in(struct sg_pt_base * objp, /* from device */
uint8_t * dxferp, int dxfer_ilen);
+
/* Set a pointer and length to be used for data transferred to device */
void set_scsi_pt_data_out(struct sg_pt_base * objp, /* to device */
const uint8_t * dxferp, int dxfer_olen);
+
/* Set a pointer and length to be used for metadata transferred to
- * (out_true=true) or from (out_true-false) device */
+ * (out_true=true) or from (out_true=false) device (NVMe only) */
void set_pt_metadata_xfer(struct sg_pt_base * objp, uint8_t * mdxferp,
uint32_t mdxfer_len, bool out_true);
+
/* The following "set_"s implementations may be dummies */
void set_scsi_pt_packet_id(struct sg_pt_base * objp, int pack_id);
void set_scsi_pt_tag(struct sg_pt_base * objp, uint64_t tag);
@@ -144,7 +158,13 @@ int do_scsi_pt(struct sg_pt_base * objp, int fd, int timeout_secs,
#define SCSI_PT_RESULT_TRANSPORT_ERR 3
#define SCSI_PT_RESULT_OS_ERR 4
/* This function, called soon after do_scsi_pt(), returns one of the above
- * result categories. The highest numbered applicable category is returned. */
+ * result categories. The highest numbered applicable category is returned.
+ *
+ * Note that the sg_cmds_process_resp() function found in sg_cmds_basic.h
+ * is useful for processing SCSI command responses.
+ * And the sg_cmds_is_nvme() function found in sg_cmds_basic.h can be called
+ * after set_scsi_pt_cdb() to "guess" which command set the given command
+ * belongs to. */
int get_scsi_pt_result_category(const struct sg_pt_base * objp);
/* If not available return 0 which implies there is no residual
diff --git a/include/sg_unaligned.h b/include/sg_unaligned.h
index b6f4698d..15e49e2d 100644
--- a/include/sg_unaligned.h
+++ b/include/sg_unaligned.h
@@ -383,7 +383,7 @@ static inline void sg_put_unaligned_le64(uint64_t val, void *p)
}
/* Following are lesser used conversions that don't have specializations
- * for endianess; big endian first. In summary these are the 24, 48 bit and
+ * for endianness; big endian first. In summary these are the 24, 48 bit and
* given-length conversions plus the "nz" conditional put conversions. */
/* Now big endian, get 24+48 then put 24+48 */
diff --git a/src/sg_seek.c b/src/sg_seek.c
index cf5bc5cb..55ef5f02 100644
--- a/src/sg_seek.c
+++ b/src/sg_seek.c
@@ -336,6 +336,7 @@ main(int argc, char * argv[])
pr2serr("clock_gettime(CLOCK_MONOTONIC) not supported\n");
}
elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ /* Note that (end_tm.tv_nsec - start_tm.tv_nsec) may be negative */
elapsed_usecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000;
}
#elif defined(HAVE_GETTIMEOFDAY)
diff --git a/src/sg_turs.c b/src/sg_turs.c
index 24a37f92..f7dbff0d 100644
--- a/src/sg_turs.c
+++ b/src/sg_turs.c
@@ -8,9 +8,9 @@
*/
/*
- * This program sends a user specified number of TEST UNIT READY commands
- * to the given sg device. Since TUR is a simple command involing no
- * data transfer (and no REQUEST SENSE command iff the unit is ready)
+ * This program sends a user specified number of TEST UNIT READY ("tur")
+ * commands to the given sg device. Since TUR is a simple command involing
+ * no data transfer (and no REQUEST SENSE command iff the unit is ready)
* then this can be used for timing per SCSI command overheads.
*/
@@ -21,22 +21,27 @@
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
+#include <errno.h>
#include <getopt.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#ifndef SG_LIB_MINGW
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+#include <time.h>
+#elif defined(HAVE_GETTIMEOFDAY)
+#include <time.h>
#include <sys/time.h>
#endif
#include "sg_lib.h"
#include "sg_cmds_basic.h"
+#include "sg_pt.h"
#include "sg_pr2serr.h"
-static const char * version_str = "3.39 20180302";
+static const char * version_str = "3.40 20180321";
#if defined(MSC_VER) || defined(__MINGW32__)
#define HAVE_MS_SLEEP
@@ -49,8 +54,12 @@ static const char * version_str = "3.39 20180302";
#define sleep_for(seconds) sleep(seconds)
#endif
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
+
+
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
+ {"low", no_argument, 0, 'l'},
{"new", no_argument, 0, 'N'},
{"number", required_argument, 0, 'n'},
{"num", required_argument, 0, 'n'}, /* added in v3.32 (sg3_utils
@@ -64,6 +73,7 @@ static struct option long_options[] = {
};
struct opts_t {
+ bool do_low;
bool do_progress;
bool do_time;
bool do_version;
@@ -74,15 +84,23 @@ struct opts_t {
const char * device_name;
};
+struct loop_res_t {
+ bool reported;
+ int num_errs;
+ int ret;
+};
+
static void
usage()
{
- printf("Usage: sg_turs [--help] [--number=NUM] [--num=NUM] [--progress] "
- "[--time]\n"
- " [--verbose] [--version] DEVICE\n"
+ printf("Usage: sg_turs [--help] [--low] [--number=NUM] [--num=NUM] "
+ "[--progress]\n"
+ " [--time] [--verbose] [--version] DEVICE\n"
" where:\n"
" --help|-h print usage message then exit\n"
+ " --low|-l use low level (sg_pt) interface for "
+ "speed\n"
" --number=NUM|-n NUM number of test_unit_ready commands "
"(def: 1)\n"
" --num=NUM|-n NUM same action as '--number=NUM'\n"
@@ -99,9 +117,10 @@ usage()
static void
usage_old()
{
- printf("Usage: sg_turs [-n=NUM] [-p] [-t] [-v] [-V] "
+ printf("Usage: sg_turs [-l] [-n=NUM] [-p] [-t] [-v] [-V] "
"DEVICE\n"
" where:\n"
+ " -l use low level interface (sg_pt) for speed\n"
" -n=NUM number of test_unit_ready commands "
"(def: 1)\n"
" -p outputs progress indication (percentage) "
@@ -131,7 +150,7 @@ new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
while (1) {
int option_index = 0;
- c = getopt_long(argc, argv, "hn:NOptvV", long_options,
+ c = getopt_long(argc, argv, "hln:NOptvV", long_options,
&option_index);
if (c == -1)
break;
@@ -141,6 +160,9 @@ new_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
case '?':
++op->do_help;
break;
+ case 'l':
+ op->do_low = true;
+ break;
case 'n':
n = sg_get_num(optarg);
if (n < 0) {
@@ -205,6 +227,9 @@ old_parse_cmd_line(struct opts_t * op, int argc, char * argv[])
if ('-' == *cp) {
for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
switch (*cp) {
+ case 'l':
+ op->do_low = true;
+ return 0;
case 'N':
op->opts_new = true;
return 0;
@@ -281,23 +306,117 @@ parse_cmd_line(struct opts_t * op, int argc, char * argv[])
return res;
}
+/* Returns number of TURs performed */
+static int
+loop_turs(int sg_fd, struct loop_res_t * resp, struct opts_t * op)
+{
+ int k, res;
+ int vb = op->do_verbose;
+ char b[80];
+
+ if (op->do_low) {
+ int err, rs, n, sense_cat;
+ struct sg_pt_base * pbp;
+ uint8_t cdb[6];
+ uint8_t sense_b[32];
+
+ pbp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+ err = 0;
+ if ((NULL == pbp) || ((err = get_scsi_pt_os_err(pbp)))) {
+ resp->ret = sg_convert_errno(err ? err : ENOMEM);
+ return 0;
+ }
+ for (k = 0; k < op->do_number; ++k) {
+ /* Might get Unit Attention on first invocation */
+ memset(cdb, 0, sizeof(cdb)); /* TUR's cdb is 6 zeros */
+ set_scsi_pt_cdb(pbp, cdb, sizeof(cdb));
+ set_scsi_pt_sense(pbp, sense_b, sizeof(sense_b));
+ rs = do_scsi_pt(pbp, -1, DEF_PT_TIMEOUT, vb);
+ n = sg_cmds_process_resp(pbp, "Test unit ready", rs,
+ SG_NO_DATA_IN, sense_b,
+ (0 == k), vb, &sense_cat);
+ if (-1 == n) {
+ resp->ret = sg_convert_errno(get_scsi_pt_os_err(pbp));
+ return k;
+ } else if (-2 == n) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ ++resp->num_errs;
+ if (1 == op->do_number) {
+ printf("device not ready\n");
+ resp->reported = true;
+ }
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ ++resp->num_errs;
+ if (vb) {
+ pr2serr("Ignoring Unit attention (sense key)\n");
+ resp->reported = true;
+ }
+ break;
+ default:
+ ++resp->num_errs;
+ if (1 == op->do_number) {
+ resp->ret = sense_cat;
+ sg_get_category_sense_str(sense_cat, sizeof(b), b, vb);
+ printf("%s\n", b);
+ resp->reported = true;
+ return k;
+ }
+ break;
+ }
+ }
+ clear_scsi_pt_obj(pbp);
+ }
+ destruct_scsi_pt_obj(pbp);
+ return k;
+ } else {
+ for (k = 0; k < op->do_number; ++k) {
+ /* Might get Unit Attention on first invocation */
+ res = sg_ll_test_unit_ready(sg_fd, k, (0 == k), vb);
+ if (res) {
+ ++resp->num_errs;
+ resp->ret = res;
+ if (1 == op->do_number) {
+ if (SG_LIB_CAT_NOT_READY == res)
+ printf("device not ready\n");
+ else {
+ sg_get_category_sense_str(res, sizeof(b), b, vb);
+ printf("%s\n", b);
+ }
+ resp->reported = true;
+ break;
+ }
+ }
+ }
+ return k;
+ }
+}
+
int
main(int argc, char * argv[])
{
- bool reported = false;
- int sg_fd, k, res, progress, pr, rem;
- int num_errs = 0;
+ bool start_tm_valid = false;
+ int sg_fd, k, res, progress, pr, rem, num_done;
int ret = 0;
-#ifndef SG_LIB_MINGW
+ int64_t elapsed_usecs = 0;
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ struct timespec start_tm, end_tm;
+#elif defined(HAVE_GETTIMEOFDAY)
struct timeval start_tm, end_tm;
#endif
+ struct loop_res_t loop_res;
+ struct loop_res_t * resp = &loop_res;
struct opts_t opts;
- struct opts_t * op;
- char b[80];
+ struct opts_t * op = &opts;
+
- op = &opts;
memset(op, 0, sizeof(opts));
+ memset(resp, 0, sizeof(loop_res));
op->do_number = 1;
res = parse_cmd_line(op, argc, argv);
if (res)
@@ -321,7 +440,7 @@ main(int argc, char * argv[])
op->do_verbose)) < 0) {
pr2serr("sg_turs: error opening file: %s: %s\n", op->device_name,
safe_strerror(-sg_fd));
- return SG_LIB_FILE_ERROR;
+ return sg_convert_errno(-sg_fd);
}
if (op->do_progress) {
for (k = 0; k < op->do_number; ++k) {
@@ -343,59 +462,67 @@ main(int argc, char * argv[])
printf("Completed %d Test Unit Ready commands\n",
((k < op->do_number) ? k + 1 : k));
} else { /* --progress not given */
-#ifndef SG_LIB_MINGW
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ if (op->do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_nsec = 0;
+ if (0 == clock_gettime(CLOCK_MONOTONIC, &start_tm))
+ start_tm_valid = true;
+ else
+ perror("clock_gettime(CLOCK_MONOTONIC)\n");
+ }
+#elif defined(HAVE_GETTIMEOFDAY)
if (op->do_time) {
start_tm.tv_sec = 0;
start_tm.tv_usec = 0;
gettimeofday(&start_tm, NULL);
+ start_tm_valid = true;
}
+#else
+ start_tm_valid = false;
#endif
- for (k = 0; k < op->do_number; ++k) {
- /* Might get Unit Attention on first invocation */
- res = sg_ll_test_unit_ready(sg_fd, k, (0 == k), op->do_verbose);
- if (res) {
- ++num_errs;
- ret = res;
- if (1 == op->do_number) {
- if (SG_LIB_CAT_NOT_READY == res)
- printf("device not ready\n");
- else {
- sg_get_category_sense_str(res, sizeof(b), b,
- op->do_verbose);
- printf("%s\n", b);
- }
- reported = true;
- break;
+
+ num_done = loop_turs(sg_fd, resp, op);
+
+ if (op->do_time && start_tm_valid) {
+#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
+ if (start_tm.tv_sec || start_tm.tv_nsec) {
+ int err;
+
+ res = clock_gettime(CLOCK_MONOTONIC, &end_tm);
+ if (res < 0) {
+ err = errno;
+ perror("clock_gettime");
+ if (EINVAL == err)
+ pr2serr("clock_gettime(CLOCK_MONOTONIC) not "
+ "supported\n");
}
+ elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ /* Note: (end_tm.tv_nsec - start_tm.tv_nsec) may be negative */
+ elapsed_usecs += (end_tm.tv_nsec - start_tm.tv_nsec) / 1000;
}
- }
-#ifndef SG_LIB_MINGW
- if (op->do_time && (start_tm.tv_sec || start_tm.tv_usec)) {
- struct timeval res_tm;
- double den, num;
-
- 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;
+#elif defined(HAVE_GETTIMEOFDAY)
+ if (start_tm.tv_sec || start_tm.tv_usec) {
+ gettimeofday(&end_tm, NULL);
+ elapsed_usecs = (end_tm.tv_sec - start_tm.tv_sec) * 1000000;
+ elapsed_usecs += (end_tm.tv_usec - start_tm.tv_usec);
}
- den = res_tm.tv_sec;
- den += (0.000001 * res_tm.tv_usec);
- num = (double)op->do_number;
- printf("time to perform commands was %d.%06d secs",
- (int)res_tm.tv_sec, (int)res_tm.tv_usec);
- if (den > 0.00001)
- printf("; %.2f operations/sec\n", num / den);
- else
- printf("\n");
- }
#endif
+ if (elapsed_usecs > 0) {
+ int64_t nom = num_done;
- if (((op->do_number > 1) || (num_errs > 0)) && (! reported))
+ printf("time to perform commands was %u.%06u secs",
+ (unsigned)(elapsed_usecs / 1000000),
+ (unsigned)(elapsed_usecs % 1000000));
+ nom *= 1000000; /* scale for integer division */
+ printf("; %d operations/sec\n", (int)(nom / elapsed_usecs));
+ } else
+ printf("Recorded 0 or less elapsed microseconds ??\n");
+ }
+ if (((op->do_number > 1) || (resp->num_errs > 0)) &&
+ (! resp->reported))
printf("Completed %d Test Unit Ready commands with %d errors\n",
- op->do_number, num_errs);
+ op->do_number, resp->num_errs);
}
sg_cmds_close_device(sg_fd);
return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
diff --git a/testing/Makefile.cplus b/testing/Makefile.cplus
index 31af2ff5..95c5438e 100644
--- a/testing/Makefile.cplus
+++ b/testing/Makefile.cplus
@@ -29,7 +29,8 @@ CPPFLAGS = -std=c++11 -pthread -g -O2 -W -Wall -iquote ../include -D_REENTRANT $
LDFLAGS = -std=c++11 -pthread
LIBFILESOLD = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_io_linux.o
-LIBFILESNEW = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_pt_linux.o ../lib/sg_pt_common.o ../lib/sg_pt_linux_nvme.o
+LIBFILESNEW = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_pt_linux.o ../lib/sg_pt_common.o \
+ ../lib/sg_pt_linux_nvme.o ../lib/sg_io_linux.o ../lib/sg_cmds_basic.o
all: $(EXECS)
@@ -55,7 +56,7 @@ sg_tst_excl3: sg_tst_excl3.o $(LIBFILESNEW)
sg_tst_context: sg_tst_context.o $(LIBFILESNEW)
$(LD) -o $@ $(LDFLAGS) $^
-sg_tst_async: sg_tst_async.o $(LIBFILESOLD)
+sg_tst_async: sg_tst_async.o $(LIBFILESNEW)
$(LD) -o $@ $(LDFLAGS) $^
install: $(EXECS)
diff --git a/testing/sg_tst_async.cpp b/testing/sg_tst_async.cpp
index 67f247cc..f061db98 100644
--- a/testing/sg_tst_async.cpp
+++ b/testing/sg_tst_async.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014-2015 Douglas Gilbert.
+ * Copyright (c) 2014-2018 Douglas Gilbert.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -54,11 +54,19 @@
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
#include "sg_lib.h"
#include "sg_io_linux.h"
#include "sg_unaligned.h"
+#include "sg_pt.h"
+#include "sg_cmds.h"
-static const char * version_str = "1.11 20150227";
+static const char * version_str = "1.12 20180319";
static const char * util_name = "sg_tst_async";
/* This is a test program for checking the async usage of the Linux sg
@@ -122,17 +130,20 @@ using namespace std::chrono;
#endif
+#define DEF_PT_TIMEOUT 60 /* 60 seconds */
#define EBUFF_SZ 256
static mutex console_mutex;
static mutex rand_lba_mutex;
static atomic<int> async_starts(0);
+static atomic<int> sync_starts(0);
static atomic<int> async_finishes(0);
static atomic<int> ebusy_count(0);
static atomic<int> start_eagain_count(0);
static atomic<int> fin_eagain_count(0);
static atomic<int> uniq_pack_id(1);
+static atomic<int> generic_errs(0);
static int page_size = 4096; /* rough guess, will ask sysconf() */
@@ -151,6 +162,7 @@ struct opts_t {
int maxq_per_thread;
int num_per_thread;
bool block;
+ bool generic_pt;
uint64_t lba;
unsigned int hi_lba; /* last one, inclusive range */
vector<unsigned int> hi_lbas; /* only used when hi_lba=-1 */
@@ -207,6 +219,8 @@ private:
static struct option long_options[] = {
{"direct", no_argument, 0, 'd'},
{"force", no_argument, 0, 'f'},
+ {"generic-pt", no_argument, 0, 'g'},
+ {"generic_pt", no_argument, 0, 'g'},
{"help", no_argument, 0, 'h'},
{"lba", required_argument, 0, 'l'},
{"maxqpt", required_argument, 0, 'M'},
@@ -230,21 +244,24 @@ static struct option long_options[] = {
static void
usage(void)
{
- printf("Usage: %s [--direct] [--force] [--help] [--lba=LBA+] "
- "[--maxqpt=QPT]\n"
- " [--numpt=NPT] [--noxfer] [--qat=AT] "
- "[-qfav=FAV]\n"
- " [--read] [--szlb=LB] [--stats] [--tnum=NT] "
- "[--tur]\n"
- " [--verbose] [--version] [--wait=MS] "
- "[--write]\n"
- " <sg_disk_device>*\n",
+ printf("Usage: %s [--direct] [--force] [--generic-pt] [--help]\n"
+ " [--lba=LBA+] [--maxqpt=QPT] [--numpt=NPT] "
+ "[--noxfer]\n"
+ " [--qat=AT] [-qfav=FAV] [--read] [--szlb=LB] "
+ "[--stats]\n"
+ " [--tnum=NT] [--tur] [--verbose] [--version] "
+ "[--wait=MS]\n"
+ " [--write] <sg_disk_device>*\n",
util_name);
printf(" where\n");
printf(" --direct|-d do direct_io (def: indirect)\n");
printf(" --force|-f force: any sg device (def: only scsi_debug "
"owned)\n");
printf(" WARNING: <lba> written to if '-W' given\n");
+ printf(" --generic-pt|-g use generic passthru in sg3_utils "
+ "instead\n");
+ printf(" of Linux sg driver and SG_IO ioctl "
+ "(def)\n");
printf(" --help|-h print this usage message then exit\n");
printf(" --lba=LBA|-l LBA logical block to access (def: %u)\n",
DEF_LBA);
@@ -325,7 +342,7 @@ get_urandom_uint(void)
{
unsigned int res = 0;
int n;
- unsigned char b[sizeof(unsigned int)];
+ uint8_t b[sizeof(unsigned int)];
lock_guard<mutex> lg(rand_lba_mutex);
int fd = open(URANDOM_DEV, O_RDONLY);
@@ -347,16 +364,16 @@ get_urandom_uint(void)
/* Returns 0 if command injected okay, else -1 */
static int
start_sg3_cmd(int sg_fd, command2execute cmd2exe, int pack_id, uint64_t lba,
- unsigned char * lbp, int xfer_bytes, int flags,
+ uint8_t * lbp, int xfer_bytes, int flags,
unsigned int & eagains)
{
struct sg_io_hdr pt;
- unsigned char turCmdBlk[TUR_CMD_LEN] = {0, 0, 0, 0, 0, 0};
- unsigned char r16CmdBlk[READ16_CMD_LEN] =
+ uint8_t turCmdBlk[TUR_CMD_LEN] = {0, 0, 0, 0, 0, 0};
+ uint8_t r16CmdBlk[READ16_CMD_LEN] =
{0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
- unsigned char w16CmdBlk[WRITE16_CMD_LEN] =
+ uint8_t w16CmdBlk[WRITE16_CMD_LEN] =
{0x8a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0};
- unsigned char sense_buffer[64];
+ uint8_t sense_buffer[64];
const char * np = NULL;
memset(&pt, 0, sizeof(pt));
@@ -419,7 +436,7 @@ finish_sg3_cmd(int sg_fd, command2execute cmd2exe, int & pack_id, int wait_ms,
{
int ok, res;
struct sg_io_hdr pt;
- unsigned char sense_buffer[64];
+ uint8_t sense_buffer[64];
const char * np = NULL;
memset(&pt, 0, sizeof(pt));
@@ -475,37 +492,87 @@ finish_sg3_cmd(int sg_fd, command2execute cmd2exe, int & pack_id, int wait_ms,
return ok ? 0 : -1;
}
-/* Should have page alignment if direct_io chosen */
-static unsigned char *
-get_aligned_heap(int bytes_at_least)
+static void
+work_sync_thread(int id, const char * dev_name, unsigned int /* hi_lba */,
+ struct opts_t * op)
{
- int n;
- void * wp;
+ bool is_rw = (SCSI_TUR != op->c2e);
+ int k, sg_fd, err, rs, n, sense_cat, ret;
+ int vb = op->verbose;
+ int num_errs = 0;
+ int thr_sync_starts = 0;
+ struct sg_pt_base * pbp = NULL;
+ uint8_t cdb[6];
+ uint8_t sense_b[32];
+ char b[120];
+
+ if (is_rw) {
+ pr2serr_lk("id=%d: only support TUR here for now\n", id);
+ goto err_out;
+ }
+ if ((sg_fd = sg_cmds_open_device(dev_name, false /* ro */, vb)) < 0) {
+ pr2serr_lk("id=%d: error opening file: %s: %s\n", id, dev_name,
+ safe_strerror(-sg_fd));
+ goto err_out;
+ }
- if (bytes_at_least < page_size)
- n = page_size;
- else
- n = bytes_at_least;
-#if 1
- int err = posix_memalign(&wp, page_size, n);
- if (err) {
- pr2serr_lk("posix_memalign: error [%d] out of memory?\n", err);
- return NULL;
+ pbp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+ err = 0;
+ if ((NULL == pbp) || ((err = get_scsi_pt_os_err(pbp)))) {
+ ret = sg_convert_errno(err ? err : ENOMEM);
+ sg_exit2str(ret, true, sizeof(b), b);
+ pr2serr_lk("id=%d: construct_scsi_pt_obj_with_fd: %s\n", id, b);
+ goto err_out;
}
- memset(wp, 0, n);
- return (unsigned char *)wp;
-#else
- /* hack if posix_memalign() is not available */
- if (n == page_size) {
- wp = calloc(page_size, 1);
- memset(wp, 0, n);
- return (unsigned char *)wp;
- } else {
- pr2serr_lk("get_aligned_heap: too fiddly to align, choose smaller "
- "lb_sz\n");
- return NULL;
+ for (k = 0; k < op->num_per_thread; ++k) {
+ /* Might get Unit Attention on first invocation */
+ memset(cdb, 0, sizeof(cdb)); /* TUR's cdb is 6 zeros */
+ set_scsi_pt_cdb(pbp, cdb, sizeof(cdb));
+ set_scsi_pt_sense(pbp, sense_b, sizeof(sense_b));
+ ++thr_sync_starts;
+ rs = do_scsi_pt(pbp, -1, DEF_PT_TIMEOUT, vb);
+ n = sg_cmds_process_resp(pbp, "Test unit ready", rs,
+ SG_NO_DATA_IN, sense_b,
+ (0 == k), vb, &sense_cat);
+ if (-1 == n) {
+ ret = sg_convert_errno(get_scsi_pt_os_err(pbp));
+ sg_exit2str(ret, true, sizeof(b), b);
+ pr2serr_lk("id=%d: do_scsi_pt: %s\n", id, b);
+ goto err_out;
+ } else if (-2 == n) {
+ switch (sense_cat) {
+ case SG_LIB_CAT_RECOVERED:
+ case SG_LIB_CAT_NO_SENSE:
+ break;
+ case SG_LIB_CAT_NOT_READY:
+ ++num_errs;
+ if (1 == op->num_per_thread) {
+ pr2serr_lk("id=%d: device not ready\n", id);
+ }
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ ++num_errs;
+ if (vb)
+ pr2serr_lk("Ignoring Unit attention (sense key)\n");
+ break;
+ default:
+ ++num_errs;
+ if (1 == op->num_per_thread) {
+ sg_get_category_sense_str(sense_cat, sizeof(b), b, vb);
+ pr2serr_lk("%s\n", b);
+ goto err_out;
+ }
+ break;
+ }
+ }
+ clear_scsi_pt_obj(pbp);
}
-#endif
+err_out:
+ if (pbp)
+ destruct_scsi_pt_obj(pbp);
+ if (num_errs > 0)
+ pr2serr_lk("id=%d: number of errors: %d\n", id, num_errs);
+ sync_starts += thr_sync_starts;
}
static void
@@ -513,6 +580,7 @@ work_thread(int id, struct opts_t * op)
{
int thr_async_starts = 0;
int thr_async_finishes = 0;
+ int vb = op->verbose;
unsigned int thr_start_eagain_count = 0;
unsigned int thr_fin_eagain_count = 0;
unsigned int seed = 0;
@@ -523,13 +591,14 @@ work_thread(int id, struct opts_t * op)
bool is_rw = (SCSI_TUR != op->c2e);
char ebuff[EBUFF_SZ];
uint64_t lba;
- unsigned char * lbp;
+ uint8_t * lbp;
+ uint8_t * free_lbp = NULL;
const char * dev_name;
const char * err = NULL;
Rand_uint * ruip = NULL;
struct pollfd pfd[1];
- list<unsigned char *> free_lst; /* of aligned lb buffers */
- map<int, unsigned char *> pi_2_buff; /* pack_id -> lb buffer */
+ list<pair<uint8_t *, uint8_t *> > free_lst; /* of aligned lb buffers */
+ map<int, uint8_t *> pi_2_buff; /* pack_id -> lb buffer */
map<int, uint64_t> pi_2_lba; /* pack_id -> LBA */
/* device name and hi_lba may depend on id */
@@ -540,14 +609,18 @@ work_thread(int id, struct opts_t * op)
else
hi_lba = op->hi_lba;
- if (op->verbose) {
- if ((op->verbose > 1) && hi_lba)
+ if (vb) {
+ if ((vb > 1) && hi_lba)
pr2serr_lk("Enter work_thread id=%d using %s\n"
" LBA range: 0x%x to 0x%x (inclusive)\n",
id, dev_name, (unsigned int)op->lba, hi_lba);
else
pr2serr_lk("Enter work_thread id=%d using %s\n", id, dev_name);
}
+ if (op->generic_pt) {
+ work_sync_thread(id, dev_name, hi_lba, op);
+ return;
+ }
if (! op->block)
open_flags |= O_NONBLOCK;
@@ -561,7 +634,7 @@ work_thread(int id, struct opts_t * op)
pfd[0].events = POLLIN;
if (is_rw && hi_lba) {
seed = get_urandom_uint();
- if (op->verbose > 1)
+ if (vb > 1)
pr2serr_lk(" id=%d, /dev/urandom seed=0x%x\n", id, seed);
ruip = new Rand_uint((unsigned int)op->lba, hi_lba, seed);
}
@@ -575,7 +648,7 @@ work_thread(int id, struct opts_t * op)
sg_flags |= SG_FLAG_DIRECT_IO;
if (op->no_xfer)
sg_flags |= SG_FLAG_NO_DXFER;
- if (op->verbose > 1)
+ if (vb > 1)
pr2serr_lk(" id=%d, sg_flags=0x%x, %s cmds\n", id, sg_flags,
((SCSI_TUR == op->c2e) ? "TUR":
((SCSI_READ16 == op->c2e) ? "READ" : "WRITE")));
@@ -591,13 +664,13 @@ work_thread(int id, struct opts_t * op)
pack_id = uniq_pack_id.fetch_add(1);
if (is_rw) { /* get new lb buffer or one from free list */
if (free_lst.empty()) {
- lbp = get_aligned_heap(op->lb_sz);
+ lbp = sg_memalign(op->lb_sz, 0, &free_lbp, vb);
if (NULL == lbp) {
err = "out of memory";
break;
}
} else {
- lbp = free_lst.back();
+ lbp = free_lst.back().first;
free_lst.pop_back();
}
} else
@@ -605,7 +678,7 @@ work_thread(int id, struct opts_t * op)
if (is_rw) {
if (ruip) {
lba = ruip->get(); /* fetch a random LBA */
- if (op->verbose > 3)
+ if (vb > 3)
pr2serr_lk(" id=%d: start IO at lba=0x%" PRIx64 "\n",
id, lba);
} else
@@ -706,13 +779,13 @@ work_thread(int id, struct opts_t * op)
lbp = p->second;
pi_2_buff.erase(p);
if (lbp)
- free_lst.push_front(lbp);
+ free_lst.push_front(make_pair(lbp, free_lbp));
}
if (ruip && (pack_id > 0)) {
auto q = pi_2_lba.find(pack_id);
if (q != pi_2_lba.end()) {
- if (op->verbose > 3)
+ if (vb > 3)
pr2serr_lk(" id=%d: finish IO at lba=0x%" PRIx64
"\n", id, q->second);
pi_2_lba.erase(q);
@@ -741,12 +814,13 @@ work_thread(int id, struct opts_t * op)
pr2serr_lk("thread id=%d Still %d elements in pi_2_buff map on "
"exit\n", id, n);
for (k = 0; ! free_lst.empty(); ++k) {
- lbp = free_lst.back();
+ lbp = free_lst.back().first;
+ free_lbp = free_lst.back().second;
free_lst.pop_back();
- if (lbp)
- free(lbp);
+ if (free_lbp)
+ free(free_lbp);
}
- if ((op->verbose > 2) && (k > 0))
+ if ((vb > 2) && (k > 0))
pr2serr_lk("thread id=%d Maximum number of READ/WRITEs queued: %d\n",
id, k);
async_starts += thr_async_starts;
@@ -766,10 +840,10 @@ do_inquiry_prod_id(const char * dev_name, int block, char * b, int b_mlen)
{
int sg_fd, ok, ret;
struct sg_io_hdr pt;
- unsigned char inqCmdBlk [INQ_CMD_LEN] =
+ uint8_t inqCmdBlk [INQ_CMD_LEN] =
{0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
- unsigned char inqBuff[INQ_REPLY_LEN];
- unsigned char sense_buffer[64];
+ uint8_t inqBuff[INQ_REPLY_LEN];
+ uint8_t sense_buffer[64];
int open_flags = O_RDWR; /* O_EXCL | O_RDONLY fails with EPERM */
if (! block)
@@ -843,9 +917,9 @@ do_read_capacity(const char * dev_name, int block, unsigned int * last_lba,
unsigned int * blk_sz)
{
int res, sg_fd;
- unsigned char rcCmdBlk [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- unsigned char rcBuff[64];
- unsigned char sense_b[64];
+ uint8_t rcCmdBlk [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t rcBuff[64];
+ uint8_t sense_b[64];
sg_io_hdr_t io_hdr;
int open_flags = O_RDWR; /* O_EXCL | O_RDONLY fails with EPERM */
@@ -926,7 +1000,7 @@ main(int argc, char * argv[])
while (1) {
int option_index = 0;
- c = getopt_long(argc, argv, "dfhl:M:n:Nq:Q:Rs:St:TvVw:W",
+ c = getopt_long(argc, argv, "dfghl:M:n:Nq:Q:Rs:St:TvVw:W",
long_options, &option_index);
if (c == -1)
break;
@@ -938,6 +1012,9 @@ main(int argc, char * argv[])
case 'f':
force = true;
break;
+ case 'g':
+ op->generic_pt = true;
+ break;
case 'h':
case '?':
usage();
@@ -1083,6 +1160,7 @@ main(int argc, char * argv[])
}
if (0 == op->dev_names.size()) {
+ fprintf(stderr, "No sg_disk_device-s given\n\n");
usage();
return 1;
}
@@ -1167,10 +1245,13 @@ main(int argc, char * argv[])
delete vt[k];
n = uniq_pack_id.load() - 1;
- if ((n > 0) && (0 == clock_gettime(CLOCK_MONOTONIC, &end_tm))) {
+ if (((n > 0) || op->generic_pt) &&
+ (0 == clock_gettime(CLOCK_MONOTONIC, &end_tm))) {
struct timespec res_tm;
double a, b;
+ if (op->generic_pt)
+ n = op->num_per_thread * num_threads;
res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec;
res_tm.tv_nsec = end_tm.tv_nsec - start_tm.tv_nsec;
if (res_tm.tv_nsec < 0) {
@@ -1188,6 +1269,7 @@ main(int argc, char * argv[])
}
if (op->verbose || op->stats) {
+ cout << "Number of sync_starts: " << sync_starts.load() << endl;
cout << "Number of async_starts: " << async_starts.load() << endl;
cout << "Number of async_finishes: " << async_finishes.load() <<
endl;
diff --git a/testing/tst_sg_lib.c b/testing/tst_sg_lib.c
index b8e2e2ad..0ee0a184 100644
--- a/testing/tst_sg_lib.c
+++ b/testing/tst_sg_lib.c
@@ -225,7 +225,7 @@ get_exit_status_str(int exit_status, bool longer, int b_len, char * b)
static uint8_t arr[64];
-#define OFF 8
+#define OFF 7 /* in byteswap mode, can test different alignments (def: 8) */
int
main(int argc, char * argv[])