aboutsummaryrefslogtreecommitdiff
path: root/lib/sg_pt_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sg_pt_linux.c')
-rw-r--r--lib/sg_pt_linux.c1181
1 files changed, 1181 insertions, 0 deletions
diff --git a/lib/sg_pt_linux.c b/lib/sg_pt_linux.c
new file mode 100644
index 00000000..6d282f50
--- /dev/null
+++ b/lib/sg_pt_linux.c
@@ -0,0 +1,1181 @@
+/*
+ * Copyright (c) 2005-2021 Douglas Gilbert.
+ * All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/* sg_pt_linux version 1.54 20210923 */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h> /* to define 'major' */
+#ifndef major
+#include <sys/types.h>
+#endif
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <linux/major.h>
+
+#include "sg_pt.h"
+#include "sg_lib.h"
+#include "sg_linux_inc.h"
+#include "sg_pt_linux.h"
+#include "sg_pr2serr.h"
+
+
+#ifdef major
+#define SG_DEV_MAJOR major
+#else
+#ifdef HAVE_LINUX_KDEV_T_H
+#include <linux/kdev_t.h>
+#endif
+#define SG_DEV_MAJOR MAJOR /* MAJOR() macro faulty if > 255 minors */
+#endif
+
+#ifndef BLOCK_EXT_MAJOR
+#define BLOCK_EXT_MAJOR 259
+#endif
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs (60 seconds) */
+
+/* sg driver displayed format: [x]xyyzz --> [x]x.[y]y.zz */
+#define SG_LINUX_SG_VER_V4_BASE 40000 /* lowest sg driver version with
+ * v4 interface */
+#define SG_LINUX_SG_VER_V4_FULL 40030 /* lowest version with full v4
+ * interface */
+
+static const char * linux_host_bytes[] = {
+ "DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT",
+ "DID_BAD_TARGET", "DID_ABORT", "DID_PARITY", "DID_ERROR",
+ "DID_RESET", "DID_BAD_INTR", "DID_PASSTHROUGH", "DID_SOFT_ERROR",
+ "DID_IMM_RETRY", "DID_REQUEUE" /* 0xd */,
+ "DID_TRANSPORT_DISRUPTED", "DID_TRANSPORT_FAILFAST",
+ "DID_TARGET_FAILURE" /* 0x10 */,
+ "DID_NEXUS_FAILURE (reservation conflict)",
+ "DID_ALLOC_FAILURE",
+ "DID_MEDIUM_ERROR",
+ "DID_TRANSPORT_MARGINAL", /*0x14 */
+};
+
+/* These where made obsolete around lk 5.12.0 . Only DRIVER_SENSE [0x8] is
+ * defined in scsi/sg.h for backward comaptibility */
+static const char * linux_driver_bytes[] = {
+ "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA",
+ "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD",
+ "DRIVER_SENSE"
+};
+
+#if 0
+
+static const char * linux_driver_suggests[] = {
+ "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP",
+ "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN",
+ "SUGGEST_SENSE"
+};
+
+#endif
+
+/*
+ * These defines are for constants that should be visible in the
+ * /usr/include/scsi directory (brought in by sg_linux_inc.h).
+ * Redefined and aliased here to decouple this code from
+ * sg_io_linux.h N.B. the SUGGEST_* constants are no longer used.
+ */
+#ifndef DRIVER_MASK
+#define DRIVER_MASK 0x0f
+#endif
+#ifndef SUGGEST_MASK
+#define SUGGEST_MASK 0xf0 /* Suggest mask is obsolete */
+#endif
+#ifndef DRIVER_SENSE
+#define DRIVER_SENSE 0x08
+#endif
+#define SG_LIB_DRIVER_MASK DRIVER_MASK
+#define SG_LIB_SUGGEST_MASK SUGGEST_MASK
+#define SG_LIB_DRIVER_SENSE DRIVER_SENSE
+
+bool sg_bsg_nvme_char_major_checked = false;
+int sg_bsg_major = 0;
+volatile int sg_nvme_char_major = 0;
+
+bool sg_checked_version_num = false;
+int sg_driver_version_num = 0;
+bool sg_duration_set_nano = false;
+
+long sg_lin_page_size = 4096; /* default, overridden with correct value */
+
+
+/* This function only needs to be called once (unless a NVMe controller
+ * can be hot-plugged into system in which case it should be called
+ * (again) after that event). */
+void
+sg_find_bsg_nvme_char_major(int verbose)
+{
+ bool got_one = false;
+ int n;
+ const char * proc_devices = "/proc/devices";
+ char * cp;
+ FILE *fp;
+ char a[128];
+ char b[128];
+
+ sg_lin_page_size = sysconf(_SC_PAGESIZE);
+ if (NULL == (fp = fopen(proc_devices, "r"))) {
+ if (verbose)
+ pr2ws("fopen %s failed: %s\n", proc_devices, strerror(errno));
+ return;
+ }
+ while ((cp = fgets(b, sizeof(b), fp))) {
+ if ((1 == sscanf(b, "%126s", a)) &&
+ (0 == memcmp(a, "Character", 9)))
+ break;
+ }
+ while (cp && (cp = fgets(b, sizeof(b), fp))) {
+ if (2 == sscanf(b, "%d %126s", &n, a)) {
+ if (0 == strcmp("bsg", a)) {
+ sg_bsg_major = n;
+ if (got_one)
+ break;
+ got_one = true;
+ } else if (0 == strcmp("nvme", a)) {
+ sg_nvme_char_major = n;
+ if (got_one)
+ break;
+ got_one = true;
+ }
+ } else
+ break;
+ }
+ if (verbose > 3) {
+ if (cp) {
+ if (sg_bsg_major > 0)
+ pr2ws("found sg_bsg_major=%d\n", sg_bsg_major);
+ if (sg_nvme_char_major > 0)
+ pr2ws("found sg_nvme_char_major=%d\n", sg_nvme_char_major);
+ } else
+ pr2ws("found no bsg not nvme char device in %s\n", proc_devices);
+ }
+ fclose(fp);
+}
+
+/* Assumes that sg_find_bsg_nvme_char_major() has already been called. Returns
+ * true if dev_fd is a scsi generic pass-through device. If yields
+ * *is_nvme_p = true with *nsid_p = 0 then dev_fd is a NVMe char device.
+ * If yields *nsid_p > 0 then dev_fd is a NVMe block device. */
+static bool
+check_file_type(int dev_fd, struct stat * dev_statp, bool * is_bsg_p,
+ bool * is_nvme_p, uint32_t * nsid_p, int * os_err_p,
+ int verbose)
+{
+ bool is_nvme = false;
+ bool is_sg = false;
+ bool is_bsg = false;
+ bool is_block = false;
+ int os_err = 0;
+ int major_num;
+ uint32_t nsid = 0; /* invalid NSID */
+
+ if (dev_fd >= 0) {
+ if (fstat(dev_fd, dev_statp) < 0) {
+ os_err = errno;
+ if (verbose)
+ pr2ws("%s: fstat() failed: %s (errno=%d)\n", __func__,
+ safe_strerror(os_err), os_err);
+ goto skip_out;
+ }
+ major_num = (int)SG_DEV_MAJOR(dev_statp->st_rdev);
+ if (S_ISCHR(dev_statp->st_mode)) {
+ if (SCSI_GENERIC_MAJOR == major_num)
+ is_sg = true;
+ else if (sg_bsg_major == major_num)
+ is_bsg = true;
+ else if (sg_nvme_char_major == major_num)
+ is_nvme = true;
+ } else if (S_ISBLK(dev_statp->st_mode)) {
+ is_block = true;
+ if (BLOCK_EXT_MAJOR == major_num) {
+ is_nvme = true;
+ nsid = ioctl(dev_fd, NVME_IOCTL_ID, NULL);
+ if (SG_NVME_BROADCAST_NSID == nsid) { /* means ioctl error */
+ os_err = errno;
+ if (verbose)
+ pr2ws("%s: ioctl(NVME_IOCTL_ID) failed: %s "
+ "(errno=%d)\n", __func__, safe_strerror(os_err),
+ os_err);
+ } else
+ os_err = 0;
+ }
+ }
+ } else {
+ os_err = EBADF;
+ if (verbose)
+ pr2ws("%s: invalid file descriptor (%d)\n", __func__, dev_fd);
+ }
+skip_out:
+ if (verbose > 3) {
+ pr2ws("%s: file descriptor is ", __func__);
+ if (is_sg)
+ pr2ws("sg device\n");
+ else if (is_bsg)
+ pr2ws("bsg device\n");
+ else if (is_nvme && (0 == nsid))
+ pr2ws("NVMe char device\n");
+ else if (is_nvme)
+ pr2ws("NVMe block device, nsid=%lld\n",
+ ((uint32_t)-1 == nsid) ? -1LL : (long long)nsid);
+ else if (is_block)
+ pr2ws("block device\n");
+ else
+ pr2ws("undetermined device, could be regular file\n");
+ }
+ if (is_bsg_p)
+ *is_bsg_p = is_bsg;
+ if (is_nvme_p)
+ *is_nvme_p = is_nvme;
+ if (nsid_p)
+ *nsid_p = nsid;
+ if (os_err_p)
+ *os_err_p = os_err;
+ return is_sg;
+}
+
+/* Assumes dev_fd is an "open" file handle associated with device_name. If
+ * the implementation (possibly for one OS) cannot determine from dev_fd if
+ * a SCSI or NVMe pass-through is referenced, then it might guess based on
+ * device_name. Returns 1 if SCSI generic pass-though device, returns 2 if
+ * secondary SCSI pass-through device (in Linux a bsg device); returns 3 is
+ * char NVMe device (i.e. no NSID); returns 4 if block NVMe device (includes
+ * NSID), or 0 if something else (e.g. ATA block device) or dev_fd < 0.
+ * If error, returns negated errno (operating system) value. */
+int
+check_pt_file_handle(int dev_fd, const char * device_name, int verbose)
+{
+ if (verbose > 4)
+ pr2ws("%s: dev_fd=%d, device_name: %s\n", __func__, dev_fd,
+ device_name);
+ /* Linux doesn't need device_name to determine which pass-through */
+ if (! sg_bsg_nvme_char_major_checked) {
+ sg_bsg_nvme_char_major_checked = true;
+ sg_find_bsg_nvme_char_major(verbose);
+ }
+ if (dev_fd >= 0) {
+ bool is_sg, is_bsg, is_nvme;
+ int err;
+ uint32_t nsid;
+ struct stat a_stat;
+
+ is_sg = check_file_type(dev_fd, &a_stat, &is_bsg, &is_nvme, &nsid,
+ &err, verbose);
+ if (err)
+ return -err;
+ else if (is_sg)
+ return 1;
+ else if (is_bsg)
+ return 2;
+ else if (is_nvme && (0 == nsid))
+ return 3;
+ else if (is_nvme)
+ return 4;
+ else
+ return 0;
+ } else
+ return 0;
+}
+
+/*
+ * We make a runtime decision whether to use the sg v3 interface or the sg
+ * v4 interface (currently exclusively used by the bsg driver). If all the
+ * following are true we use sg v4 which is only currently supported on bsg
+ * device nodes:
+ * a) there is a bsg entry in the /proc/devices file
+ * b) the device node given to scsi_pt_open() is a char device
+ * c) the char major number of the device node given to scsi_pt_open()
+ * matches the char major number of the bsg entry in /proc/devices
+ * Otherwise the sg v3 interface is used.
+ *
+ * Note that in either case we prepare the data in a sg v4 structure. If
+ * the runtime tests indicate that the v3 interface is needed then
+ * do_scsi_pt_v3() transfers the input data into a v3 structure and
+ * then the output data is transferred back into a sg v4 structure.
+ * That implementation detail could change in the future.
+ *
+ * [20120806] Only use MAJOR() macro in kdev_t.h if that header file is
+ * available and major() macro [N.B. lower case] is not available.
+ */
+
+
+#ifdef major
+#define SG_DEV_MAJOR major
+#else
+#ifdef HAVE_LINUX_KDEV_T_H
+#include <linux/kdev_t.h>
+#endif
+#define SG_DEV_MAJOR MAJOR /* MAJOR() macro faulty if > 255 minors */
+#endif
+
+
+/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed */
+/* together. The 'flags' argument is advisory and may be ignored. */
+/* Returns >= 0 if successful, otherwise returns negated errno. */
+int
+scsi_pt_open_flags(const char * device_name, int flags, int verbose)
+{
+ int fd;
+
+ if (! sg_bsg_nvme_char_major_checked) {
+ sg_bsg_nvme_char_major_checked = true;
+ sg_find_bsg_nvme_char_major(verbose);
+ }
+ if (verbose > 1) {
+ pr2ws("open %s with flags=0x%x\n", device_name, flags);
+ }
+ fd = open(device_name, flags);
+ if (fd < 0) {
+ fd = -errno;
+ if (verbose > 1)
+ pr2ws("%s: open(%s, 0x%x) failed: %s\n", __func__, device_name,
+ flags, safe_strerror(-fd));
+ }
+ return fd;
+}
+
+/* Returns >= 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
+{
+ int oflags = O_NONBLOCK;
+
+ oflags |= (read_only ? O_RDONLY : O_RDWR);
+ return scsi_pt_open_flags(device_name, oflags, verbose);
+}
+
+/* Returns 0 if successful. If error in Unix returns negated errno. */
+int
+scsi_pt_close_device(int device_fd)
+{
+ int res;
+
+ res = close(device_fd);
+ if (res < 0)
+ res = -errno;
+ return res;
+}
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+static bool checked_ev_dsense = false;
+static bool ev_dsense = false;
+#endif
+
+
+/* Caller should additionally call get_scsi_pt_os_err() after this call */
+struct sg_pt_base *
+construct_scsi_pt_obj_with_fd(int dev_fd, int verbose)
+{
+ struct sg_pt_linux_scsi * ptp;
+
+ ptp = (struct sg_pt_linux_scsi *)
+ calloc(1, sizeof(struct sg_pt_linux_scsi));
+ if (ptp) {
+ int err;
+
+#if (HAVE_NVME && (! IGNORE_NVME))
+ sntl_init_dev_stat(&ptp->dev_stat);
+ if (! checked_ev_dsense) {
+ ev_dsense = sg_get_initial_dsense();
+ checked_ev_dsense = true;
+ }
+ ptp->dev_stat.scsi_dsense = ev_dsense;
+#endif
+ err = set_pt_file_handle((struct sg_pt_base *)ptp, dev_fd, verbose);
+ if ((0 == err) && (! ptp->is_nvme)) {
+ ptp->io_hdr.guard = 'Q';
+#ifdef BSG_PROTOCOL_SCSI
+ ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI;
+#endif
+#ifdef BSG_SUB_PROTOCOL_SCSI_CMD
+ ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+#endif
+ }
+ } else if (verbose)
+ pr2ws("%s: calloc() failed, out of memory?\n", __func__);
+
+ return (struct sg_pt_base *)ptp;
+}
+
+struct sg_pt_base *
+construct_scsi_pt_obj()
+{
+ return construct_scsi_pt_obj_with_fd(-1 /* dev_fd */, 0 /* verbose */);
+}
+
+void
+destruct_scsi_pt_obj(struct sg_pt_base * vp)
+{
+
+ if (NULL == vp)
+ pr2ws(">>>>>>> Warning: %s called with NULL pointer\n", __func__);
+ else {
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (ptp->free_nvme_id_ctlp) {
+ free(ptp->free_nvme_id_ctlp);
+ ptp->free_nvme_id_ctlp = NULL;
+ ptp->nvme_id_ctlp = NULL;
+ }
+ if (vp)
+ free(vp);
+ }
+}
+
+/* Remembers previous device file descriptor */
+void
+clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (ptp) {
+ bool is_sg, is_bsg, is_nvme;
+ int fd;
+ uint32_t nvme_nsid;
+ struct sg_sntl_dev_state_t dev_stat;
+
+ fd = ptp->dev_fd;
+ is_sg = ptp->is_sg;
+ is_bsg = ptp->is_bsg;
+ is_nvme = ptp->is_nvme;
+ nvme_nsid = ptp->nvme_nsid;
+ dev_stat = ptp->dev_stat;
+ if (ptp->free_nvme_id_ctlp)
+ free(ptp->free_nvme_id_ctlp);
+ memset(ptp, 0, sizeof(struct sg_pt_linux_scsi));
+ ptp->io_hdr.guard = 'Q';
+#ifdef BSG_PROTOCOL_SCSI
+ ptp->io_hdr.protocol = BSG_PROTOCOL_SCSI;
+#endif
+#ifdef BSG_SUB_PROTOCOL_SCSI_CMD
+ ptp->io_hdr.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD;
+#endif
+ ptp->dev_fd = fd;
+ ptp->is_sg = is_sg;
+ ptp->is_bsg = is_bsg;
+ ptp->is_nvme = is_nvme;
+ ptp->nvme_our_sntl = false;
+ ptp->nvme_nsid = nvme_nsid;
+ ptp->dev_stat = dev_stat;
+ }
+}
+
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return;
+ ptp->in_err = 0;
+ ptp->os_err = 0;
+ ptp->io_hdr.device_status = 0;
+ ptp->io_hdr.transport_status = 0;
+ ptp->io_hdr.driver_status = 0;
+ ptp->io_hdr.din_xferp = 0;
+ ptp->io_hdr.din_xfer_len = 0;
+ ptp->io_hdr.dout_xferp = 0;
+ ptp->io_hdr.dout_xfer_len = 0;
+ ptp->nvme_result = 0;
+}
+
+#ifndef SG_SET_GET_EXTENDED
+
+/* If both sei_wr_mask and sei_rd_mask are 0, this ioctl does nothing */
+struct sg_extended_info {
+ uint32_t sei_wr_mask; /* OR-ed SG_SEIM_* user->driver values */
+ uint32_t sei_rd_mask; /* OR-ed SG_SEIM_* driver->user values */
+ uint32_t ctl_flags_wr_mask; /* OR-ed SG_CTL_FLAGM_* values */
+ uint32_t ctl_flags_rd_mask; /* OR-ed SG_CTL_FLAGM_* values */
+ uint32_t ctl_flags; /* bit values OR-ed, see SG_CTL_FLAGM_* */
+ uint32_t read_value; /* write SG_SEIRV_*, read back related */
+
+ uint32_t reserved_sz; /* data/sgl size of pre-allocated request */
+ uint32_t tot_fd_thresh; /* total data/sgat for this fd, 0: no limit */
+ uint32_t minor_index; /* rd: kernel's sg device minor number */
+ uint32_t share_fd; /* SHARE_FD and CHG_SHARE_FD use this */
+ uint32_t sgat_elem_sz; /* sgat element size (must be power of 2) */
+ uint8_t pad_to_96[52]; /* pad so struct is 96 bytes long */
+};
+
+#define SG_IOCTL_MAGIC_NUM 0x22
+
+#define SG_SET_GET_EXTENDED _IOWR(SG_IOCTL_MAGIC_NUM, 0x51, \
+ struct sg_extended_info)
+
+#define SG_SEIM_CTL_FLAGS 0x1
+
+#define SG_CTL_FLAGM_TIME_IN_NS 0x1
+
+#endif
+
+/* Forget any previous dev_fd and install the one given. May attempt to
+ * find file type (e.g. if pass-though) from OS so there could be an error.
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
+ * will return. dev_fd should be >= 0 for a valid file handle or -1 . */
+int
+set_pt_file_handle(struct sg_pt_base * vp, int dev_fd, int verbose)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+ struct stat a_stat;
+
+ if (! sg_bsg_nvme_char_major_checked) {
+ sg_bsg_nvme_char_major_checked = true;
+ sg_find_bsg_nvme_char_major(verbose);
+ }
+ ptp->dev_fd = dev_fd;
+ if (dev_fd >= 0) {
+ ptp->is_sg = check_file_type(dev_fd, &a_stat, &ptp->is_bsg,
+ &ptp->is_nvme, &ptp->nvme_nsid,
+ &ptp->os_err, verbose);
+ if (ptp->is_sg && (! sg_checked_version_num)) {
+ if (ioctl(dev_fd, SG_GET_VERSION_NUM, &ptp->sg_version) < 0) {
+ ptp->sg_version = 0;
+ if (verbose > 3)
+ pr2ws("%s: ioctl(SG_GET_VERSION_NUM) failed: errno: %d "
+ "[%s]\n", __func__, errno, safe_strerror(errno));
+ } else { /* got version number */
+ sg_driver_version_num = ptp->sg_version;
+ sg_checked_version_num = true;
+ }
+ if (verbose > 4) {
+ int ver = ptp->sg_version;
+
+ if (ptp->sg_version >= SG_LINUX_SG_VER_V4_BASE) {
+#ifdef IGNORE_LINUX_SGV4
+ pr2ws("%s: sg driver version %d.%02d.%02d but config "
+ "override back to v3\n", __func__, ver / 10000,
+ (ver / 100) % 100, ver % 100);
+#else
+ pr2ws("%s: sg driver version %d.%02d.%02d so choose v4\n",
+ __func__, ver / 10000, (ver / 100) % 100,
+ ver % 100);
+#endif
+ } else if (verbose > 5)
+ pr2ws("%s: sg driver version %d.%02d.%02d so choose v3\n",
+ __func__, ver / 10000, (ver / 100) % 100,
+ ver % 100);
+ }
+ } else if (ptp->is_sg)
+ ptp->sg_version = sg_driver_version_num;
+
+ if (ptp->is_sg && (ptp->sg_version >= SG_LINUX_SG_VER_V4_FULL) &&
+ getenv("SG3_UTILS_LINUX_NANO")) {
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip = &sei;
+
+ memset(seip, 0, sizeof(*seip));
+ /* try to override default of milliseconds */
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ if (ioctl(dev_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ if (verbose > 2)
+ pr2ws("%s: unable to override milli --> nanoseconds: "
+ "%s\n", __func__, safe_strerror(errno));
+ } else {
+ if (! sg_duration_set_nano)
+ sg_duration_set_nano = true;
+ if (verbose > 5)
+ pr2ws("%s: dev_fd=%d, succeeding in setting durations "
+ "to nanoseconds\n", __func__, dev_fd);
+ }
+ } else if (ptp->is_sg && (ptp->sg_version >= SG_LINUX_SG_VER_V4_BASE)
+ && getenv("SG3_UTILS_LINUX_NANO")) {
+ if (verbose > 2)
+ pr2ws("%s: dev_fd=%d, ignored SG3_UTILS_LINUX_NANO\nbecause "
+ "base version sg version 4 driver\n", __func__, dev_fd);
+ }
+ } else {
+ ptp->is_sg = false;
+ ptp->is_bsg = false;
+ ptp->is_nvme = false;
+ ptp->nvme_our_sntl = false;
+ ptp->nvme_nsid = 0;
+ ptp->os_err = 0;
+ }
+ return ptp->os_err;
+}
+
+int
+sg_linux_get_sg_version(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->sg_version;
+}
+
+/* Valid file handles (which is the return value) are >= 0 . Returns -1
+ * if there is no valid file handle. */
+int
+get_pt_file_handle(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->dev_fd;
+}
+
+void
+set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
+ int cdb_len)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.request = (__u64)(sg_uintptr_t)cdb;
+ ptp->io_hdr.request_len = cdb_len;
+}
+
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->io_hdr.request_len;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return (uint8_t *)(sg_uintptr_t)ptp->io_hdr.request;
+}
+
+void
+set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
+ int max_sense_len)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (sense) {
+ if (max_sense_len > 0)
+ memset(sense, 0, max_sense_len);
+ }
+ ptp->io_hdr.response = (__u64)(sg_uintptr_t)sense;
+ ptp->io_hdr.max_response_len = max_sense_len;
+}
+
+/* Setup for data transfer from device */
+void
+set_scsi_pt_data_in(struct sg_pt_base * vp, uint8_t * dxferp,
+ int dxfer_ilen)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (ptp->io_hdr.din_xferp)
+ ++ptp->in_err;
+ if (dxfer_ilen > 0) {
+ ptp->io_hdr.din_xferp = (__u64)(sg_uintptr_t)dxferp;
+ ptp->io_hdr.din_xfer_len = dxfer_ilen;
+ }
+}
+
+/* Setup for data transfer toward device */
+void
+set_scsi_pt_data_out(struct sg_pt_base * vp, const uint8_t * dxferp,
+ int dxfer_olen)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (ptp->io_hdr.dout_xferp)
+ ++ptp->in_err;
+ if (dxfer_olen > 0) {
+ ptp->io_hdr.dout_xferp = (__u64)(sg_uintptr_t)dxferp;
+ ptp->io_hdr.dout_xfer_len = dxfer_olen;
+ }
+}
+
+void
+set_pt_metadata_xfer(struct sg_pt_base * vp, uint8_t * dxferp,
+ uint32_t dxfer_len, bool out_true)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (dxfer_len > 0) {
+ ptp->mdxferp = dxferp;
+ ptp->mdxfer_len = dxfer_len;
+ ptp->mdxfer_out = out_true;
+ }
+}
+
+void
+set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.request_extra = pack_id; /* was placed in spare_in */
+}
+
+void
+set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.request_tag = tag;
+}
+
+/* Note that task management function codes are transport specific */
+void
+set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.subprotocol = 1; /* SCSI task management function */
+ ptp->tmf_request[0] = (uint8_t)tmf_code; /* assume it fits */
+ ptp->io_hdr.request = (__u64)(sg_uintptr_t)(&(ptp->tmf_request[0]));
+ ptp->io_hdr.request_len = 1;
+}
+
+void
+set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.request_attr = attribute;
+ ptp->io_hdr.request_priority = priority;
+}
+
+#ifndef BSG_FLAG_Q_AT_TAIL
+#define BSG_FLAG_Q_AT_TAIL 0x10
+#endif
+#ifndef BSG_FLAG_Q_AT_HEAD
+#define BSG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+/* Need this later if translated to v3 interface */
+#ifndef SG_FLAG_Q_AT_TAIL
+#define SG_FLAG_Q_AT_TAIL 0x10
+#endif
+#ifndef SG_FLAG_Q_AT_HEAD
+#define SG_FLAG_Q_AT_HEAD 0x20
+#endif
+
+void
+set_scsi_pt_flags(struct sg_pt_base * vp, int flags)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ /* default action of bsg driver (sg v4) is QUEUE_AT_HEAD */
+ /* default action of block layer SG_IO ioctl is QUEUE_AT_TAIL */
+ if (SCSI_PT_FLAGS_QUEUE_AT_HEAD & flags) { /* favour AT_HEAD */
+ ptp->io_hdr.flags |= BSG_FLAG_Q_AT_HEAD;
+ ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_TAIL;
+ } else if (SCSI_PT_FLAGS_QUEUE_AT_TAIL & flags) {
+ ptp->io_hdr.flags |= BSG_FLAG_Q_AT_TAIL;
+ ptp->io_hdr.flags &= ~BSG_FLAG_Q_AT_HEAD;
+ }
+}
+
+/* If supported it is the number of bytes requested to transfer less the
+ * number actually transferred. This it typically important for data-in
+ * transfers. For data-out (only) transfers, the 'dout_req_len -
+ * dout_act_len' is returned. For bidi transfer the "din" residual is
+ * returned. */
+/* N.B. Returns din_resid and ignores dout_resid */
+int
+get_scsi_pt_resid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if ((NULL == ptp) || (ptp->is_nvme && ! ptp->nvme_our_sntl))
+ return 0;
+ else if ((ptp->io_hdr.din_xfer_len > 0) &&
+ (ptp->io_hdr.dout_xfer_len > 0))
+ return ptp->io_hdr.din_resid;
+ else if (ptp->io_hdr.dout_xfer_len > 0)
+ return ptp->io_hdr.dout_resid;
+ return ptp->io_hdr.din_resid;
+}
+
+void
+get_pt_req_lengths(const struct sg_pt_base * vp, int * req_dinp,
+ int * req_doutp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (req_dinp) {
+ if (ptp->io_hdr.din_xfer_len > 0)
+ *req_dinp = ptp->io_hdr.din_xfer_len;
+ else
+ *req_dinp = 0;
+ }
+ if (req_doutp) {
+ if (ptp->io_hdr.dout_xfer_len > 0)
+ *req_doutp = ptp->io_hdr.dout_xfer_len;
+ else
+ *req_doutp = 0;
+ }
+}
+
+void
+get_pt_actual_lengths(const struct sg_pt_base * vp, int * act_dinp,
+ int * act_doutp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (act_dinp) {
+ if (ptp->io_hdr.din_xfer_len > 0) {
+ int res = ptp->io_hdr.din_xfer_len - ptp->io_hdr.din_resid;
+
+ *act_dinp = (res > 0) ? res : 0;
+ } else
+ *act_dinp = 0;
+ }
+ if (act_doutp) {
+ if (ptp->io_hdr.dout_xfer_len > 0)
+ *act_doutp = ptp->io_hdr.dout_xfer_len - ptp->io_hdr.dout_resid;
+ else
+ *act_doutp = 0;
+ }
+}
+
+int
+get_scsi_pt_status_response(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return 0;
+ return (int)((ptp->is_nvme && ! ptp->nvme_our_sntl) ?
+ ptp->nvme_status : ptp->io_hdr.device_status);
+}
+
+uint32_t
+get_pt_result(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ if (NULL == ptp)
+ return 0;
+ return (ptp->is_nvme && ! ptp->nvme_our_sntl) ?
+ ptp->nvme_result : ptp->io_hdr.device_status;
+}
+
+int
+get_scsi_pt_sense_len(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->io_hdr.response_len;
+}
+
+uint8_t *
+get_scsi_pt_sense_buf(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+}
+
+int
+get_scsi_pt_duration_ms(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return sg_duration_set_nano ? (ptp->io_hdr.duration / 1000) :
+ ptp->io_hdr.duration;
+}
+
+/* If not available return 0 otherwise return number of nanoseconds that the
+ * lower layers (and hardware) took to execute the command just completed. */
+uint64_t
+get_pt_duration_ns(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return sg_duration_set_nano ? (uint32_t)ptp->io_hdr.duration : 0;
+}
+
+int
+get_scsi_pt_transport_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->io_hdr.transport_status;
+}
+
+void
+set_scsi_pt_transport_err(struct sg_pt_base * vp, int err)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ ptp->io_hdr.transport_status = err;
+}
+
+/* Returns b which will contain a null char terminated string (if
+ * max_b_len > 0). Combined driver and transport (called "host" in Linux
+ * kernel) statuses */
+char *
+get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len,
+ char * b)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+ int ds = ptp->io_hdr.driver_status;
+ int hs = ptp->io_hdr.transport_status;
+ int n, m;
+ char * cp = b;
+ int driv;
+ const char * driv_cp = "invalid";
+
+ if (max_b_len < 1)
+ return b;
+ m = max_b_len;
+ n = 0;
+ if (hs) {
+ if ((hs < 0) || (hs >= (int)SG_ARRAY_SIZE(linux_host_bytes)))
+ n = snprintf(cp, m, "Host_status=0x%02x is invalid\n", hs);
+ else
+ n = snprintf(cp, m, "Host_status=0x%02x [%s]\n", hs,
+ linux_host_bytes[hs]);
+ }
+ m -= n;
+ if (m < 1) {
+ b[max_b_len - 1] = '\0';
+ return b;
+ }
+ cp += n;
+ if (ds) {
+ driv = ds & SG_LIB_DRIVER_MASK;
+ if (driv < (int)SG_ARRAY_SIZE(linux_driver_bytes))
+ driv_cp = linux_driver_bytes[driv];
+ n = snprintf(cp, m, "Driver_status=0x%02x [%s]\n", ds, driv_cp);
+ m -= n;
+ }
+ if (m < 1)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+int
+get_scsi_pt_result_category(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+ int dr_st = ptp->io_hdr.driver_status & SG_LIB_DRIVER_MASK;
+ int scsi_st = ptp->io_hdr.device_status & 0x7e;
+
+ if (ptp->os_err)
+ return SCSI_PT_RESULT_OS_ERR;
+ else if (ptp->io_hdr.transport_status)
+ return SCSI_PT_RESULT_TRANSPORT_ERR;
+ else if (dr_st && (SG_LIB_DRIVER_SENSE != dr_st))
+ return SCSI_PT_RESULT_TRANSPORT_ERR;
+ else if ((SG_LIB_DRIVER_SENSE == dr_st) ||
+ (SAM_STAT_CHECK_CONDITION == scsi_st) ||
+ (SAM_STAT_COMMAND_TERMINATED == scsi_st))
+ return SCSI_PT_RESULT_SENSE;
+ else if (scsi_st)
+ return SCSI_PT_RESULT_STATUS;
+ else
+ return SCSI_PT_RESULT_GOOD;
+}
+
+int
+get_scsi_pt_os_err(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->os_err;
+}
+
+char *
+get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+ const char * cp;
+
+ cp = safe_strerror(ptp->os_err);
+ strncpy(b, cp, max_b_len);
+ if ((int)strlen(cp) >= max_b_len)
+ b[max_b_len - 1] = '\0';
+ return b;
+}
+
+bool
+pt_device_is_nvme(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->is_nvme;
+}
+
+/* If a NVMe block device (which includes the NSID) handle is associated
+ * with 'vp', then its NSID is returned (values range from 0x1 to
+ * 0xffffffe). Otherwise 0 is returned. */
+uint32_t
+get_pt_nvme_nsid(const struct sg_pt_base * vp)
+{
+ const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+ return ptp->nvme_nsid;
+}
+
+/* Executes SCSI command using sg v3 interface */
+static int
+do_scsi_pt_v3(struct sg_pt_linux_scsi * ptp, int fd, int time_secs,
+ int verbose)
+{
+ struct sg_io_hdr v3_hdr;
+
+ memset(&v3_hdr, 0, sizeof(v3_hdr));
+ /* convert v4 to v3 header */
+ v3_hdr.interface_id = 'S';
+ v3_hdr.dxfer_direction = SG_DXFER_NONE;
+ v3_hdr.cmdp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.request;
+ v3_hdr.cmd_len = (uint8_t)ptp->io_hdr.request_len;
+ if (ptp->io_hdr.din_xfer_len > 0) {
+ if (ptp->io_hdr.dout_xfer_len > 0) {
+ if (verbose)
+ pr2ws("sgv3 doesn't support bidi\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ v3_hdr.dxferp = (void *)(long)ptp->io_hdr.din_xferp;
+ v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.din_xfer_len;
+ v3_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ } else if (ptp->io_hdr.dout_xfer_len > 0) {
+ v3_hdr.dxferp = (void *)(long)ptp->io_hdr.dout_xferp;
+ v3_hdr.dxfer_len = (unsigned int)ptp->io_hdr.dout_xfer_len;
+ v3_hdr.dxfer_direction = SG_DXFER_TO_DEV;
+ }
+ if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) {
+ v3_hdr.sbp = (uint8_t *)(sg_uintptr_t)ptp->io_hdr.response;
+ v3_hdr.mx_sb_len = (uint8_t)ptp->io_hdr.max_response_len;
+ }
+ v3_hdr.pack_id = (int)ptp->io_hdr.request_extra;
+ if (BSG_FLAG_Q_AT_HEAD & ptp->io_hdr.flags)
+ v3_hdr.flags |= SG_FLAG_Q_AT_HEAD; /* favour AT_HEAD */
+ else if (BSG_FLAG_Q_AT_TAIL & ptp->io_hdr.flags)
+ v3_hdr.flags |= SG_FLAG_Q_AT_TAIL;
+
+ if (NULL == v3_hdr.cmdp) {
+ if (verbose)
+ pr2ws("No SCSI command (cdb) given [v3]\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ /* io_hdr.timeout is in milliseconds, if greater than zero */
+ v3_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT);
+ /* Finally do the v3 SG_IO ioctl */
+ if (ioctl(fd, SG_IO, &v3_hdr) < 0) {
+ ptp->os_err = errno;
+ if (verbose > 1)
+ pr2ws("ioctl(SG_IO v3) failed: %s (errno=%d)\n",
+ safe_strerror(ptp->os_err), ptp->os_err);
+ return -ptp->os_err;
+ }
+ ptp->io_hdr.device_status = (__u32)v3_hdr.status;
+ ptp->io_hdr.driver_status = (__u32)v3_hdr.driver_status;
+ ptp->io_hdr.transport_status = (__u32)v3_hdr.host_status;
+ ptp->io_hdr.response_len = (__u32)v3_hdr.sb_len_wr;
+ ptp->io_hdr.duration = (__u32)v3_hdr.duration;
+ ptp->io_hdr.din_resid = (__s32)v3_hdr.resid;
+ /* v3_hdr.info not passed back since no mapping defined (yet) */
+ return 0;
+}
+
+/* Executes SCSI command using sg v4 interface */
+static int
+do_scsi_pt_v4(struct sg_pt_linux_scsi * ptp, int fd, int time_secs,
+ int verbose)
+{
+ if (0 == ptp->io_hdr.request) {
+ if (verbose)
+ pr2ws("No SCSI command (cdb) given [v4]\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ /* io_hdr.timeout is in milliseconds, if greater than zero */
+ ptp->io_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT);
+ if (ioctl(fd, SG_IO, &ptp->io_hdr) < 0) {
+ ptp->os_err = errno;
+ if (verbose > 1)
+ pr2ws("ioctl(SG_IO v4) failed: %s (errno=%d)\n",
+ safe_strerror(ptp->os_err), ptp->os_err);
+ return -ptp->os_err;
+ }
+ return 0;
+}
+
+/* Executes SCSI command (or at least forwards it to lower layers).
+ * Returns 0 for success, negative numbers are negated 'errno' values from
+ * OS system calls. Positive return values are errors from this package. */
+int
+do_scsi_pt(struct sg_pt_base * vp, int fd, int time_secs, int verbose)
+{
+ struct sg_pt_linux_scsi * ptp = &vp->impl;
+ bool have_checked_for_type = (ptp->dev_fd >= 0);
+
+ if (! sg_bsg_nvme_char_major_checked) {
+ sg_bsg_nvme_char_major_checked = true;
+ sg_find_bsg_nvme_char_major(verbose);
+ }
+ if (ptp->in_err) {
+ if (verbose)
+ pr2ws("Replicated or unused set_scsi_pt... functions\n");
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ if (fd >= 0) {
+ if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) {
+ if (verbose)
+ pr2ws("%s: file descriptor given to create() and here "
+ "differ\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ }
+ ptp->dev_fd = fd;
+ } else if (ptp->dev_fd < 0) {
+ if (verbose)
+ pr2ws("%s: invalid file descriptors\n", __func__);
+ return SCSI_PT_DO_BAD_PARAMS;
+ } else
+ fd = ptp->dev_fd;
+ if (! have_checked_for_type) {
+ int err = set_pt_file_handle(vp, ptp->dev_fd, verbose);
+
+ if (err)
+ return -ptp->os_err;
+ }
+ if (ptp->os_err)
+ return -ptp->os_err;
+ if (verbose > 5)
+ pr2ws("%s: is_nvme=%d, is_sg=%d, is_bsg=%d\n", __func__,
+ (int)ptp->is_nvme, (int)ptp->is_sg, (int)ptp->is_bsg);
+ if (ptp->is_nvme)
+ return sg_do_nvme_pt(vp, -1, time_secs, verbose);
+ else if (ptp->is_sg) {
+#ifdef IGNORE_LINUX_SGV4
+ return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+#else
+ if (ptp->sg_version >= SG_LINUX_SG_VER_V4_BASE)
+ return do_scsi_pt_v4(ptp, fd, time_secs, verbose);
+ else
+ return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+#endif
+ } else if (sg_bsg_major <= 0)
+ return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+ else if (ptp->is_bsg)
+ return do_scsi_pt_v4(ptp, fd, time_secs, verbose);
+ else
+ return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
+
+ pr2ws("%s: Should never reach this point\n", __func__);
+ return 0;
+}