| /* |
| * Copyright (c) 2005-2017 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. |
| */ |
| |
| /* sg_pt_linux version 1.30 20171113 */ |
| |
| |
| #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_nvme.h" |
| |
| #if (__STDC_VERSION__ >= 199901L) /* C99 or later */ |
| typedef intptr_t sg_intptr_t; |
| #else |
| typedef long sg_intptr_t; |
| #endif |
| |
| // xxxxxxxxxxxxxxxx testing <<<<<<<<<<<<<<<<<<<<<<<< |
| // #undef HAVE_LINUX_NVME_IOCTL_H |
| |
| #ifdef HAVE_LINUX_NVME_IOCTL_H |
| #include <linux/nvme_ioctl.h> |
| #else |
| |
| /* |
| * Definitions for the NVM Express ioctl interface |
| * Copyright (c) 2011-2014, Intel Corporation. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| */ |
| |
| #if 0 |
| #include <linux/types.h> |
| |
| struct nvme_user_io { |
| __u8 opcode; |
| __u8 flags; |
| __u16 control; |
| __u16 nblocks; |
| __u16 rsvd; |
| __u64 metadata; |
| __u64 addr; |
| __u64 slba; |
| __u32 dsmgmt; |
| __u32 reftag; |
| __u16 apptag; |
| __u16 appmask; |
| }; |
| |
| struct nvme_passthru_cmd { |
| __u8 opcode; |
| __u8 flags; |
| __u16 rsvd1; |
| __u32 nsid; |
| __u32 cdw2; |
| __u32 cdw3; |
| __u64 metadata; |
| __u64 addr; |
| __u32 metadata_len; |
| __u32 data_len; |
| __u32 cdw10; |
| __u32 cdw11; |
| __u32 cdw12; |
| __u32 cdw13; |
| __u32 cdw14; |
| __u32 cdw15; |
| __u32 timeout_ms; |
| __u32 result; |
| }; |
| #endif |
| |
| #define nvme_admin_cmd nvme_passthru_cmd |
| |
| #define NVME_IOCTL_ID _IO('N', 0x40) |
| #define NVME_IOCTL_ADMIN_CMD _IOWR('N', 0x41, struct nvme_admin_cmd) |
| #define NVME_IOCTL_SUBMIT_IO _IOW('N', 0x42, struct nvme_user_io) |
| #define NVME_IOCTL_IO_CMD _IOWR('N', 0x43, struct nvme_passthru_cmd) |
| #define NVME_IOCTL_RESET _IO('N', 0x44) |
| #define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45) |
| |
| #endif /* end of HAVE_LINUX_NVME_IOCTL_H */ |
| |
| #include <linux/types.h> |
| #include <linux/bsg.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 SG_NVME_BROADCAST_NSID 0xffffffff |
| |
| #define DEF_TIMEOUT 60000 /* 60,000 millisecs (60 seconds) */ |
| |
| 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", |
| }; |
| |
| #define LINUX_HOST_BYTES_SZ \ |
| (int)(sizeof(linux_host_bytes) / sizeof(linux_host_bytes[0])) |
| |
| static const char * linux_driver_bytes[] = { |
| "DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA", |
| "DRIVER_ERROR", "DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD", |
| "DRIVER_SENSE" |
| }; |
| |
| #define LINUX_DRIVER_BYTES_SZ \ |
| (int)(sizeof(linux_driver_bytes) / sizeof(linux_driver_bytes[0])) |
| |
| #if 0 |
| static const char * linux_driver_suggests[] = { |
| "SUGGEST_OK", "SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP", |
| "SUGGEST_DIE", "UNKNOWN","UNKNOWN","UNKNOWN", |
| "SUGGEST_SENSE" |
| }; |
| |
| #define LINUX_DRIVER_SUGGESTS_SZ \ |
| (int)(sizeof(linux_driver_suggests) / sizeof(linux_driver_suggests[0])) |
| #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 |
| #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 |
| |
| static bool bsg_nvme_char_major_checked = false; |
| static int bsg_major = 0; |
| static volatile int nvme_char_major = 0; |
| |
| |
| #if defined(__GNUC__) || defined(__clang__) |
| static int pr2ws(const char * fmt, ...) |
| __attribute__ ((format (printf, 1, 2))); |
| #else |
| static int pr2ws(const char * fmt, ...); |
| #endif |
| |
| |
| static int |
| pr2ws(const char * fmt, ...) |
| { |
| va_list args; |
| int n; |
| |
| va_start(args, fmt); |
| n = vfprintf(sg_warnings_strm ? sg_warnings_strm : stderr, fmt, args); |
| va_end(args); |
| return n; |
| } |
| |
| /* 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). */ |
| static void |
| 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]; |
| |
| 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)) { |
| bsg_major = n; |
| if (got_one) |
| break; |
| got_one = true; |
| } else if (0 == strcmp("nvme", a)) { |
| nvme_char_major = n; |
| if (got_one) |
| break; |
| got_one = true; |
| } |
| } else |
| break; |
| } |
| if (verbose > 3) { |
| if (cp) { |
| if (bsg_major > 0) |
| pr2ws("found bsg_major=%d\n", bsg_major); |
| if (nvme_char_major > 0) |
| pr2ws("found nvme_char_major=%d\n", nvme_char_major); |
| } else |
| pr2ws("found no bsg not nvme char device in %s\n", proc_devices); |
| } |
| fclose(fp); |
| } |
| |
| /* Assumes that 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 (bsg_major == major_num) |
| is_bsg = true; |
| else if (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 (! bsg_nvme_char_major_checked) { |
| bsg_nvme_char_major_checked = true; |
| 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; |
| } |
| |
| // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| #if defined(IGNORE_LINUX_BSG) || ! defined(HAVE_LINUX_BSG_H) |
| /* |
| * sg(v3) via SG_IO ioctl on a sg node or other node that accepts that ioctl. |
| * Decision has been made at compile time because either: |
| * a) no /usr/include/linux/bsg.h header file was found, or |
| * b) the builder gave the '--enable-no-linux-bsg' option to ./configure |
| */ |
| |
| |
| struct sg_pt_linux_scsi { |
| struct sg_io_hdr io_hdr; |
| int dev_fd; /* -1 if not given */ |
| int in_err; |
| int os_err; |
| bool is_sg; /* is_sg,is_nvme: (F,F)-->unknown; (T,F)-->sg; */ |
| bool is_nvme; /* (F,T)-->nvme; (T,T)-->illegal */ |
| bool mdxfer_out; /* direction of metadata xfer, true->data-out */ |
| uint32_t nvme_nsid; |
| uint32_t nvme_result; |
| uint32_t dxfer_ilen; |
| uint32_t dxfer_olen; |
| uint32_t mdxfer_len; |
| void * dxferip; |
| void * dxferop; |
| void * mdxferp; |
| }; |
| |
| struct sg_pt_base { |
| struct sg_pt_linux_scsi impl; |
| }; |
| |
| |
| |
| /* 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); |
| } |
| |
| /* 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 (verbose > 1) { |
| pr2ws("open %s with flags=0x%x\n", device_name, flags); |
| } |
| if (! bsg_nvme_char_major_checked) { |
| bsg_nvme_char_major_checked = true; |
| find_bsg_nvme_char_major(verbose); |
| } |
| fd = open(device_name, flags); |
| if (fd < 0) |
| fd = -errno; |
| return fd; |
| } |
| |
| /* 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; |
| } |
| |
| /* 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) |
| { |
| int err; |
| struct sg_pt_linux_scsi * ptp; |
| |
| /* The following 2 lines are temporary. It is to avoid a NULL pointer |
| * crash when an old utility is used with a newer library built after |
| * the sg_warnings_strm cleanup */ |
| if (NULL == sg_warnings_strm) |
| sg_warnings_strm = stderr; |
| |
| ptp = (struct sg_pt_linux_scsi *) |
| calloc(1, sizeof(struct sg_pt_linux_scsi)); |
| if (ptp) { |
| err = set_pt_file_handle((struct sg_pt_base *)ptp, dev_fd, verbose); |
| if ((0 == err) && (! ptp->is_nvme)) { |
| ptp->io_hdr.interface_id = 'S'; |
| ptp->io_hdr.dxfer_direction = SG_DXFER_NONE; |
| } |
| } 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) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp) |
| free(ptp); |
| } |
| |
| /* Remembers previous device file descriptor */ |
| void |
| clear_scsi_pt_obj(struct sg_pt_base * vp) |
| { |
| bool is_sg, is_nvme; |
| int fd, nvme_nsid; |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp) { |
| fd = ptp->dev_fd; |
| is_sg = ptp->is_sg; |
| is_nvme = ptp->is_nvme; |
| nvme_nsid = ptp->nvme_nsid; |
| memset(ptp, 0, sizeof(struct sg_pt_linux_scsi)); |
| ptp->io_hdr.interface_id = 'S'; |
| ptp->io_hdr.dxfer_direction = SG_DXFER_NONE; |
| ptp->dev_fd = fd; |
| ptp->is_sg = is_sg; |
| ptp->is_nvme = is_nvme; |
| ptp->nvme_nsid = nvme_nsid; |
| } |
| } |
| |
| /* 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 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 (! bsg_nvme_char_major_checked) { |
| bsg_nvme_char_major_checked = true; |
| 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, NULL, &ptp->is_nvme, |
| &ptp->nvme_nsid, &ptp->os_err, verbose); |
| else { |
| ptp->is_sg = false; |
| ptp->is_nvme = false; |
| ptp->nvme_nsid = 0; |
| ptp->os_err = 0; |
| } |
| return ptp->os_err; |
| } |
| |
| /* 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 unsigned char * cdb, |
| int cdb_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.cmdp) |
| ++ptp->in_err; |
| ptp->io_hdr.cmdp = (unsigned char *)cdb; |
| ptp->io_hdr.cmd_len = cdb_len; |
| } |
| |
| void |
| set_scsi_pt_sense(struct sg_pt_base * vp, unsigned char * sense, |
| int max_sense_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.sbp) |
| ++ptp->in_err; |
| memset(sense, 0, max_sense_len); |
| ptp->io_hdr.sbp = sense; |
| ptp->io_hdr.mx_sb_len = max_sense_len; |
| } |
| |
| /* Setup for data transfer from device */ |
| void |
| set_scsi_pt_data_in(struct sg_pt_base * vp, unsigned char * dxferp, |
| int dxfer_ilen) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->dxferip) |
| ++ptp->in_err; |
| if (dxfer_ilen > 0) { |
| ptp->io_hdr.dxferp = dxferp; |
| ptp->dxferip = dxferp; |
| ptp->io_hdr.dxfer_len = dxfer_ilen; |
| ptp->dxfer_ilen = dxfer_ilen; |
| ptp->io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; |
| } |
| } |
| |
| /* Setup for data transfer toward device */ |
| void |
| set_scsi_pt_data_out(struct sg_pt_base * vp, const unsigned char * dxferp, |
| int dxfer_olen) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->dxferop) |
| ++ptp->in_err; |
| if (dxfer_olen > 0) { |
| ptp->io_hdr.dxferp = (unsigned char *)dxferp; |
| ptp->dxferop = (void *)dxferp; |
| ptp->io_hdr.dxfer_len = dxfer_olen; |
| ptp->dxfer_olen = dxfer_olen; |
| ptp->io_hdr.dxfer_direction = SG_DXFER_TO_DEV; |
| } |
| } |
| |
| void |
| set_pt_metadata_xfer(struct sg_pt_base * vp, unsigned char * dxferp, |
| uint32_t dxfer_len, bool out_true) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->mdxferp) |
| ++ptp->in_err; |
| 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.pack_id = pack_id; |
| } |
| |
| void |
| set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| ++ptp->in_err; |
| if (tag) { ; } /* unused, suppress warning */ |
| } |
| |
| /* 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->in_err; |
| if (tmf_code) { ; } /* unused, suppress warning */ |
| } |
| |
| void |
| set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| ++ptp->in_err; |
| if (attribute) { ; } /* unused, suppress warning */ |
| if (priority) { ; } /* unused, suppress warning */ |
| } |
| |
| #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 sg driver [sg v3 interface] 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 |= SG_FLAG_Q_AT_HEAD; |
| ptp->io_hdr.flags &= ~SG_FLAG_Q_AT_TAIL; |
| } else if (SCSI_PT_FLAGS_QUEUE_AT_TAIL & flags) { |
| ptp->io_hdr.flags |= SG_FLAG_Q_AT_TAIL; |
| ptp->io_hdr.flags &= ~SG_FLAG_Q_AT_HEAD; |
| } |
| } |
| |
| /* Executes NVMe Admin 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. |
| * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds |
| * is used. */ |
| int |
| do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb) |
| { |
| int n, len; |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| struct sg_nvme_passthru_cmd cmd; |
| |
| if (vb > 3) |
| pr2ws("%s: fd=%d, time_secs=%d\n", __func__, fd, time_secs); |
| if (! ptp->io_hdr.cmdp) { |
| if (vb) |
| pr2ws("No NVMe command given (set_scsi_pt_cdb())\n"); |
| return SCSI_PT_DO_BAD_PARAMS; |
| } |
| n = ptp->io_hdr.cmd_len; |
| len = (int)sizeof(cmd); |
| n = (n < len) ? n : len; |
| if (n < 8) { |
| if (vb) |
| pr2ws("%s: command length of %d bytes is too short\n", __func__, |
| n); |
| return SCSI_PT_DO_BAD_PARAMS; |
| } |
| memcpy(&cmd, (unsigned char *)ptp->io_hdr.cmdp, n); |
| if (n < len) /* zero out rest of 'cmd' */ |
| memset((unsigned char *)&cmd + n, 0, len - n); |
| if (ptp->io_hdr.dxfer_len > 0) { |
| cmd.data_len = ptp->io_hdr.dxfer_len; |
| cmd.addr = (__u64)(sg_intptr_t)ptp->io_hdr.dxferp; |
| } |
| if (time_secs < 0) |
| cmd.timeout_ms = 0; |
| else |
| cmd.timeout_ms = 1000 * cmd.timeout_ms; |
| if (vb > 2) { |
| pr2ws("NVMe command:\n"); |
| dStrHex((const char *)&cmd, len, 1); |
| } |
| if (ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, &cmd) < 0) { |
| ptp->os_err = errno; |
| if (vb > 2) |
| pr2ws("%s: ioctl(NVME_IOCTL_ADMIN_CMD) failed: %s (errno=%d)\n", |
| __func__, strerror(ptp->os_err), ptp->os_err); |
| return -ptp->os_err; |
| } else |
| ptp->os_err = 0; |
| n = ptp->io_hdr.mx_sb_len; |
| if ((n > 0) && ptp->io_hdr.sbp) { |
| n = (n < len) ? n : len; |
| memcpy(ptp->io_hdr.sbp, &cmd, n); |
| ptp->io_hdr.sb_len_wr = n; |
| } else |
| ptp->io_hdr.sb_len_wr = 0; |
| ptp->nvme_result = cmd.result; |
| if (vb > 2) |
| pr2ws("%s: timeout_ms=%u, result=%u\n", __func__, cmd.timeout_ms, |
| ptp->nvme_result); |
| 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) |
| { |
| int err; |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| bool have_checked_for_type = (ptp->dev_fd >= 0); |
| |
| ptp->os_err = 0; |
| 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; |
| } |
| if (! have_checked_for_type) { |
| err = set_pt_file_handle(vp, ptp->dev_fd, verbose); |
| if (err) |
| return -ptp->os_err; |
| } |
| if (ptp->is_nvme) |
| return do_nvme_pt(vp, ptp->dev_fd, time_secs, verbose); |
| if (NULL == ptp->io_hdr.cmdp) { |
| if (verbose) |
| pr2ws("No SCSI command (cdb) given\n"); |
| return SCSI_PT_DO_BAD_PARAMS; |
| } |
| /* io_hdr.timeout is in milliseconds */ |
| ptp->io_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : |
| DEF_TIMEOUT); |
| if (ptp->io_hdr.sbp && (ptp->io_hdr.mx_sb_len > 0)) |
| memset(ptp->io_hdr.sbp, 0, ptp->io_hdr.mx_sb_len); |
| if (ioctl(ptp->dev_fd, SG_IO, &ptp->io_hdr) < 0) { |
| ptp->os_err = errno; |
| if (verbose > 1) |
| pr2ws("ioctl(SG_IO) failed: %s (errno=%d)\n", |
| strerror(ptp->os_err), ptp->os_err); |
| return -ptp->os_err; |
| } |
| return 0; |
| } |
| |
| 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.status & 0x7e; |
| |
| if (ptp->os_err) |
| return SCSI_PT_RESULT_OS_ERR; |
| else if (ptp->io_hdr.host_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_resid(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ((NULL == ptp) || ptp->is_nvme) ? 0 : ptp->io_hdr.resid; |
| } |
| |
| 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 ptp->is_nvme ? (int)ptp->nvme_result : ptp->io_hdr.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_result : (uint32_t)ptp->io_hdr.status; |
| } |
| |
| int |
| get_scsi_pt_sense_len(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (NULL == ptp) |
| return 0; |
| return ptp->io_hdr.sb_len_wr; /* NVMe stuffs that variable */ |
| } |
| |
| int |
| get_scsi_pt_duration_ms(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.duration; |
| } |
| |
| 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.host_status << 8) + ptp->io_hdr.driver_status; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| /* Returns b which will contain a null char terminated string (if |
| * max_b_len > 0). That string should decode Linux driver and host |
| * status values. */ |
| 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.host_status; |
| int n, m; |
| char * cp = b; |
| int driv; |
| const char * driv_cp = "unknown"; |
| |
| if (max_b_len < 1) |
| return b; |
| m = max_b_len; |
| n = 0; |
| if (hs) { |
| if ((hs < 0) || (hs >= LINUX_HOST_BYTES_SZ)) |
| n = snprintf(cp, m, "Host_status=0x%02x is unknown\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; |
| driv = ds & SG_LIB_DRIVER_MASK; |
| if (driv < LINUX_DRIVER_BYTES_SZ) |
| driv_cp = linux_driver_bytes[driv]; |
| #if 0 |
| sugg = (ds & SG_LIB_SUGGEST_MASK) >> 4; |
| if (sugg < LINUX_DRIVER_SUGGESTS_SZ) |
| sugg_cp = linux_driver_suggests[sugg]; |
| #endif |
| 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; |
| } |
| |
| 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; |
| } |
| |
| |
| // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| #else /* allow for runtime selection of sg v3 or v4 (via bsg) */ |
| // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| /* |
| * So bsg is an option. Thus we make a runtime decision. 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. |
| */ |
| |
| |
| #include <linux/types.h> |
| #include <linux/bsg.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 |
| |
| |
| struct sg_pt_linux_scsi { |
| struct sg_io_v4 io_hdr; /* use v4 header as it is more general */ |
| int dev_fd; /* -1 if not given */ |
| int in_err; |
| int os_err; |
| unsigned char tmf_request[4]; |
| bool is_sg; |
| bool is_bsg; |
| bool is_nvme; |
| bool mdxfer_out; /* direction of metadata xfer, true->data-out */ |
| uint32_t nvme_nsid; /* 1 to 0xfffffffe are possibly valid, 0 |
| * implies dev_fd is not a NVMe device |
| * (is_nvme=false) or it is a NVMe char |
| * device (e.g. /dev/nvme0 ) */ |
| uint32_t nvme_result; |
| uint32_t mdxfer_len; |
| void * mdxferp; |
| }; |
| |
| struct sg_pt_base { |
| struct sg_pt_linux_scsi impl; |
| }; |
| |
| |
| /* 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); |
| } |
| |
| /* 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 (! bsg_nvme_char_major_checked) { |
| bsg_nvme_char_major_checked = true; |
| 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_close_device(int device_fd) |
| { |
| int res; |
| |
| res = close(device_fd); |
| if (res < 0) |
| res = -errno; |
| return res; |
| } |
| |
| |
| /* 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) |
| { |
| int err; |
| struct sg_pt_linux_scsi * ptp; |
| |
| /* The following 2 lines are temporary. It is to avoid a NULL pointer |
| * crash when an old utility is used with a newer library built after |
| * the sg_warnings_strm cleanup */ |
| if (NULL == sg_warnings_strm) |
| sg_warnings_strm = stderr; |
| |
| ptp = (struct sg_pt_linux_scsi *) |
| calloc(1, sizeof(struct sg_pt_linux_scsi)); |
| if (ptp) { |
| 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) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp) |
| free(ptp); |
| } |
| |
| /* Remembers previous device file descriptor */ |
| void |
| clear_scsi_pt_obj(struct sg_pt_base * vp) |
| { |
| bool is_sg, is_bsg, is_nvme; |
| int fd, nvme_nsid; |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp) { |
| fd = ptp->dev_fd; |
| is_sg = ptp->is_sg; |
| is_bsg = ptp->is_bsg; |
| is_nvme = ptp->is_nvme; |
| nvme_nsid = ptp->nvme_nsid; |
| 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_nsid = nvme_nsid; |
| } |
| } |
| |
| /* 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 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 (! bsg_nvme_char_major_checked) { |
| bsg_nvme_char_major_checked = true; |
| 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); |
| else { |
| ptp->is_sg = false; |
| ptp->is_bsg = false; |
| ptp->is_nvme = false; |
| ptp->nvme_nsid = 0; |
| ptp->os_err = 0; |
| } |
| return ptp->os_err; |
| } |
| |
| /* 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 unsigned char * cdb, |
| int cdb_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.request) |
| ++ptp->in_err; |
| ptp->io_hdr.request = (__u64)(sg_intptr_t)cdb; |
| ptp->io_hdr.request_len = cdb_len; |
| } |
| |
| void |
| set_scsi_pt_sense(struct sg_pt_base * vp, unsigned char * sense, |
| int max_sense_len) |
| { |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| if (ptp->io_hdr.response) |
| ++ptp->in_err; |
| memset(sense, 0, max_sense_len); |
| ptp->io_hdr.response = (__u64)(sg_intptr_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, unsigned char * 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_intptr_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 unsigned char * 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_intptr_t)dxferp; |
| ptp->io_hdr.dout_xfer_len = dxfer_olen; |
| } |
| } |
| |
| void |
| set_pt_metadata_xfer(struct sg_pt_base * vp, unsigned char * 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.spare_in = pack_id; |
| } |
| |
| 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] = (unsigned char)tmf_code; /* assume it fits */ |
| ptp->io_hdr.request = (__u64)(sg_intptr_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; |
| } |
| } |
| |
| /* 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) |
| return 0; |
| return ptp->is_nvme ? 0 : ptp->io_hdr.din_resid; |
| } |
| |
| 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_result : |
| ptp->io_hdr.device_status); |
| return 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_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; |
| } |
| |
| int |
| get_scsi_pt_duration_ms(const struct sg_pt_base * vp) |
| { |
| const struct sg_pt_linux_scsi * ptp = &vp->impl; |
| |
| return ptp->io_hdr.duration; |
| } |
| |
| 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; |
| } |
| |
| /* 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 >= LINUX_HOST_BYTES_SZ)) |
| 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; |
| driv = ds & SG_LIB_DRIVER_MASK; |
| if (driv < LINUX_DRIVER_BYTES_SZ) |
| driv_cp = linux_driver_bytes[driv]; |
| #if 0 |
| sugg = (ds & SG_LIB_SUGGEST_MASK) >> 4; |
| if (sugg < LINUX_DRIVER_SUGGESTS_SZ) |
| sugg_cp = linux_driver_suggests[sugg]; |
| #endif |
| 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 = (unsigned char *)(long)ptp->io_hdr.request; |
| v3_hdr.cmd_len = (unsigned char)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 = (unsigned char *)(long)ptp->io_hdr.response; |
| v3_hdr.mx_sb_len = (unsigned char)ptp->io_hdr.max_response_len; |
| } |
| v3_hdr.pack_id = (int)ptp->io_hdr.spare_in; |
| 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\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 NVMe Admin 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. |
| * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds |
| * is used. */ |
| static int |
| do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb) |
| { |
| int n, len; |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| struct sg_nvme_passthru_cmd cmd; |
| |
| if (vb > 3) |
| pr2ws("%s: fd=%d, time_secs=%d\n", __func__, fd, time_secs); |
| if (! ptp->io_hdr.request) { |
| if (vb) |
| pr2ws("No NVMe command given (set_scsi_pt_cdb())\n"); |
| return SCSI_PT_DO_BAD_PARAMS; |
| } |
| n = ptp->io_hdr.request_len; |
| len = (int)sizeof(cmd); |
| n = (n < len) ? n : len; |
| if (n < 64) { |
| if (vb) |
| pr2ws("%s: command length of %d bytes is too short\n", __func__, |
| n); |
| return SCSI_PT_DO_BAD_PARAMS; |
| } |
| memcpy(&cmd, (unsigned char *)ptp->io_hdr.request, n); |
| if (n < len) /* zero out rest of 'cmd' */ |
| memset((unsigned char *)&cmd + n, 0, len - n); |
| if (ptp->io_hdr.din_xfer_len > 0) { |
| cmd.data_len = ptp->io_hdr.din_xfer_len; |
| cmd.addr = (__u64)(sg_intptr_t)ptp->io_hdr.din_xferp; |
| } else if (ptp->io_hdr.dout_xfer_len > 0) { |
| cmd.data_len = ptp->io_hdr.dout_xfer_len; |
| cmd.addr = (__u64)(sg_intptr_t)ptp->io_hdr.dout_xferp; |
| } |
| if (time_secs < 0) |
| cmd.timeout_ms = 0; |
| else |
| cmd.timeout_ms = 1000 * cmd.timeout_ms; |
| if (vb > 2) { |
| pr2ws("NVMe command:\n"); |
| dStrHex((const char *)&cmd, len, 1); |
| } |
| if (ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, &cmd) < 0) { |
| ptp->os_err = errno; |
| if (vb > 2) |
| pr2ws("%s: ioctl(NVME_IOCTL_ADMIN_CMD) failed: %s (errno=%d)\n", |
| __func__, strerror(ptp->os_err), ptp->os_err); |
| return -ptp->os_err; |
| } else |
| ptp->os_err = 0; |
| ptp->nvme_result = cmd.result; |
| n = ptp->io_hdr.max_response_len; |
| if ((n > 0) && ptp->io_hdr.response) { |
| n = (n < len) ? n : len; |
| memcpy((uint8_t *)ptp->io_hdr.response, &cmd, n); |
| ptp->io_hdr.response_len = n; |
| } |
| if (vb > 2) |
| pr2ws("%s: timeout_ms=%u, result=%u\n", __func__, cmd.timeout_ms, |
| cmd.result); |
| 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) |
| { |
| int err; |
| struct sg_pt_linux_scsi * ptp = &vp->impl; |
| bool have_checked_for_type = (ptp->dev_fd >= 0); |
| |
| if (! bsg_nvme_char_major_checked) { |
| bsg_nvme_char_major_checked = true; |
| 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; |
| } |
| if (! have_checked_for_type) { |
| 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 (ptp->is_nvme) |
| return do_nvme_pt(vp, ptp->dev_fd, time_secs, verbose); |
| else if (bsg_major <= 0) |
| return do_scsi_pt_v3(ptp, fd, time_secs, verbose); |
| else if (ptp->is_bsg) |
| ; /* drop through to sg v4 implementation */ |
| else |
| return do_scsi_pt_v3(ptp, fd, time_secs, verbose); |
| |
| if (! 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 */ |
| ptp->io_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) : |
| DEF_TIMEOUT); |
| #if 0 |
| /* sense buffer already zeroed */ |
| if (ptp->io_hdr.response && (ptp->io_hdr.max_response_len > 0)) { |
| void * p; |
| |
| p = (void *)(long)ptp->io_hdr.response; |
| memset(p, 0, ptp->io_hdr.max_response_len); |
| } |
| #endif |
| 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; |
| } |
| |
| #endif |
| // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |